@pushary/agent-hooks 0.83.2 → 0.84.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 +75 -0
- package/dist/bin/pushary-bell-hook.js +5 -5
- package/dist/bin/pushary-bell.js +15 -14
- package/dist/bin/pushary-claude.js +72 -131
- package/dist/bin/pushary-clean.js +13 -12
- package/dist/bin/pushary-codex-bridge.d.ts +1 -0
- package/dist/bin/pushary-codex-bridge.js +348 -0
- package/dist/bin/pushary-codex-hook.js +15 -14
- package/dist/bin/pushary-codex.js +7 -7
- package/dist/bin/pushary-connect.js +13 -12
- package/dist/bin/pushary-daemon.js +313 -41
- package/dist/bin/pushary-doctor.js +48 -25
- package/dist/bin/pushary-gemini-bridge.d.ts +1 -0
- package/dist/bin/pushary-gemini-bridge.js +298 -0
- package/dist/bin/pushary-gemini-hook.js +24 -20
- package/dist/bin/pushary-hook.js +14 -13
- package/dist/bin/pushary-login.js +15 -14
- package/dist/bin/pushary-logout.js +12 -11
- package/dist/bin/pushary-mode.js +10 -9
- package/dist/bin/pushary-notification-hook.js +5 -5
- package/dist/bin/pushary-permission-denied-hook.js +9 -9
- package/dist/bin/pushary-permission-hook.js +9 -9
- package/dist/bin/pushary-post-hook.js +5 -5
- package/dist/bin/pushary-prompt-hook.js +5 -5
- package/dist/bin/pushary-session-end-hook.js +5 -5
- package/dist/bin/pushary-session-start-hook.js +5 -5
- package/dist/bin/pushary-setup.js +42 -36
- package/dist/bin/pushary-stats.js +6 -5
- package/dist/bin/pushary-status.js +17 -16
- package/dist/bin/pushary-stop-hook.js +5 -5
- package/dist/bin/pushary-stopfailure-hook.js +5 -5
- package/dist/bin/pushary-suggestions.js +7 -6
- package/dist/bin/pushary-upgrade.js +18 -17
- package/dist/bin/pushary-wait.js +10 -9
- package/dist/bin/pushary.js +8 -6
- package/dist/{chunk-4QQOKEOS.js → chunk-37AD5K7W.js} +6 -6
- package/dist/{chunk-SAA3VBDK.js → chunk-437T77TY.js} +3 -3
- package/dist/{chunk-U27BZWFT.js → chunk-6LC4WSCL.js} +1 -1
- package/dist/{chunk-7WWZPII5.js → chunk-7PZPJSWI.js} +1 -1
- package/dist/chunk-CCH775Y6.js +343 -0
- package/dist/chunk-DINDL2CF.js +45 -0
- package/dist/{chunk-TXF4QZSQ.js → chunk-DOXSWT4Q.js} +1 -1
- package/dist/chunk-EVJ6ULHN.js +153 -0
- package/dist/{chunk-QR6MVVQ7.js → chunk-F52SIIDG.js} +4 -4
- package/dist/{chunk-26D2YMDU.js → chunk-FBGBJXVC.js} +1 -1
- package/dist/{chunk-QF7WTF2C.js → chunk-HZNOAJGB.js} +2 -2
- package/dist/{chunk-RRZZ7TFJ.js → chunk-J4KFVOCQ.js} +4 -2
- package/dist/{chunk-WDRRULBH.js → chunk-K7CSRGKZ.js} +4 -4
- package/dist/{chunk-ZUKH2NPJ.js → chunk-MGJBC6DD.js} +1 -44
- package/dist/{chunk-GQLB2GBJ.js → chunk-MHBC2XUP.js} +1 -1
- package/dist/{chunk-YWRJGYUH.js → chunk-MQMPG5X4.js} +28 -2
- package/dist/{chunk-3AGM3TDM.js → chunk-NJ7JT26G.js} +1 -1
- package/dist/chunk-NRGRUOAR.js +25 -0
- package/dist/chunk-NWYGW7HY.js +117 -0
- package/dist/{chunk-7PO2VEA4.js → chunk-OPMSMF5R.js} +38 -33
- package/dist/{chunk-DHAZ6SOT.js → chunk-QNBA5OXQ.js} +81 -25
- package/dist/{chunk-EHLTWCDZ.js → chunk-SEAHGHCE.js} +1 -1
- package/dist/{chunk-NDC3TBHI.js → chunk-UKSQWTBH.js} +1 -1
- package/dist/{chunk-UVAC2KHH.js → chunk-UXWEE3QI.js} +1 -1
- package/dist/{chunk-MUEW424A.js → chunk-VT4IVERX.js} +50 -3
- package/dist/chunk-YJCMI5GF.js +133 -0
- package/dist/src/index.d.ts +6 -3
- package/dist/src/index.js +9 -9
- package/package.json +3 -1
|
@@ -0,0 +1,343 @@
|
|
|
1
|
+
import {
|
|
2
|
+
needsShell,
|
|
3
|
+
spawnClaude
|
|
4
|
+
} from "./chunk-XHKBHWLX.js";
|
|
5
|
+
import {
|
|
6
|
+
resolveGlobalBinary
|
|
7
|
+
} from "./chunk-VT4IVERX.js";
|
|
8
|
+
import {
|
|
9
|
+
pusharyDir
|
|
10
|
+
} from "./chunk-SEAHGHCE.js";
|
|
11
|
+
import {
|
|
12
|
+
getMachineId
|
|
13
|
+
} from "./chunk-RN3NOEJF.js";
|
|
14
|
+
import {
|
|
15
|
+
getPackageVersion
|
|
16
|
+
} from "./chunk-DINDL2CF.js";
|
|
17
|
+
import {
|
|
18
|
+
readJsonSafe,
|
|
19
|
+
writeJsonAtomic
|
|
20
|
+
} from "./chunk-6MTNS63X.js";
|
|
21
|
+
import {
|
|
22
|
+
getApiKey
|
|
23
|
+
} from "./chunk-2UMNXADU.js";
|
|
24
|
+
|
|
25
|
+
// src/daemon-lifecycle.ts
|
|
26
|
+
import { dirname, join } from "path";
|
|
27
|
+
import { existsSync, mkdirSync, renameSync, unlinkSync, writeFileSync } from "fs";
|
|
28
|
+
import { createHash, randomUUID } from "crypto";
|
|
29
|
+
var daemonNeedsHandoff = (status) => status.kind === "version-mismatch" || status.kind === "configuration-mismatch" || status.kind === "unresponsive" && status.processAlive === true;
|
|
30
|
+
var statePath = (dir) => join(dir, "daemon-state.json");
|
|
31
|
+
var lockPath = (dir) => join(dir, "daemon.lock");
|
|
32
|
+
var targetPath = (dir) => join(dir, "daemon-target.json");
|
|
33
|
+
var DAEMON_LEASE_RENEW_INTERVAL_MS = 3e4;
|
|
34
|
+
var LOCAL_LEASE_TIMEOUT_MS = DAEMON_LEASE_RENEW_INTERVAL_MS * 3;
|
|
35
|
+
var buildDaemonLaunch = (input) => input.siblingEntry ? { command: input.execPath, args: [input.siblingEntry], shell: false } : {
|
|
36
|
+
command: input.daemonBinary,
|
|
37
|
+
args: [],
|
|
38
|
+
shell: needsShell(input.daemonBinary, input.platform ?? process.platform)
|
|
39
|
+
};
|
|
40
|
+
var spawnDetachedDaemon = (plan, options = {}) => {
|
|
41
|
+
const spawnImpl = options.spawnImpl ?? spawnClaude;
|
|
42
|
+
const child = spawnImpl(plan.command, plan.args, {
|
|
43
|
+
detached: true,
|
|
44
|
+
stdio: "ignore",
|
|
45
|
+
windowsHide: true,
|
|
46
|
+
shell: plan.shell
|
|
47
|
+
});
|
|
48
|
+
child.once?.("error", () => {
|
|
49
|
+
});
|
|
50
|
+
child.unref();
|
|
51
|
+
return typeof child.pid === "number" ? child.pid : null;
|
|
52
|
+
};
|
|
53
|
+
var processIsAlive = (pid) => {
|
|
54
|
+
try {
|
|
55
|
+
process.kill(pid, 0);
|
|
56
|
+
return true;
|
|
57
|
+
} catch (err) {
|
|
58
|
+
return err.code === "EPERM";
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
var isString = (value) => typeof value === "string" && value.length > 0;
|
|
62
|
+
var isTimestamp = (value) => isString(value) && Number.isFinite(Date.parse(value));
|
|
63
|
+
var currentConfigurationId = () => {
|
|
64
|
+
try {
|
|
65
|
+
return createHash("sha256").update(getApiKey()).digest("hex").slice(0, 16);
|
|
66
|
+
} catch {
|
|
67
|
+
return null;
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
var parseDaemonState = (value) => {
|
|
71
|
+
if (typeof value !== "object" || value === null) return null;
|
|
72
|
+
const state = value;
|
|
73
|
+
if (!isString(state.instanceId) || !Number.isInteger(state.pid) || state.pid <= 0 || !isString(state.version) || !(state.configurationId === void 0 || state.configurationId === null || isString(state.configurationId)) || !isString(state.machineId) || !isTimestamp(state.startedAt) || !isTimestamp(state.updatedAt) || !(state.lastSuccessfulHeartbeatAt === null || isTimestamp(state.lastSuccessfulHeartbeatAt))) return null;
|
|
74
|
+
return {
|
|
75
|
+
instanceId: state.instanceId,
|
|
76
|
+
pid: state.pid,
|
|
77
|
+
version: state.version,
|
|
78
|
+
configurationId: state.configurationId ?? null,
|
|
79
|
+
machineId: state.machineId,
|
|
80
|
+
startedAt: state.startedAt,
|
|
81
|
+
updatedAt: state.updatedAt,
|
|
82
|
+
lastSuccessfulHeartbeatAt: state.lastSuccessfulHeartbeatAt
|
|
83
|
+
};
|
|
84
|
+
};
|
|
85
|
+
var parseLockOwner = (value) => {
|
|
86
|
+
if (typeof value !== "object" || value === null) return null;
|
|
87
|
+
const owner = value;
|
|
88
|
+
return isString(owner.instanceId) && Number.isInteger(owner.pid) && owner.pid > 0 ? { instanceId: owner.instanceId, pid: owner.pid } : null;
|
|
89
|
+
};
|
|
90
|
+
var readDaemonStatus = (options = {}) => {
|
|
91
|
+
const dir = options.dir ?? pusharyDir();
|
|
92
|
+
const installedVersion = options.installedVersion ?? getPackageVersion();
|
|
93
|
+
const state = readJsonSafe(statePath(dir));
|
|
94
|
+
if (state.kind === "missing") {
|
|
95
|
+
return {
|
|
96
|
+
kind: "absent",
|
|
97
|
+
installedVersion,
|
|
98
|
+
machineId: null,
|
|
99
|
+
pid: null,
|
|
100
|
+
processAlive: null,
|
|
101
|
+
daemonVersion: null,
|
|
102
|
+
startedAt: null,
|
|
103
|
+
updatedAt: null,
|
|
104
|
+
lastSuccessfulHeartbeatAt: null
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
const value = state.kind === "ok" ? parseDaemonState(state.value) : null;
|
|
108
|
+
if (value) {
|
|
109
|
+
const alive = (options.isProcessAlive ?? processIsAlive)(value.pid);
|
|
110
|
+
const fresh = (options.now ?? Date.now)() - Date.parse(value.updatedAt) <= LOCAL_LEASE_TIMEOUT_MS;
|
|
111
|
+
const expectedConfigurationId = options.configurationId === void 0 ? currentConfigurationId() : options.configurationId;
|
|
112
|
+
return {
|
|
113
|
+
kind: !alive || !fresh ? "unresponsive" : value.version !== installedVersion ? "version-mismatch" : expectedConfigurationId !== null && value.configurationId !== null && value.configurationId !== expectedConfigurationId ? "configuration-mismatch" : "running",
|
|
114
|
+
installedVersion,
|
|
115
|
+
machineId: value.machineId,
|
|
116
|
+
pid: value.pid,
|
|
117
|
+
processAlive: alive,
|
|
118
|
+
daemonVersion: value.version,
|
|
119
|
+
startedAt: value.startedAt,
|
|
120
|
+
updatedAt: value.updatedAt,
|
|
121
|
+
lastSuccessfulHeartbeatAt: value.lastSuccessfulHeartbeatAt
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
return {
|
|
125
|
+
kind: "invalid",
|
|
126
|
+
installedVersion,
|
|
127
|
+
machineId: null,
|
|
128
|
+
pid: null,
|
|
129
|
+
processAlive: null,
|
|
130
|
+
daemonVersion: null,
|
|
131
|
+
startedAt: null,
|
|
132
|
+
updatedAt: null,
|
|
133
|
+
lastSuccessfulHeartbeatAt: null
|
|
134
|
+
};
|
|
135
|
+
};
|
|
136
|
+
var safeUnlink = (path) => {
|
|
137
|
+
try {
|
|
138
|
+
unlinkSync(path);
|
|
139
|
+
} catch (err) {
|
|
140
|
+
if (err.code !== "ENOENT") throw err;
|
|
141
|
+
}
|
|
142
|
+
};
|
|
143
|
+
var requestDaemonVersion = (version, options = {}) => {
|
|
144
|
+
const dir = options.dir ?? pusharyDir();
|
|
145
|
+
const now = options.now ?? Date.now;
|
|
146
|
+
writeJsonAtomic(targetPath(dir), { version, requestedAt: new Date(now()).toISOString() }, 384);
|
|
147
|
+
};
|
|
148
|
+
var acquireDaemonLease = (options = {}) => {
|
|
149
|
+
const dir = options.dir ?? pusharyDir();
|
|
150
|
+
const installedVersion = options.installedVersion ?? getPackageVersion();
|
|
151
|
+
const machineId = options.machineId ?? getMachineId();
|
|
152
|
+
const pid = options.pid ?? process.pid;
|
|
153
|
+
const now = options.now ?? Date.now;
|
|
154
|
+
const instanceId = randomUUID();
|
|
155
|
+
const startedAt = new Date(now()).toISOString();
|
|
156
|
+
const writeLock = () => {
|
|
157
|
+
mkdirSync(dir, { recursive: true });
|
|
158
|
+
writeFileSync(lockPath(dir), JSON.stringify({ instanceId, pid }), { flag: "wx", mode: 384 });
|
|
159
|
+
};
|
|
160
|
+
try {
|
|
161
|
+
writeLock();
|
|
162
|
+
} catch (err) {
|
|
163
|
+
if (err.code !== "EEXIST") {
|
|
164
|
+
return { kind: "unavailable", detail: err instanceof Error ? err.message : String(err) };
|
|
165
|
+
}
|
|
166
|
+
const status = readDaemonStatus(options);
|
|
167
|
+
if (status.kind === "running" || status.kind === "version-mismatch" || status.kind === "configuration-mismatch") {
|
|
168
|
+
return { kind: "already-running", status };
|
|
169
|
+
}
|
|
170
|
+
const lock = readJsonSafe(lockPath(dir));
|
|
171
|
+
const owner = lock.kind === "ok" ? parseLockOwner(lock.value) : null;
|
|
172
|
+
if (!owner) return { kind: "unavailable", detail: "daemon lock is invalid" };
|
|
173
|
+
if ((options.isProcessAlive ?? processIsAlive)(owner.pid)) {
|
|
174
|
+
return { kind: "unavailable", detail: "daemon owner is still running or starting" };
|
|
175
|
+
}
|
|
176
|
+
const stale = join(dir, `daemon.lock.${instanceId}.stale`);
|
|
177
|
+
let ownsReplacement = false;
|
|
178
|
+
try {
|
|
179
|
+
renameSync(lockPath(dir), stale);
|
|
180
|
+
writeLock();
|
|
181
|
+
ownsReplacement = true;
|
|
182
|
+
safeUnlink(statePath(dir));
|
|
183
|
+
safeUnlink(stale);
|
|
184
|
+
} catch (retryError) {
|
|
185
|
+
if (ownsReplacement) safeUnlink(lockPath(dir));
|
|
186
|
+
try {
|
|
187
|
+
safeUnlink(stale);
|
|
188
|
+
} catch {
|
|
189
|
+
}
|
|
190
|
+
return { kind: "unavailable", detail: retryError instanceof Error ? retryError.message : String(retryError) };
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
const state = {
|
|
194
|
+
instanceId,
|
|
195
|
+
pid,
|
|
196
|
+
version: installedVersion,
|
|
197
|
+
configurationId: options.configurationId === void 0 ? currentConfigurationId() : options.configurationId,
|
|
198
|
+
machineId,
|
|
199
|
+
startedAt,
|
|
200
|
+
updatedAt: startedAt,
|
|
201
|
+
lastSuccessfulHeartbeatAt: null
|
|
202
|
+
};
|
|
203
|
+
try {
|
|
204
|
+
writeJsonAtomic(statePath(dir), state, 384);
|
|
205
|
+
} catch (err) {
|
|
206
|
+
safeUnlink(lockPath(dir));
|
|
207
|
+
return { kind: "unavailable", detail: err instanceof Error ? err.message : String(err) };
|
|
208
|
+
}
|
|
209
|
+
let currentState = state;
|
|
210
|
+
const isOwner = () => {
|
|
211
|
+
const owner = readJsonSafe(lockPath(dir));
|
|
212
|
+
return owner.kind === "ok" && owner.value.instanceId === instanceId;
|
|
213
|
+
};
|
|
214
|
+
const updateState = (next) => {
|
|
215
|
+
if (!isOwner()) return;
|
|
216
|
+
try {
|
|
217
|
+
writeJsonAtomic(statePath(dir), next, 384);
|
|
218
|
+
currentState = next;
|
|
219
|
+
} catch {
|
|
220
|
+
}
|
|
221
|
+
};
|
|
222
|
+
return {
|
|
223
|
+
kind: "acquired",
|
|
224
|
+
lease: {
|
|
225
|
+
isOwner,
|
|
226
|
+
renew: (at = now()) => {
|
|
227
|
+
updateState({ ...currentState, updatedAt: new Date(at).toISOString() });
|
|
228
|
+
},
|
|
229
|
+
recordSuccessfulHeartbeat: (at = now()) => {
|
|
230
|
+
const timestamp = new Date(at).toISOString();
|
|
231
|
+
updateState({
|
|
232
|
+
...currentState,
|
|
233
|
+
updatedAt: timestamp,
|
|
234
|
+
lastSuccessfulHeartbeatAt: timestamp
|
|
235
|
+
});
|
|
236
|
+
},
|
|
237
|
+
shouldYield: () => {
|
|
238
|
+
if (!isOwner()) return true;
|
|
239
|
+
const target = readJsonSafe(targetPath(dir));
|
|
240
|
+
return target.kind === "ok" && (typeof target.value.version === "string" && target.value.version !== installedVersion || isTimestamp(target.value.requestedAt) && Date.parse(target.value.requestedAt) > Date.parse(startedAt));
|
|
241
|
+
},
|
|
242
|
+
release: () => {
|
|
243
|
+
if (!isOwner()) return;
|
|
244
|
+
safeUnlink(statePath(dir));
|
|
245
|
+
safeUnlink(lockPath(dir));
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
};
|
|
249
|
+
};
|
|
250
|
+
var hasConfiguration = () => {
|
|
251
|
+
try {
|
|
252
|
+
getApiKey();
|
|
253
|
+
return true;
|
|
254
|
+
} catch {
|
|
255
|
+
return false;
|
|
256
|
+
}
|
|
257
|
+
};
|
|
258
|
+
var wait = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
259
|
+
var requestDaemonHandoff = async (initial, options = {}) => {
|
|
260
|
+
const now = options.now ?? Date.now;
|
|
261
|
+
const requestedAt = Math.max(
|
|
262
|
+
now(),
|
|
263
|
+
initial.startedAt === null ? 0 : Date.parse(initial.startedAt) + 1
|
|
264
|
+
);
|
|
265
|
+
requestDaemonVersion(options.installedVersion ?? initial.installedVersion, {
|
|
266
|
+
dir: options.dir,
|
|
267
|
+
now: () => requestedAt
|
|
268
|
+
});
|
|
269
|
+
if (initial.pid === null || initial.processAlive !== true) return initial;
|
|
270
|
+
const sleep = options.sleep ?? wait;
|
|
271
|
+
const pollIntervalMs = options.pollIntervalMs ?? 50;
|
|
272
|
+
const attempts = Math.max(1, Math.ceil((options.waitTimeoutMs ?? 5e3) / pollIntervalMs));
|
|
273
|
+
let status = initial;
|
|
274
|
+
for (let attempt = 0; attempt < attempts; attempt++) {
|
|
275
|
+
await sleep(pollIntervalMs);
|
|
276
|
+
status = readDaemonStatus(options);
|
|
277
|
+
if (status.pid !== initial.pid || status.processAlive !== true) break;
|
|
278
|
+
}
|
|
279
|
+
return status;
|
|
280
|
+
};
|
|
281
|
+
var launchInstalledDaemon = (options) => {
|
|
282
|
+
const entryExists = options.entryExists ?? existsSync;
|
|
283
|
+
const argvEntry = process.argv[1];
|
|
284
|
+
const candidate = options.daemonEntry !== void 0 ? options.daemonEntry : argvEntry ? join(dirname(argvEntry), "pushary-daemon.js") : null;
|
|
285
|
+
const siblingEntry = candidate && entryExists(candidate) ? candidate : null;
|
|
286
|
+
const platform = options.platform ?? process.platform;
|
|
287
|
+
const daemonBinary = options.daemonBinary ?? resolveGlobalBinary("pushary-daemon") ?? (platform === "win32" ? "pushary-daemon.cmd" : "pushary-daemon");
|
|
288
|
+
const plan = buildDaemonLaunch({
|
|
289
|
+
siblingEntry,
|
|
290
|
+
execPath: options.execPath ?? process.execPath,
|
|
291
|
+
daemonBinary,
|
|
292
|
+
platform
|
|
293
|
+
});
|
|
294
|
+
spawnDetachedDaemon(plan, { spawnImpl: options.spawnImpl });
|
|
295
|
+
};
|
|
296
|
+
var ensureDaemonRunning = async (options = {}) => {
|
|
297
|
+
const initial = readDaemonStatus(options);
|
|
298
|
+
if (!(options.configured ?? hasConfiguration)()) {
|
|
299
|
+
return { kind: "not-configured", status: initial, detail: "Pushary is not configured on this machine" };
|
|
300
|
+
}
|
|
301
|
+
if (initial.kind === "running") return { kind: "running", status: initial };
|
|
302
|
+
let handoffStatus;
|
|
303
|
+
try {
|
|
304
|
+
handoffStatus = await requestDaemonHandoff(initial, options);
|
|
305
|
+
} catch (err) {
|
|
306
|
+
return {
|
|
307
|
+
kind: "unavailable",
|
|
308
|
+
status: initial,
|
|
309
|
+
detail: err instanceof Error ? err.message : String(err)
|
|
310
|
+
};
|
|
311
|
+
}
|
|
312
|
+
const sleep = options.sleep ?? wait;
|
|
313
|
+
const pollIntervalMs = options.pollIntervalMs ?? 50;
|
|
314
|
+
const attempts = Math.max(1, Math.ceil((options.waitTimeoutMs ?? 5e3) / pollIntervalMs));
|
|
315
|
+
if (handoffStatus.kind === "running") return { kind: "running", status: handoffStatus };
|
|
316
|
+
if (daemonNeedsHandoff(handoffStatus)) {
|
|
317
|
+
return { kind: "unavailable", status: handoffStatus, detail: "unhealthy daemon did not yield" };
|
|
318
|
+
}
|
|
319
|
+
try {
|
|
320
|
+
(options.spawnDaemon ?? (() => launchInstalledDaemon(options)))();
|
|
321
|
+
} catch (err) {
|
|
322
|
+
return {
|
|
323
|
+
kind: "unavailable",
|
|
324
|
+
status: initial,
|
|
325
|
+
detail: err instanceof Error ? err.message : String(err)
|
|
326
|
+
};
|
|
327
|
+
}
|
|
328
|
+
let status = initial;
|
|
329
|
+
for (let attempt = 0; attempt < attempts; attempt++) {
|
|
330
|
+
status = readDaemonStatus(options);
|
|
331
|
+
if (status.kind === "running") return { kind: "started", status };
|
|
332
|
+
await sleep(pollIntervalMs);
|
|
333
|
+
}
|
|
334
|
+
return { kind: "unavailable", status, detail: `daemon did not become healthy (${status.kind})` };
|
|
335
|
+
};
|
|
336
|
+
|
|
337
|
+
export {
|
|
338
|
+
DAEMON_LEASE_RENEW_INTERVAL_MS,
|
|
339
|
+
readDaemonStatus,
|
|
340
|
+
acquireDaemonLease,
|
|
341
|
+
requestDaemonHandoff,
|
|
342
|
+
ensureDaemonRunning
|
|
343
|
+
};
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
// src/cli/version.ts
|
|
2
|
+
import { readFileSync } from "fs";
|
|
3
|
+
import { dirname, join, parse } from "path";
|
|
4
|
+
import { fileURLToPath } from "url";
|
|
5
|
+
var PACKAGE_NAME = "@pushary/agent-hooks";
|
|
6
|
+
var findOwnPackageJson = (start) => {
|
|
7
|
+
const { root } = parse(start);
|
|
8
|
+
let dir = start;
|
|
9
|
+
while (true) {
|
|
10
|
+
const candidate = join(dir, "package.json");
|
|
11
|
+
try {
|
|
12
|
+
const pkg = JSON.parse(readFileSync(candidate, "utf-8"));
|
|
13
|
+
if (pkg.name === PACKAGE_NAME) return candidate;
|
|
14
|
+
} catch {
|
|
15
|
+
}
|
|
16
|
+
if (dir === root) return void 0;
|
|
17
|
+
const parent = dirname(dir);
|
|
18
|
+
if (parent === dir) return void 0;
|
|
19
|
+
dir = parent;
|
|
20
|
+
}
|
|
21
|
+
};
|
|
22
|
+
var getPackageVersion = () => {
|
|
23
|
+
try {
|
|
24
|
+
const found = findOwnPackageJson(dirname(fileURLToPath(import.meta.url)));
|
|
25
|
+
if (!found) return "0.0.0";
|
|
26
|
+
const pkg = JSON.parse(readFileSync(found, "utf-8"));
|
|
27
|
+
return pkg.version ?? "0.0.0";
|
|
28
|
+
} catch {
|
|
29
|
+
return "0.0.0";
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
var isNewerVersion = (candidate, current) => {
|
|
33
|
+
const a = candidate.split(".").map((part) => parseInt(part, 10) || 0);
|
|
34
|
+
const b = current.split(".").map((part) => parseInt(part, 10) || 0);
|
|
35
|
+
for (let i = 0; i < 3; i++) {
|
|
36
|
+
if ((a[i] ?? 0) > (b[i] ?? 0)) return true;
|
|
37
|
+
if ((a[i] ?? 0) < (b[i] ?? 0)) return false;
|
|
38
|
+
}
|
|
39
|
+
return false;
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
export {
|
|
43
|
+
getPackageVersion,
|
|
44
|
+
isNewerVersion
|
|
45
|
+
};
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import {
|
|
2
|
+
pusharyDir
|
|
3
|
+
} from "./chunk-SEAHGHCE.js";
|
|
4
|
+
import {
|
|
5
|
+
readJsonSafe
|
|
6
|
+
} from "./chunk-6MTNS63X.js";
|
|
7
|
+
|
|
8
|
+
// src/process-ownership.ts
|
|
9
|
+
import { createHash, randomUUID } from "crypto";
|
|
10
|
+
import { mkdirSync, readdirSync, unlinkSync, writeFileSync } from "fs";
|
|
11
|
+
import { createConnection, createServer } from "net";
|
|
12
|
+
import { join } from "path";
|
|
13
|
+
var MAX_PROOF_BYTES = 1024;
|
|
14
|
+
var ownershipDir = (options) => options.dir ?? join(pusharyDir(), "processes");
|
|
15
|
+
var proofPath = (dir, sessionId) => join(dir, `${createHash("sha256").update(sessionId).digest("hex")}.json`);
|
|
16
|
+
var isProof = (value) => {
|
|
17
|
+
if (!value || typeof value !== "object") return false;
|
|
18
|
+
const proof = value;
|
|
19
|
+
return typeof proof.sessionId === "string" && Number.isInteger(proof.pid) && proof.pid > 1 && Number.isInteger(proof.port) && proof.port > 0 && proof.port <= 65535 && typeof proof.token === "string" && proof.token.length > 0 && (proof.spawnId === void 0 || typeof proof.spawnId === "string");
|
|
20
|
+
};
|
|
21
|
+
var removeProof = (path, token) => {
|
|
22
|
+
const current = readJsonSafe(path);
|
|
23
|
+
if (current.kind !== "ok" || current.value.token !== token) return;
|
|
24
|
+
try {
|
|
25
|
+
unlinkSync(path);
|
|
26
|
+
} catch {
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
var processIsAlive = (pid) => {
|
|
30
|
+
try {
|
|
31
|
+
process.kill(pid, 0);
|
|
32
|
+
return true;
|
|
33
|
+
} catch (error) {
|
|
34
|
+
return error.code === "EPERM";
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
var pruneProofs = (dir) => {
|
|
38
|
+
try {
|
|
39
|
+
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
|
40
|
+
if (!entry.isFile() || !entry.name.endsWith(".json")) continue;
|
|
41
|
+
const path = join(dir, entry.name);
|
|
42
|
+
const stored = readJsonSafe(path);
|
|
43
|
+
if (stored.kind !== "ok" || !isProof(stored.value) || !processIsAlive(stored.value.pid)) unlinkSync(path);
|
|
44
|
+
}
|
|
45
|
+
} catch {
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
var startProcessOwnership = (identity, options = {}) => {
|
|
49
|
+
const dir = ownershipDir(options);
|
|
50
|
+
const path = proofPath(dir, identity.sessionId);
|
|
51
|
+
const token = randomUUID();
|
|
52
|
+
let stopped = false;
|
|
53
|
+
let proof = null;
|
|
54
|
+
let settleReady = () => {
|
|
55
|
+
};
|
|
56
|
+
const server = createServer((socket) => {
|
|
57
|
+
let request = "";
|
|
58
|
+
socket.setTimeout(500, () => socket.destroy());
|
|
59
|
+
socket.on("error", () => {
|
|
60
|
+
});
|
|
61
|
+
socket.on("data", (chunk) => {
|
|
62
|
+
request += String(chunk);
|
|
63
|
+
if (!proof || request.length > token.length) socket.destroy();
|
|
64
|
+
else if (request.length === token.length) {
|
|
65
|
+
if (request === token) socket.end(JSON.stringify(proof));
|
|
66
|
+
else socket.destroy();
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
});
|
|
70
|
+
server.maxConnections = 4;
|
|
71
|
+
const ready = new Promise((resolve) => {
|
|
72
|
+
settleReady = resolve;
|
|
73
|
+
const resolveOnce = () => resolve();
|
|
74
|
+
server.once("error", resolveOnce);
|
|
75
|
+
server.listen(0, "127.0.0.1", () => {
|
|
76
|
+
server.off("error", resolveOnce);
|
|
77
|
+
server.on("error", () => {
|
|
78
|
+
});
|
|
79
|
+
if (stopped) {
|
|
80
|
+
server.close();
|
|
81
|
+
resolve();
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
const address = server.address();
|
|
85
|
+
if (!address || typeof address === "string") {
|
|
86
|
+
server.close();
|
|
87
|
+
resolve();
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
try {
|
|
91
|
+
proof = { ...identity, port: address.port, token };
|
|
92
|
+
mkdirSync(dir, { recursive: true, mode: 448 });
|
|
93
|
+
pruneProofs(dir);
|
|
94
|
+
writeFileSync(path, JSON.stringify(proof), { encoding: "utf8", mode: 384 });
|
|
95
|
+
resolve();
|
|
96
|
+
} catch {
|
|
97
|
+
server.close();
|
|
98
|
+
resolve();
|
|
99
|
+
}
|
|
100
|
+
});
|
|
101
|
+
});
|
|
102
|
+
const cleanup = () => removeProof(path, token);
|
|
103
|
+
process.once("exit", cleanup);
|
|
104
|
+
return {
|
|
105
|
+
ready,
|
|
106
|
+
stop() {
|
|
107
|
+
if (stopped) return;
|
|
108
|
+
stopped = true;
|
|
109
|
+
settleReady();
|
|
110
|
+
process.off("exit", cleanup);
|
|
111
|
+
server.close();
|
|
112
|
+
cleanup();
|
|
113
|
+
}
|
|
114
|
+
};
|
|
115
|
+
};
|
|
116
|
+
var ownsLiveProcess = async (request, options = {}) => {
|
|
117
|
+
const stored = readJsonSafe(proofPath(ownershipDir(options), request.sessionId));
|
|
118
|
+
if (stored.kind !== "ok" || !isProof(stored.value)) return false;
|
|
119
|
+
const proof = stored.value;
|
|
120
|
+
if (proof.sessionId !== request.sessionId || proof.pid !== request.pid) return false;
|
|
121
|
+
if (request.spawnId && proof.spawnId !== request.spawnId) return false;
|
|
122
|
+
return new Promise((resolve) => {
|
|
123
|
+
let settled = false;
|
|
124
|
+
let raw = "";
|
|
125
|
+
const socket = createConnection({ host: "127.0.0.1", port: proof.port });
|
|
126
|
+
const finish = (owned) => {
|
|
127
|
+
if (settled) return;
|
|
128
|
+
settled = true;
|
|
129
|
+
socket.destroy();
|
|
130
|
+
resolve(owned);
|
|
131
|
+
};
|
|
132
|
+
socket.setTimeout(options.connectTimeoutMs ?? 500, () => finish(false));
|
|
133
|
+
socket.on("error", () => finish(false));
|
|
134
|
+
socket.on("connect", () => socket.write(proof.token));
|
|
135
|
+
socket.on("data", (chunk) => {
|
|
136
|
+
raw += String(chunk);
|
|
137
|
+
if (raw.length > MAX_PROOF_BYTES) finish(false);
|
|
138
|
+
});
|
|
139
|
+
socket.on("end", () => {
|
|
140
|
+
try {
|
|
141
|
+
const live = JSON.parse(raw);
|
|
142
|
+
finish(isProof(live) && live.token === proof.token && live.sessionId === proof.sessionId && live.pid === proof.pid && live.spawnId === proof.spawnId);
|
|
143
|
+
} catch {
|
|
144
|
+
finish(false);
|
|
145
|
+
}
|
|
146
|
+
});
|
|
147
|
+
});
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
export {
|
|
151
|
+
startProcessOwnership,
|
|
152
|
+
ownsLiveProcess
|
|
153
|
+
};
|
|
@@ -5,14 +5,14 @@ import {
|
|
|
5
5
|
NetworkError
|
|
6
6
|
} from "./chunk-7QLSKOSU.js";
|
|
7
7
|
import {
|
|
8
|
-
|
|
9
|
-
} from "./chunk-
|
|
8
|
+
EXIT
|
|
9
|
+
} from "./chunk-KZERVKTD.js";
|
|
10
10
|
import {
|
|
11
11
|
getBaseUrl
|
|
12
12
|
} from "./chunk-2UMNXADU.js";
|
|
13
13
|
import {
|
|
14
|
-
|
|
15
|
-
} from "./chunk-
|
|
14
|
+
isValidApiKey
|
|
15
|
+
} from "./chunk-MQMPG5X4.js";
|
|
16
16
|
|
|
17
17
|
// src/session.ts
|
|
18
18
|
var IDENTITY_PATH = "/api/v1/server/identity";
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import {
|
|
2
2
|
hasGeminiHooks
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-DOXSWT4Q.js";
|
|
4
4
|
import {
|
|
5
5
|
hasCodexHooks
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-SEAHGHCE.js";
|
|
7
7
|
|
|
8
8
|
// src/diagnostics/wiring.ts
|
|
9
9
|
var record = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
@@ -1,15 +1,15 @@
|
|
|
1
|
+
import {
|
|
2
|
+
resolveShellRc
|
|
3
|
+
} from "./chunk-BC3VCZ3E.js";
|
|
1
4
|
import {
|
|
2
5
|
backupFile,
|
|
3
6
|
readJsonSafe,
|
|
4
7
|
writeFileAtomic,
|
|
5
8
|
writeJsonAtomic
|
|
6
9
|
} from "./chunk-6MTNS63X.js";
|
|
7
|
-
import {
|
|
8
|
-
resolveShellRc
|
|
9
|
-
} from "./chunk-BC3VCZ3E.js";
|
|
10
10
|
import {
|
|
11
11
|
resolveInstallSourceFor
|
|
12
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-437T77TY.js";
|
|
13
13
|
import {
|
|
14
14
|
configFilePath
|
|
15
15
|
} from "./chunk-2UMNXADU.js";
|
|
@@ -192,52 +192,9 @@ var renderCommandHelp = (name, version) => {
|
|
|
192
192
|
].join("\n");
|
|
193
193
|
};
|
|
194
194
|
|
|
195
|
-
// src/cli/version.ts
|
|
196
|
-
import { readFileSync } from "fs";
|
|
197
|
-
import { dirname, join, parse } from "path";
|
|
198
|
-
import { fileURLToPath } from "url";
|
|
199
|
-
var PACKAGE_NAME = "@pushary/agent-hooks";
|
|
200
|
-
var findOwnPackageJson = (start) => {
|
|
201
|
-
const { root } = parse(start);
|
|
202
|
-
let dir = start;
|
|
203
|
-
while (true) {
|
|
204
|
-
const candidate = join(dir, "package.json");
|
|
205
|
-
try {
|
|
206
|
-
const pkg = JSON.parse(readFileSync(candidate, "utf-8"));
|
|
207
|
-
if (pkg.name === PACKAGE_NAME) return candidate;
|
|
208
|
-
} catch {
|
|
209
|
-
}
|
|
210
|
-
if (dir === root) return void 0;
|
|
211
|
-
const parent = dirname(dir);
|
|
212
|
-
if (parent === dir) return void 0;
|
|
213
|
-
dir = parent;
|
|
214
|
-
}
|
|
215
|
-
};
|
|
216
|
-
var getPackageVersion = () => {
|
|
217
|
-
try {
|
|
218
|
-
const found = findOwnPackageJson(dirname(fileURLToPath(import.meta.url)));
|
|
219
|
-
if (!found) return "0.0.0";
|
|
220
|
-
const pkg = JSON.parse(readFileSync(found, "utf-8"));
|
|
221
|
-
return pkg.version ?? "0.0.0";
|
|
222
|
-
} catch {
|
|
223
|
-
return "0.0.0";
|
|
224
|
-
}
|
|
225
|
-
};
|
|
226
|
-
var isNewerVersion = (candidate, current) => {
|
|
227
|
-
const a = candidate.split(".").map((part) => parseInt(part, 10) || 0);
|
|
228
|
-
const b = current.split(".").map((part) => parseInt(part, 10) || 0);
|
|
229
|
-
for (let i = 0; i < 3; i++) {
|
|
230
|
-
if ((a[i] ?? 0) > (b[i] ?? 0)) return true;
|
|
231
|
-
if ((a[i] ?? 0) < (b[i] ?? 0)) return false;
|
|
232
|
-
}
|
|
233
|
-
return false;
|
|
234
|
-
};
|
|
235
|
-
|
|
236
195
|
export {
|
|
237
196
|
COMMANDS,
|
|
238
197
|
isCommandName,
|
|
239
198
|
renderHelp,
|
|
240
|
-
renderCommandHelp
|
|
241
|
-
getPackageVersion,
|
|
242
|
-
isNewerVersion
|
|
199
|
+
renderCommandHelp
|
|
243
200
|
};
|