@zewstid/zeye-capture
v0.3.5
Published
Consented coding-session capture and continuous sync for Zeye Engineering Memory.
Readme
@zewstid/zeye-capture
Local-first capture and continuous sync for Zeye Engineering Memory. Metadata capture is the default. Complete provider-visible prompts, responses and tool records are captured only after explicit Zeye consent and local secret redaction. Hidden model reasoning is never captured.
Install the public CLI with Node 20.18.1 or newer:
npm install --global @zewstid/[email protected]Capture 0.3.x uses the @zewstid/zeye-shared 0.5.x contracts.
What exists now
FileOutbox: an atomic durable outbox. Consented transcript fields are authenticated AES-256-GCM ciphertext at rest; the separate local key and queue are owner-only. Existing v1 plaintext queues are encrypted in place on their first read before any record is returned.- Idempotent delivery records keyed by
adapter + sessionId + sourceEventId, including immutable repository/branch/HEAD facts observed at enqueue time. - Stable content hashes: replays with the same source and content collapse; changed content from the same provider event is reported as a conflict.
- Source checkpoints for JSONL/backfill cursors.
- A required
RedactionBoundary, executed before a payload reaches disk. - Delivery attempt and acknowledgement state, allowing a future HTTP transport to replay safely after an offline period.
- Claude Code hook and Codex JSONL adapters for lifecycle metadata and, when explicitly consented, provider-visible messages and tool records.
- A one-command, machine-level background service that continuously imports and retries on
macOS (launchd), Linux (systemd user service), and Windows (Task Scheduler).
The host continuously discovers repositories from installed Claude/Codex
adapters as well as explicit Zeye bindings. One
service installis enough; repositories configured later join the same service automatically, and Git worktrees remain independent roots. CaptureHttpTransport, which turns one durable local delivery into one authenticated, idempotentPOST /engineering-memory/ingestbatch.- A production-oriented local CLI: Claude Code hook install/removal, configured Codex JSONL sync, safe Git identity discovery, durable outbox locking, explicit heartbeat, and non-secret status diagnostics.
Security and limits
- Transcript content is locally redacted before either the durable outbox or network boundary. It is then AES-256-GCM encrypted in the local queue with a generated owner-only sidecar key. Authentication binds ciphertext to its delivery/source/payload identity, transcript-bearing idempotency digests are keyed HMACs rather than guessable plain hashes, and missing or altered keys/ciphertext fail closed. The API independently envelope-encrypts each uploaded segment.
- Every queued content record is bound to the consent ID active at capture. Removing or replacing that consent stops replay; the client never silently re-labels offline content under a later consent.
- The one-time enrollment token is kept in an owner-only local service config, never in the service definition, command line, repository, or outbox. Native keychain integration remains future hardening.
- VS Code/GitHub Copilot currently has a safe companion-event boundary; a packaged extension is not included in this release.
- “Complete” means all provider-visible records emitted by supported adapters. It never means hidden chain-of-thought or provider-private state.
Usage
import { FileOutbox, type RedactionBoundary } from '@zewstid/zeye-capture'
const redactor: RedactionBoundary = {
redact(event) {
return { payload: event.payload, redactionCount: 0 }
},
}
const outbox = new FileOutbox('/private/path/zeye-capture', redactor)
const result = await outbox.enqueue({
adapter: 'claude-code',
sessionId: 'provider-session-id',
sourceEventId: 'provider-event-id',
occurredAt: new Date().toISOString(),
type: 'tool.completed',
payload: { tool: 'Bash', exitCode: 0 },
})The included transport calls markAttempt() for unsuccessful uploads and
acknowledge(deliveryId, receiptId) only after a server receipt is durably
returned. It never regenerates a delivery ID or mutates an already queued
event.
Provider adapters
import {
FileOutbox, metadataRedactor, fromClaudeCodeHook,
fromCodexJsonl, CaptureHttpTransport,
} from '@zewstid/zeye-capture'
const outbox = new FileOutbox('/private/path/zeye-capture', metadataRedactor)
// Claude Code hook stdin JSON → local durable evidence.
const claudeEvent = fromClaudeCodeHook(hookPayload)
if (claudeEvent) await outbox.enqueue(claudeEvent)
// One Codex JSONL record → local durable evidence.
const codexEvent = fromCodexJsonl(JSON.parse(jsonlLine), 'rollout.jsonl')
if (codexEvent) await outbox.enqueue(codexEvent)
// A VS Code companion extension sends a deliberately metadata-only event to
// `zeye-capture vscode-copilot-event` over stdin. Its event shape is also
// available to hosts through fromVSCodeCopilotEvent().
// A host application supplies the enrollment token at runtime; it is not
// written into the outbox. The server still has to enable
// engineering-memory.session-ledger for the organization.
const transport = new CaptureHttpTransport(outbox, enrollmentConfiguration)
await transport.flush()The adapters intentionally treat unknown provider record shapes as
capture.warning. That keeps capture health honest while provider formats
change, instead of inventing a complete record.
The VS Code adapter is an integration boundary for a companion extension; it does not claim to scrape Copilot chats or private extension storage. A future packaged VSIX can emit these events using the same contract without changing the durable outbox or server API.
Local host workflow
Build the package first, then make the executable available on your PATH (or
use the built dist/src/cli.js through Node). Run all commands from the Git
repository being captured, or pass --repo /absolute/path.
# Add Zeye-owned groups to the repository-local, untracked Claude settings.
zeye-capture install claude
# Register a known Codex rollout/session JSONL source. This does not guess a
# provider-private location and does not start a background process.
zeye-capture install codex --jsonl /absolute/path/to/rollout.jsonl
# Import configured Codex sources, upload any durable outbox records, and send
# a heartbeat when runtime enrollment is available.
zeye-capture sync
# View safe local diagnostics: queue counts, lock state, Git identity, and
# whether upload variables are complete. It never prints the machine token.
zeye-capture status
zeye-capture heartbeat
# With ZEYE_CAPTURE_* values present, install and immediately start continuous
# sync. Repositories with a Zeye Claude hook or Codex source are discovered
# automatically, including ones configured after startup. Re-run only when an
# explicit internal Zeye repository binding is needed. The service survives
# logout/restart according to the host OS policy.
zeye-capture service install --repo /path/to/repo
# Transcript consent is provider-specific. Repeat the same idempotent command
# for each provider whose consent was granted; existing provider enrollments
# and repository registrations are preserved.
ZEYE_CAPTURE_CONTENT_CONSENT_ID=... ZEYE_CAPTURE_TRANSCRIPT_CONSENT=yes \
zeye-capture service install --repo /path/to/repo --provider claude-code
ZEYE_CAPTURE_CONTENT_CONSENT_ID=... ZEYE_CAPTURE_TRANSCRIPT_CONSENT=yes \
zeye-capture service install --repo /path/to/repo --provider codex
# Unregister one checkout. The host and credential remain until the final
# registered checkout is removed.
zeye-capture service uninstall --repo /path/to/repo
# Remove only hook groups/sources created for Zeye.
zeye-capture uninstall claude
zeye-capture uninstall codex --jsonl /absolute/path/to/rollout.jsonlinstall claude defaults to .claude/settings.local.json and refuses to
modify that file if Git tracks it. It preserves other hook groups. The host
configuration is stored under $XDG_CONFIG_HOME/zeye-capture/host-v1.json (or
~/.config/...) with owner-only permissions where the OS supports POSIX modes.
The default outbox and its separate 256-bit encryption key are under
$XDG_STATE_HOME/zeye-capture/ (or ~/.local/state/...) and are also
owner-restricted. Back up or protect both together: the key is deliberately
not recoverable from Zeye, and capture fails closed if it is missing. It never
generates a replacement key over an encrypted queue. Deliberately deleting the
key is cryptographic revocation of all transcript content in that repository's
queue and is irreversible; remove the corresponding queue as an explicit
separate cleanup action. Native OS keychain/credential-vault storage is not
claimed in this release—the fallback is an owner-only local key file.
ZEYE_CAPTURE_OUTBOX_DIR,
when set, is a private base directory: Zeye creates a distinct hashed child for
each repository so records cannot cross repository boundaries. Neither location
stores the upload token.
Claude hooks automatically reuse the owner-only machine service enrollment when their repository root is explicitly registered in that enrollment. This lets a hook apply the active transcript consent and upload directly without copying credentials into Claude settings or a shell profile. A different checkout—even of the same remote repository—does not inherit that enrollment.
Interactive setup supplies these variables once. service install persists
them in an owner-only local service enrollment so no scheduler is configured
by hand:
ZEYE_CAPTURE_ENDPOINT=https://api.example/engineering-memory/ingest
ZEYE_CAPTURE_TOKEN=zem_... # persisted only by service install in its owner-only enrollment
ZEYE_CAPTURE_ORGANIZATION_ID=...
ZEYE_CAPTURE_INSTALLATION_ID=...
ZEYE_CAPTURE_ACTOR_JSON={...}
ZEYE_CAPTURE_REPOSITORY_ID=... # optional internal Zeye binding
ZEYE_CAPTURE_PROVIDER_REPOSITORY_ID=... # optional provider numeric ID
ZEYE_CAPTURE_CONTENT_CONSENT_ID=... # required only for transcripts
ZEYE_CAPTURE_TRANSCRIPT_CONSENT=yes # explicit local opt-inPass --provider claude-code, --provider codex, or
--provider vscode-copilot when installing transcript consent. The private
enrollment retains a separate consent ID for every provider, preventing one
provider's consent from being presented with another provider's transcript.
The unscoped form remains supported only for enrollment files created by older
capture clients.
ZEYE_CAPTURE_ENDPOINT must be an HTTPS URL ending in
/engineering-memory/ingest. HTTP is accepted only for an explicit loopback
development collector. Without both content-consent variables, capture remains
metadata-only even if the installation is transcript-capable.
Without all required values the host remains useful offline: it captures into
the local outbox and reports uploadConfigured: false. Git discovery supplies
the remote's canonical owner/repository name, branch, and HEAD SHA when
available; the raw remote URL is not stored or printed. A configured internal
repository ID enables the server to bind those facts to existing Zeye evidence.
For continuous capture, run zeye-capture service install --repo /path/to/repo
once. The CLI maintains one machine-level service and combines its private
binding registry with repository roots already configured by Claude and Codex,
instead of creating one scheduler per repo. Raw paths remain local and are
never sent in the capture protocol; only canonical repository facts and
one-way hashes cross the network boundary.
Discovery is bounded to the Zeye-owned local adapter registry and explicit
enrollment roots. It does not crawl the home directory, workspace folders or
Git remotes. Discovery also grants no server permission: the API still resolves
the repository inside the installation's tenant and approved repository scope.
Each repository keeps its own outbox, lock, Git identity and optional Zeye
repository binding, so offline replay is idempotent and cannot cross repository
boundaries. The CLI
uses an owner-only single-process lock and waits briefly for an overlapping
hook/run before failing, so normal bursts are queued rather than dropped. A
sync exits non-zero if an input source or upload fails, while retaining its
outbox records for retry. Hook calls use the same lock.
Build and test
npm -w @zewstid/zeye-capture run build
npm -w @zewstid/zeye-capture testThe root workspace already includes packages/*, so no workspace manifest
change is required. The package uses only Node built-ins at runtime.
