@indigoai-us/hq-cli 5.47.7 → 5.47.8

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.
@@ -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]="2088e3af-a005-57eb-b642-90aca198460b")}catch(e){}}();
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=2088e3af-a005-57eb-b642-90aca198460b
245
+ //# debugId=395fa417-375a-55e7-b881-e65431742835
@@ -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]="df18b298-4724-5557-a579-edfa0f468361")}catch(e){}}();
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=df18b298-4724-5557-a579-edfa0f468361
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.7",
3
+ "version": "5.47.8",
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.5",
18
+ "@indigoai-us/hq-cloud": "^6.11.6",
19
19
  "@indigoai-us/hq-onboarding": "^0.1.0",
20
20
  "@sentry/node": "^10.49.0",
21
21
  "chalk": "^5.3.0",
@@ -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") {
@@ -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
- const s3 = mockS3WithEntries({
66
- entries: [
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
- // Default S3 stub returns the one meeting source.
76
- const s3 = mockS3WithEntries({
77
- entries: [
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) => ({