@indigoai-us/hq-cli 5.10.0 → 5.11.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.
@@ -13,11 +13,11 @@
13
13
  * hq sync status — show local journal summary
14
14
  */
15
15
 
16
- !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]="5ddbce6a-fdde-506e-8cdd-2e5b0ad20fff")}catch(e){}}();
16
+ !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]="213f074e-13c2-5b95-9519-813adf9adfa9")}catch(e){}}();
17
17
  import chalk from "chalk";
18
18
  import * as fs from "fs";
19
19
  import * as path from "path";
20
- import { share, sync, readJournal, getJournalPath, } from "@indigoai-us/hq-cloud";
20
+ import { share, sync, readJournal, getJournalPath, loadCachedTokens, } from "@indigoai-us/hq-cloud";
21
21
  import { DEFAULT_HQ_ROOT, ensureCognitoToken, buildVaultConfig, } from "../utils/cognito-session.js";
22
22
  export function registerCloudCommands(program) {
23
23
  program
@@ -85,6 +85,13 @@ export function registerCloudCommands(program) {
85
85
  const onEvent = jsonMode
86
86
  ? (event) => emitJson(event)
87
87
  : undefined;
88
+ // Stamp every uploaded object's S3 user metadata with the syncing
89
+ // user's Cognito identity (`Metadata['created-by']`). The hq-console
90
+ // vault UI's CREATED BY column reads this back via HEAD; without it,
91
+ // every row renders `—`. Resolved best-effort from the cached
92
+ // idToken — pre-vended `--creds-from-stdin` paths still get author
93
+ // attribution as long as the caller is logged in locally.
94
+ const author = resolveUploadAuthorFromCache();
88
95
  const result = await share({
89
96
  paths: targetPaths,
90
97
  company: options.company,
@@ -94,6 +101,7 @@ export function registerCloudCommands(program) {
94
101
  entityContext,
95
102
  hqRoot: options.hqRoot,
96
103
  onEvent,
104
+ ...(author ? { author } : {}),
97
105
  });
98
106
  if (jsonMode) {
99
107
  // Synthetic terminal event so subprocess consumers can read final
@@ -226,5 +234,36 @@ async function readAllStdin() {
226
234
  }
227
235
  return Buffer.concat(chunks).toString("utf8");
228
236
  }
237
+ /**
238
+ * Resolve the syncing user's `UploadAuthor` (sub + email) from the cached
239
+ * Cognito idToken. Returns `undefined` when no tokens are cached or the
240
+ * token is missing the required claims — share() then skips the metadata
241
+ * stamp gracefully (not an error).
242
+ *
243
+ * We deliberately decode the JWT here instead of verifying it: Cognito
244
+ * already verified at issuance, and we only use the public claims to
245
+ * label the upload's S3 user metadata (no auth decision rides on it).
246
+ */
247
+ function resolveUploadAuthorFromCache() {
248
+ const tokens = loadCachedTokens();
249
+ if (!tokens?.idToken)
250
+ return undefined;
251
+ const parts = tokens.idToken.split(".");
252
+ if (parts.length !== 3)
253
+ return undefined;
254
+ try {
255
+ const payload = parts[1].replace(/-/g, "+").replace(/_/g, "/");
256
+ const padded = payload + "=".repeat((4 - (payload.length % 4)) % 4);
257
+ const json = Buffer.from(padded, "base64").toString("utf-8");
258
+ const claims = JSON.parse(json);
259
+ if (claims.sub && claims.email) {
260
+ return { userSub: claims.sub, email: claims.email };
261
+ }
262
+ return undefined;
263
+ }
264
+ catch {
265
+ return undefined;
266
+ }
267
+ }
229
268
  //# sourceMappingURL=cloud.js.map
230
- //# debugId=5ddbce6a-fdde-506e-8cdd-2e5b0ad20fff
269
+ //# debugId=213f074e-13c2-5b95-9519-813adf9adfa9
@@ -0,0 +1,53 @@
1
+ import { Command } from "commander";
2
+ export declare const VALID_ROLES: Set<string>;
3
+ export type Role = "owner" | "admin" | "member" | "guest";
4
+ export interface PendingInvite {
5
+ membershipKey: string;
6
+ personUid?: string;
7
+ inviteeEmail?: string;
8
+ companyUid: string;
9
+ role: string;
10
+ status: string;
11
+ inviteToken?: string;
12
+ invitedBy: string;
13
+ invitedAt: string;
14
+ }
15
+ export interface InviteOptions {
16
+ target: string;
17
+ role: string;
18
+ paths?: string;
19
+ companyUid: string;
20
+ callerUid: string;
21
+ token: string;
22
+ }
23
+ export interface InviteResult {
24
+ inviteToken: string;
25
+ magicLink: string;
26
+ membership: {
27
+ role: string;
28
+ status: string;
29
+ };
30
+ }
31
+ export interface DetectedTarget {
32
+ type: "email" | "person";
33
+ value: string;
34
+ }
35
+ export declare function detectTarget(target: string): DetectedTarget | null;
36
+ export declare function shortDate(iso: string): string;
37
+ /**
38
+ * Resolve the caller's personUid by reading their own membership list.
39
+ * The server infers the JWT identity, so this returns the canonical
40
+ * personUid attached to the caller's active memberships.
41
+ */
42
+ export declare function getCallerPersonUid(token: string): Promise<string>;
43
+ /** Send a `/membership/invite` request and return the magic link. */
44
+ export declare function inviteMember(options: InviteOptions): Promise<InviteResult>;
45
+ export declare class InviteHttpError extends Error {
46
+ status: number;
47
+ constructor(status: number, message: string);
48
+ }
49
+ export declare function formatInviteHttpError(status: number, fallback: string): string;
50
+ export declare function listPendingInvites(token: string, companyUid: string): Promise<PendingInvite[]>;
51
+ export declare function revokeInvite(token: string, tokenOrKey: string, companyUid: string): Promise<void>;
52
+ export declare function registerMembersCommand(program: Command): void;
53
+ //# sourceMappingURL=members.d.ts.map
@@ -0,0 +1,240 @@
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]="1b3646e3-32c8-5601-9dba-1d7c0f6cf972")}catch(e){}}();
3
+ import chalk from "chalk";
4
+ import { ensureCognitoToken } from "../utils/cognito-session.js";
5
+ import { vaultApiFetch, getCompanyUid } from "../utils/vault-api.js";
6
+ const EMAIL_PATTERN = /^[^\s]+@[^\s]+$/;
7
+ const PERSON_UID_PATTERN = /^prs_[A-Za-z0-9_-]+$/;
8
+ export const VALID_ROLES = new Set(["owner", "admin", "member", "guest"]);
9
+ export function detectTarget(target) {
10
+ if (EMAIL_PATTERN.test(target)) {
11
+ return { type: "email", value: target.trim().toLowerCase() };
12
+ }
13
+ if (PERSON_UID_PATTERN.test(target)) {
14
+ return { type: "person", value: target };
15
+ }
16
+ return null;
17
+ }
18
+ export function shortDate(iso) {
19
+ return iso.slice(0, 10);
20
+ }
21
+ /**
22
+ * Resolve the caller's personUid by reading their own membership list.
23
+ * The server infers the JWT identity, so this returns the canonical
24
+ * personUid attached to the caller's active memberships.
25
+ */
26
+ export async function getCallerPersonUid(token) {
27
+ const res = await vaultApiFetch({ token, path: "/membership/me" });
28
+ if (!res.ok) {
29
+ throw new Error("Failed to resolve caller identity — run `hq login` and try again");
30
+ }
31
+ const data = (await res.json());
32
+ const personUid = data.memberships.find((m) => m.personUid)?.personUid;
33
+ if (!personUid) {
34
+ throw new Error("Your account has no person entity yet. Run `hq onboard create-company` or accept an invite first.");
35
+ }
36
+ return personUid;
37
+ }
38
+ /** Send a `/membership/invite` request and return the magic link. */
39
+ export async function inviteMember(options) {
40
+ if (!VALID_ROLES.has(options.role)) {
41
+ throw new Error(`Invalid role '${options.role}': must be one of owner, admin, member, guest`);
42
+ }
43
+ if (options.paths && options.role !== "guest") {
44
+ throw new Error("--paths is only valid with --role guest (allowedPrefixes are only meaningful for the guest role)");
45
+ }
46
+ const detected = detectTarget(options.target);
47
+ if (!detected) {
48
+ throw new Error(`Invalid target '${options.target}': must be an email address or a personUid matching prs_<alphanumeric>`);
49
+ }
50
+ const allowedPrefixes = options.paths
51
+ ? options.paths.split(",").map((p) => p.trim()).filter(Boolean)
52
+ : undefined;
53
+ const body = {
54
+ companyUid: options.companyUid,
55
+ role: options.role,
56
+ invitedBy: options.callerUid,
57
+ };
58
+ if (detected.type === "email")
59
+ body.inviteeEmail = detected.value;
60
+ else
61
+ body.personUid = detected.value;
62
+ if (allowedPrefixes)
63
+ body.allowedPrefixes = allowedPrefixes;
64
+ const res = await vaultApiFetch({
65
+ token: options.token,
66
+ path: "/membership/invite",
67
+ method: "POST",
68
+ body,
69
+ });
70
+ if (!res.ok) {
71
+ const err = (await res.json().catch(() => ({})));
72
+ throw new InviteHttpError(res.status, err.message ?? err.error ?? res.statusText);
73
+ }
74
+ const data = (await res.json());
75
+ return {
76
+ inviteToken: data.inviteToken,
77
+ magicLink: `hq://accept/${data.inviteToken}`,
78
+ membership: data.membership,
79
+ };
80
+ }
81
+ export class InviteHttpError extends Error {
82
+ status;
83
+ constructor(status, message) {
84
+ super(message);
85
+ this.status = status;
86
+ this.name = "InviteHttpError";
87
+ }
88
+ }
89
+ export function formatInviteHttpError(status, fallback) {
90
+ if (status === 401)
91
+ return "Not authenticated — please run `hq login`";
92
+ if (status === 403) {
93
+ return "Not authorized — only admins and owners can invite members";
94
+ }
95
+ if (status === 409) {
96
+ return "This person already has a membership or pending invite for this company";
97
+ }
98
+ if (status >= 500)
99
+ return `Server error: ${fallback}`;
100
+ return fallback;
101
+ }
102
+ export async function listPendingInvites(token, companyUid) {
103
+ const res = await vaultApiFetch({
104
+ token,
105
+ path: `/membership/company/${encodeURIComponent(companyUid)}/pending`,
106
+ });
107
+ if (!res.ok) {
108
+ const err = (await res.json().catch(() => ({})));
109
+ throw new InviteHttpError(res.status, err.message ?? err.error ?? res.statusText);
110
+ }
111
+ const data = (await res.json());
112
+ return data.invites;
113
+ }
114
+ export async function revokeInvite(token, tokenOrKey, companyUid) {
115
+ const res = await vaultApiFetch({
116
+ token,
117
+ path: "/membership/revoke",
118
+ method: "POST",
119
+ body: { membershipKey: tokenOrKey, companyUid },
120
+ });
121
+ if (!res.ok) {
122
+ const err = (await res.json().catch(() => ({})));
123
+ throw new InviteHttpError(res.status, err.message ?? err.error ?? res.statusText);
124
+ }
125
+ }
126
+ export function registerMembersCommand(program) {
127
+ const members = program
128
+ .command("members")
129
+ .description("Manage company memberships and invites")
130
+ .option("--company <slug>", "Company slug (resolves to companyUid)");
131
+ members
132
+ .command("invite <target>")
133
+ .description("Invite a person to the company by email or personUid (prints a magic link)")
134
+ .option("--role <role>", "Role for the invitee: owner, admin, member, or guest", "member")
135
+ .option("--paths <prefixes>", "Comma-separated allowed prefixes (only valid with --role guest)")
136
+ .action(async (target, opts) => {
137
+ try {
138
+ const token = await ensureCognitoToken();
139
+ const companySlug = members.opts().company;
140
+ const companyUid = await getCompanyUid(token, companySlug);
141
+ const callerUid = await getCallerPersonUid(token);
142
+ const result = await inviteMember({
143
+ target,
144
+ role: opts.role,
145
+ paths: opts.paths,
146
+ companyUid,
147
+ callerUid,
148
+ token,
149
+ });
150
+ console.log(chalk.green(`Invited ${target} as ${result.membership.role} (status: ${result.membership.status})`));
151
+ console.log();
152
+ console.log(chalk.bold("Magic link:"));
153
+ console.log(` ${result.magicLink}`);
154
+ console.log();
155
+ console.log(chalk.dim("Share this link with the invitee. They can run `hq onboard join --invite-token <token>` to accept."));
156
+ }
157
+ catch (err) {
158
+ if (err instanceof InviteHttpError) {
159
+ console.error(chalk.red(formatInviteHttpError(err.status, err.message)));
160
+ process.exit(1);
161
+ }
162
+ console.error(chalk.red("Error:"), err instanceof Error ? err.message : String(err));
163
+ process.exit(1);
164
+ }
165
+ });
166
+ members
167
+ .command("list")
168
+ .description("List pending invites for the company")
169
+ .action(async () => {
170
+ try {
171
+ const token = await ensureCognitoToken();
172
+ const companySlug = members.opts().company;
173
+ const companyUid = await getCompanyUid(token, companySlug);
174
+ const invites = await listPendingInvites(token, companyUid);
175
+ if (invites.length === 0) {
176
+ console.log(chalk.gray("No pending invites for this company."));
177
+ return;
178
+ }
179
+ const targetW = Math.max(6, ...invites.map((i) => (i.inviteeEmail ?? i.personUid ?? "").length));
180
+ const roleW = Math.max(4, ...invites.map((i) => i.role.length));
181
+ const byW = Math.max(10, ...invites.map((i) => i.invitedBy.length));
182
+ const keyW = Math.max(14, ...invites.map((i) => i.membershipKey.length));
183
+ console.log(chalk.bold([
184
+ "TARGET".padEnd(targetW),
185
+ "ROLE".padEnd(roleW),
186
+ "INVITED_BY".padEnd(byW),
187
+ "INVITED_AT",
188
+ "MEMBERSHIP_KEY".padEnd(keyW),
189
+ ].join(" ")));
190
+ for (const inv of invites) {
191
+ const target = inv.inviteeEmail ?? inv.personUid ?? "";
192
+ console.log([
193
+ target.padEnd(targetW),
194
+ inv.role.padEnd(roleW),
195
+ inv.invitedBy.padEnd(byW),
196
+ shortDate(inv.invitedAt),
197
+ inv.membershipKey.padEnd(keyW),
198
+ ].join(" "));
199
+ }
200
+ }
201
+ catch (err) {
202
+ if (err instanceof InviteHttpError) {
203
+ const msg = err.status === 403
204
+ ? "Not authorized — only admins and owners can list invites"
205
+ : formatInviteHttpError(err.status, err.message);
206
+ console.error(chalk.red(msg));
207
+ process.exit(1);
208
+ }
209
+ console.error(chalk.red("Error:"), err instanceof Error ? err.message : String(err));
210
+ process.exit(1);
211
+ }
212
+ });
213
+ members
214
+ .command("revoke <tokenOrKey>")
215
+ .description("Revoke a pending invite (accepts the inviteToken or membershipKey)")
216
+ .action(async (tokenOrKey) => {
217
+ try {
218
+ const token = await ensureCognitoToken();
219
+ const companySlug = members.opts().company;
220
+ const companyUid = await getCompanyUid(token, companySlug);
221
+ await revokeInvite(token, tokenOrKey, companyUid);
222
+ console.log(chalk.green(`Revoked invite '${tokenOrKey}'`));
223
+ }
224
+ catch (err) {
225
+ if (err instanceof InviteHttpError) {
226
+ const msg = err.status === 403
227
+ ? "Not authorized — only admins and owners can revoke invites"
228
+ : err.status === 404
229
+ ? "Invite not found — it may have already been accepted or revoked"
230
+ : formatInviteHttpError(err.status, err.message);
231
+ console.error(chalk.red(msg));
232
+ process.exit(1);
233
+ }
234
+ console.error(chalk.red("Error:"), err instanceof Error ? err.message : String(err));
235
+ process.exit(1);
236
+ }
237
+ });
238
+ }
239
+ //# sourceMappingURL=members.js.map
240
+ //# debugId=1b3646e3-32c8-5601-9dba-1d7c0f6cf972
@@ -1,6 +1,6 @@
1
1
  import { Command } from "commander";
