diff --git a/extensions/imessage/src/setup-core.ts b/extensions/imessage/src/setup-core.ts index e14fbef47bd..4304a482ad6 100644 --- a/extensions/imessage/src/setup-core.ts +++ b/extensions/imessage/src/setup-core.ts @@ -155,9 +155,18 @@ export const imessageSetupAdapter: ChannelSetupAdapter = { }, }; -export function createIMessageSetupWizardProxy( - loadWizard: () => Promise<{ imessageSetupWizard: ChannelSetupWizard }>, -) { +type IMessageSetupWizardHandlers = { + resolveStatusLines: NonNullable["resolveStatusLines"]; + resolveSelectionHint: NonNullable["resolveSelectionHint"]; + resolveQuickstartScore: NonNullable["resolveQuickstartScore"]; + shouldPromptCliPath: NonNullable< + NonNullable[number]["shouldPrompt"] + >; +}; + +export function createIMessageSetupWizardBase( + handlers: IMessageSetupWizardHandlers, +): ChannelSetupWizard { const imessageDmPolicy: ChannelSetupDmPolicy = { label: "iMessage", channel, @@ -193,12 +202,9 @@ export function createIMessageSetupWizardProxy( account.config.region, ); }), - resolveStatusLines: async (params) => - (await loadWizard()).imessageSetupWizard.status.resolveStatusLines?.(params) ?? [], - resolveSelectionHint: async (params) => - await (await loadWizard()).imessageSetupWizard.status.resolveSelectionHint?.(params), - resolveQuickstartScore: async (params) => - await (await loadWizard()).imessageSetupWizard.status.resolveQuickstartScore?.(params), + resolveStatusLines: handlers.resolveStatusLines, + resolveSelectionHint: handlers.resolveSelectionHint, + resolveQuickstartScore: handlers.resolveQuickstartScore, }, credentials: [], textInputs: [ @@ -209,12 +215,7 @@ export function createIMessageSetupWizardProxy( resolveIMessageAccount({ cfg, accountId }).config.cliPath ?? "imsg", currentValue: ({ cfg, accountId }) => resolveIMessageAccount({ cfg, accountId }).config.cliPath ?? "imsg", - shouldPrompt: async (params) => { - const input = (await loadWizard()).imessageSetupWizard.textInputs?.find( - (entry) => entry.inputKey === "cliPath", - ); - return (await input?.shouldPrompt?.(params)) ?? false; - }, + shouldPrompt: handlers.shouldPromptCliPath, confirmCurrentValue: false, applyCurrentValue: true, helpTitle: "iMessage", @@ -235,3 +236,22 @@ export function createIMessageSetupWizardProxy( disable: (cfg: OpenClawConfig) => setSetupChannelEnabled(cfg, channel, false), } satisfies ChannelSetupWizard; } + +export function createIMessageSetupWizardProxy( + loadWizard: () => Promise<{ imessageSetupWizard: ChannelSetupWizard }>, +) { + return createIMessageSetupWizardBase({ + resolveStatusLines: async (params) => + (await loadWizard()).imessageSetupWizard.status.resolveStatusLines?.(params) ?? [], + resolveSelectionHint: async (params) => + await (await loadWizard()).imessageSetupWizard.status.resolveSelectionHint?.(params), + resolveQuickstartScore: async (params) => + await (await loadWizard()).imessageSetupWizard.status.resolveQuickstartScore?.(params), + shouldPromptCliPath: async (params) => { + const input = (await loadWizard()).imessageSetupWizard.textInputs?.find( + (entry) => entry.inputKey === "cliPath", + ); + return (await input?.shouldPrompt?.(params)) ?? false; + }, + }); +} diff --git a/extensions/imessage/src/setup-surface.ts b/extensions/imessage/src/setup-surface.ts index b8487dff54d..c1158960cec 100644 --- a/extensions/imessage/src/setup-surface.ts +++ b/extensions/imessage/src/setup-surface.ts @@ -1,137 +1,23 @@ -import { - DEFAULT_ACCOUNT_ID, - detectBinary, - formatDocsLink, - type OpenClawConfig, - parseSetupEntriesAllowingWildcard, - promptParsedAllowFromForScopedChannel, - setChannelDmPolicyWithAllowFrom, - setSetupChannelEnabled, - type WizardPrompter, -} from "../../../src/plugin-sdk-internal/setup.js"; -import type { - ChannelSetupDmPolicy, - ChannelSetupWizard, -} from "../../../src/plugin-sdk-internal/setup.js"; -import { - listIMessageAccountIds, - resolveDefaultIMessageAccountId, - resolveIMessageAccount, -} from "./accounts.js"; -import { imessageSetupAdapter, parseIMessageAllowFromEntries } from "./setup-core.js"; +import { detectBinary } from "../../../src/plugin-sdk-internal/setup.js"; +import { createIMessageSetupWizardBase, imessageSetupAdapter } from "./setup-core.js"; -const channel = "imessage" as const; - -async function promptIMessageAllowFrom(params: { - cfg: OpenClawConfig; - prompter: WizardPrompter; - accountId?: string; -}): Promise { - return promptParsedAllowFromForScopedChannel({ - cfg: params.cfg, - channel, - accountId: params.accountId, - defaultAccountId: resolveDefaultIMessageAccountId(params.cfg), - prompter: params.prompter, - noteTitle: "iMessage allowlist", - noteLines: [ - "Allowlist iMessage DMs by handle or chat target.", - "Examples:", - "- +15555550123", - "- user@example.com", - "- chat_id:123", - "- chat_guid:... or chat_identifier:...", - "Multiple entries: comma-separated.", - `Docs: ${formatDocsLink("/imessage", "imessage")}`, - ], - message: "iMessage allowFrom (handle or chat_id)", - placeholder: "+15555550123, user@example.com, chat_id:123", - parseEntries: parseIMessageAllowFromEntries, - getExistingAllowFrom: ({ cfg, accountId }) => - resolveIMessageAccount({ cfg, accountId }).config.allowFrom ?? [], - }); -} - -const imessageDmPolicy: ChannelSetupDmPolicy = { - label: "iMessage", - channel, - policyKey: "channels.imessage.dmPolicy", - allowFromKey: "channels.imessage.allowFrom", - getCurrent: (cfg) => cfg.channels?.imessage?.dmPolicy ?? "pairing", - setPolicy: (cfg, policy) => - setChannelDmPolicyWithAllowFrom({ - cfg, - channel, - dmPolicy: policy, - }), - promptAllowFrom: promptIMessageAllowFrom, -}; - -export const imessageSetupWizard: ChannelSetupWizard = { - channel, - status: { - configuredLabel: "configured", - unconfiguredLabel: "needs setup", - configuredHint: "imsg found", - unconfiguredHint: "imsg missing", - configuredScore: 1, - unconfiguredScore: 0, - resolveConfigured: ({ cfg }) => - listIMessageAccountIds(cfg).some((accountId) => { - const account = resolveIMessageAccount({ cfg, accountId }); - return Boolean( - account.config.cliPath || - account.config.dbPath || - account.config.allowFrom || - account.config.service || - account.config.region, - ); - }), - resolveStatusLines: async ({ cfg, configured }) => { - const cliPath = cfg.channels?.imessage?.cliPath ?? "imsg"; - const cliDetected = await detectBinary(cliPath); - return [ - `iMessage: ${configured ? "configured" : "needs setup"}`, - `imsg: ${cliDetected ? "found" : "missing"} (${cliPath})`, - ]; - }, - resolveSelectionHint: async ({ cfg }) => { - const cliPath = cfg.channels?.imessage?.cliPath ?? "imsg"; - return (await detectBinary(cliPath)) ? "imsg found" : "imsg missing"; - }, - resolveQuickstartScore: async ({ cfg }) => { - const cliPath = cfg.channels?.imessage?.cliPath ?? "imsg"; - return (await detectBinary(cliPath)) ? 1 : 0; - }, +export const imessageSetupWizard = createIMessageSetupWizardBase({ + resolveStatusLines: async ({ cfg, configured }) => { + const cliPath = cfg.channels?.imessage?.cliPath ?? "imsg"; + const cliDetected = await detectBinary(cliPath); + return [ + `iMessage: ${configured ? "configured" : "needs setup"}`, + `imsg: ${cliDetected ? "found" : "missing"} (${cliPath})`, + ]; }, - credentials: [], - textInputs: [ - { - inputKey: "cliPath", - message: "imsg CLI path", - initialValue: ({ cfg, accountId }) => - resolveIMessageAccount({ cfg, accountId }).config.cliPath ?? "imsg", - currentValue: ({ cfg, accountId }) => - resolveIMessageAccount({ cfg, accountId }).config.cliPath ?? "imsg", - shouldPrompt: async ({ currentValue }) => !(await detectBinary(currentValue ?? "imsg")), - confirmCurrentValue: false, - applyCurrentValue: true, - helpTitle: "iMessage", - helpLines: ["imsg CLI path required to enable iMessage."], - }, - ], - completionNote: { - title: "iMessage next steps", - lines: [ - "This is still a work in progress.", - "Ensure OpenClaw has Full Disk Access to Messages DB.", - "Grant Automation permission for Messages when prompted.", - "List chats with: imsg chats --limit 20", - `Docs: ${formatDocsLink("/imessage", "imessage")}`, - ], + resolveSelectionHint: async ({ cfg }) => { + const cliPath = cfg.channels?.imessage?.cliPath ?? "imsg"; + return (await detectBinary(cliPath)) ? "imsg found" : "imsg missing"; }, - dmPolicy: imessageDmPolicy, - disable: (cfg) => setSetupChannelEnabled(cfg, channel, false), -}; - -export { imessageSetupAdapter, parseIMessageAllowFromEntries }; + resolveQuickstartScore: async ({ cfg }) => { + const cliPath = cfg.channels?.imessage?.cliPath ?? "imsg"; + return (await detectBinary(cliPath)) ? 1 : 0; + }, + shouldPromptCliPath: async ({ currentValue }) => !(await detectBinary(currentValue ?? "imsg")), +}); +export { imessageSetupAdapter, parseIMessageAllowFromEntries } from "./setup-core.js";