@hiroleague/taskmanager 0.0.1 → 0.0.3

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 (196) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +41 -52
  3. package/dist/assets/architecture-YZFGNWBL-DoE0KxgG.js +1 -0
  4. package/dist/assets/architectureDiagram-Q4EWVU46-DeuBhy7X.js +36 -0
  5. package/dist/assets/{blockDiagram-DXYQGD6D-DfOGNphI.js → blockDiagram-DXYQGD6D-BDBy9ns9.js} +1 -1
  6. package/dist/assets/{c4Diagram-AHTNJAMY-B2Yfcwbo.js → c4Diagram-AHTNJAMY-CpqJj_8a.js} +1 -1
  7. package/dist/assets/channel-PHRyjspt.js +1 -0
  8. package/dist/assets/{chunk-2KRD3SAO-9yt00aGC.js → chunk-2KRD3SAO-DEpUsxdZ.js} +1 -1
  9. package/dist/assets/chunk-336JU56O-BGQvSwLk.js +2 -0
  10. package/dist/assets/chunk-426QAEUC-Cl9nQN9c.js +1 -0
  11. package/dist/assets/{chunk-4TB4RGXK-DF8yJBFl.js → chunk-4TB4RGXK-Dq7aiIrZ.js} +2 -2
  12. package/dist/assets/{chunk-5FUZZQ4R-XEga0hMC.js → chunk-5FUZZQ4R-B_HuuUjf.js} +1 -1
  13. package/dist/assets/{chunk-5PVQY5BW-BrmXs2Gs.js → chunk-5PVQY5BW-cGfZCZGU.js} +2 -2
  14. package/dist/assets/{chunk-67CJDMHE-5wFKo04G.js → chunk-67CJDMHE-BMYAVZfw.js} +1 -1
  15. package/dist/assets/{chunk-7N4EOEYR-BRRGX_NC.js → chunk-7N4EOEYR-Ct-EY7Nc.js} +1 -1
  16. package/dist/assets/{chunk-AA7GKIK3-DUZv_pNI.js → chunk-AA7GKIK3-Bd4HFpeo.js} +1 -1
  17. package/dist/assets/{chunk-CIAEETIT-mA5aM_d7.js → chunk-CIAEETIT-CrFUkPMT.js} +1 -1
  18. package/dist/assets/{chunk-EDXVE4YY-DxUqDyxy.js → chunk-EDXVE4YY-DMDyt0NF.js} +1 -1
  19. package/dist/assets/{chunk-ENJZ2VHE-BgZKYo1l.js → chunk-ENJZ2VHE-DrWzOrpd.js} +1 -1
  20. package/dist/assets/{chunk-FOC6F5B3-B-cqGCPC.js → chunk-FOC6F5B3-Bemzq96j.js} +1 -1
  21. package/dist/assets/{chunk-ICPOFSXX-BNR1V8rT.js → chunk-ICPOFSXX-DkUVjrLw.js} +5 -5
  22. package/dist/assets/{chunk-K5T4RW27-BLRDzioh.js → chunk-K5T4RW27-ALKIf000.js} +5 -5
  23. package/dist/assets/{chunk-KGLVRYIC-CTkQSeKy.js → chunk-KGLVRYIC-Bg6HNTZ-.js} +1 -1
  24. package/dist/assets/{chunk-LIHQZDEY-Cf34Nu3J.js → chunk-LIHQZDEY-DeyGongE.js} +1 -1
  25. package/dist/assets/{chunk-ORNJ4GCN-D3uXgbay.js → chunk-ORNJ4GCN-Bx83s1bJ.js} +1 -1
  26. package/dist/assets/{chunk-OYMX7WX6-syQho5jf.js → chunk-OYMX7WX6-BqRUtRpL.js} +1 -1
  27. package/dist/assets/{chunk-U2HBQHQK-DTJPeU7W.js → chunk-U2HBQHQK-DogcerR6.js} +1 -1
  28. package/dist/assets/{chunk-X2U36JSP-CrTnmMqG.js → chunk-X2U36JSP-CwVWdmZV.js} +1 -1
  29. package/dist/assets/chunk-XPW4576I-DQpNCogT.js +32 -0
  30. package/dist/assets/{chunk-YZCP3GAM-9wq0QKUn.js → chunk-YZCP3GAM-crQSbji9.js} +1 -1
  31. package/dist/assets/{chunk-ZZ45TVLE-D3I1kLlo.js → chunk-ZZ45TVLE-Bk1S1YtS.js} +1 -1
  32. package/dist/assets/classDiagram-6PBFFD2Q-B_TabGaU.js +1 -0
  33. package/dist/assets/classDiagram-v2-HSJHXN6E-CGnZkUWw.js +1 -0
  34. package/dist/assets/clone-D4ka472w.js +1 -0
  35. package/dist/assets/{cose-bilkent-S5V4N54A-BygGvZGW.js → cose-bilkent-S5V4N54A-RBTHUit8.js} +1 -1
  36. package/dist/assets/cytoscape.esm-BGJwlmkf.js +321 -0
  37. package/dist/assets/dagre-B32eYLtm.js +1 -0
  38. package/dist/assets/{dagre-KV5264BT-BBqulDtd.js → dagre-KV5264BT-nX7tuXXn.js} +1 -1
  39. package/dist/assets/diagram-5BDNPKRD-DRxMXlQr.js +10 -0
  40. package/dist/assets/diagram-G4DWMVQ6-CoojevGm.js +24 -0
  41. package/dist/assets/diagram-MMDJMWI5-CWtJyfVW.js +43 -0
  42. package/dist/assets/diagram-TYMM5635-CsDJC4Hq.js +24 -0
  43. package/dist/assets/{erDiagram-SMLLAGMA-BN5eJerP.js → erDiagram-SMLLAGMA-Cf7Xtd9A.js} +2 -2
  44. package/dist/assets/{flatten-C5NL-f24.js → flatten-CYX_pHZ7.js} +1 -1
  45. package/dist/assets/{flowDiagram-DWJPFMVM-CbFskc8S.js → flowDiagram-DWJPFMVM-DQaeR16a.js} +3 -3
  46. package/dist/assets/{ganttDiagram-T4ZO3ILL-OCTvbRxF.js → ganttDiagram-T4ZO3ILL-8EIcztcH.js} +1 -1
  47. package/dist/assets/gitGraph-7Q5UKJZL-BH9A1SAZ.js +1 -0
  48. package/dist/assets/{gitGraphDiagram-UUTBAWPF-wpqI2kyI.js → gitGraphDiagram-UUTBAWPF-DO9ODqYw.js} +1 -1
  49. package/dist/assets/graphlib-bPBqlJKT.js +1 -0
  50. package/dist/assets/identity-Me9aart9.js +1 -0
  51. package/dist/assets/index-oKG1C41_.js +273 -0
  52. package/dist/assets/info-OMHHGYJF-BvKR-zWh.js +1 -0
  53. package/dist/assets/infoDiagram-42DDH7IO-pRTXCm5C.js +2 -0
  54. package/dist/assets/isEmpty-Cu0k-j1j.js +1 -0
  55. package/dist/assets/{ishikawaDiagram-UXIWVN3A-Epc23N_0.js → ishikawaDiagram-UXIWVN3A-BP2YE5QI.js} +2 -2
  56. package/dist/assets/{journeyDiagram-VCZTEJTY-BkMxoaPq.js → journeyDiagram-VCZTEJTY-B3l2juoL.js} +1 -1
  57. package/dist/assets/{kanban-definition-6JOO6SKY-C8dW_26n.js → kanban-definition-6JOO6SKY-BpIpEOZZ.js} +4 -4
  58. package/dist/assets/{line-DNzQATGr.js → line-otOkzGl8.js} +1 -1
  59. package/dist/assets/mermaid-parser.core-xWsW24Gq.js +4 -0
  60. package/dist/assets/{mindmap-definition-QFDTVHPH-CvpNtrKT.js → mindmap-definition-QFDTVHPH-B9khyC7X.js} +3 -3
  61. package/dist/assets/packet-4T2RLAQJ-D8Dw3nmf.js +1 -0
  62. package/dist/assets/pie-ZZUOXDRM-ZghowlAE.js +1 -0
  63. package/dist/assets/{pieDiagram-DEJITSTG-eENymoXZ.js → pieDiagram-DEJITSTG-v32hL3i7.js} +1 -1
  64. package/dist/assets/{quadrantDiagram-34T5L4WZ-c0iZxo2I.js → quadrantDiagram-34T5L4WZ-DIL3GDFt.js} +1 -1
  65. package/dist/assets/radar-PYXPWWZC-D-PK3JOd.js +1 -0
  66. package/dist/assets/reduce-CImcgAcU.js +1 -0
  67. package/dist/assets/{requirementDiagram-MS252O5E-CmRO3hLp.js → requirementDiagram-MS252O5E-D8os2-4y.js} +2 -2
  68. package/dist/assets/{sankeyDiagram-XADWPNL6-woJZoQ58.js → sankeyDiagram-XADWPNL6-BV70D4l5.js} +1 -1
  69. package/dist/assets/{sequenceDiagram-FGHM5R23-B7qNcwNo.js → sequenceDiagram-FGHM5R23-Cwu8hQW1.js} +1 -1
  70. package/dist/assets/stateDiagram-FHFEXIEX-oYUWv7Fb.js +1 -0
  71. package/dist/assets/stateDiagram-v2-QKLJ7IA2-CFUTpFu-.js +1 -0
  72. package/dist/assets/{timeline-definition-GMOUNBTQ-CQWqDPGG.js → timeline-definition-GMOUNBTQ-CxSdKxpL.js} +1 -1
  73. package/dist/assets/treeView-SZITEDCU-uVgaJQzG.js +1 -0
  74. package/dist/assets/treemap-W4RFUUIX-Dcad_9AN.js +1 -0
  75. package/dist/assets/vennDiagram-DHZGUBPP-D4wgD7QI.js +34 -0
  76. package/dist/assets/wardley-RL74JXVD-CFXrK8mx.js +1 -0
  77. package/dist/assets/{wardleyDiagram-NUSXRM2D-DNhPIFCg.js → wardleyDiagram-NUSXRM2D-5Q201ea3.js} +1 -1
  78. package/dist/assets/{xychartDiagram-5P7HB3ND-BDblAZ11.js → xychartDiagram-5P7HB3ND-BPZv_axd.js} +3 -3
  79. package/dist/index.html +16 -12
  80. package/package.json +99 -92
  81. package/scripts/stubs/node-domexception/index.cjs +18 -0
  82. package/scripts/stubs/node-domexception/package.json +7 -0
  83. package/skills/hiro-task-manager-cli/SKILL.md +97 -0
  84. package/skills/hiro-task-manager-cli/reference/boards.md +143 -0
  85. package/skills/hiro-task-manager-cli/reference/cli-access-policy.md +72 -0
  86. package/skills/hiro-task-manager-cli/reference/errors.md +85 -0
  87. package/skills/hiro-task-manager-cli/reference/lists.md +106 -0
  88. package/skills/hiro-task-manager-cli/reference/releases.md +87 -0
  89. package/skills/hiro-task-manager-cli/reference/search.md +38 -0
  90. package/skills/hiro-task-manager-cli/reference/statuses.md +25 -0
  91. package/skills/hiro-task-manager-cli/reference/tasks.md +144 -0
  92. package/skills/hiro-task-manager-cli/reference/trash.md +50 -0
  93. package/src/cli/bootstrap/launcher.test.ts +66 -0
  94. package/src/cli/bootstrap/launcher.ts +389 -35
  95. package/src/cli/bootstrap/program.test.ts +46 -0
  96. package/src/cli/bootstrap/program.ts +54 -1
  97. package/src/cli/bootstrap/runtime.test.ts +15 -0
  98. package/src/cli/bootstrap/runtime.ts +27 -1
  99. package/src/cli/commands/query.ts +56 -56
  100. package/src/cli/commands/server.ts +27 -19
  101. package/src/cli/handlers/boards.test.ts +669 -669
  102. package/src/cli/handlers/cli-wiring.test.ts +1 -1
  103. package/src/cli/handlers/search.test.ts +374 -374
  104. package/src/cli/handlers/search.ts +17 -17
  105. package/src/cli/handlers/server.test.ts +55 -13
  106. package/src/cli/handlers/server.ts +16 -3
  107. package/src/cli/lib/api-client.test.ts +35 -2
  108. package/src/cli/lib/api-client.ts +43 -10
  109. package/src/cli/lib/cli-http-errors.test.ts +85 -85
  110. package/src/cli/lib/command-helpers.ts +161 -154
  111. package/src/cli/lib/config.ts +4 -0
  112. package/src/cli/lib/launcherUi.test.ts +74 -0
  113. package/src/cli/lib/launcherUi.ts +213 -0
  114. package/src/cli/lib/process.test.ts +24 -5
  115. package/src/cli/lib/process.ts +86 -55
  116. package/src/cli/ports/process.ts +8 -2
  117. package/src/cli/subprocess.real-stack.test.ts +611 -598
  118. package/src/cli/subprocess.smoke.test.ts +954 -969
  119. package/src/cli/types/config.ts +2 -6
  120. package/src/client/components/auth/AuthScreen.tsx +3 -3
  121. package/src/client/components/board/BoardStatsChips.tsx +233 -233
  122. package/src/client/components/board/BoardStatsContext.tsx +41 -41
  123. package/src/client/components/board/boardHeaderButtonStyles.ts +38 -38
  124. package/src/client/components/board/shortcuts/useBoardShortcutKeydown.ts +49 -49
  125. package/src/client/components/board/useBoardCanvasPanScroll.ts +108 -108
  126. package/src/client/components/board/useBoardTaskContainerDroppableReact.ts +33 -33
  127. package/src/client/components/board/useBoardTaskSortableReact.ts +26 -26
  128. package/src/client/components/multi-select.tsx +1206 -1206
  129. package/src/client/components/routing/BoardPage.tsx +20 -20
  130. package/src/client/components/routing/NavigationRegistrar.tsx +13 -13
  131. package/src/client/components/settings/SettingsPage.tsx +1 -1
  132. package/src/client/components/task/TaskCard.tsx +643 -643
  133. package/src/client/components/ui/badge.tsx +49 -49
  134. package/src/client/components/ui/button.tsx +65 -65
  135. package/src/client/components/ui/command.tsx +193 -193
  136. package/src/client/components/ui/dialog.tsx +163 -163
  137. package/src/client/components/ui/input-group.tsx +155 -155
  138. package/src/client/components/ui/input.tsx +19 -19
  139. package/src/client/components/ui/popover.tsx +87 -87
  140. package/src/client/components/ui/separator.tsx +28 -28
  141. package/src/client/components/ui/textarea.tsx +18 -18
  142. package/src/client/index.css +248 -248
  143. package/src/client/lib/appNavigate.ts +16 -16
  144. package/src/client/lib/taskCardDate.ts +111 -111
  145. package/src/client/lib/utils.ts +6 -6
  146. package/src/server/auth.ts +351 -302
  147. package/src/server/bootstrapDev.ts +11 -2
  148. package/src/server/bootstrapInstalled.ts +6 -1
  149. package/src/server/index.ts +33 -7
  150. package/src/server/migrations/013_cli_policy_and_provenance.ts +2 -2
  151. package/src/server/migrations/019_cli_global_create_board_default_on.ts +14 -0
  152. package/src/server/migrations/registry.ts +43 -41
  153. package/src/server/parseBootstrapProfile.ts +42 -0
  154. package/src/server/storage/cliPolicy.ts +2 -1
  155. package/src/shared/runtimeConfig.ts +256 -237
  156. package/src/shared/runtimeIdentity.test.ts +47 -0
  157. package/src/shared/runtimeIdentity.ts +35 -0
  158. package/src/shared/serverStatus.ts +21 -0
  159. package/src/shared/skillsInstall.ts +70 -0
  160. package/src/shared/terminalColors.ts +24 -0
  161. package/dist/assets/architecture-YZFGNWBL-3h1eIYfB.js +0 -1
  162. package/dist/assets/architectureDiagram-Q4EWVU46-DSQ1_74_.js +0 -36
  163. package/dist/assets/channel-yBmN_ln0.js +0 -1
  164. package/dist/assets/classDiagram-6PBFFD2Q-CotFZI8-.js +0 -1
  165. package/dist/assets/classDiagram-v2-HSJHXN6E-DAPzeDGn.js +0 -1
  166. package/dist/assets/clone-BRQpYu_n.js +0 -1
  167. package/dist/assets/cytoscape.esm-BIYWHPG0.js +0 -321
  168. package/dist/assets/dagre-rhyPjnsQ.js +0 -1
  169. package/dist/assets/diagram-5BDNPKRD-Ky3EXXj0.js +0 -10
  170. package/dist/assets/diagram-G4DWMVQ6-t7LbT0Uz.js +0 -24
  171. package/dist/assets/diagram-MMDJMWI5-CdnLXEMx.js +0 -43
  172. package/dist/assets/diagram-TYMM5635-CnzTqJBM.js +0 -24
  173. package/dist/assets/gitGraph-7Q5UKJZL-CG8f8JF7.js +0 -1
  174. package/dist/assets/graphlib-COiJG5Qv.js +0 -1
  175. package/dist/assets/identity-D4WOnl_h.js +0 -1
  176. package/dist/assets/index-lyyIVcc_.js +0 -304
  177. package/dist/assets/info-OMHHGYJF-C8_SHoRO.js +0 -1
  178. package/dist/assets/infoDiagram-42DDH7IO-BbvTdpSV.js +0 -2
  179. package/dist/assets/mermaid-parser.core-6Tn8epr_.js +0 -4
  180. package/dist/assets/packet-4T2RLAQJ-BvpAX0kJ.js +0 -1
  181. package/dist/assets/pie-ZZUOXDRM-Ow26Yf-E.js +0 -1
  182. package/dist/assets/radar-PYXPWWZC-e_ron5jQ.js +0 -1
  183. package/dist/assets/reduce-BDOBPIXr.js +0 -1
  184. package/dist/assets/stateDiagram-FHFEXIEX-CYfGMoR8.js +0 -1
  185. package/dist/assets/stateDiagram-v2-QKLJ7IA2-CO1W_n55.js +0 -1
  186. package/dist/assets/treeView-SZITEDCU-DsEr3xeq.js +0 -1
  187. package/dist/assets/treemap-W4RFUUIX-DV7nk2AB.js +0 -1
  188. package/dist/assets/vennDiagram-DHZGUBPP-BjTbuhcb.js +0 -34
  189. package/dist/assets/wardley-RL74JXVD-CrrFU9AE.js +0 -1
  190. /package/dist/assets/{chunk-4BX2VUAB-ean5NKtU.js → chunk-4BX2VUAB-C70mcfQR.js} +0 -0
  191. /package/dist/assets/{chunk-55IACEB6-CvSRyJqy.js → chunk-55IACEB6-CWfnqcLM.js} +0 -0
  192. /package/dist/assets/{chunk-BSJP7CBP-D8kBlJsf.js → chunk-BSJP7CBP-B0LrXV9y.js} +0 -0
  193. /package/dist/assets/{chunk-FMBD7UC4-DrNhFt1N.js → chunk-FMBD7UC4-_mV71Mwu.js} +0 -0
  194. /package/dist/assets/{chunk-QZHKN3VN-Csp3OYJY.js → chunk-QZHKN3VN-t2nrsegL.js} +0 -0
  195. /package/dist/assets/{katex-8mXVa4k3.js → katex-B2dtGfSp.js} +0 -0
  196. /package/dist/assets/{rough.esm-DtEqI08j.js → rough.esm-DEh6Frf9.js} +0 -0
