teleportation-cli 1.0.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.
Files changed (54) hide show
  1. package/.claude/hooks/config-loader.mjs +93 -0
  2. package/.claude/hooks/heartbeat.mjs +331 -0
  3. package/.claude/hooks/notification.mjs +35 -0
  4. package/.claude/hooks/permission_request.mjs +307 -0
  5. package/.claude/hooks/post_tool_use.mjs +137 -0
  6. package/.claude/hooks/pre_tool_use.mjs +451 -0
  7. package/.claude/hooks/session-register.mjs +274 -0
  8. package/.claude/hooks/session_end.mjs +256 -0
  9. package/.claude/hooks/session_start.mjs +308 -0
  10. package/.claude/hooks/stop.mjs +277 -0
  11. package/.claude/hooks/user_prompt_submit.mjs +91 -0
  12. package/LICENSE +21 -0
  13. package/README.md +243 -0
  14. package/lib/auth/api-key.js +110 -0
  15. package/lib/auth/credentials.js +341 -0
  16. package/lib/backup/manager.js +461 -0
  17. package/lib/cli/daemon-commands.js +299 -0
  18. package/lib/cli/index.js +303 -0
  19. package/lib/cli/session-commands.js +294 -0
  20. package/lib/cli/snapshot-commands.js +223 -0
  21. package/lib/cli/worktree-commands.js +291 -0
  22. package/lib/config/manager.js +306 -0
  23. package/lib/daemon/lifecycle.js +336 -0
  24. package/lib/daemon/pid-manager.js +160 -0
  25. package/lib/daemon/teleportation-daemon.js +2009 -0
  26. package/lib/handoff/config.js +102 -0
  27. package/lib/handoff/example.js +152 -0
  28. package/lib/handoff/git-handoff.js +351 -0
  29. package/lib/handoff/handoff.js +277 -0
  30. package/lib/handoff/index.js +25 -0
  31. package/lib/handoff/session-state.js +238 -0
  32. package/lib/install/installer.js +555 -0
  33. package/lib/machine-coders/claude-code-adapter.js +329 -0
  34. package/lib/machine-coders/example.js +239 -0
  35. package/lib/machine-coders/gemini-cli-adapter.js +406 -0
  36. package/lib/machine-coders/index.js +103 -0
  37. package/lib/machine-coders/interface.js +168 -0
  38. package/lib/router/classifier.js +251 -0
  39. package/lib/router/example.js +92 -0
  40. package/lib/router/index.js +69 -0
  41. package/lib/router/mech-llms-client.js +277 -0
  42. package/lib/router/models.js +188 -0
  43. package/lib/router/router.js +382 -0
  44. package/lib/session/cleanup.js +100 -0
  45. package/lib/session/metadata.js +258 -0
  46. package/lib/session/mute-checker.js +114 -0
  47. package/lib/session-registry/manager.js +302 -0
  48. package/lib/snapshot/manager.js +390 -0
  49. package/lib/utils/errors.js +166 -0
  50. package/lib/utils/logger.js +148 -0
  51. package/lib/utils/retry.js +155 -0
  52. package/lib/worktree/manager.js +301 -0
  53. package/package.json +66 -0
  54. package/teleportation-cli.cjs +2987 -0
