@mod-computer/cli 0.2.4 → 0.2.5
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/package.json +3 -3
- package/dist/app.js +0 -227
- package/dist/cli.bundle.js.map +0 -7
- package/dist/cli.js +0 -132
- package/dist/commands/add.js +0 -245
- package/dist/commands/agents-run.js +0 -71
- package/dist/commands/auth.js +0 -259
- package/dist/commands/branch.js +0 -1411
- package/dist/commands/claude-sync.js +0 -772
- package/dist/commands/comment.js +0 -568
- package/dist/commands/diff.js +0 -182
- package/dist/commands/index.js +0 -73
- package/dist/commands/init.js +0 -597
- package/dist/commands/ls.js +0 -135
- package/dist/commands/members.js +0 -687
- package/dist/commands/mv.js +0 -282
- package/dist/commands/recover.js +0 -207
- package/dist/commands/rm.js +0 -257
- package/dist/commands/spec.js +0 -386
- package/dist/commands/status.js +0 -296
- package/dist/commands/sync.js +0 -119
- package/dist/commands/trace.js +0 -1752
- package/dist/commands/workspace.js +0 -447
- package/dist/components/conflict-resolution-ui.js +0 -120
- package/dist/components/messages.js +0 -5
- package/dist/components/thread.js +0 -8
- package/dist/config/features.js +0 -83
- package/dist/containers/branches-container.js +0 -140
- package/dist/containers/directory-container.js +0 -92
- package/dist/containers/thread-container.js +0 -214
- package/dist/containers/threads-container.js +0 -27
- package/dist/containers/workspaces-container.js +0 -27
- package/dist/daemon/conflict-resolution.js +0 -172
- package/dist/daemon/content-hash.js +0 -31
- package/dist/daemon/file-sync.js +0 -985
- package/dist/daemon/index.js +0 -203
- package/dist/daemon/mime-types.js +0 -166
- package/dist/daemon/offline-queue.js +0 -211
- package/dist/daemon/path-utils.js +0 -64
- package/dist/daemon/share-policy.js +0 -83
- package/dist/daemon/wasm-errors.js +0 -189
- package/dist/daemon/worker.js +0 -557
- package/dist/daemon-worker.js +0 -258
- package/dist/errors/workspace-errors.js +0 -48
- package/dist/lib/auth-server.js +0 -216
- package/dist/lib/browser.js +0 -35
- package/dist/lib/diff.js +0 -284
- package/dist/lib/formatters.js +0 -204
- package/dist/lib/git.js +0 -137
- package/dist/lib/local-fs.js +0 -201
- package/dist/lib/prompts.js +0 -56
- package/dist/lib/storage.js +0 -213
- package/dist/lib/trace-formatters.js +0 -314
- package/dist/services/add-service.js +0 -554
- package/dist/services/add-validation.js +0 -124
- package/dist/services/automatic-file-tracker.js +0 -303
- package/dist/services/cli-orchestrator.js +0 -227
- package/dist/services/feature-flags.js +0 -187
- package/dist/services/file-import-service.js +0 -283
- package/dist/services/file-transformation-service.js +0 -218
- package/dist/services/logger.js +0 -44
- package/dist/services/mod-config.js +0 -67
- package/dist/services/modignore-service.js +0 -328
- package/dist/services/sync-daemon.js +0 -244
- package/dist/services/thread-notification-service.js +0 -50
- package/dist/services/thread-service.js +0 -147
- package/dist/stores/use-directory-store.js +0 -96
- package/dist/stores/use-threads-store.js +0 -46
- package/dist/stores/use-workspaces-store.js +0 -54
- package/dist/types/add-types.js +0 -99
- package/dist/types/config.js +0 -16
- package/dist/types/index.js +0 -2
- package/dist/types/workspace-connection.js +0 -53
- package/dist/types.js +0 -1
package/dist/lib/diff.js
DELETED
|
@@ -1,284 +0,0 @@
|
|
|
1
|
-
// glassware[type="implementation", id="impl-cli-fd-diff--22cb6c01", requirements="requirement-cli-fd-diff-text--54d22be4,requirement-cli-fd-diff-binary--4aeb6fda,requirement-cli-fd-diff-automerge--8310553b"]
|
|
2
|
-
// spec: packages/mod-cli/specs/file-directory.md
|
|
3
|
-
/**
|
|
4
|
-
* Extract text content from ModFile content structure
|
|
5
|
-
*/
|
|
6
|
-
// glassware[type="implementation", id="impl-cli-fd-diff-automerge--19e08758", requirements="requirement-cli-fd-diff-automerge--8310553b"]
|
|
7
|
-
export function getWorkspaceContent(content) {
|
|
8
|
-
if (typeof content === 'string') {
|
|
9
|
-
return content;
|
|
10
|
-
}
|
|
11
|
-
if (content?.text) {
|
|
12
|
-
// TextFileContent or CodeFileContent
|
|
13
|
-
if (typeof content.text === 'string') {
|
|
14
|
-
return content.text;
|
|
15
|
-
}
|
|
16
|
-
// Automerge Text type - convert to string
|
|
17
|
-
if (content.text.toString) {
|
|
18
|
-
return content.text.toString();
|
|
19
|
-
}
|
|
20
|
-
}
|
|
21
|
-
return '';
|
|
22
|
-
}
|
|
23
|
-
/**
|
|
24
|
-
* Check if content appears to be binary
|
|
25
|
-
*/
|
|
26
|
-
// glassware[type="implementation", id="impl-cli-fd-diff-binary--80add1e0", requirements="requirement-cli-fd-diff-binary--4aeb6fda"]
|
|
27
|
-
export function isBinaryContent(content) {
|
|
28
|
-
// Check for null bytes or high proportion of non-printable characters
|
|
29
|
-
let nonPrintable = 0;
|
|
30
|
-
const checkLength = Math.min(content.length, 8000);
|
|
31
|
-
for (let i = 0; i < checkLength; i++) {
|
|
32
|
-
const code = content.charCodeAt(i);
|
|
33
|
-
if (code === 0) {
|
|
34
|
-
return true; // Null byte = definitely binary
|
|
35
|
-
}
|
|
36
|
-
// Non-printable excluding common whitespace
|
|
37
|
-
if (code < 32 && code !== 9 && code !== 10 && code !== 13) {
|
|
38
|
-
nonPrintable++;
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
return nonPrintable / checkLength > 0.3;
|
|
42
|
-
}
|
|
43
|
-
/**
|
|
44
|
-
* Compute unified diff between two strings
|
|
45
|
-
*/
|
|
46
|
-
// glassware[type="implementation", id="impl-cli-fd-diff-text--9c318f0e", requirements="requirement-cli-fd-diff-text--54d22be4"]
|
|
47
|
-
export function computeDiff(oldContent, newContent, contextLines = 3) {
|
|
48
|
-
const oldLines = oldContent.split('\n');
|
|
49
|
-
const newLines = newContent.split('\n');
|
|
50
|
-
// Simple LCS-based diff algorithm
|
|
51
|
-
const lcs = computeLCS(oldLines, newLines);
|
|
52
|
-
const hunks = [];
|
|
53
|
-
let oldIndex = 0;
|
|
54
|
-
let newIndex = 0;
|
|
55
|
-
let lcsIndex = 0;
|
|
56
|
-
let currentHunk = null;
|
|
57
|
-
while (oldIndex < oldLines.length || newIndex < newLines.length) {
|
|
58
|
-
// Check if current lines match LCS
|
|
59
|
-
const matchesLCS = lcsIndex < lcs.length &&
|
|
60
|
-
oldIndex < oldLines.length &&
|
|
61
|
-
newIndex < newLines.length &&
|
|
62
|
-
oldLines[oldIndex] === lcs[lcsIndex] &&
|
|
63
|
-
newLines[newIndex] === lcs[lcsIndex];
|
|
64
|
-
if (matchesLCS) {
|
|
65
|
-
// Context line
|
|
66
|
-
if (currentHunk) {
|
|
67
|
-
currentHunk.lines.push({
|
|
68
|
-
type: 'context',
|
|
69
|
-
content: oldLines[oldIndex],
|
|
70
|
-
oldLineNumber: oldIndex + 1,
|
|
71
|
-
newLineNumber: newIndex + 1,
|
|
72
|
-
});
|
|
73
|
-
}
|
|
74
|
-
oldIndex++;
|
|
75
|
-
newIndex++;
|
|
76
|
-
lcsIndex++;
|
|
77
|
-
}
|
|
78
|
-
else {
|
|
79
|
-
// Start a new hunk if needed
|
|
80
|
-
if (!currentHunk) {
|
|
81
|
-
const startOld = Math.max(0, oldIndex - contextLines);
|
|
82
|
-
const startNew = Math.max(0, newIndex - contextLines);
|
|
83
|
-
currentHunk = {
|
|
84
|
-
oldStart: startOld + 1,
|
|
85
|
-
oldLines: 0,
|
|
86
|
-
newStart: startNew + 1,
|
|
87
|
-
newLines: 0,
|
|
88
|
-
lines: [],
|
|
89
|
-
};
|
|
90
|
-
// Add leading context
|
|
91
|
-
for (let i = startOld; i < oldIndex; i++) {
|
|
92
|
-
currentHunk.lines.push({
|
|
93
|
-
type: 'context',
|
|
94
|
-
content: oldLines[i],
|
|
95
|
-
oldLineNumber: i + 1,
|
|
96
|
-
newLineNumber: startNew + (i - startOld) + 1,
|
|
97
|
-
});
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
// Handle deletions
|
|
101
|
-
while (oldIndex < oldLines.length &&
|
|
102
|
-
(lcsIndex >= lcs.length || oldLines[oldIndex] !== lcs[lcsIndex])) {
|
|
103
|
-
currentHunk.lines.push({
|
|
104
|
-
type: 'deletion',
|
|
105
|
-
content: oldLines[oldIndex],
|
|
106
|
-
oldLineNumber: oldIndex + 1,
|
|
107
|
-
});
|
|
108
|
-
oldIndex++;
|
|
109
|
-
}
|
|
110
|
-
// Handle additions
|
|
111
|
-
while (newIndex < newLines.length &&
|
|
112
|
-
(lcsIndex >= lcs.length || newLines[newIndex] !== lcs[lcsIndex])) {
|
|
113
|
-
currentHunk.lines.push({
|
|
114
|
-
type: 'addition',
|
|
115
|
-
content: newLines[newIndex],
|
|
116
|
-
newLineNumber: newIndex + 1,
|
|
117
|
-
});
|
|
118
|
-
newIndex++;
|
|
119
|
-
}
|
|
120
|
-
// Check if we should close the hunk (no more changes for a while)
|
|
121
|
-
const nextChange = findNextChange(oldLines, newLines, lcs, oldIndex, newIndex, lcsIndex);
|
|
122
|
-
if (nextChange > contextLines * 2 || (oldIndex >= oldLines.length && newIndex >= newLines.length)) {
|
|
123
|
-
// Add trailing context
|
|
124
|
-
const endContext = Math.min(contextLines, oldLines.length - oldIndex);
|
|
125
|
-
for (let i = 0; i < endContext; i++) {
|
|
126
|
-
if (oldIndex + i < oldLines.length && lcsIndex + i < lcs.length) {
|
|
127
|
-
currentHunk.lines.push({
|
|
128
|
-
type: 'context',
|
|
129
|
-
content: oldLines[oldIndex + i],
|
|
130
|
-
oldLineNumber: oldIndex + i + 1,
|
|
131
|
-
newLineNumber: newIndex + i + 1,
|
|
132
|
-
});
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
// Calculate hunk line counts
|
|
136
|
-
currentHunk.oldLines = currentHunk.lines.filter(l => l.type === 'context' || l.type === 'deletion').length;
|
|
137
|
-
currentHunk.newLines = currentHunk.lines.filter(l => l.type === 'context' || l.type === 'addition').length;
|
|
138
|
-
hunks.push(currentHunk);
|
|
139
|
-
currentHunk = null;
|
|
140
|
-
// Skip context we just added
|
|
141
|
-
oldIndex += endContext;
|
|
142
|
-
newIndex += endContext;
|
|
143
|
-
lcsIndex += endContext;
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
// Close any remaining hunk
|
|
148
|
-
if (currentHunk && currentHunk.lines.length > 0) {
|
|
149
|
-
currentHunk.oldLines = currentHunk.lines.filter(l => l.type === 'context' || l.type === 'deletion').length;
|
|
150
|
-
currentHunk.newLines = currentHunk.lines.filter(l => l.type === 'context' || l.type === 'addition').length;
|
|
151
|
-
hunks.push(currentHunk);
|
|
152
|
-
}
|
|
153
|
-
return hunks;
|
|
154
|
-
}
|
|
155
|
-
/**
|
|
156
|
-
* Compute LCS (Longest Common Subsequence) of two arrays
|
|
157
|
-
*/
|
|
158
|
-
function computeLCS(a, b) {
|
|
159
|
-
const m = a.length;
|
|
160
|
-
const n = b.length;
|
|
161
|
-
// DP table
|
|
162
|
-
const dp = Array(m + 1)
|
|
163
|
-
.fill(null)
|
|
164
|
-
.map(() => Array(n + 1).fill(0));
|
|
165
|
-
for (let i = 1; i <= m; i++) {
|
|
166
|
-
for (let j = 1; j <= n; j++) {
|
|
167
|
-
if (a[i - 1] === b[j - 1]) {
|
|
168
|
-
dp[i][j] = dp[i - 1][j - 1] + 1;
|
|
169
|
-
}
|
|
170
|
-
else {
|
|
171
|
-
dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1]);
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
}
|
|
175
|
-
// Backtrack to find LCS
|
|
176
|
-
const lcs = [];
|
|
177
|
-
let i = m;
|
|
178
|
-
let j = n;
|
|
179
|
-
while (i > 0 && j > 0) {
|
|
180
|
-
if (a[i - 1] === b[j - 1]) {
|
|
181
|
-
lcs.unshift(a[i - 1]);
|
|
182
|
-
i--;
|
|
183
|
-
j--;
|
|
184
|
-
}
|
|
185
|
-
else if (dp[i - 1][j] > dp[i][j - 1]) {
|
|
186
|
-
i--;
|
|
187
|
-
}
|
|
188
|
-
else {
|
|
189
|
-
j--;
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
return lcs;
|
|
193
|
-
}
|
|
194
|
-
/**
|
|
195
|
-
* Find distance to next change
|
|
196
|
-
*/
|
|
197
|
-
function findNextChange(oldLines, newLines, lcs, oldIndex, newIndex, lcsIndex) {
|
|
198
|
-
let count = 0;
|
|
199
|
-
while (oldIndex + count < oldLines.length &&
|
|
200
|
-
newIndex + count < newLines.length &&
|
|
201
|
-
lcsIndex + count < lcs.length &&
|
|
202
|
-
oldLines[oldIndex + count] === lcs[lcsIndex + count] &&
|
|
203
|
-
newLines[newIndex + count] === lcs[lcsIndex + count]) {
|
|
204
|
-
count++;
|
|
205
|
-
}
|
|
206
|
-
return count;
|
|
207
|
-
}
|
|
208
|
-
/**
|
|
209
|
-
* Format diff hunks as unified diff string
|
|
210
|
-
*/
|
|
211
|
-
export function formatUnifiedDiff(oldPath, newPath, hunks, color = true) {
|
|
212
|
-
if (hunks.length === 0) {
|
|
213
|
-
return '';
|
|
214
|
-
}
|
|
215
|
-
const lines = [];
|
|
216
|
-
// Header
|
|
217
|
-
const oldHeader = `--- ${oldPath}`;
|
|
218
|
-
const newHeader = `+++ ${newPath}`;
|
|
219
|
-
lines.push(color ? `\x1b[1m${oldHeader}\x1b[0m` : oldHeader);
|
|
220
|
-
lines.push(color ? `\x1b[1m${newHeader}\x1b[0m` : newHeader);
|
|
221
|
-
for (const hunk of hunks) {
|
|
222
|
-
// Hunk header
|
|
223
|
-
const hunkHeader = `@@ -${hunk.oldStart},${hunk.oldLines} +${hunk.newStart},${hunk.newLines} @@`;
|
|
224
|
-
lines.push(color ? `\x1b[36m${hunkHeader}\x1b[0m` : hunkHeader);
|
|
225
|
-
for (const line of hunk.lines) {
|
|
226
|
-
switch (line.type) {
|
|
227
|
-
case 'context':
|
|
228
|
-
lines.push(` ${line.content}`);
|
|
229
|
-
break;
|
|
230
|
-
case 'deletion':
|
|
231
|
-
const delLine = `-${line.content}`;
|
|
232
|
-
lines.push(color ? `\x1b[31m${delLine}\x1b[0m` : delLine);
|
|
233
|
-
break;
|
|
234
|
-
case 'addition':
|
|
235
|
-
const addLine = `+${line.content}`;
|
|
236
|
-
lines.push(color ? `\x1b[32m${addLine}\x1b[0m` : addLine);
|
|
237
|
-
break;
|
|
238
|
-
}
|
|
239
|
-
}
|
|
240
|
-
}
|
|
241
|
-
return lines.join('\n');
|
|
242
|
-
}
|
|
243
|
-
/**
|
|
244
|
-
* Calculate diff statistics
|
|
245
|
-
*/
|
|
246
|
-
export function calculateDiffStats(hunks) {
|
|
247
|
-
let additions = 0;
|
|
248
|
-
let deletions = 0;
|
|
249
|
-
for (const hunk of hunks) {
|
|
250
|
-
for (const line of hunk.lines) {
|
|
251
|
-
if (line.type === 'addition')
|
|
252
|
-
additions++;
|
|
253
|
-
if (line.type === 'deletion')
|
|
254
|
-
deletions++;
|
|
255
|
-
}
|
|
256
|
-
}
|
|
257
|
-
return { additions, deletions };
|
|
258
|
-
}
|
|
259
|
-
/**
|
|
260
|
-
* Format diffstat summary
|
|
261
|
-
*/
|
|
262
|
-
export function formatDiffStat(files, color = true) {
|
|
263
|
-
const lines = [];
|
|
264
|
-
let totalAdditions = 0;
|
|
265
|
-
let totalDeletions = 0;
|
|
266
|
-
const maxPathLen = Math.max(...files.map(f => f.path.length), 20);
|
|
267
|
-
for (const file of files) {
|
|
268
|
-
const total = file.additions + file.deletions;
|
|
269
|
-
const plusStr = '+'.repeat(Math.min(file.additions, 20));
|
|
270
|
-
const minusStr = '-'.repeat(Math.min(file.deletions, 20));
|
|
271
|
-
let line = ` ${file.path.padEnd(maxPathLen)} | ${String(total).padStart(4)} `;
|
|
272
|
-
if (color) {
|
|
273
|
-
line += `\x1b[32m${plusStr}\x1b[0m\x1b[31m${minusStr}\x1b[0m`;
|
|
274
|
-
}
|
|
275
|
-
else {
|
|
276
|
-
line += `${plusStr}${minusStr}`;
|
|
277
|
-
}
|
|
278
|
-
lines.push(line);
|
|
279
|
-
totalAdditions += file.additions;
|
|
280
|
-
totalDeletions += file.deletions;
|
|
281
|
-
}
|
|
282
|
-
lines.push(` ${files.length} file${files.length === 1 ? '' : 's'} changed, ${totalAdditions} insertion${totalAdditions === 1 ? '' : 's'}(+), ${totalDeletions} deletion${totalDeletions === 1 ? '' : 's'}(-)`);
|
|
283
|
-
return lines.join('\n');
|
|
284
|
-
}
|
package/dist/lib/formatters.js
DELETED
|
@@ -1,204 +0,0 @@
|
|
|
1
|
-
// glassware[type="implementation", id="impl-cli-fd-formatters--faafb955", requirements="requirement-cli-ls-output-default--6e584280,requirement-cli-ls-output-tree--43babfa8,requirement-cli-ls-output-json--d62469fe"]
|
|
2
|
-
// spec: packages/mod-cli/specs/file-directory.md
|
|
3
|
-
/**
|
|
4
|
-
* Format file size in human readable format
|
|
5
|
-
*/
|
|
6
|
-
export function formatSize(bytes) {
|
|
7
|
-
if (bytes === 0)
|
|
8
|
-
return '0 B';
|
|
9
|
-
const units = ['B', 'KB', 'MB', 'GB'];
|
|
10
|
-
const k = 1024;
|
|
11
|
-
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
12
|
-
if (i === 0)
|
|
13
|
-
return `${bytes} B`;
|
|
14
|
-
const size = bytes / Math.pow(k, i);
|
|
15
|
-
return `${size.toFixed(1)} ${units[i]}`;
|
|
16
|
-
}
|
|
17
|
-
/**
|
|
18
|
-
* Format relative time
|
|
19
|
-
*/
|
|
20
|
-
export function formatRelativeTime(isoString) {
|
|
21
|
-
const date = new Date(isoString);
|
|
22
|
-
const now = new Date();
|
|
23
|
-
const diffMs = now.getTime() - date.getTime();
|
|
24
|
-
const minutes = Math.floor(diffMs / (1000 * 60));
|
|
25
|
-
const hours = Math.floor(diffMs / (1000 * 60 * 60));
|
|
26
|
-
const days = Math.floor(diffMs / (1000 * 60 * 60 * 24));
|
|
27
|
-
if (minutes < 1)
|
|
28
|
-
return 'just now';
|
|
29
|
-
if (minutes < 60)
|
|
30
|
-
return `${minutes} min ago`;
|
|
31
|
-
if (hours < 24)
|
|
32
|
-
return `${hours} hour${hours === 1 ? '' : 's'} ago`;
|
|
33
|
-
if (days < 7)
|
|
34
|
-
return `${days} day${days === 1 ? '' : 's'} ago`;
|
|
35
|
-
return date.toLocaleDateString();
|
|
36
|
-
}
|
|
37
|
-
/**
|
|
38
|
-
* Format default table output for file list
|
|
39
|
-
*/
|
|
40
|
-
// glassware[type="implementation", id="impl-cli-ls-output-default--8774cf55", requirements="requirement-cli-ls-output-default--6e584280"]
|
|
41
|
-
export function formatFileTable(files) {
|
|
42
|
-
if (files.length === 0) {
|
|
43
|
-
return 'No files in workspace.\n\nRun `mod init` to import files from this directory.';
|
|
44
|
-
}
|
|
45
|
-
const lines = [];
|
|
46
|
-
// Calculate column widths
|
|
47
|
-
const pathWidth = Math.max(4, Math.min(50, Math.max(...files.map(f => f.path.length))));
|
|
48
|
-
// Header
|
|
49
|
-
lines.push(`${'PATH'.padEnd(pathWidth)} ${'SIZE'.padStart(10)} MODIFIED`);
|
|
50
|
-
// Rows
|
|
51
|
-
for (const file of files) {
|
|
52
|
-
const path = file.path.length > pathWidth
|
|
53
|
-
? '...' + file.path.slice(-(pathWidth - 3))
|
|
54
|
-
: file.path.padEnd(pathWidth);
|
|
55
|
-
const size = formatSize(file.size).padStart(10);
|
|
56
|
-
const modified = formatRelativeTime(file.updatedAt);
|
|
57
|
-
lines.push(`${path} ${size} ${modified}`);
|
|
58
|
-
}
|
|
59
|
-
// Summary
|
|
60
|
-
const totalSize = files.reduce((sum, f) => sum + f.size, 0);
|
|
61
|
-
lines.push('');
|
|
62
|
-
lines.push(`${files.length} file${files.length === 1 ? '' : 's'} (${formatSize(totalSize)} total)`);
|
|
63
|
-
return lines.join('\n');
|
|
64
|
-
}
|
|
65
|
-
function buildTree(files) {
|
|
66
|
-
const root = { name: '.', isFolder: true, children: new Map() };
|
|
67
|
-
for (const file of files) {
|
|
68
|
-
const parts = file.path.split('/');
|
|
69
|
-
let current = root;
|
|
70
|
-
for (let i = 0; i < parts.length; i++) {
|
|
71
|
-
const part = parts[i];
|
|
72
|
-
const isLast = i === parts.length - 1;
|
|
73
|
-
if (!current.children.has(part)) {
|
|
74
|
-
current.children.set(part, {
|
|
75
|
-
name: part,
|
|
76
|
-
isFolder: !isLast,
|
|
77
|
-
size: isLast ? file.size : undefined,
|
|
78
|
-
children: new Map(),
|
|
79
|
-
});
|
|
80
|
-
}
|
|
81
|
-
current = current.children.get(part);
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
return root;
|
|
85
|
-
}
|
|
86
|
-
/**
|
|
87
|
-
* Format tree output
|
|
88
|
-
*/
|
|
89
|
-
// glassware[type="implementation", id="impl-cli-ls-output-tree--c098fff8", requirements="requirement-cli-ls-output-tree--43babfa8"]
|
|
90
|
-
export function formatFileTree(files) {
|
|
91
|
-
if (files.length === 0) {
|
|
92
|
-
return 'No files in workspace.\n\nRun `mod init` to import files from this directory.';
|
|
93
|
-
}
|
|
94
|
-
const tree = buildTree(files);
|
|
95
|
-
const lines = ['.'];
|
|
96
|
-
function renderNode(node, prefix, isLast) {
|
|
97
|
-
const children = Array.from(node.children.values());
|
|
98
|
-
// Sort: folders first, then alphabetically
|
|
99
|
-
children.sort((a, b) => {
|
|
100
|
-
if (a.isFolder !== b.isFolder)
|
|
101
|
-
return a.isFolder ? -1 : 1;
|
|
102
|
-
return a.name.localeCompare(b.name);
|
|
103
|
-
});
|
|
104
|
-
for (let i = 0; i < children.length; i++) {
|
|
105
|
-
const child = children[i];
|
|
106
|
-
const isLastChild = i === children.length - 1;
|
|
107
|
-
const connector = isLastChild ? '\\u2514\\u2500\\u2500 ' : '\\u251c\\u2500\\u2500 ';
|
|
108
|
-
const nextPrefix = prefix + (isLastChild ? ' ' : '\\u2502 ');
|
|
109
|
-
if (child.isFolder) {
|
|
110
|
-
lines.push(`${prefix}${connector}${child.name}/`);
|
|
111
|
-
renderNode(child, nextPrefix, isLastChild);
|
|
112
|
-
}
|
|
113
|
-
else {
|
|
114
|
-
const sizeStr = child.size !== undefined ? ` (${formatSize(child.size)})` : '';
|
|
115
|
-
lines.push(`${prefix}${connector}${child.name}${sizeStr}`);
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
renderNode(tree, '', true);
|
|
120
|
-
return lines.join('\n')
|
|
121
|
-
.replace(/\\u2514/g, '\u2514')
|
|
122
|
-
.replace(/\\u2500/g, '\u2500')
|
|
123
|
-
.replace(/\\u251c/g, '\u251c')
|
|
124
|
-
.replace(/\\u2502/g, '\u2502');
|
|
125
|
-
}
|
|
126
|
-
/**
|
|
127
|
-
* Format JSON output
|
|
128
|
-
*/
|
|
129
|
-
// glassware[type="implementation", id="impl-cli-ls-output-json--75e73ac5", requirements="requirement-cli-ls-output-json--d62469fe"]
|
|
130
|
-
export function formatFileJson(files) {
|
|
131
|
-
const totalSize = files.reduce((sum, f) => sum + f.size, 0);
|
|
132
|
-
const output = {
|
|
133
|
-
files: files.map(f => ({
|
|
134
|
-
path: f.path,
|
|
135
|
-
size: f.size,
|
|
136
|
-
mimeType: f.mimeType,
|
|
137
|
-
updatedAt: f.updatedAt,
|
|
138
|
-
})),
|
|
139
|
-
totalFiles: files.length,
|
|
140
|
-
totalSize,
|
|
141
|
-
};
|
|
142
|
-
return JSON.stringify(output, null, 2);
|
|
143
|
-
}
|
|
144
|
-
/**
|
|
145
|
-
* Format file paths only (quiet mode)
|
|
146
|
-
*/
|
|
147
|
-
export function formatFilePaths(files) {
|
|
148
|
-
return files.map(f => f.path).join('\n');
|
|
149
|
-
}
|
|
150
|
-
/**
|
|
151
|
-
* Format long/detailed output
|
|
152
|
-
*/
|
|
153
|
-
export function formatFileLong(files) {
|
|
154
|
-
if (files.length === 0) {
|
|
155
|
-
return 'No files in workspace.';
|
|
156
|
-
}
|
|
157
|
-
const lines = [];
|
|
158
|
-
// Header
|
|
159
|
-
lines.push('ID PATH SIZE MIME TYPE');
|
|
160
|
-
for (const file of files) {
|
|
161
|
-
const id = file.id.slice(0, 36).padEnd(36);
|
|
162
|
-
const path = file.path.length > 32
|
|
163
|
-
? '...' + file.path.slice(-29)
|
|
164
|
-
: file.path.padEnd(32);
|
|
165
|
-
const size = formatSize(file.size).padStart(10);
|
|
166
|
-
const mime = file.mimeType;
|
|
167
|
-
lines.push(`${id} ${path} ${size} ${mime}`);
|
|
168
|
-
}
|
|
169
|
-
return lines.join('\n');
|
|
170
|
-
}
|
|
171
|
-
/**
|
|
172
|
-
* Determine MIME type category
|
|
173
|
-
*/
|
|
174
|
-
export function getMimeCategory(mimeType) {
|
|
175
|
-
if (mimeType.startsWith('text/')) {
|
|
176
|
-
if (mimeType.includes('typescript') ||
|
|
177
|
-
mimeType.includes('javascript') ||
|
|
178
|
-
mimeType.includes('python') ||
|
|
179
|
-
mimeType.includes('java') ||
|
|
180
|
-
mimeType.includes('c') ||
|
|
181
|
-
mimeType.includes('rust') ||
|
|
182
|
-
mimeType.includes('go')) {
|
|
183
|
-
return 'code';
|
|
184
|
-
}
|
|
185
|
-
return 'text';
|
|
186
|
-
}
|
|
187
|
-
if (mimeType.startsWith('application/json') ||
|
|
188
|
-
mimeType.includes('yaml') ||
|
|
189
|
-
mimeType.includes('xml') ||
|
|
190
|
-
mimeType.includes('csv')) {
|
|
191
|
-
return 'data';
|
|
192
|
-
}
|
|
193
|
-
if (mimeType.includes('javascript') ||
|
|
194
|
-
mimeType.includes('typescript')) {
|
|
195
|
-
return 'code';
|
|
196
|
-
}
|
|
197
|
-
if (mimeType.startsWith('image/') ||
|
|
198
|
-
mimeType.startsWith('audio/') ||
|
|
199
|
-
mimeType.startsWith('video/') ||
|
|
200
|
-
mimeType === 'application/octet-stream') {
|
|
201
|
-
return 'binary';
|
|
202
|
-
}
|
|
203
|
-
return 'unknown';
|
|
204
|
-
}
|
package/dist/lib/git.js
DELETED
|
@@ -1,137 +0,0 @@
|
|
|
1
|
-
// glassware[type="implementation", id="impl-cli-git-utils--7e4f29ca", requirements="requirement-cli-git-app-1--f5a33b8c,requirement-cli-git-app-2--4f3b552c,requirement-cli-git-qual-1--98d1bacb,requirement-cli-git-qual-2--503addb9,requirement-cli-git-qual-3--5af20852,requirement-cli-git-qual-4--7c40baf1"]
|
|
2
|
-
import fs from 'fs';
|
|
3
|
-
import path from 'path';
|
|
4
|
-
/**
|
|
5
|
-
* Detect if a directory is inside a git repository.
|
|
6
|
-
* Walks up the directory tree looking for a .git directory.
|
|
7
|
-
*/
|
|
8
|
-
// glassware[type="implementation", id="impl-cli-detect-git-repo--86f0b85f", specifications="specification-spec-is-git-repo--8d217687"]
|
|
9
|
-
export function detectGitRepo(directory) {
|
|
10
|
-
let currentDir = path.resolve(directory);
|
|
11
|
-
const root = path.parse(currentDir).root;
|
|
12
|
-
while (currentDir !== root) {
|
|
13
|
-
const gitDir = path.join(currentDir, '.git');
|
|
14
|
-
if (fs.existsSync(gitDir)) {
|
|
15
|
-
const headFile = path.join(gitDir, 'HEAD');
|
|
16
|
-
const branchResult = readCurrentBranchWithDetached(headFile);
|
|
17
|
-
return {
|
|
18
|
-
isGitRepo: true,
|
|
19
|
-
branch: branchResult.branch,
|
|
20
|
-
gitDir,
|
|
21
|
-
headFile,
|
|
22
|
-
isDetached: branchResult.isDetached,
|
|
23
|
-
};
|
|
24
|
-
}
|
|
25
|
-
currentDir = path.dirname(currentDir);
|
|
26
|
-
}
|
|
27
|
-
return {
|
|
28
|
-
isGitRepo: false,
|
|
29
|
-
branch: null,
|
|
30
|
-
gitDir: null,
|
|
31
|
-
headFile: null,
|
|
32
|
-
isDetached: false,
|
|
33
|
-
};
|
|
34
|
-
}
|
|
35
|
-
/**
|
|
36
|
-
* Read the current branch name from .git/HEAD with detached state info.
|
|
37
|
-
* Returns branch name and whether in detached HEAD state.
|
|
38
|
-
*/
|
|
39
|
-
// glassware[type="implementation", id="impl-cli-read-branch-detached--42ec893d", specifications="specification-spec-detect-branch--76e466f2,specification-spec-detached-head--0fce4d44"]
|
|
40
|
-
export function readCurrentBranchWithDetached(headFile) {
|
|
41
|
-
try {
|
|
42
|
-
if (!fs.existsSync(headFile)) {
|
|
43
|
-
return { branch: null, isDetached: false };
|
|
44
|
-
}
|
|
45
|
-
const content = fs.readFileSync(headFile, 'utf8').trim();
|
|
46
|
-
// Check if it's a symbolic ref (normal branch)
|
|
47
|
-
// Format: "ref: refs/heads/branch-name"
|
|
48
|
-
if (content.startsWith('ref: refs/heads/')) {
|
|
49
|
-
return {
|
|
50
|
-
branch: content.replace('ref: refs/heads/', ''),
|
|
51
|
-
isDetached: false,
|
|
52
|
-
};
|
|
53
|
-
}
|
|
54
|
-
// Detached HEAD (commit hash) - return detached-{hash} format per spec
|
|
55
|
-
if (/^[0-9a-f]{40}$/i.test(content)) {
|
|
56
|
-
return {
|
|
57
|
-
branch: `detached-${content.substring(0, 7)}`,
|
|
58
|
-
isDetached: true,
|
|
59
|
-
};
|
|
60
|
-
}
|
|
61
|
-
return { branch: null, isDetached: false };
|
|
62
|
-
}
|
|
63
|
-
catch (error) {
|
|
64
|
-
return { branch: null, isDetached: false };
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
/**
|
|
68
|
-
* Read the current branch name from .git/HEAD.
|
|
69
|
-
* Returns null if detached HEAD or unable to read.
|
|
70
|
-
*/
|
|
71
|
-
export function readCurrentBranch(headFile) {
|
|
72
|
-
return readCurrentBranchWithDetached(headFile).branch;
|
|
73
|
-
}
|
|
74
|
-
/**
|
|
75
|
-
* Get git branch for a directory.
|
|
76
|
-
* Convenience function that returns just the branch name or null.
|
|
77
|
-
*/
|
|
78
|
-
export function getGitBranch(directory) {
|
|
79
|
-
const gitInfo = detectGitRepo(directory);
|
|
80
|
-
return gitInfo.branch;
|
|
81
|
-
}
|
|
82
|
-
/**
|
|
83
|
-
* Check if a path should be ignored based on git patterns.
|
|
84
|
-
* This is a simplified check for common patterns.
|
|
85
|
-
*/
|
|
86
|
-
export function isGitIgnoredPath(relativePath) {
|
|
87
|
-
// Always ignore .git directory contents
|
|
88
|
-
if (relativePath.startsWith('.git/') || relativePath === '.git') {
|
|
89
|
-
return true;
|
|
90
|
-
}
|
|
91
|
-
// Ignore common patterns
|
|
92
|
-
const ignoredPatterns = [
|
|
93
|
-
/^node_modules\//,
|
|
94
|
-
/^\.git\//,
|
|
95
|
-
/^dist\//,
|
|
96
|
-
/^build\//,
|
|
97
|
-
/^\.next\//,
|
|
98
|
-
/^\.nuxt\//,
|
|
99
|
-
/^coverage\//,
|
|
100
|
-
/^\.cache\//,
|
|
101
|
-
/^\.automerge-data\//,
|
|
102
|
-
/\.log$/,
|
|
103
|
-
/\.lock$/,
|
|
104
|
-
/^\.DS_Store$/,
|
|
105
|
-
/^Thumbs\.db$/,
|
|
106
|
-
];
|
|
107
|
-
return ignoredPatterns.some(pattern => pattern.test(relativePath));
|
|
108
|
-
}
|
|
109
|
-
/**
|
|
110
|
-
* Check if a git operation is in progress (rebase, merge, etc).
|
|
111
|
-
* When active git operations are in progress, syncing should be paused.
|
|
112
|
-
*/
|
|
113
|
-
// glassware[type="implementation", id="impl-cli-git-operation-check--c31739c8", specifications="specification-spec-git-operations--addf801f"]
|
|
114
|
-
export function isGitOperationInProgress(directory) {
|
|
115
|
-
const gitInfo = detectGitRepo(directory);
|
|
116
|
-
if (!gitInfo.isGitRepo || !gitInfo.gitDir) {
|
|
117
|
-
return false;
|
|
118
|
-
}
|
|
119
|
-
const gitDir = gitInfo.gitDir;
|
|
120
|
-
// Check for various git operations in progress
|
|
121
|
-
const operationIndicators = [
|
|
122
|
-
path.join(gitDir, 'rebase-merge'), // git rebase in progress
|
|
123
|
-
path.join(gitDir, 'rebase-apply'), // git am or rebase --apply in progress
|
|
124
|
-
path.join(gitDir, 'MERGE_HEAD'), // git merge in progress
|
|
125
|
-
path.join(gitDir, 'CHERRY_PICK_HEAD'), // git cherry-pick in progress
|
|
126
|
-
path.join(gitDir, 'REVERT_HEAD'), // git revert in progress
|
|
127
|
-
path.join(gitDir, 'BISECT_LOG'), // git bisect in progress
|
|
128
|
-
];
|
|
129
|
-
return operationIndicators.some(indicator => fs.existsSync(indicator));
|
|
130
|
-
}
|
|
131
|
-
/**
|
|
132
|
-
* Check if the .git directory exists and is valid.
|
|
133
|
-
*/
|
|
134
|
-
export function isGitRepo(directory) {
|
|
135
|
-
const gitInfo = detectGitRepo(directory);
|
|
136
|
-
return gitInfo.isGitRepo;
|
|
137
|
-
}
|