claude-tempo 0.1.2 → 0.2.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/CLAUDE.md +1 -0
- package/README.md +98 -5
- package/dist/cli/commands.d.ts +1 -0
- package/dist/cli/commands.js +55 -17
- package/dist/cli.js +10 -0
- package/dist/copilot-bridge.d.ts +21 -0
- package/dist/copilot-bridge.js +388 -0
- package/dist/server.js +25 -19
- package/dist/tools/recruit.js +92 -22
- package/package.json +40 -1
- package/workflow-bundle.js +7680 -0
package/CLAUDE.md
CHANGED
|
@@ -16,6 +16,7 @@ claude-tempo is an MCP server that enables multiple Claude Code sessions to coor
|
|
|
16
16
|
```
|
|
17
17
|
src/
|
|
18
18
|
├── server.ts # MCP server entry point
|
|
19
|
+
├── copilot-bridge.ts # Copilot SDK bridge for Copilot CLI players
|
|
19
20
|
├── worker.ts # Temporal worker setup
|
|
20
21
|
├── workflows/
|
|
21
22
|
│ ├── session.ts # claude-session workflow
|
package/README.md
CHANGED
|
@@ -155,7 +155,8 @@ The `claude-tempo` CLI handles setup, session management, and diagnostics.
|
|
|
155
155
|
|
|
156
156
|
```
|
|
157
157
|
--temporal-address <addr> Temporal server address (default: localhost:7233)
|
|
158
|
-
-n, --name <name> Set the
|
|
158
|
+
-n, --name <name> Set the player name for the session (start/conduct/up)
|
|
159
|
+
--agent <claude|copilot> Agent type to spawn (default: claude; start/conduct)
|
|
159
160
|
--skip-preflight Skip preflight checks (start/conduct)
|
|
160
161
|
--background, -d Run Temporal in background (server only)
|
|
161
162
|
--dir <path> Target directory for init (default: cwd)
|
|
@@ -186,6 +187,7 @@ ok You're all set!
|
|
|
186
187
|
|
|
187
188
|
What next?
|
|
188
189
|
claude-tempo start myband Add a player session
|
|
190
|
+
claude-tempo start myband --agent copilot -n copilot-1 Add a Copilot player
|
|
189
191
|
claude-tempo status myband See who's active
|
|
190
192
|
Or ask the conductor to recruit players for you
|
|
191
193
|
```
|
|
@@ -331,13 +333,13 @@ The `recruit` tool and CLI automatically detect and open sessions in your termin
|
|
|
331
333
|
| Terminal.app | `.command` file | — | — |
|
|
332
334
|
| gnome-terminal | — | `--` flag | — |
|
|
333
335
|
| konsole / xterm | — | `-e` flag | — |
|
|
334
|
-
| cmd.exe / PowerShell | — | — | `
|
|
336
|
+
| cmd.exe / PowerShell | — | — | `start` command |
|
|
335
337
|
|
|
336
338
|
All macOS terminals use approaches that preserve the user's full shell environment (fish, zsh, bash) including node version managers (fnm, nvm).
|
|
337
339
|
|
|
338
340
|
### Session naming
|
|
339
341
|
|
|
340
|
-
Sessions start with a random 8-character hex ID.
|
|
342
|
+
Sessions start with a random 8-character hex ID. You can set a name at launch with `-n` or use `set_name` inside a session:
|
|
341
343
|
|
|
342
344
|
- Names are stored as Temporal search attributes (`ClaudeTempoPlayerId`) and updated in-place — no workflow restart needed
|
|
343
345
|
- Other players use the name to send messages via `cue` and discover sessions via `ensemble`
|
|
@@ -354,6 +356,7 @@ Sessions start with a random 8-character hex ID. Use `set_name` to give a sessio
|
|
|
354
356
|
| `CLAUDE_TEMPO_TASK_QUEUE` | `claude-tempo` | Task queue name |
|
|
355
357
|
| `CLAUDE_TEMPO_ENSEMBLE` | `default` | Ensemble name (isolates groups of players) |
|
|
356
358
|
| `CLAUDE_TEMPO_CONDUCTOR` | `false` | Set to `true` to enable conductor mode |
|
|
359
|
+
| `CLAUDE_TEMPO_PLAYER_NAME` | *(random hex)* | Set a player name on startup (used by `-n` flag) |
|
|
357
360
|
|
|
358
361
|
## Development
|
|
359
362
|
|
|
@@ -392,9 +395,99 @@ When a Claude Code session crashes or is closed without graceful shutdown, its T
|
|
|
392
395
|
|
|
393
396
|
This means you don't need to manually clean up crashed sessions — just `cue` the dead player and the system handles the rest.
|
|
394
397
|
|
|
395
|
-
##
|
|
398
|
+
## Copilot CLI integration (experimental)
|
|
396
399
|
|
|
397
|
-
|
|
400
|
+
GitHub Copilot CLI sessions can join an ensemble via the **Copilot bridge**. The bridge uses the [Copilot SDK](https://github.com/github/copilot-sdk) to spawn a Copilot session with claude-tempo as an MCP server, and injects incoming messages as prompts.
|
|
401
|
+
|
|
402
|
+
### Setup
|
|
403
|
+
|
|
404
|
+
The Copilot SDK is an optional dependency — install it only if you want Copilot support:
|
|
405
|
+
|
|
406
|
+
```bash
|
|
407
|
+
npm install @github/copilot-sdk
|
|
408
|
+
```
|
|
409
|
+
|
|
410
|
+
You also need:
|
|
411
|
+
- [GitHub Copilot CLI](https://docs.github.com/en/copilot/github-copilot-in-the-cli) installed and authenticated
|
|
412
|
+
- An active GitHub Copilot subscription
|
|
413
|
+
|
|
414
|
+
### Starting a Copilot player
|
|
415
|
+
|
|
416
|
+
```bash
|
|
417
|
+
# Via CLI (recommended):
|
|
418
|
+
claude-tempo start --agent copilot -n copilot-dev
|
|
419
|
+
|
|
420
|
+
# Or directly with env vars (Linux/macOS):
|
|
421
|
+
CLAUDE_TEMPO_ENSEMBLE=default COPILOT_BRIDGE_NAME=copilot-dev npx ts-node src/copilot-bridge.ts
|
|
422
|
+
|
|
423
|
+
# Or directly with env vars (Windows PowerShell):
|
|
424
|
+
$env:TEMPORAL_ADDRESS="localhost:7233"; $env:CLAUDE_TEMPO_ENSEMBLE="default"; $env:COPILOT_BRIDGE_NAME="copilot-dev"; npx ts-node src/copilot-bridge.ts
|
|
425
|
+
|
|
426
|
+
# Or from any session in the ensemble, recruit one:
|
|
427
|
+
# "Recruit a copilot session named 'copilot-dev' with agent copilot"
|
|
428
|
+
```
|
|
429
|
+
|
|
430
|
+
The CLI `--agent` flag and the `recruit` tool's `agent` parameter both accept `"claude"` (default) or `"copilot"`.
|
|
431
|
+
|
|
432
|
+
### Shell shortcuts
|
|
433
|
+
|
|
434
|
+
Add these functions to your shell profile to simplify launching Copilot bridge sessions:
|
|
435
|
+
|
|
436
|
+
**Linux/macOS** — add to `~/.bashrc` or `~/.zshrc`:
|
|
437
|
+
|
|
438
|
+
```bash
|
|
439
|
+
copilot-tempo() {
|
|
440
|
+
CLAUDE_TEMPO_ENSEMBLE="${1:-default}" COPILOT_BRIDGE_NAME="${2}" \
|
|
441
|
+
npx ts-node /path/to/claude-tempo/src/copilot-bridge.ts
|
|
442
|
+
}
|
|
443
|
+
```
|
|
444
|
+
|
|
445
|
+
**Windows** — add to your PowerShell `$PROFILE`:
|
|
446
|
+
|
|
447
|
+
```powershell
|
|
448
|
+
function copilot-tempo($ensemble = "default", $name = "") {
|
|
449
|
+
$env:TEMPORAL_ADDRESS = "localhost:7233"
|
|
450
|
+
$env:CLAUDE_TEMPO_ENSEMBLE = $ensemble
|
|
451
|
+
$env:COPILOT_BRIDGE_NAME = $name
|
|
452
|
+
npx ts-node C:\path\to\claude-tempo\src\copilot-bridge.ts
|
|
453
|
+
$env:CLAUDE_TEMPO_ENSEMBLE = ""
|
|
454
|
+
$env:COPILOT_BRIDGE_NAME = ""
|
|
455
|
+
}
|
|
456
|
+
```
|
|
457
|
+
|
|
458
|
+
Usage:
|
|
459
|
+
|
|
460
|
+
```bash
|
|
461
|
+
copilot-tempo # join "default" ensemble, auto-generated name
|
|
462
|
+
copilot-tempo my-project copilot-1 # join "my-project" ensemble as "copilot-1"
|
|
463
|
+
```
|
|
464
|
+
|
|
465
|
+
### How it works
|
|
466
|
+
|
|
467
|
+
1. The bridge spawns a Copilot CLI session via the SDK with claude-tempo configured as an MCP server
|
|
468
|
+
2. The MCP server registers the session as a Temporal workflow (same as Claude Code players)
|
|
469
|
+
3. An initial prompt is sent to trigger MCP server initialization (the SDK lazily starts MCP servers)
|
|
470
|
+
4. The bridge polls the workflow for pending messages every 2 seconds
|
|
471
|
+
5. When messages arrive, they're injected as prompts via `session.sendAndWait()`
|
|
472
|
+
6. The Copilot session can use all claude-tempo tools (`ensemble`, `cue`, `report`, etc.)
|
|
473
|
+
|
|
474
|
+
### Environment variables
|
|
475
|
+
|
|
476
|
+
| Variable | Default | Description |
|
|
477
|
+
|----------|---------|-------------|
|
|
478
|
+
| `COPILOT_BRIDGE_NAME` | *(none)* | Player name (calls `set_name` automatically) |
|
|
479
|
+
| `COPILOT_BRIDGE_MODEL` | *(Copilot default)* | Model override for the Copilot session |
|
|
480
|
+
| `GITHUB_TOKEN` | *(logged-in user)* | GitHub auth token |
|
|
481
|
+
|
|
482
|
+
### Limitations
|
|
483
|
+
|
|
484
|
+
- **`recruit` requires manual acknowledgment (Claude backend)**: Recruited Claude Code sessions use `--dangerously-load-development-channels` to enable channel-based message delivery. Claude Code shows an interactive confirmation prompt that must be manually acknowledged (press Enter) in the spawned terminal window. This will be resolved once claude-tempo is published as an approved channel plugin. The Copilot backend does not have this limitation.
|
|
485
|
+
- **No interactive access** — Copilot bridge sessions run in the background. Unlike Claude Code sessions where you can chat directly, bridge sessions only respond to cues from other players. To send messages to a bridge session, use `cue` from another player or signal the workflow directly via the Temporal CLI.
|
|
486
|
+
- **Conductor polling latency** — Copilot conductors poll for messages every 2 seconds, unlike Claude Code conductors which receive instant channel notifications. This adds slight latency to orchestration.
|
|
487
|
+
- **No push-based message delivery** — the bridge polls for messages (2s interval), unlike Claude Code sessions which receive instant channel notifications.
|
|
488
|
+
- **Copilot sessions must be spawned via the bridge** to participate (not standalone Copilot CLI).
|
|
489
|
+
- **The `@github/copilot-sdk` adds ~243MB** to node_modules when installed.
|
|
490
|
+
- **Node 20+ required for Copilot features** — the `@github/copilot-sdk` requires Node.js 20 or later. The rest of claude-tempo works on Node 18+.
|
|
398
491
|
|
|
399
492
|
## License
|
|
400
493
|
|
package/dist/cli/commands.d.ts
CHANGED
package/dist/cli/commands.js
CHANGED
|
@@ -85,25 +85,61 @@ async function start(opts) {
|
|
|
85
85
|
// No existing conductor — proceed normally
|
|
86
86
|
}
|
|
87
87
|
}
|
|
88
|
-
out.log(`Starting ${out.bold(role)} in ensemble ${out.cyan(opts.ensemble)}`);
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
88
|
+
out.log(`Starting ${out.bold(role)} in ensemble ${out.cyan(opts.ensemble)}${opts.agent === 'copilot' ? out.dim(' (copilot)') : ''}`);
|
|
89
|
+
if (opts.agent === 'copilot') {
|
|
90
|
+
// Spawn copilot-bridge as a detached headless subprocess
|
|
91
|
+
const isDev = __filename.endsWith('.ts');
|
|
92
|
+
const cmd = isDev ? 'npx' : 'node';
|
|
93
|
+
const cmdArgs = isDev
|
|
94
|
+
? ['ts-node', (0, path_1.resolve)(__dirname, '..', '..', 'src', 'copilot-bridge.ts')]
|
|
95
|
+
: [(0, path_1.resolve)(__dirname, '..', 'copilot-bridge.js')];
|
|
96
|
+
// Log bridge output for debugging
|
|
97
|
+
const fs = require('fs');
|
|
98
|
+
const logName = opts.name || `copilot-${Date.now()}`;
|
|
99
|
+
const logPath = (0, path_1.join)(workDir, 'logs', `${logName}.log`);
|
|
100
|
+
fs.mkdirSync((0, path_1.join)(workDir, 'logs'), { recursive: true });
|
|
101
|
+
const logFd = fs.openSync(logPath, 'a');
|
|
102
|
+
let child;
|
|
103
|
+
try {
|
|
104
|
+
child = (0, child_process_1.spawn)(cmd, cmdArgs, {
|
|
105
|
+
cwd: workDir,
|
|
106
|
+
detached: true,
|
|
107
|
+
stdio: ['ignore', logFd, logFd],
|
|
108
|
+
env: {
|
|
109
|
+
...process.env,
|
|
110
|
+
CLAUDE_TEMPO_ENSEMBLE: opts.ensemble,
|
|
111
|
+
COPILOT_BRIDGE_NAME: opts.name || '',
|
|
112
|
+
TEMPORAL_ADDRESS: opts.temporalAddress,
|
|
113
|
+
...(opts.conductor ? { CLAUDE_TEMPO_CONDUCTOR: 'true' } : {}),
|
|
114
|
+
},
|
|
115
|
+
});
|
|
116
|
+
child.unref();
|
|
117
|
+
}
|
|
118
|
+
finally {
|
|
119
|
+
fs.closeSync(logFd);
|
|
120
|
+
}
|
|
121
|
+
out.success(`Launched copilot bridge${opts.name ? ` "${opts.name}"` : ''} (pid ${child.pid ?? 'unknown'})`);
|
|
101
122
|
}
|
|
102
|
-
|
|
103
|
-
|
|
123
|
+
else {
|
|
124
|
+
const claudeArgs = [
|
|
125
|
+
'--dangerously-skip-permissions',
|
|
126
|
+
'--dangerously-load-development-channels', 'server:claude-tempo',
|
|
127
|
+
];
|
|
128
|
+
if (opts.name) {
|
|
129
|
+
claudeArgs.push('-n', opts.name);
|
|
130
|
+
}
|
|
131
|
+
const envVars = {
|
|
132
|
+
CLAUDE_TEMPO_ENSEMBLE: opts.ensemble,
|
|
133
|
+
};
|
|
134
|
+
if (opts.conductor) {
|
|
135
|
+
envVars.CLAUDE_TEMPO_CONDUCTOR = 'true';
|
|
136
|
+
}
|
|
137
|
+
if (opts.name) {
|
|
138
|
+
envVars.CLAUDE_TEMPO_PLAYER_NAME = opts.name;
|
|
139
|
+
}
|
|
140
|
+
const { pid } = (0, spawn_1.spawnInTerminal)(claudeArgs, workDir, envVars);
|
|
141
|
+
out.success(`Launched ${role} session${opts.name ? ` "${opts.name}"` : ''} (pid ${pid ?? 'unknown'})`);
|
|
104
142
|
}
|
|
105
|
-
const { pid } = (0, spawn_1.spawnInTerminal)(claudeArgs, workDir, envVars);
|
|
106
|
-
out.success(`Launched ${role} session${opts.name ? ` "${opts.name}"` : ''} (pid ${pid ?? 'unknown'})`);
|
|
107
143
|
out.log(` Ensemble: ${opts.ensemble}`);
|
|
108
144
|
out.log(` Directory: ${workDir}`);
|
|
109
145
|
out.log(`\nCheck status: ${out.dim('claude-tempo status ' + opts.ensemble)}`);
|
|
@@ -541,6 +577,7 @@ ${out.bold('Commands:')}
|
|
|
541
577
|
${out.bold('Options:')}
|
|
542
578
|
--temporal-address <addr> Temporal server address (default: localhost:7233)
|
|
543
579
|
-n, --name <name> Set the session window name (start/conduct/up only)
|
|
580
|
+
--agent <claude|copilot> Agent type to spawn (default: claude; start/conduct)
|
|
544
581
|
--skip-preflight Skip preflight checks (start/conduct only)
|
|
545
582
|
--background Run Temporal in background (server only)
|
|
546
583
|
--keep-mcp Don't remove .mcp.json entry (down only)
|
|
@@ -554,6 +591,7 @@ ${out.bold('Typical workflow:')}
|
|
|
554
591
|
${out.dim('claude-tempo server')} Start Temporal (once, keep running)
|
|
555
592
|
${out.dim('claude-tempo conduct myband')} Start a conductor
|
|
556
593
|
${out.dim('claude-tempo start myband')} Add player sessions
|
|
594
|
+
${out.dim('claude-tempo start myband --agent copilot -n copilot-1')} Add a Copilot player
|
|
557
595
|
${out.dim('claude-tempo status myband')} Check who's active
|
|
558
596
|
|
|
559
597
|
${out.bold('Environment:')}
|
package/dist/cli.js
CHANGED
|
@@ -68,6 +68,14 @@ function parseArgs(argv) {
|
|
|
68
68
|
else if (arg === '--keep-mcp') {
|
|
69
69
|
result.keepMcp = true;
|
|
70
70
|
}
|
|
71
|
+
else if (arg === '--agent' && i + 1 < argv.length) {
|
|
72
|
+
const val = argv[++i];
|
|
73
|
+
if (val !== 'claude' && val !== 'copilot') {
|
|
74
|
+
out.error(`Invalid agent type: "${val}". Must be "claude" or "copilot".`);
|
|
75
|
+
process.exit(1);
|
|
76
|
+
}
|
|
77
|
+
result.agent = val;
|
|
78
|
+
}
|
|
71
79
|
else if (arg === '--help' || arg === '-h') {
|
|
72
80
|
result.command = 'help';
|
|
73
81
|
}
|
|
@@ -100,6 +108,7 @@ async function main() {
|
|
|
100
108
|
temporalAddress: args.temporalAddress,
|
|
101
109
|
name: args.name,
|
|
102
110
|
skipPreflight: args.skipPreflight,
|
|
111
|
+
agent: args.agent ?? 'claude',
|
|
103
112
|
});
|
|
104
113
|
break;
|
|
105
114
|
case 'start':
|
|
@@ -109,6 +118,7 @@ async function main() {
|
|
|
109
118
|
temporalAddress: args.temporalAddress,
|
|
110
119
|
name: args.name,
|
|
111
120
|
skipPreflight: args.skipPreflight,
|
|
121
|
+
agent: args.agent ?? 'claude',
|
|
112
122
|
});
|
|
113
123
|
break;
|
|
114
124
|
case 'status':
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copilot Bridge — allows GitHub Copilot CLI sessions to participate
|
|
3
|
+
* as players in a claude-tempo ensemble.
|
|
4
|
+
*
|
|
5
|
+
* The bridge:
|
|
6
|
+
* 1. Spawns a Copilot CLI session via the Copilot SDK
|
|
7
|
+
* 2. Configures it with claude-tempo as an MCP server (so it gets all tools)
|
|
8
|
+
* 3. Polls the Temporal workflow for pending messages
|
|
9
|
+
* 4. Injects messages as prompts via the SDK
|
|
10
|
+
*
|
|
11
|
+
* Usage:
|
|
12
|
+
* npx ts-node src/copilot-bridge.ts
|
|
13
|
+
*
|
|
14
|
+
* Environment variables:
|
|
15
|
+
* CLAUDE_TEMPO_ENSEMBLE — ensemble name (default: "default")
|
|
16
|
+
* CLAUDE_TEMPO_PLAYER_NAME — player ID for workflow registration (set by spawner for deterministic workflow IDs)
|
|
17
|
+
* COPILOT_BRIDGE_NAME — player name for set_name (optional)
|
|
18
|
+
* COPILOT_BRIDGE_MODEL — model to use (optional)
|
|
19
|
+
* GITHUB_TOKEN — GitHub auth token (optional, uses logged-in user by default)
|
|
20
|
+
*/
|
|
21
|
+
export {};
|