@mod-computer/cli 0.2.3 → 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/dist/cli.bundle.js +216 -36371
- 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/commands/status.js
DELETED
|
@@ -1,296 +0,0 @@
|
|
|
1
|
-
// glassware[type="implementation", id="impl-cli-status-cmd--a59c0eef", requirements="requirement-cli-status-cmd--73a24f60,requirement-cli-status-requires-workspace--99699152,requirement-cli-status-local-modified--1e5f495a,requirement-cli-status-workspace-modified--10b85f32,requirement-cli-status-local-new--31131959,requirement-cli-status-workspace-new--47a5c7b1,requirement-cli-status-conflicts--8b6fb820,requirement-cli-status-short--81ed94d8,requirement-cli-status-json--9f708de6"]
|
|
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 { readLocalFile, getLocalFileStats, listLocalFiles } from '../lib/local-fs.js';
|
|
6
|
-
import { getWorkspaceContent } from '../lib/diff.js';
|
|
7
|
-
import { detectGitRepo } from '../lib/git.js';
|
|
8
|
-
import path from 'path';
|
|
9
|
-
import crypto from 'crypto';
|
|
10
|
-
// glassware[type="implementation", id="impl-cli-status-requires-workspace--7061acd4", requirements="requirement-cli-status-requires-workspace--99699152,requirement-cli-fd-error-no-workspace--1919b4fb"]
|
|
11
|
-
function requireWorkspaceConnection() {
|
|
12
|
-
const currentDir = process.cwd();
|
|
13
|
-
const connection = readWorkspaceConnection(currentDir);
|
|
14
|
-
if (!connection) {
|
|
15
|
-
console.error('Error: Not connected to a workspace.');
|
|
16
|
-
console.error('Run `mod init` to connect this directory first.');
|
|
17
|
-
process.exit(1);
|
|
18
|
-
}
|
|
19
|
-
return {
|
|
20
|
-
workspaceId: connection.workspaceId,
|
|
21
|
-
workspaceName: connection.workspaceName,
|
|
22
|
-
workspacePath: connection.path,
|
|
23
|
-
};
|
|
24
|
-
}
|
|
25
|
-
function parseArgs(args) {
|
|
26
|
-
const options = {
|
|
27
|
-
short: false,
|
|
28
|
-
json: false,
|
|
29
|
-
quiet: false,
|
|
30
|
-
};
|
|
31
|
-
let pathFilter;
|
|
32
|
-
for (let i = 0; i < args.length; i++) {
|
|
33
|
-
const arg = args[i];
|
|
34
|
-
if (arg === '--short' || arg === '-s') {
|
|
35
|
-
options.short = true;
|
|
36
|
-
}
|
|
37
|
-
else if (arg === '--json') {
|
|
38
|
-
options.json = true;
|
|
39
|
-
}
|
|
40
|
-
else if (arg === '--quiet' || arg === '-q') {
|
|
41
|
-
options.quiet = true;
|
|
42
|
-
}
|
|
43
|
-
else if (!arg.startsWith('-') && !pathFilter) {
|
|
44
|
-
pathFilter = arg;
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
return { pathFilter, options };
|
|
48
|
-
}
|
|
49
|
-
/**
|
|
50
|
-
* Quick hash for content comparison
|
|
51
|
-
*/
|
|
52
|
-
function hashContent(content) {
|
|
53
|
-
return crypto.createHash('md5').update(content).digest('hex');
|
|
54
|
-
}
|
|
55
|
-
// glassware[type="implementation", id="impl-cli-fd-perf-status--15f2ce0f", requirements="requirement-cli-fd-perf-status--248b3313"]
|
|
56
|
-
// glassware[type="implementation", id="impl-cli-status-branch--542da302", specifications="specification-spec-status-branch--c6241388,specification-spec-status-no-git--615a5917"]
|
|
57
|
-
export async function statusCommand(args, repo) {
|
|
58
|
-
const { workspaceId, workspacePath } = requireWorkspaceConnection();
|
|
59
|
-
const { pathFilter, options } = parseArgs(args);
|
|
60
|
-
// Detect git repo and branch
|
|
61
|
-
const gitInfo = detectGitRepo(workspacePath);
|
|
62
|
-
try {
|
|
63
|
-
const modWorkspace = createModWorkspace(repo);
|
|
64
|
-
const workspaceHandle = await modWorkspace.openWorkspace(workspaceId);
|
|
65
|
-
// Get workspace files
|
|
66
|
-
const workspaceFiles = await workspaceHandle.file.list();
|
|
67
|
-
// Build workspace file map
|
|
68
|
-
const wsFileMap = new Map();
|
|
69
|
-
for (const file of workspaceFiles) {
|
|
70
|
-
const filePath = file.metadata?.path || file.name;
|
|
71
|
-
if (pathFilter && !filePath.startsWith(pathFilter))
|
|
72
|
-
continue;
|
|
73
|
-
wsFileMap.set(filePath, {
|
|
74
|
-
id: file.id,
|
|
75
|
-
size: file.size,
|
|
76
|
-
updatedAt: file.updatedAt,
|
|
77
|
-
});
|
|
78
|
-
}
|
|
79
|
-
// Get local files
|
|
80
|
-
const localFiles = await listLocalFiles(workspacePath);
|
|
81
|
-
const localFileMap = new Map();
|
|
82
|
-
for (const relativePath of localFiles) {
|
|
83
|
-
if (pathFilter && !relativePath.startsWith(pathFilter))
|
|
84
|
-
continue;
|
|
85
|
-
// Skip common ignore patterns
|
|
86
|
-
if (relativePath.includes('node_modules/') ||
|
|
87
|
-
relativePath.includes('.git/') ||
|
|
88
|
-
relativePath.includes('.mod/') ||
|
|
89
|
-
relativePath.startsWith('.')) {
|
|
90
|
-
continue;
|
|
91
|
-
}
|
|
92
|
-
const stats = await getLocalFileStats(path.join(workspacePath, relativePath));
|
|
93
|
-
if (stats) {
|
|
94
|
-
localFileMap.set(relativePath, stats);
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
// Compare files
|
|
98
|
-
const statusList = [];
|
|
99
|
-
const processedPaths = new Set();
|
|
100
|
-
// Check workspace files against local
|
|
101
|
-
for (const [filePath, wsFile] of wsFileMap) {
|
|
102
|
-
processedPaths.add(filePath);
|
|
103
|
-
const localFile = localFileMap.get(filePath);
|
|
104
|
-
if (!localFile) {
|
|
105
|
-
// File in workspace but not local
|
|
106
|
-
statusList.push({
|
|
107
|
-
path: filePath,
|
|
108
|
-
status: 'added-workspace',
|
|
109
|
-
workspaceSize: wsFile.size,
|
|
110
|
-
});
|
|
111
|
-
}
|
|
112
|
-
else {
|
|
113
|
-
// File exists in both - compare content
|
|
114
|
-
const localContent = await readLocalFile(path.join(workspacePath, filePath));
|
|
115
|
-
if (localContent !== null) {
|
|
116
|
-
// Get workspace content
|
|
117
|
-
try {
|
|
118
|
-
const handle = await workspaceHandle.file.get(wsFile.id);
|
|
119
|
-
if (handle) {
|
|
120
|
-
const doc = handle.doc();
|
|
121
|
-
const wsContent = getWorkspaceContent(doc?.content);
|
|
122
|
-
const localHash = hashContent(localContent);
|
|
123
|
-
const wsHash = hashContent(wsContent);
|
|
124
|
-
if (localHash !== wsHash) {
|
|
125
|
-
// Content differs - check timestamps to determine direction
|
|
126
|
-
const localTime = localFile.mtime.getTime();
|
|
127
|
-
const wsTime = new Date(wsFile.updatedAt).getTime();
|
|
128
|
-
// If both modified recently (within 1 second of each other), it's a conflict
|
|
129
|
-
const timeDiff = Math.abs(localTime - wsTime);
|
|
130
|
-
if (timeDiff < 1000 && localTime !== wsTime) {
|
|
131
|
-
// Conflict - modified in both places
|
|
132
|
-
statusList.push({
|
|
133
|
-
path: filePath,
|
|
134
|
-
status: 'conflict',
|
|
135
|
-
localSize: localFile.size,
|
|
136
|
-
workspaceSize: wsFile.size,
|
|
137
|
-
});
|
|
138
|
-
}
|
|
139
|
-
else if (localTime > wsTime) {
|
|
140
|
-
// Local is newer
|
|
141
|
-
statusList.push({
|
|
142
|
-
path: filePath,
|
|
143
|
-
status: 'modified-local',
|
|
144
|
-
localSize: localFile.size,
|
|
145
|
-
workspaceSize: wsFile.size,
|
|
146
|
-
});
|
|
147
|
-
}
|
|
148
|
-
else {
|
|
149
|
-
// Workspace is newer
|
|
150
|
-
statusList.push({
|
|
151
|
-
path: filePath,
|
|
152
|
-
status: 'modified-workspace',
|
|
153
|
-
localSize: localFile.size,
|
|
154
|
-
workspaceSize: wsFile.size,
|
|
155
|
-
});
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
catch {
|
|
161
|
-
// If we can't load the file, assume local is newer
|
|
162
|
-
statusList.push({
|
|
163
|
-
path: filePath,
|
|
164
|
-
status: 'modified-local',
|
|
165
|
-
localSize: localFile.size,
|
|
166
|
-
});
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
// Check local files not in workspace
|
|
172
|
-
for (const [filePath, localFile] of localFileMap) {
|
|
173
|
-
if (processedPaths.has(filePath))
|
|
174
|
-
continue;
|
|
175
|
-
statusList.push({
|
|
176
|
-
path: filePath,
|
|
177
|
-
status: 'added-local',
|
|
178
|
-
localSize: localFile.size,
|
|
179
|
-
});
|
|
180
|
-
}
|
|
181
|
-
// Sort by path
|
|
182
|
-
statusList.sort((a, b) => a.path.localeCompare(b.path));
|
|
183
|
-
// Format output
|
|
184
|
-
if (options.json) {
|
|
185
|
-
// JSON output format with git info
|
|
186
|
-
const output = {
|
|
187
|
-
git: {
|
|
188
|
-
isGitRepo: gitInfo.isGitRepo,
|
|
189
|
-
branch: gitInfo.branch,
|
|
190
|
-
isDetached: gitInfo.isDetached,
|
|
191
|
-
},
|
|
192
|
-
localChanges: statusList.filter(f => f.status === 'modified-local' || f.status === 'added-local' || f.status === 'deleted-workspace'),
|
|
193
|
-
workspaceChanges: statusList.filter(f => f.status === 'modified-workspace' || f.status === 'added-workspace' || f.status === 'deleted-local'),
|
|
194
|
-
conflicts: statusList.filter(f => f.status === 'conflict'),
|
|
195
|
-
};
|
|
196
|
-
console.log(JSON.stringify(output, null, 2));
|
|
197
|
-
return;
|
|
198
|
-
}
|
|
199
|
-
if (options.quiet) {
|
|
200
|
-
const localCount = statusList.filter(f => f.status === 'modified-local' || f.status === 'added-local').length;
|
|
201
|
-
const wsCount = statusList.filter(f => f.status === 'modified-workspace' || f.status === 'added-workspace').length;
|
|
202
|
-
const conflictCount = statusList.filter(f => f.status === 'conflict').length;
|
|
203
|
-
console.log(`${localCount} local, ${wsCount} workspace, ${conflictCount} conflicts`);
|
|
204
|
-
return;
|
|
205
|
-
}
|
|
206
|
-
if (options.short) {
|
|
207
|
-
// Short format output
|
|
208
|
-
for (const file of statusList) {
|
|
209
|
-
const code = getStatusCode(file.status);
|
|
210
|
-
console.log(`${code} ${file.path}`);
|
|
211
|
-
}
|
|
212
|
-
if (statusList.length === 0) {
|
|
213
|
-
console.log('No changes.');
|
|
214
|
-
}
|
|
215
|
-
return;
|
|
216
|
-
}
|
|
217
|
-
// Default output - show git branch info first
|
|
218
|
-
if (!options.json && !options.short && !options.quiet) {
|
|
219
|
-
if (gitInfo.isGitRepo) {
|
|
220
|
-
const branchDisplay = gitInfo.isDetached
|
|
221
|
-
? `${gitInfo.branch} (detached HEAD)`
|
|
222
|
-
: gitInfo.branch;
|
|
223
|
-
console.log(`On git branch: ${branchDisplay}`);
|
|
224
|
-
}
|
|
225
|
-
else {
|
|
226
|
-
console.log('On git branch: (no git)');
|
|
227
|
-
}
|
|
228
|
-
console.log('');
|
|
229
|
-
}
|
|
230
|
-
if (statusList.length === 0) {
|
|
231
|
-
console.log('Everything up to date.');
|
|
232
|
-
return;
|
|
233
|
-
}
|
|
234
|
-
// Group by category
|
|
235
|
-
const localModified = statusList.filter(f => f.status === 'modified-local');
|
|
236
|
-
const localAdded = statusList.filter(f => f.status === 'added-local');
|
|
237
|
-
const wsModified = statusList.filter(f => f.status === 'modified-workspace');
|
|
238
|
-
const wsAdded = statusList.filter(f => f.status === 'added-workspace');
|
|
239
|
-
const conflicts = statusList.filter(f => f.status === 'conflict');
|
|
240
|
-
if (localModified.length > 0 || localAdded.length > 0) {
|
|
241
|
-
console.log('Changes not synced to workspace:');
|
|
242
|
-
for (const file of localModified) {
|
|
243
|
-
console.log(` modified: ${file.path}`);
|
|
244
|
-
}
|
|
245
|
-
for (const file of localAdded) {
|
|
246
|
-
console.log(` new file: ${file.path}`);
|
|
247
|
-
}
|
|
248
|
-
console.log('');
|
|
249
|
-
}
|
|
250
|
-
if (wsModified.length > 0 || wsAdded.length > 0) {
|
|
251
|
-
console.log('Changes not synced to local:');
|
|
252
|
-
for (const file of wsModified) {
|
|
253
|
-
console.log(` modified: ${file.path}`);
|
|
254
|
-
}
|
|
255
|
-
for (const file of wsAdded) {
|
|
256
|
-
console.log(` new file: ${file.path}`);
|
|
257
|
-
}
|
|
258
|
-
console.log('');
|
|
259
|
-
}
|
|
260
|
-
if (conflicts.length > 0) {
|
|
261
|
-
console.log('Conflicts (modified in both):');
|
|
262
|
-
for (const file of conflicts) {
|
|
263
|
-
console.log(` !! ${file.path}`);
|
|
264
|
-
}
|
|
265
|
-
console.log('');
|
|
266
|
-
console.log('Use `mod diff <file>` to view differences.');
|
|
267
|
-
console.log('');
|
|
268
|
-
}
|
|
269
|
-
// Summary
|
|
270
|
-
const localCount = localModified.length + localAdded.length;
|
|
271
|
-
const wsCount = wsModified.length + wsAdded.length;
|
|
272
|
-
console.log(`${localCount} file${localCount === 1 ? '' : 's'} changed locally, ${wsCount} file${wsCount === 1 ? '' : 's'} changed in workspace`);
|
|
273
|
-
}
|
|
274
|
-
catch (error) {
|
|
275
|
-
console.error('Error checking status:', error.message);
|
|
276
|
-
process.exit(1);
|
|
277
|
-
}
|
|
278
|
-
}
|
|
279
|
-
function getStatusCode(status) {
|
|
280
|
-
switch (status) {
|
|
281
|
-
case 'modified-local':
|
|
282
|
-
return 'M ';
|
|
283
|
-
case 'modified-workspace':
|
|
284
|
-
return ' M';
|
|
285
|
-
case 'added-local':
|
|
286
|
-
return 'A ';
|
|
287
|
-
case 'added-workspace':
|
|
288
|
-
return ' A';
|
|
289
|
-
case 'deleted-local':
|
|
290
|
-
return 'D ';
|
|
291
|
-
case 'deleted-workspace':
|
|
292
|
-
return ' D';
|
|
293
|
-
case 'conflict':
|
|
294
|
-
return '!!';
|
|
295
|
-
}
|
|
296
|
-
}
|
package/dist/commands/sync.js
DELETED
|
@@ -1,119 +0,0 @@
|
|
|
1
|
-
// glassware[type="implementation", id="cli-sync-command--4fa01713", requirements="requirement-cli-sync-ux-1a--55a635db,requirement-cli-sync-ux-1b--eedac360,requirement-cli-sync-ux-2--fe764dca,requirement-cli-sync-ux-3--fbb89737,requirement-cli-sync-ux-4a--f2e42b98,requirement-cli-sync-ux-4b--00ec3d9c,requirement-cli-sync-ux-5--480ee59f"]
|
|
2
|
-
import { startDaemon, stopDaemon, getDaemonStatus, } from '../daemon/index.js';
|
|
3
|
-
export async function syncCommand(args, repo) {
|
|
4
|
-
const command = args[0] || 'start';
|
|
5
|
-
try {
|
|
6
|
-
switch (command) {
|
|
7
|
-
case 'start':
|
|
8
|
-
await handleStart(args.includes('--verbose'));
|
|
9
|
-
break;
|
|
10
|
-
case 'stop':
|
|
11
|
-
await handleStop(args.includes('--force'));
|
|
12
|
-
break;
|
|
13
|
-
case 'restart':
|
|
14
|
-
await handleStop(args.includes('--force'));
|
|
15
|
-
await handleStart(args.includes('--verbose'));
|
|
16
|
-
break;
|
|
17
|
-
case 'status':
|
|
18
|
-
handleStatus();
|
|
19
|
-
break;
|
|
20
|
-
default:
|
|
21
|
-
showUsage();
|
|
22
|
-
break;
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
catch (error) {
|
|
26
|
-
console.error(`Sync command failed: ${error.message}`);
|
|
27
|
-
process.exit(1);
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
async function handleStart(verbose) {
|
|
31
|
-
const result = await startDaemon({ verbose });
|
|
32
|
-
if (result.success) {
|
|
33
|
-
console.log(result.message);
|
|
34
|
-
}
|
|
35
|
-
else {
|
|
36
|
-
console.error(result.message);
|
|
37
|
-
process.exit(1);
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
async function handleStop(force) {
|
|
41
|
-
const result = await stopDaemon({ force });
|
|
42
|
-
if (result.success) {
|
|
43
|
-
console.log(result.message);
|
|
44
|
-
}
|
|
45
|
-
else {
|
|
46
|
-
console.error(result.message);
|
|
47
|
-
process.exit(1);
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
function handleStatus() {
|
|
51
|
-
const status = getDaemonStatus();
|
|
52
|
-
if (!status.running) {
|
|
53
|
-
console.log('Sync daemon: not running');
|
|
54
|
-
console.log('Run `mod sync start` to begin tracking changes');
|
|
55
|
-
return;
|
|
56
|
-
}
|
|
57
|
-
console.log(`Sync daemon: running (pid ${status.pid})`);
|
|
58
|
-
console.log('');
|
|
59
|
-
if (status.connections.length > 0) {
|
|
60
|
-
console.log('Connected directories:');
|
|
61
|
-
for (const conn of status.connections) {
|
|
62
|
-
const branchInfo = conn.gitBranch ? ` (${conn.gitBranch})` : ' (no git)';
|
|
63
|
-
console.log(` ${conn.path} → ${conn.workspaceName}${branchInfo}`);
|
|
64
|
-
}
|
|
65
|
-
console.log('');
|
|
66
|
-
}
|
|
67
|
-
switch (status.cloudSync) {
|
|
68
|
-
case 'connected':
|
|
69
|
-
console.log('Cloud sync: connected');
|
|
70
|
-
if (status.lastSync) {
|
|
71
|
-
const ago = formatTimeAgo(status.lastSync);
|
|
72
|
-
console.log(`Last sync: ${ago}`);
|
|
73
|
-
}
|
|
74
|
-
console.log(`Pending changes: ${status.pendingChanges}`);
|
|
75
|
-
break;
|
|
76
|
-
case 'not_signed_in':
|
|
77
|
-
console.log('Cloud sync: not signed in');
|
|
78
|
-
console.log('Tracking changes locally');
|
|
79
|
-
console.log('');
|
|
80
|
-
console.log('Sign in to sync with collaborators: mod auth login');
|
|
81
|
-
break;
|
|
82
|
-
case 'disconnected':
|
|
83
|
-
console.log('Cloud sync: disconnected');
|
|
84
|
-
console.log('Changes will sync when connection is restored');
|
|
85
|
-
break;
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
function showUsage() {
|
|
89
|
-
console.log('Usage: mod sync <command>');
|
|
90
|
-
console.log('');
|
|
91
|
-
console.log('Commands:');
|
|
92
|
-
console.log(' start Start the sync daemon in the background');
|
|
93
|
-
console.log(' stop Stop the sync daemon');
|
|
94
|
-
console.log(' restart Restart the sync daemon');
|
|
95
|
-
console.log(' status Show daemon status');
|
|
96
|
-
console.log('');
|
|
97
|
-
console.log('Options:');
|
|
98
|
-
console.log(' --force Force stop operations');
|
|
99
|
-
console.log(' --verbose Show daemon output in terminal');
|
|
100
|
-
}
|
|
101
|
-
function formatTimeAgo(isoString) {
|
|
102
|
-
const date = new Date(isoString);
|
|
103
|
-
const now = new Date();
|
|
104
|
-
const diffMs = now.getTime() - date.getTime();
|
|
105
|
-
const diffSeconds = Math.floor(diffMs / 1000);
|
|
106
|
-
if (diffSeconds < 60) {
|
|
107
|
-
return `${diffSeconds} seconds ago`;
|
|
108
|
-
}
|
|
109
|
-
const diffMinutes = Math.floor(diffSeconds / 60);
|
|
110
|
-
if (diffMinutes < 60) {
|
|
111
|
-
return `${diffMinutes} minute${diffMinutes === 1 ? '' : 's'} ago`;
|
|
112
|
-
}
|
|
113
|
-
const diffHours = Math.floor(diffMinutes / 60);
|
|
114
|
-
if (diffHours < 24) {
|
|
115
|
-
return `${diffHours} hour${diffHours === 1 ? '' : 's'} ago`;
|
|
116
|
-
}
|
|
117
|
-
const diffDays = Math.floor(diffHours / 24);
|
|
118
|
-
return `${diffDays} day${diffDays === 1 ? '' : 's'} ago`;
|
|
119
|
-
}
|