@workerstack/repo 0.0.0-stage → 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 (438) hide show
  1. package/CHANGELOG.md +45 -0
  2. package/LICENSE +21 -0
  3. package/README.md +0 -3
  4. package/agents/global-settings/antigravity.json +3 -0
  5. package/agents/global-settings/claude.json +6 -0
  6. package/agents/global-settings/codex.toml +2 -0
  7. package/agents/global-settings/cursor.json +3 -0
  8. package/agents/global-settings/grok.toml +36 -0
  9. package/agents/global-settings/manifest.json +36 -0
  10. package/agents/grok/hooks/orchestrate-parent.json +60 -0
  11. package/agents/grok/hooks/rtk.json +16 -0
  12. package/agents/grok/hooks/rulesync.json +3 -0
  13. package/agents/grok/roles/scout.toml +5 -0
  14. package/agents/hooks.jsonc +67 -0
  15. package/agents/mcp-policy.jsonc +16 -0
  16. package/agents/rules/overview.md +21 -0
  17. package/agents/settings/claude.json +5 -0
  18. package/agents/skills/ask/SKILL.md +13 -0
  19. package/agents/skills/auto/SKILL.md +13 -0
  20. package/agents/skills/away/SKILL.md +19 -0
  21. package/agents/skills/blast-radius/SKILL.md +54 -0
  22. package/agents/skills/bro/SKILL.md +9 -0
  23. package/agents/skills/browser/SKILL.md +16 -0
  24. package/agents/skills/clasp/SKILL.md +11 -0
  25. package/agents/skills/commit/SKILL.md +35 -0
  26. package/agents/skills/commit-and-push/SKILL.md +9 -0
  27. package/agents/skills/commit-push/SKILL.md +59 -0
  28. package/agents/skills/commit-push-deploy/SKILL.md +74 -0
  29. package/agents/skills/commit-push-release/SKILL.md +75 -0
  30. package/agents/skills/correct/SKILL.md +39 -0
  31. package/agents/skills/do/SKILL.md +21 -0
  32. package/agents/skills/explain/SKILL.md +13 -0
  33. package/agents/skills/google-workspace/SKILL.md +12 -0
  34. package/agents/skills/handoff/SKILL.md +81 -0
  35. package/agents/skills/implement-plan/SKILL.md +29 -0
  36. package/agents/skills/implement-roadmap/SKILL.md +27 -0
  37. package/agents/skills/jev/SKILL.md +17 -0
  38. package/agents/skills/listen/SKILL.md +15 -0
  39. package/agents/skills/pickup/SKILL.md +58 -0
  40. package/agents/skills/principle-attack-the-premise/SKILL.md +24 -0
  41. package/agents/skills/principle-boundary-discipline/SKILL.md +38 -0
  42. package/agents/skills/principle-build-the-lever/SKILL.md +28 -0
  43. package/agents/skills/principle-encode-lessons-in-structure/SKILL.md +15 -0
  44. package/agents/skills/principle-exhaust-the-design-space/SKILL.md +12 -0
  45. package/agents/skills/principle-experience-first/SKILL.md +22 -0
  46. package/agents/skills/principle-explain-the-number/SKILL.md +21 -0
  47. package/agents/skills/principle-fix-root-causes/SKILL.md +24 -0
  48. package/agents/skills/principle-foundational-thinking/SKILL.md +25 -0
  49. package/agents/skills/principle-guard-the-context-window/SKILL.md +16 -0
  50. package/agents/skills/principle-laziness-protocol/SKILL.md +13 -0
  51. package/agents/skills/principle-make-operations-idempotent/SKILL.md +25 -0
  52. package/agents/skills/principle-migrate-callers-then-delete-legacy-apis/SKILL.md +18 -0
  53. package/agents/skills/principle-minimize-reader-load/SKILL.md +22 -0
  54. package/agents/skills/principle-model-the-domain/SKILL.md +26 -0
  55. package/agents/skills/principle-never-block-on-the-human/SKILL.md +17 -0
  56. package/agents/skills/principle-outcome-oriented-execution/SKILL.md +25 -0
  57. package/agents/skills/principle-prove-it-works/SKILL.md +25 -0
  58. package/agents/skills/principle-redesign-from-first-principles/SKILL.md +18 -0
  59. package/agents/skills/principle-separate-before-serializing-shared-state/SKILL.md +20 -0
  60. package/agents/skills/principle-sequence-verifiable-units/SKILL.md +22 -0
  61. package/agents/skills/principle-subtract-before-you-add/SKILL.md +21 -0
  62. package/agents/skills/principle-test-behavior-not-implementation/SKILL.md +24 -0
  63. package/agents/skills/principle-type-system-discipline/SKILL.md +17 -0
  64. package/agents/skills/review-plan/SKILL.md +28 -0
  65. package/agents/skills/review-roadmap/SKILL.md +28 -0
  66. package/agents/skills/show-me/SKILL.md +21 -0
  67. package/agents/skills/speak/SKILL.md +15 -0
  68. package/agents/skills/su/SKILL.md +11 -0
  69. package/agents/skills/teach/SKILL.md +25 -0
  70. package/agents/skills/tmp/SKILL.md +54 -0
  71. package/agents/skills/typescript-best-practices/SKILL.md +32 -0
  72. package/agents/skills/typescript-best-practices/references/patterns.md +328 -0
  73. package/agents/skills/unslop/SKILL.md +62 -0
  74. package/agents/skills/write-plan/SKILL.md +33 -0
  75. package/agents/skills/write-roadmap/SKILL.md +66 -0
  76. package/agents/subagents/implementer-deep.md +57 -0
  77. package/agents/subagents/implementer-fast.md +57 -0
  78. package/agents/subagents/orchestrator.md +43 -0
  79. package/agents/subagents/researcher.md +47 -0
  80. package/agents/subagents/scout.md +44 -0
  81. package/bin/repo.ts +4 -0
  82. package/cli/actionlint-default.test.ts +38 -0
  83. package/cli/actionlint-download.test.ts +162 -0
  84. package/cli/actionlint-real.test.ts +33 -0
  85. package/cli/actionlint.constants.ts +43 -0
  86. package/cli/actionlint.test.ts +108 -0
  87. package/cli/actionlint.ts +178 -0
  88. package/cli/bin.test.ts +54 -0
  89. package/cli/bin.ts +40 -0
  90. package/cli/check-changelog.test.ts +40 -0
  91. package/cli/check-changelog.ts +12 -0
  92. package/cli/check-comments.test.ts +157 -0
  93. package/cli/check-comments.ts +123 -0
  94. package/cli/check-env-modules.test.ts +85 -0
  95. package/cli/check-env-modules.ts +56 -0
  96. package/cli/check-magic.test.ts +139 -0
  97. package/cli/check-magic.ts +120 -0
  98. package/cli/check-manifests.constants.ts +4 -0
  99. package/cli/check-manifests.test.ts +210 -0
  100. package/cli/check-manifests.ts +71 -0
  101. package/cli/check-nightly.test.ts +103 -0
  102. package/cli/check-nightly.ts +37 -0
  103. package/cli/check-ratchets-missing.test.ts +46 -0
  104. package/cli/check-ratchets-mutation.test.ts +49 -0
  105. package/cli/check-ratchets.constants.ts +20 -0
  106. package/cli/check-ratchets.test.ts +190 -0
  107. package/cli/check-ratchets.ts +193 -0
  108. package/cli/check-rejections.test.ts +220 -0
  109. package/cli/check-rejections.ts +153 -0
  110. package/cli/check-reports.ts +37 -0
  111. package/cli/check-tsconfig.test.ts +507 -0
  112. package/cli/check-tsconfig.ts +205 -0
  113. package/cli/commands.test.ts +114 -0
  114. package/cli/commands.ts +218 -0
  115. package/cli/commit-changelog.ts +27 -0
  116. package/cli/commit-msg-changelog.test.ts +93 -0
  117. package/cli/commit-msg-range.test.ts +126 -0
  118. package/cli/commit-msg-red-proof.test.ts +147 -0
  119. package/cli/commit-msg-repo.test.ts +153 -0
  120. package/cli/commit-msg.test.ts +101 -0
  121. package/cli/commit-msg.ts +194 -0
  122. package/cli/commit-range.constants.ts +17 -0
  123. package/cli/commit-range.ts +57 -0
  124. package/cli/config-knip.test.ts +70 -0
  125. package/cli/config-nightly.test.ts +84 -0
  126. package/cli/config-tdd.test.ts +46 -0
  127. package/cli/config.test.ts +576 -0
  128. package/cli/config.ts +261 -0
  129. package/cli/conventional.ts +9 -0
  130. package/cli/coverage-config.ts +62 -0
  131. package/cli/coverage.constants.ts +6 -0
  132. package/cli/coverage.test.ts +199 -0
  133. package/cli/coverage.ts +127 -0
  134. package/cli/env.constants.ts +10 -0
  135. package/cli/env.test.ts +56 -0
  136. package/cli/env.ts +72 -0
  137. package/cli/fixtures/base-config.ts +13 -0
  138. package/cli/fixtures/gate-world.ts +48 -0
  139. package/cli/fixtures/knip-run.ts +38 -0
  140. package/cli/fixtures/mutate-fakes.ts +137 -0
  141. package/cli/fixtures/planted-repo.ts +50 -0
  142. package/cli/fixtures/publish-world.ts +71 -0
  143. package/cli/fixtures/red-proof-repo.ts +72 -0
  144. package/cli/fixtures/release-context.ts +15 -0
  145. package/cli/fixtures/release-world.ts +111 -0
  146. package/cli/fixtures/stub-bin.ts +40 -0
  147. package/cli/guard-diagnostics.test.ts +157 -0
  148. package/cli/guard-diagnostics.ts +195 -0
  149. package/cli/init.test.ts +487 -0
  150. package/cli/init.ts +275 -0
  151. package/cli/knip-check-ignores.test.ts +106 -0
  152. package/cli/knip-check-real.test.ts +65 -0
  153. package/cli/knip-check.constants.ts +46 -0
  154. package/cli/knip-check.test.ts +114 -0
  155. package/cli/knip-check.ts +169 -0
  156. package/cli/knip-schema.ts +67 -0
  157. package/cli/known-failures.constants.ts +22 -0
  158. package/cli/known-failures.test.ts +138 -0
  159. package/cli/known-failures.ts +131 -0
  160. package/cli/lint/as-needs-reason.ts +41 -0
  161. package/cli/lint/boundaries.ts +73 -0
  162. package/cli/lint/class-file.ts +39 -0
  163. package/cli/lint/config-file.ts +32 -0
  164. package/cli/lint/entry-file.ts +54 -0
  165. package/cli/lint/env-in-config.ts +66 -0
  166. package/cli/lint/filename-matches-export.ts +63 -0
  167. package/cli/lint/index-file.ts +22 -0
  168. package/cli/lint/index.ts +47 -0
  169. package/cli/lint/is-reason.ts +6 -0
  170. package/cli/lint/manifest.ts +46 -0
  171. package/cli/lint/names.ts +59 -0
  172. package/cli/lint/no-commented-out-code.ts +105 -0
  173. package/cli/lint/no-mock-of-subject.ts +64 -0
  174. package/cli/lint/no-module-side-effects.ts +94 -0
  175. package/cli/lint/no-module-state.ts +58 -0
  176. package/cli/lint/no-side-effect-import.ts +17 -0
  177. package/cli/lint/no-v8-ignore.ts +18 -0
  178. package/cli/lint/script-rules.ts +9 -0
  179. package/cli/lint/skip-on-platform.constants.ts +8 -0
  180. package/cli/lint/skip-on-platform.ts +118 -0
  181. package/cli/lint/todo-needs-id.ts +63 -0
  182. package/cli/lint/types-file.ts +28 -0
  183. package/cli/lint-schema.ts +32 -0
  184. package/cli/magic/fixtures/magic-repo.ts +64 -0
  185. package/cli/magic/magic-hit.ts +2 -0
  186. package/cli/magic/magic-nodes.ts +44 -0
  187. package/cli/magic/magic-numbers.test.ts +99 -0
  188. package/cli/magic/magic-numbers.ts +117 -0
  189. package/cli/magic/magic-strings.test.ts +38 -0
  190. package/cli/magic/magic-strings.ts +81 -0
  191. package/cli/main.test.ts +853 -0
  192. package/cli/main.ts +292 -0
  193. package/cli/mutate-args.ts +79 -0
  194. package/cli/mutate-check.test.ts +147 -0
  195. package/cli/mutate-deps.ts +42 -0
  196. package/cli/mutate-edges.test.ts +150 -0
  197. package/cli/mutate-files.test.ts +100 -0
  198. package/cli/mutate-files.ts +31 -0
  199. package/cli/mutate-floors.test.ts +91 -0
  200. package/cli/mutate-floors.ts +104 -0
  201. package/cli/mutate-real-deps.ts +57 -0
  202. package/cli/mutate-real.test.ts +96 -0
  203. package/cli/mutate-report.ts +96 -0
  204. package/cli/mutate-run.ts +128 -0
  205. package/cli/mutate-shards.ts +64 -0
  206. package/cli/mutate.constants.ts +67 -0
  207. package/cli/mutate.test.ts +182 -0
  208. package/cli/mutate.ts +64 -0
  209. package/cli/nested-vitest-flags.ts +8 -0
  210. package/cli/nightly-config.constants.ts +14 -0
  211. package/cli/nightly-config.ts +62 -0
  212. package/cli/prompt.test.ts +202 -0
  213. package/cli/prompt.ts +1 -0
  214. package/cli/red-proof-edges.test.ts +78 -0
  215. package/cli/red-proof-failures.test.ts +82 -0
  216. package/cli/red-proof-tree.test.ts +157 -0
  217. package/cli/red-proof-verdict.test.ts +44 -0
  218. package/cli/red-proof.constants.ts +14 -0
  219. package/cli/red-proof.ts +202 -0
  220. package/cli/release/changelog-parse.ts +83 -0
  221. package/cli/release/changelog.test.ts +141 -0
  222. package/cli/release/changelog.ts +106 -0
  223. package/cli/release/gate-refusals.test.ts +85 -0
  224. package/cli/release/gate.test.ts +63 -0
  225. package/cli/release/gate.ts +70 -0
  226. package/cli/release/git.ts +66 -0
  227. package/cli/release/github.test.ts +128 -0
  228. package/cli/release/github.ts +46 -0
  229. package/cli/release/list-dir.test.ts +11 -0
  230. package/cli/release/list-dir.ts +6 -0
  231. package/cli/release/main.test.ts +48 -0
  232. package/cli/release/main.ts +51 -0
  233. package/cli/release/manifest.test.ts +24 -0
  234. package/cli/release/manifest.ts +34 -0
  235. package/cli/release/pack.test.ts +107 -0
  236. package/cli/release/pack.ts +76 -0
  237. package/cli/release/plan.ts +127 -0
  238. package/cli/release/publish-run.test.ts +98 -0
  239. package/cli/release/publish.test.ts +127 -0
  240. package/cli/release/publish.ts +125 -0
  241. package/cli/release/refuse.ts +5 -0
  242. package/cli/release/release-messages.constants.ts +96 -0
  243. package/cli/release/release-refusals.test.ts +149 -0
  244. package/cli/release/release.constants.ts +128 -0
  245. package/cli/release/release.test.ts +199 -0
  246. package/cli/release/release.ts +145 -0
  247. package/cli/release/version.test.ts +118 -0
  248. package/cli/release/version.ts +69 -0
  249. package/cli/release/wait.test.ts +101 -0
  250. package/cli/release/wait.ts +95 -0
  251. package/cli/script-diagnostics.test.ts +76 -0
  252. package/cli/script-diagnostics.ts +62 -0
  253. package/cli/scripts/board.ts +519 -0
  254. package/cli/scripts/browser.ts +994 -0
  255. package/cli/scripts/change-set.ts +105 -0
  256. package/cli/scripts/check-globals.ts +199 -0
  257. package/cli/scripts/ci-skips.constants.ts +17 -0
  258. package/cli/scripts/ci-skips.ts +118 -0
  259. package/cli/scripts/ci-wait-run.ts +28 -0
  260. package/cli/scripts/ci-wait.constants.ts +44 -0
  261. package/cli/scripts/ci-wait.ts +175 -0
  262. package/cli/scripts/ci.constants.ts +15 -0
  263. package/cli/scripts/ci.ts +208 -0
  264. package/cli/scripts/clean.ts +363 -0
  265. package/cli/scripts/comments-step.ts +15 -0
  266. package/cli/scripts/doctor.ts +828 -0
  267. package/cli/scripts/emit-agent-shell-tools.ts +91 -0
  268. package/cli/scripts/emit-mcp-policy.ts +402 -0
  269. package/cli/scripts/folder-map.ts +353 -0
  270. package/cli/scripts/gate-fat-tools.ts +1095 -0
  271. package/cli/scripts/gitignore-guard.ts +338 -0
  272. package/cli/scripts/google-workspace.ts +779 -0
  273. package/cli/scripts/hook-command.ts +72 -0
  274. package/cli/scripts/hook-input.ts +178 -0
  275. package/cli/scripts/hook-io.constants.ts +5 -0
  276. package/cli/scripts/hook-io.ts +46 -0
  277. package/cli/scripts/hooks-union.ts +469 -0
  278. package/cli/scripts/interactive.ts +30 -0
  279. package/cli/scripts/jev.ts +420 -0
  280. package/cli/scripts/job.ts +934 -0
  281. package/cli/scripts/jsonc.ts +9 -0
  282. package/cli/scripts/lessons.ts +564 -0
  283. package/cli/scripts/lint-baseline.ts +233 -0
  284. package/cli/scripts/lint-main.ts +24 -0
  285. package/cli/scripts/lint-skips.ts +139 -0
  286. package/cli/scripts/lint.constants.ts +6 -0
  287. package/cli/scripts/lint.ts +142 -0
  288. package/cli/scripts/listen.ts +962 -0
  289. package/cli/scripts/managed-text.ts +47 -0
  290. package/cli/scripts/orchestrate-parent.ts +1155 -0
  291. package/cli/scripts/parse-need.ts +81 -0
  292. package/cli/scripts/persist-handoff.ts +228 -0
  293. package/cli/scripts/persist-implementation.ts +276 -0
  294. package/cli/scripts/persist-plan.ts +888 -0
  295. package/cli/scripts/persist-roadmap.ts +478 -0
  296. package/cli/scripts/persist-tmp.ts +177 -0
  297. package/cli/scripts/persist.ts +54 -0
  298. package/cli/scripts/phase-files.ts +121 -0
  299. package/cli/scripts/prompt.ts +38 -0
  300. package/cli/scripts/purge-global-mcps.ts +313 -0
  301. package/cli/scripts/rejections-step.ts +37 -0
  302. package/cli/scripts/rtk-grok.ts +102 -0
  303. package/cli/scripts/scrub-spawn-prompt.ts +103 -0
  304. package/cli/scripts/scrub-text-lib.ts +58 -0
  305. package/cli/scripts/session-map.ts +69 -0
  306. package/cli/scripts/setup.ts +862 -0
  307. package/cli/scripts/skill-conflicts.ts +82 -0
  308. package/cli/scripts/speak.ts +370 -0
  309. package/cli/scripts/strategy.ts +168 -0
  310. package/cli/scripts/sync-global-settings.ts +316 -0
  311. package/cli/scripts/turn-steps.ts +249 -0
  312. package/cli/scripts/turn.ts +157 -0
  313. package/cli/scripts/typecheck-step.ts +43 -0
  314. package/cli/scripts/vscode-tasks.ts +175 -0
  315. package/cli/scripts/workspace-root.ts +160 -0
  316. package/cli/scripts/worktree.ts +806 -0
  317. package/cli/spawn.test.ts +34 -0
  318. package/cli/spawn.ts +27 -0
  319. package/cli/staged-change.constants.ts +11 -0
  320. package/cli/staged-change.ts +79 -0
  321. package/cli/stryker-preload.test.ts +50 -0
  322. package/cli/stryker-preload.ts +9 -0
  323. package/cli/sync/agents/frontmatter.test.ts +197 -0
  324. package/cli/sync/agents/frontmatter.ts +140 -0
  325. package/cli/sync/agents/hooks.test.ts +517 -0
  326. package/cli/sync/agents/hooks.ts +401 -0
  327. package/cli/sync/agents/rules.test.ts +229 -0
  328. package/cli/sync/agents/rules.ts +84 -0
  329. package/cli/sync/agents/sources.test.ts +375 -0
  330. package/cli/sync/agents/sources.ts +244 -0
  331. package/cli/sync/agents/subagents.test.ts +149 -0
  332. package/cli/sync/agents/subagents.ts +158 -0
  333. package/cli/sync/agents-text.test.ts +31 -0
  334. package/cli/sync/agents-text.ts +36 -0
  335. package/cli/sync/fixtures/copied.README.md +35 -0
  336. package/cli/sync/fixtures/copied.tgz +0 -0
  337. package/cli/sync/fixtures/maps/golden/INDEX.md +22 -0
  338. package/cli/sync/fixtures/maps-ws.tgz +0 -0
  339. package/cli/sync/git-files.test.ts +340 -0
  340. package/cli/sync/git-files.ts +197 -0
  341. package/cli/sync/git-hooks.test.ts +94 -0
  342. package/cli/sync/golden.test.ts +15 -0
  343. package/cli/sync/hooks-mode.test.ts +99 -0
  344. package/cli/sync/hosts.ts +28 -0
  345. package/cli/sync/knip-config.constants.ts +4 -0
  346. package/cli/sync/knip-config.test.ts +147 -0
  347. package/cli/sync/knip-config.ts +108 -0
  348. package/cli/sync/manifest.test.ts +78 -0
  349. package/cli/sync/manifest.ts +60 -0
  350. package/cli/sync/maps.test.ts +304 -0
  351. package/cli/sync/maps.ts +297 -0
  352. package/cli/sync/migrate.test.ts +2000 -0
  353. package/cli/sync/migrate.ts +1104 -0
  354. package/cli/sync/nightly-jobs.test.ts +63 -0
  355. package/cli/sync/nightly.constants.ts +48 -0
  356. package/cli/sync/nightly.test.ts +100 -0
  357. package/cli/sync/nightly.ts +75 -0
  358. package/cli/sync/outputs.test.ts +351 -0
  359. package/cli/sync/outputs.ts +168 -0
  360. package/cli/sync/oxlint-config.test.ts +178 -0
  361. package/cli/sync/oxlint-config.ts +168 -0
  362. package/cli/sync/release-jobs.test.ts +29 -0
  363. package/cli/sync/release-reports.test.ts +31 -0
  364. package/cli/sync/release-workflow.constants.ts +61 -0
  365. package/cli/sync/release-workflow.test.ts +192 -0
  366. package/cli/sync/release-workflow.ts +86 -0
  367. package/cli/sync/release.constants.ts +93 -0
  368. package/cli/sync/release.test.ts +187 -0
  369. package/cli/sync/release.ts +112 -0
  370. package/cli/sync/repo-dir.test.ts +371 -0
  371. package/cli/sync/repo-dir.ts +205 -0
  372. package/cli/sync/standards.constants.ts +8 -0
  373. package/cli/sync/standards.test.ts +206 -0
  374. package/cli/sync/standards.ts +179 -0
  375. package/cli/sync/stock-history.json +2796 -0
  376. package/cli/sync/stock-history.test.ts +66 -0
  377. package/cli/sync/stock-history.ts +84 -0
  378. package/cli/sync/stock.test.ts +47 -0
  379. package/cli/sync/stock.ts +24 -0
  380. package/cli/sync/trees.test.ts +621 -0
  381. package/cli/sync/trees.ts +280 -0
  382. package/cli/sync/validate.test.ts +299 -0
  383. package/cli/sync/validate.ts +402 -0
  384. package/cli/sync/workspace.test.ts +180 -0
  385. package/cli/sync/workspace.ts +138 -0
  386. package/cli/sync/write.test.ts +341 -0
  387. package/cli/sync/write.ts +231 -0
  388. package/cli/sync.test.ts +1495 -0
  389. package/cli/sync.ts +360 -0
  390. package/cli/tdd-scope.test.ts +36 -0
  391. package/cli/tdd-scope.ts +41 -0
  392. package/cli/test-files.ts +16 -0
  393. package/cli/ts-program.ts +69 -0
  394. package/cli/tsconfig.constants.ts +8 -0
  395. package/cli/workerstack-config.types.ts +21 -0
  396. package/dist/cli/env.d.ts +10 -0
  397. package/dist/cli/env.js +64 -0
  398. package/dist/presets/temp-root-env.d.ts +10 -0
  399. package/dist/presets/temp-root-env.js +20 -0
  400. package/dist/presets/temp-root.constants.d.ts +6 -0
  401. package/dist/presets/temp-root.constants.js +6 -0
  402. package/dist/presets/temp-root.d.ts +2 -0
  403. package/dist/presets/temp-root.js +60 -0
  404. package/dist/presets/vitest.constants.d.ts +16 -0
  405. package/dist/presets/vitest.constants.js +16 -0
  406. package/dist/presets/vitest.d.ts +11 -0
  407. package/dist/presets/vitest.js +67 -0
  408. package/dist/presets/workers-plugin.d.ts +10 -0
  409. package/dist/presets/workers-plugin.js +54 -0
  410. package/package.json +141 -4
  411. package/references/INDEX.md +100 -0
  412. package/references/browser.md +66 -0
  413. package/references/cascade.md +15 -0
  414. package/references/clasp.md +21 -0
  415. package/references/cloudflare.md +36 -0
  416. package/references/google-workspace.md +79 -0
  417. package/references/hooks.md +103 -0
  418. package/references/implementation-format.md +119 -0
  419. package/references/jev.md +56 -0
  420. package/references/lessons.md +73 -0
  421. package/references/listen.md +80 -0
  422. package/references/map.md +107 -0
  423. package/references/output.md +47 -0
  424. package/references/plan-format.md +264 -0
  425. package/references/review-loop.md +48 -0
  426. package/references/roadmap-format.md +104 -0
  427. package/references/rtk.md +27 -0
  428. package/references/scope.md +76 -0
  429. package/references/setup.md +42 -0
  430. package/references/speak.md +59 -0
  431. package/tsconfig/base.json +28 -0
  432. package/tsconfig/bun.d.ts +1 -0
  433. package/tsconfig/bun.json +8 -0
  434. package/tsconfig/reset.d.ts +1 -0
  435. package/tsconfig/strict.json +12 -0
  436. package/tsconfig/web.json +7 -0
  437. package/tsconfig/worker.d.ts +1 -0
  438. package/tsconfig/worker.json +8 -0
