@indigoai-us/hq-cli 5.12.3 → 5.12.4
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/CHANGELOG.md +12 -0
- package/dist/commands/files.js +33 -48
- package/dist/index.js +1 -1
- package/package.json +1 -1
- package/src/commands/files.ts +38 -62
- package/src/index.ts +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [5.12.4] — 2026-05-12
|
|
4
|
+
|
|
5
|
+
### Changed
|
|
6
|
+
|
|
7
|
+
- **`hq files acl <prefix>` now uses a single round-trip.** Previously the
|
|
8
|
+
command issued `GET /acl` and `GET /acl/tree` in parallel to gather the
|
|
9
|
+
prefix's own row metadata (creator, open/restricted, effective permission)
|
|
10
|
+
alongside inherited/descendant grants. The server now folds `directRow`
|
|
11
|
+
and `effectivePermission` into the `/acl/tree` response, so the CLI makes
|
|
12
|
+
a single request and dedupes the company-ACL fetch on the server side.
|
|
13
|
+
Output formatting is unchanged.
|
|
14
|
+
|
|
3
15
|
## [5.12.3] — 2026-05-12
|
|
4
16
|
|
|
5
17
|
### Added
|
package/dist/commands/files.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
|
|
2
|
-
!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]="
|
|
2
|
+
!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]="7a4bfaa9-c578-5cd8-8955-ec965faada13")}catch(e){}}();
|
|
3
3
|
import chalk from "chalk";
|
|
4
4
|
import open from "open";
|
|
5
5
|
import { ensureCognitoToken } from "../utils/cognito-session.js";
|
|
@@ -205,64 +205,49 @@ export function registerFilesCommand(program) {
|
|
|
205
205
|
const token = await ensureCognitoToken();
|
|
206
206
|
const companySlug = files.opts().company;
|
|
207
207
|
const companyUid = await getCompanyUid(token, companySlug);
|
|
208
|
-
//
|
|
209
|
-
//
|
|
210
|
-
//
|
|
211
|
-
const
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
path: `/files/${encodeURIComponent(companyUid)}/acl/tree`,
|
|
220
|
-
query: { prefix: canonicalPrefix },
|
|
221
|
-
}),
|
|
222
|
-
]);
|
|
223
|
-
async function readErrorBody(res) {
|
|
224
|
-
return (await res.json().catch(() => ({})));
|
|
225
|
-
}
|
|
226
|
-
// Auth/server failures from either call are treated identically — bail
|
|
227
|
-
// out with a single message rather than printing a half-rendered view.
|
|
228
|
-
for (const res of [aclRes, treeRes]) {
|
|
229
|
-
if (res.ok || res.status === 404)
|
|
230
|
-
continue;
|
|
231
|
-
const body = await readErrorBody(res);
|
|
232
|
-
if (res.status === 401) {
|
|
208
|
+
// `/acl/tree` carries the prefix's own row metadata (directRow) and the
|
|
209
|
+
// caller's effectivePermission alongside direct/inherited/children, so
|
|
210
|
+
// a single request returns everything the "files acl" view needs.
|
|
211
|
+
const treeRes = await vaultApiFetch({
|
|
212
|
+
token,
|
|
213
|
+
path: `/files/${encodeURIComponent(companyUid)}/acl/tree`,
|
|
214
|
+
query: { prefix: canonicalPrefix },
|
|
215
|
+
});
|
|
216
|
+
if (!treeRes.ok) {
|
|
217
|
+
const body = (await treeRes.json().catch(() => ({})));
|
|
218
|
+
if (treeRes.status === 401) {
|
|
233
219
|
console.error(chalk.red("Not authenticated — please run `hq login`"));
|
|
234
220
|
}
|
|
235
|
-
else if (
|
|
221
|
+
else if (treeRes.status === 403) {
|
|
236
222
|
console.error(chalk.red("Not authorized to view this file prefix's ACL"));
|
|
237
223
|
}
|
|
238
|
-
else if (
|
|
239
|
-
console.error(chalk.red(`Server error: ${body.error ??
|
|
224
|
+
else if (treeRes.status >= 500) {
|
|
225
|
+
console.error(chalk.red(`Server error: ${body.error ?? treeRes.statusText}`));
|
|
240
226
|
}
|
|
241
227
|
else {
|
|
242
228
|
console.error(chalk.red(body.message ?? body.error ?? "Invalid request"));
|
|
243
229
|
}
|
|
244
230
|
process.exit(1);
|
|
245
231
|
}
|
|
246
|
-
const
|
|
247
|
-
const
|
|
248
|
-
// No own row AND nothing inherited or granted below —
|
|
249
|
-
// "no ACL record" exit path.
|
|
250
|
-
if (!
|
|
232
|
+
const tree = (await treeRes.json());
|
|
233
|
+
const row = tree.directRow;
|
|
234
|
+
// No own row AND nothing inherited or granted below — preserve the
|
|
235
|
+
// original "no ACL record" exit path.
|
|
236
|
+
if (!row && tree.inherited.length === 0 && tree.children.length === 0) {
|
|
251
237
|
console.error(chalk.red(`No ACL record exists for '${canonicalPrefix}'`));
|
|
252
238
|
process.exit(1);
|
|
253
239
|
}
|
|
254
|
-
const
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
console.log(`Creator: ${acl.creatorUid}`);
|
|
259
|
-
if (acl.effectivePermission) {
|
|
260
|
-
console.log(`Your effective permission: ${acl.effectivePermission}`);
|
|
261
|
-
}
|
|
240
|
+
const aclStatus = row?.open ? "open" : "restricted";
|
|
241
|
+
console.log(chalk.green(`ACL for ${tree.prefix} (${aclStatus})`));
|
|
242
|
+
if (row) {
|
|
243
|
+
console.log(`Creator: ${row.creatorUid}`);
|
|
262
244
|
}
|
|
263
245
|
else {
|
|
264
246
|
console.log(chalk.gray("No direct ACL row — access flows from the inherited/descendant grants below."));
|
|
265
247
|
}
|
|
248
|
+
if (tree.effectivePermission) {
|
|
249
|
+
console.log(`Your effective permission: ${tree.effectivePermission}`);
|
|
250
|
+
}
|
|
266
251
|
// Display labels for grantee identifiers — `company-wide` entries
|
|
267
252
|
// store `granteeId === ""` on the wire, but a blank cell is confusing
|
|
268
253
|
// in tabular output, so we render a human-readable phrase instead.
|
|
@@ -303,12 +288,12 @@ export function registerFilesCommand(program) {
|
|
|
303
288
|
console.log(cols.join(" "));
|
|
304
289
|
}
|
|
305
290
|
}
|
|
306
|
-
const directEntries =
|
|
291
|
+
const directEntries = tree.direct;
|
|
307
292
|
if (directEntries.length === 0) {
|
|
308
|
-
if (
|
|
293
|
+
if (row?.open) {
|
|
309
294
|
console.log(chalk.gray("Open ACL — all active members have read access."));
|
|
310
295
|
}
|
|
311
|
-
else if (
|
|
296
|
+
else if (row) {
|
|
312
297
|
console.log(chalk.gray("No explicit grants on this prefix — only creator has access."));
|
|
313
298
|
}
|
|
314
299
|
}
|
|
@@ -316,11 +301,11 @@ export function registerFilesCommand(program) {
|
|
|
316
301
|
console.log("\nDirect entries (granted on this prefix):");
|
|
317
302
|
printEntryTable(directEntries, false);
|
|
318
303
|
}
|
|
319
|
-
if (tree
|
|
304
|
+
if (tree.inherited.length > 0) {
|
|
320
305
|
console.log("\nInherited (granted on an ancestor prefix):");
|
|
321
306
|
printEntryTable(tree.inherited, true);
|
|
322
307
|
}
|
|
323
|
-
if (tree
|
|
308
|
+
if (tree.children.length > 0) {
|
|
324
309
|
console.log("\nGranted on descendant prefixes (do not affect this prefix's access):");
|
|
325
310
|
printEntryTable(tree.children, true);
|
|
326
311
|
}
|
|
@@ -471,4 +456,4 @@ async function runShareSession(params) {
|
|
|
471
456
|
}
|
|
472
457
|
}
|
|
473
458
|
//# sourceMappingURL=files.js.map
|
|
474
|
-
//# debugId=
|
|
459
|
+
//# debugId=7a4bfaa9-c578-5cd8-8955-ec965faada13
|
package/dist/index.js
CHANGED
|
@@ -37,7 +37,7 @@ const program = new Command();
|
|
|
37
37
|
program
|
|
38
38
|
.name("hq")
|
|
39
39
|
.description("HQ management CLI — modules, packages, and cloud sync")
|
|
40
|
-
.version("5.12.
|
|
40
|
+
.version("5.12.4");
|
|
41
41
|
// Module management subcommand group
|
|
42
42
|
const modulesCmd = program
|
|
43
43
|
.command("modules")
|
package/package.json
CHANGED
package/src/commands/files.ts
CHANGED
|
@@ -271,37 +271,23 @@ export function registerFilesCommand(program: Command): void {
|
|
|
271
271
|
const companySlug = files.opts().company as string | undefined;
|
|
272
272
|
const companyUid = await getCompanyUid(token, companySlug);
|
|
273
273
|
|
|
274
|
-
//
|
|
275
|
-
//
|
|
276
|
-
//
|
|
277
|
-
const
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
}),
|
|
283
|
-
vaultApiFetch({
|
|
284
|
-
token,
|
|
285
|
-
path: `/files/${encodeURIComponent(companyUid)}/acl/tree`,
|
|
286
|
-
query: { prefix: canonicalPrefix },
|
|
287
|
-
}),
|
|
288
|
-
]);
|
|
289
|
-
|
|
290
|
-
async function readErrorBody(res: Response): Promise<Record<string, string>> {
|
|
291
|
-
return (await res.json().catch(() => ({}))) as Record<string, string>;
|
|
292
|
-
}
|
|
274
|
+
// `/acl/tree` carries the prefix's own row metadata (directRow) and the
|
|
275
|
+
// caller's effectivePermission alongside direct/inherited/children, so
|
|
276
|
+
// a single request returns everything the "files acl" view needs.
|
|
277
|
+
const treeRes = await vaultApiFetch({
|
|
278
|
+
token,
|
|
279
|
+
path: `/files/${encodeURIComponent(companyUid)}/acl/tree`,
|
|
280
|
+
query: { prefix: canonicalPrefix },
|
|
281
|
+
});
|
|
293
282
|
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
if (res.ok || res.status === 404) continue;
|
|
298
|
-
const body = await readErrorBody(res);
|
|
299
|
-
if (res.status === 401) {
|
|
283
|
+
if (!treeRes.ok) {
|
|
284
|
+
const body = (await treeRes.json().catch(() => ({}))) as Record<string, string>;
|
|
285
|
+
if (treeRes.status === 401) {
|
|
300
286
|
console.error(chalk.red("Not authenticated — please run `hq login`"));
|
|
301
|
-
} else if (
|
|
287
|
+
} else if (treeRes.status === 403) {
|
|
302
288
|
console.error(chalk.red("Not authorized to view this file prefix's ACL"));
|
|
303
|
-
} else if (
|
|
304
|
-
console.error(chalk.red(`Server error: ${body.error ??
|
|
289
|
+
} else if (treeRes.status >= 500) {
|
|
290
|
+
console.error(chalk.red(`Server error: ${body.error ?? treeRes.statusText}`));
|
|
305
291
|
} else {
|
|
306
292
|
console.error(chalk.red(body.message ?? body.error ?? "Invalid request"));
|
|
307
293
|
}
|
|
@@ -315,53 +301,43 @@ export function registerFilesCommand(program: Command): void {
|
|
|
315
301
|
grantedBy: string;
|
|
316
302
|
grantedAt: string;
|
|
317
303
|
};
|
|
318
|
-
type AclResponse = {
|
|
319
|
-
acl: {
|
|
320
|
-
itemType: string;
|
|
321
|
-
companyUid: string;
|
|
322
|
-
// Server returns `path` (the FileAcl field name); older builds
|
|
323
|
-
// used `prefix`. Read both so the CLI works against either.
|
|
324
|
-
path?: string;
|
|
325
|
-
prefix?: string;
|
|
326
|
-
creatorUid: string;
|
|
327
|
-
open?: boolean;
|
|
328
|
-
entries: AclEntry[];
|
|
329
|
-
effectivePermission?: string | null;
|
|
330
|
-
createdAt: string;
|
|
331
|
-
updatedAt: string;
|
|
332
|
-
};
|
|
333
|
-
};
|
|
334
304
|
type TreeResponse = {
|
|
335
305
|
prefix: string;
|
|
336
306
|
direct: AclEntry[];
|
|
337
307
|
inherited: Array<AclEntry & { sourcePrefix: string }>;
|
|
338
308
|
children: Array<AclEntry & { sourcePrefix: string }>;
|
|
309
|
+
directRow: {
|
|
310
|
+
creatorUid: string;
|
|
311
|
+
open: boolean;
|
|
312
|
+
createdAt: string;
|
|
313
|
+
updatedAt: string;
|
|
314
|
+
} | null;
|
|
315
|
+
effectivePermission: string | null;
|
|
339
316
|
};
|
|
340
317
|
|
|
341
|
-
const
|
|
342
|
-
const
|
|
318
|
+
const tree = (await treeRes.json()) as TreeResponse;
|
|
319
|
+
const row = tree.directRow;
|
|
343
320
|
|
|
344
|
-
// No own row AND nothing inherited or granted below —
|
|
345
|
-
// "no ACL record" exit path.
|
|
346
|
-
if (!
|
|
321
|
+
// No own row AND nothing inherited or granted below — preserve the
|
|
322
|
+
// original "no ACL record" exit path.
|
|
323
|
+
if (!row && tree.inherited.length === 0 && tree.children.length === 0) {
|
|
347
324
|
console.error(chalk.red(`No ACL record exists for '${canonicalPrefix}'`));
|
|
348
325
|
process.exit(1);
|
|
349
326
|
}
|
|
350
327
|
|
|
351
|
-
const
|
|
352
|
-
const aclStatus = acl?.open ? "open" : "restricted";
|
|
328
|
+
const aclStatus = row?.open ? "open" : "restricted";
|
|
353
329
|
|
|
354
|
-
console.log(chalk.green(`ACL for ${
|
|
355
|
-
if (
|
|
356
|
-
console.log(`Creator: ${
|
|
357
|
-
if (acl.effectivePermission) {
|
|
358
|
-
console.log(`Your effective permission: ${acl.effectivePermission}`);
|
|
359
|
-
}
|
|
330
|
+
console.log(chalk.green(`ACL for ${tree.prefix} (${aclStatus})`));
|
|
331
|
+
if (row) {
|
|
332
|
+
console.log(`Creator: ${row.creatorUid}`);
|
|
360
333
|
} else {
|
|
361
334
|
console.log(chalk.gray(
|
|
362
335
|
"No direct ACL row — access flows from the inherited/descendant grants below.",
|
|
363
336
|
));
|
|
364
337
|
}
|
|
338
|
+
if (tree.effectivePermission) {
|
|
339
|
+
console.log(`Your effective permission: ${tree.effectivePermission}`);
|
|
340
|
+
}
|
|
365
341
|
|
|
366
342
|
// Display labels for grantee identifiers — `company-wide` entries
|
|
367
343
|
// store `granteeId === ""` on the wire, but a blank cell is confusing
|
|
@@ -405,11 +381,11 @@ export function registerFilesCommand(program: Command): void {
|
|
|
405
381
|
}
|
|
406
382
|
}
|
|
407
383
|
|
|
408
|
-
const directEntries =
|
|
384
|
+
const directEntries = tree.direct;
|
|
409
385
|
if (directEntries.length === 0) {
|
|
410
|
-
if (
|
|
386
|
+
if (row?.open) {
|
|
411
387
|
console.log(chalk.gray("Open ACL — all active members have read access."));
|
|
412
|
-
} else if (
|
|
388
|
+
} else if (row) {
|
|
413
389
|
console.log(chalk.gray("No explicit grants on this prefix — only creator has access."));
|
|
414
390
|
}
|
|
415
391
|
} else {
|
|
@@ -417,12 +393,12 @@ export function registerFilesCommand(program: Command): void {
|
|
|
417
393
|
printEntryTable(directEntries, false);
|
|
418
394
|
}
|
|
419
395
|
|
|
420
|
-
if (tree
|
|
396
|
+
if (tree.inherited.length > 0) {
|
|
421
397
|
console.log("\nInherited (granted on an ancestor prefix):");
|
|
422
398
|
printEntryTable(tree.inherited, true);
|
|
423
399
|
}
|
|
424
400
|
|
|
425
|
-
if (tree
|
|
401
|
+
if (tree.children.length > 0) {
|
|
426
402
|
console.log("\nGranted on descendant prefixes (do not affect this prefix's access):");
|
|
427
403
|
printEntryTable(tree.children, true);
|
|
428
404
|
}
|
package/src/index.ts
CHANGED