@poutine-ai/hq
v0.9.0
Published
Agent-native local data foundation CLI for project-local SQLite/libSQL data.
Readme
hq
hq (Headquarters) is an agent-native local data foundation CLI. It turns a project-local SQLite/libSQL database in .hq/ into JSONL-friendly records that agents can create, query, export, import, and extend through modules.
Status
hq is pre-1.0 software. The CLI runs as a Bun-backed package/wrapper, not as a single-file compiled binary. Current main is ahead of the latest public tag and includes the shipped cross-command write-preview contract, atomic local batch operations, reviewed outbox recovery and route-template install previews, agent-ops DSR commands (doctor, status, report), route/subscription diagnostics, local ops approval gates, experiments decision support, privacy-hardened export/history/report surfaces, analytics visitor-quality diagnostics, provider-neutral payments workflows, and the v0.9 event runtime: project-scoped signed webhook ingress, daemon-supervised routes.dispatch, named runtime adapter instances, and durable delivery receipts.
Requirements
- Bun >= 1.3
Install
bun --version # required; hq runs through the Bun runtime
npm install -g @poutine-ai/hq
hq --helpThe published npm package stays on the latest tagged release until v0.9.0 is tagged and published.
To smoke-test a packed local tarball before publishing:
npm pack
npm install -g ./poutine-ai-hq-0.9.0.tgz
hq --helpFor local development:
bun install
bun run src/cli.ts --help
bun test
bun run typecheckBefore tagging or publishing a release candidate from the source tree, run the durable local proof wrapper so the exact pre-publish checks stay package-truth-backed:
bun run release:proofBasic usage
hq init
hq schema list
hq create users.users '{"email":"[email protected]","name":"Ada Lovelace"}' --dry-run
hq create users.users '{"email":"[email protected]","name":"Ada Lovelace"}'
hq set users.users[[email protected]].name 'Ada Byron' --dry-run
hq batch --stdin --dry-run < batch.json
hq batch --stdin < batch.json
hq delete users.users[[email protected]] --dry-run
hq get users.users
hq exportWrite preview contract
Promoted hq mutation previews share one release-level story instead of several unrelated one-offs:
create,set, anddeletereturnhq.write_preview.v1previews of the reviewed target row, planned record changes, and audited history before mutation.batchreturns onehq.batch.v1envelope with per-stephq.write_preview.v1results after reviewing the whole planned transaction.hq outbox dead retry|ignore,hq routes install-template, and the promoted payments mutation commands each return their own parseable preview envelope tailored to that workflow.- Every promoted
--dry-runsurface performs zero writes, so reviewed rows, tracked files, and history stay unchanged until the real command runs.
Fixture and example scope
hq does not currently ship a public hq examples seed ... command family.
Examples remain inline fixture JSON, temp-project setup, and adapter/worker examples so the published surface stays honest to the installed CLI.
If a future release promotes a seed command, it must ship as an explicitly documented, smoke-tested public contract rather than being implied by package docs.
Atomic local batch contract
When an adapter needs several local hq writes to commit together, use the public batch surface instead of inventing a project-local wrapper first.
hq batch --stdin --dry-run < batch.json
hq batch --stdin < batch.jsonhq batch --stdin --dry-runreturns onehq.batch.v1preview envelope withstatus=dry_runand per-stephq.write_preview.v1results while rolling back the whole planned transaction.hq batch --stdinreads one JSON object withsteps: [...]and applies localcreate,set, anddeletesteps atomically.- Success output is one
hq.batch.v1JSON object withstatus,step_count, and per-stepresultsafter the whole batch commits. - If any step fails, hq rolls back the whole batch and exits with
E_BATCH_STEP_FAILED; no writes are committed. - Interpolation is intentionally not supported in the public batch contract. Every step must use literal path/table strings and literal JSON values so agents do not have to guess an implicit reference language.
Delete dry-run contract
hq delete users.users[[email protected]] --dry-runhq delete <path> --dry-runreturns onehq.write_preview.v1JSON object per matching row with the reviewed target path,record.before,record.after=null, and the planned audited delete history event.hq delete <path> --dry-runperforms zero writes, so matching rows and history stay unchanged until the real delete runs.--dry-rundoes not apply tohq delete --tableorhq delete --column; those schema-editing paths remain explicitly mutating commands.
Agent contract discovery
Agents and adapters should not scrape help prose when hq can emit machine-readable command metadata.
Built-in module discovery works before hq init, so agents can run these from
any directory; project-local module metadata appears once a project exists.
hq module describe email --format json
hq module contracts --format json
hq module contracts experiments.assign --format jsonhq module describe <module> --format jsonemits onehq.module.v1object for a built-in or project-local module, including command metadata plus any doctor/status/report contributors the module ships.hq module contracts --format jsonemits onehq.module_contracts.v1object describing built-in and project-local modules plus each command's contract metadata.hq module contracts <module.command> --format jsonemits onehq.command.v1object for a single worker-facing surface such asanalytics.collect,email.send-request,experiments.assign,outbox.claim, orpayments.event.- Use these contracts to discover argv shape, stdin expectations, output format/schema, and side-effect hints before wiring autonomous workers.
Machine-readable error envelopes
Agents that already consume structured stdout contracts should opt into parseable stderr too when they need recovery-friendly failures.
hq get users.users --error-format json
HQ_ERROR_FORMAT=json hq analytics collect --stdin < event.json- Failure output is one
hq.error.v1JSON object on stderr. - Structured errors preserve the human-readable
code,message,exit_code, retryability, and fix guidance without forcing agents to parse emoji-prefixed prose. - The opt-in works for missing-project, unknown module/command, usage/validation errors, and known module-command failures while human stderr remains the default.
Analytics measurability contract
Privacy-safe analytics accepts anonymous page views, but visitor-quality diagnostics must make measurability explicit instead of silently treating missing identity as zero uniques.
hq analytics quality --site marketing --since 24h --format json
hq analytics visitors --site marketing --since 7d --bucket day --format jsonl- Send only privacy-safe identity keys for visitor measurability:
session_id,consent_subject_id, oruser_id. - Do not use raw IP addresses, raw user-agent strings, fingerprints, cookies, or provider-specific visitor ids to fill visitor gaps.
hq analytics qualityreports whole-window page-view coverage withunique_visitors_statusofmeasurable,partial, orunmeasurableplus a fix hint when coverage is incomplete.hq analytics visitorsreports dailypage_views,sessions, andunique_visitorswithunique_visitors_statusofmeasurable,partial, orunmeasurableso each bucket states its actual identity coverage.- Buckets with mixed identified and anonymous page views emit
"unique_visitors_status": "partial"plus a fix hint so operators do not mistake identified-subset counts for full-bucket coverage. - Buckets with page views but no privacy-safe identity emit
"unique_visitors_status": "unmeasurable"and"unique_visitors": nullso operators and agents do not trust fake zero-visitor output.
Analytics conversion record contract
Project-local adapters can record an explicit conversion with source linkage and safe metadata only:
hq analytics conversion record --stdin < conversion.json{
"site_id": "example-site",
"conversion_type": "contact_form_submission",
"source_table": "form.submissions",
"source_id": "...",
"path": "/contact",
"properties": {
"locale": "en-CA",
"intake_channel": "contact_form"
}
}Anonymous conversions may omit session_id, consent_subject_id, and user_id. If you pass event_id, it must reference an analytics.events row for the same site. Linked attribution is accepted only when site policy and active consent allow it; hq does not infer conversion links from email, timing, IP, user agent, or referrer heuristics. Do not include contact email, message body, raw IP, raw user agent, or provider identifiers in conversion properties.
Example unmeasurable bucket row:
{
"site_id": "marketing",
"bucket": "2026-05-21",
"page_views": 12,
"sessions": null,
"unique_visitors": null,
"unique_visitors_status": "unmeasurable",
"fix": "Send a privacy-safe session_id, consent_subject_id, or user_id with page_view events; do not use raw IP, raw user agent, or fingerprinting."
}Example partial bucket row:
{
"site_id": "marketing",
"bucket": "2026-05-21",
"page_views": 12,
"sessions": 4,
"unique_visitors": 3,
"unique_visitors_status": "partial",
"fix": "Send a privacy-safe session_id, consent_subject_id, or user_id with every page_view event; do not use raw IP, raw user agent, or fingerprinting."
}Route fixup workflow
When hq status or hq doctor shows pending outbox work without a wakeup path, use the route diagnostics and template installer instead of hand-authoring YAML from scratch.
hq routes explain --event-id <id> --format json
hq routes suggest --event-id <id> --name email-alert-wakeup
hq routes install-template --event-id <id> --name email-alert-wakeup
hq routes install-template --event-id <id> --name email-alert-wakeup --dry-runhq routes explainreports matching subscriptions, matching routes, non-matching route reasons, and invalid route files in one JSON envelope.hq routes suggestprints privacy-safe subscription and route YAML to stdout only; it can carry forward safe boolean payload filters such aspayload.alert_intent: true, but it never copies message bodies, secrets, or freeform provider payloads.hq routes install-template --dry-runreturns one parseable preview object withaction=install-template,status=dry_run,dry_run=true, the reviewed tracked-filetarget, the reviewedsource_event,planned_filesbefore/after file transitions, exact tracked YAMLwriteskeyed by target path, emptyhistory, and aconflictslist when tracked targets already exist and the real install would fail withE_ROUTE_TEMPLATE_EXISTS, while leaving the project untouched.hq routes install-templatewrites the reviewed suggestion into tracked.hq/subscriptions/<name>.yamland.hq/routes/<name>.yamlfiles without requiring source-tree-only scripts, then returns the samehq.route_template_install.v1envelope withstatus=installed,target,source_event,writes, and emptyhistoryso agents can verify the applied tracked-file contract.
Dead outbox recovery workflow
When a worker has already marked an outbox event dead, use the reviewed recovery surfaces instead of editing rows or replaying payloads by hand.
hq outbox dead list --limit 50 --format jsonl
hq outbox dead inspect <event-id> [--with-payload]
hq outbox dead retry <event-id> --reason "operator reviewed transient failure"
hq outbox dead retry <event-id> --reason "operator reviewed transient failure" --dry-run
hq outbox dead ignore <event-id> --reason "duplicate notification already handled"
hq outbox dead ignore <event-id> --reason "duplicate notification already handled" --dry-runhq outbox dead listemits compact discovery rows only (id,type,subject_table,subject_id,attempts,last_error,created_at,updated_at) so installed workers and operators can triage dead work without dumping payloads, lease tokens, or delivery destinations.hq outbox dead inspectreturns one machine-readable envelope with dead-event metadata plus related delivery receipts; the payload stays hidden unless--with-payloadis explicit.hq outbox dead retry --dry-runandhq outbox dead ignore --dry-runreturn one parseable preview object with the target dead-event row, planned state transition, and intended audited update while performing zero writes.hq outbox dead retrymoves only a revieweddeadevent back topending, clears stale lease fields, schedules the next attempt immediately, and preserves prior delivery receipts while recording the operator reason in the normal audited write path.hq outbox dead ignoremakes a reviewed terminalignoreddecision that removes the event from dead-event triage while preserving prior delivery receipts and audit history.
Ingress runtime boundary
Current main ships a project-scoped signed webhook ingress surface for machine-to-machine adapters:
hq events ingest --stdin [--dry-run]
hq serve --foreground [--host <host>] [--port <port>]hq events ingest --stdinstores one normalized canonical inbound event inevents.ingress_eventsand queues the matching outbox wakeup inside the same transaction.hq events ingest --stdin --dry-runpreviews the canonical ingress row and queued outbox work without mutating local state.hq serve --foregroundloads tracked.hq/webhooks/*.yamlconfigs, verifies signedPOSTrequests, normalizes the accepted payload, and delegates the durable write to the same canonical ingress flow.- This ingress surface is project-scoped and machine-to-machine only. public browser-facing routes remain deployment-owned BFF contracts.
- Keep deployment secrets, provider SDK calls, captcha, rate limiting, and app-specific public request validation in the adapter or edge layer, not in hq core.
Lease worker claim contracts
External sender/delivery workers should be able to implement claim → side effect → ack/fail flows without reading hq source or raw tables.
hq email send-request claim --worker smtp-worker --limit 1 --lease-seconds 120 emits JSONL rows with at least:
{"id":"...","account_address":"[email protected]","to_address":"[email protected]","subject":"...","body_text":"...","reply_to_message_id":"...","lease_token":"...","locked_until":"...","idempotency_key":"..."}hq outbox claim --consumer worker --limit 1 emits JSONL rows with at least:
{"id":"...","type":"form.submission.created","subject_table":"form.submissions","subject_id":"...","payload":{},"lease_token":"...","locked_until":"...","attempts":1}lease_tokenis the concurrency guard that must come back oncomplete,ack, orfail.locked_untillets workers bound retries and avoid treating an expired lease as still theirs.- Email claim rows include the reviewed send payload a sender needs locally; outbox claim rows include the generic event payload and routing identifiers a delivery worker needs locally.
Privacy doctor for tracked export surfaces
Before publishing examples, exporting project state, or handing a repo to another operator, run the conservative privacy lint over tracked JSONL/config surfaces:
hq doctor privacy --format jsonhq doctor privacyscans tracked.hq/data/*.jsonl,.hq/routes/*.yaml,.hq/subscriptions/*.yaml, and.hq/views/*.yamlfiles for obvious secret, credential, provider-delivery, and contact-identifier field names.- Output is one
hq.doctor.v1JSON envelope whose rows include only file path, line, field name, classification, and surface. It never echoes the matching secret value or contact value. - false positives are possible because the default lint is intentionally conservative. Treat it as a pre-publish review gate: rename, redact, or move the flagged field when it should not live in tracked project state; otherwise document the intentional exception in your own review process.
Saved view review workflow
Before automation, cron, or cockpit glue depends on a reviewed saved view, validate the tracked spec and inspect the fast explain/count/sample surfaces from the installed CLI instead of hand-counting rows.
Tracked views may also use relative-time filters such as where.occurred_at.since: 24h and reviewed one-hop lookup enrichments instead of ad-hoc SQL joins:
lookup:
alert_address:
from: email.addresses
local: from_address
foreign: address
select: [alert, label]hq view validate <name|--all> [--format jsonl]
hq view explain recent-traffic --format json
hq view count recent-traffic --format jsonl
hq view sample recent-traffic --limit 3 --format jsonlhq view validate <name|--all> [--format jsonl]emits machine-readable JSONL status rows withname,path,from,format, andstatus; invalid rows also include anerrorstring so agents can stop before a broken view reaches production automation.hq view explainreportssource_row_count, filter-by-filterremaining_row_count, resolved relative-time values, selected-field privacy safety, andzero_reasonwhen hq can explain an empty result.- Reviewed saved-view lookups are one-hop and explicit: output only adds requested lookup fields as
<lookup-name>.<field>keys, and missing matches becomenullinstead of exposing a whole joined row. hq view countlets dashboards and operator scripts fetch one reviewed count row without downloading every underlying record.hq view sampleemits a bounded JSONL preview using the tracked projection and ordering, which is useful for manual review without rewriting the view as raw SQL.
Payments adapter boundary
hq records normalized payment, invoice, challenge, settlement, and provider-event facts locally, but it is not the provider SDK, wallet, chain watcher, payout engine, or refund worker.
hq payments customer upsert --stdin < customer.json
hq payments customer upsert --stdin --dry-run < customer.json
hq payments invoice upsert --stdin [--dry-run] < invoice.json
hq payments payment upsert --stdin < payment.json
hq payments payment upsert --stdin --dry-run < payment.json
hq payments challenge create --stdin --dry-run < challenge.json
hq payments challenge create --stdin < challenge.json
hq payments challenge get <id-or-idempotency-key> --format json
hq payments challenge fulfill <id-or-idempotency-key> --payment-id <payment-id> [--dry-run]
hq payments challenge expire <id-or-idempotency-key> --reason "expired by adapter" [--dry-run]
hq payments settlement-event record --stdin --dry-run < settlement-event.json
hq payments settlement-event record --stdin < settlement-event.json
hq payments settlement-event list --payment <payment-id> --format jsonl
hq payments event ingest --stdin --dry-run < provider-event.json
hq payments event ingest --stdin < provider-event.json
hq payments status <payment-id-or-provider-reference> --format json- Use the payments module to keep provider-neutral customer/invoice/payment facts, payment-request challenges, append-only settlement history, and provider-event ingestion inside the local hq project.
- The adapter remains responsible for provider webhooks, wallet address creation, chain/RPC polling, refunds, payouts, and all external credentials.
- v0.8 does not ship built-in product/price/subscription CRUD, refund or dispute backoffice workflows, or provider-specific payment API calls in core.
hq payments payment upsert --dry-runpreviews whether hq would create a new durable payment row or reuse the existing idempotent one without mutating local state.hq payments challenge create --dry-runpreviews whether hq would create a new durable request row or reuse the existing idempotent one without mutating local state.hq payments challenge createstores a reviewed local request only; it does not mint an address, contact a processor, or watch the chain for fulfillment.hq payments settlement-event record --dry-runpreviews the local settlement row, related references, and audited create without appending history yet.hq payments event ingest --dry-runpreviews whether the reviewed provider event would create a new durable row or reuse the existing idempotent one without mutating local state.hq payments statusandhq payments settlement-event listare the local read surfaces adapters and operators should use instead of scraping raw tables.- Do not put private keys, seed phrases, bearer tokens, raw request headers, or provider credentials into hq rows. Keep only reviewed identifiers, amounts, settlement facts, and reconciliation metadata that can safely live in local durable history.
Report explain diagnostics
When a saved view already explains its own filters but the higher-level business report is still empty, use hq report explain to see which report contributors emitted nothing in the selected window.
hq report explain business-daily --since 24h --format jsonhq report explainemits onehq.report_explain.v1JSON object with the resolved period, final section count, per-contributorstatus(emitted,empty, orfailed), and any partial failures.- If no contributor emitted a section,
zero_reasontells agents they can stop guessing and inspect the listed contributors or widen the report window. - The explain surface is read-only and does not contact external providers.
Git/clone workflow
Track exported data and project-local modules, not runtime state:
- Commit
.hq/data/*.jsonland.hq/data/manifest.jsonafterhq export; the manifest records row counts and SHA-256 checksums for review, and normalhq importverifies it before loading rows when present. Usehq import --overwritefor intentional hand-edited data updates. - Commit project-local modules under
.hq/modules/, watch subscriptions under.hq/subscriptions/, route policies under.hq/routes/, and saved views under.hq/views/when you want them shared. - Do not commit or copy
.hq/db.sqlite*,.hq/secrets.yaml,.hq/config.yaml,.hq/cache/, or.hq/daemon.*into a fresh clone.
In a clone, run hq init. hq creates fresh runtime files and auto-imports any tracked .hq/data/*.jsonl. If hq init reports E_PROJECT_EXISTS, remove the copied runtime files above while keeping .hq/data/, .hq/modules/, .hq/subscriptions/, .hq/routes/, and .hq/views/, then rerun hq init.
See spec.md for the canonical agent-facing contract and current v0.9.0 scope.
