@indigoai-us/hq-cli 5.8.5 → 5.9.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.
package/CHANGELOG.md ADDED
@@ -0,0 +1,30 @@
1
+ # Changelog
2
+
3
+ ## [5.9.0] — 2026-05-04
4
+
5
+ ### Added
6
+
7
+ - **`hq run` command** — schema-driven dev workflow. Place a `.env.schema` file in
8
+ your repo (annotated with `# @hqCompany("your-slug")` and `VARNAME=hq()` resolvers),
9
+ then run `hq run -- npm run dev` to inject all declared secrets into the child
10
+ process's environment without ever printing them to stdout/stderr. Discovers schemas
11
+ by walking up from cwd to the repo root; merges multiple schemas; respects sibling
12
+ `.env.local` files for local overrides. Supports `--check` for a dry-run summary,
13
+ `--company` to override the slug, and `--schema` to pin an explicit schema path.
14
+
15
+ - **Batch secrets endpoint** — `POST /secrets/{companyUid}/load` on the vault API
16
+ reduces N parallel single-secret fetches to chunked `ssm:GetParameters` calls
17
+ (up to 10 names per batch), cutting `hq run` latency for schemas with many vars.
18
+ Responses include both `secrets` (allowed) and `errors` (denied/not-found) per name.
19
+ Each revealed secret is audit-logged individually (same trail as `hq secrets get`).
20
+
21
+ - **`varlock` dependency** (`1.0.0`, exact pin) — used as an internal library to
22
+ parse `.env.schema` files and drive the resolver graph. The `hq()` resolver is
23
+ implemented as a varlock plugin registered at runtime; varlock is not exposed as a
24
+ public API surface.
25
+
26
+ ### Changed
27
+
28
+ - **Node minimum raised to `>=22.0.0`** — required by `varlock@1.0.0` (ESM-only,
29
+ `node>=22`). The previous minimum was unset; this makes the requirement explicit
30
+ in `engines.node`.
@@ -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]="3d902660-a8aa-5671-ab69-21ffc0143c60")}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]="a754383e-f129-5052-a8d8-59ea590b4f3d")}catch(e){}}();
3
3
  import chalk from "chalk";
4
4
  import { ensureCognitoToken } from "../utils/cognito-session.js";
5
5
  import { vaultApiFetch, getCompanyUid } from "./secrets.js";
