@indigoai-us/hq-cli 5.57.0 → 5.58.1

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.
@@ -6,21 +6,25 @@
6
6
  * hq people search <keyword> [--company <slug>] [--json]
7
7
  * hq people resolve <name> [--company <slug>] [--json]
8
8
  *
9
- * Source of truth is `companies/<company>/people/<person>/meta.yaml`. Every
10
- * subcommand operates on exactly ONE company (the active company, or the one
11
- * named by `--company`); nothing reads across company boundaries.
9
+ * Local records from `companies/<company>/people/<person>/meta.yaml` are the
10
+ * curated primary source. On misses, commands may fall back to the membership
11
+ * roster for exactly ONE company (the active company, or the one named by
12
+ * `--company`); nothing reads across company boundaries.
12
13
  */
13
14
  import { Command } from "commander";
15
+ import { type ActiveMember } from "./members.js";
14
16
  import { resolveNameToEmail, type PersonRecord } from "../utils/people.js";
15
- export type RefreshPeopleRoster = (hqRoot: string, companySlug: string) => Promise<void>;
17
+ export type FetchPeopleRoster = (hqRoot: string, companySlug: string) => Promise<PersonRecord[]>;
16
18
  interface PeopleCommandDeps {
17
- refreshRoster?: RefreshPeopleRoster;
19
+ fetchRoster?: FetchPeopleRoster;
18
20
  }
19
21
  interface PeopleLookupOpts {
20
22
  localOnly?: boolean;
21
23
  json?: boolean;
22
24
  }
23
- export declare function refreshPeopleRosterFromCloud(hqRoot: string, companySlug: string): Promise<void>;
25
+ export declare function activeMemberToPersonRecord(m: ActiveMember): PersonRecord | null;
26
+ export declare function fetchMembershipRoster(hqRoot: string, companySlug: string): Promise<PersonRecord[]>;
27
+ export declare function mergePeople(primary: PersonRecord[], extra: PersonRecord[]): PersonRecord[];
24
28
  /**
25
29
  * Resolve the single company to operate on. Explicit `--company` always wins
26
30
  * (after a path-safety check). Otherwise the active company is inferred from
@@ -33,14 +37,14 @@ export declare function resolvePersonWithRosterFallback(input: {
33
37
  slug: string;
34
38
  name: string;
35
39
  opts?: PeopleLookupOpts;
36
- refreshRoster?: RefreshPeopleRoster;
40
+ fetchRoster?: FetchPeopleRoster;
37
41
  }): Promise<ReturnType<typeof resolveNameToEmail>>;
38
42
  export declare function searchPeopleWithRosterFallback(input: {
39
43
  hqRoot: string;
40
44
  slug: string;
41
45
  keyword: string;
42
46
  opts?: PeopleLookupOpts;
43
- refreshRoster?: RefreshPeopleRoster;
47
+ fetchRoster?: FetchPeopleRoster;
44
48
  }): Promise<PersonRecord[]>;
45
49
  export declare function registerPeopleCommand(program: Command, deps?: PeopleCommandDeps): void;
46
50
  export {};
@@ -6,35 +6,73 @@
6
6
  * hq people search <keyword> [--company <slug>] [--json]
7
7
  * hq people resolve <name> [--company <slug>] [--json]
8
8
  *
9
- * Source of truth is `companies/<company>/people/<person>/meta.yaml`. Every
10
- * subcommand operates on exactly ONE company (the active company, or the one
11
- * named by `--company`); nothing reads across company boundaries.
9
+ * Local records from `companies/<company>/people/<person>/meta.yaml` are the
10
+ * curated primary source. On misses, commands may fall back to the membership
11
+ * roster for exactly ONE company (the active company, or the one named by
12
+ * `--company`); nothing reads across company boundaries.
12
13
  */
13
14
 
14
- !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]="79abc1f5-4f5a-5c95-b29b-10f04417355f")}catch(e){}}();
15
+ !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]="555055d9-98e2-5fcd-b736-4b1df12daf4d")}catch(e){}}();
15
16
  import * as fs from "fs";
16
17
  import { Option } from "commander";
17
18
  import chalk from "chalk";
18
- import { VaultClient } from "@indigoai-us/hq-cloud";
19
19
  import * as yaml from "js-yaml";
20
20
  import { findHqRoot } from "../utils/manifest.js";
21
21
  import { manifestPath } from "./cloud-provision.js";
22
- import { DEFAULT_COGNITO, buildVaultConfig, ensureCognitoToken, } from "../utils/cognito-session.js";
22
+ import { ensureCognitoToken } from "../utils/cognito-session.js";
23
23
  import { getCompanyUid } from "../utils/vault-api.js";
