@paigy/mcp 0.13.0 → 0.14.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.
@@ -8,7 +8,7 @@ import {
8
8
  readKeyFile,
9
9
  sealFields,
10
10
  verifiedRecipients
11
- } from "./chunk-O6W2T6D2.js";
11
+ } from "./chunk-WGOGS2JC.js";
12
12
 
13
13
  // src/client.ts
14
14
  import { existsSync, readFileSync } from "fs";
@@ -2597,6 +2597,15 @@ var MissedCallSchema = z.enum([
2597
2597
  "inbox",
2598
2598
  "dismiss"
2599
2599
  ]);
2600
+ var BrokerTuningSchema = z.object({
2601
+ /** 'none' = skip the spoken ack after a mapped answer (power users find it slow). */
2602
+ ackVerbosity: z.enum(["normal", "none"]).optional(),
2603
+ /** How readily the mapper asks its one clarification: 'low' = only when truly
2604
+ * uninterpretable, 'high' = whenever not fully certain. */
2605
+ clarifyEagerness: z.enum(["low", "normal", "high"]).optional(),
2606
+ /** The user's own shorthand: when they say `say`, they mean `mean`. */
2607
+ phrasebook: z.array(z.object({ say: z.string().min(1).max(60), mean: z.string().min(1).max(120) })).max(24).optional()
2608
+ });
2600
2609
  var UserSettingsSchema = z.object({
2601
2610
  permissions: z.object({
2602
2611
  call: z.boolean(),
@@ -2622,7 +2631,10 @@ var UserSettingsSchema = z.object({
2622
2631
  * settings object without it can't silently flip the account's E2EE state.
2623
2632
  * Absent = leave unchanged on write, 'off' on read (see store.ts). The demo
2624
2633
  * account is plaintext by construction and refuses any non-'off' value. */
2625
- e2eeMode: z.enum(["off", "on"]).optional()
2634
+ e2eeMode: z.enum(["off", "on"]).optional(),
2635
+ /** Rung-2 broker tuning (#381). Optional and NOT defaulted, same stale-client
2636
+ * clobber guard as voiceMode: absent = leave unchanged on write. */
2637
+ broker: BrokerTuningSchema.optional()
2626
2638
  });
2627
2639
  var HistoryItemSchema = z.object({
2628
2640
  id: z.string(),
@@ -3091,6 +3103,32 @@ var PROTO = "paigy-pair-v2|sas=24";
3091
3103
  var SAS_BITS = 24;
3092
3104
  var TOKEN_PATH = join(homedir(), ".paigy", "token.json");
3093
3105
  var KEY_PATH = join(homedir(), ".paigy", "key.json");
3106
+ var SURFACE_PATH = join(homedir(), ".paigy", "surface.json");
3107
+ function writeSurface(label, code, ttlSeconds) {
3108
+ try {
3109
+ mkdirSync(join(homedir(), ".paigy"), { recursive: true });
3110
+ writeFileSync(
3111
+ SURFACE_PATH,
3112
+ JSON.stringify({ label, code, expiresAt: Date.now() + ttlSeconds * 1e3 }),
3113
+ { mode: 384 }
3114
+ );
3115
+ } catch {
3116
+ }
3117
+ }
3118
+ function clearSurface() {
3119
+ try {
3120
+ rmSync(SURFACE_PATH, { force: true });
3121
+ } catch {
3122
+ }
3123
+ }
3124
+ function readSurface(now = Date.now) {
3125
+ try {
3126
+ const { label, code, expiresAt } = JSON.parse(readFileSync(SURFACE_PATH, "utf8"));
3127
+ return label && code && typeof expiresAt === "number" && expiresAt > now() ? { label, code } : null;
3128
+ } catch {
3129
+ return null;
3130
+ }
3131
+ }
3094
3132
  function openBrowser(url) {
3095
3133
  const cmd = platform() === "darwin" ? "open" : platform() === "win32" ? "cmd" : "xdg-open";
3096
3134
  const args = platform() === "win32" ? ["/c", "start", url] : [url];
@@ -3290,6 +3328,9 @@ export {
3290
3328
  reach,
3291
3329
  AGENT_NAME,
3292
3330
  TOKEN_PATH,
3331
+ writeSurface,
3332
+ clearSurface,
3333
+ readSurface,
3293
3334
  openBrowser,
3294
3335
  saveToken,
3295
3336
  sleep,
package/dist/index.js CHANGED
@@ -6,16 +6,16 @@ import {
6
6
  scheduleCallback,
7
7
  setTaskState,
8
8
  submitNotification
9
- } from "./chunk-KNCXKXUG.js";
9
+ } from "./chunk-OSQ3UXY6.js";
10
10
  import {
11
11
  NotifyRequestSchema,
12
12
  ScheduleCallbackSchema,
13
13
  SetTaskStateSchema,
14
+ clearSurface,
14
15
  deleteKeyFile,
15
16
  deleteToken,
16
17
  fetchCredential,
17
18
  finalizeE2ee,
18
- openBrowser,
19
19
  pairStep,
20
20
  readKeyFile,
21
21
  readToken,
@@ -24,8 +24,9 @@ import {
24
24
  saveKeyFile,
25
25
  saveToken,
26
26
  sleep,
27
- startE2ee
28
- } from "./chunk-O6W2T6D2.js";
27
+ startE2ee,
28
+ writeSurface
29
+ } from "./chunk-WGOGS2JC.js";
29
30
 
30
31
  // src/index.ts
31
32
  import { Server } from "@modelcontextprotocol/sdk/server/index.js";
@@ -137,6 +138,7 @@ function detectGit() {
137
138
  };
138
139
  }
139
140
  function pairedResult(token, sas, note, e2ee) {
141
+ clearSurface();
140
142
  const base = {
141
143
  status: "paired",
142
144
  nickname: token.nickname,
@@ -152,6 +154,7 @@ function pairedResult(token, sas, note, e2ee) {
152
154
  return { content: [{ type: "text", text: JSON.stringify(base) }] };
153
155
  }
154
156
  function awaitingConfirmResult(sas, device_code) {
157
+ writeSurface("verify code", sas, 600);
155
158
  return {
156
159
  content: [{
157
160
  type: "text",
@@ -160,8 +163,8 @@ function awaitingConfirmResult(sas, device_code) {
160
163
  e2ee: true,
161
164
  sas,
162
165
  device_code,
163
- verify_message: `This account requires end-to-end encryption, so no access token is issued until you confirm the pairing. Ask the user to check that this code matches the one on their phone and CONFIRM it there: ${sas}. Pairing is NOT complete yet.`,
164
- message: "The user must compare the SAS above and confirm on their phone. Once they do, call pair again with this device_code to finish \u2014 the token is granted only after they confirm."
166
+ verify_message: `YOUR NEXT MESSAGE TO THE USER MUST BE EXACTLY THIS CODE AND NOTHING ELSE, displayed large (a markdown heading): ${sas} \u2014 it is the verification code they must compare and type on their phone. Do not wrap it in explanation; every extra sentence buries it. This code REPLACES any earlier one (older pairing attempts are now stale).`,
167
+ message: "No token is issued until the user confirms the code match on their phone (E2EE account). After sending the bare code, call pair again with this device_code to keep waiting \u2014 the token arrives only after they confirm."
165
168
  })
166
169
  }]
167
170
  };
@@ -220,7 +223,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
220
223
  const { keyFile, offer } = startE2ee();
221
224
  const code = await requestCode(void 0, offer);
222
225
  saveKeyFile({ ...keyFile, userCode: code.user_code });
223
- openBrowser(code.verification_uri_complete);
226
+ writeSurface("pairing code", code.user_code, code.expires_in);
224
227
  const qr = qrcode(0, "M");
225
228
  qr.addData(code.verification_uri_complete);
226
229
  qr.make();
@@ -234,7 +237,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
234
237
  device_code: code.device_code,
235
238
  expires_in: code.expires_in,
236
239
  qr: qr.createASCII(1, 2),
237
- message: "Show the user verification_uri_complete and user_code (their browser should have opened). If they'd rather scan than switch to a browser themselves, print `qr` verbatim in a fenced code block (monospace, no extra indentation) \u2014 it's a scannable QR code for the same link. After they approve, call pair again with this device_code to finish."
240
+ message: `YOUR NEXT MESSAGE TO THE USER MUST BE EXACTLY THIS CODE displayed large (a markdown heading): ${code.user_code} \u2014 plus ONE short line: enter it in the Paigy app (Inbox \u2192 Add a new agent). No other prose. Do NOT open a browser for the user. If their phone is handy they may prefer scanning \`qr\` (print it verbatim in a fenced code block on request). Then immediately call pair again with this device_code \u2014 it polls for the approval.`
238
241
  })
239
242
  }]
240
243
  };
@@ -243,7 +246,13 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
243
246
  const start = Date.now();
244
247
  const capMs = 9e4;
245
248
  while (Date.now() - start < capMs) {
246
- const step = await pairStep(device_code, kf);
249
+ let step;
250
+ try {
251
+ step = await pairStep(device_code, kf);
252
+ } catch (e) {
253
+ clearSurface();
254
+ throw e;
255
+ }
247
256
  if (step.kind === "e2ee_aborted") {
248
257
  deleteKeyFile();
249
258
  kf = null;
package/dist/listen.js CHANGED
@@ -3,11 +3,11 @@ import { createRequire as __createRequire } from 'node:module'; const require =
3
3
  import {
4
4
  checkReplies,
5
5
  registerDelivery
6
- } from "./chunk-KNCXKXUG.js";
6
+ } from "./chunk-OSQ3UXY6.js";
7
7
  import {
8
8
  WAKE_EVENT,
9
9
  wakeChannel
10
- } from "./chunk-O6W2T6D2.js";
10
+ } from "./chunk-WGOGS2JC.js";
11
11
 
12
12
  // src/listen.ts
13
13
  import { createClient } from "@supabase/supabase-js";
package/dist/onboard.js CHANGED
@@ -8,7 +8,7 @@ import {
8
8
  requestCode,
9
9
  saveToken,
10
10
  sleep
11
- } from "./chunk-O6W2T6D2.js";
11
+ } from "./chunk-WGOGS2JC.js";
12
12
 
13
13
  // src/onboard.ts
14
14
  async function main() {
@@ -2,8 +2,9 @@
2
2
  import { createRequire as __createRequire } from 'node:module'; const require = __createRequire(import.meta.url);
3
3
  import {
4
4
  BACKEND_URL,
5
+ readSurface,
5
6
  readToken
6
- } from "./chunk-O6W2T6D2.js";
7
+ } from "./chunk-WGOGS2JC.js";
7
8
 
8
9
  // src/statusline.ts
9
10
  import { mkdirSync, readFileSync, realpathSync, writeFileSync } from "fs";
@@ -18,7 +19,8 @@ var MODE_LABEL = {
18
19
  all_calls: "all calls",
19
20
  silent: "silent"
20
21
  };
21
- function render(status) {
22
+ function render(status, surface = null) {
23
+ if (surface) return `paigy \u26A0 ${surface.label}: ${surface.code}`;
22
24
  if (!status) return "paigy: unpaired";
23
25
  return `paigy: ${MODE_LABEL[status.sessionMode]} \xB7 ${status.phone ? "phone \u2713" : "no phone"}`;
24
26
  }
@@ -44,6 +46,8 @@ async function fetchStatus(token) {
44
46
  return await res.json();
45
47
  }
46
48
  async function main() {
49
+ const surface = readSurface();
50
+ if (surface) return render(null, surface);
47
51
  const token = readToken();
48
52
  if (!token) return render(null);
49
53
  const cached = readCache();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@paigy/mcp",
3
- "version": "0.13.0",
3
+ "version": "0.14.0",
4
4
  "description": "Paigy MCP server — a voice inbox for your AI agents. Lets an agent notify a user and await their reply.",
5
5
  "license": "MIT",
6
6
  "type": "module",