@pushary/agent-hooks 1.7.0 → 1.7.2
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 +28 -0
- package/dist/bin/pushary-bell-hook.js +23 -23
- package/dist/bin/pushary-bell.js +10 -10
- package/dist/bin/pushary-claude.js +46 -35
- package/dist/bin/pushary-clean.js +194 -102
- package/dist/bin/pushary-codex-bridge.js +18 -17
- package/dist/bin/pushary-codex-hook.js +53 -39
- package/dist/bin/pushary-codex.js +14 -12
- package/dist/bin/pushary-connect.js +16 -16
- package/dist/bin/pushary-cowork.js +5 -5
- package/dist/bin/pushary-daemon.js +27 -27
- package/dist/bin/pushary-disconnect.js +19 -17
- package/dist/bin/pushary-doctor.js +99 -43
- package/dist/bin/pushary-elicitation-hook.js +35 -26
- package/dist/bin/pushary-gemini-bridge.js +12 -12
- package/dist/bin/pushary-gemini-hook.js +47 -32
- package/dist/bin/pushary-hook.js +34 -32
- package/dist/bin/pushary-login.js +16 -16
- package/dist/bin/pushary-logout.js +8 -8
- package/dist/bin/pushary-mcp.js +9 -8
- package/dist/bin/pushary-mode.js +10 -10
- package/dist/bin/pushary-notification-hook.js +29 -27
- package/dist/bin/pushary-opencode-hook.js +46 -32
- package/dist/bin/pushary-permission-denied-hook.js +34 -32
- package/dist/bin/pushary-permission-hook.js +33 -31
- package/dist/bin/pushary-post-hook.js +29 -27
- package/dist/bin/pushary-prompt-hook.js +29 -27
- package/dist/bin/pushary-session-end-hook.js +29 -27
- package/dist/bin/pushary-session-start-hook.js +33 -31
- package/dist/bin/pushary-setup.js +143 -105
- package/dist/bin/pushary-stats.js +5 -5
- package/dist/bin/pushary-status.js +19 -19
- package/dist/bin/pushary-stop-hook.js +29 -27
- package/dist/bin/pushary-stopfailure-hook.js +29 -27
- package/dist/bin/pushary-suggestions.js +5 -5
- package/dist/bin/pushary-transcript-register.js +19 -19
- package/dist/bin/pushary-transcripts.js +7 -7
- package/dist/bin/pushary-upgrade.js +20 -20
- package/dist/bin/pushary-wait.js +10 -10
- package/dist/bin/pushary.js +6 -6
- package/dist/{chunk-IJ6ECPA6.js → chunk-2B4XCMH6.js} +75 -232
- package/dist/{chunk-EGKXSQCY.js → chunk-2XOHXZRC.js} +12 -4
- package/dist/{chunk-D6BULEJW.js → chunk-3NSRL5E7.js} +2 -2
- package/dist/{chunk-UC65FJJT.js → chunk-3RO2UWJQ.js} +2 -2
- package/dist/{chunk-73T6MSU5.js → chunk-4S7CCOXB.js} +5 -5
- package/dist/{chunk-7OS4IHGI.js → chunk-5GIBNUBD.js} +2 -2
- package/dist/chunk-5OP5MQI7.js +45 -0
- package/dist/{chunk-B3UXMSMW.js → chunk-65RN6ZCO.js} +1 -1
- package/dist/{chunk-2PS6QWDW.js → chunk-6NHXKTQU.js} +2 -2
- package/dist/{chunk-V6QRSU2G.js → chunk-6QZK4O36.js} +4 -4
- package/dist/{chunk-ZQ7IDOKY.js → chunk-A6WQF7FQ.js} +3 -3
- package/dist/{chunk-QXAK5RFU.js → chunk-AWZNDM3B.js} +1 -1
- package/dist/{chunk-JNXVOU6Y.js → chunk-D4OGEWLH.js} +19 -2
- package/dist/{chunk-R2XCLT6I.js → chunk-E3PLF7YB.js} +4 -4
- package/dist/{chunk-2C7CCWVX.js → chunk-F6BMJOSW.js} +1 -1
- package/dist/{chunk-XNCMT7YY.js → chunk-HOIGXMI7.js} +1 -1
- package/dist/{chunk-CVALVGA7.js → chunk-I7B2DNVU.js} +1 -1
- package/dist/{chunk-S3UD4V6W.js → chunk-INR3RVQ3.js} +13 -3
- package/dist/{chunk-52NVBNJT.js → chunk-IZVAVXHQ.js} +6 -4
- package/dist/{chunk-5HLTWFJ6.js → chunk-JMSB6CJT.js} +1 -1
- package/dist/{chunk-KYQXUGH7.js → chunk-KAJT6HPI.js} +3 -3
- package/dist/{chunk-VKVIAZQO.js → chunk-KLXYMPTM.js} +1 -1
- package/dist/{chunk-PQ4F64M2.js → chunk-L6IF4JOF.js} +3 -3
- package/dist/{chunk-ROQK7IN3.js → chunk-LI3C2IQY.js} +2 -2
- package/dist/{chunk-B234346F.js → chunk-M2JGLFFS.js} +1 -1
- package/dist/chunk-MY3UZO5U.js +99 -0
- package/dist/chunk-N6IOSZOE.js +185 -0
- package/dist/{chunk-DNJUSFBL.js → chunk-NYBZJ44G.js} +40 -5
- package/dist/{chunk-BBKNJJVX.js → chunk-OJRB5QKN.js} +6 -6
- package/dist/{chunk-PUDBG3GM.js → chunk-OXEF6JHD.js} +4 -4
- package/dist/{chunk-LEZFOTMI.js → chunk-PWNQGITG.js} +1 -1
- package/dist/{chunk-B76WVEDX.js → chunk-Q7CDBJXU.js} +1 -1
- package/dist/{chunk-KXMZMZD3.js → chunk-QSZ37ROP.js} +1 -1
- package/dist/{chunk-BH2SKSSK.js → chunk-R7ZETVNR.js} +1 -1
- package/dist/{chunk-DZURML2D.js → chunk-RLRDPZG4.js} +1 -1
- package/dist/{chunk-J2355HR7.js → chunk-SEIZQZEE.js} +1 -1
- package/dist/{chunk-V4AQBAVG.js → chunk-SJN64NJX.js} +1 -1
- package/dist/{chunk-B3YQANFN.js → chunk-SKEW3TQX.js} +1 -1
- package/dist/{chunk-L4H2Q2S4.js → chunk-SKMWPXGV.js} +500 -46
- package/dist/{chunk-WUNZAE7I.js → chunk-T3PRPFDY.js} +5 -5
- package/dist/{chunk-YYINL576.js → chunk-TVJYGZXB.js} +2 -2
- package/dist/{chunk-O2LEPIU6.js → chunk-U3MS6D5X.js} +1 -1
- package/dist/{chunk-5RUIFDTP.js → chunk-U4TMAANR.js} +33 -2
- package/dist/{chunk-KW2RRR7D.js → chunk-UFNBKYWT.js} +3 -3
- package/dist/{chunk-DVQDPI3T.js → chunk-UUTWHVSV.js} +35 -35
- package/dist/{chunk-X6KDKOV5.js → chunk-VQDJ6KB3.js} +3 -3
- package/dist/{chunk-UOAQ6QMP.js → chunk-VSGKQ3HI.js} +2 -2
- package/dist/{chunk-YROGUPOZ.js → chunk-WWTLAZQA.js} +25 -8
- package/dist/{chunk-SQRWHALT.js → chunk-WXIVUIXA.js} +2 -2
- package/dist/{chunk-HDRNWNVP.js → chunk-YLCSKVQQ.js} +9 -28
- package/dist/{chunk-JXUAXO74.js → chunk-YNJ2H2FD.js} +2 -2
- package/dist/{chunk-BWYJ6AAF.js → chunk-YU4222G2.js} +2 -2
- package/dist/{chunk-4F46VIEE.js → chunk-YU54LNC6.js} +6 -6
- package/dist/{chunk-EAQMWJPG.js → chunk-ZMWX5SDA.js} +8 -8
- package/dist/{chunk-6ADZQFVW.js → chunk-ZUKZJQGR.js} +1 -1
- package/dist/{reapply-6PH3FMPX.js → reapply-D6RSDYCK.js} +9 -9
- package/dist/src/index.d.ts +1 -0
- package/dist/src/index.js +22 -20
- package/package.json +2 -1
- package/dist/chunk-DANOZVZK.js +0 -307
- package/dist/chunk-P32LPT2L.js +0 -6
- package/dist/chunk-YHG74UFF.js +0 -12
|
@@ -1,15 +1,322 @@
|
|
|
1
1
|
import {
|
|
2
2
|
MCP_BRIDGE_BINARY
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-TVJYGZXB.js";
|
|
4
4
|
import {
|
|
5
5
|
openCodeConfigDirFrom
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-SKEW3TQX.js";
|
|
7
7
|
import {
|
|
8
|
-
codexHomeFrom
|
|
9
|
-
|
|
8
|
+
codexHomeFrom,
|
|
9
|
+
resolvePathBinary
|
|
10
|
+
} from "./chunk-NYBZJ44G.js";
|
|
10
11
|
import {
|
|
11
12
|
HOOK_COMPATIBLE_PROFILES
|
|
12
|
-
} from "./chunk-
|
|
13
|
+
} from "./chunk-WWTLAZQA.js";
|
|
14
|
+
|
|
15
|
+
// src/hermes-config.ts
|
|
16
|
+
import { execFileSync } from "child_process";
|
|
17
|
+
import { existsSync, readFileSync, rmSync, writeFileSync } from "fs";
|
|
18
|
+
import { homedir, tmpdir } from "os";
|
|
19
|
+
import { join } from "path";
|
|
20
|
+
var isRecord = (value) => Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
21
|
+
var isSnapshot = (value) => isRecord(value) && typeof value.present === "boolean" && Object.hasOwn(value, "value");
|
|
22
|
+
var isHermesConfigReceipt = (value) => {
|
|
23
|
+
if (!isRecord(value)) return false;
|
|
24
|
+
return ["security", "approval", "transport", "fallback", "plugins", "enabled", "agent", "disabled"].every((key) => isSnapshot(value[key])) && typeof value.pushary_enabled === "boolean" && typeof value.clarify_disabled === "boolean";
|
|
25
|
+
};
|
|
26
|
+
var HERMES_PIP_PACKAGE = "hermes-plugin-pushary";
|
|
27
|
+
var HERMES_PLUGIN_NAME = "pushary";
|
|
28
|
+
var HERMES_CONFIG_SCRIPT = String.raw`
|
|
29
|
+
import copy, importlib.util, json, os, sys
|
|
30
|
+
from hermes_cli.config import load_config, save_config
|
|
31
|
+
|
|
32
|
+
PUSHARY_TRANSPORT = "pushary"
|
|
33
|
+
PUSHARY_FALLBACK = "builtin"
|
|
34
|
+
PUSHARY_PLUGIN = "pushary"
|
|
35
|
+
PUSHARY_PLUGIN_MODULE = "pushary_plugin"
|
|
36
|
+
QUESTION_TOOLSET = "clarify"
|
|
37
|
+
|
|
38
|
+
mode = sys.argv[1]
|
|
39
|
+
receipt_path = sys.argv[2] if len(sys.argv) > 2 else None
|
|
40
|
+
c = load_config()
|
|
41
|
+
original = copy.deepcopy(c)
|
|
42
|
+
|
|
43
|
+
def section(obj, key):
|
|
44
|
+
value = obj.get(key)
|
|
45
|
+
return value if isinstance(value, dict) else {}
|
|
46
|
+
|
|
47
|
+
def members(obj, key):
|
|
48
|
+
value = obj.get(key)
|
|
49
|
+
return value if isinstance(value, list) else []
|
|
50
|
+
|
|
51
|
+
if mode == "status":
|
|
52
|
+
print(json.dumps({
|
|
53
|
+
"plugin_installed": importlib.util.find_spec(PUSHARY_PLUGIN_MODULE) is not None,
|
|
54
|
+
"plugin_enabled": PUSHARY_PLUGIN in members(section(c, "plugins"), "enabled"),
|
|
55
|
+
"approvals_routed": section(section(c, "security"), "approval").get("transport") == PUSHARY_TRANSPORT,
|
|
56
|
+
"questions_routed": QUESTION_TOOLSET in members(section(c, "agent"), "disabled_toolsets"),
|
|
57
|
+
}))
|
|
58
|
+
sys.exit(0)
|
|
59
|
+
|
|
60
|
+
def recorded(snapshot, value):
|
|
61
|
+
return bool(snapshot.get("present")) and snapshot.get("value") == value
|
|
62
|
+
|
|
63
|
+
def pop_if(obj, key, value):
|
|
64
|
+
if obj.get(key) == value:
|
|
65
|
+
obj.pop(key, None)
|
|
66
|
+
|
|
67
|
+
def snap(obj, key):
|
|
68
|
+
return {"present": key in obj, "value": obj.get(key)}
|
|
69
|
+
|
|
70
|
+
restore_conflicts = []
|
|
71
|
+
|
|
72
|
+
def restore(obj, key, before, ours):
|
|
73
|
+
if before.get("present") and before.get("value") == ours:
|
|
74
|
+
return
|
|
75
|
+
if before.get("present"):
|
|
76
|
+
if key in obj and obj.get(key) == before.get("value"):
|
|
77
|
+
return
|
|
78
|
+
elif key not in obj:
|
|
79
|
+
return
|
|
80
|
+
if obj.get(key) != ours:
|
|
81
|
+
restore_conflicts.append(key)
|
|
82
|
+
return
|
|
83
|
+
if before.get("present"):
|
|
84
|
+
obj[key] = before.get("value")
|
|
85
|
+
else:
|
|
86
|
+
obj.pop(key, None)
|
|
87
|
+
|
|
88
|
+
def restore_container(obj, key, before, current, changed):
|
|
89
|
+
raw = obj.get(key)
|
|
90
|
+
if isinstance(before.get("value"), dict):
|
|
91
|
+
if not isinstance(raw, dict):
|
|
92
|
+
if changed:
|
|
93
|
+
restore_conflicts.append(key)
|
|
94
|
+
return
|
|
95
|
+
if current or before.get("present"):
|
|
96
|
+
obj[key] = current
|
|
97
|
+
else:
|
|
98
|
+
obj.pop(key, None)
|
|
99
|
+
elif isinstance(raw, dict) and current:
|
|
100
|
+
restore_conflicts.append(key)
|
|
101
|
+
else:
|
|
102
|
+
restore(obj, key, before, {})
|
|
103
|
+
|
|
104
|
+
def restore_list_member(obj, key, before, ours, ours_before):
|
|
105
|
+
raw = obj.get(key)
|
|
106
|
+
if isinstance(before.get("value"), list):
|
|
107
|
+
if ours_before:
|
|
108
|
+
return
|
|
109
|
+
if not isinstance(raw, list):
|
|
110
|
+
restore_conflicts.append(key)
|
|
111
|
+
return
|
|
112
|
+
remaining = [item for item in raw if item != ours]
|
|
113
|
+
if remaining or before.get("present"):
|
|
114
|
+
obj[key] = remaining
|
|
115
|
+
else:
|
|
116
|
+
obj.pop(key, None)
|
|
117
|
+
return
|
|
118
|
+
if before.get("present") and raw == before.get("value"):
|
|
119
|
+
return
|
|
120
|
+
if not before.get("present") and key not in obj:
|
|
121
|
+
return
|
|
122
|
+
if not isinstance(raw, list):
|
|
123
|
+
restore_conflicts.append(key)
|
|
124
|
+
return
|
|
125
|
+
remaining = [item for item in raw if item != ours]
|
|
126
|
+
if remaining:
|
|
127
|
+
restore_conflicts.append(key)
|
|
128
|
+
else:
|
|
129
|
+
restore(obj, key, before, raw)
|
|
130
|
+
|
|
131
|
+
def valid_snapshot(value):
|
|
132
|
+
return isinstance(value, dict) and isinstance(value.get("present"), bool) and "value" in value
|
|
133
|
+
|
|
134
|
+
def valid_receipt(value):
|
|
135
|
+
snapshots = ["security", "approval", "transport", "fallback", "plugins", "enabled", "agent", "disabled"]
|
|
136
|
+
return (isinstance(value, dict)
|
|
137
|
+
and all(valid_snapshot(value.get(key)) for key in snapshots)
|
|
138
|
+
and isinstance(value.get("pushary_enabled"), bool)
|
|
139
|
+
and isinstance(value.get("clarify_disabled"), bool))
|
|
140
|
+
|
|
141
|
+
if mode == "setup":
|
|
142
|
+
security_before = snap(c, "security")
|
|
143
|
+
s = section(c, "security")
|
|
144
|
+
approval_before = snap(s, "approval")
|
|
145
|
+
ap = section(s, "approval")
|
|
146
|
+
plugins_before = snap(c, "plugins")
|
|
147
|
+
p = section(c, "plugins")
|
|
148
|
+
agent_before = snap(c, "agent")
|
|
149
|
+
a = section(c, "agent")
|
|
150
|
+
enabled = members(p, "enabled")
|
|
151
|
+
disabled = members(a, "disabled_toolsets")
|
|
152
|
+
receipt = {
|
|
153
|
+
"security": security_before,
|
|
154
|
+
"approval": approval_before,
|
|
155
|
+
"transport": snap(ap, "transport"),
|
|
156
|
+
"fallback": snap(ap, "transport_fallback"),
|
|
157
|
+
"plugins": plugins_before,
|
|
158
|
+
"enabled": snap(p, "enabled"),
|
|
159
|
+
"pushary_enabled": PUSHARY_PLUGIN in enabled,
|
|
160
|
+
"agent": agent_before,
|
|
161
|
+
"disabled": snap(a, "disabled_toolsets"),
|
|
162
|
+
"clarify_disabled": QUESTION_TOOLSET in disabled,
|
|
163
|
+
}
|
|
164
|
+
if os.path.exists(receipt_path):
|
|
165
|
+
with open(receipt_path, encoding="utf-8") as handle:
|
|
166
|
+
existing_receipt = json.load(handle)
|
|
167
|
+
if not valid_receipt(existing_receipt):
|
|
168
|
+
raise ValueError("invalid Pushary Hermes setup receipt")
|
|
169
|
+
else:
|
|
170
|
+
os.makedirs(os.path.dirname(receipt_path), exist_ok=True)
|
|
171
|
+
temp = receipt_path + ".tmp"
|
|
172
|
+
with open(temp, "x", encoding="utf-8") as handle:
|
|
173
|
+
json.dump(receipt, handle)
|
|
174
|
+
os.chmod(temp, 0o600)
|
|
175
|
+
os.replace(temp, receipt_path)
|
|
176
|
+
ap["transport"] = PUSHARY_TRANSPORT
|
|
177
|
+
ap["transport_fallback"] = PUSHARY_FALLBACK
|
|
178
|
+
s["approval"] = ap
|
|
179
|
+
c["security"] = s
|
|
180
|
+
p["enabled"] = enabled if PUSHARY_PLUGIN in enabled else enabled + [PUSHARY_PLUGIN]
|
|
181
|
+
c["plugins"] = p
|
|
182
|
+
a["disabled_toolsets"] = disabled if QUESTION_TOOLSET in disabled else disabled + [QUESTION_TOOLSET]
|
|
183
|
+
c["agent"] = a
|
|
184
|
+
else:
|
|
185
|
+
receipt_exists = os.path.exists(receipt_path)
|
|
186
|
+
try:
|
|
187
|
+
with open(receipt_path, encoding="utf-8") as handle:
|
|
188
|
+
receipt = json.load(handle)
|
|
189
|
+
except (OSError, ValueError):
|
|
190
|
+
if receipt_exists:
|
|
191
|
+
raise
|
|
192
|
+
receipt = None
|
|
193
|
+
s = section(c, "security")
|
|
194
|
+
ap = section(s, "approval")
|
|
195
|
+
if receipt:
|
|
196
|
+
if not valid_receipt(receipt):
|
|
197
|
+
raise ValueError("invalid Pushary Hermes setup receipt")
|
|
198
|
+
approval_changed = (not receipt["transport"].get("present")
|
|
199
|
+
or receipt["transport"].get("value") != PUSHARY_TRANSPORT
|
|
200
|
+
or not receipt["fallback"].get("present")
|
|
201
|
+
or receipt["fallback"].get("value") != PUSHARY_FALLBACK)
|
|
202
|
+
plugins_changed = not (isinstance(receipt["enabled"].get("value"), list)
|
|
203
|
+
and receipt["pushary_enabled"])
|
|
204
|
+
agent_changed = not (isinstance(receipt["disabled"].get("value"), list)
|
|
205
|
+
and receipt["clarify_disabled"])
|
|
206
|
+
transport_left_over = recorded(receipt["transport"], PUSHARY_TRANSPORT)
|
|
207
|
+
if transport_left_over:
|
|
208
|
+
pop_if(ap, "transport", PUSHARY_TRANSPORT)
|
|
209
|
+
else:
|
|
210
|
+
restore(ap, "transport", receipt["transport"], PUSHARY_TRANSPORT)
|
|
211
|
+
if transport_left_over and recorded(receipt["fallback"], PUSHARY_FALLBACK):
|
|
212
|
+
pop_if(ap, "transport_fallback", PUSHARY_FALLBACK)
|
|
213
|
+
else:
|
|
214
|
+
restore(ap, "transport_fallback", receipt["fallback"], PUSHARY_FALLBACK)
|
|
215
|
+
else:
|
|
216
|
+
pop_if(ap, "transport", PUSHARY_TRANSPORT)
|
|
217
|
+
pop_if(ap, "transport_fallback", PUSHARY_FALLBACK)
|
|
218
|
+
if receipt:
|
|
219
|
+
restore_container(s, "approval", receipt["approval"], ap, approval_changed)
|
|
220
|
+
restore_container(c, "security", receipt["security"], s, approval_changed)
|
|
221
|
+
else:
|
|
222
|
+
if ap: s["approval"] = ap
|
|
223
|
+
else: s.pop("approval", None)
|
|
224
|
+
if s: c["security"] = s
|
|
225
|
+
else: c.pop("security", None)
|
|
226
|
+
|
|
227
|
+
p = section(c, "plugins")
|
|
228
|
+
if receipt:
|
|
229
|
+
restore_list_member(p, "enabled", receipt["enabled"], PUSHARY_PLUGIN, False)
|
|
230
|
+
restore_container(c, "plugins", receipt["plugins"], p, plugins_changed)
|
|
231
|
+
else:
|
|
232
|
+
enabled = [item for item in members(p, "enabled") if item != PUSHARY_PLUGIN]
|
|
233
|
+
if enabled: p["enabled"] = enabled
|
|
234
|
+
else: p.pop("enabled", None)
|
|
235
|
+
if p: c["plugins"] = p
|
|
236
|
+
else: c.pop("plugins", None)
|
|
237
|
+
|
|
238
|
+
a = section(c, "agent")
|
|
239
|
+
if receipt:
|
|
240
|
+
clarify_was_users = receipt["clarify_disabled"] and not recorded(receipt["transport"], PUSHARY_TRANSPORT)
|
|
241
|
+
restore_list_member(a, "disabled_toolsets", receipt["disabled"], QUESTION_TOOLSET, clarify_was_users)
|
|
242
|
+
restore_container(c, "agent", receipt["agent"], a, agent_changed)
|
|
243
|
+
else:
|
|
244
|
+
disabled = [item for item in members(a, "disabled_toolsets") if item != QUESTION_TOOLSET]
|
|
245
|
+
if disabled: a["disabled_toolsets"] = disabled
|
|
246
|
+
else: a.pop("disabled_toolsets", None)
|
|
247
|
+
if a: c["agent"] = a
|
|
248
|
+
else: c.pop("agent", None)
|
|
249
|
+
|
|
250
|
+
if receipt and restore_conflicts:
|
|
251
|
+
raise ValueError("Hermes config changed after Pushary setup")
|
|
252
|
+
|
|
253
|
+
changed = c != original
|
|
254
|
+
if changed and mode != "clean-preview":
|
|
255
|
+
save_config(c)
|
|
256
|
+
print(json.dumps({"changed": changed}))
|
|
257
|
+
`;
|
|
258
|
+
var resolveHermesPython = () => {
|
|
259
|
+
const launcher = resolvePathBinary("hermes");
|
|
260
|
+
if (launcher && process.platform !== "win32") {
|
|
261
|
+
try {
|
|
262
|
+
const shebang = readFileSync(launcher, "utf-8").split("\n", 1)[0];
|
|
263
|
+
const interpreter = shebang?.startsWith("#!") ? shebang.slice(2).trim().split(/\s+/)[0] : void 0;
|
|
264
|
+
if (interpreter && /python/i.test(interpreter) && existsSync(interpreter)) return interpreter;
|
|
265
|
+
} catch {
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
const root = join(homedir(), ".hermes", "hermes-agent", "venv");
|
|
269
|
+
const candidates = process.platform === "win32" ? [join(root, "Scripts", "python.exe"), join(root, "Scripts", "python3.exe")] : [join(root, "bin", "python3"), join(root, "bin", "python")];
|
|
270
|
+
return candidates.find(existsSync) ?? null;
|
|
271
|
+
};
|
|
272
|
+
var HERMES_SCRIPT_TIMEOUT_MS = 15e3;
|
|
273
|
+
var runPythonScript = (python, script, args) => {
|
|
274
|
+
const path = join(tmpdir(), `pushary-hermes-${process.pid}-${Date.now()}.py`);
|
|
275
|
+
try {
|
|
276
|
+
writeFileSync(path, script, { encoding: "utf-8", mode: 384 });
|
|
277
|
+
return execFileSync(python, [path, ...args], {
|
|
278
|
+
env: { ...process.env },
|
|
279
|
+
encoding: "utf-8",
|
|
280
|
+
stdio: "pipe",
|
|
281
|
+
timeout: HERMES_SCRIPT_TIMEOUT_MS
|
|
282
|
+
});
|
|
283
|
+
} catch {
|
|
284
|
+
return null;
|
|
285
|
+
} finally {
|
|
286
|
+
try {
|
|
287
|
+
rmSync(path, { force: true });
|
|
288
|
+
} catch {
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
};
|
|
292
|
+
var lastReport = (stdout) => {
|
|
293
|
+
const line = stdout.trim().split("\n").at(-1) ?? "";
|
|
294
|
+
try {
|
|
295
|
+
return JSON.parse(line);
|
|
296
|
+
} catch {
|
|
297
|
+
return void 0;
|
|
298
|
+
}
|
|
299
|
+
};
|
|
300
|
+
var configRunFrom = (stdout) => {
|
|
301
|
+
const report = stdout === null ? void 0 : lastReport(stdout);
|
|
302
|
+
if (!isRecord(report) || typeof report.changed !== "boolean") return "failed";
|
|
303
|
+
return report.changed ? "changed" : "unchanged";
|
|
304
|
+
};
|
|
305
|
+
var runHermesConfigScript = (python, mode, receiptPath) => configRunFrom(runPythonScript(python, HERMES_CONFIG_SCRIPT, [mode, receiptPath]));
|
|
306
|
+
var HERMES_STATUS_FIELDS = ["plugin_installed", "plugin_enabled", "approvals_routed", "questions_routed"];
|
|
307
|
+
var isHermesStatusReport = (value) => isRecord(value) && HERMES_STATUS_FIELDS.every((field) => typeof value[field] === "boolean");
|
|
308
|
+
var hermesWiredByPushary = (status, setupRecorded) => setupRecorded || status.pluginEnabled || status.approvalsRouted;
|
|
309
|
+
var readHermesStatus = (python) => {
|
|
310
|
+
const stdout = runPythonScript(python, HERMES_CONFIG_SCRIPT, ["status"]);
|
|
311
|
+
const report = stdout === null ? void 0 : lastReport(stdout);
|
|
312
|
+
if (!isHermesStatusReport(report)) return null;
|
|
313
|
+
return {
|
|
314
|
+
pluginInstalled: report.plugin_installed,
|
|
315
|
+
pluginEnabled: report.plugin_enabled,
|
|
316
|
+
approvalsRouted: report.approvals_routed,
|
|
317
|
+
questionsRouted: report.questions_routed
|
|
318
|
+
};
|
|
319
|
+
};
|
|
13
320
|
|
|
14
321
|
// src/fx-config.ts
|
|
15
322
|
var FX_SERVER_NAME = "pushary";
|
|
@@ -17,7 +324,7 @@ var FX_OPERATION_TIMEOUT_MS = 75e3;
|
|
|
17
324
|
var FX_PROFILE_MAX_BYTES = 1024 * 1024 - 1;
|
|
18
325
|
var FX_SETTINGS_MAX_BYTES = 64 * 1024;
|
|
19
326
|
var FX_PUSHARY_TOOLS_RULE = "mcp_pushary_*";
|
|
20
|
-
var FX_TOOL_DISCOVERY_NOTE = 'In fx, these tools are named mcp_pushary_ask_user, mcp_pushary_send_notification, mcp_pushary_wait_for_answer and
|
|
327
|
+
var FX_TOOL_DISCOVERY_NOTE = 'In fx, these tools are named mcp_pushary_ask_user, mcp_pushary_send_notification, mcp_pushary_wait_for_answer, mcp_pushary_cancel_question and mcp_pushary_propose_scope. fx does not load them at the start of a turn: call mcp_select_tool with the exact name, for example {"name": "mcp_pushary_ask_user"}, then call the tool. For fx this replaces the guidance above to ask the whole round in the terminal while the user is there: ask every question through mcp_pushary_ask_user, even while the user is at this terminal, rather than through ask_user_question. fx cannot show its own question prompt in the Mac notch or on the phone, so a question asked there waits unseen once the user steps away. Ask in your reply instead only when a Pushary handoffAction tells you to ask here, or when mcp_pushary_ask_user is unavailable or fails without one.';
|
|
21
328
|
var ALLOW_ACTION = "allow";
|
|
22
329
|
var PERMISSION_KEY = "permission";
|
|
23
330
|
var WORKSPACES_KEY = "workspaces";
|
|
@@ -50,7 +357,9 @@ var FX_PROFILE_REFUSALS = {
|
|
|
50
357
|
};
|
|
51
358
|
var FX_SETTINGS_REFUSALS = {
|
|
52
359
|
...FX_JSON_REFUSALS,
|
|
53
|
-
"permission-not-object": 'has a "permission" value that is not an object'
|
|
360
|
+
"permission-not-object": 'has a "permission" value that is not an object',
|
|
361
|
+
"invalid-permission-rule": "has a permission rule fx rejects, so fx ignores the whole file",
|
|
362
|
+
"invalid-permission-mode": "has a permission_mode fx does not recognise, so fx ignores the whole file"
|
|
54
363
|
};
|
|
55
364
|
var asRecord = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : void 0;
|
|
56
365
|
var stringTokenEnd = (text, start) => {
|
|
@@ -182,6 +491,8 @@ var fxSettingsText = (settings) => fxFileText(settings, FX_SETTINGS_MAX_BYTES);
|
|
|
182
491
|
var readFxSettings = (text) => {
|
|
183
492
|
const read = readFxJsonObject(text, FX_SETTINGS_MAX_BYTES);
|
|
184
493
|
if (read.kind === "refused") return read;
|
|
494
|
+
const invalid = fxSettingsRejection(read.value);
|
|
495
|
+
if (invalid) return { kind: "refused", reason: invalid };
|
|
185
496
|
const permission = read.value[PERMISSION_KEY];
|
|
186
497
|
if (permission !== void 0 && !asRecord(permission)) return { kind: "refused", reason: "permission-not-object" };
|
|
187
498
|
return { kind: "settings", settings: read.value };
|
|
@@ -196,15 +507,15 @@ var withPusharyToolsAllowed = (settings) => {
|
|
|
196
507
|
change: "added"
|
|
197
508
|
};
|
|
198
509
|
};
|
|
510
|
+
var withoutPermissionRule = (settings, permission, rule) => {
|
|
511
|
+
const remaining = Object.fromEntries(Object.entries(permission).filter(([name]) => name !== rule));
|
|
512
|
+
const withoutPermission = Object.fromEntries(Object.entries(settings).filter(([key]) => key !== PERMISSION_KEY));
|
|
513
|
+
return Object.keys(remaining).length === 0 ? withoutPermission : { ...settings, [PERMISSION_KEY]: remaining };
|
|
514
|
+
};
|
|
199
515
|
var withoutPusharyToolsAllowed = (settings) => {
|
|
200
516
|
const permission = asRecord(settings[PERMISSION_KEY]);
|
|
201
|
-
if (permission
|
|
202
|
-
|
|
203
|
-
const withoutPermission = Object.fromEntries(Object.entries(settings).filter(([key]) => key !== PERMISSION_KEY));
|
|
204
|
-
return {
|
|
205
|
-
settings: Object.keys(remaining).length === 0 ? withoutPermission : { ...settings, [PERMISSION_KEY]: remaining },
|
|
206
|
-
removed: true
|
|
207
|
-
};
|
|
517
|
+
if (!permission || permission[FX_PUSHARY_TOOLS_RULE] !== ALLOW_ACTION) return { settings, removed: false };
|
|
518
|
+
return { settings: withoutPermissionRule(settings, permission, FX_PUSHARY_TOOLS_RULE), removed: true };
|
|
208
519
|
};
|
|
209
520
|
var pusharyToolsAllowed = (settings) => asRecord(settings[PERMISSION_KEY])?.[FX_PUSHARY_TOOLS_RULE] === ALLOW_ACTION;
|
|
210
521
|
var overridesPusharyRule = (workspace) => {
|
|
@@ -212,15 +523,144 @@ var overridesPusharyRule = (workspace) => {
|
|
|
212
523
|
return permission !== void 0 && !(FX_PUSHARY_TOOLS_RULE in permission);
|
|
213
524
|
};
|
|
214
525
|
var workspacesOverridingPusharyRule = (settings) => Object.entries(asRecord(settings[WORKSPACES_KEY]) ?? {}).filter(([, workspace]) => overridesPusharyRule(workspace)).map(([path]) => path);
|
|
526
|
+
var FX_QUESTION_TOOL = "ask_user_question";
|
|
527
|
+
var DENY_ACTION = "deny";
|
|
528
|
+
var GLOBAL_TARGET_PATTERN = "*";
|
|
529
|
+
var PERMISSION_MODE_KEY = "permission_mode";
|
|
530
|
+
var FX_PERMISSION_MODE_ENV = "FX_PERMISSION_MODE";
|
|
531
|
+
var FX_DEFAULT_PERMISSION_MODE = "auto";
|
|
532
|
+
var FX_PERMISSION_MODE_SPELLINGS = {
|
|
533
|
+
ask: "ask",
|
|
534
|
+
auto: "auto",
|
|
535
|
+
"full-access": "full-access",
|
|
536
|
+
"full access": "full-access",
|
|
537
|
+
yolo: "full-access"
|
|
538
|
+
};
|
|
539
|
+
var FX_RULE_WHITESPACE = /^[ \t\r\n]+|[ \t\r\n]+$/g;
|
|
540
|
+
var FX_QUESTION_RULE_RECEIPT = { rule: FX_QUESTION_TOOL, action: DENY_ACTION };
|
|
541
|
+
var isFxQuestionRuleReceipt = (value) => {
|
|
542
|
+
const receipt = asRecord(value);
|
|
543
|
+
return receipt?.rule === FX_QUESTION_TOOL && receipt.action === DENY_ACTION;
|
|
544
|
+
};
|
|
545
|
+
var isFxRuleAction = (value) => value === "allow" || value === "ask" || value === DENY_ACTION;
|
|
546
|
+
var fxRuleAction = (value) => {
|
|
547
|
+
if (typeof value !== "string") return null;
|
|
548
|
+
const action = value.toLowerCase();
|
|
549
|
+
return isFxRuleAction(action) ? action : null;
|
|
550
|
+
};
|
|
551
|
+
var parseFxPermissionMode = (value) => typeof value === "string" ? FX_PERMISSION_MODE_SPELLINGS[value.toLowerCase()] ?? null : null;
|
|
552
|
+
var trimFxRuleText = (text) => text.replace(FX_RULE_WHITESPACE, "");
|
|
553
|
+
var isValidPatternMap = (value) => {
|
|
554
|
+
const patterns = asRecord(value);
|
|
555
|
+
return patterns !== void 0 && Object.values(patterns).every((action) => fxRuleAction(action) !== null);
|
|
556
|
+
};
|
|
557
|
+
var isValidPermissionValue = (value) => {
|
|
558
|
+
if (fxRuleAction(value) !== null) return true;
|
|
559
|
+
const permission = asRecord(value);
|
|
560
|
+
return permission !== void 0 && Object.entries(permission).every(([name, rule]) => trimFxRuleText(name).length > 0 && (fxRuleAction(rule) !== null || isValidPatternMap(rule)));
|
|
561
|
+
};
|
|
562
|
+
var permissionScopeRejection = (scope) => {
|
|
563
|
+
if (scope[PERMISSION_KEY] !== void 0 && !isValidPermissionValue(scope[PERMISSION_KEY])) return "invalid-permission-rule";
|
|
564
|
+
if (scope[PERMISSION_MODE_KEY] !== void 0 && parseFxPermissionMode(scope[PERMISSION_MODE_KEY]) === null) return "invalid-permission-mode";
|
|
565
|
+
return null;
|
|
566
|
+
};
|
|
567
|
+
var workspaceScopes = (settings) => Object.values(asRecord(settings[WORKSPACES_KEY]) ?? {}).flatMap((workspace) => {
|
|
568
|
+
const scope = asRecord(workspace);
|
|
569
|
+
return scope ? [scope] : [];
|
|
570
|
+
});
|
|
571
|
+
var fxSettingsRejection = (settings) => [settings, ...workspaceScopes(settings)].map(permissionScopeRejection).find((rejection) => rejection !== null) ?? null;
|
|
572
|
+
var rulesOfPermissionEntry = ([name, value]) => {
|
|
573
|
+
const permission = trimFxRuleText(name);
|
|
574
|
+
const action = fxRuleAction(value);
|
|
575
|
+
if (action) return [{ permission, pattern: GLOBAL_TARGET_PATTERN, action }];
|
|
576
|
+
return Object.entries(asRecord(value) ?? {}).flatMap(([pattern, target]) => {
|
|
577
|
+
const targetAction = fxRuleAction(target);
|
|
578
|
+
return targetAction ? [{ permission, pattern: trimFxRuleText(pattern), action: targetAction }] : [];
|
|
579
|
+
});
|
|
580
|
+
};
|
|
581
|
+
var fxPermissionRules = (permission) => {
|
|
582
|
+
const everything = fxRuleAction(permission);
|
|
583
|
+
if (everything) return [{ permission: GLOBAL_TARGET_PATTERN, pattern: GLOBAL_TARGET_PATTERN, action: everything }];
|
|
584
|
+
return Object.entries(asRecord(permission) ?? {}).flatMap(rulesOfPermissionEntry);
|
|
585
|
+
};
|
|
586
|
+
var fxWildcardMatch = (pattern, candidate) => {
|
|
587
|
+
let patternIndex = 0;
|
|
588
|
+
let candidateIndex = 0;
|
|
589
|
+
let starIndex = null;
|
|
590
|
+
let starCandidateIndex = 0;
|
|
591
|
+
while (candidateIndex < candidate.length) {
|
|
592
|
+
if (patternIndex < pattern.length && (pattern[patternIndex] === "?" || pattern[patternIndex] === candidate[candidateIndex])) {
|
|
593
|
+
patternIndex += 1;
|
|
594
|
+
candidateIndex += 1;
|
|
595
|
+
} else if (patternIndex < pattern.length && pattern[patternIndex] === "*") {
|
|
596
|
+
starIndex = patternIndex;
|
|
597
|
+
patternIndex += 1;
|
|
598
|
+
starCandidateIndex = candidateIndex;
|
|
599
|
+
} else if (starIndex === null) {
|
|
600
|
+
return false;
|
|
601
|
+
} else {
|
|
602
|
+
starCandidateIndex += 1;
|
|
603
|
+
candidateIndex = starCandidateIndex;
|
|
604
|
+
patternIndex = starIndex + 1;
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
while (pattern[patternIndex] === "*") patternIndex += 1;
|
|
608
|
+
return patternIndex === pattern.length;
|
|
609
|
+
};
|
|
610
|
+
var DIRECTORY_TREE_SUFFIX = "/**";
|
|
611
|
+
var directoryTreeMatches = (pattern, candidate) => {
|
|
612
|
+
if (!pattern.endsWith(DIRECTORY_TREE_SUFFIX)) return false;
|
|
613
|
+
const directory = pattern.slice(0, -DIRECTORY_TREE_SUFFIX.length);
|
|
614
|
+
return candidate === directory || candidate.startsWith(directory) && candidate[directory.length] === "/";
|
|
615
|
+
};
|
|
616
|
+
var targetMatches = (pattern, candidate) => directoryTreeMatches(pattern, candidate) || fxWildcardMatch(pattern, candidate);
|
|
617
|
+
var isGlobalTargetPattern = (pattern) => /^\*+$/.test(pattern);
|
|
618
|
+
var decisionForTarget = (rules, target) => rules.filter((rule) => targetMatches(rule.pattern, target)).at(-1)?.action ?? null;
|
|
619
|
+
var reopensTool = (rules, rule) => rule.action !== DENY_ACTION && decisionForTarget(rules, rule.pattern) !== DENY_ACTION && decisionForTarget(rules, rule.pattern) !== null;
|
|
620
|
+
var fxRulesHideTool = (rules, toolName) => {
|
|
621
|
+
const matching = rules.filter((rule) => fxWildcardMatch(rule.permission, toolName));
|
|
622
|
+
const lastGlobalRule = matching.filter((rule) => isGlobalTargetPattern(rule.pattern)).at(-1);
|
|
623
|
+
if (lastGlobalRule?.action !== DENY_ACTION) return false;
|
|
624
|
+
return !matching.slice(matching.lastIndexOf(lastGlobalRule) + 1).some((rule) => reopensTool(matching, rule));
|
|
625
|
+
};
|
|
626
|
+
var effectivePermissionMode = (scope, environment) => parseFxPermissionMode(environment[FX_PERMISSION_MODE_ENV]) ?? parseFxPermissionMode(scope[PERMISSION_MODE_KEY]) ?? FX_DEFAULT_PERMISSION_MODE;
|
|
627
|
+
var fxRunsWithoutRules = (settings, environment) => effectivePermissionMode(settings, environment) === "full-access";
|
|
628
|
+
var fxQuestionToolHidden = (permission) => fxRulesHideTool(fxPermissionRules(permission), FX_QUESTION_TOOL);
|
|
629
|
+
var fxQuestionRouting = (settings, environment) => {
|
|
630
|
+
if (fxRunsWithoutRules(settings, environment)) return "full-access";
|
|
631
|
+
return fxQuestionToolHidden(settings[PERMISSION_KEY]) ? "routed" : "overridden";
|
|
632
|
+
};
|
|
633
|
+
var withQuestionsRoutedToPushary = (settings) => {
|
|
634
|
+
const permission = asRecord(settings[PERMISSION_KEY]) ?? {};
|
|
635
|
+
const existing = permission[FX_QUESTION_TOOL];
|
|
636
|
+
if (existing === void 0) {
|
|
637
|
+
return { settings: { ...settings, [PERMISSION_KEY]: { ...permission, [FX_QUESTION_TOOL]: DENY_ACTION } }, change: "added" };
|
|
638
|
+
}
|
|
639
|
+
return { settings, change: fxRuleAction(existing) === DENY_ACTION ? "present" : "user-rule-kept" };
|
|
640
|
+
};
|
|
641
|
+
var withoutQuestionsRoutedToPushary = (settings, receipt) => {
|
|
642
|
+
const permission = asRecord(settings[PERMISSION_KEY]);
|
|
643
|
+
if (!permission || permission[FX_QUESTION_TOOL] === void 0) return { settings, outcome: "absent" };
|
|
644
|
+
if (!receipt) return { settings, outcome: "not-ours" };
|
|
645
|
+
if (fxRuleAction(permission[FX_QUESTION_TOOL]) !== DENY_ACTION) return { settings, outcome: "changed-after-setup" };
|
|
646
|
+
return { settings: withoutPermissionRule(settings, permission, FX_QUESTION_TOOL), outcome: "removed" };
|
|
647
|
+
};
|
|
648
|
+
var workspaceShowsQuestionTool = (workspace) => {
|
|
649
|
+
const scope = asRecord(workspace);
|
|
650
|
+
if (!scope) return false;
|
|
651
|
+
if (parseFxPermissionMode(scope[PERMISSION_MODE_KEY]) === "full-access") return true;
|
|
652
|
+
return scope[PERMISSION_KEY] !== void 0 && !fxQuestionToolHidden(scope[PERMISSION_KEY]);
|
|
653
|
+
};
|
|
654
|
+
var workspacesShowingQuestionTool = (settings) => Object.entries(asRecord(settings[WORKSPACES_KEY]) ?? {}).filter(([, workspace]) => workspaceShowsQuestionTool(workspace)).map(([path]) => path);
|
|
215
655
|
|
|
216
656
|
// src/fx-files.ts
|
|
217
|
-
import { existsSync, lstatSync, readFileSync, realpathSync } from "fs";
|
|
657
|
+
import { existsSync as existsSync2, lstatSync, readFileSync as readFileSync2, realpathSync } from "fs";
|
|
218
658
|
import { relative, isAbsolute } from "path";
|
|
219
659
|
var readFxFile = (path, read) => {
|
|
220
|
-
if (!
|
|
660
|
+
if (!existsSync2(path)) return { kind: "missing" };
|
|
221
661
|
let text;
|
|
222
662
|
try {
|
|
223
|
-
text =
|
|
663
|
+
text = readFileSync2(path, "utf-8");
|
|
224
664
|
} catch {
|
|
225
665
|
return { kind: "unreadable" };
|
|
226
666
|
}
|
|
@@ -241,8 +681,8 @@ var fxIgnoresInstructionsFile = (path, fxDir) => {
|
|
|
241
681
|
};
|
|
242
682
|
|
|
243
683
|
// src/vscode-config.ts
|
|
244
|
-
import { homedir } from "os";
|
|
245
|
-
import { join } from "path";
|
|
684
|
+
import { homedir as homedir2 } from "os";
|
|
685
|
+
import { join as join2 } from "path";
|
|
246
686
|
var PLUGIN_LOCATIONS_KEY = "chat.pluginLocations";
|
|
247
687
|
var blankJsonComments = (text) => {
|
|
248
688
|
let out = "";
|
|
@@ -372,13 +812,13 @@ var pluginLocationSnippet = (pluginDir) => `"${PLUGIN_LOCATIONS_KEY}": {
|
|
|
372
812
|
}`;
|
|
373
813
|
var VSCODE_PRODUCTS = ["Code", "Code - Insiders"];
|
|
374
814
|
var settingsRoot = (home, platform) => {
|
|
375
|
-
if (platform === "darwin") return
|
|
376
|
-
if (platform === "win32") return process.env.APPDATA?.trim() ||
|
|
377
|
-
return process.env.XDG_CONFIG_HOME?.trim() ||
|
|
815
|
+
if (platform === "darwin") return join2(home, "Library", "Application Support");
|
|
816
|
+
if (platform === "win32") return process.env.APPDATA?.trim() || join2(home, "AppData", "Roaming");
|
|
817
|
+
return process.env.XDG_CONFIG_HOME?.trim() || join2(home, ".config");
|
|
378
818
|
};
|
|
379
|
-
var vscodeSettingsCandidates = (home =
|
|
819
|
+
var vscodeSettingsCandidates = (home = homedir2(), platform = process.platform) => {
|
|
380
820
|
const root = settingsRoot(home, platform);
|
|
381
|
-
return VSCODE_PRODUCTS.map((product) =>
|
|
821
|
+
return VSCODE_PRODUCTS.map((product) => join2(root, product, "User", "settings.json"));
|
|
382
822
|
};
|
|
383
823
|
var vscodeSettingsTargets = (exists, home, platform) => {
|
|
384
824
|
const candidates = vscodeSettingsCandidates(home, platform);
|
|
@@ -388,9 +828,9 @@ var vscodeSettingsTargets = (exists, home, platform) => {
|
|
|
388
828
|
|
|
389
829
|
// src/setup/detect.ts
|
|
390
830
|
import { execSync } from "child_process";
|
|
391
|
-
import { existsSync as
|
|
392
|
-
import { homedir as
|
|
393
|
-
import { dirname, join as
|
|
831
|
+
import { existsSync as existsSync3 } from "fs";
|
|
832
|
+
import { homedir as homedir3 } from "os";
|
|
833
|
+
import { dirname, join as join3 } from "path";
|
|
394
834
|
var whichCommand = () => process.platform === "win32" ? "where" : "which";
|
|
395
835
|
var binaryOnPath = (binary) => {
|
|
396
836
|
try {
|
|
@@ -400,7 +840,7 @@ var binaryOnPath = (binary) => {
|
|
|
400
840
|
return false;
|
|
401
841
|
}
|
|
402
842
|
};
|
|
403
|
-
var defaultDeps = { onPath: binaryOnPath, exists:
|
|
843
|
+
var defaultDeps = { onPath: binaryOnPath, exists: existsSync3 };
|
|
404
844
|
var detectAgent = (probe, deps = defaultDeps) => {
|
|
405
845
|
const checked = [];
|
|
406
846
|
if (probe.binary) {
|
|
@@ -422,12 +862,12 @@ var detectAgent = (probe, deps = defaultDeps) => {
|
|
|
422
862
|
}
|
|
423
863
|
return { kind: "not-found", checked };
|
|
424
864
|
};
|
|
425
|
-
var agentProbes = (home =
|
|
865
|
+
var agentProbes = (home = homedir3()) => ({
|
|
426
866
|
claude_code: {
|
|
427
867
|
binary: "claude",
|
|
428
868
|
// The native installer puts it here, outside a default non-login PATH.
|
|
429
|
-
paths: [
|
|
430
|
-
configMarkers: [
|
|
869
|
+
paths: [join3(home, ".local", "bin", "claude")],
|
|
870
|
+
configMarkers: [join3(home, ".claude")]
|
|
431
871
|
},
|
|
432
872
|
codex: {
|
|
433
873
|
binary: "codex",
|
|
@@ -437,25 +877,25 @@ var agentProbes = (home = homedir2()) => ({
|
|
|
437
877
|
gemini_cli: {
|
|
438
878
|
binary: "gemini",
|
|
439
879
|
paths: [],
|
|
440
|
-
configMarkers: [
|
|
880
|
+
configMarkers: [join3(home, ".gemini")]
|
|
441
881
|
},
|
|
442
882
|
hermes: {
|
|
443
883
|
binary: "hermes",
|
|
444
884
|
paths: [],
|
|
445
|
-
configMarkers: [
|
|
885
|
+
configMarkers: [join3(home, ".hermes")]
|
|
446
886
|
},
|
|
447
887
|
cursor: {
|
|
448
888
|
// GUI editor. `cursor` on PATH only exists if the user ran "Install 'cursor'
|
|
449
889
|
// command in PATH" from the command palette, which most never do.
|
|
450
890
|
binary: "cursor",
|
|
451
|
-
paths: process.platform === "darwin" ? ["/Applications/Cursor.app"] : process.platform === "win32" ? [
|
|
452
|
-
configMarkers: [
|
|
891
|
+
paths: process.platform === "darwin" ? ["/Applications/Cursor.app"] : process.platform === "win32" ? [join3(process.env.LOCALAPPDATA ?? join3(home, "AppData", "Local"), "Programs", "cursor")] : ["/usr/share/cursor", join3(home, ".local", "share", "cursor")],
|
|
892
|
+
configMarkers: [join3(home, ".cursor")]
|
|
453
893
|
},
|
|
454
894
|
vscode: {
|
|
455
895
|
// Like Cursor, a GUI editor: `code` on PATH only exists if the user ran
|
|
456
896
|
// "Shell Command: Install 'code' command in PATH", which on macOS is opt-in.
|
|
457
897
|
binary: "code",
|
|
458
|
-
paths: process.platform === "darwin" ? ["/Applications/Visual Studio Code.app"] : process.platform === "win32" ? [
|
|
898
|
+
paths: process.platform === "darwin" ? ["/Applications/Visual Studio Code.app"] : process.platform === "win32" ? [join3(process.env.LOCALAPPDATA ?? join3(home, "AppData", "Local"), "Programs", "Microsoft VS Code")] : ["/usr/share/code", join3(home, ".local", "share", "code")],
|
|
459
899
|
// The settings directory rather than ~/.vscode, because the settings
|
|
460
900
|
// directory is what this installer actually writes to. Insiders counts:
|
|
461
901
|
// someone running only Insiders still has VS Code.
|
|
@@ -471,7 +911,7 @@ var agentProbes = (home = homedir2()) => ({
|
|
|
471
911
|
},
|
|
472
912
|
fx: {
|
|
473
913
|
binary: null,
|
|
474
|
-
paths: [
|
|
914
|
+
paths: [join3(home, ".fx", "settings.json"), join3(home, ".fx", "sessions"), join3(home, ".fx", "history.jsonl")],
|
|
475
915
|
configMarkers: []
|
|
476
916
|
},
|
|
477
917
|
...compatibleProbes(),
|
|
@@ -495,7 +935,7 @@ var detectAllAgents = (deps = defaultDeps, home) => {
|
|
|
495
935
|
}));
|
|
496
936
|
};
|
|
497
937
|
var isDetected = (result) => result.kind === "installed";
|
|
498
|
-
var shortenHome = (path, home =
|
|
938
|
+
var shortenHome = (path, home = homedir3()) => home && path.startsWith(home) ? `~${path.slice(home.length)}` : path;
|
|
499
939
|
var describeDetection = (result, home) => {
|
|
500
940
|
if (result.kind !== "installed") return null;
|
|
501
941
|
const evidence = shortenHome(result.evidence, home);
|
|
@@ -503,7 +943,7 @@ var describeDetection = (result, home) => {
|
|
|
503
943
|
};
|
|
504
944
|
|
|
505
945
|
// src/instruction-file.ts
|
|
506
|
-
import { existsSync as
|
|
946
|
+
import { existsSync as existsSync4, readFileSync as readFileSync3, writeFileSync as writeFileSync2, mkdirSync, rmSync as rmSync2 } from "fs";
|
|
507
947
|
import { dirname as dirname2 } from "path";
|
|
508
948
|
var PUSHARY_BEGIN = "<!-- BEGIN PUSHARY (managed by @pushary/agent-hooks; do not edit this block) -->";
|
|
509
949
|
var PUSHARY_END = "<!-- END PUSHARY -->";
|
|
@@ -533,10 +973,10 @@ ${body}
|
|
|
533
973
|
${PUSHARY_END}`;
|
|
534
974
|
var writeInstructionBlock = (filePath, body) => {
|
|
535
975
|
const dir = dirname2(filePath);
|
|
536
|
-
if (!
|
|
976
|
+
if (!existsSync4(dir)) mkdirSync(dir, { recursive: true });
|
|
537
977
|
let existing = "";
|
|
538
978
|
try {
|
|
539
|
-
existing =
|
|
979
|
+
existing = readFileSync3(filePath, "utf-8");
|
|
540
980
|
} catch {
|
|
541
981
|
}
|
|
542
982
|
const block = wrap(body);
|
|
@@ -549,12 +989,12 @@ var writeInstructionBlock = (filePath, body) => {
|
|
|
549
989
|
const prefix = existing.trim() ? existing.replace(/\s*$/, "") + "\n\n" : "";
|
|
550
990
|
next = prefix + block + "\n";
|
|
551
991
|
}
|
|
552
|
-
|
|
992
|
+
writeFileSync2(filePath, next, "utf-8");
|
|
553
993
|
};
|
|
554
994
|
var removeInstructionBlock = (filePath) => {
|
|
555
995
|
let existing = "";
|
|
556
996
|
try {
|
|
557
|
-
existing =
|
|
997
|
+
existing = readFileSync3(filePath, "utf-8");
|
|
558
998
|
} catch {
|
|
559
999
|
return false;
|
|
560
1000
|
}
|
|
@@ -563,21 +1003,28 @@ var removeInstructionBlock = (filePath) => {
|
|
|
563
1003
|
if (start === -1 || end === -1 || end < start) return false;
|
|
564
1004
|
const remaining = (existing.slice(0, start) + existing.slice(end + PUSHARY_END.length)).trim();
|
|
565
1005
|
if (remaining === "") {
|
|
566
|
-
|
|
1006
|
+
rmSync2(filePath, { force: true });
|
|
567
1007
|
} else {
|
|
568
|
-
|
|
1008
|
+
writeFileSync2(filePath, remaining + "\n", "utf-8");
|
|
569
1009
|
}
|
|
570
1010
|
return true;
|
|
571
1011
|
};
|
|
572
1012
|
var hasInstructionBlock = (filePath) => {
|
|
573
1013
|
try {
|
|
574
|
-
return
|
|
1014
|
+
return readFileSync3(filePath, "utf-8").includes(PUSHARY_BEGIN);
|
|
575
1015
|
} catch {
|
|
576
1016
|
return false;
|
|
577
1017
|
}
|
|
578
1018
|
};
|
|
579
1019
|
|
|
580
1020
|
export {
|
|
1021
|
+
isHermesConfigReceipt,
|
|
1022
|
+
HERMES_PIP_PACKAGE,
|
|
1023
|
+
HERMES_PLUGIN_NAME,
|
|
1024
|
+
resolveHermesPython,
|
|
1025
|
+
runHermesConfigScript,
|
|
1026
|
+
hermesWiredByPushary,
|
|
1027
|
+
readHermesStatus,
|
|
581
1028
|
FX_PUSHARY_TOOLS_RULE,
|
|
582
1029
|
FX_TOOL_DISCOVERY_NOTE,
|
|
583
1030
|
FX_PROFILE_REFUSALS,
|
|
@@ -596,6 +1043,13 @@ export {
|
|
|
596
1043
|
withoutPusharyToolsAllowed,
|
|
597
1044
|
pusharyToolsAllowed,
|
|
598
1045
|
workspacesOverridingPusharyRule,
|
|
1046
|
+
FX_QUESTION_TOOL,
|
|
1047
|
+
FX_QUESTION_RULE_RECEIPT,
|
|
1048
|
+
isFxQuestionRuleReceipt,
|
|
1049
|
+
fxQuestionRouting,
|
|
1050
|
+
withQuestionsRoutedToPushary,
|
|
1051
|
+
withoutQuestionsRoutedToPushary,
|
|
1052
|
+
workspacesShowingQuestionTool,
|
|
599
1053
|
readFxFile,
|
|
600
1054
|
fxIgnoresInstructionsFile,
|
|
601
1055
|
registerPluginLocation,
|