@pushary/agent-hooks 0.90.1 → 0.90.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 +13 -0
- package/dist/bin/pushary-bell.js +6 -61
- package/dist/bin/pushary-claude.js +5 -5
- package/dist/bin/pushary-clean.js +96 -13
- package/dist/bin/pushary-codex-bridge.js +3 -3
- package/dist/bin/pushary-codex-hook.js +3 -3
- package/dist/bin/pushary-codex.js +3 -3
- package/dist/bin/pushary-connect.js +2 -2
- package/dist/bin/pushary-daemon.js +9 -9
- package/dist/bin/pushary-disconnect.js +2 -2
- package/dist/bin/pushary-doctor.js +13 -13
- package/dist/bin/pushary-elicitation-hook.js +2 -2
- package/dist/bin/pushary-gemini-bridge.js +3 -3
- package/dist/bin/pushary-gemini-hook.js +3 -3
- package/dist/bin/pushary-hook.js +4 -4
- package/dist/bin/pushary-login.js +4 -4
- package/dist/bin/pushary-logout.js +1 -1
- package/dist/bin/pushary-mode.js +2 -2
- package/dist/bin/pushary-notification-hook.js +3 -3
- package/dist/bin/pushary-opencode-hook.js +3 -3
- package/dist/bin/pushary-permission-denied-hook.js +4 -4
- package/dist/bin/pushary-permission-hook.js +4 -4
- package/dist/bin/pushary-post-hook.js +3 -3
- package/dist/bin/pushary-prompt-hook.js +3 -3
- package/dist/bin/pushary-session-end-hook.js +3 -3
- package/dist/bin/pushary-session-start-hook.js +3 -3
- package/dist/bin/pushary-setup.js +104 -179
- package/dist/bin/pushary-status.js +4 -4
- package/dist/bin/pushary-stop-hook.js +3 -3
- package/dist/bin/pushary-stopfailure-hook.js +3 -3
- package/dist/bin/pushary-upgrade.js +8 -8
- package/dist/bin/pushary-wait.js +2 -2
- package/dist/{chunk-B6H2KAQC.js → chunk-4IEW4ZRF.js} +2 -2
- package/dist/chunk-ATUUQFDH.js +536 -0
- package/dist/{chunk-54ELC7D7.js → chunk-AWHCZOZO.js} +1 -1
- package/dist/{chunk-KUZWXPUS.js → chunk-BVNYTQ5I.js} +23 -0
- package/dist/{chunk-AJYV3VGU.js → chunk-BXTJVM7O.js} +2 -0
- package/dist/{chunk-FQL5E6NG.js → chunk-CRVJCKIN.js} +85 -2
- package/dist/chunk-DSTLA2TC.js +68 -0
- package/dist/{chunk-GL3FFM7A.js → chunk-ELHCIEMM.js} +1 -1
- package/dist/{chunk-Y6BWPERV.js → chunk-GQ5ZC2QY.js} +1 -1
- package/dist/chunk-GU3EJCVV.js +43 -0
- package/dist/{chunk-4LOPSGLP.js → chunk-TVZQMPB7.js} +1 -1
- package/dist/{chunk-BQBEH6TV.js → chunk-VAMBUKXH.js} +4 -4
- package/dist/{chunk-DZDI62LY.js → chunk-WGKC6PU2.js} +2 -2
- package/dist/{chunk-7TPQEWUZ.js → chunk-YTD6PJM7.js} +1 -1
- package/dist/{chunk-H2QZRQR2.js → chunk-YTDH3XCE.js} +1 -1
- package/dist/{chunk-F2ICEWZL.js → chunk-Z4XCN2HH.js} +2 -2
- package/dist/{chunk-DYP6SMOR.js → chunk-Z6RFZKY4.js} +1 -1
- package/dist/src/index.js +4 -4
- package/package.json +1 -1
- package/dist/chunk-AZPIQG4G.js +0 -274
- package/dist/chunk-S6LYP7QA.js +0 -127
|
@@ -15,6 +15,7 @@ import {
|
|
|
15
15
|
} from "./chunk-VNMKIJZB.js";
|
|
16
16
|
|
|
17
17
|
// src/session.ts
|
|
18
|
+
var samePusharyAccount = (left, right) => left.kind === "ok" && right.kind === "ok" && left.identity.workspaceId !== "" && left.identity.workspaceId === right.identity.workspaceId && left.identity.siteId === right.identity.siteId;
|
|
18
19
|
var IDENTITY_PATH = "/api/v1/server/identity";
|
|
19
20
|
var DEFAULT_TIMEOUT_MS = 1e4;
|
|
20
21
|
var SUBSCRIPTION_REQUIRED_CODE = "subscription_required";
|
|
@@ -165,6 +166,7 @@ var describeKeyCheck = (check, baseUrl = getBaseUrl()) => {
|
|
|
165
166
|
};
|
|
166
167
|
|
|
167
168
|
export {
|
|
169
|
+
samePusharyAccount,
|
|
168
170
|
checkApiKey,
|
|
169
171
|
keyCheckFromResponse,
|
|
170
172
|
describeKeyCheck
|
|
@@ -3,7 +3,10 @@ import {
|
|
|
3
3
|
} from "./chunk-CELX75MW.js";
|
|
4
4
|
import {
|
|
5
5
|
codexHomeFrom
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-BVNYTQ5I.js";
|
|
7
|
+
import {
|
|
8
|
+
HOOK_LOCATOR_BINARY
|
|
9
|
+
} from "./chunk-VNMKIJZB.js";
|
|
7
10
|
|
|
8
11
|
// src/vscode-config.ts
|
|
9
12
|
import { homedir } from "os";
|
|
@@ -329,6 +332,82 @@ var hasInstructionBlock = (filePath) => {
|
|
|
329
332
|
// src/hooks-spec.ts
|
|
330
333
|
var CURSOR_GATE_SCRIPT = "pushary-gate.mjs";
|
|
331
334
|
|
|
335
|
+
// src/diagnostics/bridge.ts
|
|
336
|
+
import { execFileSync } from "child_process";
|
|
337
|
+
import { existsSync as existsSync3, readFileSync as readFileSync2 } from "fs";
|
|
338
|
+
import { homedir as homedir3 } from "os";
|
|
339
|
+
import { dirname as dirname3, join as join3 } from "path";
|
|
340
|
+
var BRIDGE_BUNDLE_IDENTIFIER = "com.pushary.app";
|
|
341
|
+
var BRIDGE_HELPER_SUFFIX = "/Contents/Helpers/pushary-bridge";
|
|
342
|
+
var BRIDGE_BUNDLE_ROOTS = ["/Applications/Pushary.app", "$HOME/Applications/Pushary.app"];
|
|
343
|
+
var expandHome = (path, home) => path.replaceAll("$HOME", home);
|
|
344
|
+
var parseBridgeStub = (contents, stubPath) => {
|
|
345
|
+
const helperSuffix = contents.match(/^H=(\S+)$/m)?.[1] ?? BRIDGE_HELPER_SUFFIX;
|
|
346
|
+
const cachePath = contents.match(/^C="([^"\n]+)"$/m)?.[1] ?? join3(dirname3(stubPath), ".bridge-cache");
|
|
347
|
+
const rootsLine = contents.match(/^for P in ((?:"[^"\n]*"\s*)+); do$/m)?.[1];
|
|
348
|
+
const bundleRoots = rootsLine ? [...rootsLine.matchAll(/"([^"]*)"/g)].map((match) => match[1]) : BRIDGE_BUNDLE_ROOTS;
|
|
349
|
+
const bundleIdentifier = contents.match(/kMDItemCFBundleIdentifier == "([^"]+)"/)?.[1] ?? BRIDGE_BUNDLE_IDENTIFIER;
|
|
350
|
+
return { helperSuffix, cachePath, bundleRoots, bundleIdentifier };
|
|
351
|
+
};
|
|
352
|
+
var resolveBridgeStub = (stubPath, probe) => {
|
|
353
|
+
const contents = probe.readFile(stubPath);
|
|
354
|
+
if (contents === null) return { kind: "no-stub", stub: stubPath };
|
|
355
|
+
const shape = parseBridgeStub(contents, stubPath);
|
|
356
|
+
const helperIn = (bundle) => `${bundle}${shape.helperSuffix}`;
|
|
357
|
+
const looked = [];
|
|
358
|
+
for (const root of shape.bundleRoots.map((root2) => expandHome(root2, probe.home))) {
|
|
359
|
+
looked.push(root);
|
|
360
|
+
if (probe.exists(helperIn(root))) return { kind: "bundle", helper: helperIn(root), via: "root" };
|
|
361
|
+
}
|
|
362
|
+
const cached = shape.cachePath ? probe.readFile(shape.cachePath)?.split("\n")[0]?.trim() : void 0;
|
|
363
|
+
if (cached) {
|
|
364
|
+
looked.push(cached);
|
|
365
|
+
if (probe.exists(helperIn(cached))) return { kind: "bundle", helper: helperIn(cached), via: "cache" };
|
|
366
|
+
}
|
|
367
|
+
for (const found of probe.mdfind(shape.bundleIdentifier)) {
|
|
368
|
+
looked.push(found);
|
|
369
|
+
if (probe.exists(helperIn(found))) return { kind: "bundle", helper: helperIn(found), via: "spotlight" };
|
|
370
|
+
}
|
|
371
|
+
return { kind: "missing", stub: stubPath, looked };
|
|
372
|
+
};
|
|
373
|
+
var BRIDGE_GONE_MESSAGE = "the Mac app is gone but its hooks remain; run pushary setup or reinstall the app";
|
|
374
|
+
var describeBridgeResolution = (resolution) => {
|
|
375
|
+
switch (resolution.kind) {
|
|
376
|
+
case "bundle":
|
|
377
|
+
return `${resolution.helper} (${resolution.via === "root" ? "installed" : resolution.via === "cache" ? "cached path" : "found by Spotlight"})`;
|
|
378
|
+
case "missing":
|
|
379
|
+
return BRIDGE_GONE_MESSAGE;
|
|
380
|
+
case "no-stub":
|
|
381
|
+
return `${resolution.stub} is not there; ${BRIDGE_GONE_MESSAGE}`;
|
|
382
|
+
}
|
|
383
|
+
};
|
|
384
|
+
var isBridgeLocator = (executable) => executable !== null && executable.split(/[/\\]/).pop() === HOOK_LOCATOR_BINARY;
|
|
385
|
+
var spotlight = (bundleIdentifier) => {
|
|
386
|
+
if (process.platform !== "darwin") return [];
|
|
387
|
+
try {
|
|
388
|
+
return execFileSync("/usr/bin/mdfind", [`kMDItemCFBundleIdentifier == "${bundleIdentifier}"`], {
|
|
389
|
+
encoding: "utf-8",
|
|
390
|
+
timeout: 5e3,
|
|
391
|
+
stdio: ["ignore", "pipe", "ignore"]
|
|
392
|
+
}).split("\n").map((line) => line.trim()).filter((line) => line !== "");
|
|
393
|
+
} catch {
|
|
394
|
+
return [];
|
|
395
|
+
}
|
|
396
|
+
};
|
|
397
|
+
var readOrNull = (path) => {
|
|
398
|
+
try {
|
|
399
|
+
return readFileSync2(path, "utf-8");
|
|
400
|
+
} catch {
|
|
401
|
+
return null;
|
|
402
|
+
}
|
|
403
|
+
};
|
|
404
|
+
var liveBridgeProbe = () => ({
|
|
405
|
+
exists: existsSync3,
|
|
406
|
+
readFile: readOrNull,
|
|
407
|
+
mdfind: spotlight,
|
|
408
|
+
home: homedir3()
|
|
409
|
+
});
|
|
410
|
+
|
|
332
411
|
export {
|
|
333
412
|
registerPluginLocation,
|
|
334
413
|
unregisterPluginLocation,
|
|
@@ -346,5 +425,9 @@ export {
|
|
|
346
425
|
writeInstructionBlock,
|
|
347
426
|
removeInstructionBlock,
|
|
348
427
|
hasInstructionBlock,
|
|
349
|
-
CURSOR_GATE_SCRIPT
|
|
428
|
+
CURSOR_GATE_SCRIPT,
|
|
429
|
+
resolveBridgeStub,
|
|
430
|
+
describeBridgeResolution,
|
|
431
|
+
isBridgeLocator,
|
|
432
|
+
liveBridgeProbe
|
|
350
433
|
};
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import {
|
|
2
|
+
guardBinary
|
|
3
|
+
} from "./chunk-EZIEXHYM.js";
|
|
4
|
+
|
|
5
|
+
// src/bell/install.ts
|
|
6
|
+
import { join } from "path";
|
|
7
|
+
var asRecord = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
8
|
+
var ensureRecord = (parent, key) => {
|
|
9
|
+
const existing = asRecord(parent[key]);
|
|
10
|
+
if (existing) return existing;
|
|
11
|
+
const created = {};
|
|
12
|
+
parent[key] = created;
|
|
13
|
+
return created;
|
|
14
|
+
};
|
|
15
|
+
var BELL_HOOK_BINARY = "pushary-bell-hook";
|
|
16
|
+
var isBellHook = (entry) => {
|
|
17
|
+
const hooks = asRecord(entry)?.hooks;
|
|
18
|
+
if (!Array.isArray(hooks)) return false;
|
|
19
|
+
return hooks.some((hook) => String(asRecord(hook)?.command ?? "").includes(BELL_HOOK_BINARY));
|
|
20
|
+
};
|
|
21
|
+
var BELL_TIMEOUT_SECONDS = 5;
|
|
22
|
+
var BELL_NOTIFICATION_MATCHER = "idle_prompt|agent_needs_input|agent_completed";
|
|
23
|
+
var addBellHooks = (settings, binDir) => {
|
|
24
|
+
const resolve = (name) => guardBinary(binDir ? join(binDir, name) : name);
|
|
25
|
+
const hooks = ensureRecord(settings, "hooks");
|
|
26
|
+
const notification = (Array.isArray(hooks.Notification) ? hooks.Notification : []).filter((entry) => !isBellHook(entry));
|
|
27
|
+
notification.push({
|
|
28
|
+
matcher: BELL_NOTIFICATION_MATCHER,
|
|
29
|
+
hooks: [{ type: "command", command: resolve(BELL_HOOK_BINARY), timeout: BELL_TIMEOUT_SECONDS }]
|
|
30
|
+
});
|
|
31
|
+
hooks.Notification = notification;
|
|
32
|
+
const stop = Array.isArray(hooks.Stop) ? hooks.Stop.filter((entry) => !isBellHook(entry)) : [];
|
|
33
|
+
if (Array.isArray(hooks.Stop)) {
|
|
34
|
+
if (stop.length === 0) delete hooks.Stop;
|
|
35
|
+
else hooks.Stop = stop;
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
var removeBellHooks = (settings) => {
|
|
39
|
+
const hooks = asRecord(settings.hooks);
|
|
40
|
+
if (!hooks) return false;
|
|
41
|
+
let changed = false;
|
|
42
|
+
for (const key of ["Stop", "Notification"]) {
|
|
43
|
+
const entries = hooks[key];
|
|
44
|
+
if (!Array.isArray(entries)) continue;
|
|
45
|
+
const filtered = entries.filter((entry) => !isBellHook(entry));
|
|
46
|
+
if (filtered.length === entries.length) continue;
|
|
47
|
+
if (filtered.length === 0) delete hooks[key];
|
|
48
|
+
else hooks[key] = filtered;
|
|
49
|
+
changed = true;
|
|
50
|
+
}
|
|
51
|
+
if (Object.keys(hooks).length === 0) delete settings.hooks;
|
|
52
|
+
return changed;
|
|
53
|
+
};
|
|
54
|
+
var bellInstalled = (settings) => {
|
|
55
|
+
const hooks = asRecord(settings.hooks);
|
|
56
|
+
if (!hooks) return false;
|
|
57
|
+
return ["Stop", "Notification"].some((key) => {
|
|
58
|
+
const entries = hooks[key];
|
|
59
|
+
return Array.isArray(entries) && entries.some(isBellHook);
|
|
60
|
+
});
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
export {
|
|
64
|
+
BELL_HOOK_BINARY,
|
|
65
|
+
addBellHooks,
|
|
66
|
+
removeBellHooks,
|
|
67
|
+
bellInstalled
|
|
68
|
+
};
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
// src/diagnostics/service-runtime.ts
|
|
2
|
+
var unescapeXml = (value) => value.replaceAll(""", '"').replaceAll("'", "'").replaceAll("<", "<").replaceAll(">", ">").replaceAll("&", "&");
|
|
3
|
+
var unquoteSystemd = (value) => value.replaceAll('\\"', '"').replaceAll("\\\\", "\\").replaceAll("%%", "%");
|
|
4
|
+
var readServiceExecPath = (contents, platform) => {
|
|
5
|
+
if (platform === "darwin") {
|
|
6
|
+
const program = contents.match(/<key>ProgramArguments<\/key>\s*<array>\s*<string>([^<]*)<\/string>/)?.[1];
|
|
7
|
+
return program ? unescapeXml(program) : null;
|
|
8
|
+
}
|
|
9
|
+
if (platform === "linux") {
|
|
10
|
+
const exec = contents.match(/^ExecStart="((?:[^"\\]|\\.)*)"/m)?.[1];
|
|
11
|
+
return exec ? unquoteSystemd(exec) : null;
|
|
12
|
+
}
|
|
13
|
+
if (platform === "win32") {
|
|
14
|
+
const command = contents.match(/<Command>([^<]*)<\/Command>/)?.[1];
|
|
15
|
+
return command ? unescapeXml(command) : null;
|
|
16
|
+
}
|
|
17
|
+
return null;
|
|
18
|
+
};
|
|
19
|
+
var MANAGER_SEGMENTS = [
|
|
20
|
+
[".nvm", "nvm"],
|
|
21
|
+
["nvm", "nvm"],
|
|
22
|
+
["fnm", "fnm"],
|
|
23
|
+
[".fnm", "fnm"],
|
|
24
|
+
[".volta", "volta"],
|
|
25
|
+
["volta", "volta"],
|
|
26
|
+
[".asdf", "asdf"],
|
|
27
|
+
["asdf", "asdf"]
|
|
28
|
+
];
|
|
29
|
+
var versionManagerOf = (execPath) => {
|
|
30
|
+
const segments = execPath.split(/[/\\]/);
|
|
31
|
+
for (const segment of segments) {
|
|
32
|
+
const hit = MANAGER_SEGMENTS.find(([name]) => name === segment);
|
|
33
|
+
if (hit) return hit[1];
|
|
34
|
+
}
|
|
35
|
+
return null;
|
|
36
|
+
};
|
|
37
|
+
var describeVersionManagerPin = (execPath, manager) => `The background service is pinned to ${execPath}, which ${manager} manages. Switching Node versions strands it; run pushary daemon install afterwards.`;
|
|
38
|
+
|
|
39
|
+
export {
|
|
40
|
+
readServiceExecPath,
|
|
41
|
+
versionManagerOf,
|
|
42
|
+
describeVersionManagerPin
|
|
43
|
+
};
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import {
|
|
2
2
|
detectClaudeVersion
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-YTDH3XCE.js";
|
|
4
4
|
import {
|
|
5
5
|
claudeWired,
|
|
6
6
|
codexWired,
|
|
7
7
|
geminiWired
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-WGKC6PU2.js";
|
|
9
9
|
import {
|
|
10
10
|
GEMINI_HOOK_BINARY,
|
|
11
11
|
addClaudeMcpServer,
|
|
@@ -14,7 +14,7 @@ import {
|
|
|
14
14
|
addPusharyHooks,
|
|
15
15
|
addPusharyToolPermissions,
|
|
16
16
|
hasGeminiHooks
|
|
17
|
-
} from "./chunk-
|
|
17
|
+
} from "./chunk-ELHCIEMM.js";
|
|
18
18
|
import {
|
|
19
19
|
guardBinary
|
|
20
20
|
} from "./chunk-EZIEXHYM.js";
|
|
@@ -32,7 +32,7 @@ import {
|
|
|
32
32
|
codexHooksJson,
|
|
33
33
|
hasCodexHooks,
|
|
34
34
|
vscodePluginMcp
|
|
35
|
-
} from "./chunk-
|
|
35
|
+
} from "./chunk-BVNYTQ5I.js";
|
|
36
36
|
import {
|
|
37
37
|
readJsonSafe
|
|
38
38
|
} from "./chunk-4LL6LG5X.js";
|
|
@@ -2,11 +2,11 @@ import {
|
|
|
2
2
|
CLAUDE_HOOK_EVENTS,
|
|
3
3
|
GEMINI_HOOK_EVENTS,
|
|
4
4
|
hasGeminiHooks
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-ELHCIEMM.js";
|
|
6
6
|
import {
|
|
7
7
|
CODEX_HOOK_EVENTS,
|
|
8
8
|
hasCodexHooks
|
|
9
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-BVNYTQ5I.js";
|
|
10
10
|
import {
|
|
11
11
|
CURSOR_GATE_EVENTS,
|
|
12
12
|
HOOK_LOCATOR_BINARY
|
|
@@ -7,10 +7,10 @@ import {
|
|
|
7
7
|
} from "./chunk-L4ZCFMWW.js";
|
|
8
8
|
import {
|
|
9
9
|
getMachineId
|
|
10
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-AWHCZOZO.js";
|
|
11
11
|
import {
|
|
12
12
|
pusharyDir
|
|
13
|
-
} from "./chunk-
|
|
13
|
+
} from "./chunk-BVNYTQ5I.js";
|
|
14
14
|
import {
|
|
15
15
|
getPackageVersion
|
|
16
16
|
} from "./chunk-DINDL2CF.js";
|
package/dist/src/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
handlePreToolUse
|
|
3
|
-
} from "../chunk-
|
|
3
|
+
} from "../chunk-4IEW4ZRF.js";
|
|
4
4
|
import "../chunk-2LAHXHBR.js";
|
|
5
5
|
import {
|
|
6
6
|
fetchAccountPublicKey
|
|
@@ -16,15 +16,15 @@ import {
|
|
|
16
16
|
handlePostToolUse,
|
|
17
17
|
handleStop,
|
|
18
18
|
reportEvent
|
|
19
|
-
} from "../chunk-
|
|
19
|
+
} from "../chunk-TVZQMPB7.js";
|
|
20
20
|
import {
|
|
21
21
|
askUser,
|
|
22
22
|
cancelQuestion,
|
|
23
23
|
waitForAnswer
|
|
24
24
|
} from "../chunk-F7OURW3C.js";
|
|
25
25
|
import "../chunk-L4E2BIJ2.js";
|
|
26
|
-
import "../chunk-
|
|
27
|
-
import "../chunk-
|
|
26
|
+
import "../chunk-AWHCZOZO.js";
|
|
27
|
+
import "../chunk-BVNYTQ5I.js";
|
|
28
28
|
import "../chunk-BSZYIAZL.js";
|
|
29
29
|
import "../chunk-SAF6HGAA.js";
|
|
30
30
|
import "../chunk-7QLSKOSU.js";
|
package/package.json
CHANGED
package/dist/chunk-AZPIQG4G.js
DELETED
|
@@ -1,274 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
resolvePathBinary
|
|
3
|
-
} from "./chunk-L4ZCFMWW.js";
|
|
4
|
-
|
|
5
|
-
// src/hermes-config.ts
|
|
6
|
-
import { execFileSync } from "child_process";
|
|
7
|
-
import { existsSync, readFileSync, rmSync, writeFileSync } from "fs";
|
|
8
|
-
import { homedir, tmpdir } from "os";
|
|
9
|
-
import { join } from "path";
|
|
10
|
-
var isRecord = (value) => Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
11
|
-
var isSnapshot = (value) => isRecord(value) && typeof value.present === "boolean" && Object.hasOwn(value, "value");
|
|
12
|
-
var isHermesConfigReceipt = (value) => {
|
|
13
|
-
if (!isRecord(value)) return false;
|
|
14
|
-
return ["security", "approval", "transport", "fallback", "plugins", "enabled", "agent", "disabled"].every((key) => isSnapshot(value[key])) && typeof value.pushary_enabled === "boolean" && typeof value.clarify_disabled === "boolean";
|
|
15
|
-
};
|
|
16
|
-
var HERMES_CONFIG_SCRIPT = String.raw`
|
|
17
|
-
import json, os, sys
|
|
18
|
-
from hermes_cli.config import load_config, save_config
|
|
19
|
-
|
|
20
|
-
mode, receipt_path = sys.argv[1], sys.argv[2]
|
|
21
|
-
c = load_config()
|
|
22
|
-
|
|
23
|
-
def snap(obj, key):
|
|
24
|
-
return {"present": key in obj, "value": obj.get(key)}
|
|
25
|
-
|
|
26
|
-
restore_conflicts = []
|
|
27
|
-
|
|
28
|
-
def restore(obj, key, before, ours):
|
|
29
|
-
if before.get("present") and before.get("value") == ours:
|
|
30
|
-
return
|
|
31
|
-
if before.get("present"):
|
|
32
|
-
if key in obj and obj.get(key) == before.get("value"):
|
|
33
|
-
return
|
|
34
|
-
elif key not in obj:
|
|
35
|
-
return
|
|
36
|
-
if obj.get(key) != ours:
|
|
37
|
-
restore_conflicts.append(key)
|
|
38
|
-
return
|
|
39
|
-
if before.get("present"):
|
|
40
|
-
obj[key] = before.get("value")
|
|
41
|
-
else:
|
|
42
|
-
obj.pop(key, None)
|
|
43
|
-
|
|
44
|
-
def restore_container(obj, key, before, current, changed):
|
|
45
|
-
raw = obj.get(key)
|
|
46
|
-
if isinstance(before.get("value"), dict):
|
|
47
|
-
if not isinstance(raw, dict):
|
|
48
|
-
if changed:
|
|
49
|
-
restore_conflicts.append(key)
|
|
50
|
-
return
|
|
51
|
-
if current or before.get("present"):
|
|
52
|
-
obj[key] = current
|
|
53
|
-
else:
|
|
54
|
-
obj.pop(key, None)
|
|
55
|
-
elif isinstance(raw, dict) and current:
|
|
56
|
-
restore_conflicts.append(key)
|
|
57
|
-
else:
|
|
58
|
-
restore(obj, key, before, {})
|
|
59
|
-
|
|
60
|
-
def restore_list_member(obj, key, before, ours, ours_before):
|
|
61
|
-
raw = obj.get(key)
|
|
62
|
-
if isinstance(before.get("value"), list):
|
|
63
|
-
if ours_before:
|
|
64
|
-
return
|
|
65
|
-
if not isinstance(raw, list):
|
|
66
|
-
restore_conflicts.append(key)
|
|
67
|
-
return
|
|
68
|
-
remaining = [item for item in raw if item != ours]
|
|
69
|
-
if remaining or before.get("present"):
|
|
70
|
-
obj[key] = remaining
|
|
71
|
-
else:
|
|
72
|
-
obj.pop(key, None)
|
|
73
|
-
return
|
|
74
|
-
if before.get("present") and raw == before.get("value"):
|
|
75
|
-
return
|
|
76
|
-
if not before.get("present") and key not in obj:
|
|
77
|
-
return
|
|
78
|
-
if not isinstance(raw, list):
|
|
79
|
-
restore_conflicts.append(key)
|
|
80
|
-
return
|
|
81
|
-
remaining = [item for item in raw if item != ours]
|
|
82
|
-
if remaining:
|
|
83
|
-
restore_conflicts.append(key)
|
|
84
|
-
else:
|
|
85
|
-
restore(obj, key, before, raw)
|
|
86
|
-
|
|
87
|
-
def valid_snapshot(value):
|
|
88
|
-
return isinstance(value, dict) and isinstance(value.get("present"), bool) and "value" in value
|
|
89
|
-
|
|
90
|
-
def valid_receipt(value):
|
|
91
|
-
snapshots = ["security", "approval", "transport", "fallback", "plugins", "enabled", "agent", "disabled"]
|
|
92
|
-
return (isinstance(value, dict)
|
|
93
|
-
and all(valid_snapshot(value.get(key)) for key in snapshots)
|
|
94
|
-
and isinstance(value.get("pushary_enabled"), bool)
|
|
95
|
-
and isinstance(value.get("clarify_disabled"), bool))
|
|
96
|
-
|
|
97
|
-
if mode == "setup":
|
|
98
|
-
security_before = snap(c, "security")
|
|
99
|
-
s = c.get("security") if isinstance(c.get("security"), dict) else {}
|
|
100
|
-
approval_before = snap(s, "approval")
|
|
101
|
-
ap = s.get("approval") if isinstance(s.get("approval"), dict) else {}
|
|
102
|
-
plugins_before = snap(c, "plugins")
|
|
103
|
-
p = c.get("plugins") if isinstance(c.get("plugins"), dict) else {}
|
|
104
|
-
agent_before = snap(c, "agent")
|
|
105
|
-
a = c.get("agent") if isinstance(c.get("agent"), dict) else {}
|
|
106
|
-
enabled = p.get("enabled") if isinstance(p.get("enabled"), list) else []
|
|
107
|
-
disabled = a.get("disabled_toolsets") if isinstance(a.get("disabled_toolsets"), list) else []
|
|
108
|
-
receipt = {
|
|
109
|
-
"security": security_before,
|
|
110
|
-
"approval": approval_before,
|
|
111
|
-
"transport": snap(ap, "transport"),
|
|
112
|
-
"fallback": snap(ap, "transport_fallback"),
|
|
113
|
-
"plugins": plugins_before,
|
|
114
|
-
"enabled": snap(p, "enabled"),
|
|
115
|
-
"pushary_enabled": "pushary" in enabled,
|
|
116
|
-
"agent": agent_before,
|
|
117
|
-
"disabled": snap(a, "disabled_toolsets"),
|
|
118
|
-
"clarify_disabled": "clarify" in disabled,
|
|
119
|
-
}
|
|
120
|
-
if os.path.exists(receipt_path):
|
|
121
|
-
with open(receipt_path, encoding="utf-8") as handle:
|
|
122
|
-
existing_receipt = json.load(handle)
|
|
123
|
-
if not valid_receipt(existing_receipt):
|
|
124
|
-
raise ValueError("invalid Pushary Hermes setup receipt")
|
|
125
|
-
else:
|
|
126
|
-
os.makedirs(os.path.dirname(receipt_path), exist_ok=True)
|
|
127
|
-
temp = receipt_path + ".tmp"
|
|
128
|
-
with open(temp, "x", encoding="utf-8") as handle:
|
|
129
|
-
json.dump(receipt, handle)
|
|
130
|
-
os.chmod(temp, 0o600)
|
|
131
|
-
os.replace(temp, receipt_path)
|
|
132
|
-
ap["transport"] = "pushary"
|
|
133
|
-
ap["transport_fallback"] = "builtin"
|
|
134
|
-
s["approval"] = ap
|
|
135
|
-
c["security"] = s
|
|
136
|
-
p["enabled"] = enabled if "pushary" in enabled else enabled + ["pushary"]
|
|
137
|
-
c["plugins"] = p
|
|
138
|
-
a["disabled_toolsets"] = disabled if "clarify" in disabled else disabled + ["clarify"]
|
|
139
|
-
c["agent"] = a
|
|
140
|
-
else:
|
|
141
|
-
receipt_exists = os.path.exists(receipt_path)
|
|
142
|
-
try:
|
|
143
|
-
with open(receipt_path, encoding="utf-8") as handle:
|
|
144
|
-
receipt = json.load(handle)
|
|
145
|
-
except (OSError, ValueError):
|
|
146
|
-
if receipt_exists:
|
|
147
|
-
raise
|
|
148
|
-
receipt = None
|
|
149
|
-
s = c.get("security") if isinstance(c.get("security"), dict) else {}
|
|
150
|
-
ap = s.get("approval") if isinstance(s.get("approval"), dict) else {}
|
|
151
|
-
if receipt:
|
|
152
|
-
if not valid_receipt(receipt):
|
|
153
|
-
raise ValueError("invalid Pushary Hermes setup receipt")
|
|
154
|
-
approval_changed = (not receipt["transport"].get("present")
|
|
155
|
-
or receipt["transport"].get("value") != "pushary"
|
|
156
|
-
or not receipt["fallback"].get("present")
|
|
157
|
-
or receipt["fallback"].get("value") != "builtin")
|
|
158
|
-
plugins_changed = not (isinstance(receipt["enabled"].get("value"), list)
|
|
159
|
-
and receipt["pushary_enabled"])
|
|
160
|
-
agent_changed = not (isinstance(receipt["disabled"].get("value"), list)
|
|
161
|
-
and receipt["clarify_disabled"])
|
|
162
|
-
restore(ap, "transport", receipt["transport"], "pushary")
|
|
163
|
-
restore(ap, "transport_fallback", receipt["fallback"], "builtin")
|
|
164
|
-
else:
|
|
165
|
-
if ap.get("transport") == "pushary": ap.pop("transport", None)
|
|
166
|
-
if ap.get("transport_fallback") == "builtin": ap.pop("transport_fallback", None)
|
|
167
|
-
if receipt:
|
|
168
|
-
restore_container(s, "approval", receipt["approval"], ap, approval_changed)
|
|
169
|
-
restore_container(c, "security", receipt["security"], s, approval_changed)
|
|
170
|
-
else:
|
|
171
|
-
if ap: s["approval"] = ap
|
|
172
|
-
else: s.pop("approval", None)
|
|
173
|
-
if s: c["security"] = s
|
|
174
|
-
else: c.pop("security", None)
|
|
175
|
-
|
|
176
|
-
p = c.get("plugins") if isinstance(c.get("plugins"), dict) else {}
|
|
177
|
-
if receipt:
|
|
178
|
-
restore_list_member(p, "enabled", receipt["enabled"], "pushary", receipt["pushary_enabled"])
|
|
179
|
-
restore_container(c, "plugins", receipt["plugins"], p, plugins_changed)
|
|
180
|
-
else:
|
|
181
|
-
enabled = p.get("enabled") if isinstance(p.get("enabled"), list) else []
|
|
182
|
-
enabled = [item for item in enabled if item != "pushary"]
|
|
183
|
-
if enabled: p["enabled"] = enabled
|
|
184
|
-
else: p.pop("enabled", None)
|
|
185
|
-
if p: c["plugins"] = p
|
|
186
|
-
else: c.pop("plugins", None)
|
|
187
|
-
|
|
188
|
-
a = c.get("agent") if isinstance(c.get("agent"), dict) else {}
|
|
189
|
-
if receipt:
|
|
190
|
-
restore_list_member(a, "disabled_toolsets", receipt["disabled"], "clarify", receipt["clarify_disabled"])
|
|
191
|
-
restore_container(c, "agent", receipt["agent"], a, agent_changed)
|
|
192
|
-
else:
|
|
193
|
-
disabled = a.get("disabled_toolsets") if isinstance(a.get("disabled_toolsets"), list) else []
|
|
194
|
-
disabled = [item for item in disabled if item != "clarify"]
|
|
195
|
-
if disabled: a["disabled_toolsets"] = disabled
|
|
196
|
-
else: a.pop("disabled_toolsets", None)
|
|
197
|
-
if a: c["agent"] = a
|
|
198
|
-
else: c.pop("agent", None)
|
|
199
|
-
|
|
200
|
-
if receipt and restore_conflicts:
|
|
201
|
-
raise ValueError("Hermes config changed after Pushary setup")
|
|
202
|
-
|
|
203
|
-
save_config(c)
|
|
204
|
-
`;
|
|
205
|
-
var resolveHermesPython = () => {
|
|
206
|
-
const launcher = resolvePathBinary("hermes");
|
|
207
|
-
if (launcher && process.platform !== "win32") {
|
|
208
|
-
try {
|
|
209
|
-
const shebang = readFileSync(launcher, "utf-8").split("\n", 1)[0];
|
|
210
|
-
const interpreter = shebang?.startsWith("#!") ? shebang.slice(2).trim().split(/\s+/)[0] : void 0;
|
|
211
|
-
if (interpreter && /python/i.test(interpreter) && existsSync(interpreter)) return interpreter;
|
|
212
|
-
} catch {
|
|
213
|
-
}
|
|
214
|
-
}
|
|
215
|
-
const root = join(homedir(), ".hermes", "hermes-agent", "venv");
|
|
216
|
-
const candidates = process.platform === "win32" ? [join(root, "Scripts", "python.exe"), join(root, "Scripts", "python3.exe")] : [join(root, "bin", "python3"), join(root, "bin", "python")];
|
|
217
|
-
return candidates.find(existsSync) ?? null;
|
|
218
|
-
};
|
|
219
|
-
var runHermesPythonScript = (python, script, ...args) => {
|
|
220
|
-
const path = join(tmpdir(), `pushary-hermes-${process.pid}-${Date.now()}.py`);
|
|
221
|
-
try {
|
|
222
|
-
writeFileSync(path, script, { encoding: "utf-8", mode: 384 });
|
|
223
|
-
execFileSync(python, [path, ...args], { env: { ...process.env }, stdio: "pipe", timeout: 15e3 });
|
|
224
|
-
return true;
|
|
225
|
-
} catch {
|
|
226
|
-
return false;
|
|
227
|
-
} finally {
|
|
228
|
-
try {
|
|
229
|
-
rmSync(path, { force: true });
|
|
230
|
-
} catch {
|
|
231
|
-
}
|
|
232
|
-
}
|
|
233
|
-
};
|
|
234
|
-
|
|
235
|
-
// src/cli/dry-run.ts
|
|
236
|
-
import { execSync } from "child_process";
|
|
237
|
-
import { rmSync as rmSync2, writeFileSync as writeFileSync2 } from "fs";
|
|
238
|
-
var createGuards = (isDryRun, note) => ({
|
|
239
|
-
write: (path, contents) => {
|
|
240
|
-
if (isDryRun()) return note("rewrite", path);
|
|
241
|
-
writeFileSync2(path, contents, "utf-8");
|
|
242
|
-
},
|
|
243
|
-
remove: (path, options = {}) => {
|
|
244
|
-
if (isDryRun()) return note("remove ", path);
|
|
245
|
-
rmSync2(path, { recursive: true, force: options.force ?? false });
|
|
246
|
-
},
|
|
247
|
-
exec: (command, options, describe) => {
|
|
248
|
-
if (isDryRun()) return note("run ", describe);
|
|
249
|
-
execSync(command, options);
|
|
250
|
-
}
|
|
251
|
-
});
|
|
252
|
-
var describeService = (effects) => {
|
|
253
|
-
if (effects.controlMode === "off") return "skip the phone-start background service (--control off)";
|
|
254
|
-
const where = effects.serviceDefinitionPath ?? "the phone-start service definition";
|
|
255
|
-
const condition = effects.controlMode === "on" ? "" : ", when a supported agent and a service manager are present";
|
|
256
|
-
return `write ${where} and register it with the OS${condition}`;
|
|
257
|
-
};
|
|
258
|
-
var describeSetupSideEffects = (effects) => [
|
|
259
|
-
`run npm install -g @pushary/agent-hooks@${effects.version} (the hooks run from the global copy; up to two minutes)`,
|
|
260
|
-
`write ${effects.configFile} (the API key, mode 600)`,
|
|
261
|
-
effects.rcPath ? `edit ${effects.rcPath} (export PUSHARY_API_KEY)` : "print the export line for your shell, since no rc file was found",
|
|
262
|
-
describeService(effects),
|
|
263
|
-
"ask Add Pushary instructions to this project's CLAUDE.md/AGENTS.md? (default no)",
|
|
264
|
-
"ask Alias claude to pushary claude? (default no)"
|
|
265
|
-
];
|
|
266
|
-
|
|
267
|
-
export {
|
|
268
|
-
isHermesConfigReceipt,
|
|
269
|
-
HERMES_CONFIG_SCRIPT,
|
|
270
|
-
resolveHermesPython,
|
|
271
|
-
runHermesPythonScript,
|
|
272
|
-
createGuards,
|
|
273
|
-
describeSetupSideEffects
|
|
274
|
-
};
|