Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
8eed3ac
refactor: implement pluggable channel architecture and dynamic setup
gabi-simons Feb 25, 2026
62013b0
style: fix formatting in config.ts to pass CI
gabi-simons Feb 25, 2026
44c3a55
refactor(setup): full platform-agnostic transformation
gabi-simons Feb 26, 2026
b090413
feat(skills): transform WhatsApp into a pluggable skill
gabi-simons Feb 26, 2026
df4f37c
refactor(skills): move WhatsApp from core to pluggable skill
gabi-simons Feb 26, 2026
1039594
refactor(skills): move setup/whatsapp-auth.ts into WhatsApp skill
gabi-simons Feb 26, 2026
18a61f8
refactor(skills): convert Telegram skill to pluggable channel pattern
gabi-simons Feb 27, 2026
ef0bb10
fix(skills): fix add-whatsapp build failure and improve auth flow
gabi-simons Feb 27, 2026
aad4bab
fix: remove hardcoded WhatsApp default and stale Baileys comment
gabi-simons Feb 27, 2026
ff024ad
refactor(skills): convert Discord, Slack, Gmail skills to pluggable c…
gabi-simons Feb 27, 2026
89f0a0c
refactor: use getRegisteredChannels instead of ENABLED_CHANNELS
gabi-simons Feb 27, 2026
0813b92
docs: add breaking change notice and whatsapp migration instructions
gabi-simons Feb 27, 2026
5a78df4
docs: rewrite READMEs for pluggable multi-channel architecture
gabi-simons Feb 28, 2026
15cfe44
docs: move pluggable channel architecture details to SPEC.md
gabi-simons Feb 28, 2026
8d60b96
docs: move upgrading notice to CHANGELOG, add changelog link
gabi-simons Feb 28, 2026
b87d4de
docs: expand CHANGELOG with full PR #500 changes
gabi-simons Feb 28, 2026
47d3ef1
chore: bump version to 1.2.0 for pluggable channel architecture
gabi-simons Mar 1, 2026
faa3e78
Fix skill application
gavrielc Mar 2, 2026
fd78acb
fix: use slotted barrel file to prevent channel merge conflicts
gavrielc Mar 2, 2026
a91b904
fix: resolve real chat ID during setup for token-based channels
Koshkoshinsk Mar 2, 2026
537125f
fix: setup delegates to channel skills, fix group sync and Discord me…
gavrielc Mar 2, 2026
fdc6a17
fix: align add-whatsapp skill with main setup patterns
gavrielc Mar 2, 2026
3e7ea07
fix: add missing auth script to package.json
gabi-simons Mar 2, 2026
f2fbe9d
fix: update Discord skill tests to match onChatMetadata signature
gabi-simons Mar 2, 2026
5481fd8
docs: replace 'pluggable' jargon with clearer language
gavrielc Mar 2, 2026
45a11a6
docs: align Chinese README with English version
gavrielc Mar 2, 2026
d92a3e8
fix: warn on installed-but-unconfigured channels instead of silent skip
gavrielc Mar 2, 2026
a915514
docs: simplify changelog to one-liner with compare link
gavrielc Mar 2, 2026
bb7f90d
feat: add isMain flag and channel-prefixed group folders
gavrielc Mar 2, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 10 additions & 21 deletions .claude/skills/add-discord/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,6 @@ Read `.nanoclaw/state.yaml`. If `discord` is in `applied_skills`, skip to Phase

Use `AskUserQuestion` to collect configuration:

AskUserQuestion: Should Discord replace WhatsApp or run alongside it?
- **Replace WhatsApp** - Discord will be the only channel (sets DISCORD_ONLY=true)
- **Alongside** - Both Discord and WhatsApp channels active

AskUserQuestion: Do you have a Discord bot token, or do you need to create one?

If they have one, collect it now. If not, we'll create one in Phase 3.
Expand All @@ -41,18 +37,14 @@ npx tsx scripts/apply-skill.ts .claude/skills/add-discord
```

This deterministically:
- Adds `src/channels/discord.ts` (DiscordChannel class implementing Channel interface)
- Adds `src/channels/discord.ts` (DiscordChannel class with self-registration via `registerChannel`)
- Adds `src/channels/discord.test.ts` (unit tests with discord.js mock)
- Three-way merges Discord support into `src/index.ts` (multi-channel support, findChannel routing)
- Three-way merges Discord config into `src/config.ts` (DISCORD_BOT_TOKEN, DISCORD_ONLY exports)
- Three-way merges updated routing tests into `src/routing.test.ts`
- Appends `import './discord.js'` to the channel barrel file `src/channels/index.ts`
- Installs the `discord.js` npm dependency
- Updates `.env.example` with `DISCORD_BOT_TOKEN` and `DISCORD_ONLY`
- Records the application in `.nanoclaw/state.yaml`

If the apply reports merge conflicts, read the intent files:
- `modify/src/index.ts.intent.md` — what changed and invariants for index.ts
- `modify/src/config.ts.intent.md` — what changed for config.ts
If the apply reports merge conflicts, read the intent file:
- `modify/src/channels/index.ts.intent.md` — what changed and invariants

### Validate code changes

Expand Down Expand Up @@ -93,16 +85,12 @@ Add to `.env`:
DISCORD_BOT_TOKEN=<their-token>
```

If they chose to replace WhatsApp:

```bash
DISCORD_ONLY=true
```
Channels auto-enable when their credentials are present — no extra configuration needed.