@@ -148,22 +148,36 @@ export function registerFilesCommand(program) {
148
148
  const token = await ensureCognitoToken();
149
149
  const companySlug = files.opts().company;
150
150
  const companyUid = await getCompanyUid(token, companySlug);
151
- const res = await vaultApiFetch({
152
- token,
153
- path: `/files/${encodeURIComponent(companyUid)}/acl`,
154
- query: { prefix: canonicalPrefix },
155
- });
156
- if (!res.ok) {
157
- const body = await res.json().catch(() => ({}));
151
+ // Fetch the prefix's own ACL row (creator, open flag, effective
152
+ // permission) and the inherited/descendant tree in parallel so the
153
+ // user sees every grant that affects this prefix in one shot.
154
+ const [aclRes, treeRes] = await Promise.all([
155
+ vaultApiFetch({
156
+ token,
157
+ path: `/files/${encodeURIComponent(companyUid)}/acl`,
158
+ query: { prefix: canonicalPrefix },
159
+ }),
160
+ vaultApiFetch({
161
+ token,
162
+ path: `/files/${encodeURIComponent(companyUid)}/acl/tree`,
163
+ query: { prefix: canonicalPrefix },
164
+ }),
165
+ ]);
166
+ async function readErrorBody(res) {
167
+ return (await res.json().catch(() => ({})));
168
+ }
169
+ // Auth/server failures from either call are treated identically — bail
170
+ // out with a single message rather than printing a half-rendered view.
171
+ for (const res of [aclRes, treeRes]) {
172
+ if (res.ok || res.status === 404)
173
+ continue;
174
+ const body = await readErrorBody(res);
158
175
  if (res.status === 401) {
159
176
  console.error(chalk.red("Not authenticated — please run `hq login`"));
160
177
  }
161
178
  else if (res.status === 403) {
162
179
  console.error(chalk.red("Not authorized to view this file prefix's ACL"));
163
180
  }
164
- else if (res.status === 404) {
165
- console.error(chalk.red(`No ACL record exists for '${canonicalPrefix}'`));
166
- }
167
181
  else if (res.status >= 500) {
168
182
  console.error(chalk.red(`Server error: ${body.error ?? res.statusText}`));
169
183
  }
@@ -172,46 +186,78 @@ export function registerFilesCommand(program) {
172
186
  }
173
187
  process.exit(1);
174
188
  }
175
- const data = await res.json();
176
- const acl = data.acl;
177
- const aclStatus = acl.open ? "open" : "restricted";
178
- const aclPrefix = acl.path ?? acl.prefix ?? canonicalPrefix;
189
+ const acl = aclRes.ok ? (await aclRes.json()).acl : null;
190
+ const tree = treeRes.ok ? (await treeRes.json()) : null;
191
+ // No own row AND nothing inherited or granted below — original
192
+ // "no ACL record" exit path.
193
+ if (!acl && (!tree || (tree.inherited.length === 0 && tree.children.length === 0))) {
194
+ console.error(chalk.red(`No ACL record exists for '${canonicalPrefix}'`));
195
+ process.exit(1);
196
+ }
197
+ const aclPrefix = acl?.path ?? acl?.prefix ?? tree?.prefix ?? canonicalPrefix;
198
+ const aclStatus = acl?.open ? "open" : "restricted";
179
199
  console.log(chalk.green(`ACL for ${aclPrefix} (${aclStatus})`));
180
- console.log(`Creator: ${acl.creatorUid}`);
181
- if (acl.effectivePermission) {
182
- console.log(`Your effective permission: ${acl.effectivePermission}`);
200
+ if (acl) {
201
+ console.log(`Creator: ${acl.creatorUid}`);
202
+ if (acl.effectivePermission) {
203
+ console.log(`Your effective permission: ${acl.effectivePermission}`);
204
+ }
205
+ }
206
+ else {
207
+ console.log(chalk.gray("No direct ACL row — access flows from the inherited/descendant grants below."));
208
+ }
209
+ function printEntryTable(rows, showSource) {
210
+ const TYPE_W = Math.max(4, ...rows.map((e) => e.granteeType.length));
211
+ const GRANTEE_W = Math.max(7, ...rows.map((e) => e.granteeId.length));
212
+ const PERM_W = Math.max(10, ...rows.map((e) => e.permission.length));
213
+ const BY_W = Math.max(10, ...rows.map((e) => e.grantedBy.length));
214
+ const SRC_W = showSource
215
+ ? Math.max(6, ...rows.map((e) => (e.sourcePrefix ?? "").length))
216
+ : 0;
217
+ const headerCols = [
218
+ "TYPE".padEnd(TYPE_W),
219
+ "GRANTEE".padEnd(GRANTEE_W),
220
+ "PERMISSION".padEnd(PERM_W),
221
+ "GRANTED_BY".padEnd(BY_W),
222
+ "GRANTED_AT",
223
+ ];
224
+ if (showSource)
225
+ headerCols.splice(4, 0, "SOURCE".padEnd(SRC_W));
226
+ console.log(chalk.bold(headerCols.join(" ")));
227
+ for (const e of rows) {
228
+ const grantedAt = e.grantedAt.slice(0, 10);
229
+ const cols = [
230
+ e.granteeType.padEnd(TYPE_W),
231
+ e.granteeId.padEnd(GRANTEE_W),
232
+ e.permission.padEnd(PERM_W),
233
+ e.grantedBy.padEnd(BY_W),
234
+ grantedAt,
235
+ ];
236
+ if (showSource)
237
+ cols.splice(4, 0, (e.sourcePrefix ?? "").padEnd(SRC_W));
238
+ console.log(cols.join(" "));
239
+ }
183
240
  }
184
- if (acl.entries.length === 0) {
185
- if (acl.open) {
241
+ const directEntries = acl?.entries ?? tree?.direct ?? [];
242
+ if (directEntries.length === 0) {
243
+ if (acl?.open) {
186
244
  console.log(chalk.gray("Open ACL — all active members have read access."));
187
245
  }
188
- else {
189
- console.log(chalk.gray("No explicit grants — only creator has access."));
190
- }
191
- return;
192
- }
193
- console.log("Entries:");
194
- const TYPE_W = Math.max(4, ...acl.entries.map((e) => e.granteeType.length));
195
- const GRANTEE_W = Math.max(7, ...acl.entries.map((e) => e.granteeId.length));
196
- const PERM_W = Math.max(10, ...acl.entries.map((e) => e.permission.length));
197
- const BY_W = Math.max(10, ...acl.entries.map((e) => e.grantedBy.length));
198
- const tableHeader = [
199
- "TYPE".padEnd(TYPE_W),
200
- "GRANTEE".padEnd(GRANTEE_W),
201
- "PERMISSION".padEnd(PERM_W),
202
- "GRANTED_BY".padEnd(BY_W),
203
- "GRANTED_AT",
204
- ].join(" ");
205
- console.log(chalk.bold(tableHeader));
206
- for (const e of acl.entries) {
207
- const grantedAt = e.grantedAt.slice(0, 10);
208
- console.log([
209
- e.granteeType.padEnd(TYPE_W),
210
- e.granteeId.padEnd(GRANTEE_W),
211
- e.permission.padEnd(PERM_W),
212
- e.grantedBy.padEnd(BY_W),
213
- grantedAt,
214
- ].join(" "));
246
+ else if (acl) {
247
+ console.log(chalk.gray("No explicit grants on this prefix — only creator has access."));
248
+ }
249
+ }
250
+ else {
251
+ console.log("\nDirect entries (granted on this prefix):");
252
+ printEntryTable(directEntries, false);
253
+ }
254
+ if (tree && tree.inherited.length > 0) {
255
+ console.log("\nInherited (granted on an ancestor prefix):");
256
+ printEntryTable(tree.inherited, true);
257
+ }
258
+ if (tree && tree.children.length > 0) {
259
+ console.log("\nGranted on descendant prefixes (do not affect this prefix's access):");
260
+ printEntryTable(tree.children, true);
215
261
  }
216
262
  }
217
263
  catch (err) {
@@ -221,4 +267,4 @@ export function registerFilesCommand(program) {
221
267
  });
222
268
  }
223
269
  //# sourceMappingURL=files.js.map
224
- //# debugId=3d902660-a8aa-5671-ab69-21ffc0143c60
270
+ //# debugId=a754383e-f129-5052-a8d8-59ea590b4f3d
@@ -0,0 +1,3 @@
1
+ import { Command } from 'commander';
2
+ export declare function registerRunCommand(program: Command): void;
3
+ //# sourceMappingURL=run.d.ts.map
@@ -0,0 +1,119 @@
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]="a1b39bbf-1f26-59e7-af70-6ecf01f2ce7e")}catch(e){}}();
3
+ import { spawn } from 'node:child_process';
4
+ import * as path from 'node:path';
5
+ import * as fs from 'node:fs';
6
+ import { internal } from 'varlock';
7
+ import { ensureCognitoToken } from '../utils/cognito-session.js';
8
+ import { vaultApiFetch, getCompanyUid } from '../utils/vault-api.js';
9
+ import { discoverSchemas } from '../run/discover-schemas.js';
10
+ import { installHqPlugin, prewarmHqSecrets } from '../run/hq-plugin.js';
11
+ export function registerRunCommand(program) {
12
+ program
13
+ .command('run')
14
+ .description('Load secrets from .env.schema and run a command with them injected')
15
+ .option('--company <slug>', 'Company slug (overrides @hqCompany in schema)')
16
+ .option('--schema <path>', 'Explicit schema path (skips walk-up discovery)')
17
+ .option('--check', 'Resolve schema and validate vars without executing the command')
18
+ .allowUnknownOption(true)
19
+ .action(async (opts) => {
20
+ try {
21
+ const dashIndex = process.argv.indexOf('--');
22
+ const childArgs = dashIndex !== -1 ? process.argv.slice(dashIndex + 1) : [];
23
+ if (!opts.check && childArgs.length === 0) {
24
+ throw new Error('no command specified. Usage: hq run [options] -- <command> [args...]');
25
+ }
26
+ let schemaPaths;
27
+ let envLocalPaths;
28
+ let schemaCompanySlug;
29
+ if (opts.schema) {
30
+ const schemaAbs = path.resolve(opts.schema);
31
+ schemaPaths = [schemaAbs];
32
+ const localPath = path.join(path.dirname(schemaAbs), '.env.local');
33
+ envLocalPaths = fs.existsSync(localPath) ? [localPath] : [];
34
+ const content = fs.readFileSync(schemaAbs, 'utf8');
35
+ const m = /^# @hqCompany\("([^"]+)"\)/m.exec(content);
36
+ schemaCompanySlug = m ? m[1] : null;
37
+ }
38
+ else {
39
+ const discovered = discoverSchemas(process.cwd());
40
+ if (discovered.conflict) {
41
+ throw new Error(`conflicting @hqCompany slugs: "${discovered.conflict.slugs[0]}" in ${discovered.conflict.paths[0]} vs "${discovered.conflict.slugs[1]}" in ${discovered.conflict.paths[1]}. Use --company <slug> to override.`);
42
+ }
43
+ if (discovered.schemaPaths.length === 0) {
44
+ throw new Error('no .env.schema found. Create one or use --schema <path>.');
45
+ }
46
+ schemaPaths = discovered.schemaPaths;
47
+ envLocalPaths = discovered.envLocalPaths;
48
+ schemaCompanySlug = discovered.companySlug;
49
+ }
50
+ const slug = opts.company ?? schemaCompanySlug;
51
+ if (!slug) {
52
+ throw new Error('company slug not set. Add # @hqCompany("slug") to your .env.schema or pass --company <slug>.');
53
+ }
54
+ const token = await ensureCognitoToken();
55
+ const uid = await getCompanyUid(token, slug);
56
+ const fetchBatch = async (companyUid, names) => {
57
+ const res = await vaultApiFetch({
58
+ token,
59
+ path: `/secrets/${encodeURIComponent(companyUid)}/load`,
60
+ method: 'POST',
61
+ body: { names },
62
+ });
63
+ if (!res.ok) {
64
+ const body = await res.json().catch(() => ({}));
65
+ throw new Error(`Failed to batch-load secrets: ${body.error ?? res.statusText}`);
66
+ }
67
+ return res.json();
68
+ };
69
+ const pluginOpts = {
70
+ companyOverride: opts.company,
71
+ resolveCompanyUid: async () => uid,
72
+ fetchBatch,
73
+ };
74
+ // LAST entry = highest precedence; .env.local files trail .env.schema files so any .env.local beats any schema regardless of depth.
75
+ const paths = [...schemaPaths, ...envLocalPaths];
76
+ let state;
77
+ const graph = await internal.loadEnvGraph({
78
+ entryFilePaths: paths,
79
+ afterInit: async (g) => { state = installHqPlugin(g, pluginOpts); },
80
+ });
81
+ await prewarmHqSecrets(graph, pluginOpts, state);
82
+ await graph.resolveEnvValues();
83
+ const schemaErrors = Object.entries(graph.configSchema)
84
+ .filter(([, item]) => item.errors?.length > 0);
85
+ if (schemaErrors.length > 0) {
86
+ const msgs = schemaErrors.flatMap(([k, item]) => item.errors.map((e) => ` ${k}: ${e.message ?? String(e)}`));
87
+ process.stderr.write(`Error: failed to resolve env vars:\n${msgs.join('\n')}\n`);
88
+ process.exit(1);
89
+ }
90
+ const resolvedEnv = graph.getResolvedEnvObject();
91
+ const varCount = Object.keys(resolvedEnv).length;
92
+ process.stderr.write(`Loaded ${varCount} env vars from .env.schema (company: ${slug})\n`);
93
+ if (opts.check) {
94
+ process.exit(0);
95
+ }
96
+ const [childCmd, ...restArgs] = childArgs;
97
+ const child = spawn(childCmd, restArgs, {
98
+ stdio: 'inherit',
99
+ env: { ...process.env, ...resolvedEnv },
100
+ });
101
+ child.on('error', (err) => {
102
+ process.stderr.write(`Error: failed to start command '${childCmd}': ${err.message}\n`);
103
+ process.exit(1);
104
+ });
105
+ child.on('close', (code, signal) => {
106
+ if (signal) {
107
+ process.kill(process.pid, signal);
108
+ }
109
+ process.exit(code ?? 1);
110
+ });
111
+ }
112
+ catch (err) {
113
+ process.stderr.write(`Error: ${err instanceof Error ? err.message : String(err)}\n`);
114
+ process.exit(1);
115
+ }
116
+ });
117
+ }
118
+ //# sourceMappingURL=run.js.map
119
+ //# debugId=a1b39bbf-1f26-59e7-af70-6ecf01f2ce7e
@@ -1,12 +1,6 @@
1
1
  import { Command } from "commander";
