@indigoai-us/hq-cli 5.61.0 → 5.62.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.
Files changed (80) hide show
  1. package/dist/commands/agents.d.ts +109 -0
  2. package/dist/commands/agents.js +385 -0
  3. package/dist/commands/db-migrate.d.ts +6 -0
  4. package/dist/commands/db-migrate.js +42 -0
  5. package/dist/commands/db-provision.d.ts +15 -0
  6. package/dist/commands/db-provision.js +78 -0
  7. package/dist/commands/db-sql.d.ts +9 -0
  8. package/dist/commands/db-sql.js +81 -0
  9. package/dist/commands/db-status.d.ts +7 -0
  10. package/dist/commands/db-status.js +70 -0
  11. package/dist/commands/db.d.ts +9 -0
  12. package/dist/commands/db.js +23 -0
  13. package/dist/commands/integrations.d.ts +78 -0
  14. package/dist/commands/integrations.js +309 -0
  15. package/dist/commands/members.js +4 -4
  16. package/dist/commands/outposts.d.ts +60 -0
  17. package/dist/commands/outposts.js +255 -0
  18. package/dist/commands/secrets.d.ts +8 -0
  19. package/dist/commands/secrets.js +23 -8
  20. package/dist/commands/skill.d.ts +153 -0
  21. package/dist/commands/skill.js +593 -0
  22. package/dist/commands/workers.d.ts +48 -0
  23. package/dist/commands/workers.js +229 -0
  24. package/dist/lib/db/control-plane.d.ts +45 -0
  25. package/dist/lib/db/control-plane.js +81 -0
  26. package/dist/lib/db/local.d.ts +49 -0
  27. package/dist/lib/db/local.js +106 -0
  28. package/dist/lib/db/migrate.d.ts +41 -0
  29. package/dist/lib/db/migrate.js +104 -0
  30. package/dist/lib/db/paths.d.ts +56 -0
  31. package/dist/lib/db/paths.js +103 -0
  32. package/dist/lib/db/remote-engine.d.ts +58 -0
  33. package/dist/lib/db/remote-engine.js +90 -0
  34. package/dist/lib/db/remote-sql.d.ts +22 -0
  35. package/dist/lib/db/remote-sql.js +39 -0
  36. package/dist/lib/db/sql.d.ts +49 -0
  37. package/dist/lib/db/sql.js +132 -0
  38. package/dist/main.js +27 -2
  39. package/dist/utils/cognito-session.js +3 -3
  40. package/dist/utils/sandbox-runner-client.js +3 -3
  41. package/package.json +9 -1
  42. package/pnpm-workspace.yaml +2 -0
  43. package/src/commands/agents.test.ts +297 -0
  44. package/src/commands/agents.ts +561 -0
  45. package/src/commands/db-migrate.ts +55 -0
  46. package/src/commands/db-provision.ts +102 -0
  47. package/src/commands/db-sql.ts +124 -0
  48. package/src/commands/db-status.ts +100 -0
  49. package/src/commands/db.ts +26 -0
  50. package/src/commands/integrations.test.ts +284 -0
  51. package/src/commands/integrations.ts +438 -0
  52. package/src/commands/members.ts +2 -2
  53. package/src/commands/outposts.test.ts +177 -0
  54. package/src/commands/outposts.ts +338 -0
  55. package/src/commands/secrets.parse-destination.test.ts +38 -0
  56. package/src/commands/secrets.test.ts +24 -0
  57. package/src/commands/secrets.ts +30 -10
  58. package/src/commands/skill.test.ts +770 -0
  59. package/src/commands/skill.ts +796 -0
  60. package/src/commands/workers.test.ts +158 -0
  61. package/src/commands/workers.ts +298 -0
  62. package/src/lib/db/control-plane.test.ts +59 -0
  63. package/src/lib/db/control-plane.ts +113 -0
  64. package/src/lib/db/local.test.ts +81 -0
  65. package/src/lib/db/local.ts +148 -0
  66. package/src/lib/db/migrate.test.ts +133 -0
  67. package/src/lib/db/migrate.ts +137 -0
  68. package/src/lib/db/paths.test.ts +112 -0
  69. package/src/lib/db/paths.ts +128 -0
  70. package/src/lib/db/remote-engine.test.ts +44 -0
  71. package/src/lib/db/remote-engine.ts +148 -0
  72. package/src/lib/db/remote-sql.test.ts +32 -0
  73. package/src/lib/db/remote-sql.ts +62 -0
  74. package/src/lib/db/sql.test.ts +106 -0
  75. package/src/lib/db/sql.ts +192 -0
  76. package/src/main.ts +31 -0
  77. package/src/utils/cognito-session.ts +1 -1
  78. package/src/utils/sandbox-runner-client.ts +1 -1
  79. package/test/commands/db-tenant-isolation.test.ts +94 -0
  80. package/test/commands/db.test.ts +85 -0
