@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.
Files changed (146) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +41 -52
  3. package/dist/assets/architecture-YZFGNWBL-C1MoQeSs.js +1 -0
  4. package/dist/assets/{architectureDiagram-Q4EWVU46-DSQ1_74_.js → architectureDiagram-Q4EWVU46-DUEfvDBu.js} +1 -1
  5. package/dist/assets/{blockDiagram-DXYQGD6D-DfOGNphI.js → blockDiagram-DXYQGD6D-DQzEOPT2.js} +1 -1
  6. package/dist/assets/{chunk-2KRD3SAO-9yt00aGC.js → chunk-2KRD3SAO-C2e-_49I.js} +1 -1
  7. package/dist/assets/{chunk-4TB4RGXK-DF8yJBFl.js → chunk-4TB4RGXK-AZq3s1Dh.js} +1 -1
  8. package/dist/assets/{chunk-67CJDMHE-5wFKo04G.js → chunk-67CJDMHE-B1-M78qu.js} +1 -1
  9. package/dist/assets/{chunk-7N4EOEYR-BRRGX_NC.js → chunk-7N4EOEYR-D7mYFpz-.js} +1 -1
  10. package/dist/assets/{chunk-AA7GKIK3-DUZv_pNI.js → chunk-AA7GKIK3-VWI9k39i.js} +1 -1
  11. package/dist/assets/{chunk-CIAEETIT-mA5aM_d7.js → chunk-CIAEETIT-hnu4zamm.js} +1 -1
  12. package/dist/assets/{chunk-FOC6F5B3-B-cqGCPC.js → chunk-FOC6F5B3-BJsh9nO9.js} +1 -1
  13. package/dist/assets/{chunk-K5T4RW27-BLRDzioh.js → chunk-K5T4RW27-BLIPdXaZ.js} +1 -1
  14. package/dist/assets/{chunk-KGLVRYIC-CTkQSeKy.js → chunk-KGLVRYIC-DvaW2TkT.js} +1 -1
  15. package/dist/assets/{chunk-LIHQZDEY-Cf34Nu3J.js → chunk-LIHQZDEY-CUsM0M11.js} +1 -1
  16. package/dist/assets/{chunk-ORNJ4GCN-D3uXgbay.js → chunk-ORNJ4GCN-CfluNV0_.js} +1 -1
  17. package/dist/assets/{chunk-OYMX7WX6-syQho5jf.js → chunk-OYMX7WX6-CkWzw4JX.js} +1 -1
  18. package/dist/assets/{classDiagram-6PBFFD2Q-CotFZI8-.js → classDiagram-6PBFFD2Q-Dx_f-9b7.js} +1 -1
  19. package/dist/assets/{classDiagram-v2-HSJHXN6E-DAPzeDGn.js → classDiagram-v2-HSJHXN6E-CSfvZ-nt.js} +1 -1
  20. package/dist/assets/clone-CXokakwV.js +1 -0
  21. package/dist/assets/{dagre-rhyPjnsQ.js → dagre-Do0eD9eI.js} +1 -1
  22. package/dist/assets/{dagre-KV5264BT-BBqulDtd.js → dagre-KV5264BT-lveZDhBf.js} +1 -1
  23. package/dist/assets/{diagram-5BDNPKRD-Ky3EXXj0.js → diagram-5BDNPKRD-Dq5yM_uY.js} +1 -1
  24. package/dist/assets/{diagram-G4DWMVQ6-t7LbT0Uz.js → diagram-G4DWMVQ6-D-SYOmKm.js} +1 -1
  25. package/dist/assets/{diagram-MMDJMWI5-CdnLXEMx.js → diagram-MMDJMWI5-lU5t9BZA.js} +1 -1
  26. package/dist/assets/{diagram-TYMM5635-CnzTqJBM.js → diagram-TYMM5635-6tfUbY3R.js} +1 -1
  27. package/dist/assets/{erDiagram-SMLLAGMA-BN5eJerP.js → erDiagram-SMLLAGMA-dx09stuy.js} +1 -1
  28. package/dist/assets/{flatten-C5NL-f24.js → flatten-B2BZ0pzY.js} +1 -1
  29. package/dist/assets/{flowDiagram-DWJPFMVM-CbFskc8S.js → flowDiagram-DWJPFMVM-CJi2WISS.js} +1 -1
  30. package/dist/assets/gitGraph-7Q5UKJZL-BXTuQaDM.js +1 -0
  31. package/dist/assets/{gitGraphDiagram-UUTBAWPF-wpqI2kyI.js → gitGraphDiagram-UUTBAWPF-Bjj94M12.js} +1 -1
  32. package/dist/assets/{graphlib-COiJG5Qv.js → graphlib-BIlXYGdM.js} +1 -1
  33. package/dist/assets/{index-lyyIVcc_.js → index-CZZuue3D.js} +5 -5
  34. package/dist/assets/info-OMHHGYJF-BeeKt8-X.js +1 -0
  35. package/dist/assets/{infoDiagram-42DDH7IO-BbvTdpSV.js → infoDiagram-42DDH7IO-wq_opQKO.js} +1 -1
  36. package/dist/assets/{ishikawaDiagram-UXIWVN3A-Epc23N_0.js → ishikawaDiagram-UXIWVN3A-Cnc1bwBo.js} +1 -1
  37. package/dist/assets/{kanban-definition-6JOO6SKY-C8dW_26n.js → kanban-definition-6JOO6SKY-CwHbIze0.js} +1 -1
  38. package/dist/assets/{mermaid-parser.core-6Tn8epr_.js → mermaid-parser.core-DrLhKJ48.js} +2 -2
  39. package/dist/assets/{mindmap-definition-QFDTVHPH-CvpNtrKT.js → mindmap-definition-QFDTVHPH-DswAJiEd.js} +1 -1
  40. package/dist/assets/packet-4T2RLAQJ-DQ-H9_jd.js +1 -0
  41. package/dist/assets/pie-ZZUOXDRM-BSj0Jsyj.js +1 -0
  42. package/dist/assets/{pieDiagram-DEJITSTG-eENymoXZ.js → pieDiagram-DEJITSTG-DgQTCddl.js} +1 -1
  43. package/dist/assets/radar-PYXPWWZC-B7-oRPFL.js +1 -0
  44. package/dist/assets/{reduce-BDOBPIXr.js → reduce-Uumu9GdR.js} +1 -1
  45. package/dist/assets/{requirementDiagram-MS252O5E-CmRO3hLp.js → requirementDiagram-MS252O5E-D1moa23Z.js} +1 -1
  46. package/dist/assets/{sequenceDiagram-FGHM5R23-B7qNcwNo.js → sequenceDiagram-FGHM5R23-Dvhj7HGn.js} +1 -1
  47. package/dist/assets/{stateDiagram-FHFEXIEX-CYfGMoR8.js → stateDiagram-FHFEXIEX-Dx5CjenB.js} +1 -1
  48. package/dist/assets/{stateDiagram-v2-QKLJ7IA2-CO1W_n55.js → stateDiagram-v2-QKLJ7IA2-C_PkrTdc.js} +1 -1
  49. package/dist/assets/{timeline-definition-GMOUNBTQ-CQWqDPGG.js → timeline-definition-GMOUNBTQ-z-IncVmK.js} +1 -1
  50. package/dist/assets/treeView-SZITEDCU-CFXle9Az.js +1 -0
  51. package/dist/assets/treemap-W4RFUUIX-CAW3vWh8.js +1 -0
  52. package/dist/assets/{vennDiagram-DHZGUBPP-BjTbuhcb.js → vennDiagram-DHZGUBPP-CT1ehozU.js} +1 -1
  53. package/dist/assets/wardley-RL74JXVD-7q3ju4kc.js +1 -0
  54. package/dist/assets/{wardleyDiagram-NUSXRM2D-DNhPIFCg.js → wardleyDiagram-NUSXRM2D-D-kouujI.js} +1 -1
  55. package/dist/assets/{xychartDiagram-5P7HB3ND-BDblAZ11.js → xychartDiagram-5P7HB3ND-D1lnM0pL.js} +1 -1
  56. package/dist/index.html +1 -1
  57. package/package.json +101 -92
  58. package/scripts/postinstall-message.mjs +160 -0
  59. package/scripts/stubs/node-domexception/index.cjs +18 -0
  60. package/scripts/stubs/node-domexception/package.json +7 -0
  61. package/skills/hiro-task-manager-cli/SKILL.md +97 -0
  62. package/skills/hiro-task-manager-cli/reference/boards.md +143 -0
  63. package/skills/hiro-task-manager-cli/reference/cli-access-policy.md +72 -0
  64. package/skills/hiro-task-manager-cli/reference/errors.md +85 -0
  65. package/skills/hiro-task-manager-cli/reference/lists.md +106 -0
  66. package/skills/hiro-task-manager-cli/reference/releases.md +87 -0
  67. package/skills/hiro-task-manager-cli/reference/search.md +38 -0
  68. package/skills/hiro-task-manager-cli/reference/statuses.md +25 -0
  69. package/skills/hiro-task-manager-cli/reference/tasks.md +144 -0
  70. package/skills/hiro-task-manager-cli/reference/trash.md +50 -0
  71. package/src/cli/bootstrap/launcher.test.ts +66 -0
  72. package/src/cli/bootstrap/launcher.ts +375 -35
  73. package/src/cli/bootstrap/program.ts +4 -0
  74. package/src/cli/bootstrap/runtime.test.ts +15 -0
  75. package/src/cli/bootstrap/runtime.ts +27 -1
  76. package/src/cli/commands/query.ts +56 -56
  77. package/src/cli/commands/server.ts +27 -19
  78. package/src/cli/handlers/boards.test.ts +669 -669
  79. package/src/cli/handlers/cli-wiring.test.ts +1 -1
  80. package/src/cli/handlers/search.test.ts +374 -374
  81. package/src/cli/handlers/search.ts +17 -17
  82. package/src/cli/handlers/server.test.ts +55 -13
  83. package/src/cli/handlers/server.ts +16 -3
  84. package/src/cli/lib/api-client.test.ts +35 -2
  85. package/src/cli/lib/api-client.ts +43 -10
  86. package/src/cli/lib/cli-http-errors.test.ts +85 -85
  87. package/src/cli/lib/command-helpers.ts +161 -154
  88. package/src/cli/lib/config.ts +4 -0
  89. package/src/cli/lib/launcherUi.ts +166 -0
  90. package/src/cli/lib/process.test.ts +24 -5
  91. package/src/cli/lib/process.ts +86 -55
  92. package/src/cli/ports/process.ts +8 -2
  93. package/src/cli/subprocess.real-stack.test.ts +611 -598
  94. package/src/cli/subprocess.smoke.test.ts +954 -969
  95. package/src/cli/types/config.ts +2 -6
  96. package/src/client/components/auth/AuthScreen.tsx +3 -3
  97. package/src/client/components/board/BoardStatsChips.tsx +233 -233
  98. package/src/client/components/board/BoardStatsContext.tsx +41 -41
  99. package/src/client/components/board/boardHeaderButtonStyles.ts +38 -38
  100. package/src/client/components/board/shortcuts/useBoardShortcutKeydown.ts +49 -49
  101. package/src/client/components/board/useBoardCanvasPanScroll.ts +108 -108
  102. package/src/client/components/board/useBoardTaskContainerDroppableReact.ts +33 -33
  103. package/src/client/components/board/useBoardTaskSortableReact.ts +26 -26
  104. package/src/client/components/multi-select.tsx +1206 -1206
  105. package/src/client/components/routing/BoardPage.tsx +20 -20
  106. package/src/client/components/routing/NavigationRegistrar.tsx +13 -13
  107. package/src/client/components/settings/SettingsPage.tsx +1 -1
  108. package/src/client/components/task/TaskCard.tsx +643 -643
  109. package/src/client/components/ui/badge.tsx +49 -49
  110. package/src/client/components/ui/button.tsx +65 -65
  111. package/src/client/components/ui/command.tsx +193 -193
  112. package/src/client/components/ui/dialog.tsx +163 -163
  113. package/src/client/components/ui/input-group.tsx +155 -155
  114. package/src/client/components/ui/input.tsx +19 -19
  115. package/src/client/components/ui/popover.tsx +87 -87
  116. package/src/client/components/ui/separator.tsx +28 -28
  117. package/src/client/components/ui/textarea.tsx +18 -18
  118. package/src/client/index.css +248 -248
  119. package/src/client/lib/appNavigate.ts +16 -16
  120. package/src/client/lib/taskCardDate.ts +111 -111
  121. package/src/client/lib/utils.ts +6 -6
  122. package/src/server/auth.ts +351 -302
  123. package/src/server/bootstrapDev.ts +11 -2
  124. package/src/server/bootstrapInstalled.ts +6 -1
  125. package/src/server/index.ts +33 -7
  126. package/src/server/migrations/013_cli_policy_and_provenance.ts +2 -2
  127. package/src/server/migrations/019_cli_global_create_board_default_on.ts +14 -0
  128. package/src/server/migrations/registry.ts +43 -41
  129. package/src/server/parseBootstrapProfile.ts +42 -0
  130. package/src/server/storage/cliPolicy.ts +2 -1
  131. package/src/shared/runtimeConfig.ts +256 -237
  132. package/src/shared/runtimeIdentity.test.ts +47 -0
  133. package/src/shared/runtimeIdentity.ts +35 -0
  134. package/src/shared/serverStatus.ts +21 -0
  135. package/src/shared/skillsInstall.ts +71 -0
  136. package/src/shared/terminalColors.ts +24 -0
  137. package/dist/assets/architecture-YZFGNWBL-3h1eIYfB.js +0 -1
  138. package/dist/assets/clone-BRQpYu_n.js +0 -1
  139. package/dist/assets/gitGraph-7Q5UKJZL-CG8f8JF7.js +0 -1
  140. package/dist/assets/info-OMHHGYJF-C8_SHoRO.js +0 -1
  141. package/dist/assets/packet-4T2RLAQJ-BvpAX0kJ.js +0 -1
  142. package/dist/assets/pie-ZZUOXDRM-Ow26Yf-E.js +0 -1
  143. package/dist/assets/radar-PYXPWWZC-e_ron5jQ.js +0 -1
  144. package/dist/assets/treeView-SZITEDCU-DsEr3xeq.js +0 -1
  145. package/dist/assets/treemap-W4RFUUIX-DV7nk2AB.js +0 -1
  146. package/dist/assets/wardley-RL74JXVD-CrrFU9AE.js +0 -1
