@agentcomms/whatsapp 0.13.0 → 0.13.1
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 +1 -1
- package/dist/cli.mjs +97 -82
- package/dist/index.d.mts +2893 -1654
- package/dist/index.mjs +2 -3
- package/dist/{server-DIlBV3Wu.mjs → server-DNyMO_pq.mjs} +5 -5
- package/dist/{stdio-entry-BCIfuv69.mjs → stdio-entry-vXXVDpnA.mjs} +1 -1
- package/dist/{sync-fzUQKMxy.mjs → sync-DrWJVoXp.mjs} +2189 -757
- package/package.json +6 -4
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
|
-
|
|
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
|
|
2
|
+
import { $ as paint, A as PATH_OPTIONS, B as colorEnabled, C as checkStorePath, D as CHANNEL_SERVERS, F as approvalsOf, 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 approvalKind, 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, _t as VERSION, a as listChats, at as sendApprovesHint, b as requireAccount, c as Presenter, d as draftMessage, et as pathOverridesFromCliOptions, f as Visibility, ft as windowsSystemProgram, g as requireSupportedNode, h as WhatsAppIndex, ht as CALLER, i as CHAT_KINDS, it as runCommand, k as EXIT_CODES, l as innerText, lt as truncateDisplay, m as WhatsAppContext, mt as writeResult, n as whatsappStatus, nt as renderPrune, o as readChat, ot as serverInstallChange, p as listKey, pt as withFileLock, q as installExitStatus, r as probeStore, rt as resolvePaths, s as searchMessages, st as serverPruneChange, t as syncAccount, tt as renderInstall, ut as updateGateAtTerminal, v as PLATFORM, w as defaultStorePath, x as requireNamedConfig, y as newWhatsAppAccount, z as cliHandoffs } from "./sync-DrWJVoXp.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
|
-
|
|
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 ${
|
|
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,
|
|
3003
|
-
"agent-whatsapp",
|
|
3008
|
+
refuseAnAgent(context, [
|
|
3004
3009
|
"add",
|
|
3005
3010
|
request.name,
|
|
3006
3011
|
...request.source === void 0 ? [] : ["--source", request.source]
|
|
3007
|
-
],
|
|
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 ${
|
|
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:
|
|
3056
|
-
"agent-whatsapp",
|
|
3056
|
+
next: context.handoffs.own([
|
|
3057
3057
|
"sync",
|
|
3058
3058
|
"--account",
|
|
3059
3059
|
request.name
|
|
3060
|
-
]
|
|
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,
|
|
3070
|
-
|
|
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.
|
|
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,
|
|
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
|
-
],
|
|
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:
|
|
3155
|
-
"agent-whatsapp",
|
|
3149
|
+
next: context.handoffs.own([
|
|
3156
3150
|
"sync",
|
|
3157
3151
|
"--account",
|
|
3158
3152
|
name
|
|
3159
|
-
]
|
|
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
|
-
|
|
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 — ${
|
|
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
|
-
]
|
|
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
|
|
3384
|
-
|
|
3385
|
-
|
|
3386
|
-
|
|
3387
|
-
|
|
3388
|
-
|
|
3389
|
-
|
|
3390
|
-
|
|
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 (
|
|
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 =
|
|
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.
|
|
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,13 @@ 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 ${
|
|
3548
|
-
"agent-whatsapp",
|
|
3549
|
-
"approve",
|
|
3550
|
-
approvalId
|
|
3551
|
-
], context.platform))} in their own terminal.`,
|
|
3561
|
+
hint: handoffSentence(context.handoffs.own(["approve", approvalId]), (command) => `Ask the person to run ${command} in their own terminal.`),
|
|
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 ${
|
|
3555
|
-
"agent-whatsapp",
|
|
3556
|
-
"approve",
|
|
3557
|
-
approvalId
|
|
3558
|
-
], context.platform))} directly in a terminal.` });
|
|
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.`) });
|
|
3559
3565
|
const pending = await context.core.approvals.get(approvalId);
|
|
3560
3566
|
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:
|
|
3567
|
+
if (approvalKind(pending) !== "change") throw new CommsError("USAGE", `approval ${approvalId} is for a send, and WhatsApp never sends`, { hint: sendApprovesHint(await context.handoffs.registered(), approvalId) });
|
|
3562
3568
|
const outcome = await approveChangeAtTerminal(context.core, approvalId, env, {
|
|
3563
3569
|
json: options.json,
|
|
3564
3570
|
color: options.color,
|
|
@@ -3566,21 +3572,24 @@ a macOS dialog may be waiting) · 77 permission needed (macOS privacy) · 78 con
|
|
|
3566
3572
|
}, streams);
|
|
3567
3573
|
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
3574
|
}));
|
|
3569
|
-
|
|
3575
|
+
/** A change at this terminal; `rerun` is this command's words after the program, run again with its approval. */
|
|
3576
|
+
const changeAt = (context, change, flags, rerun) => gatedChangeAtTerminal(context.core, change, {
|
|
3570
3577
|
approvalId: flags.approval === void 0 ? void 0 : String(flags.approval),
|
|
3571
3578
|
env,
|
|
3572
3579
|
output: output(),
|
|
3573
|
-
|
|
3574
|
-
approveCommand: "agent-whatsapp approve",
|
|
3580
|
+
rerun,
|
|
3575
3581
|
streams
|
|
3576
3582
|
});
|
|
3577
3583
|
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
3584
|
ran = true;
|
|
3579
3585
|
exitCode = await runCommand(output(), async () => {
|
|
3580
3586
|
requireSupportedNode();
|
|
3581
|
-
await (deps.startMcp ?? (await import("./stdio-entry-
|
|
3587
|
+
await (deps.startMcp ?? (await import("./stdio-entry-vXXVDpnA.mjs")).startWhatsAppStdioServer)({
|
|
3582
3588
|
env,
|
|
3583
3589
|
platform,
|
|
3590
|
+
...deps,
|
|
3591
|
+
core: coreForInvocation(),
|
|
3592
|
+
pathOverrides: coreForInvocation().pathOverrides,
|
|
3584
3593
|
...flags.account ? { account: String(flags.account) } : {}
|
|
3585
3594
|
});
|
|
3586
3595
|
}, streams);
|
|
@@ -3598,13 +3607,19 @@ a macOS dialog may be waiting) · 77 permission needed (macOS privacy) · 78 con
|
|
|
3598
3607
|
"npx",
|
|
3599
3608
|
"local"
|
|
3600
3609
|
])).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:
|
|
3610
|
+
if (!flags.client) throw new CommsError("USAGE", "name the client with --client", { hint: handoffSentence(context.handoffs.own([
|
|
3611
|
+
"mcp",
|
|
3612
|
+
"install",
|
|
3613
|
+
"--client",
|
|
3614
|
+
"claude-code",
|
|
3615
|
+
"--account",
|
|
3616
|
+
"personal/whatsapp"
|
|
3617
|
+
]), (command) => `For example: ${command}.`) });
|
|
3602
3618
|
const pinned = flags.account ?? mcp.opts().account;
|
|
3603
3619
|
const launcher = flags.launcher;
|
|
3604
3620
|
const name = flags.name;
|
|
3605
3621
|
await context.migration();
|
|
3606
|
-
const again =
|
|
3607
|
-
"agent-whatsapp",
|
|
3622
|
+
const again = [
|
|
3608
3623
|
"mcp",
|
|
3609
3624
|
"install",
|
|
3610
3625
|
"--client",
|
|
@@ -3614,7 +3629,7 @@ a macOS dialog may be waiting) · 77 permission needed (macOS privacy) · 78 con
|
|
|
3614
3629
|
...launcher !== void 0 ? ["--launcher", launcher] : [],
|
|
3615
3630
|
...flags.verify === false ? ["--no-verify"] : [],
|
|
3616
3631
|
...flags.force === true ? ["--force"] : []
|
|
3617
|
-
]
|
|
3632
|
+
];
|
|
3618
3633
|
const result = await changeAt(context, serverInstallChange(context.core, env, {
|
|
3619
3634
|
channel: "whatsapp",
|
|
3620
3635
|
client: flags.client,
|
|
@@ -3634,12 +3649,11 @@ a macOS dialog may be waiting) · 77 permission needed (macOS privacy) · 78 con
|
|
|
3634
3649
|
channel: "whatsapp",
|
|
3635
3650
|
dryRun: flags.dryRun === true,
|
|
3636
3651
|
includePrinted: flags.includePrinted === true
|
|
3637
|
-
}, WHATSAPP_MCP), flags,
|
|
3638
|
-
"agent-whatsapp",
|
|
3652
|
+
}, WHATSAPP_MCP), flags, [
|
|
3639
3653
|
"mcp",
|
|
3640
3654
|
"prune",
|
|
3641
3655
|
...flags.includePrinted === true ? ["--include-printed"] : []
|
|
3642
|
-
]
|
|
3656
|
+
]);
|
|
3643
3657
|
writeResult(result, output(), () => renderPrune(result, options.color), streams);
|
|
3644
3658
|
}));
|
|
3645
3659
|
try {
|
|
@@ -3653,13 +3667,14 @@ a macOS dialog may be waiting) · 77 permission needed (macOS privacy) · 78 con
|
|
|
3653
3667
|
].includes(error.code)) return 0;
|
|
3654
3668
|
const message = error.message.replace(/^error: /, "");
|
|
3655
3669
|
return runCommand(output(), async () => {
|
|
3656
|
-
throw new CommsError("USAGE", message, { hint:
|
|
3670
|
+
throw new CommsError("USAGE", message, { hint: handoffSentence(help(), (command) => `Run ${command} to see the commands.`) });
|
|
3657
3671
|
}, streams);
|
|
3658
3672
|
}
|
|
3659
3673
|
throw error;
|
|
3660
3674
|
}
|
|
3661
3675
|
if (!ran) {
|
|
3662
|
-
|
|
3676
|
+
const said = handoffSentence(help(), (command) => `Try ${command}.`);
|
|
3677
|
+
streams.stderr.write(`${paint(output().color, "dim", `Nothing to do. ${said}`)}\n`);
|
|
3663
3678
|
return 64;
|
|
3664
3679
|
}
|
|
3665
3680
|
return exitCode;
|