primcli 1.43.0 → 1.45.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/README.md +12 -0
- package/dist/oclif/index.js +407 -55
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -923,6 +923,7 @@ session on this machine register itself and answer Primitive mail, and
|
|
|
923
923
|
primitive machine doctor --json
|
|
924
924
|
primitive machine doctor --fix --json
|
|
925
925
|
primitive machine doctor --fix --check claude.hook.stop --json
|
|
926
|
+
primitive machine doctor --fix --check claude.hook.stop --profile my-agent
|
|
926
927
|
```
|
|
927
928
|
|
|
928
929
|
Each check reports `{id, title, status, detail, fixable, fixed?, action?, path?}`
|
|
@@ -942,6 +943,17 @@ content) and `~/.omp/agent/AGENTS.md`, the bundled primitive-connect skill, and
|
|
|
942
943
|
saved agent profiles that were disconnected in Primitive (moved aside locally;
|
|
943
944
|
nothing changes server side).
|
|
944
945
|
|
|
946
|
+
`claude.hook.stop` names every per-session receive hook a repair would change
|
|
947
|
+
in `items: [{profile, session, hook, state}]`, where `hook` is the Claude event
|
|
948
|
+
(`Stop`, `SessionStart` or `PostToolUse`) and `state` is `missing`, `stale`,
|
|
949
|
+
`duplicate`, `outdated` or `old_address`; `profile` and `session` are null for
|
|
950
|
+
hooks written by old CLI versions that did not record them. After `--fix` it
|
|
951
|
+
also reports `changes`, the same fields plus `action` (`added`, `removed` or
|
|
952
|
+
`updated`), for each hook it changed. `--profile <name>` (repeatable, with
|
|
953
|
+
`--fix`) limits those per-session hook changes to the named profiles; combine it
|
|
954
|
+
with `--check claude.hook.stop` so nothing else is repaired. With `--profile`,
|
|
955
|
+
each item also carries `selected: false` when this run leaves it unchanged.
|
|
956
|
+
|
|
945
957
|
Repairs only touch Primitive-owned hooks, blocks and skill copies. Any existing
|
|
946
958
|
file is backed up beside itself as `<file>.primitive-bak-<timestamp>` before it
|
|
947
959
|
changes, writes are atomic, and a second `--fix` with nothing drifted changes
|
package/dist/oclif/index.js
CHANGED
|
@@ -13069,6 +13069,38 @@ const openapiDocument = {
|
|
|
13069
13069
|
"attachments_download_available": {
|
|
13070
13070
|
"type": "boolean",
|
|
13071
13071
|
"description": "Whether an inline attachment archive has been successfully retained and is available for download. Download authorization is checked separately; address-bound agent connection keys cannot download archives."
|
|
13072
|
+
},
|
|
13073
|
+
"to_addresses": {
|
|
13074
|
+
"type": ["array", "null"],
|
|
13075
|
+
"items": { "type": "string" },
|
|
13076
|
+
"description": "Every normalized To recipient of the send. Always populated on\nnew sends (a single-recipient send carries a one-element array);\nnull only on records that predate this field. `to_address` keeps\ncarrying the first recipient.\n"
|
|
13077
|
+
},
|
|
13078
|
+
"cc": {
|
|
13079
|
+
"type": ["array", "null"],
|
|
13080
|
+
"items": { "type": "string" },
|
|
13081
|
+
"description": "Normalized Cc address list. Null when the send had no Cc\nrecipients.\n"
|
|
13082
|
+
},
|
|
13083
|
+
"bcc": {
|
|
13084
|
+
"type": ["array", "null"],
|
|
13085
|
+
"items": { "type": "string" },
|
|
13086
|
+
"description": "Normalized Bcc address list, surfaced on the sending\norganization's own record. Bcc recipients are never written\ninto the transmitted message. Null when the send had no Bcc\nrecipients. Also null when the request uses an address-bound\nagent connection key whose address is not the sender, because\nBcc is withheld from recipients.\n"
|
|
13087
|
+
},
|
|
13088
|
+
"reply_to": {
|
|
13089
|
+
"type": ["array", "null"],
|
|
13090
|
+
"items": { "type": "string" },
|
|
13091
|
+
"description": "Normalized Reply-To address list. Null when the send had none.\n"
|
|
13092
|
+
},
|
|
13093
|
+
"tags": {
|
|
13094
|
+
"type": ["array", "null"],
|
|
13095
|
+
"description": "Customer metadata tags stored verbatim at send time. Null when\nthe send had none. Also null when the request uses an\naddress-bound agent connection key whose address is not the\nsender, because tags are withheld from recipients.\n",
|
|
13096
|
+
"items": {
|
|
13097
|
+
"type": "object",
|
|
13098
|
+
"properties": {
|
|
13099
|
+
"name": { "type": "string" },
|
|
13100
|
+
"value": { "type": "string" }
|
|
13101
|
+
},
|
|
13102
|
+
"required": ["name", "value"]
|
|
13103
|
+
}
|
|
13072
13104
|
}
|
|
13073
13105
|
}
|
|
13074
13106
|
}],
|
|
@@ -31319,6 +31351,38 @@ const operationManifest = [
|
|
|
31319
31351
|
"attachments_download_available": {
|
|
31320
31352
|
"type": "boolean",
|
|
31321
31353
|
"description": "Whether an inline attachment archive has been successfully retained and is available for download. Download authorization is checked separately; address-bound agent connection keys cannot download archives."
|
|
31354
|
+
},
|
|
31355
|
+
"to_addresses": {
|
|
31356
|
+
"type": ["array", "null"],
|
|
31357
|
+
"items": { "type": "string" },
|
|
31358
|
+
"description": "Every normalized To recipient of the send. Always populated on\nnew sends (a single-recipient send carries a one-element array);\nnull only on records that predate this field. `to_address` keeps\ncarrying the first recipient.\n"
|
|
31359
|
+
},
|
|
31360
|
+
"cc": {
|
|
31361
|
+
"type": ["array", "null"],
|
|
31362
|
+
"items": { "type": "string" },
|
|
31363
|
+
"description": "Normalized Cc address list. Null when the send had no Cc\nrecipients.\n"
|
|
31364
|
+
},
|
|
31365
|
+
"bcc": {
|
|
31366
|
+
"type": ["array", "null"],
|
|
31367
|
+
"items": { "type": "string" },
|
|
31368
|
+
"description": "Normalized Bcc address list, surfaced on the sending\norganization's own record. Bcc recipients are never written\ninto the transmitted message. Null when the send had no Bcc\nrecipients. Also null when the request uses an address-bound\nagent connection key whose address is not the sender, because\nBcc is withheld from recipients.\n"
|
|
31369
|
+
},
|
|
31370
|
+
"reply_to": {
|
|
31371
|
+
"type": ["array", "null"],
|
|
31372
|
+
"items": { "type": "string" },
|
|
31373
|
+
"description": "Normalized Reply-To address list. Null when the send had none.\n"
|
|
31374
|
+
},
|
|
31375
|
+
"tags": {
|
|
31376
|
+
"type": ["array", "null"],
|
|
31377
|
+
"description": "Customer metadata tags stored verbatim at send time. Null when\nthe send had none. Also null when the request uses an\naddress-bound agent connection key whose address is not the\nsender, because tags are withheld from recipients.\n",
|
|
31378
|
+
"items": {
|
|
31379
|
+
"type": "object",
|
|
31380
|
+
"properties": {
|
|
31381
|
+
"name": { "type": "string" },
|
|
31382
|
+
"value": { "type": "string" }
|
|
31383
|
+
},
|
|
31384
|
+
"required": ["name", "value"]
|
|
31385
|
+
}
|
|
31322
31386
|
}
|
|
31323
31387
|
}
|
|
31324
31388
|
}],
|
|
@@ -32022,6 +32086,38 @@ const operationManifest = [
|
|
|
32022
32086
|
"attachments_download_available": {
|
|
32023
32087
|
"type": "boolean",
|
|
32024
32088
|
"description": "Whether an inline attachment archive has been successfully retained and is available for download. Download authorization is checked separately; address-bound agent connection keys cannot download archives."
|
|
32089
|
+
},
|
|
32090
|
+
"to_addresses": {
|
|
32091
|
+
"type": ["array", "null"],
|
|
32092
|
+
"items": { "type": "string" },
|
|
32093
|
+
"description": "Every normalized To recipient of the send. Always populated on\nnew sends (a single-recipient send carries a one-element array);\nnull only on records that predate this field. `to_address` keeps\ncarrying the first recipient.\n"
|
|
32094
|
+
},
|
|
32095
|
+
"cc": {
|
|
32096
|
+
"type": ["array", "null"],
|
|
32097
|
+
"items": { "type": "string" },
|
|
32098
|
+
"description": "Normalized Cc address list. Null when the send had no Cc\nrecipients.\n"
|
|
32099
|
+
},
|
|
32100
|
+
"bcc": {
|
|
32101
|
+
"type": ["array", "null"],
|
|
32102
|
+
"items": { "type": "string" },
|
|
32103
|
+
"description": "Normalized Bcc address list, surfaced on the sending\norganization's own record. Bcc recipients are never written\ninto the transmitted message. Null when the send had no Bcc\nrecipients. Also null when the request uses an address-bound\nagent connection key whose address is not the sender, because\nBcc is withheld from recipients.\n"
|
|
32104
|
+
},
|
|
32105
|
+
"reply_to": {
|
|
32106
|
+
"type": ["array", "null"],
|
|
32107
|
+
"items": { "type": "string" },
|
|
32108
|
+
"description": "Normalized Reply-To address list. Null when the send had none.\n"
|
|
32109
|
+
},
|
|
32110
|
+
"tags": {
|
|
32111
|
+
"type": ["array", "null"],
|
|
32112
|
+
"description": "Customer metadata tags stored verbatim at send time. Null when\nthe send had none. Also null when the request uses an\naddress-bound agent connection key whose address is not the\nsender, because tags are withheld from recipients.\n",
|
|
32113
|
+
"items": {
|
|
32114
|
+
"type": "object",
|
|
32115
|
+
"properties": {
|
|
32116
|
+
"name": { "type": "string" },
|
|
32117
|
+
"value": { "type": "string" }
|
|
32118
|
+
},
|
|
32119
|
+
"required": ["name", "value"]
|
|
32120
|
+
}
|
|
32025
32121
|
}
|
|
32026
32122
|
}
|
|
32027
32123
|
}],
|
|
@@ -33009,6 +33105,38 @@ const operationManifest = [
|
|
|
33009
33105
|
"attachments_download_available": {
|
|
33010
33106
|
"type": "boolean",
|
|
33011
33107
|
"description": "Whether an inline attachment archive has been successfully retained and is available for download. Download authorization is checked separately; address-bound agent connection keys cannot download archives."
|
|
33108
|
+
},
|
|
33109
|
+
"to_addresses": {
|
|
33110
|
+
"type": ["array", "null"],
|
|
33111
|
+
"items": { "type": "string" },
|
|
33112
|
+
"description": "Every normalized To recipient of the send. Always populated on\nnew sends (a single-recipient send carries a one-element array);\nnull only on records that predate this field. `to_address` keeps\ncarrying the first recipient.\n"
|
|
33113
|
+
},
|
|
33114
|
+
"cc": {
|
|
33115
|
+
"type": ["array", "null"],
|
|
33116
|
+
"items": { "type": "string" },
|
|
33117
|
+
"description": "Normalized Cc address list. Null when the send had no Cc\nrecipients.\n"
|
|
33118
|
+
},
|
|
33119
|
+
"bcc": {
|
|
33120
|
+
"type": ["array", "null"],
|
|
33121
|
+
"items": { "type": "string" },
|
|
33122
|
+
"description": "Normalized Bcc address list, surfaced on the sending\norganization's own record. Bcc recipients are never written\ninto the transmitted message. Null when the send had no Bcc\nrecipients. Also null when the request uses an address-bound\nagent connection key whose address is not the sender, because\nBcc is withheld from recipients.\n"
|
|
33123
|
+
},
|
|
33124
|
+
"reply_to": {
|
|
33125
|
+
"type": ["array", "null"],
|
|
33126
|
+
"items": { "type": "string" },
|
|
33127
|
+
"description": "Normalized Reply-To address list. Null when the send had none.\n"
|
|
33128
|
+
},
|
|
33129
|
+
"tags": {
|
|
33130
|
+
"type": ["array", "null"],
|
|
33131
|
+
"description": "Customer metadata tags stored verbatim at send time. Null when\nthe send had none. Also null when the request uses an\naddress-bound agent connection key whose address is not the\nsender, because tags are withheld from recipients.\n",
|
|
33132
|
+
"items": {
|
|
33133
|
+
"type": "object",
|
|
33134
|
+
"properties": {
|
|
33135
|
+
"name": { "type": "string" },
|
|
33136
|
+
"value": { "type": "string" }
|
|
33137
|
+
},
|
|
33138
|
+
"required": ["name", "value"]
|
|
33139
|
+
}
|
|
33012
33140
|
}
|
|
33013
33141
|
}
|
|
33014
33142
|
}],
|
|
@@ -35617,6 +35745,36 @@ function writeClaudeSettings(params) {
|
|
|
35617
35745
|
release();
|
|
35618
35746
|
}
|
|
35619
35747
|
}
|
|
35748
|
+
const STATE_PHRASES = {
|
|
35749
|
+
missing: "missing",
|
|
35750
|
+
stale: "session disconnected or removed",
|
|
35751
|
+
duplicate: "duplicate",
|
|
35752
|
+
outdated: "old CLI path",
|
|
35753
|
+
old_address: "old agent address"
|
|
35754
|
+
};
|
|
35755
|
+
/** "profile my-agent, session 1234...": who a session hook serves. */
|
|
35756
|
+
function describeSessionHookOwner(item) {
|
|
35757
|
+
const profile = item.profile ? `profile ${item.profile}` : "a profile an old CLI did not record";
|
|
35758
|
+
return item.session ? `${profile}, session ${item.session}` : profile;
|
|
35759
|
+
}
|
|
35760
|
+
/** One short line naming a hook, its owner and its state. */
|
|
35761
|
+
function describeSessionHookItem(item) {
|
|
35762
|
+
return `${item.hook} hook for ${describeSessionHookOwner(item)}: ${STATE_PHRASES[item.state]}`;
|
|
35763
|
+
}
|
|
35764
|
+
/** One short line naming what a repair did to a hook. */
|
|
35765
|
+
function describeSessionHookChange(change) {
|
|
35766
|
+
return `${change.action === "added" ? "Added" : change.action === "removed" ? "Removed" : "Rewrote"} ${change.hook} hook for ${describeSessionHookOwner(change)} (${STATE_PHRASES[change.state]})`;
|
|
35767
|
+
}
|
|
35768
|
+
/** Group items by owner, at most `limit` owners, for a one-line detail. */
|
|
35769
|
+
function summarizeOwners(items, limit = 3) {
|
|
35770
|
+
const owners = [];
|
|
35771
|
+
for (const item of items) {
|
|
35772
|
+
const owner = describeSessionHookOwner(item);
|
|
35773
|
+
if (!owners.includes(owner)) owners.push(owner);
|
|
35774
|
+
}
|
|
35775
|
+
const shown = owners.slice(0, limit).join("; ");
|
|
35776
|
+
return owners.length > limit ? `${shown}; and ${owners.length - limit} more` : shown;
|
|
35777
|
+
}
|
|
35620
35778
|
const GLOBAL_HOOKS = {
|
|
35621
35779
|
"claude.hook.session_start": {
|
|
35622
35780
|
event: "SessionStart",
|
|
@@ -35804,6 +35962,87 @@ function boundSessionProfiles(configDir) {
|
|
|
35804
35962
|
} catch {}
|
|
35805
35963
|
return bound;
|
|
35806
35964
|
}
|
|
35965
|
+
function boundKey(item) {
|
|
35966
|
+
return [
|
|
35967
|
+
resolve(item.configDir),
|
|
35968
|
+
item.profile,
|
|
35969
|
+
item.session
|
|
35970
|
+
].join("\0");
|
|
35971
|
+
}
|
|
35972
|
+
/**
|
|
35973
|
+
* Bound profiles whose receive hooks are entirely absent while the same
|
|
35974
|
+
* session receives through another connected profile, or while several profiles are
|
|
35975
|
+
* bound to it and none receives. Such a profile is not restored without
|
|
35976
|
+
* being named: its hooks were removed on purpose, or it would give the
|
|
35977
|
+
* session a second address. A profile that still has any receive hook for
|
|
35978
|
+
* the session, or the only profile bound to a session, is restored as usual.
|
|
35979
|
+
*/
|
|
35980
|
+
/**
|
|
35981
|
+
* For each session, the connected profiles it receives as through the
|
|
35982
|
+
* per-session receive hooks in `hooks`, keyed like `boundKey`. A hook for a
|
|
35983
|
+
* disconnected or removed profile is stale (machine doctor removes it), so
|
|
35984
|
+
* the session does not receive through it.
|
|
35985
|
+
*/
|
|
35986
|
+
function receivingProfilesBySession(hooks, always) {
|
|
35987
|
+
const receiving = /* @__PURE__ */ new Map();
|
|
35988
|
+
for (const event of SESSION_EVENTS) {
|
|
35989
|
+
const entries = hooks[event];
|
|
35990
|
+
if (!Array.isArray(entries)) continue;
|
|
35991
|
+
for (const entry of entries) {
|
|
35992
|
+
if (!record$4(entry) || !Array.isArray(entry.hooks)) continue;
|
|
35993
|
+
for (const hook of entry.hooks) {
|
|
35994
|
+
const parsed = parseSessionHook(hook);
|
|
35995
|
+
if (!parsed?.profile || !parsed.session) continue;
|
|
35996
|
+
const session = parsed.session.toLowerCase();
|
|
35997
|
+
const key = [
|
|
35998
|
+
resolve(parsed.configDir),
|
|
35999
|
+
parsed.profile,
|
|
36000
|
+
session
|
|
36001
|
+
].join("\0");
|
|
36002
|
+
if (key !== always && (!existsSync(parsed.configDir) || !profileStillConnected(parsed.configDir, parsed.profile))) continue;
|
|
36003
|
+
const profiles = receiving.get(session) ?? /* @__PURE__ */ new Set();
|
|
36004
|
+
profiles.add(key);
|
|
36005
|
+
receiving.set(session, profiles);
|
|
36006
|
+
}
|
|
36007
|
+
}
|
|
36008
|
+
}
|
|
36009
|
+
return receiving;
|
|
36010
|
+
}
|
|
36011
|
+
/**
|
|
36012
|
+
* The other connected profiles a session already receives as, when `target`
|
|
36013
|
+
* itself has no receive hook for it. Adding `target`'s hooks then would give
|
|
36014
|
+
* the session a second address; an empty list means it would not (the
|
|
36015
|
+
* session receives as nobody else, or `target` already has hooks there and
|
|
36016
|
+
* only needs them repaired). Uses the same rule as machine doctor, so an
|
|
36017
|
+
* automatic install never binds a profile doctor would leave alone.
|
|
36018
|
+
*/
|
|
36019
|
+
function otherSessionReceivers(hooks, target) {
|
|
36020
|
+
const session = target.session.toLowerCase();
|
|
36021
|
+
const own = [
|
|
36022
|
+
resolve(target.configDir),
|
|
36023
|
+
target.profile,
|
|
36024
|
+
session
|
|
36025
|
+
].join("\0");
|
|
36026
|
+
const present = receivingProfilesBySession(hooks, own).get(session) ?? /* @__PURE__ */ new Set();
|
|
36027
|
+
if (present.has(own)) return [];
|
|
36028
|
+
return [...present].map((other) => other.split("\0")[1] ?? "").sort();
|
|
36029
|
+
}
|
|
36030
|
+
function heldSessionProfiles(hooks, bound, restore) {
|
|
36031
|
+
const receiving = receivingProfilesBySession(hooks);
|
|
36032
|
+
const held = /* @__PURE__ */ new Map();
|
|
36033
|
+
for (const item of bound) {
|
|
36034
|
+
const key = boundKey(item);
|
|
36035
|
+
const present = receiving.get(item.session) ?? /* @__PURE__ */ new Set();
|
|
36036
|
+
if (present.has(key) || restore?.has(item.profile)) continue;
|
|
36037
|
+
const others = [...present].map((other) => other.split("\0")[1] ?? "");
|
|
36038
|
+
const siblings = bound.filter((candidate) => candidate.session === item.session);
|
|
36039
|
+
if (others.length > 0 || siblings.length > 1) held.set(key, {
|
|
36040
|
+
item,
|
|
36041
|
+
receivingAs: others.sort()
|
|
36042
|
+
});
|
|
36043
|
+
}
|
|
36044
|
+
return held;
|
|
36045
|
+
}
|
|
35807
36046
|
/** Receive hooks each bound profile should have and does not. */
|
|
35808
36047
|
function missingSessionHooks(hooks, bound, node) {
|
|
35809
36048
|
return bound.flatMap((item) => SESSION_EVENTS.filter((event) => !hasSessionReceiveHook(hooks, event, item, node)).map((event) => ({
|
|
@@ -35811,6 +36050,34 @@ function missingSessionHooks(hooks, bound, node) {
|
|
|
35811
36050
|
bound: item
|
|
35812
36051
|
})));
|
|
35813
36052
|
}
|
|
36053
|
+
/** Every hook judged for repair, in settings order, then every missing one. */
|
|
36054
|
+
function sessionHookItems(hooks, verdicts, missing) {
|
|
36055
|
+
const items = missing.map(({ event, bound }) => ({
|
|
36056
|
+
profile: bound.profile,
|
|
36057
|
+
session: bound.session,
|
|
36058
|
+
hook: event,
|
|
36059
|
+
state: "missing"
|
|
36060
|
+
}));
|
|
36061
|
+
for (const event of SESSION_EVENTS) {
|
|
36062
|
+
const entries = hooks[event];
|
|
36063
|
+
if (!Array.isArray(entries)) continue;
|
|
36064
|
+
for (const entry of entries) {
|
|
36065
|
+
if (!record$4(entry) || !Array.isArray(entry.hooks)) continue;
|
|
36066
|
+
for (const hook of entry.hooks) {
|
|
36067
|
+
const verdict = verdicts.get(hook);
|
|
36068
|
+
const parsed = parseSessionHook(hook);
|
|
36069
|
+
if (!verdict || verdict === "keep" || !parsed) continue;
|
|
36070
|
+
items.push({
|
|
36071
|
+
profile: parsed.profile,
|
|
36072
|
+
session: parsed.session?.toLowerCase() ?? null,
|
|
36073
|
+
hook: event,
|
|
36074
|
+
state: verdict
|
|
36075
|
+
});
|
|
36076
|
+
}
|
|
36077
|
+
}
|
|
36078
|
+
}
|
|
36079
|
+
return items;
|
|
36080
|
+
}
|
|
35814
36081
|
/** The exact entry `agent connect` and `session-register` install. */
|
|
35815
36082
|
function sessionHookEntry(event, item, cli) {
|
|
35816
36083
|
const pending = SESSION_HOOK_KIND[event] === "pending";
|
|
@@ -35884,7 +36151,9 @@ function inspectClaudeHooks(settings, context) {
|
|
|
35884
36151
|
};
|
|
35885
36152
|
return findings;
|
|
35886
36153
|
}
|
|
35887
|
-
const
|
|
36154
|
+
const held = heldSessionProfiles(hooks, context.bound ?? [], context.restoreProfiles);
|
|
36155
|
+
const bound = (context.bound ?? []).filter((item) => !held.has(boundKey(item)));
|
|
36156
|
+
const heldNote = held.size ? ` Left alone: ${[...held.values()].map(({ item, receivingAs }) => `profile ${item.profile} is bound to session ${item.session}, which ${receivingAs.length ? `receives as ${receivingAs.join(", ")}` : "has several bound profiles and receives as none"}`).join("; ")}. Run \`primitive machine doctor --fix --profile <profile>\` to restore one deliberately.` : "";
|
|
35888
36157
|
const counts = {
|
|
35889
36158
|
keep: 0,
|
|
35890
36159
|
stale: 0,
|
|
@@ -35892,8 +36161,11 @@ function inspectClaudeHooks(settings, context) {
|
|
|
35892
36161
|
outdated: 0,
|
|
35893
36162
|
old_address: 0
|
|
35894
36163
|
};
|
|
35895
|
-
|
|
35896
|
-
|
|
36164
|
+
const verdicts = judgeSessionHooks(hooks, context.cli, bound);
|
|
36165
|
+
for (const verdict of verdicts.values()) counts[verdict]++;
|
|
36166
|
+
const missingHooks = missingSessionHooks(hooks, bound, context.cli?.node ?? process.execPath);
|
|
36167
|
+
const missing = missingHooks.length;
|
|
36168
|
+
const items = sessionHookItems(hooks, verdicts, missingHooks);
|
|
35897
36169
|
const problems = [
|
|
35898
36170
|
missing ? `${missing} missing for connected sessions` : "",
|
|
35899
36171
|
counts.old_address ? `${counts.old_address} naming an old agent address` : "",
|
|
@@ -35903,11 +36175,12 @@ function inspectClaudeHooks(settings, context) {
|
|
|
35903
36175
|
].filter(Boolean);
|
|
35904
36176
|
findings["claude.hook.stop"] = problems.length ? {
|
|
35905
36177
|
status: "fail",
|
|
35906
|
-
detail: `Per-session receive hooks need cleanup: ${problems.join(", ")}.${blockedNote}`,
|
|
35907
|
-
fixable: canFix || counts.outdated === 0 && missing === 0 && context.blocked === null
|
|
36178
|
+
detail: `Per-session receive hooks need cleanup: ${problems.join(", ")}. Affected: ${summarizeOwners(items)}.${blockedNote}${heldNote}`,
|
|
36179
|
+
fixable: canFix || counts.outdated === 0 && missing === 0 && context.blocked === null,
|
|
36180
|
+
items
|
|
35908
36181
|
} : {
|
|
35909
36182
|
status: "ok",
|
|
35910
|
-
detail: counts.keep ? `${counts.keep} per-session receive hooks, each present once and current.` : "No per-session receive hooks; each registered session adds its own exact-session hooks."
|
|
36183
|
+
detail: `${counts.keep ? `${counts.keep} per-session receive hooks, each present once and current.` : "No per-session receive hooks; each registered session adds its own exact-session hooks."}${heldNote}`,
|
|
35911
36184
|
fixable: false
|
|
35912
36185
|
};
|
|
35913
36186
|
return findings;
|
|
@@ -35939,8 +36212,10 @@ function rewriteEntries(entries, transform) {
|
|
|
35939
36212
|
* Primitive-owned hooks are added, rewritten or removed; every other key,
|
|
35940
36213
|
* entry and sibling hook is carried over unchanged.
|
|
35941
36214
|
*/
|
|
35942
|
-
function repairClaudeHooks(settings, context, checks) {
|
|
36215
|
+
function repairClaudeHooks(settings, context, checks, options = {}) {
|
|
35943
36216
|
const changed = /* @__PURE__ */ new Set();
|
|
36217
|
+
const changes = [];
|
|
36218
|
+
const selected = (profile) => !options.profiles || profile !== null && options.profiles.has(profile);
|
|
35944
36219
|
const hooks = record$4(settings.hooks) ? { ...settings.hooks } : {};
|
|
35945
36220
|
const cli = context.blocked === null ? context.cli : null;
|
|
35946
36221
|
for (const [id, spec] of Object.entries(GLOBAL_HOOKS)) {
|
|
@@ -35973,19 +36248,38 @@ function repairClaudeHooks(settings, context, checks) {
|
|
|
35973
36248
|
}
|
|
35974
36249
|
if (checks.has("claude.hook.stop") && context.blocked === null) {
|
|
35975
36250
|
if (!SESSION_EVENTS.some((event) => eventEntries(hooks, event) === "invalid")) {
|
|
35976
|
-
const
|
|
36251
|
+
const held = heldSessionProfiles(hooks, context.bound ?? [], context.restoreProfiles);
|
|
36252
|
+
const bound = (context.bound ?? []).filter((item) => !held.has(boundKey(item)));
|
|
35977
36253
|
const verdicts = judgeSessionHooks(hooks, context.cli, bound);
|
|
35978
36254
|
for (const event of SESSION_EVENTS) {
|
|
35979
36255
|
const entries = eventEntries(hooks, event);
|
|
35980
36256
|
if (entries === "invalid" || entries.length === 0) continue;
|
|
35981
36257
|
const result = rewriteEntries(entries, (hook) => {
|
|
35982
36258
|
const verdict = verdicts.get(hook);
|
|
35983
|
-
if (verdict === "stale" || verdict === "duplicate" || verdict === "old_address") return void 0;
|
|
35984
|
-
if (verdict !== "outdated" || !cli || !record$4(hook)) return hook;
|
|
35985
36259
|
const parsed = parseSessionHook(hook);
|
|
36260
|
+
if (!verdict || verdict === "keep" || !parsed) return hook;
|
|
36261
|
+
if (!selected(parsed.profile)) return hook;
|
|
36262
|
+
const item = {
|
|
36263
|
+
profile: parsed.profile,
|
|
36264
|
+
session: parsed.session?.toLowerCase() ?? null,
|
|
36265
|
+
hook: event,
|
|
36266
|
+
state: verdict
|
|
36267
|
+
};
|
|
36268
|
+
if (verdict === "stale" || verdict === "duplicate" || verdict === "old_address") {
|
|
36269
|
+
changes.push({
|
|
36270
|
+
...item,
|
|
36271
|
+
action: "removed"
|
|
36272
|
+
});
|
|
36273
|
+
return;
|
|
36274
|
+
}
|
|
36275
|
+
if (!cli || !record$4(hook)) return hook;
|
|
35986
36276
|
const args = [...hook.args];
|
|
35987
|
-
args[0] = parsed
|
|
36277
|
+
args[0] = parsed.kind === "pending" ? cli.pending : cli.wake;
|
|
35988
36278
|
args[1] = cli.entry;
|
|
36279
|
+
changes.push({
|
|
36280
|
+
...item,
|
|
36281
|
+
action: "updated"
|
|
36282
|
+
});
|
|
35989
36283
|
return {
|
|
35990
36284
|
...hook,
|
|
35991
36285
|
command: cli.node,
|
|
@@ -35998,8 +36292,16 @@ function repairClaudeHooks(settings, context, checks) {
|
|
|
35998
36292
|
}
|
|
35999
36293
|
}
|
|
36000
36294
|
if (cli) for (const missing of missingSessionHooks(hooks, bound, cli.node)) {
|
|
36295
|
+
if (!selected(missing.bound.profile)) continue;
|
|
36001
36296
|
const entries = eventEntries(hooks, missing.event);
|
|
36002
36297
|
if (entries === "invalid") continue;
|
|
36298
|
+
changes.push({
|
|
36299
|
+
profile: missing.bound.profile,
|
|
36300
|
+
session: missing.bound.session,
|
|
36301
|
+
hook: missing.event,
|
|
36302
|
+
state: "missing",
|
|
36303
|
+
action: "added"
|
|
36304
|
+
});
|
|
36003
36305
|
hooks[missing.event] = [...entries, sessionHookEntry(missing.event, missing.bound, cli)];
|
|
36004
36306
|
changed.add("claude.hook.stop");
|
|
36005
36307
|
}
|
|
@@ -36007,14 +36309,16 @@ function repairClaudeHooks(settings, context, checks) {
|
|
|
36007
36309
|
}
|
|
36008
36310
|
if (!changed.size) return {
|
|
36009
36311
|
settings,
|
|
36010
|
-
changed
|
|
36312
|
+
changed,
|
|
36313
|
+
changes
|
|
36011
36314
|
};
|
|
36012
36315
|
return {
|
|
36013
36316
|
settings: {
|
|
36014
36317
|
...settings,
|
|
36015
36318
|
hooks
|
|
36016
36319
|
},
|
|
36017
|
-
changed
|
|
36320
|
+
changed,
|
|
36321
|
+
changes
|
|
36018
36322
|
};
|
|
36019
36323
|
}
|
|
36020
36324
|
//#endregion
|
|
@@ -36126,11 +36430,18 @@ function installClaudeWakeHook(options) {
|
|
|
36126
36430
|
const agentAddress = mailAddress(options.agentAddress);
|
|
36127
36431
|
if (!SESSION_UUID$1.test(options.sessionId)) return "unavailable";
|
|
36128
36432
|
const sessionId = options.sessionId.toLowerCase();
|
|
36129
|
-
|
|
36433
|
+
const claudeDir = resolve((options.env ?? process.env).CLAUDE_CONFIG_DIR || join(homedir(), ".claude"));
|
|
36434
|
+
let held = false;
|
|
36435
|
+
if (!editClaudeSettings(claudeDir, (settings) => {
|
|
36130
36436
|
const hooks = settings.hooks ?? {};
|
|
36131
36437
|
if (!record$3(hooks)) throw new Error("Invalid hook settings");
|
|
36132
36438
|
for (const event of WAKE_EVENTS) if (!Array.isArray(hooks[event] ?? [])) throw new Error(`Invalid ${event} hooks`);
|
|
36133
36439
|
if (!Array.isArray(hooks[PENDING_EVENT] ?? [])) throw new Error(`Invalid ${PENDING_EVENT} hooks`);
|
|
36440
|
+
held = options.yieldToOtherProfiles === true && otherSessionReceivers(hooks, {
|
|
36441
|
+
configDir,
|
|
36442
|
+
profile: profileName,
|
|
36443
|
+
session: sessionId
|
|
36444
|
+
}).length > 0;
|
|
36134
36445
|
const hook = {
|
|
36135
36446
|
type: "command",
|
|
36136
36447
|
command: process.execPath,
|
|
@@ -36174,19 +36485,26 @@ function installClaudeWakeHook(options) {
|
|
|
36174
36485
|
}
|
|
36175
36486
|
};
|
|
36176
36487
|
const nextHooks = { ...hooks };
|
|
36177
|
-
for (const event of WAKE_EVENTS)
|
|
36178
|
-
|
|
36179
|
-
const
|
|
36180
|
-
|
|
36181
|
-
|
|
36182
|
-
|
|
36183
|
-
|
|
36184
|
-
|
|
36185
|
-
|
|
36186
|
-
|
|
36187
|
-
|
|
36188
|
-
|
|
36189
|
-
|
|
36488
|
+
for (const event of WAKE_EVENTS) {
|
|
36489
|
+
const entries = hooks[event];
|
|
36490
|
+
const retained = (entries ?? []).flatMap((entry) => {
|
|
36491
|
+
if (!record$3(entry) || !Array.isArray(entry.hooks)) return [entry];
|
|
36492
|
+
const siblings = entry.hooks.filter((candidate) => !isOwnHook(candidate));
|
|
36493
|
+
if (siblings.length === entry.hooks.length) return [entry];
|
|
36494
|
+
return siblings.length ? [{
|
|
36495
|
+
...entry,
|
|
36496
|
+
hooks: siblings
|
|
36497
|
+
}] : [];
|
|
36498
|
+
});
|
|
36499
|
+
if (held) {
|
|
36500
|
+
if (entries) nextHooks[event] = retained;
|
|
36501
|
+
} else nextHooks[event] = [...retained, event === "SessionStart" ? {
|
|
36502
|
+
matcher: "resume",
|
|
36503
|
+
hooks: [hook]
|
|
36504
|
+
} : { hooks: [hook] }];
|
|
36505
|
+
}
|
|
36506
|
+
const pendingEntries = hooks[PENDING_EVENT];
|
|
36507
|
+
const pendingRetained = (pendingEntries ?? []).flatMap((entry) => {
|
|
36190
36508
|
if (!record$3(entry) || !Array.isArray(entry.hooks)) return [entry];
|
|
36191
36509
|
const siblings = entry.hooks.filter((candidate) => !isOwnPendingHook(candidate));
|
|
36192
36510
|
if (siblings.length === entry.hooks.length) return [entry];
|
|
@@ -36194,12 +36512,17 @@ function installClaudeWakeHook(options) {
|
|
|
36194
36512
|
...entry,
|
|
36195
36513
|
hooks: siblings
|
|
36196
36514
|
}] : [];
|
|
36197
|
-
})
|
|
36515
|
+
});
|
|
36516
|
+
if (held) {
|
|
36517
|
+
if (pendingEntries) nextHooks[PENDING_EVENT] = pendingRetained;
|
|
36518
|
+
} else nextHooks[PENDING_EVENT] = [...pendingRetained, { hooks: [pendingHook] }];
|
|
36519
|
+
if (held && JSON.stringify(nextHooks) === JSON.stringify(hooks)) return null;
|
|
36198
36520
|
return {
|
|
36199
36521
|
...settings,
|
|
36200
36522
|
hooks: nextHooks
|
|
36201
36523
|
};
|
|
36202
|
-
})
|
|
36524
|
+
})) return "unavailable";
|
|
36525
|
+
return held ? "held_for_other_profile" : "installed_unverified";
|
|
36203
36526
|
} catch {
|
|
36204
36527
|
return "unavailable";
|
|
36205
36528
|
}
|
|
@@ -39210,6 +39533,7 @@ function withDefined(defaults, overrides) {
|
|
|
39210
39533
|
...Object.fromEntries(Object.entries(overrides ?? {}).filter(([, value]) => value !== void 0))
|
|
39211
39534
|
};
|
|
39212
39535
|
}
|
|
39536
|
+
const HELD_DETAIL = "This session already receives as another connected profile, so this profile's receive hooks were not added. Run `primitive machine doctor --fix --profile <profile>` to bind it deliberately.";
|
|
39213
39537
|
const ENDED_DETAIL = "This session ended, so its agent was disconnected. A new address is never created for the same session.";
|
|
39214
39538
|
const ENDED_PENDING_DETAIL = "This session ended, but disconnecting its agent has not been confirmed yet; `primitive machine doctor --fix` retries it.";
|
|
39215
39539
|
/**
|
|
@@ -39328,14 +39652,19 @@ async function registerSession(options) {
|
|
|
39328
39652
|
agentInfo: seeded
|
|
39329
39653
|
} : current);
|
|
39330
39654
|
};
|
|
39331
|
-
const
|
|
39332
|
-
|
|
39333
|
-
|
|
39334
|
-
|
|
39335
|
-
|
|
39336
|
-
|
|
39337
|
-
|
|
39338
|
-
|
|
39655
|
+
const claudeHook = (agentAddress) => {
|
|
39656
|
+
const outcome = deps.installClaudeHook({
|
|
39657
|
+
cliPath: options.cliPath,
|
|
39658
|
+
configDir: options.configDir,
|
|
39659
|
+
profileName,
|
|
39660
|
+
agentAddress,
|
|
39661
|
+
sessionId,
|
|
39662
|
+
env,
|
|
39663
|
+
yieldToOtherProfiles: true
|
|
39664
|
+
});
|
|
39665
|
+
return outcome === "installed_unverified" ? "installed" : outcome === "held_for_other_profile" ? "held" : "failed";
|
|
39666
|
+
};
|
|
39667
|
+
const installHook = (profile) => runtime !== "claude" || receiverMode(agentProfileDirectory(options.configDir, profileName)) !== "external" ? "installed" : claudeHook(profile.agent_address);
|
|
39339
39668
|
const existing = loadConnectedAgentProfile(options.configDir, profileName);
|
|
39340
39669
|
if (existing) {
|
|
39341
39670
|
record = await updateSessionRecord(options.configDir, sessionId, (current) => current ?? newRecord("existing", existing.agent_address));
|
|
@@ -39369,7 +39698,7 @@ async function registerSession(options) {
|
|
|
39369
39698
|
...withAddress,
|
|
39370
39699
|
receiving: RECEIVING[runtime],
|
|
39371
39700
|
status: "already_registered",
|
|
39372
|
-
detail: hooked ? "This session is already connected." : "This session is already connected, but its receive hook could not be installed. Run `primitive machine doctor`."
|
|
39701
|
+
detail: hooked === "installed" ? "This session is already connected." : hooked === "held" ? HELD_DETAIL : "This session is already connected, but its receive hook could not be installed. Run `primitive machine doctor`."
|
|
39373
39702
|
};
|
|
39374
39703
|
}
|
|
39375
39704
|
} else if (record?.address && record.createdBy === "session-register" && !enrollmentAddress(options.configDir, profileName).exists || record?.createdBy === "existing") return {
|
|
@@ -39430,15 +39759,8 @@ async function registerSession(options) {
|
|
|
39430
39759
|
const address = result.identity.agentAddress;
|
|
39431
39760
|
const submitted = verificationReplySubmitted(result.verification.state);
|
|
39432
39761
|
const status = result.connection.status;
|
|
39433
|
-
let hooked =
|
|
39434
|
-
if (runtime === "claude" && submitted && status !== "owner_inactive") hooked =
|
|
39435
|
-
cliPath: options.cliPath,
|
|
39436
|
-
configDir: options.configDir,
|
|
39437
|
-
profileName,
|
|
39438
|
-
agentAddress: address,
|
|
39439
|
-
sessionId,
|
|
39440
|
-
env
|
|
39441
|
-
}) === "installed_unverified";
|
|
39762
|
+
let hooked = "installed";
|
|
39763
|
+
if (runtime === "claude" && submitted && status !== "owner_inactive") hooked = claudeHook(address);
|
|
39442
39764
|
if (status === "connected") await finishAgentInfo();
|
|
39443
39765
|
const withAddress = {
|
|
39444
39766
|
...known,
|
|
@@ -39448,7 +39770,7 @@ async function registerSession(options) {
|
|
|
39448
39770
|
if (status === "connected") return {
|
|
39449
39771
|
...withAddress,
|
|
39450
39772
|
status: "registered",
|
|
39451
|
-
detail: hooked ? `Connected as ${address}.` : `Connected as ${address}, but the receive hook could not be installed. Run \`primitive machine doctor\`.`
|
|
39773
|
+
detail: hooked === "installed" ? `Connected as ${address}.` : hooked === "held" ? `Connected as ${address}. ${HELD_DETAIL}` : `Connected as ${address}, but the receive hook could not be installed. Run \`primitive machine doctor\`.`
|
|
39452
39774
|
};
|
|
39453
39775
|
if (status === "revoked" || status === "owner_inactive") return {
|
|
39454
39776
|
...withAddress,
|
|
@@ -47192,7 +47514,7 @@ const PRIMITIVE_TEAM_AUTHOR = {
|
|
|
47192
47514
|
url: "https://primitive.dev"
|
|
47193
47515
|
};
|
|
47194
47516
|
const SDK_VERSION_RANGE = "^1.34.0";
|
|
47195
|
-
const CLI_VERSION_RANGE = "^1.
|
|
47517
|
+
const CLI_VERSION_RANGE = "^1.45.0";
|
|
47196
47518
|
const ESBUILD_VERSION_RANGE = "^0.27.0";
|
|
47197
47519
|
function renderHandler() {
|
|
47198
47520
|
return `// env.PRIMITIVE_API_KEY, env.PRIMITIVE_WEBHOOK_SECRET, and
|
|
@@ -54258,13 +54580,15 @@ async function runMachineDoctor(options) {
|
|
|
54258
54580
|
"claude.hook.stop"
|
|
54259
54581
|
];
|
|
54260
54582
|
const repairedAhead = /* @__PURE__ */ new Set();
|
|
54583
|
+
const sessionHookChanges = [];
|
|
54261
54584
|
let settingsRead = readClaudeSettings(paths.claudeDir);
|
|
54262
54585
|
const hookContext = {
|
|
54263
54586
|
cli,
|
|
54264
54587
|
blocked: hookBlocked,
|
|
54265
54588
|
get bound() {
|
|
54266
54589
|
return boundSessionProfiles(options.configDir);
|
|
54267
|
-
}
|
|
54590
|
+
},
|
|
54591
|
+
restoreProfiles: options.profiles
|
|
54268
54592
|
};
|
|
54269
54593
|
const hookCheck = (id) => ({
|
|
54270
54594
|
inspect: () => {
|
|
@@ -54275,22 +54599,28 @@ async function runMachineDoctor(options) {
|
|
|
54275
54599
|
return check(id, finding.status, finding.detail, {
|
|
54276
54600
|
fixable: finding.fixable,
|
|
54277
54601
|
path: join(paths.claudeDir, "settings.json"),
|
|
54278
|
-
...finding.status === "fail" && !finding.fixable && hookBlocked ? { action: "install_cli" } : {}
|
|
54602
|
+
...finding.status === "fail" && !finding.fixable && hookBlocked ? { action: "install_cli" } : {},
|
|
54603
|
+
...finding.items?.length ? { items: finding.items.map((item) => options.fix && options.profiles ? {
|
|
54604
|
+
...item,
|
|
54605
|
+
selected: item.profile !== null && options.profiles.has(item.profile)
|
|
54606
|
+
} : item) } : {},
|
|
54607
|
+
...id === "claude.hook.stop" && sessionHookChanges.length ? { changes: [...sessionHookChanges] } : {}
|
|
54279
54608
|
});
|
|
54280
54609
|
},
|
|
54281
54610
|
repair: () => {
|
|
54282
54611
|
if (!settingsRead.ok) return false;
|
|
54283
54612
|
const findings = inspectClaudeHooks(settingsRead.settings, hookContext);
|
|
54284
54613
|
const ids = HOOK_CHECK_IDS.filter((hook) => (hook === id || !options.only || options.only.has(hook)) && findings[hook].status === "fail" && findings[hook].fixable);
|
|
54285
|
-
const repaired = repairClaudeHooks(settingsRead.settings, hookContext, new Set(ids));
|
|
54614
|
+
const repaired = repairClaudeHooks(settingsRead.settings, hookContext, new Set(ids), { profiles: options.profiles });
|
|
54286
54615
|
if (!repaired.changed.size) return false;
|
|
54287
|
-
for (const hook of repaired.changed) repairedAhead.add(hook);
|
|
54288
54616
|
writeClaudeSettings({
|
|
54289
54617
|
claudeDir: paths.claudeDir,
|
|
54290
54618
|
read: settingsRead.read,
|
|
54291
54619
|
settings: repaired.settings,
|
|
54292
54620
|
now
|
|
54293
54621
|
});
|
|
54622
|
+
sessionHookChanges.push(...repaired.changes);
|
|
54623
|
+
for (const hook of repaired.changed) repairedAhead.add(hook);
|
|
54294
54624
|
settingsRead = readClaudeSettings(paths.claudeDir);
|
|
54295
54625
|
return repaired.changed.has(id);
|
|
54296
54626
|
}
|
|
@@ -54537,7 +54867,7 @@ async function runMachineDoctor(options) {
|
|
|
54537
54867
|
...after,
|
|
54538
54868
|
fixed: changed && after.status !== "fail"
|
|
54539
54869
|
};
|
|
54540
|
-
if (changed && after.status === "fail") result.detail = `${after.detail} (A repair was applied but did not resolve this.)`;
|
|
54870
|
+
if (changed && after.status === "fail") result.detail = id === "claude.hook.stop" && options.profiles ? `${after.detail} (Only the selected profiles were repaired.)` : `${after.detail} (A repair was applied but did not resolve this.)`;
|
|
54541
54871
|
} catch (error) {
|
|
54542
54872
|
result = {
|
|
54543
54873
|
...result,
|
|
@@ -54569,10 +54899,25 @@ async function runMachineDoctor(options) {
|
|
|
54569
54899
|
}
|
|
54570
54900
|
//#endregion
|
|
54571
54901
|
//#region src/oclif/commands/machine-doctor.ts
|
|
54902
|
+
const ITEM_LINES = 10;
|
|
54903
|
+
/** At most `limit` indented lines, then "and N more". */
|
|
54904
|
+
function capped(lines, limit = ITEM_LINES) {
|
|
54905
|
+
const shown = lines.slice(0, limit).map((line) => ` ${line}`);
|
|
54906
|
+
if (lines.length > limit) shown.push(` and ${lines.length - limit} more`);
|
|
54907
|
+
return shown;
|
|
54908
|
+
}
|
|
54572
54909
|
function renderCheck(check) {
|
|
54573
54910
|
const tag = check.status === "ok" ? "[OK] " : check.status === "warn" ? "[WARN]" : check.status === "skip" ? "[SKIP]" : "[FAIL]";
|
|
54574
54911
|
const fixed = check.fixed ? " (fixed)" : "";
|
|
54575
|
-
|
|
54912
|
+
const lines = [`${tag} ${check.id}${fixed}: ${check.detail}`];
|
|
54913
|
+
if (check.changes?.length) lines.push(" Changed:", ...capped(check.changes.map(describeSessionHookChange)));
|
|
54914
|
+
if (check.status !== "ok" && check.items?.length) {
|
|
54915
|
+
const selected = check.items.filter((item) => item.selected !== false);
|
|
54916
|
+
const excluded = check.items.filter((item) => item.selected === false);
|
|
54917
|
+
if (selected.length) lines.push(check.changes?.length ? " Still needing repair:" : " Would change:", ...capped(selected.map(describeSessionHookItem)));
|
|
54918
|
+
if (excluded.length) lines.push(" Not selected (left unchanged by --profile):", ...capped(excluded.map(describeSessionHookItem)));
|
|
54919
|
+
}
|
|
54920
|
+
return lines.join("\n");
|
|
54576
54921
|
}
|
|
54577
54922
|
var MachineDoctorCommand = class MachineDoctorCommand extends Command {
|
|
54578
54923
|
static summary = "Check, and optionally repair, this machine's Primitive setup for coding agents";
|
|
@@ -54582,6 +54927,7 @@ var MachineDoctorCommand = class MachineDoctorCommand extends Command {
|
|
|
54582
54927
|
"<%= config.bin %> machine doctor --json",
|
|
54583
54928
|
"<%= config.bin %> machine doctor --fix --json",
|
|
54584
54929
|
"<%= config.bin %> machine doctor --fix --check claude.hook.stop --json",
|
|
54930
|
+
"<%= config.bin %> machine doctor --fix --check claude.hook.stop --profile my-agent",
|
|
54585
54931
|
"<%= config.bin %> machine doctor --runtime claude,codex --min-cli-version 1.38.0 --json"
|
|
54586
54932
|
];
|
|
54587
54933
|
static flags = {
|
|
@@ -54591,6 +54937,10 @@ var MachineDoctorCommand = class MachineDoctorCommand extends Command {
|
|
|
54591
54937
|
multiple: true,
|
|
54592
54938
|
options: [...DOCTOR_CHECK_IDS]
|
|
54593
54939
|
}),
|
|
54940
|
+
profile: Flags.string({
|
|
54941
|
+
description: "With --fix, change per-session receive hooks only for this saved agent profile; repeatable. Naming a profile is also the only way --fix restores hooks for a profile bound to a session that already receives as another profile. Other checks are unaffected, so combine with --check claude.hook.stop to repair nothing else",
|
|
54942
|
+
multiple: true
|
|
54943
|
+
}),
|
|
54594
54944
|
runtime: Flags.string({ description: "Comma-separated runtimes to check: claude, codex, omp. Defaults to every runtime found on this machine" }),
|
|
54595
54945
|
"min-cli-version": Flags.string({ description: "Report cli.version as failing when this CLI is older than the given version" }),
|
|
54596
54946
|
json: Flags.boolean({ description: "Print the report as JSON" })
|
|
@@ -54605,9 +54955,11 @@ var MachineDoctorCommand = class MachineDoctorCommand extends Command {
|
|
|
54605
54955
|
runtimes = new Set(names);
|
|
54606
54956
|
}
|
|
54607
54957
|
if (flags.check?.length && !flags.fix) this.error("--check selects which checks --fix repairs; add --fix.", { exit: 2 });
|
|
54958
|
+
if (flags.profile?.length && !flags.fix) this.error("--profile narrows what --fix repairs; add --fix.", { exit: 2 });
|
|
54608
54959
|
const report = await runMachineDoctor({
|
|
54609
54960
|
fix: flags.fix,
|
|
54610
54961
|
only: flags.check?.length ? new Set(flags.check) : void 0,
|
|
54962
|
+
profiles: flags.profile?.length ? new Set(flags.profile) : void 0,
|
|
54611
54963
|
runtimes,
|
|
54612
54964
|
configDir: this.config.configDir,
|
|
54613
54965
|
home: homedir(),
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "primcli",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.45.0",
|
|
4
4
|
"description": "Official Primitive CLI: deploy Primitive Functions, send and inspect mail, manage endpoints, all from the terminal. Wraps the @primitivedotdev/sdk runtime client with one-shot commands.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"sideEffects": false,
|