2
- export interface VaultApiOptions {
3
- token: string;
4
- path: string;
5
- method?: string;
6
- body?: Record<string, unknown>;
7
- query?: Record<string, string>;
8
- }
9
- export declare function vaultApiFetch(opts: VaultApiOptions): Promise<Response>;
10
- export declare function getCompanyUid(token: string, companySlug: string | undefined): Promise<string>;
2
+ import { vaultApiFetch, getCompanyUid } from "../utils/vault-api.js";
3
+ export type { VaultApiOptions } from "../utils/vault-api.js";
4
+ export { vaultApiFetch, getCompanyUid };
11
5
  export declare function registerSecretsCommand(program: Command): void;
12
6
  //# sourceMappingURL=secrets.d.ts.map
@@ -1,11 +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]="70f0907f-bfcb-5d2e-958a-8813d3bbaf96")}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]="0239c476-98b8-53c0-9458-1c82dc0d26a9")}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
- import { ensureCognitoToken, DEFAULT_VAULT_API_URL, } from "../utils/cognito-session.js";
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
11
  function shellSingleQuote(value) {
10
12
  return "'" + value.replace(/'/g, "'\\''") + "'";
11
13
  }
@@ -19,59 +21,6 @@ function buildSecretNamePath(companyUid, name) {
19
21
  .join("/");
20
22
  return `/secrets/${encodeURIComponent(companyUid)}/name/${encodedName}`;
21
23
  }
22
- export async function vaultApiFetch(opts) {
23
- const url = new URL(opts.path, DEFAULT_VAULT_API_URL);
24
- if (opts.query) {
25
- for (const [k, v] of Object.entries(opts.query)) {
26
- url.searchParams.set(k, v);
27
- }
28
- }
29
- return fetch(url.toString(), {
30
- method: opts.method ?? "GET",
31
- headers: {
32
- Authorization: `Bearer ${opts.token}`,
33
- "Content-Type": "application/json",
34
- },
35
- body: opts.body ? JSON.stringify(opts.body) : undefined,
36
- });
37
- }
38
- async function resolveCompanyUid(token, slug) {
39
- const res = await vaultApiFetch({
40
- token,
41
- path: `/entity/by-slug/company/${encodeURIComponent(slug)}`,
42
- });
43
- if (!res.ok) {
44
- const body = await res.json().catch(() => ({}));
45
- throw new Error(`Failed to resolve company slug '${slug}': ${body.error ?? res.statusText}`);
46
- }
47
- const data = (await res.json());
48
- return data.entity.uid;
49
- }
50
- async function resolveCompanyFromMemberships(token) {
51
- const res = await vaultApiFetch({
52
- token,
53
- path: "/membership/me",
54
- });
55
- if (!res.ok) {
56
- throw new Error("Failed to fetch memberships — run `hq login` and try again");
57
- }
58
- const data = (await res.json());
59
- const active = data.memberships.filter((m) => m.status === "active");
60
- if (active.length === 0) {
61
- throw new Error("No active company memberships found. Use --company <slug> to specify.");
62
- }
63
- if (active.length === 1) {
64
- return active[0].companyUid;
65
- }
66
- const uids = active.map((m) => m.companyUid).join(", ");
67
- throw new Error(`Multiple companies found (${uids}). Use --company <slug> to specify which one.`);
68
- }
69
- export async function getCompanyUid(token, companySlug) {
70
- if (companySlug) {
71
- return resolveCompanyUid(token, companySlug);
72
- }
73
- return resolveCompanyFromMemberships(token);
74
- }
75
24
  function parseDuration(input) {
76
25
  const match = input.match(/^(\d+)(m|h|d)$/);
77
26
  if (!match)
@@ -756,4 +705,4 @@ export function registerSecretsCommand(program) {
756
705
  });
757
706
  }
