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 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 session window name (start/conduct/up)
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 | — | — | `shell:true` |
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. Use `set_name` to give a session a human-readable name:
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
- ## Known limitations
398
+ ## Copilot CLI integration (experimental)
396
399
 
397
- - **`recruit` requires manual acknowledgment**: Recruited 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.
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
 
@@ -4,6 +4,7 @@ interface StartOpts {
4
4
  temporalAddress: string;
5
5
  name?: string;
6
6
  skipPreflight?: boolean;
7
+ agent: 'claude' | 'copilot';
7
8
  }
8
9
  export declare function start(opts: StartOpts): Promise<void>;
9
10
  interface StatusOpts {
@@ -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
- const claudeArgs = [
90
- '--dangerously-skip-permissions',
91
- '--dangerously-load-development-channels', 'server:claude-tempo',
92
- ];
93
- if (opts.name) {
94
- claudeArgs.push('-n', opts.name);
95
- }
96
- const envVars = {
97
- CLAUDE_TEMPO_ENSEMBLE: opts.ensemble,
98
- };
99
- if (opts.conductor) {
100
- envVars.CLAUDE_TEMPO_CONDUCTOR = 'true';
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
- if (opts.name) {
103
- envVars.CLAUDE_TEMPO_PLAYER_NAME = opts.name;
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 {};