@indigoai-us/hq-cli 5.7.0 → 5.8.3

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.
@@ -1,3 +1,5 @@
1
1
  export declare const SECRET_NAME_PATTERN: RegExp;
2
2
  export declare const GROUP_ID_PATTERN: RegExp;
3
+ export declare const EMAIL_PATTERN: RegExp;
4
+ export declare function normalizeFilePrefix(prefix: string): string;
3
5
  //# sourceMappingURL=_patterns.d.ts.map
@@ -1,6 +1,13 @@
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]="61b178c5-2bd1-5503-bbee-ecb98a9d72f5")}catch(e){}}();
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]="0d9c591b-d54a-52b4-85c3-32a9030953ca")}catch(e){}}();
3
3
  export const SECRET_NAME_PATTERN = /^[A-Z][A-Z0-9_]*(?:\/[A-Z][A-Z0-9_]+)*$/;
4
4
  export const GROUP_ID_PATTERN = /^grp_[A-Za-z0-9_-]+$/;
5
+ export const EMAIL_PATTERN = /^[^\s]+@[^\s]+$/;
6
+ export function normalizeFilePrefix(prefix) {
7
+ if (prefix.endsWith("/")) {
8
+ return prefix + "*";
9
+ }
10
+ return prefix;
11
+ }
5
12
  //# sourceMappingURL=_patterns.js.map
