@pushary/agent-hooks 0.89.12 → 0.90.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/CHANGELOG.md +77 -0
- package/README.md +1 -0
- package/data/SKILL.md +1 -1
- package/data/cursor-plugin/skills/pushary/SKILL.md +1 -1
- package/data/vscode-plugin/skills/pushary/SKILL.md +1 -1
- package/dist/bin/pushary-bell-hook.js +4 -4
- package/dist/bin/pushary-bell.js +8 -8
- package/dist/bin/pushary-claude.js +194 -38
- package/dist/bin/pushary-clean.js +15 -32
- package/dist/bin/pushary-codex-bridge.js +37 -12
- package/dist/bin/pushary-codex-hook.js +45 -12
- package/dist/bin/pushary-codex.js +7 -7
- package/dist/bin/pushary-connect.js +11 -10
- package/dist/bin/pushary-daemon.js +186 -62
- package/dist/bin/pushary-disconnect.js +8 -8
- package/dist/bin/pushary-doctor.js +108 -25
- package/dist/bin/pushary-elicitation-hook.js +4 -4
- package/dist/bin/pushary-gemini-bridge.js +33 -11
- package/dist/bin/pushary-gemini-hook.js +21 -10
- package/dist/bin/pushary-hook.js +13 -13
- package/dist/bin/pushary-login.js +18 -7
- package/dist/bin/pushary-logout.js +6 -6
- package/dist/bin/pushary-mode.js +28 -21
- package/dist/bin/pushary-notification-hook.js +7 -7
- package/dist/bin/pushary-opencode-hook.js +8 -8
- package/dist/bin/pushary-permission-denied-hook.js +11 -11
- package/dist/bin/pushary-permission-hook.js +11 -11
- package/dist/bin/pushary-post-hook.js +14 -8
- package/dist/bin/pushary-prompt-hook.js +7 -7
- package/dist/bin/pushary-session-end-hook.js +7 -7
- package/dist/bin/pushary-session-start-hook.js +7 -7
- package/dist/bin/pushary-setup.js +168 -45
- package/dist/bin/pushary-stats.js +4 -4
- package/dist/bin/pushary-status.js +11 -10
- package/dist/bin/pushary-stop-hook.js +14 -8
- package/dist/bin/pushary-stopfailure-hook.js +7 -7
- package/dist/bin/pushary-suggestions.js +2 -2
- package/dist/bin/pushary-transcripts.d.ts +1 -0
- package/dist/bin/pushary-transcripts.js +79 -0
- package/dist/bin/pushary-upgrade.js +15 -15
- package/dist/bin/pushary-wait.js +5 -5
- package/dist/bin/pushary.js +3 -2
- package/dist/{chunk-KVHP5ZME.js → chunk-2LAHXHBR.js} +1 -1
- package/dist/{chunk-RLQWNEPM.js → chunk-43GMYSS4.js} +15 -0
- package/dist/{chunk-5NDB3YOE.js → chunk-54ELC7D7.js} +1 -1
- package/dist/{chunk-VPLQFXCH.js → chunk-5NAZ7SFS.js} +1 -1
- package/dist/chunk-7FE2KABY.js +156 -0
- package/dist/{chunk-DHR4F2CR.js → chunk-7TPQEWUZ.js} +1 -1
- package/dist/{chunk-Y4WVZIEI.js → chunk-AJYV3VGU.js} +1 -1
- package/dist/{chunk-QFJZHVVL.js → chunk-AZPIQG4G.js} +35 -1
- package/dist/{chunk-OCMOTJTW.js → chunk-B7UZHEBL.js} +29 -22
- package/dist/{chunk-7HYJPOGG.js → chunk-BQBEH6TV.js} +5 -5
- package/dist/{chunk-OTPJ4FUR.js → chunk-BVBEURWJ.js} +1 -1
- package/dist/{chunk-7DK3ZH3N.js → chunk-CELX75MW.js} +1 -1
- package/dist/{chunk-6VXNA5JB.js → chunk-CNPAT3HL.js} +3 -2
- package/dist/{chunk-327YMKP5.js → chunk-DYP6SMOR.js} +1 -1
- package/dist/chunk-DZDI62LY.js +145 -0
- package/dist/{chunk-VF3RTVWY.js → chunk-EZIEXHYM.js} +15 -7
- package/dist/{chunk-PS7DQ2SU.js → chunk-F2ICEWZL.js} +101 -19
- package/dist/{chunk-3XSP5SML.js → chunk-FQL5E6NG.js} +2 -2
- package/dist/{chunk-JHJHCMAF.js → chunk-FXEVRLHC.js} +20 -2
- package/dist/{chunk-6V7ZDH3A.js → chunk-GL3FFM7A.js} +7 -4
- package/dist/{chunk-NBRM3FMD.js → chunk-H2QZRQR2.js} +1 -1
- package/dist/{chunk-GKPURU7P.js → chunk-I463AEG3.js} +1 -1
- package/dist/{chunk-XREW7RQ3.js → chunk-JW4KO5AF.js} +1 -1
- package/dist/{chunk-OVEGEQSB.js → chunk-KUZWXPUS.js} +3 -2
- package/dist/{chunk-JVZX5YRJ.js → chunk-L4E2BIJ2.js} +80 -7
- package/dist/{chunk-RNKPLUWC.js → chunk-OSALSIUF.js} +1 -1
- package/dist/{chunk-MFNQ5IMW.js → chunk-PYA4FLKI.js} +32 -21
- package/dist/chunk-QCGNGEGE.js +15 -0
- package/dist/chunk-S6LYP7QA.js +127 -0
- package/dist/{chunk-OADGBOP7.js → chunk-TDZFPMSZ.js} +70 -11
- package/dist/{chunk-JJV5Q4KG.js → chunk-UZNKVRLE.js} +1 -1
- package/dist/{chunk-FCUUVHTS.js → chunk-VHWCVD42.js} +1 -1
- package/dist/{chunk-3MCEES6Z.js → chunk-VNMKIJZB.js} +15 -2
- package/dist/{chunk-6GU55AQQ.js → chunk-Y6BWPERV.js} +90 -7
- package/dist/chunk-YKLCWWEI.js +76 -0
- package/dist/src/index.d.ts +3 -0
- package/dist/src/index.js +11 -11
- package/package.json +3 -2
- package/dist/chunk-NESKLV6R.js +0 -72
- package/dist/chunk-WDVQ6O4C.js +0 -84
|
@@ -91,7 +91,7 @@ var INSTALL_SOURCES = [
|
|
|
91
91
|
var isInstallSource = (value) => typeof value === "string" && INSTALL_SOURCES.includes(value);
|
|
92
92
|
var normalizeInstallSource = (value) => isInstallSource(value) ? value : "unknown";
|
|
93
93
|
var HOOK_BUDGETS = {
|
|
94
|
-
claude: { budgetSeconds:
|
|
94
|
+
claude: { budgetSeconds: 600, guardSeconds: 10 },
|
|
95
95
|
codex: { budgetSeconds: 180, guardSeconds: 10 },
|
|
96
96
|
gemini: { budgetSeconds: 180, guardSeconds: 10 },
|
|
97
97
|
// The Cursor gate is a dependency-free .mjs that cannot import this module; it
|
|
@@ -1054,6 +1054,8 @@ var resolvePolicyAcross = (config, toolName, modeOverride, toolInputs, cwd, repo
|
|
|
1054
1054
|
};
|
|
1055
1055
|
var isModeStateDegraded = (state) => state?.degraded === true;
|
|
1056
1056
|
var KILL_REASON = "Stopped by user, this agent was halted from Pushary";
|
|
1057
|
+
var PUSHARY_TOOL_PREFIX = "mcp__pushary__";
|
|
1058
|
+
var isPusharyOwnTool = (toolName) => toolName.startsWith(PUSHARY_TOOL_PREFIX);
|
|
1057
1059
|
var strictestScopeVerdict = (scope, paths) => {
|
|
1058
1060
|
if (paths.length === 0) return evaluateScope(scope, void 0);
|
|
1059
1061
|
let worst = { outcome: "in_scope" };
|
|
@@ -1085,6 +1087,13 @@ var ASKED_IN_EVERY_MODE = new Set(ASKED_IN_EVERY_MODE_TOOLS);
|
|
|
1085
1087
|
var isAskedInEveryMode = (toolName) => ASKED_IN_EVERY_MODE.has(toolName);
|
|
1086
1088
|
var resolveGate = (input) => {
|
|
1087
1089
|
const { modeState, config, toolName, toolInputs, cwd, repoKey, sessionId } = input;
|
|
1090
|
+
if (isPusharyOwnTool(toolName)) {
|
|
1091
|
+
return {
|
|
1092
|
+
kind: "defer",
|
|
1093
|
+
policy: resolvePolicyAcross(config, toolName, modeState.mode, toolInputs, cwd, repoKey, sessionId),
|
|
1094
|
+
reason: "own_tool"
|
|
1095
|
+
};
|
|
1096
|
+
}
|
|
1088
1097
|
if (modeState.kill) return { kind: "kill", reason: KILL_REASON };
|
|
1089
1098
|
const policy = resolvePolicyAcross(config, toolName, modeState.mode, toolInputs, cwd, repoKey, sessionId);
|
|
1090
1099
|
const verdict = verdictForPolicy(policy);
|
|
@@ -1128,9 +1137,11 @@ var modeStateFromResponse = (data) => {
|
|
|
1128
1137
|
// Validated rather than cast: a malformed contract must read as no contract,
|
|
1129
1138
|
// never as one that silently matches nothing.
|
|
1130
1139
|
scope: isScopeContract(data.scope) ? data.scope : null,
|
|
1131
|
-
degraded: false
|
|
1140
|
+
degraded: false,
|
|
1141
|
+
askHandoff: data.askHandoff === true
|
|
1132
1142
|
};
|
|
1133
1143
|
};
|
|
1144
|
+
var stoppedOutput = (output, stopReason) => ({ ...output, continue: false, stopReason });
|
|
1134
1145
|
var claudePreToolUseOutput = (decision, reason, updatedInput) => ({
|
|
1135
1146
|
hookSpecificOutput: {
|
|
1136
1147
|
hookEventName: "PreToolUse",
|
|
@@ -1362,10 +1373,12 @@ export {
|
|
|
1362
1373
|
resolveAutoResolveOrigin,
|
|
1363
1374
|
resolvePolicy,
|
|
1364
1375
|
KILL_REASON,
|
|
1376
|
+
isPusharyOwnTool,
|
|
1365
1377
|
shouldDeferToNativeMode,
|
|
1366
1378
|
ASKED_IN_EVERY_MODE_TOOLS,
|
|
1367
1379
|
resolveGate,
|
|
1368
1380
|
modeStateFromResponse,
|
|
1381
|
+
stoppedOutput,
|
|
1369
1382
|
claudePreToolUseOutput,
|
|
1370
1383
|
PENDING_COMMAND_PREFIX,
|
|
1371
1384
|
pendingCommandEvents,
|
|
@@ -1,22 +1,23 @@
|
|
|
1
1
|
import {
|
|
2
2
|
pusharyDir
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-KUZWXPUS.js";
|
|
4
4
|
import {
|
|
5
5
|
readJsonSafe
|
|
6
6
|
} from "./chunk-4LL6LG5X.js";
|
|
7
7
|
|
|
8
8
|
// src/process-ownership.ts
|
|
9
9
|
import { createHash, randomUUID } from "crypto";
|
|
10
|
-
import { mkdirSync, readdirSync, unlinkSync, writeFileSync } from "fs";
|
|
10
|
+
import { mkdirSync, readdirSync, statSync, unlinkSync, writeFileSync } from "fs";
|
|
11
11
|
import { createConnection, createServer } from "net";
|
|
12
12
|
import { join } from "path";
|
|
13
13
|
var MAX_PROOF_BYTES = 1024;
|
|
14
14
|
var ownershipDir = (options) => options.dir ?? join(pusharyDir(), "processes");
|
|
15
15
|
var proofPath = (dir, sessionId) => join(dir, `${createHash("sha256").update(sessionId).digest("hex")}.json`);
|
|
16
|
+
var isOptionalTimestamp = (value) => value === void 0 || typeof value === "string" && Number.isFinite(Date.parse(value));
|
|
16
17
|
var isProof = (value) => {
|
|
17
18
|
if (!value || typeof value !== "object") return false;
|
|
18
19
|
const proof = value;
|
|
19
|
-
return typeof proof.sessionId === "string" && Number.isInteger(proof.pid) && proof.pid > 1 && Number.isInteger(proof.port) && proof.port > 0 && proof.port <= 65535 && typeof proof.token === "string" && proof.token.length > 0 && (proof.spawnId === void 0 || typeof proof.spawnId === "string");
|
|
20
|
+
return typeof proof.sessionId === "string" && Number.isInteger(proof.pid) && proof.pid > 1 && Number.isInteger(proof.port) && proof.port > 0 && proof.port <= 65535 && typeof proof.token === "string" && proof.token.length > 0 && (proof.spawnId === void 0 || typeof proof.spawnId === "string") && isOptionalTimestamp(proof.startedAt) && isOptionalTimestamp(proof.heartbeatAt);
|
|
20
21
|
};
|
|
21
22
|
var removeProof = (path, token) => {
|
|
22
23
|
const current = readJsonSafe(path);
|
|
@@ -34,16 +35,74 @@ var processIsAlive = (pid) => {
|
|
|
34
35
|
return error.code === "EPERM";
|
|
35
36
|
}
|
|
36
37
|
};
|
|
37
|
-
var
|
|
38
|
+
var fileTimes = (path) => {
|
|
39
|
+
try {
|
|
40
|
+
const stat = statSync(path);
|
|
41
|
+
return { createdAt: stat.birthtimeMs || stat.mtimeMs, modifiedAt: stat.mtimeMs };
|
|
42
|
+
} catch {
|
|
43
|
+
return { createdAt: 0, modifiedAt: 0 };
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
var parseTimestamp = (value) => {
|
|
47
|
+
if (value === void 0) return null;
|
|
48
|
+
const parsed = Date.parse(value);
|
|
49
|
+
return Number.isFinite(parsed) ? parsed : null;
|
|
50
|
+
};
|
|
51
|
+
var proofEntry = (proof, path) => {
|
|
52
|
+
const times = fileTimes(path);
|
|
53
|
+
const recordedStart = parseTimestamp(proof.startedAt);
|
|
54
|
+
const startedAt = recordedStart ?? times.createdAt;
|
|
55
|
+
return {
|
|
56
|
+
sessionId: proof.sessionId,
|
|
57
|
+
pid: proof.pid,
|
|
58
|
+
spawnId: proof.spawnId ?? null,
|
|
59
|
+
startedAt,
|
|
60
|
+
lastSeenAt: parseTimestamp(proof.heartbeatAt) ?? recordedStart ?? times.modifiedAt,
|
|
61
|
+
alive: processIsAlive(proof.pid)
|
|
62
|
+
};
|
|
63
|
+
};
|
|
64
|
+
var safeUnlink = (path) => {
|
|
65
|
+
try {
|
|
66
|
+
unlinkSync(path);
|
|
67
|
+
} catch {
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
var listProcessProofs = (options = {}) => {
|
|
71
|
+
const dir = ownershipDir(options);
|
|
72
|
+
const entries = [];
|
|
38
73
|
try {
|
|
39
74
|
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
|
40
75
|
if (!entry.isFile() || !entry.name.endsWith(".json")) continue;
|
|
41
76
|
const path = join(dir, entry.name);
|
|
42
77
|
const stored = readJsonSafe(path);
|
|
43
|
-
if (stored.kind !== "ok" || !isProof(stored.value)
|
|
78
|
+
if (stored.kind !== "ok" || !isProof(stored.value)) {
|
|
79
|
+
if (options.reapDead) safeUnlink(path);
|
|
80
|
+
continue;
|
|
81
|
+
}
|
|
82
|
+
const listed = proofEntry(stored.value, path);
|
|
83
|
+
if (!listed.alive && options.reapDead) safeUnlink(path);
|
|
84
|
+
entries.push(listed);
|
|
44
85
|
}
|
|
45
86
|
} catch {
|
|
46
87
|
}
|
|
88
|
+
return entries;
|
|
89
|
+
};
|
|
90
|
+
var pruneProofs = (dir) => {
|
|
91
|
+
listProcessProofs({ dir, reapDead: true });
|
|
92
|
+
};
|
|
93
|
+
var wasSpawned = (entry) => entry.spawnId !== null;
|
|
94
|
+
var silentSpawnedProofs = (entries, now, silenceMs) => {
|
|
95
|
+
const freshestByPid = /* @__PURE__ */ new Map();
|
|
96
|
+
for (const entry of entries) {
|
|
97
|
+
freshestByPid.set(entry.pid, Math.max(freshestByPid.get(entry.pid) ?? 0, entry.lastSeenAt));
|
|
98
|
+
}
|
|
99
|
+
const reported = /* @__PURE__ */ new Set();
|
|
100
|
+
return entries.filter((entry) => {
|
|
101
|
+
if (!entry.alive || !wasSpawned(entry) || reported.has(entry.pid)) return false;
|
|
102
|
+
if (now - (freshestByPid.get(entry.pid) ?? 0) < silenceMs) return false;
|
|
103
|
+
reported.add(entry.pid);
|
|
104
|
+
return true;
|
|
105
|
+
});
|
|
47
106
|
};
|
|
48
107
|
var startProcessOwnership = (identity, options = {}) => {
|
|
49
108
|
const dir = ownershipDir(options);
|
|
@@ -88,7 +147,7 @@ var startProcessOwnership = (identity, options = {}) => {
|
|
|
88
147
|
return;
|
|
89
148
|
}
|
|
90
149
|
try {
|
|
91
|
-
proof = { ...identity, port: address.port, token };
|
|
150
|
+
proof = { ...identity, port: address.port, token, startedAt: (/* @__PURE__ */ new Date()).toISOString() };
|
|
92
151
|
mkdirSync(dir, { recursive: true, mode: 448 });
|
|
93
152
|
pruneProofs(dir);
|
|
94
153
|
writeFileSync(path, JSON.stringify(proof), {
|
|
@@ -107,6 +166,14 @@ var startProcessOwnership = (identity, options = {}) => {
|
|
|
107
166
|
process.once("exit", cleanup);
|
|
108
167
|
return {
|
|
109
168
|
ready,
|
|
169
|
+
touch(at = Date.now()) {
|
|
170
|
+
if (stopped || !proof) return;
|
|
171
|
+
proof = { ...proof, heartbeatAt: new Date(at).toISOString() };
|
|
172
|
+
try {
|
|
173
|
+
writeFileSync(path, JSON.stringify(proof), { encoding: "utf8", mode: 384 });
|
|
174
|
+
} catch {
|
|
175
|
+
}
|
|
176
|
+
},
|
|
110
177
|
stop() {
|
|
111
178
|
if (stopped) return;
|
|
112
179
|
stopped = true;
|
|
@@ -184,9 +251,25 @@ var verifyProof = (proof, options) => new Promise((resolve) => {
|
|
|
184
251
|
});
|
|
185
252
|
});
|
|
186
253
|
|
|
254
|
+
// src/session-idle.ts
|
|
255
|
+
var SESSION_IDLE_TIMEOUT_MS = 10 * 6e4;
|
|
256
|
+
var SESSION_REAP_GRACE_MS = 5 * 6e4;
|
|
257
|
+
var SESSION_REAP_SILENCE_MS = SESSION_IDLE_TIMEOUT_MS + SESSION_REAP_GRACE_MS;
|
|
258
|
+
var idleDeadlinePassed = (idleSince, now, timeoutMs = SESSION_IDLE_TIMEOUT_MS) => idleSince !== null && now - idleSince >= timeoutMs;
|
|
259
|
+
var describeIdleWindow = (timeoutMs) => {
|
|
260
|
+
const minutes = Math.round(timeoutMs / 6e4);
|
|
261
|
+
return minutes >= 1 ? `${minutes} minute${minutes === 1 ? "" : "s"}` : `${Math.round(timeoutMs / 1e3)}s`;
|
|
262
|
+
};
|
|
263
|
+
|
|
187
264
|
export {
|
|
265
|
+
listProcessProofs,
|
|
266
|
+
silentSpawnedProofs,
|
|
188
267
|
startProcessOwnership,
|
|
189
268
|
withSpawnProcessOwnership,
|
|
190
269
|
ownsLiveProcess,
|
|
191
|
-
findLiveProcessBySpawnId
|
|
270
|
+
findLiveProcessBySpawnId,
|
|
271
|
+
SESSION_IDLE_TIMEOUT_MS,
|
|
272
|
+
SESSION_REAP_SILENCE_MS,
|
|
273
|
+
idleDeadlinePassed,
|
|
274
|
+
describeIdleWindow
|
|
192
275
|
};
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import {
|
|
2
|
+
writeJsonAtomicDurable
|
|
3
|
+
} from "./chunk-4LL6LG5X.js";
|
|
4
|
+
|
|
5
|
+
// src/transcript-recipient-pin.ts
|
|
6
|
+
import { existsSync, mkdirSync, readFileSync, rmSync } from "fs";
|
|
7
|
+
import { homedir } from "os";
|
|
8
|
+
import { dirname, join } from "path";
|
|
9
|
+
var pinFile = () => process.env.PUSHARY_RECIPIENT_PIN_FILE?.trim() || join(homedir(), ".pushary", "recipients.json");
|
|
10
|
+
var asPin = (offer) => ({
|
|
11
|
+
phonePublicKey: offer.phonePublicKey,
|
|
12
|
+
providerKeyId: offer.provider?.keyId ?? null,
|
|
13
|
+
providerPublicKey: offer.provider?.publicKey ?? null
|
|
14
|
+
});
|
|
15
|
+
var loadPin = () => {
|
|
16
|
+
const path = pinFile();
|
|
17
|
+
if (!existsSync(path)) return null;
|
|
18
|
+
try {
|
|
19
|
+
const raw = JSON.parse(readFileSync(path, "utf-8"));
|
|
20
|
+
if (typeof raw.phonePublicKey !== "string" || raw.phonePublicKey.length === 0) return null;
|
|
21
|
+
return {
|
|
22
|
+
phonePublicKey: raw.phonePublicKey,
|
|
23
|
+
providerKeyId: typeof raw.providerKeyId === "string" ? raw.providerKeyId : null,
|
|
24
|
+
providerPublicKey: typeof raw.providerPublicKey === "string" ? raw.providerPublicKey : null
|
|
25
|
+
};
|
|
26
|
+
} catch {
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
var savePin = (pin) => {
|
|
31
|
+
const path = pinFile();
|
|
32
|
+
try {
|
|
33
|
+
mkdirSync(dirname(path), { recursive: true, mode: 448 });
|
|
34
|
+
writeJsonAtomicDurable(path, pin, 384);
|
|
35
|
+
return true;
|
|
36
|
+
} catch {
|
|
37
|
+
return false;
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
var clearPin = () => {
|
|
41
|
+
try {
|
|
42
|
+
rmSync(pinFile(), { force: true });
|
|
43
|
+
} catch {
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
var verifyRecipients = (offer, pinned) => {
|
|
47
|
+
if (!pinned) return { kind: "first-use", pin: asPin(offer) };
|
|
48
|
+
const next = asPin(offer);
|
|
49
|
+
const changed = [];
|
|
50
|
+
if (next.phonePublicKey !== pinned.phonePublicKey) changed.push("the phone key this machine seals to");
|
|
51
|
+
if (next.providerKeyId !== pinned.providerKeyId) changed.push("the compliance recovery key id");
|
|
52
|
+
if (next.providerPublicKey !== pinned.providerPublicKey) changed.push("the compliance recovery key");
|
|
53
|
+
return changed.length === 0 ? { kind: "match" } : { kind: "changed", changed };
|
|
54
|
+
};
|
|
55
|
+
var fingerprint = (publicKey) => {
|
|
56
|
+
const hex = Buffer.from(publicKey, "base64").toString("hex").toUpperCase();
|
|
57
|
+
return (hex.slice(0, 8).match(/.{2}/g) ?? []).join(" ");
|
|
58
|
+
};
|
|
59
|
+
var changedRecipientsMessage = (verdict, offer) => [
|
|
60
|
+
"[pushary] Transcript sync stopped: the keys this machine seals transcripts to have changed.",
|
|
61
|
+
...verdict.changed.map((what) => `[pushary] changed: ${what}`),
|
|
62
|
+
`[pushary] now offered: phone ${fingerprint(offer.phonePublicKey)}${offer.provider ? `, recovery ${offer.provider.keyId.slice(0, 8)}` : ", no recovery key"}`,
|
|
63
|
+
"[pushary] If you reinstalled the app or rotated a key, this is expected. Compare the",
|
|
64
|
+
"[pushary] fingerprint above with the one in the app under Settings, then run:",
|
|
65
|
+
"[pushary] pushary transcripts trust --rotate",
|
|
66
|
+
"[pushary] Until then nothing is uploaded, so no turn is sealed to a key you have not seen."
|
|
67
|
+
].join("\n");
|
|
68
|
+
|
|
69
|
+
export {
|
|
70
|
+
loadPin,
|
|
71
|
+
savePin,
|
|
72
|
+
clearPin,
|
|
73
|
+
verifyRecipients,
|
|
74
|
+
fingerprint,
|
|
75
|
+
changedRecipientsMessage
|
|
76
|
+
};
|
package/dist/src/index.d.ts
CHANGED
|
@@ -39,6 +39,7 @@ interface AgentEvent {
|
|
|
39
39
|
machineId?: string;
|
|
40
40
|
repoKey?: string;
|
|
41
41
|
sessionId?: string;
|
|
42
|
+
parentSessionId?: string;
|
|
42
43
|
error?: string;
|
|
43
44
|
taskTitle?: string;
|
|
44
45
|
decisionSource?: DecisionSource;
|
|
@@ -80,6 +81,7 @@ declare const handlePostToolUse: (input: {
|
|
|
80
81
|
cwd?: string;
|
|
81
82
|
session_id?: string;
|
|
82
83
|
transcript_path?: string;
|
|
84
|
+
tool_use_id?: string;
|
|
83
85
|
}, agent?: AgentIdentity) => Promise<string | undefined>;
|
|
84
86
|
interface StopHookOutput {
|
|
85
87
|
readonly decision: 'block';
|
|
@@ -88,6 +90,7 @@ interface StopHookOutput {
|
|
|
88
90
|
declare const handleStop: (input: {
|
|
89
91
|
cwd?: string;
|
|
90
92
|
session_id?: string;
|
|
93
|
+
agent_id?: string;
|
|
91
94
|
stop_hook_active?: boolean;
|
|
92
95
|
transcript_path?: string;
|
|
93
96
|
last_assistant_message?: string;
|
package/dist/src/index.js
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import {
|
|
2
2
|
handlePreToolUse
|
|
3
|
-
} from "../chunk-
|
|
4
|
-
import "../chunk-
|
|
3
|
+
} from "../chunk-B7UZHEBL.js";
|
|
4
|
+
import "../chunk-2LAHXHBR.js";
|
|
5
5
|
import {
|
|
6
6
|
fetchAccountPublicKey
|
|
7
7
|
} from "../chunk-QZCFVWA7.js";
|
|
8
|
-
import "../chunk-
|
|
9
|
-
import "../chunk-
|
|
8
|
+
import "../chunk-I463AEG3.js";
|
|
9
|
+
import "../chunk-VHWCVD42.js";
|
|
10
10
|
import "../chunk-YHG74UFF.js";
|
|
11
11
|
import {
|
|
12
12
|
fetchModeOverride,
|
|
@@ -16,20 +16,20 @@ import {
|
|
|
16
16
|
handlePostToolUse,
|
|
17
17
|
handleStop,
|
|
18
18
|
reportEvent
|
|
19
|
-
} from "../chunk-
|
|
19
|
+
} from "../chunk-TDZFPMSZ.js";
|
|
20
20
|
import {
|
|
21
21
|
askUser,
|
|
22
22
|
cancelQuestion,
|
|
23
23
|
waitForAnswer
|
|
24
|
-
} from "../chunk-
|
|
25
|
-
import "../chunk-
|
|
26
|
-
import "../chunk-
|
|
27
|
-
import "../chunk-
|
|
24
|
+
} from "../chunk-FXEVRLHC.js";
|
|
25
|
+
import "../chunk-L4E2BIJ2.js";
|
|
26
|
+
import "../chunk-54ELC7D7.js";
|
|
27
|
+
import "../chunk-KUZWXPUS.js";
|
|
28
28
|
import "../chunk-BSZYIAZL.js";
|
|
29
29
|
import "../chunk-SAF6HGAA.js";
|
|
30
30
|
import "../chunk-7QLSKOSU.js";
|
|
31
31
|
import "../chunk-KZERVKTD.js";
|
|
32
|
-
import "../chunk-
|
|
32
|
+
import "../chunk-JW4KO5AF.js";
|
|
33
33
|
import "../chunk-4LL6LG5X.js";
|
|
34
34
|
import {
|
|
35
35
|
getApiKey,
|
|
@@ -37,7 +37,7 @@ import {
|
|
|
37
37
|
} from "../chunk-2UMNXADU.js";
|
|
38
38
|
import {
|
|
39
39
|
resolvePolicy
|
|
40
|
-
} from "../chunk-
|
|
40
|
+
} from "../chunk-VNMKIJZB.js";
|
|
41
41
|
export {
|
|
42
42
|
askUser,
|
|
43
43
|
cancelQuestion,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pushary/agent-hooks",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.90.0",
|
|
4
4
|
"description": "Permission hooks for AI coding agents: route tool approvals through Pushary push notifications",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"pushary",
|
|
@@ -72,7 +72,8 @@
|
|
|
72
72
|
"pushary-upgrade": "./dist/bin/pushary-upgrade.js",
|
|
73
73
|
"pushary-bell": "./dist/bin/pushary-bell.js",
|
|
74
74
|
"pushary-bell-hook": "./dist/bin/pushary-bell-hook.js",
|
|
75
|
-
"pushary-disconnect": "./dist/bin/pushary-disconnect.js"
|
|
75
|
+
"pushary-disconnect": "./dist/bin/pushary-disconnect.js",
|
|
76
|
+
"pushary-transcripts": "./dist/bin/pushary-transcripts.js"
|
|
76
77
|
},
|
|
77
78
|
"files": [
|
|
78
79
|
"dist",
|
package/dist/chunk-NESKLV6R.js
DELETED
|
@@ -1,72 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
hasGeminiHooks
|
|
3
|
-
} from "./chunk-6V7ZDH3A.js";
|
|
4
|
-
import {
|
|
5
|
-
hasCodexHooks
|
|
6
|
-
} from "./chunk-OVEGEQSB.js";
|
|
7
|
-
import {
|
|
8
|
-
HOOK_LOCATOR_BINARY
|
|
9
|
-
} from "./chunk-3MCEES6Z.js";
|
|
10
|
-
|
|
11
|
-
// src/diagnostics/wiring.ts
|
|
12
|
-
var record = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
13
|
-
var CLAUDE_HOOK_BINARY = /pushary-[a-z-]*hook/;
|
|
14
|
-
var EXACT_HOOK_BINARY = /^pushary-[a-z-]*(hook|gate)$/;
|
|
15
|
-
var pusharyHookExecutable = (command) => {
|
|
16
|
-
const tokens = command.match(/"[^"]*"|'[^']*'|\S+/g) ?? [];
|
|
17
|
-
for (const raw of tokens.reverse()) {
|
|
18
|
-
const token = raw[0] === '"' && raw.at(-1) === '"' || raw[0] === "'" && raw.at(-1) === "'" ? raw.slice(1, -1) : raw;
|
|
19
|
-
const name = token.split(/[/\\]/).pop()?.replace(/\.(cmd|exe|mjs)$/i, "") ?? "";
|
|
20
|
-
if (name === HOOK_LOCATOR_BINARY || EXACT_HOOK_BINARY.test(name)) return token;
|
|
21
|
-
}
|
|
22
|
-
return null;
|
|
23
|
-
};
|
|
24
|
-
var claudeWired = ({ claudeJson, settings, skillExists }) => {
|
|
25
|
-
if (record(record(claudeJson)?.mcpServers)?.pushary) return true;
|
|
26
|
-
const s = record(settings);
|
|
27
|
-
if (s) {
|
|
28
|
-
const hooks = JSON.stringify(s.hooks ?? {});
|
|
29
|
-
if (CLAUDE_HOOK_BINARY.test(hooks) || hooks.includes(HOOK_LOCATOR_BINARY)) return true;
|
|
30
|
-
const allow = record(s.permissions)?.allow;
|
|
31
|
-
if (Array.isArray(allow) && allow.some((rule) => String(rule).includes("mcp__pushary__"))) return true;
|
|
32
|
-
}
|
|
33
|
-
return skillExists;
|
|
34
|
-
};
|
|
35
|
-
var codexWired = ({ config, hooks, skillExists }) => {
|
|
36
|
-
if (record(record(config)?.mcp_servers)?.pushary) return true;
|
|
37
|
-
const h = record(hooks);
|
|
38
|
-
if (h && hasCodexHooks(h)) return true;
|
|
39
|
-
return skillExists;
|
|
40
|
-
};
|
|
41
|
-
var geminiWired = ({ settings }) => {
|
|
42
|
-
const s = record(settings);
|
|
43
|
-
if (!s) return false;
|
|
44
|
-
if (record(s.mcpServers)?.pushary) return true;
|
|
45
|
-
return hasGeminiHooks(s);
|
|
46
|
-
};
|
|
47
|
-
var cursorWired = ({ userHooks, mcp, pluginDirExists }) => {
|
|
48
|
-
if (pluginDirExists) return true;
|
|
49
|
-
if (record(record(mcp)?.mcpServers)?.pushary) return true;
|
|
50
|
-
const hooks = record(record(userHooks)?.hooks);
|
|
51
|
-
if (!hooks) return false;
|
|
52
|
-
return Object.values(hooks).some((entries) => Array.isArray(entries) && entries.some((entry) => pusharyHookExecutable(String(record(entry)?.command ?? "")) !== null));
|
|
53
|
-
};
|
|
54
|
-
var cursorMcpAuthorization = (pluginMcp, userMcp) => {
|
|
55
|
-
const pluginServers = record(record(pluginMcp)?.mcpServers);
|
|
56
|
-
const usePlugin = pluginServers !== null && Object.hasOwn(pluginServers, "pushary");
|
|
57
|
-
const servers = usePlugin ? pluginServers : record(record(userMcp)?.mcpServers);
|
|
58
|
-
const headers = record(record(servers?.pushary)?.headers);
|
|
59
|
-
return {
|
|
60
|
-
authorization: typeof headers?.Authorization === "string" ? headers.Authorization : null,
|
|
61
|
-
source: usePlugin ? "plugin mcp.json" : "~/.cursor/mcp.json"
|
|
62
|
-
};
|
|
63
|
-
};
|
|
64
|
-
|
|
65
|
-
export {
|
|
66
|
-
pusharyHookExecutable,
|
|
67
|
-
claudeWired,
|
|
68
|
-
codexWired,
|
|
69
|
-
geminiWired,
|
|
70
|
-
cursorWired,
|
|
71
|
-
cursorMcpAuthorization
|
|
72
|
-
};
|
package/dist/chunk-WDVQ6O4C.js
DELETED
|
@@ -1,84 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
redactSecrets
|
|
3
|
-
} from "./chunk-3MCEES6Z.js";
|
|
4
|
-
|
|
5
|
-
// src/crypto.ts
|
|
6
|
-
import nacl from "tweetnacl";
|
|
7
|
-
var EPH_PUB_LEN = nacl.box.publicKeyLength;
|
|
8
|
-
var BOX_NONCE_LEN = nacl.box.nonceLength;
|
|
9
|
-
var SECRETBOX_NONCE_LEN = nacl.secretbox.nonceLength;
|
|
10
|
-
var SESSION_KEY_LEN = nacl.secretbox.keyLength;
|
|
11
|
-
var toBase64 = (bytes) => Buffer.from(bytes).toString("base64");
|
|
12
|
-
var fromBase64 = (value) => new Uint8Array(Buffer.from(value, "base64"));
|
|
13
|
-
var utf8Encode = (text) => new Uint8Array(Buffer.from(text, "utf-8"));
|
|
14
|
-
var decodePublicKey = (value) => fromBase64(value);
|
|
15
|
-
var generateSessionKey = () => nacl.randomBytes(SESSION_KEY_LEN);
|
|
16
|
-
var wrapSessionKey = (sessionKey, accountPublicKey) => {
|
|
17
|
-
const ephemeral = nacl.box.keyPair();
|
|
18
|
-
const nonce = nacl.randomBytes(BOX_NONCE_LEN);
|
|
19
|
-
const box = nacl.box(sessionKey, nonce, accountPublicKey, ephemeral.secretKey);
|
|
20
|
-
const bundle = new Uint8Array(EPH_PUB_LEN + BOX_NONCE_LEN + box.length);
|
|
21
|
-
bundle.set(ephemeral.publicKey, 0);
|
|
22
|
-
bundle.set(nonce, EPH_PUB_LEN);
|
|
23
|
-
bundle.set(box, EPH_PUB_LEN + BOX_NONCE_LEN);
|
|
24
|
-
return toBase64(bundle);
|
|
25
|
-
};
|
|
26
|
-
var encryptRecord = (plaintext, sessionKey) => {
|
|
27
|
-
const nonce = nacl.randomBytes(SECRETBOX_NONCE_LEN);
|
|
28
|
-
const box = nacl.secretbox(utf8Encode(plaintext), nonce, sessionKey);
|
|
29
|
-
const bundle = new Uint8Array(SECRETBOX_NONCE_LEN + box.length);
|
|
30
|
-
bundle.set(nonce, 0);
|
|
31
|
-
bundle.set(box, SECRETBOX_NONCE_LEN);
|
|
32
|
-
return toBase64(bundle);
|
|
33
|
-
};
|
|
34
|
-
|
|
35
|
-
// src/transcript-capture.ts
|
|
36
|
-
import { createHash } from "crypto";
|
|
37
|
-
var INTERNAL_TYPES = /* @__PURE__ */ new Set([
|
|
38
|
-
"file-history-snapshot",
|
|
39
|
-
"change",
|
|
40
|
-
"queue-operation",
|
|
41
|
-
"summary"
|
|
42
|
-
]);
|
|
43
|
-
var redactDeep = (value) => {
|
|
44
|
-
if (typeof value === "string") return redactSecrets(value);
|
|
45
|
-
if (Array.isArray(value)) return value.map(redactDeep);
|
|
46
|
-
if (value && typeof value === "object") {
|
|
47
|
-
const out = {};
|
|
48
|
-
for (const [key, inner] of Object.entries(value)) {
|
|
49
|
-
out[key] = redactDeep(inner);
|
|
50
|
-
}
|
|
51
|
-
return out;
|
|
52
|
-
}
|
|
53
|
-
return value;
|
|
54
|
-
};
|
|
55
|
-
var localIdFor = (parsed, rawLine) => typeof parsed.uuid === "string" && parsed.uuid.length > 0 ? parsed.uuid : createHash("sha256").update(rawLine).digest("hex").slice(0, 32);
|
|
56
|
-
var buildTranscriptRecords = (lines, sessionKey, seen = /* @__PURE__ */ new Set()) => {
|
|
57
|
-
const records = [];
|
|
58
|
-
const nextSeen = new Set(seen);
|
|
59
|
-
for (const line of lines) {
|
|
60
|
-
const trimmed = line.trim();
|
|
61
|
-
if (!trimmed) continue;
|
|
62
|
-
let parsed;
|
|
63
|
-
try {
|
|
64
|
-
parsed = JSON.parse(trimmed);
|
|
65
|
-
} catch {
|
|
66
|
-
continue;
|
|
67
|
-
}
|
|
68
|
-
const type = typeof parsed.type === "string" ? parsed.type : "";
|
|
69
|
-
if (INTERNAL_TYPES.has(type)) continue;
|
|
70
|
-
const localId = localIdFor(parsed, trimmed);
|
|
71
|
-
if (nextSeen.has(localId)) continue;
|
|
72
|
-
nextSeen.add(localId);
|
|
73
|
-
const content = encryptRecord(JSON.stringify(redactDeep(parsed)), sessionKey);
|
|
74
|
-
records.push({ localId, content });
|
|
75
|
-
}
|
|
76
|
-
return { records, seen: nextSeen };
|
|
77
|
-
};
|
|
78
|
-
|
|
79
|
-
export {
|
|
80
|
-
decodePublicKey,
|
|
81
|
-
generateSessionKey,
|
|
82
|
-
wrapSessionKey,
|
|
83
|
-
buildTranscriptRecords
|
|
84
|
-
};
|