@paigy/mcp 0.12.1 → 0.13.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.
@@ -0,0 +1,224 @@
1
+ import { createRequire as __createRequire } from 'node:module'; const require = __createRequire(import.meta.url);
2
+ import {
3
+ BACKEND_URL,
4
+ deriveAsk,
5
+ fetchRoster,
6
+ openSealedAnswer,
7
+ reach,
8
+ readKeyFile,
9
+ sealFields,
10
+ verifiedRecipients
11
+ } from "./chunk-O6W2T6D2.js";
12
+
13
+ // src/client.ts
14
+ import { existsSync, readFileSync } from "fs";
15
+ import { homedir } from "os";
16
+ import { join } from "path";
17
+
18
+ // src/when.ts
19
+ var WORD_NUMS = {
20
+ a: 1,
21
+ an: 1,
22
+ one: 1,
23
+ two: 2,
24
+ three: 3,
25
+ four: 4,
26
+ five: 5,
27
+ six: 6,
28
+ seven: 7,
29
+ eight: 8,
30
+ nine: 9,
31
+ ten: 10,
32
+ fifteen: 15,
33
+ twenty: 20,
34
+ thirty: 30,
35
+ forty: 40,
36
+ "forty-five": 45,
37
+ fifty: 50,
38
+ sixty: 60,
39
+ "half an": 0.5,
40
+ "a couple of": 2,
41
+ "a few": 3
42
+ };
43
+ function untilHour(hour, now) {
44
+ const due = new Date(now);
45
+ due.setHours(hour, 0, 0, 0);
46
+ if (due.getTime() <= now.getTime()) due.setDate(due.getDate() + 1);
47
+ return Math.round((due.getTime() - now.getTime()) / 1e3);
48
+ }
49
+ function parseDue(detail, now = /* @__PURE__ */ new Date()) {
50
+ const t = detail.toLowerCase().trim();
51
+ const rel = /(?:^|\b)in\s+(half an|a couple of|a few|an?|[\w-]+|\d+)\s*(minutes?|mins?|hours?|hrs?)\b/.exec(t);
52
+ if (rel) {
53
+ const n = /^\d+$/.test(rel[1]) ? Number(rel[1]) : WORD_NUMS[rel[1]];
54
+ if (n !== void 0) {
55
+ const unit = rel[2].startsWith("h") ? 3600 : 60;
56
+ return Math.round(n * unit);
57
+ }
58
+ }
59
+ if (/after lunch|lunchtime/.test(t)) return untilHour(13, now);
60
+ if (/tomorrow/.test(t)) {
61
+ const due = new Date(now);
62
+ due.setDate(due.getDate() + 1);
63
+ due.setHours(9, 0, 0, 0);
64
+ return Math.round((due.getTime() - now.getTime()) / 1e3);
65
+ }
66
+ if (/tonight|this evening/.test(t)) {
67
+ return now.getHours() < 20 ? untilHour(20, now) : 3600;
68
+ }
69
+ if (/end of (the )?day|eod/.test(t)) return untilHour(17, now);
70
+ if (/later|in a (bit|while)/.test(t)) return 1800;
71
+ return null;
72
+ }
73
+
74
+ // src/client.ts
75
+ var TOKEN_PATH = join(homedir(), ".paigy", "token.json");
76
+ function loadToken() {
77
+ if (process.env.PAIGY_TOKEN) return process.env.PAIGY_TOKEN;
78
+ if (existsSync(TOKEN_PATH)) {
79
+ try {
80
+ const raw = readFileSync(TOKEN_PATH, "utf8");
81
+ const parsed = JSON.parse(raw);
82
+ if (parsed.access_token) return parsed.access_token;
83
+ } catch {
84
+ }
85
+ }
86
+ return "";
87
+ }
88
+ var ONBOARD_MSG = "Not paired with Paigy yet \u2014 call the `pair` tool to connect this agent (it returns an approval link to show the user), then retry. Manual fallback: `npx -y @paigy/mcp paigy-mcp-onboard`.";
89
+ function ensureAuthed(res) {
90
+ if (res.status === 401) throw new Error(ONBOARD_MSG);
91
+ return res;
92
+ }
93
+ async function sealForE2ee(req, token, deps = {}) {
94
+ const keyFile = (deps.readKeyFile ?? readKeyFile)();
95
+ if (!keyFile?.e2ee) return req;
96
+ req = deriveAsk(req);
97
+ if (!keyFile.uikPub) {
98
+ throw new Error("E2EE pairing is missing its pinned identity key (key.json.uikPub) \u2014 re-pair; refusing to send plaintext.");
99
+ }
100
+ const roster = await (deps.fetchRoster ?? fetchRoster)(token);
101
+ if (!roster) {
102
+ throw new Error("E2EE pairing has no device roster to seal to \u2014 refusing to send plaintext. Approve a device on your phone, then retry.");
103
+ }
104
+ const recipients = verifiedRecipients(roster, keyFile.uikPub);
105
+ if (recipients.length === 0) {
106
+ throw new Error("No roster device verified against the pinned identity key \u2014 refusing to send plaintext (a server-injected device is dropped).");
107
+ }
108
+ const sealedContext = {
109
+ ...req.context,
110
+ ...req.repo !== void 0 ? { repo: req.repo } : {},
111
+ ...req.branch !== void 0 ? { branch: req.branch } : {}
112
+ };
113
+ const envelope = sealFields({ context: sealedContext, options: req.options, visuals: req.visuals }, recipients);
114
+ const { context: _c, options: _o, visuals: _v, repo: _r, branch: _b, ...meta } = req;
115
+ return { ...meta, envelope };
116
+ }
117
+ async function submitNotification(req) {
118
+ const token = loadToken();
119
+ const body = await sealForE2ee(req, token);
120
+ const res = ensureAuthed(await reach(`${BACKEND_URL}/api/notify`, {
121
+ method: "POST",
122
+ headers: { "content-type": "application/json", authorization: `Bearer ${token}` },
123
+ body: JSON.stringify(body)
124
+ }));
125
+ if (!res.ok) throw new Error(`notify failed: ${res.status} ${await res.text()}`);
126
+ return await res.json();
127
+ }
128
+ var sleep = (ms) => new Promise((r) => setTimeout(r, ms));
129
+ function decryptAnswer(reply, deps = {}) {
130
+ if (!reply.sealed) return reply.answer;
131
+ const keyFile = (deps.readKeyFile ?? readKeyFile)();
132
+ if (!keyFile?.keypair) {
133
+ throw new Error("received a sealed answer but this agent has no device key to open it \u2014 re-pair.");
134
+ }
135
+ return openSealedAnswer(reply.sealed, keyFile.keypair);
136
+ }
137
+ async function awaitReply(notificationId, opts = {}) {
138
+ const intervalMs = opts.intervalMs ?? 5e3;
139
+ const windowMs = opts.windowMs ?? 3e5;
140
+ const doSleep = opts.sleep ?? sleep;
141
+ const now = opts.now ?? Date.now;
142
+ const token = loadToken();
143
+ const start = now();
144
+ while (true) {
145
+ const res = ensureAuthed(await reach(`${BACKEND_URL}/api/await?notificationId=${encodeURIComponent(notificationId)}`, {
146
+ headers: { authorization: `Bearer ${token}` }
147
+ }));
148
+ if (!res.ok) throw new Error(`await failed: ${res.status} ${await res.text()}`);
149
+ const item = await res.json();
150
+ if (item.type !== "idle") return decryptItem(item);
151
+ if (now() - start >= windowMs) return { type: "idle" };
152
+ await doSleep(intervalMs);
153
+ }
154
+ }
155
+ function decryptItem(item) {
156
+ if (item.type !== "reply") return item;
157
+ const enriched = { ...item, intents: landIntents(item.intents) };
158
+ if (!enriched.sealed) return enriched;
159
+ const answer = decryptAnswer(enriched);
160
+ const { sealed: _s, ...rest } = enriched;
161
+ return { ...rest, answer };
162
+ }
163
+ function landIntents(intents) {
164
+ if (!intents?.length) return intents;
165
+ return intents.map((i) => {
166
+ if (i.kind !== "defer") return i;
167
+ const due = parseDue(i.detail);
168
+ return due !== null ? { ...i, dueInSeconds: due } : i;
169
+ });
170
+ }
171
+ async function checkReplies() {
172
+ const token = loadToken();
173
+ const res = ensureAuthed(await reach(`${BACKEND_URL}/api/pending`, {
174
+ headers: { authorization: `Bearer ${token}` }
175
+ }));
176
+ if (!res.ok) throw new Error(`check_replies failed: ${res.status} ${await res.text()}`);
177
+ const pending = await res.json();
178
+ return {
179
+ ...pending,
180
+ replies: pending.replies.map((raw) => {
181
+ const r = { ...raw, intents: landIntents(raw.intents) };
182
+ if (!r.sealed) return r;
183
+ const { sealed: _s, ...rest } = r;
184
+ return { ...rest, answer: decryptAnswer(r) };
185
+ })
186
+ };
187
+ }
188
+ async function setTaskState(notificationId, state) {
189
+ const res = ensureAuthed(await reach(`${BACKEND_URL}/api/notify/${notificationId}/state`, {
190
+ method: "PATCH",
191
+ headers: { "content-type": "application/json", authorization: `Bearer ${loadToken()}` },
192
+ body: JSON.stringify({ state })
193
+ }));
194
+ if (!res.ok) throw new Error(`set_task_state failed: ${res.status} ${await res.text()}`);
195
+ return await res.json();
196
+ }
197
+ async function registerDelivery(mode) {
198
+ const res = ensureAuthed(await reach(`${BACKEND_URL}/api/delivery`, {
199
+ method: "POST",
200
+ headers: { "content-type": "application/json", authorization: `Bearer ${loadToken()}` },
201
+ body: JSON.stringify({ mode })
202
+ }));
203
+ if (!res.ok) throw new Error(`register_delivery failed: ${res.status} ${await res.text()}`);
204
+ return await res.json();
205
+ }
206
+ async function scheduleCallback(req) {
207
+ const token = loadToken();
208
+ const res = ensureAuthed(await reach(`${BACKEND_URL}/api/callback`, {
209
+ method: "POST",
210
+ headers: { "content-type": "application/json", authorization: `Bearer ${token}` },
211
+ body: JSON.stringify(req)
212
+ }));
213
+ if (!res.ok) throw new Error(`schedule_callback failed: ${res.status} ${await res.text()}`);
214
+ return await res.json();
215
+ }
216
+
217
+ export {
218
+ submitNotification,
219
+ awaitReply,
220
+ checkReplies,
221
+ setTaskState,
222
+ registerDelivery,
223
+ scheduleCallback
224
+ };