@agentcomms/whatsapp 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.
package/README.md CHANGED
@@ -68,7 +68,7 @@ agent-whatsapp mcp install --client claude-code --account personal/whatsapp
68
68
  `mcp install` registers the server with your client, pinned to that account, as a change you approve: at a terminal
69
69
  you type `yes` to what it shows; run by an agent it exits `10` with a preview and an approval id, and the same command
70
70
  with `--approval <id>` registers it after your yes. Under the `confirm` change policy you approve with
71
- `agent-whatsapp approve <id>` and a code instead. Restart the client afterwards. From a chat, the core server's
71
+ the approve command the result gives, and a code, instead. Restart the client afterwards. From a chat, the core server's
72
72
  `comms_server_install` with `channel: "whatsapp"` and `account` is the same change.
73
73
 
74
74
  For WhatsApp Business, add its store with
package/dist/cli.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { $ as paint, A as EXIT_CODES, B as colorEnabled, C as checkStorePath, D as CHANNELS, F as approvalsOf, G as gatedChangeAtTerminal, H as commandText, I as approveChangeAtTerminal, J as isCommsError, K as inlineCommand, L as canPrompt, M as absoluteSearchPath, N as agentMarker, O as CHANNEL_SERVERS, P as approvalKind, Q as openCore, R as channelManifest, S as storeIdentity, T as expandSource, U as escapeForDisplay, V as commandPathOf, W as exemptFromUpdateGate, X as nameAvailable, Y as lookupName, Z as nameShapeProblem, _ as chatRefOf, a as listChats, at as shellCommand, b as requireAccount, c as Presenter, ct as updateGateAtTerminal, d as draftMessage, dt as withFileLock, et as renderInstall, f as Visibility, ft as writeResult, g as requireSupportedNode, h as WhatsAppIndex, i as CHAT_KINDS, it as serverPruneChange, k as CommsError, l as innerText, m as WhatsAppContext, n as whatsappStatus, nt as runCommand, o as readChat, p as listKey, pt as VERSION, q as installExitStatus, r as probeStore, rt as serverInstallChange, s as searchMessages, st as truncateDisplay, t as syncAccount, tt as renderPrune, ut as windowsSystemProgram, v as PLATFORM, w as defaultStorePath, x as requireNamedConfig, y as newWhatsAppAccount, z as childEnvironment } from "./sync-fzUQKMxy.mjs";
2
+ import { $ as paint, A as PATH_OPTIONS, B as colorEnabled, C as checkStorePath, D as CHANNEL_SERVERS, F as approveAndWaitSentence, G as handoffSentence, H as escapeForDisplay, I as approveChangeAtTerminal, J as isCommsError, K as handoffText, L as canPrompt, M as absoluteSearchPath, N as agentMarker, O as CommsError, P as approvalsOf, Q as openCore, R as childEnvironment, S as storeIdentity, T as expandSource, U as exemptFromUpdateGate, V as commandPathOf, W as gatedChangeAtTerminal, X as nameAvailable, Y as lookupName, Z as nameShapeProblem, _ as chatRefOf, a as listChats, at as serverInstallChange, b as requireAccount, c as Presenter, ct as truncateDisplay, d as draftMessage, dt as windowsSystemProgram, et as pathOverridesFromCliOptions, f as Visibility, ft as withFileLock, g as requireSupportedNode, gt as VERSION, h as WhatsAppIndex, i as CHAT_KINDS, it as runCommand, k as EXIT_CODES, l as innerText, lt as updateGateAtTerminal, m as WhatsAppContext, mt as CALLER, n as whatsappStatus, nt as renderPrune, o as readChat, ot as serverPruneChange, p as listKey, pt as writeResult, q as installExitStatus, r as probeStore, rt as resolvePaths, s as searchMessages, t as syncAccount, tt as renderInstall, v as PLATFORM, w as defaultStorePath, x as requireNamedConfig, y as newWhatsAppAccount, z as cliHandoffs } from "./sync-DQHqQIgH.mjs";
3
3
  import { rm } from "node:fs/promises";
4
4
  import path, { delimiter, join } from "node:path";
5
5
  import { stripVTControlCharacters } from "node:util";
