@agentskit/cli 0.11.3 → 0.13.1

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
@@ -14,6 +14,7 @@ var fs = require('fs');
14
14
  var tools = require('@agentskit/tools');
15
15
  var skills = require('@agentskit/skills');
16
16
  var memory = require('@agentskit/memory');
17
+ var integrations = require('@agentskit/integrations');
17
18
  var child_process = require('child_process');
18
19
  var jsxRuntime = require('react/jsx-runtime');
19
20
  var url = require('url');
@@ -24,6 +25,8 @@ var chokidar = require('chokidar');
24
25
  var rag = require('@agentskit/rag');
25
26
  var agentSchema = require('@agentskit/core/agent-schema');
26
27
  var yaml = require('yaml');
28
+ var security = require('@agentskit/core/security');
29
+ var core = require('@agentskit/core');
27
30
 
28
31
  function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
29
32
 
@@ -151,7 +154,7 @@ function createDemoAdapter(provider, model) {
151
154
  ].join(" ");
152
155
  for (const chunk of reply.match(/.{1,18}/g) ?? []) {
153
156
  if (cancelled) return;
154
- await new Promise((resolve4) => setTimeout(resolve4, 35));
157
+ await new Promise((resolve6) => setTimeout(resolve6, 35));
155
158
  yield { type: "text", content: chunk };
156
159
  }
157
160
  yield { type: "done" };
@@ -603,7 +606,7 @@ function instantiate(kind) {
603
606
  case "filesystem":
604
607
  return tools.filesystem({ basePath: process.cwd() });
605
608
  case "shell":
606
- return [tools.shell({ timeout: 3e4 })];
609
+ return [tools.shell({ timeout: 3e4, allowAny: true })];
607
610
  }
608
611
  }
