@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.
- package/.claude-plugin/plugin.json +5 -0
- package/README.md +110 -0
- package/bin/install.mjs +43 -0
- package/hooks/hooks.json +7 -0
- package/hooks/session-start.sh +43 -0
- package/hooks/test/session-start.bats +48 -0
- package/lib/install-utils.mjs +143 -0
- package/package.json +31 -0
- package/skills/send/SKILL.md +89 -0
- package/skills/setup/SKILL.md +168 -0
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
|
package/bin/install.mjs
ADDED
|
@@ -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
|
+
}
|
package/hooks/hooks.json
ADDED
|
@@ -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
|