@duyquangnvx/create-agent-cli 0.15.1 → 0.17.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -4,6 +4,23 @@ What changed in each release of `@duyquangnvx/agent-cli` (the Runtime) and `@duy
4
4
 
5
5
  ## Unreleased
6
6
 
7
+ ## v0.17.0 (2026-10-27)
8
+
9
+ ### Added
10
+
11
+ - A fifth Effect, `act`, for a Command that changes the state of a program already running, such as a page in a browser tab, rather than files or remote services. It is declared as a read Command is, with `run` and the new `ActCommand` type, and cannot declare a Plan or `rows`. Its help says so and that running it again may act again, its `--help --json` gives `"effect": "act"`, and of the Standard flags it gets only `--json`. The agent-cli skill says when to pick `act` over `read` and `write`.
12
+ - The Scaffolder's `AGENTS.md` names the `act` and `serve` Effects beside `read`, `write` and `destructive`.
13
+
14
+ ### Upgrade from v0.16.0
15
+
16
+ - Nothing to do.
17
+
18
+ ## v0.16.0 (2026-10-27)
19
+
20
+ ### Changed
21
+
22
+ - **Breaking:** a Packaged skill's `AGENTS.md` block is the text the CLI declares as `skill: { packaged, agents }`, written verbatim between its markers; the Runtime no longer composes a sentence from `description` and the run-as. Without `agents`, `skill install` writes no block and names no `CLAUDE.md` hand edit, its `--json` `agentsMd` is `null`, and `doctor` has no `agents` row. A CLI relying on the old block declares `agents` to keep one, and the Scaffolder's `--dependency` template declares none.
23
+
7
24
  ## v0.15.1 (2026-10-27)
8
25
 
9
26
  ### Fixed
package/dist/bin.js CHANGED
@@ -6429,6 +6429,7 @@ const FORCE_FLAG = standardFlag("--force", {
6429
6429
  });
6430
6430
  const EFFECT_FLAGS = {
6431
6431
  read: [JSON_FLAG],
6432
+ act: [JSON_FLAG],
6432
6433
  write: [JSON_FLAG, DRY_RUN_FLAG],
6433
6434
  destructive: [
6434
6435
  JSON_FLAG,
@@ -7390,7 +7391,6 @@ function dependencyInstall(cli, install) {
7390
7391
  runAs,
7391
7392
  builtIns: () => ({}),
7392
7393
  exitClasses: [],
7393
- agentsCaveat: `and only from a package that has \`${install.package}\` in its dependencies; from anywhere else npx reports the package missing`,
7394
7394
  check: (launch) => checkOf$1(cli, install, runAs, launch)
7395
7395
  };
7396
7396
  }
@@ -7492,12 +7492,6 @@ function hubSkill(cli, source) {
7492
7492
  }
7493
7493
  //#endregion
7494
7494
  //#region ../agent-cli/dist/built-ins/agents-md.js
