@pushary/agent-hooks 0.78.0 → 0.80.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 +62 -0
- package/README.md +52 -0
- package/data/SKILL.md +18 -5
- package/data/cursor-plugin/skills/pushary/SKILL.md +17 -4
- package/data/vscode-plugin/skills/pushary/SKILL.md +17 -4
- package/dist/bin/pushary-bell-hook.d.ts +1 -0
- package/dist/bin/pushary-bell-hook.js +22 -0
- package/dist/bin/pushary-bell.d.ts +1 -0
- package/dist/bin/pushary-bell.js +203 -0
- package/dist/bin/pushary-claude.js +8 -7
- package/dist/bin/pushary-clean.js +14 -14
- package/dist/bin/pushary-codex-hook.js +8 -7
- package/dist/bin/pushary-codex.js +3 -2
- package/dist/bin/pushary-connect.js +15 -14
- package/dist/bin/pushary-daemon.js +5 -5
- package/dist/bin/pushary-doctor.js +34 -64
- package/dist/bin/pushary-gemini-hook.js +8 -7
- package/dist/bin/pushary-hook.js +11 -10
- package/dist/bin/pushary-login.js +15 -14
- package/dist/bin/pushary-logout.js +12 -11
- package/dist/bin/pushary-mode.js +7 -7
- package/dist/bin/pushary-notification-hook.js +3 -2
- package/dist/bin/pushary-permission-denied-hook.js +7 -6
- package/dist/bin/pushary-permission-hook.js +7 -6
- package/dist/bin/pushary-post-hook.js +3 -2
- package/dist/bin/pushary-prompt-hook.js +3 -2
- package/dist/bin/pushary-session-end-hook.js +3 -2
- package/dist/bin/pushary-session-start-hook.js +3 -2
- package/dist/bin/pushary-setup.js +131 -41
- package/dist/bin/pushary-stats.js +7 -7
- package/dist/bin/pushary-status.js +17 -16
- package/dist/bin/pushary-stop-hook.js +3 -2
- package/dist/bin/pushary-stopfailure-hook.js +3 -2
- package/dist/bin/pushary-suggestions.js +4 -4
- package/dist/bin/pushary-upgrade.js +10 -10
- package/dist/bin/pushary-wait.js +9 -9
- package/dist/bin/pushary.js +6 -5
- package/dist/chunk-22EQOB2T.js +212 -0
- package/dist/{chunk-CC2K2SST.js → chunk-7DYFAM32.js} +1 -1
- package/dist/{chunk-FWWBEB6L.js → chunk-A5DAEWBZ.js} +2 -2
- package/dist/{chunk-MGFZXUUR.js → chunk-BOMF4C2Q.js} +114 -14
- package/dist/{chunk-HPDLDQC5.js → chunk-DUYM5SHH.js} +10 -1
- package/dist/{chunk-NHKWU7MN.js → chunk-E6I3DBJW.js} +8 -1
- package/dist/{chunk-DLBW37U6.js → chunk-EBP4YG75.js} +1 -1
- package/dist/{chunk-NQTRA7H5.js → chunk-GQLB2GBJ.js} +3 -3
- package/dist/{chunk-IA5GBKNL.js → chunk-MHA2WB7S.js} +1 -1
- package/dist/chunk-PPD3HHNJ.js +44 -0
- package/dist/{chunk-QCYN5VXB.js → chunk-Q4QULGAV.js} +1 -1
- package/dist/{chunk-3CBPY2G5.js → chunk-R6AYBATA.js} +1 -1
- package/dist/{chunk-BBK3TTU2.js → chunk-RRZZ7TFJ.js} +1 -1
- package/dist/{chunk-TJJBF73A.js → chunk-T6BNEQ2A.js} +23 -0
- package/dist/{chunk-7BBUTWBD.js → chunk-U2PZKYZM.js} +1 -1
- package/dist/{chunk-46UEJXQO.js → chunk-UBUPETEQ.js} +2 -2
- package/dist/{chunk-NN4GTN6Q.js → chunk-ULITFWLG.js} +8 -8
- package/dist/{chunk-IZA7SWXX.js → chunk-WYVKPLVT.js} +3 -3
- package/dist/{chunk-7OYGFJYZ.js → chunk-ZUKH2NPJ.js} +18 -0
- package/dist/src/index.js +7 -6
- package/package.json +4 -2
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
import {
|
|
2
|
+
readConfigFileKey
|
|
3
|
+
} from "./chunk-E6I3DBJW.js";
|
|
4
|
+
|
|
5
|
+
// src/bell/fleet.ts
|
|
6
|
+
import { mkdirSync, readdirSync, statSync, utimesSync, writeFileSync, unlinkSync } from "fs";
|
|
7
|
+
import { createHash } from "crypto";
|
|
8
|
+
import { tmpdir } from "os";
|
|
9
|
+
import { join } from "path";
|
|
10
|
+
var BELL_DIR = join(tmpdir(), "pushary-bell");
|
|
11
|
+
var SESSION_TTL_MS = 10 * 60 * 1e3;
|
|
12
|
+
var sessionFile = (sessionId) => join(BELL_DIR, createHash("sha256").update(sessionId).digest("hex").slice(0, 16));
|
|
13
|
+
var heartbeat = (sessionId, now = Date.now()) => {
|
|
14
|
+
try {
|
|
15
|
+
mkdirSync(BELL_DIR, { recursive: true });
|
|
16
|
+
const path = sessionFile(sessionId);
|
|
17
|
+
writeFileSync(path, "", "utf-8");
|
|
18
|
+
const seconds = now / 1e3;
|
|
19
|
+
utimesSync(path, seconds, seconds);
|
|
20
|
+
} catch {
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
var liveAgentCount = (now = Date.now()) => {
|
|
24
|
+
try {
|
|
25
|
+
let live = 0;
|
|
26
|
+
for (const name of readdirSync(BELL_DIR)) {
|
|
27
|
+
const path = join(BELL_DIR, name);
|
|
28
|
+
try {
|
|
29
|
+
if (now - statSync(path).mtimeMs < SESSION_TTL_MS) live += 1;
|
|
30
|
+
else unlinkSync(path);
|
|
31
|
+
} catch {
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
return live;
|
|
35
|
+
} catch {
|
|
36
|
+
return 0;
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
var recordAndCount = (sessionId, now = Date.now()) => {
|
|
40
|
+
heartbeat(sessionId, now);
|
|
41
|
+
return Math.max(1, liveAgentCount(now));
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
// src/bell/ring.ts
|
|
45
|
+
import { execFile } from "child_process";
|
|
46
|
+
import { openSync, writeSync, closeSync } from "fs";
|
|
47
|
+
var platformOf = (value = process.platform) => value === "darwin" || value === "linux" || value === "win32" ? value : "other";
|
|
48
|
+
var writeBell = () => {
|
|
49
|
+
try {
|
|
50
|
+
const fd = openSync("/dev/tty", "w");
|
|
51
|
+
try {
|
|
52
|
+
writeSync(fd, "\x07");
|
|
53
|
+
return true;
|
|
54
|
+
} finally {
|
|
55
|
+
closeSync(fd);
|
|
56
|
+
}
|
|
57
|
+
} catch {
|
|
58
|
+
try {
|
|
59
|
+
process.stderr.write("\x07");
|
|
60
|
+
return true;
|
|
61
|
+
} catch {
|
|
62
|
+
return false;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
var noticeCommand = (notice, platform = platformOf()) => {
|
|
67
|
+
const clean = (text) => text.replace(/["\\]/g, " ").replace(/\s+/g, " ").trim().slice(0, 120);
|
|
68
|
+
const title = clean(notice.title);
|
|
69
|
+
const body = clean(notice.body);
|
|
70
|
+
switch (platform) {
|
|
71
|
+
case "darwin":
|
|
72
|
+
return ["osascript", "-e", `display notification "${body}" with title "${title}"`];
|
|
73
|
+
case "linux":
|
|
74
|
+
return ["notify-send", title, body];
|
|
75
|
+
default:
|
|
76
|
+
return null;
|
|
77
|
+
}
|
|
78
|
+
};
|
|
79
|
+
var spawnNotifier = (argv) => {
|
|
80
|
+
const child = execFile(argv[0], argv.slice(1), { timeout: 5e3 }, () => {
|
|
81
|
+
});
|
|
82
|
+
child.unref?.();
|
|
83
|
+
};
|
|
84
|
+
var ring = (notice, deps = {}) => {
|
|
85
|
+
const bell = deps.bell ?? writeBell;
|
|
86
|
+
const notify = deps.notify ?? spawnNotifier;
|
|
87
|
+
const rang = bell();
|
|
88
|
+
const argv = noticeCommand(notice, deps.platform ?? platformOf());
|
|
89
|
+
if (!argv) return { rang, notified: false };
|
|
90
|
+
try {
|
|
91
|
+
notify(argv);
|
|
92
|
+
return { rang, notified: true };
|
|
93
|
+
} catch {
|
|
94
|
+
return { rang, notified: false };
|
|
95
|
+
}
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
// src/bell/upgrade.ts
|
|
99
|
+
import { existsSync, mkdirSync as mkdirSync2, statSync as statSync2, writeFileSync as writeFileSync2 } from "fs";
|
|
100
|
+
import { tmpdir as tmpdir2 } from "os";
|
|
101
|
+
import { join as join2 } from "path";
|
|
102
|
+
var DEFAULT_UPGRADE_THRESHOLD = 3;
|
|
103
|
+
var UPGRADE_MARKER = join2(tmpdir2(), "pushary-bell-upgrade-shown");
|
|
104
|
+
var ONCE_PER = 24 * 60 * 60 * 1e3;
|
|
105
|
+
var upgradeThreshold = (env = process.env) => {
|
|
106
|
+
const raw = Number(env.PUSHARY_BELL_UPGRADE_AT);
|
|
107
|
+
if (!Number.isFinite(raw) || raw < 1) return DEFAULT_UPGRADE_THRESHOLD;
|
|
108
|
+
return Math.floor(raw);
|
|
109
|
+
};
|
|
110
|
+
var readLastShown = () => {
|
|
111
|
+
try {
|
|
112
|
+
return existsSync(UPGRADE_MARKER) ? statSync2(UPGRADE_MARKER).mtimeMs : null;
|
|
113
|
+
} catch {
|
|
114
|
+
return null;
|
|
115
|
+
}
|
|
116
|
+
};
|
|
117
|
+
var writeLastShown = (at) => {
|
|
118
|
+
try {
|
|
119
|
+
mkdirSync2(tmpdir2(), { recursive: true });
|
|
120
|
+
writeFileSync2(UPGRADE_MARKER, String(at), "utf-8");
|
|
121
|
+
} catch {
|
|
122
|
+
}
|
|
123
|
+
};
|
|
124
|
+
var upgradeLine = (context) => ` ${context.liveAgents} agents running. A bell can't tell you which one needs you, and it can't reach your pocket.
|
|
125
|
+
pushary connect`;
|
|
126
|
+
var upgradeNotice = (context, deps = {}) => {
|
|
127
|
+
const env = deps.env ?? process.env;
|
|
128
|
+
if (env.PUSHARY_BELL_QUIET === "1" || env.PUSHARY_BELL_QUIET === "true") return null;
|
|
129
|
+
if (context.connected) return null;
|
|
130
|
+
if (context.liveAgents < upgradeThreshold(env)) return null;
|
|
131
|
+
const now = (deps.now ?? Date.now)();
|
|
132
|
+
const last = (deps.lastShown ?? readLastShown)();
|
|
133
|
+
if (last !== null && now - last < ONCE_PER) return null;
|
|
134
|
+
(deps.markShown ?? writeLastShown)(now);
|
|
135
|
+
return upgradeLine(context);
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
// src/bell/handler.ts
|
|
139
|
+
import { basename } from "path";
|
|
140
|
+
var FINISHED_NOTIFICATIONS = /* @__PURE__ */ new Set(["agent_completed"]);
|
|
141
|
+
var BLOCKED_NOTIFICATIONS = /* @__PURE__ */ new Set(["idle_prompt", "agent_needs_input"]);
|
|
142
|
+
var SUMMARY_MAX = 100;
|
|
143
|
+
var summarize = (message, fallback) => {
|
|
144
|
+
if (typeof message !== "string") return fallback;
|
|
145
|
+
const collapsed = message.replace(/\s+/g, " ").trim();
|
|
146
|
+
return collapsed ? collapsed.slice(0, SUMMARY_MAX) : fallback;
|
|
147
|
+
};
|
|
148
|
+
var decideBell = (input) => {
|
|
149
|
+
const project = basename(input.cwd ?? process.cwd());
|
|
150
|
+
const notifType = input.notification_type ?? input.type ?? "";
|
|
151
|
+
if (FINISHED_NOTIFICATIONS.has(notifType)) {
|
|
152
|
+
return {
|
|
153
|
+
reason: "finished",
|
|
154
|
+
title: `${project}: done`,
|
|
155
|
+
body: summarize(input.message ?? input.last_assistant_message, "Your agent finished.")
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
if (BLOCKED_NOTIFICATIONS.has(notifType)) {
|
|
159
|
+
return {
|
|
160
|
+
reason: "needs-input",
|
|
161
|
+
title: `${project}: your agent needs you`,
|
|
162
|
+
body: summarize(input.message, "Waiting for your input.")
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
return null;
|
|
166
|
+
};
|
|
167
|
+
var handleBell = (input, deps = {}) => {
|
|
168
|
+
const idle = { rang: false, reason: null, liveAgents: 0, upgrade: null };
|
|
169
|
+
try {
|
|
170
|
+
const decision = decideBell(input);
|
|
171
|
+
if (!decision) return idle;
|
|
172
|
+
const count = deps.count ?? ((sessionId) => recordAndCount(sessionId));
|
|
173
|
+
const liveAgents = count(input.session_id || "default");
|
|
174
|
+
const result = (deps.ring ?? ring)({ title: decision.title, body: decision.body }, deps.ringDeps);
|
|
175
|
+
const notice = upgradeNotice(
|
|
176
|
+
{
|
|
177
|
+
agentName: decision.title,
|
|
178
|
+
liveAgents,
|
|
179
|
+
connected: (deps.connected ?? hasKey)()
|
|
180
|
+
},
|
|
181
|
+
deps.upgradeDeps
|
|
182
|
+
);
|
|
183
|
+
if (notice) (deps.write ?? writeStderr)(`
|
|
184
|
+
${notice}
|
|
185
|
+
`);
|
|
186
|
+
return { rang: result.rang, reason: decision.reason, liveAgents, upgrade: notice };
|
|
187
|
+
} catch {
|
|
188
|
+
return idle;
|
|
189
|
+
}
|
|
190
|
+
};
|
|
191
|
+
var writeStderr = (line) => {
|
|
192
|
+
try {
|
|
193
|
+
process.stderr.write(line);
|
|
194
|
+
} catch {
|
|
195
|
+
}
|
|
196
|
+
};
|
|
197
|
+
var hasKey = () => {
|
|
198
|
+
try {
|
|
199
|
+
if (process.env.PUSHARY_API_KEY?.trim()) return true;
|
|
200
|
+
return Boolean(readConfigFileKey());
|
|
201
|
+
} catch {
|
|
202
|
+
return false;
|
|
203
|
+
}
|
|
204
|
+
};
|
|
205
|
+
|
|
206
|
+
export {
|
|
207
|
+
liveAgentCount,
|
|
208
|
+
ring,
|
|
209
|
+
DEFAULT_UPGRADE_THRESHOLD,
|
|
210
|
+
upgradeThreshold,
|
|
211
|
+
handleBell
|
|
212
|
+
};
|
|
@@ -4,12 +4,12 @@ import {
|
|
|
4
4
|
isModeStateDegraded,
|
|
5
5
|
resolveAutoResolveOrigin,
|
|
6
6
|
resolvePolicyAcross
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-DUYM5SHH.js";
|
|
8
8
|
import {
|
|
9
9
|
evaluateScope,
|
|
10
10
|
scopeChangeReason,
|
|
11
11
|
scopeRequiresHuman
|
|
12
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-T6BNEQ2A.js";
|
|
13
13
|
|
|
14
14
|
// src/answer.ts
|
|
15
15
|
var denyReasonFrom = (value, note) => {
|
|
@@ -6,6 +6,9 @@ import {
|
|
|
6
6
|
import {
|
|
7
7
|
callMcpTool
|
|
8
8
|
} from "./chunk-BSZYIAZL.js";
|
|
9
|
+
import {
|
|
10
|
+
reportedInstallSource
|
|
11
|
+
} from "./chunk-PPD3HHNJ.js";
|
|
9
12
|
|
|
10
13
|
// src/reach.ts
|
|
11
14
|
var reachVerdict = (channels) => {
|
|
@@ -67,19 +70,6 @@ var describeAppRepair = (verdict) => {
|
|
|
67
70
|
}
|
|
68
71
|
};
|
|
69
72
|
|
|
70
|
-
// src/cli/human.ts
|
|
71
|
-
var stream = process.stdout;
|
|
72
|
-
var setHumanStream = (target) => {
|
|
73
|
-
stream = target;
|
|
74
|
-
};
|
|
75
|
-
var writeHuman = (text) => {
|
|
76
|
-
stream.write(text);
|
|
77
|
-
};
|
|
78
|
-
var humanIsTty = () => stream === process.stdout && process.stdout.isTTY === true;
|
|
79
|
-
|
|
80
|
-
// src/onboarding.ts
|
|
81
|
-
import qrcodeTerminal from "qrcode-terminal";
|
|
82
|
-
|
|
83
73
|
// src/setup/verify.ts
|
|
84
74
|
var plural2 = (count, one) => `${count} ${one}${count === 1 ? "" : "s"}`;
|
|
85
75
|
var describeRecipients = (push, channels) => {
|
|
@@ -168,8 +158,111 @@ var describeVerifyOutcome = (outcome) => {
|
|
|
168
158
|
};
|
|
169
159
|
}
|
|
170
160
|
};
|
|
161
|
+
var ROUND_TRIP_QUESTION = "Setup is nearly done. Tap Yes and this machine is connected.";
|
|
162
|
+
var ROUND_TRIP_TIMEOUT_MS = 55e3;
|
|
163
|
+
var defaultAsk = (apiKey, agentName) => (
|
|
164
|
+
// wait:false so the poll is ours: the blocking form returns on the SITE's
|
|
165
|
+
// policy timeout, which a user may have set to seconds, and a setup check
|
|
166
|
+
// reporting "unanswered" after three seconds would be a lie about the product.
|
|
167
|
+
callMcpTool(apiKey, "ask_user", {
|
|
168
|
+
question: ROUND_TRIP_QUESTION,
|
|
169
|
+
type: "confirm",
|
|
170
|
+
agentName,
|
|
171
|
+
wait: false
|
|
172
|
+
}, { timeoutMs: 15e3 })
|
|
173
|
+
);
|
|
174
|
+
var defaultWait = (apiKey, correlationId) => callMcpTool(apiKey, "wait_for_answer", {
|
|
175
|
+
correlationId,
|
|
176
|
+
timeoutMs: ROUND_TRIP_TIMEOUT_MS
|
|
177
|
+
}, { timeoutMs: ROUND_TRIP_TIMEOUT_MS + 5e3 });
|
|
178
|
+
var defaultCancel = async (apiKey, correlationId) => {
|
|
179
|
+
await callMcpTool(apiKey, "cancel_question", { correlationId }, { timeoutMs: 1e4 }).catch(() => {
|
|
180
|
+
});
|
|
181
|
+
};
|
|
182
|
+
var verifyRoundTrip = async (apiKey, agentName = "Pushary setup", deps = {}) => {
|
|
183
|
+
const ask = deps.ask ?? defaultAsk;
|
|
184
|
+
const wait = deps.wait ?? defaultWait;
|
|
185
|
+
const cancel = deps.cancel ?? defaultCancel;
|
|
186
|
+
const now = deps.now ?? Date.now;
|
|
187
|
+
const started = now();
|
|
188
|
+
let asked;
|
|
189
|
+
try {
|
|
190
|
+
asked = await ask(apiKey, agentName);
|
|
191
|
+
} catch (err) {
|
|
192
|
+
return { kind: "error", detail: err instanceof Error ? err.message : "ask failed" };
|
|
193
|
+
}
|
|
194
|
+
if (asked.noDevices || asked.warning) return { kind: "no-recipients" };
|
|
195
|
+
if (asked.mode === "notify_only" || asked.status === "notified") return { kind: "notify-only" };
|
|
196
|
+
if (!asked.correlationId) return { kind: "error", detail: "no correlationId returned" };
|
|
197
|
+
if (asked.answered === true) {
|
|
198
|
+
return { kind: "answered", value: asked.value ?? "yes", latencyMs: now() - started };
|
|
199
|
+
}
|
|
200
|
+
let answer;
|
|
201
|
+
try {
|
|
202
|
+
answer = await wait(apiKey, asked.correlationId);
|
|
203
|
+
} catch (err) {
|
|
204
|
+
await cancel(apiKey, asked.correlationId);
|
|
205
|
+
return { kind: "error", detail: err instanceof Error ? err.message : "wait failed" };
|
|
206
|
+
}
|
|
207
|
+
if (answer.answered === true) {
|
|
208
|
+
return { kind: "answered", value: answer.value ?? "yes", latencyMs: now() - started };
|
|
209
|
+
}
|
|
210
|
+
await cancel(apiKey, asked.correlationId);
|
|
211
|
+
return { kind: "unanswered" };
|
|
212
|
+
};
|
|
213
|
+
var describeRoundTrip = (outcome) => {
|
|
214
|
+
switch (outcome.kind) {
|
|
215
|
+
case "answered":
|
|
216
|
+
return {
|
|
217
|
+
ok: true,
|
|
218
|
+
message: `Answered "${outcome.value}" in ${(outcome.latencyMs / 1e3).toFixed(1)}s. Your agent can reach you and you can answer it.`,
|
|
219
|
+
next: []
|
|
220
|
+
};
|
|
221
|
+
case "unanswered":
|
|
222
|
+
return {
|
|
223
|
+
ok: false,
|
|
224
|
+
message: "The question was sent and nothing came back.",
|
|
225
|
+
next: [
|
|
226
|
+
"A push that arrives and cannot be answered is the most common way this breaks, so this is worth resolving now.",
|
|
227
|
+
"Check the notification actually opened something you could tap, then run pushary doctor --roundtrip."
|
|
228
|
+
]
|
|
229
|
+
};
|
|
230
|
+
case "notify-only":
|
|
231
|
+
return {
|
|
232
|
+
ok: true,
|
|
233
|
+
message: "Sent. This site is in notify-only mode, so questions never wait for an answer.",
|
|
234
|
+
next: ["Switch with pushary mode push_first if you want approvals to hold for your phone."]
|
|
235
|
+
};
|
|
236
|
+
case "no-recipients":
|
|
237
|
+
return {
|
|
238
|
+
ok: false,
|
|
239
|
+
message: "Nothing to ask. No phone or browser is connected to this site.",
|
|
240
|
+
next: [
|
|
241
|
+
"Run pushary connect to add your phone.",
|
|
242
|
+
"Or pushary connect --app if you already have the Pushary app."
|
|
243
|
+
]
|
|
244
|
+
};
|
|
245
|
+
case "error":
|
|
246
|
+
return {
|
|
247
|
+
ok: false,
|
|
248
|
+
message: `Could not complete the round trip (${outcome.detail}).`,
|
|
249
|
+
next: ["Agents are configured. Run pushary doctor --roundtrip once the connection is back."]
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
};
|
|
253
|
+
|
|
254
|
+
// src/cli/human.ts
|
|
255
|
+
var stream = process.stdout;
|
|
256
|
+
var setHumanStream = (target) => {
|
|
257
|
+
stream = target;
|
|
258
|
+
};
|
|
259
|
+
var writeHuman = (text) => {
|
|
260
|
+
stream.write(text);
|
|
261
|
+
};
|
|
262
|
+
var humanIsTty = () => stream === process.stdout && process.stdout.isTTY === true;
|
|
171
263
|
|
|
172
264
|
// src/onboarding.ts
|
|
265
|
+
import qrcodeTerminal from "qrcode-terminal";
|
|
173
266
|
var dim = (s) => `\x1B[2m${s}\x1B[0m`;
|
|
174
267
|
var bold = (s) => `\x1B[1m${s}\x1B[0m`;
|
|
175
268
|
var green = (s) => `\x1B[32m${s}\x1B[0m`;
|
|
@@ -528,7 +621,12 @@ var startPairing = async (cliPublicKey) => {
|
|
|
528
621
|
const res = await fetch(`${apiBase()}/api/mobile/pair/start`, {
|
|
529
622
|
method: "POST",
|
|
530
623
|
headers: { "Content-Type": "application/json" },
|
|
531
|
-
|
|
624
|
+
// The only moment this fact can be carried across the handshake. This
|
|
625
|
+
// terminal knows which front door it came through and has no session; the
|
|
626
|
+
// phone that authorizes has the session and mints the key but never saw
|
|
627
|
+
// this terminal. Sending it here is what lets the minted key name its
|
|
628
|
+
// origin. An older server ignores the extra field.
|
|
629
|
+
body: JSON.stringify({ cliPublicKey, installSource: reportedInstallSource() }),
|
|
532
630
|
signal: AbortSignal.timeout(1e4)
|
|
533
631
|
});
|
|
534
632
|
if (!res.ok) return null;
|
|
@@ -687,6 +785,8 @@ var connectViaAppPairing = async (options = {}) => {
|
|
|
687
785
|
export {
|
|
688
786
|
reachVerdict,
|
|
689
787
|
describeReach,
|
|
788
|
+
verifyRoundTrip,
|
|
789
|
+
describeRoundTrip,
|
|
690
790
|
setHumanStream,
|
|
691
791
|
fetchChannels,
|
|
692
792
|
connectDevice,
|
|
@@ -5,6 +5,9 @@ import {
|
|
|
5
5
|
import {
|
|
6
6
|
installProxyDispatcher
|
|
7
7
|
} from "./chunk-SAF6HGAA.js";
|
|
8
|
+
import {
|
|
9
|
+
reportedInstallSource
|
|
10
|
+
} from "./chunk-PPD3HHNJ.js";
|
|
8
11
|
import {
|
|
9
12
|
ACTION_BODY_MAX,
|
|
10
13
|
DECISION_LINE_MAX,
|
|
@@ -20,7 +23,7 @@ import {
|
|
|
20
23
|
policyArgForms,
|
|
21
24
|
redactSecrets,
|
|
22
25
|
redactSecretsDeep
|
|
23
|
-
} from "./chunk-
|
|
26
|
+
} from "./chunk-T6BNEQ2A.js";
|
|
24
27
|
import {
|
|
25
28
|
getMachineId
|
|
26
29
|
} from "./chunk-RN3NOEJF.js";
|
|
@@ -1033,6 +1036,12 @@ var reportEvent = async (event, options = {}) => {
|
|
|
1033
1036
|
pid: event.pid ?? process.pid,
|
|
1034
1037
|
repoKey: event.repoKey ?? repoKeyFor(),
|
|
1035
1038
|
installMode: detectInstallMode(),
|
|
1039
|
+
// How the binary was INVOKED this time and which front door produced the
|
|
1040
|
+
// install are different questions, and only the second is attribution.
|
|
1041
|
+
// A machine installed from the plugin marketplace that later runs
|
|
1042
|
+
// `npx ... doctor` reports npx for that invocation and claude_plugin as
|
|
1043
|
+
// its origin, permanently.
|
|
1044
|
+
installSource: reportedInstallSource(),
|
|
1036
1045
|
// Link-back for a phone-spawned session: forward the spawn id the daemon
|
|
1037
1046
|
// set in the environment so the server can map spawnId -> sessionId.
|
|
1038
1047
|
...process.env.PUSHARY_SPAWN_ID ? { spawnId: process.env.PUSHARY_SPAWN_ID } : {},
|
|
@@ -7,6 +7,9 @@ import {
|
|
|
7
7
|
import {
|
|
8
8
|
resolveShellRc
|
|
9
9
|
} from "./chunk-BC3VCZ3E.js";
|
|
10
|
+
import {
|
|
11
|
+
resolveInstallSourceFor
|
|
12
|
+
} from "./chunk-PPD3HHNJ.js";
|
|
10
13
|
import {
|
|
11
14
|
configFilePath
|
|
12
15
|
} from "./chunk-2UMNXADU.js";
|
|
@@ -116,7 +119,11 @@ var writeKey = (apiKey, options = {}) => {
|
|
|
116
119
|
} else if (existing.kind === "unparseable") {
|
|
117
120
|
configBackupPath = backupFile(configPath);
|
|
118
121
|
}
|
|
119
|
-
writeJsonAtomic(
|
|
122
|
+
writeJsonAtomic(
|
|
123
|
+
configPath,
|
|
124
|
+
{ ...current, apiKey, installSource: resolveInstallSourceFor(current) },
|
|
125
|
+
KEY_FILE_MODE
|
|
126
|
+
);
|
|
120
127
|
restrictFile(configPath);
|
|
121
128
|
const target = (options.resolveRc ?? resolveRcTarget)();
|
|
122
129
|
const rc = target ? rewriteRcExport(target.path, apiKey, target.shell) : rcResult("no-rc");
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import {
|
|
2
|
+
normalizeInstallSource
|
|
3
|
+
} from "./chunk-T6BNEQ2A.js";
|
|
4
|
+
import {
|
|
5
|
+
configFilePath
|
|
6
|
+
} from "./chunk-2UMNXADU.js";
|
|
7
|
+
|
|
8
|
+
// src/install-source.ts
|
|
9
|
+
import { readFileSync } from "fs";
|
|
10
|
+
var ENV_OVERRIDE = "PUSHARY_INSTALL_SOURCE";
|
|
11
|
+
var PLUGIN_ROOT = "CLAUDE_PLUGIN_ROOT";
|
|
12
|
+
var EDITOR_PLUGIN_MARKERS = ["cursor-plugin", "vscode-plugin"];
|
|
13
|
+
var detectInstallSource = (env = process.env, argv = process.argv) => {
|
|
14
|
+
const declared = env[ENV_OVERRIDE]?.trim();
|
|
15
|
+
if (declared) return normalizeInstallSource(declared);
|
|
16
|
+
const pluginRoot = env[PLUGIN_ROOT]?.trim();
|
|
17
|
+
if (pluginRoot) {
|
|
18
|
+
return EDITOR_PLUGIN_MARKERS.some((marker) => pluginRoot.includes(marker)) ? "editor_plugin" : "claude_plugin";
|
|
19
|
+
}
|
|
20
|
+
const entry = argv[1] ?? "";
|
|
21
|
+
if (entry.includes("_npx")) return "npx";
|
|
22
|
+
if (entry) return "npm";
|
|
23
|
+
return "unknown";
|
|
24
|
+
};
|
|
25
|
+
var storedInstallSource = () => {
|
|
26
|
+
try {
|
|
27
|
+
const parsed = JSON.parse(readFileSync(configFilePath(), "utf-8"));
|
|
28
|
+
const stored = parsed.installSource;
|
|
29
|
+
return typeof stored === "string" && stored ? normalizeInstallSource(stored) : void 0;
|
|
30
|
+
} catch {
|
|
31
|
+
return void 0;
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
var resolveInstallSourceFor = (config) => {
|
|
35
|
+
const stored = config.installSource;
|
|
36
|
+
if (typeof stored === "string" && stored) return normalizeInstallSource(stored);
|
|
37
|
+
return detectInstallSource();
|
|
38
|
+
};
|
|
39
|
+
var reportedInstallSource = () => storedInstallSource() ?? detectInstallSource();
|
|
40
|
+
|
|
41
|
+
export {
|
|
42
|
+
resolveInstallSourceFor,
|
|
43
|
+
reportedInstallSource
|
|
44
|
+
};
|
|
@@ -1,6 +1,28 @@
|
|
|
1
1
|
// ../contracts/src/index.ts
|
|
2
2
|
var APPROVAL_MODES = ["push_only", "terminal_only", "push_first", "notify_only"];
|
|
3
3
|
var isApprovalMode = (value) => typeof value === "string" && APPROVAL_MODES.includes(value);
|
|
4
|
+
var INSTALL_SOURCES = [
|
|
5
|
+
/** `npx @pushary/agent-hooks@latest ...`, the documented one-liner. */
|
|
6
|
+
"npx",
|
|
7
|
+
/** A global or project npm install, invoked as `pushary`. */
|
|
8
|
+
"npm",
|
|
9
|
+
/** Claude Code plugin, from the marketplace. Detected via CLAUDE_PLUGIN_ROOT. */
|
|
10
|
+
"claude_plugin",
|
|
11
|
+
/** Cursor or VS Code plugin bundle. */
|
|
12
|
+
"editor_plugin",
|
|
13
|
+
/** Installed by the skills.sh wizard. */
|
|
14
|
+
"skills_sh",
|
|
15
|
+
/** An MCP client configured straight from the registry entry, no CLI involved. */
|
|
16
|
+
"mcp_registry",
|
|
17
|
+
/** One of the published framework adapters (ai-sdk, mastra, langgraph, ...). */
|
|
18
|
+
"framework_adapter",
|
|
19
|
+
/** The browser onboarding wizard. */
|
|
20
|
+
"dashboard",
|
|
21
|
+
/** Reported nothing, or reported something we do not recognise. */
|
|
22
|
+
"unknown"
|
|
23
|
+
];
|
|
24
|
+
var isInstallSource = (value) => typeof value === "string" && INSTALL_SOURCES.includes(value);
|
|
25
|
+
var normalizeInstallSource = (value) => isInstallSource(value) ? value : "unknown";
|
|
4
26
|
var HOOK_BUDGETS = {
|
|
5
27
|
claude: { budgetSeconds: 120, guardSeconds: 10 },
|
|
6
28
|
codex: { budgetSeconds: 180, guardSeconds: 10 },
|
|
@@ -760,6 +782,7 @@ var isScopeContract = (value) => {
|
|
|
760
782
|
|
|
761
783
|
export {
|
|
762
784
|
isApprovalMode,
|
|
785
|
+
normalizeInstallSource,
|
|
763
786
|
HOOK_BUDGETS,
|
|
764
787
|
hookWaitDeadline,
|
|
765
788
|
hookWaitClamped,
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import {
|
|
2
2
|
hasGeminiHooks
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-7DYFAM32.js";
|
|
4
4
|
import {
|
|
5
5
|
hasCodexHooks
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-Q4QULGAV.js";
|
|
7
7
|
|
|
8
8
|
// src/diagnostics/wiring.ts
|
|
9
9
|
var record = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
@@ -1,11 +1,15 @@
|
|
|
1
1
|
import {
|
|
2
2
|
PRETOOLUSE_HANDLED_TOOLS
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-R6AYBATA.js";
|
|
4
|
+
import {
|
|
5
|
+
isGatingMoment,
|
|
6
|
+
recordKeylessMoment
|
|
7
|
+
} from "./chunk-MHA2WB7S.js";
|
|
4
8
|
import {
|
|
5
9
|
denyReasonFrom,
|
|
6
10
|
isDeferAnswer,
|
|
7
11
|
resolveGate
|
|
8
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-A5DAEWBZ.js";
|
|
9
13
|
import {
|
|
10
14
|
DEFAULT_SESSION,
|
|
11
15
|
askUser,
|
|
@@ -25,17 +29,13 @@ import {
|
|
|
25
29
|
sendNotification,
|
|
26
30
|
throttlePass,
|
|
27
31
|
waitForAnswer
|
|
28
|
-
} from "./chunk-
|
|
29
|
-
import {
|
|
30
|
-
isGatingMoment,
|
|
31
|
-
recordKeylessMoment
|
|
32
|
-
} from "./chunk-IA5GBKNL.js";
|
|
32
|
+
} from "./chunk-DUYM5SHH.js";
|
|
33
33
|
import {
|
|
34
34
|
buildDecisionEpisodeFeatures,
|
|
35
35
|
effectiveWaitSeconds,
|
|
36
36
|
hookWaitClamped,
|
|
37
37
|
hookWaitDeadline
|
|
38
|
-
} from "./chunk-
|
|
38
|
+
} from "./chunk-T6BNEQ2A.js";
|
|
39
39
|
import {
|
|
40
40
|
getMachineId
|
|
41
41
|
} from "./chunk-RN3NOEJF.js";
|