@phnx-labs/agents-cli 1.22.53 → 1.22.55
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 +208 -0
- package/README.md +59 -9
- package/dist/bootstrap.js +55 -154
- package/dist/cli/command-registry.d.ts +5 -0
- package/dist/cli/command-registry.js +8 -1
- package/dist/commands/accounts.js +219 -173
- package/dist/commands/apply.js +6 -3
- package/dist/commands/auth-mint.d.ts +8 -0
- package/dist/commands/auth-mint.js +96 -0
- package/dist/commands/auth.js +5 -1
- package/dist/commands/browser.js +1 -1
- package/dist/commands/cost.js +8 -2
- package/dist/commands/daemon.js +2 -2
- package/dist/commands/doctor.js +6 -1
- package/dist/commands/exec.js +10 -8
- package/dist/commands/focus.d.ts +1 -0
- package/dist/commands/focus.js +2 -2
- package/dist/commands/go.d.ts +5 -4
- package/dist/commands/go.js +7 -7
- package/dist/commands/insights.js +9 -0
- package/dist/commands/monitors.js +85 -30
- package/dist/commands/output.js +8 -2
- package/dist/commands/repo.js +18 -0
- package/dist/commands/routines.js +31 -2
- package/dist/commands/secrets.js +33 -14
- package/dist/commands/sessions.d.ts +20 -12
- package/dist/commands/sessions.js +64 -20
- package/dist/commands/setup-accounts.d.ts +8 -0
- package/dist/commands/setup-accounts.js +47 -0
- package/dist/commands/setup.d.ts +1 -1
- package/dist/commands/setup.js +11 -2
- package/dist/commands/share.d.ts +79 -3
- package/dist/commands/share.js +347 -18
- package/dist/commands/ssh.d.ts +7 -0
- package/dist/commands/ssh.js +18 -2
- package/dist/commands/status.js +14 -0
- package/dist/commands/view.d.ts +11 -1
- package/dist/commands/view.js +35 -7
- package/dist/lib/account-registry.js +15 -3
- package/dist/lib/accounting/rotate.d.ts +20 -6
- package/dist/lib/accounting/rotate.js +38 -7
- package/dist/lib/accounting/usage.d.ts +68 -1
- package/dist/lib/accounting/usage.js +116 -10
- package/dist/lib/agent-spec/agents.d.ts +5 -2
- package/dist/lib/agent-spec/agents.js +25 -7
- package/dist/lib/analytics/mix-commands.js +12 -6
- package/dist/lib/auth-mint.d.ts +150 -0
- package/dist/lib/auth-mint.js +434 -0
- package/dist/lib/browser/cdp.d.ts +1 -1
- package/dist/lib/browser/cdp.js +1 -1
- package/dist/lib/browser/ffmpeg.d.ts +12 -0
- package/dist/lib/browser/ffmpeg.js +184 -0
- package/dist/lib/browser/remote-control.d.ts +9 -7
- package/dist/lib/browser/remote-control.js +9 -7
- package/dist/lib/browser/service.js +119 -25
- package/dist/lib/claude-account-token.d.ts +10 -0
- package/dist/lib/claude-account-token.js +14 -4
- package/dist/lib/config-drift.d.ts +37 -0
- package/dist/lib/config-drift.js +72 -0
- package/dist/lib/daemon/auth-sync-service.d.ts +19 -0
- package/dist/lib/daemon/auth-sync-service.js +34 -0
- package/dist/lib/daemon/browser-task-reap-service.d.ts +14 -0
- package/dist/lib/daemon/browser-task-reap-service.js +26 -0
- package/dist/lib/daemon/daemon.js +87 -176
- package/dist/lib/daemon/heartbeat-service.d.ts +13 -0
- package/dist/lib/daemon/heartbeat-service.js +26 -0
- package/dist/lib/daemon/monitor-engine-service.d.ts +9 -5
- package/dist/lib/daemon/monitor-engine-service.js +15 -7
- package/dist/lib/daemon/runner.d.ts +15 -0
- package/dist/lib/daemon/runner.js +23 -0
- package/dist/lib/daemon/secrets-broker-service.d.ts +5 -4
- package/dist/lib/daemon/secrets-broker-service.js +17 -32
- package/dist/lib/daemon/service.d.ts +2 -2
- package/dist/lib/daemon/service.js +1 -1
- package/dist/lib/daemon/session-state-service.d.ts +21 -0
- package/dist/lib/daemon/session-state-service.js +34 -0
- package/dist/lib/daemon/supervisor.d.ts +17 -7
- package/dist/lib/daemon/supervisor.js +87 -14
- package/dist/lib/daemon/tmux-reap-service.d.ts +11 -0
- package/dist/lib/daemon/tmux-reap-service.js +28 -0
- package/dist/lib/daemon/webhook-receiver-service.d.ts +9 -0
- package/dist/lib/daemon/webhook-receiver-service.js +17 -0
- package/dist/lib/daemon-services.d.ts +1 -1
- package/dist/lib/daemon-services.js +25 -0
- package/dist/lib/device-config.d.ts +3 -3
- package/dist/lib/device-config.js +5 -5
- package/dist/lib/devices/connect.d.ts +26 -0
- package/dist/lib/devices/connect.js +48 -1
- package/dist/lib/devices/doctor-findings.d.ts +5 -1
- package/dist/lib/devices/doctor-findings.js +19 -1
- package/dist/lib/devices/harness-inventory.js +5 -2
- package/dist/lib/exec.d.ts +28 -0
- package/dist/lib/exec.js +73 -7
- package/dist/lib/feed/feed.d.ts +1 -1
- package/dist/lib/feed/feed.js +23 -1
- package/dist/lib/feed-broadcast.js +1 -1
- package/dist/lib/fleet/apply.d.ts +11 -0
- package/dist/lib/fleet/apply.js +23 -3
- package/dist/lib/fleet/auth-sync.js +5 -3
- package/dist/lib/help.d.ts +9 -0
- package/dist/lib/help.js +29 -1
- package/dist/lib/hosts/passthrough.d.ts +1 -10
- package/dist/lib/hosts/passthrough.js +1 -13
- package/dist/lib/installations/versions.js +9 -1
- package/dist/lib/linux-userns.d.ts +58 -0
- package/dist/lib/linux-userns.js +116 -0
- package/dist/lib/memory.d.ts +26 -0
- package/dist/lib/memory.js +80 -1
- package/dist/lib/monitors/config.d.ts +11 -0
- package/dist/lib/monitors/config.js +8 -0
- package/dist/lib/monitors/engine.d.ts +5 -1
- package/dist/lib/monitors/engine.js +13 -4
- package/dist/lib/monitors/state.d.ts +37 -1
- package/dist/lib/monitors/state.js +79 -4
- package/dist/lib/permissions-registry.d.ts +2 -0
- package/dist/lib/permissions-registry.js +116 -14
- package/dist/lib/permissions.d.ts +5 -3
- package/dist/lib/permissions.js +25 -27
- package/dist/lib/profiles.d.ts +8 -7
- package/dist/lib/profiles.js +12 -0
- package/dist/lib/project-key.d.ts +9 -0
- package/dist/lib/project-key.js +11 -0
- package/dist/lib/scheduling/routines.d.ts +47 -0
- package/dist/lib/scheduling/routines.js +70 -1
- package/dist/lib/secrets/bundles.d.ts +35 -0
- package/dist/lib/secrets/bundles.js +78 -1
- package/dist/lib/secrets/push.d.ts +3 -8
- package/dist/lib/secrets/push.js +18 -14
- package/dist/lib/secrets/remote.d.ts +9 -18
- package/dist/lib/secrets/remote.js +11 -26
- package/dist/lib/secrets/reserved-sync.d.ts +65 -0
- package/dist/lib/secrets/reserved-sync.js +129 -0
- package/dist/lib/self-heal/checks/hook-manifest.d.ts +2 -0
- package/dist/lib/self-heal/checks/hook-manifest.js +56 -0
- package/dist/lib/self-heal/registry.js +4 -0
- package/dist/lib/self-heal/types.d.ts +1 -1
- package/dist/lib/session/active.js +1 -4
- package/dist/lib/session/db.d.ts +41 -5
- package/dist/lib/session/db.js +132 -30
- package/dist/lib/session/discover.d.ts +32 -4
- package/dist/lib/session/discover.js +119 -25
- package/dist/lib/session/insights.d.ts +14 -0
- package/dist/lib/session/insights.js +25 -2
- package/dist/lib/session/linear.js +1 -1
- package/dist/lib/session/shell-programs.d.ts +17 -0
- package/dist/lib/session/shell-programs.js +21 -0
- package/dist/lib/session/state.js +2 -1
- package/dist/lib/session/stream-render.js +2 -1
- package/dist/lib/session/tool-calls.js +2 -5
- package/dist/lib/session/trajectory-html.js +2 -1
- package/dist/lib/session/trajectory.js +3 -12
- package/dist/lib/session/types.d.ts +8 -0
- package/dist/lib/share/capture.js +11 -2
- package/dist/lib/share/publish.d.ts +56 -5
- package/dist/lib/share/publish.js +126 -18
- package/dist/lib/share/worker-template.d.ts +3 -12
- package/dist/lib/share/worker-template.js +860 -59
- package/dist/lib/startup/root-command.js +2 -1
- package/dist/lib/state.d.ts +16 -0
- package/dist/lib/state.js +178 -46
- package/dist/lib/sync-status.d.ts +4 -0
- package/dist/lib/sync-status.js +3 -0
- package/dist/lib/traces/classify.js +24 -19
- package/dist/lib/usage-refresh.js +2 -1
- package/dist/lib/view-types.d.ts +7 -0
- package/package.json +9 -2
|
@@ -19,7 +19,8 @@
|
|
|
19
19
|
export function configureRootCommand(program, name, version) {
|
|
20
20
|
return program
|
|
21
21
|
.name(name)
|
|
22
|
-
.description('
|
|
22
|
+
.description('Install, configure, run, and dispatch AI coding agents from one place.\n' +
|
|
23
|
+
'Works with Claude, Codex, Antigravity, Cursor, OpenCode, OpenClaw, and Droid.')
|
|
23
24
|
.version(version)
|
|
24
25
|
.option('--verbose', 'Show startup self-heal details on stderr')
|
|
25
26
|
.helpOption('-h, --help', 'Show help')
|
package/dist/lib/state.d.ts
CHANGED
|
@@ -391,6 +391,22 @@ export declare function getDeviceMetaPath(): string;
|
|
|
391
391
|
*/
|
|
392
392
|
export declare function getVersionResourcesPath(): string;
|
|
393
393
|
export declare function withMetaLock<T>(fn: () => T): T;
|
|
394
|
+
/**
|
|
395
|
+
* True when the top-level `~/.agents/agents.yaml` carries a metadata header that
|
|
396
|
+
* is NOT the canonical {@link META_HEADER} — the P1 frozen-header case a box only
|
|
397
|
+
* heals on its next central write (serializeCentral). An absent or headerless
|
|
398
|
+
* file is NOT stale (a headerless central file is deliberately left alone).
|
|
399
|
+
* Surfaced as config drift by `agents sync status` (PHNX-3315).
|
|
400
|
+
*/
|
|
401
|
+
export declare function hasStaleMetaHeader(): boolean;
|
|
402
|
+
/**
|
|
403
|
+
* Parse the top-level user `agents.yaml` (this box's synced central file) WITHOUT
|
|
404
|
+
* the system-repo merge, machine-local overlay, or cache — the raw on-disk central
|
|
405
|
+
* map, or null when the file is absent/unparseable. Used by config-drift detection
|
|
406
|
+
* to see the central blocks that should have folded into the device doc, without
|
|
407
|
+
* mis-attributing a system-repo default as this box's own leak (PHNX-3315).
|
|
408
|
+
*/
|
|
409
|
+
export declare function readTopLevelUserMeta(): Record<string, unknown> | null;
|
|
394
410
|
/**
|
|
395
411
|
* Write `meta` to disk (central + device docs + pins) WITHOUT taking the meta
|
|
396
412
|
* lock — the caller must already hold it via {@link withMetaLock}. Exported so a
|
package/dist/lib/state.js
CHANGED
|
@@ -858,52 +858,99 @@ function writeIfChanged(filePath, content) {
|
|
|
858
858
|
* `agents:` / `versions:` are not written (no empty committed files).
|
|
859
859
|
*/
|
|
860
860
|
/**
|
|
861
|
-
*
|
|
862
|
-
*
|
|
863
|
-
*
|
|
864
|
-
*
|
|
865
|
-
*
|
|
866
|
-
*
|
|
867
|
-
*
|
|
868
|
-
* agents.yaml key-loss (beta flags, `notify.owner`, and `feed:` all vanished
|
|
869
|
-
* this way when an older-versioned CLI on the fleet rewrote the file).
|
|
861
|
+
* Fleet-shared (`central`) Meta keys — the OPT-IN allowlist. A key listed here is
|
|
862
|
+
* written to the synced `~/.agents/agents.yaml`. Device-scope is the DEFAULT:
|
|
863
|
+
* {@link metaKeyScope} returns `'device'` for every Meta key NOT listed here, so a
|
|
864
|
+
* newly-added key can never SILENTLY churn the shared file the way a
|
|
865
|
+
* central-by-default would — the very trap behind the recurring agents.yaml churn.
|
|
866
|
+
* Making a key fleet-shared is now a deliberate edit HERE, not the path of least
|
|
867
|
+
* resistance (PHNX-3315).
|
|
870
868
|
*/
|
|
871
|
-
const
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
869
|
+
const CENTRAL_META_KEYS = [
|
|
870
|
+
'accounts',
|
|
871
|
+
'run',
|
|
872
|
+
'model',
|
|
873
|
+
'watchdog',
|
|
874
|
+
'lease',
|
|
875
|
+
'secrets',
|
|
876
|
+
'budget',
|
|
877
|
+
'feed',
|
|
878
|
+
'beta',
|
|
879
|
+
'registries',
|
|
880
|
+
'profiles',
|
|
881
|
+
'source',
|
|
882
|
+
'extraRepos',
|
|
883
|
+
'brands',
|
|
884
|
+
'actors',
|
|
885
|
+
'seededPresets',
|
|
886
|
+
'hooks',
|
|
887
|
+
'config',
|
|
888
|
+
'hosts',
|
|
889
|
+
'fleet',
|
|
890
|
+
'share',
|
|
891
|
+
'notify',
|
|
892
|
+
];
|
|
893
|
+
/**
|
|
894
|
+
* Device-scoped Meta keys with BESPOKE routing — each lands in a special file
|
|
895
|
+
* (pins JSON / version-resources JSON) or a REMAPPED device-doc sub-block
|
|
896
|
+
* (`deviceHosts`->`hosts:`, `deviceFleet`->`fleet:`, ...), so their handling is
|
|
897
|
+
* hand-written in {@link writeMetaUnlocked} / {@link overlayMachineLocal} and kept
|
|
898
|
+
* behavior-identical. A device-scoped key NOT listed here is a GENERIC device key:
|
|
899
|
+
* it round-trips through `devices/<host>/agents.yaml` under its OWN name with no
|
|
900
|
+
* bespoke wiring (see the generic loops in those two functions). The `browser`
|
|
901
|
+
* tombstone is bespoke too — drained by lib/browser/registry.ts.
|
|
902
|
+
*/
|
|
903
|
+
const BESPOKE_DEVICE_KEYS = [
|
|
904
|
+
'agents',
|
|
905
|
+
'isolatedAgents',
|
|
906
|
+
'versions',
|
|
907
|
+
'deviceRoutines',
|
|
908
|
+
'deviceConfig',
|
|
909
|
+
'deviceBrowser',
|
|
910
|
+
'deviceFleet',
|
|
911
|
+
'deviceHosts',
|
|
912
|
+
'deviceAccounts',
|
|
913
|
+
'projectRoot',
|
|
914
|
+
];
|
|
915
|
+
const CENTRAL_KEY_SET = new Set(CENTRAL_META_KEYS);
|
|
916
|
+
const _metaKeysAreExhaustive = true;
|
|
917
|
+
void _metaKeysAreExhaustive;
|
|
918
|
+
/**
|
|
919
|
+
* Sync-domain of a Meta key. `'central'` ONLY for the opt-in allowlist; `'device'`
|
|
920
|
+
* by DEFAULT for everything else — so the classification is authoritative (it
|
|
921
|
+
* DRIVES the generic device-doc router below) rather than a decorative string map,
|
|
922
|
+
* and forgetting to classify a new key routes it to the SAFE per-box file.
|
|
923
|
+
*/
|
|
924
|
+
function metaKeyScope(key) {
|
|
925
|
+
return CENTRAL_KEY_SET.has(key) ? 'central' : 'device';
|
|
926
|
+
}
|
|
927
|
+
/** Bespoke device keys as a runtime Set (the generic router skips these). */
|
|
928
|
+
const BESPOKE_DEVICE_KEY_SET = new Set([
|
|
929
|
+
...BESPOKE_DEVICE_KEYS,
|
|
930
|
+
// The `browser` tombstone is device-scoped but bespoke: lib/browser/registry.ts
|
|
931
|
+
// drains it (collision-checked) into deviceBrowser, so the generic router must
|
|
932
|
+
// never blindly relocate it.
|
|
933
|
+
'browser',
|
|
934
|
+
]);
|
|
935
|
+
/**
|
|
936
|
+
* Device-doc sub-block names the read/write paths handle BESPOKE-ly (each maps to
|
|
937
|
+
* a different `device*` Meta key, or lives in a separate file). The generic
|
|
938
|
+
* device-doc overlay skips these so it only surfaces genuine generic keys.
|
|
939
|
+
*/
|
|
940
|
+
const BESPOKE_DEVICE_DOC_KEYS = new Set([
|
|
941
|
+
'agents',
|
|
942
|
+
'isolatedAgents',
|
|
943
|
+
'routines',
|
|
944
|
+
'config',
|
|
945
|
+
'browser',
|
|
946
|
+
'fleet',
|
|
947
|
+
'hosts',
|
|
948
|
+
'accounts',
|
|
949
|
+
'projectRoot',
|
|
950
|
+
// Legacy top-level doc key the config migration folds into `config:`; never
|
|
951
|
+
// surfaced onto Meta before, so keep the generic overlay from doing so.
|
|
952
|
+
'defaultBrowserProfile',
|
|
953
|
+
]);
|
|
907
954
|
/**
|
|
908
955
|
* Every key this version models (central + device). serializeCentral deletes an
|
|
909
956
|
* on-disk key only when it is KNOWN and absent from the write's in-memory object:
|
|
@@ -913,7 +960,8 @@ const META_KEY_SCOPE = {
|
|
|
913
960
|
* a newer CLI version added — is preserved verbatim, never dropped + synced away.
|
|
914
961
|
*/
|
|
915
962
|
const KNOWN_META_KEYS = new Set([
|
|
916
|
-
...
|
|
963
|
+
...CENTRAL_META_KEYS,
|
|
964
|
+
...BESPOKE_DEVICE_KEYS,
|
|
917
965
|
// Removed browser-profile store. Kept only as a serializer tombstone so the
|
|
918
966
|
// first registry read can migrate it into this device's file and delete it.
|
|
919
967
|
'browser',
|
|
@@ -947,6 +995,56 @@ function healMetaHeader(serialized) {
|
|
|
947
995
|
// real central change). A current header round-trips to the identical bytes.
|
|
948
996
|
return stripped === serialized ? serialized : META_HEADER + stripped;
|
|
949
997
|
}
|
|
998
|
+
/**
|
|
999
|
+
* True when the top-level `~/.agents/agents.yaml` carries a metadata header that
|
|
1000
|
+
* is NOT the canonical {@link META_HEADER} — the P1 frozen-header case a box only
|
|
1001
|
+
* heals on its next central write (serializeCentral). An absent or headerless
|
|
1002
|
+
* file is NOT stale (a headerless central file is deliberately left alone).
|
|
1003
|
+
* Surfaced as config drift by `agents sync status` (PHNX-3315).
|
|
1004
|
+
*/
|
|
1005
|
+
export function hasStaleMetaHeader() {
|
|
1006
|
+
let content;
|
|
1007
|
+
try {
|
|
1008
|
+
content = fs.readFileSync(META_FILE, 'utf-8');
|
|
1009
|
+
}
|
|
1010
|
+
catch {
|
|
1011
|
+
return false;
|
|
1012
|
+
}
|
|
1013
|
+
return healMetaHeader(content) !== content;
|
|
1014
|
+
}
|
|
1015
|
+
/**
|
|
1016
|
+
* Parse the top-level user `agents.yaml` (this box's synced central file) WITHOUT
|
|
1017
|
+
* the system-repo merge, machine-local overlay, or cache — the raw on-disk central
|
|
1018
|
+
* map, or null when the file is absent/unparseable. Used by config-drift detection
|
|
1019
|
+
* to see the central blocks that should have folded into the device doc, without
|
|
1020
|
+
* mis-attributing a system-repo default as this box's own leak (PHNX-3315).
|
|
1021
|
+
*/
|
|
1022
|
+
export function readTopLevelUserMeta() {
|
|
1023
|
+
let content;
|
|
1024
|
+
try {
|
|
1025
|
+
content = fs.readFileSync(META_FILE, 'utf-8');
|
|
1026
|
+
}
|
|
1027
|
+
catch {
|
|
1028
|
+
return null;
|
|
1029
|
+
}
|
|
1030
|
+
try {
|
|
1031
|
+
const parsed = yaml.parse(content);
|
|
1032
|
+
if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {
|
|
1033
|
+
return parsed;
|
|
1034
|
+
}
|
|
1035
|
+
}
|
|
1036
|
+
catch { /* malformed — nothing to report */ }
|
|
1037
|
+
return null;
|
|
1038
|
+
}
|
|
1039
|
+
/**
|
|
1040
|
+
* Top-level keys currently on disk in the central `agents.yaml` ({} when the file
|
|
1041
|
+
* is absent or unparseable). The generic device-doc router uses this to leave a
|
|
1042
|
+
* FOREIGN key — one this version does not model, already written to central by a
|
|
1043
|
+
* newer CLI — in place instead of relocating it to this box's device doc.
|
|
1044
|
+
*/
|
|
1045
|
+
function readCentralKeys() {
|
|
1046
|
+
return new Set(Object.keys(readTopLevelUserMeta() ?? {}));
|
|
1047
|
+
}
|
|
950
1048
|
/**
|
|
951
1049
|
* Serialize the central (synced) meta to `agents.yaml` WITHOUT destroying the
|
|
952
1050
|
* hand-written comments in the committed file.
|
|
@@ -1035,6 +1133,10 @@ function serializeCentral(central) {
|
|
|
1035
1133
|
* read-snapshot-then-separately-lock race that {@link updateMeta} would impose.
|
|
1036
1134
|
*/
|
|
1037
1135
|
export function writeMetaUnlocked(meta) {
|
|
1136
|
+
// INVARIANT: every key destructured here must also be in BESPOKE_DEVICE_KEYS (and
|
|
1137
|
+
// vice versa) — a bespoke device key that is classified but NOT pulled out here
|
|
1138
|
+
// would fall into `central`, and the generic router skips it (BESPOKE_DEVICE_KEY_SET),
|
|
1139
|
+
// so it would silently sync to the shared file. Keep the two lists in lockstep.
|
|
1038
1140
|
const { agents, isolatedAgents, versions, deviceRoutines, deviceConfig, deviceBrowser, deviceFleet, deviceHosts, deviceAccounts, projectRoot, ...central } = meta;
|
|
1039
1141
|
// Write the machine-local files FIRST, then strip central — so a crash mid-write
|
|
1040
1142
|
// never removes pins/versions from central before they're persisted elsewhere.
|
|
@@ -1140,6 +1242,27 @@ export function writeMetaUnlocked(meta) {
|
|
|
1140
1242
|
doc.projectRoot = projectRoot;
|
|
1141
1243
|
else
|
|
1142
1244
|
delete doc.projectRoot;
|
|
1245
|
+
// Generic device-scoped keys (PHNX-3315): any key left in `central` that this
|
|
1246
|
+
// version classifies as device but does NOT bespoke-route round-trips through
|
|
1247
|
+
// this box's device doc under its OWN name — so a NEWLY DECLARED device-scoped
|
|
1248
|
+
// key lands per-box BY DEFAULT, with no bespoke wiring, and never reaches the
|
|
1249
|
+
// synced central file. Today the bespoke set covers every device key, so this
|
|
1250
|
+
// loop moves nothing (behavior-identical). A key that is UNKNOWN to this version
|
|
1251
|
+
// AND already present in the on-disk central file is a FOREIGN key a newer CLI
|
|
1252
|
+
// wrote as central — left in `central` and preserved verbatim by serializeCentral
|
|
1253
|
+
// (the fleet-wide config-loss guard), never relocated to this box's doc.
|
|
1254
|
+
const centralRecord = central;
|
|
1255
|
+
const onDiskCentralKeys = readCentralKeys();
|
|
1256
|
+
for (const k of Object.keys(centralRecord)) {
|
|
1257
|
+
if (metaKeyScope(k) !== 'device')
|
|
1258
|
+
continue; // fleet-shared: stays central
|
|
1259
|
+
if (BESPOKE_DEVICE_KEY_SET.has(k))
|
|
1260
|
+
continue; // bespoke (incl. browser tombstone)
|
|
1261
|
+
if (!KNOWN_META_KEYS.has(k) && onDiskCentralKeys.has(k))
|
|
1262
|
+
continue; // foreign — preserve
|
|
1263
|
+
doc[k] = centralRecord[k];
|
|
1264
|
+
delete centralRecord[k];
|
|
1265
|
+
}
|
|
1143
1266
|
if (Object.keys(doc).length > 0) {
|
|
1144
1267
|
fs.mkdirSync(path.dirname(devicePath), { recursive: true });
|
|
1145
1268
|
writeIfChanged(devicePath, META_HEADER + yaml.stringify(doc));
|
|
@@ -1259,6 +1382,15 @@ function overlayMachineLocal(meta) {
|
|
|
1259
1382
|
}
|
|
1260
1383
|
meta.deviceRoutines = dm.routines;
|
|
1261
1384
|
}
|
|
1385
|
+
// Generic device-scoped keys (PHNX-3315): device-doc keys this overlay does
|
|
1386
|
+
// NOT bespoke-handle map straight back onto Meta under their own name —
|
|
1387
|
+
// symmetry with the generic write in writeMetaUnlocked. The bespoke sub-blocks
|
|
1388
|
+
// are handled above and skipped, so a stock device doc surfaces nothing extra.
|
|
1389
|
+
for (const [k, v] of Object.entries(dm)) {
|
|
1390
|
+
if (BESPOKE_DEVICE_DOC_KEYS.has(k))
|
|
1391
|
+
continue;
|
|
1392
|
+
meta[k] = v;
|
|
1393
|
+
}
|
|
1262
1394
|
}
|
|
1263
1395
|
}
|
|
1264
1396
|
const vrPath = getVersionResourcesPath();
|
|
@@ -20,6 +20,7 @@
|
|
|
20
20
|
*/
|
|
21
21
|
import { AgentId } from './types.js';
|
|
22
22
|
import { type DoctorKind } from './doctor-diff.js';
|
|
23
|
+
import { type ConfigDrift } from './config-drift.js';
|
|
23
24
|
/**
|
|
24
25
|
* One stable status per resource, unified across every surface.
|
|
25
26
|
* - `synced` — installed copy matches the resolved source (DiffStatus 'ok').
|
|
@@ -76,6 +77,9 @@ export interface UserRepoStatus {
|
|
|
76
77
|
export interface UnifiedSyncStatus {
|
|
77
78
|
system: SystemRepoStatus;
|
|
78
79
|
user: UserRepoStatus;
|
|
80
|
+
/** Config drift: has this box drained its device-scoped state, or is it still
|
|
81
|
+
* carrying per-box state in the shared top-level agents.yaml? (PHNX-3315) */
|
|
82
|
+
config: ConfigDrift;
|
|
79
83
|
agents: AgentVersionStatus[];
|
|
80
84
|
totals: {
|
|
81
85
|
drifted: number;
|
package/dist/lib/sync-status.js
CHANGED
|
@@ -26,6 +26,7 @@ import { loadManifest } from './staleness/index.js';
|
|
|
26
26
|
import { getSystemAgentsDir, getUserAgentsDir } from './state.js';
|
|
27
27
|
import * as fs from 'fs';
|
|
28
28
|
import { isGitRepo, readOriginUrl } from './git.js';
|
|
29
|
+
import { detectConfigDrift } from './config-drift.js';
|
|
29
30
|
const STATUS_MAP = {
|
|
30
31
|
ok: 'synced',
|
|
31
32
|
diff: 'drifted',
|
|
@@ -125,6 +126,7 @@ export async function computeSyncStatus(options = {}) {
|
|
|
125
126
|
}
|
|
126
127
|
const system = await getSystemRepoStatus();
|
|
127
128
|
const user = await getUserRepoStatus();
|
|
129
|
+
const config = detectConfigDrift();
|
|
128
130
|
const agentsNeedingSync = new Set();
|
|
129
131
|
let drifted = 0, missing = 0, orphan = 0, versionsNeedingSync = 0, versionsNeverSynced = 0;
|
|
130
132
|
for (const v of agents) {
|
|
@@ -141,6 +143,7 @@ export async function computeSyncStatus(options = {}) {
|
|
|
141
143
|
return {
|
|
142
144
|
system,
|
|
143
145
|
user,
|
|
146
|
+
config,
|
|
144
147
|
agents,
|
|
145
148
|
totals: {
|
|
146
149
|
drifted,
|
|
@@ -4,29 +4,34 @@ function normalizedEvidence(input) {
|
|
|
4
4
|
.join(' ')
|
|
5
5
|
.toLowerCase();
|
|
6
6
|
}
|
|
7
|
+
const TOPIC_RULES = [
|
|
8
|
+
{ group: 'review', key: 'code-review', label: 'Code review',
|
|
9
|
+
text: /\b(review|audit|pr[-_/ ]?review|code[-_/ ]?review)\b/, tool: /review|comment/ },
|
|
10
|
+
{ group: 'ops', key: 'release', label: 'Release',
|
|
11
|
+
text: /\b(release|deploy|publish|rollout|changelog)\b/ },
|
|
12
|
+
{ group: 'research', key: 'debugging', label: 'Debugging',
|
|
13
|
+
text: /\b(debug|investigat|repro|root[-_ ]?cause|traceback|stack ?trace)\b/ },
|
|
14
|
+
{ group: 'content', key: 'content', label: 'Blog & docs',
|
|
15
|
+
text: /\b(blog|docs?|documentation|readme|post|article|content|caption)\b/ },
|
|
16
|
+
{ group: 'research', key: 'research', label: 'Research',
|
|
17
|
+
text: /\b(research|analysis|benchmark|paper|explore|spike)\b/, tool: /web_search|search_query|webfetch|browser/ },
|
|
18
|
+
{ group: 'code', key: 'bugfix', label: 'Bug fixes',
|
|
19
|
+
text: /\b(fix|bug|hotfix|patch)\b/ },
|
|
20
|
+
{ group: 'code', key: 'refactor', label: 'Refactor',
|
|
21
|
+
text: /\b(refactor|cleanup|clean[-_ ]?up|restructure|rename|tidy)\b/ },
|
|
22
|
+
{ group: 'ops', key: 'operations', label: 'Fleet / ops',
|
|
23
|
+
text: /\b(fleet|ci|infra|daemon|provision|ops|pipeline)\b/, tool: /ssh|deploy|computer/ },
|
|
24
|
+
{ group: 'code', key: 'feature', label: 'Feature work',
|
|
25
|
+
text: /\b(feat|feature|implement|add|build|code|test)\b/, tool: /edit|write|apply_patch|exec|bash|shell/ },
|
|
26
|
+
];
|
|
7
27
|
/** Classify a session from metadata and aggregate tool names; never reads transcript text. */
|
|
8
28
|
export function classifyTopic(input) {
|
|
9
29
|
const text = normalizedEvidence(input);
|
|
10
30
|
const tools = Object.keys(input.toolMix ?? {}).map((tool) => tool.toLowerCase());
|
|
11
|
-
const
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
}
|
|
16
|
-
if (/\b(research|investigat|analysis|benchmark|paper|docs?)\b/.test(text)
|
|
17
|
-
|| hasTool(/web_search|search_query|webfetch|browser/)) {
|
|
18
|
-
return { group: 'research', key: 'research', label: 'Research' };
|
|
19
|
-
}
|
|
20
|
-
if (/\b(blog|copy|content|post|article|script|caption)\b/.test(text)) {
|
|
21
|
-
return { group: 'content', key: 'content', label: 'Content' };
|
|
22
|
-
}
|
|
23
|
-
if (/\b(deploy|release|infra|ci|ops|fleet|daemon|provision)\b/.test(text)
|
|
24
|
-
|| hasTool(/ssh|deploy|computer/)) {
|
|
25
|
-
return { group: 'ops', key: 'operations', label: 'Operations' };
|
|
26
|
-
}
|
|
27
|
-
if (/\b(test|fix|bug|feat|refactor|implement|code)\b/.test(text)
|
|
28
|
-
|| hasTool(/edit|write|apply_patch|exec|bash|shell/)) {
|
|
29
|
-
return { group: 'code', key: 'engineering', label: 'Engineering' };
|
|
31
|
+
for (const rule of TOPIC_RULES) {
|
|
32
|
+
if ((rule.text && rule.text.test(text)) || (rule.tool && tools.some((tool) => rule.tool.test(tool)))) {
|
|
33
|
+
return { group: rule.group, key: rule.key, label: rule.label };
|
|
34
|
+
}
|
|
30
35
|
}
|
|
31
36
|
return { group: 'research', key: 'general', label: 'General' };
|
|
32
37
|
}
|
|
@@ -245,7 +245,8 @@ export async function buildLocalUsageAccounts() {
|
|
|
245
245
|
// fileOnly: never open the ACL-bound keychain item from the daemon —
|
|
246
246
|
// that path is the Touch ID storm. Usage reads the file-based setup-token
|
|
247
247
|
// only, never the interactive login (see loadClaudeOauth); no setup-token
|
|
248
|
-
// reads as "usage pending"
|
|
248
|
+
// reads as "usage unavailable (no usage credential)" — "usage pending"
|
|
249
|
+
// is now the cold-cache state only (#2987).
|
|
249
250
|
fetch: async () => {
|
|
250
251
|
const { getUsageInfoForIdentity } = await import('./accounting/usage.js');
|
|
251
252
|
return getUsageInfoForIdentity({
|
package/dist/lib/view-types.d.ts
CHANGED
|
@@ -21,6 +21,13 @@ export interface ViewJsonVersion {
|
|
|
21
21
|
amount: number;
|
|
22
22
|
currency: string;
|
|
23
23
|
} | null;
|
|
24
|
+
/**
|
|
25
|
+
* Human-readable reason a usage snapshot is absent: a live-refresh failure, or
|
|
26
|
+
* (without `--refresh`, on a never-cached account) a plain "not collected yet"
|
|
27
|
+
* pending state. Always a full sentence — never the internal `'stale'` cache
|
|
28
|
+
* sentinel, which `usageErrorForDisplay` normalizes away (PHNX-3348).
|
|
29
|
+
*/
|
|
30
|
+
usageError?: string | null;
|
|
24
31
|
windows: Array<{
|
|
25
32
|
key: 'session' | 'week' | 'sonnet_week' | 'month';
|
|
26
33
|
label?: string;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@phnx-labs/agents-cli",
|
|
3
|
-
"version": "1.22.
|
|
3
|
+
"version": "1.22.55",
|
|
4
4
|
"description": "One CLI for all your AI coding agents - versions, config, cloud dispatch, sessions, and teams (now with first-class Grok Build CLI support)",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -86,8 +86,11 @@
|
|
|
86
86
|
"@homebridge/node-pty-prebuilt-multiarch"
|
|
87
87
|
],
|
|
88
88
|
"dependencies": {
|
|
89
|
+
"@fontsource/inter": "^5.3.0",
|
|
90
|
+
"@fontsource/jetbrains-mono": "^5.3.0",
|
|
89
91
|
"@homebridge/node-pty-prebuilt-multiarch": "0.13.1",
|
|
90
92
|
"@inquirer/prompts": "8.5.2",
|
|
93
|
+
"@resvg/resvg-wasm": "^2.6.2",
|
|
91
94
|
"@types/proper-lockfile": "4.1.4",
|
|
92
95
|
"@xterm/headless": "6.0.0",
|
|
93
96
|
"@zed-industries/agent-client-protocol": "0.4.5",
|
|
@@ -98,17 +101,20 @@
|
|
|
98
101
|
"commander": "15.0.0",
|
|
99
102
|
"croner": "10.0.1",
|
|
100
103
|
"diff": "9.0.0",
|
|
104
|
+
"esbuild": "^0.28.2",
|
|
101
105
|
"lru-cache": "11.5.2",
|
|
102
106
|
"marked": "15.0.12",
|
|
103
107
|
"marked-terminal": "7.3.0",
|
|
104
108
|
"ora": "9.4.1",
|
|
105
109
|
"proper-lockfile": "4.1.2",
|
|
110
|
+
"satori": "0.12.2",
|
|
106
111
|
"shlex": "3.0.0",
|
|
107
112
|
"simple-git": "3.36.0",
|
|
108
113
|
"smol-toml": "1.7.0",
|
|
109
114
|
"unbash": "4.0.5",
|
|
110
115
|
"ws": "^8.21.0",
|
|
111
|
-
"yaml": "2.9.0"
|
|
116
|
+
"yaml": "2.9.0",
|
|
117
|
+
"yoga-wasm-web": "^0.3.3"
|
|
112
118
|
},
|
|
113
119
|
"devDependencies": {
|
|
114
120
|
"@types/diff": "8.0.0",
|
|
@@ -116,6 +122,7 @@
|
|
|
116
122
|
"@types/node": "26.1.0",
|
|
117
123
|
"@types/ssh2": "1.15.5",
|
|
118
124
|
"@types/ws": "^8.18.1",
|
|
125
|
+
"miniflare": "4.20260730.0",
|
|
119
126
|
"ssh2": "1.17.0",
|
|
120
127
|
"tsx": "4.23.0",
|
|
121
128
|
"typescript": "6.0.3",
|