@dadado/agent-kit-cli 5.15.0 → 5.16.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 (71) 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-7UMSGVWC.js +150 -0
  11. package/dist/chunk-2IPXQFJE.js +253 -0
  12. package/dist/chunk-2YU2H7I6.js +42 -0
  13. package/dist/chunk-4IWG5CVE.js +214 -0
  14. package/dist/chunk-5OXPNVCF.js +106 -0
  15. package/dist/chunk-5VCOWTTP.js +6 -0
  16. package/dist/chunk-6QV2BFGA.js +341 -0
  17. package/dist/chunk-77V75A7O.js +39 -0
  18. package/dist/chunk-7HDQEK6T.js +26 -0
  19. package/dist/chunk-CBCW5R5J.js +484 -0
  20. package/dist/chunk-CGTE7DHN.js +410 -0
  21. package/dist/chunk-DE3LQFSJ.js +18 -0
  22. package/dist/chunk-F6C3GFKV.js +57 -0
  23. package/dist/chunk-HFUH3HVS.js +282 -0
  24. package/dist/chunk-HS7LVVLO.js +74 -0
  25. package/dist/chunk-IHROBTNM.js +135 -0
  26. package/dist/chunk-KD2MYBB6.js +213 -0
  27. package/dist/chunk-KI6ZHBPV.js +332 -0
  28. package/dist/chunk-MAPOQEA2.js +43 -0
  29. package/dist/chunk-NQ7IQ5XT.js +26 -0
  30. package/dist/chunk-OUQZC744.js +1315 -0
  31. package/dist/chunk-OZ4QV6LM.js +1078 -0
  32. package/dist/chunk-PCCQUWE5.js +817 -0
  33. package/dist/chunk-PNFF6OU3.js +273 -0
  34. package/dist/chunk-QTON2GQX.js +203 -0
  35. package/dist/chunk-TFZ2JUXH.js +1081 -0
  36. package/dist/chunk-VGZBARQC.js +318 -0
  37. package/dist/chunk-VS2JD2XC.js +1453 -0
  38. package/dist/chunk-VY7MTPJ2.js +205 -0
  39. package/dist/chunk-WW3YZKHW.js +165 -0
  40. package/dist/chunk-XWPO3VYQ.js +53 -0
  41. package/dist/chunk-XZZAVHH4.js +503 -0
  42. package/dist/chunk-YGBBHKBE.js +82 -0
  43. package/dist/chunk-ZB43YMHW.js +20 -0
  44. package/dist/chunk-ZCTSUQRR.js +874 -0
  45. package/dist/chunk-ZXKZBTXE.js +17 -0
  46. package/dist/contribute-YKK6YCSI.js +367 -0
  47. package/dist/cursor-awareness-KTL2RGKD.js +77 -0
  48. package/dist/dashboard-YSNUGBFY.js +21 -0
  49. package/dist/dashboard-broadcast-M4CMW7RX.js +119 -0
  50. package/dist/diff-YTR7VOAN.js +74 -0
  51. package/dist/doctor-TA2FWREU.js +497 -0
  52. package/dist/dogfood-KKFVRWT6.js +366 -0
  53. package/dist/guard-EHXF76ID.js +530 -0
  54. package/dist/handoff-3UI6VKVG.js +302 -0
  55. package/dist/hook-MUV6Y7UW.js +1074 -0
  56. package/dist/index.js +62 -14068
  57. package/dist/init-TTTKGC4C.js +127 -0
  58. package/dist/install-ZMFJBPOI.js +34 -0
  59. package/dist/mission-control-LJACEVVN.js +89 -0
  60. package/dist/monitors-QV2IS3MP.js +176 -0
  61. package/dist/plan-index-JMC7Z4DW.js +44 -0
  62. package/dist/public-inbound-radar-WP3XF7GS.js +68 -0
  63. package/dist/remote-issues-SZ4HDQL5.js +44 -0
  64. package/dist/run-I3BFGULT.js +243 -0
  65. package/dist/run-plan-FO7CL4M3.js +118 -0
  66. package/dist/scan-NI2SWJ2E.js +33 -0
  67. package/dist/setup-global-JUVMJXJ5.js +340 -0
  68. package/dist/status-MVCUNCWX.js +138 -0
  69. package/dist/update-Q4EBIRUB.js +260 -0
  70. package/dist/validate-V2CW7J4B.js +201 -0
  71. package/package.json +1 -1
