@duyquangnvx/create-agent-cli 0.22.0 → 0.23.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/dist/bin.js +151 -13
  3. package/package.json +2 -2
package/CHANGELOG.md CHANGED
@@ -4,6 +4,12 @@ What changed in each release of `@duyquangnvx/agent-cli` (the Runtime) and `@duy
4
4
 
5
5
  ## Unreleased
6
6
 
7
+ ## v0.23.0 (2026-11-03)
8
+
9
+ ### Added
10
+
11
+ - A Global install of a CLI with several Commands runs a Release check: at most once a day a run asks the Install registry, in a detached process, for the latest release, and later runs give the Notice ``<cli> X.Y.Z is out, this is A.B.C; run `<cli> update` `` while the CLI is behind. No run waits on it. `CI`, or `<NAME>_NO_RELEASE_CHECK=1` for one CLI, turns it off; it keeps its answer in `$XDG_CACHE_HOME/<name>/release-check.json`, by default under `~/.cache`.
12
+
7
13
  ## v0.22.0 (2026-11-02)
8
14
 
9
15
  ### Added
package/dist/bin.js CHANGED
@@ -5959,8 +5959,12 @@ async function orMissing(pending) {
5959
5959
  function isNothingThere(error) {
5960
5960
  return isMissingFile(error) || errnoOf(error) === "ENOTDIR";
5961
5961
  }
5962
+ /** The CLI's own environment variable, such as `TOOL_DEBUG` for `cliVariable("tool", "DEBUG")`. */
5963
+ function cliVariable(cliName, suffix) {
5964
+ return `${cliName.toUpperCase().replace(/[^A-Z0-9]/g, "_")}_${suffix}`;
5965
+ }
5962
5966
  function debugVariable(cliName) {
5963
- return `${cliName.toUpperCase().replace(/[^A-Z0-9]/g, "_")}_DEBUG`;
5967
+ return cliVariable(cliName, "DEBUG");
5964
5968
  }
5965
5969
  function messageOf(error) {
5966
5970
  return error instanceof Error ? error.message : String(error);
@@ -7081,9 +7085,9 @@ function secretRows(secrets, commands) {
7081
7085
  /** The bundler's stamp, absent when the CLI runs unbundled. Read per call, so a test can define it. */
7082
7086
  function buildStamp() {
7083
7087
  return {
7084
- "sha": "4664298",
7088
+ "sha": "71583a7",
7085
7089
  "dirty": false,
7086
- "builtAt": "2026-11-02T04:03:08.000Z"
7090
+ "builtAt": "2026-11-03T06:40:47.000Z"
7087
7091
  };
7088
7092
  }
7089
7093
  /** The first line of `--version`: the version, and the commit and time of a stamped build. */
@@ -7361,15 +7365,18 @@ async function installGlobally(version, ctx) {
7361
7365
  ...ctx.registry.flags
7362
7366
  ], ctx, null);
7363
7367
  }
7364
- async function latestVersion(ctx) {
7365
- const { package: name, label: registry, flags } = ctx.registry;
7366
- const answer = await runNpm("view", [
7368
+ function latestViewArgs({ package: name, flags }) {
7369
+ return [
7367
7370
  "view",
7368
7371
  `${name}@latest`,
7369
7372
  "version",
7370
7373
  "--json",
7371
7374
  ...flags
7372
- ], ctx);
7375
+ ];
7376
+ }
7377
+ async function latestVersion(ctx) {
7378
+ const { package: name, label: registry } = ctx.registry;
7379
+ const answer = await runNpm("view", latestViewArgs(ctx.registry), ctx);
7373
7380
  const version = versionInViewAnswer(answer);
7374
7381
  if (version === void 0) throw noLatest(ctx, `npm answered no version for ${name}@latest on ${registry}: ${answer.trim()}`);
7375
7382
  return version;
@@ -7611,7 +7618,8 @@ function dependencyInstall(cli, install) {
7611
7618
  runAs,
7612
7619
  builtIns: () => ({}),
7613
7620
  exitClasses: [],
7614
- check: (launch) => checkOf$1(cli, install, runAs, launch)
7621
+ check: (launch) => checkOf$1(cli, install, runAs, launch),
7622
+ notice: async () => {}
7615
7623
  };
7616
7624
  }
7617
7625
  //#endregion
@@ -8688,6 +8696,127 @@ function updateCommand(cli, install, skill, { bin, node }) {
8688
8696
  });
8689
8697
  }
8690
8698
  //#endregion
8699
+ //#region ../agent-cli/dist/built-ins/install/release-check.js
8700
+ const CHECK_INTERVAL_MS = 864e5;
8701
+ const checkSchema = object({
8702
+ checkedAt: number(),
8703
+ latest: string().optional(),
8704
+ error: string().optional()
8705
+ });
8706
+ const CHECK_SCRIPT = `
8707
+ const { spawnSync } = require("node:child_process");
8708
+ const { mkdtempSync, renameSync, rmSync, writeFileSync } = require("node:fs");
8709
+ const { tmpdir } = require("node:os");
8710
+ const { join } = require("node:path");
8711
+ const [file, checkedAt, previous, npm, ...args] = process.argv.slice(1);
8712
+ const windows = process.platform === "win32";
8713
+ const cache = mkdtempSync(join(tmpdir(), "release-check-"));
8714
+ const write = (check) => {
8715
+ const temp = file + "." + process.pid + ".tmp";
8716
+ writeFileSync(temp, JSON.stringify({ checkedAt: Number(checkedAt), ...check }));
8717
+ renameSync(temp, file);
8718
+ };
8719
+ const latestOf = (stdout) => {
8720
+ try {
8721
+ const parsed = JSON.parse(stdout);
8722
+ const latest = Array.isArray(parsed) ? parsed.at(-1) : parsed;
8723
+ return typeof latest === "string" && /^\\d+\\.\\d+\\.\\d+/.test(latest) ? latest : undefined;
8724
+ } catch {
8725
+ return undefined;
8726
+ }
8727
+ };
8728
+ try {
8729
+ const answer = spawnSync(windows ? '"' + npm + '"' : npm, args, {
8730
+ encoding: "utf8",
8731
+ shell: windows,
8732
+ windowsHide: true,
8733
+ env: { ...process.env, npm_config_cache: join(cache, "cache"), npm_config_logs_max: "0" },
8734
+ });
8735
+ const latest = answer.status === 0 ? latestOf(answer.stdout) : undefined;
8736
+ if (latest !== undefined) {
8737
+ write({ latest });
8738
+ } else {
8739
+ const said = (answer.stderr || "").trim().split(/\\r?\\n/).slice(0, 5).join("; ");
8740
+ const error = answer.error ? answer.error.message : said || "npm answered " + answer.stdout.trim();
8741
+ write({ latest: previous || undefined, error });
8742
+ }
8743
+ } finally {
8744
+ rmSync(cache, { recursive: true, force: true });
8745
+ }
8746
+ `;
8747
+ function isOff(cliName, env) {
8748
+ return Boolean(env[cliVariable(cliName, "NO_RELEASE_CHECK")] || env.CI);
8749
+ }
8750
+ function cacheFileOf(cliName, env) {
8751
+ const cache = env.XDG_CACHE_HOME || join(env.HOME || env.USERPROFILE || homedir(), ".cache");
8752
+ return join(cache, cliName, "release-check.json");
8753
+ }
8754
+ function parsedCheck(text) {
8755
+ try {
8756
+ return checkSchema.safeParse(JSON.parse(text)).data;
8757
+ } catch {
8758
+ return;
8759
+ }
8760
+ }
8761
+ async function lastCheck(file) {
8762
+ const text = await orMissing(readFile(file, "utf8"));
8763
+ return text === void 0 ? void 0 : parsedCheck(text);
8764
+ }
8765
+ async function writeCheck(file, check) {
8766
+ await mkdir(dirname(file), { recursive: true });
8767
+ const temp = tempPathOf(file);
8768
+ await writeFile(temp, JSON.stringify(check));
8769
+ await rename(temp, file);
8770
+ }
8771
+ async function startCheck(cli, install, file, last, { node }, ctx) {
8772
+ const checkedAt = ctx.clock.now();
8773
+ const previous = last?.latest ?? "";
8774
+ await writeCheck(file, {
8775
+ checkedAt,
8776
+ latest: last?.latest
8777
+ });
8778
+ const npm = globalNpm(cli, install, ctx, node);
8779
+ const { path, env } = npmOf(npm, process.platform === "win32");
8780
+ await ctx.spawnDetached(process.execPath, [
8781
+ "-e",
8782
+ CHECK_SCRIPT,
8783
+ file,
8784
+ String(checkedAt),
8785
+ previous,
8786
+ path,
8787
+ ...latestViewArgs(npm.registry)
8788
+ ], { env });
8789
+ }
8790
+ function debugLine(cli, ctx, text) {
8791
+ if (isDebug({
8792
+ env: ctx.env,
8793
+ cliName: cli.name
8794
+ })) ctx.progress(`release check failed: ${text}`);
8795
+ }
8796
+ /**
8797
+ * Tells the caller of a newer release that an earlier Release check found, and starts the next
8798
+ * check once the last is a day old.
8799
+ */
8800
+ async function releaseCheck(cli, install, runAs, launch, ctx) {
8801
+ if (launch.builtIn || launch.bin === void 0 || cli.commands === void 0) return;
8802
+ if (isOff(cli.name, ctx.env)) return;
8803
+ try {
8804
+ await checkAndTell(cli, install, runAs, launch, ctx);
8805
+ } catch (error) {
8806
+ if (errnoOf(error) === void 0 && !(error instanceof CliError)) throw error;
8807
+ debugLine(cli, ctx, messageOf(error));
8808
+ }
8809
+ }
8810
+ async function checkAndTell(cli, install, runAs, launch, ctx) {
8811
+ const file = cacheFileOf(cli.name, ctx.env);
8812
+ const last = await lastCheck(file);
8813
+ if (last?.error !== void 0) debugLine(cli, ctx, last.error);
8814
+ if (last === void 0 || ctx.clock.now() - last.checkedAt >= CHECK_INTERVAL_MS) await startCheck(cli, install, file, last, launch, ctx);
8815
+ const latest = last?.latest;
8816
+ if (latest === void 0 || !isVersion(latest) || compareVersions(latest, cli.version) <= 0) return;
8817
+ ctx.notice(`${cli.name} ${latest} is out, this is ${cli.version}; run \`${runAs} update\``);
8818
+ }
8819
+ //#endregion
8691
8820
  //#region ../agent-cli/dist/built-ins/install/global.js
