@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 +1 -1
- package/dist/cli.mjs +97 -84
- package/dist/index.d.mts +4486 -2593
- package/dist/index.mjs +2 -3
- package/dist/{server-DIlBV3Wu.mjs → server-B0AHo-sF.mjs} +5 -5
- package/dist/{stdio-entry-BCIfuv69.mjs → stdio-entry-DE2KP-So.mjs} +1 -1
- package/dist/{sync-fzUQKMxy.mjs → sync-DQHqQIgH.mjs} +7968 -4198
- 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 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
|
-
|
|
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,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 ${
|
|
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 ${
|
|
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
|
-
|
|
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
|
-
|
|
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-
|
|
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:
|
|
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 =
|
|
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
|
-
]
|
|
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,
|
|
3638
|
-
"agent-whatsapp",
|
|
3650
|
+
}, WHATSAPP_MCP), flags, [
|
|
3639
3651
|
"mcp",
|
|
3640
3652
|
"prune",
|
|
3641
3653
|
...flags.includePrinted === true ? ["--include-printed"] : []
|
|
3642
|
-
]
|
|
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:
|
|
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
|
-
|
|
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;
|