clawdbot 2026.1.4-1 → 2026.1.5
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/CHANGELOG.md +26 -6
- package/README.md +26 -1
- package/dist/agents/pi-embedded-runner.js +2 -0
- package/dist/agents/pi-embedded-subscribe.js +18 -3
- package/dist/agents/pi-tools.js +45 -6
- package/dist/agents/tools/browser-tool.js +38 -89
- package/dist/agents/tools/cron-tool.js +8 -8
- package/dist/agents/workspace.js +8 -1
- package/dist/auto-reply/command-detection.js +26 -0
- package/dist/auto-reply/reply/agent-runner.js +15 -8
- package/dist/auto-reply/reply/commands.js +36 -25
- package/dist/auto-reply/reply/directive-handling.js +4 -2
- package/dist/auto-reply/reply/directives.js +12 -0
- package/dist/auto-reply/reply/session-updates.js +2 -4
- package/dist/auto-reply/reply.js +26 -4
- package/dist/browser/config.js +22 -4
- package/dist/browser/profiles-service.js +3 -1
- package/dist/browser/profiles.js +14 -3
- package/dist/canvas-host/a2ui/.bundle.hash +2 -0
- package/dist/cli/gateway-cli.js +2 -2
- package/dist/cli/profile.js +81 -0
- package/dist/cli/program.js +10 -1
- package/dist/cli/run-main.js +33 -0
- package/dist/commands/configure.js +5 -0
- package/dist/commands/onboard-providers.js +1 -1
- package/dist/commands/setup.js +4 -1
- package/dist/config/defaults.js +56 -0
- package/dist/config/io.js +47 -6
- package/dist/config/paths.js +2 -2
- package/dist/config/port-defaults.js +32 -0
- package/dist/config/sessions.js +3 -2
- package/dist/config/validation.js +2 -2
- package/dist/config/zod-schema.js +16 -0
- package/dist/discord/monitor.js +75 -266
- package/dist/entry.js +16 -0
- package/dist/gateway/call.js +8 -1
- package/dist/gateway/server-methods/chat.js +1 -1
- package/dist/gateway/server.js +14 -3
- package/dist/index.js +2 -2
- package/dist/infra/control-ui-assets.js +118 -0
- package/dist/infra/dotenv.js +15 -0
- package/dist/infra/shell-env.js +79 -0
- package/dist/infra/system-events.js +50 -23
- package/dist/macos/relay.js +8 -2
- package/dist/telegram/bot.js +24 -1
- package/dist/utils.js +8 -2
- package/dist/web/auto-reply.js +18 -21
- package/dist/web/inbound.js +5 -1
- package/dist/web/qr-image.js +4 -4
- package/dist/web/session.js +2 -3
- package/docs/agent.md +0 -2
- package/docs/assets/markdown.css +4 -1
- package/docs/audio.md +0 -2
- package/docs/clawd.md +0 -2
- package/docs/configuration.md +62 -3
- package/docs/docs.json +9 -1
- package/docs/faq.md +32 -7
- package/docs/gateway.md +28 -0
- package/docs/images.md +0 -2
- package/docs/index.md +2 -4
- package/docs/mac/icon.md +1 -1
- package/docs/nix.md +57 -11
- package/docs/onboarding.md +0 -2
- package/docs/refactor/webagent-session.md +0 -2
- package/docs/research/memory.md +1 -1
- package/docs/skills.md +0 -2
- package/docs/templates/AGENTS.md +2 -2
- package/docs/tools.md +15 -0
- package/docs/whatsapp.md +2 -0
- package/package.json +8 -16
- package/dist/control-ui/assets/index-BFID3yAA.css +0 -1
- package/dist/control-ui/assets/index-CE_axlTS.js +0 -2235
- package/dist/control-ui/assets/index-CE_axlTS.js.map +0 -1
- package/dist/control-ui/index.html +0 -15
- package/dist/daemon/constants.js +0 -10
- package/dist/daemon/launchd.js +0 -276
- package/dist/daemon/legacy.js +0 -63
- package/dist/daemon/program-args.js +0 -76
- package/dist/daemon/schtasks.js +0 -257
- package/dist/daemon/service.js +0 -60
- package/dist/daemon/systemd.js +0 -266
- package/dist/imessage/client.js +0 -165
- package/dist/imessage/index.js +0 -3
- package/dist/imessage/monitor.js +0 -272
- package/dist/imessage/probe.js +0 -26
- package/dist/imessage/send.js +0 -83
- package/dist/imessage/targets.js +0 -176
- package/dist/sessions/send-policy.js +0 -68
- package/dist/signal/client.js +0 -134
- package/dist/signal/daemon.js +0 -69
- package/dist/signal/index.js +0 -3
- package/dist/signal/monitor.js +0 -336
- package/dist/signal/probe.js +0 -46
- package/dist/signal/send.js +0 -91
- package/dist/slack/actions.js +0 -97
- package/dist/slack/index.js +0 -5
- package/dist/slack/monitor.js +0 -1029
- package/dist/slack/probe.js +0 -47
- package/dist/slack/send.js +0 -131
- package/dist/slack/token.js +0 -10
- package/dist/tui/commands.js +0 -74
- package/dist/tui/components/assistant-message.js +0 -16
- package/dist/tui/components/chat-log.js +0 -92
- package/dist/tui/components/custom-editor.js +0 -53
- package/dist/tui/components/selectors.js +0 -8
- package/dist/tui/components/tool-execution.js +0 -111
- package/dist/tui/components/user-message.js +0 -17
- package/dist/tui/gateway-chat.js +0 -140
- package/dist/tui/layout.js +0 -41
- package/dist/tui/message-list.js +0 -57
- package/dist/tui/theme/theme.js +0 -80
- package/dist/tui/theme.js +0 -25
- package/dist/tui/tui.js +0 -708
- package/dist/wizard/clack-prompter.js +0 -56
- package/dist/wizard/onboarding.js +0 -452
- package/dist/wizard/prompts.js +0 -6
- package/dist/wizard/session.js +0 -203
package/dist/imessage/monitor.js
DELETED
|
@@ -1,272 +0,0 @@
|
|
|
1
|
-
import { chunkText, resolveTextChunkLimit } from "../auto-reply/chunk.js";
|
|
2
|
-
import { formatAgentEnvelope } from "../auto-reply/envelope.js";
|
|
3
|
-
import { getReplyFromConfig } from "../auto-reply/reply.js";
|
|
4
|
-
import { loadConfig } from "../config/config.js";
|
|
5
|
-
import { resolveStorePath, updateLastRoute } from "../config/sessions.js";
|
|
6
|
-
import { danger, logVerbose, shouldLogVerbose } from "../globals.js";
|
|
7
|
-
import { mediaKindFromMime } from "../media/constants.js";
|
|
8
|
-
import { createIMessageRpcClient } from "./client.js";
|
|
9
|
-
import { sendMessageIMessage } from "./send.js";
|
|
10
|
-
import { formatIMessageChatTarget, isAllowedIMessageSender, normalizeIMessageHandle, } from "./targets.js";
|
|
11
|
-
function resolveRuntime(opts) {
|
|
12
|
-
return (opts.runtime ?? {
|
|
13
|
-
log: console.log,
|
|
14
|
-
error: console.error,
|
|
15
|
-
exit: (code) => {
|
|
16
|
-
throw new Error(`exit ${code}`);
|
|
17
|
-
},
|
|
18
|
-
});
|
|
19
|
-
}
|
|
20
|
-
function resolveAllowFrom(opts) {
|
|
21
|
-
const cfg = loadConfig();
|
|
22
|
-
const raw = opts.allowFrom ?? cfg.imessage?.allowFrom ?? [];
|
|
23
|
-
return raw.map((entry) => String(entry).trim()).filter(Boolean);
|
|
24
|
-
}
|
|
25
|
-
function resolveMentionRegexes(cfg) {
|
|
26
|
-
return (cfg.routing?.groupChat?.mentionPatterns
|
|
27
|
-
?.map((pattern) => {
|
|
28
|
-
try {
|
|
29
|
-
return new RegExp(pattern, "i");
|
|
30
|
-
}
|
|
31
|
-
catch {
|
|
32
|
-
return null;
|
|
33
|
-
}
|
|
34
|
-
})
|
|
35
|
-
.filter((val) => Boolean(val)) ?? []);
|
|
36
|
-
}
|
|
37
|
-
function resolveGroupRequireMention(cfg, opts, chatId) {
|
|
38
|
-
if (typeof opts.requireMention === "boolean")
|
|
39
|
-
return opts.requireMention;
|
|
40
|
-
const groupId = chatId != null ? String(chatId) : undefined;
|
|
41
|
-
if (groupId) {
|
|
42
|
-
const groupConfig = cfg.imessage?.groups?.[groupId];
|
|
43
|
-
if (typeof groupConfig?.requireMention === "boolean") {
|
|
44
|
-
return groupConfig.requireMention;
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
const groupDefault = cfg.imessage?.groups?.["*"]?.requireMention;
|
|
48
|
-
if (typeof groupDefault === "boolean")
|
|
49
|
-
return groupDefault;
|
|
50
|
-
return true;
|
|
51
|
-
}
|
|
52
|
-
function isMentioned(text, regexes) {
|
|
53
|
-
if (!text)
|
|
54
|
-
return false;
|
|
55
|
-
const cleaned = text
|
|
56
|
-
.replace(/[\u200b-\u200f\u202a-\u202e\u2060-\u206f]/g, "")
|
|
57
|
-
.toLowerCase();
|
|
58
|
-
return regexes.some((re) => re.test(cleaned));
|
|
59
|
-
}
|
|
60
|
-
async function deliverReplies(params) {
|
|
61
|
-
const { replies, target, client, runtime, maxBytes, textLimit } = params;
|
|
62
|
-
for (const payload of replies) {
|
|
63
|
-
const mediaList = payload.mediaUrls ?? (payload.mediaUrl ? [payload.mediaUrl] : []);
|
|
64
|
-
const text = payload.text ?? "";
|
|
65
|
-
if (!text && mediaList.length === 0)
|
|
66
|
-
continue;
|
|
67
|
-
if (mediaList.length === 0) {
|
|
68
|
-
for (const chunk of chunkText(text, textLimit)) {
|
|
69
|
-
await sendMessageIMessage(target, chunk, { maxBytes, client });
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
else {
|
|
73
|
-
let first = true;
|
|
74
|
-
for (const url of mediaList) {
|
|
75
|
-
const caption = first ? text : "";
|
|
76
|
-
first = false;
|
|
77
|
-
await sendMessageIMessage(target, caption, {
|
|
78
|
-
mediaUrl: url,
|
|
79
|
-
maxBytes,
|
|
80
|
-
client,
|
|
81
|
-
});
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
runtime.log?.(`imessage: delivered reply to ${target}`);
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
export async function monitorIMessageProvider(opts = {}) {
|
|
88
|
-
const runtime = resolveRuntime(opts);
|
|
89
|
-
const cfg = loadConfig();
|
|
90
|
-
const textLimit = resolveTextChunkLimit(cfg, "imessage");
|
|
91
|
-
const allowFrom = resolveAllowFrom(opts);
|
|
92
|
-
const mentionRegexes = resolveMentionRegexes(cfg);
|
|
93
|
-
const includeAttachments = opts.includeAttachments ?? cfg.imessage?.includeAttachments ?? false;
|
|
94
|
-
const mediaMaxBytes = (opts.mediaMaxMb ?? cfg.imessage?.mediaMaxMb ?? 16) * 1024 * 1024;
|
|
95
|
-
const handleMessage = async (raw) => {
|
|
96
|
-
const params = raw;
|
|
97
|
-
const message = params?.message ?? null;
|
|
98
|
-
if (!message)
|
|
99
|
-
return;
|
|
100
|
-
const senderRaw = message.sender ?? "";
|
|
101
|
-
const sender = senderRaw.trim();
|
|
102
|
-
if (!sender)
|
|
103
|
-
return;
|
|
104
|
-
if (message.is_from_me)
|
|
105
|
-
return;
|
|
106
|
-
const chatId = message.chat_id ?? undefined;
|
|
107
|
-
const chatGuid = message.chat_guid ?? undefined;
|
|
108
|
-
const chatIdentifier = message.chat_identifier ?? undefined;
|
|
109
|
-
const isGroup = Boolean(message.is_group);
|
|
110
|
-
if (isGroup && !chatId)
|
|
111
|
-
return;
|
|
112
|
-
if (!isAllowedIMessageSender({
|
|
113
|
-
allowFrom,
|
|
114
|
-
sender,
|
|
115
|
-
chatId: chatId ?? undefined,
|
|
116
|
-
chatGuid,
|
|
117
|
-
chatIdentifier,
|
|
118
|
-
})) {
|
|
119
|
-
logVerbose(`Blocked iMessage sender ${sender} (not in allowFrom)`);
|
|
120
|
-
return;
|
|
121
|
-
}
|
|
122
|
-
const messageText = (message.text ?? "").trim();
|
|
123
|
-
const mentioned = isGroup ? isMentioned(messageText, mentionRegexes) : true;
|
|
124
|
-
const requireMention = resolveGroupRequireMention(cfg, opts, chatId);
|
|
125
|
-
if (isGroup && requireMention && !mentioned) {
|
|
126
|
-
logVerbose(`imessage: skipping group message (no mention)`);
|
|
127
|
-
return;
|
|
128
|
-
}
|
|
129
|
-
const attachments = includeAttachments ? (message.attachments ?? []) : [];
|
|
130
|
-
const firstAttachment = attachments?.find((entry) => entry?.original_path && !entry?.missing);
|
|
131
|
-
const mediaPath = firstAttachment?.original_path ?? undefined;
|
|
132
|
-
const mediaType = firstAttachment?.mime_type ?? undefined;
|
|
133
|
-
const kind = mediaKindFromMime(mediaType ?? undefined);
|
|
134
|
-
const placeholder = kind
|
|
135
|
-
? `<media:${kind}>`
|
|
136
|
-
: attachments?.length
|
|
137
|
-
? "<media:attachment>"
|
|
138
|
-
: "";
|
|
139
|
-
const bodyText = messageText || placeholder;
|
|
140
|
-
if (!bodyText)
|
|
141
|
-
return;
|
|
142
|
-
const chatTarget = formatIMessageChatTarget(chatId);
|
|
143
|
-
const fromLabel = isGroup
|
|
144
|
-
? `${message.chat_name || "iMessage Group"} id:${chatId ?? "unknown"}`
|
|
145
|
-
: `${normalizeIMessageHandle(sender)} id:${sender}`;
|
|
146
|
-
const createdAt = message.created_at
|
|
147
|
-
? Date.parse(message.created_at)
|
|
148
|
-
: undefined;
|
|
149
|
-
const body = formatAgentEnvelope({
|
|
150
|
-
surface: "iMessage",
|
|
151
|
-
from: fromLabel,
|
|
152
|
-
timestamp: createdAt,
|
|
153
|
-
body: bodyText,
|
|
154
|
-
});
|
|
155
|
-
const ctxPayload = {
|
|
156
|
-
Body: body,
|
|
157
|
-
From: isGroup ? `group:${chatId}` : `imessage:${sender}`,
|
|
158
|
-
To: chatTarget || `imessage:${sender}`,
|
|
159
|
-
ChatType: isGroup ? "group" : "direct",
|
|
160
|
-
GroupSubject: isGroup ? (message.chat_name ?? undefined) : undefined,
|
|
161
|
-
GroupMembers: isGroup
|
|
162
|
-
? (message.participants ?? []).filter(Boolean).join(", ")
|
|
163
|
-
: undefined,
|
|
164
|
-
SenderName: sender,
|
|
165
|
-
Surface: "imessage",
|
|
166
|
-
MessageSid: message.id ? String(message.id) : undefined,
|
|
167
|
-
Timestamp: createdAt,
|
|
168
|
-
MediaPath: mediaPath,
|
|
169
|
-
MediaType: mediaType,
|
|
170
|
-
MediaUrl: mediaPath,
|
|
171
|
-
WasMentioned: mentioned,
|
|
172
|
-
};
|
|
173
|
-
if (!isGroup) {
|
|
174
|
-
const sessionCfg = cfg.session;
|
|
175
|
-
const mainKey = (sessionCfg?.mainKey ?? "main").trim() || "main";
|
|
176
|
-
const storePath = resolveStorePath(sessionCfg?.store);
|
|
177
|
-
const to = chatTarget || sender;
|
|
178
|
-
if (to) {
|
|
179
|
-
await updateLastRoute({
|
|
180
|
-
storePath,
|
|
181
|
-
sessionKey: mainKey,
|
|
182
|
-
channel: "imessage",
|
|
183
|
-
to,
|
|
184
|
-
});
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
|
-
if (shouldLogVerbose()) {
|
|
188
|
-
const preview = body.slice(0, 200).replace(/\n/g, "\\n");
|
|
189
|
-
logVerbose(`imessage inbound: chatId=${chatId ?? "unknown"} from=${ctxPayload.From} len=${body.length} preview="${preview}"`);
|
|
190
|
-
}
|
|
191
|
-
let blockSendChain = Promise.resolve();
|
|
192
|
-
const sendBlockReply = (payload) => {
|
|
193
|
-
if (!payload?.text &&
|
|
194
|
-
!payload?.mediaUrl &&
|
|
195
|
-
!(payload?.mediaUrls?.length ?? 0)) {
|
|
196
|
-
return;
|
|
197
|
-
}
|
|
198
|
-
blockSendChain = blockSendChain
|
|
199
|
-
.then(async () => {
|
|
200
|
-
await deliverReplies({
|
|
201
|
-
replies: [payload],
|
|
202
|
-
target: ctxPayload.To,
|
|
203
|
-
client,
|
|
204
|
-
runtime,
|
|
205
|
-
maxBytes: mediaMaxBytes,
|
|
206
|
-
textLimit,
|
|
207
|
-
});
|
|
208
|
-
})
|
|
209
|
-
.catch((err) => {
|
|
210
|
-
runtime.error?.(danger(`imessage block reply failed: ${String(err)}`));
|
|
211
|
-
});
|
|
212
|
-
};
|
|
213
|
-
const replyResult = await getReplyFromConfig(ctxPayload, { onBlockReply: sendBlockReply }, cfg);
|
|
214
|
-
const replies = replyResult
|
|
215
|
-
? Array.isArray(replyResult)
|
|
216
|
-
? replyResult
|
|
217
|
-
: [replyResult]
|
|
218
|
-
: [];
|
|
219
|
-
await blockSendChain;
|
|
220
|
-
if (replies.length === 0)
|
|
221
|
-
return;
|
|
222
|
-
await deliverReplies({
|
|
223
|
-
replies,
|
|
224
|
-
target: ctxPayload.To,
|
|
225
|
-
client,
|
|
226
|
-
runtime,
|
|
227
|
-
maxBytes: mediaMaxBytes,
|
|
228
|
-
textLimit,
|
|
229
|
-
});
|
|
230
|
-
};
|
|
231
|
-
const client = await createIMessageRpcClient({
|
|
232
|
-
cliPath: opts.cliPath ?? cfg.imessage?.cliPath,
|
|
233
|
-
dbPath: opts.dbPath ?? cfg.imessage?.dbPath,
|
|
234
|
-
runtime,
|
|
235
|
-
onNotification: (msg) => {
|
|
236
|
-
if (msg.method === "message") {
|
|
237
|
-
void handleMessage(msg.params).catch((err) => {
|
|
238
|
-
runtime.error?.(`imessage: handler failed: ${String(err)}`);
|
|
239
|
-
});
|
|
240
|
-
}
|
|
241
|
-
else if (msg.method === "error") {
|
|
242
|
-
runtime.error?.(`imessage: watch error ${JSON.stringify(msg.params)}`);
|
|
243
|
-
}
|
|
244
|
-
},
|
|
245
|
-
});
|
|
246
|
-
let subscriptionId = null;
|
|
247
|
-
const abort = opts.abortSignal;
|
|
248
|
-
const onAbort = () => {
|
|
249
|
-
if (subscriptionId) {
|
|
250
|
-
void client.request("watch.unsubscribe", {
|
|
251
|
-
subscription: subscriptionId,
|
|
252
|
-
});
|
|
253
|
-
}
|
|
254
|
-
void client.stop();
|
|
255
|
-
};
|
|
256
|
-
abort?.addEventListener("abort", onAbort, { once: true });
|
|
257
|
-
try {
|
|
258
|
-
const result = await client.request("watch.subscribe", { attachments: includeAttachments });
|
|
259
|
-
subscriptionId = result?.subscription ?? null;
|
|
260
|
-
await client.waitForClose();
|
|
261
|
-
}
|
|
262
|
-
catch (err) {
|
|
263
|
-
if (abort?.aborted)
|
|
264
|
-
return;
|
|
265
|
-
runtime.error?.(danger(`imessage: monitor failed: ${String(err)}`));
|
|
266
|
-
throw err;
|
|
267
|
-
}
|
|
268
|
-
finally {
|
|
269
|
-
abort?.removeEventListener("abort", onAbort);
|
|
270
|
-
await client.stop();
|
|
271
|
-
}
|
|
272
|
-
}
|
package/dist/imessage/probe.js
DELETED
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
import { detectBinary } from "../commands/onboard-helpers.js";
|
|
2
|
-
import { loadConfig } from "../config/config.js";
|
|
3
|
-
import { createIMessageRpcClient } from "./client.js";
|
|
4
|
-
export async function probeIMessage(timeoutMs = 2000) {
|
|
5
|
-
const cfg = loadConfig();
|
|
6
|
-
const cliPath = cfg.imessage?.cliPath?.trim() || "imsg";
|
|
7
|
-
const dbPath = cfg.imessage?.dbPath?.trim();
|
|
8
|
-
const detected = await detectBinary(cliPath);
|
|
9
|
-
if (!detected) {
|
|
10
|
-
return { ok: false, error: `imsg not found (${cliPath})` };
|
|
11
|
-
}
|
|
12
|
-
const client = await createIMessageRpcClient({
|
|
13
|
-
cliPath,
|
|
14
|
-
dbPath,
|
|
15
|
-
});
|
|
16
|
-
try {
|
|
17
|
-
await client.request("chats.list", { limit: 1 }, { timeoutMs });
|
|
18
|
-
return { ok: true };
|
|
19
|
-
}
|
|
20
|
-
catch (err) {
|
|
21
|
-
return { ok: false, error: String(err) };
|
|
22
|
-
}
|
|
23
|
-
finally {
|
|
24
|
-
await client.stop();
|
|
25
|
-
}
|
|
26
|
-
}
|
package/dist/imessage/send.js
DELETED
|
@@ -1,83 +0,0 @@
|
|
|
1
|
-
import { loadConfig } from "../config/config.js";
|
|
2
|
-
import { mediaKindFromMime } from "../media/constants.js";
|
|
3
|
-
import { saveMediaBuffer } from "../media/store.js";
|
|
4
|
-
import { loadWebMedia } from "../web/media.js";
|
|
5
|
-
import { createIMessageRpcClient } from "./client.js";
|
|
6
|
-
import { formatIMessageChatTarget, parseIMessageTarget, } from "./targets.js";
|
|
7
|
-
function resolveCliPath(explicit) {
|
|
8
|
-
const cfg = loadConfig();
|
|
9
|
-
return explicit?.trim() || cfg.imessage?.cliPath?.trim() || "imsg";
|
|
10
|
-
}
|
|
11
|
-
function resolveDbPath(explicit) {
|
|
12
|
-
const cfg = loadConfig();
|
|
13
|
-
return explicit?.trim() || cfg.imessage?.dbPath?.trim() || undefined;
|
|
14
|
-
}
|
|
15
|
-
function resolveService(explicit) {
|
|
16
|
-
const cfg = loadConfig();
|
|
17
|
-
return (explicit || cfg.imessage?.service || "auto");
|
|
18
|
-
}
|
|
19
|
-
function resolveRegion(explicit) {
|
|
20
|
-
const cfg = loadConfig();
|
|
21
|
-
return explicit?.trim() || cfg.imessage?.region?.trim() || "US";
|
|
22
|
-
}
|
|
23
|
-
async function resolveAttachment(mediaUrl, maxBytes) {
|
|
24
|
-
const media = await loadWebMedia(mediaUrl, maxBytes);
|
|
25
|
-
const saved = await saveMediaBuffer(media.buffer, media.contentType ?? undefined, "outbound", maxBytes);
|
|
26
|
-
return { path: saved.path, contentType: saved.contentType };
|
|
27
|
-
}
|
|
28
|
-
export async function sendMessageIMessage(to, text, opts = {}) {
|
|
29
|
-
const cliPath = resolveCliPath(opts.cliPath);
|
|
30
|
-
const dbPath = resolveDbPath(opts.dbPath);
|
|
31
|
-
const target = parseIMessageTarget(opts.chatId ? formatIMessageChatTarget(opts.chatId) : to);
|
|
32
|
-
const service = opts.service ?? (target.kind === "handle" ? target.service : undefined);
|
|
33
|
-
const region = resolveRegion(opts.region);
|
|
34
|
-
const maxBytes = opts.maxBytes ?? 16 * 1024 * 1024;
|
|
35
|
-
let message = text ?? "";
|
|
36
|
-
let filePath;
|
|
37
|
-
if (opts.mediaUrl?.trim()) {
|
|
38
|
-
const resolved = await resolveAttachment(opts.mediaUrl.trim(), maxBytes);
|
|
39
|
-
filePath = resolved.path;
|
|
40
|
-
if (!message.trim()) {
|
|
41
|
-
const kind = mediaKindFromMime(resolved.contentType ?? undefined);
|
|
42
|
-
if (kind)
|
|
43
|
-
message = kind === "image" ? "<media:image>" : `<media:${kind}>`;
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
if (!message.trim() && !filePath) {
|
|
47
|
-
throw new Error("iMessage send requires text or media");
|
|
48
|
-
}
|
|
49
|
-
const params = {
|
|
50
|
-
text: message,
|
|
51
|
-
service: resolveService(service),
|
|
52
|
-
region,
|
|
53
|
-
};
|
|
54
|
-
if (filePath)
|
|
55
|
-
params.file = filePath;
|
|
56
|
-
if (target.kind === "chat_id") {
|
|
57
|
-
params.chat_id = target.chatId;
|
|
58
|
-
}
|
|
59
|
-
else if (target.kind === "chat_guid") {
|
|
60
|
-
params.chat_guid = target.chatGuid;
|
|
61
|
-
}
|
|
62
|
-
else if (target.kind === "chat_identifier") {
|
|
63
|
-
params.chat_identifier = target.chatIdentifier;
|
|
64
|
-
}
|
|
65
|
-
else {
|
|
66
|
-
params.to = target.to;
|
|
67
|
-
}
|
|
68
|
-
const client = opts.client ?? (await createIMessageRpcClient({ cliPath, dbPath }));
|
|
69
|
-
const shouldClose = !opts.client;
|
|
70
|
-
try {
|
|
71
|
-
const result = await client.request("send", params, {
|
|
72
|
-
timeoutMs: opts.timeoutMs,
|
|
73
|
-
});
|
|
74
|
-
return {
|
|
75
|
-
messageId: result?.ok ? "ok" : "unknown",
|
|
76
|
-
};
|
|
77
|
-
}
|
|
78
|
-
finally {
|
|
79
|
-
if (shouldClose) {
|
|
80
|
-
await client.stop();
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
}
|
package/dist/imessage/targets.js
DELETED
|
@@ -1,176 +0,0 @@
|
|
|
1
|
-
import { normalizeE164 } from "../utils.js";
|
|
2
|
-
const CHAT_ID_PREFIXES = ["chat_id:", "chatid:", "chat:"];
|
|
3
|
-
const CHAT_GUID_PREFIXES = ["chat_guid:", "chatguid:", "guid:"];
|
|
4
|
-
const CHAT_IDENTIFIER_PREFIXES = [
|
|
5
|
-
"chat_identifier:",
|
|
6
|
-
"chatidentifier:",
|
|
7
|
-
"chatident:",
|
|
8
|
-
];
|
|
9
|
-
const SERVICE_PREFIXES = [
|
|
10
|
-
{ prefix: "imessage:", service: "imessage" },
|
|
11
|
-
{ prefix: "sms:", service: "sms" },
|
|
12
|
-
{ prefix: "auto:", service: "auto" },
|
|
13
|
-
];
|
|
14
|
-
function stripPrefix(value, prefix) {
|
|
15
|
-
return value.slice(prefix.length).trim();
|
|
16
|
-
}
|
|
17
|
-
export function normalizeIMessageHandle(raw) {
|
|
18
|
-
const trimmed = raw.trim();
|
|
19
|
-
if (!trimmed)
|
|
20
|
-
return "";
|
|
21
|
-
const lowered = trimmed.toLowerCase();
|
|
22
|
-
if (lowered.startsWith("imessage:"))
|
|
23
|
-
return normalizeIMessageHandle(trimmed.slice(9));
|
|
24
|
-
if (lowered.startsWith("sms:"))
|
|
25
|
-
return normalizeIMessageHandle(trimmed.slice(4));
|
|
26
|
-
if (lowered.startsWith("auto:"))
|
|
27
|
-
return normalizeIMessageHandle(trimmed.slice(5));
|
|
28
|
-
if (trimmed.includes("@"))
|
|
29
|
-
return trimmed.toLowerCase();
|
|
30
|
-
const normalized = normalizeE164(trimmed);
|
|
31
|
-
if (normalized)
|
|
32
|
-
return normalized;
|
|
33
|
-
return trimmed.replace(/\s+/g, "");
|
|
34
|
-
}
|
|
35
|
-
export function parseIMessageTarget(raw) {
|
|
36
|
-
const trimmed = raw.trim();
|
|
37
|
-
if (!trimmed)
|
|
38
|
-
throw new Error("iMessage target is required");
|
|
39
|
-
const lower = trimmed.toLowerCase();
|
|
40
|
-
for (const { prefix, service } of SERVICE_PREFIXES) {
|
|
41
|
-
if (lower.startsWith(prefix)) {
|
|
42
|
-
const remainder = stripPrefix(trimmed, prefix);
|
|
43
|
-
if (!remainder)
|
|
44
|
-
throw new Error(`${prefix} target is required`);
|
|
45
|
-
const remainderLower = remainder.toLowerCase();
|
|
46
|
-
const isChatTarget = CHAT_ID_PREFIXES.some((p) => remainderLower.startsWith(p)) ||
|
|
47
|
-
CHAT_GUID_PREFIXES.some((p) => remainderLower.startsWith(p)) ||
|
|
48
|
-
CHAT_IDENTIFIER_PREFIXES.some((p) => remainderLower.startsWith(p)) ||
|
|
49
|
-
remainderLower.startsWith("group:");
|
|
50
|
-
if (isChatTarget) {
|
|
51
|
-
return parseIMessageTarget(remainder);
|
|
52
|
-
}
|
|
53
|
-
return { kind: "handle", to: remainder, service };
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
for (const prefix of CHAT_ID_PREFIXES) {
|
|
57
|
-
if (lower.startsWith(prefix)) {
|
|
58
|
-
const value = stripPrefix(trimmed, prefix);
|
|
59
|
-
const chatId = Number.parseInt(value, 10);
|
|
60
|
-
if (!Number.isFinite(chatId)) {
|
|
61
|
-
throw new Error(`Invalid chat_id: ${value}`);
|
|
62
|
-
}
|
|
63
|
-
return { kind: "chat_id", chatId };
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
for (const prefix of CHAT_GUID_PREFIXES) {
|
|
67
|
-
if (lower.startsWith(prefix)) {
|
|
68
|
-
const value = stripPrefix(trimmed, prefix);
|
|
69
|
-
if (!value)
|
|
70
|
-
throw new Error("chat_guid is required");
|
|
71
|
-
return { kind: "chat_guid", chatGuid: value };
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
for (const prefix of CHAT_IDENTIFIER_PREFIXES) {
|
|
75
|
-
if (lower.startsWith(prefix)) {
|
|
76
|
-
const value = stripPrefix(trimmed, prefix);
|
|
77
|
-
if (!value)
|
|
78
|
-
throw new Error("chat_identifier is required");
|
|
79
|
-
return { kind: "chat_identifier", chatIdentifier: value };
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
if (lower.startsWith("group:")) {
|
|
83
|
-
const value = stripPrefix(trimmed, "group:");
|
|
84
|
-
const chatId = Number.parseInt(value, 10);
|
|
85
|
-
if (Number.isFinite(chatId)) {
|
|
86
|
-
return { kind: "chat_id", chatId };
|
|
87
|
-
}
|
|
88
|
-
if (!value)
|
|
89
|
-
throw new Error("group target is required");
|
|
90
|
-
return { kind: "chat_guid", chatGuid: value };
|
|
91
|
-
}
|
|
92
|
-
return { kind: "handle", to: trimmed, service: "auto" };
|
|
93
|
-
}
|
|
94
|
-
export function parseIMessageAllowTarget(raw) {
|
|
95
|
-
const trimmed = raw.trim();
|
|
96
|
-
if (!trimmed)
|
|
97
|
-
return { kind: "handle", handle: "" };
|
|
98
|
-
const lower = trimmed.toLowerCase();
|
|
99
|
-
for (const { prefix } of SERVICE_PREFIXES) {
|
|
100
|
-
if (lower.startsWith(prefix)) {
|
|
101
|
-
const remainder = stripPrefix(trimmed, prefix);
|
|
102
|
-
if (!remainder)
|
|
103
|
-
return { kind: "handle", handle: "" };
|
|
104
|
-
return parseIMessageAllowTarget(remainder);
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
for (const prefix of CHAT_ID_PREFIXES) {
|
|
108
|
-
if (lower.startsWith(prefix)) {
|
|
109
|
-
const value = stripPrefix(trimmed, prefix);
|
|
110
|
-
const chatId = Number.parseInt(value, 10);
|
|
111
|
-
if (Number.isFinite(chatId))
|
|
112
|
-
return { kind: "chat_id", chatId };
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
for (const prefix of CHAT_GUID_PREFIXES) {
|
|
116
|
-
if (lower.startsWith(prefix)) {
|
|
117
|
-
const value = stripPrefix(trimmed, prefix);
|
|
118
|
-
if (value)
|
|
119
|
-
return { kind: "chat_guid", chatGuid: value };
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
for (const prefix of CHAT_IDENTIFIER_PREFIXES) {
|
|
123
|
-
if (lower.startsWith(prefix)) {
|
|
124
|
-
const value = stripPrefix(trimmed, prefix);
|
|
125
|
-
if (value)
|
|
126
|
-
return { kind: "chat_identifier", chatIdentifier: value };
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
if (lower.startsWith("group:")) {
|
|
130
|
-
const value = stripPrefix(trimmed, "group:");
|
|
131
|
-
const chatId = Number.parseInt(value, 10);
|
|
132
|
-
if (Number.isFinite(chatId))
|
|
133
|
-
return { kind: "chat_id", chatId };
|
|
134
|
-
if (value)
|
|
135
|
-
return { kind: "chat_guid", chatGuid: value };
|
|
136
|
-
}
|
|
137
|
-
return { kind: "handle", handle: normalizeIMessageHandle(trimmed) };
|
|
138
|
-
}
|
|
139
|
-
export function isAllowedIMessageSender(params) {
|
|
140
|
-
const allowFrom = params.allowFrom.map((entry) => String(entry).trim());
|
|
141
|
-
if (allowFrom.length === 0)
|
|
142
|
-
return true;
|
|
143
|
-
if (allowFrom.includes("*"))
|
|
144
|
-
return true;
|
|
145
|
-
const senderNormalized = normalizeIMessageHandle(params.sender);
|
|
146
|
-
const chatId = params.chatId ?? undefined;
|
|
147
|
-
const chatGuid = params.chatGuid?.trim();
|
|
148
|
-
const chatIdentifier = params.chatIdentifier?.trim();
|
|
149
|
-
for (const entry of allowFrom) {
|
|
150
|
-
if (!entry)
|
|
151
|
-
continue;
|
|
152
|
-
const parsed = parseIMessageAllowTarget(entry);
|
|
153
|
-
if (parsed.kind === "chat_id" && chatId !== undefined) {
|
|
154
|
-
if (parsed.chatId === chatId)
|
|
155
|
-
return true;
|
|
156
|
-
}
|
|
157
|
-
else if (parsed.kind === "chat_guid" && chatGuid) {
|
|
158
|
-
if (parsed.chatGuid === chatGuid)
|
|
159
|
-
return true;
|
|
160
|
-
}
|
|
161
|
-
else if (parsed.kind === "chat_identifier" && chatIdentifier) {
|
|
162
|
-
if (parsed.chatIdentifier === chatIdentifier)
|
|
163
|
-
return true;
|
|
164
|
-
}
|
|
165
|
-
else if (parsed.kind === "handle" && senderNormalized) {
|
|
166
|
-
if (parsed.handle === senderNormalized)
|
|
167
|
-
return true;
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
return false;
|
|
171
|
-
}
|
|
172
|
-
export function formatIMessageChatTarget(chatId) {
|
|
173
|
-
if (!chatId || !Number.isFinite(chatId))
|
|
174
|
-
return "";
|
|
175
|
-
return `chat_id:${chatId}`;
|
|
176
|
-
}
|
|
@@ -1,68 +0,0 @@
|
|
|
1
|
-
export function normalizeSendPolicy(raw) {
|
|
2
|
-
const value = raw?.trim().toLowerCase();
|
|
3
|
-
if (value === "allow")
|
|
4
|
-
return "allow";
|
|
5
|
-
if (value === "deny")
|
|
6
|
-
return "deny";
|
|
7
|
-
return undefined;
|
|
8
|
-
}
|
|
9
|
-
function normalizeMatchValue(raw) {
|
|
10
|
-
const value = raw?.trim().toLowerCase();
|
|
11
|
-
return value ? value : undefined;
|
|
12
|
-
}
|
|
13
|
-
function deriveSurfaceFromKey(key) {
|
|
14
|
-
if (!key)
|
|
15
|
-
return undefined;
|
|
16
|
-
const parts = key.split(":").filter(Boolean);
|
|
17
|
-
if (parts.length >= 3 && (parts[1] === "group" || parts[1] === "channel")) {
|
|
18
|
-
return normalizeMatchValue(parts[0]);
|
|
19
|
-
}
|
|
20
|
-
return undefined;
|
|
21
|
-
}
|
|
22
|
-
function deriveChatTypeFromKey(key) {
|
|
23
|
-
if (!key)
|
|
24
|
-
return undefined;
|
|
25
|
-
if (key.startsWith("group:") || key.includes(":group:"))
|
|
26
|
-
return "group";
|
|
27
|
-
if (key.includes(":channel:"))
|
|
28
|
-
return "room";
|
|
29
|
-
return undefined;
|
|
30
|
-
}
|
|
31
|
-
export function resolveSendPolicy(params) {
|
|
32
|
-
const override = normalizeSendPolicy(params.entry?.sendPolicy);
|
|
33
|
-
if (override)
|
|
34
|
-
return override;
|
|
35
|
-
const policy = params.cfg.session?.sendPolicy;
|
|
36
|
-
if (!policy)
|
|
37
|
-
return "allow";
|
|
38
|
-
const surface = normalizeMatchValue(params.surface) ??
|
|
39
|
-
normalizeMatchValue(params.entry?.surface) ??
|
|
40
|
-
normalizeMatchValue(params.entry?.lastChannel) ??
|
|
41
|
-
deriveSurfaceFromKey(params.sessionKey);
|
|
42
|
-
const chatType = normalizeMatchValue(params.chatType ?? params.entry?.chatType) ??
|
|
43
|
-
normalizeMatchValue(deriveChatTypeFromKey(params.sessionKey));
|
|
44
|
-
const sessionKey = params.sessionKey ?? "";
|
|
45
|
-
let allowedMatch = false;
|
|
46
|
-
for (const rule of policy.rules ?? []) {
|
|
47
|
-
if (!rule)
|
|
48
|
-
continue;
|
|
49
|
-
const action = normalizeSendPolicy(rule.action) ?? "allow";
|
|
50
|
-
const match = rule.match ?? {};
|
|
51
|
-
const matchSurface = normalizeMatchValue(match.surface);
|
|
52
|
-
const matchChatType = normalizeMatchValue(match.chatType);
|
|
53
|
-
const matchPrefix = normalizeMatchValue(match.keyPrefix);
|
|
54
|
-
if (matchSurface && matchSurface !== surface)
|
|
55
|
-
continue;
|
|
56
|
-
if (matchChatType && matchChatType !== chatType)
|
|
57
|
-
continue;
|
|
58
|
-
if (matchPrefix && !sessionKey.startsWith(matchPrefix))
|
|
59
|
-
continue;
|
|
60
|
-
if (action === "deny")
|
|
61
|
-
return "deny";
|
|
62
|
-
allowedMatch = true;
|
|
63
|
-
}
|
|
64
|
-
if (allowedMatch)
|
|
65
|
-
return "allow";
|
|
66
|
-
const fallback = normalizeSendPolicy(policy.default);
|
|
67
|
-
return fallback ?? "allow";
|
|
68
|
-
}
|