Download Sources/PromptGarageCore/Import/ClaudeJSONLParser.swift from smlflg/PromtGarageMac: direct link, hf CLI and curl.
- Browser
- Download file 4.29 kB
-
https://huggingface.co/smlflg/PromtGarageMac/resolve/main/Sources/PromptGarageCore/Import/ClaudeJSONLParser.swift
- Command line
-
hf download hf://smlflg/PromtGarageMac/Sources/PromptGarageCore/Import/ClaudeJSONLParser.swift
-
curl -L -o ClaudeJSONLParser.swift https://huggingface.co/smlflg/PromtGarageMac/resolve/main/Sources/PromptGarageCore/Import/ClaudeJSONLParser.swift
4.29 kB
| import Foundation | |
| struct ClaudeJSONLParser: ImportParser { | |
| private static let agentPrefixes = [ | |
| "RESEARCH ONLY", "You are working", "Work in ~/", "Explore the", | |
| "Search system", "Check these", "Read ALL", "Find ALL", | |
| "do NOT modify", "Make ", "Run ", "Execute ", | |
| ] | |
| func parse(sourceURL: URL, dedupIndex: DedupIndex) throws -> ImportParseResult { | |
| let files = try ImportFiles.recursiveJSONLFiles(at: sourceURL, excluding: "subagents") | |
| var accumulator = ImportAccumulator( | |
| minimumLength: 20, | |
| maximumLength: 10_000, | |
| dedupIndex: dedupIndex | |
| ) | |
| for fileURL in files { | |
| let project = projectTag(from: fileURL) | |
| for line in try ImportFiles.lines(from: fileURL) { | |
| let root: Any | |
| do { | |
| root = try ImportJSON.object(fromLine: line) | |
| } catch { | |
| accumulator.skipMalformed() | |
| continue | |
| } | |
| guard let object = root as? [String: Any] else { | |
| accumulator.skipMalformed() | |
| continue | |
| } | |
| // Claude transcripts also contain valid system/snapshot events | |
| // without a message payload. They are not malformed prompts. | |
| guard let message = object["message"] as? [String: Any] else { continue } | |
| guard message["role"] as? String == "user" else { continue } | |
| // The legacy importer defaults a missing content field to an | |
| // empty string, so it counts as scanned + skipped_short. | |
| let text: String | |
| if message["content"] == nil { | |
| text = "" | |
| } else if let extracted = userText(from: message["content"]) { | |
| text = extracted | |
| } else { | |
| continue | |
| } | |
| let trimmed = ImportText.trimmed(text) | |
| if Self.agentPrefixes.contains(where: { trimmed.hasPrefix($0) }) { | |
| accumulator.skipAgent() | |
| continue | |
| } | |
| accumulator.accept( | |
| trimmed, | |
| sourcePrefix: "claude-raw", | |
| tags: "claude,\(project)", | |
| promptedAt: ImportTimestamp.normalize(object["timestamp"]) | |
| ) | |
| } | |
| } | |
| return accumulator.result() | |
| } | |
| private func userText(from content: Any?) -> String? { | |
| if let text = content as? String { | |
| return text | |
| } | |
| guard let blocks = content as? [Any] else { return nil } | |
| let textParts = blocks.compactMap { block -> String? in | |
| if let text = block as? String { | |
| return text | |
| } | |
| guard | |
| let object = block as? [String: Any], | |
| object["type"] as? String == "text" | |
| else { | |
| return nil | |
| } | |
| return object["text"] as? String ?? "" | |
| } | |
| return textParts.joined(separator: " ") | |
| } | |
| private func projectTag(from fileURL: URL) -> String { | |
| for component in fileURL.pathComponents { | |
| if let project = decodedProjectComponent(component, homeMarker: "-home-") { | |
| return project | |
| } | |
| if let project = decodedProjectComponent(component, homeMarker: "-Users-") { | |
| return project | |
| } | |
| } | |
| return "claude" | |
| } | |
| /// Claude encodes `/home/name/project` and `/Users/name/project` as one | |
| /// dash-prefixed path component. Strip the home marker and user name, as | |
| /// the original Linux importer did for `-home-smlflg-`. | |
| private func decodedProjectComponent(_ component: String, homeMarker: String) -> String? { | |
| guard component.hasPrefix(homeMarker) else { return nil } | |
| let remainder = component.dropFirst(homeMarker.count) | |
| guard let firstDash = remainder.firstIndex(of: "-") else { return nil } | |
| let projectStart = remainder.index(after: firstDash) | |
| guard projectStart < remainder.endIndex else { return nil } | |
| return remainder[projectStart...].lowercased() | |
| } | |
| } | |