npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

hansard

v0.5.1

Published

Local-first archive, search, and recall for AI-agent conversations.

Readme

hansard

Hansard is a local-first archive, search, and recall tool for conversations with AI coding agents. It preserves source history for later reprocessing and builds portable, provider-independent records for local use.

Highlights:

  • imports from Codex, ChatGPT exports, Claude, Gemini, Antigravity, Devin, Windsurf, Cursor, Cline, OpenCode, Aider, and other supported agent tools
  • substantially verbatim source backup plus redacted Markdown transcripts and canonical event JSONL
  • terminal search, full-session Deep Recall, a local web viewer, and an MCP recall server
  • repository-aware organization, activity statistics, memory snapshots, and attachment preservation
  • optional S3-compatible device sync and signed, repository-scoped Team sharing

Install

Install the CLI globally so agent-facing recall and continue-from commands can call hansard from PATH:

npm install -g hansard
hansard init
hansard app

Hansard does not run install-time lifecycle scripts. After a direct install, run hansard init explicitly, then use hansard doctor to check optional tools and configured integrations.

hansard init --dry-run prints the ordered setup stages and possible writes without creating config, importing history, installing integrations, starting processes, or contacting remote storage.

The npm package supplies the CLI, not the native desktop bundle. hansard app activates a separately installed Hansard app on macOS; when no registered app is available, it opens the same local viewer in the default browser. Use hansard web to request the browser viewer directly.

For a project-local install:

npm install hansard
npx hansard init

Or run the published package without keeping a local install:

npx hansard init

Requirements:

  • Node.js 20 or newer
  • sqlite3 for Codex, Cursor, and Devin local database imports
  • rg/ripgrep for faster history search
  • unzip for ZIP web exports
  • zstd or unzstd for compressed Codex session files

Run hansard doctor after install to check optional tools and configured sources.

Recall

hansard integrations add-to codex
hansard integrations add-to claude
hansard integrations add-to gemini
hansard integrations add-to antigravity
hansard integrations add-to devin
hansard integrations add-to cursor
hansard integrations add-to cline
hansard integrations add-to opencode
hansard integrations add-to aider

integrations add-to codex installs the MCP server plus Codex recall, Deep Recall, and continue-from skills. integrations add-to claude installs the MCP server plus Claude /recall, /deep-recall, and /continue-from commands plus matching Claude skills. integrations add-to gemini installs the MCP server plus Gemini /recall and /continue-from commands under ~/.gemini/commands/.

Additional native surfaces are installed where the client exposes one:

| Agent | Recall surface | | --- | --- | | Antigravity | MCP config in ~/.gemini/antigravity/mcp_config.json plus Agent Skills at ~/.gemini/antigravity/skills/{recall,continue-from}/SKILL.md | | Devin | MCP config in ~/.config/devin/config.json plus /recall and /continue-from skills under ~/.config/devin/skills/ | | Cursor | MCP config in ~/.cursor/mcp.json plus project files .cursor/commands/{recall,continue-from}.md and matching .cursor/rules/hansard-*.mdc rules | | Cline | MCP config in ~/.cline/data/settings/cline_mcp_settings.json plus /recall.md and /continue-from.md workflows under ~/Documents/Cline/Workflows/ | | OpenCode | MCP config in ~/.config/opencode/opencode.json plus /recall and /continue-from commands under ~/.config/opencode/commands/ | | Aider | Loadable recall and continue-from instructions under ~/.hansard/aider/; Aider does not expose a documented custom slash-command registry, so load them with /load ~/.hansard/aider/load-recall.aider or configure the generated markdown as read: in .aider.conf.yml |

The generated recall files use a skill-style layout with command tables, workflow steps, query-selection guidance, archive layout notes, and troubleshooting. They ask the agent to infer a focused search query, then call hansard history and read a bounded latest transcript window with hansard show <session_id> --json --msg-limit 20, so long session Markdown cannot hide the tail behind an agent harness output limit. The windowed JSON omits the full Markdown and canonical-event copies, bounds large tool-result text, and retains collaboration tool arguments. Agents can page to other message offsets when needed without direct filesystem access to ~/.hansard. Users can ask for context naturally, such as /recall the migration bug and update the test.

The generated continue-from files are stricter: after selecting a session they instruct the agent to read both the first window (hansard show <session_id> --json --msg-offset 0 --msg-limit 8) and latest window (hansard show <session_id> --json --msg-limit 20). The first window recovers the original goal and constraints; the latest window recovers the current state, files touched, test status, blockers, and next action before the agent resumes work.

Deep Recall is the archive-wide research path. It decomposes a question into keyword, repo, provider, tool, command, and time-window variants; merges ranked results; reads selected full sessions in isolated model contexts; then synthesizes an answer with [session:<session-id>] citations. Use it from the Deep Recall tab or directly:

hansard deep-recall "Compare the migration approaches and explain why each was abandoned"
hansard deep-recall "Build a decision log for the authentication rewrite" --repo github.com/acme/widgets --backend claude --model opus

Skill/command files can also be installed directly:

hansard recall install-skill codex
hansard recall install-skill claude
hansard recall install-skill gemini
hansard recall install-skill antigravity
hansard recall install-skill devin
hansard recall install-skill cursor
hansard recall install-skill cline
hansard recall install-skill opencode
hansard recall install-skill aider

The standalone MCP binary remains available for clients that only need the tool server:

hansard-recall

Daily CLI

hansard status, hansard history, hansard show, hansard import, hansard index, hansard rebuild, hansard classify, and hansard watcher login use styled human-readable output by default. Use --json on status, history, and import status/results for script-friendly structured output. hansard status includes the most recently archived conversation threads and the sources currently monitored by the supervisor; hansard history is for search and recall.

Useful history commands:

hansard history
hansard history "codebase explanation" --provider codex-cli --limit 10
hansard history --repo github.com/acme/widgets --since 90d
hansard show <session-id>
hansard show <session-id> --path
hansard delete <session-id> --yes
hansard export <session-id> --out ./hansard-session-export
hansard rebuild --since 30d
hansard app
hansard web
hansard web --no-open

