@indigoai-us/hq-cli 5.25.0 → 5.25.1
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/.github/workflows/publish.yml +21 -1
- package/dist/commands/pkg-install.js +4 -4
- package/dist/commands/pkg-list.js +4 -4
- package/dist/commands/pkg-remove.js +4 -4
- package/dist/commands/pkg-update.js +4 -4
- package/dist/commands/team-sync.js +4 -4
- package/dist/index.js +11 -2
- package/dist/utils/cognito-session.d.ts +22 -1
- package/dist/utils/cognito-session.js +26 -3
- package/dist/utils/integrity.js +4 -4
- package/dist/utils/registry-client.js +4 -4
- package/dist/utils/version-gate.d.ts +64 -0
- package/dist/utils/version-gate.js +185 -0
- package/package.json +1 -1
- package/src/commands/pkg-install.ts +2 -2
- package/src/commands/pkg-list.ts +2 -2
- package/src/commands/pkg-remove.ts +2 -2
- package/src/commands/pkg-update.ts +2 -2
- package/src/commands/team-sync.ts +2 -2
- package/src/index.ts +12 -0
- package/src/utils/cognito-session.test.ts +56 -1
- package/src/utils/cognito-session.ts +28 -1
- package/src/utils/integrity.ts +2 -2
- package/src/utils/registry-client.ts +2 -2
- package/src/utils/version-gate.test.ts +245 -0
- package/src/utils/version-gate.ts +219 -0
- package/dist/utils/hq-root.d.ts +0 -10
- package/dist/utils/hq-root.js +0 -25
- package/src/utils/hq-root.ts +0 -27
|
@@ -80,9 +80,29 @@ jobs:
|
|
|
80
80
|
echo "published=true" >> "$GITHUB_OUTPUT"
|
|
81
81
|
fi
|
|
82
82
|
|
|
83
|
+
# Sentry sourcemap upload runs on every tag push regardless of whether
|
|
84
|
+
# this run was the one that actually published to npm. The previous
|
|
85
|
+
# `if: steps.publish.outputs.published == 'true'` gate made re-runs
|
|
86
|
+
# useless when only the Sentry upload failed — the publish step's
|
|
87
|
+
# short-circuit (`npm view @ver already published`) zeroed out the
|
|
88
|
+
# output, so the upload step was skipped on every re-run. dist/ is
|
|
89
|
+
# rebuilt by the Build + Inject steps each run, so the upload is
|
|
90
|
+
# always operating on fresh, debug-id-stamped artifacts.
|
|
83
91
|
- name: Upload sourcemaps to Sentry
|
|
84
|
-
if: steps.publish.outputs.published == 'true'
|
|
85
92
|
run: |
|
|
93
|
+
# Defang paste-time whitespace in SENTRY_AUTH_TOKEN. Sentry's
|
|
94
|
+
# auth-header parser returns HTTP 401 "Token string should not
|
|
95
|
+
# contain spaces" when the secret has a trailing \n or any
|
|
96
|
+
# embedded whitespace — observed on the v5.25.0 release after a
|
|
97
|
+
# copy-paste into the GH repo secret carried a literal \n. `tr -d`
|
|
98
|
+
# is the cheap structural guard so the workflow doesn't blow up
|
|
99
|
+
# on the next dirty paste.
|
|
100
|
+
SENTRY_AUTH_TOKEN=$(printf %s "$SENTRY_AUTH_TOKEN" | tr -d '[:space:]')
|
|
101
|
+
if [ -z "$SENTRY_AUTH_TOKEN" ]; then
|
|
102
|
+
echo "::error::SENTRY_AUTH_TOKEN secret is empty (or all whitespace) after sanitization — cannot upload sourcemaps"
|
|
103
|
+
exit 1
|
|
104
|
+
fi
|
|
105
|
+
export SENTRY_AUTH_TOKEN
|
|
86
106
|
VER=$(jq -r .version package.json)
|
|
87
107
|
npx -y @sentry/cli@^2 sourcemaps upload --release "hq-cli@$VER" dist/
|
|
88
108
|
env:
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
* 9. Print next-step message
|
|
14
14
|
*/
|
|
15
15
|
|
|
16
|
-
!function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="
|
|
16
|
+
!function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="c6ab4668-f147-5612-bbfc-e1e80b2f018a")}catch(e){}}();
|
|
17
17
|
import * as fs from 'fs';
|
|
18
18
|
import * as os from 'os';
|
|
19
19
|
import * as path from 'path';
|
|
@@ -21,7 +21,7 @@ import * as yaml from 'js-yaml';
|
|
|
21
21
|
import { execSync } from 'child_process';
|
|
22
22
|
import chalk from 'chalk';
|
|
23
23
|
import { ensureCognitoToken } from '../utils/cognito-session.js';
|
|
24
|
-
import {
|
|
24
|
+
import { resolveDefaultHqRoot } from '../utils/cognito-session.js';
|
|
25
25
|
import { getRegistryUrl, RegistryClient, } from '../utils/registry-client.js';
|
|
26
26
|
import { verifySha256, verifyRsaSignature } from '../utils/integrity.js';
|
|
27
27
|
import { addToRegistry } from '../utils/registry.js';
|
|
@@ -93,7 +93,7 @@ async function installPackage(slug, company) {
|
|
|
93
93
|
}
|
|
94
94
|
}
|
|
95
95
|
// 7. Extract
|
|
96
|
-
const hqRoot =
|
|
96
|
+
const hqRoot = resolveDefaultHqRoot({ onMissing: 'throw' });
|
|
97
97
|
const installDir = path.resolve(hqRoot, 'packages', 'installed', slug);
|
|
98
98
|
// Clean existing installation
|
|
99
99
|
if (fs.existsSync(installDir)) {
|
|
@@ -156,4 +156,4 @@ async function installPackage(slug, company) {
|
|
|
156
156
|
}
|
|
157
157
|
}
|
|
158
158
|
//# sourceMappingURL=pkg-install.js.map
|
|
159
|
-
//# debugId=
|
|
159
|
+
//# debugId=c6ab4668-f147-5612-bbfc-e1e80b2f018a
|
|
@@ -4,9 +4,9 @@
|
|
|
4
4
|
* Graceful offline: if registry is unreachable, show cached data with a note.
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
!function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="
|
|
7
|
+
!function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="2ca922cd-1115-5059-850d-3927226eb9ee")}catch(e){}}();
|
|
8
8
|
import chalk from 'chalk';
|
|
9
|
-
import {
|
|
9
|
+
import { resolveDefaultHqRoot } from '../utils/cognito-session.js';
|
|
10
10
|
import { readRegistry } from '../utils/registry.js';
|
|
11
11
|
import { loadCachedTokens, isExpiring } from '@indigoai-us/hq-cloud';
|
|
12
12
|
import { getRegistryUrl, RegistryClient, } from '../utils/registry-client.js';
|
|
@@ -26,7 +26,7 @@ export function registerPackageListCommand(parent) {
|
|
|
26
26
|
});
|
|
27
27
|
}
|
|
28
28
|
async function listPackages() {
|
|
29
|
-
const hqRoot =
|
|
29
|
+
const hqRoot = resolveDefaultHqRoot({ onMissing: 'throw' });
|
|
30
30
|
const installed = readRegistry(hqRoot);
|
|
31
31
|
// Print installed packages
|
|
32
32
|
if (installed.length > 0) {
|
|
@@ -70,4 +70,4 @@ async function listPackages() {
|
|
|
70
70
|
}
|
|
71
71
|
}
|
|
72
72
|
//# sourceMappingURL=pkg-list.js.map
|
|
73
|
-
//# debugId=
|
|
73
|
+
//# debugId=2ca922cd-1115-5059-850d-3927226eb9ee
|
|
@@ -6,11 +6,11 @@
|
|
|
6
6
|
* 3. Print next-step message
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
-
!function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="
|
|
9
|
+
!function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="e44da498-3e38-5643-b124-49aa2e91f42b")}catch(e){}}();
|
|
10
10
|
import * as fs from 'fs';
|
|
11
11
|
import * as path from 'path';
|
|
12
12
|
import chalk from 'chalk';
|
|
13
|
-
import {
|
|
13
|
+
import { resolveDefaultHqRoot } from '../utils/cognito-session.js';
|
|
14
14
|
import { removeFromRegistry, readRegistry } from '../utils/registry.js';
|
|
15
15
|
export function registerPackageRemoveCommand(parent) {
|
|
16
16
|
parent
|
|
@@ -27,7 +27,7 @@ export function registerPackageRemoveCommand(parent) {
|
|
|
27
27
|
});
|
|
28
28
|
}
|
|
29
29
|
async function removePackage(slug) {
|
|
30
|
-
const hqRoot =
|
|
30
|
+
const hqRoot = resolveDefaultHqRoot({ onMissing: 'throw' });
|
|
31
31
|
const installDir = path.resolve(hqRoot, 'packages', 'installed', slug);
|
|
32
32
|
// Verify it is actually installed
|
|
33
33
|
const entries = readRegistry(hqRoot);
|
|
@@ -53,4 +53,4 @@ async function removePackage(slug) {
|
|
|
53
53
|
console.log(chalk.cyan('Run /package-remove ' + slug + ' in Claude to clean up merged content.'));
|
|
54
54
|
}
|
|
55
55
|
//# sourceMappingURL=pkg-remove.js.map
|
|
56
|
-
//# debugId=
|
|
56
|
+
//# debugId=e44da498-3e38-5643-b124-49aa2e91f42b
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* If slug given: update only that package.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
!function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="
|
|
8
|
+
!function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="35fae6a0-f8e1-5fde-90d8-34ae4adb3c1c")}catch(e){}}();
|
|
9
9
|
import * as fs from 'fs';
|
|
10
10
|
import * as os from 'os';
|
|
11
11
|
import * as path from 'path';
|
|
@@ -13,7 +13,7 @@ import * as yaml from 'js-yaml';
|
|
|
13
13
|
import { execSync } from 'child_process';
|
|
14
14
|
import chalk from 'chalk';
|
|
15
15
|
import { ensureCognitoToken } from '../utils/cognito-session.js';
|
|
16
|
-
import {
|
|
16
|
+
import { resolveDefaultHqRoot } from '../utils/cognito-session.js';
|
|
17
17
|
import { getRegistryUrl, RegistryClient, } from '../utils/registry-client.js';
|
|
18
18
|
import { verifySha256, verifyRsaSignature } from '../utils/integrity.js';
|
|
19
19
|
import { readRegistry, addToRegistry, } from '../utils/registry.js';
|
|
@@ -32,7 +32,7 @@ export function registerPackageUpdateCommand(parent) {
|
|
|
32
32
|
});
|
|
33
33
|
}
|
|
34
34
|
async function updatePackages(slug) {
|
|
35
|
-
const hqRoot =
|
|
35
|
+
const hqRoot = resolveDefaultHqRoot({ onMissing: 'throw' });
|
|
36
36
|
const entries = readRegistry(hqRoot);
|
|
37
37
|
if (entries.length === 0) {
|
|
38
38
|
console.log('No packages installed. Use "hq packages install <slug>" to install one.');
|
|
@@ -124,4 +124,4 @@ async function updatePackages(slug) {
|
|
|
124
124
|
console.log(`\n${updatedCount} package(s) updated${updatedCount > 0 ? '.' : ' — everything is current.'}`);
|
|
125
125
|
}
|
|
126
126
|
//# sourceMappingURL=pkg-update.js.map
|
|
127
|
-
//# debugId=
|
|
127
|
+
//# debugId=35fae6a0-f8e1-5fde-90d8-34ae4adb3c1c
|
|
@@ -10,13 +10,13 @@
|
|
|
10
10
|
* 6. Gracefully handle conflicts (warn, don't force overwrite)
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
|
-
!function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="
|
|
13
|
+
!function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="2b2504c1-06d5-59f8-a9b3-41d1af7f745f")}catch(e){}}();
|
|
14
14
|
import * as fs from 'fs';
|
|
15
15
|
import * as path from 'path';
|
|
16
16
|
import { execSync } from 'child_process';
|
|
17
17
|
import chalk from 'chalk';
|
|
18
18
|
import simpleGit from 'simple-git';
|
|
19
|
-
import {
|
|
19
|
+
import { resolveDefaultHqRoot } from '../utils/cognito-session.js';
|
|
20
20
|
import { ensureCognitoToken } from '../utils/cognito-session.js';
|
|
21
21
|
// ─── API helpers ────────────────────────────────────────────────────────────
|
|
22
22
|
const API_BASE = 'https://example.com/api';
|
|
@@ -308,7 +308,7 @@ export function registerTeamSyncCommand(program) {
|
|
|
308
308
|
.option('--dry-run', 'Show what would be synced without making changes')
|
|
309
309
|
.action(async (options) => {
|
|
310
310
|
try {
|
|
311
|
-
const hqRoot =
|
|
311
|
+
const hqRoot = resolveDefaultHqRoot({ onMissing: 'throw' });
|
|
312
312
|
// 1. Discover team directories
|
|
313
313
|
let teamDirs = discoverTeamDirs(hqRoot);
|
|
314
314
|
if (teamDirs.length === 0) {
|
|
@@ -422,4 +422,4 @@ export function registerTeamSyncCommand(program) {
|
|
|
422
422
|
});
|
|
423
423
|
}
|
|
424
424
|
//# sourceMappingURL=team-sync.js.map
|
|
425
|
-
//# debugId=
|
|
425
|
+
//# debugId=2b2504c1-06d5-59f8-a9b3-41d1af7f745f
|
package/dist/index.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* HQ CLI - Module management, package management, and cloud sync for HQ
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
!function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="
|
|
6
|
+
!function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="0ed956d3-5fde-54aa-96cb-1859a15f7723")}catch(e){}}();
|
|
7
7
|
import { Command } from "commander";
|
|
8
8
|
import { initSentry, Sentry } from "./sentry.js";
|
|
9
9
|
import { registerAddCommand } from "./commands/add.js";
|
|
@@ -37,6 +37,7 @@ import { registerSourcesCommand } from "./commands/sources.js";
|
|
|
37
37
|
import { registerSignalsCommand } from "./commands/signals.js";
|
|
38
38
|
import { sanitizeArgv } from "./utils/feedback-diagnostics.js";
|
|
39
39
|
import { maybeWarnNewVersion, refreshVersionCache, } from "./utils/version-check.js";
|
|
40
|
+
import { enforceVersionGate, shouldSkipGate, } from "./utils/version-gate.js";
|
|
40
41
|
import { CLI_VERSION } from "./cli-version.js";
|
|
41
42
|
// Swallow EPIPE when a downstream reader (e.g. `source <(…)`, `| head`) closes the pipe early.
|
|
42
43
|
const onPipeError = (err) => {
|
|
@@ -126,6 +127,14 @@ registerSignalsCommand(program);
|
|
|
126
127
|
message: sanitizeArgv(process.argv.slice(2)).join(" "),
|
|
127
128
|
level: "info",
|
|
128
129
|
});
|
|
130
|
+
// Hard version gate: ask hq-pro whether this CLI is below the floor and
|
|
131
|
+
// auto-update if so (exits the process on update). Skipped for inspection
|
|
132
|
+
// flags (`--version`, `--help`) so users debugging a broken install can
|
|
133
|
+
// still introspect what they have. Silent on any failure — never blocks
|
|
134
|
+
// the CLI on a flaky network or hq-pro hiccup. See `utils/version-gate.ts`.
|
|
135
|
+
if (!shouldSkipGate(process.argv)) {
|
|
136
|
+
await enforceVersionGate();
|
|
137
|
+
}
|
|
129
138
|
await program.parseAsync();
|
|
130
139
|
}
|
|
131
140
|
catch (err) {
|
|
@@ -137,4 +146,4 @@ registerSignalsCommand(program);
|
|
|
137
146
|
}
|
|
138
147
|
})();
|
|
139
148
|
//# sourceMappingURL=index.js.map
|
|
140
|
-
//# debugId=
|
|
149
|
+
//# debugId=0ed956d3-5fde-54aa-96cb-1859a15f7723
|
|
@@ -45,7 +45,28 @@ export declare const DEFAULT_VAULT_API_URL: string;
|
|
|
45
45
|
* value at registration time, which matches the user's actual cwd at process
|
|
46
46
|
* start. Re-importable as a function for tests and command-time resolution.
|
|
47
47
|
*/
|
|
48
|
-
|
|
48
|
+
/**
|
|
49
|
+
* Resolve the HQ root directory.
|
|
50
|
+
*
|
|
51
|
+
* Resolution order:
|
|
52
|
+
* 1. $HQ_ROOT env var (treated as an explicit assertion by the caller)
|
|
53
|
+
* 2. Walk up from cwd looking for `core.yaml` AND `companies/` siblings
|
|
54
|
+
* 3. Fall back to `~/hq` (or throw, per `opts.onMissing`)
|
|
55
|
+
*
|
|
56
|
+
* `opts.onMissing` controls the third arm:
|
|
57
|
+
* - `'fallback'` (default) — return `~/hq` if no HQ root is found above cwd.
|
|
58
|
+
* This preserves the module-load contract of `DEFAULT_HQ_ROOT`, which
|
|
59
|
+
* several commander.js `.option()` callers pin at registration time.
|
|
60
|
+
* - `'throw'` — throw with a user-actionable error. Used by module-management
|
|
61
|
+
* commands (pkg-install, pkg-remove, pkg-list, pkg-update, team-sync) where
|
|
62
|
+
* a silent default-path miss would silently target the wrong directory.
|
|
63
|
+
*
|
|
64
|
+
* `$HQ_ROOT` short-circuits both arms — if the env var is set, it's used
|
|
65
|
+
* as-is regardless of `onMissing`.
|
|
66
|
+
*/
|
|
67
|
+
export declare function resolveDefaultHqRoot(opts?: {
|
|
68
|
+
onMissing?: "throw" | "fallback";
|
|
69
|
+
}): string;
|
|
49
70
|
export declare const DEFAULT_HQ_ROOT: string;
|
|
50
71
|
/**
|
|
51
72
|
* Return a non-expired Cognito access token, refreshing or browser-logging-in
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
* HQ_VAULT_API_URL — vault-service API Gateway URL
|
|
20
20
|
*/
|
|
21
21
|
|
|
22
|
-
!function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="
|
|
22
|
+
!function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="cd9690e0-c7b0-5e26-a3ee-7f7bc98a0fe2")}catch(e){}}();
|
|
23
23
|
import * as fs from "fs";
|
|
24
24
|
import * as os from "os";
|
|
25
25
|
import * as path from "path";
|
|
@@ -67,7 +67,26 @@ export const DEFAULT_VAULT_API_URL = process.env.HQ_VAULT_API_URL ?? "https://hq
|
|
|
67
67
|
* value at registration time, which matches the user's actual cwd at process
|
|
68
68
|
* start. Re-importable as a function for tests and command-time resolution.
|
|
69
69
|
*/
|
|
70
|
-
|
|
70
|
+
/**
|
|
71
|
+
* Resolve the HQ root directory.
|
|
72
|
+
*
|
|
73
|
+
* Resolution order:
|
|
74
|
+
* 1. $HQ_ROOT env var (treated as an explicit assertion by the caller)
|
|
75
|
+
* 2. Walk up from cwd looking for `core.yaml` AND `companies/` siblings
|
|
76
|
+
* 3. Fall back to `~/hq` (or throw, per `opts.onMissing`)
|
|
77
|
+
*
|
|
78
|
+
* `opts.onMissing` controls the third arm:
|
|
79
|
+
* - `'fallback'` (default) — return `~/hq` if no HQ root is found above cwd.
|
|
80
|
+
* This preserves the module-load contract of `DEFAULT_HQ_ROOT`, which
|
|
81
|
+
* several commander.js `.option()` callers pin at registration time.
|
|
82
|
+
* - `'throw'` — throw with a user-actionable error. Used by module-management
|
|
83
|
+
* commands (pkg-install, pkg-remove, pkg-list, pkg-update, team-sync) where
|
|
84
|
+
* a silent default-path miss would silently target the wrong directory.
|
|
85
|
+
*
|
|
86
|
+
* `$HQ_ROOT` short-circuits both arms — if the env var is set, it's used
|
|
87
|
+
* as-is regardless of `onMissing`.
|
|
88
|
+
*/
|
|
89
|
+
export function resolveDefaultHqRoot(opts = {}) {
|
|
71
90
|
if (process.env.HQ_ROOT)
|
|
72
91
|
return path.resolve(process.env.HQ_ROOT);
|
|
73
92
|
let cur = path.resolve(process.cwd());
|
|
@@ -76,6 +95,10 @@ export function resolveDefaultHqRoot() {
|
|
|
76
95
|
return cur;
|
|
77
96
|
cur = path.dirname(cur);
|
|
78
97
|
}
|
|
98
|
+
if (opts.onMissing === "throw") {
|
|
99
|
+
throw new Error("Could not find HQ root. Run this command from within your HQ directory " +
|
|
100
|
+
"(must contain core.yaml AND a companies/ subdirectory), or set $HQ_ROOT.");
|
|
101
|
+
}
|
|
79
102
|
return path.join(os.homedir(), "hq");
|
|
80
103
|
}
|
|
81
104
|
/** True iff `dir` looks like an HQ root (has core.yaml + companies/ dir). */
|
|
@@ -170,4 +193,4 @@ export async function refreshCachedSession() {
|
|
|
170
193
|
}
|
|
171
194
|
}
|
|
172
195
|
//# sourceMappingURL=cognito-session.js.map
|
|
173
|
-
//# debugId=
|
|
196
|
+
//# debugId=cd9690e0-c7b0-5e26-a3ee-7f7bc98a0fe2
|
package/dist/utils/integrity.js
CHANGED
|
@@ -2,11 +2,11 @@
|
|
|
2
2
|
* Integrity verification — SHA256 hash and RSA signature checks (US-005)
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
-
!function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="
|
|
5
|
+
!function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="820f12b3-4caf-5f08-9add-621628715744")}catch(e){}}();
|
|
6
6
|
import * as crypto from 'crypto';
|
|
7
7
|
import * as fs from 'fs';
|
|
8
8
|
import * as path from 'path';
|
|
9
|
-
import {
|
|
9
|
+
import { resolveDefaultHqRoot } from './cognito-session.js';
|
|
10
10
|
/**
|
|
11
11
|
* Verify a file's SHA256 hash matches the expected value.
|
|
12
12
|
*/
|
|
@@ -29,7 +29,7 @@ export async function verifySha256(filePath, expectedHash) {
|
|
|
29
29
|
*/
|
|
30
30
|
export function verifyRsaSignature(sha256Hash, signature, publicKeyPath) {
|
|
31
31
|
const keyPath = publicKeyPath ??
|
|
32
|
-
path.resolve(
|
|
32
|
+
path.resolve(resolveDefaultHqRoot({ onMissing: 'throw' }), 'packages', '.keys', 'registry-public.pem');
|
|
33
33
|
if (!fs.existsSync(keyPath)) {
|
|
34
34
|
return false;
|
|
35
35
|
}
|
|
@@ -40,4 +40,4 @@ export function verifyRsaSignature(sha256Hash, signature, publicKeyPath) {
|
|
|
40
40
|
return verifier.verify(publicKey, Buffer.from(signature, 'base64'));
|
|
41
41
|
}
|
|
42
42
|
//# sourceMappingURL=integrity.js.map
|
|
43
|
-
//# debugId=
|
|
43
|
+
//# debugId=820f12b3-4caf-5f08-9add-621628715744
|
|
@@ -5,11 +5,11 @@
|
|
|
5
5
|
* Auth tokens are NEVER written to stdout or logs.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
!function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="
|
|
8
|
+
!function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="43d1600f-1bf7-5a52-819b-83e71ba30c73")}catch(e){}}();
|
|
9
9
|
import * as fs from 'fs';
|
|
10
10
|
import * as path from 'path';
|
|
11
11
|
import * as yaml from 'js-yaml';
|
|
12
|
-
import {
|
|
12
|
+
import { resolveDefaultHqRoot } from './cognito-session.js';
|
|
13
13
|
// ---------------------------------------------------------------------------
|
|
14
14
|
// URL helper (unchanged from US-004)
|
|
15
15
|
// ---------------------------------------------------------------------------
|
|
@@ -19,7 +19,7 @@ import { findHqRoot } from './hq-root.js';
|
|
|
19
19
|
* Throws if sources.yaml is missing or has no sources.
|
|
20
20
|
*/
|
|
21
21
|
export function getRegistryUrl() {
|
|
22
|
-
const hqRoot =
|
|
22
|
+
const hqRoot = resolveDefaultHqRoot({ onMissing: 'throw' });
|
|
23
23
|
const sourcesPath = path.join(hqRoot, 'packages', 'sources.yaml');
|
|
24
24
|
if (!fs.existsSync(sourcesPath)) {
|
|
25
25
|
throw new Error(`No packages/sources.yaml found at ${sourcesPath}. Is your HQ packages directory set up?`);
|
|
@@ -102,4 +102,4 @@ export class RegistryClient {
|
|
|
102
102
|
}
|
|
103
103
|
}
|
|
104
104
|
//# sourceMappingURL=registry-client.js.map
|
|
105
|
-
//# debugId=
|
|
105
|
+
//# debugId=43d1600f-1bf7-5a52-819b-83e71ba30c73
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hard version-gate: ask hq-pro whether the current CLI is below the minimum
|
|
3
|
+
* acceptable version and, if so, run `npm install -g …@latest` synchronously
|
|
4
|
+
* before any commander parsing happens. Distinct from the existing
|
|
5
|
+
* `version-check.ts` which is a passive (cached, opt-in) stderr nag against
|
|
6
|
+
* the npm registry.
|
|
7
|
+
*
|
|
8
|
+
* Why both?
|
|
9
|
+
* - `version-check.ts` answers "is there something newer?" by polling npm
|
|
10
|
+
* directly. It's a soft hint, lives on a 24h cache, and never blocks.
|
|
11
|
+
* - `version-gate.ts` answers "is the team currently allowing your version
|
|
12
|
+
* to run?" via an authoritative hq-pro endpoint. The server can yank a
|
|
13
|
+
* known-bad release without waiting for the npm `latest` dist-tag move.
|
|
14
|
+
*
|
|
15
|
+
* The endpoint is reusable across clients (hq-sync, hq-installer, create-hq).
|
|
16
|
+
* See `apps/hq-pro/src/vault-service/handlers/client-version-check.ts` for the
|
|
17
|
+
* source-of-truth table.
|
|
18
|
+
*
|
|
19
|
+
* Trust model: anonymous. The CLI may be running pre-login (e.g. fresh
|
|
20
|
+
* install) so we never send credentials. The endpoint identifies the client
|
|
21
|
+
* by `clientId` + `currentVersion`.
|
|
22
|
+
*
|
|
23
|
+
* Failure mode: silent. Network down, hq-pro returning 5xx, malformed body —
|
|
24
|
+
* the gate must never break the CLI for a user who's otherwise fine. We log
|
|
25
|
+
* to Sentry as a breadcrumb (best-effort) and return.
|
|
26
|
+
*
|
|
27
|
+
* Opt-out: `HQ_NO_UPDATE_CHECK=1` (same env as `version-check.ts` — one knob
|
|
28
|
+
* to silence both check + gate).
|
|
29
|
+
*/
|
|
30
|
+
/**
|
|
31
|
+
* Run the upgrade command in a blocking subprocess. Inherits stdio so the
|
|
32
|
+
* user sees the npm progress. We do NOT auto-rerun the CLI on completion —
|
|
33
|
+
* forcing a re-invocation would run twice on the same process and feel
|
|
34
|
+
* janky; instead we print a clear "rerun your command" message and exit.
|
|
35
|
+
*/
|
|
36
|
+
declare function performUpdate(command: string): {
|
|
37
|
+
ok: boolean;
|
|
38
|
+
detail?: string;
|
|
39
|
+
};
|
|
40
|
+
/**
|
|
41
|
+
* Public entry point. Call before commander parses argv. Blocks the CLI on
|
|
42
|
+
* network IO for up to FETCH_TIMEOUT_MS — acceptable because the alternative
|
|
43
|
+
* (a fire-and-forget background check) gives the user no chance to bail out
|
|
44
|
+
* of a known-bad version before it does damage.
|
|
45
|
+
*
|
|
46
|
+
* `--version` / `-v` callers MUST skip the gate (the user is debugging a
|
|
47
|
+
* broken install and shouldn't be force-upgraded mid-investigation). Caller
|
|
48
|
+
* is responsible for checking argv before invoking us — see index.ts.
|
|
49
|
+
*/
|
|
50
|
+
export declare function enforceVersionGate(): Promise<void>;
|
|
51
|
+
/**
|
|
52
|
+
* Cheap argv pre-check: skip the gate for `--version` / `-V` so users
|
|
53
|
+
* inspecting a broken install can still see what they have without being
|
|
54
|
+
* force-upgraded.
|
|
55
|
+
*/
|
|
56
|
+
export declare function shouldSkipGate(argv: readonly string[]): boolean;
|
|
57
|
+
export declare const __test__: {
|
|
58
|
+
CLIENT_ID: string;
|
|
59
|
+
ENDPOINT_PATH: string;
|
|
60
|
+
FETCH_TIMEOUT_MS: number;
|
|
61
|
+
performUpdate: typeof performUpdate;
|
|
62
|
+
};
|
|
63
|
+
export {};
|
|
64
|
+
//# sourceMappingURL=version-gate.d.ts.map
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hard version-gate: ask hq-pro whether the current CLI is below the minimum
|
|
3
|
+
* acceptable version and, if so, run `npm install -g …@latest` synchronously
|
|
4
|
+
* before any commander parsing happens. Distinct from the existing
|
|
5
|
+
* `version-check.ts` which is a passive (cached, opt-in) stderr nag against
|
|
6
|
+
* the npm registry.
|
|
7
|
+
*
|
|
8
|
+
* Why both?
|
|
9
|
+
* - `version-check.ts` answers "is there something newer?" by polling npm
|
|
10
|
+
* directly. It's a soft hint, lives on a 24h cache, and never blocks.
|
|
11
|
+
* - `version-gate.ts` answers "is the team currently allowing your version
|
|
12
|
+
* to run?" via an authoritative hq-pro endpoint. The server can yank a
|
|
13
|
+
* known-bad release without waiting for the npm `latest` dist-tag move.
|
|
14
|
+
*
|
|
15
|
+
* The endpoint is reusable across clients (hq-sync, hq-installer, create-hq).
|
|
16
|
+
* See `apps/hq-pro/src/vault-service/handlers/client-version-check.ts` for the
|
|
17
|
+
* source-of-truth table.
|
|
18
|
+
*
|
|
19
|
+
* Trust model: anonymous. The CLI may be running pre-login (e.g. fresh
|
|
20
|
+
* install) so we never send credentials. The endpoint identifies the client
|
|
21
|
+
* by `clientId` + `currentVersion`.
|
|
22
|
+
*
|
|
23
|
+
* Failure mode: silent. Network down, hq-pro returning 5xx, malformed body —
|
|
24
|
+
* the gate must never break the CLI for a user who's otherwise fine. We log
|
|
25
|
+
* to Sentry as a breadcrumb (best-effort) and return.
|
|
26
|
+
*
|
|
27
|
+
* Opt-out: `HQ_NO_UPDATE_CHECK=1` (same env as `version-check.ts` — one knob
|
|
28
|
+
* to silence both check + gate).
|
|
29
|
+
*/
|
|
30
|
+
|
|
31
|
+
!function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="d7d58261-f0e4-56bc-b6eb-4117731744f9")}catch(e){}}();
|
|
32
|
+
import { spawnSync } from "node:child_process";
|
|
33
|
+
import chalk from "chalk";
|
|
34
|
+
import { CLI_VERSION } from "../cli-version.js";
|
|
35
|
+
import { DEFAULT_VAULT_API_URL } from "./cognito-session.js";
|
|
36
|
+
const CLIENT_ID = "hq-cli";
|
|
37
|
+
const ENDPOINT_PATH = "/v1/client-version/check";
|
|
38
|
+
const FETCH_TIMEOUT_MS = 3_000;
|
|
39
|
+
function isOptedOut() {
|
|
40
|
+
return process.env.HQ_NO_UPDATE_CHECK === "1";
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Hit POST /v1/client-version/check. Returns the parsed body on 200, or
|
|
44
|
+
* `null` on any failure (caller treats as "no gate"). Tight 3s timeout —
|
|
45
|
+
* a hung server must not delay CLI startup.
|
|
46
|
+
*/
|
|
47
|
+
async function fetchVersionDecision() {
|
|
48
|
+
try {
|
|
49
|
+
const url = `${DEFAULT_VAULT_API_URL}${ENDPOINT_PATH}`;
|
|
50
|
+
const res = await fetch(url, {
|
|
51
|
+
method: "POST",
|
|
52
|
+
headers: {
|
|
53
|
+
"Content-Type": "application/json",
|
|
54
|
+
Accept: "application/json",
|
|
55
|
+
},
|
|
56
|
+
body: JSON.stringify({
|
|
57
|
+
clientId: CLIENT_ID,
|
|
58
|
+
currentVersion: CLI_VERSION,
|
|
59
|
+
platform: `${process.platform}-${process.arch}`,
|
|
60
|
+
}),
|
|
61
|
+
signal: AbortSignal.timeout(FETCH_TIMEOUT_MS),
|
|
62
|
+
});
|
|
63
|
+
if (!res.ok)
|
|
64
|
+
return null;
|
|
65
|
+
const body = (await res.json());
|
|
66
|
+
if (typeof body.minVersion !== "string" ||
|
|
67
|
+
typeof body.latestVersion !== "string" ||
|
|
68
|
+
typeof body.updateRequired !== "boolean") {
|
|
69
|
+
return null;
|
|
70
|
+
}
|
|
71
|
+
return body;
|
|
72
|
+
}
|
|
73
|
+
catch {
|
|
74
|
+
return null;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Run the upgrade command in a blocking subprocess. Inherits stdio so the
|
|
79
|
+
* user sees the npm progress. We do NOT auto-rerun the CLI on completion —
|
|
80
|
+
* forcing a re-invocation would run twice on the same process and feel
|
|
81
|
+
* janky; instead we print a clear "rerun your command" message and exit.
|
|
82
|
+
*/
|
|
83
|
+
function performUpdate(command) {
|
|
84
|
+
const parts = command.split(/\s+/).filter(Boolean);
|
|
85
|
+
if (parts.length === 0)
|
|
86
|
+
return { ok: false, detail: "empty command" };
|
|
87
|
+
const cmd = parts[0];
|
|
88
|
+
const args = parts.slice(1);
|
|
89
|
+
try {
|
|
90
|
+
const result = spawnSync(cmd, args, { stdio: "inherit" });
|
|
91
|
+
if (result.status !== 0) {
|
|
92
|
+
return {
|
|
93
|
+
ok: false,
|
|
94
|
+
detail: `exit ${result.status ?? "signal"}`,
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
return { ok: true };
|
|
98
|
+
}
|
|
99
|
+
catch (err) {
|
|
100
|
+
return { ok: false, detail: err instanceof Error ? err.message : String(err) };
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Soft notify when the server says we're below `latestVersion` but still ≥
|
|
105
|
+
* `minVersion`. Single chalk-yellow line on stderr; never blocks.
|
|
106
|
+
*/
|
|
107
|
+
function nudgeUpdateRecommended(decision) {
|
|
108
|
+
const msg = chalk.yellow(`⚠ A new version of hq-cli is available: ${decision.latestVersion} (current: ${decision.currentVersion}).`);
|
|
109
|
+
console.error(msg);
|
|
110
|
+
if (decision.updateCommand) {
|
|
111
|
+
console.error(chalk.dim(` Update: ${decision.updateCommand}`));
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Hard enforcement when the server says we're below `minVersion`. Print a
|
|
116
|
+
* red banner, attempt the update, then exit so the user reruns against the
|
|
117
|
+
* fresh binary. Sequence chosen so a user with a broken `npm` global prefix
|
|
118
|
+
* still gets a clear error rather than an opaque silent failure.
|
|
119
|
+
*
|
|
120
|
+
* Exit codes:
|
|
121
|
+
* 0 — update succeeded; user must rerun their command
|
|
122
|
+
* 75 — update failed (EX_TEMPFAIL; common for sudo/EACCES on system npm)
|
|
123
|
+
*/
|
|
124
|
+
function enforceUpdateRequired(decision) {
|
|
125
|
+
const banner = chalk.red.bold(`✗ hq-cli ${decision.currentVersion} is below the minimum required version (${decision.minVersion}).`);
|
|
126
|
+
console.error(banner);
|
|
127
|
+
if (decision.message)
|
|
128
|
+
console.error(chalk.dim(` ${decision.message}`));
|
|
129
|
+
const command = decision.updateCommand;
|
|
130
|
+
if (!command) {
|
|
131
|
+
console.error(chalk.red(" No updateCommand provided by hq-pro — see https://hq.indigo.ai/docs/cli-update for manual steps."));
|
|
132
|
+
if (decision.downloadUrl) {
|
|
133
|
+
console.error(chalk.dim(` Download: ${decision.downloadUrl}`));
|
|
134
|
+
}
|
|
135
|
+
process.exit(75);
|
|
136
|
+
}
|
|
137
|
+
console.error(chalk.dim(` Running: ${command}`));
|
|
138
|
+
const result = performUpdate(command);
|
|
139
|
+
if (!result.ok) {
|
|
140
|
+
console.error(chalk.red(`✗ Update failed${result.detail ? `: ${result.detail}` : ""}.`));
|
|
141
|
+
console.error(chalk.dim(` Try manually: ${command}`));
|
|
142
|
+
process.exit(75);
|
|
143
|
+
}
|
|
144
|
+
console.error(chalk.green(`✓ Updated to hq-cli ${decision.latestVersion}. Rerun your command.`));
|
|
145
|
+
process.exit(0);
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* Public entry point. Call before commander parses argv. Blocks the CLI on
|
|
149
|
+
* network IO for up to FETCH_TIMEOUT_MS — acceptable because the alternative
|
|
150
|
+
* (a fire-and-forget background check) gives the user no chance to bail out
|
|
151
|
+
* of a known-bad version before it does damage.
|
|
152
|
+
*
|
|
153
|
+
* `--version` / `-v` callers MUST skip the gate (the user is debugging a
|
|
154
|
+
* broken install and shouldn't be force-upgraded mid-investigation). Caller
|
|
155
|
+
* is responsible for checking argv before invoking us — see index.ts.
|
|
156
|
+
*/
|
|
157
|
+
export async function enforceVersionGate() {
|
|
158
|
+
if (isOptedOut())
|
|
159
|
+
return;
|
|
160
|
+
const decision = await fetchVersionDecision();
|
|
161
|
+
if (!decision)
|
|
162
|
+
return; // best-effort: silent on any failure
|
|
163
|
+
if (decision.updateRequired) {
|
|
164
|
+
enforceUpdateRequired(decision); // exits process
|
|
165
|
+
}
|
|
166
|
+
if (decision.updateRecommended) {
|
|
167
|
+
nudgeUpdateRecommended(decision);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Cheap argv pre-check: skip the gate for `--version` / `-V` so users
|
|
172
|
+
* inspecting a broken install can still see what they have without being
|
|
173
|
+
* force-upgraded.
|
|
174
|
+
*/
|
|
175
|
+
export function shouldSkipGate(argv) {
|
|
176
|
+
return argv.some((a) => a === "--version" || a === "-V" || a === "-v" || a === "--help" || a === "-h");
|
|
177
|
+
}
|
|
178
|
+
export const __test__ = {
|
|
179
|
+
CLIENT_ID,
|
|
180
|
+
ENDPOINT_PATH,
|
|
181
|
+
FETCH_TIMEOUT_MS,
|
|
182
|
+
performUpdate,
|
|
183
|
+
};
|
|
184
|
+
//# sourceMappingURL=version-gate.js.map
|
|
185
|
+
//# debugId=d7d58261-f0e4-56bc-b6eb-4117731744f9
|
package/package.json
CHANGED
|
@@ -21,7 +21,7 @@ import { execSync } from 'child_process';
|
|
|
21
21
|
import { Command } from 'commander';
|
|
22
22
|
import chalk from 'chalk';
|
|
23
23
|
import { ensureCognitoToken } from '../utils/cognito-session.js';
|
|
24
|
-
import {
|
|
24
|
+
import { resolveDefaultHqRoot } from '../utils/cognito-session.js';
|
|
25
25
|
import {
|
|
26
26
|
getRegistryUrl,
|
|
27
27
|
RegistryClient,
|
|
@@ -124,7 +124,7 @@ async function installPackage(
|
|
|
124
124
|
}
|
|
125
125
|
|
|
126
126
|
// 7. Extract
|
|
127
|
-
const hqRoot =
|
|
127
|
+
const hqRoot = resolveDefaultHqRoot({ onMissing: 'throw' });
|
|
128
128
|
const installDir = path.resolve(hqRoot, 'packages', 'installed', slug);
|
|
129
129
|
|
|
130
130
|
// Clean existing installation
|