@indigoai-us/hq-cli 5.60.0 → 5.62.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/dist/commands/agents.d.ts +109 -0
- package/dist/commands/agents.js +385 -0
- package/dist/commands/db-migrate.d.ts +6 -0
- package/dist/commands/db-migrate.js +42 -0
- package/dist/commands/db-provision.d.ts +15 -0
- package/dist/commands/db-provision.js +78 -0
- package/dist/commands/db-sql.d.ts +9 -0
- package/dist/commands/db-sql.js +81 -0
- package/dist/commands/db-status.d.ts +7 -0
- package/dist/commands/db-status.js +70 -0
- package/dist/commands/db.d.ts +9 -0
- package/dist/commands/db.js +23 -0
- package/dist/commands/integrations.d.ts +78 -0
- package/dist/commands/integrations.js +309 -0
- package/dist/commands/members.js +4 -4
- package/dist/commands/outposts.d.ts +60 -0
- package/dist/commands/outposts.js +255 -0
- package/dist/commands/pack-install.d.ts +7 -1
- package/dist/commands/pack-install.js +86 -15
- package/dist/commands/packs.d.ts +2 -1
- package/dist/commands/packs.js +13 -8
- package/dist/commands/secrets.d.ts +13 -0
- package/dist/commands/secrets.js +149 -10
- package/dist/commands/skill.d.ts +153 -0
- package/dist/commands/skill.js +593 -0
- package/dist/commands/workers.d.ts +48 -0
- package/dist/commands/workers.js +229 -0
- package/dist/index.d.ts +5 -3
- package/dist/index.js +14 -240
- package/dist/lib/db/control-plane.d.ts +45 -0
- package/dist/lib/db/control-plane.js +81 -0
- package/dist/lib/db/local.d.ts +49 -0
- package/dist/lib/db/local.js +106 -0
- package/dist/lib/db/migrate.d.ts +41 -0
- package/dist/lib/db/migrate.js +104 -0
- package/dist/lib/db/paths.d.ts +56 -0
- package/dist/lib/db/paths.js +103 -0
- package/dist/lib/db/remote-engine.d.ts +58 -0
- package/dist/lib/db/remote-engine.js +90 -0
- package/dist/lib/db/remote-sql.d.ts +22 -0
- package/dist/lib/db/remote-sql.js +39 -0
- package/dist/lib/db/sql.d.ts +49 -0
- package/dist/lib/db/sql.js +132 -0
- package/dist/main.d.ts +7 -0
- package/dist/main.js +272 -0
- package/dist/utils/cognito-session.js +3 -3
- package/dist/utils/sandbox-runner-client.d.ts +13 -0
- package/dist/utils/sandbox-runner-client.js +83 -6
- package/dist/utils/version-check.d.ts +6 -0
- package/dist/utils/version-check.js +78 -2
- package/package.json +9 -1
- package/pnpm-workspace.yaml +2 -0
- package/src/commands/agents.test.ts +297 -0
- package/src/commands/agents.ts +561 -0
- package/src/commands/db-migrate.ts +55 -0
- package/src/commands/db-provision.ts +102 -0
- package/src/commands/db-sql.ts +124 -0
- package/src/commands/db-status.ts +100 -0
- package/src/commands/db.ts +26 -0
- package/src/commands/integrations.test.ts +284 -0
- package/src/commands/integrations.ts +438 -0
- package/src/commands/members.ts +2 -2
- package/src/commands/outposts.test.ts +177 -0
- package/src/commands/outposts.ts +338 -0
- package/src/commands/pack-install.ts +115 -18
- package/src/commands/pack-update-cache.test.ts +149 -0
- package/src/commands/packs.ts +28 -7
- package/src/commands/secrets.parse-destination.test.ts +38 -0
- package/src/commands/secrets.test.ts +342 -0
- package/src/commands/secrets.ts +227 -13
- package/src/commands/skill.test.ts +770 -0
- package/src/commands/skill.ts +796 -0
- package/src/commands/workers.test.ts +158 -0
- package/src/commands/workers.ts +298 -0
- package/src/index.test.ts +32 -0
- package/src/index.ts +11 -274
- package/src/lib/db/control-plane.test.ts +59 -0
- package/src/lib/db/control-plane.ts +113 -0
- package/src/lib/db/local.test.ts +81 -0
- package/src/lib/db/local.ts +148 -0
- package/src/lib/db/migrate.test.ts +133 -0
- package/src/lib/db/migrate.ts +137 -0
- package/src/lib/db/paths.test.ts +112 -0
- package/src/lib/db/paths.ts +128 -0
- package/src/lib/db/remote-engine.test.ts +44 -0
- package/src/lib/db/remote-engine.ts +148 -0
- package/src/lib/db/remote-sql.test.ts +32 -0
- package/src/lib/db/remote-sql.ts +62 -0
- package/src/lib/db/sql.test.ts +106 -0
- package/src/lib/db/sql.ts +192 -0
- package/src/main.ts +314 -0
- package/src/utils/cognito-session.ts +1 -1
- package/src/utils/sandbox-runner-client.test.ts +128 -0
- package/src/utils/sandbox-runner-client.ts +100 -4
- package/src/utils/version-check.test.ts +30 -0
- package/src/utils/version-check.ts +72 -0
- package/test/commands/db-tenant-isolation.test.ts +94 -0
- package/test/commands/db.test.ts +85 -0
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `hq outposts` — manage your personal HQ Outposts (EC2 boxes) from the
|
|
3
|
+
* terminal instead of the web console. Targets the hq-pro `/outpost/*`
|
|
4
|
+
* control plane on `DEFAULT_VAULT_API_URL` via the shared `vaultApiFetch`
|
|
5
|
+
* helper — the same routes the console's outpost panel calls.
|
|
6
|
+
*
|
|
7
|
+
* Outposts are PERSONAL / caller-scoped: hq-pro keys every `/outpost/*` route
|
|
8
|
+
* on the caller's Cognito sub, so there is no `--company`. `--id <outpostId>`
|
|
9
|
+
* selects a specific box (passed as the `outpostId` query param); when omitted
|
|
10
|
+
* hq-pro targets the caller's primary slot.
|
|
11
|
+
*
|
|
12
|
+
* Subcommands:
|
|
13
|
+
* hq outposts list — every Outpost you own (row summaries)
|
|
14
|
+
* hq outposts status [--id] — live detail for one box
|
|
15
|
+
* hq outposts codex-enable [--id] — enable / retry Codex on the box
|
|
16
|
+
* hq outposts login [--id] — request a fresh login URL
|
|
17
|
+
* hq outposts destroy [--id] --yes — tear the box down (destructive; flag-guarded)
|
|
18
|
+
*
|
|
19
|
+
* NOTE: hq-pro exposes no rename or settings-mutation route for Outposts (the
|
|
20
|
+
* web console can't rename them either), so this CLI wraps only the lifecycle
|
|
21
|
+
* and status routes that exist. Renaming an Outpost is not a backend capability.
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
!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]="b01c6828-502e-5f7a-b15c-acafef4fa143")}catch(e){}}();
|
|
25
|
+
import chalk from "chalk";
|
|
26
|
+
import { ensureCognitoToken } from "../utils/cognito-session.js";
|
|
27
|
+
import { vaultApiFetch } from "../utils/vault-api.js";
|
|
28
|
+
/** A non-2xx from the `/outpost/*` control plane. Carries status + `step`. */
|
|
29
|
+
export class OutpostHttpError extends Error {
|
|
30
|
+
status;
|
|
31
|
+
step;
|
|
32
|
+
constructor(status, message, step) {
|
|
33
|
+
super(message);
|
|
34
|
+
this.name = "OutpostHttpError";
|
|
35
|
+
this.status = status;
|
|
36
|
+
this.step = step;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Authenticated JSON round-trip against the outpost control plane. Throws
|
|
41
|
+
* `OutpostHttpError` on any non-2xx (never swallows — hq-never-swallow-errors),
|
|
42
|
+
* decoding hq-pro's `{ error | message, step }` envelope for the reason. The
|
|
43
|
+
* `step` is preserved so callers can recognise the `destroy` route's
|
|
44
|
+
* `teardown-incomplete` 409 (which means "retry", not "failed").
|
|
45
|
+
*/
|
|
46
|
+
export async function outpostRequest(opts) {
|
|
47
|
+
const res = await vaultApiFetch(opts);
|
|
48
|
+
if (!res.ok) {
|
|
49
|
+
const body = (await res.json().catch(() => ({})));
|
|
50
|
+
const message = typeof body.message === "string"
|
|
51
|
+
? body.message
|
|
52
|
+
: typeof body.error === "string"
|
|
53
|
+
? body.error
|
|
54
|
+
: res.statusText;
|
|
55
|
+
throw new OutpostHttpError(res.status, message, body.step);
|
|
56
|
+
}
|
|
57
|
+
return (await res.json());
|
|
58
|
+
}
|
|
59
|
+
export async function listOutposts(token) {
|
|
60
|
+
const data = await outpostRequest({
|
|
61
|
+
token,
|
|
62
|
+
path: "/outpost/list",
|
|
63
|
+
});
|
|
64
|
+
return data.outposts ?? [];
|
|
65
|
+
}
|
|
66
|
+
export async function getOutpostStatus(token, outpostId) {
|
|
67
|
+
return outpostRequest({
|
|
68
|
+
token,
|
|
69
|
+
path: "/outpost/status",
|
|
70
|
+
query: outpostId ? { outpostId } : undefined,
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
export async function enableCodex(token, outpostId) {
|
|
74
|
+
return outpostRequest({
|
|
75
|
+
token,
|
|
76
|
+
path: "/outpost/codex/enable",
|
|
77
|
+
method: "POST",
|
|
78
|
+
query: outpostId ? { outpostId } : undefined,
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
export async function regenerateLoginUrl(token, outpostId) {
|
|
82
|
+
return outpostRequest({
|
|
83
|
+
token,
|
|
84
|
+
path: "/outpost/regenerate-login-url",
|
|
85
|
+
method: "POST",
|
|
86
|
+
query: outpostId ? { outpostId } : undefined,
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
export async function destroyOutpost(token, outpostId) {
|
|
90
|
+
return outpostRequest({
|
|
91
|
+
token,
|
|
92
|
+
path: "/outpost/destroy",
|
|
93
|
+
method: "POST",
|
|
94
|
+
query: outpostId ? { outpostId } : undefined,
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
// ---------------------------------------------------------------------------
|
|
98
|
+
// Command registration
|
|
99
|
+
// ---------------------------------------------------------------------------
|
|
100
|
+
function fail(err) {
|
|
101
|
+
if (err instanceof OutpostHttpError) {
|
|
102
|
+
console.error(chalk.red(err.message));
|
|
103
|
+
}
|
|
104
|
+
else {
|
|
105
|
+
console.error(chalk.red("Error:"), err instanceof Error ? err.message : String(err));
|
|
106
|
+
}
|
|
107
|
+
process.exit(1);
|
|
108
|
+
}
|
|
109
|
+
/** Print a top-level object as `key: value`, JSON-ifying nested values. */
|
|
110
|
+
function printKeyValues(obj) {
|
|
111
|
+
for (const [k, v] of Object.entries(obj)) {
|
|
112
|
+
const rendered = v && typeof v === "object" ? JSON.stringify(v) : String(v);
|
|
113
|
+
console.log(`${chalk.bold(k)}: ${rendered}`);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
export function registerOutpostsCommand(program) {
|
|
117
|
+
const outposts = program
|
|
118
|
+
.command("outposts")
|
|
119
|
+
.description("Manage your personal HQ Outposts (EC2 boxes)");
|
|
120
|
+
outposts
|
|
121
|
+
.command("list")
|
|
122
|
+
.description("List every Outpost you own")
|
|
123
|
+
.option("--json", "Emit raw JSON")
|
|
124
|
+
.action(async function (opts) {
|
|
125
|
+
try {
|
|
126
|
+
const token = await ensureCognitoToken();
|
|
127
|
+
const rows = await listOutposts(token);
|
|
128
|
+
if (opts.json) {
|
|
129
|
+
process.stdout.write(JSON.stringify(rows, null, 2) + "\n");
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
if (rows.length === 0) {
|
|
133
|
+
console.log(chalk.gray("You don't own any Outposts yet."));
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
const idW = Math.max(2, ...rows.map((r) => (r.outpostId ?? "").length));
|
|
137
|
+
const stateW = Math.max(5, ...rows.map((r) => (r.state ?? "").length));
|
|
138
|
+
const nameW = Math.max(4, ...rows.map((r) => (r.instanceName ?? "").length));
|
|
139
|
+
const regionW = Math.max(6, ...rows.map((r) => (r.region ?? "").length));
|
|
140
|
+
const rtW = Math.max(7, ...rows.map((r) => (r.agentRuntime ?? "").length));
|
|
141
|
+
console.log(chalk.bold([
|
|
142
|
+
"ID".padEnd(idW),
|
|
143
|
+
"STATE".padEnd(stateW),
|
|
144
|
+
"INSTANCE".padEnd(nameW),
|
|
145
|
+
"REGION".padEnd(regionW),
|
|
146
|
+
"RUNTIME".padEnd(rtW),
|
|
147
|
+
"PLATFORM",
|
|
148
|
+
].join(" ")));
|
|
149
|
+
for (const r of rows) {
|
|
150
|
+
console.log([
|
|
151
|
+
(r.outpostId ?? "").padEnd(idW),
|
|
152
|
+
(r.state ?? "").padEnd(stateW),
|
|
153
|
+
(r.instanceName ?? "").padEnd(nameW),
|
|
154
|
+
(r.region ?? "").padEnd(regionW),
|
|
155
|
+
(r.agentRuntime ?? "").padEnd(rtW),
|
|
156
|
+
r.platform ?? "",
|
|
157
|
+
].join(" "));
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
catch (err) {
|
|
161
|
+
fail(err);
|
|
162
|
+
}
|
|
163
|
+
});
|
|
164
|
+
outposts
|
|
165
|
+
.command("status")
|
|
166
|
+
.description("Show live detail for one Outpost")
|
|
167
|
+
.option("--id <outpostId>", "Outpost id (defaults to your primary box)")
|
|
168
|
+
.option("--json", "Emit raw JSON")
|
|
169
|
+
.action(async function (opts) {
|
|
170
|
+
try {
|
|
171
|
+
const token = await ensureCognitoToken();
|
|
172
|
+
const status = await getOutpostStatus(token, opts.id);
|
|
173
|
+
if (opts.json) {
|
|
174
|
+
process.stdout.write(JSON.stringify(status, null, 2) + "\n");
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
printKeyValues(status);
|
|
178
|
+
}
|
|
179
|
+
catch (err) {
|
|
180
|
+
fail(err);
|
|
181
|
+
}
|
|
182
|
+
});
|
|
183
|
+
outposts
|
|
184
|
+
.command("codex-enable")
|
|
185
|
+
.description("Enable (or retry) Codex on an Outpost")
|
|
186
|
+
.option("--id <outpostId>", "Outpost id (defaults to your primary box)")
|
|
187
|
+
.option("--json", "Emit raw JSON")
|
|
188
|
+
.action(async function (opts) {
|
|
189
|
+
try {
|
|
190
|
+
const token = await ensureCognitoToken();
|
|
191
|
+
const result = await enableCodex(token, opts.id);
|
|
192
|
+
if (opts.json) {
|
|
193
|
+
process.stdout.write(JSON.stringify(result, null, 2) + "\n");
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
console.log(chalk.green("Codex enablement requested for the Outpost."));
|
|
197
|
+
}
|
|
198
|
+
catch (err) {
|
|
199
|
+
fail(err);
|
|
200
|
+
}
|
|
201
|
+
});
|
|
202
|
+
outposts
|
|
203
|
+
.command("login")
|
|
204
|
+
.description("Request a fresh login URL for an Outpost")
|
|
205
|
+
.option("--id <outpostId>", "Outpost id (defaults to your primary box)")
|
|
206
|
+
.option("--json", "Emit raw JSON")
|
|
207
|
+
.action(async function (opts) {
|
|
208
|
+
try {
|
|
209
|
+
const token = await ensureCognitoToken();
|
|
210
|
+
const result = await regenerateLoginUrl(token, opts.id);
|
|
211
|
+
if (opts.json) {
|
|
212
|
+
process.stdout.write(JSON.stringify(result, null, 2) + "\n");
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
console.log(chalk.green("Login-URL regeneration requested. The box mints a fresh URL shortly — " +
|
|
216
|
+
"check `hq outposts status` to pick it up."));
|
|
217
|
+
}
|
|
218
|
+
catch (err) {
|
|
219
|
+
fail(err);
|
|
220
|
+
}
|
|
221
|
+
});
|
|
222
|
+
outposts
|
|
223
|
+
.command("destroy")
|
|
224
|
+
.description("Tear down (permanently destroy) an Outpost")
|
|
225
|
+
.option("--id <outpostId>", "Outpost id (defaults to your primary box)")
|
|
226
|
+
.option("--yes", "Confirm the irreversible teardown (required)")
|
|
227
|
+
.action(async function (opts) {
|
|
228
|
+
const target = opts.id ?? "your primary Outpost";
|
|
229
|
+
if (!opts.yes) {
|
|
230
|
+
console.error(chalk.yellow(`This will permanently destroy ${target} and delete its cloud resources. ` +
|
|
231
|
+
`This cannot be undone.\n` +
|
|
232
|
+
`Re-run with --yes to confirm: hq outposts destroy${opts.id ? ` --id ${opts.id}` : ""} --yes`));
|
|
233
|
+
process.exit(1);
|
|
234
|
+
}
|
|
235
|
+
try {
|
|
236
|
+
const token = await ensureCognitoToken();
|
|
237
|
+
await destroyOutpost(token, opts.id);
|
|
238
|
+
console.log(chalk.green(`Destroyed ${target}.`));
|
|
239
|
+
}
|
|
240
|
+
catch (err) {
|
|
241
|
+
// A 409 teardown-incomplete is not a failure — the gateway's 30s cap
|
|
242
|
+
// fired while the Lambda keeps working. The row is preserved and the
|
|
243
|
+
// operation is idempotent, so tell the caller to retry.
|
|
244
|
+
if (err instanceof OutpostHttpError &&
|
|
245
|
+
(err.status === 409 || err.step === "teardown-incomplete")) {
|
|
246
|
+
console.log(chalk.yellow(`Teardown still in progress for ${target} — this is expected for large boxes. ` +
|
|
247
|
+
`Re-run the same destroy command to finish (it's idempotent).`));
|
|
248
|
+
return;
|
|
249
|
+
}
|
|
250
|
+
fail(err);
|
|
251
|
+
}
|
|
252
|
+
});
|
|
253
|
+
}
|
|
254
|
+
//# sourceMappingURL=outposts.js.map
|
|
255
|
+
//# debugId=b01c6828-502e-5f7a-b15c-acafef4fa143
|
|
@@ -36,6 +36,12 @@
|
|
|
36
36
|
import { type KeyObject } from 'node:crypto';
|
|
37
37
|
import { type SecretResolver } from './mcp-registration.js';
|
|
38
38
|
import type { PackManifest } from '../types.js';
|
|
39
|
+
export interface ResolveLatestOptions {
|
|
40
|
+
forceRefresh?: boolean;
|
|
41
|
+
now?: number;
|
|
42
|
+
cacheTtlMs?: number;
|
|
43
|
+
fetchImpl?: typeof fetch;
|
|
44
|
+
}
|
|
39
45
|
export type Transport = 'npm' | 'git' | 'local' | 'marketplace';
|
|
40
46
|
/** Prefix that routes a source through the HQ marketplace transport (US-006). */
|
|
41
47
|
export declare const MARKETPLACE_PREFIX = "marketplace:";
|
|
@@ -181,7 +187,7 @@ export interface LatestResult {
|
|
|
181
187
|
* @param source the stamped `source:` from the installed package.yaml
|
|
182
188
|
* @param installedVersion the installed pack's manifest `version` (npm compare)
|
|
183
189
|
*/
|
|
184
|
-
export declare function resolveLatest(source: string, installedVersion?: string): LatestResult
|
|
190
|
+
export declare function resolveLatest(source: string, installedVersion?: string, opts?: ResolveLatestOptions): Promise<LatestResult>;
|
|
185
191
|
/**
|
|
186
192
|
* Async marketplace update probe (US-006): resolve the slug's latest approved
|
|
187
193
|
* listing version and compare it to the installed version. Reuses the same
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
* from each pack's package.yaml; rationale lives in the layout-fix PR.)
|
|
35
35
|
*/
|
|
36
36
|
|
|
37
|
-
!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]="
|
|
37
|
+
!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]="a90272ca-94a1-5905-85f8-af12a01cb58a")}catch(e){}}();
|
|
38
38
|
import * as fs from 'fs';
|
|
39
39
|
import * as os from 'os';
|
|
40
40
|
import * as path from 'path';
|
|
@@ -54,6 +54,58 @@ import { safeExtractTarball } from './safe-extract.js';
|
|
|
54
54
|
import { vaultApiFetchPublic } from '../utils/vault-api.js';
|
|
55
55
|
import { redactSecrets, SECRET_REDACTION, registerMcpServers, McpManifestError, } from './mcp-registration.js';
|
|
56
56
|
import { readCache, listSecretCacheScopes } from '../utils/secrets-cache.js';
|
|
57
|
+
const PACK_UPDATE_CACHE_TTL_MS = 12 * 60 * 60 * 1000;
|
|
58
|
+
const PACK_UPDATE_FETCH_TIMEOUT_MS = 3_000;
|
|
59
|
+
const gitLsRemoteMemo = new Map();
|
|
60
|
+
function packUpdateCachePath() {
|
|
61
|
+
return path.join(os.homedir(), '.hq', 'pack-update-cache.json');
|
|
62
|
+
}
|
|
63
|
+
function readPackUpdateCache() {
|
|
64
|
+
try {
|
|
65
|
+
const parsed = JSON.parse(fs.readFileSync(packUpdateCachePath(), 'utf-8'));
|
|
66
|
+
if (!parsed || typeof parsed !== 'object' || !parsed.entries || typeof parsed.entries !== 'object') {
|
|
67
|
+
return { entries: {} };
|
|
68
|
+
}
|
|
69
|
+
return { entries: parsed.entries };
|
|
70
|
+
}
|
|
71
|
+
catch {
|
|
72
|
+
return { entries: {} };
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
function writePackUpdateCache(cache) {
|
|
76
|
+
try {
|
|
77
|
+
const file = packUpdateCachePath();
|
|
78
|
+
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
79
|
+
fs.writeFileSync(file, JSON.stringify(cache));
|
|
80
|
+
}
|
|
81
|
+
catch {
|
|
82
|
+
// best-effort; update checks must never fail because the cache is unwritable
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
function cachedLatest(cacheKey, opts) {
|
|
86
|
+
if (opts.forceRefresh)
|
|
87
|
+
return undefined;
|
|
88
|
+
const entry = readPackUpdateCache().entries[cacheKey];
|
|
89
|
+
if (!entry || typeof entry.latest !== 'string' || typeof entry.fetchedAt !== 'number')
|
|
90
|
+
return undefined;
|
|
91
|
+
const now = opts.now ?? Date.now();
|
|
92
|
+
const ttl = opts.cacheTtlMs ?? PACK_UPDATE_CACHE_TTL_MS;
|
|
93
|
+
return now - entry.fetchedAt <= ttl ? entry.latest : undefined;
|
|
94
|
+
}
|
|
95
|
+
function storeCachedLatest(cacheKey, latest, opts) {
|
|
96
|
+
const cache = readPackUpdateCache();
|
|
97
|
+
cache.entries[cacheKey] = { latest, fetchedAt: opts.now ?? Date.now() };
|
|
98
|
+
writePackUpdateCache(cache);
|
|
99
|
+
}
|
|
100
|
+
async function latestWithDiskCache(cacheKey, opts, refresh) {
|
|
101
|
+
const cached = cachedLatest(cacheKey, opts);
|
|
102
|
+
if (cached)
|
|
103
|
+
return cached;
|
|
104
|
+
const latest = await refresh();
|
|
105
|
+
if (latest)
|
|
106
|
+
storeCachedLatest(cacheKey, latest, opts);
|
|
107
|
+
return latest;
|
|
108
|
+
}
|
|
57
109
|
/** Prefix that routes a source through the HQ marketplace transport (US-006). */
|
|
58
110
|
export const MARKETPLACE_PREFIX = 'marketplace:';
|
|
59
111
|
export function classify(source) {
|
|
@@ -489,13 +541,33 @@ function rsyncDir(src, dest) {
|
|
|
489
541
|
*/
|
|
490
542
|
function isNamedRef(url, ref) {
|
|
491
543
|
try {
|
|
492
|
-
const out =
|
|
544
|
+
const out = gitLsRemote(['--heads', '--tags', url, ref]);
|
|
493
545
|
return out.trim().length > 0;
|
|
494
546
|
}
|
|
495
547
|
catch {
|
|
496
548
|
return false;
|
|
497
549
|
}
|
|
498
550
|
}
|
|
551
|
+
function gitLsRemote(args) {
|
|
552
|
+
const key = args.join('\0');
|
|
553
|
+
const cached = gitLsRemoteMemo.get(key);
|
|
554
|
+
if (cached !== undefined)
|
|
555
|
+
return cached;
|
|
556
|
+
const out = execFileSync('git', ['ls-remote', ...args], { encoding: 'utf-8', stdio: ['ignore', 'pipe', 'ignore'] });
|
|
557
|
+
gitLsRemoteMemo.set(key, out);
|
|
558
|
+
return out;
|
|
559
|
+
}
|
|
560
|
+
async function fetchLatestNpmVersion(pkg, opts) {
|
|
561
|
+
const fetchImpl = opts.fetchImpl ?? fetch;
|
|
562
|
+
const res = await fetchImpl(`https://registry.npmjs.org/${encodeURIComponent(pkg)}/latest`, {
|
|
563
|
+
headers: { Accept: 'application/json' },
|
|
564
|
+
signal: AbortSignal.timeout(PACK_UPDATE_FETCH_TIMEOUT_MS),
|
|
565
|
+
});
|
|
566
|
+
if (!res.ok)
|
|
567
|
+
throw new Error(`registry returned ${res.status}`);
|
|
568
|
+
const body = (await res.json());
|
|
569
|
+
return typeof body.version === 'string' ? body.version : undefined;
|
|
570
|
+
}
|
|
499
571
|
/** Extract the ref (sha or named ref) recorded in a stamped git source. */
|
|
500
572
|
function gitRefFromSource(source) {
|
|
501
573
|
const { subpath, ref } = parseGitFragment(source);
|
|
@@ -504,6 +576,9 @@ function gitRefFromSource(source) {
|
|
|
504
576
|
void subpath;
|
|
505
577
|
return ref;
|
|
506
578
|
}
|
|
579
|
+
function isFullGitSha(ref) {
|
|
580
|
+
return /^[0-9a-f]{40}$/i.test(ref);
|
|
581
|
+
}
|
|
507
582
|
/**
|
|
508
583
|
* Probe whether a newer version of an already-installed pack is available,
|
|
509
584
|
* WITHOUT fetching or installing. Reuses the same git/npm primitives as the
|
|
@@ -513,7 +588,7 @@ function gitRefFromSource(source) {
|
|
|
513
588
|
* @param source the stamped `source:` from the installed package.yaml
|
|
514
589
|
* @param installedVersion the installed pack's manifest `version` (npm compare)
|
|
515
590
|
*/
|
|
516
|
-
export function resolveLatest(source, installedVersion) {
|
|
591
|
+
export async function resolveLatest(source, installedVersion, opts = {}) {
|
|
517
592
|
let transport;
|
|
518
593
|
try {
|
|
519
594
|
transport = classify(source);
|
|
@@ -542,15 +617,12 @@ export function resolveLatest(source, installedVersion) {
|
|
|
542
617
|
const pkg = stripVersion(source);
|
|
543
618
|
const current = installedVersion ?? (source.lastIndexOf('@') > 0 ? source.slice(source.lastIndexOf('@') + 1) : undefined);
|
|
544
619
|
try {
|
|
545
|
-
const latest =
|
|
546
|
-
encoding: 'utf-8',
|
|
547
|
-
stdio: ['ignore', 'pipe', 'ignore'],
|
|
548
|
-
}).trim();
|
|
620
|
+
const latest = await latestWithDiskCache(`npm:${pkg}`, opts, () => fetchLatestNpmVersion(pkg, opts));
|
|
549
621
|
const updateAvailable = current && latest ? semverGt(latest, current) : null;
|
|
550
622
|
return { transport, current, latest, updateAvailable };
|
|
551
623
|
}
|
|
552
624
|
catch (e) {
|
|
553
|
-
return { transport, current, updateAvailable: null, error: `npm
|
|
625
|
+
return { transport, current, updateAvailable: null, error: `npm registry check failed: ${e.message}` };
|
|
554
626
|
}
|
|
555
627
|
}
|
|
556
628
|
// git
|
|
@@ -565,13 +637,12 @@ export function resolveLatest(source, installedVersion) {
|
|
|
565
637
|
const current = gitRefFromSource(source);
|
|
566
638
|
// If install followed a named ref (branch/tag), compare that ref's tip;
|
|
567
639
|
// otherwise (default SHA-pin) compare the default branch HEAD.
|
|
568
|
-
const refArg = current && isNamedRef(url, current) ? current : 'HEAD';
|
|
640
|
+
const refArg = current && !isFullGitSha(current) && isNamedRef(url, current) ? current : 'HEAD';
|
|
569
641
|
try {
|
|
570
|
-
const
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
})
|
|
574
|
-
const latest = out.split(/\s+/)[0] || undefined;
|
|
642
|
+
const latest = await latestWithDiskCache(`git:${url}#${refArg}`, opts, () => {
|
|
643
|
+
const out = gitLsRemote([url, refArg]).trim();
|
|
644
|
+
return out.split(/\s+/)[0] || undefined;
|
|
645
|
+
});
|
|
575
646
|
const updateAvailable = current && latest ? !latest.startsWith(current) && !current.startsWith(latest) : null;
|
|
576
647
|
return { transport, current, latest, updateAvailable };
|
|
577
648
|
}
|
|
@@ -1629,4 +1700,4 @@ export async function installPack(source, opts = {}) {
|
|
|
1629
1700
|
}
|
|
1630
1701
|
}
|
|
1631
1702
|
//# sourceMappingURL=pack-install.js.map
|
|
1632
|
-
//# debugId=
|
|
1703
|
+
//# debugId=a90272ca-94a1-5905-85f8-af12a01cb58a
|
package/dist/commands/packs.d.ts
CHANGED
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
* Spec: knowledge/public/hq-core/package-yaml-spec.md.
|
|
19
19
|
*/
|
|
20
20
|
import { Command } from 'commander';
|
|
21
|
+
import { type LatestResult } from './pack-install.js';
|
|
21
22
|
import { type InstalledPack, type LinkStatus } from '../utils/pack-contributions.js';
|
|
22
23
|
import type { PackContributeKey } from '../types.js';
|
|
23
24
|
interface InstalledPackView {
|
|
@@ -48,7 +49,7 @@ interface InstalledPackView {
|
|
|
48
49
|
};
|
|
49
50
|
error?: string;
|
|
50
51
|
}
|
|
51
|
-
export declare function buildInstalledView(hqRoot: string, hqVersion: string | null, pack: InstalledPack, installedSources: Set<string>, checkUpdates: boolean): InstalledPackView;
|
|
52
|
+
export declare function buildInstalledView(hqRoot: string, hqVersion: string | null, pack: InstalledPack, installedSources: Set<string>, checkUpdates: boolean, latestProbe?: LatestResult): InstalledPackView;
|
|
52
53
|
export declare function registerPacksCommand(parent: Command): void;
|
|
53
54
|
export {};
|
|
54
55
|
//# sourceMappingURL=packs.d.ts.map
|
package/dist/commands/packs.js
CHANGED
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
* Spec: knowledge/public/hq-core/package-yaml-spec.md.
|
|
19
19
|
*/
|
|
20
20
|
|
|
21
|
-
!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]="
|
|
21
|
+
!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]="f8ee3de6-a097-5e0f-8afa-63381f29032d")}catch(e){}}();
|
|
22
22
|
import * as fs from 'fs';
|
|
23
23
|
import * as path from 'path';
|
|
24
24
|
import * as readline from 'readline';
|
|
@@ -61,7 +61,7 @@ async function confirm(question) {
|
|
|
61
61
|
});
|
|
62
62
|
return /^(y|yes)$/i.test(answer.trim());
|
|
63
63
|
}
|
|
64
|
-
export function buildInstalledView(hqRoot, hqVersion, pack, installedSources, checkUpdates) {
|
|
64
|
+
export function buildInstalledView(hqRoot, hqVersion, pack, installedSources, checkUpdates, latestProbe) {
|
|
65
65
|
if (!pack.manifest) {
|
|
66
66
|
return {
|
|
67
67
|
name: pack.name,
|
|
@@ -95,7 +95,7 @@ export function buildInstalledView(hqRoot, hqVersion, pack, installedSources, ch
|
|
|
95
95
|
const hqCoreSatisfied = hqVersion && requiresHqCore ? semverSatisfies(hqVersion, requiresHqCore) : null;
|
|
96
96
|
let updateAvailable = null;
|
|
97
97
|
if (checkUpdates && m.source) {
|
|
98
|
-
updateAvailable =
|
|
98
|
+
updateAvailable = latestProbe?.updateAvailable ?? null;
|
|
99
99
|
}
|
|
100
100
|
// US-005 — surface the pack's `initialization` block so the HQ Sync
|
|
101
101
|
// "Installed" panel can render its get-started affordance. `readPackManifest`
|
|
@@ -129,13 +129,16 @@ export function buildInstalledView(hqRoot, hqVersion, pack, installedSources, ch
|
|
|
129
129
|
...(initialization ? { initialization } : {}),
|
|
130
130
|
};
|
|
131
131
|
}
|
|
132
|
-
function buildListView(hqRoot, checkUpdates, evalConditionals) {
|
|
132
|
+
async function buildListView(hqRoot, checkUpdates, evalConditionals, refreshUpdates) {
|
|
133
133
|
const hqVersion = readHqVersion(hqRoot);
|
|
134
134
|
const packs = listInstalledPacks(hqRoot);
|
|
135
135
|
const catalog = readRecommendedPackages(hqRoot);
|
|
136
136
|
const catalogSources = new Set(catalog.map((c) => c.source));
|
|
137
137
|
const warnings = [];
|
|
138
|
-
const
|
|
138
|
+
const latestProbes = await Promise.all(packs.map((p) => checkUpdates && p.manifest?.source
|
|
139
|
+
? resolveLatest(p.manifest.source, p.manifest.version, { forceRefresh: refreshUpdates })
|
|
140
|
+
: Promise.resolve(undefined)));
|
|
141
|
+
const installed = packs.map((p, i) => buildInstalledView(hqRoot, hqVersion, p, catalogSources, checkUpdates, latestProbes[i]));
|
|
139
142
|
for (const p of installed) {
|
|
140
143
|
if (p.error)
|
|
141
144
|
warnings.push(`${p.name}: ${p.error}`);
|
|
@@ -217,7 +220,7 @@ async function runUpdate(name, opts) {
|
|
|
217
220
|
// other transport keeps the existing synchronous probe unchanged.
|
|
218
221
|
const probe = safeClassify(source) === 'marketplace'
|
|
219
222
|
? await resolveLatestMarketplace(source, m.version)
|
|
220
|
-
: resolveLatest(source, m.version);
|
|
223
|
+
: await resolveLatest(source, m.version, { forceRefresh: true });
|
|
221
224
|
const base = {
|
|
222
225
|
name: pname,
|
|
223
226
|
transport: probe.transport,
|
|
@@ -374,10 +377,11 @@ export function registerPacksCommand(parent) {
|
|
|
374
377
|
.option('--json', 'Machine-readable JSON output')
|
|
375
378
|
.option('--hq-root <path>', 'HQ root (default: auto-detect)')
|
|
376
379
|
.option('--check-updates', 'Probe each pack for available updates (network I/O)')
|
|
380
|
+
.option('--refresh', 'Bypass cached update probes')
|
|
377
381
|
.option('--eval-conditionals', 'Evaluate catalog conditional predicates (runs bash)')
|
|
378
382
|
.action(async (opts) => {
|
|
379
383
|
try {
|
|
380
|
-
const view = buildListView(resolveRoot(opts), !!opts.checkUpdates, !!opts.evalConditionals);
|
|
384
|
+
const view = await buildListView(resolveRoot(opts), !!opts.checkUpdates, !!opts.evalConditionals, !!opts.refresh);
|
|
381
385
|
if (wantsJson(opts))
|
|
382
386
|
emitJson(view);
|
|
383
387
|
else
|
|
@@ -394,6 +398,7 @@ export function registerPacksCommand(parent) {
|
|
|
394
398
|
.option('--json', 'Machine-readable JSON output')
|
|
395
399
|
.option('--hq-root <path>', 'HQ root (default: auto-detect)')
|
|
396
400
|
.option('--check-only', 'Report availability without installing')
|
|
401
|
+
.option('--refresh', 'Bypass cached update probes (update refreshes by default)')
|
|
397
402
|
.option('-y, --yes', 'Non-interactive (implies --allow-hooks and --allow-mcp)')
|
|
398
403
|
.option('--allow-hooks', 'Install pack hooks without prompting')
|
|
399
404
|
.option('--allow-mcp', 'Register pack MCP servers without prompting')
|
|
@@ -463,4 +468,4 @@ export function registerPacksCommand(parent) {
|
|
|
463
468
|
});
|
|
464
469
|
}
|
|
465
470
|
//# sourceMappingURL=packs.js.map
|
|
466
|
-
//# debugId=
|
|
471
|
+
//# debugId=f8ee3de6-a097-5e0f-8afa-63381f29032d
|
|
@@ -36,6 +36,19 @@ export interface SecretLoadResponse {
|
|
|
36
36
|
message?: string;
|
|
37
37
|
}>;
|
|
38
38
|
}
|
|
39
|
+
export interface SecretInjectionRecipe {
|
|
40
|
+
header: string;
|
|
41
|
+
scheme: "raw" | "bearer";
|
|
42
|
+
extraHeaders?: Record<string, string>;
|
|
43
|
+
}
|
|
44
|
+
export declare function parseDestinationUrl(raw: string): {
|
|
45
|
+
ok: true;
|
|
46
|
+
url: string;
|
|
47
|
+
hostname: string;
|
|
48
|
+
} | {
|
|
49
|
+
ok: false;
|
|
50
|
+
};
|
|
51
|
+
export declare function collectSecretNames(value: string, previous?: string[]): string[];
|
|
39
52
|
export declare function scrubSandboxOutput(text: string, secretNames?: string[]): string;
|
|
40
53
|
export declare function loadRevealedSecrets(token: string, companyUid: string, keys: string[], usage?: SecretUsage): Promise<Map<string, string>>;
|
|
41
54
|
export declare function registerSecretsCommand(program: Command): void;
|