@pushary/agent-hooks 0.60.0 → 0.62.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 +161 -0
- package/README.md +52 -9
- package/dist/bin/pushary-claude.js +6 -6
- package/dist/bin/pushary-clean.js +83 -38
- package/dist/bin/pushary-codex-hook.js +6 -6
- package/dist/bin/pushary-codex.js +4 -4
- package/dist/bin/pushary-connect.d.ts +1 -0
- package/dist/bin/pushary-connect.js +92 -0
- package/dist/bin/pushary-daemon.js +42 -5
- package/dist/bin/pushary-doctor.js +246 -71
- package/dist/bin/pushary-gemini-hook.js +6 -6
- package/dist/bin/pushary-hook.js +10 -5
- package/dist/bin/pushary-login.d.ts +1 -0
- package/dist/bin/pushary-login.js +156 -0
- package/dist/bin/pushary-logout.d.ts +1 -0
- package/dist/bin/pushary-logout.js +144 -0
- package/dist/bin/pushary-mode.js +24 -13
- package/dist/bin/pushary-notification-hook.js +4 -4
- package/dist/bin/pushary-permission-denied-hook.js +5 -5
- package/dist/bin/pushary-permission-hook.js +5 -5
- package/dist/bin/pushary-post-hook.js +4 -4
- package/dist/bin/pushary-prompt-hook.js +4 -4
- package/dist/bin/pushary-session-end-hook.js +4 -4
- package/dist/bin/pushary-session-start-hook.js +4 -4
- package/dist/bin/pushary-setup.js +769 -518
- package/dist/bin/pushary-stats.js +6 -1
- package/dist/bin/pushary-status.d.ts +1 -0
- package/dist/bin/pushary-status.js +206 -0
- package/dist/bin/pushary-stop-hook.js +4 -4
- package/dist/bin/pushary-stopfailure-hook.js +4 -4
- package/dist/bin/pushary-suggestions.js +10 -5
- package/dist/bin/pushary-upgrade.js +184 -14
- package/dist/bin/pushary-wait.js +21 -12
- package/dist/bin/pushary.js +42 -60
- package/dist/chunk-247C4RE5.js +236 -0
- package/dist/{chunk-NKXSILEW.js → chunk-2UMNXADU.js} +18 -2
- package/dist/chunk-3EGEA4KH.js +44 -0
- package/dist/chunk-3O27ZSRE.js +569 -0
- package/dist/{chunk-BE5X3WXL.js → chunk-5SO3CEGJ.js} +5 -5
- package/dist/chunk-7QLSKOSU.js +19 -0
- package/dist/chunk-E2U35RLD.js +108 -0
- package/dist/chunk-ER657KRJ.js +168 -0
- package/dist/{chunk-WNRYRN2R.js → chunk-HIIBSYFJ.js} +5 -5
- package/dist/{chunk-DWED7BS3.js → chunk-HNTKTK5B.js} +1 -1
- package/dist/chunk-IIZZYUWK.js +18 -0
- package/dist/{chunk-J7JWI3KU.js → chunk-MUEW424A.js} +19 -1
- package/dist/chunk-N4DA4CJB.js +57 -0
- package/dist/chunk-N7XJ4L2W.js +79 -0
- package/dist/chunk-ONVEYEVR.js +44 -0
- package/dist/chunk-QHOVJXOT.js +255 -0
- package/dist/chunk-SP7OZXCQ.js +224 -0
- package/dist/chunk-TX7KBKT7.js +79 -0
- package/dist/chunk-UXXRRXUS.js +93 -0
- package/dist/chunk-YJ7YZRVV.js +217 -0
- package/dist/src/index.js +5 -5
- package/package.json +11 -4
- package/dist/chunk-BQLCELYC.js +0 -332
|
@@ -4,14 +4,20 @@ import {
|
|
|
4
4
|
} from "../chunk-XHKBHWLX.js";
|
|
5
5
|
import {
|
|
6
6
|
resolveGlobalBinary
|
|
7
|
-
} from "../chunk-
|
|
7
|
+
} from "../chunk-MUEW424A.js";
|
|
8
|
+
import {
|
|
9
|
+
exitOnHelpFlag
|
|
10
|
+
} from "../chunk-IIZZYUWK.js";
|
|
11
|
+
import {
|
|
12
|
+
EXIT
|
|
13
|
+
} from "../chunk-SP7OZXCQ.js";
|
|
8
14
|
import {
|
|
9
15
|
getMachineId
|
|
10
16
|
} from "../chunk-RN3NOEJF.js";
|
|
11
17
|
import {
|
|
12
18
|
getApiKey,
|
|
13
19
|
getBaseUrl
|
|
14
|
-
} from "../chunk-
|
|
20
|
+
} from "../chunk-2UMNXADU.js";
|
|
15
21
|
|
|
16
22
|
// src/spawn-daemon.ts
|
|
17
23
|
import { spawn } from "child_process";
|
|
@@ -26,6 +32,19 @@ var MAX_ERROR_STREAK = 6;
|
|
|
26
32
|
var REQUEST_TIMEOUT_MS = 1e4;
|
|
27
33
|
var MAX_SPAWNS_PER_WINDOW = 5;
|
|
28
34
|
var SPAWN_WINDOW_MS = 6e4;
|
|
35
|
+
var DrainError = class extends Error {
|
|
36
|
+
constructor(status) {
|
|
37
|
+
super(`drain ${status}`);
|
|
38
|
+
this.status = status;
|
|
39
|
+
this.name = "DrainError";
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
var isAuthFailure = (err) => {
|
|
43
|
+
if (typeof err !== "object" || err === null) return false;
|
|
44
|
+
const candidate = err;
|
|
45
|
+
if (candidate.name !== "DrainError") return false;
|
|
46
|
+
return candidate.status === 401 || candidate.status === 403;
|
|
47
|
+
};
|
|
29
48
|
var NATIVE_AGENT_BINARIES = { codex: "codex", gemini: "gemini" };
|
|
30
49
|
var buildSpawnLaunch = (req, opts) => {
|
|
31
50
|
const agent = req.agent === "codex" || req.agent === "gemini" ? req.agent : "claude";
|
|
@@ -64,6 +83,9 @@ var runSpawnDaemon = async () => {
|
|
|
64
83
|
const spawnTimestamps = [];
|
|
65
84
|
let timer;
|
|
66
85
|
let inflight;
|
|
86
|
+
let fatalCode = null;
|
|
87
|
+
let finish = () => {
|
|
88
|
+
};
|
|
67
89
|
const launch = (req) => {
|
|
68
90
|
spawnTimestamps.push(Date.now());
|
|
69
91
|
const cwd = req.cwd && existsSync(req.cwd) ? req.cwd : process.cwd();
|
|
@@ -94,7 +116,7 @@ var runSpawnDaemon = async () => {
|
|
|
94
116
|
body: JSON.stringify({ machineId }),
|
|
95
117
|
signal
|
|
96
118
|
});
|
|
97
|
-
if (!res.ok) throw new
|
|
119
|
+
if (!res.ok) throw new DrainError(res.status);
|
|
98
120
|
const data = await res.json();
|
|
99
121
|
const spawn2 = data.spawn;
|
|
100
122
|
return spawn2 && typeof spawn2.prompt === "string" && typeof spawn2.id === "string" ? spawn2 : null;
|
|
@@ -122,7 +144,16 @@ var runSpawnDaemon = async () => {
|
|
|
122
144
|
} else {
|
|
123
145
|
idleTicks++;
|
|
124
146
|
}
|
|
125
|
-
} catch {
|
|
147
|
+
} catch (err) {
|
|
148
|
+
if (isAuthFailure(err)) {
|
|
149
|
+
const status = err.status;
|
|
150
|
+
fatalCode = EXIT.UNAUTHENTICATED;
|
|
151
|
+
stderr(
|
|
152
|
+
status === 401 ? "[pushary] daemon stopping: this machine's key was rejected. Run `pushary login` to sign in again.\n" : "[pushary] daemon stopping: this workspace has no active plan, so the queue is closed. Pick one at pushary.com/dashboard/settings?tab=billing\n"
|
|
153
|
+
);
|
|
154
|
+
finish();
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
126
157
|
errorStreak = Math.min(errorStreak + 1, MAX_ERROR_STREAK);
|
|
127
158
|
} finally {
|
|
128
159
|
clearTimeout(timeout);
|
|
@@ -148,11 +179,17 @@ var runSpawnDaemon = async () => {
|
|
|
148
179
|
resolve();
|
|
149
180
|
};
|
|
150
181
|
for (const signal of signals) process.on(signal, onSignal);
|
|
182
|
+
finish = () => {
|
|
183
|
+
teardown();
|
|
184
|
+
for (const signal of signals) process.off(signal, onSignal);
|
|
185
|
+
resolve();
|
|
186
|
+
};
|
|
151
187
|
});
|
|
152
|
-
return 0;
|
|
188
|
+
return fatalCode ?? 0;
|
|
153
189
|
};
|
|
154
190
|
|
|
155
191
|
// bin/pushary-daemon.ts
|
|
192
|
+
exitOnHelpFlag("daemon");
|
|
156
193
|
runSpawnDaemon().then((code) => process.exit(code)).catch((err) => {
|
|
157
194
|
process.stderr.write(
|
|
158
195
|
`[pushary] daemon error: ${err instanceof Error ? err.message : String(err)}
|
|
@@ -2,50 +2,162 @@
|
|
|
2
2
|
import {
|
|
3
3
|
describeWaitLadder
|
|
4
4
|
} from "../chunk-IIENZE2J.js";
|
|
5
|
+
import {
|
|
6
|
+
detectAllAgents
|
|
7
|
+
} from "../chunk-UXXRRXUS.js";
|
|
8
|
+
import {
|
|
9
|
+
hasInstructionBlock
|
|
10
|
+
} from "../chunk-N7XJ4L2W.js";
|
|
5
11
|
import {
|
|
6
12
|
GEMINI_HOOK_BINARY,
|
|
7
|
-
hasCodexHooks,
|
|
8
13
|
hasGeminiHooks,
|
|
9
|
-
|
|
14
|
+
missingGeminiHookEvents
|
|
15
|
+
} from "../chunk-E2U35RLD.js";
|
|
16
|
+
import {
|
|
17
|
+
codexAgentsMd,
|
|
18
|
+
codexConfigToml,
|
|
19
|
+
codexHooksJson,
|
|
20
|
+
codexSkillDir,
|
|
21
|
+
hasCodexHooks,
|
|
10
22
|
missingCodexHookEvents,
|
|
11
|
-
missingGeminiHookEvents,
|
|
12
23
|
readCodexMcpAuth,
|
|
13
24
|
untrustedCodexHookEvents
|
|
14
|
-
} from "../chunk-
|
|
25
|
+
} from "../chunk-ER657KRJ.js";
|
|
26
|
+
import {
|
|
27
|
+
describeReach,
|
|
28
|
+
fetchChannels,
|
|
29
|
+
reachVerdict
|
|
30
|
+
} from "../chunk-3O27ZSRE.js";
|
|
31
|
+
import "../chunk-3EGEA4KH.js";
|
|
32
|
+
import {
|
|
33
|
+
parseFlags
|
|
34
|
+
} from "../chunk-N4DA4CJB.js";
|
|
35
|
+
import {
|
|
36
|
+
keyPrefix,
|
|
37
|
+
readKeySource
|
|
38
|
+
} from "../chunk-247C4RE5.js";
|
|
39
|
+
import {
|
|
40
|
+
checkApiKey,
|
|
41
|
+
describeKeyCheck
|
|
42
|
+
} from "../chunk-YJ7YZRVV.js";
|
|
43
|
+
import {
|
|
44
|
+
createIo
|
|
45
|
+
} from "../chunk-TX7KBKT7.js";
|
|
46
|
+
import {
|
|
47
|
+
UsageError
|
|
48
|
+
} from "../chunk-7QLSKOSU.js";
|
|
49
|
+
import "../chunk-BC3VCZ3E.js";
|
|
15
50
|
import {
|
|
16
51
|
execNpm
|
|
17
|
-
} from "../chunk-
|
|
52
|
+
} from "../chunk-MUEW424A.js";
|
|
53
|
+
import {
|
|
54
|
+
exitOnHelpFlag
|
|
55
|
+
} from "../chunk-IIZZYUWK.js";
|
|
56
|
+
import {
|
|
57
|
+
EXIT,
|
|
58
|
+
getPackageVersion
|
|
59
|
+
} from "../chunk-SP7OZXCQ.js";
|
|
18
60
|
import {
|
|
19
61
|
readLedgerSummary
|
|
20
62
|
} from "../chunk-WLGEY4UX.js";
|
|
21
63
|
import {
|
|
22
64
|
callMcpTool,
|
|
23
65
|
sendMcpRequest
|
|
24
|
-
} from "../chunk-
|
|
66
|
+
} from "../chunk-HNTKTK5B.js";
|
|
25
67
|
import "../chunk-DQAN3JQP.js";
|
|
68
|
+
import {
|
|
69
|
+
getMachineId
|
|
70
|
+
} from "../chunk-RN3NOEJF.js";
|
|
26
71
|
import {
|
|
27
72
|
getBaseUrl
|
|
28
|
-
} from "../chunk-
|
|
73
|
+
} from "../chunk-2UMNXADU.js";
|
|
29
74
|
|
|
30
75
|
// bin/pushary-doctor.ts
|
|
31
|
-
import { existsSync, readFileSync } from "fs";
|
|
76
|
+
import { existsSync, readFileSync, writeFileSync } from "fs";
|
|
32
77
|
import { join } from "path";
|
|
33
|
-
import { homedir } from "os";
|
|
78
|
+
import { homedir as homedir2 } from "os";
|
|
34
79
|
import { execSync } from "child_process";
|
|
35
80
|
import { confirm } from "@inquirer/prompts";
|
|
36
81
|
import { parse as parseTOML } from "smol-toml";
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
var
|
|
41
|
-
|
|
42
|
-
|
|
82
|
+
|
|
83
|
+
// src/diagnostics/bundle.ts
|
|
84
|
+
import { homedir, hostname } from "os";
|
|
85
|
+
var globalVersion = () => {
|
|
86
|
+
try {
|
|
87
|
+
return execNpm("list -g --no-workspaces @pushary/agent-hooks --depth=0", { timeout: 1e4 }).toString().match(/@pushary\/agent-hooks@([\d.]+)/)?.[1] ?? null;
|
|
88
|
+
} catch {
|
|
89
|
+
return null;
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
var collectBundle = async (options2 = {}) => {
|
|
93
|
+
const now = options2.now ?? (() => /* @__PURE__ */ new Date());
|
|
94
|
+
const source = readKeySource();
|
|
95
|
+
const identity = options2.identity ?? null;
|
|
96
|
+
return {
|
|
97
|
+
generatedAt: now().toISOString(),
|
|
98
|
+
cliVersion: getPackageVersion(),
|
|
99
|
+
globalVersion: globalVersion(),
|
|
100
|
+
node: process.version,
|
|
101
|
+
platform: process.platform,
|
|
102
|
+
arch: process.arch,
|
|
103
|
+
shell: process.env.SHELL ?? null,
|
|
104
|
+
machineId: getMachineId(),
|
|
105
|
+
keySource: source.kind,
|
|
106
|
+
keyPrefix: source.key ? keyPrefix(source.key) : null,
|
|
107
|
+
// The value of the drift, not the keys: knowing two places disagree is the
|
|
108
|
+
// whole diagnostic, and the keys themselves are what must not travel.
|
|
109
|
+
keyDrift: source.drift !== null,
|
|
110
|
+
identity: identity ? stripWorkspace(identity) : null,
|
|
111
|
+
agents: detectAllAgents().map((entry) => ({ id: entry.agent, detected: entry.result })),
|
|
112
|
+
checks: options2.checks ?? []
|
|
113
|
+
};
|
|
114
|
+
};
|
|
115
|
+
var stripWorkspace = (identity) => {
|
|
116
|
+
const { workspaceId: _workspaceId, ...rest } = identity;
|
|
117
|
+
return rest;
|
|
118
|
+
};
|
|
119
|
+
var KEY_PATTERN = /pk_[A-Za-z0-9_]+\.[A-Za-z0-9_]+/g;
|
|
120
|
+
var homePattern = () => new RegExp(homedir().replace(/[.*+?^${}()|[\]\\]/g, "\\$&"), "g");
|
|
121
|
+
var redactText = (text) => text.replace(KEY_PATTERN, (match) => `${match.split(".")[0]}.REDACTED`).replace(homePattern(), "~").replace(new RegExp(hostname().replace(/[.*+?^${}()|[\]\\]/g, "\\$&"), "g"), "HOST");
|
|
122
|
+
var redact = (bundle) => JSON.parse(redactText(JSON.stringify(bundle)));
|
|
123
|
+
var bundleFileName = (bundle) => `pushary-diagnostics-${bundle.generatedAt.replace(/[:.]/g, "-")}.json`;
|
|
124
|
+
|
|
125
|
+
// bin/pushary-doctor.ts
|
|
126
|
+
exitOnHelpFlag("doctor");
|
|
127
|
+
var parseDoctorOptions = (argv) => {
|
|
128
|
+
const flags = parseFlags(argv, { bools: ["json", "roundtrip", "no-push", "bundle", "help", "h"], values: [] });
|
|
129
|
+
if (flags.command !== void 0 && flags.command !== "doctor") {
|
|
130
|
+
throw new UsageError(`doctor takes no positional arguments (got ${flags.command})`);
|
|
131
|
+
}
|
|
132
|
+
const roundtrip = flags.bools.has("roundtrip");
|
|
133
|
+
const noPush = flags.bools.has("no-push");
|
|
134
|
+
if (roundtrip && noPush) {
|
|
135
|
+
throw new UsageError("--roundtrip needs to send a push, so it cannot be combined with --no-push");
|
|
136
|
+
}
|
|
137
|
+
return { json: flags.bools.has("json"), roundtrip, noPush, bundle: flags.bools.has("bundle") };
|
|
138
|
+
};
|
|
139
|
+
var options;
|
|
140
|
+
try {
|
|
141
|
+
options = parseDoctorOptions(process.argv.slice(2));
|
|
142
|
+
} catch (err) {
|
|
143
|
+
process.stderr.write(` ! ${err instanceof Error ? err.message : String(err)}
|
|
144
|
+
`);
|
|
145
|
+
process.stderr.write(" Run `pushary doctor --help` for the full list.\n");
|
|
146
|
+
process.exit(EXIT.USAGE);
|
|
147
|
+
}
|
|
148
|
+
if (options.json) {
|
|
149
|
+
console.log = (...args) => {
|
|
150
|
+
process.stderr.write(`${args.map((a) => typeof a === "string" ? a : String(a)).join(" ")}
|
|
151
|
+
`);
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
var { bold, dim, green, yellow, red, cyan } = createIo();
|
|
43
155
|
var pass = green("\u2713");
|
|
44
156
|
var fail = red("\u2717");
|
|
45
157
|
var warn = yellow("!");
|
|
46
|
-
var CLAUDE_SETTINGS = join(
|
|
47
|
-
var SKILL_PATH = join(
|
|
48
|
-
var SHELL_FILES = [".zshrc", ".zprofile", ".bashrc", ".bash_profile"].map((f) => join(
|
|
158
|
+
var CLAUDE_SETTINGS = join(homedir2(), ".claude", "settings.json");
|
|
159
|
+
var SKILL_PATH = join(homedir2(), ".claude", "skills", "pushary", "SKILL.md");
|
|
160
|
+
var SHELL_FILES = [".zshrc", ".zprofile", ".bashrc", ".bash_profile"].map((f) => join(homedir2(), f));
|
|
49
161
|
var readJson = (path) => {
|
|
50
162
|
try {
|
|
51
163
|
return JSON.parse(readFileSync(path, "utf-8"));
|
|
@@ -101,19 +213,9 @@ var main = async () => {
|
|
|
101
213
|
console.log(` ${bold("Pushary Doctor")}`);
|
|
102
214
|
console.log();
|
|
103
215
|
console.log(` ${dim("Configuration")}`);
|
|
104
|
-
|
|
105
|
-
let
|
|
106
|
-
|
|
107
|
-
if (!apiKey) {
|
|
108
|
-
try {
|
|
109
|
-
const parsed = JSON.parse(readFileSync(PUSHARY_CONFIG_FILE, "utf-8"));
|
|
110
|
-
if (typeof parsed.apiKey === "string" && parsed.apiKey) {
|
|
111
|
-
apiKey = parsed.apiKey;
|
|
112
|
-
keySource = "config-file";
|
|
113
|
-
}
|
|
114
|
-
} catch {
|
|
115
|
-
}
|
|
116
|
-
}
|
|
216
|
+
const source = readKeySource();
|
|
217
|
+
let apiKey = source.key ?? void 0;
|
|
218
|
+
let keySource = source.kind === "env" ? "env" : source.kind === "config" ? "config-file" : null;
|
|
117
219
|
if (!apiKey) {
|
|
118
220
|
for (const f of SHELL_FILES) {
|
|
119
221
|
try {
|
|
@@ -130,14 +232,48 @@ var main = async () => {
|
|
|
130
232
|
}
|
|
131
233
|
const maskKey = (k) => `pk_${k.split(".")[0]?.slice(3, 7)}...`;
|
|
132
234
|
if (keySource === "config-file") {
|
|
133
|
-
console.log(` ${pass} API key in ${dim("
|
|
235
|
+
console.log(` ${pass} API key in ${dim(source.origin ?? "the config file")} ${dim(`(${maskKey(apiKey)})`)}`);
|
|
134
236
|
} else if (keySource === "profile") {
|
|
135
237
|
console.log(` ${pass} API key in shell profile ${dim(`(${maskKey(apiKey)})`)}`);
|
|
136
238
|
console.log(` ${warn} Re-run ${cyan("npx @pushary/agent-hooks setup")} so hooks read it from ${dim("~/.pushary/config.json")} without a shell reload`);
|
|
137
239
|
} else {
|
|
138
240
|
check(!!apiKey, "API key in environment", apiKey ? maskKey(apiKey) : "PUSHARY_API_KEY not set");
|
|
139
241
|
}
|
|
140
|
-
|
|
242
|
+
if (source.drift) {
|
|
243
|
+
check(
|
|
244
|
+
false,
|
|
245
|
+
"One key, not two",
|
|
246
|
+
`shell has ${source.drift.envPrefix}, ${source.drift.configPath} has ${source.drift.configPrefix} \u2014 the shell one wins. Re-run setup, or open a new terminal.`
|
|
247
|
+
);
|
|
248
|
+
}
|
|
249
|
+
let keyVerdict = null;
|
|
250
|
+
if (apiKey) {
|
|
251
|
+
keyVerdict = await checkApiKey(apiKey);
|
|
252
|
+
switch (keyVerdict.kind) {
|
|
253
|
+
case "ok":
|
|
254
|
+
check(
|
|
255
|
+
true,
|
|
256
|
+
"Key verified with pushary.com",
|
|
257
|
+
`${keyVerdict.identity.siteSlug || keyVerdict.identity.siteName} \xB7 plan ${keyVerdict.identity.plan} \xB7 ${keyVerdict.identity.subscriptionStatus}`
|
|
258
|
+
);
|
|
259
|
+
break;
|
|
260
|
+
case "locked":
|
|
261
|
+
check(false, "Plan active", describeKeyCheck(keyVerdict).message);
|
|
262
|
+
for (const line of describeKeyCheck(keyVerdict).next) console.log(` ${dim(line)}`);
|
|
263
|
+
break;
|
|
264
|
+
case "rejected":
|
|
265
|
+
check(false, "Key accepted by pushary.com", describeKeyCheck(keyVerdict).message);
|
|
266
|
+
for (const line of describeKeyCheck(keyVerdict).next) console.log(` ${dim(line)}`);
|
|
267
|
+
break;
|
|
268
|
+
case "malformed":
|
|
269
|
+
check(false, "Key format", "expected pk_xxx.xxx");
|
|
270
|
+
break;
|
|
271
|
+
case "unreachable":
|
|
272
|
+
console.log(` ${warn} Could not verify the key with pushary.com ${dim(`(${keyVerdict.detail})`)}`);
|
|
273
|
+
break;
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
const CLAUDE_JSON = join(homedir2(), ".claude.json");
|
|
141
277
|
const claudeJson = readJson(CLAUDE_JSON);
|
|
142
278
|
const mcpServers = claudeJson?.mcpServers ?? {};
|
|
143
279
|
const pusharyServer = mcpServers?.pushary;
|
|
@@ -179,10 +315,10 @@ var main = async () => {
|
|
|
179
315
|
} else {
|
|
180
316
|
check(false, "Claude Code: settings.json", "not found");
|
|
181
317
|
}
|
|
182
|
-
const codexConfigPath =
|
|
183
|
-
const codexHooksPath =
|
|
184
|
-
const
|
|
185
|
-
if (existsSync(codexConfigPath) ||
|
|
318
|
+
const codexConfigPath = codexConfigToml();
|
|
319
|
+
const codexHooksPath = codexHooksJson();
|
|
320
|
+
const codexHooksConfig = readJson(codexHooksPath);
|
|
321
|
+
if (existsSync(codexConfigPath) || codexHooksConfig) {
|
|
186
322
|
const codexConfig = existsSync(codexConfigPath) ? readFileSync(codexConfigPath, "utf-8") : "";
|
|
187
323
|
let codexParsed = {};
|
|
188
324
|
try {
|
|
@@ -221,11 +357,11 @@ var main = async () => {
|
|
|
221
357
|
}
|
|
222
358
|
}
|
|
223
359
|
const codexNotifyPath = codexConfig.match(/["']([^"']*pushary-codex[^"']*)["']/)?.[1] ?? null;
|
|
224
|
-
const hooksInstalled = !!
|
|
360
|
+
const hooksInstalled = !!codexHooksConfig && hasCodexHooks(codexHooksConfig);
|
|
225
361
|
if (hooksInstalled) {
|
|
226
|
-
const missingEvents = missingCodexHookEvents(
|
|
362
|
+
const missingEvents = missingCodexHookEvents(codexHooksConfig);
|
|
227
363
|
check(missingEvents.length === 0, "Codex: native hooks installed", missingEvents.length === 0 ? "all 6 events" : `missing ${missingEvents.join(", ")}, re-run setup`);
|
|
228
|
-
const hookCommand = extractHookCommand(
|
|
364
|
+
const hookCommand = extractHookCommand(codexHooksConfig.hooks?.PreToolUse, "pushary-codex-hook") ?? extractHookCommand(codexHooksConfig.hooks?.PermissionRequest, "pushary-codex-hook");
|
|
229
365
|
if (hookCommand) {
|
|
230
366
|
const resolves = commandResolves(hookCommand);
|
|
231
367
|
check(resolves, "Codex: hook command resolves", resolves ? hookCommand : `not on PATH: ${hookCommand}`);
|
|
@@ -247,13 +383,13 @@ var main = async () => {
|
|
|
247
383
|
check(resolves, "Codex: notify handler resolves", resolves ? codexNotifyPath : `not found: ${codexNotifyPath}`);
|
|
248
384
|
}
|
|
249
385
|
}
|
|
250
|
-
const codexSkillPath = join(
|
|
386
|
+
const codexSkillPath = join(codexSkillDir(), "SKILL.md");
|
|
251
387
|
check(existsSync(codexSkillPath), "Codex: skill installed");
|
|
252
|
-
const
|
|
253
|
-
const hasCodexInstructions = hasInstructionBlock(
|
|
254
|
-
check(hasCodexInstructions, "Codex: proactive instructions (AGENTS.md)", hasCodexInstructions ?
|
|
388
|
+
const codexAgentsMd2 = codexAgentsMd();
|
|
389
|
+
const hasCodexInstructions = hasInstructionBlock(codexAgentsMd2);
|
|
390
|
+
check(hasCodexInstructions, "Codex: proactive instructions (AGENTS.md)", hasCodexInstructions ? codexAgentsMd2 : "missing \u2014 re-run setup so Codex asks via push, not the terminal");
|
|
255
391
|
}
|
|
256
|
-
const geminiSettingsPath = join(
|
|
392
|
+
const geminiSettingsPath = join(homedir2(), ".gemini", "settings.json");
|
|
257
393
|
const geminiSettings = readJson(geminiSettingsPath);
|
|
258
394
|
if (geminiSettings) {
|
|
259
395
|
const geminiServers = geminiSettings.mcpServers ?? {};
|
|
@@ -291,13 +427,13 @@ var main = async () => {
|
|
|
291
427
|
} else {
|
|
292
428
|
check(false, "Gemini CLI: native hooks installed", "no Pushary hooks in ~/.gemini/settings.json \u2014 re-run setup");
|
|
293
429
|
}
|
|
294
|
-
const geminiMd = join(
|
|
430
|
+
const geminiMd = join(homedir2(), ".gemini", "GEMINI.md");
|
|
295
431
|
const hasGeminiInstructions = hasInstructionBlock(geminiMd);
|
|
296
432
|
check(hasGeminiInstructions, "Gemini CLI: proactive instructions (GEMINI.md)", hasGeminiInstructions ? geminiMd : "missing \u2014 re-run setup so Gemini asks via push, not the terminal");
|
|
297
433
|
}
|
|
298
|
-
const cursorPluginDir = join(
|
|
434
|
+
const cursorPluginDir = join(homedir2(), ".cursor", "plugins", "local", "pushary");
|
|
299
435
|
if (existsSync(cursorPluginDir)) {
|
|
300
|
-
const cursorUserHooks = join(
|
|
436
|
+
const cursorUserHooks = join(homedir2(), ".cursor", "hooks.json");
|
|
301
437
|
const userHooks = readJson(cursorUserHooks);
|
|
302
438
|
const gateEntry = userHooks.hooks?.beforeShellExecution?.find(
|
|
303
439
|
(h) => String(h.command ?? "").includes("pushary-gate")
|
|
@@ -321,22 +457,22 @@ var main = async () => {
|
|
|
321
457
|
}
|
|
322
458
|
}
|
|
323
459
|
check(existsSync(SKILL_PATH), "Skill installed", existsSync(SKILL_PATH) ? SKILL_PATH : "not found");
|
|
324
|
-
let
|
|
460
|
+
let globalVersion2 = "";
|
|
325
461
|
try {
|
|
326
|
-
|
|
462
|
+
globalVersion2 = execNpm("list -g --no-workspaces @pushary/agent-hooks --depth=0", { timeout: 1e4 }).toString().match(/@pushary\/agent-hooks@([\d.]+)/)?.[1] ?? "";
|
|
327
463
|
} catch {
|
|
328
|
-
|
|
464
|
+
globalVersion2 = "";
|
|
329
465
|
}
|
|
330
|
-
check(!!
|
|
331
|
-
if (
|
|
466
|
+
check(!!globalVersion2, "Global package installed", globalVersion2 || "not found");
|
|
467
|
+
if (globalVersion2) {
|
|
332
468
|
try {
|
|
333
469
|
const res = await fetch("https://registry.npmjs.org/@pushary/agent-hooks/latest", {
|
|
334
470
|
signal: AbortSignal.timeout(5e3)
|
|
335
471
|
});
|
|
336
472
|
const latest = res.ok ? (await res.json()).version ?? "" : "";
|
|
337
473
|
if (latest) {
|
|
338
|
-
const upToDate = latest ===
|
|
339
|
-
check(upToDate, "Global package up to date", upToDate ?
|
|
474
|
+
const upToDate = latest === globalVersion2;
|
|
475
|
+
check(upToDate, "Global package up to date", upToDate ? globalVersion2 : `${globalVersion2} installed, ${latest} available \u2014 run npx @pushary/agent-hooks@latest upgrade`);
|
|
340
476
|
}
|
|
341
477
|
} catch {
|
|
342
478
|
}
|
|
@@ -351,7 +487,7 @@ var main = async () => {
|
|
|
351
487
|
}
|
|
352
488
|
});
|
|
353
489
|
if (aliasRc) {
|
|
354
|
-
if (
|
|
490
|
+
if (globalVersion2) {
|
|
355
491
|
check(true, "claude alias \u2192 pushary claude", `set in ${aliasRc.split("/").pop()}`);
|
|
356
492
|
} else {
|
|
357
493
|
check(false, "claude alias \u2192 pushary claude", "alias is set but pushary is not installed \u2014 run `unalias claude` (this shell) or reinstall");
|
|
@@ -428,25 +564,29 @@ var main = async () => {
|
|
|
428
564
|
}
|
|
429
565
|
console.log();
|
|
430
566
|
console.log(` ${dim("Notification Delivery")}`);
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
567
|
+
const probe = await fetchChannels(apiKey);
|
|
568
|
+
if (probe.kind !== "ok") {
|
|
569
|
+
check(false, "Phone reachable", 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");
|
|
570
|
+
} else if (probe.status.precise) {
|
|
571
|
+
const channels = probe.status;
|
|
572
|
+
const verdict = reachVerdict(channels);
|
|
573
|
+
check(verdict.kind === "yours", "Phone reachable", 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`);
|
|
574
|
+
}
|
|
575
|
+
if (options.noPush) {
|
|
576
|
+
console.log(` ${dim("\u2013")} Test push skipped ${dim("(--no-push)")}`);
|
|
577
|
+
} else try {
|
|
578
|
+
const result = await callMcpTool(apiKey, "send_notification", {
|
|
579
|
+
title: "Pushary Doctor",
|
|
580
|
+
body: "If you see this, push notifications are working.",
|
|
581
|
+
agentName: "Pushary Doctor"
|
|
582
|
+
}, { sessionId, timeoutMs: 2e4 });
|
|
583
|
+
const sent = typeof result.sent === "number" ? result.sent : (result.delivery?.mobile?.recipients ?? 0) + (result.delivery?.web?.recipients ?? 0);
|
|
584
|
+
check(sent > 0, "Push notification sent", sent > 0 ? `${sent} device${sent === 1 ? "" : "s"} \u2014 check your phone` : "reached 0 devices");
|
|
445
585
|
} catch (err) {
|
|
446
586
|
const msg = err instanceof Error ? err.message : "network error";
|
|
447
587
|
check(false, "Push notification sent", msg);
|
|
448
588
|
}
|
|
449
|
-
const testQuestion = process.stdin.isTTY ? await confirm({ message: "Test question roundtrip? (sends a push notification)", default: false })
|
|
589
|
+
const testQuestion = options.roundtrip ? true : options.noPush || options.json || !process.stdin.isTTY ? false : await confirm({ message: "Test question roundtrip? (sends a push notification)", default: false });
|
|
450
590
|
if (testQuestion) {
|
|
451
591
|
console.log();
|
|
452
592
|
console.log(` ${dim("Question Roundtrip")}`);
|
|
@@ -520,6 +660,41 @@ var main = async () => {
|
|
|
520
660
|
console.log(` ${dim("Run")} ${cyan("npx @pushary/agent-hooks@latest clean")} ${dim("then")} ${cyan("setup")} ${dim("to fix.")}`);
|
|
521
661
|
}
|
|
522
662
|
console.log();
|
|
663
|
+
const keyExit = keyVerdict && (keyVerdict.kind === "rejected" || keyVerdict.kind === "locked" || keyVerdict.kind === "malformed") ? describeKeyCheck(keyVerdict).exitCode : null;
|
|
664
|
+
const exitCode = keyExit ?? (failed.length > 0 ? EXIT.PROBLEMS_FOUND : EXIT.OK);
|
|
665
|
+
if (options.bundle) {
|
|
666
|
+
try {
|
|
667
|
+
const bundle = redact(
|
|
668
|
+
await collectBundle({
|
|
669
|
+
identity: keyVerdict?.kind === "ok" ? keyVerdict.identity : null,
|
|
670
|
+
checks: results.map((r) => ({ id: r.label, ok: r.passed, detail: r.detail ?? "" }))
|
|
671
|
+
})
|
|
672
|
+
);
|
|
673
|
+
const target = join(process.cwd(), bundleFileName(bundle));
|
|
674
|
+
writeFileSync(target, `${JSON.stringify(bundle, null, 2)}
|
|
675
|
+
`, "utf-8");
|
|
676
|
+
const say = options.json ? process.stderr : process.stdout;
|
|
677
|
+
say.write(`
|
|
678
|
+
Diagnostics written to ${target}
|
|
679
|
+
`);
|
|
680
|
+
say.write(" Keys, your home directory and this machine name are redacted. Read it before sharing.\n\n");
|
|
681
|
+
} catch (err) {
|
|
682
|
+
process.stderr.write(` ! could not write the diagnostics file: ${err instanceof Error ? err.message : String(err)}
|
|
683
|
+
`);
|
|
684
|
+
}
|
|
685
|
+
}
|
|
686
|
+
if (options.json) {
|
|
687
|
+
process.stdout.write(`${JSON.stringify({
|
|
688
|
+
ok: exitCode === EXIT.OK,
|
|
689
|
+
exitCode,
|
|
690
|
+
keyCheck: keyVerdict?.kind ?? null,
|
|
691
|
+
passed: results.filter((r) => r.passed).length,
|
|
692
|
+
failed: failed.length,
|
|
693
|
+
checks: results.map((r) => ({ label: r.label, passed: r.passed, detail: r.detail ?? null }))
|
|
694
|
+
}, null, 2)}
|
|
695
|
+
`);
|
|
696
|
+
}
|
|
697
|
+
process.exit(exitCode);
|
|
523
698
|
};
|
|
524
699
|
main().catch((err) => {
|
|
525
700
|
console.error(` doctor failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
@@ -23,24 +23,24 @@ import {
|
|
|
23
23
|
savePendingQuestion,
|
|
24
24
|
sendNotification,
|
|
25
25
|
waitForAnswer
|
|
26
|
-
} from "../chunk-
|
|
27
|
-
import {
|
|
28
|
-
getMachineId
|
|
29
|
-
} from "../chunk-RN3NOEJF.js";
|
|
26
|
+
} from "../chunk-HIIBSYFJ.js";
|
|
30
27
|
import {
|
|
31
28
|
isGatingMoment,
|
|
32
29
|
recordKeylessMoment
|
|
33
30
|
} from "../chunk-WLGEY4UX.js";
|
|
34
|
-
import "../chunk-
|
|
31
|
+
import "../chunk-HNTKTK5B.js";
|
|
35
32
|
import {
|
|
36
33
|
DECISION_LINE_MAX,
|
|
37
34
|
effectiveWaitSeconds,
|
|
38
35
|
hookWaitClamped,
|
|
39
36
|
hookWaitDeadline
|
|
40
37
|
} from "../chunk-DQAN3JQP.js";
|
|
38
|
+
import {
|
|
39
|
+
getMachineId
|
|
40
|
+
} from "../chunk-RN3NOEJF.js";
|
|
41
41
|
import {
|
|
42
42
|
getApiKey
|
|
43
|
-
} from "../chunk-
|
|
43
|
+
} from "../chunk-2UMNXADU.js";
|
|
44
44
|
|
|
45
45
|
// bin/pushary-gemini-hook.ts
|
|
46
46
|
import { basename } from "path";
|
package/dist/bin/pushary-hook.js
CHANGED
|
@@ -1,17 +1,22 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
handlePreToolUse
|
|
4
|
-
} from "../chunk-
|
|
4
|
+
} from "../chunk-5SO3CEGJ.js";
|
|
5
5
|
import "../chunk-CAJZAFVS.js";
|
|
6
|
+
import {
|
|
7
|
+
exitOnHelpFlag
|
|
8
|
+
} from "../chunk-IIZZYUWK.js";
|
|
9
|
+
import "../chunk-SP7OZXCQ.js";
|
|
6
10
|
import "../chunk-YHG74UFF.js";
|
|
7
|
-
import "../chunk-
|
|
8
|
-
import "../chunk-RN3NOEJF.js";
|
|
11
|
+
import "../chunk-HIIBSYFJ.js";
|
|
9
12
|
import "../chunk-WLGEY4UX.js";
|
|
10
|
-
import "../chunk-
|
|
13
|
+
import "../chunk-HNTKTK5B.js";
|
|
11
14
|
import "../chunk-DQAN3JQP.js";
|
|
12
|
-
import "../chunk-
|
|
15
|
+
import "../chunk-RN3NOEJF.js";
|
|
16
|
+
import "../chunk-2UMNXADU.js";
|
|
13
17
|
|
|
14
18
|
// bin/pushary-hook.ts
|
|
19
|
+
exitOnHelpFlag("hook");
|
|
15
20
|
var main = async () => {
|
|
16
21
|
let rawInput = "";
|
|
17
22
|
for await (const chunk of process.stdin) {
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
#!/usr/bin/env node
|