@stravica-ai/digital-operator-pa 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +170 -0
- package/bin/desk +9 -0
- package/bin/distill +2 -0
- package/bin/estate-sync +2 -0
- package/bin/launch +2 -0
- package/bin/memory-sync +2 -0
- package/bin/migrate-inspect +2 -0
- package/bin/pa +2 -0
- package/bin/pa-coordinator-hook +2 -0
- package/bin/pa-init +2 -0
- package/bin/research +2 -0
- package/bin/scheduler +2 -0
- package/bin/secrets +2 -0
- package/bin/slice +2 -0
- package/bin/smoke +9 -0
- package/bin/sync +2 -0
- package/dist/engineering-pa-cli/README.md +205 -0
- package/dist/engineering-pa-cli/bin/estate-sync +2 -0
- package/dist/engineering-pa-cli/bin/launch +2 -0
- package/dist/engineering-pa-cli/bin/pa +2 -0
- package/dist/engineering-pa-cli/bin/pa-coordinator-hook +12 -0
- package/dist/engineering-pa-cli/bin/research +2 -0
- package/dist/engineering-pa-cli/bin/scheduler +2 -0
- package/dist/engineering-pa-cli/bin/secrets +2 -0
- package/dist/engineering-pa-cli/bin/smoke +9 -0
- package/dist/engineering-pa-cli/bin/sync +2 -0
- package/dist/engineering-pa-cli/config/coordinator-allowlist.json +118 -0
- package/dist/engineering-pa-cli/docs/coordinator-hook.md +211 -0
- package/dist/engineering-pa-cli/docs/estate-sync.md +412 -0
- package/dist/engineering-pa-cli/docs/gsc.md +175 -0
- package/dist/engineering-pa-cli/docs/lint-linkedin.md +139 -0
- package/dist/engineering-pa-cli/docs/secrets.md +221 -0
- package/dist/engineering-pa-cli/docs/smoke.md +113 -0
- package/dist/engineering-pa-cli/docs/staging-alias.md +114 -0
- package/dist/engineering-pa-cli/index.mjs +17 -0
- package/dist/engineering-pa-cli/package.json +37 -0
- package/dist/engineering-pa-cli/src/autonomy/cli.mjs +329 -0
- package/dist/engineering-pa-cli/src/autonomy/envelope.mjs +301 -0
- package/dist/engineering-pa-cli/src/coordinator-hook/cli.mjs +140 -0
- package/dist/engineering-pa-cli/src/coordinator-hook/config.mjs +134 -0
- package/dist/engineering-pa-cli/src/coordinator-hook/message.mjs +47 -0
- package/dist/engineering-pa-cli/src/coordinator-hook/policy.mjs +211 -0
- package/dist/engineering-pa-cli/src/coordinator-hook/repo-root.mjs +44 -0
- package/dist/engineering-pa-cli/src/coordinator-hook/self.mjs +30 -0
- package/dist/engineering-pa-cli/src/environment/cli.mjs +287 -0
- package/dist/engineering-pa-cli/src/environment/probes.mjs +302 -0
- package/dist/engineering-pa-cli/src/gsc/auth.mjs +148 -0
- package/dist/engineering-pa-cli/src/gsc/cli.mjs +93 -0
- package/dist/engineering-pa-cli/src/gsc/cloudflare.mjs +318 -0
- package/dist/engineering-pa-cli/src/gsc/commands/add-property.mjs +70 -0
- package/dist/engineering-pa-cli/src/gsc/commands/list-properties.mjs +33 -0
- package/dist/engineering-pa-cli/src/gsc/commands/pull-analytics.mjs +130 -0
- package/dist/engineering-pa-cli/src/gsc/commands/submit-sitemap.mjs +41 -0
- package/dist/engineering-pa-cli/src/gsc/commands/verify-via-cf-dns.mjs +189 -0
- package/dist/engineering-pa-cli/src/knowledge/call-log.mjs +59 -0
- package/dist/engineering-pa-cli/src/knowledge/cli.mjs +242 -0
- package/dist/engineering-pa-cli/src/knowledge/eval.mjs +446 -0
- package/dist/engineering-pa-cli/src/knowledge/formatters.mjs +331 -0
- package/dist/engineering-pa-cli/src/knowledge/graph.mjs +710 -0
- package/dist/engineering-pa-cli/src/launch/README.md +214 -0
- package/dist/engineering-pa-cli/src/launch/cli.mjs +51 -0
- package/dist/engineering-pa-cli/src/launch/commands/launch.mjs +106 -0
- package/dist/engineering-pa-cli/src/launch/identity.mjs +43 -0
- package/dist/engineering-pa-cli/src/launch/planner.mjs +44 -0
- package/dist/engineering-pa-cli/src/launch/repo-root.mjs +53 -0
- package/dist/engineering-pa-cli/src/launch/runner.mjs +147 -0
- package/dist/engineering-pa-cli/src/launch/task-loader.mjs +85 -0
- package/dist/engineering-pa-cli/src/lint/cli.mjs +61 -0
- package/dist/engineering-pa-cli/src/lint/commands/linkedin.mjs +133 -0
- package/dist/engineering-pa-cli/src/lint/detectors.mjs +90 -0
- package/dist/engineering-pa-cli/src/lint/linter.mjs +69 -0
- package/dist/engineering-pa-cli/src/lint/reporter.mjs +32 -0
- package/dist/engineering-pa-cli/src/lint/rules.mjs +132 -0
- package/dist/engineering-pa-cli/src/pa/cli.mjs +369 -0
- package/dist/engineering-pa-cli/src/pa/doc-groups.mjs +164 -0
- package/dist/engineering-pa-cli/src/pa/io.mjs +80 -0
- package/dist/engineering-pa-cli/src/pa/project-cli.mjs +385 -0
- package/dist/engineering-pa-cli/src/pa/projects.mjs +352 -0
- package/dist/engineering-pa-cli/src/pa/repo-root.mjs +64 -0
- package/dist/engineering-pa-cli/src/pa/session.mjs +254 -0
- package/dist/engineering-pa-cli/src/pa/work-cli.mjs +164 -0
- package/dist/engineering-pa-cli/src/pa/work.mjs +196 -0
- package/dist/engineering-pa-cli/src/pa/workstreams.mjs +201 -0
- package/dist/engineering-pa-cli/src/research/README.md +360 -0
- package/dist/engineering-pa-cli/src/research/briefs/extractor.md +103 -0
- package/dist/engineering-pa-cli/src/research/briefs/searcher.md +113 -0
- package/dist/engineering-pa-cli/src/research/briefs/synthesizer.md +124 -0
- package/dist/engineering-pa-cli/src/research/briefs/verifier.md +86 -0
- package/dist/engineering-pa-cli/src/research/cli.mjs +141 -0
- package/dist/engineering-pa-cli/src/research/commands/gates.mjs +176 -0
- package/dist/engineering-pa-cli/src/research/commands/list.mjs +56 -0
- package/dist/engineering-pa-cli/src/research/commands/mark.mjs +97 -0
- package/dist/engineering-pa-cli/src/research/commands/paths.mjs +69 -0
- package/dist/engineering-pa-cli/src/research/commands/prune.mjs +86 -0
- package/dist/engineering-pa-cli/src/research/commands/run.mjs +94 -0
- package/dist/engineering-pa-cli/src/research/commands/show.mjs +43 -0
- package/dist/engineering-pa-cli/src/research/docs/operator-present-flow.md +120 -0
- package/dist/engineering-pa-cli/src/research/format.mjs +59 -0
- package/dist/engineering-pa-cli/src/research/promote.mjs +260 -0
- package/dist/engineering-pa-cli/src/research/schema/finding.schema.json +55 -0
- package/dist/engineering-pa-cli/src/research/schema/gates.schema.json +34 -0
- package/dist/engineering-pa-cli/src/research/schema/manifest.schema.json +59 -0
- package/dist/engineering-pa-cli/src/research/schema/plan.schema.json +41 -0
- package/dist/engineering-pa-cli/src/research/schema/sources.schema.json +31 -0
- package/dist/engineering-pa-cli/src/research/store/findings.mjs +61 -0
- package/dist/engineering-pa-cli/src/research/store/gates.mjs +73 -0
- package/dist/engineering-pa-cli/src/research/store/index.mjs +312 -0
- package/dist/engineering-pa-cli/src/research/store/manifest.mjs +56 -0
- package/dist/engineering-pa-cli/src/research/store/plan.mjs +59 -0
- package/dist/engineering-pa-cli/src/research/store/sources.mjs +44 -0
- package/dist/engineering-pa-cli/src/research/store/validate.mjs +92 -0
- package/dist/engineering-pa-cli/src/research/workflows/research-normal.js +573 -0
- package/dist/engineering-pa-cli/src/scheduler/cli.mjs +197 -0
- package/dist/engineering-pa-cli/src/secrets/cli.mjs +254 -0
- package/dist/engineering-pa-cli/src/secrets/commands/backup-template.mjs +67 -0
- package/dist/engineering-pa-cli/src/secrets/commands/backup.mjs +85 -0
- package/dist/engineering-pa-cli/src/secrets/commands/delete.mjs +58 -0
- package/dist/engineering-pa-cli/src/secrets/commands/find.mjs +36 -0
- package/dist/engineering-pa-cli/src/secrets/commands/get.mjs +49 -0
- package/dist/engineering-pa-cli/src/secrets/commands/init.mjs +69 -0
- package/dist/engineering-pa-cli/src/secrets/commands/list.mjs +37 -0
- package/dist/engineering-pa-cli/src/secrets/commands/rotate.mjs +46 -0
- package/dist/engineering-pa-cli/src/secrets/commands/set.mjs +123 -0
- package/dist/engineering-pa-cli/src/secrets/commands/status.mjs +146 -0
- package/dist/engineering-pa-cli/src/secrets/commands/vaults.mjs +57 -0
- package/dist/engineering-pa-cli/src/secrets/commands/verify.mjs +143 -0
- package/dist/engineering-pa-cli/src/secrets/vault.mjs +766 -0
- package/dist/engineering-pa-cli/src/smoke/checks.mjs +199 -0
- package/dist/engineering-pa-cli/src/smoke/cli.mjs +108 -0
- package/dist/engineering-pa-cli/src/smoke/commands/run.mjs +237 -0
- package/dist/engineering-pa-cli/src/smoke/runner.mjs +191 -0
- package/dist/engineering-pa-cli/src/staging-alias/cli.mjs +91 -0
- package/dist/engineering-pa-cli/src/staging-alias/cloudflare.mjs +380 -0
- package/dist/engineering-pa-cli/src/staging-alias/commands/list.mjs +36 -0
- package/dist/engineering-pa-cli/src/staging-alias/commands/set.mjs +188 -0
- package/dist/engineering-pa-cli/src/staging-alias/commands/status.mjs +113 -0
- package/dist/engineering-pa-cli/src/staging-alias/commands/unset.mjs +95 -0
- package/dist/engineering-pa-cli/src/staging-alias/sites.mjs +105 -0
- package/dist/engineering-pa-cli/src/sync/cli.mjs +151 -0
- package/dist/engineering-pa-cli/src/sync/git.mjs +149 -0
- package/dist/engineering-pa-cli/src/sync/lock.mjs +30 -0
- package/dist/engineering-pa-cli/src/sync/regenerated-artifacts.mjs +114 -0
- package/dist/engineering-pa-cli/src/sync/registry-scope.mjs +260 -0
- package/dist/engineering-pa-cli/src/sync/run.mjs +531 -0
- package/dist/engineering-pa-cli/src/sync/runner.mjs +62 -0
- package/dist/engineering-pa-cli/src/sync/runs-digest.mjs +138 -0
- package/dist/engineering-pa-core/README.md +60 -0
- package/dist/engineering-pa-core/bin/desk +9 -0
- package/dist/engineering-pa-core/bin/distill +2 -0
- package/dist/engineering-pa-core/bin/memory-sync +2 -0
- package/dist/engineering-pa-core/bin/slice +2 -0
- package/dist/engineering-pa-core/docs/autonomy-envelope-tracker.md +111 -0
- package/dist/engineering-pa-core/docs/blueprints/README.md +45 -0
- package/dist/engineering-pa-core/docs/blueprints/docker-services/blueprint.md +168 -0
- package/dist/engineering-pa-core/docs/blueprints/git-identity/blueprint.md +116 -0
- package/dist/engineering-pa-core/docs/blueprints/ideas/custom-workspace-views.md +9 -0
- package/dist/engineering-pa-core/docs/blueprints/ideas/knowledge-domain-patterns.md +15 -0
- package/dist/engineering-pa-core/docs/blueprints/ideas/office-integration.md +11 -0
- package/dist/engineering-pa-core/docs/blueprints/ideas/team-scaling.md +9 -0
- package/dist/engineering-pa-core/docs/blueprints/ideas/work-personal-separation.md +9 -0
- package/dist/engineering-pa-core/docs/blueprints/scheduled-distillation/blueprint.md +129 -0
- package/dist/engineering-pa-core/docs/blueprints/tool-integration/blueprint.md +110 -0
- package/dist/engineering-pa-core/docs/commands/init.md +159 -0
- package/dist/engineering-pa-core/docs/commands/schedule.md +98 -0
- package/dist/engineering-pa-core/docs/commands/upgrade.md +67 -0
- package/dist/engineering-pa-core/docs/entity-location-map.md +93 -0
- package/dist/engineering-pa-core/docs/knowledge-scaffold.md +121 -0
- package/dist/engineering-pa-core/docs/lock.md +155 -0
- package/dist/engineering-pa-core/docs/memory-sync.md +28 -0
- package/dist/engineering-pa-core/docs/pa-operating-model.md +457 -0
- package/dist/engineering-pa-core/docs/scheduling.md +35 -0
- package/dist/engineering-pa-core/docs/standards/README.md +24 -0
- package/dist/engineering-pa-core/docs/standards/briefing-contract.md +161 -0
- package/dist/engineering-pa-core/docs/standards/tooling.md +240 -0
- package/dist/engineering-pa-core/docs/tools/distill.md +230 -0
- package/dist/engineering-pa-core/docs/tools/memory-sync.md +98 -0
- package/dist/engineering-pa-core/docs/tools/scheduler.md +243 -0
- package/dist/engineering-pa-core/docs/tools/slice.md +150 -0
- package/dist/engineering-pa-core/docs/upstream-issue-filing.md +88 -0
- package/dist/engineering-pa-core/docs/working-directory.md +29 -0
- package/dist/engineering-pa-core/docs/workspace-operations.md +46 -0
- package/dist/engineering-pa-core/index.mjs +21 -0
- package/dist/engineering-pa-core/knowledge/README.md +53 -0
- package/dist/engineering-pa-core/knowledge/canonical/.gitkeep +0 -0
- package/dist/engineering-pa-core/knowledge/content-voice/README.md +64 -0
- package/dist/engineering-pa-core/knowledge/content-voice/banned-ai-tells-baseline.md +133 -0
- package/dist/engineering-pa-core/knowledge/content-voice/banned-ai-tells-shape.md +58 -0
- package/dist/engineering-pa-core/knowledge/content-voice/banned-ai-tells-taxonomy.md +33 -0
- package/dist/engineering-pa-core/knowledge/content-voice/content-pipeline-routing.md +58 -0
- package/dist/engineering-pa-core/knowledge/content-voice/new-tell-capture-procedure.md +40 -0
- package/dist/engineering-pa-core/knowledge/content-voice/voice-guide-shape.md +43 -0
- package/dist/engineering-pa-core/knowledge/delegation-dispatch/README.md +34 -0
- package/dist/engineering-pa-core/knowledge/delegation-dispatch/briefing-contract.md +77 -0
- package/dist/engineering-pa-core/knowledge/delegation-dispatch/delegation-modes.md +84 -0
- package/dist/engineering-pa-core/knowledge/delegation-dispatch/dispatch-envelope.md +238 -0
- package/dist/engineering-pa-core/knowledge/delegation-dispatch/task-groups-and-orchestrators.md +62 -0
- package/dist/engineering-pa-core/knowledge/delegation-dispatch/team-improvement-loop.md +48 -0
- package/dist/engineering-pa-core/knowledge/delegation-dispatch/tool-development-policy.md +48 -0
- package/dist/engineering-pa-core/knowledge/engineering-discipline/README.md +33 -0
- package/dist/engineering-pa-core/knowledge/engineering-discipline/blast-radius-and-safety.md +50 -0
- package/dist/engineering-pa-core/knowledge/engineering-discipline/investigate-root-causes.md +48 -0
- package/dist/engineering-pa-core/knowledge/engineering-discipline/role-ladder.md +57 -0
- package/dist/engineering-pa-core/knowledge/engineering-discipline/verify-runtime-behaviour.md +53 -0
- package/dist/engineering-pa-core/knowledge/engineering-discipline/worktree-isolation.md +50 -0
- package/dist/engineering-pa-core/knowledge/knowledge-memory/README.md +33 -0
- package/dist/engineering-pa-core/knowledge/knowledge-memory/claude-md-hygiene.md +47 -0
- package/dist/engineering-pa-core/knowledge/knowledge-memory/journal-and-self-improvement.md +53 -0
- package/dist/engineering-pa-core/knowledge/knowledge-memory/knowledge-retrieval-discipline.md +62 -0
- package/dist/engineering-pa-core/knowledge/knowledge-memory/librarian-synthesis-mandate.md +56 -0
- package/dist/engineering-pa-core/knowledge/knowledge-memory/staging-and-observations.md +58 -0
- package/dist/engineering-pa-core/knowledge/operator-interface/README.md +32 -0
- package/dist/engineering-pa-core/knowledge/operator-interface/communication-style.md +33 -0
- package/dist/engineering-pa-core/knowledge/operator-interface/escalation-and-correction.md +45 -0
- package/dist/engineering-pa-core/knowledge/operator-interface/push-back-discipline.md +42 -0
- package/dist/engineering-pa-core/knowledge/operator-interface/session-boot-and-end.md +56 -0
- package/dist/engineering-pa-core/knowledge/platform-tooling/README.md +37 -0
- package/dist/engineering-pa-core/knowledge/platform-tooling/desk-cli-reference.md +52 -0
- package/dist/engineering-pa-core/knowledge/platform-tooling/environment-awareness.md +91 -0
- package/dist/engineering-pa-core/knowledge/platform-tooling/mcp-servers-catalogue.md +52 -0
- package/dist/engineering-pa-core/knowledge/platform-tooling/pa-cli-reference.md +55 -0
- package/dist/engineering-pa-core/knowledge/platform-tooling/scheduling-and-launchd.md +56 -0
- package/dist/engineering-pa-core/knowledge/platform-tooling/tool-discovery-discipline.md +49 -0
- package/dist/engineering-pa-core/knowledge/platform-tooling/working-directory-vs-project-root.md +43 -0
- package/dist/engineering-pa-core/knowledge/platform-tooling/workspace-server.md +51 -0
- package/dist/engineering-pa-core/knowledge/raw/.gitkeep +0 -0
- package/dist/engineering-pa-core/knowledge/scaffold/domains.json +5 -0
- package/dist/engineering-pa-core/knowledge/schema/.gitkeep +0 -0
- package/dist/engineering-pa-core/knowledge/work-management/README.md +33 -0
- package/dist/engineering-pa-core/knowledge/work-management/desk-cli.md +84 -0
- package/dist/engineering-pa-core/knowledge/work-management/progress-md.md +48 -0
- package/dist/engineering-pa-core/knowledge/work-management/projects.md +64 -0
- package/dist/engineering-pa-core/knowledge/work-management/spawn-log.md +48 -0
- package/dist/engineering-pa-core/knowledge/work-management/work-items.md +67 -0
- package/dist/engineering-pa-core/package.json +26 -0
- package/dist/engineering-pa-core/roles/agent-ops/definition.md +79 -0
- package/dist/engineering-pa-core/roles/dispatch-briefer/definition.md +195 -0
- package/dist/engineering-pa-core/roles/librarian/definition.md +176 -0
- package/dist/engineering-pa-core/roles/pa/definition.md +93 -0
- package/dist/engineering-pa-core/roles/system-admin/definition.md +106 -0
- package/dist/engineering-pa-core/roles/tool-developer/definition.md +116 -0
- package/dist/engineering-pa-core/roles/writer/definition.md +130 -0
- package/dist/engineering-pa-core/schema/autonomy-envelope.schema.json +61 -0
- package/dist/engineering-pa-core/schema/project.json +114 -0
- package/dist/engineering-pa-core/schema/schedule.json +63 -0
- package/dist/engineering-pa-core/schema/work-item.json +92 -0
- package/dist/engineering-pa-core/src/config/pa-package-name.mjs +30 -0
- package/dist/engineering-pa-core/src/desk/cli.mjs +95 -0
- package/dist/engineering-pa-core/src/desk/commands.mjs +398 -0
- package/dist/engineering-pa-core/src/desk/format.mjs +84 -0
- package/dist/engineering-pa-core/src/desk/repo.mjs +172 -0
- package/dist/engineering-pa-core/src/desk/team-scope.mjs +142 -0
- package/dist/engineering-pa-core/src/distill/cli.mjs +112 -0
- package/dist/engineering-pa-core/src/distill/prereqs.mjs +171 -0
- package/dist/engineering-pa-core/src/distill/prompt.mjs +74 -0
- package/dist/engineering-pa-core/src/distill/run.mjs +230 -0
- package/dist/engineering-pa-core/src/distill/runner.mjs +86 -0
- package/dist/engineering-pa-core/src/distill/staging.mjs +59 -0
- package/dist/engineering-pa-core/src/knowledge/retrieval/backlink-indexer.mjs +154 -0
- package/dist/engineering-pa-core/src/knowledge/retrieval/graph-builder.mjs +186 -0
- package/dist/engineering-pa-core/src/knowledge/retrieval/index-builder.mjs +225 -0
- package/dist/engineering-pa-core/src/knowledge/retrieval/query.mjs +263 -0
- package/dist/engineering-pa-core/src/lock/lock.mjs +202 -0
- package/dist/engineering-pa-core/src/memory-sync/cli-support.mjs +12 -0
- package/dist/engineering-pa-core/src/memory-sync/cli.mjs +56 -0
- package/dist/engineering-pa-core/src/memory-sync/commands/pull.mjs +44 -0
- package/dist/engineering-pa-core/src/memory-sync/commands/push.mjs +46 -0
- package/dist/engineering-pa-core/src/memory-sync/mirror.mjs +114 -0
- package/dist/engineering-pa-core/src/memory-sync/repo.mjs +65 -0
- package/dist/engineering-pa-core/src/scheduler/detect.mjs +111 -0
- package/dist/engineering-pa-core/src/scheduler/host.mjs +38 -0
- package/dist/engineering-pa-core/src/scheduler/index.mjs +13 -0
- package/dist/engineering-pa-core/src/scheduler/install.mjs +854 -0
- package/dist/engineering-pa-core/src/scheduler/list.mjs +168 -0
- package/dist/engineering-pa-core/src/scheduler/remove.mjs +176 -0
- package/dist/engineering-pa-core/src/scheduler/verify.mjs +435 -0
- package/dist/engineering-pa-core/src/session-boot/autonomy-injection.mjs +84 -0
- package/dist/engineering-pa-core/src/slice/cli.mjs +139 -0
- package/dist/engineering-pa-core/src/slice/inputs.mjs +174 -0
- package/dist/engineering-pa-core/src/slice/prompt.mjs +94 -0
- package/dist/engineering-pa-core/src/slice/prompts/dave-boot.md +91 -0
- package/dist/engineering-pa-core/src/slice/run.mjs +334 -0
- package/dist/engineering-pa-core-extensions-atlassian/README.md +31 -0
- package/dist/engineering-pa-core-extensions-atlassian/index.mjs +9 -0
- package/dist/engineering-pa-core-extensions-atlassian/package.json +19 -0
- package/dist/engineering-pa-core-extensions-n8n/README.md +29 -0
- package/dist/engineering-pa-core-extensions-n8n/index.mjs +8 -0
- package/dist/engineering-pa-core-extensions-n8n/package.json +19 -0
- package/dist/engineering-pa-librarian/README.md +38 -0
- package/dist/engineering-pa-librarian/index.mjs +12 -0
- package/dist/engineering-pa-librarian/package.json +19 -0
- package/dist/engineering-pa-migrate-inspect/README.md +131 -0
- package/dist/engineering-pa-migrate-inspect/bin/migrate-inspect +2 -0
- package/dist/engineering-pa-migrate-inspect/package.json +21 -0
- package/dist/engineering-pa-migrate-inspect/src/age-key-migrate.mjs +83 -0
- package/dist/engineering-pa-migrate-inspect/src/cli.mjs +97 -0
- package/dist/engineering-pa-migrate-inspect/src/inspector.mjs +237 -0
- package/dist/engineering-pa-migrate-inspect/src/platforms/linux.mjs +87 -0
- package/dist/engineering-pa-migrate-inspect/src/platforms/macos.mjs +105 -0
- package/dist/engineering-pa-migrate-inspect/src/runner.mjs +87 -0
- package/dist/engineering-pa-runbook/README.md +103 -0
- package/dist/engineering-pa-runbook/package.json +18 -0
- package/dist/engineering-pa-runbook/reference/dex-template.runbook.json +66 -0
- package/dist/engineering-pa-runbook/reference/hq-dave.runbook.json +69 -0
- package/dist/engineering-pa-runbook/reference/smarthome01.runbook.json +92 -0
- package/dist/engineering-pa-runbook/schema/inspection.schema.json +113 -0
- package/dist/engineering-pa-runbook/schema/runbook.schema.json +67 -0
- package/dist/engineering-pa-runbook/src/validator.mjs +167 -0
- package/dist/engineering-pa-scaffold/README.md +164 -0
- package/dist/engineering-pa-scaffold/bin/pa-init +2 -0
- package/dist/engineering-pa-scaffold/index.mjs +42 -0
- package/dist/engineering-pa-scaffold/package.json +22 -0
- package/dist/engineering-pa-scaffold/src/cli.mjs +115 -0
- package/dist/engineering-pa-scaffold/src/commands/init.mjs +169 -0
- package/dist/engineering-pa-scaffold/src/generator.mjs +354 -0
- package/dist/engineering-pa-scaffold/src/prompts.mjs +125 -0
- package/dist/engineering-pa-scaffold/src/runner.mjs +142 -0
- package/dist/engineering-pa-scaffold/templates/CLAUDE.md.template +196 -0
- package/dist/engineering-pa-scaffold/templates/operator-README.md.template +44 -0
- package/dist/engineering-pa-types-engineering/README.md +35 -0
- package/dist/engineering-pa-types-engineering/docs/commands/product-build.md +120 -0
- package/dist/engineering-pa-types-engineering/index.mjs +9 -0
- package/dist/engineering-pa-types-engineering/package.json +19 -0
- package/dist/engineering-pa-types-engineering/roles/product-engineer/definition.md +650 -0
- package/dist/engineering-pa-types-engineering/roles/product-engineer/templates/build-spec.md +82 -0
- package/dist/engineering-pa-types-engineering/roles/product-engineer/templates/preflight-checklist.md +158 -0
- package/dist/engineering-pa-types-engineering/roles/product-engineer/templates/worker-brief.md +130 -0
- package/dist/engineering-pa-workspace/README.md +197 -0
- package/dist/engineering-pa-workspace/app/assets/md.js +187 -0
- package/dist/engineering-pa-workspace/app/assets/modal.js +120 -0
- package/dist/engineering-pa-workspace/app/assets/theme.css +132 -0
- package/dist/engineering-pa-workspace/app/assets/theme.js +159 -0
- package/dist/engineering-pa-workspace/app/banner.js +320 -0
- package/dist/engineering-pa-workspace/app/delegations.html +345 -0
- package/dist/engineering-pa-workspace/app/home.html +1060 -0
- package/dist/engineering-pa-workspace/app/index.html +361 -0
- package/dist/engineering-pa-workspace/app/knowledge-map.html +163 -0
- package/dist/engineering-pa-workspace/app/knowledge.html +304 -0
- package/dist/engineering-pa-workspace/app/nav.json +75 -0
- package/dist/engineering-pa-workspace/app/notes.html +199 -0
- package/dist/engineering-pa-workspace/app/onboarding.html +592 -0
- package/dist/engineering-pa-workspace/app/profile.html +243 -0
- package/dist/engineering-pa-workspace/app/project-detail.html +432 -0
- package/dist/engineering-pa-workspace/app/projects.html +297 -0
- package/dist/engineering-pa-workspace/app/repos.html +598 -0
- package/dist/engineering-pa-workspace/app/schedules.html +486 -0
- package/dist/engineering-pa-workspace/app/secrets.html +164 -0
- package/dist/engineering-pa-workspace/app/staging.html +319 -0
- package/dist/engineering-pa-workspace/app/tasks.html +465 -0
- package/dist/engineering-pa-workspace/app/vendor/chart.umd.min.js +21 -0
- package/dist/engineering-pa-workspace/app/viewer.html +116 -0
- package/dist/engineering-pa-workspace/docs/DAEMON.md +104 -0
- package/dist/engineering-pa-workspace/docs/page-blueprint.md +286 -0
- package/dist/engineering-pa-workspace/docs/status-mon.md +168 -0
- package/dist/engineering-pa-workspace/index.mjs +12 -0
- package/dist/engineering-pa-workspace/nav.json.example +8 -0
- package/dist/engineering-pa-workspace/package.json +27 -0
- package/dist/engineering-pa-workspace/src/api.mjs +2086 -0
- package/dist/engineering-pa-workspace/src/install-daemon.mjs +573 -0
- package/dist/engineering-pa-workspace/src/nav.mjs +190 -0
- package/dist/engineering-pa-workspace/src/secrets-api.mjs +175 -0
- package/dist/engineering-pa-workspace/src/server.mjs +516 -0
- package/dist/engineering-pa-workspace/src/status-mon/poll.mjs +220 -0
- package/dist/engineering-pa-workspace/src/status-mon/poller.mjs +113 -0
- package/dist/engineering-pa-workspace/src/status-mon/run.mjs +133 -0
- package/dist/engineering-pa-workspace/src/supervisor.mjs +264 -0
- package/dist/engineering-pa-workspace/src/ws.mjs +633 -0
- package/index.mjs +60 -0
- package/package.json +58 -0
- package/templates/banned-ai-tells.md +102 -0
- package/templates/purpose.md +105 -0
- package/templates/voice-guide.md +98 -0
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
// scheduler — CLI wrapper.
|
|
2
|
+
//
|
|
3
|
+
// Routes verbs to the library functions in @stravica-ai/engineering-pa-core's
|
|
4
|
+
// scheduler module. Each verb gets a thin wrapper that translates argv into
|
|
5
|
+
// the library call shape.
|
|
6
|
+
//
|
|
7
|
+
// Exit codes (§6 of the tooling standard):
|
|
8
|
+
// 0 ok | 1 usage | 2 logical/operational failure | 3 transport/io
|
|
9
|
+
|
|
10
|
+
import { parseArgs } from 'node:util';
|
|
11
|
+
import { readFile } from 'node:fs/promises';
|
|
12
|
+
import { resolve } from 'node:path';
|
|
13
|
+
// Cross-package import via package-name + subpath. core declares
|
|
14
|
+
// `./src/*` in its `exports` map; cli declares core as a workspace
|
|
15
|
+
// dependency. The publish-time bundler (packages/engineering-pa/build.mjs)
|
|
16
|
+
// rewrites this string to a relative path inside the single-bundled
|
|
17
|
+
// tarball.
|
|
18
|
+
import { detect, install, list, remove, verify } from '../../../engineering-pa-core/src/scheduler/index.mjs';
|
|
19
|
+
|
|
20
|
+
const USAGE = `scheduler — operator-facing scheduling primitives
|
|
21
|
+
|
|
22
|
+
Usage:
|
|
23
|
+
scheduler detect Detect available scheduling backends
|
|
24
|
+
scheduler install <schedule.json> [--force-host]
|
|
25
|
+
Install a schedule onto its declared backend
|
|
26
|
+
scheduler list [--backend <name>] [--agent <name>]
|
|
27
|
+
List installed schedules
|
|
28
|
+
scheduler remove <id> [--backend N] [--agent <name>] [--delete-file]
|
|
29
|
+
Remove an installed schedule
|
|
30
|
+
scheduler verify <schedule.json> Verify a schedule fires correctly
|
|
31
|
+
scheduler -h, --help Show this help
|
|
32
|
+
|
|
33
|
+
Schedule files live at <repoRoot>/schedules/<id>.json. The canonical
|
|
34
|
+
schema is bundled at @stravica-ai/engineering-pa-core/schema/schedule.json.
|
|
35
|
+
|
|
36
|
+
--agent (launchd only) targets a specific agent's label prefix
|
|
37
|
+
(com.<agent>.<id>). A schedule file's own top-level "agent" field drives
|
|
38
|
+
install; list/remove take it explicitly since they don't read a schedule
|
|
39
|
+
file. Defaults to "dave" everywhere — omitting it is unchanged behaviour.
|
|
40
|
+
|
|
41
|
+
Host enforcement (install): a schedule's "host" field is matched against
|
|
42
|
+
this machine's hostname (exact, then short-name-before-first-dot). A
|
|
43
|
+
mismatch refuses install (exit 2) unless --force-host is passed (still
|
|
44
|
+
warns). A missing "host" field is a warning-only pass, never a refusal.
|
|
45
|
+
Name-collision check (install): refuses (exit 2) when another .json file
|
|
46
|
+
in the same schedules directory declares the same "id".
|
|
47
|
+
|
|
48
|
+
Exit codes:
|
|
49
|
+
0 ok
|
|
50
|
+
1 usage error
|
|
51
|
+
2 operational failure
|
|
52
|
+
3 transport/io failure
|
|
53
|
+
`;
|
|
54
|
+
|
|
55
|
+
export async function run(argv) {
|
|
56
|
+
if (argv.length === 0 || argv[0] === '-h' || argv[0] === '--help') {
|
|
57
|
+
process.stdout.write(USAGE);
|
|
58
|
+
return 0;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const verb = argv[0];
|
|
62
|
+
const rest = argv.slice(1);
|
|
63
|
+
|
|
64
|
+
try {
|
|
65
|
+
switch (verb) {
|
|
66
|
+
case 'detect': {
|
|
67
|
+
const report = await detect();
|
|
68
|
+
process.stdout.write(JSON.stringify(report, null, 2) + '\n');
|
|
69
|
+
return 0;
|
|
70
|
+
}
|
|
71
|
+
case 'install': {
|
|
72
|
+
const { values, positionals } = parseArgs({
|
|
73
|
+
args: rest,
|
|
74
|
+
options: {
|
|
75
|
+
backend: { type: 'string' },
|
|
76
|
+
'force-host': { type: 'boolean', default: false },
|
|
77
|
+
},
|
|
78
|
+
strict: true,
|
|
79
|
+
allowPositionals: true,
|
|
80
|
+
});
|
|
81
|
+
if (positionals.length === 0) {
|
|
82
|
+
process.stderr.write('[scheduler] install: expected a schedule.json path\n');
|
|
83
|
+
return 1;
|
|
84
|
+
}
|
|
85
|
+
// BUG 1 fix: core's install() reads the schedule file itself
|
|
86
|
+
// (readFileSync(path)) — it always expected a PATH, never a parsed
|
|
87
|
+
// object. This CLI used to JSON.parse the file and hand install()
|
|
88
|
+
// the resulting object, which readFileSync then choked on with
|
|
89
|
+
// ERR_INVALID_ARG_TYPE on every invocation. We still parse the file
|
|
90
|
+
// here, but only to read schedule.backend as the --backend fallback
|
|
91
|
+
// (core has no other way to know the CLI's flag-vs-file precedence
|
|
92
|
+
// rule); the value actually passed to install() is the path.
|
|
93
|
+
const schedulePath = resolve(positionals[0]);
|
|
94
|
+
const schedule = JSON.parse(await readFile(schedulePath, 'utf8'));
|
|
95
|
+
const result = await install(schedulePath, values.backend || schedule.backend, {
|
|
96
|
+
forceHost: values['force-host'],
|
|
97
|
+
});
|
|
98
|
+
process.stdout.write(JSON.stringify(result, null, 2) + '\n');
|
|
99
|
+
// BUG 2 fix: install() used to fall off the end of the function
|
|
100
|
+
// with no return value, so `result.ok` was `undefined.ok` — a
|
|
101
|
+
// TypeError swallowed by the outer catch, always reporting exit 2
|
|
102
|
+
// even on a successful install. install() now always returns an
|
|
103
|
+
// { ok, ... } object.
|
|
104
|
+
return result.ok ? 0 : 2;
|
|
105
|
+
}
|
|
106
|
+
case 'list': {
|
|
107
|
+
const { values } = parseArgs({
|
|
108
|
+
args: rest,
|
|
109
|
+
options: { backend: { type: 'string' }, agent: { type: 'string' } },
|
|
110
|
+
strict: true,
|
|
111
|
+
});
|
|
112
|
+
// Auto-detect the backend if --backend is not supplied. Falling
|
|
113
|
+
// through to list(undefined) used to throw "Unknown backend: undefined"
|
|
114
|
+
// and the catch-block translated that to a confusing exit-2 with a
|
|
115
|
+
// stack-trace-shaped error. Now: try detect() first; if there is no
|
|
116
|
+
// available backend, surface a clean exit-2 with a remediation message.
|
|
117
|
+
let backend = values.backend;
|
|
118
|
+
if (!backend) {
|
|
119
|
+
const info = await detect();
|
|
120
|
+
const pick = (info.available || []).find((b) => b.recommended)
|
|
121
|
+
|| (info.available || [])[0];
|
|
122
|
+
if (pick) {
|
|
123
|
+
backend = pick.backend;
|
|
124
|
+
} else {
|
|
125
|
+
process.stderr.write('[scheduler] list: no scheduling backend detected on this host. Pass --backend cron|launchd|systemd|schtasks to force one.\n');
|
|
126
|
+
return 2;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
// Signature-audit fix (sibling of BUG 1/2): core's list() returns
|
|
130
|
+
// an already-formatted human-readable STRING (see
|
|
131
|
+
// packages/workspace/src/api.mjs, which does
|
|
132
|
+
// `String(scheduler.list(backend)).trim()`), not JSON data. This
|
|
133
|
+
// CLI used to JSON.stringify() that string, printing a single
|
|
134
|
+
// quoted, escaped-newline blob instead of the listing. Write it
|
|
135
|
+
// through as-is.
|
|
136
|
+
const items = await list(backend, { agent: values.agent });
|
|
137
|
+
process.stdout.write(items + '\n');
|
|
138
|
+
return 0;
|
|
139
|
+
}
|
|
140
|
+
case 'remove': {
|
|
141
|
+
const { values, positionals } = parseArgs({
|
|
142
|
+
args: rest,
|
|
143
|
+
options: {
|
|
144
|
+
backend: { type: 'string' },
|
|
145
|
+
agent: { type: 'string' },
|
|
146
|
+
'delete-file': { type: 'boolean', default: false },
|
|
147
|
+
},
|
|
148
|
+
strict: true,
|
|
149
|
+
allowPositionals: true,
|
|
150
|
+
});
|
|
151
|
+
if (positionals.length === 0) {
|
|
152
|
+
process.stderr.write('[scheduler] remove: expected a schedule id\n');
|
|
153
|
+
return 1;
|
|
154
|
+
}
|
|
155
|
+
const result = await remove(positionals[0], values.backend, {
|
|
156
|
+
deleteFile: values['delete-file'],
|
|
157
|
+
agent: values.agent,
|
|
158
|
+
});
|
|
159
|
+
process.stdout.write(JSON.stringify(result, null, 2) + '\n');
|
|
160
|
+
// Same BUG-2-class fix as install(): remove() now always returns
|
|
161
|
+
// { ok, ... } instead of undefined.
|
|
162
|
+
return result.ok ? 0 : 2;
|
|
163
|
+
}
|
|
164
|
+
case 'verify': {
|
|
165
|
+
const { values, positionals } = parseArgs({
|
|
166
|
+
args: rest,
|
|
167
|
+
options: { backend: { type: 'string' }, timeout: { type: 'string' } },
|
|
168
|
+
strict: true,
|
|
169
|
+
allowPositionals: true,
|
|
170
|
+
});
|
|
171
|
+
if (positionals.length === 0) {
|
|
172
|
+
process.stderr.write('[scheduler] verify: expected a schedule.json path\n');
|
|
173
|
+
return 1;
|
|
174
|
+
}
|
|
175
|
+
// Same BUG 1 class as install(): core's verify() also reads the
|
|
176
|
+
// schedule file itself and expects a PATH. Its own backend
|
|
177
|
+
// fallback (opts.backend || sched.backend || detectDefaultBackend())
|
|
178
|
+
// already lives inside verify(), so — unlike install() — nothing
|
|
179
|
+
// needs pre-parsing here at all.
|
|
180
|
+
const schedulePath = resolve(positionals[0]);
|
|
181
|
+
const opts = {};
|
|
182
|
+
if (values.backend) opts.backend = values.backend;
|
|
183
|
+
if (values.timeout) opts.timeoutMs = parseInt(values.timeout, 10);
|
|
184
|
+
const result = await verify(schedulePath, opts);
|
|
185
|
+
process.stdout.write(JSON.stringify(result, null, 2) + '\n');
|
|
186
|
+
return result.exitCode ?? (result.ok ? 0 : 2);
|
|
187
|
+
}
|
|
188
|
+
default:
|
|
189
|
+
process.stderr.write(`[scheduler] unknown verb: ${verb}\n`);
|
|
190
|
+
process.stderr.write(USAGE);
|
|
191
|
+
return 1;
|
|
192
|
+
}
|
|
193
|
+
} catch (err) {
|
|
194
|
+
process.stderr.write(`[scheduler] ${err.message || err}\n`);
|
|
195
|
+
return err.exitCode ?? 2;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
// secrets — SOPS+age secrets CLI. Verb routing + exit-code translation.
|
|
2
|
+
//
|
|
3
|
+
// Exit code contract (@stravica-ai/engineering-pa-core/docs/standards/tooling.md §6):
|
|
4
|
+
// 0 OK
|
|
5
|
+
// 1 usage / not-found / uninitialised vault
|
|
6
|
+
// 2 operational failure (sops decrypt/encrypt failed, cannot parse)
|
|
7
|
+
// 3 transport failure (spawn failed, binary missing and unreachable)
|
|
8
|
+
//
|
|
9
|
+
import { runInit } from './commands/init.mjs';
|
|
10
|
+
import { runGet } from './commands/get.mjs';
|
|
11
|
+
import { runSet } from './commands/set.mjs';
|
|
12
|
+
import { runList } from './commands/list.mjs';
|
|
13
|
+
import { runDelete } from './commands/delete.mjs';
|
|
14
|
+
import { runRotate } from './commands/rotate.mjs';
|
|
15
|
+
import { runStatus } from './commands/status.mjs';
|
|
16
|
+
import { runBackup } from './commands/backup.mjs';
|
|
17
|
+
import { runVaults } from './commands/vaults.mjs';
|
|
18
|
+
import { runFind } from './commands/find.mjs';
|
|
19
|
+
import { runVerify } from './commands/verify.mjs';
|
|
20
|
+
import { resolveScopePaths, VaultError } from './vault.mjs';
|
|
21
|
+
|
|
22
|
+
const HELP = `secrets — SOPS+age secrets management
|
|
23
|
+
|
|
24
|
+
Usage:
|
|
25
|
+
secrets init Initialise the vault (generate keys, create empty secrets file)
|
|
26
|
+
secrets get KEY Retrieve a secret
|
|
27
|
+
secrets set KEY VALUE Store or update a secret (use \`-\` as VALUE to read from stdin)
|
|
28
|
+
--canonical-clone PATH: write the canonical
|
|
29
|
+
clone's vault file FIRST, then this workspace
|
|
30
|
+
copy. If the canonical write fails, aborts
|
|
31
|
+
before touching the workspace copy at all.
|
|
32
|
+
secrets list List all secret key names (not values)
|
|
33
|
+
secrets delete KEY Remove a secret
|
|
34
|
+
secrets rotate Re-encrypt with current recipients
|
|
35
|
+
secrets status Check vault health and report key info
|
|
36
|
+
secrets backup Show QR code of master key for backup (opens browser)
|
|
37
|
+
secrets vaults List every vault visible from the workspace
|
|
38
|
+
(scope, layout, key count, path — no decryption)
|
|
39
|
+
secrets find KEY Report which vault(s) contain KEY by name
|
|
40
|
+
(alias: which; names/paths only, never values;
|
|
41
|
+
exits 1 when not found)
|
|
42
|
+
secrets verify --values --against <path>
|
|
43
|
+
Byte-level comparison of DECRYPTED values
|
|
44
|
+
between this vault and a reference vault at
|
|
45
|
+
<path>. Reports per-key same/differs/missing.
|
|
46
|
+
--values is required — decrypt-only is never
|
|
47
|
+
a valid pass (see below). Never prints values.
|
|
48
|
+
|
|
49
|
+
Private key: ~/.config/stravica-pa/age-key.txt (never committed)
|
|
50
|
+
Public key: .vault/recipients.txt (committed to repo)
|
|
51
|
+
Encrypted store: .vault/secrets.enc.json (committed to repo)
|
|
52
|
+
SOPS config: .vault/.sops.yaml (committed to repo)
|
|
53
|
+
|
|
54
|
+
Workspace resolution (all verbs), in order:
|
|
55
|
+
1. Walk up from the current directory for a vault-bearing workspace
|
|
56
|
+
(.vault/ or vault/*.enc.json). A git repo WITHOUT a vault does not
|
|
57
|
+
stop resolution — it falls through to the next tiers with a stderr
|
|
58
|
+
notice naming the store actually targeted.
|
|
59
|
+
2. $DAVE_ROOT (or $DAVE_REPO_ROOT / $PA_REPO_ROOT).
|
|
60
|
+
3. "workspaceRoot" in ~/.config/stravica-pa/defaults.json (written by
|
|
61
|
+
you or provisioning; the CLI never creates it).
|
|
62
|
+
4. Error listing what was tried.
|
|
63
|
+
NOTE: this precedence is a change — the environment variable used to be
|
|
64
|
+
checked BEFORE the directory walk. Standing inside a workspace now
|
|
65
|
+
always targets THAT workspace, even when $DAVE_ROOT points elsewhere.
|
|
66
|
+
Exception: 'secrets init' inside a vault-less git repo initialises the
|
|
67
|
+
vault there (it creates; the repo you are standing in is its target).
|
|
68
|
+
|
|
69
|
+
Scoped vaults:
|
|
70
|
+
--scope NAME Target the scoped vault "NAME" instead of the
|
|
71
|
+
legacy single-file vault. Resolved by walking
|
|
72
|
+
up to the repo root and checking, in order:
|
|
73
|
+
.vault/scopes/NAME.enc.json (workspace layout)
|
|
74
|
+
vault/NAME.enc.json (canonical layout)
|
|
75
|
+
Both present is an error — pass --file to
|
|
76
|
+
disambiguate. Supported on: get, set, list,
|
|
77
|
+
delete, rotate, status, vaults, find.
|
|
78
|
+
--file PATH Bypass scope resolution and operate directly on
|
|
79
|
+
the encrypted file at PATH (its .sops.yaml is
|
|
80
|
+
expected alongside it, same directory). Escape
|
|
81
|
+
hatch for canonical-layout clones. Supported on
|
|
82
|
+
the same verbs as --scope.
|
|
83
|
+
|
|
84
|
+
Flags may appear anywhere in argv, e.g.:
|
|
85
|
+
secrets --scope wsd get API_KEY
|
|
86
|
+
secrets get API_KEY --scope wsd
|
|
87
|
+
secrets --file ./vault/api.enc.json set KEY -
|
|
88
|
+
|
|
89
|
+
With neither flag, get/set resolve a DEFAULT scope (see below) before
|
|
90
|
+
falling back to the legacy .vault/ vault. Other verbs (list, delete,
|
|
91
|
+
rotate, status, vaults, find, verify) are unaffected by this and always
|
|
92
|
+
use the legacy vault unless --scope/--file is given explicitly.
|
|
93
|
+
|
|
94
|
+
Default scope (get/set only, when neither --scope nor --file is given):
|
|
95
|
+
A one-line stderr notice always fires BEFORE the operation runs, naming
|
|
96
|
+
the vault actually targeted and where that default came from. Resolved
|
|
97
|
+
in order:
|
|
98
|
+
1. $SECRETS_DEFAULT_SCOPE env var.
|
|
99
|
+
2. "defaultScope" in ~/.config/stravica-pa/defaults.json (same file
|
|
100
|
+
as "workspaceRoot" above; written by you or provisioning — the
|
|
101
|
+
CLI never creates it).
|
|
102
|
+
3. The calling PA's own agents/<name>/identity.json "defaultScope"
|
|
103
|
+
field, found by walking up from the current directory (or, for
|
|
104
|
+
Dave specifically, $DAVE_ROOT/agents/dave/identity.json). A
|
|
105
|
+
malformed identity.json is a stderr warning, not a crash.
|
|
106
|
+
4. Legacy fallback: the single-file .vault/ vault — the notice says
|
|
107
|
+
so explicitly ("no scope specified and no default configured").
|
|
108
|
+
Example notices:
|
|
109
|
+
note: writing to 'hq-estate' pot (default from SECRETS_DEFAULT_SCOPE env var)
|
|
110
|
+
note: writing to 'wsd' pot (default from /home/dex/agents/barry/dex/identity.json)
|
|
111
|
+
note: writing to '(legacy)' pot — no scope specified and no default configured. Use --scope <name> or configure a default.
|
|
112
|
+
|
|
113
|
+
Canonical-first writes (set only):
|
|
114
|
+
--canonical-clone PATH Write PATH (a canonical clone's encrypted
|
|
115
|
+
vault file) FIRST, then the resolved
|
|
116
|
+
workspace copy SECOND. If the canonical
|
|
117
|
+
write fails for any reason (existing store
|
|
118
|
+
undecryptable, sops encrypt failure), the
|
|
119
|
+
command aborts with exit 2 BEFORE touching
|
|
120
|
+
the workspace copy at all — a device-side
|
|
121
|
+
vault mutation never round-trips only
|
|
122
|
+
halfway. Without this flag, 'set' behaves
|
|
123
|
+
exactly as before (workspace copy only).
|
|
124
|
+
|
|
125
|
+
Value-compare verification:
|
|
126
|
+
secrets verify --values --against <path>
|
|
127
|
+
Decrypts BOTH the resolved vault and the vault at <path>, then
|
|
128
|
+
compares every key's VALUE byte-for-byte — never decrypt-only. A
|
|
129
|
+
vault clobbered by an always-take-canonical materialisation still
|
|
130
|
+
decrypts cleanly with placeholder/stale values (same recipients);
|
|
131
|
+
only a value comparison against a known-good reference catches that.
|
|
132
|
+
Reports per-key same/differs/missing-in-working/missing-in-reference.
|
|
133
|
+
Exits 2 if any key differs or is missing on either side. Never
|
|
134
|
+
prints a decrypted value — only the comparison result per key name.
|
|
135
|
+
`;
|
|
136
|
+
|
|
137
|
+
const USAGE = `Usage: secrets {init|get|set|list|delete|rotate|status|backup|vaults|find|verify} [KEY] [VALUE]
|
|
138
|
+
|
|
139
|
+
Commands:
|
|
140
|
+
init Initialise the vault (generate keys, create config)
|
|
141
|
+
get KEY Retrieve a secret value
|
|
142
|
+
set KEY VALUE Store or update a secret (--canonical-clone PATH: write canonical first)
|
|
143
|
+
list List all secret key names
|
|
144
|
+
delete KEY Remove a secret
|
|
145
|
+
rotate Re-encrypt with current recipients
|
|
146
|
+
status Check vault health
|
|
147
|
+
backup Show QR code of master key for backup
|
|
148
|
+
vaults List every visible vault (scope, layout, key count, path)
|
|
149
|
+
find KEY Report which vault(s) contain KEY (alias: which)
|
|
150
|
+
verify --values --against <path>
|
|
151
|
+
Byte-level DECRYPTED-value comparison against another vault
|
|
152
|
+
|
|
153
|
+
Scoped vault flags (get/set/list/delete/rotate/status/vaults/find/verify only):
|
|
154
|
+
--scope NAME Target .vault/scopes/NAME.enc.json or vault/NAME.enc.json
|
|
155
|
+
--file PATH Bypass resolution, operate directly on PATH
|
|
156
|
+
|
|
157
|
+
Run 'secrets --help' for full documentation.
|
|
158
|
+
`;
|
|
159
|
+
|
|
160
|
+
// Commands that know how to operate against a scoped `paths` object.
|
|
161
|
+
// init/backup deal in vault-wide key material, not a single scope's
|
|
162
|
+
// secrets file, so --scope/--file are rejected rather than silently
|
|
163
|
+
// ignored (silently targeting the wrong vault would be worse than an
|
|
164
|
+
// explicit "not supported").
|
|
165
|
+
const SCOPE_AWARE_COMMANDS = new Set(['get', 'set', 'list', 'delete', 'rotate', 'status', 'vaults', 'find', 'which', 'verify']);
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Pull --scope/--file (and --scope=X/--file=X) out of argv wherever they
|
|
169
|
+
* appear, leaving the verb + positional args in original relative order.
|
|
170
|
+
*/
|
|
171
|
+
function extractGlobalFlags(argv) {
|
|
172
|
+
const rest = [];
|
|
173
|
+
let scope;
|
|
174
|
+
let file;
|
|
175
|
+
for (let i = 0; i < argv.length; i += 1) {
|
|
176
|
+
const a = argv[i];
|
|
177
|
+
if (a === '--scope') { scope = argv[i + 1]; i += 1; continue; }
|
|
178
|
+
if (a.startsWith('--scope=')) { scope = a.slice('--scope='.length); continue; }
|
|
179
|
+
if (a === '--file') { file = argv[i + 1]; i += 1; continue; }
|
|
180
|
+
if (a.startsWith('--file=')) { file = a.slice('--file='.length); continue; }
|
|
181
|
+
rest.push(a);
|
|
182
|
+
}
|
|
183
|
+
return { scope, file, rest };
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
export async function run(argv, overrides = {}) {
|
|
187
|
+
const { scope, file, rest } = extractGlobalFlags(argv);
|
|
188
|
+
const cmd = rest[0];
|
|
189
|
+
const cmdArgs = rest.slice(1);
|
|
190
|
+
|
|
191
|
+
if (cmd === '--help' || cmd === '-h' || cmd === 'help') {
|
|
192
|
+
process.stdout.write(HELP);
|
|
193
|
+
return 0;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// Validated up front regardless of overrides.paths — overrides.paths is
|
|
197
|
+
// a test-only injection point, not a legitimate way to make an
|
|
198
|
+
// unsupported verb accept --scope/--file in real usage.
|
|
199
|
+
if ((scope || file) && !SCOPE_AWARE_COMMANDS.has(cmd)) {
|
|
200
|
+
console.error(`Error: --scope/--file are not supported for '${cmd || '(no command)'}'. Supported: ${[...SCOPE_AWARE_COMMANDS].join(', ')}.`);
|
|
201
|
+
throw new VaultError('scope not supported for command', 1);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
let paths = overrides.paths;
|
|
205
|
+
if (!paths && (scope || file)) {
|
|
206
|
+
paths = resolveScopePaths({
|
|
207
|
+
scope,
|
|
208
|
+
file,
|
|
209
|
+
cwd: overrides.cwd,
|
|
210
|
+
env: overrides.env,
|
|
211
|
+
home: overrides.home,
|
|
212
|
+
keyDir: overrides.keyDir,
|
|
213
|
+
keyFile: overrides.keyFile,
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
const cmdOverrides = paths ? { ...overrides, paths } : overrides;
|
|
217
|
+
|
|
218
|
+
switch (cmd) {
|
|
219
|
+
case 'init': return runInit(cmdOverrides);
|
|
220
|
+
case 'get': return runGet(cmdArgs, cmdOverrides);
|
|
221
|
+
case 'set': return runSet(cmdArgs, cmdOverrides);
|
|
222
|
+
case 'list': return runList(cmdOverrides);
|
|
223
|
+
case 'delete': return runDelete(cmdArgs, cmdOverrides);
|
|
224
|
+
case 'rotate': return runRotate(cmdOverrides);
|
|
225
|
+
case 'status': return runStatus(cmdOverrides);
|
|
226
|
+
case 'vaults': return runVaults(cmdOverrides);
|
|
227
|
+
case 'find':
|
|
228
|
+
case 'which': return runFind(cmdArgs, cmdOverrides);
|
|
229
|
+
case 'verify': return runVerify(cmdArgs, cmdOverrides);
|
|
230
|
+
case 'backup': {
|
|
231
|
+
await runBackup(cmdOverrides);
|
|
232
|
+
return 0;
|
|
233
|
+
}
|
|
234
|
+
default: {
|
|
235
|
+
process.stdout.write(USAGE);
|
|
236
|
+
return 1;
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
export async function main(argv, overrides = {}) {
|
|
242
|
+
try {
|
|
243
|
+
const code = await run(argv, overrides);
|
|
244
|
+
process.exit(code ?? 0);
|
|
245
|
+
} catch (err) {
|
|
246
|
+
const code = err && typeof err.exitCode === 'number' ? err.exitCode : 2;
|
|
247
|
+
// Error messages for expected failures are already printed by the
|
|
248
|
+
// command modules. For unexpected errors we surface .message here.
|
|
249
|
+
if (!err || err.exitCode == null) {
|
|
250
|
+
console.error(`secrets: ${err && err.message ? err.message : err}`);
|
|
251
|
+
}
|
|
252
|
+
process.exit(code);
|
|
253
|
+
}
|
|
254
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
// backup-template.mjs — HTML template + embedded QR generator for the
|
|
2
|
+
// backup command. Split out so backup.mjs stays readable and so tests
|
|
3
|
+
// can assert on the structural pieces without grepping the generator.
|
|
4
|
+
//
|
|
5
|
+
// The QR generator is the same alphanumeric-mode implementation used in
|
|
6
|
+
// core/tools/workspace/app/onboarding.html — kept in sync to preserve
|
|
7
|
+
// identical rendering.
|
|
8
|
+
|
|
9
|
+
export const HTML_HEAD = `<!DOCTYPE html>
|
|
10
|
+
<html lang="en">
|
|
11
|
+
<head>
|
|
12
|
+
<meta charset="UTF-8">
|
|
13
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
14
|
+
<title>Dave — Master Key Backup</title>
|
|
15
|
+
<style>
|
|
16
|
+
:root { --bg: #121218; --bg-surface: #1a1a24; --border: #333346; --text: #d4d4d8; --text-dim: #a1a1aa; --text-bright: #f4f4f5; --accent: #6366f1; --red: #ef4444; --yellow: #f59e0b; }
|
|
17
|
+
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
18
|
+
body { font-family: 'SF Mono', 'JetBrains Mono', 'Fira Code', monospace; background: var(--bg); color: var(--text); display: flex; align-items: center; justify-content: center; min-height: 100vh; padding: 2rem; }
|
|
19
|
+
.container { max-width: 540px; width: 100%; }
|
|
20
|
+
h1 { color: var(--text-bright); font-size: 1.4rem; margin-bottom: 0.5rem; text-align: center; }
|
|
21
|
+
.subtitle { color: var(--text-dim); font-size: 0.8rem; text-align: center; margin-bottom: 2rem; }
|
|
22
|
+
.qr-box { background: var(--bg-surface); border: 1px solid var(--border); border-radius: 12px; padding: 2rem; text-align: center; margin-bottom: 1.5rem; }
|
|
23
|
+
canvas { margin: 0 auto; display: block; }
|
|
24
|
+
.warning { background: rgba(239,68,68,0.1); border: 1px solid rgba(239,68,68,0.3); border-radius: 8px; padding: 1rem; margin: 1.5rem 0; color: var(--red); font-size: 0.8rem; line-height: 1.6; text-align: center; }
|
|
25
|
+
.key-section { margin-top: 1.5rem; }
|
|
26
|
+
.key-label { color: var(--text-dim); font-size: 0.65rem; text-transform: uppercase; letter-spacing: 0.08em; margin-bottom: 0.3rem; }
|
|
27
|
+
.key-value { background: var(--bg-surface); border: 1px solid var(--border); border-radius: 6px; padding: 0.75rem; font-size: 0.7rem; word-break: break-all; color: var(--text-bright); user-select: all; cursor: text; }
|
|
28
|
+
.key-value.public { color: var(--accent); }
|
|
29
|
+
.note { color: var(--text-dim); font-size: 0.7rem; text-align: center; margin-top: 1.5rem; line-height: 1.6; }
|
|
30
|
+
</style>
|
|
31
|
+
</head>
|
|
32
|
+
<body>
|
|
33
|
+
<div class="container">
|
|
34
|
+
<h1>Master Key Backup</h1>
|
|
35
|
+
<p class="subtitle">Scan this QR code with your password manager or phone</p>
|
|
36
|
+
<div class="qr-box"><canvas id="qr-canvas"></canvas></div>
|
|
37
|
+
<div class="warning">This is your master encryption key. If you lose it, your encrypted secrets cannot be recovered. Store it somewhere safe and offline.</div>
|
|
38
|
+
<div class="key-section">
|
|
39
|
+
<div class="key-label">Secret Key (click to select)</div>
|
|
40
|
+
<div class="key-value" id="secret-key"></div>
|
|
41
|
+
</div>
|
|
42
|
+
<div class="key-section" id="pub-section" style="display:none">
|
|
43
|
+
<div class="key-label">Public Key (for reference)</div>
|
|
44
|
+
<div class="key-value public" id="public-key"></div>
|
|
45
|
+
</div>
|
|
46
|
+
<p class="note">This page is a temporary file and will not persist.<br>Close this tab when you have backed up your key.</p>
|
|
47
|
+
</div>
|
|
48
|
+
<script>
|
|
49
|
+
`;
|
|
50
|
+
|
|
51
|
+
// Shared with onboarding.html — hand-rolled alphanumeric QR generator.
|
|
52
|
+
export const QR_JS = `var QR=(function(){var EXP=new Array(256),LOG=new Array(256);(function(){var x=1;for(var i=0;i<255;i++){EXP[i]=x;LOG[x]=i;x=x*2;if(x>=256)x^=0x11d;}EXP[255]=EXP[0];})();function gfMul(a,b){return a===0||b===0?0:EXP[(LOG[a]+LOG[b])%255];}function rsGenPoly(n){var poly=[1];for(var i=0;i<n;i++){var next=new Array(poly.length+1);next[0]=0;for(var j=0;j<poly.length;j++)next[j+1]=poly[j];var factor=EXP[i];for(var j=0;j<poly.length;j++)next[j]^=gfMul(poly[j],factor);poly=next;}return poly;}function rsEncode(data,ecLen){var gen=rsGenPoly(ecLen);var msg=new Array(data.length+ecLen);for(var i=0;i<data.length;i++)msg[i]=data[i];for(var i=data.length;i<msg.length;i++)msg[i]=0;for(var i=0;i<data.length;i++){var coef=msg[i];if(coef!==0){for(var j=0;j<gen.length;j++)msg[i+j]^=gfMul(gen[j],coef);}}return msg.slice(data.length);}var ALNUM_CAP=[0,25,47,77,114,154,195,224,279,335,395];var EC_CODEWORDS=[0,7,10,15,20,26,18,20,24,30,18];var DATA_CODEWORDS=[0,19,34,55,80,108,136,156,194,232,274];var NUM_BLOCKS=[0,1,1,1,1,1,2,2,2,2,4];var BLOCK_SIZES={7:[[2,68]],8:[[2,97]],9:[[2,116]],10:[[2,68],[2,69]]};var ALNUM_TABLE='0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ $%*+-./:';function getVersion(len){for(var v=1;v<=10;v++){if(ALNUM_CAP[v]>=len)return v;}return-1;}function encodeAlphanumeric(str){var bits=[];function push(val,len){for(var i=len-1;i>=0;i--)bits.push((val>>i)&1);}push(0x2,4);var version=getVersion(str.length);var ccLen=version<=9?9:11;push(str.length,ccLen);for(var i=0;i<str.length-1;i+=2){var val=ALNUM_TABLE.indexOf(str.charAt(i))*45+ALNUM_TABLE.indexOf(str.charAt(i+1));push(val,11);}if(str.length%2===1){push(ALNUM_TABLE.indexOf(str.charAt(str.length-1)),6);}return bits;}function encodeData(str){var version=getVersion(str.length);if(version<0)return null;var bits=encodeAlphanumeric(str);var totalBits=DATA_CODEWORDS[version]*8;var termLen=Math.min(4,totalBits-bits.length);for(var i=0;i<termLen;i++)bits.push(0);while(bits.length%8!==0)bits.push(0);var pads=[0xEC,0x11],pi=0;while(bits.length<totalBits){for(var i=7;i>=0;i--)bits.push((pads[pi]>>i)&1);pi=(pi+1)%2;}var bytes=[];for(var i=0;i<bits.length;i+=8){var b=0;for(var j=0;j<8;j++)b=(b<<1)|bits[i+j];bytes.push(b);}return{version:version,data:bytes};}function interleaveBlocks(version,data){var ecLen=EC_CODEWORDS[version];var blocks=[];var offset=0;if(NUM_BLOCKS[version]<=2&&!BLOCK_SIZES[version]){var n=NUM_BLOCKS[version];var blockSize=Math.floor(data.length/n);for(var i=0;i<n;i++){var sz=i<n-1?blockSize:data.length-offset;blocks.push(data.slice(offset,offset+sz));offset+=sz;}}else if(BLOCK_SIZES[version]){var groups=BLOCK_SIZES[version];for(var g=0;g<groups.length;g++){var count=groups[g][0],sz=groups[g][1];for(var b=0;b<count;b++){blocks.push(data.slice(offset,offset+sz));offset+=sz;}}}else{blocks.push(data);}var ecBlocks=[];for(var i=0;i<blocks.length;i++){ecBlocks.push(rsEncode(blocks[i],ecLen));}var result=[];var maxDataLen=0;for(var i=0;i<blocks.length;i++)if(blocks[i].length>maxDataLen)maxDataLen=blocks[i].length;for(var j=0;j<maxDataLen;j++){for(var i=0;i<blocks.length;i++){if(j<blocks[i].length)result.push(blocks[i][j]);}}for(var j=0;j<ecLen;j++){for(var i=0;i<ecBlocks.length;i++){result.push(ecBlocks[i][j]);}}return result;}function moduleSize(version){return 17+version*4;}function createMatrix(size){var m=[],r=[];for(var i=0;i<size;i++){m.push(new Array(size));r.push(new Array(size));for(var j=0;j<size;j++){m[i][j]=0;r[i][j]=false;}}return{m:m,r:r,size:size};}function setModule(mat,row,col,val){mat.m[row][col]=val?1:0;mat.r[row][col]=true;}function placeFinderPattern(mat,row,col){for(var dr=-1;dr<=7;dr++){for(var dc=-1;dc<=7;dc++){var r=row+dr,c=col+dc;if(r<0||r>=mat.size||c<0||c>=mat.size)continue;var inOuter=dr>=0&&dr<=6&&dc>=0&&dc<=6;var inInner=dr>=2&&dr<=4&&dc>=2&&dc<=4;var onBorder=dr===0||dr===6||dc===0||dc===6;var val=inInner||(inOuter&&onBorder);setModule(mat,r,c,val);}}}function placeAlignmentPattern(mat,row,col){for(var dr=-2;dr<=2;dr++){for(var dc=-2;dc<=2;dc++){var r=row+dr,c=col+dc;if(mat.r[r][c])continue;var val=Math.abs(dr)===2||Math.abs(dc)===2||(dr===0&&dc===0);setModule(mat,r,c,val);}}}var ALIGNMENT_POS=[0,[],[6,18],[6,22],[6,26],[6,30],[6,34],[6,22,38],[6,24,42],[6,26,46],[6,28,52]];function placeFixedPatterns(mat,version){var sz=mat.size;placeFinderPattern(mat,0,0);placeFinderPattern(mat,0,sz-7);placeFinderPattern(mat,sz-7,0);for(var i=8;i<sz-8;i++){if(!mat.r[6][i])setModule(mat,6,i,i%2===0);if(!mat.r[i][6])setModule(mat,i,6,i%2===0);}if(version>=2){var pos=ALIGNMENT_POS[version];for(var i=0;i<pos.length;i++){for(var j=0;j<pos.length;j++){if((i===0&&j===0)||(i===0&&j===pos.length-1)||(i===pos.length-1&&j===0))continue;placeAlignmentPattern(mat,pos[i],pos[j]);}}}setModule(mat,sz-8,8,1);}function reserveFormatArea(mat){var sz=mat.size;for(var i=0;i<=8;i++){if(!mat.r[8][i])mat.r[8][i]=true;}for(var i=0;i<=8;i++){if(!mat.r[i][8])mat.r[i][8]=true;}for(var i=sz-8;i<sz;i++)mat.r[8][i]=true;for(var i=sz-7;i<sz;i++)mat.r[i][8]=true;}function reserveVersionArea(mat,version){if(version<7)return;var sz=mat.size;for(var i=0;i<6;i++){for(var j=0;j<3;j++){mat.r[i][sz-11+j]=true;mat.r[sz-11+j][i]=true;}}}function placeData(mat,codewords){var sz=mat.size;var bits=[];for(var i=0;i<codewords.length;i++){for(var j=7;j>=0;j--)bits.push((codewords[i]>>j)&1);}var bitIdx=0;var col=sz-1;var upward=true;while(col>0){if(col===6)col--;var rows=upward?range(sz-1,-1):range(0,sz);for(var ri=0;ri<rows.length;ri++){var row=rows[ri];for(var dc=0;dc<=1;dc++){var c=col-dc;if(mat.r[row][c])continue;mat.m[row][c]=bitIdx<bits.length?bits[bitIdx++]:0;}}upward=!upward;col-=2;}}function range(a,b){var r=[];if(a>b){for(var i=a;i>=b+1;i--)r.push(i);}else{for(var i=a;i<b;i++)r.push(i);}return r;}var MASK_FNS=[function(r,c){return(r+c)%2===0;},function(r,c){return r%2===0;},function(r,c){return c%3===0;},function(r,c){return(r+c)%3===0;},function(r,c){return(Math.floor(r/2)+Math.floor(c/3))%2===0;},function(r,c){return(r*c)%2+(r*c)%3===0;},function(r,c){return((r*c)%2+(r*c)%3)%2===0;},function(r,c){return((r+c)%2+(r*c)%3)%2===0;}];function applyMask(mat,maskIdx,reserved){var fn=MASK_FNS[maskIdx];for(var r=0;r<mat.size;r++){for(var c=0;c<mat.size;c++){if(reserved[r][c])continue;if(fn(r,c))mat.m[r][c]^=1;}}}var FORMAT_BITS=[0x77C4,0x72F3,0x7DAA,0x789D,0x662F,0x6318,0x6C41,0x6976];function writeFormatBits(mat,maskIdx){var bits=FORMAT_BITS[maskIdx];var sz=mat.size;var coords1=[[8,0],[8,1],[8,2],[8,3],[8,4],[8,5],[8,7],[8,8],[7,8],[5,8],[4,8],[3,8],[2,8],[1,8],[0,8]];var coords2=[];for(var i=sz-1;i>=sz-8;i--)coords2.push([8,i]);for(var i=sz-7;i<sz;i++)coords2.push([i,8]);for(var i=0;i<15;i++){var bit=(bits>>i)&1;mat.m[coords1[i][0]][coords1[i][1]]=bit;mat.m[coords2[i][0]][coords2[i][1]]=bit;}}function penalty(mat){var sz=mat.size,score=0;for(var r=0;r<sz;r++){var run=1;for(var c=1;c<sz;c++){if(mat.m[r][c]===mat.m[r][c-1]){run++;}else{if(run>=5)score+=run-2;run=1;}}if(run>=5)score+=run-2;}for(var c=0;c<sz;c++){var run=1;for(var r=1;r<sz;r++){if(mat.m[r][c]===mat.m[r-1][c]){run++;}else{if(run>=5)score+=run-2;run=1;}}if(run>=5)score+=run-2;}var dark=0;for(var r=0;r<sz;r++)for(var c=0;c<sz;c++)if(mat.m[r][c])dark++;var pct=dark*100/(sz*sz);score+=Math.abs(Math.floor(pct/5)*5-50)*2;return score;}function generate(text){text=text.toUpperCase();var encoded=encodeData(text);if(!encoded)return null;var version=encoded.version;var codewords=interleaveBlocks(version,encoded.data);var sz=moduleSize(version);var bestMat=null,bestScore=Infinity;for(var mi=0;mi<8;mi++){var mat=createMatrix(sz);placeFixedPatterns(mat,version);reserveFormatArea(mat);reserveVersionArea(mat,version);var reserved=[];for(var r=0;r<sz;r++)reserved.push(mat.r[r].slice());placeData(mat,codewords);applyMask(mat,mi,reserved);writeFormatBits(mat,mi);var sc=penalty(mat);if(sc<bestScore){bestScore=sc;bestMat=mat;}}return bestMat.m;}function toCanvas(canvas,matrix,scale){scale=scale||4;var sz=matrix.length;var quiet=4;var totalSize=(sz+quiet*2)*scale;canvas.width=totalSize;canvas.height=totalSize;var ctx=canvas.getContext('2d');ctx.fillStyle='#ffffff';ctx.fillRect(0,0,totalSize,totalSize);ctx.fillStyle='#000000';for(var r=0;r<sz;r++){for(var c=0;c<sz;c++){if(matrix[r][c]){ctx.fillRect((c+quiet)*scale,(r+quiet)*scale,scale,scale);}}}}return{generate:generate,toCanvas:toCanvas};})();
|
|
53
|
+
`;
|
|
54
|
+
|
|
55
|
+
export const HTML_TAIL = `document.getElementById('secret-key').textContent = SECRET_KEY;
|
|
56
|
+
if (PUBLIC_KEY) {
|
|
57
|
+
document.getElementById('public-key').textContent = PUBLIC_KEY;
|
|
58
|
+
document.getElementById('pub-section').style.display = 'block';
|
|
59
|
+
}
|
|
60
|
+
var matrix = QR.generate(SECRET_KEY);
|
|
61
|
+
if (matrix) {
|
|
62
|
+
QR.toCanvas(document.getElementById('qr-canvas'), matrix, 6);
|
|
63
|
+
} else {
|
|
64
|
+
document.getElementById('qr-canvas').parentElement.innerHTML = '<p style="color:#ef4444">Could not generate QR code — key may be too long for alphanumeric encoding.</p>';
|
|
65
|
+
}
|
|
66
|
+
</script></body></html>
|
|
67
|
+
`;
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
// backup — render an HTML file containing the master key as a QR code
|
|
2
|
+
// and open it in the default browser. Cleans up the temp HTML after 30s.
|
|
3
|
+
//
|
|
4
|
+
// The QR-generation JS is the same minified generator used in
|
|
5
|
+
// onboarding.html, so the output looks identical. No browser automation,
|
|
6
|
+
// no npm deps.
|
|
7
|
+
|
|
8
|
+
import { mkdtempSync, writeFileSync, existsSync, unlinkSync, readFileSync } from 'node:fs';
|
|
9
|
+
import { join } from 'node:path';
|
|
10
|
+
import { tmpdir } from 'node:os';
|
|
11
|
+
import { spawn } from 'node:child_process';
|
|
12
|
+
import { defaultPaths, secretKeyFromKeyFile, VaultError } from '../vault.mjs';
|
|
13
|
+
import { HTML_HEAD, QR_JS, HTML_TAIL } from './backup-template.mjs';
|
|
14
|
+
|
|
15
|
+
// JSON-encode for embedding inside a <script> tag. Escapes `<` so that
|
|
16
|
+
// `</script>` in a payload cannot break out of the tag. The real age key
|
|
17
|
+
// format cannot contain these characters, but defence in depth is cheap.
|
|
18
|
+
function jsLiteral(s) {
|
|
19
|
+
return JSON.stringify(s).replace(/</g, '\\u003c');
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function buildBackupHtml(secretKey, publicKey) {
|
|
23
|
+
const pub = publicKey || '';
|
|
24
|
+
const dataScript = `var SECRET_KEY = ${jsLiteral(secretKey)};\nvar PUBLIC_KEY = ${jsLiteral(pub)};\n`;
|
|
25
|
+
return HTML_HEAD + QR_JS + dataScript + HTML_TAIL;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export async function runBackup(overrides = {}) {
|
|
29
|
+
const paths = overrides.paths || defaultPaths(overrides);
|
|
30
|
+
const opener = overrides.opener || defaultOpener;
|
|
31
|
+
const scheduleCleanup = overrides.scheduleCleanup ?? true;
|
|
32
|
+
|
|
33
|
+
if (!existsSync(paths.keyFile)) {
|
|
34
|
+
// Missing key is an operational failure (exit 2), not an "uninitialised"
|
|
35
|
+
// state — see v0.1.3 secrets/vault.mjs::checkVault rationale. Remediation
|
|
36
|
+
// requires backup-restore, not a fresh `secrets init`.
|
|
37
|
+
console.error(`Error: No private key found at ${paths.keyFile}`);
|
|
38
|
+
console.error('Restore your key from backup before running `secrets backup`.');
|
|
39
|
+
throw new VaultError('no private key', 2);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const secretKey = secretKeyFromKeyFile(paths.keyFile);
|
|
43
|
+
if (!secretKey) {
|
|
44
|
+
console.error(`Error: Could not extract secret key from ${paths.keyFile}`);
|
|
45
|
+
throw new VaultError('could not extract secret key', 1);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
let publicKey = '';
|
|
49
|
+
if (existsSync(paths.recipientsFile)) {
|
|
50
|
+
publicKey = (readFileSync(paths.recipientsFile, 'utf-8').split('\n')[0] || '').trim();
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const html = buildBackupHtml(secretKey, publicKey);
|
|
54
|
+
const dir = mkdtempSync(join(tmpdir(), 'dave-key-backup-'));
|
|
55
|
+
const htmlPath = join(dir, 'backup.html');
|
|
56
|
+
writeFileSync(htmlPath, html);
|
|
57
|
+
|
|
58
|
+
console.log('Opening master key backup in browser...');
|
|
59
|
+
console.log('IMPORTANT: Back up the key shown, then close the browser tab.');
|
|
60
|
+
|
|
61
|
+
opener(htmlPath);
|
|
62
|
+
|
|
63
|
+
if (scheduleCleanup) {
|
|
64
|
+
// Detach a lightweight cleaner so the CLI can exit.
|
|
65
|
+
setTimeout(() => {
|
|
66
|
+
try { unlinkSync(htmlPath); } catch { /* ignore */ }
|
|
67
|
+
}, 30000).unref?.();
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return { htmlPath, html };
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function defaultOpener(path) {
|
|
74
|
+
const platform = process.platform;
|
|
75
|
+
if (platform === 'darwin') {
|
|
76
|
+
spawn('open', [path], { detached: true, stdio: 'ignore' }).unref();
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
// Linux-ish: try xdg-open, fall back to printing the path.
|
|
80
|
+
const child = spawn('xdg-open', [path], { detached: true, stdio: 'ignore' });
|
|
81
|
+
child.on('error', () => {
|
|
82
|
+
console.log(`Open this file in your browser: ${path}`);
|
|
83
|
+
});
|
|
84
|
+
child.unref();
|
|
85
|
+
}
|