6
- //# debugId=61b178c5-2bd1-5503-bbee-ecb98a9d72f5
13
+ //# debugId=0d9c591b-d54a-52b4-85c3-32a9030953ca
@@ -0,0 +1,3 @@
1
+ import { Command } from "commander";
2
+ export declare function registerFilesCommand(program: Command): void;
3
+ //# sourceMappingURL=files.d.ts.map
@@ -0,0 +1,206 @@
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]="5f7a9620-b2ba-5186-980c-76df147440f0")}catch(e){}}();
3
+ import chalk from "chalk";
4
+ import { ensureCognitoToken } from "../utils/cognito-session.js";
5
+ import { vaultApiFetch, getCompanyUid } from "./secrets.js";
6
+ import { GROUP_ID_PATTERN, EMAIL_PATTERN, normalizeFilePrefix } from "./_patterns.js";
7
+ export function registerFilesCommand(program) {
8
+ const files = program
9
+ .command("files")
10
+ .description("Manage file access controls in HQ vault")
11
+ .option("--company <slug>", "Company slug (resolves to companyUid)");
12
+ files
13
+ .command("share <prefix>")
14
+ .description("Share a file prefix with a person or group")
15
+ .requiredOption("--with <principal>", "Email address or group id to share with")
16
+ .requiredOption("--permission <level>", "Permission level: read | write")
17
+ .action(async (prefix, opts) => {
18
+ try {
19
+ const canonicalPrefix = normalizeFilePrefix(prefix);
20
+ if (!["read", "write"].includes(opts.permission)) {
21
+ console.error(chalk.red(`Invalid permission '${opts.permission}': must be one of read, write`));
22
+ process.exit(1);
23
+ }
24
+ const principal = opts.with;
25
+ const isEmail = EMAIL_PATTERN.test(principal);
26
+ const isGroup = GROUP_ID_PATTERN.test(principal);
27
+ if (!isEmail && !isGroup) {
28
+ console.error(chalk.red(`Invalid principal '${principal}': must be an email address or a group id matching grp_<alphanumeric>`));
29
+ process.exit(1);
30
+ }
31
+ const granteeType = isEmail ? "email" : "group";
32
+ const granteeId = isEmail ? principal.trim().toLowerCase() : principal;
33
+ const token = await ensureCognitoToken();
34
+ const companySlug = files.opts().company;
35
+ const companyUid = await getCompanyUid(token, companySlug);
36
+ const res = await vaultApiFetch({
37
+ token,
38
+ path: `/files/${encodeURIComponent(companyUid)}/acl/grant`,
39
+ method: "POST",
40
+ body: { prefix: canonicalPrefix, granteeType, granteeId, permission: opts.permission },
41
+ });
42
+ if (!res.ok) {
43
+ const body = await res.json().catch(() => ({}));
44
+ if (res.status === 401) {
45
+ console.error(chalk.red("Not authenticated — please run `hq login`"));
46
+ }
47
+ else if (res.status === 403) {
48
+ console.error(chalk.red("Not authorized to share this file prefix"));
49
+ }
50
+ else if (res.status === 404) {
51
+ console.error(chalk.red("ACL record not found — the prefix may not have an ACL yet"));
52
+ }
53
+ else if (res.status === 409) {
54
+ console.error(chalk.red("Concurrent modification — please retry"));
55
+ }
56
+ else if (res.status >= 500) {
57
+ console.error(chalk.red(`Server error: ${body.error ?? res.statusText}`));
58
+ }
59
+ else {
60
+ console.error(chalk.red(body.message ?? body.error ?? "Invalid request"));
61
+ }
62
+ process.exit(1);
63
+ }
64
+ const data = await res.json();
65
+ const printedPrefix = data.acl?.prefix ?? canonicalPrefix;
66
+ console.log(chalk.green(`Granted ${opts.permission} on ${printedPrefix} to ${granteeId}`));
67
+ }
68
+ catch (err) {
69
+ console.error(chalk.red("Error:"), err instanceof Error ? err.message : String(err));
70
+ process.exit(1);
71
+ }
72
+ });
73
+ files
74
+ .command("unshare <prefix>")
75
+ .description("Remove a file access grant")
76
+ .requiredOption("--with <principal>", "Email address or group id to remove")
77
+ .action(async (prefix, opts) => {
78
+ try {
79
+ const canonicalPrefix = normalizeFilePrefix(prefix);
80
+ const principal = opts.with;
81
+ const isEmail = EMAIL_PATTERN.test(principal);
82
+ const isGroup = GROUP_ID_PATTERN.test(principal);
83
+ if (!isEmail && !isGroup) {
84
+ console.error(chalk.red(`Invalid principal '${principal}': must be an email address or a group id matching grp_<alphanumeric>`));
85
+ process.exit(1);
86
+ }
87
+ const granteeType = isEmail ? "email" : "group";
88
+ const granteeId = isEmail ? principal.trim().toLowerCase() : principal;
89
+ const token = await ensureCognitoToken();
90
+ const companySlug = files.opts().company;
91
+ const companyUid = await getCompanyUid(token, companySlug);
92
+ const res = await vaultApiFetch({
93
+ token,
94
+ path: `/files/${encodeURIComponent(companyUid)}/acl/revoke`,
95
+ method: "POST",
96
+ body: { prefix: canonicalPrefix, granteeType, granteeId },
97
+ });
98
+ if (!res.ok) {
99
+ const body = await res.json().catch(() => ({}));
100
+ if (res.status === 401) {
101
+ console.error(chalk.red("Not authenticated — please run `hq login`"));
102
+ }
103
+ else if (res.status === 403) {
104
+ console.error(chalk.red("Not authorized to modify this file prefix's ACL"));
105
+ }
106
+ else if (res.status === 404) {
107
+ console.log(chalk.green(`Grant already absent for '${canonicalPrefix}' / ${granteeId}`));
108
+ return;
109
+ }
110
+ else if (res.status >= 500) {
111
+ console.error(chalk.red(`Server error: ${body.error ?? res.statusText}`));
112
+ }
113
+ else {
114
+ console.error(chalk.red(body.message ?? body.error ?? "Invalid request"));
115
+ }
116
+ process.exit(1);
117
+ }
118
+ console.log(chalk.green(`Removed grant for ${granteeId} on '${canonicalPrefix}'`));
119
+ }
120
+ catch (err) {
121
+ console.error(chalk.red("Error:"), err instanceof Error ? err.message : String(err));
122
+ process.exit(1);
123
+ }
124
+ });
125
+ files
126
+ .command("acl <prefix>")
127
+ .description("Show the ACL (access control list) for a file prefix")
128
+ .action(async (prefix) => {
129
+ try {
130
+ const canonicalPrefix = normalizeFilePrefix(prefix);
131
+ const token = await ensureCognitoToken();
132
+ const companySlug = files.opts().company;
133
+ const companyUid = await getCompanyUid(token, companySlug);
134
+ const res = await vaultApiFetch({
135
+ token,
136
+ path: `/files/${encodeURIComponent(companyUid)}/acl`,
137
+ query: { prefix: canonicalPrefix },
138
+ });
139
+ if (!res.ok) {
140
+ const body = await res.json().catch(() => ({}));
141
+ if (res.status === 401) {
142
+ console.error(chalk.red("Not authenticated — please run `hq login`"));
143
+ }
144
+ else if (res.status === 403) {
145
+ console.error(chalk.red("Not authorized to view this file prefix's ACL"));
146
+ }
147
+ else if (res.status === 404) {
148
+ console.error(chalk.red(`No ACL record exists for '${canonicalPrefix}'`));
149
+ }
150
+ else if (res.status >= 500) {
151
+ console.error(chalk.red(`Server error: ${body.error ?? res.statusText}`));
152
+ }
153
+ else {
154
+ console.error(chalk.red(body.message ?? body.error ?? "Invalid request"));
155
+ }
156
+ process.exit(1);
157
+ }
158
+ const data = await res.json();
159
+ const acl = data.acl;
160
+ const aclStatus = acl.open ? "open" : "restricted";
161
+ console.log(chalk.green(`ACL for ${acl.prefix} (${aclStatus})`));
162
+ console.log(`Creator: ${acl.creatorUid}`);
163
+ if (acl.effectivePermission) {
164
+ console.log(`Your effective permission: ${acl.effectivePermission}`);
165
+ }
166
+ if (acl.entries.length === 0) {
167
+ if (acl.open) {
168
+ console.log(chalk.gray("Open ACL — all active members have read access."));
169
+ }
170
+ else {
171
+ console.log(chalk.gray("No explicit grants — only creator has access."));
172
+ }
173
+ return;
174
+ }
175
+ console.log("Entries:");
176
+ const TYPE_W = Math.max(4, ...acl.entries.map((e) => e.granteeType.length));
177
+ const GRANTEE_W = Math.max(7, ...acl.entries.map((e) => e.granteeId.length));
178
+ const PERM_W = Math.max(10, ...acl.entries.map((e) => e.permission.length));
179
+ const BY_W = Math.max(10, ...acl.entries.map((e) => e.grantedBy.length));
180
+ const tableHeader = [
181
+ "TYPE".padEnd(TYPE_W),
182
+ "GRANTEE".padEnd(GRANTEE_W),
183
+ "PERMISSION".padEnd(PERM_W),
184
+ "GRANTED_BY".padEnd(BY_W),
185
+ "GRANTED_AT",
186
+ ].join(" ");
187
+ console.log(chalk.bold(tableHeader));
188
+ for (const e of acl.entries) {
189
+ const grantedAt = e.grantedAt.slice(0, 10);
190
+ console.log([
191
+ e.granteeType.padEnd(TYPE_W),
192
+ e.granteeId.padEnd(GRANTEE_W),
193
+ e.permission.padEnd(PERM_W),
194
+ e.grantedBy.padEnd(BY_W),
195
+ grantedAt,
196
+ ].join(" "));
197
+ }
198
+ }
199
+ catch (err) {
200
+ console.error(chalk.red("Error:"), err instanceof Error ? err.message : String(err));
201
+ process.exit(1);
202
+ }
203
+ });
204
+ }
205
+ //# sourceMappingURL=files.js.map
206
+ //# debugId=5f7a9620-b2ba-5186-980c-76df147440f0
package/dist/index.js CHANGED
@@ -3,7 +3,7 @@
3
3
  * HQ CLI - Module management, package management, and cloud sync for HQ
