@indigoai-us/hq-cli 5.119.8 → 5.119.10

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 CHANGED
@@ -2,6 +2,40 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [5.119.10] — 2026-09-18
6
+
7
+ ### Added
8
+
9
+ - `hq skill register` reserves a company skill UID from a proposal-lane
10
+ source and writes the server-stamped bytes only to `--stamped-output`.
11
+ It does not write `companies/<company>/skills/`, create a discovery
12
+ wrapper, or sync. `hq skill promote` copies those exact bytes into the
13
+ canonical skill path only after `--expected-sha256` matches and the
14
+ stamped `skill_uid` matches the server registration. A differing
15
+ canonical file requires `--reviewed-update`. Use `--no-sync` to leave
16
+ upload to a later steward command.
17
+
18
+ ## [5.119.9] — 2026-09-17
19
+
20
+ ### Added
21
+
22
+ - The CLI now prints a one-time notice when your workspace crosses a plan
23
+ threshold — reaching 80% of a Starter limit, or going past one. The server
24
+ decides when a threshold has been crossed and sends it with the response, so
25
+ a session you already have open surfaces the notice on its next API call
26
+ rather than on its next launch. Each `{resource}:{band}` is printed exactly
27
+ once per episode, remembered in `~/.hq/plan-limit-nag.json`; a dimension that
28
+ drops back under the threshold and climbs again is announced again.
29
+ `HQ_NO_PLAN_LIMIT_NAG=1` silences it along with the other plan-limit lines.
30
+
31
+ ### Fixed
32
+
33
+ - A file attached to a DM is now visible to a bot reading its mail. `hq dm
34
+ inbox`, `hq dm thread`, `hq dm channel` and `hq agent inbox` print each
35
+ attached file's name, type and vault path under the message body; the JSON
36
+ forms carry an `attachments[]` field. Previously only the body text was
37
+ rendered, so an agent sent a screenshot answered "no image came through".
38
+
5
39
  ## [5.119.8] — 2026-09-17
6
40
 
7
41
  ### Fixed
@@ -44,6 +78,15 @@
44
78
  on Starter: 1", and tells you to "disconnect integrations until you are at 1
45
79
  or fewer"; the per-turn line reads "over its 1-integration limit". Nothing in
46
80
  the CLI says Starter has no integrations any more.
81
+ - Plan-limit messages now say what is actually happening. Going over a Starter
82
+ limit never stopped the workspace, and the copy no longer implies it did:
83
+ the words "locked" and "read-only" are gone from every line `hq` prints, and
84
+ the notice states the two things that do pause — adding new files and adding
85
+ new secrets — while everything else keeps working. A test scans every
86
+ rendered string for both words so the copy cannot drift back.
87
+ - The over-limit per-turn nag is one line instead of a box, and it repeats at
88
+ most once every 6 hours (was once a day). `HQ_NO_PLAN_LIMIT_NAG=1` still
89
+ silences it.
47
90
 
48
91
  ## [5.119.4] — 2026-09-17
49
92
 
