teleportation-cli 1.1.5 → 1.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.
Files changed (42) hide show
  1. package/.claude/hooks/permission_request.mjs +326 -59
  2. package/.claude/hooks/post_tool_use.mjs +90 -0
  3. package/.claude/hooks/pre_tool_use.mjs +212 -293
  4. package/.claude/hooks/session-register.mjs +89 -104
  5. package/.claude/hooks/session_end.mjs +41 -42
  6. package/.claude/hooks/session_start.mjs +45 -60
  7. package/.claude/hooks/stop.mjs +752 -99
  8. package/.claude/hooks/user_prompt_submit.mjs +26 -3
  9. package/lib/cli/daemon-commands.js +1 -1
  10. package/lib/cli/teleport-commands.js +469 -0
  11. package/lib/daemon/daemon-v2.js +104 -0
  12. package/lib/daemon/lifecycle.js +56 -171
  13. package/lib/daemon/services/index.js +3 -0
  14. package/lib/daemon/services/polling-service.js +173 -0
  15. package/lib/daemon/services/queue-service.js +318 -0
  16. package/lib/daemon/services/session-service.js +115 -0
  17. package/lib/daemon/state.js +35 -0
  18. package/lib/daemon/task-executor-v2.js +413 -0
  19. package/lib/daemon/task-executor.js +270 -96
  20. package/lib/daemon/teleportation-daemon.js +709 -126
  21. package/lib/daemon/timeline-analyzer.js +215 -0
  22. package/lib/daemon/transcript-ingestion.js +696 -0
  23. package/lib/daemon/utils.js +91 -0
  24. package/lib/install/installer.js +184 -20
  25. package/lib/install/uhr-installer.js +136 -0
  26. package/lib/remote/providers/base-provider.js +46 -0
  27. package/lib/remote/providers/daytona-provider.js +58 -0
  28. package/lib/remote/providers/provider-factory.js +90 -19
  29. package/lib/remote/providers/sprites-provider.js +711 -0
  30. package/lib/teleport/exporters/claude-exporter.js +302 -0
  31. package/lib/teleport/exporters/gemini-exporter.js +307 -0
  32. package/lib/teleport/exporters/index.js +93 -0
  33. package/lib/teleport/exporters/interface.js +153 -0
  34. package/lib/teleport/fork-tracker.js +415 -0
  35. package/lib/teleport/git-committer.js +337 -0
  36. package/lib/teleport/index.js +48 -0
  37. package/lib/teleport/manager.js +620 -0
  38. package/lib/teleport/session-capture.js +282 -0
  39. package/package.json +9 -5
  40. package/teleportation-cli.cjs +488 -453
  41. package/.claude/hooks/heartbeat.mjs +0 -396
  42. package/lib/daemon/pid-manager.js +0 -183