@@ -1,154 +1,161 @@
1
- import { Command } from "commander";
2
- import { CLI_DEFAULTS } from "./constants";
3
- import { CLI_ERR } from "../types/errors";
4
- import { getCliOutputFormat, getCliQuiet } from "./cliFormat";
5
- import { CliError, exitWithError } from "./output";
6
-
7
- /** Shared Commander option helpers — Phase 1 CLI split from monolithic index.ts. */
8
-
9
- /** Help text for read/list `--fields` (subset projection; see `jsonFieldProjection.ts` allowlists). */
10
- export const CLI_FIELDS_OPTION_DESC =
11
- "Comma-separated API keys per row (unknown keys exit 2); use with global --format ndjson, not human tables.";
12
-
13
- /** `boards describe --entities`: list,group,priority,release,status,meta — order controls CLI stdout (and HTTP when subset). */
14
- export const CLI_BOARD_DESCRIBE_ENTITIES_DESC =
15
- "Comma-separated: list,group,priority,release,status,meta (omit for default five sections, no meta). Board + cliPolicy always in JSON; duplicate or unknown tokens exit 2; board is not a token.";
16
-
17
- /** Guarded deletes / structure replaces: paired with `confirmMutableAction` in handlers. */
18
- export const CLI_YES_OPTION_DESC = "Skip the confirmation prompt";
19
-
20
- /** Attach `-y` / `--yes` for commands that call `confirmMutableAction`. */
21
- export function addYesOption(command: Command): Command {
22
- return command.option("-y, --yes", CLI_YES_OPTION_DESC);
23
- }
24
-
25
- /** Human tables cannot apply arbitrary `--fields`; enforce before fetch. */
26
- export function requireNdjsonWhenUsingFields(
27
- fieldKeys: string[] | undefined,
28
- ): void {
29
- if (
30
- fieldKeys != null &&
31
- fieldKeys.length > 0 &&
32
- getCliOutputFormat() === "human"
33
- ) {
34
- throw new CliError("--fields requires global --format ndjson", 2, {
35
- code: CLI_ERR.invalidValue,
36
- });
37
- }
38
- }
39
-
40
- /** Pipe-friendly `--quiet` stdout is not JSON; same constraint as `--fields` vs human tables. */
41
- export function requireNdjsonWhenQuiet(): void {
42
- if (getCliQuiet() && getCliOutputFormat() === "human") {
43
- throw new CliError("--quiet requires global --format ndjson", 2, {
44
- code: CLI_ERR.invalidValue,
45
- });
46
- }
47
- }
48
-
49
- /**
50
- * With global `--quiet`, `--fields` may supply exactly one key (that column per line).
51
- * Multiple keys are ambiguous for single-column output.
52
- */
53
- export function resolveQuietExplicitField(
54
- fieldKeys: string[] | undefined,
55
- ): string | undefined {
56
- if (!getCliQuiet() || fieldKeys == null || fieldKeys.length === 0) {
57
- return undefined;
58
- }
59
- if (fieldKeys.length > 1) {
60
- throw new CliError("--quiet allows at most one --fields key", 2, {
61
- code: CLI_ERR.invalidValue,
62
- });
63
- }
64
- return fieldKeys[0];
65
- }
66
-
67
- /** Attach `--client-name` for commands that call the HTTP API (mutations send the label). */
68
- export function addClientNameOption(command: Command): Command {
69
- return command.option(
70
- "--client-name <name>",
71
- "Human-friendly client label sent with API requests (for notifications)",
72
- );
73
- }
74
-
75
- export function addProfileOption(command: Command): Command {
76
- return command.option(
77
- "--profile <name>",
78
- "Runtime profile name for this command",
79
- );
80
- }
81
-
82
- export function parsePortOption(port: string | undefined): number | undefined {
83
- if (!port?.trim()) return undefined;
84
-
85
- const parsed = Number(port);
86
- if (!Number.isInteger(parsed) || parsed <= 0) {
87
- throw new CliError("Invalid port", 2, { code: CLI_ERR.invalidValue, port });
88
- }
89
-
90
- return parsed;
91
- }
92
-
93
- export function collectMultiValue(
94
- value: string,
95
- previous: string[] = [],
96
- ): string[] {
97
- return [
98
- ...previous,
99
- ...value
100
- .split(",")
101
- .map((entry) => entry.trim())
102
- .filter((entry) => entry.length > 0),
103
- ];
104
- }
105
-
106
- /** Search: default and cap from `CLI_DEFAULTS` (same max as optional list page-all). */
107
- export function parseLimitOption(limit: string | undefined): number {
108
- if (limit == null || limit === "") return CLI_DEFAULTS.DEFAULT_SEARCH_LIMIT;
109
- const n = Number(limit);
110
- if (!Number.isInteger(n) || n < 1) {
111
- throw new CliError("Invalid limit", 2, { code: CLI_ERR.invalidValue, limit });
112
- }
113
- return Math.min(CLI_DEFAULTS.MAX_PAGE_LIMIT, n);
114
- }
115
-
116
- /** Optional list limit for tasks/trash/boards/releases; omit = no `limit` param (one full page). */
117
- export function parseOptionalListLimit(
118
- limit: string | undefined,
119
- ): number | null {
120
- if (limit == null || limit === "") return null;
121
- const n = Number(limit);
122
- if (!Number.isInteger(n) || n < 1) {
123
- throw new CliError("Invalid limit", 2, { code: CLI_ERR.invalidValue, limit });
124
- }
125
- return Math.min(CLI_DEFAULTS.MAX_PAGE_LIMIT, n);
126
- }
127
-
128
- export function parseOptionalOffset(offset: string | undefined): number {
129
- if (offset == null || offset === "") return 0;
130
- const n = Number(offset);
131
- if (!Number.isInteger(n) || n < 0) {
132
- throw new CliError("Invalid offset", 2, { code: CLI_ERR.invalidValue, offset });
133
- }
134
- return n;
135
- }
136
-
137
- /** Wrap handler execution so Commander actions share one exit path (Phase 2). */
138
- export async function withCliErrors(fn: () => Promise<void>): Promise<void> {
139
- try {
140
- await fn();
141
- } catch (error) {
142
- exitWithError(error);
143
- }
144
- }
145
-
146
- /**
147
- * Commander `.action()` helper: runs the handler inside {@link withCliErrors} so
148
- * subcommands cannot forget the shared exit path (see cli-architecture-review item 14).
149
- */
150
- export function cliAction<A extends unknown[]>(
151
- fn: (...args: A) => Promise<void>,
152
- ): (...args: A) => Promise<void> {
153
- return (...args: A) => withCliErrors(() => fn(...args));
154
- }
1
+ import { Command } from "commander";
2
+ import { CLI_DEFAULTS } from "./constants";
3
+ import { CLI_ERR } from "../types/errors";
4
+ import { getCliOutputFormat, getCliQuiet } from "./cliFormat";
5
+ import { CliError, exitWithError } from "./output";
6
+
7
+ /** Shared Commander option helpers — Phase 1 CLI split from monolithic index.ts. */
8
+
9
+ /** Help text for read/list `--fields` (subset projection; see `jsonFieldProjection.ts` allowlists). */
10
+ export const CLI_FIELDS_OPTION_DESC =
11
+ "Comma-separated API keys per row (unknown keys exit 2); use with global --format ndjson, not human tables.";
12
+
13
+ /** `boards describe --entities`: list,group,priority,release,status,meta — order controls CLI stdout (and HTTP when subset). */
14
+ export const CLI_BOARD_DESCRIBE_ENTITIES_DESC =
15
+ "Comma-separated: list,group,priority,release,status,meta (omit for default five sections, no meta). Board + cliPolicy always in JSON; duplicate or unknown tokens exit 2; board is not a token.";
16
+
17
+ /** Guarded deletes / structure replaces: paired with `confirmMutableAction` in handlers. */
18
+ export const CLI_YES_OPTION_DESC = "Skip the confirmation prompt";
19
+
20
+ /** Attach `-y` / `--yes` for commands that call `confirmMutableAction`. */
21
+ export function addYesOption(command: Command): Command {
22
+ return command.option("-y, --yes", CLI_YES_OPTION_DESC);
23
+ }
24
+
25
+ /** Human tables cannot apply arbitrary `--fields`; enforce before fetch. */
26
+ export function requireNdjsonWhenUsingFields(
27
+ fieldKeys: string[] | undefined,
28
+ ): void {
29
+ if (
30
+ fieldKeys != null &&
31
+ fieldKeys.length > 0 &&
32
+ getCliOutputFormat() === "human"
33
+ ) {
34
+ throw new CliError("--fields requires global --format ndjson", 2, {
35
+ code: CLI_ERR.invalidValue,
36
+ });
37
+ }
38
+ }
39
+
40
+ /** Pipe-friendly `--quiet` stdout is not JSON; same constraint as `--fields` vs human tables. */
41
+ export function requireNdjsonWhenQuiet(): void {
42
+ if (getCliQuiet() && getCliOutputFormat() === "human") {
43
+ throw new CliError("--quiet requires global --format ndjson", 2, {
44
+ code: CLI_ERR.invalidValue,
45
+ });
46
+ }
47
+ }
48
+
49
+ /**
50
+ * With global `--quiet`, `--fields` may supply exactly one key (that column per line).
51
+ * Multiple keys are ambiguous for single-column output.
52
+ */
53
+ export function resolveQuietExplicitField(
54
+ fieldKeys: string[] | undefined,
55
+ ): string | undefined {
56
+ if (!getCliQuiet() || fieldKeys == null || fieldKeys.length === 0) {
57
+ return undefined;
58
+ }
59
+ if (fieldKeys.length > 1) {
60
+ throw new CliError("--quiet allows at most one --fields key", 2, {
61
+ code: CLI_ERR.invalidValue,
62
+ });
63
+ }
64
+ return fieldKeys[0];
65
+ }
66
+
67
+ /** Attach `--client-name` for commands that call the HTTP API (mutations send the label). */
68
+ export function addClientNameOption(command: Command): Command {
69
+ return command.option(
70
+ "--client-name <name>",
71
+ "Human-friendly client label sent with API requests (for notifications)",
72
+ );
73
+ }
74
+
75
+ export function addProfileOption(command: Command): Command {
76
+ return command.option(
77
+ "--profile <name>",
78
+ "Runtime profile name for this command",
79
+ );
80
+ }
81
+
82
+ export function addDevOption(command: Command): Command {
83
+ return command.option(
84
+ "--dev",
85
+ "Run in dev mode (API-only with dev CORS, port 3002 default)",
86
+ );
87
+ }
88
+
89
+ export function parsePortOption(port: string | undefined): number | undefined {
90
+ if (!port?.trim()) return undefined;
91
+
92
+ const parsed = Number(port);
93
+ if (!Number.isInteger(parsed) || parsed <= 0) {
94
+ throw new CliError("Invalid port", 2, { code: CLI_ERR.invalidValue, port });
95
+ }
96
+
97
+ return parsed;
98
+ }
99
+
100
+ export function collectMultiValue(
101
+ value: string,
102
+ previous: string[] = [],
103
+ ): string[] {
104
+ return [
105
+ ...previous,
106
+ ...value
107
+ .split(",")
108
+ .map((entry) => entry.trim())
109
+ .filter((entry) => entry.length > 0),
110
+ ];
111
+ }
112
+
113
+ /** Search: default and cap from `CLI_DEFAULTS` (same max as optional list page-all). */
114
+ export function parseLimitOption(limit: string | undefined): number {
115
+ if (limit == null || limit === "") return CLI_DEFAULTS.DEFAULT_SEARCH_LIMIT;
116
+ const n = Number(limit);
117
+ if (!Number.isInteger(n) || n < 1) {
118
+ throw new CliError("Invalid limit", 2, { code: CLI_ERR.invalidValue, limit });
119
+ }
120
+ return Math.min(CLI_DEFAULTS.MAX_PAGE_LIMIT, n);
121
+ }
122
+
123
+ /** Optional list limit for tasks/trash/boards/releases; omit = no `limit` param (one full page). */
124
+ export function parseOptionalListLimit(
125
+ limit: string | undefined,
126
+ ): number | null {
127
+ if (limit == null || limit === "") return null;
128
+ const n = Number(limit);
129
+ if (!Number.isInteger(n) || n < 1) {
130
+ throw new CliError("Invalid limit", 2, { code: CLI_ERR.invalidValue, limit });
131
+ }
132
+ return Math.min(CLI_DEFAULTS.MAX_PAGE_LIMIT, n);
133
+ }
134
+
135
+ export function parseOptionalOffset(offset: string | undefined): number {
136
+ if (offset == null || offset === "") return 0;
137
+ const n = Number(offset);
138
+ if (!Number.isInteger(n) || n < 0) {
139
+ throw new CliError("Invalid offset", 2, { code: CLI_ERR.invalidValue, offset });
140
+ }
141
+ return n;
142
+ }
143
+
144
+ /** Wrap handler execution so Commander actions share one exit path (Phase 2). */
145
+ export async function withCliErrors(fn: () => Promise<void>): Promise<void> {
146
+ try {
147
+ await fn();
148
+ } catch (error) {
149
+ exitWithError(error);
150
+ }
151
+ }
152
+
153
+ /**
154
+ * Commander `.action()` helper: runs the handler inside {@link withCliErrors} so
155
+ * subcommands cannot forget the shared exit path (see cli-architecture-review item 14).
156
+ */
157
+ export function cliAction<A extends unknown[]>(
158
+ fn: (...args: A) => Promise<void>,
159
+ ): (...args: A) => Promise<void> {
160
+ return (...args: A) => withCliErrors(() => fn(...args));
161
+ }
@@ -28,6 +28,10 @@ export function setRuntimeProfile(profile: string | undefined): void {
28
28
  setRuntimeConfigSelection({ profile });
29
29
  }
