@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.
- package/CHANGELOG.md +138 -0
- package/dist/command-catalog.generated.d.ts +59 -7
- package/dist/command-catalog.generated.js +75 -7
- package/dist/commands/agent-inbox.js +2 -2
- package/dist/commands/auth.d.ts +2 -0
- package/dist/commands/auth.js +92 -3
- package/dist/commands/core.js +1 -1
- package/dist/commands/files-browse.d.ts +16 -0
- package/dist/commands/files-browse.js +52 -1
- package/dist/commands/integrations-core.d.ts +13 -1
- package/dist/commands/integrations-core.js +49 -4
- package/dist/commands/integrations.js +4 -2
- package/dist/commands/library-add.d.ts +6 -2
- package/dist/commands/library-add.js +45 -6
- package/dist/commands/library-caption.d.ts +38 -1
- package/dist/commands/library-caption.js +304 -3
- package/dist/commands/library-link.d.ts +7 -3
- package/dist/commands/library-link.js +10 -4
- package/dist/commands/library-remove.d.ts +122 -0
- package/dist/commands/library-remove.js +681 -0
- package/dist/commands/library.d.ts +26 -1
- package/dist/commands/library.js +101 -14
- package/dist/commands/secrets.d.ts +16 -0
- package/dist/commands/secrets.js +144 -8
- package/dist/commands/storage-offload.d.ts +22 -4
- package/dist/commands/storage-offload.js +196 -2
- package/dist/commands/storage.js +5 -0
- package/dist/lib/agent-kit/inbox-state.d.ts +7 -0
- package/dist/lib/agent-kit/inbox-state.js +5 -1
- package/dist/lib/agent-kit/mcp/tools.js +2 -1
- package/dist/lib/daemon/machine-login.d.ts +20 -0
- package/dist/lib/daemon/machine-login.js +53 -8
- package/dist/lib/daemon/machine-token-identity.d.ts +34 -0
- package/dist/lib/daemon/machine-token-identity.js +123 -0
- package/dist/lib/doctor/checks/sync-health.d.ts +9 -0
- package/dist/lib/doctor/checks/sync-health.js +66 -1
- package/dist/lib/doctor/company-resolution-state.d.ts +30 -0
- package/dist/lib/doctor/company-resolution-state.js +89 -0
- package/dist/lib/doctor/fix/apply.d.ts +36 -6
- package/dist/lib/doctor/fix/apply.js +128 -12
- package/dist/lib/doctor/types.d.ts +1 -1
- package/dist/lib/doctor/types.js +1 -0
- package/dist/lib/hook-engine/resolve.js +6 -2
- package/dist/lib/index-render/company-knowledge.d.ts +1 -1
- package/dist/lib/index-render/company-knowledge.js +3 -2
- package/dist/lib/index-render/index.js +44 -3
- package/dist/lib/index-render/projects.d.ts +1 -1
- package/dist/lib/index-render/projects.js +4 -2
- package/dist/lib/lanes/registry-flag.d.ts +23 -2
- package/dist/lib/lanes/registry-flag.js +256 -36
- package/dist/lib/personal-secrets-audit.d.ts +31 -0
- package/dist/lib/personal-secrets-audit.js +45 -0
- package/dist/sentry-before-send.js +1 -0
- package/dist/utils/access-denied-hint.js +6 -0
- package/dist/utils/client-health.d.ts +34 -0
- package/dist/utils/client-health.js +96 -3
- 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
|
|
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 <
|
|
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_…)
|
|
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
|
|
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 <
|
|
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_…)
|
|
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)
|
package/dist/commands/auth.d.ts
CHANGED
|
@@ -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
|
package/dist/commands/auth.js
CHANGED
|
@@ -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 {
|
package/dist/commands/core.js
CHANGED
|
@@ -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 (
|
|
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
|
|
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
|