@@ -0,0 +1,862 @@
1
+ #!/usr/bin/env bun
2
+ /** Handheld machine setup for optional repo integrations. Secrets stay in ~/.workerstack/. */
3
+ import fs from "node:fs";
4
+ import os from "node:os";
5
+ import path from "node:path";
6
+ import { isInteractive as ttyInteractive } from "./interactive.ts";
7
+ import { enabledSkills } from "../config.ts";
8
+ import {
9
+ CHROMIUM_BINS,
10
+ installHints,
11
+ jevDoctorStatus,
12
+ speakDoctorStatus,
13
+ whichBin,
14
+ workspaceDoctorStatus,
15
+ } from "./doctor.ts";
16
+ import { isRecord } from "./hook-input.ts";
17
+ import type { SpawnSyncOpts } from "./google-workspace.ts";
18
+ import type { WhichFn } from "./listen.ts";
19
+ import type { FetchResult } from "./speak.ts";
20
+ import { readEnv } from "../env.ts";
21
+ import type { Env } from "../env.ts";
22
+
23
+ export type Flags = Record<string, string>;
24
+
25
+ export interface Status {
26
+ ok: boolean;
27
+ extra: string;
28
+ }
29
+
30
+ export type Statuses = Record<string, Status>;
31
+
32
+ export type TargetStatuses = Record<"speak" | "jev" | "listen" | "google-workspace" | "browser", Status>;
33
+
34
+ /**
35
+ * Prompt and log hooks. A plain log-only io (tests, non-TTY) leaves the prompts unset;
36
+ * select and password are set on every interactive io, so callers use `!` on them.
37
+ */
38
+ export interface SetupIo {
39
+ log?(s: string): unknown;
40
+ error?(s: string): unknown;
41
+ confirm?(opts: { message: string; initialValue?: boolean }): Promise<boolean>;
42
+ select?(opts: {
43
+ message: string;
44
+ options: { value: string; label: string }[];
45
+ initialValue: string;
46
+ }): Promise<string>;
47
+ text?(opts: { message: string; initialValue?: string; defaultValue?: string }): Promise<string>;
48
+ password?(opts: { message: string }): Promise<string>;
49
+ }
50
+
51
+ export type SetupFetch = (
52
+ url: string,
53
+ init: {
54
+ method?: string | undefined;
55
+ headers?: Record<string, string> | undefined;
56
+ body?: string | FormData | undefined;
57
+ redirect?: "follow" | "manual" | "error" | undefined;
58
+ },
59
+ ) => Promise<FetchResult>;
60
+
61
+ export type SetupSpawnSync = (cmd: string, args: string[], opts: SpawnSyncOpts) => unknown;
62
+
63
+ export interface SetupCtx {
64
+ flags?: Flags | undefined;
65
+ env?: Env | undefined;
66
+ home?: string | undefined;
67
+ io?: SetupIo | null | undefined;
68
+ fetchFn?: SetupFetch | undefined;
69
+ openUrlFn?: ((url: string) => unknown) | undefined;
70
+ platform?: NodeJS.Platform | undefined;
71
+ spawnSyncFn?: SetupSpawnSync | undefined;
72
+ whichFn?: WhichFn | undefined;
73
+ pathEnv?: string | undefined;
74
+ interactive?: boolean | undefined;
75
+ loginFn?: ((dest: string, home: string) => Promise<unknown>) | undefined;
76
+ }
77
+
78
+ export interface SetupMainOpts extends Omit<SetupCtx, "flags"> {
79
+ cwd?: string;
80
+ stdin?: { isTTY?: boolean };
81
+ stdout?: { isTTY?: boolean };
82
+ }
83
+
84
+ export interface SpeakPatch {
85
+ provider?: string;
86
+ elevenlabs?: { apiKey: string };
87
+ openai?: { apiKey: string };
88
+ listen?: { device: string };
89
+ }
90
+
91
+ function rec(v: unknown): Record<string, unknown> {
92
+ return isRecord(v) ? v : {};
93
+ }
94
+
95
+ function errMessage(e: unknown): string {
96
+ const m = isRecord(e) ? e.message : undefined;
97
+ return String(m || e);
98
+ }
99
+
100
+ export const TARGETS: string[] = ["speak", "listen", "google-workspace", "browser", "jev"];
101
+ export const USAGE = `usage: setup [speak|listen|google-workspace|browser|jev]
102
+ speak --elevenlabs-key KEY | --openai-key KEY [--provider elevenlabs|openai] [--skip-verify]
103
+ listen [--openai-key KEY] [--device ID] [--skip-download]
104
+ google-workspace [--creds PATH] [--account NAME] [--project ID]
105
+ browser (engine install hints; no secrets)
106
+ jev --api-key KEY
107
+ --redo redo a target that is already ok
108
+
109
+ TTY menu shows ok/missing. Missing bins: print OS command, then re-check PATH. Never installs.
110
+ Already ok: confirm redo (TTY) unless --redo. Ends with one status line per target (not a README).
111
+ Secrets: ~/.workerstack/ (not git). Env still wins over the file. Setup does not export keys.
112
+ `;
113
+
114
+ export const ELEVENLABS_KEY_URL = "https://elevenlabs.io/app/settings/api-keys";
115
+ export const OPENAI_KEY_URL = "https://platform.openai.com/api-keys";
116
+
117
+ export function parseArgv(argv: unknown): { _: string[]; flags: Flags } {
118
+ const out: { _: string[]; flags: Flags } = { _: [], flags: {} };
119
+ const args: unknown[] = Array.isArray(argv) ? argv : [];
120
+ for (let i = 0; i < args.length; i++) {
121
+ const a = args[i];
122
+ if (a === "--") {
123
+ out._.push(...args.slice(i + 1).map(String));
124
+ break;
125
+ }
126
+ if (typeof a === "string" && a.startsWith("--")) {
127
+ const eq = a.indexOf("=");
128
+ if (eq > 2) {
129
+ out.flags[a.slice(2, eq)] = a.slice(eq + 1);
130
+ continue;
131
+ }
132
+ const key = a.slice(2);
133
+ const nxt = args[i + 1];
134
+ if (nxt && !String(nxt).startsWith("--")) {
135
+ out.flags[key] = String(nxt);
136
+ i += 1;
137
+ } else out.flags[key] = "1";
138
+ } else out._.push(String(a));
139
+ }
140
+ return out;
141
+ }
142
+
143
+ export function normalizeTarget(raw: unknown): string {
144
+ const s = String(raw || "")
145
+ .trim()
146
+ .toLowerCase();
147
+ if (!s) return "";
148
+ if (s === "gw" || s === "google" || s === "workspace" || s === "google-workspace") {
149
+ return "google-workspace";
150
+ }
151
+ if (TARGETS.includes(s)) return s;
152
+ return "";
153
+ }
154
+
155
+ function str(v: unknown, fallback = ""): string {
156
+ const s = String(v ?? "").trim();
157
+ return s || fallback;
158
+ }
159
+
160
+ export function stateHome(home: string = os.homedir()): string {
161
+ const d = path.join(home, ".workerstack");
162
+ fs.mkdirSync(d, { recursive: true, mode: 0o700 });
163
+ try {
164
+ fs.chmodSync(d, 0o700);
165
+ } catch {
166
+ /* win */
167
+ }
168
+ return d;
169
+ }
170
+
171
+ export function writeSecretJson(p: string, obj: unknown): string {
172
+ fs.mkdirSync(path.dirname(p), { recursive: true, mode: 0o700 });
173
+ fs.writeFileSync(p, JSON.stringify(obj, null, 2) + "\n", { mode: 0o600 });
174
+ try {
175
+ fs.chmodSync(p, 0o600);
176
+ } catch {
177
+ /* win */
178
+ }
179
+ return p;
180
+ }
181
+
182
+ function readJson(p: string): Record<string, unknown> {
183
+ try {
184
+ const j: unknown = JSON.parse(fs.readFileSync(p, "utf8"));
185
+ return rec(j);
186
+ } catch {
187
+ return {};
188
+ }
189
+ }
190
+
191
+ export function speakJsonPath(home: string): string {
192
+ return path.join(home, ".workerstack", "speak.json");
193
+ }
194
+
195
+ export function mergeSpeakJson(home: string, patch: SpeakPatch): string {
196
+ stateHome(home);
197
+ const p = speakJsonPath(home);
198
+ const cur = readJson(p);
199
+ const next: Record<string, unknown> = { ...cur, ...patch };
200
+ if (patch.elevenlabs) {
201
+ next.elevenlabs = { ...rec(cur.elevenlabs), ...patch.elevenlabs };
202
+ }
203
+ if (patch.openai) {
204
+ next.openai = { ...rec(cur.openai), ...patch.openai };
205
+ }
206
+ if (patch.listen) {
207
+ next.listen = { ...rec(cur.listen), ...patch.listen };
208
+ }
209
+ writeSecretJson(p, next);
210
+ return p;
211
+ }
212
+
213
+ export function parseDesktopOAuth(raw: unknown): Record<string, unknown> {
214
+ const parsed: unknown = typeof raw === "string" ? JSON.parse(raw) : raw;
215
+ const j = rec(parsed);
216
+ const inst = rec(j.installed || j.web);
217
+ if (!inst.client_id) {
218
+ throw new Error("OAuth JSON needs installed.client_id (Desktop app download)");
219
+ }
220
+ if (!j.installed) {
221
+ throw new Error("OAuth JSON must be a Desktop client (installed), not a Web client");
222
+ }
223
+ return j;
224
+ }
225
+
226
+ export function loadOAuthInput(s: unknown): Record<string, unknown> {
227
+ const t = String(s || "").trim();
228
+ if (!t) throw new Error("OAuth JSON path or JSON body required");
229
+ if (t.startsWith("{")) return parseDesktopOAuth(t);
230
+ if (!fs.existsSync(t) || !fs.statSync(t).isFile()) {
231
+ throw new Error(`OAuth JSON not found: ${t}`);
232
+ }
233
+ return parseDesktopOAuth(fs.readFileSync(t, "utf8"));
234
+ }
235
+
236
+ export function installOAuthJson({ src, dest }: { src: string; dest: string }): string {
237
+ const j = loadOAuthInput(src);
238
+ writeSecretJson(dest, j);
239
+ return dest;
240
+ }
241
+
242
+ export function findDownloadedOAuthJson(home: string): string {
243
+ const dirs = [path.join(home, "Downloads"), path.join(home, "downloads")];
244
+ const hits: { p: string; m: number }[] = [];
245
+ for (const dir of dirs) {
246
+ let ents: string[];
247
+ try {
248
+ ents = fs.readdirSync(dir);
249
+ } catch {
250
+ continue;
251
+ }
252
+ for (const name of ents) {
253
+ if (!/client_secret.*\.json$/i.test(name) && name.toLowerCase() !== "google-oauth.json") {
254
+ continue;
255
+ }
256
+ const p = path.join(dir, name);
257
+ try {
258
+ const st = fs.statSync(p);
259
+ if (st.isFile()) hits.push({ p, m: st.mtimeMs });
260
+ } catch {
261
+ /* skip */
262
+ }
263
+ }
264
+ }
265
+ hits.sort((a, b) => b.m - a.m);
266
+ return hits[0] ? hits[0].p : "";
267
+ }
268
+
269
+ export function workspaceConsoleUrls(
270
+ projectId: unknown,
271
+ ): Record<"projectCreate" | "branding" | "enableApis" | "clientCreate", string> {
272
+ const p = str(projectId);
273
+ const enc = encodeURIComponent(p);
274
+ return {
275
+ projectCreate: "https://console.cloud.google.com/projectcreate",
276
+ branding: p
277
+ ? `https://console.cloud.google.com/auth/overview/create?project=${enc}`
278
+ : "https://console.cloud.google.com/auth/overview/create",
279
+ enableApis: p
280
+ ? `https://console.cloud.google.com/flows/enableapi?apiid=calendar-json.googleapis.com,gmail.googleapis.com,sheets.googleapis.com,docs.googleapis.com,drive.googleapis.com&project=${enc}`
281
+ : "https://console.cloud.google.com/flows/enableapi?apiid=calendar-json.googleapis.com,gmail.googleapis.com,sheets.googleapis.com,docs.googleapis.com,drive.googleapis.com",
282
+ clientCreate: p
283
+ ? `https://console.cloud.google.com/auth/clients/create?project=${enc}`
284
+ : "https://console.cloud.google.com/auth/clients/create",
285
+ };
286
+ }
287
+
288
+ export async function openUrl(
289
+ url: string,
290
+ {
291
+ platform = process.platform,
292
+ spawnSyncFn,
293
+ openUrlFn,
294
+ }: {
295
+ platform?: NodeJS.Platform | undefined;
296
+ spawnSyncFn?: SetupSpawnSync | undefined;
297
+ openUrlFn?: ((url: string) => unknown) | undefined;
298
+ } = {},
299
+ ): Promise<void> {
300
+ if (typeof openUrlFn === "function") {
301
+ openUrlFn(url);
302
+ return;
303
+ }
304
+ const { openUrl: openWorkspaceUrl } = await import("./google-workspace.ts");
305
+ openWorkspaceUrl(url, spawnSyncFn ? { platform, spawnSyncFn } : { platform });
306
+ }
307
+
308
+ function flagOn(flags: Flags | undefined, key: string): boolean {
309
+ const v = flags && flags[key];
310
+ return v === "1" || v === "true";
311
+ }
312
+
313
+ export function lookupChromium(
314
+ whichFn: WhichFn = whichBin,
315
+ pathEnv: string | undefined = readEnv().PATH,
316
+ platform: NodeJS.Platform = process.platform,
317
+ ): string {
318
+ for (const n of CHROMIUM_BINS) {
319
+ const hit = whichFn(n, pathEnv, platform);
320
+ if (hit) return hit;
321
+ }
322
+ return "";
323
+ }
324
+
325
+ export function browserEngineStatus(lp: string, cr: string, platform: NodeJS.Platform = process.platform): Status {
326
+ const have = [lp && "lightpanda", cr && "chromium"].filter((x): x is string => Boolean(x));
327
+ if (lp && cr) return { ok: true, extra: have.join(",") };
328
+ if (platform === "win32" && cr) {
329
+ return { ok: true, extra: have.concat(["need lightpanda in WSL"]).join(",") };
330
+ }
331
+ const need: string[] = [];
332
+ if (!lp) need.push("need lightpanda");
333
+ if (!cr) need.push("need chromium");
334
+ return { ok: false, extra: have.concat(need).join(",") };
335
+ }
336
+
337
+ export function setupTargets(packs: string[] = []): string[] {
338
+ const on = new Set(packs);
339
+ return TARGETS.filter((target) => target === "browser" || target === "jev" || on.has(target));
340
+ }
341
+
342
+ export async function targetStatuses({
343
+ home = os.homedir(),
344
+ env = readEnv(),
345
+ whichFn = whichBin,
346
+ pathEnv,
347
+ platform = process.platform,
348
+ targets = TARGETS,
349
+ }: {
350
+ home?: string | undefined;
351
+ env?: Env | undefined;
352
+ whichFn?: WhichFn | undefined;
353
+ pathEnv?: string | undefined;
354
+ platform?: NodeJS.Platform | undefined;
355
+ targets?: string[] | undefined;
356
+ } = {}): Promise<TargetStatuses> {
357
+ const pEnv = pathEnv || env.PATH || readEnv().PATH;
358
+ const speak = speakDoctorStatus({ env, home });
359
+ const jev = jevDoctorStatus({ env, home });
360
+ const ws = workspaceDoctorStatus({ home });
361
+ const lp = whichFn("lightpanda", pEnv, platform);
362
+ const cr = lookupChromium(whichFn, pEnv, platform);
363
+ let listen: Status = { ok: false, extra: "" };
364
+ if (targets.includes("listen")) {
365
+ const { loadCfg, probeStt, findWhisperBin } = await import("./listen.ts");
366
+ const cfg = loadCfg({ env, home });
367
+ const stt = probeStt({ cfg, whichFn, pathEnv: pEnv, platform, home, cwd: "" });
368
+ const ffmpeg = whichFn("ffmpeg", pEnv, platform);
369
+ const whisper = findWhisperBin(whichFn, pEnv, platform);
370
+ const listenBits = [stt && stt.kind, ffmpeg && "ffmpeg", whisper && "whisper"].filter((x): x is string =>
371
+ Boolean(x),
372
+ );
373
+ listen = { ok: Boolean(stt), extra: listenBits.join(",") };
374
+ }
375
+ return {
376
+ speak: { ok: speak.ok, extra: speak.extra },
377
+ jev: { ok: jev.ok, extra: jev.extra },
378
+ listen,
379
+ "google-workspace": { ok: ws.ok, extra: ws.extra },
380
+ browser: browserEngineStatus(lp, cr, platform),
381
+ };
382
+ }
383
+
384
+ export function formatStatus(statuses: Statuses | null | undefined, targets: string[] = TARGETS): string {
385
+ return targets
386
+ .map((t) => {
387
+ const st = (statuses && statuses[t]) || { ok: false, extra: "" };
388
+ const b = st.ok ? "ok" : "missing";
389
+ return st.extra ? `${t} ${b} ${st.extra}` : `${t} ${b}`;
390
+ })
391
+ .join("\n");
392
+ }
393
+
394
+ export function menuOptions(
395
+ statuses: Statuses | null | undefined,
396
+ targets: string[] = TARGETS,
397
+ ): { value: string; label: string }[] {
398
+ const base = [
399
+ { value: "speak", label: "speak (ElevenLabs / OpenAI TTS)" },
400
+ { value: "listen", label: "listen (STT)" },
401
+ { value: "google-workspace", label: "google-workspace" },
402
+ { value: "browser", label: "browser (PATH engines)" },
403
+ { value: "jev", label: "jev (TypeSafe research decision)" },
404
+ ].filter((o) => targets.includes(o.value));
405
+ return base.map((o) => {
406
+ const st = (statuses && statuses[o.value]) || { ok: false, extra: "" };
407
+ const tag = st.ok ? (st.extra ? `ok ${st.extra}` : "ok") : st.extra ? `missing ${st.extra}` : "missing";
408
+ return { value: o.value, label: `${o.label} ${tag}` };
409
+ });
410
+ }
411
+
412
+ export function firstMissing(statuses: Statuses | null | undefined, targets: string[] = TARGETS): string {
413
+ for (const t of targets) {
414
+ if (!statuses || !statuses[t] || !statuses[t].ok) return t;
415
+ }
416
+ return targets[0] || "browser";
417
+ }
418
+
419
+ export async function reprobeHint({
420
+ id,
421
+ found,
422
+ probe,
423
+ platform = process.platform,
424
+ io,
425
+ interactive = false,
426
+ }: {
427
+ id: string;
428
+ found?: string | undefined;
429
+ probe?: (() => string) | undefined;
430
+ platform?: NodeJS.Platform | undefined;
431
+ io?: SetupIo | null | undefined;
432
+ interactive?: boolean | undefined;
433
+ }): Promise<string> {
434
+ let hit = found ?? "";
435
+ if (hit) {
436
+ logLine(io, `${id} ok ${hit}`);
437
+ return hit;
438
+ }
439
+ logLine(io, `${id} missing. print-only; not installed:`);
440
+ for (const h of installHints(id, platform)) logLine(io, h);
441
+ if (interactive && io && typeof io.confirm === "function") {
442
+ const again = await io.confirm({
443
+ message: `Installed ${id}? Re-check PATH`,
444
+ initialValue: false,
445
+ });
446
+ if (again) {
447
+ hit = typeof probe === "function" ? probe() : "";
448
+ if (hit) logLine(io, `${id} ok ${hit}`);
449
+ else logLine(io, `${id} still missing`);
450
+ }
451
+ }
452
+ return hit;
453
+ }
454
+
455
+ function logLine(io: SetupIo | null | undefined, s: string): void {
456
+ if (io && typeof io.log === "function") io.log(s);
457
+ else process.stdout.write(`${s}\n`);
458
+ }
459
+
460
+ function errLine(io: SetupIo | null | undefined, s: string): void {
461
+ if (io && typeof io.error === "function") io.error(s);
462
+ else process.stderr.write(`${s}\n`);
463
+ }
464
+
465
+ async function defaultIo(): Promise<SetupIo> {
466
+ const { confirm, password, select, text } = await import("./prompt.ts");
467
+ return {
468
+ confirm,
469
+ password,
470
+ select,
471
+ text,
472
+ log: (s: string) => process.stdout.write(`${s}\n`),
473
+ error: (s: string) => process.stderr.write(`${s}\n`),
474
+ };
475
+ }
476
+
477
+ export function oauthWritePath(home: string, account: unknown): string {
478
+ return path.join(home, ".workerstack", "workspace", str(account, "default"), "google-oauth.json");
479
+ }
480
+
481
+ export async function setupSpeak({
482
+ flags = {},
483
+ home = os.homedir(),
484
+ io,
485
+ fetchFn,
486
+ openUrlFn,
487
+ platform = process.platform,
488
+ spawnSyncFn,
489
+ interactive = false,
490
+ }: SetupCtx = {}): Promise<{ ok: true; dest: string; verified: boolean; who?: string }> {
491
+ const { loadCfg: loadSpeakCfg, whoami: speakWhoami } = await import("./speak.ts");
492
+ let elevenKey = str(flags["elevenlabs-key"] || flags.elevenlabs);
493
+ let openaiKey = str(flags["openai-key"] || flags.openai);
494
+ let provider = str(flags.provider).toLowerCase();
495
+ const skipVerify = flagOn(flags, "skip-verify");
496
+
497
+ if (interactive && io && !elevenKey && !openaiKey) {
498
+ provider = str(
499
+ await io.select!({
500
+ message: "Speak provider",
501
+ options: [
502
+ { value: "elevenlabs", label: "ElevenLabs" },
503
+ { value: "openai", label: "OpenAI" },
504
+ { value: "both", label: "both" },
505
+ ],
506
+ initialValue: provider === "openai" ? "openai" : "elevenlabs",
507
+ }),
508
+ );
509
+ }
510
+
511
+ const askEleven = provider === "elevenlabs" || provider === "both" || (!provider && !openaiKey);
512
+ const askOpenAi = provider === "openai" || provider === "both";
513
+
514
+ if (interactive && io) {
515
+ if (askEleven && !elevenKey) {
516
+ logLine(io, `Open: ${ELEVENLABS_KEY_URL}`);
517
+ await openUrl(ELEVENLABS_KEY_URL, { platform, spawnSyncFn, openUrlFn });
518
+ elevenKey = str(await io.password!({ message: "ElevenLabs API key" }));
519
+ }
520
+ if (askOpenAi && !openaiKey) {
521
+ logLine(io, `Open: ${OPENAI_KEY_URL}`);
522
+ await openUrl(OPENAI_KEY_URL, { platform, spawnSyncFn, openUrlFn });
523
+ openaiKey = str(await io.password!({ message: "OpenAI API key" }));
524
+ }
525
+ }
526
+
527
+ const patch: SpeakPatch = {};
528
+ if (provider && provider !== "both") patch.provider = provider;
529
+ if (elevenKey) patch.elevenlabs = { apiKey: elevenKey };
530
+ if (openaiKey) patch.openai = { apiKey: openaiKey };
531
+
532
+ if (!patch.elevenlabs && !patch.openai) {
533
+ throw new Error(
534
+ interactive
535
+ ? "need an ElevenLabs or OpenAI key"
536
+ : "need --elevenlabs-key or --openai-key (or TTY: bun repo setup speak)",
537
+ );
538
+ }
539
+
540
+ const dest = mergeSpeakJson(home, patch);
541
+ logLine(io, `wrote ${dest}`);
542
+
543
+ if (skipVerify) return { ok: true, dest, verified: false };
544
+
545
+ const cfg = loadSpeakCfg({ env: {}, home });
546
+ const who = await speakWhoami({
547
+ provider: provider === "both" || !provider ? undefined : provider,
548
+ cfg,
549
+ fetchFn,
550
+ });
551
+ logLine(io, who);
552
+ return { ok: true, dest, verified: true, who };
553
+ }
554
+
555
+ export async function setupListen({
556
+ flags = {},
557
+ env = readEnv(),
558
+ home = os.homedir(),
559
+ io,
560
+ fetchFn,
561
+ openUrlFn,
562
+ platform = process.platform,
563
+ spawnSyncFn,
564
+ whichFn = whichBin,
565
+ pathEnv = readEnv().PATH,
566
+ interactive = false,
567
+ }: SetupCtx = {}): Promise<{ ok: true; stt: Awaited<ReturnType<typeof import("./listen.ts").probeStt>> }> {
568
+ const { downloadGgml, findWhisperBin, loadCfg: loadListenCfg, probeStt } = await import("./listen.ts");
569
+ const ffmpegBin = await reprobeHint({
570
+ id: "ffmpeg",
571
+ found: whichFn("ffmpeg", pathEnv, platform),
572
+ probe: () => whichFn("ffmpeg", pathEnv, platform),
573
+ platform,
574
+ io,
575
+ interactive,
576
+ });
577
+ const whisperBin = await reprobeHint({
578
+ id: "whisper-cli",
579
+ found: findWhisperBin(whichFn, pathEnv, platform),
580
+ probe: () => findWhisperBin(whichFn, pathEnv, platform),
581
+ platform,
582
+ io,
583
+ interactive,
584
+ });
585
+
586
+ const skipDownload = flagOn(flags, "skip-download");
587
+ if (whisperBin && !skipDownload) {
588
+ let go = true;
589
+ if (interactive && io && typeof io.confirm === "function") {
590
+ go = await io.confirm({
591
+ message: "Download ggml-base.en.bin (~142MB) into ~/.workerstack/whisper/?",
592
+ initialValue: true,
593
+ });
594
+ }
595
+ if (go) {
596
+ const model = await downloadGgml({ home, fetchFn, log: { error: (s: string) => logLine(io, s) } });
597
+ logLine(io, `model ${model}`);
598
+ }
599
+ }
600
+
601
+ const openai = str(flags["openai-key"] || flags.openai);
602
+ let openaiKey = openai;
603
+ const cfgNow = loadListenCfg({ env, home });
604
+ if (!whisperBin && !cfgNow.apiKey && !openaiKey) {
605
+ if (interactive && io) {
606
+ logLine(io, `Local STT is unavailable. OpenAI Whisper fallback: ${OPENAI_KEY_URL}`);
607
+ await openUrl(OPENAI_KEY_URL, { platform, spawnSyncFn, openUrlFn });
608
+ openaiKey = str(await io.password!({ message: "OpenAI API key (fallback STT)" }));
609
+ } else {
610
+ throw new Error("need whisper-cli or --openai-key (or TTY: bun repo setup listen)");
611
+ }
612
+ }
613
+
614
+ const patch: SpeakPatch = {};
615
+ if (openaiKey) patch.openai = { apiKey: openaiKey };
616
+ const device = str(flags.device);
617
+ if (device) patch.listen = { device };
618
+ if (interactive && io && ffmpegBin && !device && typeof io.text === "function") {
619
+ const d = str(
620
+ await io.text({
621
+ message: "Mic device id (blank = default; Windows needs an id from listen devices)",
622
+ defaultValue: "",
623
+ }),
624
+ );
625
+ if (d) patch.listen = { ...(patch.listen || {}), device: d };
626
+ }
627
+ if (Object.keys(patch).length) {
628
+ const dest = mergeSpeakJson(home, patch);
629
+ logLine(io, `wrote ${dest}`);
630
+ }
631
+
632
+ const cfg = loadListenCfg({ env: {}, home });
633
+ const stt = probeStt({ cfg, whichFn, pathEnv, platform, home, cwd: "" });
634
+ if (stt && stt.kind === "local") {
635
+ logLine(io, `stt=local bin=${stt.bin} model=${stt.model}`);
636
+ } else if (stt && stt.kind === "openai") {
637
+ logLine(io, `stt=openai model=${stt.model}`);
638
+ } else {
639
+ logLine(io, "stt=none");
640
+ }
641
+ return { ok: true, stt };
642
+ }
643
+
644
+ export async function setupGoogleWorkspace({
645
+ flags = {},
646
+ home = os.homedir(),
647
+ io,
648
+ openUrlFn,
649
+ platform = process.platform,
650
+ spawnSyncFn,
651
+ interactive = false,
652
+ loginFn,
653
+ }: SetupCtx = {}): Promise<{ ok: true; dest: string; tokens: string }> {
654
+ const { loginLoopback, setAccount, tokenStorePath } = await import("./google-workspace.ts");
655
+ const account = str(flags.account, "default");
656
+ setAccount(account);
657
+ stateHome(home);
658
+ const dest = oauthWritePath(home, account);
659
+ const credsFlag = str(flags.creds || flags.json);
660
+ let projectId = str(flags.project);
661
+
662
+ if (!credsFlag && interactive && io) {
663
+ const urls0 = workspaceConsoleUrls(projectId);
664
+ logLine(io, "Each person uses their own GCP Desktop OAuth client. Do not share JSON or tokens.");
665
+ logLine(io, `Open: ${urls0.projectCreate}`);
666
+ await openUrl(urls0.projectCreate, { platform, spawnSyncFn, openUrlFn });
667
+ if (typeof io.confirm === "function") {
668
+ await io.confirm({ message: "GCP project created? Continue", initialValue: true });
669
+ }
670
+ if (typeof io.text === "function") {
671
+ projectId = str(
672
+ await io.text({
673
+ message: "Project ID (from the console URL ?project=)",
674
+ initialValue: projectId,
675
+ }),
676
+ projectId,
677
+ );
678
+ }
679
+ const urls = workspaceConsoleUrls(projectId);
680
+ logLine(io, "Consent: Internal on paid Workspace, else Testing + add your email as a test user.");
681
+ logLine(io, `Open: ${urls.branding}`);
682
+ await openUrl(urls.branding, { platform, spawnSyncFn, openUrlFn });
683
+ if (typeof io.confirm === "function") {
684
+ await io.confirm({ message: "Branding / audience done? Continue", initialValue: true });
685
+ }
686
+ logLine(io, `Open: ${urls.enableApis}`);
687
+ await openUrl(urls.enableApis, { platform, spawnSyncFn, openUrlFn });
688
+ if (typeof io.confirm === "function") {
689
+ await io.confirm({ message: "APIs enabled? Continue", initialValue: true });
690
+ }
691
+ logLine(io, "Create OAuth client: application type Desktop app → Download JSON.");
692
+ logLine(io, `Open: ${urls.clientCreate}`);
693
+ await openUrl(urls.clientCreate, { platform, spawnSyncFn, openUrlFn });
694
+ if (typeof io.confirm === "function") {
695
+ await io.confirm({ message: "JSON downloaded? Continue", initialValue: true });
696
+ }
697
+ const found = findDownloadedOAuthJson(home);
698
+ const src = str(
699
+ typeof io.text === "function"
700
+ ? await io.text({
701
+ message: "Path to Desktop OAuth JSON (or paste the JSON)",
702
+ initialValue: found,
703
+ defaultValue: found,
704
+ })
705
+ : found,
706
+ );
707
+ if (!src) throw new Error("need path to Desktop OAuth JSON");
708
+ installOAuthJson({ src, dest });
709
+ } else if (credsFlag) {
710
+ installOAuthJson({ src: credsFlag, dest });
711
+ } else if (!fs.existsSync(dest)) {
712
+ throw new Error(`need --creds PATH (or TTY: bun repo setup google-workspace)`);
713
+ }
714
+
715
+ logLine(io, `oauth ${dest}`);
716
+ const login = loginFn || loginLoopback;
717
+ await login(dest, home);
718
+ const tokens = tokenStorePath(home);
719
+ logLine(io, `tokens ${tokens}`);
720
+ return { ok: true, dest, tokens };
721
+ }
722
+
723
+ export async function setupBrowser({
724
+ io,
725
+ platform = process.platform,
726
+ pathEnv = readEnv().PATH,
727
+ whichFn = whichBin,
728
+ interactive = false,
729
+ }: SetupCtx = {}): Promise<{ ok: boolean; lightpanda: string; chromium: string }> {
730
+ const lp = await reprobeHint({
731
+ id: "lightpanda",
732
+ found: whichFn("lightpanda", pathEnv, platform),
733
+ probe: () => whichFn("lightpanda", pathEnv, platform),
734
+ platform,
735
+ io,
736
+ interactive,
737
+ });
738
+ const chromium = await reprobeHint({
739
+ id: "chromium",
740
+ found: lookupChromium(whichFn, pathEnv, platform),
741
+ probe: () => lookupChromium(whichFn, pathEnv, platform),
742
+ platform,
743
+ io,
744
+ interactive,
745
+ });
746
+ const st = browserEngineStatus(lp, chromium, platform);
747
+ logLine(io, "session: .tmp/repo/browser/");
748
+ if (lp && chromium) {
749
+ logLine(io, "browser ok lightpanda (default) + chromium (fallback)");
750
+ } else if (!lp && chromium && platform !== "win32") {
751
+ logLine(io, "Chromium is the fallback only. Install Lightpanda as well (default engine).");
752
+ } else if (lp && !chromium) {
753
+ logLine(io, "Lightpanda ok. Install a chromium-family browser as well (shot/pdf/fallback).");
754
+ }
755
+ return { ok: st.ok, lightpanda: lp, chromium };
756
+ }
757
+
758
+ export async function setupJev({ flags = {}, home = os.homedir(), io, interactive = false }: SetupCtx = {}): Promise<{
759
+ ok: true;
760
+ dest: string;
761
+ }> {
762
+ let apiKey = str(flags["api-key"] || flags.key);
763
+ if (interactive && io && !apiKey && typeof io.password === "function") {
764
+ logLine(io, "Open: https://console.typesafe.ai/keys");
765
+ apiKey = str(await io.password!({ message: "TypeSafe API key" }));
766
+ }
767
+ if (!apiKey) {
768
+ throw new Error(interactive ? "need a TypeSafe API key" : "need --api-key (or TTY: bun repo setup jev)");
769
+ }
770
+ const dest = writeSecretJson(path.join(home, ".workerstack", "jev.json"), { apiKey });
771
+ logLine(io, `wrote ${dest}`);
772
+ return { ok: true, dest };
773
+ }
774
+
775
+ async function runTarget(target: string, ctx: SetupCtx): Promise<unknown> {
776
+ if (target === "speak") return setupSpeak(ctx);
777
+ if (target === "listen") return setupListen(ctx);
778
+ if (target === "google-workspace") return setupGoogleWorkspace(ctx);
779
+ if (target === "browser") return setupBrowser(ctx);
780
+ return setupJev(ctx);
781
+ }
782
+
783
+ export async function main(argv: string[], opts: SetupMainOpts = {}): Promise<number> {
784
+ const { _, flags } = parseArgv(argv);
785
+ const env = opts.env || readEnv();
786
+ const home = opts.home || env.HOME || env.USERPROFILE || os.homedir();
787
+ const cwd = opts.cwd || process.cwd();
788
+ const targets = setupTargets([...(await enabledSkills(cwd))]);
789
+ const interactive =
790
+ opts.interactive != null
791
+ ? Boolean(opts.interactive)
792
+ : ttyInteractive({
793
+ argv,
794
+ env,
795
+ stdin: opts.stdin || process.stdin,
796
+ stdout: opts.stdout || process.stdout,
797
+ });
798
+ const io = opts.io || (interactive ? await defaultIo() : null);
799
+ const ctx: SetupCtx = {
800
+ flags,
801
+ env,
802
+ home,
803
+ io,
804
+ fetchFn: opts.fetchFn,
805
+ openUrlFn: opts.openUrlFn,
806
+ platform: opts.platform || process.platform,
807
+ spawnSyncFn: opts.spawnSyncFn,
808
+ whichFn: opts.whichFn,
809
+ pathEnv: opts.pathEnv || env.PATH || readEnv().PATH,
810
+ interactive,
811
+ loginFn: opts.loginFn,
812
+ };
813
+
814
+ const statusOpts = {
815
+ home,
816
+ env,
817
+ whichFn: ctx.whichFn,
818
+ pathEnv: ctx.pathEnv,
819
+ platform: ctx.platform,
820
+ };
821
+ let statuses: Statuses = await targetStatuses({ ...statusOpts, targets });
822
+ let target = normalizeTarget(_[0]);
823
+ if (target && !targets.includes(target)) {
824
+ errLine(io, `${target} is off. Add it to agents.skills in workerstack.config.ts, then run \`bun repo sync\`.`);
825
+ return 1;
826
+ }
827
+ if (!target && interactive && io && typeof io.select === "function") {
828
+ target = normalizeTarget(
829
+ await io.select({
830
+ message: "Setup",
831
+ options: menuOptions(statuses, targets),
832
+ initialValue: firstMissing(statuses, targets),
833
+ }),
834
+ );
835
+ }
836
+ if (!target) {
837
+ errLine(io, USAGE.trimEnd());
838
+ return 1;
839
+ }
840
+ try {
841
+ const st = statuses[target];
842
+ const force = flagOn(flags, "redo");
843
+ if (st?.ok && interactive && !force && io && typeof io.confirm === "function") {
844
+ const again = await io.confirm({
845
+ message: `${target} already ok (${st.extra}). Redo?`,
846
+ initialValue: false,
847
+ });
848
+ if (!again) {
849
+ logLine(io, `skip ${target} (already ok)`);
850
+ logLine(io, formatStatus(statuses, targets));
851
+ return 0;
852
+ }
853
+ }
854
+ await runTarget(target, ctx);
855
+ statuses = await targetStatuses({ ...statusOpts, targets });
856
+ logLine(io, formatStatus(statuses, targets));
857
+ return 0;
858
+ } catch (e) {
859
+ errLine(io, errMessage(e));
860
+ return 1;
861
+ }
862
+ }