@chorus-aidlc/openclaw-plugin 0.1.0
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/README.md +287 -0
- package/images/slug.png +0 -0
- package/openclaw.plugin.json +29 -0
- package/package.json +37 -0
- package/src/commands.ts +141 -0
- package/src/config.ts +24 -0
- package/src/event-router.ts +228 -0
- package/src/index.ts +132 -0
- package/src/mcp-client.ts +141 -0
- package/src/sse-listener.ts +184 -0
- package/src/tools/common-tools.ts +418 -0
- package/src/tools/dev-tools.ts +85 -0
- package/src/tools/pm-tools.ts +356 -0
package/README.md
ADDED
|
@@ -0,0 +1,287 @@
|
|
|
1
|
+
<p align="center">
|
|
2
|
+
<img src="images/slug.png" alt="@chorus-aidlc/openclaw-plugin" width="240" />
|
|
3
|
+
</p>
|
|
4
|
+
|
|
5
|
+
<p align="center"><strong>@chorus-aidlc/openclaw-plugin</strong></p>
|
|
6
|
+
|
|
7
|
+
<p align="center">
|
|
8
|
+
<a href="https://discord.gg/SwcCMaMmR">
|
|
9
|
+
<img src="https://img.shields.io/badge/Discord-Join%20us-5865F2?style=for-the-badge&logo=discord&logoColor=white" alt="Discord">
|
|
10
|
+
</a>
|
|
11
|
+
</p>
|
|
12
|
+
|
|
13
|
+
OpenClaw plugin for [Chorus](https://github.com/Chorus-AIDLC/Chorus) — the AI-DLC (AI-Driven Development Lifecycle) collaboration platform.
|
|
14
|
+
|
|
15
|
+
This plugin connects OpenClaw to Chorus via a persistent SSE connection and MCP tool bridge, enabling your OpenClaw agent to participate in the full Idea → Proposal → Task → Execute → Verify workflow autonomously.
|
|
16
|
+
|
|
17
|
+
## How It Works
|
|
18
|
+
|
|
19
|
+
```
|
|
20
|
+
Chorus Server
|
|
21
|
+
│
|
|
22
|
+
├── SSE (GET /api/events/notifications)
|
|
23
|
+
│ Push real-time events: task_assigned, mentioned,
|
|
24
|
+
│ proposal_rejected, elaboration_answered, etc.
|
|
25
|
+
│ │
|
|
26
|
+
│ ▼
|
|
27
|
+
│ ┌──────────────────────┐
|
|
28
|
+
│ │ SSE Listener │ ── auto-reconnect with
|
|
29
|
+
│ │ (background service)│ exponential backoff
|
|
30
|
+
│ └──────────┬───────────┘
|
|
31
|
+
│ │
|
|
32
|
+
│ ┌──────────▼───────────┐
|
|
33
|
+
│ │ Event Router │ ── filters by project,
|
|
34
|
+
│ │ │ maps event → action
|
|
35
|
+
│ └──────────┬───────────┘
|
|
36
|
+
│ │
|
|
37
|
+
│ ┌──────────▼───────────┐ POST /hooks/wake
|
|
38
|
+
│ │ Agent Trigger │ ──────────────────────► OpenClaw Agent
|
|
39
|
+
│ └──────────────────────┘ (immediate heartbeat)
|
|
40
|
+
│
|
|
41
|
+
├── MCP (POST /api/mcp)
|
|
42
|
+
│ 40 Chorus MCP tools available as native
|
|
43
|
+
│ OpenClaw agent tools via @modelcontextprotocol/sdk
|
|
44
|
+
│
|
|
45
|
+
└─────────────────────────────────────────────────────
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
**Key design decisions:**
|
|
49
|
+
|
|
50
|
+
- **MCP Client, not REST** — Uses `@modelcontextprotocol/sdk` to call Chorus MCP tools directly. Zero Chorus-side code changes needed. 40 tools registered out of the box. When Chorus adds new MCP tools, adding them to the plugin is a one-liner.
|
|
51
|
+
- **SSE for push, MCP for pull** — SSE delivers real-time notifications; MCP handles all tool operations (claim, report, submit, etc.).
|
|
52
|
+
- **Hooks-based agent wake** — Uses OpenClaw's `/hooks/wake` API to inject system events and trigger immediate heartbeats when Chorus events arrive.
|
|
53
|
+
|
|
54
|
+
## Prerequisites
|
|
55
|
+
|
|
56
|
+
- [OpenClaw](https://openclaw.ai) gateway running
|
|
57
|
+
- [Chorus](https://github.com/Chorus-AIDLC/Chorus) server accessible
|
|
58
|
+
- A Chorus API Key (`cho_` prefix) for the agent
|
|
59
|
+
- OpenClaw hooks enabled (`hooks.enabled: true` in `openclaw.json`)
|
|
60
|
+
|
|
61
|
+
## Installation
|
|
62
|
+
|
|
63
|
+
### From local path (development)
|
|
64
|
+
|
|
65
|
+
Add to your `~/.openclaw/openclaw.json`:
|
|
66
|
+
|
|
67
|
+
```json
|
|
68
|
+
{
|
|
69
|
+
"hooks": {
|
|
70
|
+
"enabled": true,
|
|
71
|
+
"token": "your-hooks-token"
|
|
72
|
+
},
|
|
73
|
+
"plugins": {
|
|
74
|
+
"enabled": true,
|
|
75
|
+
"allow": ["@chorus-aidlc/openclaw-plugin"],
|
|
76
|
+
"load": {
|
|
77
|
+
"paths": ["/path/to/openclaw-plugin"]
|
|
78
|
+
},
|
|
79
|
+
"entries": {
|
|
80
|
+
"@chorus-aidlc/openclaw-plugin": {
|
|
81
|
+
"enabled": true,
|
|
82
|
+
"config": {
|
|
83
|
+
"chorusUrl": "https://chorus.example.com",
|
|
84
|
+
"apiKey": "cho_your_api_key_here",
|
|
85
|
+
"projectUuids": [],
|
|
86
|
+
"autoStart": true
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
### From npm (planned)
|
|
95
|
+
|
|
96
|
+
```bash
|
|
97
|
+
# Coming soon
|
|
98
|
+
pnpm add @chorus-aidlc/openclaw-plugin
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
## Configuration
|
|
102
|
+
|
|
103
|
+
| Field | Type | Required | Default | Description |
|
|
104
|
+
|-------|------|----------|---------|-------------|
|
|
105
|
+
| `chorusUrl` | `string` | Yes | — | Chorus server URL (e.g., `https://chorus.example.com`) |
|
|
106
|
+
| `apiKey` | `string` | Yes | — | Chorus API Key with `cho_` prefix |
|
|
107
|
+
| `projectUuids` | `string[]` | No | `[]` | Project UUIDs to monitor. Empty = all projects. |
|
|
108
|
+
| `autoStart` | `boolean` | No | `true` | Auto-claim tasks when `task_assigned` events arrive |
|
|
109
|
+
|
|
110
|
+
### OpenClaw requirements
|
|
111
|
+
|
|
112
|
+
The plugin reads these from the main OpenClaw config:
|
|
113
|
+
|
|
114
|
+
- **`hooks.enabled`** must be `true` — required for agent wake via `/hooks/wake`
|
|
115
|
+
- **`hooks.token`** — shared secret for hook authentication (must differ from `gateway.auth.token`)
|
|
116
|
+
- **`gateway.port`** — defaults to `18789`
|
|
117
|
+
|
|
118
|
+
## Features
|
|
119
|
+
|
|
120
|
+
### Real-time SSE Events
|
|
121
|
+
|
|
122
|
+
The plugin maintains a persistent SSE connection to Chorus and reacts to these events:
|
|
123
|
+
|
|
124
|
+
| Event | Behavior |
|
|
125
|
+
|-------|----------|
|
|
126
|
+
| `task_assigned` | Auto-claim task (if `autoStart: true`) + wake agent to start work |
|
|
127
|
+
| `mentioned` | Wake agent with @mention context |
|
|
128
|
+
| `elaboration_requested` | Wake agent to review elaboration questions |
|
|
129
|
+
| `elaboration_answered` | Wake agent to review answers, @mention answerer, then validate or start new round |
|
|
130
|
+
| `proposal_rejected` | Wake agent with rejection reason to fix and resubmit, @mention reviewer |
|
|
131
|
+
| `proposal_approved` | Wake agent to check newly created tasks, @mention approver |
|
|
132
|
+
| `idea_claimed` | Wake agent when an idea is assigned to it, @mention assigner |
|
|
133
|
+
|
|
134
|
+
**Resilience:** Exponential backoff reconnect (1s → 2s → 4s → ... → 30s max). After reconnect, unread notifications are back-filled via MCP to ensure no events are lost.
|
|
135
|
+
|
|
136
|
+
### Registered Tools (40 total)
|
|
137
|
+
|
|
138
|
+
#### PM Workflow (15 tools)
|
|
139
|
+
|
|
140
|
+
| Tool | Description |
|
|
141
|
+
|------|-------------|
|
|
142
|
+
| `chorus_claim_idea` | Claim an open idea for elaboration |
|
|
143
|
+
| `chorus_start_elaboration` | Start elaboration round with structured questions |
|
|
144
|
+
| `chorus_answer_elaboration` | Submit answers for elaboration round |
|
|
145
|
+
| `chorus_validate_elaboration` | Validate answers, resolve or request follow-up |
|
|
146
|
+
| `chorus_create_proposal` | Create proposal with document + task drafts |
|
|
147
|
+
| `chorus_add_document_draft` | Add document draft to proposal |
|
|
148
|
+
| `chorus_add_task_draft` | Add task draft to proposal |
|
|
149
|
+
| `chorus_get_proposal` | View full proposal with all draft UUIDs |
|
|
150
|
+
| `chorus_update_document_draft` | Modify document draft |
|
|
151
|
+
| `chorus_update_task_draft` | Modify task draft (including dependencies) |
|
|
152
|
+
| `chorus_remove_document_draft` | Remove document draft |
|
|
153
|
+
| `chorus_remove_task_draft` | Remove task draft |
|
|
154
|
+
| `chorus_validate_proposal` | Check proposal completeness before submit |
|
|
155
|
+
| `chorus_submit_proposal` | Submit proposal for approval |
|
|
156
|
+
| `chorus_pm_create_idea` | Create a new idea in a project |
|
|
157
|
+
|
|
158
|
+
#### Developer Workflow (4 tools)
|
|
159
|
+
|
|
160
|
+
| Tool | Description |
|
|
161
|
+
|------|-------------|
|
|
162
|
+
| `chorus_claim_task` | Claim an open task |
|
|
163
|
+
| `chorus_update_task` | Update task status (in_progress / to_verify) |
|
|
164
|
+
| `chorus_report_work` | Report work progress |
|
|
165
|
+
| `chorus_submit_for_verify` | Submit completed task for verification |
|
|
166
|
+
|
|
167
|
+
#### Common & Exploration (20 tools)
|
|
168
|
+
|
|
169
|
+
| Tool | Description |
|
|
170
|
+
|------|-------------|
|
|
171
|
+
| `chorus_checkin` | Agent check-in (identity, owner info, roles, assignments) |
|
|
172
|
+
| `chorus_get_notifications` | Fetch notifications (default: unread) |
|
|
173
|
+
| `chorus_get_project` | Get project details |
|
|
174
|
+
| `chorus_get_task` | Get task details |
|
|
175
|
+
| `chorus_get_idea` | Get idea details |
|
|
176
|
+
| `chorus_get_available_tasks` | List open tasks in a project |
|
|
177
|
+
| `chorus_get_available_ideas` | List open ideas in a project |
|
|
178
|
+
| `chorus_add_comment` | Comment on idea/proposal/task/document |
|
|
179
|
+
| `chorus_search_mentionables` | Search for @mentionable users and agents |
|
|
180
|
+
| `chorus_list_projects` | List all projects |
|
|
181
|
+
| `chorus_list_tasks` | List tasks in a project (filterable by status/priority) |
|
|
182
|
+
| `chorus_get_ideas` | List ideas in a project (filterable by status) |
|
|
183
|
+
| `chorus_get_proposals` | List proposals in a project |
|
|
184
|
+
| `chorus_get_documents` | List documents in a project |
|
|
185
|
+
| `chorus_get_document` | Get full document content |
|
|
186
|
+
| `chorus_get_unblocked_tasks` | List tasks ready to start (dependencies resolved) |
|
|
187
|
+
| `chorus_get_activity` | Get project activity stream |
|
|
188
|
+
| `chorus_get_comments` | Get comments on an entity |
|
|
189
|
+
| `chorus_get_elaboration` | Get full elaboration state for an idea |
|
|
190
|
+
| `chorus_get_my_assignments` | Get all claimed ideas and tasks |
|
|
191
|
+
|
|
192
|
+
#### Admin (1 tool)
|
|
193
|
+
|
|
194
|
+
| Tool | Description |
|
|
195
|
+
|------|-------------|
|
|
196
|
+
| `chorus_admin_create_project` | Create a new project |
|
|
197
|
+
|
|
198
|
+
### Commands
|
|
199
|
+
|
|
200
|
+
Bypass LLM for fast status queries:
|
|
201
|
+
|
|
202
|
+
| Command | Description |
|
|
203
|
+
|---------|-------------|
|
|
204
|
+
| `/chorus` or `/chorus status` | Connection status, assignments, unread count |
|
|
205
|
+
| `/chorus tasks` | List your assigned tasks |
|
|
206
|
+
| `/chorus ideas` | List your assigned ideas |
|
|
207
|
+
|
|
208
|
+
## Architecture
|
|
209
|
+
|
|
210
|
+
```
|
|
211
|
+
packages/openclaw-plugin/
|
|
212
|
+
├── package.json # npm package config
|
|
213
|
+
├── openclaw.plugin.json # OpenClaw plugin manifest
|
|
214
|
+
├── tsconfig.json
|
|
215
|
+
└── src/
|
|
216
|
+
├── index.ts # Plugin entry — wires all modules together
|
|
217
|
+
├── config.ts # Zod config schema
|
|
218
|
+
├── mcp-client.ts # MCP Client (lazy connect + 404 auto-reconnect)
|
|
219
|
+
├── sse-listener.ts # SSE long-lived connection + reconnect
|
|
220
|
+
├── event-router.ts # Event → agent action mapping
|
|
221
|
+
├── commands.ts # /chorus commands
|
|
222
|
+
└── tools/
|
|
223
|
+
├── pm-tools.ts # 14 PM workflow tools
|
|
224
|
+
├── dev-tools.ts # 4 Developer tools
|
|
225
|
+
└── common-tools.ts # 21 common/exploration/admin tools
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
### MCP Client (`mcp-client.ts`)
|
|
229
|
+
|
|
230
|
+
Wraps `@modelcontextprotocol/sdk` with:
|
|
231
|
+
- **Lazy connection** — connects on first `callTool()`, not at startup
|
|
232
|
+
- **Auto-reconnect** — detects 404 (session expired), reconnects, retries the call
|
|
233
|
+
- **Status tracking** — `connected | disconnected | connecting | reconnecting`
|
|
234
|
+
|
|
235
|
+
### SSE Listener (`sse-listener.ts`)
|
|
236
|
+
|
|
237
|
+
- Native `fetch()` + `ReadableStream` (not browser EventSource — allows `Authorization` header)
|
|
238
|
+
- `Authorization: Bearer cho_xxx` authentication
|
|
239
|
+
- SSE protocol parsing (`data:` lines → JSON, `:` heartbeat lines ignored)
|
|
240
|
+
- Exponential backoff: 1s → 2s → 4s → 8s → 16s → 30s (max)
|
|
241
|
+
- Calls `onReconnect()` after successful reconnect for notification back-fill
|
|
242
|
+
|
|
243
|
+
### Event Router (`event-router.ts`)
|
|
244
|
+
|
|
245
|
+
- Fetches full notification details via MCP (SSE only sends minimal envelope)
|
|
246
|
+
- Filters by `projectUuids` config
|
|
247
|
+
- Routes by notification `action` type
|
|
248
|
+
- All handlers catch errors internally — never crashes the gateway
|
|
249
|
+
|
|
250
|
+
## Development
|
|
251
|
+
|
|
252
|
+
```bash
|
|
253
|
+
# In the openclaw-plugin directory
|
|
254
|
+
pnpm install
|
|
255
|
+
pnpm dev # Watch mode (tsc --watch)
|
|
256
|
+
pnpm build # Production build
|
|
257
|
+
|
|
258
|
+
# Test with OpenClaw gateway
|
|
259
|
+
cd /path/to/openclaw && node openclaw.mjs gateway
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
No build step needed for development — OpenClaw loads `.ts` files directly via `jiti`.
|
|
263
|
+
|
|
264
|
+
## Troubleshooting
|
|
265
|
+
|
|
266
|
+
### "plugin id mismatch" warning
|
|
267
|
+
Ensure `package.json` `name`, `openclaw.plugin.json` `id`, and `index.ts` `id` all match `@chorus-aidlc/openclaw-plugin`.
|
|
268
|
+
|
|
269
|
+
### "Wake agent failed: HTTP 405"
|
|
270
|
+
Hooks are not enabled. Add to `openclaw.json`:
|
|
271
|
+
```json
|
|
272
|
+
{ "hooks": { "enabled": true, "token": "your-distinct-token" } }
|
|
273
|
+
```
|
|
274
|
+
The `hooks.token` must be different from `gateway.auth.token`.
|
|
275
|
+
|
|
276
|
+
### "Cannot wake agent — gateway.auth.token not configured"
|
|
277
|
+
The plugin couldn't read `hooks.token` from OpenClaw config. Verify your `openclaw.json` has the `hooks` section.
|
|
278
|
+
|
|
279
|
+
### Tools return "undefined" parameters
|
|
280
|
+
OpenClaw tool `execute` signature is `execute(toolCallId, params)` — the first argument is the call ID, not the params object. If you see this, check that all tools use `execute(_id, { param1, param2 })`.
|
|
281
|
+
|
|
282
|
+
### Bedrock "inputSchema.json.type must be object"
|
|
283
|
+
All tool `parameters` must be full JSON Schema with `type: "object"` at the top level, not shorthand `{ key: { type: "string" } }`.
|
|
284
|
+
|
|
285
|
+
## License
|
|
286
|
+
|
|
287
|
+
MIT
|
package/images/slug.png
ADDED
|
Binary file
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "@chorus-aidlc/openclaw-plugin",
|
|
3
|
+
"configSchema": {
|
|
4
|
+
"type": "object",
|
|
5
|
+
"required": ["chorusUrl", "apiKey"],
|
|
6
|
+
"additionalProperties": false,
|
|
7
|
+
"properties": {
|
|
8
|
+
"chorusUrl": {
|
|
9
|
+
"type": "string",
|
|
10
|
+
"description": "Chorus server URL"
|
|
11
|
+
},
|
|
12
|
+
"apiKey": {
|
|
13
|
+
"type": "string",
|
|
14
|
+
"description": "Chorus API Key (cho_ prefix)"
|
|
15
|
+
},
|
|
16
|
+
"projectUuids": {
|
|
17
|
+
"type": "array",
|
|
18
|
+
"items": { "type": "string" },
|
|
19
|
+
"default": [],
|
|
20
|
+
"description": "Project UUIDs to monitor (empty = all)"
|
|
21
|
+
},
|
|
22
|
+
"autoStart": {
|
|
23
|
+
"type": "boolean",
|
|
24
|
+
"default": true,
|
|
25
|
+
"description": "Auto-claim tasks on assignment"
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@chorus-aidlc/openclaw-plugin",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "OpenClaw plugin for Chorus AI-DLC collaboration platform — SSE real-time events + MCP tool integration",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"main": "src/index.ts",
|
|
8
|
+
"scripts": {
|
|
9
|
+
"typecheck": "tsc --noEmit"
|
|
10
|
+
},
|
|
11
|
+
"peerDependencies": {
|
|
12
|
+
"openclaw": ">=2026.0.0"
|
|
13
|
+
},
|
|
14
|
+
"dependencies": {
|
|
15
|
+
"@modelcontextprotocol/sdk": "^1.26.0",
|
|
16
|
+
"zod": "^3.23.0"
|
|
17
|
+
},
|
|
18
|
+
"devDependencies": {
|
|
19
|
+
"typescript": "^5.9.0"
|
|
20
|
+
},
|
|
21
|
+
"openclaw": {
|
|
22
|
+
"extensions": ["src/index.ts"]
|
|
23
|
+
},
|
|
24
|
+
"repository": {
|
|
25
|
+
"type": "git",
|
|
26
|
+
"url": "https://github.com/Chorus-AIDLC/Chorus",
|
|
27
|
+
"directory": "packages/openclaw-plugin"
|
|
28
|
+
},
|
|
29
|
+
"homepage": "https://chorus-aidlc.github.io/Chorus/",
|
|
30
|
+
"keywords": ["openclaw", "chorus", "ai-dlc", "mcp", "plugin", "agent"],
|
|
31
|
+
"files": [
|
|
32
|
+
"src",
|
|
33
|
+
"images",
|
|
34
|
+
"openclaw.plugin.json",
|
|
35
|
+
"README.md"
|
|
36
|
+
]
|
|
37
|
+
}
|
package/src/commands.ts
ADDED
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import type { ChorusMcpClient } from "./mcp-client.js";
|
|
2
|
+
|
|
3
|
+
// ===== Response types from Chorus MCP tools =====
|
|
4
|
+
|
|
5
|
+
interface CheckinResponse {
|
|
6
|
+
checkinTime: string;
|
|
7
|
+
agent: {
|
|
8
|
+
uuid: string;
|
|
9
|
+
name: string;
|
|
10
|
+
roles: string[];
|
|
11
|
+
persona: string | null;
|
|
12
|
+
systemPrompt: string | null;
|
|
13
|
+
};
|
|
14
|
+
assignments: {
|
|
15
|
+
ideas: AssignedIdea[];
|
|
16
|
+
tasks: AssignedTask[];
|
|
17
|
+
};
|
|
18
|
+
pending: {
|
|
19
|
+
ideasCount: number;
|
|
20
|
+
tasksCount: number;
|
|
21
|
+
};
|
|
22
|
+
notifications: {
|
|
23
|
+
unreadCount: number;
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
interface AssignedIdea {
|
|
28
|
+
uuid: string;
|
|
29
|
+
title: string;
|
|
30
|
+
status: string;
|
|
31
|
+
project: { uuid: string; name: string };
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
interface AssignedTask {
|
|
35
|
+
uuid: string;
|
|
36
|
+
title: string;
|
|
37
|
+
status: string;
|
|
38
|
+
priority: string;
|
|
39
|
+
project: { uuid: string; name: string };
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
interface AssignmentsResponse {
|
|
43
|
+
ideas: AssignedIdea[];
|
|
44
|
+
tasks: AssignedTask[];
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// ===== Formatting helpers =====
|
|
48
|
+
|
|
49
|
+
function formatStatus(checkin: CheckinResponse, connectionStatus: string): string {
|
|
50
|
+
const lines: string[] = [
|
|
51
|
+
`Connection: ${connectionStatus}`,
|
|
52
|
+
`Assignments: ${checkin.pending.ideasCount} ideas, ${checkin.pending.tasksCount} tasks`,
|
|
53
|
+
`Notifications: ${checkin.notifications.unreadCount} unread`,
|
|
54
|
+
];
|
|
55
|
+
return lines.join("\n");
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function formatTaskList(tasks: AssignedTask[]): string {
|
|
59
|
+
if (tasks.length === 0) {
|
|
60
|
+
return "No assigned tasks.";
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const lines = tasks.map(
|
|
64
|
+
(t) => `[${t.status}] [${t.priority}] ${t.title} (${t.project.name})`
|
|
65
|
+
);
|
|
66
|
+
return `Assigned tasks (${tasks.length}):\n${lines.join("\n")}`;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function formatIdeaList(ideas: AssignedIdea[]): string {
|
|
70
|
+
if (ideas.length === 0) {
|
|
71
|
+
return "No assigned ideas.";
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const lines = ideas.map(
|
|
75
|
+
(i) => `[${i.status}] ${i.title} (${i.project.name})`
|
|
76
|
+
);
|
|
77
|
+
return `Assigned ideas (${ideas.length}):\n${lines.join("\n")}`;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const HELP_TEXT = [
|
|
81
|
+
"Chorus commands:",
|
|
82
|
+
" /chorus Show connection status and summary",
|
|
83
|
+
" /chorus status Same as above",
|
|
84
|
+
" /chorus tasks List assigned tasks",
|
|
85
|
+
" /chorus ideas List assigned ideas",
|
|
86
|
+
].join("\n");
|
|
87
|
+
|
|
88
|
+
// ===== Registration =====
|
|
89
|
+
|
|
90
|
+
export function registerChorusCommands(
|
|
91
|
+
api: any,
|
|
92
|
+
mcpClient: ChorusMcpClient,
|
|
93
|
+
getStatus: () => string
|
|
94
|
+
): void {
|
|
95
|
+
api.registerCommand({
|
|
96
|
+
name: "chorus",
|
|
97
|
+
description: "Chorus plugin commands: status, tasks, ideas",
|
|
98
|
+
async handler(ctx: { args: string }) {
|
|
99
|
+
const sub = (ctx.args ?? "").trim().toLowerCase();
|
|
100
|
+
|
|
101
|
+
// /chorus or /chorus status
|
|
102
|
+
if (!sub || sub === "status") {
|
|
103
|
+
try {
|
|
104
|
+
const checkin = (await mcpClient.callTool("chorus_checkin", {})) as CheckinResponse;
|
|
105
|
+
return { text: formatStatus(checkin, getStatus()) };
|
|
106
|
+
} catch (err) {
|
|
107
|
+
return { text: `Failed to check in: ${err instanceof Error ? err.message : String(err)}` };
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// /chorus tasks
|
|
112
|
+
if (sub === "tasks") {
|
|
113
|
+
try {
|
|
114
|
+
const data = (await mcpClient.callTool(
|
|
115
|
+
"chorus_get_my_assignments",
|
|
116
|
+
{}
|
|
117
|
+
)) as AssignmentsResponse;
|
|
118
|
+
return { text: formatTaskList(data.tasks) };
|
|
119
|
+
} catch (err) {
|
|
120
|
+
return { text: `Failed to fetch tasks: ${err instanceof Error ? err.message : String(err)}` };
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// /chorus ideas
|
|
125
|
+
if (sub === "ideas") {
|
|
126
|
+
try {
|
|
127
|
+
const data = (await mcpClient.callTool(
|
|
128
|
+
"chorus_get_my_assignments",
|
|
129
|
+
{}
|
|
130
|
+
)) as AssignmentsResponse;
|
|
131
|
+
return { text: formatIdeaList(data.ideas) };
|
|
132
|
+
} catch (err) {
|
|
133
|
+
return { text: `Failed to fetch ideas: ${err instanceof Error ? err.message : String(err)}` };
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Unknown subcommand
|
|
138
|
+
return { text: HELP_TEXT };
|
|
139
|
+
},
|
|
140
|
+
});
|
|
141
|
+
}
|
package/src/config.ts
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
|
|
3
|
+
export const chorusConfigSchema = z.object({
|
|
4
|
+
chorusUrl: z
|
|
5
|
+
.string()
|
|
6
|
+
.url()
|
|
7
|
+
.describe("Chorus server URL (e.g. https://chorus.example.com)"),
|
|
8
|
+
apiKey: z
|
|
9
|
+
.string()
|
|
10
|
+
.startsWith("cho_")
|
|
11
|
+
.describe("Chorus API Key (cho_ prefix)"),
|
|
12
|
+
projectUuids: z
|
|
13
|
+
.array(z.string().uuid())
|
|
14
|
+
.optional()
|
|
15
|
+
.default([])
|
|
16
|
+
.describe("Project UUIDs to monitor. Empty = all projects"),
|
|
17
|
+
autoStart: z
|
|
18
|
+
.boolean()
|
|
19
|
+
.optional()
|
|
20
|
+
.default(true)
|
|
21
|
+
.describe("Auto-claim and start work on task_assigned events"),
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
export type ChorusPluginConfig = z.infer<typeof chorusConfigSchema>;
|