30
30
 
31
+ export function setRuntimeCliPort(port: number | undefined): void {
32
+ setRuntimeConfigSelection({ port });
33
+ }
34
+
31
35
  export function setRuntimeKind(kind: RuntimeKind | undefined): void {
32
36
  setRuntimeConfigSelection({ kind });
33
37
  }
@@ -0,0 +1,74 @@
1
+ import { describe, expect, test } from "bun:test";
2
+ import { printSetupContinuePrompt, printSetupNextSteps } from "./launcherUi";
3
+
4
+ async function captureConsoleLog(
5
+ run: () => Promise<void> | void,
6
+ ): Promise<string> {
7
+ const originalLog = console.log;
8
+ const lines: string[] = [];
9
+ console.log = (...args: unknown[]) => {
10
+ lines.push(args.map(String).join(" "));
11
+ };
12
+ try {
13
+ await run();
14
+ } finally {
15
+ console.log = originalLog;
16
+ }
17
+ return `${lines.join("\n")}\n`;
18
+ }
19
+
20
+ describe("printSetupNextSteps", () => {
21
+ test("prints a prominent table with required repo skills install guidance", async () => {
22
+ const stdout = await captureConsoleLog(async () => {
23
+ printSetupNextSteps({
24
+ profileName: "default",
25
+ skillsInstalled: false,
26
+ });
27
+ });
28
+
29
+ expect(stdout).toContain("+-");
30
+ expect(stdout).toContain("REQUIRED BEFORE USING hirotm");
31
+ expect(stdout).toContain("Install AI agent skills now. The hirotm CLI depends on these skills.");
32
+ expect(stdout).toContain("hirotm --help");
33
+ expect(stdout).toContain("1. Repo skills : npx skills add hiro-league/hirotaskmanager");
34
+ expect(stdout).toContain("2. Update later : npx skills update");
35
+ expect(stdout).toContain("npx skills add hiro-league/hirotaskmanager");
36
+ expect(stdout).not.toContain("npx skills add \"$HOME/.taskmanager/skills\"");
37
+ });
38
+
39
+ test("prints local skills command when bundled skills were copied", async () => {
40
+ const stdout = await captureConsoleLog(async () => {
41
+ printSetupNextSteps({
42
+ profileName: "default",
43
+ skillsInstalled: true,
44
+ });
45
+ });
46
+
47
+ expect(stdout).toContain("npx skills add hiro-league/hirotaskmanager");
48
+ expect(stdout).toContain("2. Local skills : npx skills add \"$HOME/.taskmanager/skills\"");
49
+ expect(stdout).toContain("3. Update later : npx skills update");
50
+ expect(stdout).toContain("npx skills add \"$HOME/.taskmanager/skills\"");
51
+ expect(stdout).toContain("npx skills update");
52
+ });
53
+
54
+ test("prints profile-specific CLI guidance for named profiles", async () => {
55
+ const stdout = await captureConsoleLog(async () => {
56
+ printSetupNextSteps({
57
+ profileName: "work",
58
+ skillsInstalled: false,
59
+ });
60
+ });
61
+
62
+ expect(stdout).toContain("hirotm --profile work --help");
63
+ });
64
+ });
65
+
66
+ describe("printSetupContinuePrompt", () => {
67
+ test("prints a separate enter-to-continue message", async () => {
68
+ const stdout = await captureConsoleLog(() => {
69
+ printSetupContinuePrompt();
70
+ });
71
+
72
+ expect(stdout).toContain("Press Enter to continue and start TaskManager...");
73
+ });
74
+ });
@@ -0,0 +1,213 @@
1
+ /**
2
+ * hirotaskmanager interactive setup: compact prompts, inline spinners, and colored values.
3
+ * Uses ANSI only when stdout is a TTY and NO_COLOR is unset.
4
+ */
5
+ import { existsSync } from "node:fs";
6
+ import path from "node:path";
7
+ import { ansi, paint } from "../../shared/terminalColors";
8
+
9
+ const out = process.stdout;
10
+ const SPINNER_FRAMES = ["-", "\\", "|", "/"] as const;
11
+ const SPINNER_INTERVAL_MS = 100;
12
+ const DEFAULT_SPINNER_MS = 900;
13
+
14
+ function line(text = ""): void {
15
+ console.log(text);
16
+ }
17
+
18
+ function clearInline(): void {
19
+ if (typeof out.clearLine === "function" && typeof out.cursorTo === "function") {
20
+ out.clearLine(0);
21
+ out.cursorTo(0);
22
+ }
23
+ }
24
+
25
+ function writeInline(text: string): void {
26
+ clearInline();
27
+ out.write(text);
28
+ }
29
+
30
+ export function paintValue(value: string | number | boolean): string {
31
+ return paint(out, String(value), ansi.bold + ansi.cyan);
32
+ }
33
+
34
+ function paintSuccess(text: string): string {
35
+ return paint(out, text, ansi.bold + ansi.green);
36
+ }
37
+
38
+ function paintWarning(text: string): string {
39
+ return paint(out, text, ansi.bold + ansi.yellow);
40
+ }
41
+
42
+ function printAsciiTable(lines: readonly string[]): void {
43
+ const innerWidth = Math.max(76, ...lines.map((line) => line.length));
44
+ const border = ` +-${"-".repeat(innerWidth)}-+`;
45
+ line();
46
+ line(border);
47
+ for (const entry of lines) {
48
+ line(` | ${entry.padEnd(innerWidth)} |`);
49
+ }
50
+ line(border);
51
+ line();
52
+ }
53
+
54
+ export type SpinnerHandle = {
55
+ stop: (finalText?: string | null) => void;
56
+ };
57
+
58
+ export function startInlineSpinner(message: string): SpinnerHandle {
59
+ if (!out.isTTY) {
60
+ line(message);
61
+ return {
62
+ stop(finalText?: string | null): void {
63
+ if (finalText) line(finalText);
64
+ },
65
+ };
66
+ }
67
+
68
+ let frameIndex = 0;
69
+ let stopped = false;
70
+ writeInline(`${message} ${paint(out, SPINNER_FRAMES[frameIndex], ansi.dim)}`);
71
+ const timer = setInterval(() => {
72
+ frameIndex = (frameIndex + 1) % SPINNER_FRAMES.length;
73
+ writeInline(`${message} ${paint(out, SPINNER_FRAMES[frameIndex], ansi.dim)}`);
74
+ }, SPINNER_INTERVAL_MS);
75
+
76
+ return {
77
+ stop(finalText?: string | null): void {
78
+ if (stopped) return;
79
+ stopped = true;
80
+ clearInterval(timer);
81
+ clearInline();
82
+ if (finalText) out.write(`${finalText}\n`);
83
+ },
84
+ };
85
+ }
86
+
87
+ export async function spinForMoment(
88
+ message: string,
89
+ finalText = message,
90
+ durationMs = DEFAULT_SPINNER_MS,
91
+ ): Promise<void> {
92
+ const spinner = startInlineSpinner(message);
93
+ await Bun.sleep(durationMs);
94
+ spinner.stop(finalText);
95
+ }
96
+
97
+ export function formatTextPrompt(label: string, defaultValue: string): string {
98
+ return `${label}: ${paintValue(`[${defaultValue}]`)}`;
99
+ }
100
+
101
+ export function formatBooleanPrompt(
102
+ label: string,
103
+ defaultValue: boolean,
104
+ ): string {
105
+ return `${label} ${paintValue(`[${defaultValue ? "Y/n" : "y/N"}]`)}`;
106
+ }
107
+
108
+ export function printInteractiveSetupHeader(opts: {
109
+ profileName: string;
110
+ firstProfileOnMachine: boolean;
111
+ }): void {
112
+ // Keep the setup header to one line so first run stays dense and scannable.
113
+ line(
114
+ paint(
115
+ out,
116
+ opts.firstProfileOnMachine
117
+ ? "Hiro Task Manager - First-time Setup..."
118
+ : `Hiro Task Manager - Profile Setup: ${opts.profileName}`,
119
+ ansi.bold + ansi.cyan,
120
+ ),
121
+ );
122
+ }
123
+
124
+ export function printSavedProfileSummary(opts: {
125
+ created: boolean;
126
+ profileName: string;
127
+ appUrl: string;
128
+ dataDir: string;
129
+ openBrowser: boolean;
130
+ }): void {
131
+ line(
132
+ `${opts.created ? paintSuccess("Profile Created:") : paintSuccess("Profile Saved:")} ${paintValue(opts.profileName)}`,
133
+ );
134
+ line(` ${paintWarning("App URL:")} ${paintValue(opts.appUrl)}`);
135
+ line(` ${paintWarning("Data Path:")} ${paintValue(opts.dataDir)}`);
136
+ line(
137
+ ` ${paintWarning("Open Browser:")} ${paintValue(opts.openBrowser ? "Yes" : "No")}`,
138
+ );
139
+ }
140
+
141
+ export function printSetupNextSteps(opts: {
142
+ profileName: string;
143
+ skillsInstalled: boolean;
144
+ }): void {
145
+ const cliHelpCommand =
146
+ opts.profileName === "default"
147
+ ? "hirotm --help"
148
+ : `hirotm --profile ${opts.profileName} --help`;
149
+ const skillLines = [
150
+ "1. Repo skills : npx skills add hiro-league/hirotaskmanager",
151
+ ];
152
+ if (opts.skillsInstalled) {
153
+ skillLines.push("2. Local skills : npx skills add \"$HOME/.taskmanager/skills\"");
154
+ skillLines.push("3. Update later : npx skills update");
155
+ } else {
156
+ skillLines.push("2. Update later : npx skills update");
157
+ }
158
+ const lines = [
159
+ "REQUIRED BEFORE USING hirotm",
160
+ "Install AI agent skills now. The hirotm CLI depends on these skills.",
161
+ "",
162
+ ...skillLines,
163
+ "",
164
+ `After skills install: ${cliHelpCommand}`,
165
+ ];
166
+
167
+ // Package managers may block postinstall hooks, so setup must surface the
168
+ // exact skills commands in a standalone table before startup continues.
169
+ printAsciiTable(lines);
170
+ }
171
+
172
+ export function printSetupContinuePrompt(): void {
173
+ line("Press Enter to continue and start TaskManager...");
174
+ }
175
+
176
+ export async function printPassphraseHint(): Promise<void> {
177
+ // Keep the browser handoff to one line because the recovery key prints later from the server.
178
+ const text =
179
+ "Create your passphrase in the browser. Your recovery key prints here once.";
180
+ await spinForMoment(text, text);
181
+ }
182
+
183
+ export function printRecoveryKey(recoveryKey: string): void {
184
+ line();
185
+ line(paintWarning("Recovery Key:"));
186
+ line(paintValue(recoveryKey));
187
+ line(
188
+ paint(
189
+ out,
190
+ "Store it on a separate device. It will never show again.",
191
+ ansi.dim,
192
+ ),
193
+ );
194
+ line(
195
+ paint(
196
+ out,
197
+ "Use it to recover your passphrase and access your server/data.",
198
+ ansi.dim,
199
+ ),
200
+ );
201
+ line();
202
+ }
203
+
204
+ export function printRecoveryKeyExitHint(appUrl: string): void {
205
+ // Explain why Enter returns to the shell without implying the server is stopping.
206
+ line(
207
+ `After you copy the recovery key, press Enter to close this launcher. TaskManager stays running at ${paintValue(appUrl)}.`,
208
+ );
209
+ }
210
+
211
+ export function isAuthInitialized(authDir: string): boolean {
212
+ return existsSync(path.join(authDir, "auth.json"));
213
+ }