@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
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
import
|
|
4
|
-
import
|
|
1
|
+
// glassware[type="implementation", id="cli-workspace-command--7ef7e657", requirements="requirement-cli-init-ux-2--b69b045f,requirement-cli-init-ux-3--3dde4846,requirement-cli-init-app-1--1c2b11b4"]
|
|
2
|
+
// spec: packages/mod-cli/specs/workspaces.md
|
|
3
|
+
import { createModWorkspace, createModUser } from '@mod/mod-core';
|
|
4
|
+
import { listWorkspaceConnections, readWorkspaceConnection, writeWorkspaceConnection, deleteWorkspaceConnection, readConfig, } from '../lib/storage.js';
|
|
5
|
+
import { input, select, validateWorkspaceName } from '../lib/prompts.js';
|
|
5
6
|
export async function workspaceCommand(args, repo) {
|
|
6
7
|
const [subcommand, ...rest] = args;
|
|
7
8
|
switch (subcommand) {
|
|
@@ -9,415 +10,438 @@ export async function workspaceCommand(args, repo) {
|
|
|
9
10
|
await handleCreateWorkspace(rest, repo);
|
|
10
11
|
break;
|
|
11
12
|
case 'list':
|
|
12
|
-
await handleListWorkspaces(
|
|
13
|
+
await handleListWorkspaces();
|
|
13
14
|
break;
|
|
14
|
-
case '
|
|
15
|
-
await
|
|
15
|
+
case 'list-remote':
|
|
16
|
+
await handleListRemoteWorkspaces(repo);
|
|
17
|
+
break;
|
|
18
|
+
case 'connect':
|
|
19
|
+
await handleConnectWorkspace(rest, repo);
|
|
16
20
|
break;
|
|
17
21
|
case 'info':
|
|
18
|
-
await handleWorkspaceInfo(
|
|
22
|
+
await handleWorkspaceInfo();
|
|
23
|
+
break;
|
|
24
|
+
case 'disconnect':
|
|
25
|
+
await handleDisconnectWorkspace();
|
|
19
26
|
break;
|
|
20
|
-
case '
|
|
21
|
-
await
|
|
27
|
+
case 'register':
|
|
28
|
+
await handleRegisterWorkspaces(repo);
|
|
22
29
|
break;
|
|
23
|
-
case '
|
|
24
|
-
await
|
|
30
|
+
case 'clear':
|
|
31
|
+
await handleClearWorkspace(rest, repo);
|
|
25
32
|
break;
|
|
26
33
|
default:
|
|
27
34
|
if (!subcommand) {
|
|
28
|
-
await handleListWorkspaces(
|
|
35
|
+
await handleListWorkspaces();
|
|
29
36
|
}
|
|
30
37
|
else {
|
|
31
|
-
console.error('Usage: mod workspace <create|list|
|
|
32
|
-
console.error('
|
|
33
|
-
console.error('
|
|
34
|
-
console.error('
|
|
35
|
-
console.error('
|
|
36
|
-
console.error('
|
|
37
|
-
console.error('
|
|
38
|
-
console.error('
|
|
38
|
+
console.error('Usage: mod workspace <create|list|list-remote|connect|info|disconnect|register|clear>');
|
|
39
|
+
console.error('');
|
|
40
|
+
console.error('Commands:');
|
|
41
|
+
console.error(' create [name] Create new workspace in current directory');
|
|
42
|
+
console.error(' list List all locally connected workspaces');
|
|
43
|
+
console.error(' list-remote List all workspaces from your user account');
|
|
44
|
+
console.error(' connect <id> [--name ...] Connect current directory to existing workspace');
|
|
45
|
+
console.error(' info Show current directory workspace info');
|
|
46
|
+
console.error(' disconnect Disconnect current directory from workspace');
|
|
47
|
+
console.error(' register Register connected workspaces to your account');
|
|
48
|
+
console.error(' clear [id] Delete all files from a workspace (DESTRUCTIVE)');
|
|
39
49
|
process.exit(1);
|
|
40
50
|
}
|
|
41
51
|
}
|
|
42
52
|
process.exit(0);
|
|
43
53
|
}
|
|
44
54
|
async function handleCreateWorkspace(args, repo) {
|
|
45
|
-
const
|
|
46
|
-
if
|
|
47
|
-
|
|
55
|
+
const currentDir = process.cwd();
|
|
56
|
+
// Check if already connected
|
|
57
|
+
const existing = readWorkspaceConnection(currentDir);
|
|
58
|
+
if (existing) {
|
|
59
|
+
console.log(`Already connected to workspace: ${existing.workspaceName}`);
|
|
60
|
+
console.log('');
|
|
61
|
+
console.log('To create a new workspace, first disconnect:');
|
|
62
|
+
console.log(' mod workspace disconnect');
|
|
48
63
|
process.exit(1);
|
|
49
64
|
}
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
if (
|
|
53
|
-
|
|
65
|
+
// Get workspace name
|
|
66
|
+
let name = args[0];
|
|
67
|
+
if (!name) {
|
|
68
|
+
const dirName = currentDir.split('/').pop() || 'workspace';
|
|
69
|
+
const defaultName = dirName.charAt(0).toUpperCase() + dirName.slice(1);
|
|
70
|
+
name = await input('Workspace name', {
|
|
71
|
+
default: defaultName,
|
|
72
|
+
validate: validateWorkspaceName,
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
else {
|
|
76
|
+
const validationError = validateWorkspaceName(name);
|
|
77
|
+
if (validationError) {
|
|
78
|
+
console.error(`Invalid workspace name: ${validationError}`);
|
|
79
|
+
process.exit(1);
|
|
80
|
+
}
|
|
54
81
|
}
|
|
82
|
+
console.log('Creating workspace...');
|
|
55
83
|
try {
|
|
56
84
|
const modWorkspace = createModWorkspace(repo);
|
|
57
|
-
|
|
58
|
-
const
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
const config = (readModConfig() || {});
|
|
69
|
-
const updatedConfig = {
|
|
70
|
-
...config,
|
|
71
|
-
workspaceId: workspaceHandle.id,
|
|
72
|
-
workspaceName: name,
|
|
73
|
-
activeBranchId: mainBranch.id, // Set to main branch ID
|
|
74
|
-
lastWorkspaceSwitch: new Date().toISOString()
|
|
75
|
-
};
|
|
76
|
-
const recentWorkspaces = updatedConfig.recentWorkspaces || [];
|
|
77
|
-
const existingIndex = recentWorkspaces.findIndex(w => w.id === workspaceHandle.id);
|
|
78
|
-
const workspaceRef = {
|
|
79
|
-
id: workspaceHandle.id,
|
|
80
|
-
name: name,
|
|
81
|
-
lastAccessed: new Date().toISOString(),
|
|
82
|
-
accessCount: existingIndex !== -1 ? recentWorkspaces[existingIndex].accessCount + 1 : 1
|
|
85
|
+
// No branching by default (enableBranching: true to opt-in)
|
|
86
|
+
const workspace = await modWorkspace.createWorkspace({ name });
|
|
87
|
+
// Wait for workspace document to sync to server
|
|
88
|
+
console.log('Syncing to server...');
|
|
89
|
+
await new Promise(resolve => setTimeout(resolve, 3000));
|
|
90
|
+
const connection = {
|
|
91
|
+
path: currentDir,
|
|
92
|
+
workspaceId: workspace.id,
|
|
93
|
+
workspaceName: workspace.name,
|
|
94
|
+
connectedAt: new Date().toISOString(),
|
|
95
|
+
lastSyncedAt: new Date().toISOString(),
|
|
83
96
|
};
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
}
|
|
92
|
-
writeModConfig(updatedConfig);
|
|
93
|
-
await invalidateWorkspaceCache();
|
|
94
|
-
console.log(`✓ Created and switched to workspace: ${name} (${workspaceHandle.id})`);
|
|
95
|
-
if (description) {
|
|
96
|
-
console.log(` Description: ${description}`);
|
|
97
|
-
}
|
|
97
|
+
writeWorkspaceConnection(currentDir, connection);
|
|
98
|
+
console.log('');
|
|
99
|
+
console.log(`Created workspace: ${workspace.name}`);
|
|
100
|
+
console.log(`ID: ${workspace.id}`);
|
|
101
|
+
console.log(`Path: ${currentDir}`);
|
|
102
|
+
console.log('');
|
|
103
|
+
console.log('Run `mod sync start` to begin tracking changes');
|
|
98
104
|
}
|
|
99
105
|
catch (error) {
|
|
100
|
-
console.error('Failed to create workspace:', error);
|
|
101
|
-
console.log('Try running: mod workspace list to check available workspaces');
|
|
106
|
+
console.error('Failed to create workspace:', error.message);
|
|
102
107
|
process.exit(1);
|
|
103
108
|
}
|
|
104
109
|
}
|
|
105
|
-
async function handleListWorkspaces(
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
const config = readModConfig();
|
|
115
|
-
const activeWorkspaceId = config?.workspaceId;
|
|
116
|
-
console.log('Available workspaces:');
|
|
117
|
-
workspaces.forEach((workspace, index) => {
|
|
118
|
-
const isActive = activeWorkspaceId && workspace.id === activeWorkspaceId;
|
|
119
|
-
const indicator = isActive ? '* ' : ' ';
|
|
120
|
-
const quickNumber = index < 9 ? `[${index + 1}] ` : ' ';
|
|
121
|
-
console.log(`${indicator}${quickNumber}${workspace.name} (${workspace.id})`);
|
|
122
|
-
if (workspace.description) {
|
|
123
|
-
console.log(` ${workspace.description}`);
|
|
124
|
-
}
|
|
125
|
-
const stats = [
|
|
126
|
-
`${workspace.fileCount} files`,
|
|
127
|
-
`${workspace.branchCount} branches`,
|
|
128
|
-
`updated ${formatRelativeTime(workspace.lastModified)}`
|
|
129
|
-
].join(', ');
|
|
130
|
-
console.log(` ${stats}`);
|
|
131
|
-
});
|
|
132
|
-
if (workspaces.length > 1) {
|
|
133
|
-
console.log('\nQuick switch: mod workspace switch <number>');
|
|
134
|
-
}
|
|
110
|
+
async function handleListWorkspaces() {
|
|
111
|
+
const connections = listWorkspaceConnections();
|
|
112
|
+
if (connections.length === 0) {
|
|
113
|
+
console.log('No workspaces connected.');
|
|
114
|
+
console.log('');
|
|
115
|
+
console.log('To connect a directory to a workspace:');
|
|
116
|
+
console.log(' cd /path/to/project');
|
|
117
|
+
console.log(' mod init');
|
|
118
|
+
return;
|
|
135
119
|
}
|
|
136
|
-
|
|
137
|
-
|
|
120
|
+
const currentDir = process.cwd();
|
|
121
|
+
const currentConnection = readWorkspaceConnection(currentDir);
|
|
122
|
+
console.log('Connected workspaces:');
|
|
123
|
+
console.log('');
|
|
124
|
+
for (const conn of connections) {
|
|
125
|
+
const isCurrent = currentConnection?.workspaceId === conn.workspaceId &&
|
|
126
|
+
currentConnection?.path === conn.path;
|
|
127
|
+
const marker = isCurrent ? '* ' : ' ';
|
|
128
|
+
console.log(`${marker}${conn.workspaceName}`);
|
|
129
|
+
console.log(` Path: ${conn.path}`);
|
|
130
|
+
console.log(` ID: ${conn.workspaceId}`);
|
|
131
|
+
console.log(` Connected: ${formatDate(conn.connectedAt)}`);
|
|
132
|
+
console.log('');
|
|
133
|
+
}
|
|
134
|
+
if (currentConnection) {
|
|
135
|
+
console.log(`Current directory is connected to: ${currentConnection.workspaceName}`);
|
|
136
|
+
}
|
|
137
|
+
else {
|
|
138
|
+
console.log('Current directory is not connected to any workspace.');
|
|
139
|
+
console.log('Run `mod init` to connect.');
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
async function handleWorkspaceInfo() {
|
|
143
|
+
const currentDir = process.cwd();
|
|
144
|
+
const connection = readWorkspaceConnection(currentDir);
|
|
145
|
+
if (!connection) {
|
|
146
|
+
console.log('Current directory is not connected to a workspace.');
|
|
147
|
+
console.log('');
|
|
148
|
+
console.log('To connect:');
|
|
149
|
+
console.log(' mod init');
|
|
138
150
|
process.exit(1);
|
|
139
151
|
}
|
|
152
|
+
console.log(`Workspace: ${connection.workspaceName}`);
|
|
153
|
+
console.log(`ID: ${connection.workspaceId}`);
|
|
154
|
+
console.log(`Path: ${connection.path}`);
|
|
155
|
+
console.log(`Connected: ${formatDate(connection.connectedAt)}`);
|
|
156
|
+
console.log(`Last synced: ${formatDate(connection.lastSyncedAt)}`);
|
|
140
157
|
}
|
|
141
|
-
async function
|
|
142
|
-
const
|
|
143
|
-
|
|
144
|
-
|
|
158
|
+
async function handleDisconnectWorkspace() {
|
|
159
|
+
const currentDir = process.cwd();
|
|
160
|
+
const connection = readWorkspaceConnection(currentDir);
|
|
161
|
+
if (!connection) {
|
|
162
|
+
console.log('Current directory is not connected to a workspace.');
|
|
145
163
|
process.exit(1);
|
|
146
164
|
}
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
workspaces.forEach((w) => console.log(` ${w.name} (${w.id})`));
|
|
155
|
-
process.exit(1);
|
|
156
|
-
}
|
|
157
|
-
const config = (readModConfig() || {});
|
|
158
|
-
const updatedConfig = {
|
|
159
|
-
...config,
|
|
160
|
-
workspaceId: targetWorkspace.id,
|
|
161
|
-
workspaceName: targetWorkspace.name,
|
|
162
|
-
activeBranchId: undefined, // Reset to main branch
|
|
163
|
-
lastWorkspaceSwitch: new Date().toISOString()
|
|
164
|
-
};
|
|
165
|
-
const recentWorkspaces = updatedConfig.recentWorkspaces || [];
|
|
166
|
-
const existingIndex = recentWorkspaces.findIndex(w => w.id === targetWorkspace.id);
|
|
167
|
-
const workspaceRef = {
|
|
168
|
-
id: targetWorkspace.id,
|
|
169
|
-
name: targetWorkspace.name,
|
|
170
|
-
lastAccessed: new Date().toISOString(),
|
|
171
|
-
accessCount: existingIndex !== -1 ? recentWorkspaces[existingIndex].accessCount + 1 : 1
|
|
172
|
-
};
|
|
173
|
-
if (existingIndex !== -1) {
|
|
174
|
-
recentWorkspaces[existingIndex] = workspaceRef;
|
|
175
|
-
}
|
|
176
|
-
else {
|
|
177
|
-
recentWorkspaces.unshift(workspaceRef);
|
|
178
|
-
updatedConfig.recentWorkspaces = recentWorkspaces.slice(0, 10);
|
|
179
|
-
}
|
|
180
|
-
writeModConfig(updatedConfig);
|
|
181
|
-
console.log(`✓ Switched to workspace: ${targetWorkspace.name} (${targetWorkspace.id})`);
|
|
165
|
+
const choice = await select('Disconnect from workspace?', [
|
|
166
|
+
{ label: 'Yes, disconnect', value: 'yes' },
|
|
167
|
+
{ label: 'Cancel', value: 'no' },
|
|
168
|
+
]);
|
|
169
|
+
if (choice === 'no') {
|
|
170
|
+
console.log('Cancelled.');
|
|
171
|
+
return;
|
|
182
172
|
}
|
|
183
|
-
|
|
184
|
-
|
|
173
|
+
const deleted = deleteWorkspaceConnection(currentDir);
|
|
174
|
+
if (deleted) {
|
|
175
|
+
console.log(`Disconnected from workspace: ${connection.workspaceName}`);
|
|
176
|
+
console.log('');
|
|
177
|
+
console.log('The workspace data is preserved. To reconnect:');
|
|
178
|
+
console.log(' mod init');
|
|
179
|
+
}
|
|
180
|
+
else {
|
|
181
|
+
console.error('Failed to disconnect.');
|
|
185
182
|
process.exit(1);
|
|
186
183
|
}
|
|
187
184
|
}
|
|
188
|
-
async function
|
|
189
|
-
const
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
console.log(
|
|
219
|
-
|
|
220
|
-
console.log(`Files: ${targetWorkspace.fileCount}`);
|
|
221
|
-
console.log(`Branches: ${targetWorkspace.branchCount}`);
|
|
222
|
-
console.log(`Last Updated: ${formatAbsoluteTime(targetWorkspace.lastModified)}`);
|
|
223
|
-
if (targetWorkspace.permissions.length > 0) {
|
|
224
|
-
console.log(`Permissions: ${targetWorkspace.permissions.join(', ')}`);
|
|
225
|
-
}
|
|
226
|
-
const config = readModConfig();
|
|
227
|
-
if (config?.workspaceId === targetWorkspace.id && config?.activeBranchId) {
|
|
228
|
-
console.log(`Active Branch: ${config.activeBranchId}`);
|
|
185
|
+
async function handleRegisterWorkspaces(repo) {
|
|
186
|
+
const config = readConfig();
|
|
187
|
+
if (!config.auth) {
|
|
188
|
+
console.log('Not signed in.');
|
|
189
|
+
console.log('');
|
|
190
|
+
console.log('Sign in first with: mod auth login');
|
|
191
|
+
process.exit(1);
|
|
192
|
+
}
|
|
193
|
+
if (!config.auth.userDocId) {
|
|
194
|
+
console.log('User document ID not found.');
|
|
195
|
+
console.log('');
|
|
196
|
+
console.log('Try logging in again: mod auth login');
|
|
197
|
+
process.exit(1);
|
|
198
|
+
}
|
|
199
|
+
const connections = listWorkspaceConnections();
|
|
200
|
+
if (connections.length === 0) {
|
|
201
|
+
console.log('No connected workspaces found.');
|
|
202
|
+
console.log('');
|
|
203
|
+
console.log('Connect a directory to a workspace with: mod init');
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
206
|
+
console.log(`Registering ${connections.length} workspace(s) to your user account...`);
|
|
207
|
+
console.log('');
|
|
208
|
+
const modUser = createModUser(repo);
|
|
209
|
+
const userDocId = config.auth.userDocId;
|
|
210
|
+
console.log('Using user document:', userDocId);
|
|
211
|
+
let registered = 0;
|
|
212
|
+
for (const conn of connections) {
|
|
213
|
+
try {
|
|
214
|
+
await modUser.addWorkspace(userDocId, conn.workspaceId);
|
|
215
|
+
console.log(`✓ Registered: ${conn.workspaceName}`);
|
|
216
|
+
registered++;
|
|
229
217
|
}
|
|
230
|
-
|
|
231
|
-
console.
|
|
218
|
+
catch (error) {
|
|
219
|
+
console.warn(` Failed to register ${conn.workspaceName}: ${error.message}`);
|
|
232
220
|
}
|
|
233
221
|
}
|
|
234
|
-
|
|
235
|
-
|
|
222
|
+
console.log('');
|
|
223
|
+
console.log(`✓ Registered ${registered} of ${connections.length} workspaces`);
|
|
224
|
+
console.log('');
|
|
225
|
+
// Wait for changes to sync to server
|
|
226
|
+
console.log('Syncing changes to server...');
|
|
227
|
+
await new Promise(resolve => setTimeout(resolve, 2000));
|
|
228
|
+
console.log('');
|
|
229
|
+
console.log('These workspaces should now appear in the web app.');
|
|
230
|
+
}
|
|
231
|
+
async function handleListRemoteWorkspaces(repo) {
|
|
232
|
+
const config = readConfig();
|
|
233
|
+
if (!config.auth) {
|
|
234
|
+
console.log('Not signed in.');
|
|
235
|
+
console.log('');
|
|
236
|
+
console.log('Sign in first with: mod auth login');
|
|
236
237
|
process.exit(1);
|
|
237
238
|
}
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
console.error('Usage: mod workspace delete <name-or-id>');
|
|
239
|
+
if (!config.auth.userDocId) {
|
|
240
|
+
console.log('User document ID not found.');
|
|
241
|
+
console.log('');
|
|
242
|
+
console.log('Try logging in again: mod auth login');
|
|
243
243
|
process.exit(1);
|
|
244
244
|
}
|
|
245
|
+
console.log('Fetching workspaces from your user account...');
|
|
246
|
+
console.log('');
|
|
245
247
|
try {
|
|
246
|
-
const
|
|
247
|
-
const
|
|
248
|
-
|
|
249
|
-
|
|
248
|
+
const modUser = createModUser(repo);
|
|
249
|
+
const userDocId = config.auth.userDocId;
|
|
250
|
+
// Get the user doc
|
|
251
|
+
const userHandle = await repo.find(userDocId);
|
|
252
|
+
await userHandle.whenReady();
|
|
253
|
+
const userDoc = userHandle.doc();
|
|
254
|
+
if (!userDoc) {
|
|
255
|
+
console.log('Could not load user document.');
|
|
250
256
|
process.exit(1);
|
|
251
257
|
}
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
console.log('
|
|
259
|
-
|
|
258
|
+
// Access workspaces from user doc
|
|
259
|
+
const workspaceRefs = userDoc.workspaceIds || [];
|
|
260
|
+
if (workspaceRefs.length === 0) {
|
|
261
|
+
console.log('No workspaces found in your account.');
|
|
262
|
+
console.log('');
|
|
263
|
+
console.log('To register your local workspaces:');
|
|
264
|
+
console.log(' mod workspace register');
|
|
265
|
+
process.exit(0);
|
|
260
266
|
}
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
267
|
+
console.log(`Found ${workspaceRefs.length} workspace(s):\n`);
|
|
268
|
+
for (const wsId of workspaceRefs) {
|
|
269
|
+
console.log(` ${wsId}`);
|
|
270
|
+
// Try to load workspace name
|
|
271
|
+
try {
|
|
272
|
+
const wsHandle = await repo.find(wsId);
|
|
273
|
+
await wsHandle.whenReady();
|
|
274
|
+
const ws = wsHandle.doc();
|
|
275
|
+
if (ws && ws.name) {
|
|
276
|
+
console.log(` Name: ${ws.name}`);
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
catch (e) {
|
|
280
|
+
// Workspace might not be available
|
|
281
|
+
}
|
|
282
|
+
console.log('');
|
|
270
283
|
}
|
|
271
|
-
|
|
272
|
-
console.log(
|
|
273
|
-
console.log('Note: Full deletion implementation pending ModWorkspace.deleteWorkspace() interface');
|
|
284
|
+
console.log('To connect to a workspace:');
|
|
285
|
+
console.log(' mod workspace connect <workspace-id> <workspace-name>');
|
|
274
286
|
}
|
|
275
287
|
catch (error) {
|
|
276
|
-
console.error('Failed to
|
|
288
|
+
console.error('Failed to fetch workspaces:', error.message);
|
|
277
289
|
process.exit(1);
|
|
278
290
|
}
|
|
279
291
|
}
|
|
280
|
-
async function
|
|
281
|
-
const
|
|
282
|
-
|
|
283
|
-
|
|
292
|
+
async function handleConnectWorkspace(args, repo) {
|
|
293
|
+
const currentDir = process.cwd();
|
|
294
|
+
// Check if already connected
|
|
295
|
+
const existing = readWorkspaceConnection(currentDir);
|
|
296
|
+
if (existing) {
|
|
297
|
+
console.log(`Already connected to workspace: ${existing.workspaceName}`);
|
|
298
|
+
console.log('');
|
|
299
|
+
console.log('To connect to a different workspace, first disconnect:');
|
|
300
|
+
console.log(' mod workspace disconnect');
|
|
284
301
|
process.exit(1);
|
|
285
302
|
}
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
const existingWorkspaces = await getWorkspacesWithCache(modWorkspace);
|
|
294
|
-
const isDuplicate = existingWorkspaces.some((w) => w.name === newName && w.id !== targetWorkspace.id);
|
|
295
|
-
if (isDuplicate) {
|
|
296
|
-
console.error(`Workspace name "${newName}" already exists`);
|
|
297
|
-
process.exit(1);
|
|
303
|
+
// Parse args: first positional is workspace ID, --name is optional override
|
|
304
|
+
let workspaceId;
|
|
305
|
+
let nameOverride;
|
|
306
|
+
for (let i = 0; i < args.length; i++) {
|
|
307
|
+
if (args[i] === '--name' && args[i + 1]) {
|
|
308
|
+
nameOverride = args[i + 1];
|
|
309
|
+
i++; // Skip the next arg
|
|
298
310
|
}
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
const config = readModConfig();
|
|
302
|
-
if (config?.workspaceId === targetWorkspace.id) {
|
|
303
|
-
const updatedConfig = { ...config, workspaceName: newName };
|
|
304
|
-
writeModConfig(updatedConfig);
|
|
311
|
+
else if (!workspaceId && !args[i].startsWith('--')) {
|
|
312
|
+
workspaceId = args[i];
|
|
305
313
|
}
|
|
306
|
-
await invalidateWorkspaceCache();
|
|
307
|
-
console.log(`✓ Workspace renamed from "${targetWorkspace.name}" to "${newName}"`);
|
|
308
|
-
console.log('Note: Full rename implementation pending ModWorkspace.renameWorkspace() interface');
|
|
309
314
|
}
|
|
310
|
-
|
|
311
|
-
console.error('
|
|
315
|
+
if (!workspaceId) {
|
|
316
|
+
console.error('Usage: mod workspace connect <workspace-id> [--name <display-name>]');
|
|
317
|
+
console.error('');
|
|
318
|
+
console.error('To see available workspaces:');
|
|
319
|
+
console.error(' mod workspace list-remote');
|
|
312
320
|
process.exit(1);
|
|
313
321
|
}
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
}
|
|
337
|
-
async function getWorkspacesWithCache(modWorkspace) {
|
|
338
|
-
const cacheDir = '.mod/.cache';
|
|
339
|
-
const cachePath = path.join(cacheDir, 'workspaces.json');
|
|
340
|
-
// Ensure cache directory exists
|
|
341
|
-
if (!fs.existsSync(cacheDir)) {
|
|
342
|
-
fs.mkdirSync(cacheDir, { recursive: true });
|
|
343
|
-
}
|
|
344
|
-
// Check if cache exists and is recent (under 5 minutes old)
|
|
345
|
-
if (fs.existsSync(cachePath)) {
|
|
346
|
-
const stats = fs.statSync(cachePath);
|
|
347
|
-
const ageMinutes = (Date.now() - stats.mtime.getTime()) / (1000 * 60);
|
|
348
|
-
if (ageMinutes < 5) {
|
|
322
|
+
console.log(`Connecting to workspace...`);
|
|
323
|
+
console.log(`ID: ${workspaceId}`);
|
|
324
|
+
console.log('');
|
|
325
|
+
try {
|
|
326
|
+
// Fetch workspace to verify it exists and get name
|
|
327
|
+
const modWorkspace = createModWorkspace(repo);
|
|
328
|
+
const workspaceHandle = await modWorkspace.openWorkspace(workspaceId);
|
|
329
|
+
// Use override name if provided, otherwise fetch from workspace
|
|
330
|
+
const workspaceName = nameOverride || workspaceHandle.name || 'Untitled Workspace';
|
|
331
|
+
console.log(`Workspace: ${workspaceName}`);
|
|
332
|
+
// Create connection
|
|
333
|
+
const connection = {
|
|
334
|
+
path: currentDir,
|
|
335
|
+
workspaceId: workspaceId,
|
|
336
|
+
workspaceName: workspaceName,
|
|
337
|
+
connectedAt: new Date().toISOString(),
|
|
338
|
+
lastSyncedAt: new Date().toISOString(),
|
|
339
|
+
};
|
|
340
|
+
writeWorkspaceConnection(currentDir, connection);
|
|
341
|
+
// Register workspace to user document so it shows in web app
|
|
342
|
+
const config = readConfig();
|
|
343
|
+
if (config.auth?.userDocId) {
|
|
349
344
|
try {
|
|
350
|
-
|
|
351
|
-
|
|
345
|
+
console.log('Registering workspace to your account...');
|
|
346
|
+
const modUser = createModUser(repo);
|
|
347
|
+
await modUser.addWorkspace(config.auth.userDocId, workspaceId);
|
|
348
|
+
// Wait for sync
|
|
349
|
+
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
350
|
+
console.log('✓ Workspace registered to your account');
|
|
352
351
|
}
|
|
353
352
|
catch (error) {
|
|
354
|
-
|
|
353
|
+
console.warn('Note: Could not register workspace to account:', error.message);
|
|
355
354
|
}
|
|
356
355
|
}
|
|
356
|
+
console.log('✓ Connected successfully');
|
|
357
|
+
console.log('');
|
|
358
|
+
console.log('To import files from this directory:');
|
|
359
|
+
console.log(' mod init');
|
|
360
|
+
console.log(' > Select "Resume file import"');
|
|
361
|
+
}
|
|
362
|
+
catch (error) {
|
|
363
|
+
console.error('Failed to connect to workspace:', error.message);
|
|
364
|
+
process.exit(1);
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
async function handleClearWorkspace(args, repo) {
|
|
368
|
+
const [workspaceId] = args;
|
|
369
|
+
if (!workspaceId) {
|
|
370
|
+
const currentDir = process.cwd();
|
|
371
|
+
const connection = readWorkspaceConnection(currentDir);
|
|
372
|
+
if (!connection) {
|
|
373
|
+
console.error('No workspace specified and current directory is not connected.');
|
|
374
|
+
console.error('');
|
|
375
|
+
console.error('Usage: mod workspace clear <workspace-id>');
|
|
376
|
+
console.error(' or: mod workspace clear (when in a connected directory)');
|
|
377
|
+
process.exit(1);
|
|
378
|
+
}
|
|
379
|
+
console.log(`Current workspace: ${connection.workspaceName}`);
|
|
380
|
+
console.log(`ID: ${connection.workspaceId}`);
|
|
357
381
|
}
|
|
382
|
+
else {
|
|
383
|
+
console.log(`Workspace ID: ${workspaceId}`);
|
|
384
|
+
}
|
|
385
|
+
console.log('');
|
|
386
|
+
console.warn('⚠️ WARNING: This will delete ALL files from the workspace.');
|
|
387
|
+
console.warn('⚠️ This action CANNOT be undone.');
|
|
388
|
+
console.log('');
|
|
389
|
+
const confirmation = await select('Are you absolutely sure?', [
|
|
390
|
+
{ label: 'No, cancel', value: 'no' },
|
|
391
|
+
{ label: 'Yes, delete all files', value: 'yes' },
|
|
392
|
+
]);
|
|
393
|
+
if (confirmation === 'no') {
|
|
394
|
+
console.log('Cancelled.');
|
|
395
|
+
return;
|
|
396
|
+
}
|
|
397
|
+
const targetWorkspaceId = workspaceId || readWorkspaceConnection(process.cwd()).workspaceId;
|
|
358
398
|
try {
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
const
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
399
|
+
console.log('Loading workspace...');
|
|
400
|
+
const modWorkspace = createModWorkspace(repo);
|
|
401
|
+
const workspaceHandle = await modWorkspace.openWorkspace(targetWorkspaceId);
|
|
402
|
+
console.log('Fetching files...');
|
|
403
|
+
const files = await workspaceHandle.file.list();
|
|
404
|
+
console.log(`Found ${files.length} files to delete`);
|
|
405
|
+
if (files.length === 0) {
|
|
406
|
+
console.log('Workspace is already empty.');
|
|
407
|
+
return;
|
|
408
|
+
}
|
|
409
|
+
console.log('Deleting files...');
|
|
410
|
+
let deleted = 0;
|
|
411
|
+
for (const file of files) {
|
|
366
412
|
try {
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
413
|
+
await workspaceHandle.file.delete(file.id);
|
|
414
|
+
deleted++;
|
|
415
|
+
if (deleted % 50 === 0) {
|
|
416
|
+
console.log(` Deleted ${deleted}/${files.length} files...`);
|
|
417
|
+
}
|
|
371
418
|
}
|
|
372
419
|
catch (error) {
|
|
373
|
-
|
|
420
|
+
console.warn(` Failed to delete ${file.name}:`, error instanceof Error ? error.message : error);
|
|
374
421
|
}
|
|
375
|
-
workspaces.push({
|
|
376
|
-
id: handle.id,
|
|
377
|
-
name: handle.name,
|
|
378
|
-
description: undefined, // Not available from handle
|
|
379
|
-
fileCount,
|
|
380
|
-
branchCount,
|
|
381
|
-
lastModified: new Date().toISOString(), // Would need to be tracked properly
|
|
382
|
-
permissions: [] // Would need to be populated from permission system
|
|
383
|
-
});
|
|
384
422
|
}
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
}, {})
|
|
392
|
-
};
|
|
393
|
-
fs.writeFileSync(cachePath, JSON.stringify(cache, null, 2));
|
|
394
|
-
return workspaces;
|
|
423
|
+
console.log('');
|
|
424
|
+
console.log(`✓ Deleted ${deleted} files`);
|
|
425
|
+
console.log('');
|
|
426
|
+
console.log('The workspace is now empty. You can import files again with:');
|
|
427
|
+
console.log(' mod init');
|
|
428
|
+
console.log(' > Select "Resume file import"');
|
|
395
429
|
}
|
|
396
430
|
catch (error) {
|
|
397
|
-
console.
|
|
398
|
-
|
|
399
|
-
}
|
|
400
|
-
}
|
|
401
|
-
async function invalidateWorkspaceCache() {
|
|
402
|
-
const cachePath = '.mod/.cache/workspaces.json';
|
|
403
|
-
if (fs.existsSync(cachePath)) {
|
|
404
|
-
fs.unlinkSync(cachePath);
|
|
431
|
+
console.error('Failed to clear workspace:', error.message);
|
|
432
|
+
process.exit(1);
|
|
405
433
|
}
|
|
406
434
|
}
|
|
407
|
-
|
|
408
|
-
function formatRelativeTime(isoString) {
|
|
435
|
+
function formatDate(isoString) {
|
|
409
436
|
const date = new Date(isoString);
|
|
410
437
|
const now = new Date();
|
|
411
|
-
const
|
|
438
|
+
const diffMs = now.getTime() - date.getTime();
|
|
439
|
+
const diffHours = Math.floor(diffMs / (1000 * 60 * 60));
|
|
412
440
|
if (diffHours < 1)
|
|
413
441
|
return 'just now';
|
|
414
442
|
if (diffHours < 24)
|
|
415
443
|
return `${diffHours}h ago`;
|
|
416
444
|
if (diffHours < 24 * 7)
|
|
417
445
|
return `${Math.floor(diffHours / 24)}d ago`;
|
|
418
|
-
return
|
|
419
|
-
}
|
|
420
|
-
function formatAbsoluteTime(isoString) {
|
|
421
|
-
const date = new Date(isoString);
|
|
422
|
-
return date.toLocaleString();
|
|
446
|
+
return date.toLocaleDateString();
|
|
423
447
|
}
|