@mod-computer/cli 0.1.1 → 0.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/README.md +72 -0
- package/dist/cli.bundle.js +23743 -12931
- package/dist/cli.bundle.js.map +4 -4
- package/dist/cli.js +23 -12
- package/dist/commands/add.js +245 -0
- package/dist/commands/auth.js +129 -21
- package/dist/commands/comment.js +568 -0
- package/dist/commands/diff.js +182 -0
- package/dist/commands/index.js +33 -3
- package/dist/commands/init.js +475 -221
- package/dist/commands/ls.js +135 -0
- package/dist/commands/members.js +687 -0
- package/dist/commands/mv.js +282 -0
- package/dist/commands/rm.js +257 -0
- package/dist/commands/status.js +273 -306
- package/dist/commands/sync.js +99 -75
- package/dist/commands/trace.js +1752 -0
- package/dist/commands/workspace.js +354 -330
- package/dist/config/features.js +8 -3
- package/dist/config/release-profiles/development.json +4 -1
- package/dist/config/release-profiles/mvp.json +4 -2
- package/dist/daemon/conflict-resolution.js +172 -0
- package/dist/daemon/content-hash.js +31 -0
- package/dist/daemon/file-sync.js +985 -0
- package/dist/daemon/index.js +203 -0
- package/dist/daemon/mime-types.js +166 -0
- package/dist/daemon/offline-queue.js +211 -0
- package/dist/daemon/path-utils.js +64 -0
- package/dist/daemon/share-policy.js +83 -0
- package/dist/daemon/wasm-errors.js +189 -0
- package/dist/daemon/worker.js +557 -0
- package/dist/daemon-worker.js +3 -2
- package/dist/errors/workspace-errors.js +48 -0
- package/dist/lib/auth-server.js +89 -26
- package/dist/lib/browser.js +1 -1
- package/dist/lib/diff.js +284 -0
- package/dist/lib/formatters.js +204 -0
- package/dist/lib/git.js +137 -0
- package/dist/lib/local-fs.js +201 -0
- package/dist/lib/prompts.js +23 -83
- package/dist/lib/storage.js +11 -1
- package/dist/lib/trace-formatters.js +314 -0
- package/dist/services/add-service.js +554 -0
- package/dist/services/add-validation.js +124 -0
- package/dist/services/mod-config.js +8 -2
- package/dist/services/modignore-service.js +2 -0
- package/dist/stores/use-workspaces-store.js +36 -14
- package/dist/types/add-types.js +99 -0
- package/dist/types/config.js +1 -1
- package/dist/types/workspace-connection.js +53 -2
- package/package.json +7 -5
- package/commands/execute.md +0 -156
- package/commands/overview.md +0 -233
- package/commands/review.md +0 -151
- package/commands/spec.md +0 -169
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
// glassware[type="implementation", id="impl-cli-mv-cmd--7526e170", requirements="requirement-cli-mv-cmd--01f3db90,requirement-cli-mv-requires-workspace--e8eff602,requirement-cli-mv-workspace--ed3f7751,requirement-cli-mv-create-folders--62adc040,requirement-cli-mv-local--7bdf0396,requirement-cli-mv-no-overwrite--3c71bcba,requirement-cli-mv-folder--af5ef6eb"]
|
|
2
|
+
// spec: packages/mod-cli/specs/file-directory.md
|
|
3
|
+
import { createModWorkspace } from '@mod/mod-core';
|
|
4
|
+
import { readWorkspaceConnection } from '../lib/storage.js';
|
|
5
|
+
import { moveLocalFile } from '../lib/local-fs.js';
|
|
6
|
+
import path from 'path';
|
|
7
|
+
// glassware[type="implementation", id="impl-cli-mv-requires-workspace--35f86c8f", requirements="requirement-cli-mv-requires-workspace--e8eff602,requirement-cli-fd-error-no-workspace--1919b4fb"]
|
|
8
|
+
function requireWorkspaceConnection() {
|
|
9
|
+
const currentDir = process.cwd();
|
|
10
|
+
const connection = readWorkspaceConnection(currentDir);
|
|
11
|
+
if (!connection) {
|
|
12
|
+
console.error('Error: Not connected to a workspace.');
|
|
13
|
+
console.error('Run `mod init` to connect this directory first.');
|
|
14
|
+
process.exit(1);
|
|
15
|
+
}
|
|
16
|
+
return {
|
|
17
|
+
workspaceId: connection.workspaceId,
|
|
18
|
+
workspaceName: connection.workspaceName,
|
|
19
|
+
workspacePath: connection.path,
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
function parseArgs(args) {
|
|
23
|
+
const options = {
|
|
24
|
+
force: false,
|
|
25
|
+
local: false,
|
|
26
|
+
dryRun: false,
|
|
27
|
+
quiet: false,
|
|
28
|
+
};
|
|
29
|
+
const paths = [];
|
|
30
|
+
for (let i = 0; i < args.length; i++) {
|
|
31
|
+
const arg = args[i];
|
|
32
|
+
if (arg === '--force' || arg === '-f') {
|
|
33
|
+
options.force = true;
|
|
34
|
+
}
|
|
35
|
+
else if (arg === '--local') {
|
|
36
|
+
options.local = true;
|
|
37
|
+
}
|
|
38
|
+
else if (arg === '--dry-run') {
|
|
39
|
+
options.dryRun = true;
|
|
40
|
+
}
|
|
41
|
+
else if (arg === '--quiet' || arg === '-q') {
|
|
42
|
+
options.quiet = true;
|
|
43
|
+
}
|
|
44
|
+
else if (!arg.startsWith('-')) {
|
|
45
|
+
paths.push(arg);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
// Last path is destination, rest are sources
|
|
49
|
+
const dest = paths.pop();
|
|
50
|
+
return { sources: paths, dest, options };
|
|
51
|
+
}
|
|
52
|
+
export async function mvCommand(args, repo) {
|
|
53
|
+
const { workspaceId, workspacePath } = requireWorkspaceConnection();
|
|
54
|
+
const { sources, dest, options } = parseArgs(args);
|
|
55
|
+
if (sources.length === 0 || !dest) {
|
|
56
|
+
console.error('Usage: mod mv <source...> <dest>');
|
|
57
|
+
console.error('');
|
|
58
|
+
console.error('Options:');
|
|
59
|
+
console.error(' --force, -f Overwrite existing files');
|
|
60
|
+
console.error(' --local Also move local files');
|
|
61
|
+
console.error(' --dry-run Show what would be moved');
|
|
62
|
+
console.error(' --quiet, -q Only show errors');
|
|
63
|
+
process.exit(1);
|
|
64
|
+
}
|
|
65
|
+
try {
|
|
66
|
+
const modWorkspace = createModWorkspace(repo);
|
|
67
|
+
const workspaceHandle = await modWorkspace.openWorkspace(workspaceId);
|
|
68
|
+
// Get all files and folders
|
|
69
|
+
const fileRefs = await workspaceHandle.file.list();
|
|
70
|
+
const folderRefs = await workspaceHandle.folder.list();
|
|
71
|
+
// Determine if destination is a folder
|
|
72
|
+
const destIsFolder = dest.endsWith('/') ||
|
|
73
|
+
folderRefs.some(f => f.path === dest) ||
|
|
74
|
+
sources.length > 1;
|
|
75
|
+
const moveOps = [];
|
|
76
|
+
const foldersToCreate = new Set();
|
|
77
|
+
for (const source of sources) {
|
|
78
|
+
const normalizedSource = source.replace(/\/$/, '');
|
|
79
|
+
// Find matching file
|
|
80
|
+
const matchingFile = fileRefs.find(f => {
|
|
81
|
+
const filePath = f.metadata?.path || f.name;
|
|
82
|
+
return filePath === normalizedSource;
|
|
83
|
+
});
|
|
84
|
+
if (matchingFile) {
|
|
85
|
+
// Single file move/rename
|
|
86
|
+
const filePath = matchingFile.metadata?.path || matchingFile.name;
|
|
87
|
+
const fileName = path.basename(filePath);
|
|
88
|
+
let toPath;
|
|
89
|
+
let toFolderId = null;
|
|
90
|
+
let newName;
|
|
91
|
+
if (destIsFolder) {
|
|
92
|
+
// Move to folder, keep name
|
|
93
|
+
toPath = path.join(dest.replace(/\/$/, ''), fileName);
|
|
94
|
+
// Find or mark folder for creation
|
|
95
|
+
const destFolder = folderRefs.find(f => f.path === dest.replace(/\/$/, ''));
|
|
96
|
+
if (destFolder) {
|
|
97
|
+
toFolderId = destFolder.id;
|
|
98
|
+
}
|
|
99
|
+
else {
|
|
100
|
+
foldersToCreate.add(dest.replace(/\/$/, ''));
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
else {
|
|
104
|
+
// Rename
|
|
105
|
+
toPath = dest;
|
|
106
|
+
newName = path.basename(dest);
|
|
107
|
+
// Determine target folder
|
|
108
|
+
const targetDir = path.dirname(dest);
|
|
109
|
+
if (targetDir && targetDir !== '.') {
|
|
110
|
+
const targetFolder = folderRefs.find(f => f.path === targetDir);
|
|
111
|
+
if (targetFolder) {
|
|
112
|
+
toFolderId = targetFolder.id;
|
|
113
|
+
}
|
|
114
|
+
else {
|
|
115
|
+
foldersToCreate.add(targetDir);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
// Check for existing file at destination (no-overwrite check)
|
|
120
|
+
const existingFile = fileRefs.find(f => {
|
|
121
|
+
const fp = f.metadata?.path || f.name;
|
|
122
|
+
return fp === toPath && f.id !== matchingFile.id;
|
|
123
|
+
});
|
|
124
|
+
if (existingFile && !options.force) {
|
|
125
|
+
console.error(`Error: ${toPath} already exists. Use --force to overwrite.`);
|
|
126
|
+
process.exit(1);
|
|
127
|
+
}
|
|
128
|
+
moveOps.push({
|
|
129
|
+
fileId: matchingFile.id,
|
|
130
|
+
fromPath: filePath,
|
|
131
|
+
toPath,
|
|
132
|
+
toFolderId,
|
|
133
|
+
newName,
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
else {
|
|
137
|
+
// Check if it's a folder - move all contents
|
|
138
|
+
const filesInFolder = fileRefs.filter(f => {
|
|
139
|
+
const filePath = f.metadata?.path || f.name;
|
|
140
|
+
return filePath.startsWith(normalizedSource + '/') || filePath === normalizedSource;
|
|
141
|
+
});
|
|
142
|
+
if (filesInFolder.length > 0) {
|
|
143
|
+
for (const file of filesInFolder) {
|
|
144
|
+
const filePath = file.metadata?.path || file.name;
|
|
145
|
+
const relativePath = filePath.startsWith(normalizedSource + '/')
|
|
146
|
+
? filePath.slice(normalizedSource.length + 1)
|
|
147
|
+
: path.basename(filePath);
|
|
148
|
+
const toPath = path.join(dest.replace(/\/$/, ''), relativePath);
|
|
149
|
+
const targetDir = path.dirname(toPath);
|
|
150
|
+
let toFolderId = null;
|
|
151
|
+
if (targetDir && targetDir !== '.') {
|
|
152
|
+
const targetFolder = folderRefs.find(f => f.path === targetDir);
|
|
153
|
+
if (targetFolder) {
|
|
154
|
+
toFolderId = targetFolder.id;
|
|
155
|
+
}
|
|
156
|
+
else {
|
|
157
|
+
foldersToCreate.add(targetDir);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
moveOps.push({
|
|
161
|
+
fileId: file.id,
|
|
162
|
+
fromPath: filePath,
|
|
163
|
+
toPath,
|
|
164
|
+
toFolderId,
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
else {
|
|
169
|
+
console.error(`Error: File not found in workspace: ${source}`);
|
|
170
|
+
console.error("Run `mod ls` to see workspace files.");
|
|
171
|
+
process.exit(1);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
if (moveOps.length === 0) {
|
|
176
|
+
console.log('No files to move.');
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
// Dry run
|
|
180
|
+
if (options.dryRun) {
|
|
181
|
+
if (foldersToCreate.size > 0) {
|
|
182
|
+
console.log('Would create folders:');
|
|
183
|
+
for (const folder of foldersToCreate) {
|
|
184
|
+
console.log(` ${folder}/`);
|
|
185
|
+
}
|
|
186
|
+
console.log('');
|
|
187
|
+
}
|
|
188
|
+
console.log(`Would move ${moveOps.length} file${moveOps.length === 1 ? '' : 's'}:`);
|
|
189
|
+
for (const op of moveOps) {
|
|
190
|
+
console.log(` ${op.fromPath} -> ${op.toPath}`);
|
|
191
|
+
}
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
// Create folders if needed
|
|
195
|
+
const folderIdMap = new Map();
|
|
196
|
+
for (const folderPath of Array.from(foldersToCreate).sort()) {
|
|
197
|
+
try {
|
|
198
|
+
const newFolder = await workspaceHandle.folder.create(folderPath);
|
|
199
|
+
folderIdMap.set(folderPath, newFolder.id);
|
|
200
|
+
if (!options.quiet) {
|
|
201
|
+
console.log(`Created folder: ${folderPath}/`);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
catch (error) {
|
|
205
|
+
// Folder might already exist, try to find it
|
|
206
|
+
const existing = (await workspaceHandle.folder.list()).find(f => f.path === folderPath);
|
|
207
|
+
if (existing) {
|
|
208
|
+
folderIdMap.set(folderPath, existing.id);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
// Execute moves
|
|
213
|
+
let moved = 0;
|
|
214
|
+
let localMoved = 0;
|
|
215
|
+
const errors = [];
|
|
216
|
+
for (const op of moveOps) {
|
|
217
|
+
try {
|
|
218
|
+
// Update folder ID from created folders
|
|
219
|
+
let targetFolderId = op.toFolderId;
|
|
220
|
+
const targetDir = path.dirname(op.toPath);
|
|
221
|
+
if (targetDir && targetDir !== '.' && folderIdMap.has(targetDir)) {
|
|
222
|
+
targetFolderId = folderIdMap.get(targetDir);
|
|
223
|
+
}
|
|
224
|
+
// Move in workspace
|
|
225
|
+
if (op.newName) {
|
|
226
|
+
await workspaceHandle.file.rename(op.fileId, op.newName);
|
|
227
|
+
}
|
|
228
|
+
if (targetFolderId !== undefined) {
|
|
229
|
+
await workspaceHandle.file.move(op.fileId, targetFolderId);
|
|
230
|
+
}
|
|
231
|
+
moved++;
|
|
232
|
+
if (!options.quiet) {
|
|
233
|
+
console.log(`Moved in workspace: ${op.fromPath} -> ${op.toPath}`);
|
|
234
|
+
}
|
|
235
|
+
// Move local file if --local flag is set
|
|
236
|
+
if (options.local) {
|
|
237
|
+
const localFrom = path.join(workspacePath, op.fromPath);
|
|
238
|
+
const localTo = path.join(workspacePath, op.toPath);
|
|
239
|
+
const result = await moveLocalFile(localFrom, localTo);
|
|
240
|
+
if (result.success) {
|
|
241
|
+
localMoved++;
|
|
242
|
+
if (!options.quiet) {
|
|
243
|
+
console.log(`Moved local file: ${op.fromPath} -> ${op.toPath}`);
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
else if (result.error) {
|
|
247
|
+
// Permission error handling
|
|
248
|
+
console.warn(`Warning: ${result.error}`);
|
|
249
|
+
console.warn('Workspace updated, but local file not moved.');
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
catch (error) {
|
|
254
|
+
errors.push(`Failed to move ${op.fromPath}: ${error.message}`);
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
// Summary
|
|
258
|
+
if (!options.quiet) {
|
|
259
|
+
console.log('');
|
|
260
|
+
if (moveOps.length === 1) {
|
|
261
|
+
console.log(`Moved: ${moveOps[0].fromPath} -> ${moveOps[0].toPath}`);
|
|
262
|
+
}
|
|
263
|
+
else {
|
|
264
|
+
console.log(`Moved ${moved} file${moved === 1 ? '' : 's'}.`);
|
|
265
|
+
}
|
|
266
|
+
if (!options.local && moved > 0) {
|
|
267
|
+
console.log('Note: Local files unchanged. Use --local to also move locally.');
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
if (errors.length > 0) {
|
|
271
|
+
console.error('');
|
|
272
|
+
for (const error of errors) {
|
|
273
|
+
console.error(error);
|
|
274
|
+
}
|
|
275
|
+
process.exit(1);
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
catch (error) {
|
|
279
|
+
console.error('Error moving files:', error.message);
|
|
280
|
+
process.exit(1);
|
|
281
|
+
}
|
|
282
|
+
}
|
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
// glassware[type="implementation", id="impl-cli-rm-cmd--81378802", requirements="requirement-cli-rm-cmd--40931f9e,requirement-cli-rm-requires-workspace--1a926c90,requirement-cli-rm-confirm--027ace3f,requirement-cli-rm-force--5bd56511,requirement-cli-rm-workspace-only--405d41f5,requirement-cli-rm-local--a58b09c9,requirement-cli-rm-recursive--91e50991,requirement-cli-rm-dry-run--c3af989d"]
|
|
2
|
+
// spec: packages/mod-cli/specs/file-directory.md
|
|
3
|
+
import { createModWorkspace } from '@mod/mod-core';
|
|
4
|
+
import { readWorkspaceConnection } from '../lib/storage.js';
|
|
5
|
+
import { select } from '../lib/prompts.js';
|
|
6
|
+
import { matchGlob, deleteLocalFile, deleteLocalDirectory } from '../lib/local-fs.js';
|
|
7
|
+
import path from 'path';
|
|
8
|
+
// glassware[type="implementation", id="impl-cli-rm-requires-workspace--8a7b05f5", requirements="requirement-cli-rm-requires-workspace--1a926c90,requirement-cli-fd-error-no-workspace--1919b4fb"]
|
|
9
|
+
function requireWorkspaceConnection() {
|
|
10
|
+
const currentDir = process.cwd();
|
|
11
|
+
const connection = readWorkspaceConnection(currentDir);
|
|
12
|
+
if (!connection) {
|
|
13
|
+
console.error('Error: Not connected to a workspace.');
|
|
14
|
+
console.error('Run `mod init` to connect this directory first.');
|
|
15
|
+
process.exit(1);
|
|
16
|
+
}
|
|
17
|
+
return {
|
|
18
|
+
workspaceId: connection.workspaceId,
|
|
19
|
+
workspaceName: connection.workspaceName,
|
|
20
|
+
workspacePath: connection.path,
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
function parseArgs(args) {
|
|
24
|
+
const options = {
|
|
25
|
+
recursive: false,
|
|
26
|
+
force: false,
|
|
27
|
+
local: false,
|
|
28
|
+
dryRun: false,
|
|
29
|
+
quiet: false,
|
|
30
|
+
};
|
|
31
|
+
const paths = [];
|
|
32
|
+
for (let i = 0; i < args.length; i++) {
|
|
33
|
+
const arg = args[i];
|
|
34
|
+
if (arg === '--recursive' || arg === '-r') {
|
|
35
|
+
options.recursive = true;
|
|
36
|
+
}
|
|
37
|
+
else if (arg === '--force' || arg === '-f') {
|
|
38
|
+
options.force = true;
|
|
39
|
+
}
|
|
40
|
+
else if (arg === '--local') {
|
|
41
|
+
options.local = true;
|
|
42
|
+
}
|
|
43
|
+
else if (arg === '--dry-run') {
|
|
44
|
+
options.dryRun = true;
|
|
45
|
+
}
|
|
46
|
+
else if (arg === '--quiet' || arg === '-q') {
|
|
47
|
+
options.quiet = true;
|
|
48
|
+
}
|
|
49
|
+
else if (!arg.startsWith('-')) {
|
|
50
|
+
paths.push(arg);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
return { paths, options };
|
|
54
|
+
}
|
|
55
|
+
// glassware[type="implementation", id="impl-cli-fd-error-not-found--00ef7ef6", requirements="requirement-cli-fd-error-not-found--9c364146"]
|
|
56
|
+
function showNotFoundError(filePath) {
|
|
57
|
+
console.error(`Error: File not found in workspace: ${filePath}`);
|
|
58
|
+
console.error("Run `mod ls` to see workspace files.");
|
|
59
|
+
}
|
|
60
|
+
export async function rmCommand(args, repo) {
|
|
61
|
+
const { workspaceId, workspacePath } = requireWorkspaceConnection();
|
|
62
|
+
const { paths, options } = parseArgs(args);
|
|
63
|
+
if (paths.length === 0) {
|
|
64
|
+
console.error('Usage: mod rm <path...>');
|
|
65
|
+
console.error('');
|
|
66
|
+
console.error('Options:');
|
|
67
|
+
console.error(' --recursive, -r Remove directories recursively');
|
|
68
|
+
console.error(' --force, -f Skip confirmation prompt');
|
|
69
|
+
console.error(' --local Also delete from local filesystem');
|
|
70
|
+
console.error(' --dry-run Show what would be removed');
|
|
71
|
+
console.error(' --quiet, -q Only show errors');
|
|
72
|
+
process.exit(1);
|
|
73
|
+
}
|
|
74
|
+
try {
|
|
75
|
+
const modWorkspace = createModWorkspace(repo);
|
|
76
|
+
const workspaceHandle = await modWorkspace.openWorkspace(workspaceId);
|
|
77
|
+
// Get all files and folders
|
|
78
|
+
const fileRefs = await workspaceHandle.file.list();
|
|
79
|
+
const folderRefs = await workspaceHandle.folder.list();
|
|
80
|
+
// Build list of files to remove
|
|
81
|
+
const filesToRemove = [];
|
|
82
|
+
const foldersToRemove = [];
|
|
83
|
+
for (const targetPath of paths) {
|
|
84
|
+
const isGlob = targetPath.includes('*');
|
|
85
|
+
const isFolder = targetPath.endsWith('/');
|
|
86
|
+
const normalizedPath = targetPath.replace(/\/$/, '');
|
|
87
|
+
if (isGlob) {
|
|
88
|
+
// Glob pattern matching
|
|
89
|
+
for (const file of fileRefs) {
|
|
90
|
+
const filePath = file.metadata?.path || file.name;
|
|
91
|
+
if (matchGlob(filePath, targetPath)) {
|
|
92
|
+
filesToRemove.push({ id: file.id, path: filePath, isFolder: false });
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
else {
|
|
97
|
+
// Exact path or folder
|
|
98
|
+
const matchingFile = fileRefs.find(f => {
|
|
99
|
+
const filePath = f.metadata?.path || f.name;
|
|
100
|
+
return filePath === normalizedPath;
|
|
101
|
+
});
|
|
102
|
+
if (matchingFile) {
|
|
103
|
+
filesToRemove.push({
|
|
104
|
+
id: matchingFile.id,
|
|
105
|
+
path: matchingFile.metadata?.path || matchingFile.name,
|
|
106
|
+
isFolder: false,
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
else {
|
|
110
|
+
// Check if it's a folder
|
|
111
|
+
const matchingFolder = folderRefs.find(f => f.path === normalizedPath);
|
|
112
|
+
if (matchingFolder) {
|
|
113
|
+
if (!options.recursive) {
|
|
114
|
+
console.error(`Error: ${normalizedPath}/ is a folder. Use --recursive to remove.`);
|
|
115
|
+
process.exit(1);
|
|
116
|
+
}
|
|
117
|
+
foldersToRemove.push({
|
|
118
|
+
id: matchingFolder.id,
|
|
119
|
+
path: matchingFolder.path,
|
|
120
|
+
isFolder: true,
|
|
121
|
+
});
|
|
122
|
+
// Add all files in folder
|
|
123
|
+
for (const file of fileRefs) {
|
|
124
|
+
const filePath = file.metadata?.path || file.name;
|
|
125
|
+
if (filePath.startsWith(normalizedPath + '/')) {
|
|
126
|
+
filesToRemove.push({ id: file.id, path: filePath, isFolder: false });
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
else {
|
|
131
|
+
// Check if path matches folder prefix
|
|
132
|
+
const filesInPath = fileRefs.filter(f => {
|
|
133
|
+
const filePath = f.metadata?.path || f.name;
|
|
134
|
+
return filePath.startsWith(normalizedPath + '/');
|
|
135
|
+
});
|
|
136
|
+
if (filesInPath.length > 0) {
|
|
137
|
+
if (!options.recursive) {
|
|
138
|
+
console.error(`Error: ${normalizedPath}/ is a folder. Use --recursive to remove.`);
|
|
139
|
+
process.exit(1);
|
|
140
|
+
}
|
|
141
|
+
for (const file of filesInPath) {
|
|
142
|
+
filesToRemove.push({
|
|
143
|
+
id: file.id,
|
|
144
|
+
path: file.metadata?.path || file.name,
|
|
145
|
+
isFolder: false,
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
else {
|
|
150
|
+
showNotFoundError(targetPath);
|
|
151
|
+
process.exit(1);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
// Deduplicate
|
|
158
|
+
const uniqueFiles = [...new Map(filesToRemove.map(f => [f.id, f])).values()];
|
|
159
|
+
if (uniqueFiles.length === 0) {
|
|
160
|
+
console.log('No files matched the specified pattern(s).');
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
// Dry run - show what would be removed (impl-cli-rm-dry-run)
|
|
164
|
+
if (options.dryRun) {
|
|
165
|
+
console.log(`Would remove ${uniqueFiles.length} file${uniqueFiles.length === 1 ? '' : 's'}:`);
|
|
166
|
+
for (const file of uniqueFiles.slice(0, 20)) {
|
|
167
|
+
console.log(` ${file.path}`);
|
|
168
|
+
}
|
|
169
|
+
if (uniqueFiles.length > 20) {
|
|
170
|
+
console.log(` ... (${uniqueFiles.length - 20} more)`);
|
|
171
|
+
}
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
// Confirm/force check (impl-cli-rm-confirm-force)
|
|
175
|
+
if (uniqueFiles.length > 1 && !options.force) {
|
|
176
|
+
const choice = await select(`This will remove ${uniqueFiles.length} files from the workspace. Continue?`, [
|
|
177
|
+
{ label: 'Yes', value: 'yes' },
|
|
178
|
+
{ label: 'No', value: 'no' },
|
|
179
|
+
]);
|
|
180
|
+
if (choice === 'no') {
|
|
181
|
+
console.log('Cancelled.');
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
// Remove files
|
|
186
|
+
let removed = 0;
|
|
187
|
+
let localDeleted = 0;
|
|
188
|
+
const errors = [];
|
|
189
|
+
for (const file of uniqueFiles) {
|
|
190
|
+
try {
|
|
191
|
+
await workspaceHandle.file.delete(file.id);
|
|
192
|
+
removed++;
|
|
193
|
+
if (!options.quiet) {
|
|
194
|
+
console.log(`Removed from workspace: ${file.path}`);
|
|
195
|
+
}
|
|
196
|
+
// Local deletion (impl-cli-rm-local)
|
|
197
|
+
if (options.local) {
|
|
198
|
+
const localPath = path.join(workspacePath, file.path);
|
|
199
|
+
const result = await deleteLocalFile(localPath);
|
|
200
|
+
if (result.success) {
|
|
201
|
+
localDeleted++;
|
|
202
|
+
if (!options.quiet) {
|
|
203
|
+
console.log(`Deleted local file: ${file.path}`);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
else if (result.error && !options.quiet) {
|
|
207
|
+
console.warn(`Warning: ${result.error}`);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
catch (error) {
|
|
212
|
+
errors.push(`Failed to remove ${file.path}: ${error.message}`);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
// Remove empty folders if recursive
|
|
216
|
+
if (options.recursive && foldersToRemove.length > 0) {
|
|
217
|
+
for (const folder of foldersToRemove) {
|
|
218
|
+
try {
|
|
219
|
+
await workspaceHandle.folder.delete(folder.id);
|
|
220
|
+
if (!options.quiet) {
|
|
221
|
+
console.log(`Removed folder: ${folder.path}/`);
|
|
222
|
+
}
|
|
223
|
+
if (options.local) {
|
|
224
|
+
const localPath = path.join(workspacePath, folder.path);
|
|
225
|
+
await deleteLocalDirectory(localPath);
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
catch {
|
|
229
|
+
// Folder might have other contents, ignore
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
// Summary
|
|
234
|
+
if (!options.quiet) {
|
|
235
|
+
console.log('');
|
|
236
|
+
console.log(`Removed ${removed} file${removed === 1 ? '' : 's'} from workspace.`);
|
|
237
|
+
// Workspace-only reminder (impl-cli-rm-workspace-only)
|
|
238
|
+
if (!options.local && removed > 0) {
|
|
239
|
+
console.log(`Note: Local files unchanged. Use --local to also delete locally.`);
|
|
240
|
+
}
|
|
241
|
+
else if (options.local && localDeleted > 0) {
|
|
242
|
+
console.log(`Deleted ${localDeleted} local file${localDeleted === 1 ? '' : 's'}.`);
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
if (errors.length > 0) {
|
|
246
|
+
console.error('');
|
|
247
|
+
for (const error of errors) {
|
|
248
|
+
console.error(error);
|
|
249
|
+
}
|
|
250
|
+
process.exit(1);
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
catch (error) {
|
|
254
|
+
console.error('Error removing files:', error.message);
|
|
255
|
+
process.exit(1);
|
|
256
|
+
}
|
|
257
|
+
}
|