teleportation-cli 1.1.5 → 1.2.1
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/hooks/permission_request.mjs +326 -59
- package/.claude/hooks/post_tool_use.mjs +90 -0
- package/.claude/hooks/pre_tool_use.mjs +212 -293
- package/.claude/hooks/session-register.mjs +89 -104
- package/.claude/hooks/session_end.mjs +41 -42
- package/.claude/hooks/session_start.mjs +45 -60
- package/.claude/hooks/stop.mjs +752 -99
- package/.claude/hooks/user_prompt_submit.mjs +26 -3
- package/lib/cli/daemon-commands.js +1 -1
- package/lib/cli/teleport-commands.js +469 -0
- package/lib/daemon/daemon-v2.js +104 -0
- package/lib/daemon/lifecycle.js +56 -171
- package/lib/daemon/services/index.js +3 -0
- package/lib/daemon/services/polling-service.js +173 -0
- package/lib/daemon/services/queue-service.js +318 -0
- package/lib/daemon/services/session-service.js +115 -0
- package/lib/daemon/state.js +35 -0
- package/lib/daemon/task-executor-v2.js +413 -0
- package/lib/daemon/task-executor.js +270 -96
- package/lib/daemon/teleportation-daemon.js +709 -126
- package/lib/daemon/timeline-analyzer.js +215 -0
- package/lib/daemon/transcript-ingestion.js +696 -0
- package/lib/daemon/utils.js +91 -0
- package/lib/install/installer.js +184 -20
- package/lib/install/uhr-installer.js +136 -0
- package/lib/remote/providers/base-provider.js +46 -0
- package/lib/remote/providers/daytona-provider.js +58 -0
- package/lib/remote/providers/provider-factory.js +90 -19
- package/lib/remote/providers/sprites-provider.js +711 -0
- package/lib/teleport/exporters/claude-exporter.js +302 -0
- package/lib/teleport/exporters/gemini-exporter.js +307 -0
- package/lib/teleport/exporters/index.js +93 -0
- package/lib/teleport/exporters/interface.js +153 -0
- package/lib/teleport/fork-tracker.js +415 -0
- package/lib/teleport/git-committer.js +337 -0
- package/lib/teleport/index.js +48 -0
- package/lib/teleport/manager.js +620 -0
- package/lib/teleport/session-capture.js +282 -0
- package/package.json +6 -2
- package/teleportation-cli.cjs +488 -453
- package/.claude/hooks/heartbeat.mjs +0 -396
- 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';
|