4
4
  */
5
5
 
6
- !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]="8abd3fa1-9f1a-53b3-9d1c-cf764db1f670")}catch(e){}}();
6
+ !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]="79222b07-0f20-582f-962f-6bc13b8c1c88")}catch(e){}}();
7
7
  import { Command } from "commander";
8
8
  import { initSentry, Sentry } from "./sentry.js";
9
9
  import { registerAddCommand } from "./commands/add.js";
@@ -24,12 +24,13 @@ import { registerTeamSyncCommand } from "./commands/team-sync.js";
24
24
  import { registerAuthCommands } from "./commands/auth.js";
25
25
  import { registerSecretsCommand } from "./commands/secrets.js";
26
26
  import { registerGroupsCommand } from "./commands/groups.js";
27
+ import { registerFilesCommand } from "./commands/files.js";
27
28
  initSentry();
28
29
  const program = new Command();
29
30
  program
30
31
  .name("hq")
31
32
  .description("HQ management CLI — modules, packages, and cloud sync")
32
- .version("5.5.0");
33
+ .version("5.8.3");
33
34
  // Module management subcommand group
34
35
  const modulesCmd = program
35
36
  .command("modules")
@@ -73,6 +74,8 @@ registerAuthCommands(program);
73
74
  registerSecretsCommand(program);