@@ -0,0 +1,150 @@
1
+ import {
2
+ formatSkippedSymlinks,
3
+ logApplyStats
4
+ } from "./chunk-HS7LVVLO.js";
5
+ import {
6
+ REGISTRY_CLI_ARGS,
7
+ resolveRegistryFromCli
8
+ } from "./chunk-XWPO3VYQ.js";
9
+ import {
10
+ logger
11
+ } from "./chunk-ZB43YMHW.js";
12
+ import {
13
+ allPacks,
14
+ allSkills,
15
+ buildManifest,
16
+ findPack,
17
+ findSkill,
18
+ installPack,
19
+ installSkill,
20
+ loadAgentKitManifest,
21
+ loadRegistry,
22
+ resolveProtectedGlobs,
23
+ saveManifest,
24
+ upsertIdList
25
+ } from "./chunk-VS2JD2XC.js";
26
+ import "./chunk-5VCOWTTP.js";
27
+ import {
28
+ KIT_VERSION
29
+ } from "./chunk-7HDQEK6T.js";
30
+ import "./chunk-77V75A7O.js";
31
+
32
+ // src/commands/add.ts
33
+ import { defineCommand } from "citty";
34
+ var addCommand = defineCommand({
35
+ meta: {
36
+ name: "add",
37
+ description: "Install a skill or L1 pack from the registry."
38
+ },
39
+ args: {
40
+ id: {
41
+ type: "positional",
42
+ description: "Skill id or pack id from registry"
43
+ },
44
+ skill: {
45
+ type: "boolean",
46
+ description: "Force treat id as a skill (when pack shares the same id)",
47
+ default: false
48
+ },
49
+ pack: {
50
+ type: "boolean",
51
+ description: "Force treat id as a pack (when skill shares the same id)",
52
+ default: false
53
+ },
54
+ cwd: {
55
+ type: "string",
56
+ default: process.cwd()
57
+ },
58
+ ...REGISTRY_CLI_ARGS
59
+ },
60
+ async run({ args }) {
61
+ if (!args.id) throw new Error("Provide an id: agent-kit add <skill|pack>");
62
+ if (args.skill && args.pack) {
63
+ throw new Error("Use only one of --skill or --pack");
64
+ }
65
+ const existing = await loadAgentKitManifest(args.cwd, { onWarning: logger.warn });
66
+ const registry = await resolveRegistryFromCli({
67
+ cwd: args.cwd,
68
+ registry: args.registry,
69
+ url: args.url,
70
+ ref: args.ref,
71
+ refresh: args.refresh,
72
+ manifest: existing
73
+ });
74
+ try {
75
+ const protectedGlobs = resolveProtectedGlobs(existing);
76
+ const index = await loadRegistry(registry.root);
77
+ const asSkill = allSkills(index).find((s) => s.id === args.id);
78
+ const asPack = allPacks(index).find((p) => p.id === args.id);
79
+ const base = existing ? { ...existing } : buildManifest({
80
+ version: KIT_VERSION,
81
+ profile: "default",
82
+ registryUrl: registry.url,
83
+ registryRef: registry.ref
84
+ });
85
+ const skipped = (stats) => {
86
+ if (stats.skippedSymlink.length === 0) return false;
87
+ logger.error(formatSkippedSymlinks(stats.skippedSymlink));
88
+ process.exitCode = 1;
89
+ return true;
90
+ };
91
+ const finish = async (kind, id) => {
92
+ if (kind === "skill") base.skills = upsertIdList(base.skills, id);
93
+ else base.packs = upsertIdList(base.packs, id);
94
+ if (registry.url || registry.ref) {
95
+ base.registry = {
96
+ url: registry.url ?? base.registry?.url,
97
+ ref: registry.ref ?? base.registry?.ref
98
+ };
99
+ }
100
+ base.version = KIT_VERSION;
101
+ await saveManifest(args.cwd, base);
102
+ };
103
+ if (args.skill) {
104
+ const skill = await findSkill(registry.root, args.id);
105
+ const stats = await installSkill(registry.root, args.cwd, skill, { protectedGlobs });
106
+ logApplyStats(stats);
107
+ if (skipped(stats)) return;
108
+ await finish("skill", skill.id);
109
+ logger.success(`Skill '${skill.id}' added.`);
110
+ return;
111
+ }
112
+ if (args.pack) {
113
+ const pack = await findPack(registry.root, args.id);
114
+ const stats = await installPack(registry.root, args.cwd, pack.id, { protectedGlobs });
115
+ logApplyStats(stats);
116
+ if (skipped(stats)) return;
117
+ await finish("pack", pack.id);
118
+ logger.success(`Pack '${pack.id}' added.`);
119
+ return;
120
+ }
121
+ if (asSkill && asPack) {
122
+ throw new Error(
123
+ `'${args.id}' is both a skill and a pack. Disambiguate: agent-kit add --skill ${args.id} | agent-kit add --pack ${args.id}`
124
+ );
125
+ }
126
+ if (asSkill) {
127
+ const stats = await installSkill(registry.root, args.cwd, asSkill, { protectedGlobs });
128
+ logApplyStats(stats);
129
+ if (skipped(stats)) return;
130
+ await finish("skill", asSkill.id);
131
+ logger.success(`Skill '${asSkill.id}' added.`);
132
+ return;
133
+ }
134
+ if (asPack) {
135
+ const stats = await installPack(registry.root, args.cwd, asPack.id, { protectedGlobs });
136
+ logApplyStats(stats);
137
+ if (skipped(stats)) return;
138
+ await finish("pack", asPack.id);
139
+ logger.success(`Pack '${asPack.id}' added.`);
140
+ return;
141
+ }
142
+ throw new Error(`'${args.id}' not found as skill or pack in registry.`);
143
+ } finally {
144
+ await registry.unlock?.();
145
+ }
146
+ }
147
+ });
148
+ export {
149
+ addCommand
150
+ };
@@ -0,0 +1,253 @@
1
+ import {
2
+ contextConfigPath,
3
+ intervalElapsed,
4
+ loadContextConfig
5
+ } from "./chunk-NQ7IQ5XT.js";
6
+ import {
7
+ fileExists,
8
+ writeJson
9
+ } from "./chunk-77V75A7O.js";
10
+
11
+ // src/lifecycle/public-inbound-radar.ts
12
+ import { execFile } from "child_process";
13
+ import { access } from "fs/promises";
14
+ import path from "path";
15
+ import { promisify } from "util";
16
+ var execFileAsync = promisify(execFile);
17
+ var PUBLIC_INBOUND_REPO = "agent-kit-startup/agent-kit";
18
+ var DEFAULT_PREFS = {
19
+ enabled: false,
20
+ intervalDays: 7,
21
+ lastCheckedAt: null
22
+ };
23
+ function emptySummary() {
24
+ return { issues: 0, humanPullRequests: 0, dependabotPullRequests: 0, total: 0 };
25
+ }
26
+ function summarize(items) {
27
+ const summary = emptySummary();
28
+ for (const item of items) {
29
+ summary.total += 1;
30
+ if (item.class === "issue") summary.issues += 1;
31
+ else if (item.class === "dependabot") summary.dependabotPullRequests += 1;
32
+ else summary.humanPullRequests += 1;
33
+ }
34
+ return summary;
35
+ }
36
+ function labelNames(labels) {
37
+ if (!Array.isArray(labels)) return [];
38
+ return labels.map((entry) => typeof entry === "string" ? entry : entry?.name).filter((name) => typeof name === "string" && name.length > 0);
39
+ }
40
+ function isDependabotAuthor(login) {
41
+ if (!login) return false;
42
+ const normalized = login.toLowerCase();
43
+ return normalized === "dependabot" || normalized === "dependabot[bot]" || normalized === "app/dependabot" || normalized.startsWith("dependabot") || normalized === "renovate" || normalized === "renovate[bot]";
44
+ }
45
+ function classifyInbound(kind, authorLogin) {
46
+ if (kind === "issue") return "issue";
47
+ return isDependabotAuthor(authorLogin) ? "dependabot" : "human-pr";
48
+ }
49
+ function readPublicInboundCheckPrefs(config) {
50
+ if (!config || typeof config !== "object" || Array.isArray(config)) {
51
+ return { ...DEFAULT_PREFS };
52
+ }
53
+ const raw = config.publicInboundCheck;
54
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
55
+ return { ...DEFAULT_PREFS };
56
+ }
57
+ const uc = raw;
58
+ const intervalDays = typeof uc.intervalDays === "number" && Number.isInteger(uc.intervalDays) && uc.intervalDays >= 1 ? uc.intervalDays : DEFAULT_PREFS.intervalDays;
59
+ return {
60
+ enabled: uc.enabled === true,
61
+ intervalDays,
62
+ lastCheckedAt: typeof uc.lastCheckedAt === "string" ? uc.lastCheckedAt : null
63
+ };
64
+ }
65
+ async function stampPublicInboundCheck(cwd, checkedAt) {
66
+ const existing = await loadContextConfig(cwd) ?? {};
67
+ const prev = existing.publicInboundCheck && typeof existing.publicInboundCheck === "object" ? { ...existing.publicInboundCheck } : {};
68
+ existing.publicInboundCheck = {
69
+ ...DEFAULT_PREFS,
70
+ ...prev,
71
+ lastCheckedAt: (checkedAt ?? /* @__PURE__ */ new Date()).toISOString()
72
+ };
73
+ await writeJson(contextConfigPath(cwd), existing);
74
+ }
75
+ function mapRows(kind, rows) {
76
+ const items = [];
77
+ for (const row of rows) {
78
+ const number = typeof row.number === "number" ? row.number : Number(row.number);
79
+ if (!Number.isFinite(number) || number <= 0) continue;
80
+ const author = row.author?.login?.trim() || "unknown";
81
+ const title = typeof row.title === "string" && row.title.trim() ? row.title.trim() : "(no title)";
82
+ const updatedAt = typeof row.updatedAt === "string" && row.updatedAt.trim() ? row.updatedAt.trim() : "";
83
+ const url = typeof row.url === "string" && row.url.trim() ? row.url.trim() : `https://github.com/${PUBLIC_INBOUND_REPO}/${kind === "issue" ? "issues" : "pull"}/${number}`;
84
+ items.push({
85
+ kind,
86
+ class: classifyInbound(kind, author),
87
+ number,
88
+ title,
89
+ author,
90
+ updatedAt,
91
+ url,
92
+ labels: labelNames(row.labels),
93
+ cite: `${PUBLIC_INBOUND_REPO}#${number}`
94
+ });
95
+ }
96
+ return items;
97
+ }
98
+ async function defaultRunGh(args, options) {
99
+ const { stdout } = await execFileAsync("gh", args, {
100
+ cwd: options?.cwd,
101
+ encoding: "utf8",
102
+ maxBuffer: 4 * 1024 * 1024,
103
+ timeout: 3e4
104
+ });
105
+ return stdout;
106
+ }
107
+ async function ghAvailable(runGh, cwd) {
108
+ try {
109
+ await runGh(["--version"], { cwd });
110
+ return true;
111
+ } catch {
112
+ return false;
113
+ }
114
+ }
115
+ async function detectFactoryCheckout(cwd) {
116
+ const root = path.resolve(cwd);
117
+ try {
118
+ const { stdout } = await execFileAsync("git", ["remote", "get-url", "origin"], {
119
+ cwd: root,
120
+ encoding: "utf8",
121
+ timeout: 5e3
122
+ });
123
+ if (/agent-kit-dev/i.test(stdout)) return true;
124
+ } catch {
125
+ }
126
+ return fileExists(path.join(root, "dogfood", "README.md"));
127
+ }
128
+ async function listJson(runGh, cwd, resource) {
129
+ const stdout = await runGh(
130
+ [
131
+ resource,
132
+ "list",
133
+ "--repo",
134
+ PUBLIC_INBOUND_REPO,
135
+ "--state",
136
+ "open",
137
+ "--limit",
138
+ "100",
139
+ "--json",
140
+ "number,title,author,updatedAt,labels,url"
141
+ ],
142
+ { cwd }
143
+ );
144
+ const trimmed = stdout.trim();
145
+ if (!trimmed) return [];
146
+ const parsed = JSON.parse(trimmed);
147
+ return Array.isArray(parsed) ? parsed : [];
148
+ }
149
+ function resultBase(status, message, factory, now, items = []) {
150
+ return {
151
+ status,
152
+ repo: PUBLIC_INBOUND_REPO,
153
+ checkedAt: now.toISOString(),
154
+ factory,
155
+ items,
156
+ summary: summarize(items),
157
+ message,
158
+ mutateRecommended: false
159
+ };
160
+ }
161
+ async function checkPublicInbound(cwd, options = {}) {
162
+ const now = options.now?.() ?? /* @__PURE__ */ new Date();
163
+ const runGh = options.runGh ?? defaultRunGh;
164
+ const isFactory = options.isFactory ?? detectFactoryCheckout;
165
+ const root = path.resolve(cwd);
166
+ try {
167
+ await access(root);
168
+ } catch {
169
+ return resultBase("error", `cwd unreadable: ${root}`, false, now);
170
+ }
171
+ const factory = await isFactory(root);
172
+ if (!factory) {
173
+ return resultBase(
174
+ "skipped-non-factory",
175
+ "Factory-only check. Aborting outside agent-kit-dev / dogfood checkout.",
176
+ false,
177
+ now
178
+ );
179
+ }
180
+ if (options.respectPrefs) {
181
+ const prefs = readPublicInboundCheckPrefs(await loadContextConfig(root));
182
+ if (!prefs.enabled) {
183
+ return resultBase(
184
+ "skipped-disabled",
185
+ "publicInboundCheck.enabled is false (opt-in). Set true in .cursor/context/config.json to nudge.",
186
+ true,
187
+ now
188
+ );
189
+ }
190
+ if (!intervalElapsed(prefs.lastCheckedAt, prefs.intervalDays)) {
191
+ return resultBase(
192
+ "skipped-interval",
193
+ `Within publicInboundCheck.intervalDays (${prefs.intervalDays}); last check ${prefs.lastCheckedAt}.`,
194
+ true,
195
+ now
196
+ );
197
+ }
198
+ }
199
+ if (options.offline) {
200
+ return resultBase("skipped-offline", "Offline flag set; skipped GitHub fetch.", true, now);
201
+ }
202
+ if (!await ghAvailable(runGh, root)) {
203
+ return resultBase(
204
+ "skipped-gh-missing",
205
+ "gh CLI missing or not runnable; fail-open skip.",
206
+ true,
207
+ now
208
+ );
209
+ }
210
+ try {
211
+ const [issueRows, prRows] = await Promise.all([
212
+ listJson(runGh, root, "issue"),
213
+ listJson(runGh, root, "pr")
214
+ ]);
215
+ const items = [...mapRows("issue", issueRows), ...mapRows("pull_request", prRows)].sort(
216
+ (a, b) => b.number - a.number
217
+ );
218
+ const summary = summarize(items);
219
+ const message = summary.total === 0 ? `No open issues or pull requests on ${PUBLIC_INBOUND_REPO}.` : `Open inbound on ${PUBLIC_INBOUND_REPO}: ${summary.issues} issue(s), ${summary.humanPullRequests} human PR(s), ${summary.dependabotPullRequests} Dependabot PR(s).`;
220
+ const result = resultBase("ok", message, true, now, items);
221
+ if (options.stamp) {
222
+ await stampPublicInboundCheck(root, now);
223
+ }
224
+ return result;
225
+ } catch (err) {
226
+ const detail = err instanceof Error ? err.message : String(err);
227
+ if (/ENOTFOUND|ECONNREFUSED|ETIMEDOUT|network|offline|Could not resolve/i.test(detail)) {
228
+ return resultBase(
229
+ "skipped-offline",
230
+ `GitHub unreachable; fail-open skip (${detail}).`,
231
+ true,
232
+ now
233
+ );
234
+ }
235
+ return resultBase("error", `Public inbound check failed: ${detail}`, true, now);
236
+ }
237
+ }
238
+ function shouldEmitPublicInboundNudge(result) {
239
+ if (!result || result.status !== "ok") return false;
240
+ if (result.mutateRecommended === true) return false;
241
+ const summary = result.summary;
242
+ if (!summary || typeof summary !== "object") return false;
243
+ const total = summary.total;
244
+ return typeof total === "number" && total > 0;
245
+ }
246
+
247
+ export {
248
+ PUBLIC_INBOUND_REPO,
249
+ stampPublicInboundCheck,
250
+ detectFactoryCheckout,
251
+ checkPublicInbound,
252
+ shouldEmitPublicInboundNudge
253
+ };
@@ -0,0 +1,42 @@
1
+ // src/invariants/handoff-schema.ts
2
+ var MACHINE_LIST_CHECKS = [
3
+ {
4
+ heading: "Backlog plans",
5
+ fieldLabels: ["Backlog plans", "Backlog"],
6
+ code: "heading-without-field-backlog"
7
+ },
8
+ {
9
+ heading: "Parked plans",
10
+ fieldLabels: ["Parked plans"],
11
+ code: "heading-without-field-parked"
12
+ },
13
+ {
14
+ heading: "Run queue",
15
+ fieldLabels: ["Run queue"],
16
+ code: "heading-without-field-run-queue"
17
+ }
18
+ ];
19
+ var CITE = "agent-kit validate handoff (see .cursor/context/templates/handoff.md)";
20
+ function validateHandoffText(text) {
21
+ if (!text.trim()) return [];
22
+ const warnings = [];
23
+ for (const check of MACHINE_LIST_CHECKS) {
24
+ const headingRe = new RegExp(`^##\\s+${check.heading}\\s*$`, "m");
25
+ if (!headingRe.test(text)) continue;
26
+ const hasField = check.fieldLabels.some(
27
+ (label) => new RegExp(`^- \\*\\*${label}:\\*\\*`, "m").test(text)
28
+ );
29
+ if (!hasField) {
30
+ warnings.push({
31
+ code: check.code,
32
+ message: `\`## ${check.heading}\` found without \`- **${check.fieldLabels[0]}:**\` (Mission Control will miss this list; rewrite as a field bullet).`,
33
+ cite: CITE
34
+ });
35
+ }
36
+ }
37
+ return warnings;
38
+ }
39
+
40
+ export {
41
+ validateHandoffText
42
+ };
@@ -0,0 +1,214 @@
1
+ // src/readiness/env-checks.ts
2
+ import { constants, access, readFile, realpath, stat } from "fs/promises";
3
+ import { homedir } from "os";
4
+ import path from "path";
5
+ var MIN_NODE_MAJOR = 20;
6
+ var CLI_PACKAGE_JSON_REL = path.join(
7
+ "lib",
8
+ "node_modules",
9
+ "@dadado",
10
+ "agent-kit-cli",
11
+ "package.json"
12
+ );
13
+ var DIST_INDEX_RE = /(?:\$\{basedir\}|\$basedir)?[^'"\s;]*dist\/index\.js/g;
14
+ function extractDistIndexFromBinScript(contents) {
15
+ const found = [];
16
+ DIST_INDEX_RE.lastIndex = 0;
17
+ for (const match of contents.matchAll(DIST_INDEX_RE)) {
18
+ const raw = match[0]?.trim();
19
+ if (raw) found.push(raw.replace(/\\/g, "/"));
20
+ }
21
+ return found;
22
+ }
23
+ function expandBinDirVars(raw, binDir) {
24
+ return raw.replace(/\$\{basedir\}/g, binDir).replace(/\$basedir/g, binDir);
25
+ }
26
+ var SHIM_MAX_BYTES = 4096;
27
+ function packageJsonFromDistIndex(distIndex) {
28
+ return path.join(path.dirname(distIndex), "..", "package.json");
29
+ }
30
+ function parsePackageVersion(contents) {
31
+ try {
32
+ const parsed = JSON.parse(contents);
33
+ return typeof parsed.version === "string" && parsed.version.length > 0 ? parsed.version : null;
34
+ } catch {
35
+ return null;
36
+ }
37
+ }
38
+ async function readBinVersion(binPath, options = {}) {
39
+ const readFileImpl = options.readFileImpl ?? ((filePath) => readFile(filePath, "utf8"));
40
+ const binDir = path.dirname(binPath);
41
+ const candidates = [];
42
+ let realBin = binPath;
43
+ try {
44
+ realBin = await realpath(binPath);
45
+ } catch {
46
+ }
47
+ if (path.basename(realBin) === "index.js" && path.basename(path.dirname(realBin)) === "dist") {
48
+ candidates.push(packageJsonFromDistIndex(realBin));
49
+ }
50
+ candidates.push(
51
+ path.join(binDir, "..", CLI_PACKAGE_JSON_REL),
52
+ path.join(binDir, "..", "node_modules", "@dadado", "agent-kit-cli", "package.json")
53
+ );
54
+ try {
55
+ if ((await stat(realBin)).size < SHIM_MAX_BYTES) {
56
+ const contents = await readFileImpl(binPath);
57
+ for (const raw of extractDistIndexFromBinScript(contents)) {
58
+ const distIndex = path.resolve(binDir, expandBinDirVars(raw, binDir));
59
+ candidates.push(packageJsonFromDistIndex(distIndex));
60
+ }
61
+ }
62
+ } catch {
63
+ }
64
+ const seen = /* @__PURE__ */ new Set();
65
+ for (const candidate of candidates) {
66
+ const resolved = path.resolve(candidate);
67
+ if (seen.has(resolved)) continue;
68
+ seen.add(resolved);
69
+ try {
70
+ const version = parsePackageVersion(await readFileImpl(resolved));
71
+ if (version) return version;
72
+ } catch {
73
+ }
74
+ }
75
+ return null;
76
+ }
77
+ async function resolveBinOnPath(binName, env, platform) {
78
+ const pathVar = env.PATH ?? env.Path ?? "";
79
+ if (!pathVar) return null;
80
+ const dirs = pathVar.split(path.delimiter).filter(Boolean);
81
+ const candidates = platform === "win32" ? [binName, `${binName}.cmd`, `${binName}.exe`, `${binName}.bat`] : [binName];
82
+ for (const dir of dirs) {
83
+ for (const candidate of candidates) {
84
+ const abs = path.join(dir, candidate);
85
+ try {
86
+ await access(abs, platform === "win32" ? void 0 : constants.X_OK);
87
+ return abs;
88
+ } catch {
89
+ }
90
+ }
91
+ }
92
+ return null;
93
+ }
94
+ function isNodeVersionOk(nodeVersion, minMajor = MIN_NODE_MAJOR) {
95
+ const match = /^v?(\d+)/.exec(nodeVersion);
96
+ if (!match) return false;
97
+ const major = Number(match[1]);
98
+ return Number.isFinite(major) && major >= minMajor;
99
+ }
100
+ function detectShellName(env, platform) {
101
+ if (platform === "win32") {
102
+ if (env.PSModulePath) return "powershell";
103
+ if (env.ComSpec) return "cmd";
104
+ return null;
105
+ }
106
+ const shellPath = env.SHELL;
107
+ if (!shellPath) return null;
108
+ const base = path.basename(shellPath).trim();
109
+ return base || null;
110
+ }
111
+ function detectShellProfile(env, platform, homeDir) {
112
+ const shellName = detectShellName(env, platform);
113
+ if (shellName === "zsh") return path.join(homeDir, ".zshrc");
114
+ if (shellName === "bash") return path.join(homeDir, ".bashrc");
115
+ return null;
116
+ }
117
+ function parseNpmrcPrefix(content, homeDir) {
118
+ const match = /^\s*prefix\s*=\s*(.+?)\s*$/m.exec(content);
119
+ const captured = match?.[1];
120
+ if (!captured) return null;
121
+ let value = captured.trim().replace(/^["']|["']$/g, "");
122
+ if (value.startsWith("~")) {
123
+ value = path.join(homeDir, value.slice(1));
124
+ }
125
+ return value || null;
126
+ }
127
+ function heuristicPrefixFromExecPath(execPath, platform) {
128
+ const p = platform === "win32" ? path.win32 : path.posix;
129
+ return platform === "win32" ? p.dirname(execPath) : p.dirname(p.dirname(execPath));
130
+ }
131
+ async function detectNpmPrefix(options = {}) {
132
+ const env = options.env ?? process.env;
133
+ const platform = options.platform ?? process.platform;
134
+ const homeDir = options.homeDir ?? homedir();
135
+ const execPath = options.execPath ?? process.execPath;
136
+ const readFileImpl = options.readFileImpl ?? ((filePath) => readFile(filePath, "utf8"));
137
+ const envPrefix = env.npm_config_prefix ?? env.NPM_CONFIG_PREFIX;
138
+ if (envPrefix?.trim()) {
139
+ return { prefix: envPrefix.trim(), source: "env" };
140
+ }
141
+ const userconfigPath = env.NPM_CONFIG_USERCONFIG ?? path.join(homeDir, ".npmrc");
142
+ try {
143
+ const content = await readFileImpl(userconfigPath);
144
+ const npmrcPrefix = parseNpmrcPrefix(content, homeDir);
145
+ if (npmrcPrefix) {
146
+ return { prefix: npmrcPrefix, source: "npmrc" };
147
+ }
148
+ } catch {
149
+ }
150
+ return { prefix: heuristicPrefixFromExecPath(execPath, platform), source: "heuristic" };
151
+ }
152
+ async function describeUnwritablePrefix(prefix) {
153
+ try {
154
+ const info = await stat(prefix);
155
+ const currentUid = typeof process.getuid === "function" ? process.getuid() : void 0;
156
+ if (process.platform !== "win32" && currentUid !== void 0 && info.uid === 0 && currentUid !== 0) {
157
+ return `root-owned prefix (${prefix}); the classic fresh-install PATH/EACCES blocker`;
158
+ }
159
+ } catch {
160
+ }
161
+ return `npm prefix is not writable: ${prefix}`;
162
+ }
163
+ async function checkNpmPrefixWritable(options = {}) {
164
+ let detected;
165
+ try {
166
+ detected = await detectNpmPrefix(options);
167
+ } catch {
168
+ return { prefix: null, writable: false, reason: "npm prefix could not be determined" };
169
+ }
170
+ try {
171
+ await access(detected.prefix, constants.W_OK);
172
+ return { prefix: detected.prefix, writable: true, source: detected.source };
173
+ } catch {
174
+ return {
175
+ prefix: detected.prefix,
176
+ writable: false,
177
+ source: detected.source,
178
+ reason: await describeUnwritablePrefix(detected.prefix)
179
+ };
180
+ }
181
+ }
182
+ async function assessEnvironment(options = {}) {
183
+ const env = options.env ?? process.env;
184
+ const platform = options.platform ?? process.platform;
185
+ const nodeVersion = options.nodeVersion ?? process.version;
186
+ const homeDir = options.homeDir ?? homedir();
187
+ const binName = options.binName ?? "agent-kit";
188
+ const [binPath, npmPrefix] = await Promise.all([
189
+ resolveBinOnPath(binName, env, platform).catch(() => null),
190
+ checkNpmPrefixWritable(options).catch(
191
+ () => ({
192
+ prefix: null,
193
+ writable: false,
194
+ reason: "npm prefix check failed unexpectedly"
195
+ })
196
+ )
197
+ ]);
198
+ const binVersion = binPath ? await readBinVersion(binPath, options).catch(() => null) : null;
199
+ return {
200
+ binOnPath: binPath != null,
201
+ binPath,
202
+ binVersion,
203
+ npmPrefixWritable: npmPrefix.writable,
204
+ npmPrefix,
205
+ nodeVersionOk: isNodeVersionOk(nodeVersion),
206
+ nodeVersion,
207
+ shell: detectShellName(env, platform),
208
+ shellProfile: detectShellProfile(env, platform, homeDir)
209
+ };
210
+ }
211
+
212
+ export {
213
+ assessEnvironment
214
+ };