@lasso-ai/cli 1.3.2 → 1.3.3

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.
@@ -0,0 +1,33 @@
1
+ export type FileSnapshot = {
2
+ filePath: string;
3
+ content: string | null;
4
+ hash: string;
5
+ timestamp: string;
6
+ };
7
+ export type TimelineSnapshot = {
8
+ id: string;
9
+ taskId: string;
10
+ summary: string;
11
+ files: FileSnapshot[];
12
+ createdAt: string;
13
+ parentSnapshotId?: string;
14
+ };
15
+ export type ConflictInfo = {
16
+ filePath: string;
17
+ expectedHash: string;
18
+ actualHash: string;
19
+ originalContent: string;
20
+ currentContent: string;
21
+ };
22
+ export declare function createSnapshot(cwd: string, taskId: string, summary: string, filePaths: string[], parentSnapshotId?: string): TimelineSnapshot;
23
+ export declare function restoreSnapshot(cwd: string, snapshotId: string): boolean;
24
+ export declare function detectConflicts(cwd: string, expectedFiles: Array<{
25
+ filePath: string;
26
+ content: string | null;
27
+ hash?: string;
28
+ }>): ConflictInfo[];
29
+ export declare function getTimeline(cwd: string, limit?: number): TimelineSnapshot[];
30
+ export declare function deleteSnapshot(cwd: string, snapshotId: string): boolean;
31
+ export declare function clearSnapshots(cwd: string): void;
32
+ export declare function getSnapshotByTaskId(cwd: string, taskId: string): TimelineSnapshot | undefined;
33
+ export declare function getLatestSnapshot(cwd: string): TimelineSnapshot | undefined;
@@ -0,0 +1,225 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.createSnapshot = createSnapshot;
7
+ exports.restoreSnapshot = restoreSnapshot;
8
+ exports.detectConflicts = detectConflicts;
9
+ exports.getTimeline = getTimeline;
10
+ exports.deleteSnapshot = deleteSnapshot;
11
+ exports.clearSnapshots = clearSnapshots;
12
+ exports.getSnapshotByTaskId = getSnapshotByTaskId;
13
+ exports.getLatestSnapshot = getLatestSnapshot;
14
+ // src/cli/snapshot.ts
15
+ // Local history snapshot system for safe concurrent edits
16
+ const node_fs_1 = __importDefault(require("node:fs"));
17
+ const node_path_1 = __importDefault(require("node:path"));
18
+ const node_crypto_1 = __importDefault(require("node:crypto"));
19
+ const SNAPSHOTS_DIR = ".lasso/snapshots";
20
+ const TIMELINE_FILE = ".lasso/timeline.json";
21
+ const MAX_SNAPSHOTS = 50;
22
+ // Ensure snapshots directory exists
23
+ function ensureSnapshotsDir(cwd) {
24
+ const dir = node_path_1.default.join(cwd, SNAPSHOTS_DIR);
25
+ if (!node_fs_1.default.existsSync(dir)) {
26
+ node_fs_1.default.mkdirSync(dir, { recursive: true });
27
+ }
28
+ }
29
+ // Generate content hash
30
+ function hashContent(content) {
31
+ return node_crypto_1.default.createHash("sha256").update(content).digest("hex");
32
+ }
33
+ // Generate unique snapshot ID
34
+ function generateSnapshotId() {
35
+ return `snap-${Date.now()}-${node_crypto_1.default.randomBytes(4).toString("hex")}`;
36
+ }
37
+ // Load timeline from disk
38
+ function loadTimeline(cwd) {
39
+ const timelinePath = node_path_1.default.join(cwd, TIMELINE_FILE);
40
+ try {
41
+ if (node_fs_1.default.existsSync(timelinePath)) {
42
+ const data = node_fs_1.default.readFileSync(timelinePath, "utf8");
43
+ return JSON.parse(data);
44
+ }
45
+ }
46
+ catch {
47
+ // File might be corrupted, start fresh
48
+ }
49
+ return [];
50
+ }
51
+ // Save timeline to disk
52
+ function saveTimeline(cwd, timeline) {
53
+ const timelinePath = node_path_1.default.join(cwd, TIMELINE_FILE);
54
+ try {
55
+ node_fs_1.default.writeFileSync(timelinePath, JSON.stringify(timeline, null, 2));
56
+ }
57
+ catch (error) {
58
+ console.warn("[lasso] Failed to save timeline:", error);
59
+ }
60
+ }
61
+ // Create a snapshot of current file states
62
+ function createSnapshot(cwd, taskId, summary, filePaths, parentSnapshotId) {
63
+ ensureSnapshotsDir(cwd);
64
+ const snapshotId = generateSnapshotId();
65
+ const files = [];
66
+ const timestamp = new Date().toISOString();
67
+ for (const filePath of filePaths) {
68
+ const fullPath = node_path_1.default.resolve(cwd, filePath);
69
+ let content = null;
70
+ let hash = "";
71
+ try {
72
+ if (node_fs_1.default.existsSync(fullPath)) {
73
+ content = node_fs_1.default.readFileSync(fullPath, "utf8");
74
+ hash = hashContent(content);
75
+ }
76
+ }
77
+ catch (error) {
78
+ console.warn(`[lasso] Failed to snapshot ${filePath}:`, error);
79
+ }
80
+ files.push({
81
+ filePath,
82
+ content,
83
+ hash,
84
+ timestamp,
85
+ });
86
+ }
87
+ const snapshot = {
88
+ id: snapshotId,
89
+ taskId,
90
+ summary,
91
+ files,
92
+ createdAt: timestamp,
93
+ parentSnapshotId,
94
+ };
95
+ // Update timeline
96
+ const timeline = loadTimeline(cwd);
97
+ timeline.unshift(snapshot);
98
+ // Trim old snapshots
99
+ if (timeline.length > MAX_SNAPSHOTS) {
100
+ const removed = timeline.splice(MAX_SNAPSHOTS);
101
+ // Cleanup old snapshot files
102
+ for (const old of removed) {
103
+ cleanupSnapshotFiles(cwd, old.id);
104
+ }
105
+ }
106
+ saveTimeline(cwd, timeline);
107
+ return snapshot;
108
+ }
109
+ // Restore a snapshot
110
+ function restoreSnapshot(cwd, snapshotId) {
111
+ const timeline = loadTimeline(cwd);
112
+ const snapshot = timeline.find((s) => s.id === snapshotId);
113
+ if (!snapshot) {
114
+ console.warn(`[lasso] Snapshot ${snapshotId} not found`);
115
+ return false;
116
+ }
117
+ try {
118
+ for (const file of snapshot.files) {
119
+ const fullPath = node_path_1.default.resolve(cwd, file.filePath);
120
+ if (file.content === null) {
121
+ // File didn't exist, delete if it exists now
122
+ if (node_fs_1.default.existsSync(fullPath)) {
123
+ node_fs_1.default.unlinkSync(fullPath);
124
+ }
125
+ }
126
+ else {
127
+ // Restore file content
128
+ node_fs_1.default.mkdirSync(node_path_1.default.dirname(fullPath), { recursive: true });
129
+ node_fs_1.default.writeFileSync(fullPath, file.content);
130
+ }
131
+ }
132
+ console.log(`[lasso] Restored snapshot ${snapshotId}: ${snapshot.summary}`);
133
+ return true;
134
+ }
135
+ catch (error) {
136
+ console.error(`[lasso] Failed to restore snapshot ${snapshotId}:`, error);
137
+ return false;
138
+ }
139
+ }
140
+ // Check for conflicts between expected and current file states
141
+ function detectConflicts(cwd, expectedFiles) {
142
+ const conflicts = [];
143
+ for (const expected of expectedFiles) {
144
+ const fullPath = node_path_1.default.resolve(cwd, expected.filePath);
145
+ const expectedHash = expected.hash || (expected.content ? hashContent(expected.content) : "");
146
+ try {
147
+ if (!node_fs_1.default.existsSync(fullPath)) {
148
+ // File doesn't exist but was expected
149
+ if (expected.content !== null) {
150
+ conflicts.push({
151
+ filePath: expected.filePath,
152
+ expectedHash,
153
+ actualHash: "file-missing",
154
+ originalContent: expected.content || "",
155
+ currentContent: "",
156
+ });
157
+ }
158
+ continue;
159
+ }
160
+ const currentContent = node_fs_1.default.readFileSync(fullPath, "utf8");
161
+ const actualHash = hashContent(currentContent);
162
+ if (expectedHash !== actualHash) {
163
+ conflicts.push({
164
+ filePath: expected.filePath,
165
+ expectedHash,
166
+ actualHash,
167
+ originalContent: expected.content || "",
168
+ currentContent,
169
+ });
170
+ }
171
+ }
172
+ catch (error) {
173
+ console.warn(`[lasso] Failed to check conflict for ${expected.filePath}:`, error);
174
+ }
175
+ }
176
+ return conflicts;
177
+ }
178
+ // Get timeline history
179
+ function getTimeline(cwd, limit = 20) {
180
+ const timeline = loadTimeline(cwd);
181
+ return timeline.slice(0, limit);
182
+ }
183
+ // Delete a snapshot
184
+ function deleteSnapshot(cwd, snapshotId) {
185
+ const timeline = loadTimeline(cwd);
186
+ const index = timeline.findIndex((s) => s.id === snapshotId);
187
+ if (index === -1) {
188
+ return false;
189
+ }
190
+ timeline.splice(index, 1);
191
+ saveTimeline(cwd, timeline);
192
+ cleanupSnapshotFiles(cwd, snapshotId);
193
+ return true;
194
+ }
195
+ // Cleanup snapshot-specific files (if we implement per-snapshot storage)
196
+ function cleanupSnapshotFiles(cwd, snapshotId) {
197
+ // Currently all data is in timeline.json, but if we add per-snapshot files,
198
+ // this is where we'd clean them up
199
+ }
200
+ // Clear all snapshots
201
+ function clearSnapshots(cwd) {
202
+ const timelinePath = node_path_1.default.join(cwd, TIMELINE_FILE);
203
+ try {
204
+ if (node_fs_1.default.existsSync(timelinePath)) {
205
+ node_fs_1.default.unlinkSync(timelinePath);
206
+ }
207
+ const snapshotsDir = node_path_1.default.join(cwd, SNAPSHOTS_DIR);
208
+ if (node_fs_1.default.existsSync(snapshotsDir)) {
209
+ node_fs_1.default.rmSync(snapshotsDir, { recursive: true, force: true });
210
+ }
211
+ }
212
+ catch (error) {
213
+ console.warn("[lasso] Failed to clear snapshots:", error);
214
+ }
215
+ }
216
+ // Get snapshot by task ID
217
+ function getSnapshotByTaskId(cwd, taskId) {
218
+ const timeline = loadTimeline(cwd);
219
+ return timeline.find((s) => s.taskId === taskId);
220
+ }
221
+ // Get latest snapshot
222
+ function getLatestSnapshot(cwd) {
223
+ const timeline = loadTimeline(cwd);
224
+ return timeline[0];
225
+ }