@agentskit/cli 0.13.36 → 0.13.39

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/dist/bin.cjs CHANGED
@@ -21,6 +21,7 @@ var url = require('url');
21
21
  var runtime = require('@agentskit/runtime');
22
22
  var prompts = require('@inquirer/prompts');
23
23
  var kleur = require('kleur');
24
+ var module$1 = require('module');
24
25
  var chokidar = require('chokidar');
25
26
  var rag = require('@agentskit/rag');
26
27
  var agentSchema = require('@agentskit/core/agent-schema');
@@ -28,6 +29,7 @@ var yaml = require('yaml');
28
29
  var security = require('@agentskit/core/security');
29
30
  var core = require('@agentskit/core');
30
31
 
32
+ var _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null;
31
33
  function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
32
34
 
33
35
  var React2__default = /*#__PURE__*/_interopDefault(React2);
@@ -35,20 +37,30 @@ var path__default = /*#__PURE__*/_interopDefault(path);
35
37
  var kleur__default = /*#__PURE__*/_interopDefault(kleur);
36
38
  var chokidar__default = /*#__PURE__*/_interopDefault(chokidar);
37
39
 
38
- async function loadJsonConfig(path5) {
40
+ // package.json
41
+ var package_default = {
42
+ version: "0.13.39"};
43
+ async function loadJsonConfig(path6) {
39
44
  try {
40
- const raw = await promises.readFile(path5, "utf8");
45
+ const raw = await promises.readFile(path6, "utf8");
41
46
  return JSON.parse(raw);
42
- } catch {
43
- return void 0;
47
+ } catch (err) {
48
+ if (err.code === "ENOENT") return void 0;
49
+ throw new Error(`Failed to load JSON config ${path6}: ${err instanceof Error ? err.message : String(err)}`);
44
50
  }
45
51
  }
46
- async function loadTsConfig(path5) {
52
+ async function loadTsConfig(path6) {
53
+ try {
54
+ await promises.access(path6);
55
+ } catch (err) {
56
+ if (err.code === "ENOENT") return void 0;
57
+ throw err;
58
+ }
47
59
  try {
48
- const mod = await import(path5);
60
+ const mod = await import(`${path6}?agentskit_config=${Date.now()}`);
49
61
  return mod.default ?? mod;
50
- } catch {
51
- return void 0;
62
+ } catch (err) {
63
+ throw new Error(`Failed to load TypeScript config ${path6}: ${err instanceof Error ? err.message : String(err)}`);
52
64
  }
53
65
  }
54
66
  async function loadPackageJsonConfig(dir) {
@@ -59,29 +71,31 @@ async function loadPackageJsonConfig(dir) {
59
71
  return pkg.agentskit;
60
72
  }
61
73
  return void 0;
62
- } catch {
63
- return void 0;
74
+ } catch (err) {
75
+ if (err.code === "ENOENT") return void 0;
76
+ throw new Error(`Failed to load package.json config ${dir}: ${err instanceof Error ? err.message : String(err)}`);
64
77
  }
65
78
  }
66
79
  function mergeConfigs(base, override) {
67
80
  if (!base && !override) return void 0;
68
81
  if (!base) return override;
69
82
  if (!override) return base;
70
- return {
71
- ...base,
72
- ...override,
73
- tools: { ...base.tools, ...override.tools },
74
- defaults: { ...base.defaults, ...override.defaults },
75
- runtime: { ...base.runtime, ...override.runtime },
76
- observability: { ...base.observability, ...override.observability }
83
+ const merge = (left, right) => {
84
+ if (!left || typeof left !== "object" || Array.isArray(left)) return right;
85
+ if (!right || typeof right !== "object" || Array.isArray(right)) return right;
86
+ const out = { ...left };
87
+ for (const [key, value] of Object.entries(right)) {
88
+ out[key] = key in out ? merge(out[key], value) : value;
89
+ }
90
+ return out;
77
91
  };
92
+ return merge(base, override);
78
93
  }
79
94
  async function loadLocalConfig(cwd) {
80
95
  const tsConfig = await loadTsConfig(path.join(cwd, ".agentskit.config.ts"));
81
- if (tsConfig) return tsConfig;
82
96
  const jsonConfig = await loadJsonConfig(path.join(cwd, ".agentskit.config.json"));
83
- if (jsonConfig) return jsonConfig;
84
- return await loadPackageJsonConfig(cwd);
97
+ const packageConfig = await loadPackageJsonConfig(cwd);
98
+ return mergeConfigs(mergeConfigs(packageConfig, jsonConfig), tsConfig);
85
99
  }
86
100
  async function loadGlobalConfig(home) {
87
101
  const globalDir = path.join(os.homedir(), ".agentskit");
@@ -95,6 +109,21 @@ async function loadConfig(options) {
95
109
  const local = await loadLocalConfig(cwd);
96
110
  return mergeConfigs(global, local);
97
111
  }
112
+ var SECRET_KEY = /^(api[-_]?key|token|secret|password|authorization|private[-_]?key)$/i;
113
+ var SECRET_ENV_KEY = /(?:^|_)(?:api_?key|token|secret|password|authorization|private_?key)$/i;
114
+ function isSecretKey(key) {
115
+ return SECRET_KEY.test(key) || SECRET_ENV_KEY.test(key);
116
+ }
117
+ function redactConfig(value) {
118
+ if (Array.isArray(value)) return value.map(redactConfig);
119
+ if (!value || typeof value !== "object") return value;
120
+ return Object.fromEntries(
121
+ Object.entries(value).map(([key, child]) => [
122
+ key,
123
+ isSecretKey(key) ? "[REDACTED]" : redactConfig(child)
124
+ ])
125
+ );
126
+ }
98
127
  var providers = {
99
128
  openai: {
100
129
  label: "OpenAI",
@@ -166,7 +195,7 @@ function createDemoAdapter(provider, model) {
166
195
  ].join(" ");
167
196
  for (const chunk of reply.match(/.{1,18}/g) ?? []) {
168
197
  if (cancelled) return;
169
- await new Promise((resolve7) => setTimeout(resolve7, 35));
198
+ await new Promise((resolve8) => setTimeout(resolve8, 35));
170
199
  yield { type: "text", content: chunk };
171
200
  }
172
201
  yield { type: "done" };
@@ -226,7 +255,8 @@ function dirFor(cwd = process.cwd()) {
226
255
  return path.join(ROOT, cwdHash(cwd));
227
256
  }
228
257
  function ensureDir(dir) {
229
- if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
258
+ if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true, mode: 448 });
259
+ fs.chmodSync(dir, 448);
230
260
  }
231
261
  function generateSessionId() {
232
262
  const ts = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-").slice(0, 19);
@@ -241,17 +271,19 @@ function metaPath(id, cwd = process.cwd()) {
241
271
  return path.join(dirFor(cwd), `${id}${META_SUFFIX}`);
242
272
  }
243
273
  function readMeta(id, cwd = process.cwd()) {
244
- const path5 = metaPath(id, cwd);
245
- if (!fs.existsSync(path5)) return null;
274
+ const path6 = metaPath(id, cwd);
275
+ if (!fs.existsSync(path6)) return null;
246
276
  try {
247
- return JSON.parse(fs.readFileSync(path5, "utf8"));
277
+ return JSON.parse(fs.readFileSync(path6, "utf8"));
248
278
  } catch {
249
279
  return null;
250
280
  }
251
281
  }
252
282
  function writeSessionMeta(meta, cwd = process.cwd()) {
253
283
  ensureDir(dirFor(cwd));
254
- fs.writeFileSync(metaPath(meta.id, cwd), JSON.stringify(meta, null, 2));
284
+ const path6 = metaPath(meta.id, cwd);
285
+ fs.writeFileSync(path6, JSON.stringify(meta, null, 2), { mode: 384 });
286
+ fs.chmodSync(path6, 384);
255
287
  }
256
288
  function derivePreview(messages) {
257
289
  const firstUser = messages.find((m) => m.role === "user" && m.content.trim());
@@ -853,7 +885,7 @@ var HookDispatcher = class {
853
885
  result = await handler.run(current);
854
886
  } catch (err) {
855
887
  this.onError(handler, err);
856
- continue;
888
+ return { payload: current, blocked: true, reason: "hook failed" };
857
889
  }
858
890
  if (result.decision === "block") {
859
891
  return { payload: current, blocked: true, reason: result.reason };
@@ -870,6 +902,7 @@ var HookDispatcher = class {
870
902
  return handler.matcher.test(String(payload.tool ?? payload.prompt ?? ""));
871
903
  }
872
904
  };
905
+ var MAX_HOOK_STDOUT_BYTES = 64 * 1024;
873
906
  function configHooksToHandlers(config) {
874
907
  if (!config) return [];
875
908
  const handlers = [];
@@ -899,8 +932,26 @@ function runShellHook(entry, payload) {
899
932
  detached: true
900
933
  });
901
934
  let stdout = "";
935
+ let stdoutBytes = 0;
902
936
  child.stdout.on("data", (chunk) => {
903
- stdout += chunk.toString();
937
+ const text = chunk.toString();
938
+ const bytes = Buffer.byteLength(text);
939
+ if (stdoutBytes + bytes > MAX_HOOK_STDOUT_BYTES) {
940
+ if (child.pid !== void 0) {
941
+ try {
942
+ process.kill(-child.pid, "SIGKILL");
943
+ } catch {
944
+ }
945
+ }
946
+ try {
947
+ child.kill("SIGKILL");
948
+ } catch {
949
+ }
950
+ settle({ decision: "block", reason: `shell hook output exceeded ${MAX_HOOK_STDOUT_BYTES} bytes` });
951
+ return;
952
+ }
953
+ stdoutBytes += bytes;
954
+ stdout += text;
904
955
  });
905
956
  const timer = setTimeout(() => {
906
957
  if (child.pid !== void 0) {
@@ -932,9 +983,13 @@ function runShellHook(entry, payload) {
932
983
  }
933
984
  try {
934
985
  const parsed = JSON.parse(trimmed);
935
- settle(parsed);
986
+ if (!parsed || typeof parsed !== "object" || !("decision" in parsed) || !["continue", "block", "modify"].includes(String(parsed.decision))) {
987
+ settle({ decision: "block", reason: "shell hook returned an invalid decision" });
988
+ } else {
989
+ settle(parsed);
990
+ }
936
991
  } catch {
937
- settle({ decision: "continue" });
992
+ settle({ decision: "block", reason: "shell hook returned invalid JSON" });
938
993
  }
939
994
  });
940
995
  child.on("error", (err) => {
@@ -981,8 +1036,9 @@ function ChatApp(options) {
981
1036
  } = useRuntime(options);
982
1037
  const mergedTools = React2.useMemo(() => {
983
1038
  const extra = options.extraTools ?? [];
984
- return [...tools, ...extra];
985
- }, [tools, options.extraTools]);
1039
+ const combined = [...tools, ...extra];
1040
+ return options.permissionPolicy ? applyPolicyToTools(options.permissionPolicy, combined) : combined;
1041
+ }, [tools, options.extraTools, options.permissionPolicy]);
986
1042
  const mergedSkills = React2.useMemo(() => {
987
1043
  const extra = options.extraSkills ?? [];
988
1044
  if (!skills && extra.length === 0) return void 0;
@@ -1194,7 +1250,7 @@ async function loadPlugins(options = {}) {
1194
1250
  specs = [],
1195
1251
  pluginDirs = [],
1196
1252
  cwd = process.cwd(),
1197
- autoDiscoverUserDir = true,
1253
+ autoDiscoverUserDir = false,
1198
1254
  onError = (spec, err) => process.stderr.write(
1199
1255
  `[agentskit] plugin "${spec}" failed to load: ${err instanceof Error ? err.message : String(err)}
1200
1256
  `
@@ -1285,6 +1341,8 @@ function mergePluginsIntoBundle(plugins) {
1285
1341
  }
1286
1342
  return bundle;
1287
1343
  }
1344
+ var MAX_FRAME_BYTES = 1024 * 1024;
1345
+ var DISPOSE_GRACE_MS = 1e3;
1288
1346
  var McpClient = class {
1289
1347
  constructor(spec, onError = (err) => process.stderr.write(
1290
1348
  `[agentskit] mcp[${spec.name}] error: ${err instanceof Error ? err.message : String(err)}
@@ -1301,7 +1359,8 @@ var McpClient = class {
1301
1359
  if (this.child) return;
1302
1360
  const child = child_process.spawn(this.spec.command, this.spec.args ?? [], {
1303
1361
  env: { ...process.env, ...this.spec.env ?? {} },
1304
- stdio: ["pipe", "pipe", "pipe"]
1362
+ stdio: ["pipe", "pipe", "pipe"],
1363
+ detached: process.platform !== "win32"
1305
1364
  });
1306
1365
  this.child = child;
1307
1366
  child.stdout.on("data", (chunk) => this.onStdout(chunk.toString()));
@@ -1333,7 +1392,22 @@ var McpClient = class {
1333
1392
  dispose() {
1334
1393
  if (this.disposed) return;
1335
1394
  this.disposed = true;
1336
- this.child?.kill("SIGTERM");
1395
+ for (const pending of this.pending.values()) {
1396
+ pending({ jsonrpc: "2.0", id: 0, error: { code: -32800, message: "client disposed" } });
1397
+ }
1398
+ this.pending.clear();
1399
+ const child = this.child;
1400
+ if (!child || child.killed) return;
1401
+ const kill = (signal) => {
1402
+ try {
1403
+ if (process.platform !== "win32" && child.pid) process.kill(-child.pid, signal);
1404
+ else child.kill(signal);
1405
+ } catch {
1406
+ }
1407
+ };
1408
+ kill("SIGTERM");
1409
+ const timer = setTimeout(() => kill("SIGKILL"), DISPOSE_GRACE_MS);
1410
+ timer.unref();
1337
1411
  }
1338
1412
  request(method, params) {
1339
1413
  return new Promise((resolvePromise, rejectPromise) => {
@@ -1361,10 +1435,20 @@ var McpClient = class {
1361
1435
  }
1362
1436
  onStdout(chunk) {
1363
1437
  this.buffer += chunk;
1438
+ if (Buffer.byteLength(this.buffer, "utf8") > MAX_FRAME_BYTES && !this.buffer.includes("\n")) {
1439
+ this.onError(new Error(`mcp ${this.spec.name} frame exceeded ${MAX_FRAME_BYTES} bytes`));
1440
+ this.dispose();
1441
+ return;
1442
+ }
1364
1443
  let newlineIndex = this.buffer.indexOf("\n");
1365
1444
  while (newlineIndex !== -1) {
1366
1445
  const line = this.buffer.slice(0, newlineIndex).trim();
1367
1446
  this.buffer = this.buffer.slice(newlineIndex + 1);
1447
+ if (Buffer.byteLength(line, "utf8") > MAX_FRAME_BYTES) {
1448
+ this.onError(new Error(`mcp ${this.spec.name} frame exceeded ${MAX_FRAME_BYTES} bytes`));
1449
+ this.dispose();
1450
+ return;
1451
+ }
1368
1452
  if (line) {
1369
1453
  try {
1370
1454
  const parsed = JSON.parse(line);
@@ -1426,7 +1510,7 @@ function registerChatCommand(program) {
1426
1510
  "Extra directory to auto-discover plugin modules from (repeatable)",
1427
1511
  (value, prev = []) => [...prev, value],
1428
1512
  []
1429
- ).option(
1513
+ ).option("--discover-plugins", "Also auto-discover plugins from ~/.agentskit/plugins").option(
1430
1514
  "--mode <mode>",
1431
1515
  "Permission mode: default | plan | acceptEdits | bypassPermissions"
1432
1516
  ).action(async (options) => {
@@ -1463,7 +1547,8 @@ function registerChatCommand(program) {
1463
1547
  }
1464
1548
  const pluginBundle = await loadPlugins({
1465
1549
  specs: config?.plugins ?? [],
1466
- pluginDirs: options.pluginDir ?? []
1550
+ pluginDirs: options.pluginDir ?? [],
1551
+ autoDiscoverUserDir: Boolean(options.discoverPlugins)
1467
1552
  });
1468
1553
  const configHooks = configHooksToHandlers(config?.hooks);
1469
1554
  const hookHandlers = [...configHooks, ...pluginBundle.hooks];
@@ -1551,8 +1636,7 @@ function formatEvent(event) {
1551
1636
  }
1552
1637
  async function runAgent(task, options) {
1553
1638
  if (options.skill && options.skills) {
1554
- process.stderr.write("Error: --skill and --skills are mutually exclusive. Use one or the other.\n");
1555
- process.exit(1);
1639
+ throw new Error("--skill and --skills are mutually exclusive. Use one or the other.");
1556
1640
  }
1557
1641
  const { adapter } = resolveChatProvider({
1558
1642
  provider: options.provider,
@@ -1744,6 +1828,69 @@ var PROVIDER_ENV_KEY = {
1744
1828
  openrouter: "OPENROUTER_API_KEY",
1745
1829
  demo: null
1746
1830
  };
1831
+ var PACKAGE_RANGES = {
1832
+ "@agentskit/adapters": "^0.15.2",
1833
+ "@agentskit/angular": "^0.5.3",
1834
+ "@agentskit/ink": "^0.10.9",
1835
+ "@agentskit/memory": "^0.11.8",
1836
+ "@agentskit/react": "^0.8.3",
1837
+ "@agentskit/runtime": "^0.10.15",
1838
+ "@agentskit/skills": "^0.9.3",
1839
+ "@agentskit/svelte": "^0.5.3",
1840
+ "@agentskit/tools": "^0.13.6",
1841
+ "@agentskit/vue": "^0.5.3"
1842
+ };
1843
+ function align(files) {
1844
+ const result = { ...files };
1845
+ for (const file of ["package.json", "deno.json"]) {
1846
+ if (!files[file]) continue;
1847
+ const parsed = JSON.parse(files[file]);
1848
+ const sections = file === "package.json" ? ["dependencies", "devDependencies", "peerDependencies"] : ["imports"];
1849
+ for (const sectionName of sections) {
1850
+ const section = parsed[sectionName];
1851
+ if (!section || typeof section !== "object" || Array.isArray(section)) continue;
1852
+ for (const [name, range] of Object.entries(PACKAGE_RANGES)) if (name in section) {
1853
+ section[name] = file === "deno.json" ? `npm:${name}@${range}` : range;
1854
+ }
1855
+ }
1856
+ result[file] = `${JSON.stringify(parsed, null, 2)}
1857
+ `;
1858
+ }
1859
+ return result;
1860
+ }
1861
+ async function existing(filePath) {
1862
+ try {
1863
+ return await promises.lstat(filePath);
1864
+ } catch (error) {
1865
+ if (error.code === "ENOENT") return void 0;
1866
+ throw error;
1867
+ }
1868
+ }
1869
+ async function writeStarterProject(options, renderers) {
1870
+ const ctx = { template: options.template, provider: options.provider ?? "demo", tools: options.tools ?? [], memory: options.memory ?? "none", pm: options.packageManager ?? "pnpm" };
1871
+ const files = align(renderers[ctx.template](ctx));
1872
+ await promises.mkdir(options.targetDir, { recursive: true });
1873
+ const target = await promises.lstat(options.targetDir);
1874
+ if (target.isSymbolicLink() || !target.isDirectory()) throw new Error(`Refusing to write starter into non-directory target: ${options.targetDir}`);
1875
+ if ((await promises.readdir(options.targetDir)).length > 0 && !options.force) throw new Error(`Target directory is not empty: ${options.targetDir} (re-run with --force to overwrite generated files)`);
1876
+ const overwritten = [];
1877
+ for (const [relativePath, content] of Object.entries(files)) {
1878
+ const absolutePath = path__default.default.join(options.targetDir, relativePath);
1879
+ await promises.mkdir(path__default.default.dirname(absolutePath), { recursive: true });
1880
+ const destination = await existing(absolutePath);
1881
+ if (destination?.isSymbolicLink()) throw new Error(`Refusing to overwrite symlink: ${absolutePath}`);
1882
+ if (destination) overwritten.push(relativePath);
1883
+ const tempDir = await promises.mkdtemp(path__default.default.join(path__default.default.dirname(absolutePath), `.agentskit-init-${crypto.randomUUID()}-`));
1884
+ try {
1885
+ await promises.writeFile(path__default.default.join(tempDir, path__default.default.basename(absolutePath)), content, "utf8");
1886
+ await promises.rename(path__default.default.join(tempDir, path__default.default.basename(absolutePath)), absolutePath);
1887
+ } finally {
1888
+ await promises.rm(tempDir, { recursive: true, force: true }).catch(() => {
1889
+ });
1890
+ }
1891
+ }
1892
+ return overwritten;
1893
+ }
1747
1894
 
1748
1895
  // src/init.ts
1749
1896
  function adapterCall(provider, prefix = "process.env") {
@@ -3129,23 +3276,8 @@ var TEMPLATE_FN = {
3129
3276
  "deno-deploy": denoDeployStarter,
3130
3277
  angular: angularStarter
3131
3278
  };
3132
- async function writeStarterProject(options) {
3133
- const ctx = {
3134
- template: options.template,
3135
- provider: options.provider ?? "demo",
3136
- tools: options.tools ?? [],
3137
- memory: options.memory ?? "none",
3138
- pm: options.packageManager ?? "pnpm"
3139
- };
3140
- const files = TEMPLATE_FN[ctx.template](ctx);
3141
- await promises.mkdir(options.targetDir, { recursive: true });
3142
- await Promise.all(
3143
- Object.entries(files).map(async ([relativePath, content]) => {
3144
- const absolutePath = path__default.default.join(options.targetDir, relativePath);
3145
- await promises.mkdir(path__default.default.dirname(absolutePath), { recursive: true });
3146
- await promises.writeFile(absolutePath, content, "utf8");
3147
- })
3148
- );
3279
+ async function writeStarterProject2(options) {
3280
+ return writeStarterProject(options, TEMPLATE_FN);
3149
3281
  }
3150
3282
  async function runInteractiveInit(defaults = {}) {
3151
3283
  process.stdout.write(`
@@ -3294,7 +3426,7 @@ function printNextSteps(options) {
3294
3426
 
3295
3427
  // src/commands/init.ts
3296
3428
  function registerInitCommand(program) {
3297
- program.command("init").description("Generate a starter project. Run with no flags for interactive mode.").option("--template <template>", "Starter template (react|nextjs|sveltekit|nuxt|ink|vite-ink|cloudflare-workers|bun|deno-deploy|expo|angular|runtime|multi-agent)").option("--dir <directory>", "Target directory", "agentskit-app").option("--provider <provider>", "LLM provider (openai|anthropic|gemini|ollama|deepseek|grok|kimi|groq|openrouter|demo)").option("--tools <tools>", "Comma-separated tools (web_search,filesystem,shell)").option("--memory <backend>", "Memory backend (none|file|sqlite)").option("--pm <packageManager>", "Package manager (pnpm|npm|yarn|bun)").option("-y, --yes", "Skip interactive prompts; use flag values + defaults").action(async (rawOptions) => {
3429
+ program.command("init").description("Generate a starter project. Run with no flags for interactive mode.").option("--template <template>", "Starter template (react|nextjs|sveltekit|nuxt|ink|vite-ink|cloudflare-workers|bun|deno-deploy|expo|angular|runtime|multi-agent)").option("--dir <directory>", "Target directory", "agentskit-app").option("--provider <provider>", "LLM provider (openai|anthropic|gemini|ollama|deepseek|grok|kimi|groq|openrouter|demo)").option("--tools <tools>", "Comma-separated tools (web_search,filesystem,shell)").option("--memory <backend>", "Memory backend (none|file|sqlite)").option("--pm <packageManager>", "Package manager (pnpm|npm|yarn|bun)").option("--force", "Overwrite generated files in a non-empty target directory").option("-y, --yes", "Skip interactive prompts; use flag values + defaults").action(async (rawOptions) => {
3298
3430
  const isCi = !process.stdout.isTTY || rawOptions.yes || rawOptions.template;
3299
3431
  let resolved;
3300
3432
  if (isCi) {
@@ -3305,7 +3437,8 @@ function registerInitCommand(program) {
3305
3437
  provider: rawOptions.provider ?? "demo",
3306
3438
  tools: rawOptions.tools ? rawOptions.tools.split(",").map((t) => t.trim()) : [],
3307
3439
  memory: rawOptions.memory ?? "none",
3308
- packageManager: rawOptions.pm ?? "pnpm"
3440
+ packageManager: rawOptions.pm ?? "pnpm",
3441
+ force: Boolean(rawOptions.force)
3309
3442
  };
3310
3443
  } else {
3311
3444
  const result = await runInteractiveInit({
@@ -3315,17 +3448,22 @@ function registerInitCommand(program) {
3315
3448
  if (result.cancelled) {
3316
3449
  process.exit(0);
3317
3450
  }
3318
- resolved = result.options;
3451
+ resolved = { ...result.options, force: Boolean(rawOptions.force) };
3319
3452
  }
3320
- await writeStarterProject(resolved);
3453
+ const overwritten = await writeStarterProject2(resolved) ?? [];
3321
3454
  if (isCi) {
3322
3455
  process.stdout.write(
3323
3456
  `Created ${resolved.template} starter in ${path__default.default.relative(process.cwd(), resolved.targetDir) || "."}
3324
- \u2605 Star AgentsKit (solo-built \u2014 it helps a lot): https://github.com/AgentsKit-io/agentskit
3457
+ ` + (overwritten.length > 0 ? `Overwritten: ${overwritten.join(", ")}
3458
+ ` : "") + `\u2605 Star AgentsKit (solo-built \u2014 it helps a lot): https://github.com/AgentsKit-io/agentskit
3325
3459
  `
3326
3460
  );
3327
3461
  } else {
3328
3462
  printNextSteps(resolved);
3463
+ if (overwritten.length > 0) {
3464
+ process.stdout.write(`Overwritten: ${overwritten.join(", ")}
3465
+ `);
3466
+ }
3329
3467
  }
3330
3468
  });
3331
3469
  }
@@ -3574,8 +3712,8 @@ async function checkPnpm() {
3574
3712
  };
3575
3713
  }
3576
3714
  async function checkPackageJson() {
3577
- const path5 = path.join(process.cwd(), "package.json");
3578
- if (!fs.existsSync(path5)) {
3715
+ const path6 = path.join(process.cwd(), "package.json");
3716
+ if (!fs.existsSync(path6)) {
3579
3717
  return {
3580
3718
  status: "warn",
3581
3719
  name: "package.json",
@@ -3584,7 +3722,7 @@ async function checkPackageJson() {
3584
3722
  };
3585
3723
  }
3586
3724
  try {
3587
- const pkg = JSON.parse(await promises.readFile(path5, "utf8"));
3725
+ const pkg = JSON.parse(await promises.readFile(path6, "utf8"));
3588
3726
  const deps = {
3589
3727
  ...pkg.dependencies ?? {},
3590
3728
  ...pkg.devDependencies ?? {}
@@ -3817,6 +3955,14 @@ function registerDoctorCommand(program) {
3817
3955
  if (report.fail > 0) process.exit(1);
3818
3956
  });
3819
3957
  }
3958
+ var require2 = module$1.createRequire((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('bin.cjs', document.baseURI).href)));
3959
+ function resolveTsxCli() {
3960
+ try {
3961
+ return require2.resolve("tsx/cli");
3962
+ } catch {
3963
+ throw new Error("TypeScript execution requires the packaged tsx dependency");
3964
+ }
3965
+ }
3820
3966
  var DEFAULT_WATCH = [
3821
3967
  "**/*.ts",
3822
3968
  "**/*.tsx",
@@ -3844,8 +3990,8 @@ function startDev(options) {
3844
3990
  const stderr = options.stderr ?? process.stderr;
3845
3991
  const debounceMs = options.debounceMs ?? 200;
3846
3992
  const isTs = entry.endsWith(".ts") || entry.endsWith(".tsx");
3847
- const cmd = isTs ? "tsx" : "node";
3848
- const baseArgs = [entry, ...options.scriptArgs ?? []];
3993
+ const cmd = isTs ? process.execPath : "node";
3994
+ const baseArgs = isTs ? [resolveTsxCli(), entry, ...options.scriptArgs ?? []] : [entry, ...options.scriptArgs ?? []];
3849
3995
  const spawnFn = options.spawn ?? ((c, a) => child_process.spawn(c, a, {
3850
3996
  stdio: ["inherit", "pipe", "pipe"],
3851
3997
  env: { ...process.env, FORCE_COLOR: "1" }
@@ -3857,7 +4003,9 @@ function startDev(options) {
3857
4003
  let child;
3858
4004
  let restartCount = 0;
3859
4005
  let restartTimer;
4006
+ let delayedStartTimer;
3860
4007
  let stopped = false;
4008
+ let stdinListener;
3861
4009
  let resolveDone;
3862
4010
  const done = new Promise((r) => {
3863
4011
  resolveDone = r;
@@ -3883,15 +4031,18 @@ function startDev(options) {
3883
4031
  }
3884
4032
  });
3885
4033
  };
3886
- const restart = (path5) => {
4034
+ const restart = (path6) => {
3887
4035
  if (restartTimer) clearTimeout(restartTimer);
3888
4036
  restartTimer = setTimeout(() => {
3889
4037
  restartTimer = void 0;
3890
- banner(`\u21BB change detected \u2014 ${path5}`, "yellow");
4038
+ banner(`\u21BB change detected \u2014 ${path6}`, "yellow");
3891
4039
  if (child && !child.killed && child.exitCode === null) {
3892
4040
  child.kill("SIGTERM");
3893
4041
  }
3894
- setTimeout(startChild, 80);
4042
+ delayedStartTimer = setTimeout(() => {
4043
+ delayedStartTimer = void 0;
4044
+ if (!stopped) startChild();
4045
+ }, 80);
3895
4046
  }, debounceMs);
3896
4047
  };
3897
4048
  watcher.on("change", restart);
@@ -3902,6 +4053,9 @@ function startDev(options) {
3902
4053
  if (stopped) return;
3903
4054
  stopped = true;
3904
4055
  if (restartTimer) clearTimeout(restartTimer);
4056
+ if (delayedStartTimer) clearTimeout(delayedStartTimer);
4057
+ if (stdinListener) process.stdin.off("data", stdinListener);
4058
+ if (process.stdin.isTTY && process.stdin.setRawMode) process.stdin.setRawMode(false);
3905
4059
  if (child && !child.killed && child.exitCode === null) {
3906
4060
  child.kill("SIGTERM");
3907
4061
  }
@@ -3912,11 +4066,12 @@ function startDev(options) {
3912
4066
  if (process.stdin.isTTY && process.stdin.setRawMode) {
3913
4067
  process.stdin.setRawMode(true);
3914
4068
  process.stdin.resume();
3915
- process.stdin.on("data", (data) => {
4069
+ stdinListener = (data) => {
3916
4070
  const key = data.toString();
3917
4071
  if (key === "r") restart("manual");
3918
4072
  if (key === "q" || key === "") void stop();
3919
- });
4073
+ };
4074
+ process.stdin.on("data", stdinListener);
3920
4075
  }
3921
4076
  return {
3922
4077
  done,
@@ -3956,7 +4111,7 @@ function registerConfigCommand(program) {
3956
4111
  const targetPath = isLocal ? path__default.default.join(process.cwd(), ".agentskit.config.json") : path__default.default.join(os.homedir(), ".agentskit", "config.json");
3957
4112
  if (action === "show") {
3958
4113
  const config = await loadConfig();
3959
- process.stdout.write(JSON.stringify(config ?? {}, null, 2) + "\n");
4114
+ process.stdout.write(JSON.stringify(redactConfig(config ?? {}), null, 2) + "\n");
3960
4115
  return;
3961
4116
  }
3962
4117
  if (action !== "init") {
@@ -4015,6 +4170,7 @@ async function startTunnel(options) {
4015
4170
  });
4016
4171
  let requests = 0;
4017
4172
  let stopped = false;
4173
+ let signalHandler;
4018
4174
  let resolveDone;
4019
4175
  const done = new Promise((r) => {
4020
4176
  resolveDone = r;
@@ -4024,12 +4180,14 @@ async function startTunnel(options) {
4024
4180
  });
4025
4181
  tunnel.on("close", () => {
4026
4182
  if (stopped) return;
4183
+ if (signalHandler) process.off("SIGINT", signalHandler);
4027
4184
  banner(`tunnel closed by remote`, "yellow");
4028
4185
  resolveDone();
4029
4186
  });
4030
4187
  tunnel.on("error", (...args) => {
4031
4188
  const err = args[0];
4032
4189
  banner(`error: ${err?.message ?? "unknown"}`, "red");
4190
+ resolveDone();
4033
4191
  });
4034
4192
  banner(`\u2713 ready`, "green");
4035
4193
  stdout.write("\n");
@@ -4045,14 +4203,16 @@ async function startTunnel(options) {
4045
4203
  const stop = async () => {
4046
4204
  if (stopped) return;
4047
4205
  stopped = true;
4206
+ if (signalHandler) process.off("SIGINT", signalHandler);
4048
4207
  tunnel.close();
4049
4208
  banner(`stopped \u2014 proxied ${requests} request${requests === 1 ? "" : "s"}`, "cyan");
4050
4209
  resolveDone();
4051
4210
  };
4052
4211
  if (process.stdin.isTTY) {
4053
- process.on("SIGINT", () => {
4212
+ signalHandler = () => {
4054
4213
  void stop();
4055
- });
4214
+ };
4215
+ process.on("SIGINT", signalHandler);
4056
4216
  }
4057
4217
  return {
4058
4218
  url: tunnel.url,
@@ -4147,10 +4307,10 @@ async function indexSources(rag, config, cwd) {
4147
4307
  }
4148
4308
  }
4149
4309
  const documents = await Promise.all(
4150
- absolutePaths.map(async (path5) => ({
4151
- id: path5,
4152
- content: await promises.readFile(path5, "utf8"),
4153
- source: path5
4310
+ absolutePaths.map(async (path6) => ({
4311
+ id: path6,
4312
+ content: await promises.readFile(path6, "utf8"),
4313
+ source: path6
4154
4314
  }))
4155
4315
  );
4156
4316
  if (documents.length > 0) await rag.ingest(documents);
@@ -4285,7 +4445,7 @@ function scaffoldAgent(schema) {
4285
4445
  return files;
4286
4446
  }
4287
4447
  async function writeScaffold(files, outDir, opts = {}) {
4288
- const { writeFile: writeFile5, mkdir: mkdir5, access } = await import('fs/promises');
4448
+ const { writeFile: writeFile5, mkdir: mkdir5, access: access2 } = await import('fs/promises');
4289
4449
  const { dirname: dirname5, join: join11 } = await import('path');
4290
4450
  const written = [];
4291
4451
  for (const f of files) {
@@ -4293,7 +4453,7 @@ async function writeScaffold(files, outDir, opts = {}) {
4293
4453
  await mkdir5(dirname5(full), { recursive: true });
4294
4454
  if (!opts.overwrite) {
4295
4455
  try {
4296
- await access(full);
4456
+ await access2(full);
4297
4457
  continue;
4298
4458
  } catch {
4299
4459
  }
@@ -4769,66 +4929,66 @@ After writing, summarise which files landed and what each one does.
4769
4929
  // src/rules.ts
4770
4930
  var CODEX_BLOCK_START = "<!-- agentskit-codex-profile:start -->";
4771
4931
  var CODEX_BLOCK_END = "<!-- agentskit-codex-profile:end -->";
4772
- async function ensureDir2(path5) {
4773
- await promises.mkdir(path.dirname(path5), { recursive: true });
4932
+ async function ensureDir2(path6) {
4933
+ await promises.mkdir(path.dirname(path6), { recursive: true });
4774
4934
  }
4775
4935
  async function writeIfChanged(absPath, contents, force) {
4776
- let existing;
4936
+ let existing2;
4777
4937
  try {
4778
- existing = await promises.readFile(absPath, "utf8");
4938
+ existing2 = await promises.readFile(absPath, "utf8");
4779
4939
  } catch {
4780
4940
  }
4781
- if (existing === contents) return "skipped";
4782
- if (existing !== void 0 && !force) {
4941
+ if (existing2 === contents) return "skipped";
4942
+ if (existing2 !== void 0 && !force) {
4783
4943
  return "skipped";
4784
4944
  }
4785
4945
  await ensureDir2(absPath);
4786
4946
  await promises.writeFile(absPath, contents, "utf8");
4787
- return existing === void 0 ? "wrote" : "updated";
4947
+ return existing2 === void 0 ? "wrote" : "updated";
4788
4948
  }
4789
4949
  async function writeCursor(rootDir, force) {
4790
- const path5 = path.join(rootDir, ".cursor", "rules", "agentskit.mdc");
4791
- const action = await writeIfChanged(path5, CURSOR_RULE, force);
4792
- return [{ path: path5, action }];
4950
+ const path6 = path.join(rootDir, ".cursor", "rules", "agentskit.mdc");
4951
+ const action = await writeIfChanged(path6, CURSOR_RULE, force);
4952
+ return [{ path: path6, action }];
4793
4953
  }
4794
4954
  async function writeWindsurf(rootDir, force) {
4795
- const path5 = path.join(rootDir, ".windsurfrules");
4796
- const action = await writeIfChanged(path5, WINDSURF_RULE, force);
4797
- return [{ path: path5, action }];
4955
+ const path6 = path.join(rootDir, ".windsurfrules");
4956
+ const action = await writeIfChanged(path6, WINDSURF_RULE, force);
4957
+ return [{ path: path6, action }];
4798
4958
  }
4799
4959
  async function writeCodex(rootDir, force) {
4800
- const path5 = path.join(rootDir, "AGENTS.md");
4801
- let existing = "";
4960
+ const path6 = path.join(rootDir, "AGENTS.md");
4961
+ let existing2 = "";
4802
4962
  try {
4803
- existing = await promises.readFile(path5, "utf8");
4963
+ existing2 = await promises.readFile(path6, "utf8");
4804
4964
  } catch {
4805
4965
  }
4806
- const startIdx = existing.indexOf(CODEX_BLOCK_START);
4807
- const endIdx = startIdx >= 0 ? existing.lastIndexOf(CODEX_BLOCK_END, existing.length) : -1;
4966
+ const startIdx = existing2.indexOf(CODEX_BLOCK_START);
4967
+ const endIdx = startIdx >= 0 ? existing2.lastIndexOf(CODEX_BLOCK_END, existing2.length) : -1;
4808
4968
  if (startIdx >= 0 && endIdx <= startIdx) {
4809
4969
  throw new core.ConfigError({
4810
4970
  code: core.ErrorCodes.AK_CONFIG_INVALID,
4811
- message: `${path5}: found agentskit-codex-profile:start sentinel but no matching :end after it \u2014 refusing to modify a half-edited block.`,
4971
+ message: `${path6}: found agentskit-codex-profile:start sentinel but no matching :end after it \u2014 refusing to modify a half-edited block.`,
4812
4972
  hint: "Restore the closing sentinel or remove the partial block, then re-run."
4813
4973
  });
4814
4974
  }
4815
4975
  let next;
4816
4976
  if (startIdx >= 0 && endIdx > startIdx) {
4817
- next = existing.slice(0, startIdx) + CODEX_PROFILE.trimEnd() + existing.slice(endIdx + CODEX_BLOCK_END.length);
4818
- } else if (existing) {
4819
- next = `${existing.replace(/\s+$/, "")}
4977
+ next = existing2.slice(0, startIdx) + CODEX_PROFILE.trimEnd() + existing2.slice(endIdx + CODEX_BLOCK_END.length);
4978
+ } else if (existing2) {
4979
+ next = `${existing2.replace(/\s+$/, "")}
4820
4980
 
4821
4981
  ${CODEX_PROFILE}`;
4822
4982
  } else {
4823
4983
  next = CODEX_PROFILE;
4824
4984
  }
4825
- if (next === existing) return [{ path: path5, action: "skipped" }];
4826
- if (existing && !force) {
4827
- return [{ path: path5, action: "skipped" }];
4985
+ if (next === existing2) return [{ path: path6, action: "skipped" }];
4986
+ if (existing2 && !force) {
4987
+ return [{ path: path6, action: "skipped" }];
4828
4988
  }
4829
- await ensureDir2(path5);
4830
- await promises.writeFile(path5, next, "utf8");
4831
- return [{ path: path5, action: existing ? "updated" : "wrote" }];
4989
+ await ensureDir2(path6);
4990
+ await promises.writeFile(path6, next, "utf8");
4991
+ return [{ path: path6, action: existing2 ? "updated" : "wrote" }];
4832
4992
  }
4833
4993
  async function writeClaudeCode(rootDir, force) {
4834
4994
  const out = [];
@@ -4916,17 +5076,78 @@ rules failed: ${message}
4916
5076
  }
4917
5077
  var RAW_BASE = "https://raw.githubusercontent.com/AgentsKit-io/agentskit-registry/main";
4918
5078
  var HOSTED_BASE = `${RAW_BASE}/public/r`;
5079
+ var FETCH_TIMEOUT_MS = 15e3;
5080
+ var MAX_RESPONSE_BYTES = 2 * 1024 * 1024;
5081
+ var MAX_AGENT_FILES = 100;
5082
+ var SAFE_ID = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
5083
+ function assertSafeId(id) {
5084
+ if (!SAFE_ID.test(id)) throw new Error(`Invalid registry agent id: ${JSON.stringify(id)}`);
5085
+ }
5086
+ function assertSafeRelativePath(filePath) {
5087
+ if (!filePath || path.isAbsolute(filePath) || filePath.includes("\0")) {
5088
+ throw new Error(`Invalid registry file path: ${JSON.stringify(filePath)}`);
5089
+ }
5090
+ const normalized = filePath.replace(/\\/g, "/");
5091
+ if (normalized.split("/").some((part) => part === ".." || part === "")) {
5092
+ throw new Error(`Registry file path must stay relative: ${JSON.stringify(filePath)}`);
5093
+ }
5094
+ }
5095
+ function assertInside(parent, child) {
5096
+ const rel = path.relative(path.resolve(parent), path.resolve(child));
5097
+ if (rel === ".." || rel.startsWith(`..${path.sep}`) || path.isAbsolute(rel)) {
5098
+ throw new Error(`Registry destination escapes target directory: ${child}`);
5099
+ }
5100
+ }
5101
+ async function assertNoSymlinkAncestors(targetDir, baseDir) {
5102
+ for (const path6 of [targetDir, baseDir]) {
5103
+ try {
5104
+ if ((await promises.lstat(path.resolve(path6))).isSymbolicLink()) {
5105
+ throw new Error(`Registry destination contains a symlink ancestor: ${path.resolve(path6)}`);
5106
+ }
5107
+ } catch (error) {
5108
+ if (error.code !== "ENOENT") throw error;
5109
+ }
5110
+ }
5111
+ let current = path.resolve(baseDir);
5112
+ while (true) {
5113
+ try {
5114
+ if ((await promises.lstat(current)).isSymbolicLink()) {
5115
+ throw new Error(`Registry destination contains a symlink ancestor: ${current}`);
5116
+ }
5117
+ return;
5118
+ } catch (error) {
5119
+ if (error.code !== "ENOENT") throw error;
5120
+ }
5121
+ const parent = path.dirname(current);
5122
+ if (parent === current) return;
5123
+ current = parent;
5124
+ }
5125
+ }
5126
+ async function fetchWithLimits(url, fetchImpl) {
5127
+ const controller = new AbortController();
5128
+ const timer = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS);
5129
+ try {
5130
+ const response = await fetchImpl(url, { signal: controller.signal });
5131
+ if (!response.ok) throw new Error(`${response.status} ${response.statusText}`);
5132
+ const body = await response.arrayBuffer();
5133
+ if (body.byteLength > MAX_RESPONSE_BYTES) {
5134
+ throw new Error(`Registry response exceeded ${MAX_RESPONSE_BYTES} bytes`);
5135
+ }
5136
+ return new Response(body, { status: response.status, headers: response.headers });
5137
+ } finally {
5138
+ clearTimeout(timer);
5139
+ }
5140
+ }
4919
5141
  async function getJson(url, fetchImpl) {
4920
- const res = await fetchImpl(url);
4921
- if (!res.ok) throw new Error(`${res.status} ${res.statusText}`);
4922
- return res.json();
5142
+ const res = await fetchWithLimits(url, fetchImpl);
5143
+ return JSON.parse(await res.text());
4923
5144
  }
4924
5145
  async function getText(url, fetchImpl) {
4925
- const res = await fetchImpl(url);
4926
- if (!res.ok) throw new Error(`${res.status} ${res.statusText}`);
5146
+ const res = await fetchWithLimits(url, fetchImpl);
4927
5147
  return res.text();
4928
5148
  }
4929
5149
  async function fetchAgent(id, options = {}) {
5150
+ assertSafeId(id);
4930
5151
  const fetchImpl = options.fetchImpl ?? globalThis.fetch;
4931
5152
  try {
4932
5153
  const hosted = await getJson(`${HOSTED_BASE}/${id}.json`, fetchImpl);
@@ -4934,6 +5155,10 @@ async function fetchAgent(id, options = {}) {
4934
5155
  throw new Error("hosted entry missing sources");
4935
5156
  } catch {
4936
5157
  const meta = await getJson(`${RAW_BASE}/registry/${id}/meta.json`, fetchImpl);
5158
+ if (!Array.isArray(meta.files) || meta.files.length > MAX_AGENT_FILES) {
5159
+ throw new Error(`Registry agent contains too many files (maximum ${MAX_AGENT_FILES})`);
5160
+ }
5161
+ for (const rel of meta.files) assertSafeRelativePath(rel);
4937
5162
  const sources = await Promise.all(
4938
5163
  meta.files.map(async (rel) => ({
4939
5164
  path: rel,
@@ -4950,10 +5175,10 @@ function resolveSystemPrompt(agent) {
4950
5175
  const m = src.match(/systemPrompt:\s*`((?:\\.|[^`\\])*)`/);
4951
5176
  return m ? m[1].replace(/\\`/g, "`").replace(/\\\$\{/g, "${") : null;
4952
5177
  }
4953
- async function defaultExists(path5) {
4954
- const { access } = await import('fs/promises');
5178
+ async function defaultExists(path6) {
5179
+ const { access: access2 } = await import('fs/promises');
4955
5180
  try {
4956
- await access(path5);
5181
+ await access2(path6);
4957
5182
  return true;
4958
5183
  } catch {
4959
5184
  return false;
@@ -4977,17 +5202,24 @@ async function addAgent(id, options = {}) {
4977
5202
  assertInstallable(agent);
4978
5203
  const baseDir = options.outDir ?? "agents";
4979
5204
  const targetDir = path.join(baseDir, id);
5205
+ assertInside(baseDir, targetDir);
5206
+ await assertNoSymlinkAncestors(targetDir, baseDir);
4980
5207
  const exists = options.existsImpl ?? defaultExists;
4981
- const write = options.writeFileImpl ?? (async (path5, content) => {
4982
- await promises.mkdir(path.dirname(path5), { recursive: true });
4983
- await promises.writeFile(path5, content, "utf8");
5208
+ const write = options.writeFileImpl ?? (async (path6, content) => {
5209
+ await promises.mkdir(path.dirname(path6), { recursive: true });
5210
+ await promises.writeFile(path6, content, "utf8");
4984
5211
  });
4985
5212
  const written = [];
4986
5213
  for (const file of agent.sources) {
5214
+ assertSafeRelativePath(file.path);
4987
5215
  const dest = path.join(targetDir, file.path);
5216
+ assertInside(targetDir, dest);
4988
5217
  if (!options.force && await exists(dest)) {
4989
5218
  throw new Error(`${dest} already exists (re-run with --force to overwrite)`);
4990
5219
  }
5220
+ }
5221
+ for (const file of agent.sources) {
5222
+ const dest = path.join(targetDir, file.path);
4991
5223
  await write(dest, file.content);
4992
5224
  written.push(dest);
4993
5225
  }
@@ -5050,8 +5282,8 @@ function allDeps(pkg) {
5050
5282
  if (!pkg) return {};
5051
5283
  return { ...pkg.peerDependencies, ...pkg.devDependencies, ...pkg.dependencies };
5052
5284
  }
5053
- function readJson(fs, path5) {
5054
- const raw = fs.readFile(path5);
5285
+ function readJson(fs, path6) {
5286
+ const raw = fs.readFile(path6);
5055
5287
  if (raw == null) return null;
5056
5288
  try {
5057
5289
  return JSON.parse(raw);
@@ -5235,13 +5467,13 @@ function readConfig(io, root = ".") {
5235
5467
  return raw == null ? null : parseConfig(raw);
5236
5468
  }
5237
5469
  function writeConfig(io, config, root = ".") {
5238
- const path5 = path.join(root, CONFIG_PATH);
5239
- io.write(path5, serializeConfig(config));
5240
- return path5;
5470
+ const path6 = path.join(root, CONFIG_PATH);
5471
+ io.write(path6, serializeConfig(config));
5472
+ return path6;
5241
5473
  }
5242
5474
  function resolveConfig(io, scan, root = ".") {
5243
- const existing = readConfig(io, root);
5244
- if (existing) return { config: existing, fromDisk: true };
5475
+ const existing2 = readConfig(io, root);
5476
+ if (existing2) return { config: existing2, fromDisk: true };
5245
5477
  return { config: buildConfig(scan), fromDisk: false };
5246
5478
  }
5247
5479
  var IntegrityError = class extends Error {
@@ -5326,7 +5558,18 @@ function resolveAuthHeader(base, config, env = {}) {
5326
5558
  const map = config?.registryAuth;
5327
5559
  if (!map) return {};
5328
5560
  for (const [registryBase, envVar] of Object.entries(map)) {
5329
- if (base.startsWith(registryBase)) {
5561
+ let configured;
5562
+ let target;
5563
+ try {
5564
+ configured = new URL(registryBase);
5565
+ target = new URL(base);
5566
+ } catch {
5567
+ continue;
5568
+ }
5569
+ const configuredPath = configured.pathname.replace(/\/+$/, "") || "/";
5570
+ const targetPath = target.pathname.replace(/\/+$/, "") || "/";
5571
+ const samePathBoundary = configuredPath === "/" || targetPath === configuredPath || targetPath.startsWith(`${configuredPath}/`);
5572
+ if (target.origin === configured.origin && samePathBoundary) {
5330
5573
  const token = env[envVar];
5331
5574
  if (token) return { authorization: `Bearer ${token}` };
5332
5575
  }
@@ -5345,8 +5588,8 @@ function verifyChecksums(files) {
5345
5588
  function manifestUrl(ref) {
5346
5589
  return `${ref.base}/r/${ref.itemId}.json`;
5347
5590
  }
5348
- function fileUrl(ref, path5) {
5349
- return `${ref.base}/r/${ref.itemId}/${path5}`;
5591
+ function fileUrl(ref, path6) {
5592
+ return `${ref.base}/r/${ref.itemId}/${path6}`;
5350
5593
  }
5351
5594
  async function getText2(fetchImpl, url, headers) {
5352
5595
  const res = await fetchImpl(url, { headers });
@@ -5683,44 +5926,44 @@ async function addComponent(options, deps) {
5683
5926
  }
5684
5927
  function nodeScanFs() {
5685
5928
  return {
5686
- readFile(path5) {
5929
+ readFile(path6) {
5687
5930
  try {
5688
- return fs.readFileSync(path5, "utf8");
5931
+ return fs.readFileSync(path6, "utf8");
5689
5932
  } catch {
5690
5933
  return null;
5691
5934
  }
5692
5935
  },
5693
- exists(path5) {
5694
- return fs.existsSync(path5);
5936
+ exists(path6) {
5937
+ return fs.existsSync(path6);
5695
5938
  }
5696
5939
  };
5697
5940
  }
5698
5941
  function nodeWriteFs() {
5699
5942
  return {
5700
- exists(path5) {
5701
- return fs.existsSync(path5);
5943
+ exists(path6) {
5944
+ return fs.existsSync(path6);
5702
5945
  },
5703
- write(path5, content) {
5704
- fs.mkdirSync(path.dirname(path5), { recursive: true });
5705
- fs.writeFileSync(path5, content, "utf8");
5946
+ write(path6, content) {
5947
+ fs.mkdirSync(path.dirname(path6), { recursive: true });
5948
+ fs.writeFileSync(path6, content, "utf8");
5706
5949
  },
5707
- remove(path5) {
5708
- fs.rmSync(path5, { force: true });
5950
+ remove(path6) {
5951
+ fs.rmSync(path6, { force: true });
5709
5952
  }
5710
5953
  };
5711
5954
  }
5712
5955
  function nodeConfigIo() {
5713
5956
  return {
5714
- read(path5) {
5957
+ read(path6) {
5715
5958
  try {
5716
- return fs.readFileSync(path5, "utf8");
5959
+ return fs.readFileSync(path6, "utf8");
5717
5960
  } catch {
5718
5961
  return null;
5719
5962
  }
5720
5963
  },
5721
- write(path5, content) {
5722
- fs.mkdirSync(path.dirname(path5), { recursive: true });
5723
- fs.writeFileSync(path5, content, "utf8");
5964
+ write(path6, content) {
5965
+ fs.mkdirSync(path.dirname(path6), { recursive: true });
5966
+ fs.writeFileSync(path6, content, "utf8");
5724
5967
  }
5725
5968
  };
5726
5969
  }
@@ -5940,7 +6183,7 @@ update failed: ${err instanceof Error ? err.message : String(err)}
5940
6183
  // src/commands/index.ts
5941
6184
  function createCli() {
5942
6185
  const program = new commander.Command();
5943
- program.name("agentskit").description("AgentsKit CLI for chat demos and project bootstrapping.");
6186
+ program.name("agentskit").description("AgentsKit CLI for chat demos and project bootstrapping.").version(package_default.version);
5944
6187
  registerChatCommand(program);
5945
6188
  registerRunCommand(program);
5946
6189
  registerInitCommand(program);