@dadado/agent-kit-cli 5.15.1 → 5.17.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (72) hide show
  1. package/dashboard/dashboard-data.mjs +69 -65
  2. package/dashboard/lib/guards.d.mts +5 -0
  3. package/dashboard/lib/guards.mjs +31 -0
  4. package/dashboard/lib/identity-probe.mjs +56 -0
  5. package/dashboard/lib/pipeline-cache.mjs +58 -0
  6. package/dashboard/lib/processes.mjs +71 -0
  7. package/dashboard/serve.mjs +43 -1
  8. package/dashboard/start-broadcast.mjs +2 -12
  9. package/dashboard/start.mjs +2 -13
  10. package/dist/add-HTYIFKMN.js +150 -0
  11. package/dist/chunk-2IPXQFJE.js +253 -0
  12. package/dist/chunk-2RDRMGD3.js +82 -0
  13. package/dist/chunk-2YU2H7I6.js +42 -0
  14. package/dist/chunk-4IWG5CVE.js +214 -0
  15. package/dist/chunk-5VCOWTTP.js +6 -0
  16. package/dist/chunk-77V75A7O.js +39 -0
  17. package/dist/chunk-7HDQEK6T.js +26 -0
  18. package/dist/chunk-AW74INUW.js +1456 -0
  19. package/dist/chunk-B73KQ5KX.js +972 -0
  20. package/dist/chunk-CBCW5R5J.js +484 -0
  21. package/dist/chunk-DE3LQFSJ.js +18 -0
  22. package/dist/chunk-DQA434Y4.js +106 -0
  23. package/dist/chunk-F4VMYR7U.js +318 -0
  24. package/dist/chunk-F6C3GFKV.js +57 -0
  25. package/dist/chunk-HFUH3HVS.js +282 -0
  26. package/dist/chunk-HS7LVVLO.js +74 -0
  27. package/dist/chunk-IHROBTNM.js +135 -0
  28. package/dist/chunk-KD2MYBB6.js +213 -0
  29. package/dist/chunk-MAPOQEA2.js +43 -0
  30. package/dist/chunk-N3HTJVBR.js +341 -0
  31. package/dist/chunk-NQ7IQ5XT.js +26 -0
  32. package/dist/chunk-OUQZC744.js +1315 -0
  33. package/dist/chunk-PCCQUWE5.js +817 -0
  34. package/dist/chunk-PNFF6OU3.js +273 -0
  35. package/dist/chunk-PNG7LO2F.js +53 -0
  36. package/dist/chunk-QTON2GQX.js +203 -0
  37. package/dist/chunk-S6GMFDKV.js +50 -0
  38. package/dist/chunk-SMGFN3Z4.js +503 -0
  39. package/dist/chunk-STGFBAYT.js +641 -0
  40. package/dist/chunk-TFZ2JUXH.js +1081 -0
  41. package/dist/chunk-VY7MTPJ2.js +205 -0
  42. package/dist/chunk-W24CMQ32.js +525 -0
  43. package/dist/chunk-WW3YZKHW.js +165 -0
  44. package/dist/chunk-ZB43YMHW.js +20 -0
  45. package/dist/chunk-ZCTSUQRR.js +874 -0
  46. package/dist/chunk-ZXKZBTXE.js +17 -0
  47. package/dist/contribute-C6AW2P6J.js +367 -0
  48. package/dist/cursor-awareness-KTL2RGKD.js +77 -0
  49. package/dist/dashboard-YSNUGBFY.js +21 -0
  50. package/dist/dashboard-broadcast-M4CMW7RX.js +119 -0
  51. package/dist/diff-VHNR62SI.js +74 -0
  52. package/dist/doctor-PET2HRRJ.js +236 -0
  53. package/dist/dogfood-KKFVRWT6.js +366 -0
  54. package/dist/guard-EHXF76ID.js +530 -0
  55. package/dist/handoff-3UI6VKVG.js +302 -0
  56. package/dist/hook-DYIMKJYL.js +1074 -0
  57. package/dist/index.js +62 -14068
  58. package/dist/init-Q4LBM6KT.js +127 -0
  59. package/dist/install-RHOS3OYZ.js +34 -0
  60. package/dist/mission-control-LJACEVVN.js +89 -0
  61. package/dist/monitors-QV2IS3MP.js +176 -0
  62. package/dist/plan-index-JMC7Z4DW.js +44 -0
  63. package/dist/public-inbound-radar-WP3XF7GS.js +68 -0
  64. package/dist/remote-issues-SZ4HDQL5.js +44 -0
  65. package/dist/run-I3BFGULT.js +243 -0
  66. package/dist/run-plan-FO7CL4M3.js +118 -0
  67. package/dist/scan-NI2SWJ2E.js +33 -0
  68. package/dist/setup-global-M42GEODI.js +340 -0
  69. package/dist/status-XPVQVQMX.js +150 -0
  70. package/dist/update-FJ2EBZVO.js +283 -0
  71. package/dist/validate-V2CW7J4B.js +201 -0
  72. package/package.json +3 -3