@@ -2989,22 +2989,27 @@ const WHATSAPP_MCP = {
2989
2989
  * The account itself is core's generic record in `config.json` (`config.ts`). Adding one loosens no setting core's
2990
2990
  * classifier judges — a new account in `read` on a platform whose only mode is `read` — so nothing is approved here:
2991
2991
  * the person typing the command is the one deciding.
2992
+ *
2993
+ * Every command they name — the one refused, the sync to run next, the remove that frees a name — is this
2994
+ * installation's own, located (`context.handoffs`): a bare `agent-whatsapp` is on nobody's PATH by default.
2995
+ */
2996
+ /**
2997
+ * Refuses a command that is the person's to run, when an agent is running it — or when it is not at a terminal — and
2998
+ * hands the person that command: `words`, after the program, located here.
2992
2999
  */
2993
- /** Refuses a command that is the person's to run, when an agent is running it — or when it is not at a terminal. */
2994
- function refuseAnAgent(context, command, what) {
3000
+ function refuseAnAgent(context, words, what) {
2995
3001
  const marker = agentMarker(context.env);
2996
3002
  if (marker !== null || context.surface !== "cli") throw new CommsError("LOOSENING_REFUSED", `only a person ${what}`, {
2997
- hint: `Ask the person to run ${inlineCommand(command)} in their own terminal.`,
3003
+ hint: handoffSentence(context.handoffs.own(words), (command) => `Ask the person to run ${command} in their own terminal.`),
2998
3004
  ...marker === null ? {} : { details: { marker } }
2999
3005
  });
3000
3006
  }
3001
3007
  async function addAccount(context, request) {
3002
- refuseAnAgent(context, shellCommand([
3003
- "agent-whatsapp",
3008
+ refuseAnAgent(context, [
3004
3009
  "add",
3005
3010
  request.name,
3006
3011
  ...request.source === void 0 ? [] : ["--source", request.source]
3007
- ], context.platform), "chooses which WhatsApp store an agent may read");
3012
+ ], "chooses which WhatsApp store an agent may read");
3008
3013
  const problem = nameShapeProblem(request.name, PLATFORM);
3009
3014
  if (problem) throw new CommsError("USAGE", problem);
3010
3015
  const isDefault = request.source === void 0;
@@ -3016,13 +3021,9 @@ async function addAccount(context, request) {
3016
3021
  const identity = storeIdentity(path, isDefault);
3017
3022
  /** The same checks, before the store is opened and again inside the write: anything can happen in between. */
3018
3023
  const refuseTaken = (config) => {
3019
- requireNamedConfig(config);
3024
+ requireNamedConfig(config, context.handoffs);
3020
3025
  const free = nameAvailable(config, "account", request.name, PLATFORM);
3021
- if (!free.ok) throw lookupName(config, "account", request.name)?.platform === "whatsapp" ? new CommsError("USAGE", `"${request.name}" is already added`, { hint: `Remove it first with ${inlineCommand(shellCommand([
3022
- "agent-whatsapp",
3023
- "remove",
3024
- request.name
3025
- ], context.platform))} to point it at another store.` }) : free.error;
3026
+ if (!free.ok) throw lookupName(config, "account", request.name)?.platform === "whatsapp" ? new CommsError("USAGE", `"${request.name}" is already added`, { hint: handoffSentence(context.handoffs.own(["remove", request.name]), (command) => `Remove it first with ${command} to point it at another store.`) }) : free.error;
3026
3027
  for (const [name, other] of Object.entries(config.accounts)) {
3027
3028
  if (other.platform !== "whatsapp") continue;
3028
3029
  if (context.storeOf(other).path === path || other.workspace === identity.workspace) throw new CommsError("USAGE", `that store is already added, as "${name}"`, { hint: "One name per message store." });
@@ -3052,12 +3053,11 @@ async function addAccount(context, request) {
3052
3053
  default: isDefault,
3053
3054
  id: account.workspace
3054
3055
  },
3055
- next: commandText(shellCommand([
3056
- "agent-whatsapp",
3056
+ next: context.handoffs.own([
3057
3057
  "sync",
3058
3058
  "--account",
3059
3059
  request.name
3060
- ], context.platform))
3060
+ ])
3061
3061
  };
3062
3062
  }
3063
3063
  /**
@@ -3066,12 +3066,8 @@ async function addAccount(context, request) {
3066
3066
  */
3067
3067
  const REMOVE_WAIT_MS = 6e4;
3068
3068
  async function removeAccount(context, request) {
3069
- refuseAnAgent(context, shellCommand([
3070
- "agent-whatsapp",
3071
- "remove",
3072
- request.name
3073
- ], context.platform), "removes a WhatsApp account");
3074
- const { account } = requireAccount(await context.config(), request.name, context.platform);
3069
+ refuseAnAgent(context, ["remove", request.name], "removes a WhatsApp account");
3070
+ const { account } = requireAccount(await context.config(), request.name, context.handoffs);
3075
3071
  await withFileLock(context.syncLock(account), async () => {
3076
3072
  await context.core.config.update((config) => {
3077
3073
  if (lookupName(config, "account", request.name)?.id !== account.id) throw new CommsError("TRANSIENT", `"${request.name}" changed while it was being removed`, { hint: "Nothing was removed. Run it again." });
@@ -3115,7 +3111,7 @@ function effectOf(lists) {
3115
3111
  async function named(context, name, account, chatId) {
3116
3112
  let index;
3117
3113
  try {
3118
- index = await WhatsAppIndex.open(context.accountDir(account), name, new Visibility(void 0), context.platform);
3114
+ index = await WhatsAppIndex.open(context.accountDir(account), name, new Visibility(void 0), context.handoffs);
3119
3115
  } catch (error) {
3120
3116
  if (!isCommsError(error) || error.details?.reason !== "NOT_SYNCED") throw error;
3121
3117
  return {
@@ -3136,12 +3132,11 @@ async function named(context, name, account, chatId) {
3136
3132
  }
3137
3133
  /** The one path every list change takes: the person check first, before any input is read. */
3138
3134
  async function change(context, command, request, next) {
3139
- refuseAnAgent(context, shellCommand([
3140
- "agent-whatsapp",
3135
+ refuseAnAgent(context, [
3141
3136
  command,
3142
3137
  ...request.chat === void 0 ? [] : [request.chat],
3143
3138
  ...request.account === void 0 ? [] : ["--account", request.account]
3144
- ], context.platform), "changes which chats an agent may see");
3139
+ ], "changes which chats an agent may see");
3145
3140
  const chatId = request.chat === void 0 ? void 0 : chatRefOf(request.chat, { international: true }).id;
3146
3141
  const { name, account } = await context.account(request.account);
3147
3142
  const { before, after } = await context.lists.update(account.id, (lists) => next(lists, chatId));
@@ -3151,12 +3146,11 @@ async function change(context, command, request, next) {
3151
3146
  deny: [...after.deny],
3152
3147
  changed: JSON.stringify(before) !== JSON.stringify(after),
3153
3148
  effect: effectOf(after),
3154
- next: commandText(shellCommand([
3155
- "agent-whatsapp",
3149
+ next: context.handoffs.own([
3156
3150
  "sync",
3157
3151
  "--account",
3158
3152
  name
3159
- ], context.platform)),
3153
+ ]),
3160
3154
  ...chatId === void 0 ? {} : await named(context, name, account, chatId)
3161
3155
  };
3162
3156
  }
@@ -3238,12 +3232,13 @@ function size(bytes) {
3238
3232
  return `${(bytes / 1024 / 1024).toFixed(1)} MB`;
3239
3233
  }
3240
3234
  function renderAdded(result, color) {
3241
- return [`${paint(color, "green", "Added")} ${result.account} → ${escapeForDisplay(result.store.path)}${result.store.default ? " (WhatsApp for Mac)" : ""}`, `Next: ${result.next}`].join("\n");
3235
+ return [`${paint(color, "green", "Added")} ${result.account} → ${escapeForDisplay(result.store.path)}${result.store.default ? " (WhatsApp for Mac)" : ""}`, `Next: ${handoffText(result.next)}`].join("\n");
3242
3236
  }
3243
3237
  function renderRemoved(result, color) {
3244
3238
  return `${paint(color, "green", "Removed")} ${result.account}, and its local index. WhatsApp's own store was not touched.`;
3245
3239
  }
3246
- function renderStatus(result, color, platform = process.platform) {
3240
+ /** Status for a person; an account not synced yet names the sync, located as `handoffs` find this package's CLI. */
3241
+ function renderStatus(result, color, handoffs) {
3247
3242
  const lines = [];
3248
3243
  if (result.accounts.length === 0) lines.push(`No WhatsApp account yet. ${result.setup ?? ""}`);
3249
3244
  for (const account of result.accounts) {
@@ -3257,12 +3252,11 @@ function renderStatus(result, color, platform = process.platform) {
3257
3252
  lines.push(` index ${account.index.chats} chats, ${account.index.messages} messages, ${account.index.media} with media, ${account.index.statusChats} status-update chat(s) · synced ${account.index.indexedAt}`);
3258
3253
  if (account.index.unattributedStatus > 0) lines.push(` ${unattributed(account.index.unattributedStatus)}`);
3259
3254
  for (const entry of account.index.degraded) lines.push(paint(color, "dim", ` without ${entry.part}: ${entry.costs}`));
3260
- } else lines.push(` index not synced yet — ${commandText(shellCommand([
3261
- "agent-whatsapp",
3255
+ } else lines.push(` index not synced yet — ${handoffText(handoffs.own([
3262
3256
  "sync",
3263
3257
  "--account",
3264
3258
  account.account
3265
- ], platform))}`);
3259
+ ]))}`);
3266
3260
  }
3267
3261
  lines.push("", paint(color, "dim", `Reads: ${result.reads}.`), paint(color, "dim", `Sends: ${result.sends}.`));
3268
3262
  return lines.join("\n");
@@ -3290,7 +3284,7 @@ function renderChatLists(result, color) {
3290
3284
  ...result.warning ? [paint(color, "yellow", result.warning)] : [],
3291
3285
  ` allow ${list(result.allow)}`,
3292
3286
  ` deny ${list(result.deny)}`,
3293
- paint(color, "dim", `Every read and draft applies this now. What is hidden leaves the index, and what the last sync left out comes back, at the next sync: ${result.next}`)
3287
+ paint(color, "dim", `Every read and draft applies this now. What is hidden leaves the index, and what the last sync left out comes back, at the next sync: ${handoffText(result.next)}`)
3294
3288
  ].join("\n");
3295
3289
  }
3296
3290
  function renderChats(result, color) {
@@ -3353,17 +3347,6 @@ function renderDraft(result, color, opened) {
3353
3347
  }
3354
3348
  //#endregion
3355
3349
  //#region src/cli/program.ts
3356
- /**
3357
- * Every channel's approve command that can have prepared a send, from the manifests: the channels whose accounts can
3358
- * be in `send`. WhatsApp's is not among them — it never sends — and a channel added later is, without an edit here.
3359
- */
3360
- function sendApproveCommands() {
3361
- const commands = CHANNELS.flatMap((channel) => {
3362
- const manifest = channelManifest(channel);
3363
- return manifest?.approve && manifest.accounts?.modes.includes("send") ? [`\`${manifest.approve}\``] : [];
3364
- });
3365
- return commands.length <= 1 ? commands.join("") : `${commands.slice(0, -1).join(", ")} or ${commands.at(-1)}`;
3366
- }
3367
3350
  async function run(argv, deps = {}) {
3368
3351
  const streams = deps.streams ?? {
3369
3352
  stdout: process.stdout,
@@ -3380,21 +3363,51 @@ async function run(argv, deps = {}) {
3380
3363
  writeOut: (text) => streams.stdout.write(text),
3381
3364
  writeErr: (text) => streams.stderr.write(text)
3382
3365
  }).addHelpText("after", `
3383
- Getting started (a person, in a terminal):
3384
- agent-whatsapp add personal/whatsapp reads WhatsApp for Mac's store; macOS may ask to allow access
3385
- agent-whatsapp sync --account personal/whatsapp
3386
- agent-whatsapp chats --account personal/whatsapp
3387
- agent-whatsapp search "invoice" --account personal/whatsapp
3388
- agent-whatsapp draft +15555550101 "On my way" a link; you press send in WhatsApp
3389
- agent-whatsapp deny +15555550102 --account personal/whatsapp agents never see that chat
3390
- agent-whatsapp mcp install --client claude-code --account personal/whatsapp
3366
+ Getting started — a person runs these with this CLI, in a terminal:
3367
+ add personal/whatsapp reads WhatsApp for Mac's store; macOS may ask to allow access
3368
+ sync --account personal/whatsapp
3369
+ chats --account personal/whatsapp
3370
+ search "invoice" --account personal/whatsapp
3371
+ draft +15555550101 "On my way" a link; you press send in WhatsApp
3372
+ deny +15555550102 --account personal/whatsapp agents never see that chat
3373
+ mcp install --client claude-code --account personal/whatsapp
3391
3374
 
3392
3375
  Nothing here connects to WhatsApp or any other server, and nothing here can send. Needs Node 22.16 or newer.
3393
3376
 
3394
3377
  Exit codes: 0 ok · 1 unexpected · 10 only a person may do that, or a change needs approval · 11 an update
3395
- is out: update first, or put it off (agentcomms update, agentcomms update --later) · 64 usage
3378
+ is out: update first, or put it off (the stop names both commands) · 64 usage
3396
3379
  · 65 bad data (a store whose layout changed) · 66 not found · 69 unavailable · 75 temporary (retry;
3397
3380
  a macOS dialog may be waiting) · 77 permission needed (macOS privacy) · 78 configuration problem.`).exitOverride();
3381
+ for (const { flag } of PATH_OPTIONS) program.addOption(new Option(`${flag} <dir>`, "pin this suite directory for this run").argParser((value) => {
3382
+ if (value.length === 0) throw new InvalidArgumentError(`${flag} needs a non-empty directory`);
3383
+ return value;
3384
+ }));
3385
+ let invocationCore;
3386
+ const coreForInvocation = () => {
3387
+ if (invocationCore) return invocationCore;
3388
+ const options = program.opts();
3389
+ const pathOverrides = pathOverridesFromCliOptions(Object.fromEntries(PATH_OPTIONS.map(({ key, option }) => [option, options[key]])));
3390
+ invocationCore = Object.keys(pathOverrides).length === 0 && deps.core ? deps.core : openCore({
3391
+ env,
3392
+ platform,
3393
+ pathOverrides,
3394
+ caller: CALLER
3395
+ });
3396
+ return invocationCore;
3397
+ };
3398
+ /**
3399
+ * This installation's own `--help`, for a command line that named nothing it can run: located from this package, and
3400
+ * pinned to no folder, since help opens none — so it is there even before the folders are known.
3401
+ */
3402
+ const help = () => cliHandoffs({
3403
+ caller: CALLER,
3404
+ paths: resolvePaths({
3405
+ env,
3406
+ platform
3407
+ }),
3408
+ platform,
3409
+ env
3410
+ }).own(["--help"], { uses: [] });
3398
3411
  const output = () => {
3399
3412
  const options = program.opts();
3400
3413
  return {
@@ -3412,7 +3425,7 @@ a macOS dialog may be waiting) · 77 permission needed (macOS privacy) · 78 con
3412
3425
  program.hook("preAction", async (_program, command) => {
3413
3426
  const path = commandPathOf(command);
3414
3427
  if (exemptFromUpdateGate(path, ["status"])) return;
3415
- const core = openCore({ env });
3428
+ const core = coreForInvocation();
3416
3429
  let ended = null;
3417
3430
  const code = await runCommand(output(), async () => {
3418
3431
  ended = await updateGateAtTerminal({
@@ -3424,7 +3437,6 @@ a macOS dialog may be waiting) · 77 permission needed (macOS privacy) · 78 con
3424
3437
  output: output(),
3425
3438
  noInput: false,
3426
3439
  streams,
3427
- approveCommand: "agent-whatsapp approve",
3428
3440
  approvals: approvalsOf(command)
3429
3441
  });
3430
3442
  }, streams);
@@ -3441,7 +3453,9 @@ a macOS dialog may be waiting) · 77 permission needed (macOS privacy) · 78 con
3441
3453
  requireSupportedNode();
3442
3454
  await body(new WhatsAppContext({
3443
3455
  ...deps,
3456
+ core: coreForInvocation(),
3444
3457
  env,
3458
+ pathOverrides: coreForInvocation().pathOverrides,
3445
3459
  surface: "cli",
3446
3460
  log: deps.log ?? ((line) => streams.stderr.write(`${line}\n`))
3447
3461
  }), output(), ...args);
@@ -3486,7 +3500,7 @@ a macOS dialog may be waiting) · 77 permission needed (macOS privacy) · 78 con
3486
3500
  account: flags.account === void 0 ? void 0 : String(flags.account),
3487
3501
  check: flags.check !== false
3488
3502
  });
3489
- writeResult(result, options, () => renderStatus(result, options.color, context.platform), streams);
3503
+ writeResult(result, options, () => renderStatus(result, options.color, context.handoffs), streams);
3490
3504
  }));
3491
3505
  accountOption(program.command("sync")).description("copy WhatsApp's store privately, check it, and rebuild the local index from the copy").action(act(async (context, options, flags) => {
3492
3506
  const result = await syncAccount(context, { account: String(flags.account) });
@@ -3544,21 +3558,10 @@ a macOS dialog may be waiting) · 77 permission needed (macOS privacy) · 78 con
3544
3558
  program.command("approve <approvalId>").description("approve a change at this terminal — registering or pruning this server — by reading it and typing the code back; WhatsApp never sends, so there is no message to approve").action(act(async (context, options, approvalId) => {
3545
3559
  const marker = agentMarker(env);
3546
3560
  if (marker) throw new CommsError("APPROVAL_REQUIRED", "only a person can approve a change, not an agent", {
3547
- hint: `Ask the person to run ${inlineCommand(shellCommand([
3548
- "agent-whatsapp",
3549
- "approve",
3550
- approvalId
3551
- ], context.platform))} in their own terminal.`,
3561
+ hint: approveAndWaitSentence(context.handoffs, "cli", approvalId, (command, wait) => wait === void 0 ? `Ask the person to run ${command} in their own terminal.` : `Ask the person to run ${command} in their own terminal; learn when they have with ${wait}.`),
3552
3562
  details: { marker }
3553
3563
  });
3554
- if (!canPrompt(env, streams, { json: options.json })) throw new CommsError("APPROVAL_REQUIRED", "approving a change needs an interactive terminal", { hint: `Run ${inlineCommand(shellCommand([
3555
- "agent-whatsapp",
3556
- "approve",
3557
- approvalId
3558
- ], context.platform))} directly in a terminal.` });
3559
- const pending = await context.core.approvals.get(approvalId);
3560
- if (!pending) throw new CommsError("NOT_FOUND", `no approval ${approvalId}`, { hint: "It may have expired, been used, or been cancelled. Prepare the change again." });
3561
- if (approvalKind(pending) !== "change") throw new CommsError("USAGE", `approval ${approvalId} is for a send, and WhatsApp never sends`, { hint: `Approve it with the command that prepared it — ${sendApproveCommands()} — not this one.` });
3564
+ if (!canPrompt(env, streams, { json: options.json })) throw new CommsError("APPROVAL_REQUIRED", "approving a change needs an interactive terminal", { hint: handoffSentence(context.handoffs.own(["approve", approvalId]), (command) => `Run ${command} directly in a terminal.`) });
3562
3565
  const outcome = await approveChangeAtTerminal(context.core, approvalId, env, {
3563
3566
  json: options.json,
3564
3567
  color: options.color,
@@ -3566,21 +3569,25 @@ a macOS dialog may be waiting) · 77 permission needed (macOS privacy) · 78 con
3566
3569
  }, streams);
3567
3570
  streams.stdout.write(outcome.state === "approved" ? "Approved. This command approves; the change is applied by the command that prepared it.\n" : "Cancelled. Nothing was changed.\n");
3568
3571
  }));
3569
- const changeAt = (context, change, flags, command) => gatedChangeAtTerminal(context.core, change, {
3572
+ /** A change at this terminal; `rerun` is this command's words after the program, run again with its approval. */
3573
+ const changeAt = (context, change, flags, rerun) => gatedChangeAtTerminal(context.core, change, {
3574
+ channel: "whatsapp",
3570
3575
  approvalId: flags.approval === void 0 ? void 0 : String(flags.approval),
3571
3576
  env,
3572
3577
  output: output(),
3573
- command,
3574
- approveCommand: "agent-whatsapp approve",
3578
+ rerun,
3575
3579
  streams
3576
3580
  });
3577
3581
  const mcp = program.command("mcp").description("run the MCP server on stdio, for a coding agent to connect to").option("--account <name>", "pin the server to one account; every tool then acts on it and no other").action(async (flags) => {
3578
3582
  ran = true;
3579
3583
  exitCode = await runCommand(output(), async () => {
3580
3584
  requireSupportedNode();
3581
- await (deps.startMcp ?? (await import("./stdio-entry-BCIfuv69.mjs")).startWhatsAppStdioServer)({
3585
+ await (deps.startMcp ?? (await import("./stdio-entry-DE2KP-So.mjs")).startWhatsAppStdioServer)({
3582
3586
  env,
3583
3587
  platform,
3588
+ ...deps,
3589
+ core: coreForInvocation(),
3590
+ pathOverrides: coreForInvocation().pathOverrides,
3584
3591
  ...flags.account ? { account: String(flags.account) } : {}
3585
3592
  });
3586
3593
  }, streams);
@@ -3598,13 +3605,19 @@ a macOS dialog may be waiting) · 77 permission needed (macOS privacy) · 78 con
3598
3605
  "npx",
3599
3606
  "local"
3600
3607
  ])).option("--no-verify", "do not start the server to check the entry works").option("--force", "replace this server's own earlier entry — this is how you upgrade", false).option("--print", "only print what would be written", false).option("--approval <approvalId>", "register the server this approval was given for").action(act(async (context, options, flags) => {
3601
- if (!flags.client) throw new CommsError("USAGE", "name the client with --client", { hint: "For example: `agent-whatsapp mcp install --client claude-code --account personal/whatsapp`." });
3608
+ if (!flags.client) throw new CommsError("USAGE", "name the client with --client", { hint: handoffSentence(context.handoffs.own([
3609
+ "mcp",
3610
+ "install",
3611
+ "--client",
3612
+ "claude-code",
3613
+ "--account",
3614
+ "personal/whatsapp"
3615
+ ]), (command) => `For example: ${command}.`) });
3602
3616
  const pinned = flags.account ?? mcp.opts().account;
3603
3617
  const launcher = flags.launcher;
3604
3618
  const name = flags.name;
3605
3619
  await context.migration();
3606
- const again = shellCommand([
3607
- "agent-whatsapp",
3620
+ const again = [
3608
3621
  "mcp",
3609
3622
  "install",
3610
3623
  "--client",
@@ -3614,7 +3627,7 @@ a macOS dialog may be waiting) · 77 permission needed (macOS privacy) · 78 con
3614
3627
  ...launcher !== void 0 ? ["--launcher", launcher] : [],
3615
3628
  ...flags.verify === false ? ["--no-verify"] : [],
3616
3629
  ...flags.force === true ? ["--force"] : []
3617
- ], context.platform);
3630
+ ];
3618
3631
  const result = await changeAt(context, serverInstallChange(context.core, env, {
3619
3632
  channel: "whatsapp",
3620
3633
  client: flags.client,
@@ -3634,12 +3647,11 @@ a macOS dialog may be waiting) · 77 permission needed (macOS privacy) · 78 con
3634
3647
  channel: "whatsapp",
3635
3648
  dryRun: flags.dryRun === true,
3636
3649
  includePrinted: flags.includePrinted === true
3637
- }, WHATSAPP_MCP), flags, shellCommand([
3638
- "agent-whatsapp",
3650
+ }, WHATSAPP_MCP), flags, [
3639
3651
  "mcp",
3640
3652
  "prune",
3641
3653
  ...flags.includePrinted === true ? ["--include-printed"] : []
3642
- ], context.platform));
3654
+ ]);
3643
3655
  writeResult(result, output(), () => renderPrune(result, options.color), streams);
3644
3656
  }));
3645
3657
  try {
@@ -3653,13 +3665,14 @@ a macOS dialog may be waiting) · 77 permission needed (macOS privacy) · 78 con
3653
3665
  ].includes(error.code)) return 0;
3654
3666
  const message = error.message.replace(/^error: /, "");
3655
3667
  return runCommand(output(), async () => {
3656
- throw new CommsError("USAGE", message, { hint: "Run `agent-whatsapp --help` to see the commands." });
3668
+ throw new CommsError("USAGE", message, { hint: handoffSentence(help(), (command) => `Run ${command} to see the commands.`) });
3657
3669
  }, streams);
3658
3670
  }
3659
3671
  throw error;
3660
3672
  }
3661
3673
  if (!ran) {
3662
- streams.stderr.write(`${paint(output().color, "dim", "Nothing to do. Try `agent-whatsapp --help`.")}\n`);
3674
+ const said = handoffSentence(help(), (command) => `Try ${command}.`);
3675
+ streams.stderr.write(`${paint(output().color, "dim", `Nothing to do. ${said}`)}\n`);
3663
3676
  return 64;
3664
3677
  }
3665
3678
  return exitCode;