diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index 033f2c7e9..c765b12d9 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -16,6 +16,7 @@ Thanks to everyone who has contributed to NanoClaw! - [flobo3](https://github.com/flobo3) — Flo - [edwinwzhe](https://github.com/edwinwzhe) — Edwin He - [scottgl9](https://github.com/scottgl9) — Scott Glover +- [ingyukoh](https://github.com/ingyukoh) — Ingyu Koh - [cschmidt](https://github.com/cschmidt) — Carl Schmidt - [leonalfredbot-ship-it](https://github.com/leonalfredbot-ship-it) — Alfred-the-buttler - [moktamd](https://github.com/moktamd) diff --git a/package.json b/package.json index 446bc1bec..f305bec3d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "nanoclaw", - "version": "2.0.27", + "version": "2.0.28", "description": "Personal Claude assistant. Lightweight, secure, customizable.", "type": "module", "packageManager": "pnpm@10.33.0", diff --git a/setup/auto.ts b/setup/auto.ts index 009532119..b57672ffb 100644 --- a/setup/auto.ts +++ b/setup/auto.ts @@ -60,7 +60,7 @@ import { isValidTimezone } from '../src/timezone.js'; const CLI_AGENT_NAME = 'Terminal Agent'; const RUN_START = Date.now(); -type ChannelChoice = 'telegram' | 'discord' | 'whatsapp' | 'signal' | 'teams' | 'slack' | 'imessage' | 'skip'; +type ChannelChoice = 'telegram' | 'discord' | 'whatsapp' | 'signal' | 'teams' | 'slack' | 'imessage' | 'other' | 'skip'; async function main(): Promise { // Make sure ~/.local/bin is on PATH for every child process we spawn. @@ -441,7 +441,7 @@ async function main(): Promise { if (!skip.has('channel')) { channelChoice = await askChannelChoice(); - if (channelChoice !== 'skip') { + if (channelChoice !== 'skip' && channelChoice !== 'other') { await resolveDisplayName(); } if (channelChoice === 'telegram') { @@ -458,6 +458,8 @@ async function main(): Promise { await runSlackChannel(displayName!); } else if (channelChoice === 'imessage') { await runIMessageChannel(displayName!); + } else if (channelChoice === 'other') { + await askOtherChannelName(); } else { p.log.info( brandBody( @@ -1076,6 +1078,7 @@ async function askChannelChoice(): Promise { hint: 'needs public URL', }, { value: 'teams', label: 'Yes, connect Microsoft Teams', hint: 'complex setup' }, + { value: 'other', label: 'Other…', hint: 'install via /add- after setup' }, { value: 'skip', label: 'Skip for now', hint: "I'll just use the terminal" }, ], }), @@ -1085,6 +1088,26 @@ async function askChannelChoice(): Promise { return choice; } +async function askOtherChannelName(): Promise { + const answer = ensureAnswer( + await p.text({ + message: 'Which channel would you like to install?', + placeholder: 'e.g. matrix, github, linear, webex', + }), + ); + const name = (answer as string).trim().toLowerCase().replace(/^\/?(add-)?/, ''); + setupLog.userInput('other_channel', name); + phEmit('channel_other_named', { channel: name }); + p.log.info( + brandBody( + wrapForGutter( + `No bash installer for ${k.bold(name)} — open Claude Code after setup and run ${k.bold(`/add-${name}`)} to install it.`, + 4, + ), + ), + ); +} + // ─── interactive / env helpers ───────────────────────────────────────── function ensureLocalBinOnPath(): void { diff --git a/src/channels/chat-sdk-bridge.ts b/src/channels/chat-sdk-bridge.ts index 18ab2cbf8..52c92ba64 100644 --- a/src/channels/chat-sdk-bridge.ts +++ b/src/channels/chat-sdk-bridge.ts @@ -253,12 +253,12 @@ export function createChatSdkBridge(config: ChatSdkBridgeConfig): ChannelAdapter // Chat SDK dispatch (handling-events.mdx §"Handler dispatch order") is // exclusive: subscribed → onSubscribedMessage; unsubscribed+mention → // onNewMention; unsubscribed+pattern-match → onNewMessage. Registering - // with `/./` lets the router see every plain message on every - // unsubscribed thread the bot can see. The router short-circuits via + // with `/[\s\S]*/` lets the router see every plain message (including + // media-only messages with empty text) on every unsubscribed thread the // getMessagingGroupWithAgentCount (~1 DB read) for unwired channels, // so forwarding every one is cheap enough to not need a bridge-side // flood gate. - chat.onNewMessage(/./, async (thread, message) => { + chat.onNewMessage(/[\s\S]*/, async (thread, message) => { const channelId = adapter.channelIdFromThreadId(thread.id); await setupConfig.onInbound(channelId, thread.id, await messageToInbound(message, false, true)); });