@hiroleague/taskmanager 0.0.1 → 0.0.2
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-C1MoQeSs.js +1 -0
- package/dist/assets/{architectureDiagram-Q4EWVU46-DSQ1_74_.js → architectureDiagram-Q4EWVU46-DUEfvDBu.js} +1 -1
- package/dist/assets/{blockDiagram-DXYQGD6D-DfOGNphI.js → blockDiagram-DXYQGD6D-DQzEOPT2.js} +1 -1
- package/dist/assets/{chunk-2KRD3SAO-9yt00aGC.js → chunk-2KRD3SAO-C2e-_49I.js} +1 -1
- package/dist/assets/{chunk-4TB4RGXK-DF8yJBFl.js → chunk-4TB4RGXK-AZq3s1Dh.js} +1 -1
- package/dist/assets/{chunk-67CJDMHE-5wFKo04G.js → chunk-67CJDMHE-B1-M78qu.js} +1 -1
- package/dist/assets/{chunk-7N4EOEYR-BRRGX_NC.js → chunk-7N4EOEYR-D7mYFpz-.js} +1 -1
- package/dist/assets/{chunk-AA7GKIK3-DUZv_pNI.js → chunk-AA7GKIK3-VWI9k39i.js} +1 -1
- package/dist/assets/{chunk-CIAEETIT-mA5aM_d7.js → chunk-CIAEETIT-hnu4zamm.js} +1 -1
- package/dist/assets/{chunk-FOC6F5B3-B-cqGCPC.js → chunk-FOC6F5B3-BJsh9nO9.js} +1 -1
- package/dist/assets/{chunk-K5T4RW27-BLRDzioh.js → chunk-K5T4RW27-BLIPdXaZ.js} +1 -1
- package/dist/assets/{chunk-KGLVRYIC-CTkQSeKy.js → chunk-KGLVRYIC-DvaW2TkT.js} +1 -1
- package/dist/assets/{chunk-LIHQZDEY-Cf34Nu3J.js → chunk-LIHQZDEY-CUsM0M11.js} +1 -1
- package/dist/assets/{chunk-ORNJ4GCN-D3uXgbay.js → chunk-ORNJ4GCN-CfluNV0_.js} +1 -1
- package/dist/assets/{chunk-OYMX7WX6-syQho5jf.js → chunk-OYMX7WX6-CkWzw4JX.js} +1 -1
- package/dist/assets/{classDiagram-6PBFFD2Q-CotFZI8-.js → classDiagram-6PBFFD2Q-Dx_f-9b7.js} +1 -1
- package/dist/assets/{classDiagram-v2-HSJHXN6E-DAPzeDGn.js → classDiagram-v2-HSJHXN6E-CSfvZ-nt.js} +1 -1
- package/dist/assets/clone-CXokakwV.js +1 -0
- package/dist/assets/{dagre-rhyPjnsQ.js → dagre-Do0eD9eI.js} +1 -1
- package/dist/assets/{dagre-KV5264BT-BBqulDtd.js → dagre-KV5264BT-lveZDhBf.js} +1 -1
- package/dist/assets/{diagram-5BDNPKRD-Ky3EXXj0.js → diagram-5BDNPKRD-Dq5yM_uY.js} +1 -1
- package/dist/assets/{diagram-G4DWMVQ6-t7LbT0Uz.js → diagram-G4DWMVQ6-D-SYOmKm.js} +1 -1
- package/dist/assets/{diagram-MMDJMWI5-CdnLXEMx.js → diagram-MMDJMWI5-lU5t9BZA.js} +1 -1
- package/dist/assets/{diagram-TYMM5635-CnzTqJBM.js → diagram-TYMM5635-6tfUbY3R.js} +1 -1
- package/dist/assets/{erDiagram-SMLLAGMA-BN5eJerP.js → erDiagram-SMLLAGMA-dx09stuy.js} +1 -1
- package/dist/assets/{flatten-C5NL-f24.js → flatten-B2BZ0pzY.js} +1 -1
- package/dist/assets/{flowDiagram-DWJPFMVM-CbFskc8S.js → flowDiagram-DWJPFMVM-CJi2WISS.js} +1 -1
- package/dist/assets/gitGraph-7Q5UKJZL-BXTuQaDM.js +1 -0
- package/dist/assets/{gitGraphDiagram-UUTBAWPF-wpqI2kyI.js → gitGraphDiagram-UUTBAWPF-Bjj94M12.js} +1 -1
- package/dist/assets/{graphlib-COiJG5Qv.js → graphlib-BIlXYGdM.js} +1 -1
- package/dist/assets/{index-lyyIVcc_.js → index-CZZuue3D.js} +5 -5
- package/dist/assets/info-OMHHGYJF-BeeKt8-X.js +1 -0
- package/dist/assets/{infoDiagram-42DDH7IO-BbvTdpSV.js → infoDiagram-42DDH7IO-wq_opQKO.js} +1 -1
- package/dist/assets/{ishikawaDiagram-UXIWVN3A-Epc23N_0.js → ishikawaDiagram-UXIWVN3A-Cnc1bwBo.js} +1 -1
- package/dist/assets/{kanban-definition-6JOO6SKY-C8dW_26n.js → kanban-definition-6JOO6SKY-CwHbIze0.js} +1 -1
- package/dist/assets/{mermaid-parser.core-6Tn8epr_.js → mermaid-parser.core-DrLhKJ48.js} +2 -2
- package/dist/assets/{mindmap-definition-QFDTVHPH-CvpNtrKT.js → mindmap-definition-QFDTVHPH-DswAJiEd.js} +1 -1
- package/dist/assets/packet-4T2RLAQJ-DQ-H9_jd.js +1 -0
- package/dist/assets/pie-ZZUOXDRM-BSj0Jsyj.js +1 -0
- package/dist/assets/{pieDiagram-DEJITSTG-eENymoXZ.js → pieDiagram-DEJITSTG-DgQTCddl.js} +1 -1
- package/dist/assets/radar-PYXPWWZC-B7-oRPFL.js +1 -0
- package/dist/assets/{reduce-BDOBPIXr.js → reduce-Uumu9GdR.js} +1 -1
- package/dist/assets/{requirementDiagram-MS252O5E-CmRO3hLp.js → requirementDiagram-MS252O5E-D1moa23Z.js} +1 -1
- package/dist/assets/{sequenceDiagram-FGHM5R23-B7qNcwNo.js → sequenceDiagram-FGHM5R23-Dvhj7HGn.js} +1 -1
- package/dist/assets/{stateDiagram-FHFEXIEX-CYfGMoR8.js → stateDiagram-FHFEXIEX-Dx5CjenB.js} +1 -1
- package/dist/assets/{stateDiagram-v2-QKLJ7IA2-CO1W_n55.js → stateDiagram-v2-QKLJ7IA2-C_PkrTdc.js} +1 -1
- package/dist/assets/{timeline-definition-GMOUNBTQ-CQWqDPGG.js → timeline-definition-GMOUNBTQ-z-IncVmK.js} +1 -1
- package/dist/assets/treeView-SZITEDCU-CFXle9Az.js +1 -0
- package/dist/assets/treemap-W4RFUUIX-CAW3vWh8.js +1 -0
- package/dist/assets/{vennDiagram-DHZGUBPP-BjTbuhcb.js → vennDiagram-DHZGUBPP-CT1ehozU.js} +1 -1
- package/dist/assets/wardley-RL74JXVD-7q3ju4kc.js +1 -0
- package/dist/assets/{wardleyDiagram-NUSXRM2D-DNhPIFCg.js → wardleyDiagram-NUSXRM2D-D-kouujI.js} +1 -1
- package/dist/assets/{xychartDiagram-5P7HB3ND-BDblAZ11.js → xychartDiagram-5P7HB3ND-D1lnM0pL.js} +1 -1
- package/dist/index.html +1 -1
- package/package.json +101 -92
- package/scripts/postinstall-message.mjs +160 -0
- 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 +375 -35
- package/src/cli/bootstrap/program.ts +4 -0
- 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.ts +166 -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 +71 -0
- package/src/shared/terminalColors.ts +24 -0
- package/dist/assets/architecture-YZFGNWBL-3h1eIYfB.js +0 -1
- package/dist/assets/clone-BRQpYu_n.js +0 -1
- package/dist/assets/gitGraph-7Q5UKJZL-CG8f8JF7.js +0 -1
- package/dist/assets/info-OMHHGYJF-C8_SHoRO.js +0 -1
- 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/treeView-SZITEDCU-DsEr3xeq.js +0 -1
- package/dist/assets/treemap-W4RFUUIX-DV7nk2AB.js +0 -1
- package/dist/assets/wardley-RL74JXVD-CrrFU9AE.js +0 -1
|
@@ -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,22 @@ 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
|
+
printPassphraseHint,
|
|
33
|
+
printRecoveryKey,
|
|
34
|
+
printRecoveryKeyExitHint,
|
|
35
|
+
printInteractiveSetupHeader,
|
|
36
|
+
printSavedProfileSummary,
|
|
37
|
+
spinForMoment,
|
|
38
|
+
startInlineSpinner,
|
|
39
|
+
} from "../lib/launcherUi";
|
|
21
40
|
|
|
22
41
|
/** Phase 3: installed-app launcher logic (formerly all of app.ts). */
|
|
23
42
|
|
|
@@ -28,6 +47,50 @@ interface LauncherOptions {
|
|
|
28
47
|
profile?: string;
|
|
29
48
|
}
|
|
30
49
|
|
|
50
|
+
interface LauncherServerOptions {
|
|
51
|
+
profile?: string;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
interface LauncherServerStartOptions extends LauncherServerOptions {
|
|
55
|
+
dataDir?: string;
|
|
56
|
+
foreground?: boolean;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export interface LauncherSetupResult {
|
|
60
|
+
config: CliConfigFile;
|
|
61
|
+
setupMeta: {
|
|
62
|
+
/** User went through interactive prompts (not bunx / non-TTY defaults). */
|
|
63
|
+
justFinishedInteractiveSetup: boolean;
|
|
64
|
+
/** No profile had config.json on disk before this run’s save. */
|
|
65
|
+
firstProfileOnMachine: boolean;
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export function resolveLauncherStartPlan(options: {
|
|
70
|
+
shouldRunSetup: boolean;
|
|
71
|
+
needsRecoveryKeyExitFlow: boolean;
|
|
72
|
+
alreadyRunning: boolean;
|
|
73
|
+
shouldOpenBrowser: boolean;
|
|
74
|
+
preferForegroundWhenNotSetup?: boolean;
|
|
75
|
+
}): {
|
|
76
|
+
startMode: ServerStartMode;
|
|
77
|
+
readyLabel: "Started" | "Already started";
|
|
78
|
+
shouldOpenBrowserOnReady: boolean;
|
|
79
|
+
} {
|
|
80
|
+
return {
|
|
81
|
+
startMode: options.shouldRunSetup
|
|
82
|
+
? options.needsRecoveryKeyExitFlow
|
|
83
|
+
? "background-attached"
|
|
84
|
+
: "foreground"
|
|
85
|
+
: options.preferForegroundWhenNotSetup
|
|
86
|
+
? "foreground"
|
|
87
|
+
: "background",
|
|
88
|
+
readyLabel: options.alreadyRunning ? "Already started" : "Started",
|
|
89
|
+
shouldOpenBrowserOnReady:
|
|
90
|
+
options.shouldOpenBrowser && !options.alreadyRunning,
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
|
|
31
94
|
function parseBrowserMode(browser: string | undefined): boolean | undefined {
|
|
32
95
|
if (!browser?.trim()) return undefined;
|
|
33
96
|
|
|
@@ -83,7 +146,7 @@ async function promptWithDefault(
|
|
|
83
146
|
output: process.stdout,
|
|
84
147
|
});
|
|
85
148
|
try {
|
|
86
|
-
const answer = await rl.question(`${question}
|
|
149
|
+
const answer = await rl.question(`${question} `);
|
|
87
150
|
return answer.trim() || defaultValue;
|
|
88
151
|
} finally {
|
|
89
152
|
rl.close();
|
|
@@ -94,14 +157,13 @@ async function promptBoolean(
|
|
|
94
157
|
question: string,
|
|
95
158
|
defaultValue: boolean,
|
|
96
159
|
): Promise<boolean> {
|
|
97
|
-
const label = defaultValue ? "Y/n" : "y/N";
|
|
98
160
|
const rl = createInterface({
|
|
99
161
|
input: process.stdin,
|
|
100
162
|
output: process.stdout,
|
|
101
163
|
});
|
|
102
164
|
try {
|
|
103
165
|
for (;;) {
|
|
104
|
-
const answer = (await rl.question(`${question}
|
|
166
|
+
const answer = (await rl.question(`${question}: `))
|
|
105
167
|
.trim()
|
|
106
168
|
.toLowerCase();
|
|
107
169
|
if (!answer) return defaultValue;
|
|
@@ -119,10 +181,11 @@ async function runLauncherSetup(overrides: {
|
|
|
119
181
|
port?: number;
|
|
120
182
|
dataDir?: string;
|
|
121
183
|
openBrowser?: boolean;
|
|
122
|
-
}): Promise<
|
|
184
|
+
}): Promise<LauncherSetupResult> {
|
|
123
185
|
const defaults = resolveLauncherDefaults(overrides);
|
|
124
186
|
const configScope = { profile: overrides.profile, kind: "installed" as const };
|
|
125
187
|
const existing = readConfigFile(configScope);
|
|
188
|
+
const machineHadNoProfilesBefore = !hasAnyProfileConfigOnDisk();
|
|
126
189
|
|
|
127
190
|
// Keep the first-run path working for bunx and other non-interactive entry
|
|
128
191
|
// points by saving sane defaults instead of failing on missing prompts.
|
|
@@ -134,18 +197,47 @@ async function runLauncherSetup(overrides: {
|
|
|
134
197
|
dataDir: config.data_dir,
|
|
135
198
|
authDir: config.auth_dir,
|
|
136
199
|
});
|
|
137
|
-
return
|
|
200
|
+
return {
|
|
201
|
+
config,
|
|
202
|
+
setupMeta: {
|
|
203
|
+
justFinishedInteractiveSetup: false,
|
|
204
|
+
firstProfileOnMachine: machineHadNoProfilesBefore,
|
|
205
|
+
},
|
|
206
|
+
};
|
|
138
207
|
}
|
|
139
208
|
|
|
140
|
-
|
|
209
|
+
printInteractiveSetupHeader({
|
|
210
|
+
profileName: resolveProfileName(configScope),
|
|
211
|
+
firstProfileOnMachine: machineHadNoProfilesBefore,
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
// Show the profile context line first so the user understands why the
|
|
215
|
+
// following prompts are being asked.
|
|
216
|
+
await spinForMoment(
|
|
217
|
+
"Looking for existing profiles...",
|
|
218
|
+
machineHadNoProfilesBefore
|
|
219
|
+
? `Creating Profile: ${paintValue(resolveProfileName(configScope))}`
|
|
220
|
+
: `Using Profile: ${paintValue(resolveProfileName(configScope))}`,
|
|
221
|
+
);
|
|
222
|
+
|
|
223
|
+
const portValue = await promptWithDefault(
|
|
224
|
+
formatTextPrompt("Pick a port for web/api", String(defaults.port)),
|
|
225
|
+
String(defaults.port),
|
|
226
|
+
);
|
|
141
227
|
|
|
142
|
-
const portValue = await promptWithDefault("Port", String(defaults.port));
|
|
143
228
|
const dataDirValue = await promptWithDefault(
|
|
144
|
-
|
|
229
|
+
formatTextPrompt(
|
|
230
|
+
"Pick a Data Directory to place the database",
|
|
231
|
+
defaults.data_dir,
|
|
232
|
+
),
|
|
145
233
|
defaults.data_dir,
|
|
146
234
|
);
|
|
235
|
+
|
|
147
236
|
const openBrowser = await promptBoolean(
|
|
148
|
-
|
|
237
|
+
formatBooleanPrompt(
|
|
238
|
+
"Open default browser when starting the server with hirotaskmanager",
|
|
239
|
+
defaults.open_browser,
|
|
240
|
+
),
|
|
149
241
|
defaults.open_browser,
|
|
150
242
|
);
|
|
151
243
|
|
|
@@ -156,14 +248,34 @@ async function runLauncherSetup(overrides: {
|
|
|
156
248
|
auth_dir: defaults.auth_dir,
|
|
157
249
|
open_browser: openBrowser,
|
|
158
250
|
};
|
|
159
|
-
|
|
251
|
+
writeConfigFile(config, configScope);
|
|
160
252
|
ensureRuntimeDirectories({
|
|
161
253
|
...configScope,
|
|
162
254
|
dataDir: config.data_dir,
|
|
163
255
|
authDir: config.auth_dir,
|
|
164
256
|
});
|
|
165
|
-
|
|
166
|
-
|
|
257
|
+
|
|
258
|
+
await spinForMoment(
|
|
259
|
+
machineHadNoProfilesBefore
|
|
260
|
+
? `Saving Profile: ${paintValue(resolveProfileName(configScope))}`
|
|
261
|
+
: `Saving Profile: ${paintValue(resolveProfileName(configScope))}`,
|
|
262
|
+
);
|
|
263
|
+
|
|
264
|
+
printSavedProfileSummary({
|
|
265
|
+
created: machineHadNoProfilesBefore,
|
|
266
|
+
profileName: resolveProfileName(configScope),
|
|
267
|
+
appUrl: `http://127.0.0.1:${config.port}`,
|
|
268
|
+
dataDir: path.resolve(config.data_dir!),
|
|
269
|
+
openBrowser,
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
return {
|
|
273
|
+
config,
|
|
274
|
+
setupMeta: {
|
|
275
|
+
justFinishedInteractiveSetup: true,
|
|
276
|
+
firstProfileOnMachine: machineHadNoProfilesBefore,
|
|
277
|
+
},
|
|
278
|
+
};
|
|
167
279
|
}
|
|
168
280
|
|
|
169
281
|
async function openBrowser(url: string): Promise<void> {
|
|
@@ -188,6 +300,48 @@ async function openBrowser(url: string): Promise<void> {
|
|
|
188
300
|
}
|
|
189
301
|
}
|
|
190
302
|
|
|
303
|
+
/**
|
|
304
|
+
* Poll for the recovery-key sidecar file written by the server during
|
|
305
|
+
* `setupPassphrase`. Returns the key string once available, then deletes the
|
|
306
|
+
* file so it is never left on disk.
|
|
307
|
+
*/
|
|
308
|
+
async function waitForRecoveryKeyFile(authDir: string): Promise<string> {
|
|
309
|
+
const keyPath = path.join(authDir, "recovery-key.tmp");
|
|
310
|
+
while (!existsSync(keyPath)) {
|
|
311
|
+
await Bun.sleep(250);
|
|
312
|
+
}
|
|
313
|
+
const key = readFileSync(keyPath, "utf8").trim();
|
|
314
|
+
try {
|
|
315
|
+
unlinkSync(keyPath);
|
|
316
|
+
} catch {
|
|
317
|
+
// Best-effort cleanup; the file has owner-only perms already.
|
|
318
|
+
}
|
|
319
|
+
return key;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
async function waitForEnterToExitLauncher(): Promise<void> {
|
|
323
|
+
const rl = createInterface({
|
|
324
|
+
input: process.stdin,
|
|
325
|
+
output: process.stdout,
|
|
326
|
+
});
|
|
327
|
+
try {
|
|
328
|
+
await rl.question("");
|
|
329
|
+
} finally {
|
|
330
|
+
rl.close();
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
function resolveInstalledLauncherProfile(profile: string | undefined): string {
|
|
335
|
+
return resolveProfileName({
|
|
336
|
+
profile,
|
|
337
|
+
kind: "installed",
|
|
338
|
+
});
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
function printLauncherJson(data: unknown): void {
|
|
342
|
+
process.stdout.write(`${JSON.stringify(data)}\n`);
|
|
343
|
+
}
|
|
344
|
+
|
|
191
345
|
export function createHirotaskmanagerProgram(): Command {
|
|
192
346
|
const program = new Command();
|
|
193
347
|
program
|
|
@@ -206,21 +360,34 @@ export function createHirotaskmanagerProgram(): Command {
|
|
|
206
360
|
? path.resolve(options.dataDir.trim())
|
|
207
361
|
: undefined;
|
|
208
362
|
const overrideOpenBrowser = parseBrowserMode(options.browser);
|
|
209
|
-
const selectedProfile =
|
|
210
|
-
profile: options.profile,
|
|
211
|
-
kind: "installed",
|
|
212
|
-
});
|
|
363
|
+
const selectedProfile = resolveInstalledLauncherProfile(options.profile);
|
|
213
364
|
|
|
214
365
|
const shouldRunSetup =
|
|
215
366
|
options.setup ||
|
|
216
367
|
!hasCliConfigFile({ profile: selectedProfile, kind: "installed" });
|
|
217
|
-
|
|
368
|
+
|
|
369
|
+
const setupResult: LauncherSetupResult = shouldRunSetup
|
|
218
370
|
? await runLauncherSetup({
|
|
219
371
|
profile: selectedProfile,
|
|
220
372
|
dataDir: overrideDataDir,
|
|
221
373
|
openBrowser: overrideOpenBrowser,
|
|
222
374
|
})
|
|
223
|
-
:
|
|
375
|
+
: {
|
|
376
|
+
config: readConfigFile({
|
|
377
|
+
profile: selectedProfile,
|
|
378
|
+
kind: "installed",
|
|
379
|
+
}),
|
|
380
|
+
setupMeta: {
|
|
381
|
+
justFinishedInteractiveSetup: false,
|
|
382
|
+
firstProfileOnMachine: false,
|
|
383
|
+
},
|
|
384
|
+
};
|
|
385
|
+
|
|
386
|
+
// Safety net: copy bundled skills to ~/.taskmanager/skills/ if
|
|
387
|
+
// postinstall was skipped (--ignore-scripts, CI, bunx) or stale.
|
|
388
|
+
ensureBundledSkills();
|
|
389
|
+
|
|
390
|
+
const launcherConfig = setupResult.config;
|
|
224
391
|
|
|
225
392
|
const port =
|
|
226
393
|
launcherConfig.port ?? CLI_DEFAULTS.INSTALLED_DEFAULT_PORT;
|
|
@@ -232,32 +399,205 @@ export function createHirotaskmanagerProgram(): Command {
|
|
|
232
399
|
kind: "installed",
|
|
233
400
|
}),
|
|
234
401
|
);
|
|
402
|
+
const authDir = path.resolve(
|
|
403
|
+
launcherConfig.auth_dir ??
|
|
404
|
+
getDefaultInstalledAuthDir({
|
|
405
|
+
profile: selectedProfile,
|
|
406
|
+
kind: "installed",
|
|
407
|
+
}),
|
|
408
|
+
);
|
|
235
409
|
const shouldOpenBrowser =
|
|
236
410
|
overrideOpenBrowser ?? launcherConfig.open_browser ?? true;
|
|
237
411
|
|
|
412
|
+
const url = `http://127.0.0.1:${port}`;
|
|
413
|
+
const needsRecoveryKeyExitFlow =
|
|
414
|
+
setupResult.setupMeta.justFinishedInteractiveSetup &&
|
|
415
|
+
!isAuthInitialized(authDir);
|
|
416
|
+
// Keep normal launcher runs non-blocking, and avoid reopening the browser
|
|
417
|
+
// when the launcher is only attaching to an already running profile.
|
|
418
|
+
const alreadyRunning = shouldRunSetup
|
|
419
|
+
? false
|
|
420
|
+
: (
|
|
421
|
+
await readServerStatus({
|
|
422
|
+
kind: "installed",
|
|
423
|
+
profile: selectedProfile,
|
|
424
|
+
port,
|
|
425
|
+
})
|
|
426
|
+
).running;
|
|
427
|
+
const startPlan = resolveLauncherStartPlan({
|
|
428
|
+
shouldRunSetup,
|
|
429
|
+
needsRecoveryKeyExitFlow,
|
|
430
|
+
alreadyRunning,
|
|
431
|
+
shouldOpenBrowser,
|
|
432
|
+
});
|
|
433
|
+
|
|
434
|
+
const startupSpinner = startInlineSpinner(
|
|
435
|
+
`${alreadyRunning ? "Checking Server" : "Starting Server"} with profile ${paintValue(selectedProfile)}: ${paintValue(url)}`,
|
|
436
|
+
);
|
|
437
|
+
const previousSilentStartup = process.env.TASKMANAGER_SILENT_STARTUP_LOG;
|
|
438
|
+
// Let the launcher own startup copy so first-time setup stays compact.
|
|
439
|
+
process.env.TASKMANAGER_SILENT_STARTUP_LOG = "1";
|
|
440
|
+
|
|
238
441
|
let browserHandled = false;
|
|
239
|
-
|
|
240
|
-
|
|
442
|
+
let runningUrl = url;
|
|
443
|
+
try {
|
|
444
|
+
await startServer(
|
|
445
|
+
{
|
|
446
|
+
kind: "installed",
|
|
447
|
+
profile: selectedProfile,
|
|
448
|
+
port,
|
|
449
|
+
dataDir,
|
|
450
|
+
},
|
|
451
|
+
startPlan.startMode,
|
|
452
|
+
async (status) => {
|
|
453
|
+
const finalUrl = status.url;
|
|
454
|
+
runningUrl = finalUrl;
|
|
455
|
+
startupSpinner.stop(
|
|
456
|
+
`${startPlan.readyLabel}, listening at ${paintValue(finalUrl)}`,
|
|
457
|
+
);
|
|
458
|
+
|
|
459
|
+
if (!browserHandled && startPlan.shouldOpenBrowserOnReady) {
|
|
460
|
+
browserHandled = true;
|
|
461
|
+
await openBrowser(finalUrl);
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
if (needsRecoveryKeyExitFlow) {
|
|
465
|
+
await printPassphraseHint();
|
|
466
|
+
}
|
|
467
|
+
},
|
|
468
|
+
);
|
|
469
|
+
|
|
470
|
+
if (needsRecoveryKeyExitFlow) {
|
|
471
|
+
const recoveryKey = await waitForRecoveryKeyFile(authDir);
|
|
472
|
+
printRecoveryKey(recoveryKey);
|
|
473
|
+
printRecoveryKeyExitHint(runningUrl);
|
|
474
|
+
await waitForEnterToExitLauncher();
|
|
475
|
+
}
|
|
476
|
+
} finally {
|
|
477
|
+
if (previousSilentStartup === undefined) {
|
|
478
|
+
delete process.env.TASKMANAGER_SILENT_STARTUP_LOG;
|
|
479
|
+
} else {
|
|
480
|
+
process.env.TASKMANAGER_SILENT_STARTUP_LOG = previousSilentStartup;
|
|
481
|
+
}
|
|
482
|
+
startupSpinner.stop(null);
|
|
483
|
+
}
|
|
484
|
+
} catch (error) {
|
|
485
|
+
exitWithError(error);
|
|
486
|
+
}
|
|
487
|
+
});
|
|
488
|
+
|
|
489
|
+
const server = program
|
|
490
|
+
.command("server")
|
|
491
|
+
.description("Start, stop, or inspect the installed TaskManager server");
|
|
492
|
+
|
|
493
|
+
server
|
|
494
|
+
.command("start")
|
|
495
|
+
.description("Start the installed TaskManager server")
|
|
496
|
+
.option("--profile <name>", "Launcher profile name for this command")
|
|
497
|
+
.option("--data-dir <path>", "Override the task data directory")
|
|
498
|
+
.option("--foreground", "Run the server in the foreground")
|
|
499
|
+
.action(async (options: LauncherServerStartOptions, command: Command) => {
|
|
500
|
+
try {
|
|
501
|
+
const profile = resolveInstalledLauncherProfile(
|
|
502
|
+
(command.optsWithGlobals() as LauncherServerStartOptions).profile ?? options.profile,
|
|
503
|
+
);
|
|
504
|
+
const overrideDataDir = options.dataDir?.trim()
|
|
505
|
+
? path.resolve(options.dataDir.trim())
|
|
506
|
+
: undefined;
|
|
507
|
+
const config = readConfigFile({ profile, kind: "installed" });
|
|
508
|
+
const port = config.port ?? CLI_DEFAULTS.INSTALLED_DEFAULT_PORT;
|
|
509
|
+
const dataDir = path.resolve(
|
|
510
|
+
overrideDataDir ??
|
|
511
|
+
config.data_dir ??
|
|
512
|
+
getDefaultInstalledDataDir({ profile, kind: "installed" }),
|
|
513
|
+
);
|
|
514
|
+
const status = await readServerStatus({
|
|
515
|
+
kind: "installed",
|
|
516
|
+
profile,
|
|
517
|
+
port,
|
|
518
|
+
});
|
|
519
|
+
const startPlan = resolveLauncherStartPlan({
|
|
520
|
+
shouldRunSetup: false,
|
|
521
|
+
needsRecoveryKeyExitFlow: false,
|
|
522
|
+
alreadyRunning: status.running,
|
|
523
|
+
shouldOpenBrowser: false,
|
|
524
|
+
preferForegroundWhenNotSetup: options.foreground === true,
|
|
525
|
+
});
|
|
526
|
+
const startupSpinner = startInlineSpinner(
|
|
527
|
+
`${status.running ? "Checking Server" : "Starting Server"} with profile ${paintValue(profile)}: ${paintValue(status.running ? status.url : `http://127.0.0.1:${port}`)}`,
|
|
528
|
+
);
|
|
529
|
+
|
|
530
|
+
try {
|
|
531
|
+
// Launcher `server start` is human-facing, so prefer concise text
|
|
532
|
+
// instead of JSON while still sharing the same server lifecycle path.
|
|
533
|
+
await startServer(
|
|
534
|
+
{
|
|
535
|
+
kind: "installed",
|
|
536
|
+
profile,
|
|
537
|
+
port,
|
|
538
|
+
dataDir,
|
|
539
|
+
},
|
|
540
|
+
startPlan.startMode,
|
|
541
|
+
async (started) => {
|
|
542
|
+
startupSpinner.stop(
|
|
543
|
+
`${startPlan.readyLabel}, listening at ${paintValue(started.url)}`,
|
|
544
|
+
);
|
|
545
|
+
},
|
|
546
|
+
);
|
|
547
|
+
} finally {
|
|
548
|
+
startupSpinner.stop(null);
|
|
549
|
+
}
|
|
550
|
+
} catch (error) {
|
|
551
|
+
exitWithError(error);
|
|
552
|
+
}
|
|
553
|
+
});
|
|
554
|
+
|
|
555
|
+
server
|
|
556
|
+
.command("status")
|
|
557
|
+
.description("Show whether the installed TaskManager server is running")
|
|
558
|
+
.option("--profile <name>", "Launcher profile name for this command")
|
|
559
|
+
.action(async (options: LauncherServerOptions, command: Command) => {
|
|
560
|
+
try {
|
|
561
|
+
const profile = resolveInstalledLauncherProfile(
|
|
562
|
+
(command.optsWithGlobals() as LauncherServerOptions).profile ?? options.profile,
|
|
563
|
+
);
|
|
564
|
+
printLauncherJson(
|
|
565
|
+
await readServerStatus({
|
|
241
566
|
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
|
-
},
|
|
567
|
+
profile,
|
|
568
|
+
}),
|
|
255
569
|
);
|
|
256
570
|
} catch (error) {
|
|
257
571
|
exitWithError(error);
|
|
258
572
|
}
|
|
259
573
|
});
|
|
260
574
|
|
|
575
|
+
server
|
|
576
|
+
.command("stop")
|
|
577
|
+
.description("Stop a background installed server started for this profile")
|
|
578
|
+
.option("--profile <name>", "Launcher profile name for this command")
|
|
579
|
+
.action(async (options: LauncherServerOptions, command: Command) => {
|
|
580
|
+
try {
|
|
581
|
+
const profile = resolveInstalledLauncherProfile(
|
|
582
|
+
(command.optsWithGlobals() as LauncherServerOptions).profile ?? options.profile,
|
|
583
|
+
);
|
|
584
|
+
const stopSpinner = startInlineSpinner(
|
|
585
|
+
`Stopping Server with profile ${paintValue(profile)}`,
|
|
586
|
+
);
|
|
587
|
+
try {
|
|
588
|
+
await stopServer({
|
|
589
|
+
kind: "installed",
|
|
590
|
+
profile,
|
|
591
|
+
});
|
|
592
|
+
stopSpinner.stop("Server stopped");
|
|
593
|
+
} finally {
|
|
594
|
+
stopSpinner.stop(null);
|
|
595
|
+
}
|
|
596
|
+
} catch (error) {
|
|
597
|
+
exitWithError(error);
|
|
598
|
+
}
|
|
599
|
+
});
|
|
600
|
+
|
|
261
601
|
return program;
|
|
262
602
|
}
|
|
263
603
|
|
|
@@ -21,6 +21,10 @@ export function createHirotmProgram(): Command {
|
|
|
21
21
|
.name("hirotm")
|
|
22
22
|
.description("TaskManager CLI for local app control and JSON queries")
|
|
23
23
|
.option("--profile <name>", "Runtime profile name (default: default, dev)")
|
|
24
|
+
.option(
|
|
25
|
+
"--port <port>",
|
|
26
|
+
"HTTP port for the local API (default: from profile config.json)",
|
|
27
|
+
)
|
|
24
28
|
.option(
|
|
25
29
|
"--client-name <name>",
|
|
26
30
|
"Human-friendly client label sent with API requests (for notifications)",
|
|
@@ -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
|
+
});
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { setRuntimeCliClientName } from "../lib/clientIdentity";
|
|
2
|
-
import { setRuntimeProfile } from "../lib/config";
|
|
2
|
+
import { setRuntimeCliPort, setRuntimeKind, setRuntimeProfile } from "../lib/config";
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Parse argv before Commander runs so profile and client name match this invocation
|
|
@@ -33,7 +33,33 @@ export function readProfileArg(argv: string[]): string | undefined {
|
|
|
33
33
|
return undefined;
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
+
export function readPortArg(argv: string[]): number | undefined {
|
|
37
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
38
|
+
const current = argv[index];
|
|
39
|
+
if (current === "--port") {
|
|
40
|
+
const next = argv[index + 1];
|
|
41
|
+
if (typeof next === "string") {
|
|
42
|
+
const parsed = Number(next);
|
|
43
|
+
if (Number.isInteger(parsed) && parsed > 0) return parsed;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
if (current.startsWith("--port=")) {
|
|
47
|
+
const parsed = Number(current.slice("--port=".length));
|
|
48
|
+
if (Number.isInteger(parsed) && parsed > 0) return parsed;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return undefined;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function readDevFlag(argv: string[]): boolean {
|
|
55
|
+
return argv.includes("--dev");
|
|
56
|
+
}
|
|
57
|
+
|
|
36
58
|
export function applyCliRuntimeFromArgv(argv: string[]): void {
|
|
37
59
|
setRuntimeCliClientName(readClientNameArg(argv));
|
|
38
60
|
setRuntimeProfile(readProfileArg(argv));
|
|
61
|
+
setRuntimeCliPort(readPortArg(argv));
|
|
62
|
+
if (readDevFlag(argv)) {
|
|
63
|
+
setRuntimeKind("dev");
|
|
64
|
+
}
|
|
39
65
|
}
|