@moxxy/plugin-channel-imessage 0.29.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/LICENSE +21 -0
- package/dist/bluebubbles-client.d.ts +79 -0
- package/dist/bluebubbles-client.d.ts.map +1 -0
- package/dist/bluebubbles-client.js +113 -0
- package/dist/bluebubbles-client.js.map +1 -0
- package/dist/channel/chunker.d.ts +66 -0
- package/dist/channel/chunker.d.ts.map +1 -0
- package/dist/channel/chunker.js +130 -0
- package/dist/channel/chunker.js.map +1 -0
- package/dist/channel/turn-runner.d.ts +32 -0
- package/dist/channel/turn-runner.d.ts.map +1 -0
- package/dist/channel/turn-runner.js +58 -0
- package/dist/channel/turn-runner.js.map +1 -0
- package/dist/channel.d.ts +87 -0
- package/dist/channel.d.ts.map +1 -0
- package/dist/channel.js +264 -0
- package/dist/channel.js.map +1 -0
- package/dist/index.d.ts +27 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +211 -0
- package/dist/index.js.map +1 -0
- package/dist/keys.d.ts +70 -0
- package/dist/keys.d.ts.map +1 -0
- package/dist/keys.js +111 -0
- package/dist/keys.js.map +1 -0
- package/dist/message-gate.d.ts +40 -0
- package/dist/message-gate.d.ts.map +1 -0
- package/dist/message-gate.js +54 -0
- package/dist/message-gate.js.map +1 -0
- package/dist/permission.d.ts +25 -0
- package/dist/permission.d.ts.map +1 -0
- package/dist/permission.js +31 -0
- package/dist/permission.js.map +1 -0
- package/dist/schema.d.ts +96 -0
- package/dist/schema.d.ts.map +1 -0
- package/dist/schema.js +41 -0
- package/dist/schema.js.map +1 -0
- package/dist/setup-wizard.d.ts +15 -0
- package/dist/setup-wizard.d.ts.map +1 -0
- package/dist/setup-wizard.js +127 -0
- package/dist/setup-wizard.js.map +1 -0
- package/package.json +96 -0
- package/src/bluebubbles-client.ts +182 -0
- package/src/channel/chunker.test.ts +132 -0
- package/src/channel/chunker.ts +144 -0
- package/src/channel/turn-runner.ts +82 -0
- package/src/channel.test.ts +256 -0
- package/src/channel.ts +352 -0
- package/src/index.ts +273 -0
- package/src/keys.test.ts +86 -0
- package/src/keys.ts +109 -0
- package/src/message-gate.test.ts +126 -0
- package/src/message-gate.ts +94 -0
- package/src/permission.ts +40 -0
- package/src/schema.test.ts +51 -0
- package/src/schema.ts +47 -0
- package/src/setup-wizard.ts +155 -0
- package/src/subcommands.test.ts +194 -0
package/dist/keys.js
ADDED
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Vault keys + pure handle helpers shared by the channel, its subcommands, and
|
|
3
|
+
* the interactive setup wizard. Kept in their own module (the signal/whatsapp
|
|
4
|
+
* `keys.ts` pattern) so wizard helpers can import them without pulling in the
|
|
5
|
+
* plugin's full index (or, worse, socket.io-client — which stays behind a lazy
|
|
6
|
+
* import in `bluebubbles-client.ts`).
|
|
7
|
+
*
|
|
8
|
+
* Note on custody: the BlueBubbles server (a native macOS app) holds the actual
|
|
9
|
+
* iMessage identity + message store. The moxxy vault only stores the localhost
|
|
10
|
+
* server URL, its password, and two JSON handle arrays — the allow-list of OTHER
|
|
11
|
+
* people who may drive the agent, and the owner's own handle(s) that enable
|
|
12
|
+
* texting moxxy from your own devices (the "self-chat").
|
|
13
|
+
*/
|
|
14
|
+
/** Vault key for the BlueBubbles server URL (e.g. http://localhost:1234). */
|
|
15
|
+
export const IMESSAGE_SERVER_URL_KEY = 'imessage_server_url';
|
|
16
|
+
/** Env override for the server URL — beats the vault (shared precedence). */
|
|
17
|
+
export const IMESSAGE_SERVER_URL_ENV = 'MOXXY_IMESSAGE_SERVER_URL';
|
|
18
|
+
/** Vault key for the BlueBubbles server password. */
|
|
19
|
+
export const IMESSAGE_SERVER_PASSWORD_KEY = 'imessage_server_password';
|
|
20
|
+
/** Env override for the server password — beats the vault. */
|
|
21
|
+
export const IMESSAGE_SERVER_PASSWORD_ENV = 'MOXXY_IMESSAGE_SERVER_PASSWORD';
|
|
22
|
+
/**
|
|
23
|
+
* Vault key for the allow-list: a JSON array of iMessage handles (E.164 phone
|
|
24
|
+
* numbers and/or Apple-ID emails) that OTHER people may use to drive the
|
|
25
|
+
* session. The owner's own handle is NOT listed here — it lives in
|
|
26
|
+
* {@link IMESSAGE_OWNER_HANDLES_KEY} so an inbound from a friend and an outbound
|
|
27
|
+
* from the owner stay distinguishable (never react to the owner's private
|
|
28
|
+
* conversations with allow-listed friends).
|
|
29
|
+
*/
|
|
30
|
+
export const IMESSAGE_ALLOWED_HANDLES_KEY = 'imessage_allowed_handles';
|
|
31
|
+
/**
|
|
32
|
+
* Vault key for the owner's OWN handle(s): a JSON array of the account owner's
|
|
33
|
+
* E.164 numbers / Apple-ID emails. A message the account itself sent
|
|
34
|
+
* (`isFromMe`) drives a turn only when it lands in a 1:1 chat with one of these
|
|
35
|
+
* handles — i.e. the owner texting their own "self-chat" from any Apple device.
|
|
36
|
+
* Empty (the default) means self-chat is off and every `isFromMe` message is
|
|
37
|
+
* dropped (fail closed); the friend allow-list path is unaffected.
|
|
38
|
+
*/
|
|
39
|
+
export const IMESSAGE_OWNER_HANDLES_KEY = 'imessage_owner_handles';
|
|
40
|
+
/** E.164 shape: `+` then 7–15 digits, no leading zero. */
|
|
41
|
+
export const E164_RE = /^\+[1-9]\d{6,14}$/;
|
|
42
|
+
/** Pragmatic email shape (Apple-ID handles). Deliberately loose but anchored. */
|
|
43
|
+
export const EMAIL_RE = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
44
|
+
/** Whether a normalized string is a plausible iMessage handle. */
|
|
45
|
+
export function isHandle(value) {
|
|
46
|
+
return E164_RE.test(value) || EMAIL_RE.test(value);
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Canonical handle for allow-list comparisons: trimmed, and lowercased when it
|
|
50
|
+
* is an email (Apple-ID handles are case-insensitive). Phone numbers are left
|
|
51
|
+
* as-is (they are already digits + `+`).
|
|
52
|
+
*/
|
|
53
|
+
export function normalizeHandle(raw) {
|
|
54
|
+
const trimmed = raw.trim();
|
|
55
|
+
return trimmed.includes('@') ? trimmed.toLowerCase() : trimmed;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Parse a stored handle list. Returns `[]` for a missing OR corrupt value
|
|
59
|
+
* rather than throwing — a corrupt vault entry must degrade to "nobody extra is
|
|
60
|
+
* allowed" (fail closed), never crash the channel or silently allow. Non-string
|
|
61
|
+
* entries and entries that are neither an E.164 number nor an email are dropped.
|
|
62
|
+
*/
|
|
63
|
+
export function parseHandleList(raw) {
|
|
64
|
+
if (!raw)
|
|
65
|
+
return [];
|
|
66
|
+
let parsed;
|
|
67
|
+
try {
|
|
68
|
+
parsed = JSON.parse(raw);
|
|
69
|
+
}
|
|
70
|
+
catch {
|
|
71
|
+
return [];
|
|
72
|
+
}
|
|
73
|
+
if (!Array.isArray(parsed))
|
|
74
|
+
return [];
|
|
75
|
+
const out = new Set();
|
|
76
|
+
for (const entry of parsed) {
|
|
77
|
+
if (typeof entry !== 'string')
|
|
78
|
+
continue;
|
|
79
|
+
const handle = normalizeHandle(entry);
|
|
80
|
+
if (isHandle(handle))
|
|
81
|
+
out.add(handle);
|
|
82
|
+
}
|
|
83
|
+
return [...out];
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Parse the chat GUID BlueBubbles stamps on every message
|
|
87
|
+
* (`SERVICE;TYPE;IDENTIFIER`, e.g. `iMessage;-;+15551234567`). TYPE is `-` for a
|
|
88
|
+
* 1:1 chat and `+` for a group. Returns null for anything that is not a
|
|
89
|
+
* well-formed 1:1 GUID (groups included) so callers treat it as "not a DM" and
|
|
90
|
+
* drop it (v1 is direct-message only).
|
|
91
|
+
*/
|
|
92
|
+
export function parseDmChatGuid(guid) {
|
|
93
|
+
if (typeof guid !== 'string')
|
|
94
|
+
return null;
|
|
95
|
+
const parts = guid.split(';');
|
|
96
|
+
if (parts.length !== 3)
|
|
97
|
+
return null;
|
|
98
|
+
const [rawService, rawType, rawIdentifier] = parts;
|
|
99
|
+
if (rawService === undefined || rawType === undefined || rawIdentifier === undefined)
|
|
100
|
+
return null;
|
|
101
|
+
const service = rawService.trim();
|
|
102
|
+
const type = rawType.trim();
|
|
103
|
+
const identifier = rawIdentifier.trim();
|
|
104
|
+
if (service.length === 0 || type !== '-' || identifier.length === 0)
|
|
105
|
+
return null;
|
|
106
|
+
const handle = normalizeHandle(identifier);
|
|
107
|
+
if (!isHandle(handle))
|
|
108
|
+
return null;
|
|
109
|
+
return { service, handle };
|
|
110
|
+
}
|
|
111
|
+
//# sourceMappingURL=keys.js.map
|
package/dist/keys.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"keys.js","sourceRoot":"","sources":["../src/keys.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The single inbound gate every session-reaching path goes through (AGENTS.md:
|
|
3
|
+
* gate EVERY session-reaching path behind pairing; A8: zod-validate inbound
|
|
4
|
+
* before it touches the session). Pure — the channel feeds it the raw
|
|
5
|
+
* BlueBubbles `new-message` payload plus its identity/allow-list state and
|
|
6
|
+
* branches on the verdict.
|
|
7
|
+
*
|
|
8
|
+
* Order of checks (each is load-bearing, mirroring the WhatsApp gate):
|
|
9
|
+
* 1. shape-validate + size-cap the consumed fields (zod).
|
|
10
|
+
* 2. drop own echoes: BlueBubbles emits `new-message` for the account's OWN
|
|
11
|
+
* sends too (our replies come back `isFromMe`), so without the sent-id check
|
|
12
|
+
* the bot would answer itself in a loop — and a self-chat reply would be
|
|
13
|
+
* mistaken for a fresh owner prompt.
|
|
14
|
+
* 3. direct messages only (v1): a group chat GUID is dropped — group fan-in
|
|
15
|
+
* needs its own trust story.
|
|
16
|
+
* 4. drop `isFromMe` messages outside the owner's self-chat: those are the
|
|
17
|
+
* owner talking to OTHER people from an Apple device — never a prompt. Only
|
|
18
|
+
* `isFromMe` in a 1:1 chat with one of the owner's own handles drives a turn.
|
|
19
|
+
* 5. allow-list by normalized handle for inbound (non-`isFromMe`) messages.
|
|
20
|
+
* Unauthorized senders are dropped SILENTLY — replying would leak the bot's
|
|
21
|
+
* existence.
|
|
22
|
+
*/
|
|
23
|
+
export type GateVerdict = {
|
|
24
|
+
readonly ok: false;
|
|
25
|
+
readonly reason: string;
|
|
26
|
+
} | {
|
|
27
|
+
readonly ok: true;
|
|
28
|
+
readonly chatGuid: string;
|
|
29
|
+
readonly text: string;
|
|
30
|
+
};
|
|
31
|
+
export interface GateState {
|
|
32
|
+
/** The owner's own normalized handle(s) — the self-chat identities. */
|
|
33
|
+
readonly ownerHandles: ReadonlySet<string>;
|
|
34
|
+
/** Normalized handles of OTHER people allowed to drive the session. */
|
|
35
|
+
readonly allowedHandles: ReadonlySet<string>;
|
|
36
|
+
/** True for a guid/tempGuid of THIS channel's own recent send (echo/loop protection). */
|
|
37
|
+
readonly isOwnSend: (id: string) => boolean;
|
|
38
|
+
}
|
|
39
|
+
export declare function gateInboundMessage(state: GateState, raw: unknown): GateVerdict;
|
|
40
|
+
//# sourceMappingURL=message-gate.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"message-gate.d.ts","sourceRoot":"","sources":["../src/message-gate.ts"],"names":[],"mappings":"AAGA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,MAAM,MAAM,WAAW,GACnB;IAAE,QAAQ,CAAC,EAAE,EAAE,KAAK,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GAC/C;IAAE,QAAQ,CAAC,EAAE,EAAE,IAAI,CAAC;IAAC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC;AAE5E,MAAM,WAAW,SAAS;IACxB,uEAAuE;IACvE,QAAQ,CAAC,YAAY,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;IAC3C,uEAAuE;IACvE,QAAQ,CAAC,cAAc,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;IAC7C,yFAAyF;IACzF,QAAQ,CAAC,SAAS,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,OAAO,CAAC;CAC7C;AAED,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,SAAS,EAAE,GAAG,EAAE,OAAO,GAAG,WAAW,CAmC9E"}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { normalizeHandle, parseDmChatGuid } from './keys.js';
|
|
2
|
+
import { messageSchema } from './schema.js';
|
|
3
|
+
export function gateInboundMessage(state, raw) {
|
|
4
|
+
const parsed = messageSchema.safeParse(raw);
|
|
5
|
+
if (!parsed.success)
|
|
6
|
+
return drop('invalid message shape');
|
|
7
|
+
const message = parsed.data;
|
|
8
|
+
const chats = message.chats;
|
|
9
|
+
const firstChat = chats && chats.length > 0 ? chats[0] : undefined;
|
|
10
|
+
if (!firstChat)
|
|
11
|
+
return drop('message without a chat');
|
|
12
|
+
const chatGuid = firstChat.guid;
|
|
13
|
+
// Own-echo drop FIRST — a reply we sent into a self-chat comes back isFromMe
|
|
14
|
+
// and would otherwise re-enter as an owner prompt (loop).
|
|
15
|
+
if (state.isOwnSend(message.guid))
|
|
16
|
+
return drop('own outbound echo (guid)');
|
|
17
|
+
const tempGuid = message.tempGuid;
|
|
18
|
+
if (tempGuid && state.isOwnSend(tempGuid))
|
|
19
|
+
return drop('own outbound echo (tempGuid)');
|
|
20
|
+
const dm = parseDmChatGuid(chatGuid);
|
|
21
|
+
if (!dm)
|
|
22
|
+
return drop('not a 1:1 direct message');
|
|
23
|
+
if (message.isFromMe === true) {
|
|
24
|
+
// Same-account messages: only the owner's own self-chat is a prompt surface.
|
|
25
|
+
if (!state.ownerHandles.has(dm.handle)) {
|
|
26
|
+
return drop('own message in a foreign chat');
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
else {
|
|
30
|
+
const senderHandle = resolveSender(message.handle, dm.handle);
|
|
31
|
+
if (!state.allowedHandles.has(senderHandle)) {
|
|
32
|
+
return drop('sender not allow-listed');
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
const text = typeof message.text === 'string' ? message.text.trim() : '';
|
|
36
|
+
if (text.length === 0)
|
|
37
|
+
return drop('no message text');
|
|
38
|
+
return { ok: true, chatGuid, text };
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* The sender's handle for an inbound message: the message's own `handle`
|
|
42
|
+
* address when present, else the 1:1 chat's counterpart (they are the same
|
|
43
|
+
* party in a DM). Narrowed once, then plain access — no optional-chain depth.
|
|
44
|
+
*/
|
|
45
|
+
function resolveSender(handle, chatHandle) {
|
|
46
|
+
if (handle && typeof handle.address === 'string' && handle.address.length > 0) {
|
|
47
|
+
return normalizeHandle(handle.address);
|
|
48
|
+
}
|
|
49
|
+
return chatHandle;
|
|
50
|
+
}
|
|
51
|
+
function drop(reason) {
|
|
52
|
+
return { ok: false, reason };
|
|
53
|
+
}
|
|
54
|
+
//# sourceMappingURL=message-gate.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"message-gate.js","sourceRoot":"","sources":["../src/message-gate.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,eAAe,EAAE,MAAM,WAAW,CAAC;AAC7D,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAsC5C,MAAM,UAAU,kBAAkB,CAAC,KAAgB,EAAE,GAAY;IAC/D,MAAM,MAAM,GAAG,aAAa,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC;IAC5C,IAAI,CAAC,MAAM,CAAC,OAAO;QAAE,OAAO,IAAI,CAAC,uBAAuB,CAAC,CAAC;IAC1D,MAAM,OAAO,GAAG,MAAM,CAAC,IAAI,CAAC;IAE5B,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC;IAC5B,MAAM,SAAS,GAAG,KAAK,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;IACnE,IAAI,CAAC,SAAS;QAAE,OAAO,IAAI,CAAC,wBAAwB,CAAC,CAAC;IACtD,MAAM,QAAQ,GAAG,SAAS,CAAC,IAAI,CAAC;IAEhC,6EAA6E;IAC7E,0DAA0D;IAC1D,IAAI,KAAK,CAAC,SAAS,CAAC,OAAO,CAAC,IAAI,CAAC;QAAE,OAAO,IAAI,CAAC,0BAA0B,CAAC,CAAC;IAC3E,MAAM,QAAQ,GAAG,OAAO,CAAC,QAAQ,CAAC;IAClC,IAAI,QAAQ,IAAI,KAAK,CAAC,SAAS,CAAC,QAAQ,CAAC;QAAE,OAAO,IAAI,CAAC,8BAA8B,CAAC,CAAC;IAEvF,MAAM,EAAE,GAAG,eAAe,CAAC,QAAQ,CAAC,CAAC;IACrC,IAAI,CAAC,EAAE;QAAE,OAAO,IAAI,CAAC,0BAA0B,CAAC,CAAC;IAEjD,IAAI,OAAO,CAAC,QAAQ,KAAK,IAAI,EAAE,CAAC;QAC9B,6EAA6E;QAC7E,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,GAAG,CAAC,EAAE,CAAC,MAAM,CAAC,EAAE,CAAC;YACvC,OAAO,IAAI,CAAC,+BAA+B,CAAC,CAAC;QAC/C,CAAC;IACH,CAAC;SAAM,CAAC;QACN,MAAM,YAAY,GAAG,aAAa,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,CAAC;QAC9D,IAAI,CAAC,KAAK,CAAC,cAAc,CAAC,GAAG,CAAC,YAAY,CAAC,EAAE,CAAC;YAC5C,OAAO,IAAI,CAAC,yBAAyB,CAAC,CAAC;QACzC,CAAC;IACH,CAAC;IAED,MAAM,IAAI,GAAG,OAAO,OAAO,CAAC,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;IACzE,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC,iBAAiB,CAAC,CAAC;IAEtD,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;AACtC,CAAC;AAED;;;;GAIG;AACH,SAAS,aAAa,CACpB,MAA+D,EAC/D,UAAkB;IAElB,IAAI,MAAM,IAAI,OAAO,MAAM,CAAC,OAAO,KAAK,QAAQ,IAAI,MAAM,CAAC,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC9E,OAAO,eAAe,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;IACzC,CAAC;IACD,OAAO,UAAU,CAAC;AACpB,CAAC;AAED,SAAS,IAAI,CAAC,MAAc;IAC1B,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC;AAC/B,CAAC"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { PermissionResolver } from '@moxxy/sdk';
|
|
2
|
+
export interface ImessagePermissionLogger {
|
|
3
|
+
info?(msg: string, meta?: Record<string, unknown>): void;
|
|
4
|
+
}
|
|
5
|
+
/**
|
|
6
|
+
* Build the iMessage channel's autonomous permission resolver.
|
|
7
|
+
*
|
|
8
|
+
* Like Signal and Slack (and unlike Telegram's inline-keyboard prompts), the
|
|
9
|
+
* iMessage channel runs hands-off: the operator declares trust upfront via
|
|
10
|
+
* `channels.imessage.allowedTools`, and any tool NOT in that list is denied.
|
|
11
|
+
* The trust check + `'*'` expansion + audit-on-auto-approve wiring is the shared
|
|
12
|
+
* {@link createAuditedAllowListResolver} from `@moxxy/channel-kit`; this wrapper
|
|
13
|
+
* binds it to the channel logger so every auto-approved call leaves a trail. An
|
|
14
|
+
* empty list denies everything (effectively read-only).
|
|
15
|
+
*
|
|
16
|
+
* Because this resolver decides synchronously (no deferred operator prompt), it
|
|
17
|
+
* has no pending-prompt state, so `stop()` has nothing to `abortAll` — aborting
|
|
18
|
+
* the in-flight turn is enough.
|
|
19
|
+
*/
|
|
20
|
+
export declare function buildImessagePermissionResolver(opts: {
|
|
21
|
+
allowedTools: ReadonlyArray<string>;
|
|
22
|
+
allToolNames: ReadonlyArray<string>;
|
|
23
|
+
logger?: ImessagePermissionLogger;
|
|
24
|
+
}): PermissionResolver;
|
|
25
|
+
//# sourceMappingURL=permission.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"permission.d.ts","sourceRoot":"","sources":["../src/permission.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAErD,MAAM,WAAW,wBAAwB;IACvC,IAAI,CAAC,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;CAC1D;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,+BAA+B,CAAC,IAAI,EAAE;IACpD,YAAY,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;IACpC,YAAY,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;IACpC,MAAM,CAAC,EAAE,wBAAwB,CAAC;CACnC,GAAG,kBAAkB,CAarB"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { createAuditedAllowListResolver } from '@moxxy/channel-kit';
|
|
2
|
+
/**
|
|
3
|
+
* Build the iMessage channel's autonomous permission resolver.
|
|
4
|
+
*
|
|
5
|
+
* Like Signal and Slack (and unlike Telegram's inline-keyboard prompts), the
|
|
6
|
+
* iMessage channel runs hands-off: the operator declares trust upfront via
|
|
7
|
+
* `channels.imessage.allowedTools`, and any tool NOT in that list is denied.
|
|
8
|
+
* The trust check + `'*'` expansion + audit-on-auto-approve wiring is the shared
|
|
9
|
+
* {@link createAuditedAllowListResolver} from `@moxxy/channel-kit`; this wrapper
|
|
10
|
+
* binds it to the channel logger so every auto-approved call leaves a trail. An
|
|
11
|
+
* empty list denies everything (effectively read-only).
|
|
12
|
+
*
|
|
13
|
+
* Because this resolver decides synchronously (no deferred operator prompt), it
|
|
14
|
+
* has no pending-prompt state, so `stop()` has nothing to `abortAll` — aborting
|
|
15
|
+
* the in-flight turn is enough.
|
|
16
|
+
*/
|
|
17
|
+
export function buildImessagePermissionResolver(opts) {
|
|
18
|
+
return createAuditedAllowListResolver({
|
|
19
|
+
name: 'imessage-allow-list',
|
|
20
|
+
allowedTools: opts.allowedTools,
|
|
21
|
+
allToolNames: opts.allToolNames,
|
|
22
|
+
onAutoApprove: (call, { wildcard }) => {
|
|
23
|
+
opts.logger?.info?.('imessage: auto-approved tool call', {
|
|
24
|
+
tool: call.name,
|
|
25
|
+
callId: call.callId,
|
|
26
|
+
wildcard,
|
|
27
|
+
});
|
|
28
|
+
},
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
//# sourceMappingURL=permission.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"permission.js","sourceRoot":"","sources":["../src/permission.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,8BAA8B,EAAE,MAAM,oBAAoB,CAAC;AAOpE;;;;;;;;;;;;;;GAcG;AACH,MAAM,UAAU,+BAA+B,CAAC,IAI/C;IACC,OAAO,8BAA8B,CAAC;QACpC,IAAI,EAAE,qBAAqB;QAC3B,YAAY,EAAE,IAAI,CAAC,YAAY;QAC/B,YAAY,EAAE,IAAI,CAAC,YAAY;QAC/B,aAAa,EAAE,CAAC,IAAI,EAAE,EAAE,QAAQ,EAAE,EAAE,EAAE;YACpC,IAAI,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,mCAAmC,EAAE;gBACvD,IAAI,EAAE,IAAI,CAAC,IAAI;gBACf,MAAM,EAAE,IAAI,CAAC,MAAM;gBACnB,QAAQ;aACT,CAAC,CAAC;QACL,CAAC;KACF,CAAC,CAAC;AACL,CAAC"}
|
package/dist/schema.d.ts
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
/**
|
|
3
|
+
* Zod validation for the BlueBubbles socket.io `new-message` payload — a
|
|
4
|
+
* serialized Message object. Every inbound message is validated + size-capped
|
|
5
|
+
* BEFORE any field reaches the session (channel invariant A8). Only the fields
|
|
6
|
+
* the channel consumes are typed; unknown extras pass through untouched so a
|
|
7
|
+
* newer BlueBubbles server doesn't fail validation, but nothing un-modeled is
|
|
8
|
+
* ever read.
|
|
9
|
+
*/
|
|
10
|
+
/** Cap on inbound prompt text we will forward to the model. */
|
|
11
|
+
export declare const MAX_INBOUND_TEXT_CHARS = 16000;
|
|
12
|
+
/** A single participant handle on a chat / message (`{ address, ... }`). */
|
|
13
|
+
export declare const handleSchema: z.ZodObject<{
|
|
14
|
+
address: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
15
|
+
}, "passthrough", z.ZodTypeAny, z.objectOutputType<{
|
|
16
|
+
address: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
17
|
+
}, z.ZodTypeAny, "passthrough">, z.objectInputType<{
|
|
18
|
+
address: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
19
|
+
}, z.ZodTypeAny, "passthrough">>;
|
|
20
|
+
/** A chat the message belongs to. `guid` is `SERVICE;TYPE;IDENTIFIER`. */
|
|
21
|
+
export declare const chatSchema: z.ZodObject<{
|
|
22
|
+
guid: z.ZodString;
|
|
23
|
+
}, "passthrough", z.ZodTypeAny, z.objectOutputType<{
|
|
24
|
+
guid: z.ZodString;
|
|
25
|
+
}, z.ZodTypeAny, "passthrough">, z.objectInputType<{
|
|
26
|
+
guid: z.ZodString;
|
|
27
|
+
}, z.ZodTypeAny, "passthrough">>;
|
|
28
|
+
/**
|
|
29
|
+
* The serialized Message. `guid` is the stable per-message id (used for the
|
|
30
|
+
* anti-echo drop); `tempGuid` echoes back the value the sender chose for an
|
|
31
|
+
* apple-script send, so our own replies can be recognised even before their
|
|
32
|
+
* permanent guid is known.
|
|
33
|
+
*/
|
|
34
|
+
export declare const messageSchema: z.ZodObject<{
|
|
35
|
+
guid: z.ZodString;
|
|
36
|
+
tempGuid: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
37
|
+
text: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
38
|
+
isFromMe: z.ZodOptional<z.ZodNullable<z.ZodBoolean>>;
|
|
39
|
+
handle: z.ZodOptional<z.ZodNullable<z.ZodObject<{
|
|
40
|
+
address: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
41
|
+
}, "passthrough", z.ZodTypeAny, z.objectOutputType<{
|
|
42
|
+
address: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
43
|
+
}, z.ZodTypeAny, "passthrough">, z.objectInputType<{
|
|
44
|
+
address: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
45
|
+
}, z.ZodTypeAny, "passthrough">>>>;
|
|
46
|
+
chats: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
47
|
+
guid: z.ZodString;
|
|
48
|
+
}, "passthrough", z.ZodTypeAny, z.objectOutputType<{
|
|
49
|
+
guid: z.ZodString;
|
|
50
|
+
}, z.ZodTypeAny, "passthrough">, z.objectInputType<{
|
|
51
|
+
guid: z.ZodString;
|
|
52
|
+
}, z.ZodTypeAny, "passthrough">>, "many">>;
|
|
53
|
+
dateCreated: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
54
|
+
}, "passthrough", z.ZodTypeAny, z.objectOutputType<{
|
|
55
|
+
guid: z.ZodString;
|
|
56
|
+
tempGuid: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
57
|
+
text: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
58
|
+
isFromMe: z.ZodOptional<z.ZodNullable<z.ZodBoolean>>;
|
|
59
|
+
handle: z.ZodOptional<z.ZodNullable<z.ZodObject<{
|
|
60
|
+
address: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
61
|
+
}, "passthrough", z.ZodTypeAny, z.objectOutputType<{
|
|
62
|
+
address: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
63
|
+
}, z.ZodTypeAny, "passthrough">, z.objectInputType<{
|
|
64
|
+
address: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
65
|
+
}, z.ZodTypeAny, "passthrough">>>>;
|
|
66
|
+
chats: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
67
|
+
guid: z.ZodString;
|
|
68
|
+
}, "passthrough", z.ZodTypeAny, z.objectOutputType<{
|
|
69
|
+
guid: z.ZodString;
|
|
70
|
+
}, z.ZodTypeAny, "passthrough">, z.objectInputType<{
|
|
71
|
+
guid: z.ZodString;
|
|
72
|
+
}, z.ZodTypeAny, "passthrough">>, "many">>;
|
|
73
|
+
dateCreated: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
74
|
+
}, z.ZodTypeAny, "passthrough">, z.objectInputType<{
|
|
75
|
+
guid: z.ZodString;
|
|
76
|
+
tempGuid: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
77
|
+
text: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
78
|
+
isFromMe: z.ZodOptional<z.ZodNullable<z.ZodBoolean>>;
|
|
79
|
+
handle: z.ZodOptional<z.ZodNullable<z.ZodObject<{
|
|
80
|
+
address: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
81
|
+
}, "passthrough", z.ZodTypeAny, z.objectOutputType<{
|
|
82
|
+
address: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
83
|
+
}, z.ZodTypeAny, "passthrough">, z.objectInputType<{
|
|
84
|
+
address: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
85
|
+
}, z.ZodTypeAny, "passthrough">>>>;
|
|
86
|
+
chats: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
87
|
+
guid: z.ZodString;
|
|
88
|
+
}, "passthrough", z.ZodTypeAny, z.objectOutputType<{
|
|
89
|
+
guid: z.ZodString;
|
|
90
|
+
}, z.ZodTypeAny, "passthrough">, z.objectInputType<{
|
|
91
|
+
guid: z.ZodString;
|
|
92
|
+
}, z.ZodTypeAny, "passthrough">>, "many">>;
|
|
93
|
+
dateCreated: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
94
|
+
}, z.ZodTypeAny, "passthrough">>;
|
|
95
|
+
export type ImessageMessage = z.infer<typeof messageSchema>;
|
|
96
|
+
//# sourceMappingURL=schema.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../src/schema.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB;;;;;;;GAOG;AAEH,+DAA+D;AAC/D,eAAO,MAAM,sBAAsB,QAAS,CAAC;AAE7C,4EAA4E;AAC5E,eAAO,MAAM,YAAY;;;;;;gCAIT,CAAC;AAEjB,0EAA0E;AAC1E,eAAO,MAAM,UAAU;;;;;;gCAIP,CAAC;AAEjB;;;;;GAKG;AACH,eAAO,MAAM,aAAa;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;gCAUV,CAAC;AAEjB,MAAM,MAAM,eAAe,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,aAAa,CAAC,CAAC"}
|
package/dist/schema.js
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
/**
|
|
3
|
+
* Zod validation for the BlueBubbles socket.io `new-message` payload — a
|
|
4
|
+
* serialized Message object. Every inbound message is validated + size-capped
|
|
5
|
+
* BEFORE any field reaches the session (channel invariant A8). Only the fields
|
|
6
|
+
* the channel consumes are typed; unknown extras pass through untouched so a
|
|
7
|
+
* newer BlueBubbles server doesn't fail validation, but nothing un-modeled is
|
|
8
|
+
* ever read.
|
|
9
|
+
*/
|
|
10
|
+
/** Cap on inbound prompt text we will forward to the model. */
|
|
11
|
+
export const MAX_INBOUND_TEXT_CHARS = 16_000;
|
|
12
|
+
/** A single participant handle on a chat / message (`{ address, ... }`). */
|
|
13
|
+
export const handleSchema = z
|
|
14
|
+
.object({
|
|
15
|
+
address: z.string().min(1).max(256).nullable().optional(),
|
|
16
|
+
})
|
|
17
|
+
.passthrough();
|
|
18
|
+
/** A chat the message belongs to. `guid` is `SERVICE;TYPE;IDENTIFIER`. */
|
|
19
|
+
export const chatSchema = z
|
|
20
|
+
.object({
|
|
21
|
+
guid: z.string().min(1).max(256),
|
|
22
|
+
})
|
|
23
|
+
.passthrough();
|
|
24
|
+
/**
|
|
25
|
+
* The serialized Message. `guid` is the stable per-message id (used for the
|
|
26
|
+
* anti-echo drop); `tempGuid` echoes back the value the sender chose for an
|
|
27
|
+
* apple-script send, so our own replies can be recognised even before their
|
|
28
|
+
* permanent guid is known.
|
|
29
|
+
*/
|
|
30
|
+
export const messageSchema = z
|
|
31
|
+
.object({
|
|
32
|
+
guid: z.string().min(1).max(256),
|
|
33
|
+
tempGuid: z.string().min(1).max(256).nullable().optional(),
|
|
34
|
+
text: z.string().max(MAX_INBOUND_TEXT_CHARS).nullable().optional(),
|
|
35
|
+
isFromMe: z.boolean().nullable().optional(),
|
|
36
|
+
handle: handleSchema.nullable().optional(),
|
|
37
|
+
chats: z.array(chatSchema).max(16).optional(),
|
|
38
|
+
dateCreated: z.number().nullable().optional(),
|
|
39
|
+
})
|
|
40
|
+
.passthrough();
|
|
41
|
+
//# sourceMappingURL=schema.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"schema.js","sourceRoot":"","sources":["../src/schema.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB;;;;;;;GAOG;AAEH,+DAA+D;AAC/D,MAAM,CAAC,MAAM,sBAAsB,GAAG,MAAM,CAAC;AAE7C,4EAA4E;AAC5E,MAAM,CAAC,MAAM,YAAY,GAAG,CAAC;KAC1B,MAAM,CAAC;IACN,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,QAAQ,EAAE,CAAC,QAAQ,EAAE;CAC1D,CAAC;KACD,WAAW,EAAE,CAAC;AAEjB,0EAA0E;AAC1E,MAAM,CAAC,MAAM,UAAU,GAAG,CAAC;KACxB,MAAM,CAAC;IACN,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC;CACjC,CAAC;KACD,WAAW,EAAE,CAAC;AAEjB;;;;;GAKG;AACH,MAAM,CAAC,MAAM,aAAa,GAAG,CAAC;KAC3B,MAAM,CAAC;IACN,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC;IAChC,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,QAAQ,EAAE,CAAC,QAAQ,EAAE;IAC1D,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,sBAAsB,CAAC,CAAC,QAAQ,EAAE,CAAC,QAAQ,EAAE;IAClE,QAAQ,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,EAAE;IAC3C,MAAM,EAAE,YAAY,CAAC,QAAQ,EAAE,CAAC,QAAQ,EAAE;IAC1C,KAAK,EAAE,CAAC,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,QAAQ,EAAE;IAC7C,WAAW,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,EAAE;CAC9C,CAAC;KACD,WAAW,EAAE,CAAC"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { ChannelSubcommandContext } from '@moxxy/sdk';
|
|
2
|
+
/**
|
|
3
|
+
* Interactive iMessage (BlueBubbles) setup wizard (the channel's
|
|
4
|
+
* `interactiveCommand`).
|
|
5
|
+
*
|
|
6
|
+
* Walks the operator through:
|
|
7
|
+
* 1. the BlueBubbles server URL + password (stored in the vault),
|
|
8
|
+
* 2. a best-effort reachability check against that server,
|
|
9
|
+
* 3. the handle allow-list — who besides yourself may talk to the agent,
|
|
10
|
+
* 4. your own handle(s), so texting your own "self-chat" reaches moxxy,
|
|
11
|
+
* 5. the autonomous tool allow-list,
|
|
12
|
+
* then starts the channel.
|
|
13
|
+
*/
|
|
14
|
+
export declare function runImessageWizard(ctx: ChannelSubcommandContext): Promise<number>;
|
|
15
|
+
//# sourceMappingURL=setup-wizard.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"setup-wizard.d.ts","sourceRoot":"","sources":["../src/setup-wizard.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,YAAY,CAAC;AAiB3D;;;;;;;;;;;GAWG;AACH,wBAAsB,iBAAiB,CAAC,GAAG,EAAE,wBAAwB,GAAG,OAAO,CAAC,MAAM,CAAC,CAiHtF"}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import { cancel, intro, isCancel, log, note, outro, password, text } from '@clack/prompts';
|
|
2
|
+
import { IMESSAGE_ALLOWED_HANDLES_KEY, IMESSAGE_OWNER_HANDLES_KEY, IMESSAGE_SERVER_PASSWORD_KEY, IMESSAGE_SERVER_URL_KEY, isHandle, normalizeHandle, parseHandleList, } from './keys.js';
|
|
3
|
+
import { BlueBubblesClient } from './bluebubbles-client.js';
|
|
4
|
+
const ANSI = process.stdout.isTTY && !process.env.NO_COLOR;
|
|
5
|
+
const bold = (s) => (ANSI ? `\x1b[1m${s}\x1b[22m` : s);
|
|
6
|
+
const dim = (s) => (ANSI ? `\x1b[2m${s}\x1b[22m` : s);
|
|
7
|
+
/**
|
|
8
|
+
* Interactive iMessage (BlueBubbles) setup wizard (the channel's
|
|
9
|
+
* `interactiveCommand`).
|
|
10
|
+
*
|
|
11
|
+
* Walks the operator through:
|
|
12
|
+
* 1. the BlueBubbles server URL + password (stored in the vault),
|
|
13
|
+
* 2. a best-effort reachability check against that server,
|
|
14
|
+
* 3. the handle allow-list — who besides yourself may talk to the agent,
|
|
15
|
+
* 4. your own handle(s), so texting your own "self-chat" reaches moxxy,
|
|
16
|
+
* 5. the autonomous tool allow-list,
|
|
17
|
+
* then starts the channel.
|
|
18
|
+
*/
|
|
19
|
+
export async function runImessageWizard(ctx) {
|
|
20
|
+
const vault = ctx.deps.vault;
|
|
21
|
+
intro(bold('moxxy imessage setup'));
|
|
22
|
+
note('moxxy talks to iMessage through a BlueBubbles server running on THIS Mac.\n' +
|
|
23
|
+
'Install it from https://bluebubbles.app, sign in to Messages, and set a\n' +
|
|
24
|
+
'server password. This channel uses the stock apple-script send method — no\n' +
|
|
25
|
+
'SIP changes or Private API needed. v1 handles 1:1 text chats only.', 'how the iMessage channel works');
|
|
26
|
+
const existingUrl = await vault.get(IMESSAGE_SERVER_URL_KEY);
|
|
27
|
+
const serverUrl = await text({
|
|
28
|
+
message: 'BlueBubbles server URL',
|
|
29
|
+
placeholder: 'http://localhost:1234',
|
|
30
|
+
initialValue: existingUrl ?? 'http://localhost:1234',
|
|
31
|
+
validate: (v) => {
|
|
32
|
+
if (!v || !v.trim())
|
|
33
|
+
return 'required';
|
|
34
|
+
try {
|
|
35
|
+
// eslint-disable-next-line no-new
|
|
36
|
+
new URL(v.trim());
|
|
37
|
+
return undefined;
|
|
38
|
+
}
|
|
39
|
+
catch {
|
|
40
|
+
return 'expected a URL, e.g. http://localhost:1234';
|
|
41
|
+
}
|
|
42
|
+
},
|
|
43
|
+
});
|
|
44
|
+
if (isCancel(serverUrl)) {
|
|
45
|
+
cancel('cancelled.');
|
|
46
|
+
return 0;
|
|
47
|
+
}
|
|
48
|
+
const url = String(serverUrl).trim();
|
|
49
|
+
const serverPassword = await password({
|
|
50
|
+
message: 'BlueBubbles server password',
|
|
51
|
+
validate: (v) => (v && v.length > 0 ? undefined : 'required'),
|
|
52
|
+
});
|
|
53
|
+
if (isCancel(serverPassword)) {
|
|
54
|
+
cancel('cancelled.');
|
|
55
|
+
return 0;
|
|
56
|
+
}
|
|
57
|
+
const pass = String(serverPassword);
|
|
58
|
+
await vault.set(IMESSAGE_SERVER_URL_KEY, url, ['imessage']);
|
|
59
|
+
await vault.set(IMESSAGE_SERVER_PASSWORD_KEY, pass, ['imessage']);
|
|
60
|
+
log.success('Stored the BlueBubbles server URL + password in the vault.');
|
|
61
|
+
// Best-effort reachability probe — the ping only uses fetch, no socket.
|
|
62
|
+
try {
|
|
63
|
+
await new BlueBubblesClient({ serverUrl: url, password: pass }).ping();
|
|
64
|
+
log.success('Reached the BlueBubbles server.');
|
|
65
|
+
}
|
|
66
|
+
catch (err) {
|
|
67
|
+
log.warn(`Could not reach the server yet: ${err instanceof Error ? err.message : String(err)}\n` +
|
|
68
|
+
'You can still finish setup — the channel will retry when it starts.');
|
|
69
|
+
}
|
|
70
|
+
const allowAnswer = await text({
|
|
71
|
+
message: 'Handles allowed to talk to moxxy (comma-separated E.164 numbers / Apple-ID emails)',
|
|
72
|
+
placeholder: '+15551234567, friend@icloud.com',
|
|
73
|
+
initialValue: parseHandleList(await vault.get(IMESSAGE_ALLOWED_HANDLES_KEY)).join(', '),
|
|
74
|
+
});
|
|
75
|
+
if (isCancel(allowAnswer)) {
|
|
76
|
+
cancel('cancelled.');
|
|
77
|
+
return 0;
|
|
78
|
+
}
|
|
79
|
+
const allowed = parseHandleInput(String(allowAnswer));
|
|
80
|
+
await vault.set(IMESSAGE_ALLOWED_HANDLES_KEY, JSON.stringify(allowed), ['imessage']);
|
|
81
|
+
log.success(allowed.length > 0
|
|
82
|
+
? `Allow-list saved (${allowed.length} handle(s)).`
|
|
83
|
+
: 'Allow-list cleared (no external senders allowed).');
|
|
84
|
+
note('To talk to moxxy from your OWN Apple devices (texting your "self-chat"),\n' +
|
|
85
|
+
'add your own iMessage handle(s) below. Leave blank to keep self-chat off —\n' +
|
|
86
|
+
"your outbound messages to other people are never treated as prompts.", 'your own handles (self-chat)');
|
|
87
|
+
const ownerAnswer = await text({
|
|
88
|
+
message: 'Your own handle(s) (comma-separated; blank to skip)',
|
|
89
|
+
placeholder: '+15559876543, you@icloud.com',
|
|
90
|
+
initialValue: parseHandleList(await vault.get(IMESSAGE_OWNER_HANDLES_KEY)).join(', '),
|
|
91
|
+
});
|
|
92
|
+
if (isCancel(ownerAnswer)) {
|
|
93
|
+
cancel('cancelled.');
|
|
94
|
+
return 0;
|
|
95
|
+
}
|
|
96
|
+
const owner = parseHandleInput(String(ownerAnswer));
|
|
97
|
+
await vault.set(IMESSAGE_OWNER_HANDLES_KEY, JSON.stringify(owner), ['imessage']);
|
|
98
|
+
log.success(owner.length > 0 ? `Self-chat enabled for ${owner.length} handle(s).` : 'Self-chat left off.');
|
|
99
|
+
const allowToolsAnswer = await text({
|
|
100
|
+
message: 'Autonomous tool allow-list (comma-separated; "*" = all, blank = read-only)',
|
|
101
|
+
placeholder: 'Read, Grep, Glob',
|
|
102
|
+
});
|
|
103
|
+
if (isCancel(allowToolsAnswer)) {
|
|
104
|
+
cancel('cancelled.');
|
|
105
|
+
return 0;
|
|
106
|
+
}
|
|
107
|
+
const allowedTools = String(allowToolsAnswer)
|
|
108
|
+
.split(',')
|
|
109
|
+
.map((s) => s.trim())
|
|
110
|
+
.filter(Boolean);
|
|
111
|
+
log.info('Starting the channel. Press Ctrl+C to stop.');
|
|
112
|
+
outro(dim('handing off to the channel…'));
|
|
113
|
+
return ctx.startChannel({ allowedTools });
|
|
114
|
+
}
|
|
115
|
+
/** Split comma/whitespace-separated handle input, normalize, drop non-handles. */
|
|
116
|
+
function parseHandleInput(raw) {
|
|
117
|
+
const out = new Set();
|
|
118
|
+
for (const token of raw.split(/[,\s]+/)) {
|
|
119
|
+
if (!token)
|
|
120
|
+
continue;
|
|
121
|
+
const handle = normalizeHandle(token);
|
|
122
|
+
if (isHandle(handle))
|
|
123
|
+
out.add(handle);
|
|
124
|
+
}
|
|
125
|
+
return [...out];
|
|
126
|
+
}
|
|
127
|
+
//# sourceMappingURL=setup-wizard.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"setup-wizard.js","sourceRoot":"","sources":["../src/setup-wizard.ts"],"names":[],"mappings":"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"}
|