@indigoai-us/hq-cli 5.274.0 → 5.275.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +52 -0
- package/assets/scaffold/core/scripts/checkpoint-stop-gate.sh +74 -18
- package/dist/command-catalog.generated.d.ts +34 -1
- package/dist/command-catalog.generated.js +43 -1
- package/dist/commands/cloud.js +22 -9
- package/dist/commands/company.js +66 -3
- package/dist/commands/core-checkpoint-gate-setting.d.ts +69 -0
- package/dist/commands/core-checkpoint-gate-setting.js +163 -0
- package/dist/commands/core.js +2 -0
- package/dist/commands/daemon.js +7 -2
- package/dist/commands/lanes-message.js +40 -7
- package/dist/commands/lanes-shared.d.ts +4 -0
- package/dist/commands/lanes-shared.js +5 -0
- package/dist/commands/lanes.js +41 -17
- package/dist/lib/daemon/catalog.d.ts +7 -0
- package/dist/lib/daemon/catalog.js +6 -0
- package/dist/lib/daemon/flag.d.ts +23 -0
- package/dist/lib/daemon/flag.js +57 -0
- package/dist/lib/daemon/install.js +6 -1
- package/dist/lib/daemon/remote-control.d.ts +6 -0
- package/dist/lib/daemon/remote-control.js +12 -0
- package/dist/lib/daemon/run.js +115 -2
- package/dist/lib/daemon/succession.d.ts +116 -0
- package/dist/lib/daemon/succession.js +262 -0
- package/dist/lib/daemon/supervisor.d.ts +34 -1
- package/dist/lib/daemon/supervisor.js +190 -23
- package/dist/lib/lanes/hire-grant-dispatch.d.ts +2 -1
- package/dist/lib/lanes/hire-grant-dispatch.js +37 -2
- package/dist/lib/lanes/host-capacity.js +7 -1
- package/dist/lib/lanes/keep-alive.js +2 -2
- package/dist/lib/lanes/models.d.ts +2 -0
- package/dist/lib/lanes/models.js +6 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,58 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [5.275.0] — 2026-09-28
|
|
6
|
+
|
|
7
|
+
### Fixed
|
|
8
|
+
|
|
9
|
+
- `hq lanes` accepts full Claude model ids such as `claude-sonnet-5-5` and
|
|
10
|
+
`claude-opus-5-5` (spawn, `resume --from-failed`, account moves) when
|
|
11
|
+
`claude --help` names only the aliases. Before, the model check refused them
|
|
12
|
+
with `model_unsupported`.
|
|
13
|
+
|
|
14
|
+
### Added
|
|
15
|
+
|
|
16
|
+
- Outpost daemons can take over running services after a CLI update instead
|
|
17
|
+
of restarting all of them, behind the `daemon.supervisor-handoff` flag (off
|
|
18
|
+
by default). On Linux, the updated daemon adopts the services the previous
|
|
19
|
+
one left running and replaces only the ones whose command changed, one at a
|
|
20
|
+
time. Bots finish their running turns first. hq-cloud services keep running,
|
|
21
|
+
and the remote-control hosts restart their session once it is quiet. This
|
|
22
|
+
needs the systemd unit from this release: run `sudo hq daemon install` again
|
|
23
|
+
on each outpost.
|
|
24
|
+
- Company owners can turn the checkpoint Stop gate off or on for their company:
|
|
25
|
+
`hq company settings set --checkpoint-gate-enabled false|true`. Any member can
|
|
26
|
+
read the value with `hq company settings get`. The Stop gate reads a local
|
|
27
|
+
cache (`~/.hq/cache/checkpoint-gate/<company>.json`, refreshed in the
|
|
28
|
+
background at most every ten minutes by `hq core checkpoint-gate-setting
|
|
29
|
+
--refresh`) and skips the checkpoint block only when the session's company has
|
|
30
|
+
the gate turned off. Unset, unreachable with no cache, and sessions with no
|
|
31
|
+
company keep today's behaviour; `HQ_CHECKPOINT_GATE` still wins. Gated by the
|
|
32
|
+
hq-flag `checkpoint.gate.company-setting` (default off).
|
|
33
|
+
|
|
34
|
+
### Changed
|
|
35
|
+
|
|
36
|
+
- The systemd system unit now uses `KillMode=process`, so systemd signals only
|
|
37
|
+
the daemon. The daemon still stops its services on `systemctl stop`, and it
|
|
38
|
+
stops any service left over from a crashed daemon before starting new ones.
|
|
39
|
+
|
|
40
|
+
### Fixed
|
|
41
|
+
|
|
42
|
+
- `hq lanes create --placement auto` now runs on the caller when it is the best
|
|
43
|
+
capacity candidate. It matches the host id, machine identity or registered
|
|
44
|
+
Outpost id. Explicit `--outpost` self-targets still return
|
|
45
|
+
`outpost_self_target`.
|
|
46
|
+
- `hq sync status` validates an explicit `--hq-root` and continues to read
|
|
47
|
+
journals from the configured state directory, which it now prints.
|
|
48
|
+
|
|
49
|
+
## [5.274.1] — 2026-09-28
|
|
50
|
+
|
|
51
|
+
### Fixed
|
|
52
|
+
|
|
53
|
+
- With lineage-aware supersession enabled, same-profile workers under the same
|
|
54
|
+
session senior no longer supersede each other. The recorded senior can
|
|
55
|
+
message its worker if it is later superseded.
|
|
56
|
+
|
|
5
57
|
## [5.274.0] — 2026-09-28
|
|
6
58
|
|
|
7
59
|
### Fixed
|
|
@@ -137,6 +137,72 @@ exec 3>&2
|
|
|
137
137
|
return 0
|
|
138
138
|
}
|
|
139
139
|
|
|
140
|
+
# checkpoint_gate_bound_company <hq-root> <session-id> — the session's
|
|
141
|
+
# declared company slug: scope-capability.json first, then meta.yaml. Prints
|
|
142
|
+
# empty when the session has no binding.
|
|
143
|
+
checkpoint_gate_bound_company() {
|
|
144
|
+
local root="$1" sid="$2" co="" cap meta
|
|
145
|
+
[ -n "$sid" ] || return 0
|
|
146
|
+
cap="$root/workspace/sessions/$sid/scope-capability.json"
|
|
147
|
+
if [ -r "$cap" ]; then
|
|
148
|
+
co="$(jq -r '.company_slug // empty' "$cap" 2>/dev/null || true)"
|
|
149
|
+
fi
|
|
150
|
+
if [ -z "$co" ]; then
|
|
151
|
+
meta="$root/workspace/sessions/$sid/meta.yaml"
|
|
152
|
+
if [ -r "$meta" ]; then
|
|
153
|
+
co="$(awk '
|
|
154
|
+
$1 == "company_slug:" {
|
|
155
|
+
sub(/^[^:]+:[[:space:]]*/, "")
|
|
156
|
+
gsub(/^"|"$/, "")
|
|
157
|
+
print
|
|
158
|
+
exit
|
|
159
|
+
}
|
|
160
|
+
' "$meta" 2>/dev/null || true)"
|
|
161
|
+
fi
|
|
162
|
+
fi
|
|
163
|
+
printf '%s' "$co"
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
# checkpoint_gate_company_off <hq-root> <session-id> — succeeds only when the
|
|
167
|
+
# session's company owner has turned the checkpoint gate off
|
|
168
|
+
# (companySettings.checkpointGateEnabled = false) according to the local
|
|
169
|
+
# cache written by `hq core checkpoint-gate-setting --refresh`. The hook
|
|
170
|
+
# never calls the network: a missing or stale cache starts one detached,
|
|
171
|
+
# rate-limited refresh and the current turn uses whatever the cache holds.
|
|
172
|
+
# On, unset, no cache, no company, `personal`, or anything unreadable keeps
|
|
173
|
+
# the gate on. The refresh removes the cache while the
|
|
174
|
+
# `checkpoint.gate.company-setting` hq-flag is off, so the setting has no
|
|
175
|
+
# effect until the flag is on.
|
|
176
|
+
checkpoint_gate_company_off() {
|
|
177
|
+
local co dir cache stamp value fetched now stamp_mtime
|
|
178
|
+
co="$(checkpoint_gate_bound_company "$1" "$2")"
|
|
179
|
+
case "$co" in
|
|
180
|
+
""|personal) return 1 ;;
|
|
181
|
+
*[!a-z0-9-]*) return 1 ;;
|
|
182
|
+
esac
|
|
183
|
+
[ -n "${HOME:-}" ] || return 1
|
|
184
|
+
dir="${HOME}/.hq/cache/checkpoint-gate"
|
|
185
|
+
cache="$dir/$co.json"
|
|
186
|
+
stamp="$dir/$co.refresh"
|
|
187
|
+
value=""
|
|
188
|
+
fetched=""
|
|
189
|
+
if [ -r "$cache" ]; then
|
|
190
|
+
value="$(jq -r 'if .checkpointGateEnabled == false then "off" elif .checkpointGateEnabled == true then "on" else "" end' "$cache" 2>/dev/null || true)"
|
|
191
|
+
fetched="$(jq -r '.fetched_at // empty | numbers | floor' "$cache" 2>/dev/null || true)"
|
|
192
|
+
fi
|
|
193
|
+
now="$(date +%s 2>/dev/null || true)"
|
|
194
|
+
case "$now" in ''|*[!0-9]*) now="" ;; esac
|
|
195
|
+
case "$fetched" in ''|*[!0-9]*) fetched=0 ;; esac
|
|
196
|
+
if [ -n "$now" ] && [ $((now - fetched)) -gt 600 ] && command -v hq >/dev/null 2>&1; then
|
|
197
|
+
stamp_mtime="$(stat -c %Y "$stamp" 2>/dev/null || stat -f %m "$stamp" 2>/dev/null || printf 0)"
|
|
198
|
+
case "$stamp_mtime" in ''|*[!0-9]*) stamp_mtime=0 ;; esac
|
|
199
|
+
if [ $((now - stamp_mtime)) -gt 600 ] && mkdir -p "$dir" 2>/dev/null && touch "$stamp" 2>/dev/null; then
|
|
200
|
+
(nohup hq core checkpoint-gate-setting --refresh --company "$co" >/dev/null 2>&1 3>&- &)
|
|
201
|
+
fi
|
|
202
|
+
fi
|
|
203
|
+
[ "$value" = off ]
|
|
204
|
+
}
|
|
205
|
+
|
|
140
206
|
input="$(cat 2>/dev/null || printf '{}')"
|
|
141
207
|
input_reply_evidence="$(printf '%s' "$input" | jq -r '
|
|
142
208
|
if has("last_assistant_message") then
|
|
@@ -201,24 +267,7 @@ exec 3>&2
|
|
|
201
267
|
fi
|
|
202
268
|
if [ "$gate_match" = 1 ]; then
|
|
203
269
|
gate_root="${HQ_ROOT:-${CLAUDE_PROJECT_DIR:-$self_hq}}"
|
|
204
|
-
bound_co=""
|
|
205
|
-
gate_cap="$gate_root/workspace/sessions/$gate_session_id/scope-capability.json"
|
|
206
|
-
if [ -r "$gate_cap" ]; then
|
|
207
|
-
bound_co="$(jq -r '.company_slug // empty' "$gate_cap" 2>/dev/null || true)"
|
|
208
|
-
fi
|
|
209
|
-
if [ -z "$bound_co" ]; then
|
|
210
|
-
gate_meta="$gate_root/workspace/sessions/$gate_session_id/meta.yaml"
|
|
211
|
-
if [ -r "$gate_meta" ]; then
|
|
212
|
-
bound_co="$(awk '
|
|
213
|
-
$1 == "company_slug:" {
|
|
214
|
-
sub(/^[^:]+:[[:space:]]*/, "")
|
|
215
|
-
gsub(/^"|"$/, "")
|
|
216
|
-
print
|
|
217
|
-
exit
|
|
218
|
-
}
|
|
219
|
-
' "$gate_meta" 2>/dev/null || true)"
|
|
220
|
-
fi
|
|
221
|
-
fi
|
|
270
|
+
bound_co="$(checkpoint_gate_bound_company "$gate_root" "$gate_session_id")"
|
|
222
271
|
# A binding satisfies the gate only when it names a REAL scope: the
|
|
223
272
|
# reserved `personal`, or an actual tenant directory. hq-session will
|
|
224
273
|
# still store a typo'd or invented slug (it validates only the
|
|
@@ -239,6 +288,13 @@ exec 3>&2
|
|
|
239
288
|
;;
|
|
240
289
|
esac
|
|
241
290
|
|
|
291
|
+
# Company setting: an owner can turn the checkpoint requirement off for
|
|
292
|
+
# their company's sessions. An explicit operator switch (0 or 1) wins over it.
|
|
293
|
+
if [ -z "$gate_switch" ] &&
|
|
294
|
+
checkpoint_gate_company_off "${HQ_ROOT:-${CLAUDE_PROJECT_DIR:-$self_hq}}" "$session_id"; then
|
|
295
|
+
exit 0
|
|
296
|
+
fi
|
|
297
|
+
|
|
242
298
|
runtime="${HQ_CHECKPOINT_RUNTIME:-claude}"
|
|
243
299
|
|
|
244
300
|
# Operator switches precede runtime and identity eligibility. A forced gate
|
|
@@ -3818,7 +3818,7 @@ export declare const COMMAND_CATALOG: readonly [{
|
|
|
3818
3818
|
readonly options: readonly [];
|
|
3819
3819
|
readonly subcommands: readonly [{
|
|
3820
3820
|
readonly name: "set";
|
|
3821
|
-
readonly description: "Set per-company settings (PUT /company-settings). Require at least one of --crm-enabled / --ontology-enabled / --accounting-enabled.";
|
|
3821
|
+
readonly description: "Set per-company settings (PUT /company-settings). Require at least one of --crm-enabled / --ontology-enabled / --accounting-enabled / --checkpoint-gate-enabled.";
|
|
3822
3822
|
readonly aliases: readonly [];
|
|
3823
3823
|
readonly hidden: false;
|
|
3824
3824
|
readonly usage: "[options]";
|
|
@@ -3832,6 +3832,21 @@ export declare const COMMAND_CATALOG: readonly [{
|
|
|
3832
3832
|
}, {
|
|
3833
3833
|
readonly flags: "--accounting-enabled <true|false>";
|
|
3834
3834
|
readonly description: "Enable/disable the accounting pack for this company";
|
|
3835
|
+
}, {
|
|
3836
|
+
readonly flags: "--checkpoint-gate-enabled <true|false>";
|
|
3837
|
+
readonly description: "Require (true) or stop requiring (false) a checkpoint before HQ sessions in this company end a turn";
|
|
3838
|
+
}];
|
|
3839
|
+
readonly subcommands: readonly [];
|
|
3840
|
+
}, {
|
|
3841
|
+
readonly name: "get";
|
|
3842
|
+
readonly description: "Show whether the checkpoint Stop gate is on for this company (GET /company-settings/checkpoint-gate; any member may read it).";
|
|
3843
|
+
readonly aliases: readonly [];
|
|
3844
|
+
readonly hidden: false;
|
|
3845
|
+
readonly usage: "[options]";
|
|
3846
|
+
readonly arguments: readonly [];
|
|
3847
|
+
readonly options: readonly [{
|
|
3848
|
+
readonly flags: "--json";
|
|
3849
|
+
readonly description: "Print the result as JSON";
|
|
3835
3850
|
}];
|
|
3836
3851
|
readonly subcommands: readonly [];
|
|
3837
3852
|
}];
|
|
@@ -5191,6 +5206,24 @@ export declare const COMMAND_CATALOG: readonly [{
|
|
|
5191
5206
|
}];
|
|
5192
5207
|
readonly subcommands: readonly [];
|
|
5193
5208
|
}];
|
|
5209
|
+
}, {
|
|
5210
|
+
readonly name: "checkpoint-gate-setting";
|
|
5211
|
+
readonly description: "Refresh the local cache of a company's checkpoint gate setting (used by the Stop gate)";
|
|
5212
|
+
readonly aliases: readonly [];
|
|
5213
|
+
readonly hidden: false;
|
|
5214
|
+
readonly usage: "[options]";
|
|
5215
|
+
readonly arguments: readonly [];
|
|
5216
|
+
readonly options: readonly [{
|
|
5217
|
+
readonly flags: "--company <slug>";
|
|
5218
|
+
readonly description: "company whose setting to read";
|
|
5219
|
+
}, {
|
|
5220
|
+
readonly flags: "--refresh";
|
|
5221
|
+
readonly description: "read the setting from HQ and update the local cache";
|
|
5222
|
+
}, {
|
|
5223
|
+
readonly flags: "--json";
|
|
5224
|
+
readonly description: "print the result as JSON";
|
|
5225
|
+
}];
|
|
5226
|
+
readonly subcommands: readonly [];
|
|
5194
5227
|
}, {
|
|
5195
5228
|
readonly name: "generate-workers-registry";
|
|
5196
5229
|
readonly description: "Regenerate the derived workers registry";
|
|
@@ -4926,7 +4926,7 @@ export const COMMAND_CATALOG = [
|
|
|
4926
4926
|
"subcommands": [
|
|
4927
4927
|
{
|
|
4928
4928
|
"name": "set",
|
|
4929
|
-
"description": "Set per-company settings (PUT /company-settings). Require at least one of --crm-enabled / --ontology-enabled / --accounting-enabled.",
|
|
4929
|
+
"description": "Set per-company settings (PUT /company-settings). Require at least one of --crm-enabled / --ontology-enabled / --accounting-enabled / --checkpoint-gate-enabled.",
|
|
4930
4930
|
"aliases": [],
|
|
4931
4931
|
"hidden": false,
|
|
4932
4932
|
"usage": "[options]",
|
|
@@ -4943,6 +4943,25 @@ export const COMMAND_CATALOG = [
|
|
|
4943
4943
|
{
|
|
4944
4944
|
"flags": "--accounting-enabled <true|false>",
|
|
4945
4945
|
"description": "Enable/disable the accounting pack for this company"
|
|
4946
|
+
},
|
|
4947
|
+
{
|
|
4948
|
+
"flags": "--checkpoint-gate-enabled <true|false>",
|
|
4949
|
+
"description": "Require (true) or stop requiring (false) a checkpoint before HQ sessions in this company end a turn"
|
|
4950
|
+
}
|
|
4951
|
+
],
|
|
4952
|
+
"subcommands": []
|
|
4953
|
+
},
|
|
4954
|
+
{
|
|
4955
|
+
"name": "get",
|
|
4956
|
+
"description": "Show whether the checkpoint Stop gate is on for this company (GET /company-settings/checkpoint-gate; any member may read it).",
|
|
4957
|
+
"aliases": [],
|
|
4958
|
+
"hidden": false,
|
|
4959
|
+
"usage": "[options]",
|
|
4960
|
+
"arguments": [],
|
|
4961
|
+
"options": [
|
|
4962
|
+
{
|
|
4963
|
+
"flags": "--json",
|
|
4964
|
+
"description": "Print the result as JSON"
|
|
4946
4965
|
}
|
|
4947
4966
|
],
|
|
4948
4967
|
"subcommands": []
|
|
@@ -6713,6 +6732,29 @@ export const COMMAND_CATALOG = [
|
|
|
6713
6732
|
}
|
|
6714
6733
|
]
|
|
6715
6734
|
},
|
|
6735
|
+
{
|
|
6736
|
+
"name": "checkpoint-gate-setting",
|
|
6737
|
+
"description": "Refresh the local cache of a company's checkpoint gate setting (used by the Stop gate)",
|
|
6738
|
+
"aliases": [],
|
|
6739
|
+
"hidden": false,
|
|
6740
|
+
"usage": "[options]",
|
|
6741
|
+
"arguments": [],
|
|
6742
|
+
"options": [
|
|
6743
|
+
{
|
|
6744
|
+
"flags": "--company <slug>",
|
|
6745
|
+
"description": "company whose setting to read"
|
|
6746
|
+
},
|
|
6747
|
+
{
|
|
6748
|
+
"flags": "--refresh",
|
|
6749
|
+
"description": "read the setting from HQ and update the local cache"
|
|
6750
|
+
},
|
|
6751
|
+
{
|
|
6752
|
+
"flags": "--json",
|
|
6753
|
+
"description": "print the result as JSON"
|
|
6754
|
+
}
|
|
6755
|
+
],
|
|
6756
|
+
"subcommands": []
|
|
6757
|
+
},
|
|
6716
6758
|
{
|
|
6717
6759
|
"name": "generate-workers-registry",
|
|
6718
6760
|
"description": "Regenerate the derived workers registry",
|
package/dist/commands/cloud.js
CHANGED
|
@@ -1061,8 +1061,18 @@ export function registerCloudCommands(program) {
|
|
|
1061
1061
|
.command("status")
|
|
1062
1062
|
.description("Show local sync journal summary")
|
|
1063
1063
|
.option("--hq-root <path>", `Local HQ tree root (default: ${DEFAULT_HQ_ROOT})`, DEFAULT_HQ_ROOT)
|
|
1064
|
-
.action((options) => {
|
|
1064
|
+
.action((options, command) => {
|
|
1065
|
+
const explicitHqRoot = command.getOptionValueSource("hqRoot") === "cli";
|
|
1065
1066
|
try {
|
|
1067
|
+
const hqRoot = options.hqRoot;
|
|
1068
|
+
if (explicitHqRoot) {
|
|
1069
|
+
if (!fs.existsSync(hqRoot)) {
|
|
1070
|
+
throw new Error(`HQ root does not exist: ${hqRoot}`);
|
|
1071
|
+
}
|
|
1072
|
+
if (!fs.statSync(hqRoot).isDirectory()) {
|
|
1073
|
+
throw new Error(`HQ root is not a directory: ${hqRoot}`);
|
|
1074
|
+
}
|
|
1075
|
+
}
|
|
1066
1076
|
// The engine SHARDS the journal by slug — the personal-vault fanout
|
|
1067
1077
|
// slot, one shard per cloud company, and the legacy "personal" shard.
|
|
1068
1078
|
// Enumerate EVERY shard the engine can write (via the engine's own
|
|
@@ -1071,13 +1081,7 @@ export function registerCloudCommands(program) {
|
|
|
1071
1081
|
// looked at `sync-journal._Users_<user>_hq.json` — a slug the engine
|
|
1072
1082
|
// never writes — and printed "No sync journal yet" right after a
|
|
1073
1083
|
// successful sync (feedback_9fbf1f82 / feedback_46288b7b).
|
|
1074
|
-
const
|
|
1075
|
-
if (journals.length === 0) {
|
|
1076
|
-
console.log(chalk.dim("No sync journal yet — run `hq sync now`, `hq sync push`, or `hq sync pull` to create one."));
|
|
1077
|
-
console.log(chalk.dim(` Looked in: ${getStateDir()} (sync-journal.*.json)`));
|
|
1078
|
-
return;
|
|
1079
|
-
}
|
|
1080
|
-
const configPath = path.join(options.hqRoot, ".hq", "config.json");
|
|
1084
|
+
const configPath = path.join(hqRoot, ".hq", "config.json");
|
|
1081
1085
|
let activeCompany;
|
|
1082
1086
|
if (fs.existsSync(configPath)) {
|
|
1083
1087
|
try {
|
|
@@ -1088,13 +1092,22 @@ export function registerCloudCommands(program) {
|
|
|
1088
1092
|
// ignore
|
|
1089
1093
|
}
|
|
1090
1094
|
}
|
|
1095
|
+
// Journals live in the machine-wide state directory, independently
|
|
1096
|
+
// of the HQ content root selected for this status display.
|
|
1097
|
+
const stateDir = getStateDir();
|
|
1098
|
+
const journals = listJournals();
|
|
1091
1099
|
let grandFiles = 0;
|
|
1092
1100
|
let grandBytes = 0;
|
|
1093
1101
|
let grandLastSync = "";
|
|
1094
1102
|
console.log(chalk.bold("\nHQ Sync — Status"));
|
|
1095
|
-
console.log(` HQ root: ${
|
|
1103
|
+
console.log(` HQ root: ${hqRoot}`);
|
|
1096
1104
|
console.log(` Active company: ${activeCompany ?? chalk.dim("(none)")}`);
|
|
1105
|
+
console.log(` State dir: ${stateDir}`);
|
|
1097
1106
|
console.log(` Journals: ${journals.length}`);
|
|
1107
|
+
if (journals.length === 0) {
|
|
1108
|
+
console.log(chalk.dim("No sync journal yet — run `hq sync now`, `hq sync push`, or `hq sync pull` to create one."));
|
|
1109
|
+
return;
|
|
1110
|
+
}
|
|
1098
1111
|
for (const j of journals) {
|
|
1099
1112
|
const entries = Object.entries(j.journal.files ?? {});
|
|
1100
1113
|
// Tombstones record removed files — exclude them from "tracked".
|
package/dist/commands/company.js
CHANGED
|
@@ -2,6 +2,7 @@ import chalk from "chalk";
|
|
|
2
2
|
import { ensureCognitoToken } from "../utils/cognito-session.js";
|
|
3
3
|
import { vaultApiFetch, getEntityUid } from "../utils/vault-api.js";
|
|
4
4
|
import { registerCompanyTransferCommand } from "./company-transfer.js";
|
|
5
|
+
import { describeRefreshOutcome, refreshCheckpointGateSetting, } from "./core-checkpoint-gate-setting.js";
|
|
5
6
|
/**
|
|
6
7
|
* Parse a `--flag <true|false>` string into a boolean, or throw. Commander
|
|
7
8
|
* passes the raw string; we validate strictly so a typo never silently writes
|
|
@@ -28,10 +29,12 @@ export function registerCompanyCommand(program) {
|
|
|
28
29
|
settings
|
|
29
30
|
.command("set")
|
|
30
31
|
.description("Set per-company settings (PUT /company-settings). Require at least one " +
|
|
31
|
-
"of --crm-enabled / --ontology-enabled / --accounting-enabled
|
|
32
|
+
"of --crm-enabled / --ontology-enabled / --accounting-enabled / " +
|
|
33
|
+
"--checkpoint-gate-enabled.")
|
|
32
34
|
.option("--crm-enabled <true|false>", "Enable/disable the native CRM for this company")
|
|
33
35
|
.option("--ontology-enabled <true|false>", "Enable/disable the ontology gardener for this company")
|
|
34
36
|
.option("--accounting-enabled <true|false>", "Enable/disable the accounting pack for this company")
|
|
37
|
+
.option("--checkpoint-gate-enabled <true|false>", "Require (true) or stop requiring (false) a checkpoint before HQ sessions in this company end a turn")
|
|
35
38
|
.action(async (opts) => {
|
|
36
39
|
try {
|
|
37
40
|
const companySlug = company.opts().company;
|
|
@@ -41,8 +44,9 @@ export function registerCompanyCommand(program) {
|
|
|
41
44
|
}
|
|
42
45
|
if (opts.crmEnabled === undefined &&
|
|
43
46
|
opts.ontologyEnabled === undefined &&
|
|
44
|
-
opts.accountingEnabled === undefined
|
|
45
|
-
|
|
47
|
+
opts.accountingEnabled === undefined &&
|
|
48
|
+
opts.checkpointGateEnabled === undefined) {
|
|
49
|
+
console.error(chalk.red("Error: provide at least one of --crm-enabled / --ontology-enabled / --accounting-enabled / --checkpoint-gate-enabled."));
|
|
46
50
|
process.exit(1);
|
|
47
51
|
}
|
|
48
52
|
const body = {};
|
|
@@ -55,6 +59,9 @@ export function registerCompanyCommand(program) {
|
|
|
55
59
|
if (opts.accountingEnabled !== undefined) {
|
|
56
60
|
body.accountingEnabled = parseBoolFlag(opts.accountingEnabled, "--accounting-enabled");
|
|
57
61
|
}
|
|
62
|
+
if (opts.checkpointGateEnabled !== undefined) {
|
|
63
|
+
body.checkpointGateEnabled = parseBoolFlag(opts.checkpointGateEnabled, "--checkpoint-gate-enabled");
|
|
64
|
+
}
|
|
58
65
|
const token = await ensureCognitoToken();
|
|
59
66
|
const companyUid = await getEntityUid(token, { companySlug });
|
|
60
67
|
body.companyUid = companyUid;
|
|
@@ -78,6 +85,62 @@ export function registerCompanyCommand(program) {
|
|
|
78
85
|
.map(([k, v]) => `${k}=${v}`)
|
|
79
86
|
.join(", ");
|
|
80
87
|
console.log(chalk.green(`Company settings updated for ${companySlug}: ${applied}`));
|
|
88
|
+
if (body.checkpointGateEnabled !== undefined) {
|
|
89
|
+
// Update this machine's Stop gate cache now instead of waiting up to
|
|
90
|
+
// ten minutes. Other machines pick the change up on their next refresh.
|
|
91
|
+
try {
|
|
92
|
+
const outcome = await refreshCheckpointGateSetting(companySlug);
|
|
93
|
+
console.log(chalk.dim(describeRefreshOutcome(outcome)));
|
|
94
|
+
}
|
|
95
|
+
catch {
|
|
96
|
+
// The setting is saved; the cache refreshes on the next Stop.
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
catch (err) {
|
|
101
|
+
console.error(chalk.red("Error:"), err instanceof Error ? err.message : String(err));
|
|
102
|
+
process.exit(1);
|
|
103
|
+
}
|
|
104
|
+
});
|
|
105
|
+
settings
|
|
106
|
+
.command("get")
|
|
107
|
+
.description("Show whether the checkpoint Stop gate is on for this company " +
|
|
108
|
+
"(GET /company-settings/checkpoint-gate; any member may read it).")
|
|
109
|
+
.option("--json", "Print the result as JSON")
|
|
110
|
+
.action(async (opts) => {
|
|
111
|
+
try {
|
|
112
|
+
const companySlug = company.opts().company;
|
|
113
|
+
if (!companySlug) {
|
|
114
|
+
console.error(chalk.red("Error: --company <slug> is required."));
|
|
115
|
+
process.exit(1);
|
|
116
|
+
}
|
|
117
|
+
const token = await ensureCognitoToken();
|
|
118
|
+
const companyUid = await getEntityUid(token, { companySlug });
|
|
119
|
+
const res = await vaultApiFetch({
|
|
120
|
+
token,
|
|
121
|
+
path: "/company-settings/checkpoint-gate",
|
|
122
|
+
query: { companyUid },
|
|
123
|
+
});
|
|
124
|
+
const resBody = (await res.json().catch(() => ({})));
|
|
125
|
+
if (!res.ok) {
|
|
126
|
+
if (res.status === 403) {
|
|
127
|
+
console.error(chalk.red("Requires membership of this company to read its settings."));
|
|
128
|
+
process.exit(1);
|
|
129
|
+
}
|
|
130
|
+
console.error(chalk.red(`Failed to read company settings: ${resBody.error ?? res.statusText}`));
|
|
131
|
+
process.exit(1);
|
|
132
|
+
}
|
|
133
|
+
const enabled = resBody.checkpointGateEnabled;
|
|
134
|
+
if (typeof enabled !== "boolean") {
|
|
135
|
+
console.error(chalk.red("Failed to read company settings: no checkpointGateEnabled in the response."));
|
|
136
|
+
process.exit(1);
|
|
137
|
+
}
|
|
138
|
+
if (opts.json) {
|
|
139
|
+
console.log(JSON.stringify({ company: companySlug, checkpointGateEnabled: enabled }));
|
|
140
|
+
}
|
|
141
|
+
else {
|
|
142
|
+
console.log(`checkpointGateEnabled=${enabled}`);
|
|
143
|
+
}
|
|
81
144
|
}
|
|
82
145
|
catch (err) {
|
|
83
146
|
console.error(chalk.red("Error:"), err instanceof Error ? err.message : String(err));
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `hq core checkpoint-gate-setting` — keeps the local cache that lets a
|
|
3
|
+
* company owner turn the checkpoint Stop gate off for their company (CO-58).
|
|
4
|
+
*
|
|
5
|
+
* The owner sets `companySettings.checkpointGateEnabled` on hq-pro
|
|
6
|
+
* (`hq company settings set --checkpoint-gate-enabled false`, or the console
|
|
7
|
+
* Settings page). The Stop gate script never calls the network: it reads
|
|
8
|
+
* `~/.hq/cache/checkpoint-gate/<slug>.json` and, when that is missing or older
|
|
9
|
+
* than ten minutes, starts this command detached with `--refresh`.
|
|
10
|
+
*
|
|
11
|
+
* `--refresh` reads the setting through the member-readable
|
|
12
|
+
* `GET /company-settings/checkpoint-gate` route and writes the cache only while
|
|
13
|
+
* the hq-flag `checkpoint.gate.company-setting` is on for the company. With the
|
|
14
|
+
* flag off it removes the cache, so the gate keeps today's behaviour. Any
|
|
15
|
+
* failure (no login, API unreachable, timeout) leaves the cache as it was: the
|
|
16
|
+
* gate then uses the last known value, or today's behaviour when there is none.
|
|
17
|
+
* Only the named company's setting is ever read.
|
|
18
|
+
*/
|
|
19
|
+
import { Command } from "commander";
|
|
20
|
+
export declare const CHECKPOINT_GATE_SETTING_FLAG = "checkpoint.gate.company-setting";
|
|
21
|
+
/** The refresh gives up after this long; the gate keeps the cached value. */
|
|
22
|
+
export declare const CHECKPOINT_GATE_REFRESH_TIMEOUT_MS = 3000;
|
|
23
|
+
export declare function assertCompanySlug(slug: string): void;
|
|
24
|
+
export interface CheckpointGateCacheEntry {
|
|
25
|
+
company: string;
|
|
26
|
+
checkpointGateEnabled: boolean;
|
|
27
|
+
/** Unix seconds. The gate treats an entry older than 600 s as stale. */
|
|
28
|
+
fetched_at: number;
|
|
29
|
+
}
|
|
30
|
+
export declare function checkpointGateCacheDir(home?: string): string;
|
|
31
|
+
export declare function checkpointGateCachePath(slug: string, home?: string): string;
|
|
32
|
+
export type CheckpointGateRefreshOutcome = {
|
|
33
|
+
status: "written";
|
|
34
|
+
company: string;
|
|
35
|
+
checkpointGateEnabled: boolean;
|
|
36
|
+
} | {
|
|
37
|
+
status: "removed";
|
|
38
|
+
company: string;
|
|
39
|
+
reason: "flag_off";
|
|
40
|
+
} | {
|
|
41
|
+
status: "kept";
|
|
42
|
+
company: string;
|
|
43
|
+
reason: string;
|
|
44
|
+
};
|
|
45
|
+
export interface CheckpointGateRefreshDeps {
|
|
46
|
+
home?: string;
|
|
47
|
+
now?: () => Date;
|
|
48
|
+
timeoutMs?: number;
|
|
49
|
+
getToken?: () => Promise<string>;
|
|
50
|
+
resolveCompanyUid?: (token: string, slug: string) => Promise<string>;
|
|
51
|
+
/** Returns the company's effective setting; throws on any failure. */
|
|
52
|
+
fetchSetting?: (token: string, companyUid: string, signal: AbortSignal) => Promise<boolean>;
|
|
53
|
+
/** Returns the rollout flag, or null when the registry could not be read. */
|
|
54
|
+
readFlag?: (slug: string, companyUid: string) => Promise<boolean | null>;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* `readLanesRegistryFlag` resolves a failed registry read to `false`. The
|
|
58
|
+
* refresh must not treat that as "flag off" and delete a valid cache, so a
|
|
59
|
+
* reported failure becomes null (unknown).
|
|
60
|
+
*/
|
|
61
|
+
export declare function readCheckpointGateSettingFlag(slug: string, companyUid: string): Promise<boolean | null>;
|
|
62
|
+
/**
|
|
63
|
+
* Refresh one company's cache entry. Never throws for a network or auth
|
|
64
|
+
* failure: those return `kept` and leave the cache untouched.
|
|
65
|
+
*/
|
|
66
|
+
export declare function refreshCheckpointGateSetting(slug: string, deps?: CheckpointGateRefreshDeps): Promise<CheckpointGateRefreshOutcome>;
|
|
67
|
+
export declare function describeRefreshOutcome(outcome: CheckpointGateRefreshOutcome): string;
|
|
68
|
+
export declare function registerCoreCheckpointGateSettingCommand(core: Command): void;
|
|
69
|
+
//# sourceMappingURL=core-checkpoint-gate-setting.d.ts.map
|