@@ -1877,7 +1877,7 @@ export declare const COMMAND_CATALOG: readonly [{
1877
1877
  readonly subcommands: readonly [];
1878
1878
  }, {
1879
1879
  readonly name: "skill";
1880
- readonly description: "Create company skills and discuss improvements";
1880
+ readonly description: "Create, stage, promote, and discuss company skills";
1881
1881
  readonly aliases: readonly [];
1882
1882
  readonly hidden: false;
1883
1883
  readonly usage: "[options] [command]";
@@ -1914,6 +1914,59 @@ export declare const COMMAND_CATALOG: readonly [{
1914
1914
  readonly description: "Refresh local skill discovery without registration or sync";
1915
1915
  }];
1916
1916
  readonly subcommands: readonly [];
1917
+ }, {
1918
+ readonly name: "register";
1919
+ readonly description: "Register a company skill to a proposal-lane stamped file without writing the canonical tree";
1920
+ readonly aliases: readonly [];
1921
+ readonly hidden: false;
1922
+ readonly usage: "[options] <slug>";
1923
+ readonly arguments: readonly [{
1924
+ readonly name: "slug";
1925
+ readonly required: true;
1926
+ readonly variadic: false;
1927
+ }];
1928
+ readonly options: readonly [{
1929
+ readonly flags: "--source <path>";
1930
+ readonly description: "Caller-authored SKILL.md (or its directory) to send to the server validator";
1931
+ }, {
1932
+ readonly flags: "--stamped-output <path>";
1933
+ readonly description: "Write the complete server-stamped bytes here, outside companies/<company>/skills/";
1934
+ }, {
1935
+ readonly flags: "--no-canonical-write";
1936
+ readonly description: "Refuse to write companies/<company>/skills/ (always the register behavior)";
1937
+ }, {
1938
+ readonly flags: "--no-surface";
1939
+ readonly description: "Do not create a discovery wrapper (always the register behavior)";
1940
+ }, {
1941
+ readonly flags: "--no-sync";
1942
+ readonly description: "Do not upload the skill file (always the register behavior)";
1943
+ }];
1944
+ readonly subcommands: readonly [];
1945
+ }, {
1946
+ readonly name: "promote";
1947
+ readonly description: "Copy independently-cleared stamped bytes into the canonical skill path after SHA-256 and skill_uid checks";
1948
+ readonly aliases: readonly [];
1949
+ readonly hidden: false;
1950
+ readonly usage: "[options] <slug>";
1951
+ readonly arguments: readonly [{
1952
+ readonly name: "slug";
1953
+ readonly required: true;
1954
+ readonly variadic: false;
1955
+ }];
1956
+ readonly options: readonly [{
1957
+ readonly flags: "--from <path>";
1958
+ readonly description: "Independently-cleared stamped SKILL.md (or its directory)";
1959
+ }, {
1960
+ readonly flags: "--expected-sha256 <hex>";
1961
+ readonly description: "SHA-256 of the exact stamped file bytes that were cleared";
1962
+ }, {
1963
+ readonly flags: "--reviewed-update";
1964
+ readonly description: "Replace an existing canonical SKILL.md whose bytes differ from the stamped file";
1965
+ }, {
1966
+ readonly flags: "--no-sync";
1967
+ readonly description: "Promote locally without uploading the canonical file";
1968
+ }];
1969
+ readonly subcommands: readonly [];
1917
1970
  }, {
1918
1971
  readonly name: "propose";
1919
1972
  readonly description: "Post a comment-only improvement for a skill";
@@ -2403,7 +2403,7 @@ export const COMMAND_CATALOG = [
2403
2403
  },
2404
2404
  {
2405
2405
  "name": "skill",
2406
- "description": "Create company skills and discuss improvements",
2406
+ "description": "Create, stage, promote, and discuss company skills",
2407
2407
  "aliases": [],
2408
2408
  "hidden": false,
2409
2409
  "usage": "[options] [command]",
@@ -2452,6 +2452,76 @@ export const COMMAND_CATALOG = [
2452
2452
  ],
2453
2453
  "subcommands": []
2454
2454
  },
2455
+ {
2456
+ "name": "register",
2457
+ "description": "Register a company skill to a proposal-lane stamped file without writing the canonical tree",
2458
+ "aliases": [],
2459
+ "hidden": false,
2460
+ "usage": "[options] <slug>",
2461
+ "arguments": [
2462
+ {
2463
+ "name": "slug",
2464
+ "required": true,
2465
+ "variadic": false
2466
+ }
2467
+ ],
2468
+ "options": [
2469
+ {
2470
+ "flags": "--source <path>",
2471
+ "description": "Caller-authored SKILL.md (or its directory) to send to the server validator"
2472
+ },
2473
+ {
2474
+ "flags": "--stamped-output <path>",
2475
+ "description": "Write the complete server-stamped bytes here, outside companies/<company>/skills/"
2476
+ },
2477
+ {
2478
+ "flags": "--no-canonical-write",
2479
+ "description": "Refuse to write companies/<company>/skills/ (always the register behavior)"
2480
+ },
2481
+ {
2482
+ "flags": "--no-surface",
2483
+ "description": "Do not create a discovery wrapper (always the register behavior)"
2484
+ },
2485
+ {
2486
+ "flags": "--no-sync",
2487
+ "description": "Do not upload the skill file (always the register behavior)"
2488
+ }
2489
+ ],
2490
+ "subcommands": []
2491
+ },
2492
+ {
2493
+ "name": "promote",
2494
+ "description": "Copy independently-cleared stamped bytes into the canonical skill path after SHA-256 and skill_uid checks",
2495
+ "aliases": [],
2496
+ "hidden": false,
2497
+ "usage": "[options] <slug>",
2498
+ "arguments": [
2499
+ {
2500
+ "name": "slug",
2501
+ "required": true,
2502
+ "variadic": false
2503
+ }
2504
+ ],
2505
+ "options": [
2506
+ {
2507
+ "flags": "--from <path>",
2508
+ "description": "Independently-cleared stamped SKILL.md (or its directory)"
2509
+ },
2510
+ {
2511
+ "flags": "--expected-sha256 <hex>",
2512
+ "description": "SHA-256 of the exact stamped file bytes that were cleared"
2513
+ },
2514
+ {
2515
+ "flags": "--reviewed-update",
2516
+ "description": "Replace an existing canonical SKILL.md whose bytes differ from the stamped file"
2517
+ },
2518
+ {
2519
+ "flags": "--no-sync",
2520
+ "description": "Promote locally without uploading the canonical file"
2521
+ }
2522
+ ],
2523
+ "subcommands": []
2524
+ },
2455
2525
  {
2456
2526
  "name": "propose",
2457
2527
  "description": "Post a comment-only improvement for a skill",
@@ -52,6 +52,12 @@ export function registerAgentInboxCommand(agent) {
52
52
  console.log(`${chalk.bold(r.id)}${state} ${r.channel ?? "?"} from ${r.from}${r.fromUid ? ` <${r.fromUid}>` : ""} ${r.at ?? ""}`);
53
53
  if (r.text)
54
54
  console.log(` ${r.text.replace(/\n/g, "\n ")}`);
55
+ for (const f of r.attachments ?? []) {
56
+ const label = f.name ?? f.vaultPath.split("/").pop() ?? "file";
57
+ const type = f.contentType ? ` (${f.contentType})` : "";
58
+ console.log(` ${chalk.dim("[file]")} ${label}${type}`);
59
+ console.log(` ${chalk.dim(`vault: ${f.vaultPath}`)}`);
60
+ }
55
61
  }
56
62
  if (!opts.all)
57
63
  console.log(chalk.dim(`\nReply with hq dm <uid> "…", then: hq agent inbox done <id>`));
@@ -201,6 +201,9 @@ export interface DmInboxEvent {
201
201
  details?: string;
202
202
  prompt?: string;
203
203
  acknowledgedAt?: string;
204
+ /** US-007 file references. Singular `attachment` is the legacy form. */
205
+ attachments?: DmAttachment[];
206
+ attachment?: DmAttachment;
204
207
  }
205
208
  /** One message in a 1:1 thread as returned by GET /v1/notify/thread. */
206
209
  export interface DmThreadMessage {
@@ -213,6 +216,8 @@ export interface DmThreadMessage {
213
216
  direction: "in" | "out";
214
217
  details?: string;
215
218
  prompt?: string;
219
+ attachments?: DmAttachment[];
220
+ attachment?: DmAttachment;
216
221
  }
217
222
  /** One channel/group message from GET /v1/notify/channels/{id}/messages. */
218
223
  export interface ChannelMessageItem {
@@ -223,7 +228,33 @@ export interface ChannelMessageItem {
223
228
  fromDisplayName?: string;
224
229
  body: string;
225
230
  createdAt: string;
231
+ attachments?: DmAttachment[];
232
+ attachment?: DmAttachment;
226
233
  }
234
+ /**
235
+ * One file attached to a DM (US-007 `attachments[]` on the read endpoints). The
236
+ * server returns a vault path, not bytes: a reader fetches it with
237
+ * a presigned read of that key.
238
+ */
239
+ export interface DmAttachment {
240
+ vaultPath: string;
241
+ name?: string;
242
+ sizeBytes?: number;
243
+ contentType?: string;
244
+ kind?: string;
245
+ companyUid?: string;
246
+ }
247
+ /**
248
+ * Render the attachment lines for one message. An agent reading its DMs through
249
+ * the CLI only ever sees this text, so the vault path AND the command that
250
+ * fetches it both have to be on screen — a bare "[image]" marker is why a bot
251
+ * answers "no image came through". Returns "" when there is nothing attached.
252
+ * Pure → unit-testable.
253
+ */
254
+ export declare function formatAttachments(m: {
255
+ attachments?: DmAttachment[];
256
+ attachment?: DmAttachment;
257
+ }, indent?: string): string;
227
258
  /**
228
259
  * Human label for a message sender — display name, else email, else uid. Pure →
229
260
  * unit-testable.
@@ -693,6 +693,27 @@ async function runDmRequests() {
693
693
  process.exit(1);
694
694
  }
695
695
  }
696
+ /**
697
+ * Render the attachment lines for one message. An agent reading its DMs through
698
+ * the CLI only ever sees this text, so the vault path AND the command that
699
+ * fetches it both have to be on screen — a bare "[image]" marker is why a bot
700
+ * answers "no image came through". Returns "" when there is nothing attached.
701
+ * Pure → unit-testable.
702
+ */
703
+ export function formatAttachments(m, indent = " ") {
704
+ const files = (m.attachments?.length ? m.attachments : m.attachment ? [m.attachment] : [])
705
+ .filter((f) => Boolean(f?.vaultPath));
706
+ if (files.length === 0)
707
+ return "";
708
+ return files
709
+ .map((f) => {
710
+ const label = f.name?.trim() || f.vaultPath.split("/").pop() || "file";
711
+ const type = f.contentType ? ` ${chalk.dim(f.contentType)}` : "";
712
+ const company = f.companyUid ? chalk.dim(` company=${f.companyUid}`) : "";
713
+ return `\n${indent}${chalk.dim("[file]")} ${label}${type}\n${indent} ${chalk.dim(`vault: ${f.vaultPath}`)}${company}`;
714
+ })
715
+ .join("");
716
+ }
696
717
  /**
697
718
  * Human label for a message sender — display name, else email, else uid. Pure →
698
719
  * unit-testable.
@@ -773,20 +794,20 @@ export function formatInboxEvent(e, nowMs) {
773
794
  const when = chalk.dim(formatRelativeTime(e.createdAt, nowMs));
774
795
  const who = chalk.bold(senderLabel(e));
775
796
  const email = e.fromEmail && e.fromDisplayName ? chalk.dim(` <${e.fromEmail}>`) : "";
776
- return `${marker} ${when} ${who}${email}\n ${firstLine(e.body)}`;
797
+ return `${marker} ${when} ${who}${email}\n ${firstLine(e.body)}${formatAttachments(e)}`;
777
798
  }
778
799
  /** Render one 1:1 thread line, tagged by direction. */
779
800
  export function formatThreadMessage(m, nowMs) {
780
801
  const arrow = m.direction === "out" ? chalk.dim("→") : chalk.cyan("←");
781
802
  const who = m.direction === "out" ? "you" : senderLabel(m);
782
803
  const when = chalk.dim(formatRelativeTime(m.createdAt, nowMs));
783
- return `${arrow} ${chalk.bold(who)} ${when}\n ${firstLine(m.body)}`;
804
+ return `${arrow} ${chalk.bold(who)} ${when}\n ${firstLine(m.body)}${formatAttachments(m)}`;
784
805
  }
785
806
  /** Render one channel/group message line. */
786
807
  export function formatChannelMessage(m, nowMs) {
787
808
  const who = chalk.bold(senderLabel(m));
788
809
  const when = chalk.dim(formatRelativeTime(m.createdAt, nowMs));
789
- return `${who} ${when}\n ${firstLine(m.body)}`;
810
+ return `${who} ${when}\n ${firstLine(m.body)}${formatAttachments(m)}`;
790
811
  }
791
812
  /** POST /v1/notify/inbox/ack — idempotently mark messages read. */
792
813
  async function ackEvents(token, eventIds) {
@@ -3,6 +3,11 @@
3
3
  *
4
4
  * `hq skill create <slug>` registers a canonical company skill, stamps its
5
5
  * immutable UID, surfaces its generated runtime wrapper, and syncs it.
6
+ * `hq skill register <slug>` reserves that UID from a proposal-lane source and
7
+ * writes server-stamped bytes only to `--stamped-output` — never under
8
+ * `companies/<company>/skills/`, and never as a discovery wrapper or sync.
9
+ * `hq skill promote <slug>` copies independently-cleared stamped bytes into the
10
+ * canonical tree only after the expected SHA-256 and registered skill_uid match.
6
11
  * `hq skill propose <uid|path> --message "…"` posts a whole-skill comment to
7
12
  * the same improvement thread shown in HQ Console. It never uploads a modified
8
13
  * SKILL.md and cannot overwrite live content. `hq skill delete <target>` removes
@@ -39,7 +44,21 @@ export declare function makeSkillTemplate(input: {
39
44
  description?: string;
40
45
  }): string;
41
46
  /** Replace SKILL.md without exposing a partially-written identity to agents. */
42
- export declare function writeSkillFileAtomically(filePath: string, content: string): void;
47
+ export declare function writeSkillFileAtomically(filePath: string, content: string | Buffer): void;
48
+ export declare function sha256Hex(data: string | Buffer): string;
49
+ export declare function skillVaultPath(skillSlug: string): string;
50
+ export declare function isInsideCompanySkillsTree(hqRoot: string, candidatePath: string): boolean;
51
+ export declare function resolveSkillMarkdownPath(target: string, cwd: string): string;
52
+ export declare function assertStagedOutputPath(hqRoot: string, outputPath: string): string;
53
+ export declare function assertSha256HexMatch(actualHex: string, expectedHex: string): void;
54
+ export declare function assertRegisteredSkillResponse(registered: {
55
+ skillUid?: unknown;
56
+ content?: unknown;
57
+ }): {
58
+ skillUid: string;
59
+ content: string;
60
+ };
61
+ export declare function normalizeLiveSkillVaultPath(rawPath: string): string;
43
62
  export declare function mapSkillError(status: number, body: Record<string, unknown>): string;
44
63
  /**
45
64
  * Type a failed skills-API response by its HTTP status so the top-level error