@indigoai-us/hq-cli 5.345.154 → 5.345.156

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 (57) hide show
  1. package/CHANGELOG.md +138 -0
  2. package/dist/command-catalog.generated.d.ts +59 -7
  3. package/dist/command-catalog.generated.js +75 -7
  4. package/dist/commands/agent-inbox.js +2 -2
  5. package/dist/commands/auth.d.ts +2 -0
  6. package/dist/commands/auth.js +92 -3
  7. package/dist/commands/core.js +1 -1
  8. package/dist/commands/files-browse.d.ts +16 -0
  9. package/dist/commands/files-browse.js +52 -1
  10. package/dist/commands/integrations-core.d.ts +13 -1
  11. package/dist/commands/integrations-core.js +49 -4
  12. package/dist/commands/integrations.js +4 -2
  13. package/dist/commands/library-add.d.ts +6 -2
  14. package/dist/commands/library-add.js +45 -6
  15. package/dist/commands/library-caption.d.ts +38 -1
  16. package/dist/commands/library-caption.js +304 -3
  17. package/dist/commands/library-link.d.ts +7 -3
  18. package/dist/commands/library-link.js +10 -4
  19. package/dist/commands/library-remove.d.ts +122 -0
  20. package/dist/commands/library-remove.js +681 -0
  21. package/dist/commands/library.d.ts +26 -1
  22. package/dist/commands/library.js +101 -14
  23. package/dist/commands/secrets.d.ts +16 -0
  24. package/dist/commands/secrets.js +144 -8
  25. package/dist/commands/storage-offload.d.ts +22 -4
  26. package/dist/commands/storage-offload.js +196 -2
  27. package/dist/commands/storage.js +5 -0
  28. package/dist/lib/agent-kit/inbox-state.d.ts +7 -0
  29. package/dist/lib/agent-kit/inbox-state.js +5 -1
  30. package/dist/lib/agent-kit/mcp/tools.js +2 -1
  31. package/dist/lib/daemon/machine-login.d.ts +20 -0
  32. package/dist/lib/daemon/machine-login.js +53 -8
  33. package/dist/lib/daemon/machine-token-identity.d.ts +34 -0
  34. package/dist/lib/daemon/machine-token-identity.js +123 -0
  35. package/dist/lib/doctor/checks/sync-health.d.ts +9 -0
  36. package/dist/lib/doctor/checks/sync-health.js +66 -1
  37. package/dist/lib/doctor/company-resolution-state.d.ts +30 -0
  38. package/dist/lib/doctor/company-resolution-state.js +89 -0
  39. package/dist/lib/doctor/fix/apply.d.ts +36 -6
  40. package/dist/lib/doctor/fix/apply.js +128 -12
  41. package/dist/lib/doctor/types.d.ts +1 -1
  42. package/dist/lib/doctor/types.js +1 -0
  43. package/dist/lib/hook-engine/resolve.js +6 -2
  44. package/dist/lib/index-render/company-knowledge.d.ts +1 -1
  45. package/dist/lib/index-render/company-knowledge.js +3 -2
  46. package/dist/lib/index-render/index.js +44 -3
  47. package/dist/lib/index-render/projects.d.ts +1 -1
  48. package/dist/lib/index-render/projects.js +4 -2
  49. package/dist/lib/lanes/registry-flag.d.ts +23 -2
  50. package/dist/lib/lanes/registry-flag.js +256 -36
  51. package/dist/lib/personal-secrets-audit.d.ts +31 -0
  52. package/dist/lib/personal-secrets-audit.js +45 -0
  53. package/dist/sentry-before-send.js +1 -0
  54. package/dist/utils/access-denied-hint.js +6 -0
  55. package/dist/utils/client-health.d.ts +34 -0
  56. package/dist/utils/client-health.js +96 -3
  57. package/package.json +7 -7
