@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.
- package/LICENSE +21 -0
- package/README.md +41 -52
- package/dist/assets/architecture-YZFGNWBL-DoE0KxgG.js +1 -0
- package/dist/assets/architectureDiagram-Q4EWVU46-DeuBhy7X.js +36 -0
- package/dist/assets/{blockDiagram-DXYQGD6D-DfOGNphI.js → blockDiagram-DXYQGD6D-BDBy9ns9.js} +1 -1
- package/dist/assets/{c4Diagram-AHTNJAMY-B2Yfcwbo.js → c4Diagram-AHTNJAMY-CpqJj_8a.js} +1 -1
- package/dist/assets/channel-PHRyjspt.js +1 -0
- package/dist/assets/{chunk-2KRD3SAO-9yt00aGC.js → chunk-2KRD3SAO-DEpUsxdZ.js} +1 -1
- package/dist/assets/chunk-336JU56O-BGQvSwLk.js +2 -0
- package/dist/assets/chunk-426QAEUC-Cl9nQN9c.js +1 -0
- package/dist/assets/{chunk-4TB4RGXK-DF8yJBFl.js → chunk-4TB4RGXK-Dq7aiIrZ.js} +2 -2
- package/dist/assets/{chunk-5FUZZQ4R-XEga0hMC.js → chunk-5FUZZQ4R-B_HuuUjf.js} +1 -1
- package/dist/assets/{chunk-5PVQY5BW-BrmXs2Gs.js → chunk-5PVQY5BW-cGfZCZGU.js} +2 -2
- package/dist/assets/{chunk-67CJDMHE-5wFKo04G.js → chunk-67CJDMHE-BMYAVZfw.js} +1 -1
- package/dist/assets/{chunk-7N4EOEYR-BRRGX_NC.js → chunk-7N4EOEYR-Ct-EY7Nc.js} +1 -1
- package/dist/assets/{chunk-AA7GKIK3-DUZv_pNI.js → chunk-AA7GKIK3-Bd4HFpeo.js} +1 -1
- package/dist/assets/{chunk-CIAEETIT-mA5aM_d7.js → chunk-CIAEETIT-CrFUkPMT.js} +1 -1
- package/dist/assets/{chunk-EDXVE4YY-DxUqDyxy.js → chunk-EDXVE4YY-DMDyt0NF.js} +1 -1
- package/dist/assets/{chunk-ENJZ2VHE-BgZKYo1l.js → chunk-ENJZ2VHE-DrWzOrpd.js} +1 -1
- package/dist/assets/{chunk-FOC6F5B3-B-cqGCPC.js → chunk-FOC6F5B3-Bemzq96j.js} +1 -1
- package/dist/assets/{chunk-ICPOFSXX-BNR1V8rT.js → chunk-ICPOFSXX-DkUVjrLw.js} +5 -5
- package/dist/assets/{chunk-K5T4RW27-BLRDzioh.js → chunk-K5T4RW27-ALKIf000.js} +5 -5
- package/dist/assets/{chunk-KGLVRYIC-CTkQSeKy.js → chunk-KGLVRYIC-Bg6HNTZ-.js} +1 -1
- package/dist/assets/{chunk-LIHQZDEY-Cf34Nu3J.js → chunk-LIHQZDEY-DeyGongE.js} +1 -1
- package/dist/assets/{chunk-ORNJ4GCN-D3uXgbay.js → chunk-ORNJ4GCN-Bx83s1bJ.js} +1 -1
- package/dist/assets/{chunk-OYMX7WX6-syQho5jf.js → chunk-OYMX7WX6-BqRUtRpL.js} +1 -1
- package/dist/assets/{chunk-U2HBQHQK-DTJPeU7W.js → chunk-U2HBQHQK-DogcerR6.js} +1 -1
- package/dist/assets/{chunk-X2U36JSP-CrTnmMqG.js → chunk-X2U36JSP-CwVWdmZV.js} +1 -1
- package/dist/assets/chunk-XPW4576I-DQpNCogT.js +32 -0
- package/dist/assets/{chunk-YZCP3GAM-9wq0QKUn.js → chunk-YZCP3GAM-crQSbji9.js} +1 -1
- package/dist/assets/{chunk-ZZ45TVLE-D3I1kLlo.js → chunk-ZZ45TVLE-Bk1S1YtS.js} +1 -1
- package/dist/assets/classDiagram-6PBFFD2Q-B_TabGaU.js +1 -0
- package/dist/assets/classDiagram-v2-HSJHXN6E-CGnZkUWw.js +1 -0
- package/dist/assets/clone-D4ka472w.js +1 -0
- package/dist/assets/{cose-bilkent-S5V4N54A-BygGvZGW.js → cose-bilkent-S5V4N54A-RBTHUit8.js} +1 -1
- package/dist/assets/cytoscape.esm-BGJwlmkf.js +321 -0
- package/dist/assets/dagre-B32eYLtm.js +1 -0
- package/dist/assets/{dagre-KV5264BT-BBqulDtd.js → dagre-KV5264BT-nX7tuXXn.js} +1 -1
- package/dist/assets/diagram-5BDNPKRD-DRxMXlQr.js +10 -0
- package/dist/assets/diagram-G4DWMVQ6-CoojevGm.js +24 -0
- package/dist/assets/diagram-MMDJMWI5-CWtJyfVW.js +43 -0
- package/dist/assets/diagram-TYMM5635-CsDJC4Hq.js +24 -0
- package/dist/assets/{erDiagram-SMLLAGMA-BN5eJerP.js → erDiagram-SMLLAGMA-Cf7Xtd9A.js} +2 -2
- package/dist/assets/{flatten-C5NL-f24.js → flatten-CYX_pHZ7.js} +1 -1
- package/dist/assets/{flowDiagram-DWJPFMVM-CbFskc8S.js → flowDiagram-DWJPFMVM-DQaeR16a.js} +3 -3
- package/dist/assets/{ganttDiagram-T4ZO3ILL-OCTvbRxF.js → ganttDiagram-T4ZO3ILL-8EIcztcH.js} +1 -1
- package/dist/assets/gitGraph-7Q5UKJZL-BH9A1SAZ.js +1 -0
- package/dist/assets/{gitGraphDiagram-UUTBAWPF-wpqI2kyI.js → gitGraphDiagram-UUTBAWPF-DO9ODqYw.js} +1 -1
- package/dist/assets/graphlib-bPBqlJKT.js +1 -0
- package/dist/assets/identity-Me9aart9.js +1 -0
- package/dist/assets/index-oKG1C41_.js +273 -0
- package/dist/assets/info-OMHHGYJF-BvKR-zWh.js +1 -0
- package/dist/assets/infoDiagram-42DDH7IO-pRTXCm5C.js +2 -0
- package/dist/assets/isEmpty-Cu0k-j1j.js +1 -0
- package/dist/assets/{ishikawaDiagram-UXIWVN3A-Epc23N_0.js → ishikawaDiagram-UXIWVN3A-BP2YE5QI.js} +2 -2
- package/dist/assets/{journeyDiagram-VCZTEJTY-BkMxoaPq.js → journeyDiagram-VCZTEJTY-B3l2juoL.js} +1 -1
- package/dist/assets/{kanban-definition-6JOO6SKY-C8dW_26n.js → kanban-definition-6JOO6SKY-BpIpEOZZ.js} +4 -4
- package/dist/assets/{line-DNzQATGr.js → line-otOkzGl8.js} +1 -1
- package/dist/assets/mermaid-parser.core-xWsW24Gq.js +4 -0
- package/dist/assets/{mindmap-definition-QFDTVHPH-CvpNtrKT.js → mindmap-definition-QFDTVHPH-B9khyC7X.js} +3 -3
- package/dist/assets/packet-4T2RLAQJ-D8Dw3nmf.js +1 -0
- package/dist/assets/pie-ZZUOXDRM-ZghowlAE.js +1 -0
- package/dist/assets/{pieDiagram-DEJITSTG-eENymoXZ.js → pieDiagram-DEJITSTG-v32hL3i7.js} +1 -1
- package/dist/assets/{quadrantDiagram-34T5L4WZ-c0iZxo2I.js → quadrantDiagram-34T5L4WZ-DIL3GDFt.js} +1 -1
- package/dist/assets/radar-PYXPWWZC-D-PK3JOd.js +1 -0
- package/dist/assets/reduce-CImcgAcU.js +1 -0
- package/dist/assets/{requirementDiagram-MS252O5E-CmRO3hLp.js → requirementDiagram-MS252O5E-D8os2-4y.js} +2 -2
- package/dist/assets/{sankeyDiagram-XADWPNL6-woJZoQ58.js → sankeyDiagram-XADWPNL6-BV70D4l5.js} +1 -1
- package/dist/assets/{sequenceDiagram-FGHM5R23-B7qNcwNo.js → sequenceDiagram-FGHM5R23-Cwu8hQW1.js} +1 -1
- package/dist/assets/stateDiagram-FHFEXIEX-oYUWv7Fb.js +1 -0
- package/dist/assets/stateDiagram-v2-QKLJ7IA2-CFUTpFu-.js +1 -0
- package/dist/assets/{timeline-definition-GMOUNBTQ-CQWqDPGG.js → timeline-definition-GMOUNBTQ-CxSdKxpL.js} +1 -1
- package/dist/assets/treeView-SZITEDCU-uVgaJQzG.js +1 -0
- package/dist/assets/treemap-W4RFUUIX-Dcad_9AN.js +1 -0
- package/dist/assets/vennDiagram-DHZGUBPP-D4wgD7QI.js +34 -0
- package/dist/assets/wardley-RL74JXVD-CFXrK8mx.js +1 -0
- package/dist/assets/{wardleyDiagram-NUSXRM2D-DNhPIFCg.js → wardleyDiagram-NUSXRM2D-5Q201ea3.js} +1 -1
- package/dist/assets/{xychartDiagram-5P7HB3ND-BDblAZ11.js → xychartDiagram-5P7HB3ND-BPZv_axd.js} +3 -3
- package/dist/index.html +16 -12
- package/package.json +99 -92
- package/scripts/stubs/node-domexception/index.cjs +18 -0
- package/scripts/stubs/node-domexception/package.json +7 -0
- package/skills/hiro-task-manager-cli/SKILL.md +97 -0
- package/skills/hiro-task-manager-cli/reference/boards.md +143 -0
- package/skills/hiro-task-manager-cli/reference/cli-access-policy.md +72 -0
- package/skills/hiro-task-manager-cli/reference/errors.md +85 -0
- package/skills/hiro-task-manager-cli/reference/lists.md +106 -0
- package/skills/hiro-task-manager-cli/reference/releases.md +87 -0
- package/skills/hiro-task-manager-cli/reference/search.md +38 -0
- package/skills/hiro-task-manager-cli/reference/statuses.md +25 -0
- package/skills/hiro-task-manager-cli/reference/tasks.md +144 -0
- package/skills/hiro-task-manager-cli/reference/trash.md +50 -0
- package/src/cli/bootstrap/launcher.test.ts +66 -0
- package/src/cli/bootstrap/launcher.ts +389 -35
- package/src/cli/bootstrap/program.test.ts +46 -0
- package/src/cli/bootstrap/program.ts +54 -1
- package/src/cli/bootstrap/runtime.test.ts +15 -0
- package/src/cli/bootstrap/runtime.ts +27 -1
- package/src/cli/commands/query.ts +56 -56
- package/src/cli/commands/server.ts +27 -19
- package/src/cli/handlers/boards.test.ts +669 -669
- package/src/cli/handlers/cli-wiring.test.ts +1 -1
- package/src/cli/handlers/search.test.ts +374 -374
- package/src/cli/handlers/search.ts +17 -17
- package/src/cli/handlers/server.test.ts +55 -13
- package/src/cli/handlers/server.ts +16 -3
- package/src/cli/lib/api-client.test.ts +35 -2
- package/src/cli/lib/api-client.ts +43 -10
- package/src/cli/lib/cli-http-errors.test.ts +85 -85
- package/src/cli/lib/command-helpers.ts +161 -154
- package/src/cli/lib/config.ts +4 -0
- package/src/cli/lib/launcherUi.test.ts +74 -0
- package/src/cli/lib/launcherUi.ts +213 -0
- package/src/cli/lib/process.test.ts +24 -5
- package/src/cli/lib/process.ts +86 -55
- package/src/cli/ports/process.ts +8 -2
- package/src/cli/subprocess.real-stack.test.ts +611 -598
- package/src/cli/subprocess.smoke.test.ts +954 -969
- package/src/cli/types/config.ts +2 -6
- package/src/client/components/auth/AuthScreen.tsx +3 -3
- package/src/client/components/board/BoardStatsChips.tsx +233 -233
- package/src/client/components/board/BoardStatsContext.tsx +41 -41
- package/src/client/components/board/boardHeaderButtonStyles.ts +38 -38
- package/src/client/components/board/shortcuts/useBoardShortcutKeydown.ts +49 -49
- package/src/client/components/board/useBoardCanvasPanScroll.ts +108 -108
- package/src/client/components/board/useBoardTaskContainerDroppableReact.ts +33 -33
- package/src/client/components/board/useBoardTaskSortableReact.ts +26 -26
- package/src/client/components/multi-select.tsx +1206 -1206
- package/src/client/components/routing/BoardPage.tsx +20 -20
- package/src/client/components/routing/NavigationRegistrar.tsx +13 -13
- package/src/client/components/settings/SettingsPage.tsx +1 -1
- package/src/client/components/task/TaskCard.tsx +643 -643
- package/src/client/components/ui/badge.tsx +49 -49
- package/src/client/components/ui/button.tsx +65 -65
- package/src/client/components/ui/command.tsx +193 -193
- package/src/client/components/ui/dialog.tsx +163 -163
- package/src/client/components/ui/input-group.tsx +155 -155
- package/src/client/components/ui/input.tsx +19 -19
- package/src/client/components/ui/popover.tsx +87 -87
- package/src/client/components/ui/separator.tsx +28 -28
- package/src/client/components/ui/textarea.tsx +18 -18
- package/src/client/index.css +248 -248
- package/src/client/lib/appNavigate.ts +16 -16
- package/src/client/lib/taskCardDate.ts +111 -111
- package/src/client/lib/utils.ts +6 -6
- package/src/server/auth.ts +351 -302
- package/src/server/bootstrapDev.ts +11 -2
- package/src/server/bootstrapInstalled.ts +6 -1
- package/src/server/index.ts +33 -7
- package/src/server/migrations/013_cli_policy_and_provenance.ts +2 -2
- package/src/server/migrations/019_cli_global_create_board_default_on.ts +14 -0
- package/src/server/migrations/registry.ts +43 -41
- package/src/server/parseBootstrapProfile.ts +42 -0
- package/src/server/storage/cliPolicy.ts +2 -1
- package/src/shared/runtimeConfig.ts +256 -237
- package/src/shared/runtimeIdentity.test.ts +47 -0
- package/src/shared/runtimeIdentity.ts +35 -0
- package/src/shared/serverStatus.ts +21 -0
- package/src/shared/skillsInstall.ts +70 -0
- package/src/shared/terminalColors.ts +24 -0
- package/dist/assets/architecture-YZFGNWBL-3h1eIYfB.js +0 -1
- package/dist/assets/architectureDiagram-Q4EWVU46-DSQ1_74_.js +0 -36
- package/dist/assets/channel-yBmN_ln0.js +0 -1
- package/dist/assets/classDiagram-6PBFFD2Q-CotFZI8-.js +0 -1
- package/dist/assets/classDiagram-v2-HSJHXN6E-DAPzeDGn.js +0 -1
- package/dist/assets/clone-BRQpYu_n.js +0 -1
- package/dist/assets/cytoscape.esm-BIYWHPG0.js +0 -321
- package/dist/assets/dagre-rhyPjnsQ.js +0 -1
- package/dist/assets/diagram-5BDNPKRD-Ky3EXXj0.js +0 -10
- package/dist/assets/diagram-G4DWMVQ6-t7LbT0Uz.js +0 -24
- package/dist/assets/diagram-MMDJMWI5-CdnLXEMx.js +0 -43
- package/dist/assets/diagram-TYMM5635-CnzTqJBM.js +0 -24
- package/dist/assets/gitGraph-7Q5UKJZL-CG8f8JF7.js +0 -1
- package/dist/assets/graphlib-COiJG5Qv.js +0 -1
- package/dist/assets/identity-D4WOnl_h.js +0 -1
- package/dist/assets/index-lyyIVcc_.js +0 -304
- package/dist/assets/info-OMHHGYJF-C8_SHoRO.js +0 -1
- package/dist/assets/infoDiagram-42DDH7IO-BbvTdpSV.js +0 -2
- package/dist/assets/mermaid-parser.core-6Tn8epr_.js +0 -4
- package/dist/assets/packet-4T2RLAQJ-BvpAX0kJ.js +0 -1
- package/dist/assets/pie-ZZUOXDRM-Ow26Yf-E.js +0 -1
- package/dist/assets/radar-PYXPWWZC-e_ron5jQ.js +0 -1
- package/dist/assets/reduce-BDOBPIXr.js +0 -1
- package/dist/assets/stateDiagram-FHFEXIEX-CYfGMoR8.js +0 -1
- package/dist/assets/stateDiagram-v2-QKLJ7IA2-CO1W_n55.js +0 -1
- package/dist/assets/treeView-SZITEDCU-DsEr3xeq.js +0 -1
- package/dist/assets/treemap-W4RFUUIX-DV7nk2AB.js +0 -1
- package/dist/assets/vennDiagram-DHZGUBPP-BjTbuhcb.js +0 -34
- package/dist/assets/wardley-RL74JXVD-CrrFU9AE.js +0 -1
- /package/dist/assets/{chunk-4BX2VUAB-ean5NKtU.js → chunk-4BX2VUAB-C70mcfQR.js} +0 -0
- /package/dist/assets/{chunk-55IACEB6-CvSRyJqy.js → chunk-55IACEB6-CWfnqcLM.js} +0 -0
- /package/dist/assets/{chunk-BSJP7CBP-D8kBlJsf.js → chunk-BSJP7CBP-B0LrXV9y.js} +0 -0
- /package/dist/assets/{chunk-FMBD7UC4-DrNhFt1N.js → chunk-FMBD7UC4-_mV71Mwu.js} +0 -0
- /package/dist/assets/{chunk-QZHKN3VN-Csp3OYJY.js → chunk-QZHKN3VN-t2nrsegL.js} +0 -0
- /package/dist/assets/{katex-8mXVa4k3.js → katex-B2dtGfSp.js} +0 -0
- /package/dist/assets/{rough.esm-DtEqI08j.js → rough.esm-DEh6Frf9.js} +0 -0
|
@@ -1,8 +1,13 @@
|
|
|
1
|
+
import { existsSync, readFileSync, unlinkSync } from "node:fs";
|
|
1
2
|
import path from "node:path";
|
|
2
3
|
import process from "node:process";
|
|
3
4
|
import { createInterface } from "node:readline/promises";
|
|
4
5
|
import { Command } from "commander";
|
|
5
|
-
import {
|
|
6
|
+
import {
|
|
7
|
+
ensureRuntimeDirectories,
|
|
8
|
+
hasAnyProfileConfigOnDisk,
|
|
9
|
+
} from "../../shared/runtimeConfig";
|
|
10
|
+
import { ensureBundledSkills } from "../../shared/skillsInstall";
|
|
6
11
|
import {
|
|
7
12
|
getDefaultInstalledAuthDir,
|
|
8
13
|
getDefaultInstalledDataDir,
|
|
@@ -16,8 +21,24 @@ import { parsePortOption } from "../lib/command-helpers";
|
|
|
16
21
|
import { CLI_ERR } from "../types/errors";
|
|
17
22
|
import { CLI_DEFAULTS } from "../lib/constants";
|
|
18
23
|
import { CliError, exitWithError } from "../lib/output";
|
|
19
|
-
import { startServer } from "../lib/process";
|
|
24
|
+
import { readServerStatus, startServer, stopServer } from "../lib/process";
|
|
20
25
|
import { canPromptInteractively } from "../lib/tty";
|
|
26
|
+
import type { ServerStartMode } from "../ports/process";
|
|
27
|
+
import {
|
|
28
|
+
formatBooleanPrompt,
|
|
29
|
+
formatTextPrompt,
|
|
30
|
+
isAuthInitialized,
|
|
31
|
+
paintValue,
|
|
32
|
+
printSetupContinuePrompt,
|
|
33
|
+
printSetupNextSteps,
|
|
34
|
+
printPassphraseHint,
|
|
35
|
+
printRecoveryKey,
|
|
36
|
+
printRecoveryKeyExitHint,
|
|
37
|
+
printInteractiveSetupHeader,
|
|
38
|
+
printSavedProfileSummary,
|
|
39
|
+
spinForMoment,
|
|
40
|
+
startInlineSpinner,
|
|
41
|
+
} from "../lib/launcherUi";
|
|
21
42
|
|
|
22
43
|
/** Phase 3: installed-app launcher logic (formerly all of app.ts). */
|
|
23
44
|
|
|
@@ -28,6 +49,50 @@ interface LauncherOptions {
|
|
|
28
49
|
profile?: string;
|
|
29
50
|
}
|
|
30
51
|
|
|
52
|
+
interface LauncherServerOptions {
|
|
53
|
+
profile?: string;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
interface LauncherServerStartOptions extends LauncherServerOptions {
|
|
57
|
+
dataDir?: string;
|
|
58
|
+
foreground?: boolean;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export interface LauncherSetupResult {
|
|
62
|
+
config: CliConfigFile;
|
|
63
|
+
setupMeta: {
|
|
64
|
+
/** User went through interactive prompts (not bunx / non-TTY defaults). */
|
|
65
|
+
justFinishedInteractiveSetup: boolean;
|
|
66
|
+
/** No profile had config.json on disk before this run’s save. */
|
|
67
|
+
firstProfileOnMachine: boolean;
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export function resolveLauncherStartPlan(options: {
|
|
72
|
+
shouldRunSetup: boolean;
|
|
73
|
+
needsRecoveryKeyExitFlow: boolean;
|
|
74
|
+
alreadyRunning: boolean;
|
|
75
|
+
shouldOpenBrowser: boolean;
|
|
76
|
+
preferForegroundWhenNotSetup?: boolean;
|
|
77
|
+
}): {
|
|
78
|
+
startMode: ServerStartMode;
|
|
79
|
+
readyLabel: "Started" | "Already started";
|
|
80
|
+
shouldOpenBrowserOnReady: boolean;
|
|
81
|
+
} {
|
|
82
|
+
return {
|
|
83
|
+
startMode: options.shouldRunSetup
|
|
84
|
+
? options.needsRecoveryKeyExitFlow
|
|
85
|
+
? "background-attached"
|
|
86
|
+
: "foreground"
|
|
87
|
+
: options.preferForegroundWhenNotSetup
|
|
88
|
+
? "foreground"
|
|
89
|
+
: "background",
|
|
90
|
+
readyLabel: options.alreadyRunning ? "Already started" : "Started",
|
|
91
|
+
shouldOpenBrowserOnReady:
|
|
92
|
+
options.shouldOpenBrowser && !options.alreadyRunning,
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
|
|
31
96
|
function parseBrowserMode(browser: string | undefined): boolean | undefined {
|
|
32
97
|
if (!browser?.trim()) return undefined;
|
|
33
98
|
|
|
@@ -83,7 +148,7 @@ async function promptWithDefault(
|
|
|
83
148
|
output: process.stdout,
|
|
84
149
|
});
|
|
85
150
|
try {
|
|
86
|
-
const answer = await rl.question(`${question}
|
|
151
|
+
const answer = await rl.question(`${question} `);
|
|
87
152
|
return answer.trim() || defaultValue;
|
|
88
153
|
} finally {
|
|
89
154
|
rl.close();
|
|
@@ -94,14 +159,13 @@ async function promptBoolean(
|
|
|
94
159
|
question: string,
|
|
95
160
|
defaultValue: boolean,
|
|
96
161
|
): Promise<boolean> {
|
|
97
|
-
const label = defaultValue ? "Y/n" : "y/N";
|
|
98
162
|
const rl = createInterface({
|
|
99
163
|
input: process.stdin,
|
|
100
164
|
output: process.stdout,
|
|
101
165
|
});
|
|
102
166
|
try {
|
|
103
167
|
for (;;) {
|
|
104
|
-
const answer = (await rl.question(`${question}
|
|
168
|
+
const answer = (await rl.question(`${question}: `))
|
|
105
169
|
.trim()
|
|
106
170
|
.toLowerCase();
|
|
107
171
|
if (!answer) return defaultValue;
|
|
@@ -119,10 +183,11 @@ async function runLauncherSetup(overrides: {
|
|
|
119
183
|
port?: number;
|
|
120
184
|
dataDir?: string;
|
|
121
185
|
openBrowser?: boolean;
|
|
122
|
-
}): Promise<
|
|
186
|
+
}): Promise<LauncherSetupResult> {
|
|
123
187
|
const defaults = resolveLauncherDefaults(overrides);
|
|
124
188
|
const configScope = { profile: overrides.profile, kind: "installed" as const };
|
|
125
189
|
const existing = readConfigFile(configScope);
|
|
190
|
+
const machineHadNoProfilesBefore = !hasAnyProfileConfigOnDisk();
|
|
126
191
|
|
|
127
192
|
// Keep the first-run path working for bunx and other non-interactive entry
|
|
128
193
|
// points by saving sane defaults instead of failing on missing prompts.
|
|
@@ -134,18 +199,47 @@ async function runLauncherSetup(overrides: {
|
|
|
134
199
|
dataDir: config.data_dir,
|
|
135
200
|
authDir: config.auth_dir,
|
|
136
201
|
});
|
|
137
|
-
return
|
|
202
|
+
return {
|
|
203
|
+
config,
|
|
204
|
+
setupMeta: {
|
|
205
|
+
justFinishedInteractiveSetup: false,
|
|
206
|
+
firstProfileOnMachine: machineHadNoProfilesBefore,
|
|
207
|
+
},
|
|
208
|
+
};
|
|
138
209
|
}
|
|
139
210
|
|
|
140
|
-
|
|
211
|
+
printInteractiveSetupHeader({
|
|
212
|
+
profileName: resolveProfileName(configScope),
|
|
213
|
+
firstProfileOnMachine: machineHadNoProfilesBefore,
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
// Show the profile context line first so the user understands why the
|
|
217
|
+
// following prompts are being asked.
|
|
218
|
+
await spinForMoment(
|
|
219
|
+
"Looking for existing profiles...",
|
|
220
|
+
machineHadNoProfilesBefore
|
|
221
|
+
? `Creating Profile: ${paintValue(resolveProfileName(configScope))}`
|
|
222
|
+
: `Using Profile: ${paintValue(resolveProfileName(configScope))}`,
|
|
223
|
+
);
|
|
224
|
+
|
|
225
|
+
const portValue = await promptWithDefault(
|
|
226
|
+
formatTextPrompt("Pick a port for web/api", String(defaults.port)),
|
|
227
|
+
String(defaults.port),
|
|
228
|
+
);
|
|
141
229
|
|
|
142
|
-
const portValue = await promptWithDefault("Port", String(defaults.port));
|
|
143
230
|
const dataDirValue = await promptWithDefault(
|
|
144
|
-
|
|
231
|
+
formatTextPrompt(
|
|
232
|
+
"Pick a Data Directory to place the database",
|
|
233
|
+
defaults.data_dir,
|
|
234
|
+
),
|
|
145
235
|
defaults.data_dir,
|
|
146
236
|
);
|
|
237
|
+
|
|
147
238
|
const openBrowser = await promptBoolean(
|
|
148
|
-
|
|
239
|
+
formatBooleanPrompt(
|
|
240
|
+
"Open default browser when starting the server with hirotaskmanager",
|
|
241
|
+
defaults.open_browser,
|
|
242
|
+
),
|
|
149
243
|
defaults.open_browser,
|
|
150
244
|
);
|
|
151
245
|
|
|
@@ -156,14 +250,34 @@ async function runLauncherSetup(overrides: {
|
|
|
156
250
|
auth_dir: defaults.auth_dir,
|
|
157
251
|
open_browser: openBrowser,
|
|
158
252
|
};
|
|
159
|
-
|
|
253
|
+
writeConfigFile(config, configScope);
|
|
160
254
|
ensureRuntimeDirectories({
|
|
161
255
|
...configScope,
|
|
162
256
|
dataDir: config.data_dir,
|
|
163
257
|
authDir: config.auth_dir,
|
|
164
258
|
});
|
|
165
|
-
|
|
166
|
-
|
|
259
|
+
|
|
260
|
+
await spinForMoment(
|
|
261
|
+
machineHadNoProfilesBefore
|
|
262
|
+
? `Saving Profile: ${paintValue(resolveProfileName(configScope))}`
|
|
263
|
+
: `Saving Profile: ${paintValue(resolveProfileName(configScope))}`,
|
|
264
|
+
);
|
|
265
|
+
|
|
266
|
+
printSavedProfileSummary({
|
|
267
|
+
created: machineHadNoProfilesBefore,
|
|
268
|
+
profileName: resolveProfileName(configScope),
|
|
269
|
+
appUrl: `http://127.0.0.1:${config.port}`,
|
|
270
|
+
dataDir: path.resolve(config.data_dir!),
|
|
271
|
+
openBrowser,
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
return {
|
|
275
|
+
config,
|
|
276
|
+
setupMeta: {
|
|
277
|
+
justFinishedInteractiveSetup: true,
|
|
278
|
+
firstProfileOnMachine: machineHadNoProfilesBefore,
|
|
279
|
+
},
|
|
280
|
+
};
|
|
167
281
|
}
|
|
168
282
|
|
|
169
283
|
async function openBrowser(url: string): Promise<void> {
|
|
@@ -188,6 +302,48 @@ async function openBrowser(url: string): Promise<void> {
|
|
|
188
302
|
}
|
|
189
303
|
}
|
|
190
304
|
|
|
305
|
+
/**
|
|
306
|
+
* Poll for the recovery-key sidecar file written by the server during
|
|
307
|
+
* `setupPassphrase`. Returns the key string once available, then deletes the
|
|
308
|
+
* file so it is never left on disk.
|
|
309
|
+
*/
|
|
310
|
+
async function waitForRecoveryKeyFile(authDir: string): Promise<string> {
|
|
311
|
+
const keyPath = path.join(authDir, "recovery-key.tmp");
|
|
312
|
+
while (!existsSync(keyPath)) {
|
|
313
|
+
await Bun.sleep(250);
|
|
314
|
+
}
|
|
315
|
+
const key = readFileSync(keyPath, "utf8").trim();
|
|
316
|
+
try {
|
|
317
|
+
unlinkSync(keyPath);
|
|
318
|
+
} catch {
|
|
319
|
+
// Best-effort cleanup; the file has owner-only perms already.
|
|
320
|
+
}
|
|
321
|
+
return key;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
async function waitForEnterKey(): Promise<void> {
|
|
325
|
+
const rl = createInterface({
|
|
326
|
+
input: process.stdin,
|
|
327
|
+
output: process.stdout,
|
|
328
|
+
});
|
|
329
|
+
try {
|
|
330
|
+
await rl.question("");
|
|
331
|
+
} finally {
|
|
332
|
+
rl.close();
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
function resolveInstalledLauncherProfile(profile: string | undefined): string {
|
|
337
|
+
return resolveProfileName({
|
|
338
|
+
profile,
|
|
339
|
+
kind: "installed",
|
|
340
|
+
});
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
function printLauncherJson(data: unknown): void {
|
|
344
|
+
process.stdout.write(`${JSON.stringify(data)}\n`);
|
|
345
|
+
}
|
|
346
|
+
|
|
191
347
|
export function createHirotaskmanagerProgram(): Command {
|
|
192
348
|
const program = new Command();
|
|
193
349
|
program
|
|
@@ -206,21 +362,46 @@ export function createHirotaskmanagerProgram(): Command {
|
|
|
206
362
|
? path.resolve(options.dataDir.trim())
|
|
207
363
|
: undefined;
|
|
208
364
|
const overrideOpenBrowser = parseBrowserMode(options.browser);
|
|
209
|
-
const selectedProfile =
|
|
210
|
-
profile: options.profile,
|
|
211
|
-
kind: "installed",
|
|
212
|
-
});
|
|
365
|
+
const selectedProfile = resolveInstalledLauncherProfile(options.profile);
|
|
213
366
|
|
|
214
367
|
const shouldRunSetup =
|
|
215
368
|
options.setup ||
|
|
216
369
|
!hasCliConfigFile({ profile: selectedProfile, kind: "installed" });
|
|
217
|
-
|
|
370
|
+
|
|
371
|
+
const setupResult: LauncherSetupResult = shouldRunSetup
|
|
218
372
|
? await runLauncherSetup({
|
|
219
373
|
profile: selectedProfile,
|
|
220
374
|
dataDir: overrideDataDir,
|
|
221
375
|
openBrowser: overrideOpenBrowser,
|
|
222
376
|
})
|
|
223
|
-
:
|
|
377
|
+
: {
|
|
378
|
+
config: readConfigFile({
|
|
379
|
+
profile: selectedProfile,
|
|
380
|
+
kind: "installed",
|
|
381
|
+
}),
|
|
382
|
+
setupMeta: {
|
|
383
|
+
justFinishedInteractiveSetup: false,
|
|
384
|
+
firstProfileOnMachine: false,
|
|
385
|
+
},
|
|
386
|
+
};
|
|
387
|
+
|
|
388
|
+
// Registry installs can skip lifecycle hooks, so setup itself must
|
|
389
|
+
// print the exact `npx skills` commands users should run next.
|
|
390
|
+
const skillsInstalled = ensureBundledSkills();
|
|
391
|
+
if (shouldRunSetup) {
|
|
392
|
+
printSetupNextSteps({
|
|
393
|
+
profileName: selectedProfile,
|
|
394
|
+
skillsInstalled,
|
|
395
|
+
});
|
|
396
|
+
if (setupResult.setupMeta.justFinishedInteractiveSetup) {
|
|
397
|
+
// Keep setup blocked here so users see the required skills step
|
|
398
|
+
// before the server starts and the browser steals focus.
|
|
399
|
+
printSetupContinuePrompt();
|
|
400
|
+
await waitForEnterKey();
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
const launcherConfig = setupResult.config;
|
|
224
405
|
|
|
225
406
|
const port =
|
|
226
407
|
launcherConfig.port ?? CLI_DEFAULTS.INSTALLED_DEFAULT_PORT;
|
|
@@ -232,32 +413,205 @@ export function createHirotaskmanagerProgram(): Command {
|
|
|
232
413
|
kind: "installed",
|
|
233
414
|
}),
|
|
234
415
|
);
|
|
416
|
+
const authDir = path.resolve(
|
|
417
|
+
launcherConfig.auth_dir ??
|
|
418
|
+
getDefaultInstalledAuthDir({
|
|
419
|
+
profile: selectedProfile,
|
|
420
|
+
kind: "installed",
|
|
421
|
+
}),
|
|
422
|
+
);
|
|
235
423
|
const shouldOpenBrowser =
|
|
236
424
|
overrideOpenBrowser ?? launcherConfig.open_browser ?? true;
|
|
237
425
|
|
|
426
|
+
const url = `http://127.0.0.1:${port}`;
|
|
427
|
+
const needsRecoveryKeyExitFlow =
|
|
428
|
+
setupResult.setupMeta.justFinishedInteractiveSetup &&
|
|
429
|
+
!isAuthInitialized(authDir);
|
|
430
|
+
// Keep normal launcher runs non-blocking, and avoid reopening the browser
|
|
431
|
+
// when the launcher is only attaching to an already running profile.
|
|
432
|
+
const alreadyRunning = shouldRunSetup
|
|
433
|
+
? false
|
|
434
|
+
: (
|
|
435
|
+
await readServerStatus({
|
|
436
|
+
kind: "installed",
|
|
437
|
+
profile: selectedProfile,
|
|
438
|
+
port,
|
|
439
|
+
})
|
|
440
|
+
).running;
|
|
441
|
+
const startPlan = resolveLauncherStartPlan({
|
|
442
|
+
shouldRunSetup,
|
|
443
|
+
needsRecoveryKeyExitFlow,
|
|
444
|
+
alreadyRunning,
|
|
445
|
+
shouldOpenBrowser,
|
|
446
|
+
});
|
|
447
|
+
|
|
448
|
+
const startupSpinner = startInlineSpinner(
|
|
449
|
+
`${alreadyRunning ? "Checking Server" : "Starting Server"} with profile ${paintValue(selectedProfile)}: ${paintValue(url)}`,
|
|
450
|
+
);
|
|
451
|
+
const previousSilentStartup = process.env.TASKMANAGER_SILENT_STARTUP_LOG;
|
|
452
|
+
// Let the launcher own startup copy so first-time setup stays compact.
|
|
453
|
+
process.env.TASKMANAGER_SILENT_STARTUP_LOG = "1";
|
|
454
|
+
|
|
238
455
|
let browserHandled = false;
|
|
239
|
-
|
|
240
|
-
|
|
456
|
+
let runningUrl = url;
|
|
457
|
+
try {
|
|
458
|
+
await startServer(
|
|
459
|
+
{
|
|
460
|
+
kind: "installed",
|
|
461
|
+
profile: selectedProfile,
|
|
462
|
+
port,
|
|
463
|
+
dataDir,
|
|
464
|
+
},
|
|
465
|
+
startPlan.startMode,
|
|
466
|
+
async (status) => {
|
|
467
|
+
const finalUrl = status.url;
|
|
468
|
+
runningUrl = finalUrl;
|
|
469
|
+
startupSpinner.stop(
|
|
470
|
+
`${startPlan.readyLabel}, listening at ${paintValue(finalUrl)}`,
|
|
471
|
+
);
|
|
472
|
+
|
|
473
|
+
if (!browserHandled && startPlan.shouldOpenBrowserOnReady) {
|
|
474
|
+
browserHandled = true;
|
|
475
|
+
await openBrowser(finalUrl);
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
if (needsRecoveryKeyExitFlow) {
|
|
479
|
+
await printPassphraseHint();
|
|
480
|
+
}
|
|
481
|
+
},
|
|
482
|
+
);
|
|
483
|
+
|
|
484
|
+
if (needsRecoveryKeyExitFlow) {
|
|
485
|
+
const recoveryKey = await waitForRecoveryKeyFile(authDir);
|
|
486
|
+
printRecoveryKey(recoveryKey);
|
|
487
|
+
printRecoveryKeyExitHint(runningUrl);
|
|
488
|
+
await waitForEnterKey();
|
|
489
|
+
}
|
|
490
|
+
} finally {
|
|
491
|
+
if (previousSilentStartup === undefined) {
|
|
492
|
+
delete process.env.TASKMANAGER_SILENT_STARTUP_LOG;
|
|
493
|
+
} else {
|
|
494
|
+
process.env.TASKMANAGER_SILENT_STARTUP_LOG = previousSilentStartup;
|
|
495
|
+
}
|
|
496
|
+
startupSpinner.stop(null);
|
|
497
|
+
}
|
|
498
|
+
} catch (error) {
|
|
499
|
+
exitWithError(error);
|
|
500
|
+
}
|
|
501
|
+
});
|
|
502
|
+
|
|
503
|
+
const server = program
|
|
504
|
+
.command("server")
|
|
505
|
+
.description("Start, stop, or inspect the installed TaskManager server");
|
|
506
|
+
|
|
507
|
+
server
|
|
508
|
+
.command("start")
|
|
509
|
+
.description("Start the installed TaskManager server")
|
|
510
|
+
.option("--profile <name>", "Launcher profile name for this command")
|
|
511
|
+
.option("--data-dir <path>", "Override the task data directory")
|
|
512
|
+
.option("--foreground", "Run the server in the foreground")
|
|
513
|
+
.action(async (options: LauncherServerStartOptions, command: Command) => {
|
|
514
|
+
try {
|
|
515
|
+
const profile = resolveInstalledLauncherProfile(
|
|
516
|
+
(command.optsWithGlobals() as LauncherServerStartOptions).profile ?? options.profile,
|
|
517
|
+
);
|
|
518
|
+
const overrideDataDir = options.dataDir?.trim()
|
|
519
|
+
? path.resolve(options.dataDir.trim())
|
|
520
|
+
: undefined;
|
|
521
|
+
const config = readConfigFile({ profile, kind: "installed" });
|
|
522
|
+
const port = config.port ?? CLI_DEFAULTS.INSTALLED_DEFAULT_PORT;
|
|
523
|
+
const dataDir = path.resolve(
|
|
524
|
+
overrideDataDir ??
|
|
525
|
+
config.data_dir ??
|
|
526
|
+
getDefaultInstalledDataDir({ profile, kind: "installed" }),
|
|
527
|
+
);
|
|
528
|
+
const status = await readServerStatus({
|
|
529
|
+
kind: "installed",
|
|
530
|
+
profile,
|
|
531
|
+
port,
|
|
532
|
+
});
|
|
533
|
+
const startPlan = resolveLauncherStartPlan({
|
|
534
|
+
shouldRunSetup: false,
|
|
535
|
+
needsRecoveryKeyExitFlow: false,
|
|
536
|
+
alreadyRunning: status.running,
|
|
537
|
+
shouldOpenBrowser: false,
|
|
538
|
+
preferForegroundWhenNotSetup: options.foreground === true,
|
|
539
|
+
});
|
|
540
|
+
const startupSpinner = startInlineSpinner(
|
|
541
|
+
`${status.running ? "Checking Server" : "Starting Server"} with profile ${paintValue(profile)}: ${paintValue(status.running ? status.url : `http://127.0.0.1:${port}`)}`,
|
|
542
|
+
);
|
|
543
|
+
|
|
544
|
+
try {
|
|
545
|
+
// Launcher `server start` is human-facing, so prefer concise text
|
|
546
|
+
// instead of JSON while still sharing the same server lifecycle path.
|
|
547
|
+
await startServer(
|
|
548
|
+
{
|
|
549
|
+
kind: "installed",
|
|
550
|
+
profile,
|
|
551
|
+
port,
|
|
552
|
+
dataDir,
|
|
553
|
+
},
|
|
554
|
+
startPlan.startMode,
|
|
555
|
+
async (started) => {
|
|
556
|
+
startupSpinner.stop(
|
|
557
|
+
`${startPlan.readyLabel}, listening at ${paintValue(started.url)}`,
|
|
558
|
+
);
|
|
559
|
+
},
|
|
560
|
+
);
|
|
561
|
+
} finally {
|
|
562
|
+
startupSpinner.stop(null);
|
|
563
|
+
}
|
|
564
|
+
} catch (error) {
|
|
565
|
+
exitWithError(error);
|
|
566
|
+
}
|
|
567
|
+
});
|
|
568
|
+
|
|
569
|
+
server
|
|
570
|
+
.command("status")
|
|
571
|
+
.description("Show whether the installed TaskManager server is running")
|
|
572
|
+
.option("--profile <name>", "Launcher profile name for this command")
|
|
573
|
+
.action(async (options: LauncherServerOptions, command: Command) => {
|
|
574
|
+
try {
|
|
575
|
+
const profile = resolveInstalledLauncherProfile(
|
|
576
|
+
(command.optsWithGlobals() as LauncherServerOptions).profile ?? options.profile,
|
|
577
|
+
);
|
|
578
|
+
printLauncherJson(
|
|
579
|
+
await readServerStatus({
|
|
241
580
|
kind: "installed",
|
|
242
|
-
profile
|
|
243
|
-
|
|
244
|
-
dataDir,
|
|
245
|
-
},
|
|
246
|
-
false,
|
|
247
|
-
async (status) => {
|
|
248
|
-
const url = status.url ?? `http://127.0.0.1:${port}`;
|
|
249
|
-
console.log(`TaskManager running at ${url}`);
|
|
250
|
-
if (!browserHandled && shouldOpenBrowser) {
|
|
251
|
-
browserHandled = true;
|
|
252
|
-
await openBrowser(url);
|
|
253
|
-
}
|
|
254
|
-
},
|
|
581
|
+
profile,
|
|
582
|
+
}),
|
|
255
583
|
);
|
|
256
584
|
} catch (error) {
|
|
257
585
|
exitWithError(error);
|
|
258
586
|
}
|
|
259
587
|
});
|
|
260
588
|
|
|
589
|
+
server
|
|
590
|
+
.command("stop")
|
|
591
|
+
.description("Stop a background installed server started for this profile")
|
|
592
|
+
.option("--profile <name>", "Launcher profile name for this command")
|
|
593
|
+
.action(async (options: LauncherServerOptions, command: Command) => {
|
|
594
|
+
try {
|
|
595
|
+
const profile = resolveInstalledLauncherProfile(
|
|
596
|
+
(command.optsWithGlobals() as LauncherServerOptions).profile ?? options.profile,
|
|
597
|
+
);
|
|
598
|
+
const stopSpinner = startInlineSpinner(
|
|
599
|
+
`Stopping Server with profile ${paintValue(profile)}`,
|
|
600
|
+
);
|
|
601
|
+
try {
|
|
602
|
+
await stopServer({
|
|
603
|
+
kind: "installed",
|
|
604
|
+
profile,
|
|
605
|
+
});
|
|
606
|
+
stopSpinner.stop("Server stopped");
|
|
607
|
+
} finally {
|
|
608
|
+
stopSpinner.stop(null);
|
|
609
|
+
}
|
|
610
|
+
} catch (error) {
|
|
611
|
+
exitWithError(error);
|
|
612
|
+
}
|
|
613
|
+
});
|
|
614
|
+
|
|
261
615
|
return program;
|
|
262
616
|
}
|
|
263
617
|
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
import {
|
|
3
|
+
getHirotmLauncherSetupCommand,
|
|
4
|
+
shouldRequireLauncherSetupForHirotm,
|
|
5
|
+
} from "./program";
|
|
6
|
+
|
|
7
|
+
describe("shouldRequireLauncherSetupForHirotm", () => {
|
|
8
|
+
test("requires launcher setup for installed mode without a saved profile", () => {
|
|
9
|
+
expect(
|
|
10
|
+
shouldRequireLauncherSetupForHirotm({
|
|
11
|
+
runtimeKind: "installed",
|
|
12
|
+
hasInstalledProfileConfig: false,
|
|
13
|
+
}),
|
|
14
|
+
).toBe(true);
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
test("allows installed mode once the profile config exists", () => {
|
|
18
|
+
expect(
|
|
19
|
+
shouldRequireLauncherSetupForHirotm({
|
|
20
|
+
runtimeKind: "installed",
|
|
21
|
+
hasInstalledProfileConfig: true,
|
|
22
|
+
}),
|
|
23
|
+
).toBe(false);
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
test("never blocks dev mode on launcher setup", () => {
|
|
27
|
+
expect(
|
|
28
|
+
shouldRequireLauncherSetupForHirotm({
|
|
29
|
+
runtimeKind: "dev",
|
|
30
|
+
hasInstalledProfileConfig: false,
|
|
31
|
+
}),
|
|
32
|
+
).toBe(false);
|
|
33
|
+
});
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
describe("getHirotmLauncherSetupCommand", () => {
|
|
37
|
+
test("uses the plain launcher command for the default profile", () => {
|
|
38
|
+
expect(getHirotmLauncherSetupCommand("default")).toBe("hirotaskmanager");
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
test("includes the selected profile for named profiles", () => {
|
|
42
|
+
expect(getHirotmLauncherSetupCommand("work")).toBe(
|
|
43
|
+
"hirotaskmanager --profile work",
|
|
44
|
+
);
|
|
45
|
+
});
|
|
46
|
+
});
|
|
@@ -8,8 +8,56 @@ import { registerStatusCommands } from "../commands/statuses";
|
|
|
8
8
|
import { registerTaskCommands } from "../commands/tasks";
|
|
9
9
|
import { registerTrashCommands } from "../commands/trash";
|
|
10
10
|
import { createDefaultCliContext } from "../handlers/context";
|
|
11
|
+
import { hasCliConfigFile, resolveProfileName, resolveRuntimeKind } from "../lib/config";
|
|
11
12
|
import { syncCliOutputFormatFromGlobals } from "../lib/cliFormat";
|
|
12
|
-
import { exitWithError, resetCliOutputFormat } from "../lib/output";
|
|
13
|
+
import { CliError, exitWithError, resetCliOutputFormat } from "../lib/output";
|
|
14
|
+
import { CLI_ERR } from "../types/errors";
|
|
15
|
+
|
|
16
|
+
export function shouldRequireLauncherSetupForHirotm(options: {
|
|
17
|
+
runtimeKind: "installed" | "dev";
|
|
18
|
+
hasInstalledProfileConfig: boolean;
|
|
19
|
+
}): boolean {
|
|
20
|
+
return (
|
|
21
|
+
options.runtimeKind === "installed" &&
|
|
22
|
+
options.hasInstalledProfileConfig === false
|
|
23
|
+
);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function getHirotmLauncherSetupCommand(profileName: string): string {
|
|
27
|
+
return profileName === "default"
|
|
28
|
+
? "hirotaskmanager"
|
|
29
|
+
: `hirotaskmanager --profile ${profileName}`;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function ensureInstalledProfileIsReadyForHirotm(): void {
|
|
33
|
+
const runtimeKind = resolveRuntimeKind();
|
|
34
|
+
const profileName = resolveProfileName({ kind: "installed" });
|
|
35
|
+
const hasInstalledProfileConfig = hasCliConfigFile({
|
|
36
|
+
profile: profileName,
|
|
37
|
+
kind: "installed",
|
|
38
|
+
});
|
|
39
|
+
if (
|
|
40
|
+
!shouldRequireLauncherSetupForHirotm({
|
|
41
|
+
runtimeKind,
|
|
42
|
+
hasInstalledProfileConfig,
|
|
43
|
+
})
|
|
44
|
+
) {
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const setupCommand = getHirotmLauncherSetupCommand(profileName);
|
|
49
|
+
// Package managers can skip lifecycle scripts, so actionable `hirotm`
|
|
50
|
+
// commands must direct users back to the launcher for first-run setup.
|
|
51
|
+
throw new CliError(
|
|
52
|
+
`No installed TaskManager profile found for "${profileName}". Run \`${setupCommand}\` first to create and configure this profile.`,
|
|
53
|
+
2,
|
|
54
|
+
{
|
|
55
|
+
code: CLI_ERR.missingRequired,
|
|
56
|
+
profile: profileName,
|
|
57
|
+
hint: `Run \`${setupCommand}\` first.`,
|
|
58
|
+
},
|
|
59
|
+
);
|
|
60
|
+
}
|
|
13
61
|
|
|
14
62
|
/**
|
|
15
63
|
* Build the hirotm Commander program.
|
|
@@ -21,6 +69,10 @@ export function createHirotmProgram(): Command {
|
|
|
21
69
|
.name("hirotm")
|
|
22
70
|
.description("TaskManager CLI for local app control and JSON queries")
|
|
23
71
|
.option("--profile <name>", "Runtime profile name (default: default, dev)")
|
|
72
|
+
.option(
|
|
73
|
+
"--port <port>",
|
|
74
|
+
"HTTP port for the local API (default: from profile config.json)",
|
|
75
|
+
)
|
|
24
76
|
.option(
|
|
25
77
|
"--client-name <name>",
|
|
26
78
|
"Human-friendly client label sent with API requests (for notifications)",
|
|
@@ -41,6 +93,7 @@ export function createHirotmProgram(): Command {
|
|
|
41
93
|
quiet?: boolean;
|
|
42
94
|
};
|
|
43
95
|
syncCliOutputFormatFromGlobals(opts);
|
|
96
|
+
ensureInstalledProfileIsReadyForHirotm();
|
|
44
97
|
});
|
|
45
98
|
|
|
46
99
|
const ctx = createDefaultCliContext();
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { describe, expect, test } from "bun:test";
|
|
2
2
|
import {
|
|
3
3
|
readClientNameArg,
|
|
4
|
+
readPortArg,
|
|
4
5
|
readProfileArg,
|
|
5
6
|
} from "./runtime";
|
|
6
7
|
|
|
@@ -33,3 +34,17 @@ describe("readProfileArg", () => {
|
|
|
33
34
|
expect(readProfileArg([])).toBeUndefined();
|
|
34
35
|
});
|
|
35
36
|
});
|
|
37
|
+
|
|
38
|
+
describe("readPortArg", () => {
|
|
39
|
+
test("parses --port value form", () => {
|
|
40
|
+
expect(readPortArg(["hirotm", "--port", "3002", "boards"])).toBe(3002);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
test("parses --port=value form", () => {
|
|
44
|
+
expect(readPortArg(["--port=4000"])).toBe(4000);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
test("returns undefined when absent", () => {
|
|
48
|
+
expect(readPortArg(["boards", "list"])).toBeUndefined();
|
|
49
|
+
});
|
|
50
|
+
});
|