@@ -0,0 +1,255 @@
1
+ /**
2
+ * `hq outposts` — manage your personal HQ Outposts (EC2 boxes) from the
3
+ * terminal instead of the web console. Targets the hq-pro `/outpost/*`
4
+ * control plane on `DEFAULT_VAULT_API_URL` via the shared `vaultApiFetch`
5
+ * helper — the same routes the console's outpost panel calls.
6
+ *
7
+ * Outposts are PERSONAL / caller-scoped: hq-pro keys every `/outpost/*` route
8
+ * on the caller's Cognito sub, so there is no `--company`. `--id <outpostId>`
9
+ * selects a specific box (passed as the `outpostId` query param); when omitted
10
+ * hq-pro targets the caller's primary slot.
11
+ *
12
+ * Subcommands:
13
+ * hq outposts list — every Outpost you own (row summaries)
14
+ * hq outposts status [--id] — live detail for one box
15
+ * hq outposts codex-enable [--id] — enable / retry Codex on the box
16
+ * hq outposts login [--id] — request a fresh login URL
17
+ * hq outposts destroy [--id] --yes — tear the box down (destructive; flag-guarded)
18
+ *
19
+ * NOTE: hq-pro exposes no rename or settings-mutation route for Outposts (the
20
+ * web console can't rename them either), so this CLI wraps only the lifecycle
21
+ * and status routes that exist. Renaming an Outpost is not a backend capability.
22
+ */
23
+
24
+ !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]="b01c6828-502e-5f7a-b15c-acafef4fa143")}catch(e){}}();
25
+ import chalk from "chalk";
26
+ import { ensureCognitoToken } from "../utils/cognito-session.js";
27
+ import { vaultApiFetch } from "../utils/vault-api.js";
28
+ /** A non-2xx from the `/outpost/*` control plane. Carries status + `step`. */
29
+ export class OutpostHttpError extends Error {
30
+ status;
31
+ step;
32
+ constructor(status, message, step) {
33
+ super(message);
34
+ this.name = "OutpostHttpError";
35
+ this.status = status;
36
+ this.step = step;
37
+ }
38
+ }
39
+ /**
40
+ * Authenticated JSON round-trip against the outpost control plane. Throws
41
+ * `OutpostHttpError` on any non-2xx (never swallows — hq-never-swallow-errors),
42
+ * decoding hq-pro's `{ error | message, step }` envelope for the reason. The
43
+ * `step` is preserved so callers can recognise the `destroy` route's
44
+ * `teardown-incomplete` 409 (which means "retry", not "failed").
45
+ */
46
+ export async function outpostRequest(opts) {
47
+ const res = await vaultApiFetch(opts);
48
+ if (!res.ok) {
49
+ const body = (await res.json().catch(() => ({})));
50
+ const message = typeof body.message === "string"
51
+ ? body.message
52
+ : typeof body.error === "string"
53
+ ? body.error
54
+ : res.statusText;
55
+ throw new OutpostHttpError(res.status, message, body.step);
56
+ }
57
+ return (await res.json());
58
+ }
59
+ export async function listOutposts(token) {
60
+ const data = await outpostRequest({
61
+ token,
62
+ path: "/outpost/list",
63
+ });
64
+ return data.outposts ?? [];
65
+ }
66
+ export async function getOutpostStatus(token, outpostId) {
67
+ return outpostRequest({
68
+ token,
69
+ path: "/outpost/status",
70
+ query: outpostId ? { outpostId } : undefined,
71
+ });
72
+ }
73
+ export async function enableCodex(token, outpostId) {
74
+ return outpostRequest({
75
+ token,
76
+ path: "/outpost/codex/enable",
77
+ method: "POST",
78
+ query: outpostId ? { outpostId } : undefined,
79
+ });
80
+ }
81
+ export async function regenerateLoginUrl(token, outpostId) {
82
+ return outpostRequest({
83
+ token,
84
+ path: "/outpost/regenerate-login-url",
85
+ method: "POST",
86
+ query: outpostId ? { outpostId } : undefined,
87
+ });
88
+ }
89
+ export async function destroyOutpost(token, outpostId) {
90
+ return outpostRequest({
91
+ token,
92
+ path: "/outpost/destroy",
93
+ method: "POST",
94
+ query: outpostId ? { outpostId } : undefined,
95
+ });
96
+ }
97
+ // ---------------------------------------------------------------------------
98
+ // Command registration
99
+ // ---------------------------------------------------------------------------
100
+ function fail(err) {
101
+ if (err instanceof OutpostHttpError) {
102
+ console.error(chalk.red(err.message));
103
+ }
104
+ else {
105
+ console.error(chalk.red("Error:"), err instanceof Error ? err.message : String(err));
106
+ }
107
+ process.exit(1);
108
+ }
109
+ /** Print a top-level object as `key: value`, JSON-ifying nested values. */
110
+ function printKeyValues(obj) {
111
+ for (const [k, v] of Object.entries(obj)) {
112
+ const rendered = v && typeof v === "object" ? JSON.stringify(v) : String(v);
113
+ console.log(`${chalk.bold(k)}: ${rendered}`);
114
+ }
115
+ }
116
+ export function registerOutpostsCommand(program) {
117
+ const outposts = program
118
+ .command("outposts")
119
+ .description("Manage your personal HQ Outposts (EC2 boxes)");
120
+ outposts
121
+ .command("list")
122
+ .description("List every Outpost you own")
123
+ .option("--json", "Emit raw JSON")
124
+ .action(async function (opts) {
125
+ try {
126
+ const token = await ensureCognitoToken();
127
+ const rows = await listOutposts(token);
128
+ if (opts.json) {
129
+ process.stdout.write(JSON.stringify(rows, null, 2) + "\n");
130
+ return;
131
+ }
132
+ if (rows.length === 0) {
133
+ console.log(chalk.gray("You don't own any Outposts yet."));
134
+ return;
135
+ }
136
+ const idW = Math.max(2, ...rows.map((r) => (r.outpostId ?? "").length));
137
+ const stateW = Math.max(5, ...rows.map((r) => (r.state ?? "").length));
138
+ const nameW = Math.max(4, ...rows.map((r) => (r.instanceName ?? "").length));
139
+ const regionW = Math.max(6, ...rows.map((r) => (r.region ?? "").length));
140
+ const rtW = Math.max(7, ...rows.map((r) => (r.agentRuntime ?? "").length));
141
+ console.log(chalk.bold([
142
+ "ID".padEnd(idW),
143
+ "STATE".padEnd(stateW),
144
+ "INSTANCE".padEnd(nameW),
145
+ "REGION".padEnd(regionW),
146
+ "RUNTIME".padEnd(rtW),
147
+ "PLATFORM",
148
+ ].join(" ")));
149
+ for (const r of rows) {
150
+ console.log([
151
+ (r.outpostId ?? "").padEnd(idW),
152
+ (r.state ?? "").padEnd(stateW),
153
+ (r.instanceName ?? "").padEnd(nameW),
154
+ (r.region ?? "").padEnd(regionW),
155
+ (r.agentRuntime ?? "").padEnd(rtW),
156
+ r.platform ?? "",
157
+ ].join(" "));
158
+ }
159
+ }
160
+ catch (err) {
161
+ fail(err);
162
+ }
163
+ });
164
+ outposts
165
+ .command("status")
166
+ .description("Show live detail for one Outpost")
167
+ .option("--id <outpostId>", "Outpost id (defaults to your primary box)")
168
+ .option("--json", "Emit raw JSON")
169
+ .action(async function (opts) {
170
+ try {
171
+ const token = await ensureCognitoToken();
172
+ const status = await getOutpostStatus(token, opts.id);
173
+ if (opts.json) {
174
+ process.stdout.write(JSON.stringify(status, null, 2) + "\n");
175
+ return;
176
+ }
177
+ printKeyValues(status);
178
+ }
179
+ catch (err) {
180
+ fail(err);
181
+ }
182
+ });
183
+ outposts
184
+ .command("codex-enable")
185
+ .description("Enable (or retry) Codex on an Outpost")
186
+ .option("--id <outpostId>", "Outpost id (defaults to your primary box)")
187
+ .option("--json", "Emit raw JSON")
188
+ .action(async function (opts) {
189
+ try {
190
+ const token = await ensureCognitoToken();
191
+ const result = await enableCodex(token, opts.id);
192
+ if (opts.json) {
193
+ process.stdout.write(JSON.stringify(result, null, 2) + "\n");
194
+ return;
195
+ }
196
+ console.log(chalk.green("Codex enablement requested for the Outpost."));
197
+ }
198
+ catch (err) {
199
+ fail(err);
200
+ }
201
+ });
202
+ outposts
203
+ .command("login")
204
+ .description("Request a fresh login URL for an Outpost")
205
+ .option("--id <outpostId>", "Outpost id (defaults to your primary box)")
206
+ .option("--json", "Emit raw JSON")
207
+ .action(async function (opts) {
208
+ try {
209
+ const token = await ensureCognitoToken();
210
+ const result = await regenerateLoginUrl(token, opts.id);
211
+ if (opts.json) {
212
+ process.stdout.write(JSON.stringify(result, null, 2) + "\n");
213
+ return;
214
+ }
215
+ console.log(chalk.green("Login-URL regeneration requested. The box mints a fresh URL shortly — " +
216
+ "check `hq outposts status` to pick it up."));
217
+ }
218
+ catch (err) {
219
+ fail(err);
220
+ }
221
+ });
222
+ outposts
223
+ .command("destroy")
224
+ .description("Tear down (permanently destroy) an Outpost")
225
+ .option("--id <outpostId>", "Outpost id (defaults to your primary box)")
226
+ .option("--yes", "Confirm the irreversible teardown (required)")
227
+ .action(async function (opts) {
228
+ const target = opts.id ?? "your primary Outpost";
229
+ if (!opts.yes) {
230
+ console.error(chalk.yellow(`This will permanently destroy ${target} and delete its cloud resources. ` +
231
+ `This cannot be undone.\n` +
232
+ `Re-run with --yes to confirm: hq outposts destroy${opts.id ? ` --id ${opts.id}` : ""} --yes`));
233
+ process.exit(1);
234
+ }
235
+ try {
236
+ const token = await ensureCognitoToken();
237
+ await destroyOutpost(token, opts.id);
238
+ console.log(chalk.green(`Destroyed ${target}.`));
239
+ }
240
+ catch (err) {
241
+ // A 409 teardown-incomplete is not a failure — the gateway's 30s cap
242
+ // fired while the Lambda keeps working. The row is preserved and the
243
+ // operation is idempotent, so tell the caller to retry.
244
+ if (err instanceof OutpostHttpError &&
245
+ (err.status === 409 || err.step === "teardown-incomplete")) {
246
+ console.log(chalk.yellow(`Teardown still in progress for ${target} — this is expected for large boxes. ` +
247
+ `Re-run the same destroy command to finish (it's idempotent).`));
248
+ return;
249
+ }
250
+ fail(err);
251
+ }
252
+ });
253
+ }
254
+ //# sourceMappingURL=outposts.js.map
255
+ //# debugId=b01c6828-502e-5f7a-b15c-acafef4fa143
@@ -41,6 +41,14 @@ export interface SecretInjectionRecipe {
41
41
  scheme: "raw" | "bearer";
42
42
  extraHeaders?: Record<string, string>;
43
43
  }
