@babylonjs-toolkit/agent 1.1.2 → 1.1.5
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 +6 -3
- package/bin/bt-agent.js +15 -2
- package/lib/selfupdate.js +109 -23
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# Babylon Toolkit Agent Skills (1.
|
|
1
|
+
# Babylon Toolkit Agent Skills (1.1.5)
|
|
2
2
|
|
|
3
3
|
Universal [Agent Skills](https://agentskills.io) for the `Babylon Toolkit` web game development framework.
|
|
4
4
|
Each `SKILL.md` follows the open standard, so the **same file works unchanged** in Claude Code, Codex CLI, and GitHub Copilot.
|
|
@@ -53,10 +53,13 @@ refresh the persona, and prune skills the new release no longer ships.
|
|
|
53
53
|
|
|
54
54
|
If the upgrade is not possible it says so and continues with what is on disk:
|
|
55
55
|
|
|
56
|
-
- running through `npx`, from a git checkout,
|
|
57
|
-
no global package to upgrade, so it reinstalls the
|
|
56
|
+
- running through `npx`, from a git checkout, a `npm link`ed working copy, or as a
|
|
57
|
+
project dependency — there is no global package to upgrade, so it reinstalls the
|
|
58
|
+
bundled files
|
|
58
59
|
- the registry is unreachable — it reinstalls the version you already have
|
|
59
60
|
- the global install needs elevated permissions — it prints the exact command to run
|
|
61
|
+
- npm reports success but this CLI still runs the old version — you have more than
|
|
62
|
+
one Node/npm on PATH, and it tells you which path was left behind
|
|
60
63
|
|
|
61
64
|
Use `--no-self-update` to skip the npm check entirely.
|
|
62
65
|
|
package/bin/bt-agent.js
CHANGED
|
@@ -166,9 +166,17 @@ function printSelfUpdate(result) {
|
|
|
166
166
|
break;
|
|
167
167
|
case 'upgrade-failed':
|
|
168
168
|
console.log(`\nCould not upgrade ${PACKAGE_NAME} ${result.current} -> ${result.latest}: ${result.reason}`);
|
|
169
|
-
console.log(`Run this yourself (it may need
|
|
169
|
+
console.log(`Run this yourself (it may need elevated permissions): ${result.command}`);
|
|
170
|
+
console.log(` installed at: ${prettyPath(result.root)}`);
|
|
170
171
|
console.log('Reinstalling the currently installed version instead.');
|
|
171
172
|
break;
|
|
173
|
+
case 'upgrade-stale':
|
|
174
|
+
console.log(`\n${result.command} reported success, but this CLI still runs ${result.onDisk} from`);
|
|
175
|
+
console.log(` ${prettyPath(result.root)}`);
|
|
176
|
+
console.log('That path was not updated, so you likely have more than one Node/npm');
|
|
177
|
+
console.log('installation. Check which one is on PATH:');
|
|
178
|
+
console.log(process.platform === 'win32' ? ' where bt-agent && npm root -g' : ' which bt-agent && npm root -g');
|
|
179
|
+
break;
|
|
172
180
|
case 'check-failed':
|
|
173
181
|
console.log(`\nCould not check npm for a newer version: ${result.reason}`);
|
|
174
182
|
console.log(`Reinstalling ${PACKAGE_NAME} ${result.current} from disk.`);
|
|
@@ -260,6 +268,7 @@ function main() {
|
|
|
260
268
|
// payload already on disk — so it could never deliver a new release. Bring
|
|
261
269
|
// the package itself up to date first, then hand over to the new CLI.
|
|
262
270
|
let selfResult = null;
|
|
271
|
+
let selfFailed = false;
|
|
263
272
|
if (opts.selfUpdate) {
|
|
264
273
|
selfResult = selfUpdate({
|
|
265
274
|
dryRun: opts.dryRun,
|
|
@@ -269,6 +278,10 @@ function main() {
|
|
|
269
278
|
});
|
|
270
279
|
if (!opts.json) printSelfUpdate(selfResult);
|
|
271
280
|
|
|
281
|
+
// The files were installed correctly, but the release the user asked for
|
|
282
|
+
// is not the one now running — that is a failure worth a non-zero exit.
|
|
283
|
+
selfFailed = selfResult.status === 'upgrade-stale' || selfResult.status === 'upgrade-failed';
|
|
284
|
+
|
|
272
285
|
if (selfResult.status === 'upgraded') {
|
|
273
286
|
const passthrough = process.argv.slice(2).filter((a) => a !== 'update');
|
|
274
287
|
const code = reexec(['install', ...passthrough], selfResult.latest);
|
|
@@ -299,7 +312,7 @@ function main() {
|
|
|
299
312
|
console.log('\nDry run — nothing was written.\n');
|
|
300
313
|
}
|
|
301
314
|
}
|
|
302
|
-
process.exit(opts.dryRun || check.ok ? 0 : 1);
|
|
315
|
+
process.exit(opts.dryRun || (check.ok && !selfFailed) ? 0 : 1);
|
|
303
316
|
break;
|
|
304
317
|
}
|
|
305
318
|
|
package/lib/selfupdate.js
CHANGED
|
@@ -22,10 +22,11 @@ function segments(p) {
|
|
|
22
22
|
/**
|
|
23
23
|
* Where is this copy of the CLI running from?
|
|
24
24
|
*
|
|
25
|
-
* `update` may only shell out to a package manager when the answer is a
|
|
26
|
-
* install. An npx run already fetched the newest version, a git checkout
|
|
27
|
-
* developer's own working tree, and a project dependency belongs
|
|
28
|
-
* lockfile — upgrading any of those
|
|
25
|
+
* `update` may only shell out to a package manager when the answer is a real
|
|
26
|
+
* global install. An npx run already fetched the newest version, a git checkout or
|
|
27
|
+
* `npm link` is the developer's own working tree, and a project dependency belongs
|
|
28
|
+
* to that project's lockfile — upgrading any of those would write files nobody
|
|
29
|
+
* asked for, and in the linked case would silently replace the link.
|
|
29
30
|
*/
|
|
30
31
|
function detectInstall() {
|
|
31
32
|
const parts = segments(PACKAGE_ROOT);
|
|
@@ -38,6 +39,13 @@ function detectInstall() {
|
|
|
38
39
|
return { kind: 'npx', manager: null, root: PACKAGE_ROOT };
|
|
39
40
|
}
|
|
40
41
|
|
|
42
|
+
// Every installed copy — global or project — lives under a node_modules
|
|
43
|
+
// directory. Anything else is a working copy reached through a symlink
|
|
44
|
+
// (`npm link`, `npm i -g <dir>`); upgrading it would delete the link.
|
|
45
|
+
if (!lower.includes('node_modules')) {
|
|
46
|
+
return { kind: 'linked', manager: null, root: PACKAGE_ROOT };
|
|
47
|
+
}
|
|
48
|
+
|
|
41
49
|
const manager = lower.includes('pnpm') || lower.includes('.pnpm')
|
|
42
50
|
? 'pnpm'
|
|
43
51
|
: lower.includes('.yarn') || lower.includes('yarn')
|
|
@@ -46,31 +54,43 @@ function detectInstall() {
|
|
|
46
54
|
? 'bun'
|
|
47
55
|
: 'npm';
|
|
48
56
|
|
|
49
|
-
return { kind:
|
|
57
|
+
return { kind: globalKind(), manager, root: PACKAGE_ROOT };
|
|
50
58
|
}
|
|
51
59
|
|
|
52
|
-
|
|
53
|
-
function isGlobal() {
|
|
54
|
-
const roots = [];
|
|
60
|
+
function npmGlobalRoot() {
|
|
55
61
|
try {
|
|
56
|
-
|
|
62
|
+
return String(runManager('npm', ['root', '-g'], { encoding: 'utf8', timeout: 20000 })).trim() || null;
|
|
57
63
|
} catch {
|
|
58
|
-
|
|
64
|
+
return null;
|
|
59
65
|
}
|
|
66
|
+
}
|
|
60
67
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
68
|
+
const norm = (p) => path.resolve(p).toLowerCase().replace(/[\\/]+$/, '');
|
|
69
|
+
|
|
70
|
+
/** 'global' | 'linked' | 'local' for a package that is not npx or a git checkout. */
|
|
71
|
+
function globalKind() {
|
|
72
|
+
const root = npmGlobalRoot();
|
|
73
|
+
|
|
74
|
+
if (root) {
|
|
75
|
+
const entry = path.join(root, ...PACKAGE_NAME.split('/'));
|
|
76
|
+
try {
|
|
77
|
+
// `npm link` and `npm i -g <dir>` leave a symlink to the developer's tree.
|
|
78
|
+
// Upgrading that would delete the link and hide their working copy.
|
|
79
|
+
if (fs.lstatSync(entry).isSymbolicLink() && norm(fs.realpathSync(entry)) === norm(PACKAGE_ROOT)) {
|
|
80
|
+
return 'linked';
|
|
81
|
+
}
|
|
82
|
+
} catch {
|
|
83
|
+
/* this package is not installed under that prefix */
|
|
84
|
+
}
|
|
85
|
+
if (norm(PACKAGE_ROOT).startsWith(norm(root) + path.sep.toLowerCase())) return 'global';
|
|
86
|
+
}
|
|
64
87
|
|
|
65
88
|
// pnpm/yarn/bun global stores never match `npm root -g`; recognise their shapes,
|
|
66
89
|
// and treat anything outside the current project tree as global rather than local.
|
|
67
90
|
const parts = segments(PACKAGE_ROOT).map((s) => s.toLowerCase());
|
|
68
|
-
if (parts.includes('.pnpm') || parts.includes('.yarn') || parts.includes('.bun')) return
|
|
69
|
-
return !here.startsWith(norm(process.cwd()) + path.sep.toLowerCase());
|
|
70
|
-
}
|
|
91
|
+
if (parts.includes('.pnpm') || parts.includes('.yarn') || parts.includes('.bun')) return 'global';
|
|
71
92
|
|
|
72
|
-
|
|
73
|
-
return IS_WINDOWS ? 'npm.cmd' : 'npm';
|
|
93
|
+
return norm(PACKAGE_ROOT).startsWith(norm(process.cwd()) + path.sep.toLowerCase()) ? 'local' : 'global';
|
|
74
94
|
}
|
|
75
95
|
|
|
76
96
|
function managerBin(manager) {
|
|
@@ -78,6 +98,50 @@ function managerBin(manager) {
|
|
|
78
98
|
return IS_WINDOWS && name !== 'bun' ? `${name}.cmd` : name;
|
|
79
99
|
}
|
|
80
100
|
|
|
101
|
+
/**
|
|
102
|
+
* npm's own JS entry point, shipped beside the node binary.
|
|
103
|
+
*
|
|
104
|
+
* Running it with `process.execPath` is the one invocation that behaves the same
|
|
105
|
+
* on every platform. It matters most on Windows: since the CVE-2024-27980 fix,
|
|
106
|
+
* Node refuses to `execFile` a `.cmd` shim without `shell: true` and throws
|
|
107
|
+
* EINVAL — which is exactly why `update` reported an upgrade it never performed.
|
|
108
|
+
*/
|
|
109
|
+
function npmCliPath() {
|
|
110
|
+
const dir = path.dirname(process.execPath);
|
|
111
|
+
const candidates = [
|
|
112
|
+
process.env.npm_execpath,
|
|
113
|
+
path.join(dir, 'node_modules', 'npm', 'bin', 'npm-cli.js'),
|
|
114
|
+
path.join(dir, '..', 'lib', 'node_modules', 'npm', 'bin', 'npm-cli.js'),
|
|
115
|
+
path.join(dir, '..', 'node_modules', 'npm', 'bin', 'npm-cli.js'),
|
|
116
|
+
];
|
|
117
|
+
for (const c of candidates) {
|
|
118
|
+
if (c && c.endsWith('.js') && fs.existsSync(c)) return path.resolve(c);
|
|
119
|
+
}
|
|
120
|
+
return null;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/** Quote an argument for cmd.exe, which is what `shell: true` uses on Windows. */
|
|
124
|
+
function shellQuote(arg) {
|
|
125
|
+
return /[\s"^&|<>()]/.test(arg) ? `"${arg.replace(/"/g, '""')}"` : arg;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Run a package manager command portably.
|
|
130
|
+
*
|
|
131
|
+
* npm goes through its JS entry point when we can find it; everything else falls
|
|
132
|
+
* back to the platform shim, with `shell: true` on Windows so a `.cmd` is allowed.
|
|
133
|
+
*/
|
|
134
|
+
function runManager(manager, args, opts = {}) {
|
|
135
|
+
if (manager === 'npm') {
|
|
136
|
+
const cli = npmCliPath();
|
|
137
|
+
if (cli) return execFileSync(process.execPath, [cli, ...args], opts);
|
|
138
|
+
}
|
|
139
|
+
if (IS_WINDOWS && manager !== 'bun') {
|
|
140
|
+
return execFileSync(managerBin(manager), args.map(shellQuote), { ...opts, shell: true });
|
|
141
|
+
}
|
|
142
|
+
return execFileSync(managerBin(manager), args, opts);
|
|
143
|
+
}
|
|
144
|
+
|
|
81
145
|
/** The exact command a user can copy/paste if the automatic upgrade fails. */
|
|
82
146
|
function upgradeCommand(manager, version) {
|
|
83
147
|
const spec = `${PACKAGE_NAME}@${version || 'latest'}`;
|
|
@@ -122,7 +186,7 @@ function compareVersions(a, b) {
|
|
|
122
186
|
*/
|
|
123
187
|
function fetchLatestVersion({ timeout = 20000 } = {}) {
|
|
124
188
|
try {
|
|
125
|
-
const out =
|
|
189
|
+
const out = runManager('npm', ['view', PACKAGE_NAME, 'version'], {
|
|
126
190
|
encoding: 'utf8',
|
|
127
191
|
timeout,
|
|
128
192
|
stdio: ['ignore', 'pipe', 'pipe'],
|
|
@@ -161,6 +225,15 @@ function fetchLatestFromRegistrySync(timeout) {
|
|
|
161
225
|
}
|
|
162
226
|
}
|
|
163
227
|
|
|
228
|
+
/** Version of the package as it now sits on disk, read fresh (not the require cache). */
|
|
229
|
+
function installedVersionOnDisk() {
|
|
230
|
+
try {
|
|
231
|
+
return JSON.parse(fs.readFileSync(path.join(PACKAGE_ROOT, 'package.json'), 'utf8')).version || null;
|
|
232
|
+
} catch {
|
|
233
|
+
return null;
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
164
237
|
/**
|
|
165
238
|
* Bring the installed package itself up to date.
|
|
166
239
|
*
|
|
@@ -183,13 +256,14 @@ function selfUpdate({ dryRun = false, timeout = 20000, log = () => {} } = {}) {
|
|
|
183
256
|
npx: 'running via npx — npx already fetched the latest version',
|
|
184
257
|
dev: 'running from a git checkout — nothing to upgrade',
|
|
185
258
|
local: 'installed as a project dependency — upgrade it through that project',
|
|
259
|
+
linked: `linked to ${PACKAGE_ROOT} — upgrade that working copy instead`,
|
|
186
260
|
}[install.kind];
|
|
187
261
|
return { ...base, status: 'skipped', reason: why };
|
|
188
262
|
}
|
|
189
263
|
|
|
190
264
|
log(`Checking npm for a newer ${PACKAGE_NAME} (installed: ${CURRENT_VERSION})...`);
|
|
191
265
|
const { version: latest, error } = fetchLatestVersion({ timeout });
|
|
192
|
-
if (!latest) return { ...base, status: 'check-failed', reason: error };
|
|
266
|
+
if (!latest) return { ...base, status: 'check-failed', reason: error, root: PACKAGE_ROOT };
|
|
193
267
|
|
|
194
268
|
if (compareVersions(latest, CURRENT_VERSION) <= 0) {
|
|
195
269
|
return { ...base, latest, status: 'current' };
|
|
@@ -200,16 +274,25 @@ function selfUpdate({ dryRun = false, timeout = 20000, log = () => {} } = {}) {
|
|
|
200
274
|
|
|
201
275
|
log(`Upgrading ${CURRENT_VERSION} -> ${latest} (${command})`);
|
|
202
276
|
try {
|
|
203
|
-
|
|
277
|
+
runManager(install.manager, upgradeArgs(install.manager, latest), {
|
|
204
278
|
stdio: ['ignore', 'inherit', 'inherit'],
|
|
205
279
|
timeout: 10 * 60 * 1000,
|
|
206
280
|
});
|
|
207
281
|
} catch (err) {
|
|
208
282
|
const reason = err && err.message ? String(err.message).split('\n')[0] : 'upgrade command failed';
|
|
209
|
-
return { ...base, latest, status: 'upgrade-failed', command, reason };
|
|
283
|
+
return { ...base, latest, status: 'upgrade-failed', command, reason, root: PACKAGE_ROOT };
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
// Trust the files, not the exit code. A machine with more than one npm prefix
|
|
287
|
+
// (nvm-windows, a second Node install) happily reports success while installing
|
|
288
|
+
// somewhere this CLI does not run from — which looks exactly like "update did
|
|
289
|
+
// nothing", so name it instead of claiming an upgrade that did not land here.
|
|
290
|
+
const onDisk = installedVersionOnDisk();
|
|
291
|
+
if (onDisk && compareVersions(onDisk, CURRENT_VERSION) <= 0) {
|
|
292
|
+
return { ...base, latest, status: 'upgrade-stale', command, onDisk, root: PACKAGE_ROOT };
|
|
210
293
|
}
|
|
211
294
|
|
|
212
|
-
return { ...base, latest, status: 'upgraded', command };
|
|
295
|
+
return { ...base, latest, status: 'upgraded', command, onDisk };
|
|
213
296
|
}
|
|
214
297
|
|
|
215
298
|
/**
|
|
@@ -231,6 +314,9 @@ function reexec(argv, version) {
|
|
|
231
314
|
|
|
232
315
|
module.exports = {
|
|
233
316
|
PACKAGE_NAME,
|
|
317
|
+
runManager,
|
|
318
|
+
npmCliPath,
|
|
319
|
+
installedVersionOnDisk,
|
|
234
320
|
CURRENT_VERSION,
|
|
235
321
|
GUARD_ENV,
|
|
236
322
|
detectInstall,
|
package/package.json
CHANGED