@pushary/agent-hooks 0.9.1 → 0.10.1
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/dist/bin/pushary-setup.js +222 -104
- package/package.json +2 -1
|
@@ -20,6 +20,160 @@ import { execSync } from "child_process";
|
|
|
20
20
|
import { checkbox, input, confirm } from "@inquirer/prompts";
|
|
21
21
|
import { fileURLToPath } from "url";
|
|
22
22
|
import { parse as parseTOML, stringify as stringifyTOML } from "smol-toml";
|
|
23
|
+
|
|
24
|
+
// src/onboarding.ts
|
|
25
|
+
import qrcodeTerminal from "qrcode-terminal";
|
|
26
|
+
var dim = (s) => `\x1B[2m${s}\x1B[0m`;
|
|
27
|
+
var bold = (s) => `\x1B[1m${s}\x1B[0m`;
|
|
28
|
+
var green = (s) => `\x1B[32m${s}\x1B[0m`;
|
|
29
|
+
var cyan = (s) => `\x1B[36m${s}\x1B[0m`;
|
|
30
|
+
var yellow = (s) => `\x1B[33m${s}\x1B[0m`;
|
|
31
|
+
var check = green("\u2713");
|
|
32
|
+
var API_BASE = process.env.PUSHARY_API_URL?.trim() || "https://pushary.com";
|
|
33
|
+
var CONNECT_POLL_INTERVAL_MS = 2500;
|
|
34
|
+
var CONNECT_TIMEOUT_MS = 18e4;
|
|
35
|
+
var DELIVERY_POLL_INTERVAL_MS = 1500;
|
|
36
|
+
var DELIVERY_TIMEOUT_MS = 2e4;
|
|
37
|
+
var authHeaders = (apiKey) => ({
|
|
38
|
+
"Content-Type": "application/json",
|
|
39
|
+
Authorization: `Bearer ${apiKey}`
|
|
40
|
+
});
|
|
41
|
+
var getJson = async (url, apiKey) => {
|
|
42
|
+
try {
|
|
43
|
+
const res = await fetch(url, { headers: authHeaders(apiKey), signal: AbortSignal.timeout(1e4) });
|
|
44
|
+
if (!res.ok) return null;
|
|
45
|
+
return await res.json().catch(() => null);
|
|
46
|
+
} catch {
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
var fetchSite = async (apiKey) => {
|
|
51
|
+
const data = await getJson(`${API_BASE}/api/v1/server/site`, apiKey);
|
|
52
|
+
if (!data || typeof data.slug !== "string" || typeof data.subscribeUrl !== "string") return null;
|
|
53
|
+
return { slug: data.slug, name: typeof data.name === "string" ? data.name : data.slug, subscribeUrl: data.subscribeUrl };
|
|
54
|
+
};
|
|
55
|
+
var activeSubscriberCount = async (apiKey) => {
|
|
56
|
+
const data = await getJson(`${API_BASE}/api/v1/server/subscribers/count`, apiKey);
|
|
57
|
+
return data && typeof data.active === "number" ? data.active : 0;
|
|
58
|
+
};
|
|
59
|
+
var sendTestNotification = async (apiKey) => {
|
|
60
|
+
const res = await fetch(`${API_BASE}/api/v1/server/send`, {
|
|
61
|
+
method: "POST",
|
|
62
|
+
headers: authHeaders(apiKey),
|
|
63
|
+
body: JSON.stringify({
|
|
64
|
+
title: "Pushary is connected",
|
|
65
|
+
body: "Your AI agent can now reach you right here.",
|
|
66
|
+
metadata: { source: "cli-onboarding" }
|
|
67
|
+
}),
|
|
68
|
+
signal: AbortSignal.timeout(15e3)
|
|
69
|
+
});
|
|
70
|
+
const data = await res.json().catch(() => ({}));
|
|
71
|
+
if (!res.ok) throw new Error(typeof data.error === "string" ? data.error : res.statusText);
|
|
72
|
+
return Array.isArray(data.notificationIds) ? data.notificationIds : [];
|
|
73
|
+
};
|
|
74
|
+
var isDelivered = async (apiKey, id) => {
|
|
75
|
+
const data = await getJson(`${API_BASE}/api/v1/server/notifications/${id}`, apiKey);
|
|
76
|
+
return !!data && (data.status === "delivered" || data.deliveredAt != null);
|
|
77
|
+
};
|
|
78
|
+
var sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
79
|
+
var startSpinner = (getLabel) => {
|
|
80
|
+
const frames = [" ", ". ", ".. ", "..."];
|
|
81
|
+
let i = 0;
|
|
82
|
+
const interval = setInterval(() => {
|
|
83
|
+
process.stdout.write(`\r ${dim(frames[i++ % frames.length])} ${getLabel()}`);
|
|
84
|
+
}, 200);
|
|
85
|
+
return (finalGlyph, finalLabel) => {
|
|
86
|
+
clearInterval(interval);
|
|
87
|
+
process.stdout.write(`\r ${finalGlyph} ${finalLabel}\x1B[K
|
|
88
|
+
`);
|
|
89
|
+
};
|
|
90
|
+
};
|
|
91
|
+
var printQr = (url) => new Promise((resolve, reject) => {
|
|
92
|
+
try {
|
|
93
|
+
qrcodeTerminal.generate(url, { small: true }, (qr) => {
|
|
94
|
+
process.stdout.write("\n" + qr.split("\n").map((line) => " " + line).join("\n") + "\n");
|
|
95
|
+
resolve();
|
|
96
|
+
});
|
|
97
|
+
} catch (err) {
|
|
98
|
+
reject(err);
|
|
99
|
+
}
|
|
100
|
+
});
|
|
101
|
+
var waitForDevice = async (apiKey) => {
|
|
102
|
+
const deadline = Date.now() + CONNECT_TIMEOUT_MS;
|
|
103
|
+
const stop = startSpinner(() => {
|
|
104
|
+
const left = Math.max(0, Math.ceil((deadline - Date.now()) / 1e3));
|
|
105
|
+
return `Waiting for your phone ${dim(`(${left}s)`)}`;
|
|
106
|
+
});
|
|
107
|
+
try {
|
|
108
|
+
while (Date.now() < deadline) {
|
|
109
|
+
if (await activeSubscriberCount(apiKey) > 0) {
|
|
110
|
+
stop(check, "Phone connected");
|
|
111
|
+
return true;
|
|
112
|
+
}
|
|
113
|
+
await sleep(CONNECT_POLL_INTERVAL_MS);
|
|
114
|
+
}
|
|
115
|
+
stop(yellow("!"), "Didn't detect a connection yet");
|
|
116
|
+
return false;
|
|
117
|
+
} catch (err) {
|
|
118
|
+
stop(yellow("!"), `Couldn't check connection ${dim(`(${err instanceof Error ? err.message : "network error"})`)}`);
|
|
119
|
+
return false;
|
|
120
|
+
}
|
|
121
|
+
};
|
|
122
|
+
var sendAndConfirm = async (apiKey) => {
|
|
123
|
+
let notificationIds = [];
|
|
124
|
+
const stopSend = startSpinner(() => "Sending a test notification");
|
|
125
|
+
try {
|
|
126
|
+
notificationIds = await sendTestNotification(apiKey);
|
|
127
|
+
stopSend(check, "Test notification sent");
|
|
128
|
+
} catch (err) {
|
|
129
|
+
stopSend(yellow("!"), `Couldn't send test notification ${dim(`(${err instanceof Error ? err.message : "error"})`)}`);
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
if (notificationIds.length === 0) return;
|
|
133
|
+
const deadline = Date.now() + DELIVERY_TIMEOUT_MS;
|
|
134
|
+
const stop = startSpinner(() => "Confirming it reached your phone");
|
|
135
|
+
while (Date.now() < deadline) {
|
|
136
|
+
const results = await Promise.all(notificationIds.map((id) => isDelivered(apiKey, id)));
|
|
137
|
+
if (results.some(Boolean)) {
|
|
138
|
+
stop(check, "Received on your phone");
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
await sleep(DELIVERY_POLL_INTERVAL_MS);
|
|
142
|
+
}
|
|
143
|
+
stop(yellow("!"), "Sent \u2014 couldn't confirm receipt; check your phone");
|
|
144
|
+
};
|
|
145
|
+
var connectDevice = async (apiKey) => {
|
|
146
|
+
const site = await fetchSite(apiKey);
|
|
147
|
+
if (!site) {
|
|
148
|
+
console.log(` ${yellow("!")} Couldn't load your subscribe link. Connect your phone later at ${cyan("pushary.com")}.`);
|
|
149
|
+
return false;
|
|
150
|
+
}
|
|
151
|
+
if (await activeSubscriberCount(apiKey) > 0) {
|
|
152
|
+
console.log(` ${check} Phone already connected`);
|
|
153
|
+
await sendAndConfirm(apiKey);
|
|
154
|
+
return true;
|
|
155
|
+
}
|
|
156
|
+
console.log();
|
|
157
|
+
console.log(` ${bold("Connect your phone")}`);
|
|
158
|
+
console.log(` ${dim("Scan this with your phone camera to get push notifications:")}`);
|
|
159
|
+
await printQr(site.subscribeUrl);
|
|
160
|
+
console.log(` ${dim("or open")} ${cyan(site.subscribeUrl)}`);
|
|
161
|
+
console.log();
|
|
162
|
+
const connected = await waitForDevice(apiKey);
|
|
163
|
+
if (!connected) {
|
|
164
|
+
console.log(` ${dim("No rush \u2014 open")} ${cyan(site.subscribeUrl)} ${dim("on your phone whenever you're ready.")}`);
|
|
165
|
+
return false;
|
|
166
|
+
}
|
|
167
|
+
await sendAndConfirm(apiKey);
|
|
168
|
+
return true;
|
|
169
|
+
};
|
|
170
|
+
var printConnectInstructions = async (apiKey) => {
|
|
171
|
+
const site = await fetchSite(apiKey);
|
|
172
|
+
const target = site?.subscribeUrl ?? "https://pushary.com";
|
|
173
|
+
console.log(` ${dim("Connect your phone for push notifications at")} ${cyan(target)}`);
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
// bin/pushary-setup.ts
|
|
23
177
|
var CLAUDE_SETTINGS = join(homedir(), ".claude", "settings.json");
|
|
24
178
|
var CLAUDE_JSON = join(homedir(), ".claude.json");
|
|
25
179
|
var CURSOR_MCP = join(".cursor", "mcp.json");
|
|
@@ -27,12 +181,12 @@ var CURSOR_RULES_DIR = join(".cursor", "rules");
|
|
|
27
181
|
var CLAUDE_SKILL_DIR = join(homedir(), ".claude", "skills", "pushary");
|
|
28
182
|
var CODEX_SKILL_DIR = join(homedir(), ".codex", "skills", "pushary");
|
|
29
183
|
var SHELL_FILES = [".zshrc", ".zprofile", ".bashrc", ".bash_profile"].map((f) => join(homedir(), f));
|
|
30
|
-
var
|
|
31
|
-
var
|
|
32
|
-
var
|
|
33
|
-
var
|
|
34
|
-
var
|
|
35
|
-
var
|
|
184
|
+
var dim2 = (s) => `\x1B[2m${s}\x1B[0m`;
|
|
185
|
+
var bold2 = (s) => `\x1B[1m${s}\x1B[0m`;
|
|
186
|
+
var green2 = (s) => `\x1B[32m${s}\x1B[0m`;
|
|
187
|
+
var cyan2 = (s) => `\x1B[36m${s}\x1B[0m`;
|
|
188
|
+
var yellow2 = (s) => `\x1B[33m${s}\x1B[0m`;
|
|
189
|
+
var check2 = green2("\u2713");
|
|
36
190
|
var parseKeyFlag = () => {
|
|
37
191
|
const args = process.argv.slice(2);
|
|
38
192
|
for (let i = 0; i < args.length; i++) {
|
|
@@ -50,17 +204,17 @@ var isInstalled = (command) => {
|
|
|
50
204
|
return false;
|
|
51
205
|
}
|
|
52
206
|
};
|
|
53
|
-
var readJson = (
|
|
207
|
+
var readJson = (filePath) => {
|
|
54
208
|
try {
|
|
55
|
-
return JSON.parse(readFileSync(
|
|
209
|
+
return JSON.parse(readFileSync(filePath, "utf-8"));
|
|
56
210
|
} catch {
|
|
57
211
|
return {};
|
|
58
212
|
}
|
|
59
213
|
};
|
|
60
|
-
var writeJson = (
|
|
61
|
-
const dir =
|
|
214
|
+
var writeJson = (filePath, data) => {
|
|
215
|
+
const dir = dirname(filePath);
|
|
62
216
|
if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
|
|
63
|
-
writeFileSync(
|
|
217
|
+
writeFileSync(filePath, JSON.stringify(data, null, 2) + "\n", "utf-8");
|
|
64
218
|
};
|
|
65
219
|
var formatError = (err) => {
|
|
66
220
|
if (err instanceof Error) {
|
|
@@ -76,16 +230,16 @@ var spinner = async (label, fn, options = {}) => {
|
|
|
76
230
|
const frames = [" ", ". ", ".. ", "..."];
|
|
77
231
|
let i = 0;
|
|
78
232
|
const interval = setInterval(() => {
|
|
79
|
-
process.stdout.write(`\r ${
|
|
233
|
+
process.stdout.write(`\r ${dim2(frames[i++ % frames.length])} ${label}`);
|
|
80
234
|
}, 200);
|
|
81
235
|
try {
|
|
82
236
|
await fn();
|
|
83
237
|
clearInterval(interval);
|
|
84
|
-
process.stdout.write(`\r ${
|
|
238
|
+
process.stdout.write(`\r ${check2} ${label}
|
|
85
239
|
`);
|
|
86
240
|
} catch (err) {
|
|
87
241
|
clearInterval(interval);
|
|
88
|
-
process.stdout.write(`\r ${
|
|
242
|
+
process.stdout.write(`\r ${yellow2("!")} ${label} ${dim2(`(${formatError(err)})`)}
|
|
89
243
|
`);
|
|
90
244
|
if (!options.optional) throw err;
|
|
91
245
|
}
|
|
@@ -107,8 +261,8 @@ var checkForUpdates = async (current) => {
|
|
|
107
261
|
const data = await res.json();
|
|
108
262
|
const latest = data.version;
|
|
109
263
|
if (latest && latest !== current) {
|
|
110
|
-
console.log(` ${
|
|
111
|
-
console.log(` ${
|
|
264
|
+
console.log(` ${yellow2("!")} Update available: ${dim2(current)} \u2192 ${green2(latest)}`);
|
|
265
|
+
console.log(` ${dim2("Run:")} npx @pushary/agent-hooks@${latest} setup`);
|
|
112
266
|
console.log();
|
|
113
267
|
}
|
|
114
268
|
} catch {
|
|
@@ -158,7 +312,7 @@ ${body}`;
|
|
|
158
312
|
};
|
|
159
313
|
var setupClaudeCode = async (apiKey) => {
|
|
160
314
|
console.log(`
|
|
161
|
-
${
|
|
315
|
+
${bold2("Setting up Claude Code")}
|
|
162
316
|
`);
|
|
163
317
|
const settings = readJson(CLAUDE_SETTINGS);
|
|
164
318
|
await spinner("Adding MCP server (type: http)", async () => {
|
|
@@ -184,11 +338,11 @@ var setupClaudeCode = async (apiKey) => {
|
|
|
184
338
|
});
|
|
185
339
|
await installSkillToDir(CLAUDE_SKILL_DIR, "Installing Pushary skill");
|
|
186
340
|
console.log();
|
|
187
|
-
console.log(` ${
|
|
188
|
-
console.log(` ${
|
|
189
|
-
console.log(` ${
|
|
190
|
-
console.log(` ${
|
|
191
|
-
console.log(` ${
|
|
341
|
+
console.log(` ${dim2("What this configured:")}`);
|
|
342
|
+
console.log(` ${dim2("\u2022")} MCP server: your agent can send notifications and ask questions`);
|
|
343
|
+
console.log(` ${dim2("\u2022")} Skill: teaches your agent when and how to use Pushary`);
|
|
344
|
+
console.log(` ${dim2("\u2022")} Hooks: route permission approvals through push notifications`);
|
|
345
|
+
console.log(` ${dim2("\u2022")} Auto-allowed tools: no permission prompts for Pushary MCP calls`);
|
|
192
346
|
};
|
|
193
347
|
var findPython310Plus = () => {
|
|
194
348
|
const candidates = ["python3.13", "python3.12", "python3.11", "python3.10", "python3", "python"];
|
|
@@ -218,11 +372,11 @@ var installPythonPlugin = (pythonBin) => {
|
|
|
218
372
|
};
|
|
219
373
|
var setupHermes = async (_apiKey) => {
|
|
220
374
|
console.log(`
|
|
221
|
-
${
|
|
375
|
+
${bold2("Setting up Hermes Agent")}
|
|
222
376
|
`);
|
|
223
377
|
if (!isInstalled("hermes")) {
|
|
224
|
-
console.log(` ${
|
|
225
|
-
console.log(` ${
|
|
378
|
+
console.log(` ${yellow2("!")} Hermes CLI not found. Skipping.`);
|
|
379
|
+
console.log(` ${dim2("Install Hermes and re-run setup to configure.")}`);
|
|
226
380
|
return;
|
|
227
381
|
}
|
|
228
382
|
await spinner("Installing hermes-plugin-pushary", async () => {
|
|
@@ -248,14 +402,14 @@ var setupHermes = async (_apiKey) => {
|
|
|
248
402
|
} catch {
|
|
249
403
|
}
|
|
250
404
|
} else if (process.platform === "linux") {
|
|
251
|
-
for (const [
|
|
405
|
+
for (const [check3, install] of [
|
|
252
406
|
["which apt-get", "sudo apt-get update -qq && sudo apt-get install -y -qq python3 python3-pip"],
|
|
253
407
|
["which dnf", "sudo dnf install -y -q python3 python3-pip"],
|
|
254
408
|
["which yum", "sudo yum install -y -q python3 python3-pip"],
|
|
255
409
|
["which pacman", "sudo pacman -S --noconfirm python python-pip"]
|
|
256
410
|
]) {
|
|
257
411
|
try {
|
|
258
|
-
execSync(
|
|
412
|
+
execSync(check3, { stdio: "ignore", timeout: 5e3 });
|
|
259
413
|
execSync(install, { stdio: "pipe", timeout: 3e5 });
|
|
260
414
|
python = findPython310Plus();
|
|
261
415
|
if (python) break;
|
|
@@ -288,17 +442,17 @@ var setupHermes = async (_apiKey) => {
|
|
|
288
442
|
execSync("hermes plugins enable pushary", { stdio: "ignore", timeout: 1e4 });
|
|
289
443
|
});
|
|
290
444
|
console.log();
|
|
291
|
-
console.log(` ${
|
|
292
|
-
console.log(` ${
|
|
293
|
-
console.log(` ${
|
|
445
|
+
console.log(` ${dim2("What this configured:")}`);
|
|
446
|
+
console.log(` ${dim2("\u2022")} Native tools: pushary_notify, pushary_ask, pushary_wait, pushary_cancel`);
|
|
447
|
+
console.log(` ${dim2("\u2022")} Auto-notifications: push alert when tools return errors`);
|
|
294
448
|
};
|
|
295
449
|
var setupCodex = async (_apiKey) => {
|
|
296
450
|
console.log(`
|
|
297
|
-
${
|
|
451
|
+
${bold2("Setting up Codex")}
|
|
298
452
|
`);
|
|
299
453
|
if (!isInstalled("codex")) {
|
|
300
|
-
console.log(` ${
|
|
301
|
-
console.log(` ${
|
|
454
|
+
console.log(` ${yellow2("!")} Codex CLI not found. Skipping.`);
|
|
455
|
+
console.log(` ${dim2("Install Codex and re-run setup to configure.")}`);
|
|
302
456
|
return;
|
|
303
457
|
}
|
|
304
458
|
await installGlobally();
|
|
@@ -346,14 +500,14 @@ var setupCodex = async (_apiKey) => {
|
|
|
346
500
|
});
|
|
347
501
|
await installSkillToDir(CODEX_SKILL_DIR, "Installing Pushary skill");
|
|
348
502
|
console.log();
|
|
349
|
-
console.log(` ${
|
|
350
|
-
console.log(` ${
|
|
351
|
-
console.log(` ${
|
|
352
|
-
console.log(` ${
|
|
503
|
+
console.log(` ${dim2("What this configured:")}`);
|
|
504
|
+
console.log(` ${dim2("\u2022")} MCP server: Codex can send notifications and ask questions`);
|
|
505
|
+
console.log(` ${dim2("\u2022")} Auto-allowed tools: no permission prompts for Pushary MCP calls`);
|
|
506
|
+
console.log(` ${dim2("\u2022")} Notify handler: captures turn completions and approval requests`);
|
|
353
507
|
};
|
|
354
508
|
var setupCursor = async (apiKey) => {
|
|
355
509
|
console.log(`
|
|
356
|
-
${
|
|
510
|
+
${bold2("Setting up Cursor")}
|
|
357
511
|
`);
|
|
358
512
|
await spinner("Adding MCP server to .cursor/mcp.json", async () => {
|
|
359
513
|
const config = readJson(CURSOR_MCP);
|
|
@@ -383,43 +537,6 @@ export PUSHARY_API_KEY='${apiKey}'
|
|
|
383
537
|
process.env.PUSHARY_API_KEY = apiKey;
|
|
384
538
|
});
|
|
385
539
|
};
|
|
386
|
-
var sendTestNotification = async (apiKey) => {
|
|
387
|
-
const frames = [" ", ". ", ".. ", "..."];
|
|
388
|
-
let i = 0;
|
|
389
|
-
const interval = setInterval(() => {
|
|
390
|
-
process.stdout.write(`\r ${dim(frames[i++ % frames.length])} Sending test notification`);
|
|
391
|
-
}, 200);
|
|
392
|
-
try {
|
|
393
|
-
const response = await fetch("https://pushary.com/api/v1/server/send", {
|
|
394
|
-
method: "POST",
|
|
395
|
-
headers: {
|
|
396
|
-
"Content-Type": "application/json",
|
|
397
|
-
"Authorization": `Bearer ${apiKey}`
|
|
398
|
-
},
|
|
399
|
-
body: JSON.stringify({
|
|
400
|
-
title: "Pushary is working",
|
|
401
|
-
body: "Your AI agent can now send you push notifications."
|
|
402
|
-
})
|
|
403
|
-
});
|
|
404
|
-
const data = await response.json().catch(() => ({}));
|
|
405
|
-
clearInterval(interval);
|
|
406
|
-
if (!response.ok) {
|
|
407
|
-
const reason = data.error ?? response.statusText;
|
|
408
|
-
process.stdout.write(`\r ${yellow("!")} Sending test notification ${dim(`(${reason})`)}
|
|
409
|
-
`);
|
|
410
|
-
console.log(` ${dim("Make sure you enabled notifications at")} ${cyan("pushary.com")}`);
|
|
411
|
-
} else {
|
|
412
|
-
process.stdout.write(`\r ${check} Sending test notification
|
|
413
|
-
`);
|
|
414
|
-
console.log(` ${dim("Check your phone!")}`);
|
|
415
|
-
}
|
|
416
|
-
} catch (err) {
|
|
417
|
-
clearInterval(interval);
|
|
418
|
-
const msg = err instanceof Error ? err.message : "network error";
|
|
419
|
-
process.stdout.write(`\r ${yellow("!")} Sending test notification ${dim(`(${msg})`)}
|
|
420
|
-
`);
|
|
421
|
-
}
|
|
422
|
-
};
|
|
423
540
|
var AGENT_SETUP = {
|
|
424
541
|
claude_code: setupClaudeCode,
|
|
425
542
|
codex: setupCodex,
|
|
@@ -429,8 +546,8 @@ var AGENT_SETUP = {
|
|
|
429
546
|
var main = async () => {
|
|
430
547
|
const version = getPackageVersion();
|
|
431
548
|
console.log();
|
|
432
|
-
console.log(` ${
|
|
433
|
-
console.log(` ${
|
|
549
|
+
console.log(` ${bold2("Pushary")} ${dim2("v" + version)}`);
|
|
550
|
+
console.log(` ${dim2("Push notifications for AI coding agents")}`);
|
|
434
551
|
console.log();
|
|
435
552
|
await checkForUpdates(version);
|
|
436
553
|
const flagKey = parseKeyFlag();
|
|
@@ -438,12 +555,12 @@ var main = async () => {
|
|
|
438
555
|
let trimmedKey;
|
|
439
556
|
if (flagKey && isValidApiKey(flagKey)) {
|
|
440
557
|
const masked = `${flagKey.slice(0, 8)}...${flagKey.slice(-4)}`;
|
|
441
|
-
console.log(` ${
|
|
558
|
+
console.log(` ${check2} Using API key: ${dim2(masked)}`);
|
|
442
559
|
console.log();
|
|
443
560
|
trimmedKey = flagKey;
|
|
444
561
|
} else if (envKey && isValidApiKey(envKey)) {
|
|
445
562
|
const masked = `${envKey.slice(0, 8)}...${envKey.slice(-4)}`;
|
|
446
|
-
console.log(` ${
|
|
563
|
+
console.log(` ${check2} Found API key in environment: ${dim2(masked)}`);
|
|
447
564
|
console.log();
|
|
448
565
|
const useExisting = await confirm({ message: "Use this key?", default: true });
|
|
449
566
|
if (useExisting) {
|
|
@@ -453,17 +570,17 @@ var main = async () => {
|
|
|
453
570
|
trimmedKey = apiKey.trim();
|
|
454
571
|
}
|
|
455
572
|
} else {
|
|
456
|
-
console.log(` ${
|
|
457
|
-
console.log(` ${
|
|
573
|
+
console.log(` ${dim2("Paste your API key from the onboarding page.")}`);
|
|
574
|
+
console.log(` ${dim2("Can't find it? Copy it from:")} ${cyan2("pushary.com/dashboard/agent/settings")}`);
|
|
458
575
|
console.log();
|
|
459
576
|
const apiKey = await input({ message: "API key:" });
|
|
460
577
|
trimmedKey = apiKey.trim();
|
|
461
578
|
}
|
|
462
579
|
if (!trimmedKey || !isValidApiKey(trimmedKey)) {
|
|
463
580
|
console.log(`
|
|
464
|
-
${
|
|
465
|
-
console.log(` ${
|
|
466
|
-
console.log(` ${
|
|
581
|
+
${yellow2("!")} Invalid key format. Expected: ${dim2("pk_xxx.xxx")}`);
|
|
582
|
+
console.log(` ${dim2("Copy your key from")} ${cyan2("https://pushary.com/dashboard/agent/settings")}`);
|
|
583
|
+
console.log(` ${dim2("Or sign up at")} ${cyan2("https://pushary.com/sign-up?from=ai-coding")}
|
|
467
584
|
`);
|
|
468
585
|
process.exit(1);
|
|
469
586
|
}
|
|
@@ -475,18 +592,18 @@ var main = async () => {
|
|
|
475
592
|
};
|
|
476
593
|
const hint = Object.values(detected).some(Boolean) ? "(detected agents pre-selected)" : "(space = toggle, enter = confirm)";
|
|
477
594
|
const agents = await checkbox({
|
|
478
|
-
message: "Which agents do you use? " +
|
|
595
|
+
message: "Which agents do you use? " + dim2(hint),
|
|
479
596
|
choices: [
|
|
480
|
-
{ name: `Claude Code ${
|
|
481
|
-
{ name: `Codex ${
|
|
482
|
-
{ name: `Hermes ${
|
|
483
|
-
{ name: `Cursor ${
|
|
597
|
+
{ name: `Claude Code ${dim2("MCP + hooks + auto-allowed tools")}`, value: "claude_code", checked: detected.claude_code },
|
|
598
|
+
{ name: `Codex ${dim2("MCP + notify handler + auto-allowed tools")}`, value: "codex", checked: detected.codex },
|
|
599
|
+
{ name: `Hermes ${dim2("native plugin + auto-error notifications")}`, value: "hermes", checked: detected.hermes },
|
|
600
|
+
{ name: `Cursor ${dim2("MCP server")}`, value: "cursor", checked: detected.cursor }
|
|
484
601
|
]
|
|
485
602
|
});
|
|
486
603
|
await saveApiKey(trimmedKey);
|
|
487
604
|
if (agents.length === 0) {
|
|
488
605
|
console.log(`
|
|
489
|
-
${
|
|
606
|
+
${dim2("No agents selected. API key saved.")}`);
|
|
490
607
|
} else {
|
|
491
608
|
const failed = [];
|
|
492
609
|
for (const agent of agents) {
|
|
@@ -494,33 +611,34 @@ var main = async () => {
|
|
|
494
611
|
await AGENT_SETUP[agent](trimmedKey);
|
|
495
612
|
} catch (err) {
|
|
496
613
|
failed.push(agent.replace("_", " "));
|
|
497
|
-
console.log(` ${
|
|
498
|
-
console.log(` ${
|
|
614
|
+
console.log(` ${yellow2("!")} ${agent.replace("_", " ")} setup failed: ${formatError(err)}`);
|
|
615
|
+
console.log(` ${dim2("Other agents will continue. Re-run setup to retry.")}`);
|
|
499
616
|
}
|
|
500
617
|
}
|
|
501
618
|
if (failed.length > 0) {
|
|
502
619
|
console.log();
|
|
503
|
-
console.log(` ${
|
|
620
|
+
console.log(` ${yellow2("!")} Failed: ${failed.join(", ")} ${dim2("(others completed successfully)")}`);
|
|
504
621
|
}
|
|
505
622
|
}
|
|
506
|
-
const
|
|
507
|
-
if (
|
|
508
|
-
await
|
|
623
|
+
const skipPhone = process.argv.includes("--skip-phone");
|
|
624
|
+
if (skipPhone || !process.stdout.isTTY) {
|
|
625
|
+
await printConnectInstructions(trimmedKey);
|
|
626
|
+
} else {
|
|
627
|
+
await connectDevice(trimmedKey);
|
|
509
628
|
}
|
|
510
629
|
console.log();
|
|
511
|
-
console.log(` ${
|
|
630
|
+
console.log(` ${green2(bold2("Setup complete."))}`);
|
|
512
631
|
console.log();
|
|
513
|
-
console.log(` ${
|
|
514
|
-
console.log(` ${
|
|
515
|
-
console.log(` ${
|
|
516
|
-
console.log(` ${
|
|
517
|
-
console.log(` ${dim("4.")} Run ${cyan("npx @pushary/agent-hooks doctor")} to verify`);
|
|
632
|
+
console.log(` ${dim2("Next:")}`);
|
|
633
|
+
console.log(` ${dim2("1.")} Load your API key: ${cyan2("source ~/.zshrc")} ${dim2("(or open a new terminal)")}`);
|
|
634
|
+
console.log(` ${dim2("2.")} Restart your agent to load the new config`);
|
|
635
|
+
console.log(` ${dim2("3.")} Run ${cyan2("npx @pushary/agent-hooks doctor")} to verify`);
|
|
518
636
|
console.log();
|
|
519
637
|
};
|
|
520
638
|
main().catch((err) => {
|
|
521
639
|
console.log();
|
|
522
|
-
console.log(` ${
|
|
523
|
-
console.log(` ${
|
|
640
|
+
console.log(` ${yellow2("!")} Setup failed: ${formatError(err)}`);
|
|
641
|
+
console.log(` ${dim2("Run")} ${cyan2("npx @pushary/agent-hooks doctor")} ${dim2("after fixing the issue, then rerun setup.")}`);
|
|
524
642
|
console.log();
|
|
525
643
|
process.exit(1);
|
|
526
644
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pushary/agent-hooks",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.10.1",
|
|
4
4
|
"description": "Permission hooks for AI coding agents: route tool approvals through Pushary push notifications",
|
|
5
5
|
"author": "Pushary <business@pushary.com>",
|
|
6
6
|
"homepage": "https://pushary.com",
|
|
@@ -36,6 +36,7 @@
|
|
|
36
36
|
},
|
|
37
37
|
"dependencies": {
|
|
38
38
|
"@inquirer/prompts": "^8.4.2",
|
|
39
|
+
"qrcode-terminal": "^0.12.0",
|
|
39
40
|
"smol-toml": "^1.6.1"
|
|
40
41
|
},
|
|
41
42
|
"devDependencies": {
|