74
75
  // Groups management (subcommand group — hq groups create|delete|add|remove|list|members)
75
76
  registerGroupsCommand(program);
77
+ // Files ACL management (subcommand group — hq files share|unshare|acl)
78
+ registerFilesCommand(program);
76
79
  // Onboarding (top-level — Cognito + vault-service provisioning)
77
80
  registerOnboardCommand(program);
78
81
  (async () => {
@@ -88,4 +91,4 @@ registerOnboardCommand(program);
88
91
  }
89
92
  })();
90
93
  //# sourceMappingURL=index.js.map
91
- //# debugId=8abd3fa1-9f1a-53b3-9d1c-cf764db1f670
94
+ //# debugId=79222b07-0f20-582f-962f-6bc13b8c1c88
@@ -19,7 +19,7 @@
19
19
  * HQ_VAULT_API_URL — vault-service API Gateway URL
20
20
  */
21
21
 
22
- !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]="147a783f-029d-547f-ba36-a1dbf8ed0f65")}catch(e){}}();
22
+ !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]="71d8f748-f249-50df-aa1f-eae381ea608d")}catch(e){}}();
23
23
  import * as os from "os";
24
24
  import * as path from "path";
25
25
  import chalk from "chalk";
@@ -40,8 +40,7 @@ export const DEFAULT_COGNITO = {
40
40
  ? process.env.HQ_COGNITO_IDENTITY_PROVIDER || undefined
41
41
  : "Google",
42
42
  };
43
- export const DEFAULT_VAULT_API_URL = process.env.HQ_VAULT_API_URL ??
44
- "https://4nfy67z28h.execute-api.us-east-1.amazonaws.com";
43
+ export const DEFAULT_VAULT_API_URL = process.env.HQ_VAULT_API_URL ?? "https://hqapi.getindigo.ai";
45
44
  export const DEFAULT_HQ_ROOT = path.join(os.homedir(), "hq");
