@indigoai-us/hq-cli 5.289.0 → 5.291.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 +61 -0
- package/assets/scaffold/core/scripts/handoff-finalize.sh +670 -0
- package/assets/scaffold/core/scripts/handoff-open-steps.sh +81 -0
- package/assets/scaffold/core/scripts/handoff-post.sh +187 -0
- package/assets/scaffold/core/scripts/hook-lib.sh +345 -50
- package/assets/scaffold/core/scripts/hq-session.sh +182 -21
- package/assets/scaffold/core/scripts/jobs-schedule-parse.sh +310 -0
- package/assets/scaffold/core/scripts/jobs-validate.sh +451 -0
- package/assets/scaffold/core/scripts/lib/session-id.sh +3 -2
- package/assets/scaffold/core/scripts/lib/work-mesh-enqueue.sh +2 -1
- package/assets/scaffold/core/scripts/qmd-reindex-bg.sh +40 -0
- package/dist/command-catalog.generated.d.ts +110 -0
- package/dist/command-catalog.generated.js +136 -0
- package/dist/commands/cloud.d.ts +26 -14
- package/dist/commands/cloud.js +207 -56
- package/dist/commands/core-handoff.d.ts +22 -0
- package/dist/commands/core-handoff.js +1240 -0
- package/dist/commands/core-jobs-native.d.ts +8 -0
- package/dist/commands/core-jobs-native.js +624 -0
- package/dist/commands/core-native-jobs-flag.d.ts +4 -0
- package/dist/commands/core-native-jobs-flag.js +7 -0
- package/dist/commands/core-ontology.d.ts +6 -1
- package/dist/commands/core-ontology.js +42 -7
- package/dist/commands/core-policy.js +24 -5
- package/dist/commands/core.js +15 -1
- package/dist/commands/integrations-core.js +7 -0
- package/dist/commands/lanes.js +111 -35
- package/dist/commands/members.js +5 -0
- package/dist/commands/mesh.js +46 -25
- package/dist/commands/personal-targeted-push-flag.d.ts +4 -0
- package/dist/commands/personal-targeted-push-flag.js +7 -0
- package/dist/commands/personal-targeted-push-paths.d.ts +3 -0
- package/dist/commands/personal-targeted-push-paths.js +67 -0
- package/dist/commands/secrets.js +9 -0
- package/dist/commands/skill.d.ts +3 -0
- package/dist/commands/skill.js +28 -0
- package/dist/fast-core.js +1 -0
- package/dist/lib/billing/free-month.d.ts +18 -0
- package/dist/lib/billing/free-month.js +31 -0
- package/dist/lib/billing/plan-limit-refusals.d.ts +69 -0
- package/dist/lib/billing/plan-limit-refusals.js +187 -0
- package/dist/lib/billing/plan-lock.d.ts +2 -6
- package/dist/lib/billing/plan-lock.js +5 -16
- package/dist/lib/checkpoint-ontology/entity-file.d.ts +21 -0
- package/dist/lib/checkpoint-ontology/entity-file.js +100 -0
- package/dist/lib/checkpoint-ontology/merge-copies.d.ts +34 -2
- package/dist/lib/checkpoint-ontology/merge-copies.js +541 -27
- package/dist/lib/core-utils/secret-patterns.d.ts +3 -1
- package/dist/lib/core-utils/secret-patterns.js +3 -2
- package/dist/lib/flag-registry-worker-source.generated.d.ts +1 -1
- package/dist/lib/flag-registry-worker-source.generated.js +1 -1
- package/dist/lib/lanes/autopilot.d.ts +69 -0
- package/dist/lib/lanes/autopilot.js +418 -0
- package/dist/lib/lanes/mesh-events.js +2 -0
- package/dist/lib/lanes/store.d.ts +4 -2
- package/dist/lib/lanes/store.js +18 -9
- package/dist/lib/mesh/api.js +4 -0
- package/dist/lib/mesh/live/daemon/state.d.ts +7 -1
- package/dist/lib/mesh/live/daemon/state.js +34 -2
- package/dist/main.js +23 -12
- package/dist/utils/plan-gate-error.d.ts +24 -0
- package/dist/utils/plan-gate-error.js +219 -9
- package/dist/utils/self-update.d.ts +3 -0
- package/dist/utils/self-update.js +232 -1
- package/package.json +2 -2
|
@@ -57,10 +57,13 @@
|
|
|
57
57
|
* version-check), `hq rescue --no-self-update`, and the re-exec guard env.
|
|
58
58
|
*/
|
|
59
59
|
import { spawnSync } from "node:child_process";
|
|
60
|
+
import { randomBytes } from "node:crypto";
|
|
61
|
+
import * as fs from "node:fs";
|
|
62
|
+
import * as path from "node:path";
|
|
60
63
|
import semver from "semver";
|
|
61
64
|
import chalk from "chalk";
|
|
62
65
|
import { CLI_NAME, CLI_VERSION } from "../cli-version.js";
|
|
63
|
-
import { buildBunInstallArgv, buildPnpmInstallArgv, buildPrefixedInstallArgv, buildSpawnPlan, checkUpdateConvergence, isLocalDependencyInstall, isPrefixWritable, nonWritablePrefixNote, openInstallOutput, pnpmUpdateEnv, resolveRunningInstall, runUpdateCommand, runSupervisedUpdateCommand, } from "./version-gate.js";
|
|
66
|
+
import { buildBunInstallArgv, buildPnpmInstallArgv, buildPrefixedInstallArgv, buildSpawnPlan, checkUpdateConvergence, derivePnpmHome, isBunManagedPackageDir, isLocalDependencyInstall, isPnpmManagedPackageDir, isPrefixWritable, nonWritablePrefixNote, openInstallOutput, pnpmUpdateEnv, resolveRunningInstall, runUpdateCommand, runSupervisedUpdateCommand, } from "./version-gate.js";
|
|
64
67
|
import { acquireUpdateLock as acquireSharedUpdateLock } from "./update-lock.js";
|
|
65
68
|
import { markLatestIneffective } from "./version-check.js";
|
|
66
69
|
import { restartDaemonServiceIfInstalled } from "../lib/mesh/live/daemon/install.js";
|
|
@@ -71,6 +74,211 @@ import { restartDaemonServiceIfInstalled } from "../lib/mesh/live/daemon/install
|
|
|
71
74
|
export const REEXEC_GUARD_ENV = "HQ_RESCUE_SELF_UPDATED";
|
|
72
75
|
const REGISTRY_URL = `https://registry.npmjs.org/${encodeURIComponent(CLI_NAME)}/latest`;
|
|
73
76
|
const FETCH_TIMEOUT_MS = 3_000;
|
|
77
|
+
/**
|
|
78
|
+
* Move the working CLI package beside itself before asking the package manager
|
|
79
|
+
* to replace it. If the move is unavailable, the caller proceeds without
|
|
80
|
+
* rollback protection. pnpm's .pnpm package tree is immutable store content,
|
|
81
|
+
* so only its selecting public link is moved.
|
|
82
|
+
*/
|
|
83
|
+
function findPnpmPublicLink(packageRoot) {
|
|
84
|
+
const home = derivePnpmHome({ manager: "pnpm", prefix: null, packageRoot });
|
|
85
|
+
if (!home)
|
|
86
|
+
return null;
|
|
87
|
+
let resolvedPackageRoot;
|
|
88
|
+
let storeVersions;
|
|
89
|
+
try {
|
|
90
|
+
resolvedPackageRoot = fs.realpathSync(packageRoot);
|
|
91
|
+
storeVersions = fs.readdirSync(path.join(home, "global"));
|
|
92
|
+
}
|
|
93
|
+
catch {
|
|
94
|
+
return null;
|
|
95
|
+
}
|
|
96
|
+
const packageSegments = CLI_NAME.split("/");
|
|
97
|
+
for (const storeVersion of storeVersions) {
|
|
98
|
+
const storeRoot = path.join(home, "global", storeVersion);
|
|
99
|
+
const candidates = [path.join(storeRoot, "node_modules", ...packageSegments)];
|
|
100
|
+
if (/^v\d+$/.test(storeVersion)) {
|
|
101
|
+
try {
|
|
102
|
+
for (const hash of fs.readdirSync(storeRoot)) {
|
|
103
|
+
candidates.push(path.join(storeRoot, hash, "node_modules", ...packageSegments));
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
catch {
|
|
107
|
+
// Ignore inaccessible pnpm store versions and try the remaining roots.
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
for (const candidate of candidates) {
|
|
111
|
+
if (candidate === packageRoot)
|
|
112
|
+
continue;
|
|
113
|
+
try {
|
|
114
|
+
if (!fs.lstatSync(candidate).isSymbolicLink())
|
|
115
|
+
continue;
|
|
116
|
+
if (fs.realpathSync(candidate) !== resolvedPackageRoot)
|
|
117
|
+
continue;
|
|
118
|
+
return { path: candidate, target: fs.readlinkSync(candidate) };
|
|
119
|
+
}
|
|
120
|
+
catch {
|
|
121
|
+
// A missing or dangling candidate is not the public link for this install.
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
return null;
|
|
126
|
+
}
|
|
127
|
+
function isPathInside(candidate, parent) {
|
|
128
|
+
const relative = path.relative(path.resolve(parent), path.resolve(candidate));
|
|
129
|
+
return relative !== "" && relative !== ".." && !relative.startsWith(`..${path.sep}`) && !path.isAbsolute(relative);
|
|
130
|
+
}
|
|
131
|
+
function isInsideNodeModules(candidate) {
|
|
132
|
+
const segments = path.resolve(candidate).replace(/\\/g, "/").split("/").filter(Boolean);
|
|
133
|
+
const index = process.platform === "win32"
|
|
134
|
+
? segments.map((segment) => segment.toLowerCase()).lastIndexOf("node_modules")
|
|
135
|
+
: segments.lastIndexOf("node_modules");
|
|
136
|
+
return index >= 0 && index < segments.length - 1;
|
|
137
|
+
}
|
|
138
|
+
function isRecognizedGlobalInstall(install, packageRoot) {
|
|
139
|
+
if (!isInsideNodeModules(packageRoot))
|
|
140
|
+
return false;
|
|
141
|
+
if (install.manager === "pnpm")
|
|
142
|
+
return isPnpmManagedPackageDir(packageRoot, true);
|
|
143
|
+
if (install.manager === "bun")
|
|
144
|
+
return isBunManagedPackageDir(packageRoot);
|
|
145
|
+
if (!install.prefix)
|
|
146
|
+
return false;
|
|
147
|
+
const globalNodeModules = process.platform === "win32"
|
|
148
|
+
? path.join(install.prefix, "node_modules")
|
|
149
|
+
: path.join(install.prefix, "lib", "node_modules");
|
|
150
|
+
return isPathInside(packageRoot, globalNodeModules);
|
|
151
|
+
}
|
|
152
|
+
function snapshotPathsAreSafe(snapshot) {
|
|
153
|
+
const paths = [
|
|
154
|
+
snapshot.installPath,
|
|
155
|
+
snapshot.backupPath,
|
|
156
|
+
snapshot.movedPath,
|
|
157
|
+
snapshot.resolvedTarget,
|
|
158
|
+
snapshot.publicLinkPath,
|
|
159
|
+
].filter((value) => value !== undefined);
|
|
160
|
+
return paths.every(isInsideNodeModules);
|
|
161
|
+
}
|
|
162
|
+
function snapshotInstall(install, renamePath = fs.renameSync) {
|
|
163
|
+
const packageRoot = install.packageRoot;
|
|
164
|
+
if (!packageRoot)
|
|
165
|
+
return null;
|
|
166
|
+
let movedPath;
|
|
167
|
+
let backupPath;
|
|
168
|
+
let didMove = false;
|
|
169
|
+
try {
|
|
170
|
+
const entry = fs.lstatSync(packageRoot);
|
|
171
|
+
let sourcePath = packageRoot;
|
|
172
|
+
let symlinkTarget;
|
|
173
|
+
let resolvedTarget;
|
|
174
|
+
if (entry.isSymbolicLink()) {
|
|
175
|
+
symlinkTarget = fs.readlinkSync(packageRoot);
|
|
176
|
+
resolvedTarget = fs.realpathSync(packageRoot);
|
|
177
|
+
sourcePath = resolvedTarget;
|
|
178
|
+
}
|
|
179
|
+
else if (!entry.isDirectory()) {
|
|
180
|
+
return null;
|
|
181
|
+
}
|
|
182
|
+
// The running package root can be a source checkout (for example `npm
|
|
183
|
+
// link` or `node dist/...`). Only recognized global layouts are safe to
|
|
184
|
+
// move; the package manager update itself still proceeds without rollback.
|
|
185
|
+
if (!isRecognizedGlobalInstall(install, sourcePath))
|
|
186
|
+
return null;
|
|
187
|
+
const publicLink = install.manager === "pnpm" && !entry.isSymbolicLink()
|
|
188
|
+
? findPnpmPublicLink(packageRoot)
|
|
189
|
+
: null;
|
|
190
|
+
if (!fs.statSync(sourcePath).isDirectory())
|
|
191
|
+
return null;
|
|
192
|
+
// pnpm's .pnpm tree contains the package store. Moving a package directory
|
|
193
|
+
// out of that tree can invalidate pnpm's global project, so back up only
|
|
194
|
+
// the public link that selects the running store package.
|
|
195
|
+
const isPnpmStorePath = sourcePath.split(path.sep).includes(".pnpm");
|
|
196
|
+
const backupKind = install.manager === "pnpm" && isPnpmStorePath ? "pnpm-public-link" : "package";
|
|
197
|
+
const pnpmPublicLinkPath = publicLink?.path ?? (entry.isSymbolicLink() ? packageRoot : undefined);
|
|
198
|
+
if (backupKind === "pnpm-public-link" && !pnpmPublicLinkPath)
|
|
199
|
+
return null;
|
|
200
|
+
movedPath = backupKind === "pnpm-public-link" ? pnpmPublicLinkPath : sourcePath;
|
|
201
|
+
backupPath = path.join(path.dirname(movedPath), `.hq-cli-backup-${process.pid}-${randomBytes(8).toString("hex")}`);
|
|
202
|
+
renamePath(movedPath, backupPath);
|
|
203
|
+
didMove = true;
|
|
204
|
+
return {
|
|
205
|
+
installPath: packageRoot,
|
|
206
|
+
backupPath,
|
|
207
|
+
movedPath: backupKind === "pnpm-public-link" ? pnpmPublicLinkPath : sourcePath,
|
|
208
|
+
backupKind,
|
|
209
|
+
symlinkTarget,
|
|
210
|
+
resolvedTarget,
|
|
211
|
+
publicLinkPath: backupKind === "pnpm-public-link" ? pnpmPublicLinkPath : publicLink?.path,
|
|
212
|
+
publicLinkTarget: backupKind === "pnpm-public-link"
|
|
213
|
+
? publicLink?.target ?? symlinkTarget
|
|
214
|
+
: publicLink?.target,
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
catch {
|
|
218
|
+
if (didMove && movedPath && backupPath) {
|
|
219
|
+
try {
|
|
220
|
+
fs.renameSync(backupPath, movedPath);
|
|
221
|
+
}
|
|
222
|
+
catch {
|
|
223
|
+
// The update may proceed without rollback if the move cannot be undone.
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
return null;
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
function restoreInstall(snapshot, renamePath = fs.renameSync) {
|
|
230
|
+
if (!snapshotPathsAreSafe(snapshot)) {
|
|
231
|
+
reportFailedRestore(snapshot);
|
|
232
|
+
return false;
|
|
233
|
+
}
|
|
234
|
+
try {
|
|
235
|
+
if (snapshot.backupKind === "pnpm-public-link") {
|
|
236
|
+
const publicLinkPath = snapshot.publicLinkPath ?? snapshot.movedPath;
|
|
237
|
+
fs.rmSync(publicLinkPath, { recursive: true, force: true });
|
|
238
|
+
fs.mkdirSync(path.dirname(publicLinkPath), { recursive: true });
|
|
239
|
+
renamePath(snapshot.backupPath, publicLinkPath);
|
|
240
|
+
return true;
|
|
241
|
+
}
|
|
242
|
+
fs.rmSync(snapshot.installPath, { recursive: true, force: true });
|
|
243
|
+
if (snapshot.symlinkTarget !== undefined && snapshot.resolvedTarget !== undefined) {
|
|
244
|
+
fs.rmSync(snapshot.resolvedTarget, { recursive: true, force: true });
|
|
245
|
+
fs.mkdirSync(path.dirname(snapshot.resolvedTarget), { recursive: true });
|
|
246
|
+
renamePath(snapshot.backupPath, snapshot.resolvedTarget);
|
|
247
|
+
fs.mkdirSync(path.dirname(snapshot.installPath), { recursive: true });
|
|
248
|
+
const target = process.platform === "win32" ? snapshot.resolvedTarget : snapshot.symlinkTarget;
|
|
249
|
+
fs.symlinkSync(target, snapshot.installPath, process.platform === "win32" ? "junction" : "dir");
|
|
250
|
+
}
|
|
251
|
+
else {
|
|
252
|
+
fs.mkdirSync(path.dirname(snapshot.installPath), { recursive: true });
|
|
253
|
+
renamePath(snapshot.backupPath, snapshot.installPath);
|
|
254
|
+
}
|
|
255
|
+
if (snapshot.publicLinkPath && snapshot.publicLinkTarget !== undefined) {
|
|
256
|
+
fs.rmSync(snapshot.publicLinkPath, { recursive: true, force: true });
|
|
257
|
+
fs.mkdirSync(path.dirname(snapshot.publicLinkPath), { recursive: true });
|
|
258
|
+
fs.symlinkSync(snapshot.publicLinkTarget, snapshot.publicLinkPath, process.platform === "win32" ? "junction" : "dir");
|
|
259
|
+
}
|
|
260
|
+
return true;
|
|
261
|
+
}
|
|
262
|
+
catch {
|
|
263
|
+
reportFailedRestore(snapshot);
|
|
264
|
+
return false;
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
function discardInstallSnapshot(snapshot) {
|
|
268
|
+
if (!snapshotPathsAreSafe(snapshot)) {
|
|
269
|
+
console.error(chalk.yellow(`⚠ Could not remove the hq-cli install backup at ${snapshot.backupPath}.`));
|
|
270
|
+
return;
|
|
271
|
+
}
|
|
272
|
+
try {
|
|
273
|
+
fs.rmSync(snapshot.backupPath, { recursive: true, force: true });
|
|
274
|
+
}
|
|
275
|
+
catch {
|
|
276
|
+
console.error(chalk.yellow(`⚠ Could not remove the hq-cli install backup at ${snapshot.backupPath}.`));
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
function reportFailedRestore(snapshot) {
|
|
280
|
+
console.error(chalk.yellow(`⚠ Could not restore the previous hq-cli install; its backup is at ${snapshot.backupPath}.`));
|
|
281
|
+
}
|
|
74
282
|
/** npm `latest` for this package, or null on any failure (offline, 5xx, bad body). */
|
|
75
283
|
async function fetchLatestVersion() {
|
|
76
284
|
try {
|
|
@@ -209,6 +417,11 @@ async function updateAndReexec(argv, flavor, known, deps) {
|
|
|
209
417
|
const releaseLock = flavor.lock ? (deps.acquireLock ?? acquireUpdateLock)() : () => { };
|
|
210
418
|
if (!releaseLock)
|
|
211
419
|
return { action: "skipped", latest };
|
|
420
|
+
const renamePath = deps.renamePath ?? fs.renameSync;
|
|
421
|
+
const installSnapshot = snapshotInstall(install, renamePath);
|
|
422
|
+
if (!installSnapshot) {
|
|
423
|
+
console.error(chalk.dim(" No backup could be made for this hq-cli install; proceeding without rollback protection."));
|
|
424
|
+
}
|
|
212
425
|
let result;
|
|
213
426
|
const plan = buildSelfUpdatePlan(install);
|
|
214
427
|
// A pnpm global install needs PNPM_HOME to find its global bin dir. A
|
|
@@ -219,6 +432,7 @@ async function updateAndReexec(argv, flavor, known, deps) {
|
|
|
219
432
|
// regardless of the parent environment. No-op for npm/bun and when the
|
|
220
433
|
// parent already sets PNPM_HOME.
|
|
221
434
|
const updateEnv = pnpmUpdateEnv(install, env);
|
|
435
|
+
let restoredAfterFailure = false;
|
|
222
436
|
try {
|
|
223
437
|
console.error(chalk.dim(`Updating hq-cli ${current} → ${latest}…`));
|
|
224
438
|
const defaultRunner = flavor.verbose ? runUpdateCommand : runUpdateQuiet;
|
|
@@ -230,11 +444,26 @@ async function updateAndReexec(argv, flavor, known, deps) {
|
|
|
230
444
|
updateEnv === undefined
|
|
231
445
|
? runner(plan.cmd, plan.args)
|
|
232
446
|
: runner(plan.cmd, plan.args, updateEnv);
|
|
447
|
+
if (!result.ok && installSnapshot) {
|
|
448
|
+
restoredAfterFailure = restoreInstall(installSnapshot, renamePath);
|
|
449
|
+
if (restoredAfterFailure)
|
|
450
|
+
discardInstallSnapshot(installSnapshot);
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
catch (error) {
|
|
454
|
+
if (installSnapshot) {
|
|
455
|
+
if (restoreInstall(installSnapshot, renamePath))
|
|
456
|
+
discardInstallSnapshot(installSnapshot);
|
|
457
|
+
}
|
|
458
|
+
throw error;
|
|
233
459
|
}
|
|
234
460
|
finally {
|
|
235
461
|
releaseLock();
|
|
236
462
|
}
|
|
237
463
|
if (!result.ok) {
|
|
464
|
+
if (restoredAfterFailure) {
|
|
465
|
+
console.error(chalk.dim(" The previous hq-cli install was restored."));
|
|
466
|
+
}
|
|
238
467
|
// The one case that still warrants the old yellow banner: we could not get
|
|
239
468
|
// the user onto the new version, so they need to know it exists and how to
|
|
240
469
|
// install it by hand.
|
|
@@ -259,6 +488,8 @@ async function updateAndReexec(argv, flavor, known, deps) {
|
|
|
259
488
|
console.error(chalk.dim(` Continuing the ${flavor.noun} on ${current}.`));
|
|
260
489
|
return { action: "update-failed", latest };
|
|
261
490
|
}
|
|
491
|
+
if (installSnapshot)
|
|
492
|
+
discardInstallSnapshot(installSnapshot);
|
|
262
493
|
// Read-your-writes: a "successful" `npm install -g` proves only that the
|
|
263
494
|
// prefix was rewritten, NOT that the `hq` this user's PATH resolves is the
|
|
264
495
|
// copy we just wrote. When a ghost/shadowing install keeps winning PATH
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@indigoai-us/hq-cli",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.291.0",
|
|
4
4
|
"description": "HQ by Indigo management CLI — modules and cloud sync",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -44,7 +44,7 @@
|
|
|
44
44
|
"dependencies": {
|
|
45
45
|
"@aws-sdk/client-iot-data-plane": "^3.1096.0",
|
|
46
46
|
"@aws-sdk/client-s3": "^3.1049.0",
|
|
47
|
-
"@indigoai-us/hq-cloud": "~6.18.
|
|
47
|
+
"@indigoai-us/hq-cloud": "~6.18.17",
|
|
48
48
|
"@indigoai-us/hq-flags-client": "^0.1.2",
|
|
49
49
|
"@sentry/node": "^10.49.0",
|
|
50
50
|
"@tobilu/qmd": "2.5.3",
|