8692
8821
  function checkOf(cli, install, { bin, node }) {
8693
8822
  return {
@@ -8722,12 +8851,14 @@ function globalInstall(cli, install) {
8722
8851
  runAs: runAsOf(cli),
8723
8852
  builtIns: (launch) => ({ update: updateCommand(cli, install, skillOf$1(cli, adapter), launch) }),
8724
8853
  exitClasses: ["source"],
8725
- check: (launch) => checkOf(cli, install, launch)
8854
+ check: (launch) => checkOf(cli, install, launch),
8855
+ notice: (launch, ctx) => releaseCheck(cli, install, adapter.runAs, launch, ctx)
8726
8856
  };
8727
8857
  return adapter;
8728
8858
  }
8729
8859
  //#endregion
8730
8860
  //#region ../agent-cli/dist/built-ins/install/install.js
8861
+ const noNotice = async () => {};
8731
8862
  function noInstall(cli) {
8732
8863
  const skipped = async () => ({
8733
8864
  state: "skipped",
@@ -8740,7 +8871,8 @@ function noInstall(cli) {
8740
8871
  check: () => ({
8741
8872
  install: skipped,
8742
8873
  release: async () => ({ skipped: await skipped() })
8743
- })
8874
+ }),
8875
+ notice: noNotice
8744
8876
  };
8745
8877
  }
8746
8878
  function installOf(cli) {
@@ -11178,9 +11310,10 @@ async function runCommand(cli, routed, options, write, baseFormat) {
11178
11310
  let served = false;
11179
11311
  let settled;
11180
11312
  try {
11181
- const outcome = await control.settle(skillOf$1(cli, installOf(cli)).notice({
11313
+ const outcome = await control.settle(noticesOf(cli, {
11182
11314
  builtIn,
11183
- bin: options.bin
11315
+ bin: options.bin,
11316
+ node: options.node
11184
11317
  }, ctx).then(() => execute(command, input, ctx, {
11185
11318
  dryRun: flags.dryRun,
11186
11319
  confirm: flags.confirm,
@@ -11240,6 +11373,11 @@ function once(build) {
11240
11373
  throw outcome.error;
11241
11374
  };
11242
11375
  }
11376
+ async function noticesOf(cli, launch, ctx) {
11377
+ const install = installOf(cli);
11378
+ await skillOf$1(cli, install).notice(launch, ctx);
11379
+ await install.notice(launch, ctx);
11380
+ }
11243
11381
  //#endregion
11244
11382
  //#region ../agent-cli/dist/style.js
11245
11383
  function colourEnabled(env, stdoutIsTTY) {
@@ -11394,7 +11532,7 @@ async function runProcess(cli) {
11394
11532
  //#region package.json
11395
11533
  var package_default = {
11396
11534
  name: "@duyquangnvx/create-agent-cli",
11397
- version: "0.22.0",
11535
+ version: "0.23.0",
11398
11536
  description: "Creates a new Agent CLI repository wired to @duyquangnvx/agent-cli.",
11399
11537
  type: "module",
11400
11538
  engines: { "node": ">=22.12.0" },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@duyquangnvx/create-agent-cli",
3
- "version": "0.22.0",
3
+ "version": "0.23.0",
4
4
  "description": "Creates a new Agent CLI repository wired to @duyquangnvx/agent-cli.",
5
5
  "type": "module",
6
6
  "engines": {
@@ -21,7 +21,7 @@
21
21
  "access": "public"
22
22
  },
23
23
  "devDependencies": {
24
- "@duyquangnvx/agent-cli": "0.22.0",
24
+ "@duyquangnvx/agent-cli": "0.23.0",
25
25
  "tsdown": "0.23.0",
26
26
  "zod": "4.6.7"
27
27
  },