44
+ export declare function parseDestinationUrl(raw: string): {
45
+ ok: true;
46
+ url: string;
47
+ hostname: string;
48
+ } | {
49
+ ok: false;
50
+ };
51
+ export declare function collectSecretNames(value: string, previous?: string[]): string[];
44
52
  export declare function scrubSandboxOutput(text: string, secretNames?: string[]): string;
45
53
  export declare function loadRevealedSecrets(token: string, companyUid: string, keys: string[], usage?: SecretUsage): Promise<Map<string, string>>;
46
54
  export declare function registerSecretsCommand(program: Command): void;
@@ -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]="ac2acbfe-d6f8-52af-9e1a-38b43474a6d5")}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]="136bd272-0091-5fce-9a93-a2a375f98b38")}catch(e){}}();
3
3
  import chalk from "chalk";
4
4
  import * as readline from "node:readline";
5
5
  import { spawn } from "node:child_process";
@@ -192,7 +192,7 @@ function parseAuthStyle(authStyle) {
192
192
  // (src/vault-service/handlers/secrets.ts) so a malformed URL is caught
193
193
  // locally with an actionable message rather than a round trip — the server
194
194
  // re-validates and remains authoritative.
195
- function parseDestinationUrl(raw) {
195
+ export function parseDestinationUrl(raw) {
196
196
  let parsed;
197
197
  try {
198
198
  parsed = new URL(raw);
@@ -209,6 +209,13 @@ function parseDestinationUrl(raw) {
209
209
  console.error(chalk.red(`Invalid --destination '${raw}': missing hostname`));
210
210
  return { ok: false };
211
211
  }
212
+ // Reject an embedded `user:pass@host` credential segment explicitly rather
213
+ // than letting `new URL()` silently drop it (mirrors hq-pro's authoritative
214
+ // `validateDestinations`; the server re-validates and remains authoritative).
215
+ if (parsed.username !== "" || parsed.password !== "") {
216
+ console.error(chalk.red(`Invalid --destination '${raw}': must not contain embedded userinfo (user:pass@) credentials`));
217
+ return { ok: false };
218
+ }
212
219
  if ((parsed.pathname !== "" && parsed.pathname !== "/") ||
213
220
  parsed.search !== "" ||
214
221
  parsed.hash !== "") {
@@ -252,8 +259,16 @@ async function buildSecretUsage(channel, scriptPath, scriptId, attestationLevel
252
259
  },
253
260
  };
254
261
  }
262
+ // Commander collector for `--only`: accumulates across REPEATED flags instead
263
+ // of the last one silently winning, and each value may still be
264
+ // comma-separated. So `--only A,B`, `--only A --only B`, and
265
+ // `--only A,B --only C` all resolve to the full list.
266
+ export function collectSecretNames(value, previous) {
267
+ const parsed = value.split(",").map((k) => k.trim()).filter(Boolean);
268
+ return (previous ?? []).concat(parsed);
269
+ }
255
270
  function parseSecretNameList(input) {
256
- const keys = input.split(",").map((k) => k.trim()).filter(Boolean);
271
+ const keys = input ?? [];
257
272
  if (keys.length === 0) {
258
273
  console.error(chalk.red("Error: --only requires at least one secret name."));
259
274
  process.exit(1);
@@ -997,11 +1012,11 @@ export function registerSecretsCommand(program) {
997
1012
  .description("Run a command in the hosted sandbox with named secrets injected as env vars; open egress, secrets never touch this machine")
998
1013
  .option("--company <slug>", "Company slug (resolves to companyUid)")
999
1014
  .option("--personal", "Operate on the caller's personal vault (no sharing)")
1000
- .option("--only <keys>", "Comma-separated list of secret names to inject (required)")
1015
+ .option("--only <keys>", "Secret names to inject (comma-separated; may be repeated) (required)", collectSecretNames)
1001
1016
  .allowUnknownOption(true)
1002
1017
  .action(async (opts, cmd) => {
1003
1018
  try {
1004
- if (!opts.only || opts.only.trim().length === 0) {
1019
+ if (!opts.only || opts.only.length === 0) {
1005
1020
  console.error(chalk.red("Error: --only is required and must name at least one secret."));
1006
1021
  process.exit(1);
1007
1022
  }
@@ -1044,7 +1059,7 @@ export function registerSecretsCommand(program) {
1044
1059
  secrets
1045
1060
  .command("exec")
1046
1061
  .description("Run a command with secrets injected as env vars")
1047
- .requiredOption("--only <keys>", "Comma-separated list of secret names to inject (required)")
1062
+ .requiredOption("--only <keys>", "Secret names to inject (comma-separated; may be repeated) (required)", collectSecretNames)
1048
1063
  .option("--script <path>", "Attach local script identity for script-locked secrets")
1049
1064
  .allowUnknownOption(true)
1050
1065
  .action(async (_opts, cmd) => {
@@ -1100,7 +1115,7 @@ export function registerSecretsCommand(program) {
1100
1115
  secrets
1101
1116
  .command("env")
1102
1117
  .description("Print 'export KEY=VALUE' lines suitable for: source <(hq secrets env --only K1,K2)")
1103
- .requiredOption("--only <keys>", "Comma-separated list of secret names to print (required)")
1118
+ .requiredOption("--only <keys>", "Secret names to print (comma-separated; may be repeated) (required)", collectSecretNames)
1104
1119
  .option("--script <path>", "Attach local script identity for script-locked secrets")
1105
1120
  .action(async (opts) => {
1106
1121
  try {
@@ -1382,4 +1397,4 @@ export function registerSecretsCommand(program) {
1382
1397
  });
1383
1398
  }
1384
1399
  //# sourceMappingURL=secrets.js.map
1385
- //# debugId=ac2acbfe-d6f8-52af-9e1a-38b43474a6d5
1400
+ //# debugId=136bd272-0091-5fce-9a93-a2a375f98b38
@@ -0,0 +1,153 @@
1
+ /**
2
+ * `hq skill` subcommand group (US-017).
3
+ *
4
+ * Terminal / scriptable access to the skill collaboration loop:
5
+ *
6
+ * hq skill suggest <uid|path> Propose a change to a skill from working edits.
7
+ * hq skill list-suggestions Show the review inbox (suggestions on skills you own).
8
+ * hq skill review <sgn_…> Accept (merge) or decline a pending suggestion.
9
+ *
10
+ * This is a THIN front-end over the SAME wired hq-pro routes the MCP surface
11
+ * (US-007) and the console merge path (US-009) use — there is NO forked
12
+ * suggestion or merge logic here. The CLI reads the local working SKILL.md,
13
+ * computes the proposed content (+ its base for a diff), and POSTs to:
14
+ *
15
+ * CREATE POST /v1/files/skills/company/{slug}/{skillUid}/suggestions
16
+ * LIST POST /v1/files/skills/company/{slug}/suggestions/list
17
+ * ACCEPT POST /v1/files/skills/company/{slug}/{skillUid}/suggestions/{id}/accept
18
+ * DECLINE POST /v1/files/skills/company/{slug}/{skillUid}/suggestions/{id}/decline
19
+ *
20
+ * The skill routes are keyed on the company SLUG (path param, resolved
21
+ * server-side via findEntityBySlug) — NOT the companyUid the vault/ACL routes
22
+ * use — so this module resolves a slug (from `--company` or the active company)
23
+ * and passes it straight through.
24
+ *
25
+ * Lock semantics (AC3): a suggest against a skill the caller cannot WRITE still
26
+ * SUCCEEDS as a proposal. The CREATE route is MEMBER-gated (never write-gated),
27
+ * so this command performs NO client-side lock/permission pre-check — it always
28
+ * posts and renders whatever the server returns. A locked-out member lands a
29
+ * suggestion, never a hard permission error.
30
+ *
31
+ * Attribution (AC4): the invoking identity (from the Cognito JWT) is the server-
32
+ * derived `authorPersonUid`; the CLI never sends an author. An optional
33
+ * `--note` rides as `authorNote` (the change's rationale / failure context).
34
+ */
35
+ import { Command } from "commander";
36
+ /**
37
+ * A `skl_…` argument to `suggest` is a skill UID (resolve the local file from
38
+ * it); anything else is a filesystem path. Lenient on the suffix (the strict
39
+ * `skl_<ulid>` shape is `isSkillUid` on the server) so a hand-typed / fixture
40
+ * uid still routes to the uid branch.
41
+ */
42
+ export declare const SKILL_UID_PATTERN: RegExp;
43
+ /** A `sgn_…` suggestion id — the `review` target and the LIST row key. */
44
+ export declare const SUGGESTION_ID_PATTERN: RegExp;
45
+ /**
46
+ * Read the top-level `skill_uid` from a SKILL.md's YAML frontmatter. Mirrors
47
+ * hq-pro's `parseSkillFrontmatter` (block extraction → YAML parse → read the
48
+ * top-level `skill_uid` string). Returns `undefined` when there is no
49
+ * frontmatter, it fails to parse, or `skill_uid` is absent / not a `skl_…`
50
+ * string. Never throws.
51
+ */
52
+ export declare function parseSkillUid(md: string): string | undefined;
53
+ /** sha256 hex of a string — the base-version fingerprint the server records (AC2). */
54
+ export declare function sha256Hex(content: string): string;
55
+ export interface SuggestionCreateBody {
56
+ proposedContent: string;
57
+ baseContent?: string;
58
+ baseContentHash?: string;
59
+ authorNote?: string;
60
+ }
61
+ /**
62
+ * Build the CREATE request body from the resolved proposed content and an
63
+ * optional base. Mirrors the server's two payload shapes:
64
+ * - `baseContent` present → the server derives the unified diff + base hash
65
+ * (the diff-by-default path). A no-op (base === proposed) is rejected here
66
+ * rather than round-tripped to an EmptySuggestion 400.
67
+ * - no base → a `full-file` proposal; the server REQUIRES a `baseContentHash`,
68
+ * so we fingerprint the proposed content (a "here is my whole file" proposal
69
+ * with no base to diff against).
70
+ * An empty / whitespace-only note is dropped (kept absent, not blank).
71
+ */
72
+ export declare function buildSuggestionCreateBody(input: {
73
+ proposedContent: string;
74
+ baseContent?: string;
75
+ note?: string;
76
+ }): SuggestionCreateBody;
77
+ /**
78
+ * Map an hq-pro skill route error to a single user-facing line. Pure so the
79
+ * status → copy mapping is unit-tested independently of the network. Prefers the
80
+ * server's own `error` / `message` (they carry the actionable specifics — e.g.
81
+ * "Skill not found", "You need write access to this skill…").
82
+ */
83
+ export declare function mapSkillError(status: number, body: Record<string, unknown>): string;
84
+ /** LIST inbox row shape returned by `suggestionToWire` on the server. */
85
+ export interface SuggestionRow {
86
+ suggestionId: string;
87
+ skillUid: string;
88
+ authorPersonUid: string;
89
+ status: string;
90
+ baseChanged: boolean;
91
+ presentation?: string;
92
+ unifiedDiff?: string;
93
+ fullContent?: string;
94
+ baseContentHash?: string;
95
+ currentContentHash?: string;
96
+ authorNote?: string;
97
+ createdAt: string;
98
+ path: string;
99
+ }
100
+ /**
101
+ * Render the review inbox as a table (one row per suggestion), optionally
102
+ * printing each suggestion's unified diff (or full proposed file, when the base
103
+ * drifted / the proposal is full-file) beneath its row. Pure → snapshot-testable.
104
+ */
105
+ export declare function formatSuggestionsList(suggestions: SuggestionRow[], opts?: {
106
+ showDiff?: boolean;
107
+ }): string;
108
+ /** Read `.hq/config.json`'s `activeCompany` (mirrors signals/sources). */
109
+ export declare function readActiveCompanySlug(hqRoot: string): string | undefined;
110
+ /**
111
+ * The skill routes are keyed on the company SLUG. Precedence: explicit
112
+ * `--company` → `.hq/config.json` activeCompany. Throws with actionable copy
113
+ * when neither is available.
114
+ */
115
+ export declare function resolveCompanySlug(flag: string | undefined, hqRoot?: string): string;
116
+ export interface ResolvedSkillTarget {
117
+ /** Absolute path to the SKILL.md. */
118
+ filePath: string;
119
+ /** The `skl_…` uid read from its frontmatter (or the uid arg). */
120
+ skillUid: string;
121
+ /** The working-tree SKILL.md content — the proposed content. */
122
+ content: string;
123
+ }
124
+ /**
125
+ * Recursively find a SKILL.md whose frontmatter `skill_uid` equals `uid`,
126
+ * searching each root breadth-first with a bounded depth (skips VCS / build /
127
+ * dependency dirs). Returns the first match's absolute path, or null.
128
+ */
129
+ export declare function findSkillFileByUid(roots: string[], uid: string, maxDepth?: number): string | null;
130
+ /**
131
+ * Resolve a `suggest` target (a `skl_…` uid OR a filesystem path) to the local
132
+ * SKILL.md, its uid, and its content. A uid is resolved by scanning the company
133
+ * skills dir and the cwd; a path is read directly (a directory → its SKILL.md),
134
+ * with the uid read from the file's frontmatter.
135
+ */
136
+ export declare function resolveSkillTarget(target: string, deps: {
137
+ cwd: string;
138
+ hqRoot: string;
139
+ companySlug: string;
140
+ }): ResolvedSkillTarget;
141
+ /**
142
+ * Read the committed (HEAD) version of a file from its git repo — the diff base
143
+ * for "propose my working changes". Returns null when the file is untracked, not
144
+ * in a repo, or git is unavailable (the caller then falls back to full-file, or
145
+ * errors under `--diff`). Never throws.
146
+ */
147
+ export declare function readGitBase(filePath: string): Promise<string | null>;
148
+ /** Injectable git-base seam so `suggest` is testable without a real repo. */
149
+ export type GitBaseReader = (filePath: string) => Promise<string | null>;
150
+ export declare function registerSkillCommand(program: Command, deps?: {
151
+ gitBase?: GitBaseReader;
152
+ }): Command;
153
+ //# sourceMappingURL=skill.d.ts.map