@@ -0,0 +1,318 @@
1
+ import {
2
+ assessEnvironment
3
+ } from "./chunk-4IWG5CVE.js";
4
+ import {
5
+ compareSemver,
6
+ normalizeSemver
7
+ } from "./chunk-SMGFN3Z4.js";
8
+ import {
9
+ MANIFEST_RELATIVE_PATH
10
+ } from "./chunk-AW74INUW.js";
11
+ import {
12
+ KIT_VERSION,
13
+ pinnedCliSpec
14
+ } from "./chunk-7HDQEK6T.js";
15
+ import {
16
+ fileExists
17
+ } from "./chunk-77V75A7O.js";
18
+
19
+ // src/utils/terminal.ts
20
+ import { readdir } from "fs/promises";
21
+ import { homedir } from "os";
22
+ import path from "path";
23
+ import { confirm, isCancel } from "@clack/prompts";
24
+ function isNonInteractive() {
25
+ if (process.env.CI === "true" || process.env.CI === "1") return true;
26
+ if (process.env.AGENT_KIT_YES === "1") return true;
27
+ return !process.stdin.isTTY;
28
+ }
29
+ var NESTED_REPO_AMBIGUITY_THRESHOLD = 2;
30
+ var NESTED_REPO_SCAN_LIMIT = 200;
31
+ async function findNestedRepoChildren(resolved) {
32
+ let entries;
33
+ try {
34
+ entries = await readdir(resolved, { withFileTypes: true });
35
+ } catch {
36
+ return [];
37
+ }
38
+ const nested = [];
39
+ let scanned = 0;
40
+ for (const entry of entries) {
41
+ if (nested.length >= NESTED_REPO_AMBIGUITY_THRESHOLD) break;
42
+ if (scanned >= NESTED_REPO_SCAN_LIMIT) break;
43
+ if (!entry.isDirectory()) continue;
44
+ if (entry.name.startsWith(".") || entry.name === "node_modules") continue;
45
+ scanned += 1;
46
+ if (await fileExists(path.join(resolved, entry.name, ".git"))) {
47
+ nested.push(entry.name);
48
+ }
49
+ }
50
+ return nested;
51
+ }
52
+ async function validateProjectRoot(resolved) {
53
+ const home = path.resolve(homedir());
54
+ if (resolved === "/" || resolved === home) {
55
+ return {
56
+ ok: false,
57
+ reason: `Refused to use ${resolved} as a project root.`,
58
+ recovery: "Change into a project directory and re-run. Agent Kit installs per project."
59
+ };
60
+ }
61
+ const hasGit = await fileExists(path.join(resolved, ".git"));
62
+ const hasManifest = await fileExists(path.join(resolved, MANIFEST_RELATIVE_PATH));
63
+ if (!hasGit && !hasManifest) {
64
+ return {
65
+ ok: false,
66
+ reason: `Refused ${resolved}: no .git and no ${MANIFEST_RELATIVE_PATH}.`,
67
+ recovery: [
68
+ "Starting from an empty folder? Pick one of these:",
69
+ " 1. git init - then re-run. Recommended: readiness and the",
70
+ " staging -> prod flow both want Git.",
71
+ " 2. --force-root - install without Git. /agent-kit-onboard will",
72
+ " still offer to initialize it later.",
73
+ " 3. Answer yes to the 'Proceed anyway?' prompt in an interactive terminal.",
74
+ "Or re-run from the project directory you actually meant."
75
+ ].join("\n")
76
+ };
77
+ }
78
+ if (hasGit && !hasManifest) {
79
+ const nested = await findNestedRepoChildren(resolved);
80
+ if (nested.length >= NESTED_REPO_AMBIGUITY_THRESHOLD) {
81
+ return {
82
+ ok: false,
83
+ reason: `Refused ${resolved}: it has .git but also contains child repositories (${nested.join(", ")}). This looks like a parent-of-repos folder, not a project root.`,
84
+ recovery: [
85
+ "L0 belongs in one project, not in the folder that holds several.",
86
+ " 1. cd into the project you meant, then re-run.",
87
+ " 2. --force-root - only if this parent folder really is the project root."
88
+ ].join("\n")
89
+ };
90
+ }
91
+ }
92
+ return { ok: true };
93
+ }
94
+ async function confirmProjectRoot(cwd, opts) {
95
+ const resolved = path.resolve(cwd);
96
+ if (opts.forceRoot) {
97
+ return resolved;
98
+ }
99
+ const validation = await validateProjectRoot(resolved);
100
+ if (!validation.ok) {
101
+ if (opts.nonInteractive) {
102
+ throw new RootRefusedError(resolved, validation.reason, validation.recovery);
103
+ }
104
+ const ok2 = await confirm({
105
+ message: `${validation.reason} Proceed anyway?`,
106
+ initialValue: false
107
+ });
108
+ if (isCancel(ok2) || !ok2) {
109
+ throw new RootRefusedError(resolved, validation.reason, validation.recovery);
110
+ }
111
+ return resolved;
112
+ }
113
+ if (opts.nonInteractive) {
114
+ return resolved;
115
+ }
116
+ const ok = await confirm({
117
+ message: `${opts.command === "install" ? "Install" : "Update"} Agent Kit in: ${resolved}`,
118
+ initialValue: true
119
+ });
120
+ if (isCancel(ok) || !ok) {
121
+ throw new RootRefusedError(resolved);
122
+ }
123
+ return resolved;
124
+ }
125
+ var RootRefusedError = class extends Error {
126
+ constructor(root, reason, recovery) {
127
+ super(reason ?? `Refused to write into ${root}. Re-run from the correct project directory.`);
128
+ this.root = root;
129
+ this.recovery = recovery;
130
+ this.name = "RootRefusedError";
131
+ }
132
+ root;
133
+ recovery;
134
+ };
135
+ var NPM_GLOBAL_PREFIX_PATH_RE = /\/lib\/node_modules|Program Files\\nodejs\\node_modules/;
136
+ var NPM_GLOBAL_NODE_MODULES_RE = /node_modules/;
137
+ function isNpmGlobalPrefixError(msg, code) {
138
+ const isPermissionError = code === "EPERM" || code === "EACCES" || /EPERM|EACCES/.test(msg);
139
+ if (!isPermissionError) return false;
140
+ return NPM_GLOBAL_PREFIX_PATH_RE.test(msg) || NPM_GLOBAL_NODE_MODULES_RE.test(msg);
141
+ }
142
+ function classifyInstallError(err) {
143
+ const msg = err instanceof Error ? err.message : String(err);
144
+ const code = err?.code;
145
+ if (isNpmGlobalPrefixError(msg, code)) {
146
+ return {
147
+ kind: "npm-global-eacces",
148
+ message: `Permission error (root-owned npm prefix): ${msg}`,
149
+ recovery: [
150
+ "npm's global install prefix (e.g. /usr/local/lib/node_modules) is owned by root, so global installs fail.",
151
+ "Recovery options:",
152
+ " 1. Run: npx @dadado/agent-kit-cli@latest setup-global (relocates npm's prefix to a folder you own, fixes PATH, reinstalls)",
153
+ ' 2. Manual fix: mkdir -p ~/.npm-global && npm config set prefix "~/.npm-global" && export PATH="~/.npm-global/bin:$PATH" (add to your shell profile) && npm i -g @dadado/agent-kit-cli@latest',
154
+ " 3. Use Port B fallback: drag install.md into the Cursor chat"
155
+ ].join("\n")
156
+ };
157
+ }
158
+ if (code === "EPERM" || code === "EACCES" || /EPERM|EACCES/.test(msg)) {
159
+ return {
160
+ kind: "eperm",
161
+ message: `Permission error: ${msg}`,
162
+ recovery: [
163
+ "The npm cache may have ownership drift (root-written files in a user dir).",
164
+ "Recovery options:",
165
+ " 1. npx --cache .npm-cache @dadado/agent-kit-cli@latest install",
166
+ " 2. npm cache clean --force && npx @dadado/agent-kit-cli@latest install",
167
+ " 3. Use Port B fallback: drag install.md into the Cursor chat"
168
+ ].join("\n")
169
+ };
170
+ }
171
+ if (/403|Forbidden|unauthorized|E401|ENEEDAUTH/.test(msg)) {
172
+ return {
173
+ kind: "registry-auth",
174
+ message: `Registry access denied: ${msg}`,
175
+ recovery: [
176
+ "The npm registry returned 403/401 for the scoped package.",
177
+ "Recovery options:",
178
+ " 1. Check npm auth: npm whoami (login if needed: npm login)",
179
+ " 2. If using a private registry, verify .npmrc scope config",
180
+ " 3. Use Port B fallback: drag install.md into the Cursor chat"
181
+ ].join("\n")
182
+ };
183
+ }
184
+ if (/ENOTFOUND|ETIMEDOUT|ECONNREFUSED|ECONNRESET|EAI_AGAIN|fetch failed/.test(msg) || code === "ENOTFOUND" || code === "ETIMEDOUT") {
185
+ return {
186
+ kind: "network",
187
+ message: `Network error: ${msg}`,
188
+ recovery: [
189
+ "Could not reach the registry or git remote.",
190
+ "Recovery options:",
191
+ " 1. Check network/proxy/VPN settings",
192
+ " 2. Retry: npx @dadado/agent-kit-cli@latest install",
193
+ " 3. Use --registry <local-path> if you have a local checkout"
194
+ ].join("\n")
195
+ };
196
+ }
197
+ return {
198
+ kind: "unknown",
199
+ message: msg,
200
+ recovery: "Unexpected error. Check the message above and retry, or use Port B fallback."
201
+ };
202
+ }
203
+
204
+ // src/lifecycle/path-cli.ts
205
+ import { spawn } from "child_process";
206
+ import path2 from "path";
207
+ function npxPinned(runtimeVersion, subcommand) {
208
+ return `npx -y ${pinnedCliSpec(runtimeVersion)} ${subcommand}`;
209
+ }
210
+ function isBinUnderPrefix(binPath, prefix) {
211
+ const bin = path2.resolve(binPath);
212
+ const root = path2.resolve(prefix);
213
+ return bin === root || bin.startsWith(`${root}${path2.sep}`);
214
+ }
215
+ function pathCliStatus(env, runtimeVersion) {
216
+ if (!env.binOnPath || !env.binPath) return "missing";
217
+ const running = normalizeSemver(runtimeVersion);
218
+ const pathVer = env.binVersion ? normalizeSemver(env.binVersion) : null;
219
+ if (!pathVer || !running) return "unknown";
220
+ try {
221
+ return compareSemver(pathVer, running) >= 0 ? "current" : "behind";
222
+ } catch {
223
+ return "unknown";
224
+ }
225
+ }
226
+ function driftLines(env, runtimeVersion) {
227
+ const pathVer = env.binVersion ?? "unknown";
228
+ const where = env.binPath ?? "(unresolved)";
229
+ return [
230
+ `This CLI is v${runtimeVersion}. PATH \`agent-kit\` is v${pathVer} at:`,
231
+ ` ${where}`,
232
+ "Do not run bare `agent-kit update` or `init` from that binary. It re-stamps the old version.",
233
+ "Keep using the pinned npx form until PATH matches:",
234
+ ` ${npxPinned(runtimeVersion, "update")}`,
235
+ ` ${npxPinned(runtimeVersion, "status")}`
236
+ ];
237
+ }
238
+ function isShadow(env) {
239
+ if (!env.binPath || !env.npmPrefix.prefix) return false;
240
+ return !isBinUnderPrefix(env.binPath, env.npmPrefix.prefix);
241
+ }
242
+ async function spawnNpmGlobalInstall(spec) {
243
+ return new Promise((resolve) => {
244
+ const child = spawn("npm", ["i", "-g", spec], { stdio: "inherit" });
245
+ child.on("error", (error) => resolve({ ok: false, error }));
246
+ child.on("close", (code) => {
247
+ if (code === 0) resolve({ ok: true });
248
+ else resolve({ ok: false, error: new Error(`npm exited with code ${code ?? "unknown"}`) });
249
+ });
250
+ });
251
+ }
252
+ async function syncPathCliToRuntime(options = {}) {
253
+ const runtimeVersion = options.runtimeVersion ?? KIT_VERSION;
254
+ const assess = options.assessEnvironmentImpl ?? assessEnvironment;
255
+ let env = options.env ?? await assess();
256
+ const autoInstall = options.autoInstall ?? !isNonInteractive();
257
+ const lines = [];
258
+ let status = pathCliStatus(env, runtimeVersion);
259
+ if (status === "current") {
260
+ return { status, env, upgraded: false, lines };
261
+ }
262
+ if (status === "behind" || status === "unknown") {
263
+ lines.push(...driftLines(env, runtimeVersion));
264
+ if (isShadow(env)) {
265
+ lines.push(
266
+ "That PATH hit is not npm's global bin, so `npm i -g` may not replace it.",
267
+ "Run `hash -r` and `which -a agent-kit`. Put npm's global bin first, or remove the stale shim."
268
+ );
269
+ }
270
+ }
271
+ if (!autoInstall) {
272
+ return { status, env, upgraded: false, lines };
273
+ }
274
+ if (status === "missing") {
275
+ lines.push("No bare `agent-kit` on PATH yet (npx is ephemeral).");
276
+ }
277
+ if (!env.npmPrefixWritable) {
278
+ lines.push(
279
+ "npm's global prefix is not writable, so this process cannot upgrade PATH.",
280
+ `Run ${npxPinned(runtimeVersion, "setup-global")} (or fix prefix permissions).`
281
+ );
282
+ return { status, env, upgraded: false, lines };
283
+ }
284
+ const spec = pinnedCliSpec(runtimeVersion);
285
+ const install = options.npmInstallImpl ?? spawnNpmGlobalInstall;
286
+ lines.push(`Installing ${spec} into the writable npm prefix...`);
287
+ const outcome = await install(spec);
288
+ if (!outcome.ok) {
289
+ const detail = outcome.error instanceof Error ? outcome.error.message : "npm i -g failed";
290
+ lines.push(`Global install failed: ${detail}`);
291
+ return { status, env, upgraded: false, lines };
292
+ }
293
+ env = await assess();
294
+ status = pathCliStatus(env, runtimeVersion);
295
+ if (status === "current") {
296
+ lines.push(
297
+ `PATH \`agent-kit\` is now v${env.binVersion ?? runtimeVersion}.`,
298
+ "If this shell still shows the old version, run `hash -r` or open a new terminal."
299
+ );
300
+ return { status, env, upgraded: true, lines };
301
+ }
302
+ lines.push(
303
+ `Installed ${spec}, but PATH still resolves to v${env.binVersion ?? "unknown"} at:`,
304
+ ` ${env.binPath ?? "(unresolved)"}`,
305
+ "Run `hash -r` and `which -a agent-kit`. Until PATH matches, keep using the npx pin above."
306
+ );
307
+ return { status, env, upgraded: true, lines };
308
+ }
309
+
310
+ export {
311
+ isNonInteractive,
312
+ confirmProjectRoot,
313
+ RootRefusedError,
314
+ classifyInstallError,
315
+ npxPinned,
316
+ pathCliStatus,
317
+ syncPathCliToRuntime
318
+ };
@@ -0,0 +1,57 @@
1
+ // src/types.ts
2
+ var GIT_PLATFORM_META = {
3
+ github: {
4
+ cli: "gh",
5
+ prTerm: "Pull Request",
6
+ prCommand: "gh pr create",
7
+ ciDefault: "github-actions"
8
+ },
9
+ gitlab: {
10
+ cli: "glab",
11
+ prTerm: "Merge Request",
12
+ prCommand: "glab mr create",
13
+ ciDefault: "gitlab-ci"
14
+ },
15
+ bitbucket: {
16
+ cli: "bb",
17
+ prTerm: "Pull Request",
18
+ prCommand: "bb pr create",
19
+ ciDefault: "bitbucket-pipelines"
20
+ },
21
+ "azure-devops": {
22
+ cli: "az repos",
23
+ prTerm: "Pull Request",
24
+ prCommand: "az repos pr create",
25
+ ciDefault: "azure-pipelines"
26
+ },
27
+ gitea: { cli: "tea", prTerm: "Pull Request", prCommand: "tea pr create", ciDefault: "none" }
28
+ };
29
+ var CI_PLATFORM_FILES = {
30
+ "github-actions": ".github/workflows",
31
+ "gitlab-ci": ".gitlab-ci.yml",
32
+ "azure-pipelines": "azure-pipelines.yml",
33
+ "bitbucket-pipelines": "bitbucket-pipelines.yml",
34
+ jenkins: "Jenkinsfile",
35
+ circleci: ".circleci/config.yml",
36
+ travis: ".travis.yml"
37
+ };
38
+ var PM_TOOL_LABELS = {
39
+ clickup: "ClickUp",
40
+ jira: "Jira",
41
+ linear: "Linear",
42
+ asana: "Asana",
43
+ "github-issues": "GitHub Issues",
44
+ "github-projects": "GitHub Projects",
45
+ "azure-boards": "Azure Boards",
46
+ trello: "Trello",
47
+ shortcut: "Shortcut",
48
+ notion: "Notion",
49
+ youtrack: "YouTrack",
50
+ none: "None"
51
+ };
52
+
53
+ export {
54
+ GIT_PLATFORM_META,
55
+ CI_PLATFORM_FILES,
56
+ PM_TOOL_LABELS
57
+ };
@@ -0,0 +1,282 @@
1
+ import {
2
+ logger
3
+ } from "./chunk-ZB43YMHW.js";
4
+
5
+ // src/commands/dashboard.ts
6
+ import { execFileSync, spawn as spawn2 } from "child_process";
7
+ import { existsSync, readFileSync as readFileSync2, realpathSync } from "fs";
8
+ import { access } from "fs/promises";
9
+ import path from "path";
10
+ import { fileURLToPath } from "url";
11
+ import { defineCommand } from "citty";
12
+
13
+ // ../../dashboard/lib/guards.mjs
14
+ import { randomBytes, timingSafeEqual } from "crypto";
15
+ import { networkInterfaces } from "os";
16
+ import { resolve } from "path";
17
+ var REPO_ROOT_ENV = "MISSION_CONTROL_REPO_ROOT";
18
+ var KIT_ROOT_ENV_KEYS = Object.freeze(["MISSION_CONTROL_KIT_ROOT", "AGENT_KIT_HOME"]);
19
+ var CONTEXT_CONFIG_REL = ".cursor/context/config.json";
20
+ var CONFIG_PERSONA_IDS = Object.freeze(["autopilot", "night-shift", "ghost-runner"]);
21
+ var CONFIG_PERSONA_MODES = Object.freeze(["continue-plan", "run-plan", "cli-run-plan"]);
22
+ var CONFIG_REVIEW_BACKENDS = Object.freeze(["auto", "claude", "cursor"]);
23
+ var CONFIG_REVIEW_MODES = Object.freeze(["paste", "autonomous"]);
24
+ var CONFIG_REVIEW_PREFLIGHT = Object.freeze(["off", "warn", "block"]);
25
+ function resolveContextConfigPath(repoRoot, fsHooks = {}) {
26
+ const exists = fsHooks.existsSync;
27
+ const realpath = fsHooks.realpathSync;
28
+ const mkdir = fsHooks.mkdirSync;
29
+ if (typeof repoRoot !== "string" || !repoRoot) {
30
+ return { ok: false, error: "invalid repo root" };
31
+ }
32
+ const abs = resolve(repoRoot, CONTEXT_CONFIG_REL);
33
+ const contextDir = resolve(repoRoot, ".cursor", "context");
34
+ const absNorm = abs.replace(/\\/g, "/");
35
+ if (!absNorm.endsWith("/.cursor/context/config.json")) {
36
+ return { ok: false, error: "path escape" };
37
+ }
38
+ try {
39
+ if (typeof mkdir === "function" && typeof exists === "function" && !exists(contextDir)) {
40
+ mkdir(contextDir, { recursive: true });
41
+ }
42
+ if (typeof realpath === "function" && typeof exists === "function" && exists(abs)) {
43
+ const fileReal = String(realpath(abs)).replace(/\\/g, "/");
44
+ const rootReal = String(realpath(repoRoot)).replace(/\\/g, "/");
45
+ if (!fileReal.startsWith(`${rootReal}/`)) {
46
+ return { ok: false, error: "path escape" };
47
+ }
48
+ if (!fileReal.endsWith("/.cursor/context/config.json")) {
49
+ return { ok: false, error: "path escape" };
50
+ }
51
+ return { ok: true, path: realpath(abs) };
52
+ }
53
+ } catch {
54
+ return { ok: false, error: "path escape" };
55
+ }
56
+ return { ok: true, path: abs };
57
+ }
58
+
59
+ // ../../dashboard/lib/open-browser.mjs
60
+ import { spawn, spawnSync } from "child_process";
61
+ import { readFileSync } from "fs";
62
+ import { platform as osPlatform } from "os";
63
+ var OS_DEFAULT_TOKENS = /* @__PURE__ */ new Set(["", "default", "os", "ask"]);
64
+ function isSafePreferredBrowser(value) {
65
+ if (typeof value !== "string") return false;
66
+ const s = value.trim();
67
+ if (!s) return false;
68
+ if (/[/\\]/.test(s)) return false;
69
+ if (/[\0-\x1f\x7f]/.test(s)) return false;
70
+ if (/[$`;&|<>(){}[\]!*?#~"'%^=,+]/.test(s)) return false;
71
+ if (s.includes(":")) return false;
72
+ if (/^-/.test(s)) return false;
73
+ return true;
74
+ }
75
+ function normalizePreferredBrowser(value) {
76
+ if (value == null) return null;
77
+ const s = String(value).trim();
78
+ if (!s || OS_DEFAULT_TOKENS.has(s.toLowerCase())) return null;
79
+ if (!isSafePreferredBrowser(s)) return null;
80
+ return s;
81
+ }
82
+ function readPreferredBrowserFromConfig(configPath, fsHooks = {}) {
83
+ const read = fsHooks.readFileSync ?? readFileSync;
84
+ try {
85
+ const raw = read(configPath, "utf8");
86
+ const data = JSON.parse(raw);
87
+ if (!data || typeof data !== "object" || Array.isArray(data)) return null;
88
+ const mc = data.missionControl;
89
+ if (!mc || typeof mc !== "object" || Array.isArray(mc)) return null;
90
+ return mc.preferredBrowser ?? null;
91
+ } catch {
92
+ return null;
93
+ }
94
+ }
95
+
96
+ // src/commands/dashboard.ts
97
+ function readPreferredBrowserFromWorkspace(cwd, readFile = readFileSync2) {
98
+ const resolved = resolveContextConfigPath(path.resolve(cwd), {
99
+ existsSync,
100
+ realpathSync
101
+ });
102
+ if (!resolved.ok) return null;
103
+ const value = readPreferredBrowserFromConfig(resolved.path, { readFileSync: readFile });
104
+ return normalizePreferredBrowser(value);
105
+ }
106
+ function applyDashboardOpenEnv(env, opts) {
107
+ const next = { ...env };
108
+ if (opts.noOpen) next.MISSION_CONTROL_NO_OPEN = "1";
109
+ const flag = opts.browser?.trim();
110
+ if (flag) {
111
+ const safe = normalizePreferredBrowser(flag);
112
+ if (safe) next.MISSION_CONTROL_PREFERRED_BROWSER = safe;
113
+ } else if (!next.MISSION_CONTROL_PREFERRED_BROWSER && opts.cwd) {
114
+ const fromConfig = readPreferredBrowserFromWorkspace(opts.cwd);
115
+ if (fromConfig) next.MISSION_CONTROL_PREFERRED_BROWSER = fromConfig;
116
+ }
117
+ return next;
118
+ }
119
+ function dashboardSpawnEnv(env, snapshotRoot, opts) {
120
+ const root = path.resolve(snapshotRoot);
121
+ return applyDashboardOpenEnv({ ...env, MISSION_CONTROL_REPO_ROOT: root }, { ...opts, cwd: root });
122
+ }
123
+ function dashboardProcessTitle(snapshotRoot) {
124
+ const base = path.basename(path.resolve(snapshotRoot)) || "workspace";
125
+ return `Mission Control \xB7 ${base}`;
126
+ }
127
+ function bundledDashboardCandidates(filename, moduleUrl = import.meta.url) {
128
+ const here = path.dirname(fileURLToPath(moduleUrl));
129
+ return [
130
+ // Published / built: <pkg>/dist/index.js -> <pkg>/dashboard/
131
+ path.join(here, "..", "dashboard", filename),
132
+ // Dev source: packages/cli/src/commands/*.ts -> packages/cli/dashboard/ (sync copy)
133
+ path.join(here, "..", "..", "dashboard", filename),
134
+ // From src/commands: packages/dashboard/ (unused layout); from dist: two levels above package
135
+ path.join(here, "..", "..", "..", "dashboard", filename)
136
+ ];
137
+ }
138
+ async function firstExisting(candidates) {
139
+ for (const candidate of candidates) {
140
+ try {
141
+ await access(candidate);
142
+ return path.resolve(candidate);
143
+ } catch {
144
+ }
145
+ }
146
+ return null;
147
+ }
148
+ async function findDashboardStart(cwd, env = process.env, options = {}) {
149
+ let dir = path.resolve(cwd);
150
+ for (; ; ) {
151
+ const candidate = path.join(dir, "dashboard", "start.mjs");
152
+ try {
153
+ await access(candidate);
154
+ return candidate;
155
+ } catch {
156
+ }
157
+ const parent = path.dirname(dir);
158
+ if (parent === dir) break;
159
+ dir = parent;
160
+ }
161
+ for (const key of ["MISSION_CONTROL_KIT_ROOT", "AGENT_KIT_HOME"]) {
162
+ const base = env[key];
163
+ if (!base || typeof base !== "string" || !base.trim()) continue;
164
+ const candidate = path.join(path.resolve(base.trim()), "dashboard", "start.mjs");
165
+ try {
166
+ await access(candidate);
167
+ return candidate;
168
+ } catch {
169
+ }
170
+ }
171
+ const sibling = path.join(path.resolve(cwd), "..", "agent-kit", "dashboard", "start.mjs");
172
+ try {
173
+ await access(sibling);
174
+ return path.resolve(sibling);
175
+ } catch {
176
+ }
177
+ return firstExisting(
178
+ bundledDashboardCandidates("start.mjs", options.moduleUrl ?? import.meta.url)
179
+ );
180
+ }
181
+ function resolveDashboardSnapshotRoot(cwd) {
182
+ const abs = path.resolve(cwd);
183
+ let dir = abs;
184
+ for (; ; ) {
185
+ if (existsSync(path.join(dir, ".cursor", "agent-kit.json"))) {
186
+ return dir;
187
+ }
188
+ const parent = path.dirname(dir);
189
+ if (parent === dir) break;
190
+ dir = parent;
191
+ }
192
+ try {
193
+ const top = execFileSync("git", ["-C", abs, "rev-parse", "--show-toplevel"], {
194
+ encoding: "utf8",
195
+ timeout: 5e3,
196
+ stdio: ["ignore", "pipe", "ignore"]
197
+ }).trim();
198
+ if (top) return path.resolve(top);
199
+ } catch {
200
+ }
201
+ return abs;
202
+ }
203
+ function runStartScript(startPath, env) {
204
+ return new Promise((resolve2, reject) => {
205
+ const child = spawn2(process.execPath, [startPath], {
206
+ cwd: path.dirname(path.dirname(startPath)),
207
+ env,
208
+ stdio: "inherit"
209
+ });
210
+ child.on("error", reject);
211
+ child.on("close", (code) => resolve2(code ?? 1));
212
+ });
213
+ }
214
+ var dashboardCommand = defineCommand({
215
+ meta: {
216
+ name: "dashboard",
217
+ description: "Start Mission Control for this workspace (loopback; opens panel URL)."
218
+ },
219
+ args: {
220
+ cwd: {
221
+ type: "string",
222
+ default: process.cwd(),
223
+ description: "Workspace to snapshot (nearest .cursor/agent-kit.json, else git root); also searched upward for dashboard/start.mjs"
224
+ },
225
+ "no-open": {
226
+ type: "boolean",
227
+ default: false,
228
+ description: "Do not open a browser; only ensure the server is up and print the URL"
229
+ },
230
+ browser: {
231
+ type: "string",
232
+ description: "Preferred browser app/binary for this launch (overrides config missionControl.preferredBrowser)"
233
+ }
234
+ },
235
+ async run({ args }) {
236
+ const snapshotRoot = resolveDashboardSnapshotRoot(args.cwd);
237
+ const startPath = await findDashboardStart(args.cwd);
238
+ if (!startPath) {
239
+ logger.error("No dashboard/start.mjs found.");
240
+ console.error(
241
+ [
242
+ "",
243
+ "The dashboard runtime is not available in this workspace.",
244
+ "L0 install provides the /dashboard command text but not the panel itself.",
245
+ "",
246
+ "Recovery (pick one):",
247
+ " 1. Upgrade the CLI: npx @dadado/agent-kit-cli@latest dashboard",
248
+ " (Path C ships dashboard/ from 4.8.2 onward)",
249
+ " 2. Set an env var pointing to an agent-kit checkout:",
250
+ " export MISSION_CONTROL_KIT_ROOT=/path/to/agent-kit",
251
+ " agent-kit dashboard",
252
+ " 3. Place an agent-kit sibling: ../agent-kit/dashboard/start.mjs",
253
+ " 4. Run directly from a kit tree: node dashboard/start.mjs",
254
+ "",
255
+ "Works in Cursor, VS Code, and any Node.js terminal.",
256
+ ""
257
+ ].join("\n")
258
+ );
259
+ process.exitCode = 1;
260
+ return;
261
+ }
262
+ const env = dashboardSpawnEnv({ ...process.env }, snapshotRoot, {
263
+ noOpen: Boolean(args["no-open"]),
264
+ browser: args.browser
265
+ });
266
+ process.title = dashboardProcessTitle(snapshotRoot);
267
+ const code = await runStartScript(startPath, env);
268
+ if (code !== 0) process.exitCode = code;
269
+ }
270
+ });
271
+
272
+ export {
273
+ REPO_ROOT_ENV,
274
+ readPreferredBrowserFromWorkspace,
275
+ applyDashboardOpenEnv,
276
+ dashboardSpawnEnv,
277
+ dashboardProcessTitle,
278
+ bundledDashboardCandidates,
279
+ findDashboardStart,
280
+ resolveDashboardSnapshotRoot,
281
+ dashboardCommand
282
+ };