@@ -0,0 +1,337 @@
1
+ /**
2
+ * Git Committer
3
+ *
4
+ * Manages automatic periodic commits for teleported sessions.
5
+ * Ensures work is preserved even if cloud sessions crash.
6
+ *
7
+ * Commit Strategy:
8
+ * - WIP commits every 30 minutes: "WIP: {activity} (teleport auto-save)"
9
+ * - Final commit on success: "Complete: {taskDescription}"
10
+ * - Partial commit on failure: "Partial: {accomplished} (stopped: {reason})"
11
+ *
12
+ * @module lib/teleport/git-committer
13
+ */
14
+
15
+ import { exec } from 'child_process';
16
+ import { promisify } from 'util';
17
+
18
+ const execAsync = promisify(exec);
19
+
20
+ /**
21
+ * Default auto-commit interval (30 minutes)
22
+ * @type {number}
23
+ */
24
+ export const DEFAULT_COMMIT_INTERVAL = 30 * 60 * 1000;
25
+
26
+ /**
27
+ * GitCommitter manages automatic commits for teleported sessions.
28
+ */
29
+ export class GitCommitter {
30
+ /**
31
+ * Initialize GitCommitter
32
+ *
33
+ * @param {Object} config - Configuration
34
+ * @param {string} config.repoPath - Path to git repository
35
+ * @param {string} config.branch - Branch name for commits
36
+ * @param {number} [config.interval] - Auto-commit interval in ms (default: 30 min)
37
+ * @param {string} [config.teleportId] - Teleport session ID for commit messages
38
+ */
39
+ constructor(config) {
40
+ if (!config) {
41
+ throw new Error('GitCommitter config is required');
42
+ }
43
+ if (!config.repoPath) {
44
+ throw new Error('repoPath is required');
45
+ }
46
+ if (!config.branch) {
47
+ throw new Error('branch is required');
48
+ }
49
+
50
+ this.repoPath = config.repoPath;
51
+ this.branch = config.branch;
52
+ this.interval = config.interval || DEFAULT_COMMIT_INTERVAL;
53
+ this.teleportId = config.teleportId || 'unknown';
54
+
55
+ // State
56
+ this.isRunning = false;
57
+ this.intervalId = null;
58
+ this.currentActivity = 'Working on task';
59
+ this.commitCount = 0;
60
+ this.lastCommitAt = null;
61
+ this.errors = [];
62
+ }
63
+
64
+ /**
65
+ * Start automatic commits at the configured interval.
66
+ *
67
+ * @returns {void}
68
+ */
69
+ startAutoCommit() {
70
+ if (this.isRunning) {
71
+ return; // Already running
72
+ }
73
+
74
+ this.isRunning = true;
75
+ this.intervalId = setInterval(async () => {
76
+ try {
77
+ await this.createWIPCommit(this.currentActivity);
78
+ } catch (error) {
79
+ this.errors.push({
80
+ type: 'wip_commit',
81
+ message: error.message,
82
+ timestamp: new Date().toISOString(),
83
+ });
84
+ // Don't stop on errors - just log and continue
85
+ console.error(`[git-committer] WIP commit failed: ${error.message}`);
86
+ }
87
+ }, this.interval);
88
+ }
89
+
90
+ /**
91
+ * Stop automatic commits.
92
+ *
93
+ * @returns {void}
94
+ */
95
+ stopAutoCommit() {
96
+ if (this.intervalId) {
97
+ clearInterval(this.intervalId);
98
+ this.intervalId = null;
99
+ }
100
+ this.isRunning = false;
101
+ }
102
+
103
+ /**
104
+ * Update the current activity description.
105
+ * Used for WIP commit messages.
106
+ *
107
+ * @param {string} activity - Current activity description
108
+ */
109
+ updateActivity(activity) {
110
+ if (activity && typeof activity === 'string') {
111
+ this.currentActivity = activity.slice(0, 100); // Limit length
112
+ }
113
+ }
114
+
115
+ /**
116
+ * Create a WIP (Work In Progress) commit.
117
+ *
118
+ * @param {string} [activity] - Activity description for commit message
119
+ * @returns {Promise<Object>} Commit result
120
+ */
121
+ async createWIPCommit(activity = this.currentActivity) {
122
+ const message = `WIP: ${activity} (teleport auto-save)`;
123
+ return this._createCommit(message, 'wip');
124
+ }
125
+
126
+ /**
127
+ * Create a final commit for successful task completion.
128
+ *
129
+ * @param {string} taskDescription - Description of completed task
130
+ * @returns {Promise<Object>} Commit result
131
+ */
132
+ async createFinalCommit(taskDescription) {
133
+ const message = `Complete: ${taskDescription}`;
134
+ return this._createCommit(message, 'final');
135
+ }
136
+
137
+ /**
138
+ * Create a partial commit for incomplete work.
139
+ *
140
+ * @param {string} accomplished - What was accomplished
141
+ * @param {string} reason - Why the session stopped
142
+ * @returns {Promise<Object>} Commit result
143
+ */
144
+ async createPartialCommit(accomplished, reason) {
145
+ const message = `Partial: ${accomplished} (stopped: ${reason})`;
146
+ return this._createCommit(message, 'partial');
147
+ }
148
+
149
+ /**
150
+ * Create a commit with the given message.
151
+ *
152
+ * @private
153
+ * @param {string} message - Commit message
154
+ * @param {string} type - Commit type (wip, final, partial)
155
+ * @returns {Promise<Object>} Commit result
156
+ */
157
+ async _createCommit(message, type) {
158
+ const cwd = this.repoPath;
159
+
160
+ try {
161
+ // Check if there are changes to commit
162
+ const hasChanges = await this._hasChanges();
163
+ if (!hasChanges) {
164
+ return {
165
+ success: true,
166
+ skipped: true,
167
+ reason: 'No changes to commit',
168
+ type,
169
+ };
170
+ }
171
+
172
+ // Stage all changes
173
+ await execAsync('git add -A', { cwd });
174
+
175
+ // Create commit with teleport attribution
176
+ const fullMessage = `${message}\n\nTeleport-ID: ${this.teleportId}\nCommit-Type: ${type}`;
177
+ await execAsync(`git commit -m ${this._escapeMessage(fullMessage)}`, { cwd });
178
+
179
+ // Get commit SHA
180
+ const { stdout: sha } = await execAsync('git rev-parse HEAD', { cwd });
181
+
182
+ this.commitCount++;
183
+ this.lastCommitAt = new Date().toISOString();
184
+
185
+ return {
186
+ success: true,
187
+ skipped: false,
188
+ sha: sha.trim(),
189
+ message,
190
+ type,
191
+ timestamp: this.lastCommitAt,
192
+ };
193
+ } catch (error) {
194
+ // Handle "nothing to commit" gracefully
195
+ if (error.message.includes('nothing to commit')) {
196
+ return {
197
+ success: true,
198
+ skipped: true,
199
+ reason: 'Nothing to commit',
200
+ type,
201
+ };
202
+ }
203
+
204
+ throw new Error(`Commit failed: ${error.message}`);
205
+ }
206
+ }
207
+
208
+ /**
209
+ * Check if there are uncommitted changes.
210
+ *
211
+ * @private
212
+ * @returns {Promise<boolean>} True if there are changes
213
+ */
214
+ async _hasChanges() {
215
+ try {
216
+ const { stdout } = await execAsync('git status --porcelain', { cwd: this.repoPath });
217
+ return stdout.trim().length > 0;
218
+ } catch {
219
+ return false;
220
+ }
221
+ }
222
+
223
+ /**
224
+ * Escape commit message for shell.
225
+ *
226
+ * @private
227
+ * @param {string} message - Message to escape
228
+ * @returns {string} Escaped message
229
+ */
230
+ _escapeMessage(message) {
231
+ // Use $'...' syntax for proper escaping
232
+ return `$'${message.replace(/'/g, "\\'").replace(/\n/g, "\\n")}'`;
233
+ }
234
+
235
+ /**
236
+ * Push commits to remote.
237
+ *
238
+ * @param {boolean} [force=false] - Force push (for WIP branches)
239
+ * @returns {Promise<Object>} Push result
240
+ */
241
+ async push(force = false) {
242
+ const cwd = this.repoPath;
243
+ const forceFlag = force ? '--force' : '';
244
+
245
+ try {
246
+ await execAsync(`git push origin ${this.branch} ${forceFlag}`, { cwd });
247
+ return { success: true };
248
+ } catch (error) {
249
+ throw new Error(`Push failed: ${error.message}`);
250
+ }
251
+ }
252
+
253
+ /**
254
+ * Create and checkout teleport branch.
255
+ *
256
+ * @param {string|null} baseBranch - Branch to base off of (null = current HEAD)
257
+ * @returns {Promise<Object>} Branch creation result
258
+ */
259
+ async createTeleportBranch(baseBranch = 'main') {
260
+ const cwd = this.repoPath;
261
+
262
+ try {
263
+ // Fetch latest (ignore errors if no remote)
264
+ await execAsync('git fetch origin', { cwd }).catch(() => {});
265
+
266
+ // Create branch - from origin/baseBranch if specified, otherwise from HEAD
267
+ if (baseBranch) {
268
+ try {
269
+ await execAsync(`git checkout -b ${this.branch} origin/${baseBranch}`, { cwd });
270
+ } catch {
271
+ // If origin/baseBranch doesn't exist, try local baseBranch
272
+ await execAsync(`git checkout -b ${this.branch} ${baseBranch}`, { cwd });
273
+ }
274
+ } else {
275
+ // Create from current HEAD
276
+ await execAsync(`git checkout -b ${this.branch}`, { cwd });
277
+ }
278
+
279
+ return { success: true, branch: this.branch };
280
+ } catch (error) {
281
+ // Branch might already exist
282
+ if (error.message.includes('already exists')) {
283
+ await execAsync(`git checkout ${this.branch}`, { cwd });
284
+ return { success: true, branch: this.branch, existed: true };
285
+ }
286
+ throw new Error(`Branch creation failed: ${error.message}`);
287
+ }
288
+ }
289
+
290
+ /**
291
+ * Get commit history for this teleport session.
292
+ *
293
+ * @param {number} [limit=50] - Maximum commits to return
294
+ * @returns {Promise<Object[]>} Array of commit objects
295
+ */
296
+ async getCommitHistory(limit = 50) {
297
+ const cwd = this.repoPath;
298
+
299
+ try {
300
+ const { stdout } = await execAsync(
301
+ `git log --format="%H|%s|%ci" -n ${limit}`,
302
+ { cwd }
303
+ );
304
+
305
+ const commits = stdout
306
+ .trim()
307
+ .split('\n')
308
+ .filter(Boolean)
309
+ .map(line => {
310
+ const [sha, message, date] = line.split('|');
311
+ return { sha, message, date };
312
+ });
313
+
314
+ return commits;
315
+ } catch {
316
+ return [];
317
+ }
318
+ }
319
+
320
+ /**
321
+ * Get statistics about commits made.
322
+ *
323
+ * @returns {Object} Commit statistics
324
+ */
325
+ getStats() {
326
+ return {
327
+ commitCount: this.commitCount,
328
+ lastCommitAt: this.lastCommitAt,
329
+ isRunning: this.isRunning,
330
+ interval: this.interval,
331
+ branch: this.branch,
332
+ errors: this.errors,
333
+ };
334
+ }
335
+ }
336
+
337
+ export default GitCommitter;
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Teleport Module
3
+ *
4
+ * Core module for session teleportation functionality.
5
+ * Enables developers to "teleport" active coding sessions to cloud dev environments.
6
+ *
7
+ * @module lib/teleport
8
+ */
9
+
10
+ // Transcript Exporters
11
+ export {
12
+ TranscriptExporter,
13
+ ClaudeTranscriptExporter,
14
+ GeminiTranscriptExporter,
15
+ getExporterForCoder,
16
+ getSupportedCoders,
17
+ isCoderSupported,
18
+ CODER_NAMES,
19
+ } from './exporters/index.js';
20
+
21
+ // Session Capture
22
+ export {
23
+ captureSessionState,
24
+ isGitRepository,
25
+ captureGitState,
26
+ captureUncommittedChanges,
27
+ findClaudeTranscript,
28
+ getProjectSlug,
29
+ } from './session-capture.js';
30
+
31
+ // Teleport Manager
32
+ export {
33
+ TeleportManager,
34
+ TeleportMode,
35
+ TeleportStatus,
36
+ } from './manager.js';
37
+
38
+ // Git Auto-Commit
39
+ export {
40
+ GitCommitter,
41
+ DEFAULT_COMMIT_INTERVAL,
42
+ } from './git-committer.js';
43
+
44
+ // Fork Mode Tracking
45
+ export {
46
+ ForkTracker,
47
+ ForkSessionState,
48
+ } from './fork-tracker.js';