758
707
  //# sourceMappingURL=secrets.js.map
759
- //# debugId=70f0907f-bfcb-5d2e-958a-8813d3bbaf96
708
+ //# debugId=0239c476-98b8-53c0-9458-1c82dc0d26a9
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]="79222b07-0f20-582f-962f-6bc13b8c1c88")}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]="ad8b9496-9cb2-5e12-ae08-5b29ef77c209")}catch(e){}}();
7
7
  import { Command } from "commander";
8
8
  import { initSentry, Sentry } from "./sentry.js";
9
9
  import { registerAddCommand } from "./commands/add.js";
@@ -23,6 +23,7 @@ import { registerPackageListCommand } from "./commands/pkg-list.js";
23
23
  import { registerTeamSyncCommand } from "./commands/team-sync.js";
24
24
  import { registerAuthCommands } from "./commands/auth.js";
25
25
  import { registerSecretsCommand } from "./commands/secrets.js";
26
+ import { registerRunCommand } from "./commands/run.js";
26
27
  import { registerGroupsCommand } from "./commands/groups.js";
27
28
  import { registerFilesCommand } from "./commands/files.js";
28
29
  initSentry();
@@ -30,7 +31,7 @@ const program = new Command();
30
31
  program
31
32
  .name("hq")
32
33
  .description("HQ management CLI — modules, packages, and cloud sync")
