@pushary/agent-hooks 0.68.0 → 0.70.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 +138 -0
- package/README.md +3 -2
- package/data/cursor-plugin/scripts/pushary-gate.mjs +70 -15
- package/data/vscode-plugin/scripts/pushary-gate.mjs +59 -11
- package/dist/bin/pushary-claude.js +21 -11
- package/dist/bin/pushary-clean.js +60 -22
- package/dist/bin/pushary-codex-hook.js +38 -23
- package/dist/bin/pushary-codex.js +1 -1
- package/dist/bin/pushary-connect.js +6 -4
- package/dist/bin/pushary-daemon.js +3 -3
- package/dist/bin/pushary-doctor.js +120 -28
- package/dist/bin/pushary-gemini-hook.js +32 -16
- package/dist/bin/pushary-hook.js +5 -5
- package/dist/bin/pushary-login.js +2 -2
- package/dist/bin/pushary-logout.js +2 -2
- package/dist/bin/pushary-mode.js +2 -2
- package/dist/bin/pushary-notification-hook.js +1 -1
- package/dist/bin/pushary-permission-denied-hook.js +3 -3
- package/dist/bin/pushary-permission-hook.js +3 -3
- package/dist/bin/pushary-post-hook.js +1 -1
- package/dist/bin/pushary-prompt-hook.js +1 -1
- package/dist/bin/pushary-session-end-hook.js +1 -1
- package/dist/bin/pushary-session-start-hook.js +1 -1
- package/dist/bin/pushary-setup.js +90 -13
- package/dist/bin/pushary-stats.js +2 -2
- package/dist/bin/pushary-status.js +4 -4
- package/dist/bin/pushary-stop-hook.js +1 -1
- package/dist/bin/pushary-stopfailure-hook.js +1 -1
- package/dist/bin/pushary-suggestions.js +2 -2
- package/dist/bin/pushary-upgrade.js +2 -2
- package/dist/bin/pushary-wait.js +2 -2
- package/dist/bin/pushary.js +1 -1
- package/dist/{chunk-JGH37QRB.js → chunk-7OYGFJYZ.js} +1 -1
- package/dist/chunk-B7R4YSAB.js +38 -0
- package/dist/{chunk-ATP6UQPH.js → chunk-BBK3TTU2.js} +1 -1
- package/dist/{chunk-XOUYM27W.js → chunk-FZIQSDTQ.js} +207 -113
- package/dist/chunk-GD275GEX.js +66 -0
- package/dist/{chunk-SONXGCZY.js → chunk-HU43EZ5Q.js} +45 -27
- package/dist/{chunk-KB4ODLGB.js → chunk-I4BYZIX4.js} +2 -0
- package/dist/{chunk-YMFZUXL5.js → chunk-MGFZXUUR.js} +57 -4
- package/dist/{chunk-YZT2CBC3.js → chunk-NQTRA7H5.js} +1 -1
- package/dist/src/index.d.ts +1 -0
- package/dist/src/index.js +3 -3
- package/package.json +1 -1
- package/dist/chunk-YHG74UFF.js +0 -12
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
|
+
KILL_REASON,
|
|
3
4
|
denyReasonFrom,
|
|
4
|
-
isDeferAnswer
|
|
5
|
-
|
|
5
|
+
isDeferAnswer,
|
|
6
|
+
resolveGate
|
|
7
|
+
} from "../chunk-GD275GEX.js";
|
|
6
8
|
import {
|
|
7
9
|
CODEX_AGENT,
|
|
8
10
|
DEFAULT_SESSION,
|
|
@@ -24,14 +26,16 @@ import {
|
|
|
24
26
|
permissionTimeoutDecision,
|
|
25
27
|
preToolUseTimeoutDecision,
|
|
26
28
|
readLastPrompt,
|
|
29
|
+
repoKeyFor,
|
|
27
30
|
reportEvent,
|
|
28
|
-
resolvePolicy,
|
|
29
31
|
savePendingQuestion,
|
|
32
|
+
scopePathFor,
|
|
30
33
|
sendNotification,
|
|
31
34
|
toCodexWire,
|
|
32
35
|
toPolicyLookup,
|
|
36
|
+
toPolicyLookups,
|
|
33
37
|
waitForAnswer
|
|
34
|
-
} from "../chunk-
|
|
38
|
+
} from "../chunk-FZIQSDTQ.js";
|
|
35
39
|
import {
|
|
36
40
|
isGatingMoment,
|
|
37
41
|
recordKeylessMoment
|
|
@@ -57,7 +61,6 @@ import "../chunk-KZERVKTD.js";
|
|
|
57
61
|
import { basename, join } from "path";
|
|
58
62
|
import { tmpdir, userInfo } from "os";
|
|
59
63
|
import { existsSync, mkdirSync, statSync, unlinkSync, writeFileSync } from "fs";
|
|
60
|
-
var KILL_REASON = "Stopped by user: this agent was halted from Pushary";
|
|
61
64
|
var START_MS = Date.now();
|
|
62
65
|
var APPROVAL_TTL_MS = 10 * 60 * 1e3;
|
|
63
66
|
var sanitizeId = (value) => value.replace(/[^A-Za-z0-9_-]/g, "_").slice(0, 128);
|
|
@@ -167,18 +170,33 @@ var notifyApprovalNeeded = async (apiKey, input, lookup) => {
|
|
|
167
170
|
} catch {
|
|
168
171
|
}
|
|
169
172
|
};
|
|
173
|
+
var codexGate = async (apiKey, input) => {
|
|
174
|
+
const modeState = await fetchModeState(apiKey, input.session_id);
|
|
175
|
+
const lookup = toPolicyLookup(input.tool_name ?? "", input.tool_input ?? {}, input.cwd);
|
|
176
|
+
const lookups = toPolicyLookups(input.tool_name ?? "", input.tool_input ?? {}, input.cwd);
|
|
177
|
+
const config = await getPolicy(apiKey, modeState.policyVersion, "codex");
|
|
178
|
+
const verdict = resolveGate({
|
|
179
|
+
modeState,
|
|
180
|
+
config,
|
|
181
|
+
toolName: lookups.tool,
|
|
182
|
+
toolInputs: lookups.inputs,
|
|
183
|
+
// Skipped entirely without a contract, which is the state of nearly every
|
|
184
|
+
// session. A multi-file patch contributes one path per file.
|
|
185
|
+
scopePaths: modeState.scope ? lookups.inputs.map((entry) => scopePathFor(lookups.tool, entry, input.cwd)).filter((path) => typeof path === "string") : [],
|
|
186
|
+
cwd: input.cwd,
|
|
187
|
+
repoKey: repoKeyFor(input.cwd)
|
|
188
|
+
});
|
|
189
|
+
return { verdict, lookup };
|
|
190
|
+
};
|
|
170
191
|
var decidePermissionRequest = async (input) => {
|
|
171
192
|
try {
|
|
172
193
|
const apiKey = getApiKey();
|
|
173
|
-
const
|
|
174
|
-
if (
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
if (toolPolicy.timeoutSeconds === 0 && toolPolicy.timeoutAction === "deny") {
|
|
180
|
-
return codexDeny(`Denied by policy for ${toolPolicy.tool}`);
|
|
181
|
-
}
|
|
194
|
+
const { verdict, lookup } = await codexGate(apiKey, input);
|
|
195
|
+
if (verdict.kind === "kill") return codexDeny(KILL_REASON);
|
|
196
|
+
if (verdict.kind === "allow") return codexAllow();
|
|
197
|
+
if (verdict.kind === "deny") return codexDeny(verdict.reason);
|
|
198
|
+
if (verdict.kind === "defer") return codexPass();
|
|
199
|
+
const toolPolicy = verdict.policy;
|
|
182
200
|
if (toolPolicy.mode === "terminal_only") return codexPass();
|
|
183
201
|
if (toolPolicy.mode === "notify_only") {
|
|
184
202
|
if (claimNotify(input.tool_use_id)) await notifyApprovalNeeded(apiKey, input, lookup);
|
|
@@ -229,15 +247,12 @@ var decidePreToolUse = async (input) => {
|
|
|
229
247
|
return codexPass();
|
|
230
248
|
}
|
|
231
249
|
try {
|
|
232
|
-
const
|
|
233
|
-
if (
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
if (toolPolicy.timeoutSeconds === 0 && toolPolicy.timeoutAction === "deny") {
|
|
239
|
-
return codexDeny(`Denied by policy for ${toolPolicy.tool}`);
|
|
240
|
-
}
|
|
250
|
+
const { verdict, lookup } = await codexGate(apiKey, input);
|
|
251
|
+
if (verdict.kind === "kill") return codexDeny(KILL_REASON);
|
|
252
|
+
if (verdict.kind === "allow") return codexPass();
|
|
253
|
+
if (verdict.kind === "deny") return codexDeny(verdict.reason);
|
|
254
|
+
if (verdict.kind === "defer") return codexPass();
|
|
255
|
+
const toolPolicy = verdict.policy;
|
|
241
256
|
if (toolPolicy.mode === "push_only") {
|
|
242
257
|
if (consumeApproval(input.tool_use_id)) return codexPass();
|
|
243
258
|
let pushed;
|
|
@@ -6,7 +6,7 @@ import {
|
|
|
6
6
|
confirmAppConnection,
|
|
7
7
|
connectDevice,
|
|
8
8
|
printConnectInstructions
|
|
9
|
-
} from "../chunk-
|
|
9
|
+
} from "../chunk-MGFZXUUR.js";
|
|
10
10
|
import "../chunk-3EGEA4KH.js";
|
|
11
11
|
import {
|
|
12
12
|
readKeySource
|
|
@@ -24,8 +24,8 @@ import {
|
|
|
24
24
|
} from "../chunk-GMXKITVA.js";
|
|
25
25
|
import {
|
|
26
26
|
exitOnHelpFlag
|
|
27
|
-
} from "../chunk-
|
|
28
|
-
import "../chunk-
|
|
27
|
+
} from "../chunk-BBK3TTU2.js";
|
|
28
|
+
import "../chunk-7OYGFJYZ.js";
|
|
29
29
|
import "../chunk-BSZYIAZL.js";
|
|
30
30
|
import "../chunk-SAF6HGAA.js";
|
|
31
31
|
import {
|
|
@@ -84,7 +84,9 @@ var main = async () => {
|
|
|
84
84
|
console.log();
|
|
85
85
|
process.exit(EXIT.OK);
|
|
86
86
|
}
|
|
87
|
-
console.log(` ${dim("No device of yours can answer yet.
|
|
87
|
+
console.log(` ${dim("No device of yours can answer yet.")}`);
|
|
88
|
+
for (const line of result.repair ?? []) console.log(` ${dim(line)}`);
|
|
89
|
+
console.log(` ${dim("Then run")} ${cyan("pushary connect")} ${dim("again.")}`);
|
|
88
90
|
console.log();
|
|
89
91
|
process.exit(EXIT.NO_DEVICE);
|
|
90
92
|
};
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import {
|
|
3
3
|
HEARTBEAT_INTERVAL_MS,
|
|
4
4
|
sendHeartbeat
|
|
5
|
-
} from "../chunk-
|
|
5
|
+
} from "../chunk-NQTRA7H5.js";
|
|
6
6
|
import {
|
|
7
7
|
spawnClaude
|
|
8
8
|
} from "../chunk-XHKBHWLX.js";
|
|
@@ -11,8 +11,8 @@ import {
|
|
|
11
11
|
} from "../chunk-MUEW424A.js";
|
|
12
12
|
import {
|
|
13
13
|
exitOnHelpFlag
|
|
14
|
-
} from "../chunk-
|
|
15
|
-
import "../chunk-
|
|
14
|
+
} from "../chunk-BBK3TTU2.js";
|
|
15
|
+
import "../chunk-7OYGFJYZ.js";
|
|
16
16
|
import {
|
|
17
17
|
getMachineId
|
|
18
18
|
} from "../chunk-RN3NOEJF.js";
|
|
@@ -3,11 +3,19 @@ import {
|
|
|
3
3
|
describeWaitLadder
|
|
4
4
|
} from "../chunk-IIENZE2J.js";
|
|
5
5
|
import {
|
|
6
|
+
claudeWired,
|
|
7
|
+
codexWired,
|
|
8
|
+
geminiWired
|
|
9
|
+
} from "../chunk-B7R4YSAB.js";
|
|
10
|
+
import {
|
|
11
|
+
agentProbes,
|
|
12
|
+
detectAgent,
|
|
6
13
|
detectAllAgents,
|
|
7
14
|
hasInstructionBlock,
|
|
15
|
+
isDetected,
|
|
8
16
|
isPluginRegistered,
|
|
9
17
|
vscodeSettingsTargets
|
|
10
|
-
} from "../chunk-
|
|
18
|
+
} from "../chunk-I4BYZIX4.js";
|
|
11
19
|
import {
|
|
12
20
|
GEMINI_HOOK_BINARY,
|
|
13
21
|
hasGeminiHooks,
|
|
@@ -27,6 +35,7 @@ import {
|
|
|
27
35
|
geminiSettings,
|
|
28
36
|
hasCodexHooks,
|
|
29
37
|
missingCodexHookEvents,
|
|
38
|
+
pusharyConfigFile,
|
|
30
39
|
readCodexMcpAuth,
|
|
31
40
|
untrustedCodexHookEvents,
|
|
32
41
|
vscodePluginDir
|
|
@@ -35,7 +44,7 @@ import {
|
|
|
35
44
|
describeReach,
|
|
36
45
|
fetchChannels,
|
|
37
46
|
reachVerdict
|
|
38
|
-
} from "../chunk-
|
|
47
|
+
} from "../chunk-MGFZXUUR.js";
|
|
39
48
|
import "../chunk-3EGEA4KH.js";
|
|
40
49
|
import {
|
|
41
50
|
keyPrefix,
|
|
@@ -58,10 +67,10 @@ import {
|
|
|
58
67
|
} from "../chunk-MUEW424A.js";
|
|
59
68
|
import {
|
|
60
69
|
exitOnHelpFlag
|
|
61
|
-
} from "../chunk-
|
|
70
|
+
} from "../chunk-BBK3TTU2.js";
|
|
62
71
|
import {
|
|
63
72
|
getPackageVersion
|
|
64
|
-
} from "../chunk-
|
|
73
|
+
} from "../chunk-7OYGFJYZ.js";
|
|
65
74
|
import {
|
|
66
75
|
readLedgerSummary
|
|
67
76
|
} from "../chunk-WLGEY4UX.js";
|
|
@@ -135,6 +144,16 @@ var redactText = (text) => text.replace(KEY_PATTERN, (match) => `${match.split("
|
|
|
135
144
|
var redact = (bundle) => JSON.parse(redactText(JSON.stringify(bundle)));
|
|
136
145
|
var bundleFileName = (bundle) => `pushary-diagnostics-${bundle.generatedAt.replace(/[:.]/g, "-")}.json`;
|
|
137
146
|
|
|
147
|
+
// src/diagnostics/exit-code.ts
|
|
148
|
+
var resolveDoctorExit = ({ keyExit, failures }) => {
|
|
149
|
+
if (keyExit !== null) return keyExit;
|
|
150
|
+
if (failures.length === 0) return EXIT.OK;
|
|
151
|
+
const every = (kind) => failures.every((f) => f.kind === kind);
|
|
152
|
+
if (every("unreachable")) return EXIT.UNREACHABLE;
|
|
153
|
+
if (every("reachability")) return EXIT.NO_DEVICE;
|
|
154
|
+
return EXIT.PROBLEMS_FOUND;
|
|
155
|
+
};
|
|
156
|
+
|
|
138
157
|
// bin/pushary-doctor.ts
|
|
139
158
|
exitOnHelpFlag("doctor");
|
|
140
159
|
var parseDoctorOptions = (argv) => {
|
|
@@ -215,12 +234,21 @@ var extractHookTimeout = (entries, needle) => {
|
|
|
215
234
|
return null;
|
|
216
235
|
};
|
|
217
236
|
var results = [];
|
|
218
|
-
var check = (passed, label, detail) => {
|
|
219
|
-
results.push({ passed, label, detail });
|
|
237
|
+
var check = (passed, label, detail, kind = "config") => {
|
|
238
|
+
results.push({ passed, label, detail, kind });
|
|
220
239
|
const icon = passed ? pass : fail;
|
|
221
240
|
const suffix = detail ? ` ${dim(`(${detail})`)}` : "";
|
|
222
241
|
console.log(` ${icon} ${label}${suffix}`);
|
|
223
242
|
};
|
|
243
|
+
var probes = agentProbes();
|
|
244
|
+
var installed = (agent) => isDetected(detectAgent(probes[agent]));
|
|
245
|
+
var agentsChecked = [];
|
|
246
|
+
var skipped = (label, why) => {
|
|
247
|
+
console.log(` ${dim("\u2013")} ${label} ${dim(`(${why})`)}`);
|
|
248
|
+
};
|
|
249
|
+
var checking = (label) => {
|
|
250
|
+
agentsChecked.push(label);
|
|
251
|
+
};
|
|
224
252
|
var main = async () => {
|
|
225
253
|
console.log();
|
|
226
254
|
console.log(` ${bold("Pushary Doctor")}`);
|
|
@@ -247,8 +275,12 @@ var main = async () => {
|
|
|
247
275
|
if (keySource === "config-file") {
|
|
248
276
|
console.log(` ${pass} API key in ${dim(source.origin ?? "the config file")} ${dim(`(${maskKey(apiKey)})`)}`);
|
|
249
277
|
} else if (keySource === "profile") {
|
|
250
|
-
|
|
251
|
-
|
|
278
|
+
check(
|
|
279
|
+
false,
|
|
280
|
+
"API key readable by hooks",
|
|
281
|
+
`found only in a shell profile (${maskKey(apiKey)}), which hooks do not read \u2014 re-run setup, or open a new terminal`
|
|
282
|
+
);
|
|
283
|
+
console.log(` ${dim("Hooks read $PUSHARY_API_KEY, then")} ${dim(pusharyConfigFile())}`);
|
|
252
284
|
} else {
|
|
253
285
|
check(!!apiKey, "API key in environment", apiKey ? maskKey(apiKey) : "PUSHARY_API_KEY not set");
|
|
254
286
|
}
|
|
@@ -282,19 +314,34 @@ var main = async () => {
|
|
|
282
314
|
check(false, "Key format", "expected pk_xxx.xxx");
|
|
283
315
|
break;
|
|
284
316
|
case "unreachable":
|
|
285
|
-
|
|
317
|
+
check(false, "Key verified with pushary.com", `could not reach pushary.com (${keyVerdict.detail})`, "unreachable");
|
|
286
318
|
break;
|
|
287
319
|
}
|
|
288
320
|
}
|
|
289
321
|
const CLAUDE_JSON = claudeJson();
|
|
290
|
-
const
|
|
322
|
+
const claudeInPlay = claudeWired({
|
|
323
|
+
claudeJson: readJson(CLAUDE_JSON),
|
|
324
|
+
settings: readJson(CLAUDE_SETTINGS),
|
|
325
|
+
skillExists: existsSync(SKILL_PATH)
|
|
326
|
+
});
|
|
327
|
+
const claudeJson2 = claudeInPlay ? readJson(CLAUDE_JSON) : null;
|
|
291
328
|
const mcpServers = claudeJson2?.mcpServers ?? {};
|
|
292
329
|
const pusharyServer = mcpServers?.pushary;
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
330
|
+
if (!claudeInPlay) {
|
|
331
|
+
skipped("Claude Code", installed("claude_code") ? "installed, but Pushary is not set up for it" : "not installed");
|
|
332
|
+
} else {
|
|
333
|
+
checking("Claude Code");
|
|
334
|
+
check(!!pusharyServer, "Claude Code: MCP server configured");
|
|
335
|
+
if (pusharyServer) {
|
|
336
|
+
check(pusharyServer.type === "http", "Claude Code: MCP server type", pusharyServer.type ? String(pusharyServer.type) : 'missing \u2014 add type: "http"');
|
|
337
|
+
const claudeAuth = pusharyServer.headers?.Authorization;
|
|
338
|
+
if (typeof claudeAuth === "string" && apiKey) {
|
|
339
|
+
const matches = claudeAuth === `Bearer ${apiKey}`;
|
|
340
|
+
check(matches, "Claude Code: MCP key matches active key", matches ? void 0 : "the key in ~/.claude.json is not the key hooks use (rotated?) \u2014 re-run setup");
|
|
341
|
+
}
|
|
342
|
+
}
|
|
296
343
|
}
|
|
297
|
-
const settings = readJson(CLAUDE_SETTINGS);
|
|
344
|
+
const settings = claudeInPlay ? readJson(CLAUDE_SETTINGS) : null;
|
|
298
345
|
if (settings) {
|
|
299
346
|
const hooks = settings.hooks;
|
|
300
347
|
const CLAUDE_HOOK_CHECKS = [
|
|
@@ -325,19 +372,24 @@ var main = async () => {
|
|
|
325
372
|
if (hasLegacyPerms) {
|
|
326
373
|
console.log(` ${warn} Legacy individual permissions detected ${dim("(run pushary clean, then setup again)")}`);
|
|
327
374
|
}
|
|
328
|
-
} else {
|
|
375
|
+
} else if (claudeInPlay) {
|
|
329
376
|
check(false, "Claude Code: settings.json", "not found");
|
|
330
377
|
}
|
|
331
378
|
const codexConfigPath = codexConfigToml();
|
|
332
379
|
const codexHooksPath = codexHooksJson();
|
|
333
380
|
const codexHooksConfig = readJson(codexHooksPath);
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
381
|
+
const codexConfig = existsSync(codexConfigPath) ? readFileSync(codexConfigPath, "utf-8") : "";
|
|
382
|
+
let codexParsed = {};
|
|
383
|
+
try {
|
|
384
|
+
codexParsed = parseTOML(codexConfig);
|
|
385
|
+
} catch {
|
|
386
|
+
}
|
|
387
|
+
if (codexWired({
|
|
388
|
+
config: codexParsed,
|
|
389
|
+
hooks: codexHooksConfig,
|
|
390
|
+
skillExists: existsSync(join(codexSkillDir(), "SKILL.md"))
|
|
391
|
+
})) {
|
|
392
|
+
checking("Codex");
|
|
341
393
|
const codexMcp = codexParsed.mcp_servers?.pushary;
|
|
342
394
|
const codexTransport = !!codexMcp && (typeof codexMcp.url === "string" || typeof codexMcp.command === "string");
|
|
343
395
|
const codexAuth = readCodexMcpAuth(codexParsed);
|
|
@@ -401,10 +453,13 @@ var main = async () => {
|
|
|
401
453
|
const codexAgentsMd2 = codexAgentsMd();
|
|
402
454
|
const hasCodexInstructions = hasInstructionBlock(codexAgentsMd2);
|
|
403
455
|
check(hasCodexInstructions, "Codex: proactive instructions (AGENTS.md)", hasCodexInstructions ? codexAgentsMd2 : "missing \u2014 re-run setup so Codex asks via push, not the terminal");
|
|
456
|
+
} else {
|
|
457
|
+
skipped("Codex", installed("codex") ? "installed, but Pushary is not set up for it" : "not installed");
|
|
404
458
|
}
|
|
405
459
|
const geminiSettingsPath = geminiSettings();
|
|
406
460
|
const geminiSettings2 = readJson(geminiSettingsPath);
|
|
407
|
-
if (geminiSettings2) {
|
|
461
|
+
if (geminiSettings2 && geminiWired({ settings: geminiSettings2 })) {
|
|
462
|
+
checking("Gemini CLI");
|
|
408
463
|
const geminiServers = geminiSettings2.mcpServers ?? {};
|
|
409
464
|
const geminiPushary = geminiServers.pushary;
|
|
410
465
|
check(!!geminiPushary, "Gemini CLI: MCP server configured");
|
|
@@ -443,12 +498,15 @@ var main = async () => {
|
|
|
443
498
|
const geminiMd2 = geminiMd();
|
|
444
499
|
const hasGeminiInstructions = hasInstructionBlock(geminiMd2);
|
|
445
500
|
check(hasGeminiInstructions, "Gemini CLI: proactive instructions (GEMINI.md)", hasGeminiInstructions ? geminiMd2 : "missing \u2014 re-run setup so Gemini asks via push, not the terminal");
|
|
501
|
+
} else {
|
|
502
|
+
skipped("Gemini CLI", installed("gemini_cli") ? "installed, but Pushary is not set up for it" : "not installed");
|
|
446
503
|
}
|
|
447
504
|
const cursorPluginDir2 = cursorPluginDir();
|
|
448
505
|
if (existsSync(cursorPluginDir2)) {
|
|
506
|
+
checking("Cursor");
|
|
449
507
|
const cursorUserHooks2 = cursorUserHooks();
|
|
450
508
|
const userHooks = readJson(cursorUserHooks2);
|
|
451
|
-
const gateEntry = userHooks
|
|
509
|
+
const gateEntry = userHooks?.hooks?.beforeShellExecution?.find(
|
|
452
510
|
(h) => String(h.command ?? "").includes("pushary-gate")
|
|
453
511
|
);
|
|
454
512
|
if (!gateEntry) {
|
|
@@ -465,12 +523,17 @@ var main = async () => {
|
|
|
465
523
|
check(false, "Cursor: API key linked", "no Authorization in plugin mcp.json \u2014 re-run setup");
|
|
466
524
|
} else if (cursorAuth.includes("PUSHARY_API_KEY")) {
|
|
467
525
|
check(false, "Cursor: API key linked", "plugin relies on $PUSHARY_API_KEY; re-run setup to embed the key so the gate works when Cursor is launched from the GUI");
|
|
526
|
+
} else if (apiKey && cursorAuth !== `Bearer ${apiKey}`) {
|
|
527
|
+
check(false, "Cursor: API key matches active key", "the embedded key is not the key hooks use (rotated?) \u2014 re-run setup");
|
|
468
528
|
} else {
|
|
469
529
|
check(true, "Cursor: API key linked", "embedded in plugin mcp.json");
|
|
470
530
|
}
|
|
531
|
+
} else {
|
|
532
|
+
skipped("Cursor", installed("cursor") ? "installed, but Pushary is not set up for it" : "not installed");
|
|
471
533
|
}
|
|
472
534
|
const vscodePluginDir2 = vscodePluginDir();
|
|
473
535
|
if (existsSync(vscodePluginDir2)) {
|
|
536
|
+
checking("VS Code");
|
|
474
537
|
const registeredIn = vscodeSettingsTargets(existsSync).filter(
|
|
475
538
|
(settingsPath) => existsSync(settingsPath) && isPluginRegistered(readFileSync(settingsPath, "utf-8"), vscodePluginDir2)
|
|
476
539
|
);
|
|
@@ -499,11 +562,22 @@ var main = async () => {
|
|
|
499
562
|
check(false, "VS Code: API key linked", "no Authorization in plugin .mcp.json \u2014 re-run setup");
|
|
500
563
|
} else if (vscodeAuth.includes("PUSHARY_API_KEY")) {
|
|
501
564
|
check(false, "VS Code: API key linked", "plugin relies on $PUSHARY_API_KEY; re-run setup to embed the key so the gate works when VS Code is launched from the GUI");
|
|
565
|
+
} else if (apiKey && vscodeAuth !== `Bearer ${apiKey}`) {
|
|
566
|
+
check(false, "VS Code: API key matches active key", "the embedded key is not the key hooks use (rotated?) \u2014 re-run setup");
|
|
502
567
|
} else {
|
|
503
568
|
check(true, "VS Code: API key linked", "embedded in plugin .mcp.json");
|
|
504
569
|
}
|
|
570
|
+
} else {
|
|
571
|
+
skipped("VS Code", installed("vscode") ? "installed, but Pushary is not set up for it" : "not installed");
|
|
572
|
+
}
|
|
573
|
+
check(
|
|
574
|
+
agentsChecked.length > 0,
|
|
575
|
+
"Wired to at least one agent",
|
|
576
|
+
agentsChecked.length > 0 ? agentsChecked.join(", ") : "Pushary is installed but configured for no agent \u2014 run npx @pushary/agent-hooks@latest setup"
|
|
577
|
+
);
|
|
578
|
+
if (claudeInPlay) {
|
|
579
|
+
check(existsSync(SKILL_PATH), "Skill installed", existsSync(SKILL_PATH) ? SKILL_PATH : "not found");
|
|
505
580
|
}
|
|
506
|
-
check(existsSync(SKILL_PATH), "Skill installed", existsSync(SKILL_PATH) ? SKILL_PATH : "not found");
|
|
507
581
|
let globalVersion2 = "";
|
|
508
582
|
try {
|
|
509
583
|
globalVersion2 = execNpm("list -g --no-workspaces @pushary/agent-hooks --depth=0", { timeout: 1e4 }).toString().match(/@pushary\/agent-hooks@([\d.]+)/)?.[1] ?? "";
|
|
@@ -613,11 +687,21 @@ var main = async () => {
|
|
|
613
687
|
console.log(` ${dim("Notification Delivery")}`);
|
|
614
688
|
const probe = await fetchChannels(apiKey);
|
|
615
689
|
if (probe.kind !== "ok") {
|
|
616
|
-
check(
|
|
690
|
+
check(
|
|
691
|
+
false,
|
|
692
|
+
"Phone reachable",
|
|
693
|
+
probe.kind === "unreachable" ? `could not read your devices (${probe.detail})` : probe.kind === "locked" ? "no plan active, so nothing can be delivered" : "the server rejected this key",
|
|
694
|
+
probe.kind === "unreachable" ? "unreachable" : "config"
|
|
695
|
+
);
|
|
617
696
|
} else if (probe.status.precise) {
|
|
618
697
|
const channels = probe.status;
|
|
619
698
|
const verdict = reachVerdict(channels);
|
|
620
|
-
check(
|
|
699
|
+
check(
|
|
700
|
+
verdict.kind === "yours",
|
|
701
|
+
"Phone reachable",
|
|
702
|
+
verdict.kind === "yours" ? describeReach(channels) : verdict.kind === "nobody" ? "nothing connected, run pushary connect or get the app at https://pushary.com/download" : `${describeReach(channels)}; run pushary connect to add your own`,
|
|
703
|
+
"reachability"
|
|
704
|
+
);
|
|
621
705
|
}
|
|
622
706
|
const pushSuppressed = !options.roundtrip && (options.noPush || options.json || !process.stdout.isTTY);
|
|
623
707
|
if (pushSuppressed) {
|
|
@@ -662,6 +746,14 @@ var main = async () => {
|
|
|
662
746
|
});
|
|
663
747
|
const elapsed = ((Date.now() - start) / 1e3).toFixed(1);
|
|
664
748
|
check(waitResult.answered === true, "Answer received", waitResult.answered ? `"${waitResult.value}" (${elapsed}s roundtrip)` : "no response within timeout");
|
|
749
|
+
if (waitResult.answered !== true) {
|
|
750
|
+
await callMcpTool(apiKey, "cancel_question", { correlationId: askResult.correlationId }, {
|
|
751
|
+
id: 5,
|
|
752
|
+
sessionId,
|
|
753
|
+
timeoutMs: 1e4
|
|
754
|
+
}).catch(() => {
|
|
755
|
+
});
|
|
756
|
+
}
|
|
665
757
|
} else {
|
|
666
758
|
check(false, "Question sent", "failed to get correlationId");
|
|
667
759
|
}
|
|
@@ -710,7 +802,7 @@ var main = async () => {
|
|
|
710
802
|
}
|
|
711
803
|
console.log();
|
|
712
804
|
const keyExit = keyVerdict && (keyVerdict.kind === "rejected" || keyVerdict.kind === "locked" || keyVerdict.kind === "malformed") ? describeKeyCheck(keyVerdict).exitCode : null;
|
|
713
|
-
const exitCode = keyExit
|
|
805
|
+
const exitCode = resolveDoctorExit({ keyExit, failures: failed });
|
|
714
806
|
if (options.bundle) {
|
|
715
807
|
try {
|
|
716
808
|
const bundle = redact(
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
|
+
KILL_REASON,
|
|
3
4
|
denyReasonFrom,
|
|
4
|
-
isDeferAnswer
|
|
5
|
-
|
|
5
|
+
isDeferAnswer,
|
|
6
|
+
resolveGate
|
|
7
|
+
} from "../chunk-GD275GEX.js";
|
|
6
8
|
import {
|
|
7
9
|
DEFAULT_SESSION,
|
|
8
10
|
askUser,
|
|
@@ -18,12 +20,13 @@ import {
|
|
|
18
20
|
handleUserPrompt,
|
|
19
21
|
readLastPrompt,
|
|
20
22
|
readLastUserPrompt,
|
|
23
|
+
repoKeyFor,
|
|
21
24
|
reportEvent,
|
|
22
|
-
resolvePolicy,
|
|
23
25
|
savePendingQuestion,
|
|
26
|
+
scopePathFor,
|
|
24
27
|
sendNotification,
|
|
25
28
|
waitForAnswer
|
|
26
|
-
} from "../chunk-
|
|
29
|
+
} from "../chunk-FZIQSDTQ.js";
|
|
27
30
|
import {
|
|
28
31
|
isGatingMoment,
|
|
29
32
|
recordKeylessMoment
|
|
@@ -49,6 +52,7 @@ import "../chunk-KZERVKTD.js";
|
|
|
49
52
|
import { basename } from "path";
|
|
50
53
|
|
|
51
54
|
// src/gemini-adapter.ts
|
|
55
|
+
import { isAbsolute, resolve } from "path";
|
|
52
56
|
var GEMINI_AGENT = { type: "gemini_cli", label: "Gemini CLI" };
|
|
53
57
|
var GEMINI_TOOL_MAP = {
|
|
54
58
|
run_shell_command: "Bash",
|
|
@@ -57,7 +61,7 @@ var GEMINI_TOOL_MAP = {
|
|
|
57
61
|
read_file: "Read"
|
|
58
62
|
};
|
|
59
63
|
var firstString = (...values) => values.find((v) => typeof v === "string" && v.length > 0);
|
|
60
|
-
var toGeminiPolicyLookup = (toolName, toolInput) => {
|
|
64
|
+
var toGeminiPolicyLookup = (toolName, toolInput, cwd) => {
|
|
61
65
|
const canonical = GEMINI_TOOL_MAP[toolName];
|
|
62
66
|
if (!canonical) return { tool: toolName, input: toolInput };
|
|
63
67
|
if (canonical === "Bash") {
|
|
@@ -65,7 +69,11 @@ var toGeminiPolicyLookup = (toolName, toolInput) => {
|
|
|
65
69
|
return { tool: "Bash", input: command ? { command } : {} };
|
|
66
70
|
}
|
|
67
71
|
const filePath = firstString(toolInput.file_path, toolInput.absolute_path, toolInput.path);
|
|
68
|
-
return { tool: canonical, input:
|
|
72
|
+
if (!filePath) return { tool: canonical, input: {} };
|
|
73
|
+
return {
|
|
74
|
+
tool: canonical,
|
|
75
|
+
input: { file_path: cwd && !isAbsolute(filePath) ? resolve(cwd, filePath) : filePath }
|
|
76
|
+
};
|
|
69
77
|
};
|
|
70
78
|
var geminiAllow = () => ({ kind: "allow" });
|
|
71
79
|
var geminiDeny = (reason) => ({ kind: "deny", reason });
|
|
@@ -82,9 +90,8 @@ var geminiTimeoutDecision = (timeoutAction, denyReason = "No response within tim
|
|
|
82
90
|
};
|
|
83
91
|
|
|
84
92
|
// bin/pushary-gemini-hook.ts
|
|
85
|
-
var KILL_REASON = "Stopped by user: this agent was halted from Pushary";
|
|
86
93
|
var START_MS = Date.now();
|
|
87
|
-
var sleep = (ms) => new Promise((
|
|
94
|
+
var sleep = (ms) => new Promise((resolve2) => setTimeout(resolve2, ms));
|
|
88
95
|
var pollForAnswer = async (apiKey, correlationId, deadlineMs, pollInterval = 2e3) => {
|
|
89
96
|
while (Date.now() < deadlineMs) {
|
|
90
97
|
const remaining = Math.min(Math.max(deadlineMs - Date.now(), 1e3), 3e4);
|
|
@@ -210,14 +217,23 @@ var decideBeforeTool = async (input) => {
|
|
|
210
217
|
}
|
|
211
218
|
try {
|
|
212
219
|
const modeState = await fetchModeState(apiKey, input.session_id);
|
|
213
|
-
|
|
214
|
-
const
|
|
215
|
-
const
|
|
216
|
-
const
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
220
|
+
const lookup = toGeminiPolicyLookup(input.tool_name ?? "", input.tool_input ?? {}, input.cwd);
|
|
221
|
+
const config = await getPolicy(apiKey, modeState.policyVersion, "gemini_cli");
|
|
222
|
+
const scopePath = modeState.scope ? scopePathFor(lookup.tool, lookup.input, input.cwd) : void 0;
|
|
223
|
+
const verdict = resolveGate({
|
|
224
|
+
modeState,
|
|
225
|
+
config,
|
|
226
|
+
toolName: lookup.tool,
|
|
227
|
+
toolInputs: [lookup.input],
|
|
228
|
+
scopePaths: scopePath ? [scopePath] : [],
|
|
229
|
+
cwd: input.cwd,
|
|
230
|
+
repoKey: repoKeyFor(input.cwd)
|
|
231
|
+
});
|
|
232
|
+
if (verdict.kind === "kill") return geminiDeny(KILL_REASON);
|
|
233
|
+
if (verdict.kind === "allow") return geminiAllow();
|
|
234
|
+
if (verdict.kind === "deny") return geminiDeny(verdict.reason);
|
|
235
|
+
if (verdict.kind === "defer") return geminiPass();
|
|
236
|
+
const toolPolicy = verdict.policy;
|
|
221
237
|
switch (toolPolicy.mode) {
|
|
222
238
|
case "push_only":
|
|
223
239
|
return handlePushOnly(apiKey, input, lookup, toolPolicy.timeoutSeconds, toolPolicy.timeoutAction);
|
package/dist/bin/pushary-hook.js
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
handlePreToolUse
|
|
4
|
-
} from "../chunk-
|
|
4
|
+
} from "../chunk-HU43EZ5Q.js";
|
|
5
5
|
import "../chunk-CAJZAFVS.js";
|
|
6
6
|
import {
|
|
7
7
|
exitOnHelpFlag
|
|
8
|
-
} from "../chunk-
|
|
9
|
-
import "../chunk-
|
|
10
|
-
import "../chunk-
|
|
11
|
-
import "../chunk-
|
|
8
|
+
} from "../chunk-BBK3TTU2.js";
|
|
9
|
+
import "../chunk-7OYGFJYZ.js";
|
|
10
|
+
import "../chunk-GD275GEX.js";
|
|
11
|
+
import "../chunk-FZIQSDTQ.js";
|
|
12
12
|
import "../chunk-WLGEY4UX.js";
|
|
13
13
|
import "../chunk-BSZYIAZL.js";
|
|
14
14
|
import "../chunk-SAF6HGAA.js";
|
|
@@ -21,10 +21,10 @@ import {
|
|
|
21
21
|
} from "../chunk-GMXKITVA.js";
|
|
22
22
|
import {
|
|
23
23
|
exitOnHelpFlag
|
|
24
|
-
} from "../chunk-
|
|
24
|
+
} from "../chunk-BBK3TTU2.js";
|
|
25
25
|
import {
|
|
26
26
|
getPackageVersion
|
|
27
|
-
} from "../chunk-
|
|
27
|
+
} from "../chunk-7OYGFJYZ.js";
|
|
28
28
|
import "../chunk-SAF6HGAA.js";
|
|
29
29
|
import {
|
|
30
30
|
UsageError
|
|
@@ -19,8 +19,8 @@ import {
|
|
|
19
19
|
} from "../chunk-GMXKITVA.js";
|
|
20
20
|
import {
|
|
21
21
|
exitOnHelpFlag
|
|
22
|
-
} from "../chunk-
|
|
23
|
-
import "../chunk-
|
|
22
|
+
} from "../chunk-BBK3TTU2.js";
|
|
23
|
+
import "../chunk-7OYGFJYZ.js";
|
|
24
24
|
import {
|
|
25
25
|
UsageError
|
|
26
26
|
} from "../chunk-7QLSKOSU.js";
|
package/dist/bin/pushary-mode.js
CHANGED
|
@@ -11,8 +11,8 @@ import {
|
|
|
11
11
|
} from "../chunk-GMXKITVA.js";
|
|
12
12
|
import {
|
|
13
13
|
exitOnHelpFlag
|
|
14
|
-
} from "../chunk-
|
|
15
|
-
import "../chunk-
|
|
14
|
+
} from "../chunk-BBK3TTU2.js";
|
|
15
|
+
import "../chunk-7OYGFJYZ.js";
|
|
16
16
|
import "../chunk-SAF6HGAA.js";
|
|
17
17
|
import {
|
|
18
18
|
UsageError
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
handlePermissionDenied
|
|
4
|
-
} from "../chunk-
|
|
4
|
+
} from "../chunk-HU43EZ5Q.js";
|
|
5
5
|
import "../chunk-CAJZAFVS.js";
|
|
6
|
-
import "../chunk-
|
|
7
|
-
import "../chunk-
|
|
6
|
+
import "../chunk-GD275GEX.js";
|
|
7
|
+
import "../chunk-FZIQSDTQ.js";
|
|
8
8
|
import "../chunk-WLGEY4UX.js";
|
|
9
9
|
import "../chunk-BSZYIAZL.js";
|
|
10
10
|
import "../chunk-SAF6HGAA.js";
|