pushary 1.8.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 +1943 -0
- package/LICENSE +21 -0
- package/README.md +330 -0
- package/data/SKILL.md +591 -0
- package/data/cowork/SKILL.md +77 -0
- package/data/cursor-plugin/.cursor-plugin/plugin.json +25 -0
- package/data/cursor-plugin/CHANGELOG.md +70 -0
- package/data/cursor-plugin/CONTRIBUTING.md +40 -0
- package/data/cursor-plugin/LICENSE +21 -0
- package/data/cursor-plugin/README.md +113 -0
- package/data/cursor-plugin/SECURITY.md +33 -0
- package/data/cursor-plugin/assets/logo.png +0 -0
- package/data/cursor-plugin/commands/notify-when-done.md +14 -0
- package/data/cursor-plugin/commands/pushary-test.md +13 -0
- package/data/cursor-plugin/hooks/hooks.json +75 -0
- package/data/cursor-plugin/mcp.json +11 -0
- package/data/cursor-plugin/rules/pushary.mdc +40 -0
- package/data/cursor-plugin/scripts/pushary-gate.mjs +724 -0
- package/data/cursor-plugin/scripts/pushary-gate.test.mjs +137 -0
- package/data/cursor-plugin/scripts/redaction.mjs +51 -0
- package/data/cursor-plugin/skills/pushary/SKILL.md +584 -0
- package/data/vscode-plugin/.claude-plugin/plugin.json +28 -0
- package/data/vscode-plugin/.mcp.json +11 -0
- package/data/vscode-plugin/CHANGELOG.md +49 -0
- package/data/vscode-plugin/CONTRIBUTING.md +40 -0
- package/data/vscode-plugin/LICENSE +21 -0
- package/data/vscode-plugin/README.md +128 -0
- package/data/vscode-plugin/SECURITY.md +56 -0
- package/data/vscode-plugin/assets/logo.png +0 -0
- package/data/vscode-plugin/commands/notify-when-done.md +14 -0
- package/data/vscode-plugin/commands/pushary-test.md +13 -0
- package/data/vscode-plugin/hooks/hooks.json +60 -0
- package/data/vscode-plugin/scripts/pushary-gate.mjs +762 -0
- package/data/vscode-plugin/scripts/redaction.mjs +51 -0
- package/data/vscode-plugin/skills/pushary/SKILL.md +584 -0
- package/dist/bin/pushary-bell-hook.d.ts +1 -0
- package/dist/bin/pushary-bell-hook.js +46 -0
- package/dist/bin/pushary-bell.d.ts +1 -0
- package/dist/bin/pushary-bell.js +146 -0
- package/dist/bin/pushary-claude.d.ts +1 -0
- package/dist/bin/pushary-claude.js +1916 -0
- package/dist/bin/pushary-clean.d.ts +1 -0
- package/dist/bin/pushary-clean.js +1300 -0
- package/dist/bin/pushary-codex-bridge.d.ts +1 -0
- package/dist/bin/pushary-codex-bridge.js +599 -0
- package/dist/bin/pushary-codex-hook.d.ts +1 -0
- package/dist/bin/pushary-codex-hook.js +886 -0
- package/dist/bin/pushary-codex.d.ts +1 -0
- package/dist/bin/pushary-codex.js +125 -0
- package/dist/bin/pushary-connect.d.ts +1 -0
- package/dist/bin/pushary-connect.js +100 -0
- package/dist/bin/pushary-cowork.d.ts +1 -0
- package/dist/bin/pushary-cowork.js +36 -0
- package/dist/bin/pushary-daemon-supervisor.d.ts +1 -0
- package/dist/bin/pushary-daemon-supervisor.js +27 -0
- package/dist/bin/pushary-daemon.d.ts +1 -0
- package/dist/bin/pushary-daemon.js +878 -0
- package/dist/bin/pushary-disconnect.d.ts +1 -0
- package/dist/bin/pushary-disconnect.js +264 -0
- package/dist/bin/pushary-doctor.d.ts +1 -0
- package/dist/bin/pushary-doctor.js +1433 -0
- package/dist/bin/pushary-elicitation-hook.d.ts +1 -0
- package/dist/bin/pushary-elicitation-hook.js +149 -0
- package/dist/bin/pushary-gemini-bridge.d.ts +1 -0
- package/dist/bin/pushary-gemini-bridge.js +339 -0
- package/dist/bin/pushary-gemini-hook.d.ts +1 -0
- package/dist/bin/pushary-gemini-hook.js +381 -0
- package/dist/bin/pushary-hook.d.ts +1 -0
- package/dist/bin/pushary-hook.js +95 -0
- package/dist/bin/pushary-login.d.ts +1 -0
- package/dist/bin/pushary-login.js +198 -0
- package/dist/bin/pushary-logout.d.ts +1 -0
- package/dist/bin/pushary-logout.js +147 -0
- package/dist/bin/pushary-mcp.d.ts +1 -0
- package/dist/bin/pushary-mcp.js +114 -0
- package/dist/bin/pushary-mode.d.ts +1 -0
- package/dist/bin/pushary-mode.js +130 -0
- package/dist/bin/pushary-notification-hook.d.ts +1 -0
- package/dist/bin/pushary-notification-hook.js +56 -0
- package/dist/bin/pushary-opencode-hook.d.ts +1 -0
- package/dist/bin/pushary-opencode-hook.js +338 -0
- package/dist/bin/pushary-permission-denied-hook.d.ts +1 -0
- package/dist/bin/pushary-permission-denied-hook.js +60 -0
- package/dist/bin/pushary-permission-hook.d.ts +1 -0
- package/dist/bin/pushary-permission-hook.js +65 -0
- package/dist/bin/pushary-post-hook.d.ts +1 -0
- package/dist/bin/pushary-post-hook.js +70 -0
- package/dist/bin/pushary-prompt-hook.d.ts +1 -0
- package/dist/bin/pushary-prompt-hook.js +61 -0
- package/dist/bin/pushary-session-end-hook.d.ts +1 -0
- package/dist/bin/pushary-session-end-hook.js +59 -0
- package/dist/bin/pushary-session-start-hook.d.ts +1 -0
- package/dist/bin/pushary-session-start-hook.js +72 -0
- package/dist/bin/pushary-setup.d.ts +1 -0
- package/dist/bin/pushary-setup.js +2660 -0
- package/dist/bin/pushary-stats.d.ts +1 -0
- package/dist/bin/pushary-stats.js +44 -0
- package/dist/bin/pushary-status.d.ts +1 -0
- package/dist/bin/pushary-status.js +262 -0
- package/dist/bin/pushary-stop-hook.d.ts +1 -0
- package/dist/bin/pushary-stop-hook.js +63 -0
- package/dist/bin/pushary-stopfailure-hook.d.ts +1 -0
- package/dist/bin/pushary-stopfailure-hook.js +56 -0
- package/dist/bin/pushary-suggestions.d.ts +1 -0
- package/dist/bin/pushary-suggestions.js +100 -0
- package/dist/bin/pushary-transcript-register.d.ts +1 -0
- package/dist/bin/pushary-transcript-register.js +40 -0
- package/dist/bin/pushary-transcripts.d.ts +1 -0
- package/dist/bin/pushary-transcripts.js +77 -0
- package/dist/bin/pushary-upgrade.d.ts +1 -0
- package/dist/bin/pushary-upgrade.js +599 -0
- package/dist/bin/pushary-wait.d.ts +1 -0
- package/dist/bin/pushary-wait.js +122 -0
- package/dist/bin/pushary.d.ts +1 -0
- package/dist/bin/pushary.js +74 -0
- package/dist/chunk-2ABTSGFT.js +173 -0
- package/dist/chunk-2OY3ZZ5N.js +124 -0
- package/dist/chunk-3EGEA4KH.js +44 -0
- package/dist/chunk-3EVPNIBE.js +185 -0
- package/dist/chunk-5OP5MQI7.js +45 -0
- package/dist/chunk-6J2JC2RT.js +149 -0
- package/dist/chunk-7QLSKOSU.js +19 -0
- package/dist/chunk-7Z7Q4GGS.js +88 -0
- package/dist/chunk-A2A2YTMG.js +835 -0
- package/dist/chunk-AHV4LNB5.js +377 -0
- package/dist/chunk-AXPCESYO.js +197 -0
- package/dist/chunk-C2WCPBF7.js +86 -0
- package/dist/chunk-CJVVKOSR.js +73 -0
- package/dist/chunk-CULUZTWQ.js +2117 -0
- package/dist/chunk-CVQYGMSR.js +142 -0
- package/dist/chunk-CVWG2N3Y.js +16 -0
- package/dist/chunk-D2FX3EPW.js +15 -0
- package/dist/chunk-DB3ONZB4.js +44 -0
- package/dist/chunk-DE4Y6TCC.js +160 -0
- package/dist/chunk-E4FJTUH4.js +249 -0
- package/dist/chunk-EPCXUP3P.js +41 -0
- package/dist/chunk-EX3GAPA4.js +44 -0
- package/dist/chunk-EYK3GSRH.js +143 -0
- package/dist/chunk-GMXKITVA.js +70 -0
- package/dist/chunk-GPEGOGRJ.js +49 -0
- package/dist/chunk-GU3EJCVV.js +43 -0
- package/dist/chunk-HQ6G3B5R.js +59 -0
- package/dist/chunk-HZVVNGEW.js +22 -0
- package/dist/chunk-IPVKH2ST.js +231 -0
- package/dist/chunk-J5TIV3J6.js +238 -0
- package/dist/chunk-JU6W3XAU.js +64 -0
- package/dist/chunk-JYG4B33G.js +227 -0
- package/dist/chunk-JZTHGAPA.js +503 -0
- package/dist/chunk-KGDWDSNL.js +102 -0
- package/dist/chunk-KVQBWI5T.js +99 -0
- package/dist/chunk-KZERVKTD.js +16 -0
- package/dist/chunk-LXQ5FBBD.js +47 -0
- package/dist/chunk-M5ICYGHT.js +138 -0
- package/dist/chunk-M5JDZ45W.js +943 -0
- package/dist/chunk-MDEPU45E.js +1226 -0
- package/dist/chunk-MPWXGONV.js +157 -0
- package/dist/chunk-N6IOSZOE.js +185 -0
- package/dist/chunk-NGDD6TXY.js +192 -0
- package/dist/chunk-NQTHFF4W.js +2830 -0
- package/dist/chunk-OKXS7WDZ.js +68 -0
- package/dist/chunk-OLQRRFBX.js +21 -0
- package/dist/chunk-P7WQEVWA.js +307 -0
- package/dist/chunk-POFBE6ZY.js +12 -0
- package/dist/chunk-PQ4K74WL.js +30 -0
- package/dist/chunk-PUZZELE6.js +37 -0
- package/dist/chunk-Q7E4I6UA.js +767 -0
- package/dist/chunk-QCGNGEGE.js +15 -0
- package/dist/chunk-R7437YI3.js +151 -0
- package/dist/chunk-RHR6NARM.js +305 -0
- package/dist/chunk-SAXBC4AZ.js +60 -0
- package/dist/chunk-SGZJ5LYH.js +76 -0
- package/dist/chunk-SIFVFP6T.js +70 -0
- package/dist/chunk-SLFABHW3.js +150 -0
- package/dist/chunk-T7E75EKA.js +67 -0
- package/dist/chunk-TY4JT7W3.js +38 -0
- package/dist/chunk-U4TMAANR.js +111 -0
- package/dist/chunk-UI36QSBI.js +177 -0
- package/dist/chunk-VQKRG3AC.js +339 -0
- package/dist/chunk-WE2J62NC.js +1131 -0
- package/dist/chunk-XHKBHWLX.js +14 -0
- package/dist/chunk-ZSZSSJCV.js +275 -0
- package/dist/chunk-ZVSKMPBZ.js +135 -0
- package/dist/reapply-CW3MA66V.js +34 -0
- package/dist/src/index.d.ts +204 -0
- package/dist/src/index.js +64 -0
- package/package.json +108 -0
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
// src/cli/human.ts
|
|
2
|
+
var stream = process.stdout;
|
|
3
|
+
var setHumanStream = (target) => {
|
|
4
|
+
stream = target;
|
|
5
|
+
};
|
|
6
|
+
var writeHuman = (text) => {
|
|
7
|
+
stream.write(text);
|
|
8
|
+
};
|
|
9
|
+
var humanIsTty = () => stream === process.stdout && process.stdout.isTTY === true;
|
|
10
|
+
|
|
11
|
+
export {
|
|
12
|
+
setHumanStream,
|
|
13
|
+
writeHuman,
|
|
14
|
+
humanIsTty
|
|
15
|
+
};
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import {
|
|
2
|
+
buildTranscriptRecords,
|
|
3
|
+
decodePublicKey,
|
|
4
|
+
generateSessionKey,
|
|
5
|
+
wrapSessionKey
|
|
6
|
+
} from "./chunk-MPWXGONV.js";
|
|
7
|
+
import {
|
|
8
|
+
fetchTranscriptRecipients,
|
|
9
|
+
uploadTranscriptRecords
|
|
10
|
+
} from "./chunk-SAXBC4AZ.js";
|
|
11
|
+
|
|
12
|
+
// src/live-session.ts
|
|
13
|
+
var MAX_TRANSCRIPT_QUEUE = 100;
|
|
14
|
+
var MAX_TRANSCRIPT_TEXT = 32 * 1024;
|
|
15
|
+
var LIVE_SESSION_HEARTBEAT_MS = 6e4;
|
|
16
|
+
var liveSessionSleep = (ms, signal) => new Promise((resolve) => {
|
|
17
|
+
if (signal.aborted) return resolve();
|
|
18
|
+
const timer = setTimeout(done, ms);
|
|
19
|
+
function done() {
|
|
20
|
+
clearTimeout(timer);
|
|
21
|
+
signal.removeEventListener("abort", done);
|
|
22
|
+
resolve();
|
|
23
|
+
}
|
|
24
|
+
signal.addEventListener("abort", done, { once: true });
|
|
25
|
+
});
|
|
26
|
+
var postLiveSession = (options, path, body) => {
|
|
27
|
+
const controller = new AbortController();
|
|
28
|
+
const abort = () => controller.abort();
|
|
29
|
+
const timeout = setTimeout(abort, options.requestTimeoutMs ?? 1e4);
|
|
30
|
+
if (options.signal.aborted) abort();
|
|
31
|
+
else options.signal.addEventListener("abort", abort, { once: true });
|
|
32
|
+
return fetch(`${options.baseUrl}${path}`, {
|
|
33
|
+
method: "POST",
|
|
34
|
+
headers: { Authorization: `Bearer ${options.apiKey}`, "Content-Type": "application/json" },
|
|
35
|
+
body: JSON.stringify(body),
|
|
36
|
+
signal: controller.signal
|
|
37
|
+
}).finally(() => {
|
|
38
|
+
clearTimeout(timeout);
|
|
39
|
+
options.signal.removeEventListener("abort", abort);
|
|
40
|
+
});
|
|
41
|
+
};
|
|
42
|
+
var postLiveSessionUntilAccepted = async (options, path, body) => {
|
|
43
|
+
while (!options.signal.aborted) {
|
|
44
|
+
try {
|
|
45
|
+
const response = await postLiveSession(options, path, body);
|
|
46
|
+
if (response.ok) return;
|
|
47
|
+
if (response.status < 500 && response.status !== 429) {
|
|
48
|
+
throw new Error(`${path} rejected (${response.status})`);
|
|
49
|
+
}
|
|
50
|
+
} catch (error) {
|
|
51
|
+
if (options.signal.aborted) throw error;
|
|
52
|
+
if (error instanceof Error && error.message.startsWith(`${path} rejected`)) throw error;
|
|
53
|
+
}
|
|
54
|
+
await liveSessionSleep(options.pollIntervalMs ?? 2e3, options.signal);
|
|
55
|
+
}
|
|
56
|
+
throw new Error("Live session stopped before it linked");
|
|
57
|
+
};
|
|
58
|
+
var startLiveSessionHeartbeat = (options, body, intervalMs = LIVE_SESSION_HEARTBEAT_MS, onBeat) => {
|
|
59
|
+
const timer = setInterval(() => {
|
|
60
|
+
if (!options.signal.aborted) {
|
|
61
|
+
onBeat?.();
|
|
62
|
+
void postLiveSession(options, "/api/agent/event", body).catch(() => {
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
}, intervalMs);
|
|
66
|
+
timer.unref();
|
|
67
|
+
return () => clearInterval(timer);
|
|
68
|
+
};
|
|
69
|
+
var LiveTranscriptRelay = class {
|
|
70
|
+
constructor(options, sessionId) {
|
|
71
|
+
this.options = options;
|
|
72
|
+
this.sessionId = sessionId;
|
|
73
|
+
}
|
|
74
|
+
queue = [];
|
|
75
|
+
sessionKey = null;
|
|
76
|
+
wrappedKey = null;
|
|
77
|
+
providerWrappedKey = null;
|
|
78
|
+
providerKeyId = null;
|
|
79
|
+
keyUploaded = false;
|
|
80
|
+
keyRejected = false;
|
|
81
|
+
flushing = null;
|
|
82
|
+
add(value) {
|
|
83
|
+
if (this.keyRejected) return;
|
|
84
|
+
const raw = JSON.stringify(value);
|
|
85
|
+
if (raw.length > MAX_TRANSCRIPT_TEXT) return;
|
|
86
|
+
if (this.queue.length === MAX_TRANSCRIPT_QUEUE) this.queue.shift();
|
|
87
|
+
this.queue.push(raw);
|
|
88
|
+
void this.flush();
|
|
89
|
+
}
|
|
90
|
+
async flush() {
|
|
91
|
+
if (this.flushing) return this.flushing;
|
|
92
|
+
if (this.queue.length === 0) return;
|
|
93
|
+
this.flushing = this.drainQueue().finally(() => {
|
|
94
|
+
this.flushing = null;
|
|
95
|
+
});
|
|
96
|
+
return this.flushing;
|
|
97
|
+
}
|
|
98
|
+
async drainQueue() {
|
|
99
|
+
while (this.queue.length > 0 && await this.flushBatch()) {
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
async flushBatch() {
|
|
103
|
+
try {
|
|
104
|
+
if (!this.sessionKey) {
|
|
105
|
+
const recipients = await fetchTranscriptRecipients(this.options.apiKey, this.options.baseUrl);
|
|
106
|
+
if (!recipients) return false;
|
|
107
|
+
this.sessionKey = generateSessionKey();
|
|
108
|
+
this.wrappedKey = wrapSessionKey(this.sessionKey, decodePublicKey(recipients.phonePublicKey));
|
|
109
|
+
if (recipients.provider) {
|
|
110
|
+
this.providerWrappedKey = wrapSessionKey(this.sessionKey, decodePublicKey(recipients.provider.publicKey));
|
|
111
|
+
this.providerKeyId = recipients.provider.keyId;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
const count = Math.min(this.queue.length, 50);
|
|
115
|
+
const records = buildTranscriptRecords(this.queue.slice(0, count), this.sessionKey).records;
|
|
116
|
+
if (records.length === 0) {
|
|
117
|
+
this.queue.splice(0, count);
|
|
118
|
+
return true;
|
|
119
|
+
}
|
|
120
|
+
const result = await uploadTranscriptRecords(this.options.apiKey, {
|
|
121
|
+
sessionId: this.sessionId,
|
|
122
|
+
machineId: this.options.machineId,
|
|
123
|
+
dataEncryptionKey: this.keyUploaded ? void 0 : this.wrappedKey ?? void 0,
|
|
124
|
+
providerWrappedSessionKey: this.keyUploaded ? void 0 : this.providerWrappedKey ?? void 0,
|
|
125
|
+
providerKeyId: this.keyUploaded ? void 0 : this.providerKeyId ?? void 0,
|
|
126
|
+
records
|
|
127
|
+
}, this.options.baseUrl);
|
|
128
|
+
if (result.keyConflict) {
|
|
129
|
+
this.keyRejected = true;
|
|
130
|
+
this.queue.length = 0;
|
|
131
|
+
return false;
|
|
132
|
+
}
|
|
133
|
+
if (result.ok) {
|
|
134
|
+
this.keyUploaded ||= result.keyRegistered;
|
|
135
|
+
this.queue.splice(0, count);
|
|
136
|
+
return true;
|
|
137
|
+
}
|
|
138
|
+
return false;
|
|
139
|
+
} catch {
|
|
140
|
+
return false;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
export {
|
|
146
|
+
liveSessionSleep,
|
|
147
|
+
postLiveSession,
|
|
148
|
+
postLiveSessionUntilAccepted,
|
|
149
|
+
startLiveSessionHeartbeat,
|
|
150
|
+
LiveTranscriptRelay
|
|
151
|
+
};
|
|
@@ -0,0 +1,305 @@
|
|
|
1
|
+
import {
|
|
2
|
+
spawnClaude
|
|
3
|
+
} from "./chunk-XHKBHWLX.js";
|
|
4
|
+
import {
|
|
5
|
+
codexHome,
|
|
6
|
+
codexHooksJsonIn,
|
|
7
|
+
isPusharyCodexCommand,
|
|
8
|
+
resolvePathBinary
|
|
9
|
+
} from "./chunk-A2A2YTMG.js";
|
|
10
|
+
import {
|
|
11
|
+
getPackageVersion
|
|
12
|
+
} from "./chunk-NQTHFF4W.js";
|
|
13
|
+
|
|
14
|
+
// src/codex-app-server.ts
|
|
15
|
+
import { existsSync, realpathSync } from "fs";
|
|
16
|
+
import { join } from "path";
|
|
17
|
+
var CODEX_HOOK_STATUS = {
|
|
18
|
+
trusted: "trusted",
|
|
19
|
+
untrusted: "untrusted",
|
|
20
|
+
modified: "modified",
|
|
21
|
+
managed: "managed",
|
|
22
|
+
disabled: "disabled",
|
|
23
|
+
notListed: "not listed"
|
|
24
|
+
};
|
|
25
|
+
var CODEX_APP_SERVER_ARGS = ["app-server"];
|
|
26
|
+
var CODEX_APP_SERVER_TIMEOUT_MS = 1e4;
|
|
27
|
+
var TERMINATE_GRACE_MS = 1e3;
|
|
28
|
+
var MAX_RPC_LINE = 4 * 1024 * 1024;
|
|
29
|
+
var STDERR_TAIL_LIMIT = 400;
|
|
30
|
+
var PUSHARY_CLIENT_NAME = "pushary";
|
|
31
|
+
var METHOD_INITIALIZE = "initialize";
|
|
32
|
+
var METHOD_INITIALIZED = "initialized";
|
|
33
|
+
var METHOD_HOOKS_LIST = "hooks/list";
|
|
34
|
+
var METHOD_CONFIG_BATCH_WRITE = "config/batchWrite";
|
|
35
|
+
var HOOKS_STATE_KEY_PATH = "hooks.state";
|
|
36
|
+
var CODEX_INSTALLATION_ID_FILE = "installation_id";
|
|
37
|
+
var CODEX_NOT_ASKED = {
|
|
38
|
+
notInteractive: "Pushary only starts Codex when you run setup, upgrade or doctor yourself at a terminal",
|
|
39
|
+
noCodexOnPath: "there is no codex command on PATH",
|
|
40
|
+
neverOpened: "Codex has not been opened on this machine yet",
|
|
41
|
+
insideAnApp: "the codex command resolves inside an app bundle, which Pushary does not run"
|
|
42
|
+
};
|
|
43
|
+
var UNRECOGNISED_LISTING = "Codex answered hooks/list in a shape Pushary does not recognise";
|
|
44
|
+
var isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
45
|
+
var isCodexListedHook = (value) => isRecord(value) && typeof value.key === "string" && typeof value.eventName === "string" && typeof value.command === "string" && typeof value.sourcePath === "string" && typeof value.enabled === "boolean" && typeof value.isManaged === "boolean" && typeof value.currentHash === "string" && typeof value.trustStatus === "string";
|
|
46
|
+
var listedHookFields = (hook) => ({
|
|
47
|
+
key: hook.key,
|
|
48
|
+
eventName: hook.eventName,
|
|
49
|
+
command: hook.command,
|
|
50
|
+
sourcePath: hook.sourcePath,
|
|
51
|
+
enabled: hook.enabled,
|
|
52
|
+
isManaged: hook.isManaged,
|
|
53
|
+
currentHash: hook.currentHash,
|
|
54
|
+
trustStatus: hook.trustStatus
|
|
55
|
+
});
|
|
56
|
+
var diagnosticMessages = (value) => {
|
|
57
|
+
if (!Array.isArray(value)) return [];
|
|
58
|
+
return value.flatMap((entry) => {
|
|
59
|
+
if (typeof entry === "string") return [entry];
|
|
60
|
+
if (isRecord(entry) && typeof entry.message === "string") return [entry.message];
|
|
61
|
+
return [];
|
|
62
|
+
});
|
|
63
|
+
};
|
|
64
|
+
var hooksListingOf = (result) => {
|
|
65
|
+
if (!isRecord(result) || !Array.isArray(result.data)) return null;
|
|
66
|
+
const hooks = [];
|
|
67
|
+
const warnings = [];
|
|
68
|
+
for (const layer of result.data) {
|
|
69
|
+
if (!isRecord(layer) || !Array.isArray(layer.hooks)) return null;
|
|
70
|
+
hooks.push(...layer.hooks.filter(isCodexListedHook).map(listedHookFields));
|
|
71
|
+
warnings.push(...diagnosticMessages(layer.warnings), ...diagnosticMessages(layer.errors));
|
|
72
|
+
}
|
|
73
|
+
return { hooks, warnings };
|
|
74
|
+
};
|
|
75
|
+
var isPusharyHookIn = (hooksJsonPath, hook) => hook.sourcePath === hooksJsonPath && isPusharyCodexCommand(hook.command);
|
|
76
|
+
var isTrustableByPushary = (hook) => hook.enabled && !hook.isManaged && hook.trustStatus !== CODEX_HOOK_STATUS.managed;
|
|
77
|
+
var pusharyHooksIn = (hooks, hooksJsonPath) => hooks.filter((hook) => isPusharyHookIn(hooksJsonPath, hook));
|
|
78
|
+
var selectPusharyCodexHooks = (hooks, hooksJsonPath) => pusharyHooksIn(hooks, hooksJsonPath).filter(isTrustableByPushary);
|
|
79
|
+
var codexTrustVerdict = (hooks, hooksJsonPath) => {
|
|
80
|
+
if (pusharyHooksIn(hooks, hooksJsonPath).length === 0) return { kind: "unlisted" };
|
|
81
|
+
const untrusted = selectPusharyCodexHooks(hooks, hooksJsonPath).filter((hook) => hook.trustStatus !== CODEX_HOOK_STATUS.trusted);
|
|
82
|
+
return untrusted.length === 0 ? { kind: "trusted" } : { kind: "untrusted", hooks: untrusted };
|
|
83
|
+
};
|
|
84
|
+
var codexTrustEdit = (hooks) => ({
|
|
85
|
+
edits: [{
|
|
86
|
+
keyPath: HOOKS_STATE_KEY_PATH,
|
|
87
|
+
mergeStrategy: "upsert",
|
|
88
|
+
value: Object.fromEntries(hooks.map((hook) => [hook.key, { trusted_hash: hook.currentHash }]))
|
|
89
|
+
}],
|
|
90
|
+
reloadUserConfig: true
|
|
91
|
+
});
|
|
92
|
+
var initializeParams = (clientVersion) => ({
|
|
93
|
+
clientInfo: { name: PUSHARY_CLIENT_NAME, title: null, version: clientVersion }
|
|
94
|
+
});
|
|
95
|
+
var hooksListParams = (cwd) => ({ cwds: [cwd] });
|
|
96
|
+
var hookEventOf = (eventName) => eventName.charAt(0).toUpperCase() + eventName.slice(1);
|
|
97
|
+
var readingOf = (hook) => ({
|
|
98
|
+
event: hookEventOf(hook.eventName),
|
|
99
|
+
status: hook.enabled ? hook.trustStatus : CODEX_HOOK_STATUS.disabled
|
|
100
|
+
});
|
|
101
|
+
var codexHookTrustByEvent = (events, hooks) => events.map((event) => hooks.map(readingOf).find((reading) => reading.event === event) ?? { event, status: CODEX_HOOK_STATUS.notListed });
|
|
102
|
+
var CODEX_TRUST_FAILURES = /* @__PURE__ */ new Set([CODEX_HOOK_STATUS.untrusted, CODEX_HOOK_STATUS.modified]);
|
|
103
|
+
var isCodexTrustFailure = (reading) => CODEX_TRUST_FAILURES.has(reading.status);
|
|
104
|
+
var eventsWithStatus = (readings, status) => readings.filter((reading) => reading.status === status).map((reading) => reading.event);
|
|
105
|
+
var assessCodexHookTrust = (events, hooks) => {
|
|
106
|
+
const readings = codexHookTrustByEvent(events, hooks);
|
|
107
|
+
const notListed = eventsWithStatus(readings, CODEX_HOOK_STATUS.notListed);
|
|
108
|
+
if (readings.length > 0 && notListed.length === readings.length) return { kind: "unlisted" };
|
|
109
|
+
const disabled = eventsWithStatus(readings, CODEX_HOOK_STATUS.disabled);
|
|
110
|
+
const failures = readings.filter(isCodexTrustFailure);
|
|
111
|
+
if (failures.length > 0) return { kind: "untrusted", failures, notListed, disabled };
|
|
112
|
+
const trusted = eventsWithStatus(readings, CODEX_HOOK_STATUS.trusted).length;
|
|
113
|
+
return { kind: "trusted", trusted, total: readings.length, notListed, disabled };
|
|
114
|
+
};
|
|
115
|
+
var mayAskCodex = (conditions) => {
|
|
116
|
+
if (!conditions.interactive) return { kind: "skipped", reason: CODEX_NOT_ASKED.notInteractive };
|
|
117
|
+
if (!conditions.codexOnPath) return { kind: "skipped", reason: CODEX_NOT_ASKED.noCodexOnPath };
|
|
118
|
+
if (!conditions.codexOpenedBefore) return { kind: "skipped", reason: CODEX_NOT_ASKED.neverOpened };
|
|
119
|
+
if (conditions.codexOnPath.split(/[\\/]/).some((part) => part.toLowerCase().endsWith(".app"))) {
|
|
120
|
+
return { kind: "skipped", reason: CODEX_NOT_ASKED.insideAnApp };
|
|
121
|
+
}
|
|
122
|
+
return { kind: "ask", binary: conditions.codexOnPath };
|
|
123
|
+
};
|
|
124
|
+
var canonicalPath = (path) => {
|
|
125
|
+
try {
|
|
126
|
+
return realpathSync(path);
|
|
127
|
+
} catch {
|
|
128
|
+
return path;
|
|
129
|
+
}
|
|
130
|
+
};
|
|
131
|
+
var withCanonicalSources = (hooks) => hooks.map((hook) => ({ ...hook, sourcePath: canonicalPath(hook.sourcePath) }));
|
|
132
|
+
var hasExited = (child) => child.exitCode !== null || child.signalCode !== null;
|
|
133
|
+
var waitForExit = (child, ms) => new Promise((resolve) => {
|
|
134
|
+
if (hasExited(child)) return resolve(true);
|
|
135
|
+
const timer = setTimeout(() => resolve(false), ms);
|
|
136
|
+
child.once("exit", () => {
|
|
137
|
+
clearTimeout(timer);
|
|
138
|
+
resolve(true);
|
|
139
|
+
});
|
|
140
|
+
});
|
|
141
|
+
var terminate = async (child) => {
|
|
142
|
+
if (child.pid === void 0 || hasExited(child)) return;
|
|
143
|
+
child.stdin?.end();
|
|
144
|
+
child.kill("SIGTERM");
|
|
145
|
+
if (await waitForExit(child, TERMINATE_GRACE_MS)) return;
|
|
146
|
+
child.kill("SIGKILL");
|
|
147
|
+
await waitForExit(child, TERMINATE_GRACE_MS);
|
|
148
|
+
};
|
|
149
|
+
var rpcErrorMessage = (error) => isRecord(error) && typeof error.message === "string" ? error.message : "Codex returned an error";
|
|
150
|
+
var describeExit = (code, signal, stderr) => {
|
|
151
|
+
const how = signal ? `signal ${signal}` : `code ${code ?? "unknown"}`;
|
|
152
|
+
const tail = stderr.trim();
|
|
153
|
+
return tail ? `Codex app-server exited (${how}): ${tail}` : `Codex app-server exited (${how})`;
|
|
154
|
+
};
|
|
155
|
+
var runCodexAppServer = (launch, conversation) => {
|
|
156
|
+
const child = spawnClaude(launch.binary, CODEX_APP_SERVER_ARGS, {
|
|
157
|
+
cwd: launch.codexHome,
|
|
158
|
+
env: { ...process.env, CODEX_HOME: launch.codexHome },
|
|
159
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
160
|
+
windowsHide: true
|
|
161
|
+
});
|
|
162
|
+
const pending = /* @__PURE__ */ new Map();
|
|
163
|
+
let nextId = 1;
|
|
164
|
+
let buffer = "";
|
|
165
|
+
let stderrTail = "";
|
|
166
|
+
let stoppedReason = null;
|
|
167
|
+
let stop = () => {
|
|
168
|
+
};
|
|
169
|
+
const stopped = new Promise((resolve) => {
|
|
170
|
+
stop = (reason) => {
|
|
171
|
+
if (stoppedReason !== null) return;
|
|
172
|
+
stoppedReason = reason;
|
|
173
|
+
for (const settle of pending.values()) settle({ kind: "error", message: reason });
|
|
174
|
+
pending.clear();
|
|
175
|
+
resolve({ kind: "unavailable", reason });
|
|
176
|
+
};
|
|
177
|
+
});
|
|
178
|
+
const write = (message) => {
|
|
179
|
+
if (stoppedReason === null) child.stdin?.write(`${JSON.stringify(message)}
|
|
180
|
+
`);
|
|
181
|
+
};
|
|
182
|
+
const handleLine = (line) => {
|
|
183
|
+
let message;
|
|
184
|
+
try {
|
|
185
|
+
message = JSON.parse(line);
|
|
186
|
+
} catch {
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
if (!isRecord(message) || typeof message.id !== "number" || "method" in message) return;
|
|
190
|
+
const settle = pending.get(message.id);
|
|
191
|
+
if (!settle) return;
|
|
192
|
+
pending.delete(message.id);
|
|
193
|
+
settle("error" in message && message.error !== void 0 && message.error !== null ? { kind: "error", message: rpcErrorMessage(message.error) } : { kind: "result", value: message.result });
|
|
194
|
+
};
|
|
195
|
+
child.stdin?.on("error", () => {
|
|
196
|
+
});
|
|
197
|
+
child.stdout?.setEncoding("utf8");
|
|
198
|
+
child.stdout?.on("data", (chunk) => {
|
|
199
|
+
buffer += chunk;
|
|
200
|
+
let newline = buffer.indexOf("\n");
|
|
201
|
+
while (newline >= 0) {
|
|
202
|
+
handleLine(buffer.slice(0, newline));
|
|
203
|
+
buffer = buffer.slice(newline + 1);
|
|
204
|
+
newline = buffer.indexOf("\n");
|
|
205
|
+
}
|
|
206
|
+
if (buffer.length > MAX_RPC_LINE) stop("Codex app-server sent an oversized message");
|
|
207
|
+
});
|
|
208
|
+
child.stderr?.setEncoding("utf8");
|
|
209
|
+
child.stderr?.on("data", (chunk) => {
|
|
210
|
+
stderrTail = (stderrTail + chunk).slice(-STDERR_TAIL_LIMIT);
|
|
211
|
+
});
|
|
212
|
+
child.once("error", (error) => stop(`could not start ${launch.binary}: ${error.message}`));
|
|
213
|
+
child.once("close", (code, signal) => stop(describeExit(code, signal, stderrTail)));
|
|
214
|
+
const timer = setTimeout(
|
|
215
|
+
() => stop(`Codex app-server did not answer within ${launch.timeoutMs / 1e3}s`),
|
|
216
|
+
launch.timeoutMs
|
|
217
|
+
);
|
|
218
|
+
const session = {
|
|
219
|
+
request: (method, params) => {
|
|
220
|
+
if (stoppedReason !== null) return Promise.resolve({ kind: "error", message: stoppedReason });
|
|
221
|
+
const id = nextId++;
|
|
222
|
+
return new Promise((resolve) => {
|
|
223
|
+
pending.set(id, resolve);
|
|
224
|
+
write({ id, method, params });
|
|
225
|
+
});
|
|
226
|
+
},
|
|
227
|
+
notify: (method) => write({ method })
|
|
228
|
+
};
|
|
229
|
+
const conversed = conversation(session).catch((error) => ({
|
|
230
|
+
kind: "unavailable",
|
|
231
|
+
reason: error instanceof Error ? error.message : String(error)
|
|
232
|
+
}));
|
|
233
|
+
return Promise.race([conversed, stopped]).finally(async () => {
|
|
234
|
+
clearTimeout(timer);
|
|
235
|
+
await terminate(child);
|
|
236
|
+
});
|
|
237
|
+
};
|
|
238
|
+
var launchOf = (options) => {
|
|
239
|
+
const home = options.codexHome ?? codexHome();
|
|
240
|
+
const binary = options.codexOnPath === void 0 ? resolvePathBinary("codex") ?? null : options.codexOnPath;
|
|
241
|
+
const decision = mayAskCodex({
|
|
242
|
+
interactive: options.interactive ?? process.stdout.isTTY === true,
|
|
243
|
+
codexOnPath: binary ? canonicalPath(binary) : null,
|
|
244
|
+
codexOpenedBefore: existsSync(join(home, CODEX_INSTALLATION_ID_FILE))
|
|
245
|
+
});
|
|
246
|
+
if (decision.kind === "skipped") return decision;
|
|
247
|
+
return { binary: decision.binary, codexHome: home, timeoutMs: options.timeoutMs ?? CODEX_APP_SERVER_TIMEOUT_MS };
|
|
248
|
+
};
|
|
249
|
+
var isSkipped = (value) => "kind" in value && value.kind === "skipped";
|
|
250
|
+
var openSession = async (session) => {
|
|
251
|
+
const initialized = await session.request(METHOD_INITIALIZE, initializeParams(getPackageVersion()));
|
|
252
|
+
if (initialized.kind === "error") return { kind: "unavailable", reason: `Codex refused initialize: ${initialized.message}` };
|
|
253
|
+
session.notify(METHOD_INITIALIZED);
|
|
254
|
+
return null;
|
|
255
|
+
};
|
|
256
|
+
var listHooks = async (session, launch) => {
|
|
257
|
+
const listed = await session.request(METHOD_HOOKS_LIST, hooksListParams(launch.codexHome));
|
|
258
|
+
if (listed.kind === "error") return { kind: "unavailable", reason: `Codex could not list hooks: ${listed.message}` };
|
|
259
|
+
const listing = hooksListingOf(listed.value);
|
|
260
|
+
if (!listing) return { kind: "unavailable", reason: UNRECOGNISED_LISTING };
|
|
261
|
+
return { hooks: withCanonicalSources(listing.hooks), warnings: listing.warnings };
|
|
262
|
+
};
|
|
263
|
+
var isListing = (value) => !("kind" in value);
|
|
264
|
+
var confirmationAfterWrite = (listing, hooksJsonPath, writeError) => {
|
|
265
|
+
const verdict = codexTrustVerdict(listing.hooks, hooksJsonPath);
|
|
266
|
+
if (verdict.kind === "trusted") return { kind: "verified", wrote: true };
|
|
267
|
+
if (verdict.kind === "unlisted") return { kind: "unlisted", warnings: listing.warnings };
|
|
268
|
+
return { kind: "refused", hooks: verdict.hooks.map(readingOf), writeError };
|
|
269
|
+
};
|
|
270
|
+
var trustConversation = (launch, hooksJsonPath) => async (session) => {
|
|
271
|
+
const unavailable = await openSession(session);
|
|
272
|
+
if (unavailable) return unavailable;
|
|
273
|
+
const before = await listHooks(session, launch);
|
|
274
|
+
if (!isListing(before)) return before;
|
|
275
|
+
const verdict = codexTrustVerdict(before.hooks, hooksJsonPath);
|
|
276
|
+
if (verdict.kind === "trusted") return { kind: "verified", wrote: false };
|
|
277
|
+
if (verdict.kind === "unlisted") return { kind: "unlisted", warnings: before.warnings };
|
|
278
|
+
const written = await session.request(METHOD_CONFIG_BATCH_WRITE, codexTrustEdit(verdict.hooks));
|
|
279
|
+
const after = await listHooks(session, launch);
|
|
280
|
+
if (!isListing(after)) return after;
|
|
281
|
+
return confirmationAfterWrite(after, hooksJsonPath, written.kind === "error" ? written.message : null);
|
|
282
|
+
};
|
|
283
|
+
var listingConversation = (launch, hooksJsonPath) => async (session) => {
|
|
284
|
+
const unavailable = await openSession(session);
|
|
285
|
+
if (unavailable) return unavailable;
|
|
286
|
+
const listing = await listHooks(session, launch);
|
|
287
|
+
if (!isListing(listing)) return listing;
|
|
288
|
+
return { kind: "listed", hooks: pusharyHooksIn(listing.hooks, hooksJsonPath) };
|
|
289
|
+
};
|
|
290
|
+
var ensureCodexTrustsPusharyHooks = async (options = {}) => {
|
|
291
|
+
const launch = launchOf(options);
|
|
292
|
+
if (isSkipped(launch)) return launch;
|
|
293
|
+
return runCodexAppServer(launch, trustConversation(launch, canonicalPath(codexHooksJsonIn(launch.codexHome))));
|
|
294
|
+
};
|
|
295
|
+
var readCodexTrustForPusharyHooks = async (options = {}) => {
|
|
296
|
+
const launch = launchOf(options);
|
|
297
|
+
if (isSkipped(launch)) return launch;
|
|
298
|
+
return runCodexAppServer(launch, listingConversation(launch, canonicalPath(codexHooksJsonIn(launch.codexHome))));
|
|
299
|
+
};
|
|
300
|
+
|
|
301
|
+
export {
|
|
302
|
+
assessCodexHookTrust,
|
|
303
|
+
ensureCodexTrustsPusharyHooks,
|
|
304
|
+
readCodexTrustForPusharyHooks
|
|
305
|
+
};
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import {
|
|
2
|
+
getBaseUrl
|
|
3
|
+
} from "./chunk-GPEGOGRJ.js";
|
|
4
|
+
|
|
5
|
+
// src/transcript-api.ts
|
|
6
|
+
var fetchTranscriptRecipients = async (apiKey, baseUrl = getBaseUrl()) => {
|
|
7
|
+
try {
|
|
8
|
+
const res = await fetch(`${baseUrl}/api/agent/encryption-account`, {
|
|
9
|
+
headers: { Authorization: `Bearer ${apiKey}` },
|
|
10
|
+
signal: AbortSignal.timeout(5e3)
|
|
11
|
+
});
|
|
12
|
+
if (!res.ok) return null;
|
|
13
|
+
const data = await res.json();
|
|
14
|
+
if (typeof data.publicKey !== "string" || data.publicKey.length === 0) return null;
|
|
15
|
+
if (data.providerRecovery === void 0 || data.providerRecovery === null) {
|
|
16
|
+
return { phonePublicKey: data.publicKey, provider: null };
|
|
17
|
+
}
|
|
18
|
+
if (typeof data.providerRecovery !== "object") return null;
|
|
19
|
+
const provider = data.providerRecovery;
|
|
20
|
+
if (typeof provider.required !== "boolean" || typeof provider.keyId !== "string" || provider.keyId.length === 0 || typeof provider.publicKey !== "string" || provider.publicKey.length === 0) return null;
|
|
21
|
+
return {
|
|
22
|
+
phonePublicKey: data.publicKey,
|
|
23
|
+
provider: { required: provider.required, keyId: provider.keyId, publicKey: provider.publicKey }
|
|
24
|
+
};
|
|
25
|
+
} catch {
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
var fetchAccountPublicKey = async (apiKey, baseUrl = getBaseUrl()) => (await fetchTranscriptRecipients(apiKey, baseUrl))?.phonePublicKey ?? null;
|
|
30
|
+
var uploadTranscriptRecords = async (apiKey, params, baseUrl = getBaseUrl()) => {
|
|
31
|
+
try {
|
|
32
|
+
const res = await fetch(`${baseUrl}/api/agent/transcript`, {
|
|
33
|
+
method: "POST",
|
|
34
|
+
headers: { Authorization: `Bearer ${apiKey}`, "Content-Type": "application/json" },
|
|
35
|
+
body: JSON.stringify(params),
|
|
36
|
+
signal: AbortSignal.timeout(15e3)
|
|
37
|
+
});
|
|
38
|
+
const body = await res.json().catch(() => null);
|
|
39
|
+
const keyRegistered = body?.keyRegistered === true;
|
|
40
|
+
const keyConflict = body?.keyConflict === true;
|
|
41
|
+
const keyUnavailable = res.status === 503;
|
|
42
|
+
const ids = (value) => Array.isArray(value) ? value.filter((v) => typeof v === "string") : [];
|
|
43
|
+
return {
|
|
44
|
+
ok: res.ok && !keyConflict && (params.dataEncryptionKey === void 0 || keyRegistered),
|
|
45
|
+
keyRegistered,
|
|
46
|
+
keyConflict,
|
|
47
|
+
keyUnavailable,
|
|
48
|
+
settledIds: Array.isArray(body?.settledIds) ? ids(body.settledIds) : null,
|
|
49
|
+
rejectedIds: ids(body?.rejectedIds)
|
|
50
|
+
};
|
|
51
|
+
} catch {
|
|
52
|
+
return { ok: false, keyRegistered: false, keyConflict: false, keyUnavailable: false, settledIds: null, rejectedIds: [] };
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
export {
|
|
57
|
+
fetchTranscriptRecipients,
|
|
58
|
+
fetchAccountPublicKey,
|
|
59
|
+
uploadTranscriptRecords
|
|
60
|
+
};
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import {
|
|
2
|
+
writeJsonAtomicDurable
|
|
3
|
+
} from "./chunk-T7E75EKA.js";
|
|
4
|
+
|
|
5
|
+
// src/transcript-recipient-pin.ts
|
|
6
|
+
import { existsSync, mkdirSync, readFileSync, rmSync } from "fs";
|
|
7
|
+
import { homedir } from "os";
|
|
8
|
+
import { dirname, join } from "path";
|
|
9
|
+
var pinFile = () => process.env.PUSHARY_RECIPIENT_PIN_FILE?.trim() || join(homedir(), ".pushary", "recipients.json");
|
|
10
|
+
var asPin = (offer) => ({
|
|
11
|
+
phonePublicKey: offer.phonePublicKey,
|
|
12
|
+
providerKeyId: offer.provider?.keyId ?? null,
|
|
13
|
+
providerPublicKey: offer.provider?.publicKey ?? null
|
|
14
|
+
});
|
|
15
|
+
var loadPin = () => {
|
|
16
|
+
const path = pinFile();
|
|
17
|
+
if (!existsSync(path)) return null;
|
|
18
|
+
try {
|
|
19
|
+
const raw = JSON.parse(readFileSync(path, "utf-8"));
|
|
20
|
+
if (typeof raw.phonePublicKey !== "string" || raw.phonePublicKey.length === 0) return null;
|
|
21
|
+
return {
|
|
22
|
+
phonePublicKey: raw.phonePublicKey,
|
|
23
|
+
providerKeyId: typeof raw.providerKeyId === "string" ? raw.providerKeyId : null,
|
|
24
|
+
providerPublicKey: typeof raw.providerPublicKey === "string" ? raw.providerPublicKey : null
|
|
25
|
+
};
|
|
26
|
+
} catch {
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
var savePin = (pin) => {
|
|
31
|
+
const path = pinFile();
|
|
32
|
+
try {
|
|
33
|
+
mkdirSync(dirname(path), { recursive: true, mode: 448 });
|
|
34
|
+
writeJsonAtomicDurable(path, pin, 384);
|
|
35
|
+
return true;
|
|
36
|
+
} catch {
|
|
37
|
+
return false;
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
var clearPin = () => {
|
|
41
|
+
try {
|
|
42
|
+
rmSync(pinFile(), { force: true });
|
|
43
|
+
} catch {
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
var verifyRecipients = (offer, pinned) => {
|
|
47
|
+
if (!pinned) return { kind: "first-use", pin: asPin(offer) };
|
|
48
|
+
const next = asPin(offer);
|
|
49
|
+
const changed = [];
|
|
50
|
+
if (next.phonePublicKey !== pinned.phonePublicKey) changed.push("the phone key this machine seals to");
|
|
51
|
+
if (next.providerKeyId !== pinned.providerKeyId) changed.push("the compliance recovery key id");
|
|
52
|
+
if (next.providerPublicKey !== pinned.providerPublicKey) changed.push("the compliance recovery key");
|
|
53
|
+
return changed.length === 0 ? { kind: "match" } : { kind: "changed", changed };
|
|
54
|
+
};
|
|
55
|
+
var fingerprint = (publicKey) => {
|
|
56
|
+
const hex = Buffer.from(publicKey, "base64").toString("hex").toUpperCase();
|
|
57
|
+
return (hex.slice(0, 8).match(/.{2}/g) ?? []).join(" ");
|
|
58
|
+
};
|
|
59
|
+
var changedRecipientsMessage = (verdict, offer) => [
|
|
60
|
+
"[pushary] Transcript sync stopped: the keys this machine seals transcripts to have changed.",
|
|
61
|
+
...verdict.changed.map((what) => `[pushary] changed: ${what}`),
|
|
62
|
+
`[pushary] now offered: phone ${fingerprint(offer.phonePublicKey)}${offer.provider ? `, recovery ${offer.provider.keyId.slice(0, 8)}` : ", no recovery key"}`,
|
|
63
|
+
"[pushary] If you reinstalled the app or rotated a key, this is expected. Compare the",
|
|
64
|
+
"[pushary] fingerprint above with the one in the app under Settings, then run:",
|
|
65
|
+
"[pushary] pushary transcripts trust --rotate",
|
|
66
|
+
"[pushary] Until then nothing is uploaded, so no turn is sealed to a key you have not seen."
|
|
67
|
+
].join("\n");
|
|
68
|
+
|
|
69
|
+
export {
|
|
70
|
+
loadPin,
|
|
71
|
+
savePin,
|
|
72
|
+
clearPin,
|
|
73
|
+
verifyRecipients,
|
|
74
|
+
fingerprint,
|
|
75
|
+
changedRecipientsMessage
|
|
76
|
+
};
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import {
|
|
2
|
+
parseVersion
|
|
3
|
+
} from "./chunk-3EVPNIBE.js";
|
|
4
|
+
|
|
5
|
+
// src/setup/agent-probe.ts
|
|
6
|
+
import { execSync } from "child_process";
|
|
7
|
+
import { readFileSync, realpathSync } from "fs";
|
|
8
|
+
|
|
9
|
+
// src/setup/claude-version.ts
|
|
10
|
+
import { dirname, basename, join } from "path";
|
|
11
|
+
var versionFromInstallPath = (path) => {
|
|
12
|
+
const name = basename(path);
|
|
13
|
+
return /^\d+\.\d+\.\d+$/.test(name) ? parseVersion(name) : null;
|
|
14
|
+
};
|
|
15
|
+
var versionFromManifest = (probe, binPath) => {
|
|
16
|
+
let dir = dirname(binPath);
|
|
17
|
+
for (let depth = 0; depth < 4; depth++) {
|
|
18
|
+
const raw = probe.readFile(join(dir, "package.json"));
|
|
19
|
+
if (raw) {
|
|
20
|
+
try {
|
|
21
|
+
const { version } = JSON.parse(raw);
|
|
22
|
+
if (typeof version === "string") {
|
|
23
|
+
const parsed = parseVersion(version);
|
|
24
|
+
if (parsed) return parsed;
|
|
25
|
+
}
|
|
26
|
+
} catch {
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
const parent = dirname(dir);
|
|
30
|
+
if (parent === dir) break;
|
|
31
|
+
dir = parent;
|
|
32
|
+
}
|
|
33
|
+
return null;
|
|
34
|
+
};
|
|
35
|
+
var readClaudeVersion = (probe) => {
|
|
36
|
+
const onPath2 = probe.resolveOnPath();
|
|
37
|
+
if (!onPath2) return null;
|
|
38
|
+
const resolved = probe.realpath(onPath2) ?? onPath2;
|
|
39
|
+
return versionFromInstallPath(resolved) ?? versionFromManifest(probe, resolved);
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
// src/setup/agent-probe.ts
|
|
43
|
+
var onPath = (name) => {
|
|
44
|
+
const command = process.platform === "win32" ? "where" : "which";
|
|
45
|
+
try {
|
|
46
|
+
const found = execSync(`${command} ${name}`, { encoding: "utf-8", stdio: "pipe", timeout: 5e3 }).split("\n")[0].trim();
|
|
47
|
+
return found || null;
|
|
48
|
+
} catch {
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
var realpath = (path) => {
|
|
53
|
+
try {
|
|
54
|
+
return realpathSync(path);
|
|
55
|
+
} catch {
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
var readFile = (path) => {
|
|
60
|
+
try {
|
|
61
|
+
return readFileSync(path, "utf-8");
|
|
62
|
+
} catch {
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
var detectClaudeVersion = () => readClaudeVersion({ resolveOnPath: () => onPath("claude"), realpath, readFile });
|
|
67
|
+
|
|
68
|
+
export {
|
|
69
|
+
detectClaudeVersion
|
|
70
|
+
};
|