@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.
Files changed (372) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +170 -0
  3. package/bin/desk +9 -0
  4. package/bin/distill +2 -0
  5. package/bin/estate-sync +2 -0
  6. package/bin/launch +2 -0
  7. package/bin/memory-sync +2 -0
  8. package/bin/migrate-inspect +2 -0
  9. package/bin/pa +2 -0
  10. package/bin/pa-coordinator-hook +2 -0
  11. package/bin/pa-init +2 -0
  12. package/bin/research +2 -0
  13. package/bin/scheduler +2 -0
  14. package/bin/secrets +2 -0
  15. package/bin/slice +2 -0
  16. package/bin/smoke +9 -0
  17. package/bin/sync +2 -0
  18. package/dist/engineering-pa-cli/README.md +205 -0
  19. package/dist/engineering-pa-cli/bin/estate-sync +2 -0
  20. package/dist/engineering-pa-cli/bin/launch +2 -0
  21. package/dist/engineering-pa-cli/bin/pa +2 -0
  22. package/dist/engineering-pa-cli/bin/pa-coordinator-hook +12 -0
  23. package/dist/engineering-pa-cli/bin/research +2 -0
  24. package/dist/engineering-pa-cli/bin/scheduler +2 -0
  25. package/dist/engineering-pa-cli/bin/secrets +2 -0
  26. package/dist/engineering-pa-cli/bin/smoke +9 -0
  27. package/dist/engineering-pa-cli/bin/sync +2 -0
  28. package/dist/engineering-pa-cli/config/coordinator-allowlist.json +118 -0
  29. package/dist/engineering-pa-cli/docs/coordinator-hook.md +211 -0
  30. package/dist/engineering-pa-cli/docs/estate-sync.md +412 -0
  31. package/dist/engineering-pa-cli/docs/gsc.md +175 -0
  32. package/dist/engineering-pa-cli/docs/lint-linkedin.md +139 -0
  33. package/dist/engineering-pa-cli/docs/secrets.md +221 -0
  34. package/dist/engineering-pa-cli/docs/smoke.md +113 -0
  35. package/dist/engineering-pa-cli/docs/staging-alias.md +114 -0
  36. package/dist/engineering-pa-cli/index.mjs +17 -0
  37. package/dist/engineering-pa-cli/package.json +37 -0
  38. package/dist/engineering-pa-cli/src/autonomy/cli.mjs +329 -0
  39. package/dist/engineering-pa-cli/src/autonomy/envelope.mjs +301 -0
  40. package/dist/engineering-pa-cli/src/coordinator-hook/cli.mjs +140 -0
  41. package/dist/engineering-pa-cli/src/coordinator-hook/config.mjs +134 -0
  42. package/dist/engineering-pa-cli/src/coordinator-hook/message.mjs +47 -0
  43. package/dist/engineering-pa-cli/src/coordinator-hook/policy.mjs +211 -0
  44. package/dist/engineering-pa-cli/src/coordinator-hook/repo-root.mjs +44 -0
  45. package/dist/engineering-pa-cli/src/coordinator-hook/self.mjs +30 -0
  46. package/dist/engineering-pa-cli/src/environment/cli.mjs +287 -0
  47. package/dist/engineering-pa-cli/src/environment/probes.mjs +302 -0
  48. package/dist/engineering-pa-cli/src/gsc/auth.mjs +148 -0
  49. package/dist/engineering-pa-cli/src/gsc/cli.mjs +93 -0
  50. package/dist/engineering-pa-cli/src/gsc/cloudflare.mjs +318 -0
  51. package/dist/engineering-pa-cli/src/gsc/commands/add-property.mjs +70 -0
  52. package/dist/engineering-pa-cli/src/gsc/commands/list-properties.mjs +33 -0
  53. package/dist/engineering-pa-cli/src/gsc/commands/pull-analytics.mjs +130 -0
  54. package/dist/engineering-pa-cli/src/gsc/commands/submit-sitemap.mjs +41 -0
  55. package/dist/engineering-pa-cli/src/gsc/commands/verify-via-cf-dns.mjs +189 -0
  56. package/dist/engineering-pa-cli/src/knowledge/call-log.mjs +59 -0
  57. package/dist/engineering-pa-cli/src/knowledge/cli.mjs +242 -0
  58. package/dist/engineering-pa-cli/src/knowledge/eval.mjs +446 -0
  59. package/dist/engineering-pa-cli/src/knowledge/formatters.mjs +331 -0
  60. package/dist/engineering-pa-cli/src/knowledge/graph.mjs +710 -0
  61. package/dist/engineering-pa-cli/src/launch/README.md +214 -0
  62. package/dist/engineering-pa-cli/src/launch/cli.mjs +51 -0
  63. package/dist/engineering-pa-cli/src/launch/commands/launch.mjs +106 -0
  64. package/dist/engineering-pa-cli/src/launch/identity.mjs +43 -0
  65. package/dist/engineering-pa-cli/src/launch/planner.mjs +44 -0
  66. package/dist/engineering-pa-cli/src/launch/repo-root.mjs +53 -0
  67. package/dist/engineering-pa-cli/src/launch/runner.mjs +147 -0
  68. package/dist/engineering-pa-cli/src/launch/task-loader.mjs +85 -0
  69. package/dist/engineering-pa-cli/src/lint/cli.mjs +61 -0
  70. package/dist/engineering-pa-cli/src/lint/commands/linkedin.mjs +133 -0
  71. package/dist/engineering-pa-cli/src/lint/detectors.mjs +90 -0
  72. package/dist/engineering-pa-cli/src/lint/linter.mjs +69 -0
  73. package/dist/engineering-pa-cli/src/lint/reporter.mjs +32 -0
  74. package/dist/engineering-pa-cli/src/lint/rules.mjs +132 -0
  75. package/dist/engineering-pa-cli/src/pa/cli.mjs +369 -0
  76. package/dist/engineering-pa-cli/src/pa/doc-groups.mjs +164 -0
  77. package/dist/engineering-pa-cli/src/pa/io.mjs +80 -0
  78. package/dist/engineering-pa-cli/src/pa/project-cli.mjs +385 -0
  79. package/dist/engineering-pa-cli/src/pa/projects.mjs +352 -0
  80. package/dist/engineering-pa-cli/src/pa/repo-root.mjs +64 -0
  81. package/dist/engineering-pa-cli/src/pa/session.mjs +254 -0
  82. package/dist/engineering-pa-cli/src/pa/work-cli.mjs +164 -0
  83. package/dist/engineering-pa-cli/src/pa/work.mjs +196 -0
  84. package/dist/engineering-pa-cli/src/pa/workstreams.mjs +201 -0
  85. package/dist/engineering-pa-cli/src/research/README.md +360 -0
  86. package/dist/engineering-pa-cli/src/research/briefs/extractor.md +103 -0
  87. package/dist/engineering-pa-cli/src/research/briefs/searcher.md +113 -0
  88. package/dist/engineering-pa-cli/src/research/briefs/synthesizer.md +124 -0
  89. package/dist/engineering-pa-cli/src/research/briefs/verifier.md +86 -0
  90. package/dist/engineering-pa-cli/src/research/cli.mjs +141 -0
  91. package/dist/engineering-pa-cli/src/research/commands/gates.mjs +176 -0
  92. package/dist/engineering-pa-cli/src/research/commands/list.mjs +56 -0
  93. package/dist/engineering-pa-cli/src/research/commands/mark.mjs +97 -0
  94. package/dist/engineering-pa-cli/src/research/commands/paths.mjs +69 -0
  95. package/dist/engineering-pa-cli/src/research/commands/prune.mjs +86 -0
  96. package/dist/engineering-pa-cli/src/research/commands/run.mjs +94 -0
  97. package/dist/engineering-pa-cli/src/research/commands/show.mjs +43 -0
  98. package/dist/engineering-pa-cli/src/research/docs/operator-present-flow.md +120 -0
  99. package/dist/engineering-pa-cli/src/research/format.mjs +59 -0
  100. package/dist/engineering-pa-cli/src/research/promote.mjs +260 -0
  101. package/dist/engineering-pa-cli/src/research/schema/finding.schema.json +55 -0
  102. package/dist/engineering-pa-cli/src/research/schema/gates.schema.json +34 -0
  103. package/dist/engineering-pa-cli/src/research/schema/manifest.schema.json +59 -0
  104. package/dist/engineering-pa-cli/src/research/schema/plan.schema.json +41 -0
  105. package/dist/engineering-pa-cli/src/research/schema/sources.schema.json +31 -0
  106. package/dist/engineering-pa-cli/src/research/store/findings.mjs +61 -0
  107. package/dist/engineering-pa-cli/src/research/store/gates.mjs +73 -0
  108. package/dist/engineering-pa-cli/src/research/store/index.mjs +312 -0
  109. package/dist/engineering-pa-cli/src/research/store/manifest.mjs +56 -0
  110. package/dist/engineering-pa-cli/src/research/store/plan.mjs +59 -0
  111. package/dist/engineering-pa-cli/src/research/store/sources.mjs +44 -0
  112. package/dist/engineering-pa-cli/src/research/store/validate.mjs +92 -0
  113. package/dist/engineering-pa-cli/src/research/workflows/research-normal.js +573 -0
  114. package/dist/engineering-pa-cli/src/scheduler/cli.mjs +197 -0
  115. package/dist/engineering-pa-cli/src/secrets/cli.mjs +254 -0
  116. package/dist/engineering-pa-cli/src/secrets/commands/backup-template.mjs +67 -0
  117. package/dist/engineering-pa-cli/src/secrets/commands/backup.mjs +85 -0
  118. package/dist/engineering-pa-cli/src/secrets/commands/delete.mjs +58 -0
  119. package/dist/engineering-pa-cli/src/secrets/commands/find.mjs +36 -0
  120. package/dist/engineering-pa-cli/src/secrets/commands/get.mjs +49 -0
  121. package/dist/engineering-pa-cli/src/secrets/commands/init.mjs +69 -0
  122. package/dist/engineering-pa-cli/src/secrets/commands/list.mjs +37 -0
  123. package/dist/engineering-pa-cli/src/secrets/commands/rotate.mjs +46 -0
  124. package/dist/engineering-pa-cli/src/secrets/commands/set.mjs +123 -0
  125. package/dist/engineering-pa-cli/src/secrets/commands/status.mjs +146 -0
  126. package/dist/engineering-pa-cli/src/secrets/commands/vaults.mjs +57 -0
  127. package/dist/engineering-pa-cli/src/secrets/commands/verify.mjs +143 -0
  128. package/dist/engineering-pa-cli/src/secrets/vault.mjs +766 -0
  129. package/dist/engineering-pa-cli/src/smoke/checks.mjs +199 -0
  130. package/dist/engineering-pa-cli/src/smoke/cli.mjs +108 -0
  131. package/dist/engineering-pa-cli/src/smoke/commands/run.mjs +237 -0
  132. package/dist/engineering-pa-cli/src/smoke/runner.mjs +191 -0
  133. package/dist/engineering-pa-cli/src/staging-alias/cli.mjs +91 -0
  134. package/dist/engineering-pa-cli/src/staging-alias/cloudflare.mjs +380 -0
  135. package/dist/engineering-pa-cli/src/staging-alias/commands/list.mjs +36 -0
  136. package/dist/engineering-pa-cli/src/staging-alias/commands/set.mjs +188 -0
  137. package/dist/engineering-pa-cli/src/staging-alias/commands/status.mjs +113 -0
  138. package/dist/engineering-pa-cli/src/staging-alias/commands/unset.mjs +95 -0
  139. package/dist/engineering-pa-cli/src/staging-alias/sites.mjs +105 -0
  140. package/dist/engineering-pa-cli/src/sync/cli.mjs +151 -0
  141. package/dist/engineering-pa-cli/src/sync/git.mjs +149 -0
  142. package/dist/engineering-pa-cli/src/sync/lock.mjs +30 -0
  143. package/dist/engineering-pa-cli/src/sync/regenerated-artifacts.mjs +114 -0
  144. package/dist/engineering-pa-cli/src/sync/registry-scope.mjs +260 -0
  145. package/dist/engineering-pa-cli/src/sync/run.mjs +531 -0
  146. package/dist/engineering-pa-cli/src/sync/runner.mjs +62 -0
  147. package/dist/engineering-pa-cli/src/sync/runs-digest.mjs +138 -0
  148. package/dist/engineering-pa-core/README.md +60 -0
  149. package/dist/engineering-pa-core/bin/desk +9 -0
  150. package/dist/engineering-pa-core/bin/distill +2 -0
  151. package/dist/engineering-pa-core/bin/memory-sync +2 -0
  152. package/dist/engineering-pa-core/bin/slice +2 -0
  153. package/dist/engineering-pa-core/docs/autonomy-envelope-tracker.md +111 -0
  154. package/dist/engineering-pa-core/docs/blueprints/README.md +45 -0
  155. package/dist/engineering-pa-core/docs/blueprints/docker-services/blueprint.md +168 -0
  156. package/dist/engineering-pa-core/docs/blueprints/git-identity/blueprint.md +116 -0
  157. package/dist/engineering-pa-core/docs/blueprints/ideas/custom-workspace-views.md +9 -0
  158. package/dist/engineering-pa-core/docs/blueprints/ideas/knowledge-domain-patterns.md +15 -0
  159. package/dist/engineering-pa-core/docs/blueprints/ideas/office-integration.md +11 -0
  160. package/dist/engineering-pa-core/docs/blueprints/ideas/team-scaling.md +9 -0
  161. package/dist/engineering-pa-core/docs/blueprints/ideas/work-personal-separation.md +9 -0
  162. package/dist/engineering-pa-core/docs/blueprints/scheduled-distillation/blueprint.md +129 -0
  163. package/dist/engineering-pa-core/docs/blueprints/tool-integration/blueprint.md +110 -0
  164. package/dist/engineering-pa-core/docs/commands/init.md +159 -0
  165. package/dist/engineering-pa-core/docs/commands/schedule.md +98 -0
  166. package/dist/engineering-pa-core/docs/commands/upgrade.md +67 -0
  167. package/dist/engineering-pa-core/docs/entity-location-map.md +93 -0
  168. package/dist/engineering-pa-core/docs/knowledge-scaffold.md +121 -0
  169. package/dist/engineering-pa-core/docs/lock.md +155 -0
  170. package/dist/engineering-pa-core/docs/memory-sync.md +28 -0
  171. package/dist/engineering-pa-core/docs/pa-operating-model.md +457 -0
  172. package/dist/engineering-pa-core/docs/scheduling.md +35 -0
  173. package/dist/engineering-pa-core/docs/standards/README.md +24 -0
  174. package/dist/engineering-pa-core/docs/standards/briefing-contract.md +161 -0
  175. package/dist/engineering-pa-core/docs/standards/tooling.md +240 -0
  176. package/dist/engineering-pa-core/docs/tools/distill.md +230 -0
  177. package/dist/engineering-pa-core/docs/tools/memory-sync.md +98 -0
  178. package/dist/engineering-pa-core/docs/tools/scheduler.md +243 -0
  179. package/dist/engineering-pa-core/docs/tools/slice.md +150 -0
  180. package/dist/engineering-pa-core/docs/upstream-issue-filing.md +88 -0
  181. package/dist/engineering-pa-core/docs/working-directory.md +29 -0
  182. package/dist/engineering-pa-core/docs/workspace-operations.md +46 -0
  183. package/dist/engineering-pa-core/index.mjs +21 -0
  184. package/dist/engineering-pa-core/knowledge/README.md +53 -0
  185. package/dist/engineering-pa-core/knowledge/canonical/.gitkeep +0 -0
  186. package/dist/engineering-pa-core/knowledge/content-voice/README.md +64 -0
  187. package/dist/engineering-pa-core/knowledge/content-voice/banned-ai-tells-baseline.md +133 -0
  188. package/dist/engineering-pa-core/knowledge/content-voice/banned-ai-tells-shape.md +58 -0
  189. package/dist/engineering-pa-core/knowledge/content-voice/banned-ai-tells-taxonomy.md +33 -0
  190. package/dist/engineering-pa-core/knowledge/content-voice/content-pipeline-routing.md +58 -0
  191. package/dist/engineering-pa-core/knowledge/content-voice/new-tell-capture-procedure.md +40 -0
  192. package/dist/engineering-pa-core/knowledge/content-voice/voice-guide-shape.md +43 -0
  193. package/dist/engineering-pa-core/knowledge/delegation-dispatch/README.md +34 -0
  194. package/dist/engineering-pa-core/knowledge/delegation-dispatch/briefing-contract.md +77 -0
  195. package/dist/engineering-pa-core/knowledge/delegation-dispatch/delegation-modes.md +84 -0
  196. package/dist/engineering-pa-core/knowledge/delegation-dispatch/dispatch-envelope.md +238 -0
  197. package/dist/engineering-pa-core/knowledge/delegation-dispatch/task-groups-and-orchestrators.md +62 -0
  198. package/dist/engineering-pa-core/knowledge/delegation-dispatch/team-improvement-loop.md +48 -0
  199. package/dist/engineering-pa-core/knowledge/delegation-dispatch/tool-development-policy.md +48 -0
  200. package/dist/engineering-pa-core/knowledge/engineering-discipline/README.md +33 -0
  201. package/dist/engineering-pa-core/knowledge/engineering-discipline/blast-radius-and-safety.md +50 -0
  202. package/dist/engineering-pa-core/knowledge/engineering-discipline/investigate-root-causes.md +48 -0
  203. package/dist/engineering-pa-core/knowledge/engineering-discipline/role-ladder.md +57 -0
  204. package/dist/engineering-pa-core/knowledge/engineering-discipline/verify-runtime-behaviour.md +53 -0
  205. package/dist/engineering-pa-core/knowledge/engineering-discipline/worktree-isolation.md +50 -0
  206. package/dist/engineering-pa-core/knowledge/knowledge-memory/README.md +33 -0
  207. package/dist/engineering-pa-core/knowledge/knowledge-memory/claude-md-hygiene.md +47 -0
  208. package/dist/engineering-pa-core/knowledge/knowledge-memory/journal-and-self-improvement.md +53 -0
  209. package/dist/engineering-pa-core/knowledge/knowledge-memory/knowledge-retrieval-discipline.md +62 -0
  210. package/dist/engineering-pa-core/knowledge/knowledge-memory/librarian-synthesis-mandate.md +56 -0
  211. package/dist/engineering-pa-core/knowledge/knowledge-memory/staging-and-observations.md +58 -0
  212. package/dist/engineering-pa-core/knowledge/operator-interface/README.md +32 -0
  213. package/dist/engineering-pa-core/knowledge/operator-interface/communication-style.md +33 -0
  214. package/dist/engineering-pa-core/knowledge/operator-interface/escalation-and-correction.md +45 -0
  215. package/dist/engineering-pa-core/knowledge/operator-interface/push-back-discipline.md +42 -0
  216. package/dist/engineering-pa-core/knowledge/operator-interface/session-boot-and-end.md +56 -0
  217. package/dist/engineering-pa-core/knowledge/platform-tooling/README.md +37 -0
  218. package/dist/engineering-pa-core/knowledge/platform-tooling/desk-cli-reference.md +52 -0
  219. package/dist/engineering-pa-core/knowledge/platform-tooling/environment-awareness.md +91 -0
  220. package/dist/engineering-pa-core/knowledge/platform-tooling/mcp-servers-catalogue.md +52 -0
  221. package/dist/engineering-pa-core/knowledge/platform-tooling/pa-cli-reference.md +55 -0
  222. package/dist/engineering-pa-core/knowledge/platform-tooling/scheduling-and-launchd.md +56 -0
  223. package/dist/engineering-pa-core/knowledge/platform-tooling/tool-discovery-discipline.md +49 -0
  224. package/dist/engineering-pa-core/knowledge/platform-tooling/working-directory-vs-project-root.md +43 -0
  225. package/dist/engineering-pa-core/knowledge/platform-tooling/workspace-server.md +51 -0
  226. package/dist/engineering-pa-core/knowledge/raw/.gitkeep +0 -0
  227. package/dist/engineering-pa-core/knowledge/scaffold/domains.json +5 -0
  228. package/dist/engineering-pa-core/knowledge/schema/.gitkeep +0 -0
  229. package/dist/engineering-pa-core/knowledge/work-management/README.md +33 -0
  230. package/dist/engineering-pa-core/knowledge/work-management/desk-cli.md +84 -0
  231. package/dist/engineering-pa-core/knowledge/work-management/progress-md.md +48 -0
  232. package/dist/engineering-pa-core/knowledge/work-management/projects.md +64 -0
  233. package/dist/engineering-pa-core/knowledge/work-management/spawn-log.md +48 -0
  234. package/dist/engineering-pa-core/knowledge/work-management/work-items.md +67 -0
  235. package/dist/engineering-pa-core/package.json +26 -0
  236. package/dist/engineering-pa-core/roles/agent-ops/definition.md +79 -0
  237. package/dist/engineering-pa-core/roles/dispatch-briefer/definition.md +195 -0
  238. package/dist/engineering-pa-core/roles/librarian/definition.md +176 -0
  239. package/dist/engineering-pa-core/roles/pa/definition.md +93 -0
  240. package/dist/engineering-pa-core/roles/system-admin/definition.md +106 -0
  241. package/dist/engineering-pa-core/roles/tool-developer/definition.md +116 -0
  242. package/dist/engineering-pa-core/roles/writer/definition.md +130 -0
  243. package/dist/engineering-pa-core/schema/autonomy-envelope.schema.json +61 -0
  244. package/dist/engineering-pa-core/schema/project.json +114 -0
  245. package/dist/engineering-pa-core/schema/schedule.json +63 -0
  246. package/dist/engineering-pa-core/schema/work-item.json +92 -0
  247. package/dist/engineering-pa-core/src/config/pa-package-name.mjs +30 -0
  248. package/dist/engineering-pa-core/src/desk/cli.mjs +95 -0
  249. package/dist/engineering-pa-core/src/desk/commands.mjs +398 -0
  250. package/dist/engineering-pa-core/src/desk/format.mjs +84 -0
  251. package/dist/engineering-pa-core/src/desk/repo.mjs +172 -0
  252. package/dist/engineering-pa-core/src/desk/team-scope.mjs +142 -0
  253. package/dist/engineering-pa-core/src/distill/cli.mjs +112 -0
  254. package/dist/engineering-pa-core/src/distill/prereqs.mjs +171 -0
  255. package/dist/engineering-pa-core/src/distill/prompt.mjs +74 -0
  256. package/dist/engineering-pa-core/src/distill/run.mjs +230 -0
  257. package/dist/engineering-pa-core/src/distill/runner.mjs +86 -0
  258. package/dist/engineering-pa-core/src/distill/staging.mjs +59 -0
  259. package/dist/engineering-pa-core/src/knowledge/retrieval/backlink-indexer.mjs +154 -0
  260. package/dist/engineering-pa-core/src/knowledge/retrieval/graph-builder.mjs +186 -0
  261. package/dist/engineering-pa-core/src/knowledge/retrieval/index-builder.mjs +225 -0
  262. package/dist/engineering-pa-core/src/knowledge/retrieval/query.mjs +263 -0
  263. package/dist/engineering-pa-core/src/lock/lock.mjs +202 -0
  264. package/dist/engineering-pa-core/src/memory-sync/cli-support.mjs +12 -0
  265. package/dist/engineering-pa-core/src/memory-sync/cli.mjs +56 -0
  266. package/dist/engineering-pa-core/src/memory-sync/commands/pull.mjs +44 -0
  267. package/dist/engineering-pa-core/src/memory-sync/commands/push.mjs +46 -0
  268. package/dist/engineering-pa-core/src/memory-sync/mirror.mjs +114 -0
  269. package/dist/engineering-pa-core/src/memory-sync/repo.mjs +65 -0
  270. package/dist/engineering-pa-core/src/scheduler/detect.mjs +111 -0
  271. package/dist/engineering-pa-core/src/scheduler/host.mjs +38 -0
  272. package/dist/engineering-pa-core/src/scheduler/index.mjs +13 -0
  273. package/dist/engineering-pa-core/src/scheduler/install.mjs +854 -0
  274. package/dist/engineering-pa-core/src/scheduler/list.mjs +168 -0
  275. package/dist/engineering-pa-core/src/scheduler/remove.mjs +176 -0
  276. package/dist/engineering-pa-core/src/scheduler/verify.mjs +435 -0
  277. package/dist/engineering-pa-core/src/session-boot/autonomy-injection.mjs +84 -0
  278. package/dist/engineering-pa-core/src/slice/cli.mjs +139 -0
  279. package/dist/engineering-pa-core/src/slice/inputs.mjs +174 -0
  280. package/dist/engineering-pa-core/src/slice/prompt.mjs +94 -0
  281. package/dist/engineering-pa-core/src/slice/prompts/dave-boot.md +91 -0
  282. package/dist/engineering-pa-core/src/slice/run.mjs +334 -0
  283. package/dist/engineering-pa-core-extensions-atlassian/README.md +31 -0
  284. package/dist/engineering-pa-core-extensions-atlassian/index.mjs +9 -0
  285. package/dist/engineering-pa-core-extensions-atlassian/package.json +19 -0
  286. package/dist/engineering-pa-core-extensions-n8n/README.md +29 -0
  287. package/dist/engineering-pa-core-extensions-n8n/index.mjs +8 -0
  288. package/dist/engineering-pa-core-extensions-n8n/package.json +19 -0
  289. package/dist/engineering-pa-librarian/README.md +38 -0
  290. package/dist/engineering-pa-librarian/index.mjs +12 -0
  291. package/dist/engineering-pa-librarian/package.json +19 -0
  292. package/dist/engineering-pa-migrate-inspect/README.md +131 -0
  293. package/dist/engineering-pa-migrate-inspect/bin/migrate-inspect +2 -0
  294. package/dist/engineering-pa-migrate-inspect/package.json +21 -0
  295. package/dist/engineering-pa-migrate-inspect/src/age-key-migrate.mjs +83 -0
  296. package/dist/engineering-pa-migrate-inspect/src/cli.mjs +97 -0
  297. package/dist/engineering-pa-migrate-inspect/src/inspector.mjs +237 -0
  298. package/dist/engineering-pa-migrate-inspect/src/platforms/linux.mjs +87 -0
  299. package/dist/engineering-pa-migrate-inspect/src/platforms/macos.mjs +105 -0
  300. package/dist/engineering-pa-migrate-inspect/src/runner.mjs +87 -0
  301. package/dist/engineering-pa-runbook/README.md +103 -0
  302. package/dist/engineering-pa-runbook/package.json +18 -0
  303. package/dist/engineering-pa-runbook/reference/dex-template.runbook.json +66 -0
  304. package/dist/engineering-pa-runbook/reference/hq-dave.runbook.json +69 -0
  305. package/dist/engineering-pa-runbook/reference/smarthome01.runbook.json +92 -0
  306. package/dist/engineering-pa-runbook/schema/inspection.schema.json +113 -0
  307. package/dist/engineering-pa-runbook/schema/runbook.schema.json +67 -0
  308. package/dist/engineering-pa-runbook/src/validator.mjs +167 -0
  309. package/dist/engineering-pa-scaffold/README.md +164 -0
  310. package/dist/engineering-pa-scaffold/bin/pa-init +2 -0
  311. package/dist/engineering-pa-scaffold/index.mjs +42 -0
  312. package/dist/engineering-pa-scaffold/package.json +22 -0
  313. package/dist/engineering-pa-scaffold/src/cli.mjs +115 -0
  314. package/dist/engineering-pa-scaffold/src/commands/init.mjs +169 -0
  315. package/dist/engineering-pa-scaffold/src/generator.mjs +354 -0
  316. package/dist/engineering-pa-scaffold/src/prompts.mjs +125 -0
  317. package/dist/engineering-pa-scaffold/src/runner.mjs +142 -0
  318. package/dist/engineering-pa-scaffold/templates/CLAUDE.md.template +196 -0
  319. package/dist/engineering-pa-scaffold/templates/operator-README.md.template +44 -0
  320. package/dist/engineering-pa-types-engineering/README.md +35 -0
  321. package/dist/engineering-pa-types-engineering/docs/commands/product-build.md +120 -0
  322. package/dist/engineering-pa-types-engineering/index.mjs +9 -0
  323. package/dist/engineering-pa-types-engineering/package.json +19 -0
  324. package/dist/engineering-pa-types-engineering/roles/product-engineer/definition.md +650 -0
  325. package/dist/engineering-pa-types-engineering/roles/product-engineer/templates/build-spec.md +82 -0
  326. package/dist/engineering-pa-types-engineering/roles/product-engineer/templates/preflight-checklist.md +158 -0
  327. package/dist/engineering-pa-types-engineering/roles/product-engineer/templates/worker-brief.md +130 -0
  328. package/dist/engineering-pa-workspace/README.md +197 -0
  329. package/dist/engineering-pa-workspace/app/assets/md.js +187 -0
  330. package/dist/engineering-pa-workspace/app/assets/modal.js +120 -0
  331. package/dist/engineering-pa-workspace/app/assets/theme.css +132 -0
  332. package/dist/engineering-pa-workspace/app/assets/theme.js +159 -0
  333. package/dist/engineering-pa-workspace/app/banner.js +320 -0
  334. package/dist/engineering-pa-workspace/app/delegations.html +345 -0
  335. package/dist/engineering-pa-workspace/app/home.html +1060 -0
  336. package/dist/engineering-pa-workspace/app/index.html +361 -0
  337. package/dist/engineering-pa-workspace/app/knowledge-map.html +163 -0
  338. package/dist/engineering-pa-workspace/app/knowledge.html +304 -0
  339. package/dist/engineering-pa-workspace/app/nav.json +75 -0
  340. package/dist/engineering-pa-workspace/app/notes.html +199 -0
  341. package/dist/engineering-pa-workspace/app/onboarding.html +592 -0
  342. package/dist/engineering-pa-workspace/app/profile.html +243 -0
  343. package/dist/engineering-pa-workspace/app/project-detail.html +432 -0
  344. package/dist/engineering-pa-workspace/app/projects.html +297 -0
  345. package/dist/engineering-pa-workspace/app/repos.html +598 -0
  346. package/dist/engineering-pa-workspace/app/schedules.html +486 -0
  347. package/dist/engineering-pa-workspace/app/secrets.html +164 -0
  348. package/dist/engineering-pa-workspace/app/staging.html +319 -0
  349. package/dist/engineering-pa-workspace/app/tasks.html +465 -0
  350. package/dist/engineering-pa-workspace/app/vendor/chart.umd.min.js +21 -0
  351. package/dist/engineering-pa-workspace/app/viewer.html +116 -0
  352. package/dist/engineering-pa-workspace/docs/DAEMON.md +104 -0
  353. package/dist/engineering-pa-workspace/docs/page-blueprint.md +286 -0
  354. package/dist/engineering-pa-workspace/docs/status-mon.md +168 -0
  355. package/dist/engineering-pa-workspace/index.mjs +12 -0
  356. package/dist/engineering-pa-workspace/nav.json.example +8 -0
  357. package/dist/engineering-pa-workspace/package.json +27 -0
  358. package/dist/engineering-pa-workspace/src/api.mjs +2086 -0
  359. package/dist/engineering-pa-workspace/src/install-daemon.mjs +573 -0
  360. package/dist/engineering-pa-workspace/src/nav.mjs +190 -0
  361. package/dist/engineering-pa-workspace/src/secrets-api.mjs +175 -0
  362. package/dist/engineering-pa-workspace/src/server.mjs +516 -0
  363. package/dist/engineering-pa-workspace/src/status-mon/poll.mjs +220 -0
  364. package/dist/engineering-pa-workspace/src/status-mon/poller.mjs +113 -0
  365. package/dist/engineering-pa-workspace/src/status-mon/run.mjs +133 -0
  366. package/dist/engineering-pa-workspace/src/supervisor.mjs +264 -0
  367. package/dist/engineering-pa-workspace/src/ws.mjs +633 -0
  368. package/index.mjs +60 -0
  369. package/package.json +58 -0
  370. package/templates/banned-ai-tells.md +102 -0
  371. package/templates/purpose.md +105 -0
  372. 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
+ }