46
45
  /**
47
46
  * Return a non-expired Cognito access token, refreshing or browser-logging-in
@@ -109,4 +108,4 @@ export async function refreshCachedSession() {
109
108
  }
110
109
  }
111
110
  //# sourceMappingURL=cognito-session.js.map
112
- //# debugId=147a783f-029d-547f-ba36-a1dbf8ed0f65
111
+ //# debugId=71d8f748-f249-50df-aa1f-eae381ea608d
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@indigoai-us/hq-cli",
3
- "version": "5.7.0",
3
+ "version": "5.8.3",
4
4
  "description": "HQ by Indigo management CLI — modules and cloud sync",
5
5
  "main": "dist/index.js",
6
6
  "bin": {
@@ -1,2 +1,10 @@
1
1
  export const SECRET_NAME_PATTERN = /^[A-Z][A-Z0-9_]*(?:\/[A-Z][A-Z0-9_]+)*$/;
2
2
  export const GROUP_ID_PATTERN = /^grp_[A-Za-z0-9_-]+$/;
3
+ export const EMAIL_PATTERN = /^[^\s]+@[^\s]+$/;
4
+
5
+ export function normalizeFilePrefix(prefix: string): string {
6
+ if (prefix.endsWith("/")) {
7
+ return prefix + "*";
8
+ }
9
+ return prefix;
10
+ }
@@ -0,0 +1,227 @@
1
+ import { Command } from "commander";
2
+ import chalk from "chalk";
3
+ import { ensureCognitoToken } from "../utils/cognito-session.js";
4
+ import { vaultApiFetch, getCompanyUid } from "./secrets.js";
5
+ import { GROUP_ID_PATTERN, EMAIL_PATTERN, normalizeFilePrefix } from "./_patterns.js";
6
+
7
+ export function registerFilesCommand(program: Command): void {
8
+ const files = program
9
+ .command("files")
10
+ .description("Manage file access controls in HQ vault")
11
+ .option("--company <slug>", "Company slug (resolves to companyUid)");
12
+
13
+ files
14
+ .command("share <prefix>")
15
+ .description("Share a file prefix with a person or group")
16
+ .requiredOption("--with <principal>", "Email address or group id to share with")
17
+ .requiredOption("--permission <level>", "Permission level: read | write")
18
+ .action(async (prefix: string, opts: { with: string; permission: string }) => {
19
+ try {
20
+ const canonicalPrefix = normalizeFilePrefix(prefix);
21
+
22
+ if (!["read", "write"].includes(opts.permission)) {
23
+ console.error(chalk.red(`Invalid permission '${opts.permission}': must be one of read, write`));
24
+ process.exit(1);
25
+ }
26
+
27
+ const principal = opts.with;
28
+ const isEmail = EMAIL_PATTERN.test(principal);
29
+ const isGroup = GROUP_ID_PATTERN.test(principal);
30
+ if (!isEmail && !isGroup) {
31
+ console.error(chalk.red(`Invalid principal '${principal}': must be an email address or a group id matching grp_<alphanumeric>`));
32
+ process.exit(1);
33
+ }
34
+ const granteeType = isEmail ? "email" : "group";
35
+ const granteeId = isEmail ? principal.trim().toLowerCase() : principal;
36
+
37
+ const token = await ensureCognitoToken();
38
+ const companySlug = files.opts().company as string | undefined;
39
+ const companyUid = await getCompanyUid(token, companySlug);
40
+
41
+ const res = await vaultApiFetch({
42
+ token,
43
+ path: `/files/${encodeURIComponent(companyUid)}/acl/grant`,
44
+ method: "POST",
45
+ body: { prefix: canonicalPrefix, granteeType, granteeId, permission: opts.permission },
46
+ });
47
+
48
+ if (!res.ok) {
49
+ const body = await res.json().catch(() => ({})) as Record<string, string>;
50
+ if (res.status === 401) {
51
+ console.error(chalk.red("Not authenticated — please run `hq login`"));
52
+ } else if (res.status === 403) {
53
+ console.error(chalk.red("Not authorized to share this file prefix"));
54
+ } else if (res.status === 404) {
55
+ console.error(chalk.red("ACL record not found — the prefix may not have an ACL yet"));
56
+ } else if (res.status === 409) {
57
+ console.error(chalk.red("Concurrent modification — please retry"));
58
+ } else if (res.status >= 500) {
59
+ console.error(chalk.red(`Server error: ${body.error ?? res.statusText}`));
60
+ } else {
61
+ console.error(chalk.red(body.message ?? body.error ?? "Invalid request"));
62
+ }
63
+ process.exit(1);
64
+ }
65
+
66
+ const data = await res.json() as { acl?: { prefix?: string } };
67
+ const printedPrefix = data.acl?.prefix ?? canonicalPrefix;
68
+ console.log(chalk.green(`Granted ${opts.permission} on ${printedPrefix} to ${granteeId}`));
69
+ } catch (err) {
70
+ console.error(chalk.red("Error:"), err instanceof Error ? err.message : String(err));
71
+ process.exit(1);
72
+ }
73
+ });
74
+
75
+ files
76
+ .command("unshare <prefix>")
77
+ .description("Remove a file access grant")
78
+ .requiredOption("--with <principal>", "Email address or group id to remove")
79
+ .action(async (prefix: string, opts: { with: string }) => {
80
+ try {
81
+ const canonicalPrefix = normalizeFilePrefix(prefix);
82
+
83
+ const principal = opts.with;
84
+ const isEmail = EMAIL_PATTERN.test(principal);
85
+ const isGroup = GROUP_ID_PATTERN.test(principal);
86
+ if (!isEmail && !isGroup) {
87
+ console.error(chalk.red(`Invalid principal '${principal}': must be an email address or a group id matching grp_<alphanumeric>`));
88
+ process.exit(1);
89
+ }
90
+ const granteeType = isEmail ? "email" : "group";
91
+ const granteeId = isEmail ? principal.trim().toLowerCase() : principal;
92
+
93
+ const token = await ensureCognitoToken();
94
+ const companySlug = files.opts().company as string | undefined;
95
+ const companyUid = await getCompanyUid(token, companySlug);
96
+
97
+ const res = await vaultApiFetch({
98
+ token,
99
+ path: `/files/${encodeURIComponent(companyUid)}/acl/revoke`,
100
+ method: "POST",
101
+ body: { prefix: canonicalPrefix, granteeType, granteeId },
102
+ });
103
+
104
+ if (!res.ok) {
105
+ const body = await res.json().catch(() => ({})) as Record<string, string>;
106
+ if (res.status === 401) {
107
+ console.error(chalk.red("Not authenticated — please run `hq login`"));
108
+ } else if (res.status === 403) {
109
+ console.error(chalk.red("Not authorized to modify this file prefix's ACL"));
110
+ } else if (res.status === 404) {
111
+ console.log(chalk.green(`Grant already absent for '${canonicalPrefix}' / ${granteeId}`));
112
+ return;
113
+ } else if (res.status >= 500) {
114
+ console.error(chalk.red(`Server error: ${body.error ?? res.statusText}`));
115
+ } else {
116
+ console.error(chalk.red(body.message ?? body.error ?? "Invalid request"));
117
+ }
118
+ process.exit(1);
119
+ }
120
+
121
+ console.log(chalk.green(`Removed grant for ${granteeId} on '${canonicalPrefix}'`));
122
+ } catch (err) {
123
+ console.error(chalk.red("Error:"), err instanceof Error ? err.message : String(err));
124
+ process.exit(1);
125
+ }
126
+ });
127
+
128
+ files
129
+ .command("acl <prefix>")
130
+ .description("Show the ACL (access control list) for a file prefix")
131
+ .action(async (prefix: string) => {
132
+ try {
133
+ const canonicalPrefix = normalizeFilePrefix(prefix);
134
+
135
+ const token = await ensureCognitoToken();
136
+ const companySlug = files.opts().company as string | undefined;
137
+ const companyUid = await getCompanyUid(token, companySlug);
138
+
139
+ const res = await vaultApiFetch({
140
+ token,
141
+ path: `/files/${encodeURIComponent(companyUid)}/acl`,
142
+ query: { prefix: canonicalPrefix },
143
+ });
144
+
145
+ if (!res.ok) {
146
+ const body = await res.json().catch(() => ({})) as Record<string, string>;
147
+ if (res.status === 401) {
148
+ console.error(chalk.red("Not authenticated — please run `hq login`"));
149
+ } else if (res.status === 403) {
150
+ console.error(chalk.red("Not authorized to view this file prefix's ACL"));
151
+ } else if (res.status === 404) {
152
+ console.error(chalk.red(`No ACL record exists for '${canonicalPrefix}'`));
153
+ } else if (res.status >= 500) {
154
+ console.error(chalk.red(`Server error: ${body.error ?? res.statusText}`));
155
+ } else {
156
+ console.error(chalk.red(body.message ?? body.error ?? "Invalid request"));
157
+ }
158
+ process.exit(1);
159
+ }
160
+
161
+ const data = await res.json() as {
162
+ acl: {
163
+ itemType: string;
164
+ companyUid: string;
165
+ prefix: string;
166
+ creatorUid: string;
167
+ open?: boolean;
168
+ entries: Array<{
169
+ granteeType: string;
170
+ granteeId: string;
171
+ permission: string;
172
+ grantedBy: string;
173
+ grantedAt: string;
174
+ }>;
175
+ effectivePermission?: string | null;
176
+ createdAt: string;
177
+ updatedAt: string;
178
+ };
179
+ };
180
+
181
+ const acl = data.acl;
182
+ const aclStatus = acl.open ? "open" : "restricted";
183
+
184
+ console.log(chalk.green(`ACL for ${acl.prefix} (${aclStatus})`));
185
+ console.log(`Creator: ${acl.creatorUid}`);
186
+ if (acl.effectivePermission) {
187
+ console.log(`Your effective permission: ${acl.effectivePermission}`);
188
+ }
189
+
190
+ if (acl.entries.length === 0) {
191
+ if (acl.open) {
192
+ console.log(chalk.gray("Open ACL — all active members have read access."));
193
+ } else {
194
+ console.log(chalk.gray("No explicit grants — only creator has access."));
195
+ }
196
+ return;
197
+ }
198
+
199
+ console.log("Entries:");
200
+ const TYPE_W = Math.max(4, ...acl.entries.map((e) => e.granteeType.length));
201
+ const GRANTEE_W = Math.max(7, ...acl.entries.map((e) => e.granteeId.length));
202
+ const PERM_W = Math.max(10, ...acl.entries.map((e) => e.permission.length));
203
+ const BY_W = Math.max(10, ...acl.entries.map((e) => e.grantedBy.length));
204
+ const tableHeader = [
205
+ "TYPE".padEnd(TYPE_W),
206
+ "GRANTEE".padEnd(GRANTEE_W),
207
+ "PERMISSION".padEnd(PERM_W),
208
+ "GRANTED_BY".padEnd(BY_W),
209
+ "GRANTED_AT",
210
+ ].join(" ");
211
+ console.log(chalk.bold(tableHeader));
212
+ for (const e of acl.entries) {
213
+ const grantedAt = e.grantedAt.slice(0, 10);
214
+ console.log([
215
+ e.granteeType.padEnd(TYPE_W),
216
+ e.granteeId.padEnd(GRANTEE_W),
217
+ e.permission.padEnd(PERM_W),
218
+ e.grantedBy.padEnd(BY_W),
219
+ grantedAt,
220
+ ].join(" "));
221
+ }
222
+ } catch (err) {
223
+ console.error(chalk.red("Error:"), err instanceof Error ? err.message : String(err));
224
+ process.exit(1);
225
+ }
226
+ });
227
+ }
package/src/index.ts CHANGED
@@ -24,6 +24,7 @@ import { registerTeamSyncCommand } from "./commands/team-sync.js";
24
24
  import { registerAuthCommands } from "./commands/auth.js";
25
25
  import { registerSecretsCommand } from "./commands/secrets.js";
26
26
  import { registerGroupsCommand } from "./commands/groups.js";
27
+ import { registerFilesCommand } from "./commands/files.js";
27
28
 
28
29
  initSentry();
29
30
 
@@ -32,7 +33,7 @@ const program = new Command();
32
33
  program
33
34
  .name("hq")
34
35
  .description("HQ management CLI — modules, packages, and cloud sync")
35
- .version("5.5.0");
36
+ .version("5.8.3");
36
37
 
37
38
  // Module management subcommand group
38
39
  const modulesCmd = program
@@ -92,6 +93,9 @@ registerSecretsCommand(program);
92
93
  // Groups management (subcommand group — hq groups create|delete|add|remove|list|members)
93
94
  registerGroupsCommand(program);
94
95
 
96
+ // Files ACL management (subcommand group — hq files share|unshare|acl)
97
+ registerFilesCommand(program);
98
+
95
99
  // Onboarding (top-level — Cognito + vault-service provisioning)
96
100
  registerOnboardCommand(program);
97
101
 
@@ -50,8 +50,7 @@ export const DEFAULT_COGNITO: CognitoAuthConfig = {
50
50
  };
51
51
 
52
52
  export const DEFAULT_VAULT_API_URL =
53
- process.env.HQ_VAULT_API_URL ??
54
- "https://4nfy67z28h.execute-api.us-east-1.amazonaws.com";
53
+ process.env.HQ_VAULT_API_URL ?? "https://hqapi.getindigo.ai";
55
54
 
56
55
  export const DEFAULT_HQ_ROOT = path.join(os.homedir(), "hq");
57
56