hansard app is the normal post-setup entry point. It activates the native macOS app when installed and otherwise falls back to the browser viewer. hansard web starts the local conversation viewer and opens the system default browser. Pass --no-open to print only the local URL and keep the server running. Deep Recall is the seventh primary tab. It uses the authenticated Codex or Claude command-line client already installed on the machine, so no separate API key is required; the model selector switches between available CLI models. Every submitted question runs the archive-wide planner, search, full-session readers, and cited synthesis pipeline. Real research stages and generated text appear inline in the transcript instead of in a detached progress bar. Research threads are stored under data/hansard/chat/ for compatibility, outside the normal session index, so they never appear in Conversations, search, stats, projects, or notifications. CLI calls are ephemeral/non-persistent where supported, read-only, and stamped with an internal sentinel as a second guard against re-import. New Research is only a local composer reset: the first submitted question creates the durable thread, and legacy zero-message records are removed. Stored General Chat threads remain readable, but new turns always run Deep Recall and never receive folder context.

The left rail is a repo tree sorted by the latest updated session, each folder pages through sessions with a load-more control, search results reuse the same session reader, and the transcript pane can switch between readable chat bubbles and the raw markdown archive. Readable chat bubbles load from pre-baked *.view.json payloads; large windowed transcripts use derived *.view.pages/ shards so the server reads only the requested messages. Tool cards initially carry only their name and summary; opening one fetches its arguments, diff, and output from the same bounded page shard and caches that detail for later opens. Raw markdown is fetched only when source view is opened. Search updates while text is entered with a short debounce. The web endpoint uses a warm compatible index when available, but it will not synchronously parse or rebuild an obsolete index during an interactive query or scan every rendered conversation as a fallback; rebuilds are left to hansard index rebuild or the supervisor. Search tries an all-term FTS match first; when a longer natural recall query under-fills, one bounded relaxed pass favors selective terms and reranks by coverage across the whole session. This tolerates an extra remembered word or terms split across messages without making every query a broad scan. Canonical titles and title aliases have their own exact-match table in the FTS sidecar, so preserving exact-title precedence does not walk session metadata on every keystroke. Session lists and stats are backed by derived indexes under hansard/indexes/, so repeat page loads avoid walking every metadata file. Settings → Lists controls the initial item count and load-more increment for Conversations and Memories independently. Each value defaults to 20, accepts 1–50, and applies separately inside every folder. Opening a direct memory link still reveals the selected item when it falls beyond the configured initial count. The static viewer uses shadcn/ui-style design tokens and compact button/input/select/sidebar patterns without requiring a frontend build step. Archives still keep stable path:<hash> keys for folders without git identity, but the UI displays the local path. In Conversations, configured local roots stay flat in the recency-sorted rail: each actual project is a direct device/configured-root/relative-path row rather than a synthetic parent that fans out. Use the pencil on a local project to give its relative component a shorter display name. The alias is stored under web.projectDisplayNames in config.json; it never renames the folder, changes the canonical project key, or requires a reimport. Settings → Folders provides the same cosmetic naming control for a detected path and all of its local subfolders at once. Memories use the same direct cosmetic paths while retaining their raw source path as provenance. Remote Git repos and web-chat accounts keep their provider-owned names. Projects with linked subagent transcripts show a disclosure chevron; the child rows are fetched only when expanded. Opening one uses a full transcript subpage with Original, previous, and next navigation instead of a modal.

Hosted viewer

By default hansard web binds only to 127.0.0.1 and uses a per-launch ?token= handoff to set an HttpOnly cookie. To serve it on another interface, pass --host <addr>; bare --host binds 0.0.0.0. Any non-loopback bind requires a password:

HANSARD_WEB_PASSWORD='choose-a-long-password' hansard web --host 0.0.0.0
# or
hansard web --host 0.0.0.0 --password 'choose-a-long-password'

The desktop app runs this loopback viewer with --parent-pipe. Its cookie is a browser-session cookie with no clock expiry, so an app left open continues to refresh for the lifetime of its child viewer process. Standalone and hosted web sessions retain the bounded web-cookie policy.

Use --read-only when sharing the viewer. Remote browsers are always blocked from mutating endpoints; --read-only also blocks those writes from loopback. The hosted mode is intended for trusted networks such as Tailscale, a VPN, or a LAN, or behind a TLS reverse proxy. hansard does not set a Secure cookie over plain HTTP.

Provider filters use one stable order: OpenAI (codex-cli, codex-desktop, codex-sdk, chatgpt), Anthropic (claude, claude-code-desktop, claude-cowork, claude-web, claude-sdk), Google (gemini-cli, antigravity), Cognition (devin-cli, devin-desktop), then other local tools (cursor, cline, opencode, aider).

The supervisor is hansard's local background watcher. When it is running, it watches each source's on-disk history roots with filesystem events (FSEvents on macOS): writes mark the source dirty and an import runs a few seconds later, so new conversation turns usually land in the archive within seconds. SQLite-backed stores such as Cursor's state.vscdb use a wider ~20-second coalesce window because their databases churn while the app is open. Events are a latency optimization, never the source of truth — watched sources are still re-polled on a 15-minute heartbeat to catch dropped events, sources whose roots can't be watched (Aider project folders, JetBrains Cline) keep the original 30-second poll with a 5-minute idle cadence, and failing sources retry with exponential backoff, so an idle machine isn't spawning import workers all day. Imports use the configured import window, defaulting to the last 30 days. When login startup is disabled, hansard does not install a login item. During setup, uncheck Start watcher at login or run hansard init --no-autostart. A one-time catch-up remains available through hansard import --source all, along with hansard watcher start to watch for the current session, or hansard watcher login enable later. The default watcher choices are Codex CLI, Codex Desktop, Claude Code CLI, Claude Code Desktop, Claude Cowork, Gemini CLI, Antigravity CLI, Antigravity 2.0, Antigravity IDE, Devin CLI, Devin Desktop, Windsurf, GitHub Copilot CLI, Factory Droid, Grok Build, pi, Cursor, Cline, OpenCode CLI, OpenCode Desktop, OpenCode Web, and Aider. New configs still support imports.autoDiscoverSources=true, but init records the chosen watcher list exactly by setting imports.autoDiscoverSources=false. Cursor raw SQLite recovery is intentionally left to explicit imports such as hansard import --source cursor --since all; the supervisor handles incremental Cursor logs going forward and prunes duplicate transcript snapshots. The detailed watcher/import contract, including sourcePath replacement rules for multi-session stores such as Cursor SQLite and Devin sessions.db, lives in docs/history-source-handling.md.

Claude Code can retain an abandoned prompt draft in its JSONL when a revised prompt is submitted before the assistant responds. Current imports collapse those same-parent pending branches to the revised human turn while preserving the original records in the raw archive. Existing Claude CLI archives need a clean reimport to apply that normalization:

hansard import --source claude --since all

