@agentage/cli 0.45.0 → 0.47.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/README.md CHANGED
@@ -189,6 +189,8 @@ Releases publish to npm via GitHub Actions when a `chore(release): X.Y.Z` commit
189
189
  `package.json` lands on `master` (the squash-merge subject of a release PR). The workflow skips
190
190
  any version already on npm.
191
191
 
192
+ An extension release (`agentage extension release`) skips npm for a package with `"private": true`: it pushes the images, attaches the record and prints `PASS npm: private package, not published`.
193
+
192
194
  ## License
193
195
 
194
196
  MIT - see [LICENSE](./LICENSE).
@@ -76,13 +76,28 @@ const renameLock = (dir, id) => {
76
76
  Object.assign(at, { name });
77
77
  save(join(dir, LOCK), lock);
78
78
  };
79
+ // Bounded, a partial folder removed; git's own first line says why (no repo, no network, no access).
79
80
  const clone = async (url, from, dir) => {
80
81
  const env = { ...process.env, GIT_TERMINAL_PROMPT: '0' };
81
- await run('git', ['clone', '-q', '--depth', '1', url, dir], { env }).catch(() => {
82
- throw new Error(`template ${from} not found`);
82
+ await run('git', ['clone', '-q', '--depth', '1', url, dir], { env, timeout: 120_000 }).catch((err) => {
83
+ rmSync(dir, { recursive: true, force: true });
84
+ const why = (err.stderr?.trim() || err.message).split('\n')[0];
85
+ throw new Error(`cannot clone template ${from}: ${why}`);
83
86
  });
84
87
  rmSync(join(dir, '.git'), { recursive: true, force: true });
85
88
  };
89
+ // A half-made folder is removed, so a re-run is not refused as "exists".
90
+ const shape = (dir, from, id, kept) => {
91
+ try {
92
+ drop(dir, kept);
93
+ rewrite(dir, id);
94
+ renameLock(dir, id);
95
+ }
96
+ catch (err) {
97
+ rmSync(dir, { recursive: true, force: true });
98
+ throw new Error(`template ${from}: ${err.message}`);
99
+ }
100
+ };
86
101
  // Clones the template's default branch into ./extension-<id> as a new working copy, no history.
87
102
  export const runExtensionCreate = async (opts, url = (from) => `https://github.com/${from}`) => {
88
103
  const { id, from = TEMPLATE } = opts;
@@ -92,9 +107,7 @@ export const runExtensionCreate = async (opts, url = (from) => `https://github.c
92
107
  throw new Error(refused);
93
108
  const dir = `extension-${id}`;
94
109
  await clone(url(from), from, dir);
95
- drop(dir, kept);
96
- rewrite(dir, id);
97
- renameLock(dir, id);
110
+ shape(dir, from, id, kept);
98
111
  const kit = String(json(join(dir, 'package.json'))['dependencies']?.['@agentage/extension'] ?? '?');
99
112
  console.log(` template ${from} (default branch, kit ${kit.replace(/^[\^~]/, '')})`);
100
113
  console.log(` kept ${kept.join(', ')}`);
@@ -39,17 +39,35 @@ const tokenConfig = async (token) => {
39
39
  await writeFile(file, '//registry.npmjs.org/:_authToken=${NPM_TOKEN}\n');
40
40
  return ['--userconfig', file];
41
41
  };
42
+ // Only E404 means "not on npm"; a registry that cannot answer stops the release, never publishes.
43
+ const onNpm = async (run, args, what) => {
44
+ const viewed = await run('npm', ['view', ...args]);
45
+ if (viewed.ok)
46
+ return viewed.out.trim() !== '';
47
+ if (!/E404/.test(viewed.err))
48
+ fail(`npm: cannot tell whether ${what} is on npm: ${viewed.err}`);
49
+ return false;
50
+ };
51
+ // The upload with provenance signs first: its own bound, longer than the checks.
52
+ const PUBLISH_SECONDS = 300;
53
+ // A killed publish may have landed: stop, never stage it again; a re-run reads "already on npm".
54
+ const direct = async (push, spec, flags) => {
55
+ const published = await push('npm', ['publish', ...flags]);
56
+ if (/timed out after/.test(published.err))
57
+ fail(`npm: publish of ${spec} ${published.err}; re-run the release: it reads "already on npm" if the upload landed`);
58
+ return published.ok;
59
+ };
42
60
  // OIDC trusted publishing; a new name (NPM_TOKEN set) or an unauthorized direct publish is staged.
43
- const publish = async (run, { name, version }, token) => {
61
+ const publish = async (run, push, { name, version }, token) => {
44
62
  const cfg = await tokenConfig(token);
45
63
  const spec = `${name}@${version}`;
46
- if ((await run('npm', ['view', spec, 'version', ...cfg])).out.trim())
64
+ if (await onNpm(run, [spec, 'version', ...cfg], spec))
47
65
  return step(`npm: ${spec} is already on npm`);
48
66
  const flags = ['--access', 'public', '--provenance', '--ignore-scripts', ...cfg];
49
- const fresh = token !== undefined && !(await run('npm', ['view', name, 'name', ...cfg])).ok;
50
- if (!fresh && (await run('npm', ['publish', ...flags])).ok)
67
+ const fresh = token !== undefined && !(await onNpm(run, [name, 'name', ...cfg], name));
68
+ if (!fresh && (await direct(push, spec, flags)))
51
69
  return step(`npm: ${spec} published`);
52
- const staged = await run('npm', ['stage', 'publish', ...flags]);
70
+ const staged = await push('npm', ['stage', 'publish', ...flags]);
53
71
  if (!staged.ok)
54
72
  fail(`npm: ${staged.err}`);
55
73
  const why = fresh
@@ -64,7 +82,7 @@ const npmVersion = async (run) => {
64
82
  fail(`npm: npm ${v} cannot publish with OIDC: needs npm >= 11.5.1`);
65
83
  step(`npm: npm ${v}`);
66
84
  };
67
- const preflight = async (run, pkg, tag, sha) => {
85
+ const preflight = async (run, pkg, tag, sha, isPrivate) => {
68
86
  if (pkg.agentage.id === 'template')
69
87
  fail('id: this repo is the starting point for extensions: create one, release that');
70
88
  step(`id: ${pkg.agentage.id} is not the template`);
@@ -76,7 +94,8 @@ const preflight = async (run, pkg, tag, sha) => {
76
94
  if (tag !== v)
77
95
  fail(`tag: ${tag} is not ${v}, the package.json version; tag the commit that sets it`);
78
96
  step(`tag: ${tag} is the package.json version`);
79
- await npmVersion(run);
97
+ if (!isPrivate)
98
+ await npmVersion(run);
80
99
  };
81
100
  // One line per step, PASS or FAIL; any refusal exits 1 before anything is pushed or published.
82
101
  export const runExtensionRelease = async ({ dir = '.', timeout = 60 } = {}, deps = defaultDeps) => {
@@ -86,25 +105,29 @@ export const runExtensionRelease = async ({ dir = '.', timeout = 60 } = {}, deps
86
105
  return fail(`inputs: ${missing} is not set; run it on a tag push`);
87
106
  const secrets = takeSecrets(deps.env);
88
107
  const pkg = await (await deps.tool()).readManifest(dir);
89
- const run = scoped(deps.exec(dir), secrets);
90
- await preflight(run, pkg, tag, sha);
108
+ const run = scoped(deps.exec(dir, timeout), secrets);
109
+ const isPrivate = pkg['private'] === true;
110
+ await preflight(run, pkg, tag, sha, isPrivate);
91
111
  const { id } = pkg.agentage;
92
112
  const g = { dir, tag, sha, repo, id, version: pkg.version, parts: imageParts(pkg) };
93
- const bounded = scoped(deps.exec(dir, timeout), secrets);
94
- const decision = await releaseGuard(g, bounded).catch((e) => fail(`guard: ${e.message}`));
113
+ const decision = await releaseGuard(g, run).catch((e) => fail(`guard: ${e.message}`));
95
114
  const build = decision === 'build';
96
- step(`guard: ${tag} ${build ? 'has no record and no image: build' : 'is released: npm only'}`);
115
+ const done = isPrivate ? 'is released' : 'is released: npm only';
116
+ step(`guard: ${tag} ${build ? 'has no record and no image: build' : done}`);
97
117
  if (build) {
98
118
  await runExtensionBuild({ push: true, dir }, deps.tool);
99
119
  await attach(run, tag, repo);
100
120
  }
101
- await publish(run, pkg, secrets['npm']?.['NPM_TOKEN']);
121
+ if (isPrivate)
122
+ return step('npm: private package, not published');
123
+ const push = scoped(deps.exec(dir, PUBLISH_SECONDS), secrets);
124
+ await publish(run, push, pkg, secrets['npm']?.['NPM_TOKEN']);
102
125
  };
103
126
  export const addRelease = (group, release) => {
104
127
  group
105
128
  .command('release')
106
129
  .description('Release an extension from its v* tag in CI: guard, images, record, npm package')
107
130
  .option('--dir <path>', 'the extension package folder (default: the current folder)')
108
- .option('--timeout <seconds>', 'bound on each GitHub and registry check (default: 60)', Number)
131
+ .option('--timeout <seconds>', 'bound on each git, GitHub and npm check (default: 60; npm publish: 300)', Number)
109
132
  .action(release);
110
133
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agentage/cli",
3
- "version": "0.45.0",
3
+ "version": "0.47.0",
4
4
  "description": "The agentage CLI - connect this machine to agentage from the terminal",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -55,7 +55,7 @@
55
55
  "prepublishOnly": "npm run verify"
56
56
  },
57
57
  "dependencies": {
58
- "@agentage/extension": "^0.5.1",
58
+ "@agentage/extension": "^0.6.0",
59
59
  "chalk": "^6.0.0",
60
60
  "commander": "^15.0.0",
61
61
  "open": "^11.0.0"