2
- import { vaultApiFetch, getCompanyUid } from "../utils/vault-api.js";
2
+ import { vaultApiFetch, getCompanyUid, getEntityUid } from "../utils/vault-api.js";
3
3
  export type { VaultApiOptions } from "../utils/vault-api.js";
4
- export { vaultApiFetch, getCompanyUid };
4
+ export { vaultApiFetch, getCompanyUid, getEntityUid };
5
5
  export declare function registerSecretsCommand(program: Command): void;
6
6
  //# sourceMappingURL=secrets.d.ts.map
@@ -1,13 +1,26 @@
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]="0239c476-98b8-53c0-9458-1c82dc0d26a9")}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]="217ceadb-01cd-5778-9cc0-6f6d895d8d67")}catch(e){}}();
3
3
  import chalk from "chalk";
4
4
  import * as readline from "node:readline";
5
5
  import { spawn } from "node:child_process";
6
6
  import { ensureCognitoToken } from "../utils/cognito-session.js";
7
7
  import { readCache, writeCache, removeCacheEntry, clearAllCache, } from "../utils/secrets-cache.js";
8
8
  import { SECRET_NAME_PATTERN, GROUP_ID_PATTERN } from "./_patterns.js";
9
- import { vaultApiFetch, getCompanyUid } from "../utils/vault-api.js";
10
- export { vaultApiFetch, getCompanyUid };
9
+ import { vaultApiFetch, getCompanyUid, getEntityUid, } from "../utils/vault-api.js";
10
+ export { vaultApiFetch, getCompanyUid, getEntityUid };
11
+ function scopeOpts(opts) {
12
+ if (opts.personal && opts.company) {
13
+ console.error(chalk.red("Error: --personal cannot be combined with --company."));
14
+ process.exit(1);
15
+ }
16
+ return { personal: !!opts.personal, companySlug: opts.company };
17
+ }
18
+ function rejectIfPersonal(opts, action) {
19
+ if (opts.personal) {
20
+ console.error(chalk.red(`Error: ${action} is not supported with --personal.`));
21
+ process.exit(1);
22
+ }
23
+ }
11
24
  function shellSingleQuote(value) {
12
25
  return "'" + value.replace(/'/g, "'\\''") + "'";
13
26
  }
@@ -115,7 +128,8 @@ export function registerSecretsCommand(program) {
115
128
  const secrets = program
116
129
  .command("secrets")
117
130
  .description("Manage secrets in HQ vault (SSM Parameter Store)")
118
- .option("--company <slug>", "Company slug (resolves to companyUid)");
131
+ .option("--company <slug>", "Company slug (resolves to companyUid)")
132
+ .option("--personal", "Operate on the caller's personal vault (no sharing)");
119
133
  secrets
120
134
  .command("set <name>")
121
135
  .description("Create or update a secret")
@@ -150,8 +164,7 @@ export function registerSecretsCommand(program) {
150
164
  process.exit(1);
151
165
  }
152
166
  const token = await ensureCognitoToken();
153
- const companySlug = secrets.opts().company;
154
- const companyUid = await getCompanyUid(token, companySlug);
167
+ const companyUid = await getEntityUid(token, scopeOpts(secrets.opts()));
155
168
  const res = await vaultApiFetch({
156
169
  token,
157
170
  path: `/secrets/${encodeURIComponent(companyUid)}`,
@@ -178,8 +191,7 @@ export function registerSecretsCommand(program) {
178
191
  .action(async (name, opts) => {
179
192
  try {
180
193
  const token = await ensureCognitoToken();
181
- const companySlug = secrets.opts().company;
182
- const companyUid = await getCompanyUid(token, companySlug);
194
+ const companyUid = await getEntityUid(token, scopeOpts(secrets.opts()));
183
195
  const query = {};
184
196
  if (opts.reveal) {
185
197
  query.reveal = "true";
@@ -233,8 +245,7 @@ export function registerSecretsCommand(program) {
233
245
  normalizedPrefix = normalized;
234
246
  }
235
247
  const token = await ensureCognitoToken();
236
- const companySlug = secrets.opts().company;
237
- const companyUid = await getCompanyUid(token, companySlug);
248
+ const companyUid = await getEntityUid(token, scopeOpts(secrets.opts()));
238
249
  const query = {};
239
250
  if (normalizedPrefix) {
240
251
  query.prefix = normalizedPrefix;
@@ -298,8 +309,7 @@ export function registerSecretsCommand(program) {
298
309
  }
299
310
  }
300
311
  const token = await ensureCognitoToken();
301
- const companySlug = secrets.opts().company;
302
- const companyUid = await getCompanyUid(token, companySlug);
312
+ const companyUid = await getEntityUid(token, scopeOpts(secrets.opts()));
303
313
  const res = await vaultApiFetch({
304
314
  token,
305
315
  path: buildSecretNamePath(companyUid, name),
@@ -350,8 +360,7 @@ export function registerSecretsCommand(program) {
350
360
  }
351
361
  }
352
362
  const token = await ensureCognitoToken();
353
- const companySlug = secrets.opts().company;
354
- const companyUid = await getCompanyUid(token, companySlug);
363
+ const companyUid = await getEntityUid(token, scopeOpts(secrets.opts()));
355
364
  const revealed = await Promise.all(keys.map(async (key) => {
356
365
  const cached = readCache(companyUid, key);
357
366
  if (cached !== null) {
@@ -420,8 +429,7 @@ export function registerSecretsCommand(program) {
420
429
  }
421
430
  }
422
431
  const token = await ensureCognitoToken();
423
- const companySlug = secrets.opts().company;
424
- const companyUid = await getCompanyUid(token, companySlug);
432
+ const companyUid = await getEntityUid(token, scopeOpts(secrets.opts()));
425
433
  const revealed = await Promise.all(keys.map(async (key) => {
426
434
  const cached = readCache(companyUid, key);
427
435
  if (cached !== null) {
@@ -459,6 +467,7 @@ export function registerSecretsCommand(program) {
459
467
  .option("--expires <duration>", "Token expiry duration (e.g. 24h, 2d, 30m)", "24h")
460
468
  .action(async (name, opts) => {
461
469
  try {
470
+ rejectIfPersonal(secrets.opts(), "generate-link");
462
471
  if (!SECRET_NAME_PATTERN.test(name)) {
463
472
  console.error(chalk.red(`Invalid secret name '${name}': must match ^[A-Z][A-Z0-9_]*(/[A-Z][A-Z0-9_]+)*$ (e.g. MY_API_KEY or DEV/MY_KEY)`));
464
473
  process.exit(1);
@@ -474,8 +483,7 @@ export function registerSecretsCommand(program) {
474
483
  process.exit(1);
475
484
  }
476
485
  const token = await ensureCognitoToken();
477
- const companySlug = secrets.opts().company;
478
- const companyUid = await getCompanyUid(token, companySlug);
486
+ const companyUid = await getEntityUid(token, scopeOpts(secrets.opts()));
479
487
  const res = await vaultApiFetch({
480
488
  token,
481
489
  path: buildSecretNamePath(companyUid, name),
@@ -507,6 +515,7 @@ export function registerSecretsCommand(program) {
507
515
  .requiredOption("--permission <level>", "Permission level: read | write | admin")
508
516
  .action(async (path, opts) => {
509
517
  try {
518
+ rejectIfPersonal(secrets.opts(), "share");
510
519
  if (!SECRET_NAME_PATTERN.test(path)) {
511
520
  console.error(chalk.red(`Invalid secret path '${path}': must match ^[A-Z][A-Z0-9_]*(/[A-Z][A-Z0-9_]+)*$ (e.g. MY_KEY or PROD/DB_PASSWORD)`));
512
521
  process.exit(1);
@@ -523,8 +532,7 @@ export function registerSecretsCommand(program) {
523
532
  const granteeType = isEmail ? "email" : "group";
524
533
  const granteeId = opts.with;
525
534
  const token = await ensureCognitoToken();
526
- const companySlug = secrets.opts().company;
527
- const companyUid = await getCompanyUid(token, companySlug);
535
+ const companyUid = await getEntityUid(token, scopeOpts(secrets.opts()));
528
536
  const res = await vaultApiFetch({
529
537
  token,
530
538
  path: `/secrets/${encodeURIComponent(companyUid)}/acl/grant`,
@@ -566,6 +574,7 @@ export function registerSecretsCommand(program) {
566
574
  .requiredOption("--from <principal>", "Email address or group id to remove")
567
575
  .action(async (path, opts) => {
568
576
  try {
577
+ rejectIfPersonal(secrets.opts(), "unshare");
569
578
  if (!SECRET_NAME_PATTERN.test(path)) {
570
579
  console.error(chalk.red(`Invalid secret path '${path}': must match ^[A-Z][A-Z0-9_]*(/[A-Z][A-Z0-9_]+)*$ (e.g. MY_KEY or PROD/DB_PASSWORD)`));
571
580
  process.exit(1);
@@ -578,8 +587,7 @@ export function registerSecretsCommand(program) {
578
587
  const granteeType = isEmailFrom ? "email" : "group";
579
588
  const granteeId = opts.from;
580
589
  const token = await ensureCognitoToken();
581
- const companySlug = secrets.opts().company;
582
- const companyUid = await getCompanyUid(token, companySlug);
590
+ const companyUid = await getEntityUid(token, scopeOpts(secrets.opts()));
583
591
  const res = await vaultApiFetch({
584
592
  token,
585
593
  path: `/secrets/${encodeURIComponent(companyUid)}/acl/revoke`,
@@ -618,13 +626,13 @@ export function registerSecretsCommand(program) {
618
626
  .description("Show the ACL (access control list) for a secret path")
619
627
  .action(async (path) => {
620
628
  try {
629
+ rejectIfPersonal(secrets.opts(), "acl");
621
630
  if (!SECRET_NAME_PATTERN.test(path)) {
622
631
  console.error(chalk.red(`Invalid secret path '${path}': must match ^[A-Z][A-Z0-9_]*(/[A-Z][A-Z0-9_]+)*$ (e.g. MY_KEY or PROD/DB_PASSWORD)`));
623
632
  process.exit(1);
624
633
  }
625
634
  const token = await ensureCognitoToken();
626
- const companySlug = secrets.opts().company;
627
- const companyUid = await getCompanyUid(token, companySlug);
635
+ const companyUid = await getEntityUid(token, scopeOpts(secrets.opts()));
628
636
  const secretPath = path;
629
637
  const res = await vaultApiFetch({
630
638
  token,
@@ -705,4 +713,4 @@ export function registerSecretsCommand(program) {
705
713
  });
706
714
  }
707
715
  //# sourceMappingURL=secrets.js.map
708
- //# debugId=0239c476-98b8-53c0-9458-1c82dc0d26a9
716
+ //# debugId=217ceadb-01cd-5778-9cc0-6f6d895d8d67
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]="ebc3116c-f836-550a-b5bb-4a74bce24abc")}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]="e834c407-cd5c-5dc1-af3b-7f041af651ef")}catch(e){}}();
7
7
  import { Command } from "commander";
8
8
  import { initSentry, Sentry } from "./sentry.js";
9
9
  import { registerAddCommand } from "./commands/add.js";
@@ -27,6 +27,7 @@ import { registerSecretsCommand } from "./commands/secrets.js";
27
27
  import { registerRunCommand } from "./commands/run.js";
28
28
  import { registerGroupsCommand } from "./commands/groups.js";
29
29
  import { registerFilesCommand } from "./commands/files.js";
30
+ import { registerMembersCommand } from "./commands/members.js";
30
31
  initSentry();
31
32
  const program = new Command();
32
33
  program
@@ -81,6 +82,8 @@ registerRunCommand(program);
81
82
  registerGroupsCommand(program);
82
83
  // Files ACL management (subcommand group — hq files share|unshare|acl)
83
84
  registerFilesCommand(program);
85
+ // Membership management (subcommand group — hq members invite|list|revoke)
86
+ registerMembersCommand(program);
84
87
  // Onboarding (top-level — Cognito + vault-service provisioning)
85
88
  registerOnboardCommand(program);
86
89
  (async () => {
@@ -96,4 +99,4 @@ registerOnboardCommand(program);
96
99
  }
97
100
  })();
98
101
  //# sourceMappingURL=index.js.map
99
- //# debugId=ebc3116c-f836-550a-b5bb-4a74bce24abc
102
+ //# debugId=e834c407-cd5c-5dc1-af3b-7f041af651ef
@@ -7,4 +7,9 @@ export interface VaultApiOptions {
7
7
  }
8
8
  export declare function vaultApiFetch(opts: VaultApiOptions): Promise<Response>;
9
9
  export declare function getCompanyUid(token: string, companySlug: string | undefined): Promise<string>;
10
+ export declare function resolveCallerPersonUid(token: string): Promise<string>;
11
+ export declare function getEntityUid(token: string, opts: {
12
+ personal?: boolean;
13
+ companySlug?: string;
14
+ }): Promise<string>;
10
15
  //# sourceMappingURL=vault-api.d.ts.map
@@ -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]="4e482805-d129-5563-a77b-97b80410154b")}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]="05d73a12-54b0-559c-a964-de7dff2d48eb")}catch(e){}}();
3
3
  import { DEFAULT_VAULT_API_URL } from './cognito-session.js';
4
4
  export async function vaultApiFetch(opts) {
5
5
  const url = new URL(opts.path, DEFAULT_VAULT_API_URL);
@@ -54,5 +54,38 @@ export async function getCompanyUid(token, companySlug) {
54
54
  }
55
55
  return resolveCompanyFromMemberships(token);
56
56
  }
57
+ // Same selection rule as the backend's `resolveCallerPersonUid`: ascending by
58
+ // createdAt, tie-break by uid ascending. Returns the `prs_*` UID.
59
+ export async function resolveCallerPersonUid(token) {
60
+ const res = await vaultApiFetch({
61
+ token,
62
+ path: '/entity/by-type/person',
63
+ });
64
+ if (!res.ok) {
65
+ throw new Error("Failed to fetch person entity — run `hq login` and try again");
66
+ }
67
+ const data = (await res.json());
68
+ const persons = (data.entities ?? []).filter((e) => e.type === 'person');
69
+ if (persons.length === 0) {
70
+ throw new Error('No person entity found for the caller. Sign in to HQ once to provision one.');
71
+ }
72
+ persons.sort((a, b) => {
73
+ const ac = a.createdAt ?? '';
74
+ const bc = b.createdAt ?? '';
75
+ if (ac !== bc)
76
+ return ac < bc ? -1 : 1;
77
+ return a.uid < b.uid ? -1 : 1;
78
+ });
79
+ return persons[0].uid;
80
+ }
81
+ // Resolves the scope UID (cmp_* or prs_*) for a secrets command. Precedence:
82
+ // `--personal` → caller's canonical person entity; else `--company <slug>` →
83
+ // resolved company UID; else fallback to single active company membership.
84
+ export async function getEntityUid(token, opts) {
85
+ if (opts.personal) {
86
+ return resolveCallerPersonUid(token);
87
+ }
88
+ return getCompanyUid(token, opts.companySlug);
89
+ }
57
90
  //# sourceMappingURL=vault-api.js.map
58
- //# debugId=4e482805-d129-5563-a77b-97b80410154b
91
+ //# debugId=05d73a12-54b0-559c-a964-de7dff2d48eb
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@indigoai-us/hq-cli",
3
- "version": "5.10.0",
3
+ "version": "5.11.0",
4
4
  "description": "HQ by Indigo management CLI — modules and cloud sync",
5
5
  "main": "dist/index.js",
6
6
  "bin": {