@indigoai-us/hq-cli 5.75.0 → 5.77.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.js +8 -3
- package/dist/commands/files.d.ts +61 -0
- package/dist/commands/files.js +274 -0
- package/dist/commands/mcp-registration.d.ts +4 -5
- package/dist/commands/mcp-registration.js +5 -4
- package/dist/commands/outposts.d.ts +20 -4
- package/dist/commands/outposts.js +79 -10
- package/dist/commands/pack-install.d.ts +14 -17
- package/dist/commands/pack-install.js +53 -29
- package/dist/commands/pkg-install.js +3 -1
- package/dist/commands/run.d.ts +2 -0
- package/dist/commands/run.js +9 -3
- package/dist/commands/secrets.js +189 -87
- package/dist/run/hq-plugin.js +94 -31
- package/dist/utils/billing-gate.d.ts +15 -0
- package/dist/utils/billing-gate.js +35 -0
- package/dist/utils/sandbox-runner-client.d.ts +1 -0
- package/dist/utils/sandbox-runner-client.js +1 -0
- package/dist/utils/secrets-cache.d.ts +4 -5
- package/dist/utils/secrets-cache.js +5 -8
- package/package.json +3 -2
- package/pnpm-workspace.yaml +2 -0
- package/src/commands/agents.test.ts +41 -0
- package/src/commands/agents.ts +7 -3
- package/src/commands/files-recovery.test.ts +361 -0
- package/src/commands/files.ts +410 -0
- package/src/commands/mcp-registration.ts +9 -9
- package/src/commands/outposts.test.ts +155 -24
- package/src/commands/outposts.ts +199 -45
- package/src/commands/pack-install-secret-authorization.test.ts +115 -0
- package/src/commands/pack-install.test.ts +5 -1
- package/src/commands/pack-install.ts +67 -29
- package/src/commands/pkg-install.ts +3 -1
- package/src/commands/run.test.ts +45 -0
- package/src/commands/run.ts +20 -4
- package/src/commands/secrets.test.ts +366 -25
- package/src/commands/secrets.ts +222 -96
- package/src/run/hq-plugin.test.ts +186 -10
- package/src/run/hq-plugin.ts +102 -32
- package/src/utils/__fixtures__/scan-packages.generated-block.sh +23 -0
- package/src/utils/billing-gate.ts +46 -0
- package/src/utils/pack-contributions.test.ts +90 -31
- package/src/utils/sandbox-runner-client.test.ts +28 -0
- package/src/utils/sandbox-runner-client.ts +2 -0
- package/src/utils/secrets-cache.ts +5 -8
- package/test/commands/signals.test.ts +2 -2
- package/test/commands/sources.test.ts +2 -2
- package/test/helpers/vault-service-mock.ts +76 -17
- package/test/sources-signals/smoke.test.ts +2 -2
|
@@ -1,17 +1,32 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* vault-service-mock.ts —
|
|
2
|
+
* vault-service-mock.ts — vault-service test double.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
4
|
+
* Two collaborating pieces:
|
|
5
|
+
* 1. A `globalThis.fetch` stub for the vault-service JSON API (membership,
|
|
6
|
+
* entity lookup, STS vend, files list/presign).
|
|
7
|
+
* 2. A real ephemeral localhost HTTP server that serves the presigned-object
|
|
8
|
+
* GETs. As of hq-cloud >=6.14.x, `PresignObjectIO` fetches presigned URLs
|
|
9
|
+
* through Node's core HTTP client (`nodePresignedGet`), NOT
|
|
10
|
+
* `globalThis.fetch`, to bypass the runtime-bundled undici parser — so a
|
|
11
|
+
* fetch stub alone never intercepts them and the request escapes to real
|
|
12
|
+
* DNS (`ENOTFOUND`). A real (loopback) server is transport-agnostic: it
|
|
13
|
+
* works for both the fetch and node-http code paths, and future ones.
|
|
7
14
|
*
|
|
8
|
-
*
|
|
15
|
+
* `mockVaultService` is async (it awaits the server's `listen`) and returns a
|
|
16
|
+
* restore function for `afterEach` that both un-installs the fetch stub and
|
|
17
|
+
* closes the server.
|
|
18
|
+
*
|
|
19
|
+
* Handled JSON endpoints (URL path suffixes):
|
|
9
20
|
* GET .../membership/me returns { memberships: [...] }
|
|
10
21
|
* GET .../entity/by-slug/{type}/{slug} returns { entity: {...} }
|
|
11
22
|
* GET .../entity/{uid} returns { entity: {...} } (direct UID lookup)
|
|
12
|
-
*
|
|
23
|
+
* GET .../v1/files/list returns { objects, cursor, truncated }
|
|
24
|
+
* POST .../v1/files/presign returns { results, expiresAt } (URLs point at the local server)
|
|
25
|
+
* POST .../sts/vend returns { credentials, expiresAt }
|
|
13
26
|
*/
|
|
14
27
|
|
|
28
|
+
import http from "node:http";
|
|
29
|
+
import type { AddressInfo } from "node:net";
|
|
15
30
|
import type { Membership, EntityInfo } from "@indigoai-us/hq-cloud";
|
|
16
31
|
|
|
17
32
|
export interface MockEntity {
|
|
@@ -56,8 +71,6 @@ export interface MockVaultOptions {
|
|
|
56
71
|
files?: MockVaultFile[];
|
|
57
72
|
}
|
|
58
73
|
|
|
59
|
-
/** Host for mock presigned URLs minted by POST /v1/files/presign. */
|
|
60
|
-
const PRESIGN_URL_HOST = "https://presigned.test";
|
|
61
74
|
const DEFAULT_FILE_DATE = new Date("2026-01-01T00:00:00Z");
|
|
62
75
|
|
|
63
76
|
const DEFAULT_STS: MockStsCredentials = {
|
|
@@ -67,14 +80,56 @@ const DEFAULT_STS: MockStsCredentials = {
|
|
|
67
80
|
};
|
|
68
81
|
|
|
69
82
|
/**
|
|
70
|
-
*
|
|
83
|
+
* Start a loopback HTTP server that serves presigned-object GETs from the
|
|
84
|
+
* in-memory `files`. Each minted presign URL is `${origin}/obj?key=<key>`;
|
|
85
|
+
* the handler resolves the file (200 + body) or 404s (→ NoSuchKey in the
|
|
86
|
+
* reader). Returns the server + its `http://127.0.0.1:<port>` origin.
|
|
87
|
+
*/
|
|
88
|
+
async function startPresignedFileServer(
|
|
89
|
+
files: MockVaultFile[],
|
|
90
|
+
): Promise<{ server: http.Server; origin: string }> {
|
|
91
|
+
const server = http.createServer((req, res) => {
|
|
92
|
+
const requestUrl = new URL(req.url ?? "/", "http://127.0.0.1");
|
|
93
|
+
const key = requestUrl.searchParams.get("key") ?? "";
|
|
94
|
+
const file = files.find((f) => f.key === key);
|
|
95
|
+
if (!file) {
|
|
96
|
+
res.statusCode = 404;
|
|
97
|
+
res.setHeader("content-length", "0");
|
|
98
|
+
res.end();
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
const body = Buffer.from(file.content, "utf-8");
|
|
102
|
+
res.writeHead(200, {
|
|
103
|
+
"content-type": "text/markdown",
|
|
104
|
+
"content-length": String(body.byteLength),
|
|
105
|
+
"last-modified": (file.lastModified ?? DEFAULT_FILE_DATE).toUTCString(),
|
|
106
|
+
etag: '"mock-etag"',
|
|
107
|
+
});
|
|
108
|
+
res.end(body);
|
|
109
|
+
});
|
|
110
|
+
await new Promise<void>((resolve) => {
|
|
111
|
+
server.listen(0, "127.0.0.1", resolve);
|
|
112
|
+
});
|
|
113
|
+
// Don't keep the event loop (or vitest) alive on this listener.
|
|
114
|
+
server.unref();
|
|
115
|
+
const { port } = server.address() as AddressInfo;
|
|
116
|
+
return { server, origin: `http://127.0.0.1:${port}` };
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Installs the vault-service test double (fetch stub + presigned-file server).
|
|
71
121
|
*
|
|
72
|
-
* @returns A restore function — call it in afterEach to un-install the stub
|
|
122
|
+
* @returns A restore function — call it in afterEach to un-install the stub and
|
|
123
|
+
* close the server.
|
|
73
124
|
*/
|
|
74
|
-
export function mockVaultService(
|
|
125
|
+
export async function mockVaultService(
|
|
126
|
+
opts: MockVaultOptions,
|
|
127
|
+
): Promise<() => void> {
|
|
75
128
|
const { entities, stsCredentials = DEFAULT_STS, files = [] } = opts;
|
|
76
129
|
const originalFetch = globalThis.fetch as typeof fetch | undefined;
|
|
77
130
|
|
|
131
|
+
const { server, origin: presignOrigin } = await startPresignedFileServer(files);
|
|
132
|
+
|
|
78
133
|
globalThis.fetch = async (
|
|
79
134
|
input: RequestInfo | URL,
|
|
80
135
|
init?: RequestInit,
|
|
@@ -89,9 +144,10 @@ export function mockVaultService(opts: MockVaultOptions): () => void {
|
|
|
89
144
|
(init?.method ?? (input instanceof Request ? input.method : "GET")).toUpperCase();
|
|
90
145
|
|
|
91
146
|
// ── Presigned-URL transport (HQ-59 company reads) ──────────────────────
|
|
92
|
-
//
|
|
93
|
-
//
|
|
94
|
-
|
|
147
|
+
// Production fetches presigned URLs over node http (served by the loopback
|
|
148
|
+
// server above), but keep a fetch branch too so any fetch-based transport
|
|
149
|
+
// resolves identically without escaping to the network.
|
|
150
|
+
if (url.startsWith(`${presignOrigin}/`)) {
|
|
95
151
|
const key = new URL(url).searchParams.get("key") ?? "";
|
|
96
152
|
const file = files.find((f) => f.key === key);
|
|
97
153
|
if (!file) return new Response("", { status: 404 });
|
|
@@ -122,8 +178,9 @@ export function mockVaultService(opts: MockVaultOptions): () => void {
|
|
|
122
178
|
}
|
|
123
179
|
|
|
124
180
|
// POST /v1/files/presign (VaultClient.presign) — mint a per-key URL that
|
|
125
|
-
// the GET
|
|
126
|
-
// 404s, which the reader normalizes to
|
|
181
|
+
// the loopback server (and the GET branch above) resolves. Unknown keys
|
|
182
|
+
// still get a URL → that GET 404s, which the reader normalizes to
|
|
183
|
+
// NoSuchKey (the not-found path).
|
|
127
184
|
if (method === "POST" && /\/v1\/files\/presign$/.test(url)) {
|
|
128
185
|
const body = init?.body ? JSON.parse(init.body.toString()) : {};
|
|
129
186
|
const keys = (body.keys ?? []) as Array<{ key: string; op?: string }>;
|
|
@@ -134,7 +191,7 @@ export function mockVaultService(opts: MockVaultOptions): () => void {
|
|
|
134
191
|
const results = keys.map((k) => ({
|
|
135
192
|
key: k.key,
|
|
136
193
|
op: k.op ?? "get",
|
|
137
|
-
url: `${
|
|
194
|
+
url: `${presignOrigin}/obj?key=${encodeURIComponent(k.key)}`,
|
|
138
195
|
}));
|
|
139
196
|
return json({
|
|
140
197
|
results,
|
|
@@ -225,6 +282,8 @@ export function mockVaultService(opts: MockVaultOptions): () => void {
|
|
|
225
282
|
} else {
|
|
226
283
|
delete (globalThis as Record<string, unknown>).fetch;
|
|
227
284
|
}
|
|
285
|
+
server.closeAllConnections?.();
|
|
286
|
+
server.close();
|
|
228
287
|
};
|
|
229
288
|
}
|
|
230
289
|
|
|
@@ -176,7 +176,7 @@ describe("mockVaultService — membership endpoint", () => {
|
|
|
176
176
|
});
|
|
177
177
|
|
|
178
178
|
it("GET /membership/me returns memberships for all configured entities", async () => {
|
|
179
|
-
restore = mockVaultService({
|
|
179
|
+
restore = await mockVaultService({
|
|
180
180
|
entities: [
|
|
181
181
|
{ uid: "cmp_indigo_001", slug: "indigo", bucketName: "hq-indigo-bucket" },
|
|
182
182
|
{ uid: "prs_personal_001", slug: "personal", bucketName: "hq-personal-bucket" },
|
|
@@ -194,7 +194,7 @@ describe("mockVaultService — membership endpoint", () => {
|
|
|
194
194
|
});
|
|
195
195
|
|
|
196
196
|
it("POST /sts/vend returns credentials", async () => {
|
|
197
|
-
restore = mockVaultService({
|
|
197
|
+
restore = await mockVaultService({
|
|
198
198
|
entities: [{ uid: "cmp_indigo_001", slug: "indigo", bucketName: "hq-indigo-bucket" }],
|
|
199
199
|
});
|
|
200
200
|
|