@skyf0xx/hedgehog 5.1.2 → 5.1.4
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/bin/cli.mjs +73 -8
- package/package.json +1 -1
- package/src/hosts/gemini/gemini-extension.json +1 -1
- package/src/hosts/version.mjs +23 -0
package/bin/cli.mjs
CHANGED
|
@@ -17,7 +17,7 @@ import { cp, mkdir, access, readdir, stat, rm, readFile, writeFile } from 'node:
|
|
|
17
17
|
import { constants, existsSync } from 'node:fs';
|
|
18
18
|
import { fileURLToPath } from 'node:url';
|
|
19
19
|
import { dirname, join, relative, resolve } from 'node:path';
|
|
20
|
-
import { spawn } from 'node:child_process';
|
|
20
|
+
import { spawn, execFileSync } from 'node:child_process';
|
|
21
21
|
import { dbInit, DB_PATH, dbAbsPath, openDb } from '../src/db/init.mjs';
|
|
22
22
|
import { loadCore, lintCore, isModuleAxis } from '../src/db/core.mjs';
|
|
23
23
|
import { planTasks, CORE_INTENT_ID } from '../src/db/plan.mjs';
|
|
@@ -52,7 +52,12 @@ import { rebuildDb } from '../src/db/rebuild.mjs';
|
|
|
52
52
|
import { loadOverrides, addOverride, orphanedOverrides, OVERRIDES_DIR } from '../src/db/overrides.mjs';
|
|
53
53
|
import { HOSTS, HOST_FLAGS, DEFAULT_HOST, availableHosts } from '../src/hosts/index.mjs';
|
|
54
54
|
import { recordHosts, installedHosts } from '../src/hosts/installed.mjs';
|
|
55
|
-
import {
|
|
55
|
+
import {
|
|
56
|
+
recordVersion,
|
|
57
|
+
checkForUpdate,
|
|
58
|
+
checkBinaryStaleness,
|
|
59
|
+
installedVersion,
|
|
60
|
+
} from '../src/hosts/version.mjs';
|
|
56
61
|
import { loadRegistry, resolveCore } from '../src/registry/index.mjs';
|
|
57
62
|
import { fetchCore, cachedCore, cachedVersions } from '../src/registry/fetch.mjs';
|
|
58
63
|
import { recordCore, installedCore } from '../src/registry/installed.mjs';
|
|
@@ -1363,17 +1368,76 @@ async function noteAvailableUpdate() {
|
|
|
1363
1368
|
if (process.env.HEDGEHOG_NO_UPDATE_CHECK) return;
|
|
1364
1369
|
try {
|
|
1365
1370
|
const { installed, latest, stale } = await checkForUpdate(DEST_ROOT);
|
|
1366
|
-
if (
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1371
|
+
if (stale) {
|
|
1372
|
+
console.error(
|
|
1373
|
+
`\n${yellow(bold('Hedgehog update available.'))} ${dim(
|
|
1374
|
+
`${installed} → ${latest}. Run \`npx @skyf0xx/hedgehog@latest update\` to refresh this project's agents and skills.`,
|
|
1375
|
+
)}`,
|
|
1376
|
+
);
|
|
1377
|
+
}
|
|
1372
1378
|
} catch {
|
|
1373
1379
|
// Advisory only — a failed check is never worth failing a command over.
|
|
1374
1380
|
}
|
|
1375
1381
|
}
|
|
1376
1382
|
|
|
1383
|
+
// Hedgehog runs as a global install: every host (Claude Code, Cursor,
|
|
1384
|
+
// Gemini CLI) drives it through the `hedgehog` binary on PATH, and `npx
|
|
1385
|
+
// @skyf0xx/hedgehog` is expected to arrive at that same global install
|
|
1386
|
+
// rather than an ephemeral, unmanaged copy. This makes both halves of
|
|
1387
|
+
// that true on every invocation, before any command's real work starts:
|
|
1388
|
+
//
|
|
1389
|
+
// - **No global install yet** (a fresh `npx` fetch, or a dev checkout
|
|
1390
|
+
// run directly): install the latest release globally now.
|
|
1391
|
+
// - **A global install exists but is behind latest** (the running code's
|
|
1392
|
+
// own version, not any project's stamped `.hedgehog/version.json` —
|
|
1393
|
+
// that stamp answers a different question, whether a project's payload
|
|
1394
|
+
// is current): update it to latest now.
|
|
1395
|
+
//
|
|
1396
|
+
// Either way this reuses `latestVersion`'s day-long cache, so it costs a
|
|
1397
|
+
// network round trip at most once a day, not on every invocation. Runs
|
|
1398
|
+
// unattended — `npm install -g`, no confirmation prompt. Every caller of
|
|
1399
|
+
// this binary is either an agent driving it non-interactively or a human
|
|
1400
|
+
// who ran a command expecting it to just work, and there is no stdin
|
|
1401
|
+
// channel to prompt on in the general case. A failure here (offline,
|
|
1402
|
+
// permission-restricted global dir, npm itself missing) is swallowed the
|
|
1403
|
+
// same as a failed staleness check — the current process keeps running on
|
|
1404
|
+
// whatever code it already loaded rather than failing the command over
|
|
1405
|
+
// an advisory step.
|
|
1406
|
+
//
|
|
1407
|
+
// The install/update itself always runs regardless of `quiet` — silence
|
|
1408
|
+
// governs only whether this prints, matching `boundary --quiet`'s "exit
|
|
1409
|
+
// code only, nothing on either stream" contract for shell hooks.
|
|
1410
|
+
async function ensureGlobalInstall({ quiet = false } = {}) {
|
|
1411
|
+
if (process.env.HEDGEHOG_NO_UPDATE_CHECK) return;
|
|
1412
|
+
try {
|
|
1413
|
+
const globalRoot = execFileSync('npm', ['root', '-g'], {
|
|
1414
|
+
encoding: 'utf8',
|
|
1415
|
+
timeout: 5_000,
|
|
1416
|
+
}).trim();
|
|
1417
|
+
const runningFromGlobal = PKG_ROOT === join(globalRoot, '@skyf0xx/hedgehog');
|
|
1418
|
+
const { latest, stale } = await checkBinaryStaleness(DEST_ROOT, PKG_VERSION);
|
|
1419
|
+
if (runningFromGlobal && !stale) return;
|
|
1420
|
+
if (!latest) return;
|
|
1421
|
+
|
|
1422
|
+
execFileSync('npm', ['install', '-g', `@skyf0xx/hedgehog@${latest}`], {
|
|
1423
|
+
stdio: 'ignore',
|
|
1424
|
+
timeout: 60_000,
|
|
1425
|
+
});
|
|
1426
|
+
if (!quiet) {
|
|
1427
|
+
console.error(
|
|
1428
|
+
`\n${dim(
|
|
1429
|
+
runningFromGlobal
|
|
1430
|
+
? `Updated hedgehog ${PKG_VERSION} → ${latest}. This run continues on ${PKG_VERSION}; the next command picks up ${latest}.`
|
|
1431
|
+
: `Installed hedgehog ${latest} globally. Future commands resolve to it directly.`,
|
|
1432
|
+
)}`,
|
|
1433
|
+
);
|
|
1434
|
+
}
|
|
1435
|
+
} catch {
|
|
1436
|
+
// Advisory only — a failed check or install is never worth failing a
|
|
1437
|
+
// command over.
|
|
1438
|
+
}
|
|
1439
|
+
}
|
|
1440
|
+
|
|
1377
1441
|
async function nextCommand() {
|
|
1378
1442
|
await ensureDb();
|
|
1379
1443
|
|
|
@@ -2716,6 +2780,7 @@ async function main() {
|
|
|
2716
2780
|
console.log(PKG_VERSION);
|
|
2717
2781
|
return;
|
|
2718
2782
|
}
|
|
2783
|
+
await ensureGlobalInstall({ quiet: args.includes('--quiet') });
|
|
2719
2784
|
const cmd = args[0];
|
|
2720
2785
|
const force = args.includes('--force') || args.includes('-f');
|
|
2721
2786
|
|
package/package.json
CHANGED
package/src/hosts/version.mjs
CHANGED
|
@@ -152,3 +152,26 @@ export async function checkForUpdate(root, { force = false } = {}) {
|
|
|
152
152
|
stale: Boolean(installed && latest && isNewer(latest, installed)),
|
|
153
153
|
};
|
|
154
154
|
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Whether the CLI binary actually executing right now — `binaryVersion`,
|
|
158
|
+
* read from this package's own package.json, not from any project's
|
|
159
|
+
* `.hedgehog/version.json` — is behind the newest published release.
|
|
160
|
+
*
|
|
161
|
+
* This is a distinct question from `checkForUpdate`: that one compares a
|
|
162
|
+
* *project's* stamped version against latest, so a shadowed, stale global
|
|
163
|
+
* install (a `hedgehog` binary resolved from PATH ahead of the scoped
|
|
164
|
+
* `npx @skyf0xx/hedgehog` package) reads its own old code, sees the
|
|
165
|
+
* project's stamp already at-or-past whatever it thinks "latest" is, and
|
|
166
|
+
* says nothing — the exact case this function exists to catch instead, by
|
|
167
|
+
* comparing the running code's own version rather than a file the running
|
|
168
|
+
* code could itself be stale relative to.
|
|
169
|
+
*/
|
|
170
|
+
export async function checkBinaryStaleness(root, binaryVersion, { force = false } = {}) {
|
|
171
|
+
const latest = await latestVersion(root, { force });
|
|
172
|
+
return {
|
|
173
|
+
binaryVersion,
|
|
174
|
+
latest,
|
|
175
|
+
stale: Boolean(binaryVersion && latest && isNewer(latest, binaryVersion)),
|
|
176
|
+
};
|
|
177
|
+
}
|