claude-tempo 0.3.0 → 0.4.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 +9 -12
- package/dist/cli/commands.d.ts +10 -0
- package/dist/cli/commands.js +229 -29
- package/dist/cli/config-command.js +14 -0
- package/dist/cli/mcp.d.ts +8 -0
- package/dist/cli/mcp.js +61 -0
- package/dist/cli/preflight.js +5 -14
- package/dist/cli.js +25 -4
- package/dist/config.d.ts +5 -0
- package/dist/config.js +8 -0
- package/dist/server.js +1 -1
- package/dist/workflows/session.js +21 -2
- package/package.json +1 -1
- package/workflow-bundle.js +22 -3
package/README.md
CHANGED
|
@@ -98,7 +98,8 @@ claude-tempo <command> [options]
|
|
|
98
98
|
| `start [ensemble]` | Start a player session |
|
|
99
99
|
| `status [ensemble]` | Show active sessions and Temporal health |
|
|
100
100
|
| `config` | Configure Temporal connection settings (interactive or `set`/`show`) |
|
|
101
|
-
| `
|
|
101
|
+
| `stop [ensemble]` | Stop sessions (`-n <name>` for one, `--all` for everything) |
|
|
102
|
+
| `init` | Register claude-tempo MCP server globally (`--project` for per-directory) |
|
|
102
103
|
| `preflight` | Run environment checks |
|
|
103
104
|
| `help` | Show usage info |
|
|
104
105
|
|
|
@@ -180,19 +181,15 @@ Verifies your environment: Node.js >= 18, Temporal reachable, `claude` on PATH,
|
|
|
180
181
|
|
|
181
182
|
### `claude-tempo init`
|
|
182
183
|
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
```
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
"claude-tempo": {
|
|
189
|
-
"command": "npx",
|
|
190
|
-
"args": ["claude-tempo-server"]
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
}
|
|
184
|
+
Registers the claude-tempo MCP server globally so it's available in every Claude Code session:
|
|
185
|
+
|
|
186
|
+
```bash
|
|
187
|
+
claude-tempo init # global install (recommended)
|
|
188
|
+
claude-tempo init --project # per-directory .mcp.json instead
|
|
194
189
|
```
|
|
195
190
|
|
|
191
|
+
If the `claude` CLI is not available, falls back to creating `.mcp.json` in the current directory.
|
|
192
|
+
|
|
196
193
|
## MCP tools
|
|
197
194
|
|
|
198
195
|
These tools are available inside Claude Code sessions connected to claude-tempo:
|
package/dist/cli/commands.d.ts
CHANGED
|
@@ -15,6 +15,7 @@ interface StatusOpts extends CliOverrides {
|
|
|
15
15
|
export declare function status(opts: StatusOpts): Promise<void>;
|
|
16
16
|
interface InitOpts {
|
|
17
17
|
dir: string;
|
|
18
|
+
project?: boolean;
|
|
18
19
|
}
|
|
19
20
|
export declare function init(opts: InitOpts): Promise<void>;
|
|
20
21
|
interface ServerOpts extends CliOverrides {
|
|
@@ -32,6 +33,15 @@ interface DownOpts extends CliOverrides {
|
|
|
32
33
|
dir: string;
|
|
33
34
|
}
|
|
34
35
|
export declare function down(opts: DownOpts): Promise<void>;
|
|
36
|
+
interface StopOpts extends CliOverrides {
|
|
37
|
+
/** Stop a specific player by name. */
|
|
38
|
+
name?: string;
|
|
39
|
+
/** Stop all sessions in this ensemble. */
|
|
40
|
+
ensemble?: string;
|
|
41
|
+
/** Stop every session across all ensembles. */
|
|
42
|
+
all?: boolean;
|
|
43
|
+
}
|
|
44
|
+
export declare function stop(opts: StopOpts): Promise<void>;
|
|
35
45
|
export declare function help(): void;
|
|
36
46
|
export declare function version(): void;
|
|
37
47
|
export {};
|
package/dist/cli/commands.js
CHANGED
|
@@ -39,6 +39,7 @@ exports.init = init;
|
|
|
39
39
|
exports.server = server;
|
|
40
40
|
exports.up = up;
|
|
41
41
|
exports.down = down;
|
|
42
|
+
exports.stop = stop;
|
|
42
43
|
exports.help = help;
|
|
43
44
|
exports.version = version;
|
|
44
45
|
const fs_1 = require("fs");
|
|
@@ -48,7 +49,9 @@ const client_1 = require("@temporalio/client");
|
|
|
48
49
|
const spawn_1 = require("../spawn");
|
|
49
50
|
const config_1 = require("../config");
|
|
50
51
|
const connection_1 = require("../connection");
|
|
52
|
+
const signals_1 = require("../workflows/signals");
|
|
51
53
|
const preflight_1 = require("./preflight");
|
|
54
|
+
const mcp_1 = require("./mcp");
|
|
52
55
|
const out = __importStar(require("./output"));
|
|
53
56
|
/** Package root is two levels up from dist/cli/ */
|
|
54
57
|
const PACKAGE_ROOT = (0, path_1.resolve)(__dirname, '..', '..');
|
|
@@ -223,7 +226,36 @@ async function status(opts) {
|
|
|
223
226
|
console.log();
|
|
224
227
|
}
|
|
225
228
|
async function init(opts) {
|
|
226
|
-
|
|
229
|
+
if (opts.project) {
|
|
230
|
+
// Per-project .mcp.json mode
|
|
231
|
+
return initProject(opts.dir);
|
|
232
|
+
}
|
|
233
|
+
// Default: global install via `claude mcp add`
|
|
234
|
+
if ((0, mcp_1.isGlobalMcpRegistered)()) {
|
|
235
|
+
out.success('claude-tempo already registered globally');
|
|
236
|
+
out.log(` ${out.dim('claude mcp list -s user')}`);
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
239
|
+
const claudePath = (0, spawn_1.resolveClaudePath)();
|
|
240
|
+
if (claudePath === 'claude') {
|
|
241
|
+
out.warn('claude binary not found — falling back to project-level .mcp.json');
|
|
242
|
+
return initProject(opts.dir);
|
|
243
|
+
}
|
|
244
|
+
if ((0, mcp_1.addGlobalMcp)()) {
|
|
245
|
+
out.success('Registered claude-tempo globally (user scope)');
|
|
246
|
+
out.log(` ${out.dim('Available in all Claude Code sessions')}`);
|
|
247
|
+
}
|
|
248
|
+
else {
|
|
249
|
+
out.warn('Failed to register globally — falling back to project-level .mcp.json');
|
|
250
|
+
return initProject(opts.dir);
|
|
251
|
+
}
|
|
252
|
+
out.log(`\nNext steps:`);
|
|
253
|
+
out.log(` 1. Start Temporal: ${out.dim('temporal server start-dev')}`);
|
|
254
|
+
out.log(` 2. Start conductor: ${out.dim('claude-tempo conduct')}`);
|
|
255
|
+
}
|
|
256
|
+
/** Per-project .mcp.json install (legacy, used with --project flag). */
|
|
257
|
+
function initProject(dir) {
|
|
258
|
+
const mcpPath = (0, path_1.join)(dir, '.mcp.json');
|
|
227
259
|
const entry = {
|
|
228
260
|
command: 'npx',
|
|
229
261
|
args: ['claude-tempo-server'],
|
|
@@ -236,7 +268,6 @@ async function init(opts) {
|
|
|
236
268
|
out.log(` ${out.dim(mcpPath)}`);
|
|
237
269
|
return;
|
|
238
270
|
}
|
|
239
|
-
// Merge into existing config
|
|
240
271
|
existing.mcpServers = existing.mcpServers || {};
|
|
241
272
|
existing.mcpServers['claude-tempo'] = entry;
|
|
242
273
|
(0, fs_1.writeFileSync)(mcpPath, JSON.stringify(existing, null, 2) + '\n');
|
|
@@ -426,22 +457,13 @@ async function up(opts) {
|
|
|
426
457
|
}
|
|
427
458
|
// Step 3: Register search attributes
|
|
428
459
|
registerSearchAttributes(config.temporalAddress, config.temporalNamespace);
|
|
429
|
-
// Step 4:
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
if ((0, fs_1.existsSync)(mcpPath)) {
|
|
433
|
-
try {
|
|
434
|
-
const mcp = JSON.parse((0, fs_1.readFileSync)(mcpPath, 'utf8'));
|
|
435
|
-
mcpExists = !!mcp?.mcpServers?.['claude-tempo'];
|
|
436
|
-
}
|
|
437
|
-
catch { /* invalid */ }
|
|
438
|
-
}
|
|
439
|
-
if (mcpExists) {
|
|
440
|
-
out.check('.mcp.json configured', true);
|
|
460
|
+
// Step 4: Register MCP server if needed
|
|
461
|
+
if ((0, mcp_1.isMcpConfigured)(process.cwd())) {
|
|
462
|
+
out.check('MCP configured', true);
|
|
441
463
|
}
|
|
442
464
|
else {
|
|
443
465
|
await init({ dir: process.cwd() });
|
|
444
|
-
out.check('
|
|
466
|
+
out.check('MCP configured', true);
|
|
445
467
|
}
|
|
446
468
|
// Always forward all resolved Temporal settings to child processes.
|
|
447
469
|
const temporalEnvVars = {
|
|
@@ -525,7 +547,9 @@ async function down(opts) {
|
|
|
525
547
|
out.warn('Could not terminate active sessions');
|
|
526
548
|
}
|
|
527
549
|
}
|
|
528
|
-
// Step 2:
|
|
550
|
+
// Step 2: Kill bridge processes via PID files
|
|
551
|
+
killBridgeProcesses();
|
|
552
|
+
// Step 3: Stop Temporal server
|
|
529
553
|
if (temporalUp) {
|
|
530
554
|
// Find and kill the temporal dev server process
|
|
531
555
|
try {
|
|
@@ -545,18 +569,26 @@ async function down(opts) {
|
|
|
545
569
|
else {
|
|
546
570
|
out.log(` ${out.dim('Temporal not running')}`);
|
|
547
571
|
}
|
|
548
|
-
// Step
|
|
572
|
+
// Step 4: Remove MCP config (global + project-level)
|
|
549
573
|
if (opts.removeMcp) {
|
|
574
|
+
// Remove global registration
|
|
575
|
+
if ((0, mcp_1.isGlobalMcpRegistered)()) {
|
|
576
|
+
if ((0, mcp_1.removeGlobalMcp)()) {
|
|
577
|
+
out.success('Removed claude-tempo from global MCP config');
|
|
578
|
+
}
|
|
579
|
+
else {
|
|
580
|
+
out.warn('Could not remove global MCP entry');
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
// Also remove project-level .mcp.json entry if present
|
|
550
584
|
const mcpPath = (0, path_1.join)(opts.dir, '.mcp.json');
|
|
551
585
|
if ((0, fs_1.existsSync)(mcpPath)) {
|
|
552
586
|
try {
|
|
553
587
|
const existing = JSON.parse((0, fs_1.readFileSync)(mcpPath, 'utf8'));
|
|
554
588
|
if (existing?.mcpServers?.['claude-tempo']) {
|
|
555
589
|
delete existing.mcpServers['claude-tempo'];
|
|
556
|
-
// If no other MCP servers remain, remove the file entirely
|
|
557
590
|
if (Object.keys(existing.mcpServers).length === 0) {
|
|
558
|
-
|
|
559
|
-
unlinkSync(mcpPath);
|
|
591
|
+
(0, fs_1.unlinkSync)(mcpPath);
|
|
560
592
|
out.success('Removed .mcp.json (no other servers configured)');
|
|
561
593
|
}
|
|
562
594
|
else {
|
|
@@ -564,23 +596,186 @@ async function down(opts) {
|
|
|
564
596
|
out.success('Removed claude-tempo from .mcp.json');
|
|
565
597
|
}
|
|
566
598
|
}
|
|
567
|
-
else {
|
|
568
|
-
out.log(` ${out.dim('.mcp.json has no claude-tempo entry')}`);
|
|
569
|
-
}
|
|
570
599
|
}
|
|
571
600
|
catch {
|
|
572
601
|
out.warn(`Could not update ${mcpPath}`);
|
|
573
602
|
}
|
|
574
603
|
}
|
|
575
|
-
else {
|
|
576
|
-
out.log(` ${out.dim('No .mcp.json found')}`);
|
|
577
|
-
}
|
|
578
604
|
}
|
|
579
605
|
console.log();
|
|
580
606
|
out.success('claude-tempo is shut down');
|
|
581
607
|
out.log(` ${out.dim('Temporal data preserved in ~/.claude-tempo/ (delete manually to reset)')}`);
|
|
582
608
|
console.log();
|
|
583
609
|
}
|
|
610
|
+
async function stop(opts) {
|
|
611
|
+
const config = (0, config_1.getConfig)(opts);
|
|
612
|
+
if (!opts.name && !opts.ensemble && !opts.all) {
|
|
613
|
+
out.error('Specify what to stop:');
|
|
614
|
+
out.log(` ${out.dim('claude-tempo stop <ensemble>')} Stop all sessions in an ensemble`);
|
|
615
|
+
out.log(` ${out.dim('claude-tempo stop <ensemble> -n <name>')} Stop a specific session`);
|
|
616
|
+
out.log(` ${out.dim('claude-tempo stop --all')} Stop everything`);
|
|
617
|
+
process.exit(1);
|
|
618
|
+
}
|
|
619
|
+
let connection;
|
|
620
|
+
try {
|
|
621
|
+
connection = await Promise.race([
|
|
622
|
+
(0, connection_1.createTemporalConnection)(config),
|
|
623
|
+
new Promise((_, reject) => setTimeout(() => reject(new Error('timeout')), 3000)),
|
|
624
|
+
]);
|
|
625
|
+
}
|
|
626
|
+
catch {
|
|
627
|
+
out.error(`Cannot connect to Temporal at ${config.temporalAddress}`);
|
|
628
|
+
process.exit(1);
|
|
629
|
+
return;
|
|
630
|
+
}
|
|
631
|
+
const client = new client_1.Client({ connection, namespace: config.temporalNamespace });
|
|
632
|
+
if (opts.name) {
|
|
633
|
+
// Stop a specific player by name (optionally scoped to ensemble)
|
|
634
|
+
await stopByName(client, opts.name, config, opts.ensemble);
|
|
635
|
+
}
|
|
636
|
+
else {
|
|
637
|
+
// Stop multiple sessions (--ensemble or --all)
|
|
638
|
+
let query = 'WorkflowType = "claudeSessionWorkflow" AND ExecutionStatus = "Running"';
|
|
639
|
+
if (opts.ensemble) {
|
|
640
|
+
query += ` AND ClaudeTempoEnsemble = "${opts.ensemble}"`;
|
|
641
|
+
}
|
|
642
|
+
let stopped = 0;
|
|
643
|
+
for await (const wf of client.workflow.list({ query })) {
|
|
644
|
+
try {
|
|
645
|
+
const handle = client.workflow.getHandle(wf.workflowId);
|
|
646
|
+
await handle.signal(signals_1.shutdownSignal);
|
|
647
|
+
stopped++;
|
|
648
|
+
out.log(` ${out.dim('stopped')} ${wf.workflowId}`);
|
|
649
|
+
}
|
|
650
|
+
catch {
|
|
651
|
+
// already closed
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
// Clean up PID files
|
|
655
|
+
if (opts.ensemble || opts.all) {
|
|
656
|
+
killBridgeProcesses();
|
|
657
|
+
}
|
|
658
|
+
if (stopped > 0) {
|
|
659
|
+
out.success(`Stopped ${stopped} session${stopped !== 1 ? 's' : ''}`);
|
|
660
|
+
}
|
|
661
|
+
else {
|
|
662
|
+
out.log(opts.ensemble
|
|
663
|
+
? `No active sessions in ensemble "${opts.ensemble}".`
|
|
664
|
+
: 'No active sessions found.');
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
await connection.close();
|
|
668
|
+
}
|
|
669
|
+
async function stopByName(client, name, config, ensemble) {
|
|
670
|
+
// Find the workflow by player name via search attribute
|
|
671
|
+
let query = `WorkflowType = "claudeSessionWorkflow" AND ExecutionStatus = "Running" AND ClaudeTempoPlayerId = "${name}"`;
|
|
672
|
+
if (ensemble) {
|
|
673
|
+
query += ` AND ClaudeTempoEnsemble = "${ensemble}"`;
|
|
674
|
+
}
|
|
675
|
+
let found = false;
|
|
676
|
+
for await (const wf of client.workflow.list({ query })) {
|
|
677
|
+
found = true;
|
|
678
|
+
const handle = client.workflow.getHandle(wf.workflowId);
|
|
679
|
+
// Check if this is a conductor — warn about it
|
|
680
|
+
try {
|
|
681
|
+
const metadata = (await handle.query('getMetadata'));
|
|
682
|
+
if (metadata.isConductor) {
|
|
683
|
+
out.warn(`"${name}" is a conductor session`);
|
|
684
|
+
}
|
|
685
|
+
// Notify the conductor that this session was stopped (if it's not the conductor itself)
|
|
686
|
+
if (!metadata.isConductor && metadata.ensemble) {
|
|
687
|
+
try {
|
|
688
|
+
const conductorWfId = (0, config_1.conductorWorkflowId)(metadata.ensemble);
|
|
689
|
+
const conductorHandle = client.workflow.getHandle(conductorWfId);
|
|
690
|
+
await conductorHandle.signal(signals_1.playerReportSignal, {
|
|
691
|
+
playerId: name,
|
|
692
|
+
text: 'Session stopped by CLI',
|
|
693
|
+
type: 'result',
|
|
694
|
+
});
|
|
695
|
+
}
|
|
696
|
+
catch {
|
|
697
|
+
// No conductor or conductor not running — fine
|
|
698
|
+
}
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
catch {
|
|
702
|
+
// Query failed — proceed with shutdown anyway
|
|
703
|
+
}
|
|
704
|
+
// Send shutdown signal (graceful)
|
|
705
|
+
try {
|
|
706
|
+
await handle.signal(signals_1.shutdownSignal);
|
|
707
|
+
out.success(`Stopped "${name}"`);
|
|
708
|
+
}
|
|
709
|
+
catch {
|
|
710
|
+
out.warn(`Could not signal "${name}" — it may have already exited`);
|
|
711
|
+
}
|
|
712
|
+
// Try to kill bridge process via PID file
|
|
713
|
+
killBridgePid(name);
|
|
714
|
+
break;
|
|
715
|
+
}
|
|
716
|
+
if (!found) {
|
|
717
|
+
out.error(`No active session found with name "${name}"`);
|
|
718
|
+
process.exit(1);
|
|
719
|
+
}
|
|
720
|
+
}
|
|
721
|
+
/**
|
|
722
|
+
* Kill a bridge process by reading its PID file from logs/.
|
|
723
|
+
* Cleans up the PID file after.
|
|
724
|
+
*/
|
|
725
|
+
function killBridgePid(name) {
|
|
726
|
+
const pidPath = (0, path_1.join)(process.cwd(), 'logs', `${name}.pid`);
|
|
727
|
+
if (!(0, fs_1.existsSync)(pidPath))
|
|
728
|
+
return;
|
|
729
|
+
try {
|
|
730
|
+
const pid = parseInt((0, fs_1.readFileSync)(pidPath, 'utf8').trim(), 10);
|
|
731
|
+
if (!isNaN(pid)) {
|
|
732
|
+
try {
|
|
733
|
+
process.kill(pid);
|
|
734
|
+
out.log(` ${out.dim(`Killed bridge process (pid ${pid})`)}`);
|
|
735
|
+
}
|
|
736
|
+
catch {
|
|
737
|
+
// Process already dead
|
|
738
|
+
}
|
|
739
|
+
}
|
|
740
|
+
(0, fs_1.unlinkSync)(pidPath);
|
|
741
|
+
}
|
|
742
|
+
catch {
|
|
743
|
+
// PID file unreadable — ignore
|
|
744
|
+
}
|
|
745
|
+
}
|
|
746
|
+
/**
|
|
747
|
+
* Kill all bridge processes found in logs/*.pid and clean up PID files.
|
|
748
|
+
*/
|
|
749
|
+
function killBridgeProcesses() {
|
|
750
|
+
const logsDir = (0, path_1.join)(process.cwd(), 'logs');
|
|
751
|
+
if (!(0, fs_1.existsSync)(logsDir))
|
|
752
|
+
return;
|
|
753
|
+
try {
|
|
754
|
+
const pidFiles = (0, fs_1.readdirSync)(logsDir).filter(f => f.endsWith('.pid'));
|
|
755
|
+
for (const pidFile of pidFiles) {
|
|
756
|
+
const pidPath = (0, path_1.join)(logsDir, pidFile);
|
|
757
|
+
try {
|
|
758
|
+
const pid = parseInt((0, fs_1.readFileSync)(pidPath, 'utf8').trim(), 10);
|
|
759
|
+
if (!isNaN(pid)) {
|
|
760
|
+
try {
|
|
761
|
+
process.kill(pid);
|
|
762
|
+
out.log(` ${out.dim(`Killed bridge process ${pidFile.replace('.pid', '')} (pid ${pid})`)}`);
|
|
763
|
+
}
|
|
764
|
+
catch {
|
|
765
|
+
// already dead
|
|
766
|
+
}
|
|
767
|
+
}
|
|
768
|
+
(0, fs_1.unlinkSync)(pidPath);
|
|
769
|
+
}
|
|
770
|
+
catch {
|
|
771
|
+
// unreadable — skip
|
|
772
|
+
}
|
|
773
|
+
}
|
|
774
|
+
}
|
|
775
|
+
catch {
|
|
776
|
+
// logs dir unreadable
|
|
777
|
+
}
|
|
778
|
+
}
|
|
584
779
|
function help() {
|
|
585
780
|
console.log(`
|
|
586
781
|
${out.bold('claude-tempo')} — Multi-session Claude Code coordination via Temporal
|
|
@@ -597,9 +792,10 @@ ${out.bold('Commands:')}
|
|
|
597
792
|
${out.cyan('server')} Start the Temporal dev server and register search attributes
|
|
598
793
|
${out.cyan('conduct')} [ensemble] Start a conductor session (one per ensemble)
|
|
599
794
|
${out.cyan('start')} [ensemble] Start a player session
|
|
795
|
+
${out.cyan('stop')} [ensemble] Stop sessions (-n <name> for one, or --all)
|
|
600
796
|
${out.cyan('status')} [ensemble] Show active sessions and Temporal health
|
|
601
797
|
${out.cyan('config')} Configure Temporal connection settings
|
|
602
|
-
${out.cyan('init')}
|
|
798
|
+
${out.cyan('init')} Register MCP server globally (or --project for .mcp.json)
|
|
603
799
|
${out.cyan('preflight')} Run preflight checks only
|
|
604
800
|
${out.cyan('help')} Show this help message
|
|
605
801
|
|
|
@@ -612,10 +808,13 @@ ${out.bold('Connection options (all commands):')}
|
|
|
612
808
|
|
|
613
809
|
${out.bold('Other options:')}
|
|
614
810
|
-n, --name <name> Set the session window name (start/conduct/up only)
|
|
615
|
-
--agent <claude|copilot> Agent type to spawn (default:
|
|
811
|
+
--agent <claude|copilot> Agent type to spawn (default: from config; start/conduct)
|
|
616
812
|
--skip-preflight Skip preflight checks (start/conduct only)
|
|
617
813
|
--background Run Temporal in background (server only)
|
|
618
|
-
--
|
|
814
|
+
--project Use per-project .mcp.json instead of global (init only)
|
|
815
|
+
--keep-mcp Don't remove MCP config (down only)
|
|
816
|
+
--all Stop all sessions (stop only)
|
|
817
|
+
--ensemble <name> Target a specific ensemble (stop only)
|
|
619
818
|
-d, --dir <path> Target directory (default: cwd)
|
|
620
819
|
|
|
621
820
|
${out.bold('Config command:')}
|
|
@@ -646,6 +845,7 @@ ${out.bold('Environment:')}
|
|
|
646
845
|
TEMPORAL_API_KEY Temporal API key
|
|
647
846
|
TEMPORAL_TLS_CERT_PATH Path to TLS client certificate
|
|
648
847
|
TEMPORAL_TLS_KEY_PATH Path to TLS client key
|
|
848
|
+
CLAUDE_TEMPO_DEFAULT_AGENT Default agent type: claude or copilot (fallback: claude)
|
|
649
849
|
`);
|
|
650
850
|
}
|
|
651
851
|
function version() {
|
|
@@ -126,6 +126,12 @@ async function configInteractive() {
|
|
|
126
126
|
config.temporalTlsCertPath = await ask('TLS cert path', existing.temporalTlsCertPath);
|
|
127
127
|
config.temporalTlsKeyPath = await ask('TLS key path', existing.temporalTlsKeyPath);
|
|
128
128
|
}
|
|
129
|
+
// Default agent type
|
|
130
|
+
const agentChoice = await choose('Default agent', ['claude', 'copilot']);
|
|
131
|
+
if (agentChoice === 'copilot') {
|
|
132
|
+
config.defaultAgent = 'copilot';
|
|
133
|
+
}
|
|
134
|
+
// Don't set defaultAgent if claude — it's the default, keeps config clean
|
|
129
135
|
(0, config_1.saveConfigFile)(config);
|
|
130
136
|
out.success(`Saved to ${config_1.CONFIG_FILE_PATH}`);
|
|
131
137
|
// Test connection
|
|
@@ -165,6 +171,8 @@ function configSet(key, value) {
|
|
|
165
171
|
temporalTlsKeyPath: 'temporalTlsKeyPath',
|
|
166
172
|
'temporal-tls-key': 'temporalTlsKeyPath',
|
|
167
173
|
'temporal-tls-key-path': 'temporalTlsKeyPath',
|
|
174
|
+
defaultAgent: 'defaultAgent',
|
|
175
|
+
'default-agent': 'defaultAgent',
|
|
168
176
|
};
|
|
169
177
|
const configKey = keyMap[key];
|
|
170
178
|
if (!configKey) {
|
|
@@ -172,6 +180,11 @@ function configSet(key, value) {
|
|
|
172
180
|
out.log(` Valid keys: ${Object.keys(keyMap).join(', ')}`);
|
|
173
181
|
process.exit(1);
|
|
174
182
|
}
|
|
183
|
+
// Validate agent type
|
|
184
|
+
if (configKey === 'defaultAgent' && value !== 'claude' && value !== 'copilot') {
|
|
185
|
+
out.error(`Invalid agent type: "${value}". Must be "claude" or "copilot".`);
|
|
186
|
+
process.exit(1);
|
|
187
|
+
}
|
|
175
188
|
config[configKey] = value;
|
|
176
189
|
(0, config_1.saveConfigFile)(config);
|
|
177
190
|
out.success(`Set ${configKey} = ${configKey.includes('Key') ? '****' : value}`);
|
|
@@ -186,6 +199,7 @@ function configShow() {
|
|
|
186
199
|
{ key: 'temporalApiKey', configKey: 'temporalApiKey' },
|
|
187
200
|
{ key: 'temporalTlsCertPath', configKey: 'temporalTlsCertPath' },
|
|
188
201
|
{ key: 'temporalTlsKeyPath', configKey: 'temporalTlsKeyPath' },
|
|
202
|
+
{ key: 'defaultAgent', configKey: 'defaultAgent' },
|
|
189
203
|
];
|
|
190
204
|
out.log(` Config file: ${out.dim(config_1.CONFIG_FILE_PATH)}`);
|
|
191
205
|
console.log();
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/** Check if claude-tempo is registered in `claude mcp list` (global user scope). */
|
|
2
|
+
export declare function isGlobalMcpRegistered(): boolean;
|
|
3
|
+
/** Register claude-tempo globally via `claude mcp add`. */
|
|
4
|
+
export declare function addGlobalMcp(): boolean;
|
|
5
|
+
/** Remove claude-tempo from global MCP config via `claude mcp remove`. */
|
|
6
|
+
export declare function removeGlobalMcp(): boolean;
|
|
7
|
+
/** Check if claude-tempo MCP is configured (global or project-level). */
|
|
8
|
+
export declare function isMcpConfigured(projectDir: string): boolean;
|
package/dist/cli/mcp.js
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.isGlobalMcpRegistered = isGlobalMcpRegistered;
|
|
4
|
+
exports.addGlobalMcp = addGlobalMcp;
|
|
5
|
+
exports.removeGlobalMcp = removeGlobalMcp;
|
|
6
|
+
exports.isMcpConfigured = isMcpConfigured;
|
|
7
|
+
const fs_1 = require("fs");
|
|
8
|
+
const child_process_1 = require("child_process");
|
|
9
|
+
const path_1 = require("path");
|
|
10
|
+
/** Check if claude-tempo is registered in `claude mcp list` (global user scope). */
|
|
11
|
+
function isGlobalMcpRegistered() {
|
|
12
|
+
try {
|
|
13
|
+
const output = (0, child_process_1.execFileSync)('claude', ['mcp', 'list', '-s', 'user'], {
|
|
14
|
+
encoding: 'utf8',
|
|
15
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
16
|
+
});
|
|
17
|
+
return output.includes('claude-tempo');
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
return false;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
/** Register claude-tempo globally via `claude mcp add`. */
|
|
24
|
+
function addGlobalMcp() {
|
|
25
|
+
try {
|
|
26
|
+
(0, child_process_1.execFileSync)('claude', [
|
|
27
|
+
'mcp', 'add', 'claude-tempo', '-s', 'user',
|
|
28
|
+
'--', 'npx', 'claude-tempo-server',
|
|
29
|
+
], { stdio: ['ignore', 'ignore', 'pipe'] });
|
|
30
|
+
return true;
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
return false;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
/** Remove claude-tempo from global MCP config via `claude mcp remove`. */
|
|
37
|
+
function removeGlobalMcp() {
|
|
38
|
+
try {
|
|
39
|
+
(0, child_process_1.execFileSync)('claude', [
|
|
40
|
+
'mcp', 'remove', 'claude-tempo', '-s', 'user',
|
|
41
|
+
], { stdio: ['ignore', 'ignore', 'pipe'] });
|
|
42
|
+
return true;
|
|
43
|
+
}
|
|
44
|
+
catch {
|
|
45
|
+
return false;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
/** Check if claude-tempo MCP is configured (global or project-level). */
|
|
49
|
+
function isMcpConfigured(projectDir) {
|
|
50
|
+
if (isGlobalMcpRegistered())
|
|
51
|
+
return true;
|
|
52
|
+
const mcpPath = (0, path_1.join)(projectDir, '.mcp.json');
|
|
53
|
+
if ((0, fs_1.existsSync)(mcpPath)) {
|
|
54
|
+
try {
|
|
55
|
+
const mcp = JSON.parse((0, fs_1.readFileSync)(mcpPath, 'utf8'));
|
|
56
|
+
return !!mcp?.mcpServers?.['claude-tempo'];
|
|
57
|
+
}
|
|
58
|
+
catch { /* invalid json */ }
|
|
59
|
+
}
|
|
60
|
+
return false;
|
|
61
|
+
}
|
package/dist/cli/preflight.js
CHANGED
|
@@ -34,12 +34,11 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
34
34
|
})();
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
36
|
exports.runPreflight = runPreflight;
|
|
37
|
-
const fs_1 = require("fs");
|
|
38
37
|
const child_process_1 = require("child_process");
|
|
39
|
-
const path_1 = require("path");
|
|
40
38
|
const config_1 = require("../config");
|
|
41
39
|
const connection_1 = require("../connection");
|
|
42
40
|
const spawn_1 = require("../spawn");
|
|
41
|
+
const mcp_1 = require("./mcp");
|
|
43
42
|
const out = __importStar(require("./output"));
|
|
44
43
|
function whichSync(cmd) {
|
|
45
44
|
const bin = process.platform === 'win32' ? 'where' : 'which';
|
|
@@ -87,19 +86,11 @@ async function runPreflight(opts) {
|
|
|
87
86
|
out.check('claude-tempo-server found', serverOk, serverOk ? serverPath : 'not on PATH');
|
|
88
87
|
if (!serverOk)
|
|
89
88
|
errors.push('claude-tempo-server not found on PATH. Run: npm install -g claude-tempo');
|
|
90
|
-
// 5. MCP config
|
|
91
|
-
const
|
|
92
|
-
|
|
93
|
-
if ((0, fs_1.existsSync)(mcpPath)) {
|
|
94
|
-
try {
|
|
95
|
-
const mcp = JSON.parse((0, fs_1.readFileSync)(mcpPath, 'utf8'));
|
|
96
|
-
mcpOk = !!mcp?.mcpServers?.['claude-tempo'];
|
|
97
|
-
}
|
|
98
|
-
catch { /* invalid json */ }
|
|
99
|
-
}
|
|
100
|
-
out.check('.mcp.json configured', mcpOk, mcpOk ? mcpPath : 'missing or no claude-tempo entry');
|
|
89
|
+
// 5. MCP config (global or project-level)
|
|
90
|
+
const mcpOk = (0, mcp_1.isMcpConfigured)(opts.dir);
|
|
91
|
+
out.check('MCP configured', mcpOk, mcpOk ? 'global or project' : 'not found');
|
|
101
92
|
if (!mcpOk)
|
|
102
|
-
warnings.push(
|
|
93
|
+
warnings.push('No claude-tempo MCP config found. Run: claude-tempo init');
|
|
103
94
|
console.log();
|
|
104
95
|
return { ok: errors.length === 0, errors, warnings };
|
|
105
96
|
}
|
package/dist/cli.js
CHANGED
|
@@ -47,6 +47,8 @@ function parseArgs(argv) {
|
|
|
47
47
|
skipPreflight: false,
|
|
48
48
|
background: false,
|
|
49
49
|
keepMcp: false,
|
|
50
|
+
all: false,
|
|
51
|
+
project: false,
|
|
50
52
|
};
|
|
51
53
|
let i = 0;
|
|
52
54
|
while (i < argv.length) {
|
|
@@ -81,6 +83,15 @@ function parseArgs(argv) {
|
|
|
81
83
|
else if (arg === '--keep-mcp') {
|
|
82
84
|
result.keepMcp = true;
|
|
83
85
|
}
|
|
86
|
+
else if (arg === '--all') {
|
|
87
|
+
result.all = true;
|
|
88
|
+
}
|
|
89
|
+
else if (arg === '--project') {
|
|
90
|
+
result.project = true;
|
|
91
|
+
}
|
|
92
|
+
else if (arg === '--ensemble' && i + 1 < argv.length) {
|
|
93
|
+
result.ensemble = argv[++i];
|
|
94
|
+
}
|
|
84
95
|
else if (arg === '--agent' && i + 1 < argv.length) {
|
|
85
96
|
const val = argv[++i];
|
|
86
97
|
if (val !== 'claude' && val !== 'copilot') {
|
|
@@ -124,6 +135,8 @@ async function main() {
|
|
|
124
135
|
const args = parseArgs(process.argv.slice(2));
|
|
125
136
|
const ensemble = args.positional[1] || process.env[config_1.ENV.ENSEMBLE] || 'default';
|
|
126
137
|
const overrides = cliOverrides(args);
|
|
138
|
+
// Resolve the default agent from config (only needed for commands that use it)
|
|
139
|
+
const resolvedAgent = () => args.agent ?? (0, config_1.getConfig)(overrides).defaultAgent;
|
|
127
140
|
switch (args.command) {
|
|
128
141
|
case 'conduct':
|
|
129
142
|
await (0, commands_1.start)({
|
|
@@ -131,7 +144,7 @@ async function main() {
|
|
|
131
144
|
conductor: true,
|
|
132
145
|
name: args.name,
|
|
133
146
|
skipPreflight: args.skipPreflight,
|
|
134
|
-
agent:
|
|
147
|
+
agent: resolvedAgent(),
|
|
135
148
|
dir: args.dir,
|
|
136
149
|
...overrides,
|
|
137
150
|
});
|
|
@@ -142,7 +155,7 @@ async function main() {
|
|
|
142
155
|
conductor: false,
|
|
143
156
|
name: args.name,
|
|
144
157
|
skipPreflight: args.skipPreflight,
|
|
145
|
-
agent:
|
|
158
|
+
agent: resolvedAgent(),
|
|
146
159
|
dir: args.dir,
|
|
147
160
|
...overrides,
|
|
148
161
|
});
|
|
@@ -159,6 +172,14 @@ async function main() {
|
|
|
159
172
|
...overrides,
|
|
160
173
|
});
|
|
161
174
|
break;
|
|
175
|
+
case 'stop':
|
|
176
|
+
await (0, commands_1.stop)({
|
|
177
|
+
name: args.name,
|
|
178
|
+
ensemble: args.positional[1],
|
|
179
|
+
all: args.all || undefined,
|
|
180
|
+
...overrides,
|
|
181
|
+
});
|
|
182
|
+
break;
|
|
162
183
|
case 'down':
|
|
163
184
|
await (0, commands_1.down)({
|
|
164
185
|
removeMcp: !args.keepMcp,
|
|
@@ -170,12 +191,12 @@ async function main() {
|
|
|
170
191
|
await (0, commands_1.up)({
|
|
171
192
|
ensemble,
|
|
172
193
|
name: args.name,
|
|
173
|
-
agent:
|
|
194
|
+
agent: resolvedAgent(),
|
|
174
195
|
...overrides,
|
|
175
196
|
});
|
|
176
197
|
break;
|
|
177
198
|
case 'init':
|
|
178
|
-
await (0, commands_1.init)({ dir: args.dir });
|
|
199
|
+
await (0, commands_1.init)({ dir: args.dir, project: args.project });
|
|
179
200
|
break;
|
|
180
201
|
case 'config':
|
|
181
202
|
await (0, config_command_1.configCommand)(args.positional);
|