package/CHANGELOG.md CHANGED
@@ -2,6 +2,144 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [5.345.156] — 2026-10-21
6
+ - `hq doctor --fix` stops pulling a company the sync service could not find
7
+ for your HQ account. When a pull fails because the company does not exist
8
+ for you or access is refused, the next check shows that company's stale
9
+ sync journal as "not on this machine" with one next step: check you are
10
+ signed in to the right account and ask to be re-invited, or delete the old
11
+ journal. It is no longer auto-fixable, so the client-health hook stops
12
+ reporting it. This classification requires another company pull to succeed
13
+ in the same run; an outage affecting every company remains visible. Any
14
+ later successful sync invalidates the cached failure, and `--fix` retries
15
+ unresolved companies after 14 days.
16
+ - The same company's stale sync manifest now follows its journal: it shows as
17
+ "not on this machine" and `--fix` stops re-uploading it. A manifest upload
18
+ that fails with "No membership found" counts as the same evidence, under the
19
+ same rule that another company must succeed in that run. A later successful
20
+ sync or manifest upload clears it.
21
+ - One `hq doctor --fix` run now refreshes up to 10 stale sync journals
22
+ instead of 2. Unattended runs (`--yes`) stop starting new pulls after 120
23
+ seconds so they still finish inside the health hook's time limit. When a
24
+ run stops early it says how many are left and asks you to run it again.
25
+ - `hq files cat` / `get` and the agent file tools now say why a signed vault download failed after access was granted: a response that did not come from the storage host is reported as a likely proxy or egress block naming the host to allow, and a storage refusal shows S3's error code and request id. These failures no longer send you to `hq access`, which only re-checked the grant that had already passed.
26
+ - `hq agent inbox --json` and the agent `hq_inbox_read` tool now include `source`: `server` when the server inbox was read, `local-mirror` when the server could not be reached and the list may be stale, so automation can tell a stale fallback from a real empty inbox.
27
+ - Fix company knowledge and project index rebuilds to honor help and explicit
28
+ company arguments. Invalid arguments fail before writing, and a
29
+ company-scoped project rebuild leaves the personal index untouched.
30
+ - `hq integrations call` has a new `--timeout <seconds>` option (default 270,
31
+ max 1800) that sets how long it waits for a call the gateway runs as a
32
+ background job. When a call that asked for background execution instead
33
+ comes back as an inline HTTP 500, 502 or 504, the error now says the call
34
+ likely hit the gateway's 30-second inline limit and that async integration
35
+ jobs must be enabled for the company, instead of only suggesting a retry.
36
+ Feedback 4b2573d6-fab7-4b63-8fa6-f50b472a887f.
37
+ - Every `hq` command (including `hq secrets exec`) sends a client-health
38
+ heartbeat, and about once every 15 minutes that heartbeat re-reads the local
39
+ sync journals. That read loads the whole local sync state store into memory,
40
+ and on a large store it took the `hq` process itself to several GB. Commands
41
+ that are not sync commands now do this read in a separate child process
42
+ capped at a 512 MB heap and 20 seconds. If the child hits a cap, the command
43
+ keeps the last known sync time and does not retry the read for 15 minutes.
44
+ Sync commands still read the journals in-process.
45
+ Feedback d70e9d63-7b46-42bc-8c28-a545235cd7c6.
46
+ - `hq secrets script approve --remote-path` now accepts a path relative to the
47
+ HQ root (for example `companies/<co>/skills/<skill>/<file>.mjs`). Once
48
+ hq-pro has the matching change deployed, that one approval matches the
49
+ script on every HQ root, including a hosted agent's. For a relative path a
50
+ leading `./` is dropped and `\` becomes `/`. Approve refuses, with a plain
51
+ error and no API call, a relative path that could never match: a bare file
52
+ name with no folder, a `.`, `..` or empty segment, a trailing `/`, a leading
53
+ `~`, a drive letter without a separator, or surrounding whitespace. An
54
+ absolute path is sent exactly as typed and must match exactly. If the
55
+ server answers without an active approval for the id (for example the id was
56
+ revoked earlier), approve now fails with a hint to use a new `--id` instead
57
+ of printing success. The success line names the stored path.
58
+ Feedback 6e0a1040-4d8e-4743-863e-386eb98aa0a4.
59
+ - Installing the native hook engine no longer fails with `ERR_FS_CP_EEXIST` on
60
+ Node 24.22 and later. The engine is now copied into a new directory inside the
61
+ staging folder instead of onto the staging folder itself.
62
+
63
+ ## [5.345.155] — 2026-10-21
64
+ - `hq secrets exec --personal` now works in a session locked to one company.
65
+ It injects your own personal-vault secrets into the command, read-only, and
66
+ appends one line per use to `workspace/audit/personal-secrets-use.jsonl`
67
+ (timestamp, session id, locked company, secret names; never values). If the
68
+ audit line cannot be written, nothing is read. In a company-locked session
69
+ every other personal-vault command (`set`, `get`, `list`, `env`, `delete`,
70
+ `share`, `generate-link`, `sandbox --personal` and the rest) is refused, as
71
+ is personal scope next to more than one locked company, API-key auth, or
72
+ another person's `prs_` scope. A `prs_`/`agt_` value passed as `--company`
73
+ is treated as personal scope and gets the same rules. Owner decision dq_20261020234444_f1b1ea4185;
74
+ feedback 5b6c67f7-59b1-410a-a4ee-b5db27fe7051.
75
+ - Lanes flag reads now retry once on a timeout, network error or 5xx, and refresh the token once on a 401. When the registry is still unreachable (timeout, network error or 5xx), the read falls back to the last value the registry confirmed within the past 24 hours (kept in the HQ state dir) before the hard-coded default, so a brief registry outage no longer turns `monitor.enabled` off. A 401 after the refresh, a 403 or other 4xx, or a missing token never uses that cached value. `flag_read_failed` events now include the HTTP status, a redacted error message, the duration, and whether the cache was used.
76
+ - `hq library add` now works on an `HQ_API_KEY` with upload rights
77
+ (`hq api-keys create --preset library-agent --allow-upload`) and no login.
78
+ `--company` takes the company's slug or its uid (`cmp_…`), and the
79
+ placeholder always goes under `companies/<slug>/library` (or a `--to` folder
80
+ inside `companies/<slug>/`), never a `companies/cmp_…` folder. On a key the
81
+ slug and uid are matched through `companies/manifest.yaml`, because a key
82
+ cannot read memberships. Before this, a key got "Could not load your
83
+ companies (HTTP 404). Run `hq login`." `hq library describe` had the same
84
+ problem and is fixed the same way. `hq library link` on a key now also
85
+ accepts `--company <slug>`, and with no `--company` it uses the
86
+ placeholder's company (`HQ_COMPANY_UID` still wins when set); before, both
87
+ failed and only `--company cmp_…` worked. A key without `files:write` now
88
+ gets "This API key cannot upload (missing files:write)" instead of the login
89
+ message. Signed-in use is unchanged, except that `--company` also accepts
90
+ the uid there.
91
+ - `hq library add` and `hq library describe` now caption a HEIC/HEIF photo from
92
+ its primary image. Before, ffmpeg's default stream was used, which for an
93
+ iPhone HEIC is a single tile of the photo or, for Portrait-mode photos, the
94
+ black-and-white depth/segmentation matte, so captions described a blurry
95
+ fragment or "a segmentation mask". The primary image is decoded with `sips`
96
+ on macOS, then ImageMagick (`magick`, or `convert` outside Windows), then by
97
+ assembling ffmpeg's primary tile grid, and each result must be a valid JPEG
98
+ of at most 1024px before it is used; otherwise the previous ffmpeg behavior
99
+ is kept. HEIC/HEIF is detected by content as well as by a `.heic`, `.heif`
100
+ or `.hif` name in any case. Photos shot upside down or sideways are turned
101
+ upright on the `sips` and ImageMagick paths, and the `sips` frame has its
102
+ EXIF/XMP removed before it is sent for captioning. `.heif` files are now
103
+ uploaded as `image/heif` and captioned. Other image formats and videos are
104
+ unchanged, and captioning still never blocks an upload.
105
+ - `hq library rm <placeholder-or-original> [more...]` (alias `remove`) removes
106
+ company library assets. It deletes the local original if one was fetched
107
+ (only when its sha256 matches the library copy), then the local placeholder,
108
+ then the placeholder's vault copy, in that order so sync cannot bring the
109
+ placeholder back midway. It previews and asks first; `--yes` skips the
110
+ question, `--dry-run` changes nothing, and `--json` prints the result as
111
+ JSON. The shared blob `.hq-offload/blobs/<sha256>` is kept by default,
112
+ because other placeholders (including teammates') may use the same bytes.
113
+ `--purge` also deletes the blob, and is refused if any other placeholder in
114
+ the local tree or the company vault points at it. The vault check lists
115
+ every placeholder you can see, so it can take a few minutes on a large
116
+ vault. Personal placeholders, plain offload placeholders and placeholders
117
+ keyed to a synced path are refused. An `HQ_API_KEY` needs files:write (and
118
+ files:read for `--purge`); it is refused before any local file is touched.
119
+ The vault is versioned, so `hq files restore <key>` undoes the vault side.
120
+ - `hq auth machine-session --output -` prints a machine identity session to a
121
+ pipe instead of a file, for example
122
+ `hq auth machine-session --output - --field refreshToken | gh secret set NAME`.
123
+ It signs in fresh in memory and writes no token cache, lock, or temp file
124
+ (the CLI may still write its usual non-secret state files). Before printing,
125
+ it checks the minted tokens against the machine identity the same way
126
+ hq-cloud does for every machine mint (client, subject, entity type and uid,
127
+ and no company-outpost claims on a non-company identity), and prints nothing
128
+ if they do not match. Without `--field` it prints the same JSON as the file
129
+ mode (`accessToken`, `idToken`, `expiresAt`) followed by a newline.
130
+ `--field <name>` prints one value raw with no trailing newline;
131
+ `refreshToken` is available only this way. A refresh token outlives the
132
+ machine secret: revoke a leaked one with a Cognito global sign-out for the
133
+ machine user or `RevokeToken`. The creds file is `HQ_MACHINE_CREDS_FILE`,
134
+ else `~/.hq-agent/machine-creds.json`; `HQ_AGENT_DIR` does not select it.
135
+ It refuses to print to a terminal unless `--force-tty` is given, and
136
+ supports hosted agent and outpost identities (not external or
137
+ company-outpost ones). `--output <path>` is unchanged. Behavior change:
138
+ `--output -` used to write a file literally named `-` in the current
139
+ directory; it now means stdout. To write a file with that name, use
140
+ `--output ./-`.
141
+ - `hq storage offload --current` can turn a library asset restored with `hq library fetch` back into a placeholder. It used to skip the file with "run hq sync first", because fetch never records the file in the sync journal. Offload now checks the company vault for the asset's blob at `.hq-offload/blobs/<sha256>`, which is the placeholder's own key, so nothing is uploaded. Before removing the local file it confirms that the blob's size and recorded sha256 match the file, and it hashes the file again. When the library placeholder sits beside the file, offload leaves the placeholder as is. When fetch removed it, offload writes back the vault's own placeholder for that path (keeping its tags and caption), or a plain library pointer when the vault no longer has one. Files the sync journal knows, and placeholders keyed by the file's own path, work as before. A file whose bytes differ from the library copy is kept, with the reason shown.
142
+
5
143
  ## [5.345.154] — 2026-10-21
6
144
  - `hq sync status` now lists local files that sit under a vault prefix you can
7
145
  only read and that were never synced. Sync will never upload these files, for
@@ -1764,17 +1764,28 @@ export declare const COMMAND_CATALOG: readonly [{
1764
1764
  readonly options: readonly [];
1765
1765
  readonly subcommands: readonly [{
1766
1766
  readonly name: "machine-session";
1767
- readonly description: "Write the existing machine identity session to a private file";
1767
+ readonly description: "Write the existing machine identity session to a private file, or to a pipe with --output -";
1768
1768
  readonly aliases: readonly [];
1769
1769
  readonly hidden: false;
1770
1770
  readonly helpOption: true;
1771
1771
  readonly helpCommand: false;
1772
1772
  readonly usage: "[options]";
1773
- readonly helpText: readonly [];
1773
+ readonly helpText: readonly [{
1774
+ readonly position: "after";
1775
+ readonly text: "\nStdout format (--output -):\n no --field one JSON line {\"accessToken\",\"idToken\",\"expiresAt\"} then a newline\n --field X the raw value of X with no trailing newline\n\nCreds come from HQ_MACHINE_CREDS_FILE, else ~/.hq-agent/machine-creds.json.\nHQ_AGENT_DIR does not select them; set HQ_MACHINE_CREDS_FILE on hosts with several agents.\n\n--field refreshToken prints a long-lived Cognito refresh token. Rotating the machine\nsecret does not revoke it; revoke a leaked one with a Cognito global sign-out for the\nmachine user (AdminUserGlobalSignOut) or RevokeToken.";
1776
+ }];
1774
1777
  readonly arguments: readonly [];
1775
1778
  readonly options: readonly [{
1776
1779
  readonly flags: "--output <path>";
1777
- readonly description: "path for the private machine session JSON";
1780
+ readonly description: "path for the private machine session JSON, or - for stdout (pipe only). Creds come from HQ_MACHINE_CREDS_FILE, else ~/.hq-agent/machine-creds.json";
1781
+ readonly hidden: false;
1782
+ }, {
1783
+ readonly flags: "--field <name>";
1784
+ readonly description: "with --output -, print one field raw with no trailing newline (accessToken, idToken, expiresAt, refreshToken)";
1785
+ readonly hidden: false;
1786
+ }, {
1787
+ readonly flags: "--force-tty";
1788
+ readonly description: "with --output -, allow printing to a terminal";
1778
1789
  readonly hidden: false;
1779
1790
  }];
1780
1791
  readonly subcommands: readonly [];
@@ -2055,7 +2066,7 @@ export declare const COMMAND_CATALOG: readonly [{
2055
2066
  readonly hidden: false;
2056
2067
  }, {
2057
2068
  readonly flags: "--remote-path <path>";
2058
- readonly description: "Script path reported by the target runtime (defaults to the local path)";
2069
+ readonly description: "Script path to approve instead of the local path. A path relative to the HQ root (for example companies/<co>/skills/<skill>/<file>) matches on every HQ root; an absolute path must match the runtime's path exactly";
2059
2070
  readonly hidden: false;
2060
2071
  }, {
2061
2072
  readonly flags: "--attestation <level>";
@@ -5064,6 +5075,10 @@ export declare const COMMAND_CATALOG: readonly [{
5064
5075
  readonly flags: "--idempotency-key <key>";
5065
5076
  readonly description: "Stable key so retries execute at most once (default: generated)";
5066
5077
  readonly hidden: false;
5078
+ }, {
5079
+ readonly flags: "--timeout <seconds>";
5080
+ readonly description: "How long to wait for a long call that runs as a background job (default 270, max 1800)";
5081
+ readonly hidden: false;
5067
5082
  }, {
5068
5083
  readonly flags: "--json";
5069
5084
  readonly description: "Machine-readable output";
@@ -10564,8 +10579,8 @@ export declare const COMMAND_CATALOG: readonly [{
10564
10579
  readonly description: "Asset description";
10565
10580
  readonly hidden: false;
10566
10581
  }, {
10567
- readonly flags: "--company <slug>";
10568
- readonly description: "Company whose vault receives the files";
10582
+ readonly flags: "--company <company>";
10583
+ readonly description: "Company slug or uid (cmp_…) whose vault receives the files";
10569
10584
  readonly hidden: false;
10570
10585
  }, {
10571
10586
  readonly flags: "--no-caption";
@@ -10693,7 +10708,7 @@ export declare const COMMAND_CATALOG: readonly [{
10693
10708
  }];
10694
10709
  readonly options: readonly [{
10695
10710
  readonly flags: "--company <company>";
10696
- readonly description: "Company uid (cmp_…), or a slug on a session; default: the placeholder's company or the key's";
10711
+ readonly description: "Company slug or uid (cmp_…); default: the placeholder's company or the key's";
10697
10712
  readonly hidden: false;
10698
10713
  }, {
10699
10714
  readonly flags: "--expires <minutes>";
@@ -10705,6 +10720,43 @@ export declare const COMMAND_CATALOG: readonly [{
10705
10720
  readonly hidden: false;
10706
10721
  }];
10707
10722
  readonly subcommands: readonly [];
10723
+ }, {
10724
+ readonly name: "rm";
10725
+ readonly description: "Remove library assets: the local original (if fetched), the placeholder, and its vault copy. Keeps the shared blob unless --purge. Previews and asks first unless --yes";
10726
+ readonly aliases: readonly ["remove"];
10727
+ readonly hidden: false;
10728
+ readonly helpOption: true;
10729
+ readonly helpCommand: false;
10730
+ readonly usage: "[options] <targets...>";
10731
+ readonly helpText: readonly [];
10732
+ readonly arguments: readonly [{
10733
+ readonly name: "targets";
10734
+ readonly required: true;
10735
+ readonly variadic: true;
10736
+ readonly description: "Library .hqcloud placeholders (or their original paths)";
10737
+ }];
10738
+ readonly options: readonly [{
10739
+ readonly flags: "--company <company>";
10740
+ readonly description: "Company slug (or cmp_ uid); default: the targets' companies/<slug>/";
10741
+ readonly hidden: false;
10742
+ }, {
10743
+ readonly flags: "--purge";
10744
+ readonly description: "Also delete the blob, after checking no other placeholder (local or in the vault) uses it";
10745
+ readonly hidden: false;
10746
+ }, {
10747
+ readonly flags: "--dry-run";
10748
+ readonly description: "Show what would be removed without changing anything";
10749
+ readonly hidden: false;
10750
+ }, {
10751
+ readonly flags: "-y, --yes";
10752
+ readonly description: "Skip the confirmation prompt";
10753
+ readonly hidden: false;
10754
+ }, {
10755
+ readonly flags: "--json";
10756
+ readonly description: "Print machine-readable JSON (the preview goes to stderr)";
10757
+ readonly hidden: false;
10758
+ }];
10759
+ readonly subcommands: readonly [];
10708
10760
  }];
10709
10761
  }, {
10710
10762
  readonly name: "index";
@@ -2117,18 +2117,33 @@ export const COMMAND_CATALOG = [
2117
2117
  "subcommands": [
2118
2118
  {
2119
2119
  "name": "machine-session",
2120
- "description": "Write the existing machine identity session to a private file",
2120
+ "description": "Write the existing machine identity session to a private file, or to a pipe with --output -",
2121
2121
  "aliases": [],
2122
2122
  "hidden": false,
2123
2123
  "helpOption": true,
2124
2124
  "helpCommand": false,
2125
2125
  "usage": "[options]",
2126
- "helpText": [],
2126
+ "helpText": [
2127
+ {
2128
+ "position": "after",
2129
+ "text": "\nStdout format (--output -):\n no --field one JSON line {\"accessToken\",\"idToken\",\"expiresAt\"} then a newline\n --field X the raw value of X with no trailing newline\n\nCreds come from HQ_MACHINE_CREDS_FILE, else ~/.hq-agent/machine-creds.json.\nHQ_AGENT_DIR does not select them; set HQ_MACHINE_CREDS_FILE on hosts with several agents.\n\n--field refreshToken prints a long-lived Cognito refresh token. Rotating the machine\nsecret does not revoke it; revoke a leaked one with a Cognito global sign-out for the\nmachine user (AdminUserGlobalSignOut) or RevokeToken."
2130
+ }
2131
+ ],
2127
2132
  "arguments": [],
2128
2133
  "options": [
2129
2134
  {
2130
2135
  "flags": "--output <path>",
2131
- "description": "path for the private machine session JSON",
2136
+ "description": "path for the private machine session JSON, or - for stdout (pipe only). Creds come from HQ_MACHINE_CREDS_FILE, else ~/.hq-agent/machine-creds.json",
2137
+ "hidden": false
2138
+ },
2139
+ {
2140
+ "flags": "--field <name>",
2141
+ "description": "with --output -, print one field raw with no trailing newline (accessToken, idToken, expiresAt, refreshToken)",
2142
+ "hidden": false
2143
+ },
2144
+ {
2145
+ "flags": "--force-tty",
2146
+ "description": "with --output -, allow printing to a terminal",
2132
2147
  "hidden": false
2133
2148
  }
2134
2149
  ],
@@ -2466,7 +2481,7 @@ export const COMMAND_CATALOG = [
2466
2481
  },
2467
2482
  {
2468
2483
  "flags": "--remote-path <path>",
2469
- "description": "Script path reported by the target runtime (defaults to the local path)",
2484
+ "description": "Script path to approve instead of the local path. A path relative to the HQ root (for example companies/<co>/skills/<skill>/<file>) matches on every HQ root; an absolute path must match the runtime's path exactly",
2470
2485
  "hidden": false
2471
2486
  },
2472
2487
  {
@@ -6130,6 +6145,11 @@ export const COMMAND_CATALOG = [
6130
6145
  "description": "Stable key so retries execute at most once (default: generated)",
6131
6146
  "hidden": false
6132
6147
  },
6148
+ {
6149
+ "flags": "--timeout <seconds>",
6150
+ "description": "How long to wait for a long call that runs as a background job (default 270, max 1800)",
6151
+ "hidden": false
6152
+ },
6133
6153
  {
6134
6154
  "flags": "--json",
6135
6155
  "description": "Machine-readable output",
@@ -12811,8 +12831,8 @@ export const COMMAND_CATALOG = [
12811
12831
  "hidden": false
12812
12832
  },
12813
12833
  {
12814
- "flags": "--company <slug>",
12815
- "description": "Company whose vault receives the files",
12834
+ "flags": "--company <company>",
12835
+ "description": "Company slug or uid (cmp_…) whose vault receives the files",
12816
12836
  "hidden": false
12817
12837
  },
12818
12838
  {
@@ -12970,7 +12990,7 @@ export const COMMAND_CATALOG = [
12970
12990
  "options": [
12971
12991
  {
12972
12992
  "flags": "--company <company>",
12973
- "description": "Company uid (cmp_…), or a slug on a session; default: the placeholder's company or the key's",
12993
+ "description": "Company slug or uid (cmp_…); default: the placeholder's company or the key's",
12974
12994
  "hidden": false
12975
12995
  },
12976
12996
  {
@@ -12985,6 +13005,54 @@ export const COMMAND_CATALOG = [
12985
13005
  }
12986
13006
  ],
12987
13007
  "subcommands": []
13008
+ },
13009
+ {
13010
+ "name": "rm",
13011
+ "description": "Remove library assets: the local original (if fetched), the placeholder, and its vault copy. Keeps the shared blob unless --purge. Previews and asks first unless --yes",
13012
+ "aliases": [
13013
+ "remove"
13014
+ ],
13015
+ "hidden": false,
13016
+ "helpOption": true,
13017
+ "helpCommand": false,
13018
+ "usage": "[options] <targets...>",
13019
+ "helpText": [],
13020
+ "arguments": [
13021
+ {
13022
+ "name": "targets",
13023
+ "required": true,
13024
+ "variadic": true,
13025
+ "description": "Library .hqcloud placeholders (or their original paths)"
13026
+ }
13027
+ ],
13028
+ "options": [
13029
+ {
13030
+ "flags": "--company <company>",
13031
+ "description": "Company slug (or cmp_ uid); default: the targets' companies/<slug>/",
13032
+ "hidden": false
13033
+ },
13034
+ {
13035
+ "flags": "--purge",
13036
+ "description": "Also delete the blob, after checking no other placeholder (local or in the vault) uses it",
13037
+ "hidden": false
13038
+ },
13039
+ {
13040
+ "flags": "--dry-run",
13041
+ "description": "Show what would be removed without changing anything",
13042
+ "hidden": false
13043
+ },
13044
+ {
13045
+ "flags": "-y, --yes",
13046
+ "description": "Skip the confirmation prompt",
13047
+ "hidden": false
13048
+ },
13049
+ {
13050
+ "flags": "--json",
13051
+ "description": "Print machine-readable JSON (the preview goes to stderr)",
13052
+ "hidden": false
13053
+ }
13054
+ ],
13055
+ "subcommands": []
12988
13056
  }
12989
13057
  ]
12990
13058
  },
@@ -26,7 +26,7 @@ export function registerAgentInboxCommand(agent) {
26
26
  // Merged with the server, never the local mirror alone: a dead kit
27
27
  // poller would otherwise make this command answer confidently with
28
28
  // yesterday's mail and no sign anything was wrong.
29
- const { entries, note } = await mergeAgentInbox(paths, async () => {
29
+ const { entries, note, source } = await mergeAgentInbox(paths, async () => {
30
30
  const token = await ensureCognitoToken({ tokenSource: "machine", interactive: false });
31
31
  const res = await vaultApiFetch({
32
32
  token,
@@ -39,7 +39,7 @@ export function registerAgentInboxCommand(agent) {
39
39
  .filter((e) => opts.all || !done.has(e.id))
40
40
  .map((e) => summarizeInboxEntry(redactAgentInboxItem(e), done.has(e.id)));
41
41
  if (opts.json) {
42
- console.log(JSON.stringify({ count: rows.length, messages: rows, ...(note ? { note } : {}) }, null, 2));
42
+ console.log(JSON.stringify({ count: rows.length, source, messages: rows, ...(note ? { note } : {}) }, null, 2));
43
43
  return;
44
44
  }
45
45
  if (note)
@@ -7,6 +7,8 @@
7
7
  * hq auth refresh — ensure a valid cached Cognito session (non-interactive)
8
8
  * hq auth status — show whether a valid session is cached + expiry
9
9
  * hq auth machine-session --output <path> — write machine tokens to a private file
10
+ * hq auth machine-session --output - [--field <name>] — print a fresh machine
11
+ * session to a pipe; nothing is written to disk
10
12
  *
11
13
  * Sign-up is owned by the onboarding web app at
12
14
  * https://onboarding.hq.computer. `hq auth login` signs an existing account
@@ -7,6 +7,8 @@
7
7
  * hq auth refresh — ensure a valid cached Cognito session (non-interactive)
8
8
  * hq auth status — show whether a valid session is cached + expiry
9
9
  * hq auth machine-session --output <path> — write machine tokens to a private file
10
+ * hq auth machine-session --output - [--field <name>] — print a fresh machine
11
+ * session to a pipe; nothing is written to disk
10
12
  *
11
13
  * Sign-up is owned by the onboarding web app at
12
14
  * https://onboarding.hq.computer. `hq auth login` signs an existing account
@@ -21,7 +23,8 @@ import * as fs from "node:fs";
21
23
  import { open, realpath } from "node:fs/promises";
22
24
  import path from "node:path";
23
25
  import chalk from "chalk";
24
- import { clearCachedTokens, loadCachedTokens, isExpiring, loadMachineCreds, CognitoAuthError, } from "@indigoai-us/hq-cloud";
26
+ import { clearCachedTokens, loadCachedTokens, isExpiring, loadMachineCreds, isExternalMachineCreds, CognitoAuthError, } from "@indigoai-us/hq-cloud";
27
+ import { authenticateMachineSession, MachineLoginError } from "../lib/daemon/machine-login.js";
25
28
  import { DEFAULT_COGNITO, refreshCachedSession, resolveCognitoTokenSource, ensureMachineSessionTokens, personTokenCacheFile, machineTokenCacheFile, loadMachineCachedTokens, } from "../utils/cognito-session.js";
26
29
  // Static (node builtins only): a lazy import here would need a resident
27
30
  // preload entry for the auth-login path.
@@ -134,15 +137,101 @@ function machineIdentityLabel() {
134
137
  const creds = loadMachineCreds();
135
138
  return creds ? `machine identity ${creds.username}` : "machine identity";
136
139
  }
140
+ const MACHINE_SESSION_FIELDS = ["accessToken", "idToken", "expiresAt", "refreshToken"];
141
+ /**
142
+ * `hq auth machine-session --output -`: mint a fresh machine session in memory
143
+ * and write it to stdout. No token cache, lock, or temp file is written. The
144
+ * full JSON has the same shape as the file mode; the refresh token is printed
145
+ * only when asked for with `--field refreshToken`.
146
+ */
147
+ async function writeMachineSessionToStdout(options) {
148
+ const fail = (message) => {
149
+ console.error(message);
150
+ process.exitCode = 1;
151
+ };
152
+ const field = options.field;
153
+ if (field !== undefined && !MACHINE_SESSION_FIELDS.includes(field)) {
154
+ fail(`Unknown --field. Use one of: ${MACHINE_SESSION_FIELDS.join(", ")}.`);
155
+ return;
156
+ }
157
+ if (process.stdout.isTTY && !options.forceTty) {
158
+ fail("Refusing to print a machine session to a terminal. Pipe it to a command, or pass --force-tty.");
159
+ return;
160
+ }
161
+ const creds = loadMachineCreds();
162
+ if (!creds) {
163
+ fail("Machine credentials are unavailable.");
164
+ return;
165
+ }
166
+ if (isExternalMachineCreds(creds) || creds.entityType === "company-outpost") {
167
+ fail("--output - supports hosted agent and outpost machine identities only.");
168
+ return;
169
+ }
170
+ let tokens;
171
+ try {
172
+ tokens = await authenticateMachineSession(creds);
173
+ }
174
+ catch (error) {
175
+ // The identity check runs on the token claims, so a creds file with no or
176
+ // a wrong entityType still cannot print a company-scoped or foreign session.
177
+ fail(error instanceof MachineLoginError && error.code === "identity_mismatch"
178
+ ? "Refusing to print a machine session: the minted tokens do not match this machine identity."
179
+ : "Could not obtain machine session.");
180
+ return;
181
+ }
182
+ let contents;
183
+ if (field === undefined) {
184
+ contents = JSON.stringify({
185
+ accessToken: tokens.accessToken,
186
+ idToken: tokens.idToken,
187
+ expiresAt: tokens.expiresAt,
188
+ }) + "\n";
189
+ }
190
+ else {
191
+ const value = tokens[field];
192
+ if (value === undefined || value === "") {
193
+ fail(`The machine sign-in did not return ${field}.`);
194
+ return;
195
+ }
196
+ contents = String(value);
197
+ }
198
+ await new Promise((resolve, reject) => {
199
+ process.stdout.write(contents, (error) => (error ? reject(error) : resolve()));
200
+ }).catch(() => fail("Could not write machine session to stdout."));
201
+ }
137
202
  export function registerAuthCommands(program) {
138
203
  const authCmd = program
139
204
  .command("auth")
140
205
  .description("Manage the local HQ Cognito session");
141
206
  authCmd
142
207
  .command("machine-session")
143
- .description("Write the existing machine identity session to a private file")
144
- .requiredOption("--output <path>", "path for the private machine session JSON")
208
+ .description("Write the existing machine identity session to a private file, or to a pipe with --output -")
209
+ .requiredOption("--output <path>", "path for the private machine session JSON, or - for stdout (pipe only). Creds come from HQ_MACHINE_CREDS_FILE, else ~/.hq-agent/machine-creds.json")
210
+ .option("--field <name>", `with --output -, print one field raw with no trailing newline (${MACHINE_SESSION_FIELDS.join(", ")})`)
211
+ .option("--force-tty", "with --output -, allow printing to a terminal")
212
+ .addHelpText("after", [
213
+ "",
214
+ "Stdout format (--output -):",
215
+ ' no --field one JSON line {"accessToken","idToken","expiresAt"} then a newline',
216
+ " --field X the raw value of X with no trailing newline",
217
+ "",
218
+ "Creds come from HQ_MACHINE_CREDS_FILE, else ~/.hq-agent/machine-creds.json.",
219
+ "HQ_AGENT_DIR does not select them; set HQ_MACHINE_CREDS_FILE on hosts with several agents.",
220
+ "",
221
+ "--field refreshToken prints a long-lived Cognito refresh token. Rotating the machine",
222
+ "secret does not revoke it; revoke a leaked one with a Cognito global sign-out for the",
223
+ "machine user (AdminUserGlobalSignOut) or RevokeToken.",
224
+ ].join("\n"))
145
225
  .action(async (options) => {
226
+ if (options.output === "-") {
227
+ await writeMachineSessionToStdout(options);
228
+ return;
229
+ }
230
+ if (options.field !== undefined || options.forceTty) {
231
+ console.error("--field and --force-tty require --output -.");
232
+ process.exitCode = 1;
233
+ return;
234
+ }
146
235
  const requestedPath = path.resolve(options.output);
147
236
  let handle;
148
237
  try {
@@ -663,7 +663,7 @@ export function registerCoreCommands(program) {
663
663
  const rendererArgs = passthrough.filter((arg) => arg !== "--accept");
664
664
  const isCompaniesTarget = entry.renderer === "companies";
665
665
  const wantsHelp = rendererArgs.some((arg) => arg === "--help" || arg === "-h");
666
- if (isCompaniesTarget && wantsHelp) {
666
+ if (wantsHelp && ["companies", "company-knowledge", "projects"].includes(entry.renderer)) {
667
667
  renderIndexTarget(entry.renderer, { root: "", output: (message) => process.stdout.write(message) }, rendererArgs);
668
668
  return;
669
669
  }
@@ -443,6 +443,22 @@ export declare const PRESIGN_BUFFER_MAX_BYTES: number;
443
443
  * access token + companyUid are captured here; the orchestrator just calls
444
444
  * `send(...)` as if it held an S3 client.
445
445
  */
446
+ /** Error code for a presigned storage download that failed after the API authorized it. */
447
+ export declare const STORAGE_DOWNLOAD_FAILED_CODE = "FILES_STORAGE_DOWNLOAD_FAILED";
448
+ /**
449
+ * Build the error for a failed presigned download. The API already checked the
450
+ * caller's ACL before it signed the URL, so a failure here is a storage or
451
+ * network outcome, not a missing grant. The error carries no `status: 403` and
452
+ * a code that `isAccessDeniedError` treats as not an access denial, so the CLI
453
+ * does not send the caller to `hq access` (which only re-checks the ACL that
454
+ * already passed).
455
+ *
456
+ * A response that really came from S3 carries `x-amz-request-id`; we report
457
+ * S3's error code from the XML body. A response without it came from something
458
+ * between this machine and the storage host, usually a sandbox egress proxy or
459
+ * firewall that does not allow the bucket host, and we say so.
460
+ */
461
+ export declare function presignedDownloadError(key: string, url: string, dl: Response): Promise<Error>;
446
462
  export declare function createCompanyPresignClient(input: {
447
463
  token: string;
448
464
  companyUid: string;
@@ -709,6 +709,57 @@ export const PRESIGN_BUFFER_MAX_BYTES = 32 * 1024 * 1024;
709
709
  * access token + companyUid are captured here; the orchestrator just calls
710
710
  * `send(...)` as if it held an S3 client.
711
711
  */
712
+ /** Error code for a presigned storage download that failed after the API authorized it. */
713
+ export const STORAGE_DOWNLOAD_FAILED_CODE = "FILES_STORAGE_DOWNLOAD_FAILED";
714
+ /**
715
+ * Build the error for a failed presigned download. The API already checked the
716
+ * caller's ACL before it signed the URL, so a failure here is a storage or
717
+ * network outcome, not a missing grant. The error carries no `status: 403` and
718
+ * a code that `isAccessDeniedError` treats as not an access denial, so the CLI
719
+ * does not send the caller to `hq access` (which only re-checks the ACL that
720
+ * already passed).
721
+ *
722
+ * A response that really came from S3 carries `x-amz-request-id`; we report
723
+ * S3's error code from the XML body. A response without it came from something
724
+ * between this machine and the storage host, usually a sandbox egress proxy or
725
+ * firewall that does not allow the bucket host, and we say so.
726
+ */
727
+ export async function presignedDownloadError(key, url, dl) {
728
+ let host = "the storage host";
729
+ try {
730
+ host = new URL(url).host;
731
+ }
732
+ catch {
733
+ // keep the generic label
734
+ }
735
+ const requestId = dl.headers.get("x-amz-request-id");
736
+ const bodyText = (await dl.text().catch(() => "")).slice(0, 4096);
737
+ const fromStorage = requestId !== null && requestId.length > 0;
738
+ let message;
739
+ let storageCode;
740
+ if (fromStorage) {
741
+ storageCode = /<Code>([^<]{1,100})<\/Code>/.exec(bodyText)?.[1];
742
+ message =
743
+ `Failed to download '${key}' (HTTP ${dl.status}` +
744
+ (storageCode ? `, storage error ${storageCode}` : "") +
745
+ `, request id ${requestId}). Your file access was granted; the storage ` +
746
+ `service refused the signed download. Report this with: hq feedback bug`;
747
+ }
748
+ else {
749
+ message =
750
+ `Failed to download '${key}' (HTTP ${dl.status}). Your file access was ` +
751
+ `granted, but the response did not come from ${host}. A proxy or network ` +
752
+ `egress rule on this machine likely blocked it. Allow outbound HTTPS to ` +
753
+ `${host} (HQ vault files are served from *.s3.us-east-1.amazonaws.com).`;
754
+ }
755
+ return Object.assign(new Error(message), {
756
+ code: STORAGE_DOWNLOAD_FAILED_CODE,
757
+ key,
758
+ httpStatus: dl.status,
759
+ fromStorage,
760
+ ...(storageCode ? { storageCode } : {}),
761
+ });
762
+ }
712
763
  export function createCompanyPresignClient(input) {
713
764
  const { token, companyUid, companySlug, baseUrl } = input;
714
765
  const denied = (message, key, extra) => Object.assign(new Error(message), {
@@ -780,7 +831,7 @@ export function createCompanyPresignClient(input) {
780
831
  }
781
832
  const dl = await fetch(first.url, { signal: AbortSignal.timeout(120_000) });
782
833
  if (!dl.ok) {
783
- throw new Error(`Failed to download '${key}' (HTTP ${dl.status})`);
834
+ throw await presignedDownloadError(key, first.url, dl);
784
835
  }
785
836
  // HQ-CLI-5: when the response declares a length at or below the ceiling,
786
837
  // DRAIN it here — read the whole body before send() resolves and replay it