@indigoai-us/hq-cli 5.47.7 → 5.47.9
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/signals.js +8 -3
- package/dist/commands/sources.js +8 -3
- package/package.json +2 -2
- package/src/commands/signals.ts +6 -0
- package/src/commands/sources.ts +6 -0
- package/test/commands/signals.test.ts +15 -8
- package/test/commands/sources.test.ts +15 -10
- package/test/helpers/vault-service-mock.ts +72 -1
package/dist/commands/signals.js
CHANGED
|
@@ -20,11 +20,11 @@
|
|
|
20
20
|
* --type flag parsing.
|
|
21
21
|
*/
|
|
22
22
|
|
|
23
|
-
!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]="
|
|
23
|
+
!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]="395fa417-375a-55e7-b881-e65431742835")}catch(e){}}();
|
|
24
24
|
import * as fs from "node:fs";
|
|
25
25
|
import * as path from "node:path";
|
|
26
26
|
import chalk from "chalk";
|
|
27
|
-
import { SIGNAL_TYPES, assertSignalType, resolveEntity, listAvailableEntities, listSignals, getSignal, } from "@indigoai-us/hq-cloud";
|
|
27
|
+
import { SIGNAL_TYPES, assertSignalType, resolveEntity, listAvailableEntities, listSignals, getSignal, VaultClient, } from "@indigoai-us/hq-cloud";
|
|
28
28
|
import { ensureCognitoToken, buildVaultConfig, DEFAULT_HQ_ROOT, } from "../utils/cognito-session.js";
|
|
29
29
|
import { Sentry } from "../sentry.js";
|
|
30
30
|
// ---------------------------------------------------------------------------
|
|
@@ -110,6 +110,9 @@ async function runList(options) {
|
|
|
110
110
|
limit,
|
|
111
111
|
continuationToken: options.pageToken,
|
|
112
112
|
includeFrontmatter: options.includeFrontmatter,
|
|
113
|
+
// HQ-59: route company (cmp_) reads through the ACL-filtered presign
|
|
114
|
+
// transport (no direct S3). Personal vaults / no-client stay on S3.
|
|
115
|
+
vault: new VaultClient(vaultConfig),
|
|
113
116
|
});
|
|
114
117
|
if (format === "json") {
|
|
115
118
|
process.stdout.write(JSON.stringify(result, null, 2) + "\n");
|
|
@@ -138,6 +141,8 @@ async function runGet(options) {
|
|
|
138
141
|
entity,
|
|
139
142
|
signalType,
|
|
140
143
|
signalId: options.id,
|
|
144
|
+
// HQ-59: company (cmp_) reads over presign; personal/no-client stay on S3.
|
|
145
|
+
vault: new VaultClient(vaultConfig),
|
|
141
146
|
});
|
|
142
147
|
if (format === "json") {
|
|
143
148
|
process.stdout.write(JSON.stringify(doc, null, 2) + "\n");
|
|
@@ -237,4 +242,4 @@ export function registerSignalsCommand(program) {
|
|
|
237
242
|
.action(withErrorHandling(async (options) => runEntities(options)));
|
|
238
243
|
}
|
|
239
244
|
//# sourceMappingURL=signals.js.map
|
|
240
|
-
//# debugId=
|
|
245
|
+
//# debugId=395fa417-375a-55e7-b881-e65431742835
|
package/dist/commands/sources.js
CHANGED
|
@@ -18,11 +18,11 @@
|
|
|
18
18
|
* --limit 50.
|
|
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]="1123e933-4c37-5679-ae51-84cffd42e31b")}catch(e){}}();
|
|
22
22
|
import * as fs from "node:fs";
|
|
23
23
|
import * as path from "node:path";
|
|
24
24
|
import chalk from "chalk";
|
|
25
|
-
import { SOURCE_CHANNELS, assertSourceChannel, resolveEntity, listAvailableEntities, listSources, getSource, } from "@indigoai-us/hq-cloud";
|
|
25
|
+
import { SOURCE_CHANNELS, assertSourceChannel, resolveEntity, listAvailableEntities, listSources, getSource, VaultClient, } from "@indigoai-us/hq-cloud";
|
|
26
26
|
import { ensureCognitoToken, buildVaultConfig, DEFAULT_HQ_ROOT, } from "../utils/cognito-session.js";
|
|
27
27
|
import { Sentry } from "../sentry.js";
|
|
28
28
|
// ---------------------------------------------------------------------------
|
|
@@ -118,6 +118,9 @@ async function runList(options) {
|
|
|
118
118
|
limit,
|
|
119
119
|
continuationToken: options.pageToken,
|
|
120
120
|
includeFrontmatter: options.includeFrontmatter,
|
|
121
|
+
// HQ-59: route company (cmp_) reads through the ACL-filtered presign
|
|
122
|
+
// transport (no direct S3). Personal vaults / no-client stay on S3.
|
|
123
|
+
vault: new VaultClient(vaultConfig),
|
|
121
124
|
});
|
|
122
125
|
if (format === "json") {
|
|
123
126
|
process.stdout.write(JSON.stringify(result, null, 2) + "\n");
|
|
@@ -147,6 +150,8 @@ async function runGet(options) {
|
|
|
147
150
|
channel,
|
|
148
151
|
sourceId: options.id,
|
|
149
152
|
includeRaw: options.includeRaw,
|
|
153
|
+
// HQ-59: company (cmp_) reads over presign; personal/no-client stay on S3.
|
|
154
|
+
vault: new VaultClient(vaultConfig),
|
|
150
155
|
});
|
|
151
156
|
if (format === "json") {
|
|
152
157
|
process.stdout.write(JSON.stringify(doc, null, 2) + "\n");
|
|
@@ -247,4 +252,4 @@ export function registerSourcesCommand(program) {
|
|
|
247
252
|
.action(withErrorHandling(async (options) => runEntities(options)));
|
|
248
253
|
}
|
|
249
254
|
//# sourceMappingURL=sources.js.map
|
|
250
|
-
//# debugId=
|
|
255
|
+
//# debugId=1123e933-4c37-5679-ae51-84cffd42e31b
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@indigoai-us/hq-cli",
|
|
3
|
-
"version": "5.47.
|
|
3
|
+
"version": "5.47.9",
|
|
4
4
|
"description": "HQ by Indigo management CLI — modules and cloud sync",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
"clean": "rm -rf dist"
|
|
16
16
|
},
|
|
17
17
|
"dependencies": {
|
|
18
|
-
"@indigoai-us/hq-cloud": "^6.11.
|
|
18
|
+
"@indigoai-us/hq-cloud": "^6.11.7",
|
|
19
19
|
"@indigoai-us/hq-onboarding": "^0.1.0",
|
|
20
20
|
"@sentry/node": "^10.49.0",
|
|
21
21
|
"chalk": "^5.3.0",
|
package/src/commands/signals.ts
CHANGED
|
@@ -31,6 +31,7 @@ import {
|
|
|
31
31
|
listAvailableEntities,
|
|
32
32
|
listSignals,
|
|
33
33
|
getSignal,
|
|
34
|
+
VaultClient,
|
|
34
35
|
type SignalType,
|
|
35
36
|
type SignalSummary,
|
|
36
37
|
type SignalDocument,
|
|
@@ -164,6 +165,9 @@ async function runList(options: ListOptions): Promise<void> {
|
|
|
164
165
|
limit,
|
|
165
166
|
continuationToken: options.pageToken,
|
|
166
167
|
includeFrontmatter: options.includeFrontmatter,
|
|
168
|
+
// HQ-59: route company (cmp_) reads through the ACL-filtered presign
|
|
169
|
+
// transport (no direct S3). Personal vaults / no-client stay on S3.
|
|
170
|
+
vault: new VaultClient(vaultConfig),
|
|
167
171
|
});
|
|
168
172
|
|
|
169
173
|
if (format === "json") {
|
|
@@ -212,6 +216,8 @@ async function runGet(options: GetOptions): Promise<void> {
|
|
|
212
216
|
entity,
|
|
213
217
|
signalType,
|
|
214
218
|
signalId: options.id,
|
|
219
|
+
// HQ-59: company (cmp_) reads over presign; personal/no-client stay on S3.
|
|
220
|
+
vault: new VaultClient(vaultConfig),
|
|
215
221
|
});
|
|
216
222
|
|
|
217
223
|
if (format === "json") {
|
package/src/commands/sources.ts
CHANGED
|
@@ -29,6 +29,7 @@ import {
|
|
|
29
29
|
listAvailableEntities,
|
|
30
30
|
listSources,
|
|
31
31
|
getSource,
|
|
32
|
+
VaultClient,
|
|
32
33
|
type SourceChannel,
|
|
33
34
|
type SourceSummary,
|
|
34
35
|
type SourceDocument,
|
|
@@ -172,6 +173,9 @@ async function runList(options: ListOptions): Promise<void> {
|
|
|
172
173
|
limit,
|
|
173
174
|
continuationToken: options.pageToken,
|
|
174
175
|
includeFrontmatter: options.includeFrontmatter,
|
|
176
|
+
// HQ-59: route company (cmp_) reads through the ACL-filtered presign
|
|
177
|
+
// transport (no direct S3). Personal vaults / no-client stay on S3.
|
|
178
|
+
vault: new VaultClient(vaultConfig),
|
|
175
179
|
});
|
|
176
180
|
|
|
177
181
|
if (format === "json") {
|
|
@@ -222,6 +226,8 @@ async function runGet(options: GetOptions): Promise<void> {
|
|
|
222
226
|
channel,
|
|
223
227
|
sourceId: options.id,
|
|
224
228
|
includeRaw: options.includeRaw,
|
|
229
|
+
// HQ-59: company (cmp_) reads over presign; personal/no-client stay on S3.
|
|
230
|
+
vault: new VaultClient(vaultConfig),
|
|
225
231
|
});
|
|
226
232
|
|
|
227
233
|
if (format === "json") {
|
|
@@ -24,7 +24,6 @@ import {
|
|
|
24
24
|
_setSignalsS3Factory,
|
|
25
25
|
_resetSignalsS3Factory,
|
|
26
26
|
} from "@indigoai-us/hq-cloud";
|
|
27
|
-
import { mockS3WithEntries } from "../helpers/s3-list-mock.js";
|
|
28
27
|
import { mockVaultService } from "../helpers/vault-service-mock.js";
|
|
29
28
|
import { runCli } from "../helpers/cli-runner.js";
|
|
30
29
|
|
|
@@ -62,8 +61,21 @@ beforeEach(() => {
|
|
|
62
61
|
|
|
63
62
|
tmpHqRoot = mkdtempSync(join(tmpdir(), "hq-signals-test-"));
|
|
64
63
|
|
|
65
|
-
|
|
66
|
-
|
|
64
|
+
// Transport-switch proof: every read in this suite targets a COMPANY (cmp_)
|
|
65
|
+
// entity, which routes through presign as of HQ-59. The direct-S3 factory is
|
|
66
|
+
// wired to THROW so any read accidentally falling back to S3 fails loudly
|
|
67
|
+
// (mirrors hq-cloud's #88 "S3 stub throws if touched" tests).
|
|
68
|
+
_setSignalsS3Factory(() => {
|
|
69
|
+
throw new Error(
|
|
70
|
+
"S3 transport must not be used for a company (cmp_) read — HQ-59 routes these through presign",
|
|
71
|
+
);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
// `files` serves the fixture over the presigned-URL transport (the path
|
|
75
|
+
// company reads take).
|
|
76
|
+
restoreFetch = mockVaultService({
|
|
77
|
+
entities: [{ uid: "cmp_indigo_001", slug: "indigo", bucketName: "hq-indigo-bucket" }],
|
|
78
|
+
files: [
|
|
67
79
|
{
|
|
68
80
|
key: "signals/action_item/xyz.md",
|
|
69
81
|
content: ACTION_ITEM_MD,
|
|
@@ -71,11 +83,6 @@ beforeEach(() => {
|
|
|
71
83
|
},
|
|
72
84
|
],
|
|
73
85
|
});
|
|
74
|
-
_setSignalsS3Factory(() => s3);
|
|
75
|
-
|
|
76
|
-
restoreFetch = mockVaultService({
|
|
77
|
-
entities: [{ uid: "cmp_indigo_001", slug: "indigo", bucketName: "hq-indigo-bucket" }],
|
|
78
|
-
});
|
|
79
86
|
});
|
|
80
87
|
|
|
81
88
|
afterEach(() => {
|
|
@@ -26,7 +26,6 @@ import {
|
|
|
26
26
|
_setSourcesS3Factory,
|
|
27
27
|
_resetSourcesS3Factory,
|
|
28
28
|
} from "@indigoai-us/hq-cloud";
|
|
29
|
-
import { mockS3WithEntries } from "../helpers/s3-list-mock.js";
|
|
30
29
|
import { mockVaultService } from "../helpers/vault-service-mock.js";
|
|
31
30
|
import { runCli } from "../helpers/cli-runner.js";
|
|
32
31
|
|
|
@@ -72,9 +71,21 @@ beforeEach(() => {
|
|
|
72
71
|
// test writes config.json into it.
|
|
73
72
|
tmpHqRoot = mkdtempSync(join(tmpdir(), "hq-sources-test-"));
|
|
74
73
|
|
|
75
|
-
//
|
|
76
|
-
|
|
77
|
-
|
|
74
|
+
// Transport-switch proof: every read in this suite targets a COMPANY (cmp_)
|
|
75
|
+
// entity, which routes through presign as of HQ-59. The direct-S3 factory is
|
|
76
|
+
// wired to THROW so that any read accidentally falling back to S3 fails the
|
|
77
|
+
// test loudly (mirrors hq-cloud's #88 "S3 stub throws if touched" tests).
|
|
78
|
+
_setSourcesS3Factory(() => {
|
|
79
|
+
throw new Error(
|
|
80
|
+
"S3 transport must not be used for a company (cmp_) read — HQ-59 routes these through presign",
|
|
81
|
+
);
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
// Default vault-service mock with one entity 'indigo'. `files` serves the
|
|
85
|
+
// fixture over the presigned-URL transport (the path company reads take).
|
|
86
|
+
restoreFetch = mockVaultService({
|
|
87
|
+
entities: [{ uid: "cmp_indigo_001", slug: "indigo", bucketName: "hq-indigo-bucket" }],
|
|
88
|
+
files: [
|
|
78
89
|
{
|
|
79
90
|
key: "sources/meetings/abc.md",
|
|
80
91
|
content: MEETING_MD,
|
|
@@ -82,12 +93,6 @@ beforeEach(() => {
|
|
|
82
93
|
},
|
|
83
94
|
],
|
|
84
95
|
});
|
|
85
|
-
_setSourcesS3Factory(() => s3);
|
|
86
|
-
|
|
87
|
-
// Default vault-service mock with one entity 'indigo'.
|
|
88
|
-
restoreFetch = mockVaultService({
|
|
89
|
-
entities: [{ uid: "cmp_indigo_001", slug: "indigo", bucketName: "hq-indigo-bucket" }],
|
|
90
|
-
});
|
|
91
96
|
});
|
|
92
97
|
|
|
93
98
|
afterEach(() => {
|
|
@@ -33,12 +33,33 @@ export interface MockStsCredentials {
|
|
|
33
33
|
sessionToken: string;
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
+
/** A file served over the presigned-URL transport (HQ-59 company reads). */
|
|
37
|
+
export interface MockVaultFile {
|
|
38
|
+
/** Object key (e.g. "sources/meetings/abc.md") */
|
|
39
|
+
key: string;
|
|
40
|
+
/** Full file content as UTF-8 string */
|
|
41
|
+
content: string;
|
|
42
|
+
/** Optional override; defaults to 2026-01-01T00:00:00Z */
|
|
43
|
+
lastModified?: Date;
|
|
44
|
+
}
|
|
45
|
+
|
|
36
46
|
export interface MockVaultOptions {
|
|
37
47
|
entities: MockEntity[];
|
|
38
48
|
/** STS credentials returned for every POST /entities/{uid}/sts; defaults to test creds */
|
|
39
49
|
stsCredentials?: MockStsCredentials;
|
|
50
|
+
/**
|
|
51
|
+
* Files served over the presigned-URL transport (`GET /v1/files/list`,
|
|
52
|
+
* `POST /v1/files/presign`, then a GET against the minted URL). Supplied so
|
|
53
|
+
* company (cmp_) reads — which route through presign as of HQ-59 — resolve
|
|
54
|
+
* without touching real S3. Absent → those endpoints return empty/404.
|
|
55
|
+
*/
|
|
56
|
+
files?: MockVaultFile[];
|
|
40
57
|
}
|
|
41
58
|
|
|
59
|
+
/** Host for mock presigned URLs minted by POST /v1/files/presign. */
|
|
60
|
+
const PRESIGN_URL_HOST = "https://presigned.test";
|
|
61
|
+
const DEFAULT_FILE_DATE = new Date("2026-01-01T00:00:00Z");
|
|
62
|
+
|
|
42
63
|
const DEFAULT_STS: MockStsCredentials = {
|
|
43
64
|
accessKeyId: "ASIAMOCK000000000001",
|
|
44
65
|
secretAccessKey: "mockSecretAccessKey",
|
|
@@ -51,7 +72,7 @@ const DEFAULT_STS: MockStsCredentials = {
|
|
|
51
72
|
* @returns A restore function — call it in afterEach to un-install the stub.
|
|
52
73
|
*/
|
|
53
74
|
export function mockVaultService(opts: MockVaultOptions): () => void {
|
|
54
|
-
const { entities, stsCredentials = DEFAULT_STS } = opts;
|
|
75
|
+
const { entities, stsCredentials = DEFAULT_STS, files = [] } = opts;
|
|
55
76
|
const originalFetch = globalThis.fetch as typeof fetch | undefined;
|
|
56
77
|
|
|
57
78
|
globalThis.fetch = async (
|
|
@@ -67,6 +88,56 @@ export function mockVaultService(opts: MockVaultOptions): () => void {
|
|
|
67
88
|
const method =
|
|
68
89
|
(init?.method ?? (input instanceof Request ? input.method : "GET")).toUpperCase();
|
|
69
90
|
|
|
91
|
+
// ── Presigned-URL transport (HQ-59 company reads) ──────────────────────
|
|
92
|
+
// A GET against a URL minted by POST /v1/files/presign below. Serve the
|
|
93
|
+
// file body (or 404) so PresignObjectIO.getObject resolves like real S3.
|
|
94
|
+
if (url.startsWith(`${PRESIGN_URL_HOST}/`)) {
|
|
95
|
+
const key = new URL(url).searchParams.get("key") ?? "";
|
|
96
|
+
const file = files.find((f) => f.key === key);
|
|
97
|
+
if (!file) return new Response("", { status: 404 });
|
|
98
|
+
return new Response(file.content, {
|
|
99
|
+
status: 200,
|
|
100
|
+
headers: {
|
|
101
|
+
"Content-Type": "text/markdown",
|
|
102
|
+
"Content-Length": String(Buffer.byteLength(file.content, "utf-8")),
|
|
103
|
+
"Last-Modified": (file.lastModified ?? DEFAULT_FILE_DATE).toUTCString(),
|
|
104
|
+
ETag: '"mock-etag"',
|
|
105
|
+
},
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// GET /v1/files/list?company=<uid>&prefix=<p>&cursor=<c> (VaultClient.listFiles)
|
|
110
|
+
if (method === "GET" && /\/v1\/files\/list(\?|$)/.test(url)) {
|
|
111
|
+
const prefix = new URL(url).searchParams.get("prefix") ?? "";
|
|
112
|
+
const objects = files
|
|
113
|
+
.filter((f) => f.key.startsWith(prefix))
|
|
114
|
+
.map((f) => ({
|
|
115
|
+
key: f.key,
|
|
116
|
+
size: Buffer.byteLength(f.content, "utf-8"),
|
|
117
|
+
lastModified: (f.lastModified ?? DEFAULT_FILE_DATE).toISOString(),
|
|
118
|
+
etag: "mock-etag",
|
|
119
|
+
permission: "read",
|
|
120
|
+
}));
|
|
121
|
+
return json({ objects, cursor: null, truncated: false });
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// POST /v1/files/presign (VaultClient.presign) — mint a per-key URL that
|
|
125
|
+
// the GET handler above resolves. Unknown keys still get a URL → that GET
|
|
126
|
+
// 404s, which the reader normalizes to NoSuchKey (the not-found path).
|
|
127
|
+
if (method === "POST" && /\/v1\/files\/presign$/.test(url)) {
|
|
128
|
+
const body = init?.body ? JSON.parse(init.body.toString()) : {};
|
|
129
|
+
const keys = (body.keys ?? []) as Array<{ key: string }>;
|
|
130
|
+
const results = keys.map((k) => ({
|
|
131
|
+
key: k.key,
|
|
132
|
+
url: `${PRESIGN_URL_HOST}/obj?key=${encodeURIComponent(k.key)}`,
|
|
133
|
+
error: null,
|
|
134
|
+
}));
|
|
135
|
+
return json({
|
|
136
|
+
results,
|
|
137
|
+
expiresAt: new Date(Date.now() + 3600 * 1000).toISOString(),
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
|
|
70
141
|
// GET /membership/me
|
|
71
142
|
if (method === "GET" && /\/membership\/me(\?.*)?$/.test(url)) {
|
|
72
143
|
const memberships: Partial<Membership>[] = entities.map((e) => ({
|