@indigoai-us/hq-cli 5.345.153 → 5.345.155
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 +88 -0
- package/dist/command-catalog.generated.d.ts +54 -6
- package/dist/command-catalog.generated.js +69 -6
- package/dist/commands/auth.d.ts +2 -0
- package/dist/commands/auth.js +92 -3
- package/dist/commands/cloud.d.ts +17 -0
- package/dist/commands/cloud.js +48 -1
- package/dist/commands/lanes.js +7 -1
- 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.js +72 -5
- 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/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/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/lib/sync/read-only-local-only.d.ts +45 -0
- package/dist/lib/sync/read-only-local-only.js +122 -0
- package/package.json +7 -7
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,94 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [5.345.155] — 2026-10-21
|
|
6
|
+
- `hq secrets exec --personal` now works in a session locked to one company.
|
|
7
|
+
It injects your own personal-vault secrets into the command, read-only, and
|
|
8
|
+
appends one line per use to `workspace/audit/personal-secrets-use.jsonl`
|
|
9
|
+
(timestamp, session id, locked company, secret names; never values). If the
|
|
10
|
+
audit line cannot be written, nothing is read. In a company-locked session
|
|
11
|
+
every other personal-vault command (`set`, `get`, `list`, `env`, `delete`,
|
|
12
|
+
`share`, `generate-link`, `sandbox --personal` and the rest) is refused, as
|
|
13
|
+
is personal scope next to more than one locked company, API-key auth, or
|
|
14
|
+
another person's `prs_` scope. A `prs_`/`agt_` value passed as `--company`
|
|
15
|
+
is treated as personal scope and gets the same rules. Owner decision dq_20261020234444_f1b1ea4185;
|
|
16
|
+
feedback 5b6c67f7-59b1-410a-a4ee-b5db27fe7051.
|
|
17
|
+
- 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.
|
|
18
|
+
- `hq library add` now works on an `HQ_API_KEY` with upload rights
|
|
19
|
+
(`hq api-keys create --preset library-agent --allow-upload`) and no login.
|
|
20
|
+
`--company` takes the company's slug or its uid (`cmp_…`), and the
|
|
21
|
+
placeholder always goes under `companies/<slug>/library` (or a `--to` folder
|
|
22
|
+
inside `companies/<slug>/`), never a `companies/cmp_…` folder. On a key the
|
|
23
|
+
slug and uid are matched through `companies/manifest.yaml`, because a key
|
|
24
|
+
cannot read memberships. Before this, a key got "Could not load your
|
|
25
|
+
companies (HTTP 404). Run `hq login`." `hq library describe` had the same
|
|
26
|
+
problem and is fixed the same way. `hq library link` on a key now also
|
|
27
|
+
accepts `--company <slug>`, and with no `--company` it uses the
|
|
28
|
+
placeholder's company (`HQ_COMPANY_UID` still wins when set); before, both
|
|
29
|
+
failed and only `--company cmp_…` worked. A key without `files:write` now
|
|
30
|
+
gets "This API key cannot upload (missing files:write)" instead of the login
|
|
31
|
+
message. Signed-in use is unchanged, except that `--company` also accepts
|
|
32
|
+
the uid there.
|
|
33
|
+
- `hq library add` and `hq library describe` now caption a HEIC/HEIF photo from
|
|
34
|
+
its primary image. Before, ffmpeg's default stream was used, which for an
|
|
35
|
+
iPhone HEIC is a single tile of the photo or, for Portrait-mode photos, the
|
|
36
|
+
black-and-white depth/segmentation matte, so captions described a blurry
|
|
37
|
+
fragment or "a segmentation mask". The primary image is decoded with `sips`
|
|
38
|
+
on macOS, then ImageMagick (`magick`, or `convert` outside Windows), then by
|
|
39
|
+
assembling ffmpeg's primary tile grid, and each result must be a valid JPEG
|
|
40
|
+
of at most 1024px before it is used; otherwise the previous ffmpeg behavior
|
|
41
|
+
is kept. HEIC/HEIF is detected by content as well as by a `.heic`, `.heif`
|
|
42
|
+
or `.hif` name in any case. Photos shot upside down or sideways are turned
|
|
43
|
+
upright on the `sips` and ImageMagick paths, and the `sips` frame has its
|
|
44
|
+
EXIF/XMP removed before it is sent for captioning. `.heif` files are now
|
|
45
|
+
uploaded as `image/heif` and captioned. Other image formats and videos are
|
|
46
|
+
unchanged, and captioning still never blocks an upload.
|
|
47
|
+
- `hq library rm <placeholder-or-original> [more...]` (alias `remove`) removes
|
|
48
|
+
company library assets. It deletes the local original if one was fetched
|
|
49
|
+
(only when its sha256 matches the library copy), then the local placeholder,
|
|
50
|
+
then the placeholder's vault copy, in that order so sync cannot bring the
|
|
51
|
+
placeholder back midway. It previews and asks first; `--yes` skips the
|
|
52
|
+
question, `--dry-run` changes nothing, and `--json` prints the result as
|
|
53
|
+
JSON. The shared blob `.hq-offload/blobs/<sha256>` is kept by default,
|
|
54
|
+
because other placeholders (including teammates') may use the same bytes.
|
|
55
|
+
`--purge` also deletes the blob, and is refused if any other placeholder in
|
|
56
|
+
the local tree or the company vault points at it. The vault check lists
|
|
57
|
+
every placeholder you can see, so it can take a few minutes on a large
|
|
58
|
+
vault. Personal placeholders, plain offload placeholders and placeholders
|
|
59
|
+
keyed to a synced path are refused. An `HQ_API_KEY` needs files:write (and
|
|
60
|
+
files:read for `--purge`); it is refused before any local file is touched.
|
|
61
|
+
The vault is versioned, so `hq files restore <key>` undoes the vault side.
|
|
62
|
+
- `hq auth machine-session --output -` prints a machine identity session to a
|
|
63
|
+
pipe instead of a file, for example
|
|
64
|
+
`hq auth machine-session --output - --field refreshToken | gh secret set NAME`.
|
|
65
|
+
It signs in fresh in memory and writes no token cache, lock, or temp file
|
|
66
|
+
(the CLI may still write its usual non-secret state files). Before printing,
|
|
67
|
+
it checks the minted tokens against the machine identity the same way
|
|
68
|
+
hq-cloud does for every machine mint (client, subject, entity type and uid,
|
|
69
|
+
and no company-outpost claims on a non-company identity), and prints nothing
|
|
70
|
+
if they do not match. Without `--field` it prints the same JSON as the file
|
|
71
|
+
mode (`accessToken`, `idToken`, `expiresAt`) followed by a newline.
|
|
72
|
+
`--field <name>` prints one value raw with no trailing newline;
|
|
73
|
+
`refreshToken` is available only this way. A refresh token outlives the
|
|
74
|
+
machine secret: revoke a leaked one with a Cognito global sign-out for the
|
|
75
|
+
machine user or `RevokeToken`. The creds file is `HQ_MACHINE_CREDS_FILE`,
|
|
76
|
+
else `~/.hq-agent/machine-creds.json`; `HQ_AGENT_DIR` does not select it.
|
|
77
|
+
It refuses to print to a terminal unless `--force-tty` is given, and
|
|
78
|
+
supports hosted agent and outpost identities (not external or
|
|
79
|
+
company-outpost ones). `--output <path>` is unchanged. Behavior change:
|
|
80
|
+
`--output -` used to write a file literally named `-` in the current
|
|
81
|
+
directory; it now means stdout. To write a file with that name, use
|
|
82
|
+
`--output ./-`.
|
|
83
|
+
- `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.
|
|
84
|
+
|
|
85
|
+
## [5.345.154] — 2026-10-21
|
|
86
|
+
- `hq sync status` now lists local files that sit under a vault prefix you can
|
|
87
|
+
only read and that were never synced. Sync will never upload these files, for
|
|
88
|
+
example notes written before the vault write-access hook blocked that folder.
|
|
89
|
+
The list comes from the local `.hq/vault-access.json` access data and the
|
|
90
|
+
sync journal, so the command makes no extra network calls.
|
|
91
|
+
- `hq lanes resume` and `hq lanes message` without `--account` keep a Claude lane on the account it was created with via `--account`, instead of rebalancing it onto another account (which could belong to another company). Repair of an unusable account still moves the lane.
|
|
92
|
+
|
|
5
93
|
## [5.345.153] — 2026-10-21
|
|
6
94
|
- `hq-auth-refresh --min-validity-seconds N` now applies to person sessions,
|
|
7
95
|
not only machine tokens: a person session with less than N seconds left
|
|
@@ -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 [];
|
|
@@ -10564,8 +10575,8 @@ export declare const COMMAND_CATALOG: readonly [{
|
|
|
10564
10575
|
readonly description: "Asset description";
|
|
10565
10576
|
readonly hidden: false;
|
|
10566
10577
|
}, {
|
|
10567
|
-
readonly flags: "--company <
|
|
10568
|
-
readonly description: "Company whose vault receives the files";
|
|
10578
|
+
readonly flags: "--company <company>";
|
|
10579
|
+
readonly description: "Company slug or uid (cmp_…) whose vault receives the files";
|
|
10569
10580
|
readonly hidden: false;
|
|
10570
10581
|
}, {
|
|
10571
10582
|
readonly flags: "--no-caption";
|
|
@@ -10693,7 +10704,7 @@ export declare const COMMAND_CATALOG: readonly [{
|
|
|
10693
10704
|
}];
|
|
10694
10705
|
readonly options: readonly [{
|
|
10695
10706
|
readonly flags: "--company <company>";
|
|
10696
|
-
readonly description: "Company uid (cmp_…)
|
|
10707
|
+
readonly description: "Company slug or uid (cmp_…); default: the placeholder's company or the key's";
|
|
10697
10708
|
readonly hidden: false;
|
|
10698
10709
|
}, {
|
|
10699
10710
|
readonly flags: "--expires <minutes>";
|
|
@@ -10705,6 +10716,43 @@ export declare const COMMAND_CATALOG: readonly [{
|
|
|
10705
10716
|
readonly hidden: false;
|
|
10706
10717
|
}];
|
|
10707
10718
|
readonly subcommands: readonly [];
|
|
10719
|
+
}, {
|
|
10720
|
+
readonly name: "rm";
|
|
10721
|
+
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";
|
|
10722
|
+
readonly aliases: readonly ["remove"];
|
|
10723
|
+
readonly hidden: false;
|
|
10724
|
+
readonly helpOption: true;
|
|
10725
|
+
readonly helpCommand: false;
|
|
10726
|
+
readonly usage: "[options] <targets...>";
|
|
10727
|
+
readonly helpText: readonly [];
|
|
10728
|
+
readonly arguments: readonly [{
|
|
10729
|
+
readonly name: "targets";
|
|
10730
|
+
readonly required: true;
|
|
10731
|
+
readonly variadic: true;
|
|
10732
|
+
readonly description: "Library .hqcloud placeholders (or their original paths)";
|
|
10733
|
+
}];
|
|
10734
|
+
readonly options: readonly [{
|
|
10735
|
+
readonly flags: "--company <company>";
|
|
10736
|
+
readonly description: "Company slug (or cmp_ uid); default: the targets' companies/<slug>/";
|
|
10737
|
+
readonly hidden: false;
|
|
10738
|
+
}, {
|
|
10739
|
+
readonly flags: "--purge";
|
|
10740
|
+
readonly description: "Also delete the blob, after checking no other placeholder (local or in the vault) uses it";
|
|
10741
|
+
readonly hidden: false;
|
|
10742
|
+
}, {
|
|
10743
|
+
readonly flags: "--dry-run";
|
|
10744
|
+
readonly description: "Show what would be removed without changing anything";
|
|
10745
|
+
readonly hidden: false;
|
|
10746
|
+
}, {
|
|
10747
|
+
readonly flags: "-y, --yes";
|
|
10748
|
+
readonly description: "Skip the confirmation prompt";
|
|
10749
|
+
readonly hidden: false;
|
|
10750
|
+
}, {
|
|
10751
|
+
readonly flags: "--json";
|
|
10752
|
+
readonly description: "Print machine-readable JSON (the preview goes to stderr)";
|
|
10753
|
+
readonly hidden: false;
|
|
10754
|
+
}];
|
|
10755
|
+
readonly subcommands: readonly [];
|
|
10708
10756
|
}];
|
|
10709
10757
|
}, {
|
|
10710
10758
|
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
|
],
|
|
@@ -12811,8 +12826,8 @@ export const COMMAND_CATALOG = [
|
|
|
12811
12826
|
"hidden": false
|
|
12812
12827
|
},
|
|
12813
12828
|
{
|
|
12814
|
-
"flags": "--company <
|
|
12815
|
-
"description": "Company whose vault receives the files",
|
|
12829
|
+
"flags": "--company <company>",
|
|
12830
|
+
"description": "Company slug or uid (cmp_…) whose vault receives the files",
|
|
12816
12831
|
"hidden": false
|
|
12817
12832
|
},
|
|
12818
12833
|
{
|
|
@@ -12970,7 +12985,7 @@ export const COMMAND_CATALOG = [
|
|
|
12970
12985
|
"options": [
|
|
12971
12986
|
{
|
|
12972
12987
|
"flags": "--company <company>",
|
|
12973
|
-
"description": "Company uid (cmp_…)
|
|
12988
|
+
"description": "Company slug or uid (cmp_…); default: the placeholder's company or the key's",
|
|
12974
12989
|
"hidden": false
|
|
12975
12990
|
},
|
|
12976
12991
|
{
|
|
@@ -12985,6 +13000,54 @@ export const COMMAND_CATALOG = [
|
|
|
12985
13000
|
}
|
|
12986
13001
|
],
|
|
12987
13002
|
"subcommands": []
|
|
13003
|
+
},
|
|
13004
|
+
{
|
|
13005
|
+
"name": "rm",
|
|
13006
|
+
"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",
|
|
13007
|
+
"aliases": [
|
|
13008
|
+
"remove"
|
|
13009
|
+
],
|
|
13010
|
+
"hidden": false,
|
|
13011
|
+
"helpOption": true,
|
|
13012
|
+
"helpCommand": false,
|
|
13013
|
+
"usage": "[options] <targets...>",
|
|
13014
|
+
"helpText": [],
|
|
13015
|
+
"arguments": [
|
|
13016
|
+
{
|
|
13017
|
+
"name": "targets",
|
|
13018
|
+
"required": true,
|
|
13019
|
+
"variadic": true,
|
|
13020
|
+
"description": "Library .hqcloud placeholders (or their original paths)"
|
|
13021
|
+
}
|
|
13022
|
+
],
|
|
13023
|
+
"options": [
|
|
13024
|
+
{
|
|
13025
|
+
"flags": "--company <company>",
|
|
13026
|
+
"description": "Company slug (or cmp_ uid); default: the targets' companies/<slug>/",
|
|
13027
|
+
"hidden": false
|
|
13028
|
+
},
|
|
13029
|
+
{
|
|
13030
|
+
"flags": "--purge",
|
|
13031
|
+
"description": "Also delete the blob, after checking no other placeholder (local or in the vault) uses it",
|
|
13032
|
+
"hidden": false
|
|
13033
|
+
},
|
|
13034
|
+
{
|
|
13035
|
+
"flags": "--dry-run",
|
|
13036
|
+
"description": "Show what would be removed without changing anything",
|
|
13037
|
+
"hidden": false
|
|
13038
|
+
},
|
|
13039
|
+
{
|
|
13040
|
+
"flags": "-y, --yes",
|
|
13041
|
+
"description": "Skip the confirmation prompt",
|
|
13042
|
+
"hidden": false
|
|
13043
|
+
},
|
|
13044
|
+
{
|
|
13045
|
+
"flags": "--json",
|
|
13046
|
+
"description": "Print machine-readable JSON (the preview goes to stderr)",
|
|
13047
|
+
"hidden": false
|
|
13048
|
+
}
|
|
13049
|
+
],
|
|
13050
|
+
"subcommands": []
|
|
12988
13051
|
}
|
|
12989
13052
|
]
|
|
12990
13053
|
},
|
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/cloud.d.ts
CHANGED
|
@@ -401,4 +401,21 @@ export declare function registerCloudCommands(program: Command): void;
|
|
|
401
401
|
export declare function runSyncDoctorReconcileConflicts(hqRoot: string, yes: boolean): Promise<void>;
|
|
402
402
|
/** Exported for the US-003 sync-health exit-path regression test. */
|
|
403
403
|
export declare function runPullPersonal(hqRoot: string, onConflict?: ConflictStrategy): Promise<void>;
|
|
404
|
+
/**
|
|
405
|
+
* Human-friendly label for a journal slug in `hq sync status`. The
|
|
406
|
+
* personal-vault fanout slot and the legacy `companies/personal` company both
|
|
407
|
+
* carry opaque slugs; everything else is a real company slug shown as-is.
|
|
408
|
+
*/
|
|
409
|
+
/**
|
|
410
|
+
* `hq sync status`: list local files under read-only vault prefixes that are
|
|
411
|
+
* not in the sync journal. Push never uploads them (feedback_510661bc,
|
|
412
|
+
* Shepherd #2813). Uses only the local vault-access manifest and the journals
|
|
413
|
+
* already loaded, with no network calls. Prints nothing when nothing applies.
|
|
414
|
+
*/
|
|
415
|
+
export declare function printReadOnlyLocalOnly(hqRoot: string, journals: ReadonlyArray<{
|
|
416
|
+
slug: string;
|
|
417
|
+
journal: {
|
|
418
|
+
files?: Record<string, unknown>;
|
|
419
|
+
};
|
|
420
|
+
}>, log?: (line: string) => void): number;
|
|
404
421
|
//# sourceMappingURL=cloud.d.ts.map
|
package/dist/commands/cloud.js
CHANGED
|
@@ -13,10 +13,11 @@
|
|
|
13
13
|
* hq sync status — show local journal summary
|
|
14
14
|
*/
|
|
15
15
|
import { isAccessDeniedError, accessDeniedKeyOf, formatAccessDenied, } from "../utils/access-denied-hint.js";
|
|
16
|
+
import { findReadOnlyLocalOnly, isGrantEnforced, readVaultAccessManifest, } from "../lib/sync/read-only-local-only.js";
|
|
16
17
|
import chalk from "chalk";
|
|
17
18
|
import * as fs from "fs";
|
|
18
19
|
import * as path from "path";
|
|
19
|
-
import { share, getStateDir, listJournals, VaultClient, computePersonalVaultPaths, PERSONAL_VAULT_JOURNAL_SLUG, resolvePullScope, syncDoctor, } from "@indigoai-us/hq-cloud";
|
|
20
|
+
import { share, getStateDir, listJournals, createIgnoreFilter, VaultClient, computePersonalVaultPaths, PERSONAL_VAULT_JOURNAL_SLUG, resolvePullScope, syncDoctor, } from "@indigoai-us/hq-cloud";
|
|
20
21
|
import { syncLeg } from "../utils/sync-operation-lock.js";
|
|
21
22
|
import { DEFAULT_HQ_ROOT, ensureCognitoToken, buildVaultConfig, loadCachedTokens, } from "../utils/cognito-session.js";
|
|
22
23
|
import { resolveUploadAuthor } from "../utils/upload-author.js";
|
|
@@ -1353,6 +1354,7 @@ export function registerCloudCommands(program) {
|
|
|
1353
1354
|
console.log(` Total size: ${formatBytes(grandBytes)}`);
|
|
1354
1355
|
console.log(` Last sync: ${grandLastSync || "never"}`);
|
|
1355
1356
|
}
|
|
1357
|
+
printReadOnlyLocalOnly(hqRoot, journals);
|
|
1356
1358
|
}
|
|
1357
1359
|
catch (err) {
|
|
1358
1360
|
console.error(chalk.red("✗ Status failed:"), err instanceof Error ? err.message : String(err));
|
|
@@ -2094,6 +2096,51 @@ function readActiveCompanySlug(hqRoot) {
|
|
|
2094
2096
|
* personal-vault fanout slot and the legacy `companies/personal` company both
|
|
2095
2097
|
* carry opaque slugs; everything else is a real company slug shown as-is.
|
|
2096
2098
|
*/
|
|
2099
|
+
/**
|
|
2100
|
+
* `hq sync status`: list local files under read-only vault prefixes that are
|
|
2101
|
+
* not in the sync journal. Push never uploads them (feedback_510661bc,
|
|
2102
|
+
* Shepherd #2813). Uses only the local vault-access manifest and the journals
|
|
2103
|
+
* already loaded, with no network calls. Prints nothing when nothing applies.
|
|
2104
|
+
*/
|
|
2105
|
+
export function printReadOnlyLocalOnly(hqRoot, journals, log = (line) => console.log(line)) {
|
|
2106
|
+
const companies = readVaultAccessManifest(hqRoot)?.companies;
|
|
2107
|
+
if (!companies)
|
|
2108
|
+
return 0;
|
|
2109
|
+
let shouldSync;
|
|
2110
|
+
let grand = 0;
|
|
2111
|
+
for (const [slug, company] of Object.entries(companies)) {
|
|
2112
|
+
if (!isGrantEnforced(company))
|
|
2113
|
+
continue;
|
|
2114
|
+
if (!fs.existsSync(path.join(hqRoot, "companies", slug)))
|
|
2115
|
+
continue;
|
|
2116
|
+
if (!shouldSync) {
|
|
2117
|
+
try {
|
|
2118
|
+
shouldSync = createIgnoreFilter(hqRoot);
|
|
2119
|
+
}
|
|
2120
|
+
catch {
|
|
2121
|
+
shouldSync = () => true;
|
|
2122
|
+
}
|
|
2123
|
+
}
|
|
2124
|
+
const journal = journals.find((j) => j.slug === slug)?.journal;
|
|
2125
|
+
const journalKeys = new Set(Object.keys(journal?.files ?? {}));
|
|
2126
|
+
const found = findReadOnlyLocalOnly({ hqRoot, slug, company, journalKeys, shouldSync });
|
|
2127
|
+
if (found.total === 0)
|
|
2128
|
+
continue;
|
|
2129
|
+
grand += found.total;
|
|
2130
|
+
log(chalk.yellow(`\n ! ${slug}: ${found.total} local-only file(s) under read-only access will not upload`));
|
|
2131
|
+
for (const f of found.files) {
|
|
2132
|
+
log(` ${f.path}${f.permission === "none" ? chalk.dim(" (no access)") : ""}`);
|
|
2133
|
+
}
|
|
2134
|
+
if (found.total > found.files.length) {
|
|
2135
|
+
log(chalk.dim(` ... and ${found.total - found.files.length} more`));
|
|
2136
|
+
}
|
|
2137
|
+
}
|
|
2138
|
+
if (grand > 0) {
|
|
2139
|
+
log(chalk.dim(" Copy them somewhere you can write, or ask a company admin for write access. " +
|
|
2140
|
+
"Access data: .hq/vault-access.json (refresh: bash core/scripts/refresh-vault-access.sh)."));
|
|
2141
|
+
}
|
|
2142
|
+
return grand;
|
|
2143
|
+
}
|
|
2097
2144
|
function scopeLabel(slug) {
|
|
2098
2145
|
if (slug === PERSONAL_VAULT_JOURNAL_SLUG)
|
|
2099
2146
|
return "personal vault";
|
package/dist/commands/lanes.js
CHANGED
|
@@ -7801,7 +7801,13 @@ async function selectAutomaticClaudeResumeAccount(ctx, lane, workerProfile, opti
|
|
|
7801
7801
|
accountModel: lane.account === undefined ? undefined : config.providers.claude.accounts[lane.account]?.model,
|
|
7802
7802
|
providerModel: config.providers.claude.model,
|
|
7803
7803
|
});
|
|
7804
|
-
|
|
7804
|
+
// An account the operator chose with --account (account_source "cli") is a
|
|
7805
|
+
// pin, not a balancing hint. Plain resumes and messages keep it; only a repair
|
|
7806
|
+
// of an unusable account may move the lane, and only an explicit --account
|
|
7807
|
+
// may pick another healthy account. Rebalancing a pinned lane moved company
|
|
7808
|
+
// lanes onto another company's account (feedback_4a563e61).
|
|
7809
|
+
const cliAccountPin = lane.account !== undefined && lane.account_source === "cli";
|
|
7810
|
+
const mayRebalance = !cliAccountPin && BALANCE_MOVABLE_STATES.includes(lane.state);
|
|
7805
7811
|
if (!accountNeedsRepair && !mayRebalance)
|
|
7806
7812
|
return undefined;
|
|
7807
7813
|
const balanceHosts = await loadClaudeBalanceHostSnapshots(ctx, hqRoot, config, {
|
|
@@ -23,8 +23,12 @@ export interface PutResponse {
|
|
|
23
23
|
export interface LibraryAddDeps {
|
|
24
24
|
hqRoot: string;
|
|
25
25
|
now: () => Date;
|
|
26
|
-
/**
|
|
27
|
-
|
|
26
|
+
/**
|
|
27
|
+
* Resolve `--company` (a slug or a cmp_ uid) to the company. `folderSlug` is
|
|
28
|
+
* the companies/<slug>/ of an explicit `--to`, for callers that cannot map a
|
|
29
|
+
* uid to its folder on their own (an HQ_API_KEY).
|
|
30
|
+
*/
|
|
31
|
+
resolveCompany: (ref: string, folderSlug?: string) => Promise<LibraryCompany>;
|
|
28
32
|
/** POST /v1/files/presign. */
|
|
29
33
|
presign: (body: Record<string, unknown>) => Promise<Response>;
|
|
30
34
|
/** Open a fresh byte stream of a source. Called once to hash and once to upload. */
|