@windyroad/connect 0.2.0-preview.31

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.
@@ -0,0 +1,5 @@
1
+ {
2
+ "name": "wr-connect",
3
+ "version": "0.1.0",
4
+ "description": "Connect Claude Code sessions across repos via Discord (experimental)"
5
+ }
package/README.md ADDED
@@ -0,0 +1,110 @@
1
+ # @windyroad/connect
2
+
3
+ > **EXPERIMENTAL** — This plugin uses Claude Code's `--channels` feature, which is a
4
+ > research preview as of April 2026. The API surface may change. See
5
+ > [ADR-006](../../docs/decisions/006-connect-plugin.proposed.md) for details.
6
+
7
+ Connect Claude Code sessions across repos via Discord so they can collaborate.
8
+
9
+ ## What It Does
10
+
11
+ When running Claude Code sessions across multiple repos, this plugin lets sessions
12
+ communicate — with zero idle token cost. Sessions can hand off findings, ask questions,
13
+ share context, or coordinate work. The receiving session wakes up only when a message
14
+ arrives, using Discord as the collaboration channel.
15
+
16
+ **Example:** Session A (repo-a) discovers a bug in a package from repo-b. It sends a
17
+ message via `/wr-connect:send`, and Session B (repo-b) receives it immediately through
18
+ the Discord channel.
19
+
20
+ ## Install
21
+
22
+ ```bash
23
+ npx @windyroad/connect
24
+ ```
25
+
26
+ Or via the meta-installer:
27
+
28
+ ```bash
29
+ npx @windyroad/agent-plugins --plugin connect
30
+ ```
31
+
32
+ ## Setup
33
+
34
+ Run the interactive setup skill:
35
+
36
+ ```
37
+ /wr-connect:setup
38
+ ```
39
+
40
+ This walks you through:
41
+ 1. Creating a Discord bot
42
+ 2. Configuring environment variables (bot token, channel ID, session name)
43
+ 3. Installing the Discord channel plugin
44
+ 4. Configuring the security allowlist
45
+
46
+ You can opt out at any point during setup.
47
+
48
+ ## Usage
49
+
50
+ ### Sending a message
51
+
52
+ ```
53
+ /wr-connect:send BUG: Widget.parse() throws on null input at line 47
54
+ ```
55
+
56
+ ### Receiving messages
57
+
58
+ Start Claude Code with the channels flag:
59
+
60
+ ```bash
61
+ claude --channels plugin:discord@claude-plugins-official
62
+ ```
63
+
64
+ No explicit "wait" command is needed. The session listens automatically and wakes
65
+ when a message arrives.
66
+
67
+ ## Environment Variables
68
+
69
+ | Variable | Description |
70
+ |----------|-------------|
71
+ | `WR_CONNECT_BOT_TOKEN` | Discord bot token |
72
+ | `WR_CONNECT_CHANNEL_ID` | Discord channel ID |
73
+ | `WR_CONNECT_SESSION_NAME` | Human-readable name for this session (e.g. `repo-b`) |
74
+
75
+ Set these in your shell profile (`~/.zshrc`, `~/.bashrc`) or a `.env` file that is
76
+ in `.gitignore`.
77
+
78
+ ## Hooks
79
+
80
+ | Event | Script | Behaviour |
81
+ |-------|--------|-----------|
82
+ | SessionStart | `session-start.sh` | Warns if env vars are set but `--channels` is not active. Silent if env vars are not set. Never blocks. |
83
+
84
+ ## Security
85
+
86
+ - **Bot token is a credential** — it gives anyone who has it the ability to send
87
+ messages to your Claude Code session (which has filesystem and shell access).
88
+ Treat it like a password.
89
+ - **Environment variables only** — tokens are never stored in project files.
90
+ This is consistent with the suite's `secret-leak-gate`.
91
+ - **Discord allowlist** — configure the Discord channel plugin to only accept
92
+ messages from your own Discord user ID.
93
+ - **Private channel** — use a private Discord server or channel.
94
+ - **Dedicated bot** — use one bot per developer, not a shared team bot.
95
+
96
+ ## Update
97
+
98
+ ```bash
99
+ npx @windyroad/connect --update
100
+ ```
101
+
102
+ ## Uninstall
103
+
104
+ ```bash
105
+ npx @windyroad/connect --uninstall
106
+ ```
107
+
108
+ ## Licence
109
+
110
+ MIT
@@ -0,0 +1,43 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { resolve, dirname } from "node:path";
4
+ import { fileURLToPath } from "node:url";
5
+
6
+ const __dirname = dirname(fileURLToPath(import.meta.url));
7
+ const utils = await import(resolve(__dirname, "../lib/install-utils.mjs"));
8
+
9
+ const PLUGIN = "wr-connect";
10
+ const DEPS = [];
11
+
12
+ const flags = utils.parseStandardArgs(process.argv);
13
+
14
+ if (flags.help) {
15
+ console.log(`
16
+ Usage: npx @windyroad/connect [options]
17
+
18
+ Connect Claude Code sessions across repos via Discord (experimental)
19
+
20
+ Options:
21
+ --update Update this plugin and its skills
22
+ --uninstall Remove this plugin
23
+ --scope Installation scope: project (default) or user
24
+ --dry-run Show what would be done without executing
25
+ --help, -h Show this help
26
+ `);
27
+ process.exit(0);
28
+ }
29
+
30
+ if (flags.dryRun) {
31
+ utils.setDryRun(true);
32
+ console.log("[dry-run mode — no commands will be executed]\n");
33
+ }
34
+
35
+ utils.checkPrerequisites();
36
+
37
+ if (flags.uninstall) {
38
+ utils.uninstallPackage(PLUGIN);
39
+ } else if (flags.update) {
40
+ utils.updatePackage(PLUGIN);
41
+ } else {
42
+ utils.installPackage(PLUGIN, { deps: DEPS, scope: flags.scope });
43
+ }
@@ -0,0 +1,7 @@
1
+ {
2
+ "hooks": {
3
+ "SessionStart": [
4
+ { "hooks": [{ "type": "command", "command": "${CLAUDE_PLUGIN_ROOT}/hooks/session-start.sh" }] }
5
+ ]
6
+ }
7
+ }
@@ -0,0 +1,43 @@
1
+ #!/bin/bash
2
+ # wr-connect - SessionStart hook
3
+ # Warns if env vars are configured but --channels is not active.
4
+ # Always exits 0 (warns, never blocks).
5
+
6
+ # If bot token is not set, plugin is inactive — exit silently
7
+ if [ -z "${WR_CONNECT_BOT_TOKEN:-}" ]; then
8
+ exit 0
9
+ fi
10
+
11
+ # If --channels is active, output collaboration primer
12
+ # NOTE: CLAUDE_CHANNELS is the expected env var when --channels is active.
13
+ # This may change in future Claude Code versions; update if needed.
14
+ if [ -n "${CLAUDE_CHANNELS:-}" ]; then
15
+ SESSION_NAME="${WR_CONNECT_SESSION_NAME:-unnamed}"
16
+ cat <<PRIMER
17
+ wr-connect: Collaboration channel active. Your session name is "${SESSION_NAME}".
18
+
19
+ You are connected to a shared channel with other Claude Code sessions and
20
+ potentially human participants. Follow these guidelines:
21
+
22
+ LISTENING:
23
+ - Read all messages for context, but only respond if relevant to your work.
24
+ - Messages containing @${SESSION_NAME} are directed at you — prioritise these.
25
+ - Messages with @someone-else are for another session — read for context but
26
+ stay quiet unless you have something relevant to add.
27
+ - Messages with no @ are broadcast — respond if relevant to your domain.
28
+
29
+ SENDING:
30
+ - Use /wr-connect:send to message the channel.
31
+ - Use @<session-name> to direct a message to a specific session.
32
+ - Be concise — other sessions will read your messages too.
33
+ PRIMER
34
+ exit 0
35
+ fi
36
+
37
+ # Env vars set but --channels not active — warn
38
+ cat <<'EOF'
39
+ wr-connect: Environment configured but --channels is not active.
40
+ To enable cross-repo collaboration, restart with:
41
+ claude --channels plugin:discord@claude-plugins-official
42
+ EOF
43
+ exit 0
@@ -0,0 +1,48 @@
1
+ #!/usr/bin/env bats
2
+
3
+ # Tests for session-start.sh (SessionStart hook)
4
+ # Verifies three states: no config, config without channels, config with channels.
5
+
6
+ setup() {
7
+ SCRIPT_DIR="$(cd "$(dirname "$BATS_TEST_FILENAME")/.." && pwd)"
8
+ HOOK="$SCRIPT_DIR/session-start.sh"
9
+ # Clear env vars for each test
10
+ unset WR_CONNECT_BOT_TOKEN
11
+ unset WR_CONNECT_CHANNEL_ID
12
+ unset WR_CONNECT_SESSION_NAME
13
+ unset CLAUDE_CHANNELS
14
+ }
15
+
16
+ @test "no env vars: exits 0, no output" {
17
+ run "$HOOK"
18
+ [ "$status" -eq 0 ]
19
+ [ -z "$output" ]
20
+ }
21
+
22
+ @test "env vars set, no CLAUDE_CHANNELS: exits 0, warns about --channels" {
23
+ export WR_CONNECT_BOT_TOKEN="test-token"
24
+ run "$HOOK"
25
+ [ "$status" -eq 0 ]
26
+ [[ "$output" == *"--channels"* ]]
27
+ [[ "$output" == *"plugin:discord@claude-plugins-official"* ]]
28
+ }
29
+
30
+ @test "env vars set, CLAUDE_CHANNELS active: exits 0, outputs primer with session name" {
31
+ export WR_CONNECT_BOT_TOKEN="test-token"
32
+ export WR_CONNECT_SESSION_NAME="repo-b"
33
+ export CLAUDE_CHANNELS=1
34
+ run "$HOOK"
35
+ [ "$status" -eq 0 ]
36
+ [[ "$output" == *"repo-b"* ]]
37
+ [[ "$output" == *"@repo-b"* ]]
38
+ }
39
+
40
+ @test "primer tells agent to prioritise @mentions" {
41
+ export WR_CONNECT_BOT_TOKEN="test-token"
42
+ export WR_CONNECT_SESSION_NAME="my-repo"
43
+ export CLAUDE_CHANNELS=1
44
+ run "$HOOK"
45
+ [ "$status" -eq 0 ]
46
+ [[ "$output" == *"@my-repo"* ]]
47
+ [[ "$output" == *"relevant"* ]]
48
+ }
@@ -0,0 +1,143 @@
1
+ /**
2
+ * Shared install utilities for @windyroad/* packages.
3
+ * Used by both per-plugin installers and the meta-installer.
4
+ */
5
+
6
+ import { execSync } from "node:child_process";
7
+
8
+ const MARKETPLACE_REPO = "windyroad/agent-plugins";
9
+ const MARKETPLACE_NAME = "windyroad";
10
+
11
+ let _dryRun = false;
12
+
13
+ export { MARKETPLACE_REPO, MARKETPLACE_NAME };
14
+
15
+ export function setDryRun(value) {
16
+ _dryRun = value;
17
+ }
18
+
19
+ export function isDryRun() {
20
+ return _dryRun;
21
+ }
22
+
23
+ export function run(cmd, label) {
24
+ console.log(` ${label}...`);
25
+ if (_dryRun) {
26
+ console.log(` [dry-run] ${cmd}`);
27
+ return true;
28
+ }
29
+ try {
30
+ execSync(cmd, { stdio: "inherit" });
31
+ return true;
32
+ } catch {
33
+ console.error(` FAILED: ${label}`);
34
+ return false;
35
+ }
36
+ }
37
+
38
+ export function checkPrerequisites() {
39
+ if (_dryRun) return;
40
+
41
+ try {
42
+ execSync("claude --version", { stdio: "pipe" });
43
+ } catch {
44
+ console.error(
45
+ "Error: 'claude' CLI not found. Install Claude Code first:\n https://docs.anthropic.com/en/docs/claude-code\n"
46
+ );
47
+ process.exit(1);
48
+ }
49
+ }
50
+
51
+ export function addMarketplace() {
52
+ return run(
53
+ `claude plugin marketplace add ${MARKETPLACE_REPO}`,
54
+ `Marketplace: ${MARKETPLACE_NAME}`
55
+ );
56
+ }
57
+
58
+ export function installPlugin(pluginName, { scope = "project" } = {}) {
59
+ return run(
60
+ `claude plugin install ${pluginName}@${MARKETPLACE_NAME} --scope ${scope}`,
61
+ pluginName
62
+ );
63
+ }
64
+
65
+ export function updatePlugin(pluginName) {
66
+ return run(`claude plugin update ${pluginName}`, pluginName);
67
+ }
68
+
69
+ export function uninstallPlugin(pluginName) {
70
+ return run(`claude plugin uninstall ${pluginName}`, `Removing ${pluginName}`);
71
+ }
72
+
73
+ /**
74
+ * Install a single package: marketplace add + plugin install.
75
+ */
76
+ export function installPackage(pluginName, { deps = [], scope = "project" } = {}) {
77
+ console.log(`\nInstalling @windyroad/${pluginName.replace("wr-", "")} (${scope} scope)...\n`);
78
+
79
+ addMarketplace();
80
+ installPlugin(pluginName, { scope });
81
+
82
+ if (deps.length > 0) {
83
+ console.log(`\nNote: This plugin works best with:`);
84
+ for (const dep of deps) {
85
+ console.log(` - @windyroad/${dep.replace("wr-", "")} (npx @windyroad/${dep.replace("wr-", "")})`);
86
+ }
87
+ }
88
+
89
+ console.log(
90
+ `\nDone! Restart Claude Code to activate.\n`
91
+ );
92
+ }
93
+
94
+ /**
95
+ * Update a single package.
96
+ */
97
+ export function updatePackage(pluginName) {
98
+ console.log(`\nUpdating @windyroad/${pluginName.replace("wr-", "")}...\n`);
99
+
100
+ run(
101
+ `claude plugin marketplace update ${MARKETPLACE_NAME}`,
102
+ "Updating marketplace"
103
+ );
104
+ updatePlugin(pluginName);
105
+
106
+ console.log("\nDone! Restart Claude Code to apply updates.\n");
107
+ }
108
+
109
+ /**
110
+ * Uninstall a single package.
111
+ */
112
+ export function uninstallPackage(pluginName) {
113
+ console.log(`\nUninstalling @windyroad/${pluginName.replace("wr-", "")}...\n`);
114
+
115
+ uninstallPlugin(pluginName);
116
+
117
+ console.log("\nDone. Restart Claude Code to apply changes.\n");
118
+ }
119
+
120
+ /**
121
+ * Parse standard flags used by all per-plugin installers.
122
+ */
123
+ export function parseStandardArgs(argv) {
124
+ const args = argv.slice(2);
125
+ const flags = {
126
+ help: args.includes("--help") || args.includes("-h"),
127
+ uninstall: args.includes("--uninstall"),
128
+ update: args.includes("--update"),
129
+ dryRun: args.includes("--dry-run"),
130
+ scope: "project",
131
+ };
132
+ const scopeIdx = args.indexOf("--scope");
133
+ if (scopeIdx !== -1 && args[scopeIdx + 1]) {
134
+ const val = args[scopeIdx + 1];
135
+ if (["project", "user", "local"].includes(val)) {
136
+ flags.scope = val;
137
+ } else {
138
+ console.error("--scope requires: project, user, or local");
139
+ process.exit(1);
140
+ }
141
+ }
142
+ return flags;
143
+ }
package/package.json ADDED
@@ -0,0 +1,31 @@
1
+ {
2
+ "name": "@windyroad/connect",
3
+ "version": "0.2.0-preview.31",
4
+ "description": "Connect Claude Code sessions across repos via Discord (experimental)",
5
+ "bin": {
6
+ "windyroad-connect": "./bin/install.mjs"
7
+ },
8
+ "type": "module",
9
+ "license": "MIT",
10
+ "repository": {
11
+ "type": "git",
12
+ "url": "https://github.com/windyroad/agent-plugins.git",
13
+ "directory": "packages/connect"
14
+ },
15
+ "keywords": [
16
+ "claude-code",
17
+ "claude-code-plugin",
18
+ "ai-agent",
19
+ "ai-coding",
20
+ "cross-repo",
21
+ "discord",
22
+ "collaboration"
23
+ ],
24
+ "files": [
25
+ "bin/",
26
+ "hooks/",
27
+ "skills/",
28
+ ".claude-plugin/",
29
+ "lib/"
30
+ ]
31
+ }
@@ -0,0 +1,89 @@
1
+ ---
2
+ name: wr-connect:send
3
+ description: Send a message to other Claude Code sessions via Discord.
4
+ allowed-tools: Bash, AskUserQuestion
5
+ ---
6
+
7
+ # Send Message
8
+
9
+ Send a message from this session to other Claude Code sessions listening on the
10
+ configured Discord channel.
11
+
12
+ ## Instructions
13
+
14
+ ### 1. Check environment variables
15
+
16
+ Verify these environment variables are set:
17
+ - `WR_CONNECT_BOT_TOKEN`
18
+ - `WR_CONNECT_CHANNEL_ID`
19
+ - `WR_CONNECT_SESSION_NAME`
20
+
21
+ ```bash
22
+ [ -n "$WR_CONNECT_BOT_TOKEN" ] && [ -n "$WR_CONNECT_CHANNEL_ID" ] && [ -n "$WR_CONNECT_SESSION_NAME" ] && echo "OK" || echo "MISSING"
23
+ ```
24
+
25
+ If any are missing, tell the user:
26
+
27
+ > wr-connect is not configured. Run `/wr-connect:setup` first.
28
+
29
+ Then stop.
30
+
31
+ ### 2. Get the message
32
+
33
+ The message to send is provided via `$ARGUMENTS`.
34
+
35
+ If `$ARGUMENTS` is empty, use AskUserQuestion to ask:
36
+
37
+ > What message would you like to send to the other session(s)?
38
+ > To direct your message to a specific session, start with @session-name.
39
+
40
+ ### 3. Send the message
41
+
42
+ Format the message as: `[wr-connect] from: <SESSION_NAME> | <message>`
43
+
44
+ **@mentions:** If the user's message starts with `@<name>`, preserve it as-is in the
45
+ message body. This tells the target session to prioritise the message. Messages
46
+ without an `@` are treated as broadcast to all listening sessions.
47
+
48
+ Send via the Discord API:
49
+
50
+ ```bash
51
+ curl -s -o /tmp/wr-connect-response.json -w "%{http_code}" \
52
+ -X POST "https://discord.com/api/v10/channels/${WR_CONNECT_CHANNEL_ID}/messages" \
53
+ -H "Authorization: Bot ${WR_CONNECT_BOT_TOKEN}" \
54
+ -H "Content-Type: application/json" \
55
+ -d "{\"content\": \"[wr-connect] from: ${WR_CONNECT_SESSION_NAME} | <MESSAGE>\"}"
56
+ ```
57
+
58
+ Replace `<MESSAGE>` with the actual message content. Escape any double quotes in the
59
+ message before inserting into the JSON payload.
60
+
61
+ ### 4. Report result
62
+
63
+ Check the HTTP status code returned by curl:
64
+ - **200** or **201**: Message sent successfully. Report the formatted message to the user.
65
+ - **429**: Rate limited. Tell the user to wait a moment and try again.
66
+ - **401** or **403**: Authentication failed. Tell the user to check their bot token.
67
+ - **Other**: Report the status code and response body for debugging.
68
+
69
+ ## Examples
70
+
71
+ **Broadcast (all sessions):**
72
+ ```
73
+ /wr-connect:send BUG: Widget.parse() throws on null input at line 47
74
+ ```
75
+ Sends:
76
+ ```
77
+ [wr-connect] from: repo-a | BUG: Widget.parse() throws on null input at line 47
78
+ ```
79
+
80
+ **Directed (specific session):**
81
+ ```
82
+ /wr-connect:send @repo-b BUG: Widget.parse() throws on null input at line 47
83
+ ```
84
+ Sends:
85
+ ```
86
+ [wr-connect] from: repo-a | @repo-b BUG: Widget.parse() throws on null input at line 47
87
+ ```
88
+
89
+ $ARGUMENTS
@@ -0,0 +1,168 @@
1
+ ---
2
+ name: wr-connect:setup
3
+ description: Set up cross-repo collaboration via Discord. Interactive walkthrough with explicit opt-out before any configuration.
4
+ allowed-tools: Read, Bash, Glob, Grep, AskUserQuestion
5
+ ---
6
+
7
+ # Connect Setup
8
+
9
+ > **EXPERIMENTAL:** This plugin uses Claude Code's `--channels` feature, which is a
10
+ > research preview. The API surface may change. See ADR-006 for details.
11
+
12
+ This skill configures Discord as a collaboration channel so Claude Code sessions
13
+ across different repos can communicate with zero idle token cost.
14
+
15
+ ## Steps
16
+
17
+ ### 1. Explain what this does
18
+
19
+ Tell the user:
20
+
21
+ > This plugin connects your Claude Code sessions across repos so they can
22
+ > collaborate. For example, if Session A discovers a bug in a package from repo-b,
23
+ > it can notify Session B (which is working on repo-b) without Session B polling or
24
+ > wasting tokens. Sessions can hand off findings, ask questions, share context, or
25
+ > coordinate work.
26
+ >
27
+ > It works by using Discord as a message channel. You will need:
28
+ > - A Discord account
29
+ > - A Discord bot (created in the next step)
30
+ > - A private Discord server or channel
31
+
32
+ ### 2. Opt-out checkpoint
33
+
34
+ Use AskUserQuestion to ask the user:
35
+
36
+ > Would you like to proceed with setting up cross-repo collaboration via Discord?
37
+ > This will require creating a Discord bot and configuring environment variables.
38
+ > No files will be written to your project.
39
+ >
40
+ > Reply **yes** to continue or **no** to skip.
41
+
42
+ If the user says no, respond with:
43
+
44
+ > Setup skipped. The connect plugin is inactive until configured.
45
+ > Run `/wr-connect:setup` any time to start again.
46
+
47
+ Then stop — do not proceed to any further steps.
48
+
49
+ ### 3. Create a Discord bot
50
+
51
+ Guide the user through these steps:
52
+
53
+ 1. Go to https://discord.com/developers/applications
54
+ 2. Click **New Application** — name it something like `claude-connect`
55
+ 3. Go to **Bot** > click **Add Bot** > confirm
56
+ 4. Under **Token**, click **Reset Token** and copy it — they will need it in step 5
57
+ 5. Under **Privileged Gateway Intents**, enable **Message Content Intent**
58
+ 6. Go to **OAuth2 > URL Generator**:
59
+ - Scopes: `bot`
60
+ - Bot permissions: `Send Messages`, `Read Messages/View Channels`
61
+ 7. Copy the generated URL, open it in a browser, and add the bot to their server
62
+
63
+ ### 4. Get the channel ID
64
+
65
+ Guide the user:
66
+
67
+ 1. In Discord, go to **User Settings > Advanced** and enable **Developer Mode**
68
+ 2. Right-click the channel to use for collaboration and click **Copy Channel ID**
69
+
70
+ ### 5. Configure environment variables
71
+
72
+ Guide the user to add these to their shell profile (`~/.zshrc`, `~/.bashrc`) or a
73
+ `.env` file that is already in `.gitignore`:
74
+
75
+ ```bash
76
+ export WR_CONNECT_BOT_TOKEN="<the bot token from step 3>"
77
+ export WR_CONNECT_CHANNEL_ID="<the channel ID from step 4>"
78
+ export WR_CONNECT_SESSION_NAME="<a name for this session, e.g. repo-b>"
79
+ ```
80
+
81
+ **Security warning:** The bot token gives anyone who has it the ability to send
82
+ messages to your Claude Code session. Never commit it to source control. Use
83
+ environment variables or a `.env` file that is in `.gitignore`.
84
+
85
+ If the user chooses a `.env` file, verify `.env` is in `.gitignore`:
86
+
87
+ ```bash
88
+ grep -q '\.env' .gitignore 2>/dev/null && echo ".env is in .gitignore" || echo "WARNING: .env is NOT in .gitignore — add it now"
89
+ ```
90
+
91
+ ### 6. Verify environment variables
92
+
93
+ Check the env vars are set in the current shell:
94
+
95
+ ```bash
96
+ [ -n "$WR_CONNECT_BOT_TOKEN" ] && echo "BOT_TOKEN: set" || echo "BOT_TOKEN: NOT SET"
97
+ [ -n "$WR_CONNECT_CHANNEL_ID" ] && echo "CHANNEL_ID: set" || echo "CHANNEL_ID: NOT SET"
98
+ [ -n "$WR_CONNECT_SESSION_NAME" ] && echo "SESSION_NAME: set" || echo "SESSION_NAME: NOT SET"
99
+ ```
100
+
101
+ If any are not set, remind the user to `source ~/.zshrc` (or their profile) or
102
+ restart their terminal before continuing.
103
+
104
+ ### 7. Install the Discord channel plugin
105
+
106
+ ```bash
107
+ claude plugin install discord@claude-plugins-official
108
+ ```
109
+
110
+ ### 8. Restart with channels active
111
+
112
+ Tell the user to restart Claude Code with:
113
+
114
+ ```bash
115
+ claude --channels plugin:discord@claude-plugins-official
116
+ ```
117
+
118
+ Claude will send a pairing code. Follow the prompts to pair the Discord account.
119
+
120
+ ### 9. Configure the Discord allowlist (security)
121
+
122
+ This is critical. Without the allowlist, anyone who can message the bot can send
123
+ instructions to the Claude Code session.
124
+
125
+ Guide the user to configure the allowlist so only their own Discord user ID can
126
+ send messages. The exact command depends on the Discord channel plugin's interface —
127
+ check its documentation for the allowlist or access policy setting.
128
+
129
+ Tell the user:
130
+
131
+ > To find your Discord user ID: In Discord with Developer Mode enabled,
132
+ > click your username at the bottom left, then click **Copy User ID**.
133
+
134
+ ### 10. Test the setup
135
+
136
+ Ask the user if they would like to send a test message. If yes, tell them to use:
137
+
138
+ ```
139
+ /wr-connect:send test message from setup
140
+ ```
141
+
142
+ Or from another terminal:
143
+
144
+ ```bash
145
+ curl -s -X POST "https://discord.com/api/v10/channels/$WR_CONNECT_CHANNEL_ID/messages" \
146
+ -H "Authorization: Bot $WR_CONNECT_BOT_TOKEN" \
147
+ -H "Content-Type: application/json" \
148
+ -d '{"content": "[wr-connect] from: test | setup verification"}'
149
+ ```
150
+
151
+ If the session with `--channels` active receives the message, setup is complete.
152
+
153
+ ### 11. Explain collaboration behaviour
154
+
155
+ Tell the user:
156
+
157
+ > Your session is now part of a shared collaboration channel. Here's how it works:
158
+ >
159
+ > - **Multiple sessions and humans** can share the same Discord channel.
160
+ > - Use `@session-name` in messages to direct them at a specific session
161
+ > (e.g. `/wr-connect:send @repo-b please fix Widget.parse()`).
162
+ > - Messages without `@` are broadcast — all sessions see them.
163
+ > - Each session reads everything for context but only responds when the message
164
+ > is relevant to its work.
165
+ > - Your session name is whatever you set in `WR_CONNECT_SESSION_NAME`. Other
166
+ > sessions will use `@your-name` to get your attention.
167
+
168
+ $ARGUMENTS