609
612
  function gateTool(tool) {
@@ -623,9 +626,19 @@ function resolveTools(toolNames) {
623
626
  case "shell":
624
627
  tools.push(...instantiate(name));
625
628
  break;
626
- default:
627
- process.stderr.write(`Unknown tool: ${name}
629
+ default: {
630
+ if (integrations.getIntegration(name)) {
631
+ const { tools: projected, credentialFound, envVar } = integrations.integrationToolsFromEnv(name, process.env);
632
+ if (!credentialFound) {
633
+ process.stderr.write(`Integration "${name}" needs ${envVar} set in the environment.
628
634
  `);
635
+ }
636
+ tools.push(...projected);
637
+ } else {
638
+ process.stderr.write(`Unknown tool: ${name}
639
+ `);
640
+ }
641
+ }
629
642
  }
630
643
  }
631
644
  return tools;
@@ -856,20 +869,39 @@ function configHooksToHandlers(config) {
856
869
  function runShellHook(entry, payload) {
857
870
  return new Promise((resolvePromise) => {
858
871
  const timeoutMs = entry.timeout ?? 5e3;
872
+ let settled = false;
873
+ const settle = (result) => {
874
+ if (settled) return;
875
+ settled = true;
876
+ clearTimeout(timer);
877
+ resolvePromise(result);
878
+ };
859
879
  const child = child_process.spawn("sh", ["-c", entry.run], {
860
- stdio: ["pipe", "pipe", "inherit"]
880
+ stdio: ["pipe", "pipe", "inherit"],
881
+ detached: true
861
882
  });
862
883
  let stdout = "";
863
884
  child.stdout.on("data", (chunk) => {
864
885
  stdout += chunk.toString();
865
886
  });
866
887
  const timer = setTimeout(() => {
867
- child.kill("SIGTERM");
888
+ if (child.pid !== void 0) {
889
+ try {
890
+ process.kill(-child.pid, "SIGKILL");
891
+ } catch {
892
+ }
893
+ }
894
+ try {
895
+ child.kill("SIGKILL");
896
+ } catch {
897
+ }
898
+ settle({ decision: "block", reason: `shell hook timed out after ${timeoutMs}ms` });
868
899
  }, timeoutMs);
900
+ child.stdin.on("error", () => {
901
+ });
869
902
  child.on("close", (code) => {
870
- clearTimeout(timer);
871
903
  if (code !== 0) {
872
- resolvePromise({
904
+ settle({
873
905
  decision: "block",
874
906
  reason: `shell hook exited with code ${code}`
875
907
  });
@@ -877,19 +909,18 @@ function runShellHook(entry, payload) {
877
909
  }
878
910
  const trimmed = stdout.trim();
879
911
  if (!trimmed) {
880
- resolvePromise({ decision: "continue" });
912
+ settle({ decision: "continue" });
881
913
  return;
882
914
  }
883
915
  try {
884
916
  const parsed = JSON.parse(trimmed);
885
- resolvePromise(parsed);
917
+ settle(parsed);
886
918
  } catch {
887
- resolvePromise({ decision: "continue" });
919
+ settle({ decision: "continue" });
888
920
  }
889
921
  });
890
922
  child.on("error", (err) => {
891
- clearTimeout(timer);
892
- resolvePromise({ decision: "block", reason: err.message });
923
+ settle({ decision: "block", reason: err.message });
893
924
  });
894
925
  try {
895
926
  child.stdin.write(JSON.stringify(payload));
@@ -3679,13 +3710,6 @@ function registerConfigCommand(program) {
3679
3710
  `);
3680
3711
  process.exit(2);
3681
3712
  }
3682
- if (fs.existsSync(targetPath) && !options.force) {
3683
- process.stderr.write(
3684
- `Config already exists at ${targetPath}. Re-run with --force to overwrite.
3685
- `
3686
- );
3687
- process.exit(1);
3688
- }
3689
3713
  const template = {
3690
3714
  defaults: {
3691
3715
  provider: "openai",
@@ -3696,7 +3720,20 @@ function registerConfigCommand(program) {
3696
3720
  }
3697
3721
  };
3698
3722
  fs.mkdirSync(path__default.default.dirname(targetPath), { recursive: true });
3699
- fs.writeFileSync(targetPath, JSON.stringify(template, null, 2) + "\n");
3723
+ try {
3724
+ fs.writeFileSync(targetPath, JSON.stringify(template, null, 2) + "\n", {
3725
+ flag: options.force ? "w" : "wx"
3726
+ });
3727
+ } catch (err) {
3728
+ if (err.code === "EEXIST") {
3729
+ process.stderr.write(
3730
+ `Config already exists at ${targetPath}. Re-run with --force to overwrite.
3731
+ `
3732
+ );
3733
+ process.exit(1);
3734
+ }
3735
+ throw err;
3736
+ }
3700
3737
  process.stdout.write(
3701
3738
  `Wrote ${targetPath}
3702
3739
  Edit it to taste, then run:
@@ -3994,12 +4031,12 @@ function scaffoldAgent(schema) {
3994
4031
  return files;
3995
4032
  }
3996
4033
  async function writeScaffold(files, outDir, opts = {}) {
3997
- const { writeFile: writeFile2, mkdir: mkdir2, access } = await import('fs/promises');
3998
- const { dirname, join: join5 } = await import('path');
4034
+ const { writeFile: writeFile5, mkdir: mkdir5, access } = await import('fs/promises');
4035
+ const { dirname: dirname4, join: join8 } = await import('path');
3999
4036
  const written = [];
4000
4037
  for (const f of files) {
4001
- const full = join5(outDir, f.path);
4002
- await mkdir2(dirname(full), { recursive: true });
4038
+ const full = join8(outDir, f.path);
4039
+ await mkdir5(dirname4(full), { recursive: true });
4003
4040
  if (!opts.overwrite) {
4004
4041
  try {
4005
4042
  await access(full);
@@ -4007,7 +4044,7 @@ async function writeScaffold(files, outDir, opts = {}) {
4007
4044
  } catch {
4008
4045
  }
4009
4046
  }
4010
- await writeFile2(full, f.content, "utf8");
4047
+ await writeFile5(full, f.content, "utf8");
4011
4048
  written.push(f.path);
4012
4049
  }
4013
4050
  return written;
@@ -4160,6 +4197,724 @@ function registerFlowCommand(program) {
4160
4197
  }
4161
4198
  });
4162
4199
  }
4200
+ function lintTaxonomyFile(filePath) {
4201
+ const absolute = path.resolve(filePath);
4202
+ let raw;
4203
+ try {
4204
+ raw = fs.readFileSync(absolute, "utf8");
4205
+ } catch (err) {
4206
+ return {
4207
+ file: absolute,
4208
+ ruleCount: 0,
4209
+ result: {
4210
+ ok: false,
4211
+ issues: [{ index: -1, path: "", message: `cannot read file: ${err.message}` }]
4212
+ }
4213
+ };
4214
+ }
4215
+ let parsed;
4216
+ try {
4217
+ parsed = JSON.parse(raw);
4218
+ } catch (err) {
4219
+ return {
4220
+ file: absolute,
4221
+ ruleCount: 0,
4222
+ result: {
4223
+ ok: false,
4224
+ issues: [{ index: -1, path: "", message: `invalid JSON: ${err.message}` }]
4225
+ }
4226
+ };
4227
+ }
4228
+ const result = security.validatePIITaxonomy(parsed);
4229
+ const ruleCount = parsed && typeof parsed === "object" && Array.isArray(parsed.rules) ? parsed.rules.length : 0;
4230
+ return { file: absolute, result, ruleCount };
4231
+ }
4232
+ function renderLintReport(report, opts = {}) {
4233
+ const { color = false } = opts;
4234
+ const red = (s) => color ? `\x1B[31m${s}\x1B[0m` : s;
4235
+ const green = (s) => color ? `\x1B[32m${s}\x1B[0m` : s;
4236
+ const dim = (s) => color ? `\x1B[2m${s}\x1B[0m` : s;
4237
+ const lines = [];
4238
+ lines.push(dim(report.file));
4239
+ if (report.result.ok) {
4240
+ lines.push(green(`\u2713 valid \xB7 ${report.ruleCount} rule${report.ruleCount === 1 ? "" : "s"}`));
4241
+ } else {
4242
+ lines.push(red(`\u2717 ${report.result.issues.length} issue${report.result.issues.length === 1 ? "" : "s"}`));
4243
+ for (const issue of report.result.issues) {
4244
+ lines.push(` ${red("\u2022")} ${issue.path || "<root>"}: ${issue.message}`);
4245
+ }
4246
+ }
4247
+ return `${lines.join("\n")}
4248
+ `;
4249
+ }
4250
+
4251
+ // src/commands/pii.ts
4252
+ function registerPiiCommand(program) {
4253
+ const pii = program.command("pii").description("Inspect and validate PII taxonomies.");
4254
+ pii.command("lint <file...>").description("Validate one or more PII taxonomy JSON files.").option("--json", "Emit JSON instead of formatted output").action((files, options) => {
4255
+ const reports = files.map(lintTaxonomyFile);
4256
+ const failed = reports.filter((r) => !r.result.ok).length;
4257
+ if (options.json) {
4258
+ process.stdout.write(JSON.stringify(reports, null, 2) + "\n");
4259
+ } else {
4260
+ for (const report of reports) {
4261
+ process.stdout.write(renderLintReport(report, { color: process.stdout.isTTY }));
4262
+ }
4263
+ if (failed > 0) {
4264
+ process.stderr.write(`
4265
+ ${failed} of ${reports.length} taxonomy file${reports.length === 1 ? "" : "s"} failed validation
4266
+ `);
4267
+ }
4268
+ }
4269
+ if (failed > 0) process.exit(1);
4270
+ });
4271
+ }
4272
+
4273
+ // src/rules/cursor.ts
4274
+ var CURSOR_RULE = `---
4275
+ description: AgentsKit conventions \u2014 apply to every file in this workspace
4276
+ globs: ["**/*.ts", "**/*.tsx"]
4277
+ alwaysApply: true
4278
+ ---
4279
+
4280
+ # AgentsKit project rules
4281
+
4282
+ When writing or editing TypeScript in this workspace, follow these rules. They
4283
+ are non-negotiable and enforced by CI.
4284
+
4285
+ ## Imports
4286
+
4287
+ - Use **named exports only**. No \`export default\`.
4288
+ - Import from package roots: \`@agentskit/core\`, \`@agentskit/runtime\`, \`@agentskit/react\`,
4289
+ \`@agentskit/ink\`, \`@agentskit/adapters\`, \`@agentskit/tools\`, \`@agentskit/skills\`,
4290
+ \`@agentskit/memory\`, \`@agentskit/rag\`, \`@agentskit/observability\`, \`@agentskit/eval\`,
4291
+ \`@agentskit/sandbox\`, \`@agentskit/cli\`, \`@agentskit/templates\`.
4292
+ - For tools subpaths, use \`@agentskit/tools/integrations\`, \`@agentskit/tools/mcp\`,
4293
+ \`@agentskit/tools/mcp-devtools\`.
4294
+
4295
+ ## Types
4296
+
4297
+ - TypeScript strict mode is on. **Do not use \`any\`** \u2014 use \`unknown\` and narrow.
4298
+ - Headless React components: no hardcoded styles, use \`data-ak-*\` attributes.
4299
+
4300
+ ## Errors
4301
+
4302
+ - Never \`throw new Error(...)\` inside package source. Use the typed errors from
4303
+ \`@agentskit/core\`: \`AdapterError\`, \`ToolError\`, \`MemoryError\`, \`RuntimeError\`,
4304
+ \`SandboxError\`, \`SkillError\`, \`ConfigError\`. Pair with \`ErrorCodes.<...>\`.
4305
+
4306
+ ## Tests
4307
+
4308
+ - vitest. Place tests under \`packages/<pkg>/tests/\` mirroring \`src/\`.
4309
+ - E2E lives in \`apps/example-*\` with Playwright.
4310
+
4311
+ ## Versioning
4312
+
4313
+ - Every PR with a behavior change requires a Changeset (\`pnpm changeset\`).
4314
+
4315
+ ## Where to look first
4316
+
4317
+ - Architecture, contracts, ADRs: \`docs/architecture/adrs/\`
4318
+ - Per-package conventions: \`packages/<pkg>/CONVENTIONS.md\`
4319
+ - Agent-facing index: \`AGENTS.md\` and \`apps/docs-next/content/docs/for-agents/\`
4320
+ `;
4321
+
4322
+ // src/rules/windsurf.ts
4323
+ var WINDSURF_RULE = `# AgentsKit project rules (read first)
4324
+
4325
+ This workspace builds AgentsKit \u2014 a JavaScript agent toolkit with a 10 KB core,
4326
+ six formal contracts, and ~19 plug-and-play packages. When generating or
4327
+ editing code, follow these rules \u2014 they are enforced by CI.
4328
+
4329
+ ## Imports
4330
+ - **Named exports only.** No \`export default\`.
4331
+ - Import from package roots (\`@agentskit/core\`, \`@agentskit/runtime\`, \`@agentskit/react\`, etc.).
4332
+ - Tools subpaths: \`@agentskit/tools/integrations\`, \`@agentskit/tools/mcp\`, \`@agentskit/tools/mcp-devtools\`.
4333
+
4334
+ ## Types
4335
+ - Strict mode TypeScript. **No \`any\`** \u2014 use \`unknown\` and narrow.
4336
+
4337
+ ## Errors
4338
+ - **Do not** \`throw new Error(...)\` in package source. Use \`AdapterError\` /
4339
+ \`ToolError\` / \`MemoryError\` / \`RuntimeError\` / \`SandboxError\` / \`SkillError\` /
4340
+ \`ConfigError\` from \`@agentskit/core\`, paired with \`ErrorCodes\`.
4341
+
4342
+ ## Tests
4343
+ - vitest. Tests live in \`packages/<pkg>/tests/\` mirroring \`src/\`.
4344
+ - E2E lives in \`apps/example-*\` with Playwright.
4345
+
4346
+ ## Versioning
4347
+ - Every behavior change needs a Changeset (\`pnpm changeset\`).
4348
+
4349
+ ## Read first
4350
+ - \`AGENTS.md\` \u2014 universal agent guidance
4351
+ - \`apps/docs-next/content/docs/for-agents/\` \u2014 per-package agent docs
4352
+ - \`docs/architecture/adrs/\` \u2014 six core contracts (Adapter, Tool, Memory, Retriever, Skill, Runtime)
4353
+ - \`packages/<pkg>/CONVENTIONS.md\` \u2014 per-package contribution rules
4354
+ `;
4355
+
4356
+ // src/rules/codex.ts
4357
+ var CODEX_PROFILE = `<!-- agentskit-codex-profile:start -->
4358
+ ## Codex / Aider profile
4359
+
4360
+ Structured hints for CLI coding agents (OpenAI Codex, Aider, Claude Code,
4361
+ Cursor) running against this workspace. Update via \`agentskit rules codex\`.
4362
+
4363
+ \`\`\`yaml
4364
+ # AgentsKit Codex profile v1
4365
+ profile: agentskit
4366
+ runtime: node-25
4367
+ package_manager: pnpm
4368
+ test_runner: vitest
4369
+ build_runner: turborepo
4370
+
4371
+ allowed_commands:
4372
+ - pnpm install
4373
+ - pnpm build
4374
+ - pnpm test
4375
+ - pnpm lint
4376
+ - pnpm changeset
4377
+ - pnpm --filter @agentskit/* test
4378
+ - pnpm --filter @agentskit/* lint
4379
+ - pnpm --filter @agentskit/* build
4380
+
4381
+ restricted_paths:
4382
+ - packages/core/src/errors.ts # Touch carefully \u2014 every package depends on the typed errors here.
4383
+ - packages/core/src/security/ # Security-sensitive \u2014 small surface, requires review.
4384
+ - docs/architecture/adrs/ # Contract changes need a new ADR + major bump.
4385
+
4386
+ models_recommended:
4387
+ - claude-sonnet-4-6 # Default \u2014 long context, fast.
4388
+ - gpt-5 # Strong reasoning for refactors / cross-package work.
4389
+ - claude-opus-4-7 # Heavy refactors, contract redesigns.
4390
+
4391
+ invariants:
4392
+ core_max_kb_gzip: 10
4393
+ no_default_exports: true
4394
+ strict_typescript: true
4395
+ no_bare_throw_new_error: true
4396
+ named_exports_only: true
4397
+ changeset_required_for_behavior_changes: true
4398
+
4399
+ read_first:
4400
+ - AGENTS.md
4401
+ - apps/docs-next/content/docs/for-agents/
4402
+ - docs/architecture/adrs/
4403
+ \`\`\`
4404
+ <!-- agentskit-codex-profile:end -->
4405
+ `;
4406
+
4407
+ // src/rules/claude-code.ts
4408
+ var CLAUDE_CODE_SKILL = [
4409
+ {
4410
+ path: "SKILL.md",
4411
+ contents: `---
4412
+ name: agentskit
4413
+ description: Scaffold AgentsKit projects, add tools/skills, run doctor, and inspect the runtime \u2014 wraps the agentskit CLI.
4414
+ ---
4415
+
4416
+ # AgentsKit
4417
+
4418
+ Skill bundle for working with the AgentsKit toolkit from inside Claude Code.
4419
+
4420
+ Pair this skill with the project-scoped slash commands at \`.claude/commands/agentskit-*.md\`:
4421
+
4422
+ - \`/agentskit-new-agent\` \u2014 interactive scaffold (\`agentskit init\`)
4423
+ - \`/agentskit-doctor\` \u2014 diagnose the local environment (\`agentskit doctor\`)
4424
+ - \`/agentskit-add-tool\` \u2014 add a tool integration template
4425
+ - \`/agentskit-add-skill\` \u2014 add a skill template
4426
+ - \`/agentskit-lint-pii\` \u2014 validate a PII taxonomy file
4427
+ - \`/agentskit-rules\` \u2014 write \`.cursor/rules\` / \`.windsurfrules\` / Codex profile
4428
+
4429
+ When the user is inside an AgentsKit workspace, prefer the slash commands over
4430
+ hand-rolling shell invocations \u2014 they share the canonical defaults.
4431
+
4432
+ For convention reminders (named exports only, no bare throw, etc.), read
4433
+ \`AGENTS.md\` at the workspace root.
4434
+ `
4435
+ }
4436
+ ];
4437
+ var CLAUDE_CODE_SLASH_COMMANDS = [
4438
+ {
4439
+ path: "agentskit-new-agent.md",
4440
+ contents: `---
4441
+ description: Scaffold a new AgentsKit project (interactive)
4442
+ allowed-tools: Bash(npx agentskit init:*)
4443
+ ---
4444
+
4445
+ Run \`npx @agentskit/cli init\` interactively in the user's chosen directory.
4446
+ After scaffold completes, summarise the layout (templates created, next
4447
+ commands the user should run).
4448
+ `
4449
+ },
4450
+ {
4451
+ path: "agentskit-doctor.md",
4452
+ contents: `---
4453
+ description: Run the AgentsKit environment doctor and summarise findings
4454
+ allowed-tools: Bash(npx agentskit doctor:*)
4455
+ ---
4456
+
4457
+ Run \`npx @agentskit/cli doctor\` in the workspace root. Format the output
4458
+ into pass / warn / fail buckets and propose a fix for each fail / warn.
4459
+ `
4460
+ },
4461
+ {
4462
+ path: "agentskit-add-tool.md",
4463
+ contents: `---
4464
+ description: Add a tool template to the current package
4465
+ ---
4466
+
4467
+ Ask the user which tool to add. Use \`packages/templates/src/blueprints/tool.ts\`
4468
+ to scaffold the file under \`packages/<pkg>/src/tools/\`. Update the package's
4469
+ \`src/index.ts\` to re-export the new tool. Add a vitest mock test under
4470
+ \`packages/<pkg>/tests/\`.
4471
+ `
4472
+ },
4473
+ {
4474
+ path: "agentskit-add-skill.md",
4475
+ contents: `---
4476
+ description: Add a skill template to @agentskit/skills
4477
+ ---
4478
+
4479
+ Use \`packages/templates/src/blueprints/skill.ts\` to scaffold a new skill
4480
+ under \`packages/skills/src/\`. Re-export from the package index. Add a
4481
+ golden-dataset test fixture under \`packages/skills/tests/\` (10\u201350 input/
4482
+ expected examples, per the conventions).
4483
+ `
4484
+ },
4485
+ {
4486
+ path: "agentskit-lint-pii.md",
4487
+ contents: `---
4488
+ description: Validate a PII taxonomy JSON file
4489
+ allowed-tools: Bash(npx agentskit pii lint:*)
4490
+ ---
4491
+
4492
+ Ask the user for a path. Run \`npx @agentskit/cli pii lint <path>\` and
4493
+ display the report. If issues exist, suggest concrete fixes per the
4494
+ issue messages.
4495
+ `
4496
+ },
4497
+ {
4498
+ path: "agentskit-rules.md",
4499
+ contents: `---
4500
+ description: Write editor rule files (Cursor / Windsurf / Codex / Claude Code)
4501
+ allowed-tools: Bash(npx agentskit rules:*)
4502
+ ---
4503
+
4504
+ Ask which editor (or "all"). Run the matching:
4505
+ - \`npx @agentskit/cli rules cursor\`
4506
+ - \`npx @agentskit/cli rules windsurf\`
4507
+ - \`npx @agentskit/cli rules codex\`
4508
+ - \`npx @agentskit/cli rules claude-code\`
4509
+
4510
+ After writing, summarise which files landed and what each one does.
4511
+ `
4512
+ }
4513
+ ];
4514
+
4515
+ // src/rules.ts
4516
+ var CODEX_BLOCK_START = "<!-- agentskit-codex-profile:start -->";
4517
+ var CODEX_BLOCK_END = "<!-- agentskit-codex-profile:end -->";
4518
+ async function ensureDir2(path5) {
4519
+ await promises.mkdir(path.dirname(path5), { recursive: true });
4520
+ }
4521
+ async function writeIfChanged(absPath, contents, force) {
4522
+ let existing;
4523
+ try {
4524
+ existing = await promises.readFile(absPath, "utf8");
4525
+ } catch {
4526
+ }
4527
+ if (existing === contents) return "skipped";
4528
+ if (existing !== void 0 && !force) {
4529
+ return "skipped";
4530
+ }
4531
+ await ensureDir2(absPath);
4532
+ await promises.writeFile(absPath, contents, "utf8");
4533
+ return existing === void 0 ? "wrote" : "updated";
4534
+ }
4535
+ async function writeCursor(rootDir, force) {
4536
+ const path5 = path.join(rootDir, ".cursor", "rules", "agentskit.mdc");
4537
+ const action = await writeIfChanged(path5, CURSOR_RULE, force);
4538
+ return [{ path: path5, action }];
4539
+ }
4540
+ async function writeWindsurf(rootDir, force) {
4541
+ const path5 = path.join(rootDir, ".windsurfrules");
4542
+ const action = await writeIfChanged(path5, WINDSURF_RULE, force);
4543
+ return [{ path: path5, action }];
4544
+ }
4545
+ async function writeCodex(rootDir, force) {
4546
+ const path5 = path.join(rootDir, "AGENTS.md");
4547
+ let existing = "";
4548
+ try {
4549
+ existing = await promises.readFile(path5, "utf8");
4550
+ } catch {
4551
+ }
4552
+ const startIdx = existing.indexOf(CODEX_BLOCK_START);
4553
+ const endIdx = startIdx >= 0 ? existing.lastIndexOf(CODEX_BLOCK_END, existing.length) : -1;
4554
+ if (startIdx >= 0 && endIdx <= startIdx) {
4555
+ throw new core.ConfigError({
4556
+ code: core.ErrorCodes.AK_CONFIG_INVALID,
4557
+ message: `${path5}: found agentskit-codex-profile:start sentinel but no matching :end after it \u2014 refusing to modify a half-edited block.`,
4558
+ hint: "Restore the closing sentinel or remove the partial block, then re-run."
4559
+ });
4560
+ }
4561
+ let next;
4562
+ if (startIdx >= 0 && endIdx > startIdx) {
4563
+ next = existing.slice(0, startIdx) + CODEX_PROFILE.trimEnd() + existing.slice(endIdx + CODEX_BLOCK_END.length);
4564
+ } else if (existing) {
4565
+ next = `${existing.replace(/\s+$/, "")}
4566
+
4567
+ ${CODEX_PROFILE}`;
4568
+ } else {
4569
+ next = CODEX_PROFILE;
4570
+ }
4571
+ if (next === existing) return [{ path: path5, action: "skipped" }];
4572
+ if (existing && !force) {
4573
+ return [{ path: path5, action: "skipped" }];
4574
+ }
4575
+ await ensureDir2(path5);
4576
+ await promises.writeFile(path5, next, "utf8");
4577
+ return [{ path: path5, action: existing ? "updated" : "wrote" }];
4578
+ }
4579
+ async function writeClaudeCode(rootDir, force) {
4580
+ const out = [];
4581
+ const skillRoot = path.join(rootDir, ".claude", "skills", "agentskit");
4582
+ for (const file of CLAUDE_CODE_SKILL) {
4583
+ const abs = path.join(skillRoot, file.path);
4584
+ const action = await writeIfChanged(abs, file.contents, force);
4585
+ out.push({ path: abs, action });
4586
+ }
4587
+ const commandsRoot = path.join(rootDir, ".claude", "commands");
4588
+ for (const cmd of CLAUDE_CODE_SLASH_COMMANDS) {
4589
+ const abs = path.join(commandsRoot, cmd.path);
4590
+ const action = await writeIfChanged(abs, cmd.contents, force);
4591
+ out.push({ path: abs, action });
4592
+ }
4593
+ return out;
4594
+ }
4595
+ async function writeRules(editor, options = {}) {
4596
+ const root = path.resolve(options.rootDir ?? process.cwd());
4597
+ const force = options.force === true;
4598
+ if (editor === "all") {
4599
+ return [
4600
+ { editor: "cursor", files: await writeCursor(root, force) },
4601
+ { editor: "windsurf", files: await writeWindsurf(root, force) },
4602
+ { editor: "codex", files: await writeCodex(root, force) },
4603
+ { editor: "claude-code", files: await writeClaudeCode(root, force) }
4604
+ ];
4605
+ }
4606
+ switch (editor) {
4607
+ case "cursor":
4608
+ return [{ editor, files: await writeCursor(root, force) }];
4609
+ case "windsurf":
4610
+ return [{ editor, files: await writeWindsurf(root, force) }];
4611
+ case "codex":
4612
+ return [{ editor, files: await writeCodex(root, force) }];
4613
+ case "claude-code":
4614
+ return [{ editor, files: await writeClaudeCode(root, force) }];
4615
+ }
4616
+ }
4617
+
4618
+ // src/commands/rules.ts
4619
+ var VALID = ["cursor", "windsurf", "codex", "claude-code", "all"];
4620
+ function registerRulesCommand(program) {
4621
+ program.command("rules <editor>").description(
4622
+ "Write editor rule files (cursor | windsurf | codex | claude-code | all). Teaches the editor AgentsKit conventions so generated code respects named-export-only, package boundaries, and the for-agents/* manifest."
4623
+ ).option("--out <dir>", "Workspace root to write files into (default: cwd)").option("-f, --force", "Overwrite existing rule files (codex / claude-code skill always update in place)").action(async (editor, options) => {
4624
+ if (!VALID.includes(editor)) {
4625
+ process.stderr.write(`unknown editor "${editor}". Valid: ${VALID.join(", ")}
4626
+ `);
4627
+ process.exit(1);
4628
+ }
4629
+ try {
4630
+ const results = await writeRules(editor, {
4631
+ rootDir: options.out,
4632
+ force: options.force === true
4633
+ });
4634
+ const counts = { wrote: 0, updated: 0, skipped: 0 };
4635
+ for (const { editor: ed, files } of results) {
4636
+ process.stdout.write(`
4637
+ [${ed}]
4638
+ `);
4639
+ for (const f of files) {
4640
+ counts[f.action]++;
4641
+ process.stdout.write(` ${f.action.padEnd(7)} ${f.path}
4642
+ `);
4643
+ }
4644
+ }
4645
+ process.stdout.write(
4646
+ `
4647
+ Done \u2014 ${counts.wrote} wrote, ${counts.updated} updated, ${counts.skipped} skipped.
4648
+ `
4649
+ );
4650
+ if (counts.skipped > 0 && options.force !== true) {
4651
+ process.stdout.write(`(re-run with --force to overwrite skipped files)
4652
+ `);
4653
+ }
4654
+ } catch (err) {
4655
+ const message = err instanceof Error ? err.message : String(err);
4656
+ process.stderr.write(`
4657
+ rules failed: ${message}
4658
+ `);
4659
+ process.exit(1);
4660
+ }
4661
+ });
4662
+ }
4663
+ var RAW_BASE = "https://raw.githubusercontent.com/AgentsKit-io/agentskit-registry/main";
4664
+ var HOSTED_BASE = `${RAW_BASE}/public/r`;
4665
+ async function getJson(url, fetchImpl) {
4666
+ const res = await fetchImpl(url);
4667
+ if (!res.ok) throw new Error(`${res.status} ${res.statusText}`);
4668
+ return res.json();
4669
+ }
4670
+ async function getText(url, fetchImpl) {
4671
+ const res = await fetchImpl(url);
4672
+ if (!res.ok) throw new Error(`${res.status} ${res.statusText}`);
4673
+ return res.text();
4674
+ }
4675
+ async function fetchAgent(id, options = {}) {
4676
+ const fetchImpl = options.fetchImpl ?? globalThis.fetch;
4677
+ try {
4678
+ const hosted = await getJson(`${HOSTED_BASE}/${id}.json`, fetchImpl);
4679
+ if (hosted?.sources?.length) return hosted;
4680
+ throw new Error("hosted entry missing sources");
4681
+ } catch {
4682
+ const meta = await getJson(`${RAW_BASE}/registry/${id}/meta.json`, fetchImpl);
4683
+ const sources = await Promise.all(
4684
+ meta.files.map(async (rel) => ({
4685
+ path: rel,
4686
+ content: await getText(`${RAW_BASE}/registry/${id}/${rel}`, fetchImpl)
4687
+ }))
4688
+ );
4689
+ return { ...meta, sources };
4690
+ }
4691
+ }
4692
+ function resolveSystemPrompt(agent) {
4693
+ if (agent.skill?.systemPrompt) return agent.skill.systemPrompt;
4694
+ const src = agent.sources.find((f) => f.path === "agent.ts")?.content;
4695
+ if (!src) return null;
4696
+ const m = src.match(/systemPrompt:\s*`((?:\\.|[^`\\])*)`/);
4697
+ return m ? m[1].replace(/\\`/g, "`").replace(/\\\$\{/g, "${") : null;
4698
+ }
4699
+ async function defaultExists(path5) {
4700
+ const { access } = await import('fs/promises');
4701
+ try {
4702
+ await access(path5);
4703
+ return true;
4704
+ } catch {
4705
+ return false;
4706
+ }
4707
+ }
4708
+ async function addAgent(id, options = {}) {
4709
+ const agent = await fetchAgent(id, options);
4710
+ const baseDir = options.outDir ?? "agents";
4711
+ const targetDir = path.join(baseDir, id);
4712
+ const exists = options.existsImpl ?? defaultExists;
4713
+ const write = options.writeFileImpl ?? (async (path5, content) => {
4714
+ await promises.mkdir(path.dirname(path5), { recursive: true });
4715
+ await promises.writeFile(path5, content, "utf8");
4716
+ });
4717
+ const written = [];
4718
+ for (const file of agent.sources) {
4719
+ const dest = path.join(targetDir, file.path);
4720
+ if (!options.force && await exists(dest)) {
4721
+ throw new Error(`${dest} already exists (re-run with --force to overwrite)`);
4722
+ }
4723
+ await write(dest, file.content);
4724
+ written.push(dest);
4725
+ }
4726
+ return { agent, written, targetDir };
4727
+ }
4728
+ function lineDiff(a, b) {
4729
+ const x = a.split("\n");
4730
+ const y = b.split("\n");
4731
+ const m = x.length;
4732
+ const n = y.length;
4733
+ const lcs = Array.from({ length: m + 1 }, () => new Array(n + 1).fill(0));
4734
+ for (let i2 = m - 1; i2 >= 0; i2--) {
4735
+ for (let j2 = n - 1; j2 >= 0; j2--) {
4736
+ lcs[i2][j2] = x[i2] === y[j2] ? lcs[i2 + 1][j2 + 1] + 1 : Math.max(lcs[i2 + 1][j2], lcs[i2][j2 + 1]);
4737
+ }
4738
+ }
4739
+ const out = [];
4740
+ let i = 0;
4741
+ let j = 0;
4742
+ while (i < m && j < n) {
4743
+ if (x[i] === y[j]) {
4744
+ out.push({ type: " ", text: x[i] });
4745
+ i++;
4746
+ j++;
4747
+ } else if (lcs[i + 1][j] >= lcs[i][j + 1]) {
4748
+ out.push({ type: "-", text: x[i++] });
4749
+ } else {
4750
+ out.push({ type: "+", text: y[j++] });
4751
+ }
4752
+ }
4753
+ while (i < m) out.push({ type: "-", text: x[i++] });
4754
+ while (j < n) out.push({ type: "+", text: y[j++] });
4755
+ return out;
4756
+ }
4757
+ async function diffAgent(id, options = {}) {
4758
+ const agent = await fetchAgent(id, options);
4759
+ const targetDir = path.join(options.outDir ?? "agents", id);
4760
+ const readLocal = options.readFileImpl ?? (async (p) => {
4761
+ const { readFile: readFile6 } = await import('fs/promises');
4762
+ try {
4763
+ return await readFile6(p, "utf8");
4764
+ } catch {
4765
+ return null;
4766
+ }
4767
+ });
4768
+ const files = [];
4769
+ for (const f of agent.sources) {
4770
+ const local = await readLocal(path.join(targetDir, f.path));
4771
+ if (local == null) {
4772
+ files.push({ path: f.path, status: "missing-local", upstream: f.content });
4773
+ } else if (local === f.content) {
4774
+ files.push({ path: f.path, status: "unchanged", upstream: f.content });
4775
+ } else {
4776
+ files.push({ path: f.path, status: "modified", diff: lineDiff(local, f.content), upstream: f.content });
4777
+ }
4778
+ }
4779
+ return { agent, targetDir, files };
4780
+ }
4781
+
4782
+ // src/commands/add.ts
4783
+ function registerAddCommand(program) {
4784
+ program.command("add <agent>").description(
4785
+ 'Add a ready-made agent from the AgentsKit registry (registry.agentskit.io). Copies the agent source into your project \u2014 you own the code. With --run, also executes it. e.g. `agentskit add research` or `agentskit add legal-contract-reviewer --run "review this NDA\u2026" --provider ollama`.'
4786
+ ).option("--out <dir>", "Directory to write the agent into (default: ./agents)").option("-f, --force", "Overwrite existing files").option("--run <task>", "Run the agent on this task right after adding it").option("--provider <provider>", "Provider for --run (openai, anthropic, gemini, ollama, demo)", "demo").option("--model <model>", "Model for --run").option("--api-key <key>", "API key for --run (else read from the provider env var)").action(
4787
+ async (agent, options) => {
4788
+ try {
4789
+ const result = await addAgent(agent, { outDir: options.out, force: options.force === true });
4790
+ process.stdout.write(`
4791
+ Added "${result.agent.title}" \u2192 ${result.targetDir}/
4792
+ `);
4793
+ for (const f of result.written) process.stdout.write(` wrote ${f}
4794
+ `);
4795
+ if (result.agent.packages.length > 0) {
4796
+ process.stdout.write(`
4797
+ Install the packages it uses:
4798
+ `);
4799
+ process.stdout.write(` npm install ${result.agent.packages.join(" ")} @agentskit/adapters
4800
+ `);
4801
+ }
4802
+ const required = (result.agent.env ?? []).filter((e) => e.required);
4803
+ if (required.length > 0) {
4804
+ process.stdout.write(`
4805
+ Required environment:
4806
+ `);
4807
+ for (const e of required) process.stdout.write(` ${e.name} \u2014 ${e.description}
4808
+ `);
4809
+ }
4810
+ const readme2 = result.written.find((f) => f.toLowerCase().endsWith("readme.md"));
4811
+ process.stdout.write(readme2 ? `
4812
+ See ${readme2} for usage.
4813
+ ` : "\n");
4814
+ if (options.run) {
4815
+ const systemPrompt = resolveSystemPrompt(result.agent);
4816
+ if (!systemPrompt) {
4817
+ process.stderr.write(
4818
+ `
4819
+ --run is not supported for "${agent}" (it composes tools/keys). Use it as a library \u2014 see the README.
4820
+ `
4821
+ );
4822
+ process.exit(1);
4823
+ }
4824
+ process.stdout.write(`
4825
+ Running "${result.agent.title}" via ${options.provider}\u2026
4826
+
4827
+ `);
4828
+ await runAgent(options.run, {
4829
+ provider: options.provider,
4830
+ model: options.model,
4831
+ apiKey: options.apiKey,
4832
+ systemPrompt,
4833
+ maxSteps: "8"
4834
+ });
4835
+ }
4836
+ } catch (err) {
4837
+ const message = err instanceof Error ? err.message : String(err);
4838
+ process.stderr.write(`
4839
+ add failed: ${message}
4840
+ `);
4841
+ process.stderr.write(`(browse agents at https://registry.agentskit.io)
4842
+ `);
4843
+ process.exit(1);
4844
+ }
4845
+ }
4846
+ );
4847
+ }
4848
+ function registerDiffCommand(program) {
4849
+ program.command("diff <agent>").description("Show how your local copy of a registry agent differs from the current registry source.").option("--out <dir>", "Directory the agent was added into (default: ./agents)").action(async (agent, options) => {
4850
+ try {
4851
+ const { targetDir, files } = await diffAgent(agent, { outDir: options.out });
4852
+ let changed = 0;
4853
+ for (const f of files) {
4854
+ if (f.status === "unchanged") continue;
4855
+ changed++;
4856
+ process.stdout.write(`
4857
+ ${f.status === "missing-local" ? "missing" : "modified"}: ${path.join(targetDir, f.path)}
4858
+ `);
4859
+ if (f.diff) {
4860
+ for (const line of f.diff) {
4861
+ if (line.type === " ") continue;
4862
+ process.stdout.write(` ${line.type} ${line.text}
4863
+ `);
4864
+ }
4865
+ }
4866
+ }
4867
+ process.stdout.write(
4868
+ changed === 0 ? `
4869
+ ${agent}: up to date with the registry.
4870
+ ` : `
4871
+ ${changed} file(s) differ. Run \`agentskit update ${agent}\` to apply the registry version.
4872
+ `
4873
+ );
4874
+ } catch (err) {
4875
+ process.stderr.write(`
4876
+ diff failed: ${err instanceof Error ? err.message : String(err)}
4877
+ `);
4878
+ process.exit(1);
4879
+ }
4880
+ });
4881
+ }
4882
+ function registerUpdateCommand(program) {
4883
+ program.command("update <agent>").description("Update your local copy of a registry agent to the current registry source.").option("--out <dir>", "Directory the agent was added into (default: ./agents)").option("-f, --force", "Apply without listing the changes first").action(async (agent, options) => {
4884
+ try {
4885
+ const { targetDir, files } = await diffAgent(agent, { outDir: options.out });
4886
+ const changed = files.filter((f) => f.status !== "unchanged");
4887
+ if (changed.length === 0) {
4888
+ process.stdout.write(`
4889
+ ${agent}: already up to date.
4890
+ `);
4891
+ return;
4892
+ }
4893
+ if (!options.force) {
4894
+ process.stdout.write(`
4895
+ Will overwrite ${changed.length} file(s) with the registry version:
4896
+ `);
4897
+ for (const f of changed) process.stdout.write(` ${f.path} (${f.status})
4898
+ `);
4899
+ }
4900
+ for (const f of changed) {
4901
+ const dest = path.join(targetDir, f.path);
4902
+ await promises.mkdir(path.dirname(dest), { recursive: true });
4903
+ await promises.writeFile(dest, f.upstream, "utf8");
4904
+ process.stdout.write(` updated ${dest}
4905
+ `);
4906
+ }
4907
+ process.stdout.write(`
4908
+ Updated ${agent}. Review the changes with your VCS before committing.
4909
+ `);
4910
+ } catch (err) {
4911
+ process.stderr.write(`
4912
+ update failed: ${err instanceof Error ? err.message : String(err)}
4913
+ `);
4914
+ process.exit(1);
4915
+ }
4916
+ });
4917
+ }
4163
4918
 
4164
4919
  // src/commands/index.ts
4165
4920
  function createCli() {
@@ -4175,6 +4930,11 @@ function createCli() {
4175
4930
  registerRagCommand(program);
4176
4931
  registerAiCommand(program);
4177
4932
  registerFlowCommand(program);
4933
+ registerPiiCommand(program);
4934
+ registerRulesCommand(program);
4935
+ registerAddCommand(program);
4936
+ registerDiffCommand(program);
4937
+ registerUpdateCommand(program);
4178
4938
  return program;
4179
4939
  }
4180
4940