Sync to container environment:

```bash
cp .env data/env/env
mkdir -p data/env && cp .env data/env/env
```

The container reads environment from `data/env/env`, not `.env` directly.
Expand Down Expand Up @@ -134,15 +122,16 @@ Wait for the user to provide the channel ID (format: `dc:1234567890123456`).

Use the IPC register flow or register directly. The channel ID, name, and folder name are needed.

For a main channel (responds to all messages, uses the `main` folder):
For a main channel (responds to all messages):

```typescript
registerGroup("dc:<channel-id>", {
name: "<server-name> #<channel-name>",
folder: "main",
folder: "discord_main",
trigger: `@${ASSISTANT_NAME}`,
added_at: new Date().toISOString(),
requiresTrigger: false,
isMain: true,
});
```

Expand All @@ -151,7 +140,7 @@ For additional channels (trigger-only):
```typescript
registerGroup("dc:<channel-id>", {
name: "<server-name> #<channel-name>",
folder: "<folder-name>",
folder: "discord_<channel-name>",
trigger: `@${ASSISTANT_NAME}`,
added_at: new Date().toISOString(),
requiresTrigger: true,
Expand Down
14 changes: 14 additions & 0 deletions .claude/skills/add-discord/add/src/channels/discord.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,12 @@ import { describe, it, expect, beforeEach, vi, afterEach } from 'vitest';

// --- Mocks ---

// Mock registry (registerChannel runs at import time)
vi.mock('./registry.js', () => ({ registerChannel: vi.fn() }));

// Mock env reader (used by the factory, not needed in unit tests)
vi.mock('../env.js', () => ({ readEnvFile: vi.fn(() => ({})) }));

// Mock config
vi.mock('../config.js', () => ({
ASSISTANT_NAME: 'Andy',
Expand Down Expand Up @@ -256,6 +262,8 @@ describe('DiscordChannel', () => {
'dc:1234567890123456',
expect.any(String),
'Test Server #general',
'discord',
true,
);
expect(opts.onMessage).toHaveBeenCalledWith(
'dc:1234567890123456',
Expand Down Expand Up @@ -286,6 +294,8 @@ describe('DiscordChannel', () => {
'dc:9999999999999999',
expect.any(String),
expect.any(String),
'discord',
true,
);
expect(opts.onMessage).not.toHaveBeenCalled();
});
Expand Down Expand Up @@ -365,6 +375,8 @@ describe('DiscordChannel', () => {
'dc:1234567890123456',
expect.any(String),
'Alice',
'discord',
false,
);
});

Expand All @@ -384,6 +396,8 @@ describe('DiscordChannel', () => {
'dc:1234567890123456',
expect.any(String),
'My Server #bot-chat',
'discord',
true,
);
});
});
Expand Down
16 changes: 15 additions & 1 deletion .claude/skills/add-discord/add/src/channels/discord.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import { Client, Events, GatewayIntentBits, Message, TextChannel } from 'discord.js';

import { ASSISTANT_NAME, TRIGGER_PATTERN } from '../config.js';
import { readEnvFile } from '../env.js';
import { logger } from '../logger.js';
import { registerChannel, ChannelOpts } from './registry.js';
import {
Channel,
OnChatMetadata,
Expand Down Expand Up @@ -122,7 +124,8 @@ export class DiscordChannel implements Channel {
}

// Store chat metadata for discovery
this.opts.onChatMetadata(chatJid, timestamp, chatName);
const isGroup = message.guild !== null;
this.opts.onChatMetadata(chatJid, timestamp, chatName, 'discord', isGroup);

// Only deliver full message for registered groups
const group = this.opts.registeredGroups()[chatJid];
Expand Down Expand Up @@ -234,3 +237,14 @@ export class DiscordChannel implements Channel {
}
}
}

registerChannel('discord', (opts: ChannelOpts) => {
const envVars = readEnvFile(['DISCORD_BOT_TOKEN']);
const token =
process.env.DISCORD_BOT_TOKEN || envVars.DISCORD_BOT_TOKEN || '';
if (!token) {
logger.warn('Discord: DISCORD_BOT_TOKEN not set');
return null;
}
return new DiscordChannel(token, opts);
});
5 changes: 1 addition & 4 deletions .claude/skills/add-discord/manifest.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,12 @@ adds:
- src/channels/discord.ts
- src/channels/discord.test.ts
modifies:
- src/index.ts
- src/config.ts
- src/routing.test.ts
- src/channels/index.ts
structured:
npm_dependencies:
discord.js: "^14.18.0"
env_additions:
- DISCORD_BOT_TOKEN
- DISCORD_ONLY
conflicts: []
depends: []
test: "npx vitest run src/channels/discord.test.ts"
13 changes: 13 additions & 0 deletions .claude/skills/add-discord/modify/src/channels/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// Channel self-registration barrel file.
// Each import triggers the channel module's registerChannel() call.

// discord
import './discord.js';

// gmail

// slack

// telegram

// whatsapp
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# Intent: Add Discord channel import

Add `import './discord.js';` to the channel barrel file so the Discord
module self-registers with the channel registry on startup.

This is an append-only change — existing import lines for other channels
must be preserved.
77 changes: 0 additions & 77 deletions .claude/skills/add-discord/modify/src/config.ts

This file was deleted.

21 changes: 0 additions & 21 deletions .claude/skills/add-discord/modify/src/config.ts.intent.md

This file was deleted.

Loading