33
- .version("5.8.5");
34
+ .version("5.8.6");
34
35
  // Module management subcommand group
35
36
  const modulesCmd = program
36
37
  .command("modules")
@@ -72,6 +73,8 @@ registerWhoamiCommand(program);
72
73
  registerAuthCommands(program);
73
74
  // Secrets management (subcommand group — hq secrets set|get|list|delete|exec|generate-link|cache)
74
75
  registerSecretsCommand(program);
76
+ // Schema-driven dev runner — hq run [options] -- <cmd>
77
+ registerRunCommand(program);
75
78
  // Groups management (subcommand group — hq groups create|delete|add|remove|list|members)
76
79
  registerGroupsCommand(program);
77
80
  // Files ACL management (subcommand group — hq files share|unshare|acl)
@@ -91,4 +94,4 @@ registerOnboardCommand(program);
91
94
  }
92
95
  })();
93
96
  //# sourceMappingURL=index.js.map
94
- //# debugId=79222b07-0f20-582f-962f-6bc13b8c1c88
97
+ //# debugId=ad8b9496-9cb2-5e12-ae08-5b29ef77c209
@@ -0,0 +1,12 @@
1
+ export interface SchemaConflict {
2
+ paths: [string, string];
3
+ slugs: [string, string];
4
+ }
5
+ export interface DiscoverSchemasResult {
6
+ schemaPaths: string[];
7
+ envLocalPaths: string[];
8
+ companySlug: string | null;
9
+ conflict: SchemaConflict | null;
10
+ }
11
+ export declare function discoverSchemas(cwd: string): DiscoverSchemasResult;
12
+ //# sourceMappingURL=discover-schemas.d.ts.map
@@ -0,0 +1,64 @@
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]="17c9937b-a219-5b2d-a120-0acbf96f945a")}catch(e){}}();
3
+ import * as fs from 'node:fs';
4
+ import * as path from 'node:path';
5
+ const SLUG_RE = /^# @hqCompany\("([^"]+)"\)/m;
6
+ function parseSlug(filePath) {
7
+ try {
8
+ const content = fs.readFileSync(filePath, 'utf8');
9
+ const m = SLUG_RE.exec(content);
10
+ return m ? m[1] : null;
11
+ }
12
+ catch {
13
+ return null;
14
+ }
15
+ }
16
+ export function discoverSchemas(cwd) {
17
+ const schemaPaths = [];
18
+ const envLocalPaths = [];
19
+ let dir = path.resolve(cwd);
20
+ while (true) {
21
+ const schemaPath = path.join(dir, '.env.schema');
22
+ if (fs.existsSync(schemaPath)) {
23
+ schemaPaths.push(schemaPath);
24
+ const localPath = path.join(dir, '.env.local');
25
+ if (fs.existsSync(localPath)) {
26
+ envLocalPaths.push(localPath);
27
+ }
28
+ }
29
+ const isGitRoot = fs.existsSync(path.join(dir, '.git'));
30
+ if (isGitRoot)
31
+ break;
32
+ const parent = path.dirname(dir);
33
+ if (parent === dir)
34
+ break; // filesystem root
35
+ dir = parent;
36
+ }
37
+ // cwd-closest is pushed last during walk-up, so reversing puts it last.
38
+ // (During walk-up cwd is checked first → pushed first → reversed → ends up last.)
39
+ schemaPaths.reverse();
40
+ envLocalPaths.reverse();
41
+ let companySlug = null;
42
+ let companySlugPath = null;
43
+ let conflict = null;
44
+ for (const schemaPath of schemaPaths) {
45
+ const slug = parseSlug(schemaPath);
46
+ if (slug == null)
47
+ continue;
48
+ if (companySlug == null) {
49
+ companySlug = slug;
50
+ companySlugPath = schemaPath;
51
+ }
52
+ else if (companySlug !== slug) {
53
+ conflict = {
54
+ paths: [companySlugPath, schemaPath],
55
+ slugs: [companySlug, slug],
56
+ };
57
+ companySlug = null;
58
+ break;
59
+ }
60
+ }
61
+ return { schemaPaths, envLocalPaths, companySlug, conflict };
62
+ }
63
+ //# sourceMappingURL=discover-schemas.js.map
64
+ //# debugId=17c9937b-a219-5b2d-a120-0acbf96f945a
@@ -0,0 +1,26 @@
1
+ export interface InstallHqPluginOpts {
2
+ companyOverride?: string;
3
+ resolveCompanyUid: (slug: string) => Promise<string>;
4
+ fetchBatch: (uid: string, names: string[]) => Promise<{
5
+ secrets: Array<{
6
+ name: string;
7
+ value: string;
8
+ }>;
9
+ errors: Array<{
10
+ name: string;
11
+ code: string;
12
+ message?: string;
13
+ }>;
14
+ }>;
15
+ }
16
+ export interface PluginState {
17
+ schemaCompanySlug: string | null;
18
+ uid: string | null;
19
+ errorsByName: Map<string, {
20
+ code: string;
21
+ message?: string;
22
+ }>;
23
+ }
24
+ export declare function installHqPlugin(graph: any, opts: InstallHqPluginOpts): PluginState;
25
+ export declare function prewarmHqSecrets(graph: any, opts: InstallHqPluginOpts, state: PluginState): Promise<void>;
26
+ //# sourceMappingURL=hq-plugin.d.ts.map