feat: add web and local session ingestion#1
Open
jamespheffernan wants to merge 32 commits into
Open
Conversation
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
aicrawlnow has functional ingestion for the main non-export sources this project needs: live/captured ChatGPT and Claude web payloads, native ChatGPT macOS app cache conversation IDs as live-web detail seeds, plus local OpenClaw, Codex, Gemini, Claude Code (including Claude desktop/local-agent transcript roots), Cursor, and Hermes session stores/files or roots. OpenClaw imports preserve Discord/Telegram-stylesourceChanneland sender metadata when present, and directory imports tolerate control-only session files without leaking private paths. It also has safe source-audit dry-runs, official-export reconciliation reports, status-level web-sync and local-source freshness with durable cursor metadata, dedicated browser-profile launch/reuse for web sync, a macOS LaunchAgent generator for recurring bounded web sync, recurring local transcript-root imports, and one-command default local-root scheduling, and an aggregate-only live-provider smoke runner for final field validation.sync web --provider chatgpt|claude --cdp-url <url>attaches to an already authenticated browser target, finds or opens a provider page, runs same-origin list/detailfetch()calls from page context, imports the fetched detail batch into SQLite underchatgpt_weborclaude_web, preserves raw payloads, updatessync_state, and makes messages searchable. For ChatGPT,--chatgpt-app-cache <dir>can seed that live detail fetch from native macOS appconversations-v3-*/*.datafilenames without reading opaque cache bodies. Without--cdp-url, livesync weblaunches or reuses a dedicated browser profile using--profile, binds CDP to127.0.0.1, discovers Chrome's local endpoint fromDevToolsActivePort, and reportslogin_requiredon first-run profiles without writing the archive.sync web --source <json-or-zip>still imports captured provider detail payloads through the same archive path.aicrawl importaccepts--provider openclaw,--provider codex,--provider gemini,--provider claude-code,--provider cursor, and--provider hermesfor characterized local files/stores, and recursively imports local provider roots for OpenClaw/Codex/Claude Code*.jsonl, Gemini*.json, Cursorstore.db/state.vscdb, and Hermesstate.dbor sidecar session JSON/JSONL. Hermes root imports preferstate.dbover sidecar files. Directory import and dry-run skip control-only local transcript files with redacted warnings, reportskipped_sources, and cap warning arrays at 100 entries plus a truncation summary.aicrawl import --dry-run --jsonaudits candidate counts without creating or writing the archive.aicrawl reconcile <official-export> --provider chatgpt|claude|auto --jsoncompares official exports against the archive read-only and reports source/archived/missing coverage plus divergent normalized message projections.aicrawl schedule launchd ...writes CDP-attach, profile-launch, or local-import LaunchAgent plists without loading them or storing browser credentials. With--import-path, recurring local imports runaicrawl import <path> --provider <provider> --jsonfor OpenClaw, Codex, Gemini, Claude Code/Claude desktop local-agent, Cursor, or Hermes roots and rely on import idempotency to avoid duplicate rows. With--local-defaults, setup discovers standard roots that already exist and writes the matching import LaunchAgents in one pass, with distinct labels for Claude Code CLI and Claude Desktop roots. Live web batches skip individual 403/404/410 conversation detail responses while preserving accessible conversations in the same batch, record those observations inconversation_sync_statusasinaccessible, and avoid destructive deletion for archived/deleted/inaccessible items.Dry-run web sync validates the browser-profile/CDP boundary, inspects optional redacted network captures for list/detail endpoint contracts, reports captured source candidate counts, performs best-effort list-only live candidate counting when
--cdp-urlis supplied, and reports freshness without writing. Non-dry-run sync with stale/partial/missing captures fails before archive writes with stablecontract_<state>errors.status --jsonalso exposesweb_syncfreshness forchatgpt_webandclaude_web, includinglast_checked_at, candidate counts, and cursor metadata, so stale or never-run syncs are visible from the normal control surface. Live web sync recordsprovider_updated_atwatermarks when provider payloads expose timestamps; later runs pass the cursor back into the adapter, skip older list candidates, and return a successful no-change result without detail fetches or archive writes.Design Notes
--user-data-dir, CDP bound to127.0.0.1, and an ephemeral remote-debugging port by default; reuse a live profile endpoint before starting a new browser process.login_requiredguidance without writing archive rows or scraping credentials.fetch()from the provider page context through a narrow CDP client; do not extract credentials.matched,partial,stale,missing,empty, ornot_checked; non-matched captures fail before writes withcontract_<state>errors, and provider live fetch fails before archive writes if required response keys are absent.conversation_sync_status, and do not delete existing archive rows.import --dry-runshares the same parsers as import, reports counts/warnings, caps warning arrays, reports skipped directory sources, and does not initialize or write the archive.sync web --dry-run --cdp-urlattaches only to an already open provider page, fetches list endpoints to count candidates, does not fetch details, and returnslive_list_unavailablewarnings instead of failing the whole preflight when candidate inspection cannot attach. With--chatgpt-app-cache, dry-run counts filename-derived ChatGPT app cache IDs locally aschatgpt_app_cache_idswithout reading cache bodies or attaching to CDP.reconcileshares official export parsers with import, opens the archive read-only, and reports missing coverage plus divergent normalized message projections. Backfill/refresh remains explicit throughaicrawl import.status --json.web_syncreports ChatGPT/Claude web source freshness, andstatus --json.local_sourcesreports OpenClaw, Codex, Gemini, Claude Code/Claude desktop local-agent, Cursor, and Hermes import freshness fromsync_state, includingnever_importedsources.provider_updated_atcursors for live web syncs, and per-conversationseen/inaccessiblestatus rows; unchanged cursor runs update check freshness without re-importing rows.schedule launchdwrites plists that run boundedsync webvia--cdp-urlor--profile, recurringaicrawl import <path> --provider <provider> --jsonvia--import-path, or one-pass standard local-root import jobs via--local-defaults.~/Library/Application Support/Claude), Cursorstore.db, Hermesstate.db/session JSON/JSONL, and provider-specific local roots with aggregate reports that avoid full private paths. OpenClaw preserves source-channel and sender metadata when present.conversations-v3-*/*.datafilenames, sort them by newest file modification time, and fetch exact details through the authenticated ChatGPT web path; never read or decode opaque.datacache bodies.store.dbor installedstate.vscdbread-only as one conversation; use legacy meta identity/title or workspace-state directory identity, rowid ordering, and visible user/assistant/system text; skip non-message blobs, tool calls, and tool results.state.dbread-only as the canonical Hermes session store when present; import exported session JSON/JSONL as fallback; index visible user/assistant/system/developer/tool content; skipsession_metaand empty/internal records.Residuals
The live ChatGPT/Claude path is deliberately bounded: it syncs a configurable number of recent conversations per run, or exact ChatGPT IDs seeded from the native macOS app cache, and depends on the current web app response shape. Logged-in ChatGPT and Claude CDP write smokes have now passed against Jimmy's Chrome profiles using isolated temp archives. Unattended provider login remains out of scope; the browser profile is launched, but the user logs in normally.
Cursor branch/path metadata is not exposed through the observed
store.dborstate.vscdbmessage blobs, so Cursor imports mark path membership unknown instead of fabricating edges.Validation
GOWORK=off go mod tidygit diff --exit-code -- go.mod go.sumGOWORK=off go vet -p 1 ./...GOWORK=off go test -p 1 -count=1 ./...git diff --checkbash -n scripts/live-provider-smoke.shscripts/live-provider-smoke.sh --helpscripts/live-provider-smoke.sh --provider chatgpt --cdp-url http://127.0.0.1:1 --chatgpt-app-cache <synthetic-cache> --mode dry-run --max-conversations 1docs/RELEASE_CHECKLIST.md: build CLI into a temp dir, run fixture imports, web source syncs, reconciliation, status, searches, read-only SQL rejection, LaunchAgent generation, Markdown export, and CrawlBar manifest generationseenconversation statuses; live web sync records provider update watermarks plus inaccessible detail statuses; repeated unchanged live syncs return no-change stats and updatelast_checked_at./cmd/aicrawl, initialize a temp-home archive, runreconcile ./testdata/redacted/chatgpt-export.fixture.zip --provider chatgpt --jsonbefore import and verify missing rows, import the fixture, then reconcile again and verify missing coverage and divergent messages are zero./cmd/aicrawland verify--helpexposes--cdp-url,--max-conversations,--browser,--remote-debugging-port,claude-code,cursor,hermes, andreconcileimport ./testdata/redacted/chatgpt-export.fixture.json --dry-run --jsonreportschatgpt/chatgpt_exportcounts and leavesstatus --jsonasuninitializedschedule launchd --provider chatgpt --cdp-url http://127.0.0.1:9222 --out <tmp>writes a plist withProgramArguments,--cdp-url, andStartIntervalschedule launchd --provider chatgpt --profile <tmp-profile> --browser /tmp/fake-chrome --remote-debugging-port 0 --out <tmp>writes a profile-launch plist with--profile,--browser, and--remote-debugging-portand no credential materialschedule launchd --provider codex --import-path <tmp-codex-root> --out <tmp>reportsmode: import, writes anaicrawl import <path> --provider codex --jsonplist, validates withplutil -lint, and includes no web-sync or credential materialschedule launchd --provider claude-code --import-path "~/Library/Application Support/Claude" --out <tmp>expands the real space-containing Claude desktop path, validates the plist withplutil -lint, and preserves the path as one ProgramArguments stringstatus --jsonreportslocal_sourceswith Codex asseen,source_kind: codex_jsonl, imported counts,cursor_kind: source_hash, and OpenClaw asnever_importedschedule launchd --local-defaults --interval-minutes 5 --jsonin a temp home with Codex, Claude Desktop, and Hermes roots writes three import plists, skips four missing roots, preserves the Claude Desktop path with spaces, and all generated plists passplutil -linttestdata/redacted/chatgpt-web-conversation.fixture.json,status --jsonreportsweb_syncwithchatgpt_webasseenandclaude_webasnever_syncedcontract_staleandstatus --jsonremainsuninitializedtestdata/redacted/claude-web-conversation.fixture.json, search forweb sync claude fixture assistant phrase, then dry-runsync weband verifyclaude_webfreshness isseengemini cli fixture assistant phrasethrough the CLI~/Library/Application Support/com.openai.chat: scanned filename-derived cache IDs without writing the archive or reading.databodies, reportedsource_kind: chatgpt_app_cache_ids,candidate_conversations: 215,messages: 0,source_warnings: 0, andfreshness_state: archive_missing~/.codex/sessions: scanned 1,255 JSONL sources without writing the archive, found 1,235 conversations and 56,933 messages, skipped 20 metadata/control-only sources, and emitted 20 redacted ordinal-only warnings~/.claude/projects: scanned 2,067 JSONL sources without writing the archive, found 2,067 conversations and 15,977 messages with no warnings~/Library/Application Support/Claude: scanned 285 JSONL sources without writing the archive, found 285 conversations and 4,077 messages with no warnings~/.gemini: shape-filtered session JSON discovery skipped config/auth JSON without printing paths, scanned 309 session JSON sources, found 309 conversations and 615 messages, and emitted 101 bounded parser warnings withtruncated 1 additional warningsas the final summarystate.vscdbsources without writing the archive, found 1 importable conversation and 3,313 messages, skipped 3 non-chat/empty state stores, and emitted 3 redacted ordinal-only warnings~/.hermes: selected the rootstate.dbcanonical source without writing the archive, reportedprovider: hermes,source_kind: hermes_session,sources: 1,conversations: 15,messages: 617,attachments: 0, andwarnings: 9without printing transcript text~/.openclaw/agents/main/sessions: scanned 769 JSONL sources without writing the archive, found 416 conversations and 30,627 messages, skipped 353 control-only sources, and emitted 101 bounded warnings withtruncated 537 additional warningsas the final summaryuninitialized, import the directory, search forcodex jsonl fixture assistant phrase, and repeat import to verifyalready_imported_sources: 1reconcilereportsdivergent_messages: 1without message bodiestestdata/redacted/claude-code-session.fixture.jsonlwith--provider claude-code, and search forclaude code jsonl fixture assistant phrasestore.db, Cursorstate.vscdb, and Hermes parser/CLI import/search paths create synthetic SQLite stores at test time, avoiding checked-in binary/private DB fixturesstate.db, exported session JSON, exported session JSONL, no-visible-text warning aggregation, root-directorystate.dbpreference over sidecar sessions, and provider-filtered searchTestLaunchProfileStartsBrowserWithDedicatedProfile,TestLaunchProfileReusesExistingProfileEndpoint, andTestSyncWebLaunchesMissingProfileAndReportsLoginRequiredexercise profile launch/reuse/login-required behavior without real credentialssource.kind: live_list, preserves an uninitialized archive, and reportslive_list_unavailableas a warning when no provider page target is attachedTestSyncWebLiveCDPImportsSearchableChatGPTPayloadexercises command path, CDP attach, page-context fetch responses, archive import, and search without real provider credentials;TestSyncWebLiveCDPRecordsInaccessibleDetailStatusverifies accessible details import while skipped 404 details are stored inconversation_sync_statuswith timestamps127.0.0.1:9224: page network reload showed ChatGPT itself reading/backend-api/conversation/<id>with HTTP 200, while cookie-only synthetic fetches returned empty lists/404 detail responses; the fix hydrates the bearer token from same-origin/api/auth/sessioninside the page and never prints or persists the token127.0.0.1:9224: normal authenticated history list reportedsource_kind: live_list,candidate_conversations: 3, andwarnings: 0127.0.0.1:9224: normal list write imported 1 conversation and 11 messages into the isolated temp archive withwarnings: 0warnings: 0127.0.0.1:9225: authenticated profile reportedsource_kind: live_list,candidate_conversations: 3, andwarnings: 0after raising the CDP websocket read limit for larger Claude list responses127.0.0.1:9225: imported 1 conversation and 6 messages into the isolated temp archive withwarnings: 0agent-browseris installed, but this repo has no browser app, dev server, HTML UI, or route files to exercise.