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