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.
- package/.claude/hooks/config-loader.mjs +93 -0
- package/.claude/hooks/heartbeat.mjs +331 -0
- package/.claude/hooks/notification.mjs +35 -0
- package/.claude/hooks/permission_request.mjs +307 -0
- package/.claude/hooks/post_tool_use.mjs +137 -0
- package/.claude/hooks/pre_tool_use.mjs +451 -0
- package/.claude/hooks/session-register.mjs +274 -0
- package/.claude/hooks/session_end.mjs +256 -0
- package/.claude/hooks/session_start.mjs +308 -0
- package/.claude/hooks/stop.mjs +277 -0
- package/.claude/hooks/user_prompt_submit.mjs +91 -0
- package/LICENSE +21 -0
- package/README.md +243 -0
- package/lib/auth/api-key.js +110 -0
- package/lib/auth/credentials.js +341 -0
- package/lib/backup/manager.js +461 -0
- package/lib/cli/daemon-commands.js +299 -0
- package/lib/cli/index.js +303 -0
- package/lib/cli/session-commands.js +294 -0
- package/lib/cli/snapshot-commands.js +223 -0
- package/lib/cli/worktree-commands.js +291 -0
- package/lib/config/manager.js +306 -0
- package/lib/daemon/lifecycle.js +336 -0
- package/lib/daemon/pid-manager.js +160 -0
- package/lib/daemon/teleportation-daemon.js +2009 -0
- package/lib/handoff/config.js +102 -0
- package/lib/handoff/example.js +152 -0
- package/lib/handoff/git-handoff.js +351 -0
- package/lib/handoff/handoff.js +277 -0
- package/lib/handoff/index.js +25 -0
- package/lib/handoff/session-state.js +238 -0
- package/lib/install/installer.js +555 -0
- package/lib/machine-coders/claude-code-adapter.js +329 -0
- package/lib/machine-coders/example.js +239 -0
- package/lib/machine-coders/gemini-cli-adapter.js +406 -0
- package/lib/machine-coders/index.js +103 -0
- package/lib/machine-coders/interface.js +168 -0
- package/lib/router/classifier.js +251 -0
- package/lib/router/example.js +92 -0
- package/lib/router/index.js +69 -0
- package/lib/router/mech-llms-client.js +277 -0
- package/lib/router/models.js +188 -0
- package/lib/router/router.js +382 -0
- package/lib/session/cleanup.js +100 -0
- package/lib/session/metadata.js +258 -0
- package/lib/session/mute-checker.js +114 -0
- package/lib/session-registry/manager.js +302 -0
- package/lib/snapshot/manager.js +390 -0
- package/lib/utils/errors.js +166 -0
- package/lib/utils/logger.js +148 -0
- package/lib/utils/retry.js +155 -0
- package/lib/worktree/manager.js +301 -0
- package/package.json +66 -0
- package/teleportation-cli.cjs +2987 -0
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* CLI Commands for Session Registry Management
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import {
|
|
7
|
+
listSessions,
|
|
8
|
+
getSession,
|
|
9
|
+
getActiveSessionsInRepo,
|
|
10
|
+
detectConflicts,
|
|
11
|
+
cleanupStaleSessions,
|
|
12
|
+
getSessionStats,
|
|
13
|
+
completeSession,
|
|
14
|
+
pauseSession,
|
|
15
|
+
resumeSession
|
|
16
|
+
} from '../session-registry/manager.js';
|
|
17
|
+
import { getCurrentSessionId } from '../worktree/manager.js';
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* List all sessions
|
|
21
|
+
*/
|
|
22
|
+
export async function commandSessionList(args) {
|
|
23
|
+
const { status, repoOnly = false } = args;
|
|
24
|
+
|
|
25
|
+
try {
|
|
26
|
+
let sessions;
|
|
27
|
+
|
|
28
|
+
if (repoOnly) {
|
|
29
|
+
sessions = await getActiveSessionsInRepo();
|
|
30
|
+
} else {
|
|
31
|
+
sessions = await listSessions(status);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if (sessions.length === 0) {
|
|
35
|
+
console.log('No sessions found.');
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
console.log('\nRegistered Sessions:\n');
|
|
40
|
+
console.log(
|
|
41
|
+
'SESSION ID'.padEnd(25),
|
|
42
|
+
'AGENT'.padEnd(15),
|
|
43
|
+
'BRANCH'.padEnd(25),
|
|
44
|
+
'STATUS'.padEnd(12),
|
|
45
|
+
'LAST ACTIVE'
|
|
46
|
+
);
|
|
47
|
+
console.log('-'.repeat(100));
|
|
48
|
+
|
|
49
|
+
for (const session of sessions) {
|
|
50
|
+
const lastActive = new Date(session.lastActiveAt).toLocaleString();
|
|
51
|
+
console.log(
|
|
52
|
+
session.id.padEnd(25),
|
|
53
|
+
session.agent.padEnd(15),
|
|
54
|
+
session.branch.padEnd(25),
|
|
55
|
+
session.status.padEnd(12),
|
|
56
|
+
lastActive
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Show current session if we're in one
|
|
61
|
+
const currentSessionId = getCurrentSessionId();
|
|
62
|
+
if (currentSessionId) {
|
|
63
|
+
console.log(`\n→ Current session: ${currentSessionId}`);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return sessions;
|
|
67
|
+
} catch (error) {
|
|
68
|
+
console.error(`Failed to list sessions: ${error.message}`);
|
|
69
|
+
throw error;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Show session details
|
|
75
|
+
*/
|
|
76
|
+
export async function commandSessionInfo(args) {
|
|
77
|
+
const { sessionId } = args;
|
|
78
|
+
|
|
79
|
+
try {
|
|
80
|
+
let targetSessionId = sessionId;
|
|
81
|
+
|
|
82
|
+
// If no session ID, try to get current
|
|
83
|
+
if (!targetSessionId) {
|
|
84
|
+
targetSessionId = getCurrentSessionId();
|
|
85
|
+
if (!targetSessionId) {
|
|
86
|
+
throw new Error('Not in a worktree. Specify --session-id to get info for a specific session.');
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const session = await getSession(targetSessionId);
|
|
91
|
+
|
|
92
|
+
if (!session) {
|
|
93
|
+
throw new Error(`Session not found: ${targetSessionId}`);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
console.log('\nSession Information:\n');
|
|
97
|
+
console.log(`Session ID: ${session.id}`);
|
|
98
|
+
console.log(`Agent: ${session.agent}`);
|
|
99
|
+
console.log(`Branch: ${session.branch}`);
|
|
100
|
+
console.log(`Worktree: ${session.worktreePath}`);
|
|
101
|
+
console.log(`Repository: ${session.repoRoot}`);
|
|
102
|
+
console.log(`Status: ${session.status}`);
|
|
103
|
+
console.log(`Started: ${new Date(session.startedAt).toLocaleString()}`);
|
|
104
|
+
console.log(`Last Active: ${new Date(session.lastActiveAt).toLocaleString()}`);
|
|
105
|
+
|
|
106
|
+
if (session.modifiedFiles.length > 0) {
|
|
107
|
+
console.log(`\nModified Files (${session.modifiedFiles.length}):`);
|
|
108
|
+
session.modifiedFiles.slice(0, 15).forEach(file => {
|
|
109
|
+
console.log(` - ${file}`);
|
|
110
|
+
});
|
|
111
|
+
if (session.modifiedFiles.length > 15) {
|
|
112
|
+
console.log(` ... and ${session.modifiedFiles.length - 15} more`);
|
|
113
|
+
}
|
|
114
|
+
} else {
|
|
115
|
+
console.log('\nNo modified files tracked.');
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
return session;
|
|
119
|
+
} catch (error) {
|
|
120
|
+
console.error(`Failed to get session info: ${error.message}`);
|
|
121
|
+
throw error;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Check for conflicts between sessions
|
|
127
|
+
*/
|
|
128
|
+
export async function commandCheckConflicts(args) {
|
|
129
|
+
const { sessionId } = args;
|
|
130
|
+
|
|
131
|
+
try {
|
|
132
|
+
let targetSessionId = sessionId;
|
|
133
|
+
|
|
134
|
+
// If no session ID, try to get current
|
|
135
|
+
if (!targetSessionId) {
|
|
136
|
+
targetSessionId = getCurrentSessionId();
|
|
137
|
+
if (!targetSessionId) {
|
|
138
|
+
throw new Error('Not in a worktree. Specify --session-id to check conflicts for a specific session.');
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
console.log(`Checking for conflicts with session: ${targetSessionId}\n`);
|
|
143
|
+
|
|
144
|
+
const conflicts = await detectConflicts(targetSessionId);
|
|
145
|
+
|
|
146
|
+
if (conflicts.length === 0) {
|
|
147
|
+
console.log('✓ No conflicts detected with other active sessions.');
|
|
148
|
+
return [];
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
console.log(`⚠ Found conflicts with ${conflicts.length} other session(s):\n`);
|
|
152
|
+
|
|
153
|
+
for (const conflict of conflicts) {
|
|
154
|
+
console.log(`Session: ${conflict.sessionId}`);
|
|
155
|
+
console.log(` Agent: ${conflict.agent}`);
|
|
156
|
+
console.log(` Branch: ${conflict.branch}`);
|
|
157
|
+
console.log(` Conflicting files:`);
|
|
158
|
+
conflict.conflictingFiles.forEach(file => {
|
|
159
|
+
console.log(` - ${file}`);
|
|
160
|
+
});
|
|
161
|
+
console.log();
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
console.log('Recommendation: Coordinate with other sessions before merging.');
|
|
165
|
+
console.log('Consider creating a snapshot before proceeding with any merges.');
|
|
166
|
+
|
|
167
|
+
return conflicts;
|
|
168
|
+
} catch (error) {
|
|
169
|
+
console.error(`Failed to check conflicts: ${error.message}`);
|
|
170
|
+
throw error;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Show session statistics
|
|
176
|
+
*/
|
|
177
|
+
export async function commandSessionStats() {
|
|
178
|
+
try {
|
|
179
|
+
const stats = await getSessionStats();
|
|
180
|
+
|
|
181
|
+
console.log('\nSession Statistics:\n');
|
|
182
|
+
console.log(`Total Sessions: ${stats.total}`);
|
|
183
|
+
console.log(`Active: ${stats.active}`);
|
|
184
|
+
console.log(`Paused: ${stats.paused}`);
|
|
185
|
+
console.log(`Completed: ${stats.completed}`);
|
|
186
|
+
|
|
187
|
+
return stats;
|
|
188
|
+
} catch (error) {
|
|
189
|
+
console.error(`Failed to get session stats: ${error.message}`);
|
|
190
|
+
throw error;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Pause a session
|
|
196
|
+
*/
|
|
197
|
+
export async function commandSessionPause(args) {
|
|
198
|
+
const { sessionId } = args;
|
|
199
|
+
|
|
200
|
+
try {
|
|
201
|
+
let targetSessionId = sessionId;
|
|
202
|
+
|
|
203
|
+
// If no session ID, try to get current
|
|
204
|
+
if (!targetSessionId) {
|
|
205
|
+
targetSessionId = getCurrentSessionId();
|
|
206
|
+
if (!targetSessionId) {
|
|
207
|
+
throw new Error('Not in a worktree. Specify --session-id to pause a specific session.');
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
await pauseSession(targetSessionId);
|
|
212
|
+
console.log(`✓ Session paused: ${targetSessionId}`);
|
|
213
|
+
|
|
214
|
+
return true;
|
|
215
|
+
} catch (error) {
|
|
216
|
+
console.error(`Failed to pause session: ${error.message}`);
|
|
217
|
+
throw error;
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* Resume a session
|
|
223
|
+
*/
|
|
224
|
+
export async function commandSessionResume(args) {
|
|
225
|
+
const { sessionId } = args;
|
|
226
|
+
|
|
227
|
+
try {
|
|
228
|
+
let targetSessionId = sessionId;
|
|
229
|
+
|
|
230
|
+
// If no session ID, try to get current
|
|
231
|
+
if (!targetSessionId) {
|
|
232
|
+
targetSessionId = getCurrentSessionId();
|
|
233
|
+
if (!targetSessionId) {
|
|
234
|
+
throw new Error('Not in a worktree. Specify --session-id to resume a specific session.');
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
await resumeSession(targetSessionId);
|
|
239
|
+
console.log(`✓ Session resumed: ${targetSessionId}`);
|
|
240
|
+
|
|
241
|
+
return true;
|
|
242
|
+
} catch (error) {
|
|
243
|
+
console.error(`Failed to resume session: ${error.message}`);
|
|
244
|
+
throw error;
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* Complete a session
|
|
250
|
+
*/
|
|
251
|
+
export async function commandSessionComplete(args) {
|
|
252
|
+
const { sessionId } = args;
|
|
253
|
+
|
|
254
|
+
try {
|
|
255
|
+
let targetSessionId = sessionId;
|
|
256
|
+
|
|
257
|
+
// If no session ID, try to get current
|
|
258
|
+
if (!targetSessionId) {
|
|
259
|
+
targetSessionId = getCurrentSessionId();
|
|
260
|
+
if (!targetSessionId) {
|
|
261
|
+
throw new Error('Not in a worktree. Specify --session-id to complete a specific session.');
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
await completeSession(targetSessionId);
|
|
266
|
+
console.log(`✓ Session marked as completed: ${targetSessionId}`);
|
|
267
|
+
|
|
268
|
+
return true;
|
|
269
|
+
} catch (error) {
|
|
270
|
+
console.error(`Failed to complete session: ${error.message}`);
|
|
271
|
+
throw error;
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
/**
|
|
276
|
+
* Clean up stale sessions
|
|
277
|
+
*/
|
|
278
|
+
export async function commandSessionCleanup() {
|
|
279
|
+
try {
|
|
280
|
+
console.log('Cleaning up stale sessions...');
|
|
281
|
+
const count = await cleanupStaleSessions();
|
|
282
|
+
|
|
283
|
+
if (count === 0) {
|
|
284
|
+
console.log('No stale sessions found.');
|
|
285
|
+
} else {
|
|
286
|
+
console.log(`✓ Cleaned up ${count} stale session(s)`);
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
return count;
|
|
290
|
+
} catch (error) {
|
|
291
|
+
console.error(`Failed to cleanup sessions: ${error.message}`);
|
|
292
|
+
throw error;
|
|
293
|
+
}
|
|
294
|
+
}
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* CLI Commands for Snapshot Management
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import {
|
|
7
|
+
createSnapshot,
|
|
8
|
+
restoreSnapshot,
|
|
9
|
+
listSnapshots,
|
|
10
|
+
deleteSnapshot,
|
|
11
|
+
deleteAllSnapshots,
|
|
12
|
+
getSnapshotDiff,
|
|
13
|
+
SnapshotType
|
|
14
|
+
} from '../snapshot/manager.js';
|
|
15
|
+
import { getCurrentSessionId } from '../worktree/manager.js';
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Create a snapshot
|
|
19
|
+
*/
|
|
20
|
+
export async function commandSnapshotCreate(args) {
|
|
21
|
+
const {
|
|
22
|
+
sessionId,
|
|
23
|
+
type = SnapshotType.CHECKPOINT,
|
|
24
|
+
message = ''
|
|
25
|
+
} = args;
|
|
26
|
+
|
|
27
|
+
try {
|
|
28
|
+
let targetSessionId = sessionId;
|
|
29
|
+
|
|
30
|
+
// If no session ID, try to get current
|
|
31
|
+
if (!targetSessionId) {
|
|
32
|
+
targetSessionId = getCurrentSessionId();
|
|
33
|
+
if (!targetSessionId) {
|
|
34
|
+
throw new Error('Not in a worktree. Specify --session-id to create snapshot for a specific session.');
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
console.log(`Creating ${type} snapshot for session: ${targetSessionId}`);
|
|
39
|
+
if (message) {
|
|
40
|
+
console.log(`Message: ${message}`);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const snapshot = await createSnapshot(targetSessionId, type, message);
|
|
44
|
+
|
|
45
|
+
console.log(`✓ Snapshot created: ${snapshot.id}`);
|
|
46
|
+
console.log(` Type: ${snapshot.type}`);
|
|
47
|
+
console.log(` Branch: ${snapshot.branch}`);
|
|
48
|
+
console.log(` Commit: ${snapshot.commitHash.substring(0, 8)}`);
|
|
49
|
+
console.log(` Timestamp: ${new Date(snapshot.timestamp).toLocaleString()}`);
|
|
50
|
+
|
|
51
|
+
if (snapshot.stashRef) {
|
|
52
|
+
console.log(` Stash: ${snapshot.stashRef}`);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return snapshot;
|
|
56
|
+
} catch (error) {
|
|
57
|
+
console.error(`Failed to create snapshot: ${error.message}`);
|
|
58
|
+
throw error;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* List snapshots
|
|
64
|
+
*/
|
|
65
|
+
export async function commandSnapshotList(args) {
|
|
66
|
+
const { sessionId } = args;
|
|
67
|
+
|
|
68
|
+
try {
|
|
69
|
+
let targetSessionId = sessionId;
|
|
70
|
+
|
|
71
|
+
// If no session ID, try to get current
|
|
72
|
+
if (!targetSessionId) {
|
|
73
|
+
targetSessionId = getCurrentSessionId();
|
|
74
|
+
if (!targetSessionId) {
|
|
75
|
+
throw new Error('Not in a worktree. Specify --session-id to list snapshots for a specific session.');
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const snapshots = await listSnapshots(targetSessionId);
|
|
80
|
+
|
|
81
|
+
if (snapshots.length === 0) {
|
|
82
|
+
console.log(`No snapshots found for session: ${targetSessionId}`);
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
console.log(`\nSnapshots for session: ${targetSessionId}\n`);
|
|
87
|
+
console.log('ID'.padEnd(50), 'TYPE'.padEnd(15), 'DATE'.padEnd(25), 'MESSAGE');
|
|
88
|
+
console.log('-'.repeat(120));
|
|
89
|
+
|
|
90
|
+
for (const snapshot of snapshots) {
|
|
91
|
+
const date = new Date(snapshot.timestamp).toLocaleString();
|
|
92
|
+
const msg = snapshot.message || '(no message)';
|
|
93
|
+
console.log(
|
|
94
|
+
snapshot.id.padEnd(50),
|
|
95
|
+
snapshot.type.padEnd(15),
|
|
96
|
+
date.padEnd(25),
|
|
97
|
+
msg.substring(0, 30)
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return snapshots;
|
|
102
|
+
} catch (error) {
|
|
103
|
+
console.error(`Failed to list snapshots: ${error.message}`);
|
|
104
|
+
throw error;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Restore a snapshot
|
|
110
|
+
*/
|
|
111
|
+
export async function commandSnapshotRestore(args) {
|
|
112
|
+
const { snapshotId, force = false } = args;
|
|
113
|
+
|
|
114
|
+
if (!snapshotId) {
|
|
115
|
+
throw new Error('Snapshot ID is required (--snapshot-id or -s)');
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
try {
|
|
119
|
+
console.log(`Restoring snapshot: ${snapshotId}`);
|
|
120
|
+
|
|
121
|
+
if (!force) {
|
|
122
|
+
console.log('WARNING: This will overwrite your current working directory.');
|
|
123
|
+
console.log('Use --force to confirm.');
|
|
124
|
+
return false;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
await restoreSnapshot(snapshotId, force);
|
|
128
|
+
|
|
129
|
+
console.log(`✓ Snapshot restored: ${snapshotId}`);
|
|
130
|
+
console.log('Your working directory has been restored to the snapshot state.');
|
|
131
|
+
|
|
132
|
+
return true;
|
|
133
|
+
} catch (error) {
|
|
134
|
+
console.error(`Failed to restore snapshot: ${error.message}`);
|
|
135
|
+
throw error;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Show diff between current state and snapshot
|
|
141
|
+
*/
|
|
142
|
+
export async function commandSnapshotDiff(args) {
|
|
143
|
+
const { snapshotId } = args;
|
|
144
|
+
|
|
145
|
+
if (!snapshotId) {
|
|
146
|
+
throw new Error('Snapshot ID is required (--snapshot-id or -s)');
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
try {
|
|
150
|
+
const diff = await getSnapshotDiff(snapshotId);
|
|
151
|
+
|
|
152
|
+
if (!diff || diff.trim().length === 0) {
|
|
153
|
+
console.log('No differences found.');
|
|
154
|
+
return '';
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
console.log(diff);
|
|
158
|
+
return diff;
|
|
159
|
+
} catch (error) {
|
|
160
|
+
console.error(`Failed to get snapshot diff: ${error.message}`);
|
|
161
|
+
throw error;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Delete a snapshot
|
|
167
|
+
*/
|
|
168
|
+
export async function commandSnapshotDelete(args) {
|
|
169
|
+
const { snapshotId, force = false } = args;
|
|
170
|
+
|
|
171
|
+
if (!snapshotId) {
|
|
172
|
+
throw new Error('Snapshot ID is required (--snapshot-id or -s)');
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
try {
|
|
176
|
+
if (!force) {
|
|
177
|
+
console.log(`This will permanently delete snapshot: ${snapshotId}`);
|
|
178
|
+
console.log('Use --force to confirm.');
|
|
179
|
+
return false;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
await deleteSnapshot(snapshotId);
|
|
183
|
+
console.log(`✓ Snapshot deleted: ${snapshotId}`);
|
|
184
|
+
|
|
185
|
+
return true;
|
|
186
|
+
} catch (error) {
|
|
187
|
+
console.error(`Failed to delete snapshot: ${error.message}`);
|
|
188
|
+
throw error;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Delete all snapshots for a session
|
|
194
|
+
*/
|
|
195
|
+
export async function commandSnapshotDeleteAll(args) {
|
|
196
|
+
const { sessionId, force = false } = args;
|
|
197
|
+
|
|
198
|
+
try {
|
|
199
|
+
let targetSessionId = sessionId;
|
|
200
|
+
|
|
201
|
+
// If no session ID, try to get current
|
|
202
|
+
if (!targetSessionId) {
|
|
203
|
+
targetSessionId = getCurrentSessionId();
|
|
204
|
+
if (!targetSessionId) {
|
|
205
|
+
throw new Error('Not in a worktree. Specify --session-id to delete snapshots for a specific session.');
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
if (!force) {
|
|
210
|
+
console.log(`This will permanently delete ALL snapshots for session: ${targetSessionId}`);
|
|
211
|
+
console.log('Use --force to confirm.');
|
|
212
|
+
return false;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
const count = await deleteAllSnapshots(targetSessionId);
|
|
216
|
+
console.log(`✓ Deleted ${count} snapshot(s)`);
|
|
217
|
+
|
|
218
|
+
return count;
|
|
219
|
+
} catch (error) {
|
|
220
|
+
console.error(`Failed to delete snapshots: ${error.message}`);
|
|
221
|
+
throw error;
|
|
222
|
+
}
|
|
223
|
+
}
|