7495
- /** One paragraph telling every session that the CLI exists, how to run it and which skill to load. */
7496
- function agentsBlock(cli, { runAs, agentsCaveat }) {
7497
- const what = cli.description.replace(/\.$/, "");
7498
- const run = `Run it as \`${runAs}\`${agentsCaveat === void 0 ? "" : `, ${agentsCaveat}`}`;
7499
- return `\`${cli.name}\`: ${what}. ${run}. Load the \`${cli.name}\` skill before using it.`;
7500
- }
7501
7495
  function markersOf(name) {
7502
7496
  return {
7503
7497
  begin: `<!-- BEGIN:${name} -->`,
@@ -7505,33 +7499,32 @@ function markersOf(name) {
7505
7499
  };
7506
7500
  }
7507
7501
  /** `text` with the CLI's block between its markers, appended when they are missing; nothing else changes. */
7508
- function withBlock(cli, install, text) {
7509
- const { begin, end } = markersOf(cli.name);
7510
- const whole = `${begin}\n${agentsBlock(cli, install)}\n${end}`;
7502
+ function withBlock({ name, text: block }, text) {
7503
+ const { begin, end } = markersOf(name);
7504
+ const whole = `${begin}\n${block}\n${end}`;
7511
7505
  const start = text.indexOf(begin);
7512
7506
  const stop = start === -1 ? -1 : text.indexOf(end, start);
7513
7507
  if (stop !== -1) return `${text.slice(0, start)}${whole}${text.slice(stop + end.length)}`;
7514
7508
  if (text === "") return `${whole}\n`;
7515
7509
  return `${text}${text.endsWith("\n") ? "" : "\n"}\n${whole}\n`;
7516
7510
  }
7517
- async function agentsMdAt(cli, install, root) {
7511
+ async function agentsMdAt(agents, root) {
7518
7512
  const path = join(root, "AGENTS.md");
7519
7513
  const existing = await orMissing(readFile(path, "utf8"));
7520
- const block = agentsBlock(cli, install);
7521
7514
  if (existing === void 0) return {
7522
7515
  path,
7523
- block,
7516
+ agents,
7524
7517
  change: "create"
7525
7518
  };
7526
- if (withBlock(cli, install, existing) === existing) return {
7519
+ if (withBlock(agents, existing) === existing) return {
7527
7520
  path,
7528
- block,
7521
+ agents,
7529
7522
  change: null
7530
7523
  };
7531
7524
  return {
7532
7525
  path,
7533
- block,
7534
- change: existing.includes(markersOf(cli.name).begin) ? "rewrite" : "add"
7526
+ agents,
7527
+ change: existing.includes(markersOf(agents.name).begin) ? "rewrite" : "add"
7535
7528
  };
7536
7529
  }
7537
7530
  async function handEditsAt(root) {
@@ -7540,25 +7533,26 @@ async function handEditsAt(root) {
7540
7533
  if (text === void 0 || text.split(/\r?\n/).some((line) => line.trim() === "@AGENTS.md")) return [];
7541
7534
  return [`add a line \`@AGENTS.md\` to ${claudeMd}, so Claude Code reads AGENTS.md`];
7542
7535
  }
7543
- function agentsRow(cli, install) {
7536
+ function agentsRow(agents, install) {
7537
+ const { name } = agents;
7544
7538
  return {
7545
7539
  name: "agents",
7546
7540
  advisory: true,
7547
7541
  run: async (ctx) => {
7548
7542
  const root = await projectRootOf(ctx.cwd);
7549
- const { path, change } = await agentsMdAt(cli, install, root);
7543
+ const { path, change } = await agentsMdAt(agents, root);
7550
7544
  const fix = `${install.runAs} skill install`;
7551
7545
  if (change === "rewrite") return {
7552
7546
  state: "problem",
7553
- message: `${path} holds a ${cli.name} block other than the one ${fix} writes`,
7547
+ message: `${path} holds a ${name} block other than the one ${fix} writes`,
7554
7548
  fix
7555
7549
  };
7556
7550
  if (change !== null) return {
7557
7551
  state: "problem",
7558
- message: `${path} holds no ${cli.name} block, so sessions in this repo do not know ${cli.name}`,
7552
+ message: `${path} holds no ${name} block, so sessions in this repo do not know ${name}`,
7559
7553
  fix
7560
7554
  };
7561
- const holds = `${path} holds the ${cli.name} block`;
7555
+ const holds = `${path} holds the ${name} block`;
7562
7556
  const [edit] = await handEditsAt(root);
7563
7557
  return edit === void 0 ? {
7564
7558
  state: "ok",
@@ -7936,14 +7930,14 @@ const installSchema = object({
7936
7930
  agentsMd: object({
7937
7931
  path: string(),
7938
7932
  changed: boolean()
7939
- }),
7933
+ }).nullable(),
7940
7934
  handEdits: array(string())
7941
7935
  });
7942
7936
  async function sameContent(left, right) {
7943
7937
  const [a, b] = await Promise.all([readFile(left), readFile(right)]);
7944
7938
  return a.equals(b);
7945
7939
  }
7946
- async function planOf(cli, folder, install, cwd, packageRoot) {
7940
+ async function planOf(cli, folder, agents, cwd, packageRoot) {
7947
7941
  const source = join(packageRoot, folder);
7948
7942
  const { root, copy } = await skillCopyOf(cli, cwd);
7949
7943
  const [shipped, present] = await Promise.all([filesIn(source), filesIn(copy)]);
@@ -7955,8 +7949,8 @@ async function planOf(cli, folder, install, cwd, packageRoot) {
7955
7949
  replace: shipped.filter((_file, index) => differs[index]),
7956
7950
  remove: present.filter((file) => !shipped.includes(file)),
7957
7951
  sameVersion: (await skillVersionsOf(source, copy)).same,
7958
- agentsMd: await agentsMdAt(cli, install, root),
7959
- handEdits: await handEditsAt(root)
7952
+ agentsMd: agents === void 0 ? null : await agentsMdAt(agents, root),
7953
+ handEdits: agents === void 0 ? [] : await handEditsAt(root)
7960
7954
  };
7961
7955
  }
7962
7956
  const BLOCK_VERBS = {
@@ -7970,11 +7964,11 @@ function renderPlan(cli, plan) {
7970
7964
  ...plan.replace.map((file) => ` replace ${file}`),
7971
7965
  ...plan.remove.map((file) => ` remove ${file}`)
7972
7966
  ];
7973
- const { path, block, change } = plan.agentsMd;
7967
+ const { agentsMd } = plan;
7974
7968
  return [
7975
7969
  files.length === 0 ? `the ${cli.name} skill at ${plan.copy} already matches the package` : `would copy the ${cli.name} skill to ${plan.copy}:`,
7976
7970
  ...files,
7977
- change === null ? `${path} already holds the ${cli.name} block` : `would ${BLOCK_VERBS[change]} ${path}:\n${block}`,
7971
+ ...agentsMd === null ? [] : [agentsMd.change === null ? `${agentsMd.path} already holds the ${cli.name} block` : `would ${BLOCK_VERBS[agentsMd.change]} ${agentsMd.path}:\n${agentsMd.agents.text}`],
7978
7972
  ...plan.handEdits.map((edit) => `by hand: ${edit}`)
7979
7973
  ].join("\n");
7980
7974
  }
@@ -7982,7 +7976,7 @@ function renderInstalled(cli, data) {
7982
7976
  const { written, removed, agentsMd } = data;
7983
7977
  return [
7984
7978
  written.length + removed.length === 0 ? `the ${cli.name} skill at ${data.copy} already matches the package` : `copied the ${cli.name} skill to ${data.copy}: wrote ${written.length} files, removed ${removed.length}`,
7985
- agentsMd.changed ? `wrote the ${cli.name} block in ${agentsMd.path}` : `${agentsMd.path} already holds the ${cli.name} block`,
7979
+ ...agentsMd === null ? [] : [agentsMd.changed ? `wrote the ${cli.name} block in ${agentsMd.path}` : `${agentsMd.path} already holds the ${cli.name} block`],
7986
7980
  ...data.handEdits.map((edit) => `by hand: ${edit}`)
7987
7981
  ].join("\n");
7988
7982
  }
@@ -7995,11 +7989,11 @@ function editedRemovals(plan, ctx) {
7995
7989
  });
7996
7990
  }
7997
7991
  /** The built-in `skill install`, for a CLI whose skill ships in its package. */
7998
- function skillInstallCommand(cli, folder, install) {
7992
+ function skillInstallCommand(cli, folder, install, agents) {
7999
7993
  const { runAs } = install;
8000
7994
  return defineCommand$1({
8001
7995
  name: "install",
8002
- description: "copy this CLI's skill into the project and write its AGENTS.md block",
7996
+ description: agents === void 0 ? "copy this CLI's skill into the project" : "copy this CLI's skill into the project and write its AGENTS.md block",
8003
7997
  examples: [{
8004
7998
  command: `${runAs} skill install`,
8005
7999
  note: "install the skill this version ships"
@@ -8010,24 +8004,24 @@ function skillInstallCommand(cli, folder, install) {
8010
8004
  effect: "write",
8011
8005
  output: installSchema,
8012
8006
  force: "replace or remove hand-edited files of a skill copy at the package's version",
8013
- plan: (_input, ctx) => planOf(cli, folder, install, ctx.cwd, ctx.packageRoot),
8007
+ plan: (_input, ctx) => planOf(cli, folder, agents, ctx.cwd, ctx.packageRoot),
8014
8008
  outputs: (plan) => plan.sameVersion ? [...plan.write, ...plan.replace].map((file) => join(plan.copy, file)) : [],
8015
8009
  apply: async (plan, ctx) => {
8016
8010
  if (plan.sameVersion && plan.remove.length > 0 && !ctx.force) throw editedRemovals(plan, ctx);
8017
8011
  for (const file of [...plan.write, ...plan.replace]) await ctx.files.write(join(plan.copy, file), await readFile(join(plan.source, file)));
8018
8012
  for (const file of plan.remove) await ctx.files.remove(join(plan.copy, file));
8019
- const { path, change } = plan.agentsMd;
8020
- if (change !== null) {
8021
- const existing = await orMissing(readFile(path, "utf8")) ?? "";
8022
- await ctx.files.write(path, withBlock(cli, install, existing));
8013
+ const { agentsMd } = plan;
8014
+ if (agentsMd !== null && agentsMd.change !== null) {
8015
+ const existing = await orMissing(readFile(agentsMd.path, "utf8")) ?? "";
8016
+ await ctx.files.write(agentsMd.path, withBlock(agentsMd.agents, existing));
8023
8017
  }
8024
8018
  return {
8025
8019
  copy: plan.copy,
8026
8020
  written: [...plan.write, ...plan.replace].sort(),
8027
8021
  removed: [...plan.remove],
8028
- agentsMd: {
8029
- path,
8030
- changed: change !== null
8022
+ agentsMd: agentsMd === null ? null : {
8023
+ path: agentsMd.path,
8024
+ changed: agentsMd.change !== null
8031
8025
  },
8032
8026
  handEdits: [...plan.handEdits]
8033
8027
  };
@@ -8102,16 +8096,20 @@ async function notice(cli, folder, install, { builtIn, bin }, ctx) {
8102
8096
  if (finding.state !== "problem") return;
8103
8097
  ctx.notice(finding.fix === void 0 ? finding.message : `${finding.message}; run \`${finding.fix}\``);
8104
8098
  }
8105
- /** A skill shipped in the package at `folder`, which `skill install` copies to the project root. */
8106
- function packagedSkill(cli, folder, install) {
8099
+ /** A skill shipped in the package at `packaged`, which `skill install` copies to the project root. */
8100
+ function packagedSkill(cli, { packaged: folder, agents: text }, install) {
8101
+ const agents = text === void 0 ? void 0 : {
8102
+ name: cli.name,
8103
+ text
8104
+ };
8107
8105
  return {
8108
8106
  kind: "packaged",
8109
- builtIns: { skill: { install: skillInstallCommand(cli, folder, install) } },
8107
+ builtIns: { skill: { install: skillInstallCommand(cli, folder, install, agents) } },
8110
8108
  rows: [{
8111
8109
  name: "skill",
8112
8110
  advisory: true,
8113
8111
  run: (ctx) => findingOf(cli, folder, install, ctx)
8114
- }, agentsRow(cli, install)],
8112
+ }, ...agents === void 0 ? [] : [agentsRow(agents, install)]],
8115
8113
  notice: (launch, ctx) => notice(cli, folder, install, launch, ctx),
8116
8114
  findingAt: (_version, ctx) => findingOf(cli, folder, install, ctx)
8117
8115
  };
@@ -8121,7 +8119,7 @@ function packagedSkill(cli, folder, install) {
8121
8119
  /** A Packaged skill, or else a Skill Hub's, also for a CLI that declares no `skill`. */
8122
8120
  function skillOf(cli, install) {
8123
8121
  const { skill } = cli;
8124
- return skill !== void 0 && "packaged" in skill ? packagedSkill(cli, skill.packaged, install) : hubSkill(cli, skill?.source);
8122
+ return skill !== void 0 && "packaged" in skill ? packagedSkill(cli, skill, install) : hubSkill(cli, skill?.source);
8125
8123
  }
8126
8124
  //#endregion
8127
8125
  //#region ../agent-cli/dist/interrupts.js
@@ -8509,7 +8507,6 @@ function globalInstall(cli, install) {
8509
8507
  runAs: runAsOf(cli),
8510
8508
  builtIns: (launch) => ({ update: updateCommand(cli, install, skillOf(cli, adapter), launch) }),
8511
8509
  exitClasses: ["source"],
8512
- agentsCaveat: void 0,
8513
8510
  check: (launch) => checkOf(cli, install, launch)
8514
8511
  };
8515
8512
  return adapter;
@@ -8525,7 +8522,6 @@ function noInstall(cli) {
8525
8522
  runAs: runAsOf(cli),
8526
8523
  builtIns: () => ({}),
8527
8524
  exitClasses: [],
8528
- agentsCaveat: void 0,
8529
8525
  check: () => ({
8530
8526
  install: skipped,
8531
8527
  release: async () => ({ skipped: await skipped() })
@@ -9120,6 +9116,7 @@ function groupHelp(group, style) {
9120
9116
  }
9121
9117
  const EFFECT_NOTES = {
9122
9118
  read: "changes nothing",
9119
+ act: "changes the state of a running program, not files or services; may act again each time it runs",
9123
9120
  write: "changes something; show the Plan with --dry-run",
9124
9121
  destructive: "changes something hard to undo; show the Plan with --dry-run, run with --confirm",
9125
9122
  serve: "changes nothing; prints its data at once, then runs until Ctrl-C, which exits 0"
@@ -9509,7 +9506,7 @@ async function execute(command, input, ctx, { dryRun, confirm, yes, page, invoca
9509
9506
  ...input,
9510
9507
  ...page
9511
9508
  }, ctx), (data, style) => command.render(data, style));
9512
- if (command.effect === "read") return outcomeOf(await command.run(input, ctx), (data, style) => command.render(data, style));
9509
+ if (command.effect === "read" || command.effect === "act") return outcomeOf(await command.run(input, ctx), (data, style) => command.render(data, style));
9513
9510
  if (command.effect === "serve") {
9514
9511
  let end = () => {};
9515
9512
  const ended = new Promise((resolve) => {
@@ -10258,14 +10255,14 @@ async function runProcess(cli) {
10258
10255
  }
10259
10256
  //#endregion
10260
10257
  //#region package.json
10261
- var version$1 = "0.15.1";
10258
+ var version$1 = "0.17.0";
10262
10259
  var description = "Creates a new Agent CLI repository wired to @duyquangnvx/agent-cli.";
10263
10260
  //#endregion
10264
10261
  //#region src/build.ts
10265
10262
  const build = {
10266
- "sha": "da48f36",
10263
+ "sha": "8bb3938",
10267
10264
  "dirty": false,
10268
- "builtAt": "2026-10-27T08:31:19.000Z"
10265
+ "builtAt": "2026-10-27T10:54:44.000Z"
10269
10266
  };
10270
10267
  //#endregion
10271
10268
  //#region src/fill.ts
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@duyquangnvx/create-agent-cli",
3
- "version": "0.15.1",
3
+ "version": "0.17.0",
4
4
  "description": "Creates a new Agent CLI repository wired to @duyquangnvx/agent-cli.",
5
5
  "type": "module",
6
6
  "engines": {
@@ -20,7 +20,7 @@
20
20
  "access": "public"
21
21
  },
22
22
  "devDependencies": {
23
- "@duyquangnvx/agent-cli": "0.15.1",
23
+ "@duyquangnvx/agent-cli": "0.17.0",
24
24
  "tsdown": "0.23.0",
25
25
  "zod": "4.6.7"
26
26
  },
@@ -19,7 +19,7 @@ Greenfield with no deployments or users: change code directly. Breaking changes
19
19
  ## Coding standards
20
20
 
21
21
  - Every Command is declared in `src/cli.ts` with the `defineCommand` that `agentCli<Ports>()` returns: a description, realistic examples, Zod schemas for args and options (with `.describe()` on each field), and its Effect.
22
- - A Command never prints. A `read` Command returns data from `run` and turns it into short text in `render`. A `write` or `destructive` Command returns a Plan from `plan`, with no side effects, and performs it in `apply`; the Runtime adds `--dry-run`, and `--confirm` for a destructive one.
22
+ - A Command never prints. A `read` Command returns data from `run` and turns it into short text in `render`. An `act` Command, which changes the state of a program already running rather than files or services, does the same with no Plan. A `write` or `destructive` Command returns a Plan from `plan`, with no side effects, and performs it in `apply`; the Runtime adds `--dry-run`, and `--confirm` for a destructive one. A `serve` Command returns data from `start` and keeps running until `ctx.signal` aborts.
23
23
  - A Command that writes files names them in `outputs(plan)`, and the Runtime refuses to overwrite one without `--force`. A Command that costs money states it in `cost(plan)`, a sentence or `undefined` when cheap, and the Runtime refuses a costly run without `--yes`.
24
24
  - A Command reaches a remote service through a port: an interface in `src/ports.ts`, built by the `ports` factory passed to `defineCli` and read as `ctx.ports`. It writes files through `ctx.files` and runs programs through `ctx.spawn`, so a test replaces each of them.
25
25
  - Every error code is declared once in a `defineErrors` table passed to `defineCli({ errors })`, with the Exit class that must act on it and an optional `details` schema, and thrown through `cliError(errors, code, { message, hint })`, whose `hint` names the command to run next. An undeclared code, and anything else thrown, becomes an internal error (exit 70), whose stack trace `{{debugVariable}}=1` shows.