@@ -0,0 +1,329 @@
1
+ /**
2
+ * Claude Code Adapter
3
+ *
4
+ * Adapter for Claude Code CLI. Uses the existing hook system for approvals.
5
+ *
6
+ * Key characteristics:
7
+ * - Hooks handle approval via Teleportation Relay
8
+ * - Supports session resume via --resume
9
+ * - Uses --dangerously-skip-permissions for auto-approve mode
10
+ */
11
+
12
+ import { spawn } from 'child_process';
13
+ import { promisify } from 'util';
14
+ import { exec } from 'child_process';
15
+ import { MachineCoder, CODER_NAMES } from './interface.js';
16
+
17
+ const execAsync = promisify(exec);
18
+
19
+ /**
20
+ * Check if a command exists on the system
21
+ * @param {string} command
22
+ * @returns {Promise<boolean>}
23
+ */
24
+ async function commandExists(command) {
25
+ try {
26
+ await execAsync(`which ${command}`);
27
+ return true;
28
+ } catch {
29
+ return false;
30
+ }
31
+ }
32
+
33
+ /**
34
+ * Claude Code Adapter
35
+ */
36
+ export class ClaudeCodeAdapter extends MachineCoder {
37
+ name = CODER_NAMES.CLAUDE_CODE;
38
+ displayName = 'Claude Code';
39
+
40
+ // Track running processes for stop()
41
+ #runningProcesses = new Map();
42
+
43
+ /**
44
+ * Check if Claude Code CLI is available
45
+ * @returns {Promise<boolean>}
46
+ */
47
+ async isAvailable() {
48
+ return commandExists('claude');
49
+ }
50
+
51
+ /**
52
+ * Get Claude Code status
53
+ * @returns {Promise<Object>}
54
+ */
55
+ async getStatus() {
56
+ const available = await this.isAvailable();
57
+
58
+ if (!available) {
59
+ return { available: false };
60
+ }
61
+
62
+ try {
63
+ const { stdout } = await execAsync('claude --version');
64
+ return {
65
+ available: true,
66
+ version: stdout.trim(),
67
+ };
68
+ } catch {
69
+ return { available: true };
70
+ }
71
+ }
72
+
73
+ /**
74
+ * Execute a task with Claude Code
75
+ *
76
+ * Note: Claude Code uses hooks for approval, so we don't handle
77
+ * onToolCall here. The hooks communicate with Relay directly.
78
+ *
79
+ * @param {Object} options
80
+ * @returns {Promise<Object>}
81
+ */
82
+ async execute(options) {
83
+ const {
84
+ projectPath,
85
+ sessionId,
86
+ prompt,
87
+ onProgress,
88
+ timeoutMs = 600000, // 10 min default
89
+ autoApprove = false,
90
+ } = options;
91
+
92
+ const executionId = `claude-${Date.now()}`;
93
+ const args = [];
94
+
95
+ // Add prompt
96
+ args.push('-p', prompt);
97
+
98
+ // Auto-approve mode (dangerous - skips all permission checks)
99
+ if (autoApprove) {
100
+ args.push('--dangerously-skip-permissions');
101
+ }
102
+
103
+ // Output format for parsing
104
+ args.push('--output-format', 'json');
105
+
106
+ return new Promise((resolve, reject) => {
107
+ const startTime = Date.now();
108
+ let stdout = '';
109
+ let stderr = '';
110
+ const toolCalls = [];
111
+
112
+ const proc = spawn('claude', args, {
113
+ cwd: projectPath,
114
+ env: {
115
+ ...process.env,
116
+ // Pass session ID to hooks
117
+ TELEPORTATION_SESSION_ID: sessionId,
118
+ },
119
+ });
120
+
121
+ this.#runningProcesses.set(executionId, proc);
122
+
123
+ // Timeout handling
124
+ const timeout = setTimeout(() => {
125
+ proc.kill('SIGTERM');
126
+ reject(new Error(`Execution timed out after ${timeoutMs}ms`));
127
+ }, timeoutMs);
128
+
129
+ proc.stdout.on('data', (data) => {
130
+ const chunk = data.toString();
131
+ stdout += chunk;
132
+
133
+ // Try to parse JSON events for progress
134
+ try {
135
+ const lines = chunk.split('\n').filter(l => l.trim());
136
+ for (const line of lines) {
137
+ const event = JSON.parse(line);
138
+
139
+ // Track tool calls
140
+ if (event.type === 'tool_use') {
141
+ toolCalls.push({
142
+ id: event.tool_use_id || `tool-${toolCalls.length}`,
143
+ tool: event.tool_name,
144
+ input: event.tool_input,
145
+ timestamp: Date.now(),
146
+ });
147
+ }
148
+
149
+ onProgress?.({
150
+ type: event.type,
151
+ timestamp: Date.now(),
152
+ data: event,
153
+ });
154
+ }
155
+ } catch {
156
+ // Not JSON, just raw output
157
+ onProgress?.({
158
+ type: 'output',
159
+ timestamp: Date.now(),
160
+ data: { text: chunk },
161
+ });
162
+ }
163
+ });
164
+
165
+ proc.stderr.on('data', (data) => {
166
+ stderr += data.toString();
167
+ onProgress?.({
168
+ type: 'error',
169
+ timestamp: Date.now(),
170
+ data: { text: data.toString() },
171
+ });
172
+ });
173
+
174
+ proc.on('close', (code) => {
175
+ clearTimeout(timeout);
176
+ this.#runningProcesses.delete(executionId);
177
+
178
+ const durationMs = Date.now() - startTime;
179
+
180
+ // Try to parse final JSON output
181
+ let output = stdout;
182
+ let stats = { durationMs };
183
+
184
+ try {
185
+ const result = JSON.parse(stdout);
186
+ output = result.result || result.response || result.output || stdout;
187
+ if (result.usage) {
188
+ stats.tokensUsed = result.usage.total_tokens;
189
+ }
190
+ if (result.cost) {
191
+ stats.cost = result.cost;
192
+ }
193
+ if (result.model) {
194
+ stats.model = result.model;
195
+ }
196
+ } catch {
197
+ // Not JSON, use raw output
198
+ }
199
+
200
+ resolve({
201
+ success: code === 0,
202
+ output,
203
+ error: code !== 0 ? stderr || `Exit code: ${code}` : undefined,
204
+ toolCalls,
205
+ stats,
206
+ executionId,
207
+ });
208
+ });
209
+
210
+ proc.on('error', (err) => {
211
+ clearTimeout(timeout);
212
+ this.#runningProcesses.delete(executionId);
213
+ reject(err);
214
+ });
215
+ });
216
+ }
217
+
218
+ /**
219
+ * Resume a Claude Code session with a new prompt
220
+ * @param {string} sessionId - Claude session ID to resume
221
+ * @param {string} prompt - New prompt
222
+ * @param {Object} options - Additional options
223
+ * @returns {Promise<Object>}
224
+ */
225
+ async resume(sessionId, prompt, options = {}) {
226
+ const {
227
+ projectPath = process.cwd(),
228
+ onProgress,
229
+ timeoutMs = 600000,
230
+ autoApprove = false,
231
+ } = options;
232
+
233
+ const executionId = `claude-resume-${Date.now()}`;
234
+ const args = ['--resume', sessionId, '-p', prompt];
235
+
236
+ if (autoApprove) {
237
+ args.push('--dangerously-skip-permissions');
238
+ }
239
+
240
+ args.push('--output-format', 'json');
241
+
242
+ return new Promise((resolve, reject) => {
243
+ const startTime = Date.now();
244
+ let stdout = '';
245
+ let stderr = '';
246
+ const toolCalls = [];
247
+
248
+ const proc = spawn('claude', args, {
249
+ cwd: projectPath,
250
+ env: {
251
+ ...process.env,
252
+ TELEPORTATION_SESSION_ID: sessionId,
253
+ },
254
+ });
255
+
256
+ this.#runningProcesses.set(executionId, proc);
257
+
258
+ const timeout = setTimeout(() => {
259
+ proc.kill('SIGTERM');
260
+ reject(new Error(`Resume timed out after ${timeoutMs}ms`));
261
+ }, timeoutMs);
262
+
263
+ proc.stdout.on('data', (data) => {
264
+ stdout += data.toString();
265
+ onProgress?.({
266
+ type: 'output',
267
+ timestamp: Date.now(),
268
+ data: { text: data.toString() },
269
+ });
270
+ });
271
+
272
+ proc.stderr.on('data', (data) => {
273
+ stderr += data.toString();
274
+ });
275
+
276
+ proc.on('close', (code) => {
277
+ clearTimeout(timeout);
278
+ this.#runningProcesses.delete(executionId);
279
+
280
+ resolve({
281
+ success: code === 0,
282
+ output: stdout,
283
+ error: code !== 0 ? stderr || `Exit code: ${code}` : undefined,
284
+ toolCalls,
285
+ stats: { durationMs: Date.now() - startTime },
286
+ executionId,
287
+ });
288
+ });
289
+
290
+ proc.on('error', (err) => {
291
+ clearTimeout(timeout);
292
+ this.#runningProcesses.delete(executionId);
293
+ reject(err);
294
+ });
295
+ });
296
+ }
297
+
298
+ /**
299
+ * Stop a running execution
300
+ * @param {string} executionId
301
+ * @returns {Promise<boolean>}
302
+ */
303
+ async stop(executionId) {
304
+ const proc = this.#runningProcesses.get(executionId);
305
+ if (proc) {
306
+ proc.kill('SIGTERM');
307
+ this.#runningProcesses.delete(executionId);
308
+ return true;
309
+ }
310
+ return false;
311
+ }
312
+
313
+ /**
314
+ * Get capabilities
315
+ * @returns {Object}
316
+ */
317
+ getCapabilities() {
318
+ return {
319
+ supportsResume: true,
320
+ supportsStreaming: true,
321
+ hasNativeApproval: true, // Via hooks
322
+ supportsModelSelection: true,
323
+ maxContextTokens: 200000, // Claude 3.5 Sonnet
324
+ supportedTools: ['Bash', 'Read', 'Edit', 'Write', 'Glob', 'Grep', 'LS', 'MultiEdit'],
325
+ };
326
+ }
327
+ }
328
+
329
+ export default ClaudeCodeAdapter;
@@ -0,0 +1,239 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Machine Coders Example / Test Script
4
+ *
5
+ * Demonstrates the unified machine coder interface.
6
+ *
7
+ * Usage:
8
+ * node lib/machine-coders/example.js status
9
+ * node lib/machine-coders/example.js list
10
+ * node lib/machine-coders/example.js run <coder> <prompt>
11
+ * node lib/machine-coders/example.js test
12
+ */
13
+
14
+ import { getAvailableCoders, getBestCoder } from './index.js';
15
+ import { ClaudeCodeAdapter } from './claude-code-adapter.js';
16
+ import { GeminiCliAdapter } from './gemini-cli-adapter.js';
17
+
18
+ const projectPath = process.cwd();
19
+ const sessionId = process.env.TELEPORT_SESSION_ID || `test-${Date.now()}`;
20
+
21
+ async function showStatus() {
22
+ console.log('=== Machine Coders Status ===\n');
23
+
24
+ const coders = [
25
+ new ClaudeCodeAdapter(),
26
+ new GeminiCliAdapter(),
27
+ ];
28
+
29
+ for (const coder of coders) {
30
+ const status = await coder.getStatus();
31
+ const caps = coder.getCapabilities();
32
+
33
+ console.log(`${coder.displayName} (${coder.name}):`);
34
+ console.log(` Available: ${status.available ? '✓' : '✗'}`);
35
+ if (status.version) {
36
+ console.log(` Version: ${status.version}`);
37
+ }
38
+ if (status.models) {
39
+ console.log(` Models: ${status.models.join(', ')}`);
40
+ }
41
+ console.log(` Capabilities:`);
42
+ console.log(` - Resume: ${caps.supportsResume ? '✓' : '✗'}`);
43
+ console.log(` - Streaming: ${caps.supportsStreaming ? '✓' : '✗'}`);
44
+ console.log(` - Native Approval: ${caps.hasNativeApproval ? '✓' : '✗'}`);
45
+ console.log(` - Model Selection: ${caps.supportsModelSelection ? '✓' : '✗'}`);
46
+ console.log(` - Max Context: ${(caps.maxContextTokens / 1000).toFixed(0)}K tokens`);
47
+ console.log(` - Tools: ${caps.supportedTools.join(', ')}`);
48
+ console.log('');
49
+ }
50
+ }
51
+
52
+ async function listAvailable() {
53
+ console.log('=== Available Machine Coders ===\n');
54
+
55
+ const available = await getAvailableCoders();
56
+
57
+ if (available.length === 0) {
58
+ console.log('No machine coders available on this system.');
59
+ console.log('Install one of: claude, gemini, goose');
60
+ return;
61
+ }
62
+
63
+ console.log(`Found ${available.length} available coder(s):\n`);
64
+
65
+ for (const coder of available) {
66
+ const status = await coder.getStatus();
67
+ console.log(` • ${coder.displayName}`);
68
+ console.log(` Name: ${coder.name}`);
69
+ if (status.version) {
70
+ console.log(` Version: ${status.version}`);
71
+ }
72
+ }
73
+
74
+ console.log('');
75
+
76
+ // Show best coder
77
+ const best = await getBestCoder();
78
+ if (best) {
79
+ console.log(`Best coder (default priority): ${best.displayName}`);
80
+ }
81
+
82
+ const bestLargeContext = await getBestCoder({ largeContext: true });
83
+ if (bestLargeContext) {
84
+ console.log(`Best for large context: ${bestLargeContext.displayName}`);
85
+ }
86
+ }
87
+
88
+ async function runCoder(coderName, prompt) {
89
+ console.log(`=== Running ${coderName} ===\n`);
90
+ console.log(`Project: ${projectPath}`);
91
+ console.log(`Session: ${sessionId}`);
92
+ console.log(`Prompt: ${prompt}`);
93
+ console.log('');
94
+
95
+ let coder;
96
+ switch (coderName) {
97
+ case 'claude':
98
+ case 'claude-code':
99
+ coder = new ClaudeCodeAdapter();
100
+ break;
101
+ case 'gemini':
102
+ case 'gemini-cli':
103
+ coder = new GeminiCliAdapter();
104
+ break;
105
+ default:
106
+ console.error(`Unknown coder: ${coderName}`);
107
+ console.log('Available: claude, gemini');
108
+ process.exit(1);
109
+ }
110
+
111
+ const available = await coder.isAvailable();
112
+ if (!available) {
113
+ console.error(`${coder.displayName} is not available on this system.`);
114
+ process.exit(1);
115
+ }
116
+
117
+ console.log(`Using: ${coder.displayName}`);
118
+ console.log('');
119
+ console.log('--- Output ---');
120
+
121
+ try {
122
+ const result = await coder.execute({
123
+ projectPath,
124
+ sessionId,
125
+ prompt,
126
+ autoApprove: true, // For testing
127
+ onProgress: (event) => {
128
+ switch (event.type) {
129
+ case 'init':
130
+ console.log(`[init] Session: ${event.data.sessionId}, Model: ${event.data.model}`);
131
+ break;
132
+ case 'message':
133
+ if (event.data.role === 'assistant') {
134
+ console.log(`[assistant] ${event.data.content?.substring(0, 200)}...`);
135
+ }
136
+ break;
137
+ case 'tool_use':
138
+ console.log(`[tool] ${event.data.tool}: ${JSON.stringify(event.data.input).substring(0, 100)}`);
139
+ break;
140
+ case 'tool_result':
141
+ console.log(`[result] ${event.data.status}: ${event.data.output?.substring(0, 100)}`);
142
+ break;
143
+ case 'error':
144
+ console.log(`[error] ${event.data.message}`);
145
+ break;
146
+ case 'done':
147
+ console.log(`[done] Tokens: ${event.data.tokensUsed}, Duration: ${event.data.durationMs}ms`);
148
+ break;
149
+ }
150
+ },
151
+ onToolCall: async (tool) => {
152
+ // For testing, auto-allow all tools
153
+ console.log(`[approval] Tool ${tool.tool} auto-allowed (test mode)`);
154
+ return 'allow';
155
+ },
156
+ });
157
+
158
+ console.log('');
159
+ console.log('--- Result ---');
160
+ console.log(`Success: ${result.success}`);
161
+ console.log(`Tool Calls: ${result.toolCalls.length}`);
162
+ if (result.stats) {
163
+ console.log(`Tokens: ${result.stats.tokensUsed || 'N/A'}`);
164
+ console.log(`Duration: ${result.stats.durationMs}ms`);
165
+ console.log(`Model: ${result.stats.model || 'N/A'}`);
166
+ }
167
+ if (result.error) {
168
+ console.log(`Error: ${result.error}`);
169
+ }
170
+ console.log('');
171
+ console.log('Output:');
172
+ console.log(result.output?.substring(0, 500) || '(no output)');
173
+
174
+ } catch (err) {
175
+ console.error(`\nExecution failed: ${err.message}`);
176
+ process.exit(1);
177
+ }
178
+ }
179
+
180
+ async function runTest() {
181
+ console.log('=== Machine Coders Test ===\n');
182
+
183
+ // Test 1: Check availability
184
+ console.log('Test 1: Checking availability...');
185
+ const available = await getAvailableCoders();
186
+ console.log(` Found ${available.length} available coder(s)`);
187
+
188
+ if (available.length === 0) {
189
+ console.log('\n✗ No coders available - cannot run further tests');
190
+ return;
191
+ }
192
+
193
+ // Test 2: Get best coder
194
+ console.log('\nTest 2: Getting best coder...');
195
+ const best = await getBestCoder();
196
+ console.log(` Best coder: ${best?.displayName || 'none'}`);
197
+
198
+ // Test 3: Get capabilities
199
+ console.log('\nTest 3: Checking capabilities...');
200
+ for (const coder of available) {
201
+ const caps = coder.getCapabilities();
202
+ console.log(` ${coder.displayName}:`);
203
+ console.log(` Max context: ${(caps.maxContextTokens / 1000).toFixed(0)}K`);
204
+ console.log(` Native approval: ${caps.hasNativeApproval}`);
205
+ }
206
+
207
+ console.log('\n✓ All tests passed');
208
+ }
209
+
210
+ // Main
211
+ const command = process.argv[2] || 'status';
212
+ const arg1 = process.argv[3];
213
+ const arg2 = process.argv.slice(4).join(' ');
214
+
215
+ switch (command) {
216
+ case 'status':
217
+ await showStatus();
218
+ break;
219
+ case 'list':
220
+ await listAvailable();
221
+ break;
222
+ case 'run':
223
+ if (!arg1 || !arg2) {
224
+ console.log('Usage: node example.js run <coder> <prompt>');
225
+ console.log('Example: node example.js run gemini "List files in current directory"');
226
+ process.exit(1);
227
+ }
228
+ await runCoder(arg1, arg2);
229
+ break;
230
+ case 'test':
231
+ await runTest();
232
+ break;
233
+ default:
234
+ console.log('Usage:');
235
+ console.log(' node lib/machine-coders/example.js status');
236
+ console.log(' node lib/machine-coders/example.js list');
237
+ console.log(' node lib/machine-coders/example.js run <coder> <prompt>');
238
+ console.log(' node lib/machine-coders/example.js test');
239
+ }