@indigoai-us/hq-cli 5.345.171 → 5.345.173
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 +34 -0
- package/dist/command-catalog.generated.d.ts +74 -2
- package/dist/command-catalog.generated.js +88 -2
- package/dist/command-registration-plan.d.ts +7 -0
- package/dist/command-registration-plan.js +1 -0
- package/dist/commands/bot.js +46 -8
- package/dist/commands/cloud.js +9 -5
- package/dist/commands/daemon.js +16 -1
- package/dist/commands/files.d.ts +1 -0
- package/dist/commands/files.js +22 -4
- package/dist/commands/sync-core.d.ts +22 -0
- package/dist/commands/sync-core.js +126 -0
- package/dist/hq-cloud-sync-runner/bin/sync-runner.js +278 -224
- package/dist/hq-cloud-sync-runner/package.json +1 -1
- package/dist/lib/bot/fleet-local.d.ts +16 -1
- package/dist/lib/bot/fleet-local.js +99 -48
- package/dist/lib/bot/fleet-python.d.ts +47 -0
- package/dist/lib/bot/fleet-python.js +218 -0
- package/dist/lib/bot/index.d.ts +1 -0
- package/dist/lib/bot/index.js +1 -0
- package/dist/lib/bot/prompt.d.ts +6 -1
- package/dist/lib/bot/prompt.js +8 -3
- package/dist/lib/daemon/catalog.d.ts +12 -4
- package/dist/lib/daemon/catalog.js +70 -27
- package/dist/lib/daemon/config.d.ts +0 -7
- package/dist/lib/daemon/config.js +0 -32
- package/dist/lib/daemon/run.d.ts +5 -1
- package/dist/lib/daemon/run.js +48 -11
- package/dist/lib/daemon/supervisor.js +3 -1
- package/dist/lib/sync-core/engine-home.d.ts +45 -0
- package/dist/lib/sync-core/engine-home.js +194 -0
- package/dist/lib/sync-core/flag.d.ts +17 -0
- package/dist/lib/sync-core/flag.js +47 -0
- package/dist/lib/sync-core/reconcile.d.ts +31 -0
- package/dist/lib/sync-core/reconcile.js +217 -0
- package/dist/lib/sync-core/release.d.ts +32 -0
- package/dist/lib/sync-core/release.js +142 -0
- package/dist/lib/sync-core/rollout-state.d.ts +28 -0
- package/dist/lib/sync-core/rollout-state.js +115 -0
- package/dist/lib/sync-core/skip.d.ts +7 -0
- package/dist/lib/sync-core/skip.js +30 -0
- package/dist/lib/vault-sync-switches.d.ts +5 -0
- package/dist/lib/vault-sync-switches.js +43 -0
- package/dist/utils/remote-list-not-converging.d.ts +21 -0
- package/dist/utils/remote-list-not-converging.js +40 -0
- package/package.json +8 -8
- package/dist/lib/daemon/sync-core-canary.d.ts +0 -12
- package/dist/lib/daemon/sync-core-canary.js +0 -61
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,40 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [5.345.173] — 2026-10-27
|
|
6
|
+
- The sync-core reconcile now counts the conflict and merge actions in a vault's read-only plan as writes: a vault is held read-only while its plan would write more than 100 conflict or merge copies, the count is recorded next to the plan's upload, download and delete counts in `~/.hq/sync-core/rollout.json`, and `hq sync-core status` prints it.
|
|
7
|
+
- A machine can keep one vault off the new sync engine while its company switch is on: `~/.hq/sync-core/skip.json` with `{"vaults": {"<vault uid>": "<reason>"}}` makes the reconcile leave that vault on the current sync (stopping its engine unit if one ran), record the uid and reason under `skipped` in `rollout.json`, and show it in `hq sync-core status`. A malformed file is reported as a warning and skips nothing.
|
|
8
|
+
- The reconcile never downgrades the engine: when `~/.hq/bin/hq-syncd.release` names a release newer than the pinned one and the binary is present, it keeps that binary and runs the engine units with it.
|
|
9
|
+
|
|
10
|
+
## [5.345.172] — 2026-10-27
|
|
11
|
+
- The daemon now moves vaults to the new sync engine by itself, following a per-company switch (`sync.rust-engine-rollout`) instead of a hand-written `syncCoreCanary` entry in `~/.hq/daemon/config.json`. A new `sync-core` service on desktop and self-hosted machines runs `hq daemon service sync-core-reconcile` every 10 minutes: it reads the switch for every company this machine belongs to, downloads the pinned hq-syncd release (v0.1.13) from the public mirror and checks it against the release's SHA256SUMS, installs it at `~/.hq/bin/hq-syncd`, writes one engine home per switched vault under `~/.hq/sync-core/engines/<vault>/`, and records the result in `~/.hq/sync-core/rollout.json`. Each vault's engine first runs read-only alongside the current sync until it has built its index and planned a pass; the reconcile records that plan's upload, download, delete, server-row and local-file counts per vault (`hq sync-core status` shows them). A vault is then promoted to full mode and turned off in the current sync (`menubar.json`) only when the plan would upload or delete at most 100 paths, and the daemon runs its `sync-core:<vault>` unit in full mode only after the current sync has stopped touching that vault. Company vaults stay read-only in this release: the engine's server snapshot is filtered by the caller's visibility while the current sync pulled the whole tree, so a full-mode company engine would upload the difference; promotion waits for an engine release that scopes the local tree the same way. The personal vault moves when at least one of the person's companies is switched on. When a switch turns off, the daemon stops that vault's engine, turns the current sync back on for it, and leaves the engine's index on disk. A switch that cannot be read keeps the vault where it was. `hq sync-core reconcile` and `hq sync-core status` run the same logic by hand (agent boxes run `reconcile --companies-only`). The old `syncCoreCanary` config key is ignored.
|
|
12
|
+
|
|
13
|
+
- `hq files share 'clients/*/11_reports' --with <person>` now sends the path as typed instead of refusing it. The vault accepts one `*` standing for a whole folder name, so a single grant covers the `11_reports` folder under every client. A `*` inside a folder name (`acme*`), two wildcard folders, or a path made only of wildcards is still refused by the vault. Feedback 600e747f-e608-4266-8e1a-a9a9a4034828.
|
|
14
|
+
- A bot created with `--hq-fleet-runtime` now starts with HQ knowledge: its standing HQ instructions (identity, owner, HQ folder, worker definition, persona and memory rules) become the runtime persona, the HQ folder's skills are available to it, and the runtime's own lifecycle and system notices no longer reach the conversation. The CLI checks those instructions with the runtime's own scanner before saving them and refreshes them from the worker folder on every start and restart. `hq bot list` and `hq bot status` read a runtime bot's process from its gateway, and the HQ background service no longer starts a second, legacy copy of a runtime bot. Pins hq-fleet-local v0.4.5.
|
|
15
|
+
- Creating a bot with `--hq-fleet-runtime` no longer needs Python on the computer: the CLI installs a private,
|
|
16
|
+
pinned Python under the HQ fleet folder and builds the bot's runtime from it, on macOS, Linux and Windows.
|
|
17
|
+
- `hq-hook` 0.3.1: the bridge runs a hook port only for a bash script version
|
|
18
|
+
the port was checked against. It hashes the `--script` it is given, and any
|
|
19
|
+
other version of `policy-enforcement-gate.sh` runs bash alone (logged as
|
|
20
|
+
`script-mismatch`), so an hq-core change to the gate is never answered by
|
|
21
|
+
an older engine.
|
|
22
|
+
- The gate port now opens only the path column of the policy list, as bash
|
|
23
|
+
does, and never the excerpt text.
|
|
24
|
+
- On Windows the gate port goes to bash for:
|
|
25
|
+
- UNC, device and MSYS-form policy paths, and for root forms Win32 and MSYS
|
|
26
|
+
read differently;
|
|
27
|
+
- a state folder it cannot create;
|
|
28
|
+
- Windows device names used as session ids.
|
|
29
|
+
- The pass stamp records the bytes the decision read.
|
|
30
|
+
- The nightly `hq-hook` workflow now runs the gate parity test on every target,
|
|
31
|
+
using Git Bash on Windows.
|
|
32
|
+
- In `on` mode, a hook port that declines a payload makes `hq-hook bridge`
|
|
33
|
+
exit 75 with no output. master-hook then runs the bash hook itself, so the
|
|
34
|
+
engine no longer starts bash for those calls. On Windows this removes about
|
|
35
|
+
200 to 330 ms from every gate call the port declines.
|
|
36
|
+
- Agent sign-in no longer hangs on a stale network connection (hq-cloud 6.18.103).
|
|
37
|
+
- When a company's sync stops because HQ could not finish listing the company's vault files (error code `REMOTE_LIST_NOT_CONVERGING` from hq-cloud), `hq sync now`, `hq sync push`, `hq sync pull` and their `--all` and `--personal` forms now print "HQ could not finish listing this company's files. Try hq sync now again in a minute; if it keeps happening, run hq feedback bug." with the original engine message on a following `Detail:` line, instead of only the raw engine message. `hq sync push --json` carries the same text in its `fatal` event. Menubar and background sync use the sync runner bundled from hq-cloud, which reports per-company errors as plain text without the code, so it still shows the engine message. Feedback e5eda195-9191-49b7-a73c-886f8139670b (DEV-5465).
|
|
38
|
+
|
|
5
39
|
## [5.345.171] — 2026-10-26
|
|
6
40
|
- The daemon's sync-core unit now accepts a `~/.hq/sync-core/config.toml` that sets `full_vault = true` instead of `canary_path`, so the Rust engine can keep the whole personal vault in sync under the same supervision (ownership check, legacy personal sync paused first, health restarts). A config that sets both keys, or neither, keeps the unit blocked with a reason that names the fix. `hq daemon status` describes the unit as the personal vault rather than a canary, and the unit's environment carries `HQ_SYNC_FULL_VAULT=1`. Company vaults and machines without the sync-core setting are unchanged. Requires an hq-sync-core release that understands `full_vault` (after 0.1.6).
|
|
7
41
|
|
|
@@ -3051,7 +3051,7 @@ export declare const COMMAND_CATALOG: readonly [{
|
|
|
3051
3051
|
}];
|
|
3052
3052
|
}, {
|
|
3053
3053
|
readonly name: "files";
|
|
3054
|
-
readonly description: "Manage file access controls in HQ vault. A trailing folder slash (foo/) is a private folder; the quoted glob 'foo/*' shares all children.";
|
|
3054
|
+
readonly description: "Manage file access controls in HQ vault. A trailing folder slash (foo/) is a private folder; the quoted glob 'foo/*' shares all children, and a quoted 'clients/*/reports' shares that folder under every client.";
|
|
3055
3055
|
readonly aliases: readonly [];
|
|
3056
3056
|
readonly hidden: false;
|
|
3057
3057
|
readonly helpOption: true;
|
|
@@ -3066,7 +3066,7 @@ export declare const COMMAND_CATALOG: readonly [{
|
|
|
3066
3066
|
}];
|
|
3067
3067
|
readonly subcommands: readonly [{
|
|
3068
3068
|
readonly name: "share";
|
|
3069
|
-
readonly description: "Share file paths. foo/ is a private folder (children stay creator-only); quote the shared-folder glob 'foo/*' so the shell passes it literally. Without --with: mint a share-session URL and open it in the browser. With --with: grant access directly to a person, group, or @all.";
|
|
3069
|
+
readonly description: "Share file paths. foo/ is a private folder (children stay creator-only); quote the shared-folder glob 'foo/*', or a one-folder wildcard such as 'clients/*/reports', so the shell passes it literally. Without --with: mint a share-session URL and open it in the browser. With --with: grant access directly to a person, group, or @all.";
|
|
3070
3070
|
readonly aliases: readonly [];
|
|
3071
3071
|
readonly hidden: false;
|
|
3072
3072
|
readonly helpOption: true;
|
|
@@ -18341,6 +18341,22 @@ export declare const COMMAND_CATALOG: readonly [{
|
|
|
18341
18341
|
readonly arguments: readonly [];
|
|
18342
18342
|
readonly options: readonly [];
|
|
18343
18343
|
readonly subcommands: readonly [];
|
|
18344
|
+
}, {
|
|
18345
|
+
readonly name: "sync-core-reconcile";
|
|
18346
|
+
readonly description: "Read each company's new-sync-engine switch and write this machine's engine homes and legacy off-switches";
|
|
18347
|
+
readonly aliases: readonly [];
|
|
18348
|
+
readonly hidden: false;
|
|
18349
|
+
readonly helpOption: true;
|
|
18350
|
+
readonly helpCommand: false;
|
|
18351
|
+
readonly usage: "[options]";
|
|
18352
|
+
readonly helpText: readonly [];
|
|
18353
|
+
readonly arguments: readonly [];
|
|
18354
|
+
readonly options: readonly [{
|
|
18355
|
+
readonly flags: "--hq-root <path>";
|
|
18356
|
+
readonly description: "HQ folder the engine works against";
|
|
18357
|
+
readonly hidden: false;
|
|
18358
|
+
}];
|
|
18359
|
+
readonly subcommands: readonly [];
|
|
18344
18360
|
}, {
|
|
18345
18361
|
readonly name: "remote-control";
|
|
18346
18362
|
readonly description: "Keep `claude remote-control` running in a tmux session in the HQ folder";
|
|
@@ -18463,6 +18479,62 @@ export declare const COMMAND_CATALOG: readonly [{
|
|
|
18463
18479
|
readonly subcommands: readonly [];
|
|
18464
18480
|
}];
|
|
18465
18481
|
}];
|
|
18482
|
+
}, {
|
|
18483
|
+
readonly name: "sync-core";
|
|
18484
|
+
readonly description: "Move this machine's vaults between the current sync and the new sync engine, following each company's switch";
|
|
18485
|
+
readonly aliases: readonly [];
|
|
18486
|
+
readonly hidden: true;
|
|
18487
|
+
readonly helpOption: true;
|
|
18488
|
+
readonly helpCommand: true;
|
|
18489
|
+
readonly usage: "[options] [command]";
|
|
18490
|
+
readonly helpText: readonly [];
|
|
18491
|
+
readonly arguments: readonly [];
|
|
18492
|
+
readonly options: readonly [];
|
|
18493
|
+
readonly subcommands: readonly [{
|
|
18494
|
+
readonly name: "reconcile";
|
|
18495
|
+
readonly description: "Read each company's switch, install the engine when needed, and write the engine homes and legacy off-switches";
|
|
18496
|
+
readonly aliases: readonly [];
|
|
18497
|
+
readonly hidden: false;
|
|
18498
|
+
readonly helpOption: true;
|
|
18499
|
+
readonly helpCommand: false;
|
|
18500
|
+
readonly usage: "[options]";
|
|
18501
|
+
readonly helpText: readonly [];
|
|
18502
|
+
readonly arguments: readonly [];
|
|
18503
|
+
readonly options: readonly [{
|
|
18504
|
+
readonly flags: "--hq-root <path>";
|
|
18505
|
+
readonly description: "HQ folder (default: the daemon's)";
|
|
18506
|
+
readonly hidden: false;
|
|
18507
|
+
}, {
|
|
18508
|
+
readonly flags: "--companies-only";
|
|
18509
|
+
readonly description: "Never move the personal vault (agent boxes)";
|
|
18510
|
+
readonly hidden: false;
|
|
18511
|
+
}, {
|
|
18512
|
+
readonly flags: "--token-file <path>";
|
|
18513
|
+
readonly description: "Token file the engine reads (default: this user's hq sign-in)";
|
|
18514
|
+
readonly hidden: false;
|
|
18515
|
+
}, {
|
|
18516
|
+
readonly flags: "--json";
|
|
18517
|
+
readonly description: "Print the written rollout state as JSON";
|
|
18518
|
+
readonly hidden: false;
|
|
18519
|
+
}];
|
|
18520
|
+
readonly subcommands: readonly [];
|
|
18521
|
+
}, {
|
|
18522
|
+
readonly name: "status";
|
|
18523
|
+
readonly description: "Show which vaults are on the new sync engine on this machine";
|
|
18524
|
+
readonly aliases: readonly [];
|
|
18525
|
+
readonly hidden: false;
|
|
18526
|
+
readonly helpOption: true;
|
|
18527
|
+
readonly helpCommand: false;
|
|
18528
|
+
readonly usage: "[options]";
|
|
18529
|
+
readonly helpText: readonly [];
|
|
18530
|
+
readonly arguments: readonly [];
|
|
18531
|
+
readonly options: readonly [{
|
|
18532
|
+
readonly flags: "--json";
|
|
18533
|
+
readonly description: "Print JSON";
|
|
18534
|
+
readonly hidden: false;
|
|
18535
|
+
}];
|
|
18536
|
+
readonly subcommands: readonly [];
|
|
18537
|
+
}];
|
|
18466
18538
|
}, {
|
|
18467
18539
|
readonly name: "bot";
|
|
18468
18540
|
readonly description: "Personal local bots that run on this computer with your own model login";
|
|
@@ -3674,7 +3674,7 @@ export const COMMAND_CATALOG = [
|
|
|
3674
3674
|
},
|
|
3675
3675
|
{
|
|
3676
3676
|
"name": "files",
|
|
3677
|
-
"description": "Manage file access controls in HQ vault. A trailing folder slash (foo/) is a private folder; the quoted glob 'foo/*' shares all children.",
|
|
3677
|
+
"description": "Manage file access controls in HQ vault. A trailing folder slash (foo/) is a private folder; the quoted glob 'foo/*' shares all children, and a quoted 'clients/*/reports' shares that folder under every client.",
|
|
3678
3678
|
"aliases": [],
|
|
3679
3679
|
"hidden": false,
|
|
3680
3680
|
"helpOption": true,
|
|
@@ -3692,7 +3692,7 @@ export const COMMAND_CATALOG = [
|
|
|
3692
3692
|
"subcommands": [
|
|
3693
3693
|
{
|
|
3694
3694
|
"name": "share",
|
|
3695
|
-
"description": "Share file paths. foo/ is a private folder (children stay creator-only); quote the shared-folder glob 'foo/*' so the shell passes it literally. Without --with: mint a share-session URL and open it in the browser. With --with: grant access directly to a person, group, or @all.",
|
|
3695
|
+
"description": "Share file paths. foo/ is a private folder (children stay creator-only); quote the shared-folder glob 'foo/*', or a one-folder wildcard such as 'clients/*/reports', so the shell passes it literally. Without --with: mint a share-session URL and open it in the browser. With --with: grant access directly to a person, group, or @all.",
|
|
3696
3696
|
"aliases": [],
|
|
3697
3697
|
"hidden": false,
|
|
3698
3698
|
"helpOption": true,
|
|
@@ -22349,6 +22349,25 @@ export const COMMAND_CATALOG = [
|
|
|
22349
22349
|
"options": [],
|
|
22350
22350
|
"subcommands": []
|
|
22351
22351
|
},
|
|
22352
|
+
{
|
|
22353
|
+
"name": "sync-core-reconcile",
|
|
22354
|
+
"description": "Read each company's new-sync-engine switch and write this machine's engine homes and legacy off-switches",
|
|
22355
|
+
"aliases": [],
|
|
22356
|
+
"hidden": false,
|
|
22357
|
+
"helpOption": true,
|
|
22358
|
+
"helpCommand": false,
|
|
22359
|
+
"usage": "[options]",
|
|
22360
|
+
"helpText": [],
|
|
22361
|
+
"arguments": [],
|
|
22362
|
+
"options": [
|
|
22363
|
+
{
|
|
22364
|
+
"flags": "--hq-root <path>",
|
|
22365
|
+
"description": "HQ folder the engine works against",
|
|
22366
|
+
"hidden": false
|
|
22367
|
+
}
|
|
22368
|
+
],
|
|
22369
|
+
"subcommands": []
|
|
22370
|
+
},
|
|
22352
22371
|
{
|
|
22353
22372
|
"name": "remote-control",
|
|
22354
22373
|
"description": "Keep `claude remote-control` running in a tmux session in the HQ folder",
|
|
@@ -22497,6 +22516,73 @@ export const COMMAND_CATALOG = [
|
|
|
22497
22516
|
}
|
|
22498
22517
|
]
|
|
22499
22518
|
},
|
|
22519
|
+
{
|
|
22520
|
+
"name": "sync-core",
|
|
22521
|
+
"description": "Move this machine's vaults between the current sync and the new sync engine, following each company's switch",
|
|
22522
|
+
"aliases": [],
|
|
22523
|
+
"hidden": true,
|
|
22524
|
+
"helpOption": true,
|
|
22525
|
+
"helpCommand": true,
|
|
22526
|
+
"usage": "[options] [command]",
|
|
22527
|
+
"helpText": [],
|
|
22528
|
+
"arguments": [],
|
|
22529
|
+
"options": [],
|
|
22530
|
+
"subcommands": [
|
|
22531
|
+
{
|
|
22532
|
+
"name": "reconcile",
|
|
22533
|
+
"description": "Read each company's switch, install the engine when needed, and write the engine homes and legacy off-switches",
|
|
22534
|
+
"aliases": [],
|
|
22535
|
+
"hidden": false,
|
|
22536
|
+
"helpOption": true,
|
|
22537
|
+
"helpCommand": false,
|
|
22538
|
+
"usage": "[options]",
|
|
22539
|
+
"helpText": [],
|
|
22540
|
+
"arguments": [],
|
|
22541
|
+
"options": [
|
|
22542
|
+
{
|
|
22543
|
+
"flags": "--hq-root <path>",
|
|
22544
|
+
"description": "HQ folder (default: the daemon's)",
|
|
22545
|
+
"hidden": false
|
|
22546
|
+
},
|
|
22547
|
+
{
|
|
22548
|
+
"flags": "--companies-only",
|
|
22549
|
+
"description": "Never move the personal vault (agent boxes)",
|
|
22550
|
+
"hidden": false
|
|
22551
|
+
},
|
|
22552
|
+
{
|
|
22553
|
+
"flags": "--token-file <path>",
|
|
22554
|
+
"description": "Token file the engine reads (default: this user's hq sign-in)",
|
|
22555
|
+
"hidden": false
|
|
22556
|
+
},
|
|
22557
|
+
{
|
|
22558
|
+
"flags": "--json",
|
|
22559
|
+
"description": "Print the written rollout state as JSON",
|
|
22560
|
+
"hidden": false
|
|
22561
|
+
}
|
|
22562
|
+
],
|
|
22563
|
+
"subcommands": []
|
|
22564
|
+
},
|
|
22565
|
+
{
|
|
22566
|
+
"name": "status",
|
|
22567
|
+
"description": "Show which vaults are on the new sync engine on this machine",
|
|
22568
|
+
"aliases": [],
|
|
22569
|
+
"hidden": false,
|
|
22570
|
+
"helpOption": true,
|
|
22571
|
+
"helpCommand": false,
|
|
22572
|
+
"usage": "[options]",
|
|
22573
|
+
"helpText": [],
|
|
22574
|
+
"arguments": [],
|
|
22575
|
+
"options": [
|
|
22576
|
+
{
|
|
22577
|
+
"flags": "--json",
|
|
22578
|
+
"description": "Print JSON",
|
|
22579
|
+
"hidden": false
|
|
22580
|
+
}
|
|
22581
|
+
],
|
|
22582
|
+
"subcommands": []
|
|
22583
|
+
}
|
|
22584
|
+
]
|
|
22585
|
+
},
|
|
22500
22586
|
{
|
|
22501
22587
|
"name": "bot",
|
|
22502
22588
|
"description": "Personal local bots that run on this computer with your own model login",
|
|
@@ -515,6 +515,13 @@ export declare const REGISTRATION_PLAN: readonly [{
|
|
|
515
515
|
readonly module: "./commands/daemon.js";
|
|
516
516
|
readonly exportName: "registerDaemonCommand";
|
|
517
517
|
readonly hidden: true;
|
|
518
|
+
}, {
|
|
519
|
+
readonly type: "registrar";
|
|
520
|
+
readonly root: "sync-core";
|
|
521
|
+
readonly parent: "program";
|
|
522
|
+
readonly module: "./commands/sync-core.js";
|
|
523
|
+
readonly exportName: "registerSyncCoreCommand";
|
|
524
|
+
readonly hidden: true;
|
|
518
525
|
}, {
|
|
519
526
|
readonly type: "registrar";
|
|
520
527
|
readonly root: "bot";
|
|
@@ -88,6 +88,7 @@ export const REGISTRATION_PLAN = [
|
|
|
88
88
|
{ type: "registrar", root: "lanes", parent: "program", module: "./commands/lanes.js", exportName: "registerLanesCommand" },
|
|
89
89
|
{ type: "registrar", root: "monitor", parent: "program", module: "./commands/monitor.js", exportName: "registerMonitorCommand" },
|
|
90
90
|
{ type: "registrar", root: "daemon", parent: "program", module: "./commands/daemon.js", exportName: "registerDaemonCommand", hidden: true },
|
|
91
|
+
{ type: "registrar", root: "sync-core", parent: "program", module: "./commands/sync-core.js", exportName: "registerSyncCoreCommand", hidden: true },
|
|
91
92
|
{ type: "registrar", root: "bot", parent: "program", module: "./commands/bot.js", exportName: "registerBotCommand" },
|
|
92
93
|
{ type: "registrar", root: "report", parent: "program", module: "./commands/report.js", exportName: "registerReportCommand" },
|
|
93
94
|
{ type: "group", root: "deploy", id: "deploy", name: "deploy", description: "Manage deployments in hq-deploy" },
|
package/dist/commands/bot.js
CHANGED
|
@@ -52,7 +52,7 @@ import { isAncestorProcess } from "../lib/bot/self-process.js";
|
|
|
52
52
|
import { runtimeDisplayName } from "../lib/bot/runtime-sign-in.js";
|
|
53
53
|
import { classifyRuntimeFailure, runtimeFailureReason } from "../lib/bot/runtime-failure.js";
|
|
54
54
|
import { PROBE_DEFAULT_TIMEOUT_MS, probeBotRuntime } from "../lib/bot/probe.js";
|
|
55
|
-
import { BotApi, BotApiError, BOT_RESTORE_DEFAULTS, buildBotLocalConfig, describeBotLocalConfig, orphanBotRunMessage, parseBotLocalConfig, removeOrphanBotDaemon, botDaemonStatus, botDir, botCredsPath, botLogPath, botsRoot, botTokenStateDir, botWorkerRelDir, buildBotDaemonPaths, createBotLogger, deleteBotCreds, installBotDaemon, repairBotDaemonIfStale, healBotDaemonNodeFromJob, introDmText, isBotMemoryMode, botLocalMemoryDir, botMemoryMode, resolveBotMemoryDir, validateBotIntro, validateBotDisplayName, validateBotKickoff, validateBotEffort, validateBotModel, effectiveBotEffort, BOT_EFFORT_LEVELS, DEFAULT_BOT_EFFORT, BOT_MEMORY_MODES, BOT_KINDS, isBotKind, effectiveBotKind, effectiveBotCompanies, normalizeBotCompanies, SETUP_BOT_WORKER_ID, isBotRuntimeId, localNameForRemoteBot, remoteBotRunnability, isPidAlive, listBotWorkerOptions, patchBotConfig, readBotConfig, readBotCredsIdentity, readBotStatus, resolveBotWorker, runBot, runtimeFor, scaffoldBotMemory, scaffoldBotWorker, startBotDaemon, stopBotDaemon, uninstallBotDaemon, validateBotName, writeBotConfig, writeBotCreds, BOT_RUNTIMES, BOT_INTRO_MAX_CHARS, BOT_DISPLAY_NAME_MAX_CHARS, BOT_KICKOFF_MAX_CHARS, detectBotPlatform, withBotLock, localBotRole, remoteBotRole, remoteBotDisplayName, personFacingName, writeCloudDisplayName, setupSlugFor, ASSISTANT_ROLE, HQ_FLEET_RUNTIME_ENGINE_ID, authenticateFleetLocalBot, bootstrapFleetLocalBot, controlFleetLocalBot, fleetLocalPromotionRuntime, installPinnedFleetLocalDistribution, removeFleetLocalBot, } from "../lib/bot/index.js";
|
|
55
|
+
import { BotApi, BotApiError, BOT_RESTORE_DEFAULTS, buildBotLocalConfig, describeBotLocalConfig, orphanBotRunMessage, parseBotLocalConfig, removeOrphanBotDaemon, botDaemonStatus, botDir, botCredsPath, botLogPath, botsRoot, botTokenStateDir, botWorkerRelDir, buildBotDaemonPaths, createBotLogger, deleteBotCreds, installBotDaemon, repairBotDaemonIfStale, healBotDaemonNodeFromJob, introDmText, isBotMemoryMode, botLocalMemoryDir, botMemoryMode, resolveBotMemoryDir, validateBotIntro, validateBotDisplayName, validateBotKickoff, validateBotEffort, validateBotModel, effectiveBotEffort, BOT_EFFORT_LEVELS, DEFAULT_BOT_EFFORT, BOT_MEMORY_MODES, BOT_KINDS, isBotKind, effectiveBotKind, effectiveBotCompanies, normalizeBotCompanies, SETUP_BOT_WORKER_ID, isBotRuntimeId, localNameForRemoteBot, remoteBotRunnability, isPidAlive, listBotWorkerOptions, patchBotConfig, readBotConfig, readBotCredsIdentity, readBotStatus, resolveBotWorker, runBot, runtimeFor, scaffoldBotMemory, scaffoldBotWorker, startBotDaemon, stopBotDaemon, uninstallBotDaemon, validateBotName, writeBotConfig, writeBotCreds, BOT_RUNTIMES, BOT_INTRO_MAX_CHARS, BOT_DISPLAY_NAME_MAX_CHARS, BOT_KICKOFF_MAX_CHARS, detectBotPlatform, withBotLock, localBotRole, remoteBotRole, remoteBotDisplayName, personFacingName, writeCloudDisplayName, setupSlugFor, ASSISTANT_ROLE, HQ_FLEET_RUNTIME_ENGINE_ID, authenticateFleetLocalBot, bootstrapFleetLocalBot, buildSystemPrompt, controlFleetLocalBot, ensureFleetPython, fleetLocalGatewayPid, refreshFleetLocalInstructions, fleetLocalPromotionRuntime, installPinnedFleetLocalDistribution, readPromptSources, removeFleetLocalBot, } from "../lib/bot/index.js";
|
|
56
56
|
function collectRepeated(value, previous) {
|
|
57
57
|
return [...previous, value];
|
|
58
58
|
}
|
|
@@ -117,7 +117,37 @@ function requireBot(name) {
|
|
|
117
117
|
function isFleetRuntimeBot(config) {
|
|
118
118
|
return config.engine?.id === HQ_FLEET_RUNTIME_ENGINE_ID;
|
|
119
119
|
}
|
|
120
|
+
/**
|
|
121
|
+
* The standing HQ instructions an HQ Fleet Runtime bot runs under: the same
|
|
122
|
+
* text a legacy local bot gets, read fresh from its worker folder, with the
|
|
123
|
+
* runtime's own way of labelling senders.
|
|
124
|
+
*/
|
|
125
|
+
function fleetBotSystemPrompt(name, config, overrides = {}) {
|
|
126
|
+
return buildSystemPrompt({
|
|
127
|
+
botName: name,
|
|
128
|
+
...(config.displayName ? { displayName: config.displayName } : {}),
|
|
129
|
+
agentUid: config.agentUid,
|
|
130
|
+
ownerUid: config.ownerUid,
|
|
131
|
+
hqRoot: config.hqRoot,
|
|
132
|
+
workerDir: config.workerDir,
|
|
133
|
+
workerSource: config.workerSource,
|
|
134
|
+
workerId: config.workerId,
|
|
135
|
+
companySlug: config.companySlug,
|
|
136
|
+
kind: overrides.kind ?? effectiveBotKind(config),
|
|
137
|
+
companies: overrides.companies ?? effectiveBotCompanies(config),
|
|
138
|
+
memoryDir: config.memoryDir,
|
|
139
|
+
fleetRuntime: true,
|
|
140
|
+
sources: readPromptSources(config.hqRoot, config.workerDir, undefined, {
|
|
141
|
+
memoryDir: config.memoryDir,
|
|
142
|
+
workerSource: config.workerSource,
|
|
143
|
+
companySlug: config.companySlug,
|
|
144
|
+
}),
|
|
145
|
+
});
|
|
146
|
+
}
|
|
120
147
|
function runFleetRuntimeControl(name, config, action, json) {
|
|
148
|
+
if (action === "start" || action === "restart") {
|
|
149
|
+
refreshFleetLocalInstructions(config.agentUid, fleetBotSystemPrompt(name, config));
|
|
150
|
+
}
|
|
121
151
|
const result = controlFleetLocalBot(config.agentUid, action);
|
|
122
152
|
if (json) {
|
|
123
153
|
console.log(JSON.stringify({ name, runtime: HQ_FLEET_RUNTIME_ENGINE_ID, ...result }, null, 2));
|
|
@@ -149,15 +179,16 @@ async function ownerToken(interactive = process.stdin.isTTY === true) {
|
|
|
149
179
|
function rowFor(name, opts = {}) {
|
|
150
180
|
const dir = botDir(name);
|
|
151
181
|
const config = readBotConfig(dir);
|
|
152
|
-
const
|
|
153
|
-
const
|
|
154
|
-
const
|
|
182
|
+
const fleet = isFleetRuntimeBot(config);
|
|
183
|
+
const status = fleet ? null : readBotStatus(dir);
|
|
184
|
+
const pid = fleet ? fleetLocalGatewayPid(config.agentUid) : (status?.pid ?? null);
|
|
185
|
+
const processAlive = pid !== null && isPidAlive(pid) && (fleet || status?.state === "running");
|
|
155
186
|
// Turns this bot has taken and not finished: what "thinking" means.
|
|
156
187
|
const inflightTurns = readInflightTurns(dir);
|
|
157
188
|
let daemonInstalled = false;
|
|
158
189
|
let daemonLoaded = false;
|
|
159
190
|
let daemonNodeMissing = null;
|
|
160
|
-
if (opts.withDaemon !== false) {
|
|
191
|
+
if (opts.withDaemon !== false && !fleet) {
|
|
161
192
|
try {
|
|
162
193
|
const d = botDaemonStatus(buildBotDaemonPaths({ name, botDir: dir, hqRoot: config.hqRoot }), daemonDeps());
|
|
163
194
|
daemonInstalled = d.installed;
|
|
@@ -678,6 +709,7 @@ async function runBotCreateLocked(name, opts) {
|
|
|
678
709
|
const fleetInstallation = useFleetRuntime
|
|
679
710
|
? await installPinnedFleetLocalDistribution()
|
|
680
711
|
: null;
|
|
712
|
+
const fleetPython = fleetInstallation ? await ensureFleetPython() : null;
|
|
681
713
|
const token = await ownerToken();
|
|
682
714
|
const owner = await resolveCallerPersonUid(token, undefined, { command: "hq bot create" });
|
|
683
715
|
if (!owner)
|
|
@@ -912,15 +944,18 @@ async function runBotCreateLocked(name, opts) {
|
|
|
912
944
|
const scaffold = worker
|
|
913
945
|
? scaffoldBotMemory(hqRoot, name, undefined, scaffoldOpts)
|
|
914
946
|
: scaffoldBotWorker(hqRoot, name, agentUid, fleetInstallation ? HQ_FLEET_RUNTIME_ENGINE_ID : runtime, undefined, scaffoldOpts);
|
|
915
|
-
|
|
916
|
-
|
|
947
|
+
if (fleetInstallation)
|
|
948
|
+
fs.rmSync(path.join(dir, "status.json"), { force: true });
|
|
949
|
+
const fleetBootstrap = fleetInstallation && fleetPython
|
|
950
|
+
? await bootstrapFleetLocalBot({
|
|
917
951
|
agentUid,
|
|
918
952
|
ownerUid: config.ownerUid,
|
|
919
953
|
hqRoot,
|
|
920
954
|
botDir: dir,
|
|
921
955
|
installation: fleetInstallation,
|
|
922
956
|
machineCredentials: botCredsPath(dir),
|
|
923
|
-
|
|
957
|
+
systemPrompt: fleetBotSystemPrompt(name, config, { kind, companies }),
|
|
958
|
+
}, { start: opts.start !== false, python: fleetPython })
|
|
924
959
|
: null;
|
|
925
960
|
const fleetProfile = fleetBootstrap?.profile ?? null;
|
|
926
961
|
const runtimeLabel = fleetInstallation ? HQ_FLEET_RUNTIME_ENGINE_ID : runtime;
|
|
@@ -1076,6 +1111,9 @@ async function runBotResident(nameArg) {
|
|
|
1076
1111
|
const name = validateBotName(nameArg);
|
|
1077
1112
|
const dir = botDir(name);
|
|
1078
1113
|
const config = readBotConfig(dir);
|
|
1114
|
+
if (config && isFleetRuntimeBot(config)) {
|
|
1115
|
+
throw Object.assign(new Error(`${name} runs on the HQ Fleet Runtime under its own background service. Use hq bot start ${name}, hq bot status ${name} or hq bot logs ${name}.`), { expected: true });
|
|
1116
|
+
}
|
|
1079
1117
|
if (!config) {
|
|
1080
1118
|
// A LaunchAgent that outlived its bot: exiting non-zero here made launchd
|
|
1081
1119
|
// relaunch this every five seconds forever. Say so once, take the stale
|
package/dist/commands/cloud.js
CHANGED
|
@@ -13,6 +13,7 @@
|
|
|
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 { describeSyncError, formatRemoteListNotConverging, } from "../utils/remote-list-not-converging.js";
|
|
16
17
|
import { findReadOnlyLocalOnly, isGrantEnforced, readVaultAccessManifest, } from "../lib/sync/read-only-local-only.js";
|
|
17
18
|
import { SERVER_OWNED_FOLDERS, findServerOwnedLocalOnly, } from "../lib/sync/server-owned-local-only.js";
|
|
18
19
|
import chalk from "chalk";
|
|
@@ -74,6 +75,9 @@ export function formatSyncFailure(err, ctx = {}) {
|
|
|
74
75
|
const unreachable = formatUnreachablePushPaths(err, ctx);
|
|
75
76
|
if (unreachable)
|
|
76
77
|
return unreachable;
|
|
78
|
+
const remoteList = formatRemoteListNotConverging(err);
|
|
79
|
+
if (remoteList)
|
|
80
|
+
return remoteList;
|
|
77
81
|
// A plan-limit refusal the runner threw (a 402 on the whole request) renders
|
|
78
82
|
// with the same reason, fix options and upgrade link as every other refusal.
|
|
79
83
|
const planGate = planGateErrorFromVaultError(err);
|
|
@@ -1744,7 +1748,7 @@ export function registerCloudCommands(program) {
|
|
|
1744
1748
|
await withSyncHealthReport(() => runUnderSyncWatchdog(watchdogLimits, "hq sync now", () => runNowSingle(options.hqRoot, options.company, options.personal === true, options.message, options.onConflict, options.modeAll === true, options.forceScopeShrink === true)));
|
|
1745
1749
|
}
|
|
1746
1750
|
catch (err) {
|
|
1747
|
-
console.error(chalk.red("\n✗ Sync now failed:"),
|
|
1751
|
+
console.error(chalk.red("\n✗ Sync now failed:"), describeSyncError(err));
|
|
1748
1752
|
process.exit(1);
|
|
1749
1753
|
}
|
|
1750
1754
|
});
|
|
@@ -1894,7 +1898,7 @@ async function runPullAll(hqRoot, onConflict, modeAllOverride, skipPersonal, for
|
|
|
1894
1898
|
});
|
|
1895
1899
|
}
|
|
1896
1900
|
catch (err) {
|
|
1897
|
-
console.error(chalk.red("\n✗ Pull-all failed:"),
|
|
1901
|
+
console.error(chalk.red("\n✗ Pull-all failed:"), describeSyncError(err));
|
|
1898
1902
|
// Unwind (never process.exit) so withSyncHealthReport records the
|
|
1899
1903
|
// terminal sync_failure before exiting on our behalf.
|
|
1900
1904
|
throw new SyncExitError(1);
|
|
@@ -1963,7 +1967,7 @@ export async function runPullPersonal(hqRoot, onConflict) {
|
|
|
1963
1967
|
catch (err) {
|
|
1964
1968
|
if (err instanceof SyncExitError)
|
|
1965
1969
|
throw err;
|
|
1966
|
-
console.error(chalk.red("\n✗ Pull (personal) failed:"),
|
|
1970
|
+
console.error(chalk.red("\n✗ Pull (personal) failed:"), describeSyncError(err));
|
|
1967
1971
|
throw new SyncExitError(1);
|
|
1968
1972
|
}
|
|
1969
1973
|
}
|
|
@@ -2048,7 +2052,7 @@ async function runPushAll(hqRoot, message, onConflict, skipPersonal, skipCompani
|
|
|
2048
2052
|
});
|
|
2049
2053
|
}
|
|
2050
2054
|
catch (err) {
|
|
2051
|
-
console.error(chalk.red("\n✗ Push-all failed:"),
|
|
2055
|
+
console.error(chalk.red("\n✗ Push-all failed:"), describeSyncError(err));
|
|
2052
2056
|
// Unwind (never process.exit) so withSyncHealthReport records the
|
|
2053
2057
|
// terminal sync_failure before exiting on our behalf.
|
|
2054
2058
|
throw new SyncExitError(1);
|
|
@@ -2399,7 +2403,7 @@ async function runNowSingle(hqRoot, company, personal, message, onConflict, mode
|
|
|
2399
2403
|
catch (err) {
|
|
2400
2404
|
if (err instanceof SyncExitError)
|
|
2401
2405
|
throw err;
|
|
2402
|
-
console.error(chalk.red("\n✗ Sync now failed:"),
|
|
2406
|
+
console.error(chalk.red("\n✗ Sync now failed:"), describeSyncError(err));
|
|
2403
2407
|
throw new SyncExitError(1);
|
|
2404
2408
|
}
|
|
2405
2409
|
}
|
package/dist/commands/daemon.js
CHANGED
|
@@ -426,7 +426,7 @@ function formatUnit(u) {
|
|
|
426
426
|
? `last run ${u.lastResult}`
|
|
427
427
|
: (u.reason ?? "");
|
|
428
428
|
const restarts = u.restarts > 0 ? ` (restarted ${u.restarts}x)` : "";
|
|
429
|
-
return ` ${u.id.padEnd(
|
|
429
|
+
return ` ${u.id.padEnd(40)} ${u.status.padEnd(12)} ${detail}${restarts}`;
|
|
430
430
|
}
|
|
431
431
|
async function runStatus(d, opts) {
|
|
432
432
|
const host = currentHost(d);
|
|
@@ -1538,6 +1538,21 @@ export function registerDaemonCommand(program, over = {}) {
|
|
|
1538
1538
|
.command("self-update")
|
|
1539
1539
|
.description("Update the hq CLI, Claude, Codex and Grok, then restart the daemon onto a new CLI")
|
|
1540
1540
|
.action(wrap((d) => runServiceSelfUpdate(d)));
|
|
1541
|
+
service
|
|
1542
|
+
.command("sync-core-reconcile")
|
|
1543
|
+
.description("Read each company's new-sync-engine switch and write this machine's engine homes and legacy off-switches")
|
|
1544
|
+
.option("--hq-root <path>", "HQ folder the engine works against")
|
|
1545
|
+
.action(wrap(async (d, opts) => {
|
|
1546
|
+
const { runSyncCoreReconcile } = await import("./sync-core.js");
|
|
1547
|
+
await runSyncCoreReconcile({ hqRoot: opts.hqRoot }, {
|
|
1548
|
+
home: d.home,
|
|
1549
|
+
env: d.env,
|
|
1550
|
+
platform: d.platform,
|
|
1551
|
+
arch: process.arch,
|
|
1552
|
+
out: d.out,
|
|
1553
|
+
err: d.err,
|
|
1554
|
+
});
|
|
1555
|
+
}));
|
|
1541
1556
|
service
|
|
1542
1557
|
.command("remote-control")
|
|
1543
1558
|
.description("Keep `claude remote-control` running in a tmux session in the HQ folder")
|
package/dist/commands/files.d.ts
CHANGED
|
@@ -78,6 +78,7 @@ export declare function registerFilesCommand(program: Command): Command;
|
|
|
78
78
|
* whether it is a folder (`Makefile`, `bin/hq`); resolveBareSharePrefix asks
|
|
79
79
|
* the vault.
|
|
80
80
|
*/
|
|
81
|
+
export declare function isSingleSegmentWildcardPrefix(prefix: string): boolean;
|
|
81
82
|
export declare function looksLikeBareFolderPrefix(prefix: string): boolean;
|
|
82
83
|
/**
|
|
83
84
|
* Refuse a bare prefix that would become a malformed grant if it were
|
package/dist/commands/files.js
CHANGED
|
@@ -164,7 +164,7 @@ export function formatShareSessionError(err) {
|
|
|
164
164
|
export function registerFilesCommand(program) {
|
|
165
165
|
const files = program
|
|
166
166
|
.command("files")
|
|
167
|
-
.description("Manage file access controls in HQ vault. A trailing folder slash (foo/) is a private folder; the quoted glob 'foo/*' shares all children.")
|
|
167
|
+
.description("Manage file access controls in HQ vault. A trailing folder slash (foo/) is a private folder; the quoted glob 'foo/*' shares all children, and a quoted 'clients/*/reports' shares that folder under every client.")
|
|
168
168
|
.option("--company <slug>", "Company slug (resolves to companyUid)");
|
|
169
169
|
// An exported HQ_API_KEY must never fall back to the caller's Cognito session
|
|
170
170
|
// (that would ignore the key's scope entirely), so every subcommand is gated.
|
|
@@ -193,7 +193,7 @@ export function registerFilesCommand(program) {
|
|
|
193
193
|
});
|
|
194
194
|
files
|
|
195
195
|
.command("share [paths...]")
|
|
196
|
-
.description("Share file paths. foo/ is a private folder (children stay creator-only); quote the shared-folder glob 'foo/*' so the shell passes it literally. Without --with: mint a share-session URL and open it in the browser. With --with: grant access directly to a person, group, or @all.")
|
|
196
|
+
.description("Share file paths. foo/ is a private folder (children stay creator-only); quote the shared-folder glob 'foo/*', or a one-folder wildcard such as 'clients/*/reports', so the shell passes it literally. Without --with: mint a share-session URL and open it in the browser. With --with: grant access directly to a person, group, or @all.")
|
|
197
197
|
.option("--with <principal>", "Email address, group id, company Outpost id (out_...), or '@all' to share with every active company member")
|
|
198
198
|
.option("--permission <level>", "Permission level (only with --with): read | write")
|
|
199
199
|
.option("--exact", "With --with: grant exactly the object named by <prefix>, even when it looks like a folder (by default a bare folder name such as 'knowledge' is granted as the folder glob 'knowledge/*').")
|
|
@@ -763,6 +763,8 @@ export function registerFilesCommand(program) {
|
|
|
763
763
|
async function warnIfEmptySharePrefix(params) {
|
|
764
764
|
if (isPrivateFolderPattern(params.prefix))
|
|
765
765
|
return;
|
|
766
|
+
if (isSingleSegmentWildcardPrefix(params.prefix))
|
|
767
|
+
return;
|
|
766
768
|
let enabled;
|
|
767
769
|
try {
|
|
768
770
|
enabled = await resolveCompanyFlagGate(params.companyUid, params.token, FILES_SHARE_EMPTY_PREFIX_WARNING_FLAG, { fallback: false }, () => false);
|
|
@@ -879,9 +881,21 @@ async function resolveMentionPersonUid(mention, companySlug) {
|
|
|
879
881
|
* whether it is a folder (`Makefile`, `bin/hq`); resolveBareSharePrefix asks
|
|
880
882
|
* the vault.
|
|
881
883
|
*/
|
|
884
|
+
export function isSingleSegmentWildcardPrefix(prefix) {
|
|
885
|
+
const body = prefix.endsWith("/*") ? prefix.slice(0, -2) : prefix;
|
|
886
|
+
const segments = body.split("/");
|
|
887
|
+
if (segments.some((segment) => segment.length === 0))
|
|
888
|
+
return false;
|
|
889
|
+
if (segments.some((segment) => segment !== "*" && segment.includes("*")))
|
|
890
|
+
return false;
|
|
891
|
+
const wildcards = segments.filter((segment) => segment === "*").length;
|
|
892
|
+
return wildcards === 1 && wildcards < segments.length;
|
|
893
|
+
}
|
|
882
894
|
export function looksLikeBareFolderPrefix(prefix) {
|
|
883
895
|
if (prefix === "*" || prefix.endsWith("/") || prefix.endsWith("/*"))
|
|
884
896
|
return false;
|
|
897
|
+
if (isSingleSegmentWildcardPrefix(prefix))
|
|
898
|
+
return false;
|
|
885
899
|
const last = prefix.slice(prefix.lastIndexOf("/") + 1);
|
|
886
900
|
return last.length > 0 && !last.includes(".");
|
|
887
901
|
}
|
|
@@ -894,8 +908,12 @@ export function malformedBareSharePrefix(prefix) {
|
|
|
894
908
|
if (!looksLikeBareFolderPrefix(prefix))
|
|
895
909
|
return null;
|
|
896
910
|
if (prefix.includes("*")) {
|
|
897
|
-
|
|
898
|
-
|
|
911
|
+
const reason = prefix.split("/").some((segment) => segment !== "*" && segment.includes("*"))
|
|
912
|
+
? "has a '*' inside a path segment"
|
|
913
|
+
: "has more than one '*' folder";
|
|
914
|
+
return (`'${prefix}' ${reason}. A '*' must be a whole folder name: once per path ` +
|
|
915
|
+
`(for example 'clients/*/reports'), as a trailing '/*' (for example 'knowledge/*'), ` +
|
|
916
|
+
`or '*' alone (--full).`);
|
|
899
917
|
}
|
|
900
918
|
if (prefix.split("/").some((segment) => segment.length === 0)) {
|
|
901
919
|
return `'${prefix}' has an empty path segment. Pass a company-relative path such as 'knowledge'.`;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { Command } from "commander";
|
|
2
|
+
import { type SyncCoreReconcileResult } from "../lib/sync-core/reconcile.js";
|
|
3
|
+
export interface SyncCoreReconcileCommandOptions {
|
|
4
|
+
hqRoot?: string;
|
|
5
|
+
companiesOnly?: boolean;
|
|
6
|
+
tokenFile?: string;
|
|
7
|
+
json?: boolean;
|
|
8
|
+
}
|
|
9
|
+
export interface SyncCoreCommandDeps {
|
|
10
|
+
home: string;
|
|
11
|
+
env: NodeJS.ProcessEnv;
|
|
12
|
+
platform: NodeJS.Platform;
|
|
13
|
+
arch: string;
|
|
14
|
+
out: (line: string) => void;
|
|
15
|
+
err: (line: string) => void;
|
|
16
|
+
}
|
|
17
|
+
export declare function runSyncCoreReconcile(opts: SyncCoreReconcileCommandOptions, d?: SyncCoreCommandDeps): Promise<SyncCoreReconcileResult>;
|
|
18
|
+
export declare function runSyncCoreStatus(opts: {
|
|
19
|
+
json?: boolean;
|
|
20
|
+
}, d?: SyncCoreCommandDeps): void;
|
|
21
|
+
export declare function registerSyncCoreCommand(program: Command, over?: Partial<SyncCoreCommandDeps>): void;
|
|
22
|
+
//# sourceMappingURL=sync-core.d.ts.map
|