@@ -1,237 +1,256 @@
1
- import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
2
- import path from "node:path";
3
-
4
- export type RuntimeKind = "installed" | "dev";
5
-
6
- export interface RuntimeConfigFile {
7
- api_key?: string;
8
- port?: number;
9
- data_dir?: string;
10
- auth_dir?: string;
11
- open_browser?: boolean;
12
- }
13
-
14
- export interface RuntimeConfigOverrides {
15
- kind?: RuntimeKind;
16
- profile?: string;
17
- port?: number;
18
- dataDir?: string;
19
- authDir?: string;
20
- openBrowser?: boolean;
21
- }
22
-
23
- let selectedRuntimeKind: RuntimeKind | undefined;
24
- let selectedProfileName: string | undefined;
25
-
26
- function normalizeProfileName(profile: string | undefined): string | undefined {
27
- const trimmed = profile?.trim();
28
- return trimmed ? trimmed : undefined;
29
- }
30
-
31
- function inferRuntimeKindFromProfile(profile: string | undefined): RuntimeKind | undefined {
32
- return profile === "dev" ? "dev" : undefined;
33
- }
34
-
35
- function readRuntimeKindFromEnv(): RuntimeKind | undefined {
36
- const value = process.env.TASKMANAGER_RUNTIME?.trim().toLowerCase();
37
- if (value === "installed" || value === "dev") return value;
38
- return undefined;
39
- }
40
-
41
- function resolveHomeDir(): string {
42
- return process.env.HOME ?? process.env.USERPROFILE ?? process.cwd();
43
- }
44
-
45
- function normalizePort(value: unknown): number | undefined {
46
- if (typeof value === "number" && Number.isInteger(value) && value > 0) {
47
- return value;
48
- }
49
-
50
- if (typeof value === "string" && value.trim()) {
51
- const parsed = Number(value);
52
- if (Number.isInteger(parsed) && parsed > 0) return parsed;
53
- }
54
-
55
- return undefined;
56
- }
57
-
58
- function normalizeBoolean(value: unknown): boolean | undefined {
59
- if (typeof value === "boolean") return value;
60
- if (typeof value === "string") {
61
- const normalized = value.trim().toLowerCase();
62
- if (normalized === "true" || normalized === "1") return true;
63
- if (normalized === "false" || normalized === "0") return false;
64
- }
65
- return undefined;
66
- }
67
-
68
- export function setRuntimeConfigSelection(selection: {
69
- kind?: RuntimeKind;
70
- profile?: string;
71
- }): void {
72
- if (selection.kind) selectedRuntimeKind = selection.kind;
73
- if (selection.profile !== undefined) {
74
- selectedProfileName = normalizeProfileName(selection.profile);
75
- }
76
- }
77
-
78
- export function resolveRuntimeKind(overrides: RuntimeConfigOverrides = {}): RuntimeKind {
79
- const inferredFromProfile =
80
- inferRuntimeKindFromProfile(normalizeProfileName(overrides.profile)) ??
81
- inferRuntimeKindFromProfile(selectedProfileName) ??
82
- inferRuntimeKindFromProfile(normalizeProfileName(process.env.TASKMANAGER_PROFILE));
83
-
84
- return (
85
- overrides.kind ??
86
- selectedRuntimeKind ??
87
- readRuntimeKindFromEnv() ??
88
- inferredFromProfile ??
89
- "installed"
90
- );
91
- }
92
-
93
- export function resolveProfileName(overrides: RuntimeConfigOverrides = {}): string {
94
- return (
95
- normalizeProfileName(overrides.profile) ??
96
- selectedProfileName ??
97
- normalizeProfileName(process.env.TASKMANAGER_PROFILE) ??
98
- (resolveRuntimeKind(overrides) === "dev" ? "dev" : "default")
99
- );
100
- }
101
-
102
- export function getTaskManagerHomeDir(): string {
103
- return path.join(resolveHomeDir(), ".taskmanager");
104
- }
105
-
106
- export function getProfileRootDir(overrides: RuntimeConfigOverrides = {}): string {
107
- return path.join(getTaskManagerHomeDir(), "profiles", resolveProfileName(overrides));
108
- }
109
-
110
- export function ensureProfileRootDir(overrides: RuntimeConfigOverrides = {}): string {
111
- const profileRootDir = getProfileRootDir(overrides);
112
- mkdirSync(profileRootDir, { recursive: true });
113
- return profileRootDir;
114
- }
115
-
116
- export function ensureRuntimeDirectories(
117
- overrides: RuntimeConfigOverrides = {},
118
- ): {
119
- profileRootDir: string;
120
- dataDir: string;
121
- authDir: string;
122
- } {
123
- const profileRootDir = ensureProfileRootDir(overrides);
124
- const dataDir = resolveDataDir(overrides);
125
- const authDir = resolveAuthDir(overrides);
126
-
127
- mkdirSync(dataDir, { recursive: true });
128
- mkdirSync(authDir, { recursive: true });
129
-
130
- return {
131
- profileRootDir,
132
- dataDir,
133
- authDir,
134
- };
135
- }
136
-
137
- export function getProfileConfigFilePath(overrides: RuntimeConfigOverrides = {}): string {
138
- return path.join(getProfileRootDir(overrides), "config.json");
139
- }
140
-
141
- export function hasProfileConfigFile(overrides: RuntimeConfigOverrides = {}): boolean {
142
- return existsSync(getProfileConfigFilePath(overrides));
143
- }
144
-
145
- export function readProfileConfig(
146
- overrides: RuntimeConfigOverrides = {},
147
- ): RuntimeConfigFile {
148
- const configFilePath = getProfileConfigFilePath(overrides);
149
- if (!existsSync(configFilePath)) return {};
150
-
151
- try {
152
- const raw = readFileSync(configFilePath, "utf8").trim();
153
- if (!raw) return {};
154
- return JSON.parse(raw) as RuntimeConfigFile;
155
- } catch {
156
- return {};
157
- }
158
- }
159
-
160
- export function writeProfileConfig(
161
- config: RuntimeConfigFile,
162
- overrides: RuntimeConfigOverrides = {},
163
- ): string {
164
- const configFilePath = path.join(ensureProfileRootDir(overrides), "config.json");
165
- writeFileSync(configFilePath, `${JSON.stringify(config, null, 2)}\n`, "utf8");
166
- return configFilePath;
167
- }
168
-
169
- export function getDefaultPort(overrides: RuntimeConfigOverrides = {}): number {
170
- return resolveRuntimeKind(overrides) === "dev" ? 3002 : 3001;
171
- }
172
-
173
- export function getDefaultDataDir(overrides: RuntimeConfigOverrides = {}): string {
174
- if (resolveRuntimeKind(overrides) === "dev") {
175
- // Keep repository development pointed at the tracked local SQLite file
176
- // without relying on env vars in the normal dev workflow.
177
- return path.resolve(import.meta.dir, "../..", "data");
178
- }
179
- return path.join(getProfileRootDir(overrides), "data");
180
- }
181
-
182
- export function getDefaultAuthDir(overrides: RuntimeConfigOverrides = {}): string {
183
- return path.join(getProfileRootDir(overrides), "auth");
184
- }
185
-
186
- export function getServerPidFilePath(overrides: RuntimeConfigOverrides = {}): string {
187
- return path.join(getProfileRootDir(overrides), "server.pid.json");
188
- }
189
-
190
- export function resolvePort(overrides: RuntimeConfigOverrides = {}): number {
191
- const config = readProfileConfig(overrides);
192
- return (
193
- normalizePort(overrides.port) ??
194
- normalizePort(process.env.TASKMANAGER_PORT) ??
195
- normalizePort(config.port) ??
196
- getDefaultPort(overrides)
197
- );
198
- }
199
-
200
- export function resolveDataDir(overrides: RuntimeConfigOverrides = {}): string {
201
- const config = readProfileConfig(overrides);
202
- return path.resolve(
203
- overrides.dataDir ??
204
- process.env.TASKMANAGER_DATA_DIR ??
205
- config.data_dir ??
206
- getDefaultDataDir(overrides),
207
- );
208
- }
209
-
210
- export function resolveAuthDir(overrides: RuntimeConfigOverrides = {}): string {
211
- const config = readProfileConfig(overrides);
212
- return path.resolve(
213
- overrides.authDir ??
214
- process.env.TASKMANAGER_AUTH_DIR ??
215
- config.auth_dir ??
216
- getDefaultAuthDir(overrides),
217
- );
218
- }
219
-
220
- export function resolveOpenBrowser(overrides: RuntimeConfigOverrides = {}): boolean {
221
- const config = readProfileConfig(overrides);
222
- return (
223
- normalizeBoolean(overrides.openBrowser) ??
224
- normalizeBoolean(process.env.TASKMANAGER_OPEN_BROWSER) ??
225
- normalizeBoolean(config.open_browser) ??
226
- true
227
- );
228
- }
229
-
230
- export function resolveApiKey(overrides: RuntimeConfigOverrides = {}): string | undefined {
231
- const config = readProfileConfig(overrides);
232
-
233
- if (process.env.API_KEY?.trim()) return process.env.API_KEY;
234
- if (config.api_key?.trim()) return config.api_key;
235
-
236
- return undefined;
237
- }
1
+ import {
2
+ existsSync,
3
+ mkdirSync,
4
+ readdirSync,
5
+ readFileSync,
6
+ writeFileSync,
7
+ } from "node:fs";
8
+ import path from "node:path";
9
+
10
+ export type RuntimeKind = "installed" | "dev";
11
+
12
+ export interface RuntimeConfigFile {
13
+ api_key?: string;
14
+ port?: number;
15
+ data_dir?: string;
16
+ auth_dir?: string;
17
+ open_browser?: boolean;
18
+ }
19
+
20
+ export interface RuntimeConfigOverrides {
21
+ kind?: RuntimeKind;
22
+ profile?: string;
23
+ port?: number;
24
+ dataDir?: string;
25
+ authDir?: string;
26
+ openBrowser?: boolean;
27
+ }
28
+
29
+ let selectedRuntimeKind: RuntimeKind | undefined;
30
+ let selectedProfileName: string | undefined;
31
+ /** Set from global `hirotm --port` (parsed before Commander); not read from any env var. */
32
+ let selectedCliPort: number | undefined;
33
+
34
+ function normalizeProfileName(profile: string | undefined): string | undefined {
35
+ const trimmed = profile?.trim();
36
+ return trimmed ? trimmed : undefined;
37
+ }
38
+
39
+ function readRuntimeKindFromEnv(): RuntimeKind | undefined {
40
+ const value = process.env.TASKMANAGER_RUNTIME?.trim().toLowerCase();
41
+ if (value === "installed" || value === "dev") return value;
42
+ return undefined;
43
+ }
44
+
45
+ function resolveHomeDir(): string {
46
+ return process.env.HOME ?? process.env.USERPROFILE ?? process.cwd();
47
+ }
48
+
49
+ function normalizePort(value: unknown): number | undefined {
50
+ if (typeof value === "number" && Number.isInteger(value) && value > 0) {
51
+ return value;
52
+ }
53
+
54
+ if (typeof value === "string" && value.trim()) {
55
+ const parsed = Number(value);
56
+ if (Number.isInteger(parsed) && parsed > 0) return parsed;
57
+ }
58
+
59
+ return undefined;
60
+ }
61
+
62
+ function normalizeBoolean(value: unknown): boolean | undefined {
63
+ if (typeof value === "boolean") return value;
64
+ if (typeof value === "string") {
65
+ const normalized = value.trim().toLowerCase();
66
+ if (normalized === "true" || normalized === "1") return true;
67
+ if (normalized === "false" || normalized === "0") return false;
68
+ }
69
+ return undefined;
70
+ }
71
+
72
+ export function setRuntimeConfigSelection(selection: {
73
+ kind?: RuntimeKind;
74
+ profile?: string;
75
+ port?: number;
76
+ }): void {
77
+ if (selection.kind) selectedRuntimeKind = selection.kind;
78
+ if (selection.profile !== undefined) {
79
+ selectedProfileName = normalizeProfileName(selection.profile);
80
+ }
81
+ if (selection.port !== undefined) {
82
+ const p = normalizePort(selection.port);
83
+ selectedCliPort = p;
84
+ }
85
+ }
86
+
87
+ // Runtime kind is set explicitly via --dev flag, setRuntimeConfigSelection, or
88
+ // TASKMANAGER_RUNTIME env. Profile name no longer implies runtime kind.
89
+ export function resolveRuntimeKind(overrides: RuntimeConfigOverrides = {}): RuntimeKind {
90
+ return (
91
+ overrides.kind ??
92
+ selectedRuntimeKind ??
93
+ readRuntimeKindFromEnv() ??
94
+ "installed"
95
+ );
96
+ }
97
+
98
+ export function resolveProfileName(overrides: RuntimeConfigOverrides = {}): string {
99
+ return (
100
+ normalizeProfileName(overrides.profile) ??
101
+ selectedProfileName ??
102
+ "default"
103
+ );
104
+ }
105
+
106
+ export function getTaskManagerHomeDir(): string {
107
+ return path.join(resolveHomeDir(), ".taskmanager");
108
+ }
109
+
110
+ /** True if any `~/.taskmanager/profiles/<name>/config.json` exists (any named profile). */
111
+ export function hasAnyProfileConfigOnDisk(): boolean {
112
+ const profilesRoot = path.join(getTaskManagerHomeDir(), "profiles");
113
+ if (!existsSync(profilesRoot)) return false;
114
+ try {
115
+ for (const ent of readdirSync(profilesRoot, { withFileTypes: true })) {
116
+ if (!ent.isDirectory()) continue;
117
+ if (existsSync(path.join(profilesRoot, ent.name, "config.json"))) {
118
+ return true;
119
+ }
120
+ }
121
+ } catch {
122
+ return false;
123
+ }
124
+ return false;
125
+ }
126
+
127
+ export function getProfileRootDir(overrides: RuntimeConfigOverrides = {}): string {
128
+ return path.join(getTaskManagerHomeDir(), "profiles", resolveProfileName(overrides));
129
+ }
130
+
131
+ export function ensureProfileRootDir(overrides: RuntimeConfigOverrides = {}): string {
132
+ const profileRootDir = getProfileRootDir(overrides);
133
+ mkdirSync(profileRootDir, { recursive: true });
134
+ return profileRootDir;
135
+ }
136
+
137
+ export function ensureRuntimeDirectories(
138
+ overrides: RuntimeConfigOverrides = {},
139
+ ): {
140
+ profileRootDir: string;
141
+ dataDir: string;
142
+ authDir: string;
143
+ } {
144
+ const profileRootDir = ensureProfileRootDir(overrides);
145
+ const dataDir = resolveDataDir(overrides);
146
+ const authDir = resolveAuthDir(overrides);
147
+
148
+ mkdirSync(dataDir, { recursive: true });
149
+ mkdirSync(authDir, { recursive: true });
150
+
151
+ return {
152
+ profileRootDir,
153
+ dataDir,
154
+ authDir,
155
+ };
156
+ }
157
+
158
+ export function getProfileConfigFilePath(overrides: RuntimeConfigOverrides = {}): string {
159
+ return path.join(getProfileRootDir(overrides), "config.json");
160
+ }
161
+
162
+ export function hasProfileConfigFile(overrides: RuntimeConfigOverrides = {}): boolean {
163
+ return existsSync(getProfileConfigFilePath(overrides));
164
+ }
165
+
166
+ export function readProfileConfig(
167
+ overrides: RuntimeConfigOverrides = {},
168
+ ): RuntimeConfigFile {
169
+ const configFilePath = getProfileConfigFilePath(overrides);
170
+ if (!existsSync(configFilePath)) return {};
171
+
172
+ try {
173
+ const raw = readFileSync(configFilePath, "utf8").trim();
174
+ if (!raw) return {};
175
+ return JSON.parse(raw) as RuntimeConfigFile;
176
+ } catch {
177
+ return {};
178
+ }
179
+ }
180
+
181
+ export function writeProfileConfig(
182
+ config: RuntimeConfigFile,
183
+ overrides: RuntimeConfigOverrides = {},
184
+ ): string {
185
+ const configFilePath = path.join(ensureProfileRootDir(overrides), "config.json");
186
+ writeFileSync(configFilePath, `${JSON.stringify(config, null, 2)}\n`, "utf8");
187
+ return configFilePath;
188
+ }
189
+
190
+ export function getDefaultPort(overrides: RuntimeConfigOverrides = {}): number {
191
+ return resolveRuntimeKind(overrides) === "dev" ? 3002 : 3001;
192
+ }
193
+
194
+ // All profiles (including dev) use the profile-based data dir under
195
+ // ~/.taskmanager/profiles/<name>/data. Use --data-dir or config.json
196
+ // data_dir to override.
197
+ export function getDefaultDataDir(overrides: RuntimeConfigOverrides = {}): string {
198
+ return path.join(getProfileRootDir(overrides), "data");
199
+ }
200
+
201
+ export function getDefaultAuthDir(overrides: RuntimeConfigOverrides = {}): string {
202
+ return path.join(getProfileRootDir(overrides), "auth");
203
+ }
204
+
205
+ export function getServerPidFilePath(overrides: RuntimeConfigOverrides = {}): string {
206
+ return path.join(getProfileRootDir(overrides), "server.pid.json");
207
+ }
208
+
209
+ export function resolvePort(overrides: RuntimeConfigOverrides = {}): number {
210
+ const config = readProfileConfig(overrides);
211
+ return (
212
+ normalizePort(overrides.port) ??
213
+ normalizePort(selectedCliPort) ??
214
+ normalizePort(config.port) ??
215
+ getDefaultPort(overrides)
216
+ );
217
+ }
218
+
219
+ export function resolveDataDir(overrides: RuntimeConfigOverrides = {}): string {
220
+ const config = readProfileConfig(overrides);
221
+ return path.resolve(
222
+ overrides.dataDir ??
223
+ process.env.TASKMANAGER_DATA_DIR ??
224
+ config.data_dir ??
225
+ getDefaultDataDir(overrides),
226
+ );
227
+ }
228
+
229
+ export function resolveAuthDir(overrides: RuntimeConfigOverrides = {}): string {
230
+ const config = readProfileConfig(overrides);
231
+ return path.resolve(
232
+ overrides.authDir ??
233
+ process.env.TASKMANAGER_AUTH_DIR ??
234
+ config.auth_dir ??
235
+ getDefaultAuthDir(overrides),
236
+ );
237
+ }
238
+
239
+ export function resolveOpenBrowser(overrides: RuntimeConfigOverrides = {}): boolean {
240
+ const config = readProfileConfig(overrides);
241
+ return (
242
+ normalizeBoolean(overrides.openBrowser) ??
243
+ normalizeBoolean(process.env.TASKMANAGER_OPEN_BROWSER) ??
244
+ normalizeBoolean(config.open_browser) ??
245
+ true
246
+ );
247
+ }
248
+
249
+ export function resolveApiKey(overrides: RuntimeConfigOverrides = {}): string | undefined {
250
+ const config = readProfileConfig(overrides);
251
+
252
+ if (process.env.API_KEY?.trim()) return process.env.API_KEY;
253
+ if (config.api_key?.trim()) return config.api_key;
254
+
255
+ return undefined;
256
+ }
@@ -0,0 +1,47 @@
1
+ import { afterEach, describe, expect, test } from "bun:test";
2
+ import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
3
+ import { tmpdir } from "node:os";
4
+ import path from "node:path";
5
+ import { pathToFileURL } from "node:url";
6
+ import { resolveRuntimeSource } from "./runtimeIdentity";
7
+
8
+ describe("resolveRuntimeSource", () => {
9
+ const tempRoots: string[] = [];
10
+
11
+ afterEach(() => {
12
+ for (const tempRoot of tempRoots.splice(0)) {
13
+ try {
14
+ rmSync(tempRoot, { recursive: true, force: true });
15
+ } catch {
16
+ /* Windows may briefly keep temp files open */
17
+ }
18
+ }
19
+ });
20
+
21
+ test("reports repo when the package root contains .git", () => {
22
+ const tempRoot = mkdtempSync(path.join(tmpdir(), "runtime-source-repo-"));
23
+ tempRoots.push(tempRoot);
24
+ const srcDir = path.join(tempRoot, "src", "server");
25
+ mkdirSync(srcDir, { recursive: true });
26
+ mkdirSync(path.join(tempRoot, ".git"), { recursive: true });
27
+ writeFileSync(path.join(tempRoot, "package.json"), "{}\n");
28
+ writeFileSync(path.join(srcDir, "index.ts"), "export {};\n");
29
+
30
+ expect(
31
+ resolveRuntimeSource(pathToFileURL(path.join(srcDir, "index.ts")).href),
32
+ ).toBe("repo");
33
+ });
34
+
35
+ test("reports installed when the package root has no .git", () => {
36
+ const tempRoot = mkdtempSync(path.join(tmpdir(), "runtime-source-installed-"));
37
+ tempRoots.push(tempRoot);
38
+ const srcDir = path.join(tempRoot, "src", "server");
39
+ mkdirSync(srcDir, { recursive: true });
40
+ writeFileSync(path.join(tempRoot, "package.json"), "{}\n");
41
+ writeFileSync(path.join(srcDir, "index.ts"), "export {};\n");
42
+
43
+ expect(
44
+ resolveRuntimeSource(pathToFileURL(path.join(srcDir, "index.ts")).href),
45
+ ).toBe("installed");
46
+ });
47
+ });
@@ -0,0 +1,35 @@
1
+ import { existsSync } from "node:fs";
2
+ import path from "node:path";
3
+ import { fileURLToPath } from "node:url";
4
+ import type { RuntimeKind } from "./runtimeConfig";
5
+
6
+ export type RuntimeSource = "repo" | "installed";
7
+
8
+ export interface RuntimeIdentity {
9
+ kind: RuntimeKind;
10
+ source: RuntimeSource;
11
+ }
12
+
13
+ function findPackageRoot(startDir: string): string {
14
+ let currentDir = path.resolve(startDir);
15
+
16
+ for (;;) {
17
+ if (existsSync(path.join(currentDir, "package.json"))) {
18
+ return currentDir;
19
+ }
20
+
21
+ const parentDir = path.dirname(currentDir);
22
+ if (parentDir === currentDir) {
23
+ return startDir;
24
+ }
25
+ currentDir = parentDir;
26
+ }
27
+ }
28
+
29
+ export function resolveRuntimeSource(fromMetaUrl: string = import.meta.url): RuntimeSource {
30
+ const sourceFilePath = fileURLToPath(fromMetaUrl);
31
+ const packageRoot = findPackageRoot(path.dirname(sourceFilePath));
32
+ // The repo checkout includes `.git`; packed/global installs do not. This keeps
33
+ // runtime/source reporting aligned with the user's repo-vs-installed question.
34
+ return existsSync(path.join(packageRoot, ".git")) ? "repo" : "installed";
35
+ }
@@ -0,0 +1,21 @@
1
+ import type { RuntimeKind } from "./runtimeConfig";
2
+ import type { RuntimeSource } from "./runtimeIdentity";
3
+
4
+ export interface RunningServerStatus {
5
+ pid: number;
6
+ port: number;
7
+ running: true;
8
+ runtime: RuntimeKind;
9
+ source: RuntimeSource;
10
+ url: string;
11
+ }
12
+
13
+ export interface StoppedServerStatus {
14
+ running: false;
15
+ }
16
+
17
+ export type ServerStatus = RunningServerStatus | StoppedServerStatus;
18
+
19
+ export function buildLocalServerUrl(port: number): string {
20
+ return `http://127.0.0.1:${port}`;
21
+ }