24
- import { createCompanyPresignClient, runGet } from "./files-browse.js";
24
+ import { listActiveMembers } from "./members.js";
25
25
  import { assertSafeCompanySlug, listCompanyPeople, searchPeople, resolveNameToEmail, companyPeopleDir, } from "../utils/people.js";
26
- export async function refreshPeopleRosterFromCloud(hqRoot, companySlug) {
27
- const accessToken = await ensureCognitoToken();
28
- const client = new VaultClient(buildVaultConfig(accessToken));
29
- await getCompanyUid(accessToken, companySlug);
30
- await runGet({
31
- path: `companies/${companySlug}/people/`,
32
- hqRoot,
33
- companySlug,
34
- vaultClient: client,
35
- companyClient: ({ companyUid }) => createCompanyPresignClient({ token: accessToken, companyUid }),
36
- region: DEFAULT_COGNITO.region,
37
- });
26
+ function slugify(value) {
27
+ return value
28
+ .toLowerCase()
29
+ .replace(/[^a-z0-9]+/g, "-")
30
+ .replace(/-+/g, "-")
31
+ .replace(/^-|-$/g, "");
32
+ }
33
+ export function activeMemberToPersonRecord(m) {
34
+ const name = m.personName?.trim() ||
35
+ m.personEmail?.trim() ||
36
+ m.personSlug?.trim() ||
37
+ "";
38
+ if (!name)
39
+ return null;
40
+ const email = m.personEmail?.trim() || undefined;
41
+ const slug = m.personSlug?.trim() || slugify(name) || m.personUid;
42
+ return {
43
+ slug,
44
+ name,
45
+ email,
46
+ role: m.role || undefined,
47
+ type: "internal",
48
+ source: `hq-pro membership: /membership/company/${m.companyUid}`,
49
+ };
50
+ }
51
+ export async function fetchMembershipRoster(hqRoot, companySlug) {
52
+ void hqRoot;
53
+ const token = await ensureCognitoToken();
54
+ const companyUid = await getCompanyUid(token, companySlug);
55
+ const members = await listActiveMembers(token, companyUid);
56
+ return members
57
+ .map(activeMemberToPersonRecord)
58
+ .filter((r) => r !== null);
59
+ }
60
+ function personIdentityKey(person) {
61
+ return (person.email?.toLowerCase() ||
62
+ person.slug?.toLowerCase() ||
63
+ person.name.toLowerCase());
64
+ }
65
+ export function mergePeople(primary, extra) {
66
+ const seen = new Set(primary.map(personIdentityKey));
67
+ const merged = [...primary];
68
+ for (const person of extra) {
69
+ const key = personIdentityKey(person);
70
+ if (seen.has(key))
71
+ continue;
72
+ seen.add(key);
73
+ merged.push(person);
74
+ }
75
+ return merged;
38
76
  }
39
77
  /** Companies that still exist (anything not explicitly `status: archived`). */
40
78
  function activeCompanySlugs(manifest) {
@@ -103,37 +141,38 @@ function fail(message) {
103
141
  console.error(chalk.red(message));
104
142
  process.exit(1);
105
143
  }
106
- function logRefreshFailure(companySlug, err) {
144
+ function logRosterFetchFailure(companySlug, err) {
107
145
  const message = err instanceof Error ? err.message : String(err);
108
- console.error(chalk.dim(` Could not refresh people roster for '${companySlug}': ${message}`));
146
+ console.error(chalk.dim(` Could not fetch people roster for '${companySlug}': ${message}`));
109
147
  }
110
- async function tryRefreshRoster(refreshRoster, hqRoot, slug) {
148
+ async function tryFetchRoster(fetchRoster, hqRoot, slug) {
111
149
  try {
112
- await refreshRoster(hqRoot, slug);
113
- return true;
150
+ return await fetchRoster(hqRoot, slug);
114
151
  }
115
152
  catch (err) {
116
- logRefreshFailure(slug, err);
117
- return false;
153
+ logRosterFetchFailure(slug, err);
154
+ return null;
118
155
  }
119
156
  }
120
157
  export async function resolvePersonWithRosterFallback(input) {
121
- const local = resolveNameToEmail(listCompanyPeople(input.hqRoot, input.slug), input.name);
158
+ const localPeople = listCompanyPeople(input.hqRoot, input.slug);
159
+ const local = resolveNameToEmail(localPeople, input.name);
122
160
  if (local.status !== "not_found" || input.opts?.localOnly)
123
161
  return local;
124
- const refreshed = await tryRefreshRoster(input.refreshRoster ?? refreshPeopleRosterFromCloud, input.hqRoot, input.slug);
125
- if (!refreshed)
162
+ const roster = await tryFetchRoster(input.fetchRoster ?? fetchMembershipRoster, input.hqRoot, input.slug);
163
+ if (!roster)
126
164
  return local;
127
- return resolveNameToEmail(listCompanyPeople(input.hqRoot, input.slug), input.name);
165
+ return resolveNameToEmail(mergePeople(localPeople, roster), input.name);
128
166
  }
129
167
  export async function searchPeopleWithRosterFallback(input) {
130
- const local = searchPeople(listCompanyPeople(input.hqRoot, input.slug), input.keyword);
168
+ const localPeople = listCompanyPeople(input.hqRoot, input.slug);
169
+ const local = searchPeople(localPeople, input.keyword);
131
170
  if (local.length > 0 || input.opts?.localOnly)
132
171
  return local;
133
- const refreshed = await tryRefreshRoster(input.refreshRoster ?? refreshPeopleRosterFromCloud, input.hqRoot, input.slug);
134
- if (!refreshed)
172
+ const roster = await tryFetchRoster(input.fetchRoster ?? fetchMembershipRoster, input.hqRoot, input.slug);
173
+ if (!roster)
135
174
  return local;
136
- return searchPeople(listCompanyPeople(input.hqRoot, input.slug), input.keyword);
175
+ return searchPeople(mergePeople(localPeople, roster), input.keyword);
137
176
  }
138
177
  export function registerPeopleCommand(program, deps = {}) {
139
178
  const people = program
@@ -147,12 +186,19 @@ export function registerPeopleCommand(program, deps = {}) {
147
186
  .command("list")
148
187
  .description("List all people recorded for the company")
149
188
  .option("--json", "Output JSON instead of a table")
150
- .action((opts) => {
189
+ .option("--local-only", "Skip network fallback; list only the local people roster")
190
+ .action(async (opts) => {
151
191
  try {
152
192
  const scope = people.opts();
153
193
  const hqRoot = resolveHqRoot(scope);
154
194
  const slug = resolveCompanySlug(hqRoot, scope.company);
155
- const records = listCompanyPeople(hqRoot, slug);
195
+ let records = listCompanyPeople(hqRoot, slug);
196
+ if (!opts.localOnly) {
197
+ const roster = await tryFetchRoster(deps.fetchRoster ?? fetchMembershipRoster, hqRoot, slug);
198
+ if (roster)
199
+ records = mergePeople(records, roster);
200
+ }
201
+ records = records.sort((a, b) => a.name.localeCompare(b.name));
156
202
  if (opts.json) {
157
203
  console.log(JSON.stringify(records, null, 2));
158
204
  return;
@@ -171,7 +217,7 @@ export function registerPeopleCommand(program, deps = {}) {
171
217
  .command("search <keyword>")
172
218
  .description("Keyword search over people names and emails")
173
219
  .option("--json", "Output JSON instead of a table")
174
- .option("--local-only", "Skip cloud fallback; search only the local people roster")
220
+ .option("--local-only", "Skip network fallback; search only the local people roster")
175
221
  .action(async (keyword, opts) => {
176
222
  try {
177
223
  const scope = people.opts();
@@ -182,7 +228,7 @@ export function registerPeopleCommand(program, deps = {}) {
182
228
  slug,
183
229
  keyword,
184
230
  opts,
185
- refreshRoster: deps.refreshRoster,
231
+ fetchRoster: deps.fetchRoster,
186
232
  });
187
233
  if (opts.json) {
188
234
  console.log(JSON.stringify(matches, null, 2));
@@ -202,7 +248,7 @@ export function registerPeopleCommand(program, deps = {}) {
202
248
  .command("resolve <name>")
203
249
  .description("Resolve a person name to their email address")
204
250
  .option("--json", "Output JSON instead of plain text")
205
- .option("--local-only", "Skip cloud fallback; resolve only from the local people roster")
251
+ .option("--local-only", "Skip network fallback; resolve only from the local people roster")
206
252
  .action(async (name, opts) => {
207
253
  try {
208
254
  const scope = people.opts();
@@ -213,7 +259,7 @@ export function registerPeopleCommand(program, deps = {}) {
213
259
  slug,
214
260
  name,
215
261
  opts,
216
- refreshRoster: deps.refreshRoster,
262
+ fetchRoster: deps.fetchRoster,
217
263
  });
218
264
  if (opts.json) {
219
265
  console.log(JSON.stringify(result, null, 2));
@@ -247,4 +293,4 @@ export function registerPeopleCommand(program, deps = {}) {
247
293
  });
248
294
  }
249
295
  //# sourceMappingURL=people.js.map
250
- //# debugId=79abc1f5-4f5a-5c95-b29b-10f04417355f
296
+ //# debugId=555055d9-98e2-5fcd-b736-4b1df12daf4d
@@ -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]="18312d3d-7daf-5faf-ac29-c82ce7bd0b29")}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]="12b04dfd-a264-56c7-bfbb-a448c35689b3")}catch(e){}}();
3
3
  import chalk from "chalk";
4
4
  import * as readline from "node:readline";
5
5
  import { spawn } from "node:child_process";
@@ -217,14 +217,8 @@ export function scrubSandboxOutput(text, secretNames = []) {
217
217
  .replace(/\b[A-Za-z0-9+/]{40,}={0,2}\b/g, "[REDACTED]");
218
218
  }
219
219
  function renderSandboxJobResult(job, secretNames) {
220
- if (job.stdout) {
221
- process.stdout.write(scrubSandboxOutput(job.stdout, secretNames));
222
- }
223
- if (job.stderr) {
224
- process.stderr.write(scrubSandboxOutput(job.stderr, secretNames));
225
- }
226
- if (job.logsTail) {
227
- process.stderr.write(scrubSandboxOutput(job.logsTail, secretNames));
220
+ if (job.output) {
221
+ process.stdout.write(scrubSandboxOutput(job.output, secretNames));
228
222
  }
229
223
  }
230
224
  function normalizePolicyRecord(secretPath, data) {
@@ -872,47 +866,46 @@ export function registerSecretsCommand(program) {
872
866
  });
873
867
  secrets
874
868
  .command("sandbox")
875
- .description("Run a skill in the hosted sandbox with secrets injected server-side")
869
+ .description("Run a command in the hosted sandbox with named secrets injected as env vars; open egress, secrets never touch this machine")
876
870
  .option("--company <slug>", "Company slug (resolves to companyUid)")
877
871
  .option("--personal", "Operate on the caller's personal vault (no sharing)")
878
- .option("--only <keys>", "Comma-separated list of secret names the skill may use")
879
- .option("--script <path>", "Attach local script identity for script-locked secrets")
872
+ .option("--only <keys>", "Comma-separated list of secret names to inject (required)")
880
873
  .allowUnknownOption(true)
881
874
  .action(async (opts, cmd) => {
882
875
  try {
883
- const dashIndex = process.argv.indexOf("--");
884
- const rawArgs = dashIndex !== -1 ? process.argv.slice(dashIndex + 1) : cmd.args;
885
- if (rawArgs.length === 0) {
886
- console.error(chalk.red("Error: no skill specified. Usage: hq secrets sandbox [--company X] [--only KEY1,KEY2] -- <skill> [args...]"));
876
+ if (!opts.only || opts.only.trim().length === 0) {
877
+ console.error(chalk.red("Error: --only is required and must name at least one secret."));
878
+ process.exit(1);
879
+ }
880
+ const rawArgs = cmd.args;
881
+ const command = rawArgs.join(" ").trim();
882
+ if (command.length === 0) {
883
+ console.error(chalk.red("Error: no command specified. Usage: hq secrets sandbox --company <slug> --only KEY1,KEY2 -- <command>"));
887
884
  process.exit(1);
888
885
  }
889
- const [skillId, ...skillArgs] = rawArgs;
890
- const keys = opts.only ? parseSecretNameList(opts.only) : [];
886
+ if (command.length > 8192) {
887
+ console.error(chalk.red("Error: sandbox command exceeds the 8192 character limit."));
888
+ process.exit(1);
889
+ }
890
+ const keys = parseSecretNameList(opts.only);
891
891
  const token = await ensureCognitoToken();
892
892
  const scope = scopeOpts(mergeScopeOpts(secrets.opts(), opts));
893
893
  const companyUid = await getEntityUid(token, scope);
894
- const usage = await buildSecretUsage("sandbox", opts.script);
895
894
  const client = new SandboxRunnerClient();
896
895
  const started = await client.startJob(token, {
897
- skillId,
898
896
  companyUid,
899
- args: skillArgs.length > 0 ? { argv: skillArgs } : undefined,
900
- companySlug: scope.companySlug,
901
- only: keys.length > 0 ? keys : undefined,
902
- usage,
897
+ secretNames: keys,
898
+ command,
903
899
  });
904
900
  const job = started.status === "succeeded" || started.status === "failed"
905
901
  ? await client.getJob(token, started.jobId)
906
902
  : await client.pollJob(token, started.jobId);
907
903
  renderSandboxJobResult(job, keys);
908
- if (job.status === "failed") {
909
- if (job.error) {
910
- console.error(chalk.red("Sandbox job failed:"), scrubSandboxOutput(job.error, keys));
911
- }
912
- process.exit(job.exitCode && job.exitCode > 0 ? job.exitCode : 1);
913
- }
914
- if (job.exitCode && job.exitCode !== 0) {
915
- process.exit(job.exitCode);
904
+ const exitCode = typeof job.exitCode === "number" ? job.exitCode : undefined;
905
+ if (job.success === false || (exitCode !== undefined && exitCode !== 0) || job.status === "failed") {
906
+ const code = exitCode && exitCode !== 0 ? exitCode : 1;
907
+ console.error(chalk.red(`Sandbox command failed with exit code ${code}.`));
908
+ process.exit(code);
916
909
  }
917
910
  }
918
911
  catch (err) {
@@ -1261,4 +1254,4 @@ export function registerSecretsCommand(program) {
1261
1254
  });
1262
1255
  }
1263
1256
  //# sourceMappingURL=secrets.js.map
1264
- //# debugId=18312d3d-7daf-5faf-ac29-c82ce7bd0b29
1257
+ //# debugId=12b04dfd-a264-56c7-bfbb-a448c35689b3
@@ -1,11 +1,8 @@
1
1
  export type SandboxRunnerState = "queued" | "running" | "succeeded" | "failed";
2
2
  export interface SandboxRunnerStartRequest {
3
- skillId: string;
4
3
  companyUid: string;
5
- args?: Record<string, unknown>;
6
- companySlug?: string;
7
- only?: string[];
8
- usage?: unknown;
4
+ secretNames: string[];
5
+ command: string;
9
6
  }
10
7
  export interface SandboxRunnerStartResponse {
11
8
  jobId: string;
@@ -14,11 +11,9 @@ export interface SandboxRunnerStartResponse {
14
11
  export interface SandboxRunnerJob {
15
12
  jobId: string;
16
13
  status: SandboxRunnerState;
17
- stdout?: string;
18
- stderr?: string;
19
- logsTail?: string;
14
+ output?: string;
20
15
  exitCode?: number;
21
- error?: string;
16
+ success?: boolean;
22
17
  }
23
18
  export interface SandboxRunnerClientOptions {
24
19
  baseUrl?: string;
@@ -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]="8203259b-d3ae-5c9b-845e-f4c1470c30f3")}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]="3cbec729-3b2b-5d53-9e90-5680dd725b7d")}catch(e){}}();
3
3
  const DEFAULT_SANDBOX_RUNNER_URL = "https://hqapi.getindigo.ai/sandbox";
4
4
  function normalizeBaseUrl(baseUrl) {
5
5
  return baseUrl.replace(/\/+$/, "");
@@ -25,21 +25,14 @@ function normalizeJob(body, jobIdFallback) {
25
25
  if (!isSandboxRunnerState(status)) {
26
26
  throw new Error("Sandbox Runner returned an invalid job status.");
27
27
  }
28
- const output = typeof body.stdout === "string"
29
- ? body.stdout
30
- : typeof body.output === "string"
31
- ? body.output
32
- : undefined;
33
28
  return {
34
29
  jobId: typeof body.jobId === "string" && body.jobId.length > 0
35
30
  ? body.jobId
36
31
  : jobIdFallback ?? requireString(body, "jobId"),
37
32
  status,
38
- stdout: output,
39
- stderr: typeof body.stderr === "string" ? body.stderr : undefined,
40
- logsTail: typeof body.logsTail === "string" ? body.logsTail : undefined,
33
+ output: typeof body.output === "string" ? body.output : undefined,
41
34
  exitCode: typeof body.exitCode === "number" ? body.exitCode : undefined,
42
- error: typeof body.error === "string" ? body.error : undefined,
35
+ success: typeof body.success === "boolean" ? body.success : undefined,
43
36
  };
44
37
  }
45
38
  function delay(ms) {
@@ -110,4 +103,4 @@ export class SandboxRunnerClient {
110
103
  }
111
104
  }
112
105
  //# sourceMappingURL=sandbox-runner-client.js.map
113
- //# debugId=8203259b-d3ae-5c9b-845e-f4c1470c30f3
106
+ //# debugId=3cbec729-3b2b-5d53-9e90-5680dd725b7d
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@indigoai-us/hq-cli",
3
- "version": "5.57.0",
3
+ "version": "5.58.1",
4
4
  "description": "HQ by Indigo management CLI — modules and cloud sync",
5
5
  "main": "dist/index.js",
6
6
  "bin": {