Codex rollouts can likewise mirror one submitted prompt as both a durable response_item and a live event_msg, or retain an unanswered prompt after an explicit abort/rollback. Current imports keep one normalized human turn while leaving every source record in the raw archive. Codex goal-mode threads, whose only human intent arrives via thread_goal_updated events, now import a /goal <objective> user message (deduped across heartbeat re-emissions) that also titles the session. Claude Cowork now ignores same-UUID replay copies and uses historical audit timestamps, and legacy Cursor imports assign each global generation breadcrumb to one Composer before collapsing only short, unanswered prompt revisions. Existing derived archives need provider reimports for these parser changes:

hansard import --sources codex-cli,codex-desktop,codex-sdk --since all
hansard import --source claude-cowork --since all
hansard import --source cursor --since all

ChatGPT and Claude.ai export messages with distinct provider IDs are retained, even when their text repeats, because those can be real retries rather than duplicate transport records.

Slack notifications are an optional, off-by-default notify layer. Run hansard notify slack setup for the guided path: it opens Slack's app creation page pre-filled from a manifest (one personal app, chat:write plus chat:write.customize for per-speaker identities), verifies the pasted bot token, connects a channel, and sends a test post. Two surfaces toggle independently, each with its own channel: summaries (one recap per session after it goes quiet for --quiet-minutes) and the firehose (live turns streamed into one thread per session, batched every --batch-seconds). Adjust with hansard notify slack enable --summaries on|off --firehose on|off --summary-channel C… --firehose-channel C…. Only redacted archive output is posted, the token can also come from SLACK_BOT_TOKEN, and --repos restricts posting to an allowlist. Posting is at-least-once on a local queue: going offline pauses posting but never archiving, and everything drains on reconnect.

  • Desktop notifications are configured per browser/app under Settings → Notifications. New assistant replies can produce OS-level alerts and open the matching session when clicked; preferences stay in browser storage. In a browser they use the web Notification API (and show under the browser's name in OS notification settings); in the Hansard desktop app they post as native app notifications, so Hansard appears in macOS System Settings → Notifications.

For finer control after setup, routes decide which sessions post where: a session fans out to every matching route, so a #all route plus a #acme-org route puts Acme sessions in both. Mark a route exclusive to stop it falling through. Each route can match by owner/provider/repo/path/folder and carry its own bot token, so multiple bots can post to different channels. Manage them with hansard notify slack route <list|add|rm|reorder> (e.g. route add --match-owners acme --token ref:acme-bot --summary-channel C… --exclusive) and store named tokens with hansard notify slack token <list|add|rm>. All of this — the default credential, routes, channels, filters, live tool inclusion, and pasting or replacing bot tokens — is also editable from the web viewer's dedicated Settings → Slack tab (loopback-guarded; stored credentials are write-only and shown masked). Slack owns the app profile, so the same panel links to Slack's app dashboard for name and icon changes. No reimport is needed.

Folders group projects in the Projects view above the repo level — a Codex folder, a Claude Code folder, or an org — the same way GitHub owners and Claude.ai accounts already act as containers. Settings → Folders automatically lists the same groups detected from the archive; choose Configure to rename the detected path cosmetically, rename any local subfolder, or spin a subfolder (including its detected descendants) into its own top-level display folder, then save the layout explicitly. These controls never rename directories or change canonical archive keys, and discovery never changes config.json on its own. Folders can also be defined with hansard config set containers '<json>' (an array of { id, label, icon, pathRoot, match }, where match selects sessions by provider/owner/repo/path). Folders are evaluated at render time, so edits apply with no reimport. Projects keeps the parent-and-children hierarchy. Conversations and Memories instead show every actual local project as a direct device/folder/relative-path row in normal recency order; folder rank never pins those rows above newer activity.

Remote sync can be configured during init or from the CLI. Local storage remains canonical. Three sync modes exist: upload (default) pushes changed archive objects to the remote target, receive mirrors other devices' namespaces into this machine's archive, and two-way does both in one run. No mode ever deletes objects — not from the bucket when files are missing locally, and not locally when objects are missing remotely.

hansard sync configure
hansard sync status
hansard sync

hansard sync configure opens a picker in a terminal. Choose an existing configured/env remote, or choose the last option to configure a new R2, S3, S3-compatible, or local-folder target. After a target is selected, the CLI asks what to do next: sync now, create a snapshot, change autosync cadence, or exit. Non-interactive scripts can still pass flags such as --endpoint, --bucket, and --access-key-id.

The same values can be supplied with HANSARD_REMOTE_ENDPOINT, HANSARD_REMOTE_BUCKET, HANSARD_REMOTE_ACCESS_KEY_ID, and HANSARD_REMOTE_SECRET_ACCESS_KEY. R2 also accepts R2_ENDPOINT, R2_BUCKET, R2_ACCESS_KEY_ID, and R2_SECRET_ACCESS_KEY.

The bucket should already exist before running sync. For Cloudflare R2, copy the S3 API URL from the bucket's Settings > General page. Cloudflare may include the bucket in that URL, such as https://<account-id>.r2.cloudflarestorage.com/<bucket>; hansard accepts that and derives the bucket automatically. Create an R2 API token from the R2 Object Storage overview with Manage API Tokens, scoped to the hansard bucket with read, write, and list permissions. For AWS S3, use an IAM access key scoped to the bucket or hansard/ prefix with ListBucket and object read/write permissions.

During hansard init, a configured cloud destination is uploaded immediately after the initial backfill. If the watcher is allowed to start at login, init also asks for a cloud autosync cadence; the supervisor then uploads new archive changes on that interval whenever it is running.

Init always records a device name and a person name in the main hansard config (person defaults to git user.name, then the OS username). New session metadata includes both identities; remote sync uses the device slug so multiple machines stay distinguishable in one archive bucket, and the person identity keeps sessions attributable when several people share a bucket in team setups. Existing archived sessions are not rewritten by init; run hansard import --source all --since all to reimport old local history with the current device metadata.

Remote objects are namespaced by device so multiple machines can upload into one bucket without overwriting each other's archive roots:

s3://<bucket>/hansard/
  devices/
    work-laptop/
      sessions/...
      raw-sources/...
      memory/...
      telemetry/...
  snapshots/
    20260504T173000Z/
      work-laptop/
        sessions/...

Sync uploads canonical archive objects, so memory items and their snapshot history (the Memories tab data) ride along automatically next to sessions. Derived local indexes under hansard/indexes/ are excluded, and research threads under hansard/chat/ stay device-local by default because a Deep Recall answer can combine private material from several projects. Each device rebuilds its indexes from the canonical archive objects.

Personal Sync

Personal Sync is the private, first-class way to keep one person's complete Hansard archive available across their own devices. It is intentionally separate from Teams: it has one random profile identity, no sharing selector, and always includes sessions, memories, unredacted provider backups under session .raw/ and shared raw-sources/, attachments, and artifacts.

hansard personal-sync create --endpoint file:///absolute/private/folder
hansard personal-sync create --target r2 --endpoint https://<acct>.r2.cloudflarestorage.com --bucket private-hansard
hansard personal-sync connect --endpoint file:///absolute/private/folder
hansard personal-sync status

Create writes <prefix>/personal.json with a random profile ID. Other devices connect to that identity and upload below <prefix>/personal/<profile-id>/devices/<device-id>/...; renaming a person or device does not change either stable ID. One Personal Sync profile can be connected per local Hansard installation, while any number of devices tied to the same profile can connect. Endpoint, bucket, region, prefix, direction, cadence, and credentials stay device-local. Use a private destination and narrowly scoped credentials. Team targets have a narrower contract and never upload raw provider backups or memories.

In a terminal, hansard sync prompts for the remote target, previews the upload-only plan, and requires a confirmation phrase before writing. This is intentional even when only one remote is configured, because multiple remotes can exist in config/env and a normal sync can overwrite same-key objects for the selected device namespace. Use --dry-run to preview in scripts, or --yes to skip the guided confirmation.

Use hansard sync snapshot to upload a redundant point-in-time copy under hansard/snapshots/<timestamp>/<device>/.... In a terminal it prompts for the remote, lists existing snapshots, requests a snapshot name, previews the write, and then confirms before uploading:

hansard sync snapshot

If the remote copy has stale objects after a local reset, cleanup, or full reimport, use the explicit replace path. In a terminal it previews the selected remote, shows how many remote objects will be deleted, and requires typed confirmation before it deletes this device namespace under hansard/devices/<device>/ and uploads the current local archive.

hansard sync replace
hansard sync wipe

hansard sync wipe is delete-only. It asks which remote to use, asks for the scope (device, one snapshot, all snapshots, prefix, or bucket), previews the exact target and prefix, then requires a two-step typed confirmation. device leaves snapshots untouched; snapshots leaves normal device uploads untouched. Run hansard sync afterward to upload this device again. Scripts can use --dry-run or --yes.

Multi-device sync

hansard sync receive (or sync.mode: receive / two-way in config) mirrors every other device's namespace from the same bucket into this machine's archive under received/devices/<device>/…. Received sessions then appear alongside local ones everywhere — recent lists, search, stats, and the web viewer, which labels their origin device and offers a device filter. The same session id on two devices is shown once, preferring the local copy.

hansard sync receive --dry-run   # preview what would be mirrored
hansard sync receive             # mirror other devices now
hansard config set sync.mode two-way   # supervisor autosync uploads + receives

Safety rules, in both directions: receive never deletes local files that are gone remotely, never writes outside received/, and never touches this device's own live archive; upload never publishes the received/ mirror back under this device's namespace. Received sessions are read-only. Remote indexes (indexes/) are skipped as derived, and large raw-sources/ are skipped unless --include-raw is passed (or sync.receive.includeRaw is set). Limit which devices are mirrored with sync.receive.devices (a list of device slugs; empty means all). After a receive downloads new sessions, the supervisor's next reindex pass makes them searchable.

Teams

To share only a project's sessions with collaborators, point a team target at a shared bucket (an R2 bucket per project works well):

# creator: generates a manager key, signs team.json, and prints a verified invite
hansard team create --name "Example Team" --endpoint https://<acct>.r2.cloudflarestorage.com \
  --target r2 --bucket example-team-hansard \
  --repos github.com/acme/widgets --owners another-org \
  --upload-interval 5 --receive-interval 5

# print a fresh invite after later policy revisions
hansard team invite example-team

# each collaborator pastes that invite and supplies storage credentials separately
hansard team join --invite 'hansard-team-invite-v1.…'

hansard team status      # scope, cadence, member namespaces seen in the bucket

team.json is a signed schema-3 envelope carrying the Team identity, manager keys, revision chain, scope, and cadence. Create keeps the Ed25519 manager key in this installation's private key store. A verified invite pins the Team ID, policy revision and hash, manager fingerprint, and storage locator, but contains no credentials. Bucket access alone cannot join a Team or authorize a policy change. Unsigned or tampered manifests, unknown signers, rollback, forks, and revision gaps fail closed. Shared Team selectors use canonical repositories and repository owners only; local path-prefix, container, provider, or other device-specific selector fields are rejected. Repositories and owners can be mixed, and a session matching any configured value uploads. The selector chooses whole sessions, not individual messages: a matched transcript can still mention or contain files from outside the selected repo. Team sync excludes the memory archive and every raw-provider backup, including session .raw/ folders, shared provider databases under raw-sources/, and byte-preserved web exports under raw/. It removes structural local filesystem paths from shared session metadata, manifests, artifact records, transcript metadata, viewer summaries, and conversation frontmatter. Full message text and tool calls/results are not rewritten, however, and attachments are shared as bytes; all of them can still contain local paths or out-of-scope material.

Team targets live in sync.targets[] alongside the personal remote, each with its own upload/receive intervals, so a project bucket can run at minutes-level freshness while a personal backup stays at 30.

The viewer's Settings → Personal Sync page creates or connects the complete private backup. Settings → Teams creates a signed Team or joins one with a manager-issued verified invite. Pick Shared Folder, Cloudflare R2, Amazon S3, or another S3-compatible store and Hansard shows only the relevant path, endpoint, bucket, signing region, and credential fields, including temporary session tokens. Upload scope is a repeatable dropdown list that can mix folders of detected repositories, specific repositories, and repository owners; folder choices expand to portable canonical repository keys. Create refuses a bucket/prefix that already contains team.json.

On an existing Team, only a device holding a current manager key sees Edit shared policy and Copy invite. A signed shared-policy edit changes the name, upload scope, and cadence for every member. Every member can use Edit this device for only this machine's endpoint, bucket, prefix, sync direction, and write-only credentials. A local target cannot replace the shared Team selector with path, container, provider, or other scope overrides. Saved credentials are retained only while the storage destination is unchanged, and portable selectors with AND logic or exclusions that the row editor cannot represent are shown as advanced and preserved unchanged. Disconnect removes this device's connection only — it does not delete the bucket or anything already uploaded. Settings refreshes the shared policy and observed device namespaces directly from file, R2, or other S3-compatible storage before presenting them.

Two separate checks protect a Team connection. Cryptographic trust pins the Team ID, manager key, policy hash, and accepted revision floor. Upload consent is also fingerprinted locally and bound to the exact destination, prefix, Team identity, namespace version, selector, and non-secret account/credential identifier. If a valid signed team.json revision changes the selector —including a change from selected repositories to all conversations—or the destination/account changes, scheduled uploads stop before enumerating or uploading the new set. Preview the matched-session count and destination, then explicitly confirm that exact preview before uploads resume.

Current Team namespaces are collision-resistant: uploads land under members/<person>--<id16>/devices/<device>--<id16>/, where each id16 is the first 16 hexadecimal characters of SHA-256 over the complete stable identity. Two members who both named their laptop "MacBook Pro" therefore do not merge. Received teammate sessions appear in the merged viewer with person/device attribution, read-only.

Sharing disclaimer (shown at create/join and before the first upload): matching sessions upload their normalized archive, including full transcripts, tool output, metadata, attachments, and captured artifacts—even content from outside the matched repos. New Teams uploads exclude raw provider backups, shared raw databases, and byte-preserved web exports. Structural path metadata is removed, but message text, tool calls/results, attachments, and artifacts can still reveal local paths or unrelated material. Redaction is pattern-based rather than semantic, and binary attachments or artifacts are not content-redacted. Anyone with bucket access can read the uploaded data. That storage access is a confidentiality concern, but it does not grant trusted membership or permission to sign a Team policy.

Normal sync never deletes remote objects. To audit and remove current raw keys left by older Teams builds, use the explicit migration:

hansard team purge-legacy-raw <team-name> --dry-run
hansard team purge-legacy-raw <team-name> --yes

The cleanup removes only raw keys whose current or state-recorded legacy member/device namespace can be identified as this device's. Ambiguous legacy keys are reported and left untouched. It also removes legacy raw files from all local read-only Teams mirrors on that machine, because the historical mirror layout did not record which team target created each file. Cloud cleanup needs ListBucket and DeleteObject access to both <prefix>/members/ and <prefix>/snapshots/; ordinary sync does not need those broader cleanup permissions.

purge-legacy-raw is deliberately not a complete migration or erasure tool. An existing Team destination may retain normalized personal or out-of-scope sessions uploaded under an older policy, prior 8-character or renamed member/device namespaces, snapshots, unidentified raw keys, object versions, retention copies, replication, and provider backups. Start the hardened Teams setup with a fresh bucket or prefix; handle retention/history in the storage provider, and retire the old destination separately. Unsigned schema-1/2 Team manifests are not accepted by the signed v1 contract; create the Team again on that fresh destination and distribute verified invites.

Local mirrors are garbage-collected the same explicit way: hansard sync receive --prune deletes received-mirror files whose remote object is gone (after the normal receive pass), never anything outside received/devices/ or received/members/. It follows the same wipe-style confirmation — --dry-run previews, non-TTY requires --yes, and a TTY asks for a typed PRUNE RECEIVED <scope> code. Plain receive, two-way, and supervisor autosync never prune ordinary archive data or legacy team raw mirrors.

On first-class Personal Sync targets, received devices' memories and raw backups are mirrored too and appear read-only in the web viewer's Memories tab, labeled with their origin device; the same memory id on two devices is listed once, preferring the local copy. Team targets never upload or receive memories.

With teammates syncing into one bucket, a daily team digest can post to Slack: set notify.slack.digest (enabled, channel, token as ref:<name> or inline, atHour, lookbackHours) and the supervisor posts one summary per day grouping the window's sessions by person (device when no person is stamped) — session counts, active repos, models, token totals. Preview any time with hansard notify slack digest run --dry-run; empty windows post nothing.

To test the full loop without a bucket, point two HANSARD_HOMEs at one local folder target (file:///some/dir): sync from the first home, then hansard sync receive in the second.

Archived sessions are stored as readable markdown first, with metadata, raw transcript JSONL, normalized canonical event JSONL, a pre-baked viewer payload, and source/attachment sidecars alongside them:

~/.hansard/data/hansard/
  sessions/
    repo=<encoded_repo>/
      provider=<provider>/
        year=2026/
          month=04/
            day=27/
              session=<id>.conversation.md
              session=<id>.metadata.json
              session=<id>.transcript.jsonl
              session=<id>.events.jsonl
              session=<id>.view.json
              session=<id>.view.pages/       # derived viewer cache
              session=<id>.attachments/
                attachments.json
                <sha256>.<ext>
              session=<id>.raw/
                manifest.json
                001-<original-file-name>

hansard memory backup mirrors the same repo/global partitioning for provider memory. Codex's individual rollout_summaries/*.md files inherit the project of their archived thread (falling back to the summary's cwd), while cross-project aggregates such as MEMORY.md and global instructions remain in the provider's global folder. Rollout summaries preserve thread_id as their origin session, use the first Markdown H1 as the display title, and use the Rollout context: paragraph as their description. Claude Code project-memory frontmatter similarly preserves name, description, and metadata.originSessionId. Re-running the backup backfills this metadata for existing items without changing IDs or adding content snapshots. It also moves existing live items and source-deleted rollout history without changing snapshot history. Codex's recursive memory-file walk accepts readable UTF-8 text only; binary and cache artifacts are ignored, and previously archived local artifacts are pruned without deleting the provider file.

The web viewer links this provenance in both directions. A conversation with one linked memory has a direct Memory action; conversations with several linked memories expose a compact picker. Memory detail links back to the originating conversation when that session is present in the local archive and clearly marks unavailable origins otherwise. These links are provider-qualified, so matching identifiers from different providers are never joined.

Search the current version of those memories from the CLI or the Memories tab:

hansard memory search "deployment preference"
hansard memory search "project convention" --provider codex --repo github.com/example/project --json

In the Memories tab, use the rail search button or press / to search across all current memory content and metadata. Cmd/Ctrl+F remains a separate, in-memory finder for the currently selected item.

Memory search uses its own small FTS sidecar, so a changed memory does not rebuild the conversation index. It searches complete current text (up to the archive's 5 MB item limit) plus title, description, name, ID, origin session, provider, source, account, path, and scope metadata. Historical snapshots remain visible in memory detail but are intentionally excluded from search results.

For large multi-session stores such as Cursor SQLite, the per-session raw manifest may reference one shared copy under raw-sources/ instead of copying the same database into every session folder. Web chat imports may also reference a shared raw export archive; ChatGPT attachments remain preserved there and fresh imports render image/file cards in the readable transcript when the export includes the file bytes. Claude Code images/documents and Codex inline images are copied into the content-addressed .attachments/ sidecar. Transcript and viewer JSON retain compact attachment descriptors only, so inline base64 is not duplicated across normalized files.

Installs that imported Claude Code or Codex sessions before attachment support must perform a clean provider reimport to recover those bytes from the original history:

hansard import --sources codex-cli,codex-desktop,codex-sdk,claude,claude-code-desktop,claude-cowork,claude-sdk --since all

events.jsonl uses the local agentlog.events.v7 canonical event shape: session.started, prompt.submitted, response.generated, tool.called, tool.completed, memory.read, memory.write, memory.loaded, delegation.updated, and delegation.completed; completed tool events link back to the matching call when the source exposes stable ids or matching names. Tool calls and results may include provider diff hunk metadata such as structuredPatch for numbered viewer diffs. Parser versions are stamped by source type so importer output changes can trigger reimport with a new fingerprint. Recall/search builds the interactive index from canonical event text; legacy transcript/markdown scanning is an explicit recovery path rather than a hidden fallback during normal queries. The normal local search rebuild writes a small JSON summary plus a SQLite FTS5 sidecar for fast web, terminal, and MCP queries. The older full BM25 JSON index remains available only to explicit compatibility callers, avoiding huge JSON serializations during hansard update on large web-chat archives. Both index paths are keyed by the session-list fingerprint so stale checks avoid statting every archived file. When the index contract changes, run hansard index rebuild to rederive search and common stats payloads from the archive without a full source reimport. When canonical diff metadata changes, run hansard update --yes --since all --sources codex or hansard import --source codex --since all for older Codex archives that need fresh patch_apply_end structured hunks from raw source events; the bumped view schema rederives .view.json and its windowed page cache on read. The first open after a view-schema bump performs that work lazily; run hansard rebuild --since all to prewarm every session instead. Memory read/write/load events are also rendered as memory activity in the web and Markdown views, so memory-file edits do not appear as ordinary source-code diffs after the view payload is regenerated.

Stats are import-time metadata, not viewer-time transcript repair. Archive metadata stores message counts, user-message counts, token usage, and models for each session, and the web stats view reads those fields directly. Token totals include cache-read/cache-write tokens when providers report them, while the stats payload and UI also keep input, output, cache, and reasoning sub-counts separately when available. New archive metadata also stores compact tool-usage summaries and provider/export cost fields when present; the stats view uses those fields for most-used tools, session-time summaries, and estimated spend charts. Archive schema v6 also stores compact output-token work metadata (text, toolUse, reasoning, unknown) and lightweight outcome counts (editToolCalls, filesTouched, knowledgeCaptures, memoryReads, memoryWrites, memoryLoads) at import time. Canonical events v5 include memory.read, memory.write, and memory.loaded events for provider memory files and instruction files such as AGENTS.md and CLAUDE.md. The web stats view uses only those metadata fields for output job-mix charts and tokens-per-meaningful-event ratios; it does not reread transcripts or events in the request path. Existing archives need hansard update --yes --since all or a clean reimport before these new charts have full coverage. Subagent child sessions stay direct-addressable. Bare CLI/API stats exclude them unless asked, while the web Stats page includes them by default so delegated work is visible; its Subagents toggle supports parent-only comparisons. Spend is labeled as an estimate unless a provider supplied an actual cost field, and unpriced tokens stay visible instead of being folded into a blended-rate guess. The model-pricing table lives in src/pricing.js with an explicit pricing_version, and the stats payload exposes a 30-day usage capsule with latest-day spend, 7-day spend, 30-day spend, tokens, prompts, sessions, and top model. The web stats endpoint keeps the first payload light by shipping the current chart window and rolling activity heatmap first; all-time daily series are fetched only when the viewer asks for the all-time range or an older activity year. The Usage page's browser-local Subscription value calculator lazily requests the same all-time daily series and attributes usage to explicit source-level harnesses, so Codex CLI and Desktop usage can be counted without silently including Codex SDK jobs. Each period stores a start date, either an inclusive fixed end date or an ongoing state, the exact total paid, and selected harnesses in local browser storage. New periods are ongoing by default and evaluate matching usage through the viewer's current local day; switching to a fixed end date preserves inclusive bounds. API-equivalent value is the selected usage priced at public API rates; net value is API-equivalent value minus paid cost, value multiple is value divided by paid cost, and the ROI proxy is net value divided by paid cost. Shared-harness date overlaps are rejected, zero-cost periods leave ratio metrics undefined, and unpriced tokens make the reported value a lower bound. The proxy does not estimate time saved or business outcomes.

The Usage page has a Plan usage dashboard for local Codex and Claude CLI accounts. Codex capture uses the official app-server account and rate-limit reads, including every reported rolling quota window. Claude capture combines claude auth status --json for account and plan metadata with a bounded, best-effort claude /usage read for the quota windows reported by the installed CLI. On macOS, a signed-out standalone Claude binary falls back to an installed cmux Claude wrapper so captures match the account available in cmux terminals. A failed or unsupported Claude usage read leaves the detected account and plan intact. A capture updates the account currently signed in to each CLI and keeps previously seen accounts, so switching accounts does not erase their last status or utilization history. Hansard prefers provider account or user IDs, then falls back to a lowercased email; usernames are not used. Claude membership identity also includes the reported organization ID, which separates individual, team, or enterprise memberships that use the same email. The raw identity is replaced with a keyed, installation-local opaque fingerprint before persistence. If a CLI exposes no identity, that provider uses one unresolved entry and captures from different sign-ins may be combined. The 0600 fingerprint key and normalized usage state are stored separately and should be preserved together to retain account continuity. Raw emails, provider account identifiers, Claude usage output, browser cookies, and tokens are never written to the usage history.

Plan usage can be captured manually or tracked every 15 minutes by the Hansard supervisor. Interactive init selects continuous tracking by default when Codex or Claude is selected; unattended setup requires --track-usage. Tracking can also be changed with hansard config setup --track-usage, hansard config setup --no-track-usage, or from the Usage page. Each reported reset boundary creates a quota cycle, and Hansard retains the highest utilization observed in that cycle. These captured peaks are lower bounds when samples are missed; unobserved cycles are never recorded as zero or presented as exact maxima. A Claude capture that temporarily omits a previously reported window retains it as last reported without advancing that window's observation history. A detected catalog plan can be added to the Subscription value calculator as a new ongoing period, but subscription start dates and exact charges remain manual because the provider surfaces do not expose billing history. Claude captures only labeled current-session and weekly quota sections; activity statistics printed by /usage are discarded.

Codex imports preserve threads.tokens_used as the provider total and split rollout token_count events into fresh input, cache read, output, and reasoning metadata. Codex SDK and Claude SDK batch jobs are kept out of primary activity totals, streaks, folder rankings, and provider/model charts; the stats payload and web view expose them as a separate SDK jobs section so high-volume automation does not drown out interactive work. If SDK jobs are explicitly enabled, they re-enter resource, provider/model, tool, and command accounting totals, but conversation metrics still exclude them from conversation shape, user activity, session timing, human-normalized ratios, and the Behavior page. The SDK-inclusive JSON payload carries those batch-free values under behavior_stats. Cursor sessions without provider-reported token usage can also carry separately labeled estimatedUsage, which the stats view includes while reporting estimated token coverage. Cursor agent-transcripts imports also preserve Composer model configuration in session metadata, including the canonical model and reasoning effort parameters. They infer working directories from explicit transcript paths before falling back to Cursor's project-folder slug, which avoids stale slug attribution when Cursor writes an agent transcript under the wrong project directory; existing Cursor transcript archives need hansard update --yes --since all or a clean Cursor reimport to gain that metadata and attribution. Cursor transcript imports are the preferred v1 source when both modern transcript files and legacy Cursor SQLite rows describe the same Composer conversation, and a full Cursor reimport prunes those older duplicate copies. ChatGPT and Claude.ai exports without provider usage get estimated metadata.usage on their native chat messages, split into non-assistant input, assistant output, and Claude thinking output where the export provides separate parts. During pre-v1 development, if those stats fields or parser semantics change, rebuild the local archive with hansard update --yes --since all.

Classify

hansard classify labels archived sessions by work mode, outcome, and the strength of success and failure evidence. Those four related judgments share one structured model request per session. An optional task_value classifier adds a separate estimated-value request. Classification is off by default; enable it with hansard classify enable, then run hansard classify run --limit 25 to process pending sessions.

The classifier builds a bounded evidence digest from genuine human turns, final assistant responses, canonical tool outcomes, and deterministic session telemetry. Stored results include evidence references, prompt and digest hashes, resolved-model and usage provenance where the backend supplies it, and explicit complete, partial, retry, or configuration-error state. Changes to the source session, digest contract, prompt, model, or temperature make the result pending again. A result counts as verified success only when a high model score cites digest evidence and the archive also contains a concrete successful command, edit, or code-change signal.

Provider/user session titles are excluded from the digest because archive titles are not guaranteed to have passed through transcript redaction. Loopback Ollama requests refuse HTTP redirects, and web consent is bound to the exact configured backend destination so a changed destination cannot reuse an earlier approval.

The default local-model backend keeps digests on this machine when its Ollama endpoint is loopback. A non-loopback Ollama endpoint, claude-cli, codex-cli, anthropic, and openai send the digest off-machine and require confirmation before a run. CLI backends use the existing signed-in session and may leave the model blank to use the provider default; local and API backends require a model id. Hansard stores a separate model choice for each backend so switching providers does not reuse an incompatible model name. The viewer presents the five destinations as explicit grouped choices, keeps the four core judgments fixed, and exposes task value as a separate opt-in.

ChatGPT and Claude.ai are manual export providers. Run hansard import chatgpt or hansard import claude-web for current export instructions; after the provider emails a download link, pass the official .zip, unzipped export folder, or direct JSON file back to hansard. These imports are stored as local scoped web-chat archives and displayed through virtual conversation roots such as [chatgpt]conversations/<account-id> and [claude]conversations/<account-id>/<project>. The importer records account metadata in ~/.hansard/state/web-accounts.json; use hansard import accounts list to inspect mappings and hansard import accounts rename <provider> <account-id-or-username> --display-name <name> to change the viewer display name.

For newer OpenAI privacy exports named OpenAI-export, unzip the download and import the User Online Activity folder. Running hansard import chatgpt without a path starts a walkthrough that asks for export paths one at a time, then account username/email and display name. ChatGPT conversations may be split across multiple Conversations__...chatgpt...part-000N ZIPs or folders; passing the parent folder is best, but the walkthrough can also collect the split part folders individually and preserve chat.html, manifests, ZIPs, and attached files in the shared raw export archive. Claude.ai exports preserve conversation summaries and split structured thinking parts from visible assistant answers when the export includes that detail. Repeated manual uploads are incremental: unchanged conversations are skipped, and updated conversations replace the stable session for that provider/account/conversation id. Existing malformed pre-v1 web-chat archives are not migrated automatically; reimport from the original export after a reset or cleanup.

Tool calls and tool results are normalized before archive write where provider data is available. For example, Devin tool calls live in message.metadata.toolCalls[] and render as tool cards in the viewer without being appended to assistant prose as synthetic Grep(...) text. Canonical tool events also carry viewer-facing category, categoryLabel, icon, inputPreview, and target fields so the web viewer can render Bash, edit, read, search, web, task, skill, and MCP calls consistently across providers. Injected context is normalized the same way: compound Codex and Claude records become separate context/system cards plus the genuine user prompt, while Devin hidden ACP resources are separated from text blocks. Codex multi-agent routing envelopes become payload-only subagent-result cards, opaque encrypted delivery stubs stay raw-only by default, and child transcripts start at their own task boundary instead of replaying the forked parent history. Archives created before this normalization need a full source reimport, for example hansard import --sources codex-cli,codex-desktop,codex-sdk,claude,claude-sdk,devin-cli --since all.

Codex v2 subagent NEW_TASK assignments, MESSAGE follow-ups, and outbound send_message/followup_task arguments are encrypted in the local rollout, so Hansard cannot decrypt them locally. An explicit repair command can ask the authenticated Codex Responses path to recover selected collaboration messages:

hansard repair codex-subagent-prompts --dry-run
hansard repair codex-subagent-prompts --yes
hansard repair codex-subagent-prompts --session <id> --since all --limit all --yes
hansard repair codex-subagent-prompts --session <id> --model gpt-5.6-luna --effort low --yes

The full command is hansard repair codex-subagent-prompts [--session <id>] [--since 30d|all] [--limit 25|all] [--batch-size 1..8] [--model <id>] [--effort <level>] [--force] [--dry-run] [--yes] [--json]. It groups selected messages from the same child and source model into bounded batches (at most eight messages and 256 KiB of ciphertext), replays each batch through one isolated, ephemeral Codex turn, requests verbatim model-mediated extraction under fixed schema keys, and stores the recovered plaintext locally in ~/.hansard/state/codex-subagent-prompts.json with mode 0600, and reimports the affected Codex sessions. A batch is validated and saved atomically, cached ciphertext is never replayed unless --force is explicit, and token usage is reported when Codex emits it. Recovery defaults to the source session model at low reasoning effort; --model can select a lighter compatible model for this copy-only task. The first NEW_TASK remains the child boundary and parent run preview; later tasks and messages reappear in transcript order, outbound collaboration tool cards expose their recovered message argument, and matching receiver-side copies render as provider-generated subagent updates without inflating human prompt counts. This is not local decryption: it sends archived ciphertext to OpenAI and may incur model usage. The original rollout and its raw Hansard backup remain unchanged.

That targeted reimport applies to the parser-level transcript normalization in the preceding paragraph. It is not required solely for the relationship fields below. Archive schema v8 also gives linked child transcripts a canonical parentSessionId and gives parent summaries a versioned subagentRuns list, while retaining provider-specific details alongside it. Existing local archives need hansard update --yes --since all before project disclosure and subagent navigation have complete relationship metadata. Factory worker sessions are now imported as factory_subagent; existing Factory archives need hansard import --source factory --since all (or the all-source update above) to gain the same stats, search, and viewer behavior. Devin CLI imports also read tool_call_state and same-session subagent roots from sessions.db; existing Devin CLI archives created before parser v2 should be rebuilt with hansard import --source devin-cli --since all.

Use hansard reset to stop hansard, disable autostart, and remove hansard's local home, config, state, cache, logs, and archive objects. Source application histories and MCP configuration files are not changed. Add --keep-autostart to leave the login item in place.

Use hansard update after installing a newer npm package to rebuild the local archive with the new importer/parser logic without redoing setup:

npm install -g hansard@latest
hansard update --yes

hansard update preserves config.json, redaction settings, web account labels, manually imported ChatGPT/Claude.ai archives, local archives whose original source files are no longer present, source histories, and recall integrations. It removes derived local archive, import, index, cache, and sync bookkeeping, then reimports configured local sources from the stored preferences. If a source application has already aged out a transcript, such as Claude Code's default local cleanup, the previous hansard archive is restored instead of being silently dropped, with a source/provider/sourceType breakdown. When Hansard has raw backups for missing Claude Code source files, repair them explicitly before a clean reimport:

hansard repair claude-code-backups --dry-run
hansard repair claude-code-backups --yes
hansard update --yes --since all --sources claude

hansard doctor reports preserved unavailable source archives and recommends the same repair helper when applicable. The rebuild window comes from the initial backfill or an explicit all-source import such as hansard import --source all --since all; the fallback for legacy configs is all. The watcher's rolling imports.defaultSinceDays is not used by hansard update. It does not touch remote sync objects by default; use hansard sync replace when the remote should match the rebuilt local archive.

Use hansard config to change ~/.hansard/config.json without rerunning the init wizard:

hansard config path
hansard config setup
hansard config setup --watch-sources codex-cli,cursor --default-since-days 90
hansard config setup --sync-interval-minutes manual --no-autostart
hansard config setup --track-usage
hansard config set imports.defaultSinceDays 90
hansard config set sync.intervalMinutes manual
hansard config sources edit
hansard config sources set codex-cli,cursor,cline
hansard config sources add gemini-cli
hansard config sources remove claude-cowork

hansard config setup reopens the preferences parts of init, including what the watcher polls, the rolling import window, autosync cadence, login startup, and plan usage tracking. It does not run an import. hansard config sources edit opens just the watcher source picker from init, with the current config preselected.

Import Windows

hansard init starts with interactive setup: name this device, choose archive destinations, choose the full local archive/cache path, choose whether the local watcher starts at login and samples plan usage, and install recall commands or skills, then discover sources and optionally backfill history. After backfill, init asks which sources the background watcher should keep polling, then offers local OTel bridges for Claude Code, Gemini CLI, and Cline. Local archive storage is always enabled; R2, S3, and custom remote sync targets can be added as upload-only optional destinations. The configured device name is stored in config.json, stamped into new session metadata, and used for the remote namespace. Discovery and import phases show progress bars while they scan local stores.

After discovery, init offers a checkbox-style source picker. Rows marked [x] are selected; type one or more row numbers, such as 1 3 8, to toggle sources on or off, then press Enter with no input to accept the current selection. Codex SDK jobs and Claude SDK jobs are shown as separate opt-in sources because batch SDK traffic can exceed interactive sessions. The selected sources are saved in config and used by later hansard import --source all runs unless --sources is provided explicitly.

Default init sources:

  • Codex CLI sessions and Codex Desktop sessions from Codex state, shown as separate toggles, including linked Codex subagent child sessions when thread_spawn_edges metadata is present; Codex SDK jobs are available as an opt-in batch source
  • Claude Code CLI transcripts from ~/.claude/projects, including subagent definition snapshots and subagents/*.jsonl runs imported as child sessions
  • Claude Code Desktop metadata and Claude Cowork/local-agent sessions from the Claude app data, shown as separate toggles
  • Gemini CLI saved chats/checkpoints under ~/.gemini/tmp, plus session/export JSONL stores with tool, usage, and checkpoint metadata
  • Antigravity transcript logs and task/plan/walkthrough artifacts under ~/.gemini/antigravity/brain, including linked subagent child sessions when INVOKE_SUBAGENT steps point at spawned conversations, plus partial trajectory summaries from Antigravity app state when no readable artifacts exist
  • Antigravity CLI sessions (the Gemini CLI successor) from ~/.gemini/antigravity-cli/brain transcript logs and artifacts, with workspace attribution from the CLI's history.jsonl prompt log, imported as a separate source from the 2.0 app
  • Antigravity IDE sessions from ~/.gemini/antigravity-ide/brain, imported as a third separate source; stale migration copies of 2.0 conversations are skipped
  • Devin for Terminal sessions from ~/.local/share/devin/cli/sessions.db, including tool-call state, visible thinking-duration traces, and same-session subagent roots imported as linked child sessions
  • Devin Desktop ACP event logs from ~/Library/Application Support/Devin/User/acp-events, indexed by Devin's global state.vscdb
  • Cursor chats from older workspace state.vscdb SQLite stores and global cursorDiskKV Composer/Agent rows, including aiService prompt/generation fallbacks, raw SQLite salvage from Cursor global/workspace backups and WAL files, conservative matching of raw assistant/tool companion fragments back to same-project workspace sessions, duplicate prefix pruning, and newer ~/.cursor/projects/<project>/agent-transcripts files, including subagent child transcripts when Cursor writes subagents/ folders