@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.
Files changed (75) hide show
  1. package/dist/cli.bundle.js +216 -36371
  2. package/package.json +3 -3
  3. package/dist/app.js +0 -227
  4. package/dist/cli.bundle.js.map +0 -7
  5. package/dist/cli.js +0 -132
  6. package/dist/commands/add.js +0 -245
  7. package/dist/commands/agents-run.js +0 -71
  8. package/dist/commands/auth.js +0 -259
  9. package/dist/commands/branch.js +0 -1411
  10. package/dist/commands/claude-sync.js +0 -772
  11. package/dist/commands/comment.js +0 -568
  12. package/dist/commands/diff.js +0 -182
  13. package/dist/commands/index.js +0 -73
  14. package/dist/commands/init.js +0 -597
  15. package/dist/commands/ls.js +0 -135
  16. package/dist/commands/members.js +0 -687
  17. package/dist/commands/mv.js +0 -282
  18. package/dist/commands/recover.js +0 -207
  19. package/dist/commands/rm.js +0 -257
  20. package/dist/commands/spec.js +0 -386
  21. package/dist/commands/status.js +0 -296
  22. package/dist/commands/sync.js +0 -119
  23. package/dist/commands/trace.js +0 -1752
  24. package/dist/commands/workspace.js +0 -447
  25. package/dist/components/conflict-resolution-ui.js +0 -120
  26. package/dist/components/messages.js +0 -5
  27. package/dist/components/thread.js +0 -8
  28. package/dist/config/features.js +0 -83
  29. package/dist/containers/branches-container.js +0 -140
  30. package/dist/containers/directory-container.js +0 -92
  31. package/dist/containers/thread-container.js +0 -214
  32. package/dist/containers/threads-container.js +0 -27
  33. package/dist/containers/workspaces-container.js +0 -27
  34. package/dist/daemon/conflict-resolution.js +0 -172
  35. package/dist/daemon/content-hash.js +0 -31
  36. package/dist/daemon/file-sync.js +0 -985
  37. package/dist/daemon/index.js +0 -203
  38. package/dist/daemon/mime-types.js +0 -166
  39. package/dist/daemon/offline-queue.js +0 -211
  40. package/dist/daemon/path-utils.js +0 -64
  41. package/dist/daemon/share-policy.js +0 -83
  42. package/dist/daemon/wasm-errors.js +0 -189
  43. package/dist/daemon/worker.js +0 -557
  44. package/dist/daemon-worker.js +0 -258
  45. package/dist/errors/workspace-errors.js +0 -48
  46. package/dist/lib/auth-server.js +0 -216
  47. package/dist/lib/browser.js +0 -35
  48. package/dist/lib/diff.js +0 -284
  49. package/dist/lib/formatters.js +0 -204
  50. package/dist/lib/git.js +0 -137
  51. package/dist/lib/local-fs.js +0 -201
  52. package/dist/lib/prompts.js +0 -56
  53. package/dist/lib/storage.js +0 -213
  54. package/dist/lib/trace-formatters.js +0 -314
  55. package/dist/services/add-service.js +0 -554
  56. package/dist/services/add-validation.js +0 -124
  57. package/dist/services/automatic-file-tracker.js +0 -303
  58. package/dist/services/cli-orchestrator.js +0 -227
  59. package/dist/services/feature-flags.js +0 -187
  60. package/dist/services/file-import-service.js +0 -283
  61. package/dist/services/file-transformation-service.js +0 -218
  62. package/dist/services/logger.js +0 -44
  63. package/dist/services/mod-config.js +0 -67
  64. package/dist/services/modignore-service.js +0 -328
  65. package/dist/services/sync-daemon.js +0 -244
  66. package/dist/services/thread-notification-service.js +0 -50
  67. package/dist/services/thread-service.js +0 -147
  68. package/dist/stores/use-directory-store.js +0 -96
  69. package/dist/stores/use-threads-store.js +0 -46
  70. package/dist/stores/use-workspaces-store.js +0 -54
  71. package/dist/types/add-types.js +0 -99
  72. package/dist/types/config.js +0 -16
  73. package/dist/types/index.js +0 -2
  74. package/dist/types/workspace-connection.js +0 -53
  75. package/dist/types.js +0 -1
@@ -1,201 +0,0 @@
1
- // glassware[type="implementation", id="impl-cli-fd-local-fs--3a1efb54", requirements="requirement-cli-fd-local-read--bbd1aae6,requirement-cli-fd-local-delete--df1be435,requirement-cli-fd-local-move--022534b1"]
2
- // spec: packages/mod-cli/specs/file-directory.md
3
- import fs from 'fs/promises';
4
- import path from 'path';
5
- /**
6
- * Read local file content as string
7
- */
8
- // glassware[type="implementation", id="impl-cli-fd-local-read--99aa05fa", requirements="requirement-cli-fd-local-read--bbd1aae6"]
9
- export async function readLocalFile(filePath) {
10
- try {
11
- const absolutePath = path.resolve(filePath);
12
- const content = await fs.readFile(absolutePath, 'utf-8');
13
- return content;
14
- }
15
- catch (error) {
16
- if (error.code === 'ENOENT') {
17
- return null;
18
- }
19
- throw error;
20
- }
21
- }
22
- /**
23
- * Check if local file exists
24
- */
25
- export async function localFileExists(filePath) {
26
- try {
27
- const absolutePath = path.resolve(filePath);
28
- await fs.access(absolutePath);
29
- return true;
30
- }
31
- catch {
32
- return false;
33
- }
34
- }
35
- /**
36
- * Get local file stats
37
- */
38
- export async function getLocalFileStats(filePath) {
39
- try {
40
- const absolutePath = path.resolve(filePath);
41
- const stats = await fs.stat(absolutePath);
42
- return {
43
- size: stats.size,
44
- mtime: stats.mtime,
45
- };
46
- }
47
- catch {
48
- return null;
49
- }
50
- }
51
- /**
52
- * Delete local file with proper error handling
53
- */
54
- // glassware[type="implementation", id="impl-cli-fd-local-delete--74bf0b5b", requirements="requirement-cli-fd-local-delete--df1be435"]
55
- export async function deleteLocalFile(filePath) {
56
- try {
57
- const absolutePath = path.resolve(filePath);
58
- await fs.unlink(absolutePath);
59
- return { success: true };
60
- }
61
- catch (error) {
62
- if (error.code === 'ENOENT') {
63
- return { success: true }; // Already deleted
64
- }
65
- if (error.code === 'EACCES' || error.code === 'EPERM') {
66
- return { success: false, error: `Permission denied: ${filePath}` };
67
- }
68
- return { success: false, error: error.message };
69
- }
70
- }
71
- /**
72
- * Delete local directory recursively
73
- */
74
- export async function deleteLocalDirectory(dirPath) {
75
- try {
76
- const absolutePath = path.resolve(dirPath);
77
- await fs.rm(absolutePath, { recursive: true, force: true });
78
- return { success: true };
79
- }
80
- catch (error) {
81
- if (error.code === 'EACCES' || error.code === 'EPERM') {
82
- return { success: false, error: `Permission denied: ${dirPath}` };
83
- }
84
- return { success: false, error: error.message };
85
- }
86
- }
87
- /**
88
- * Move local file with cross-device fallback
89
- */
90
- // glassware[type="implementation", id="impl-cli-fd-local-move--692fd328", requirements="requirement-cli-fd-local-move--022534b1"]
91
- export async function moveLocalFile(src, dest) {
92
- try {
93
- const absoluteSrc = path.resolve(src);
94
- const absoluteDest = path.resolve(dest);
95
- // Ensure destination directory exists
96
- const destDir = path.dirname(absoluteDest);
97
- await fs.mkdir(destDir, { recursive: true });
98
- try {
99
- await fs.rename(absoluteSrc, absoluteDest);
100
- }
101
- catch (err) {
102
- if (err.code === 'EXDEV') {
103
- // Cross-device: copy then delete
104
- await fs.copyFile(absoluteSrc, absoluteDest);
105
- await fs.unlink(absoluteSrc);
106
- }
107
- else {
108
- throw err;
109
- }
110
- }
111
- return { success: true };
112
- }
113
- catch (error) {
114
- if (error.code === 'ENOENT') {
115
- return { success: false, error: `File not found: ${src}` };
116
- }
117
- if (error.code === 'EACCES' || error.code === 'EPERM') {
118
- return { success: false, error: `Permission denied: ${src}` };
119
- }
120
- return { success: false, error: error.message };
121
- }
122
- }
123
- /**
124
- * List files in a local directory recursively
125
- */
126
- export async function listLocalFiles(dirPath, pattern) {
127
- const files = [];
128
- const absolutePath = path.resolve(dirPath);
129
- async function walk(dir) {
130
- try {
131
- const entries = await fs.readdir(dir, { withFileTypes: true });
132
- for (const entry of entries) {
133
- const fullPath = path.join(dir, entry.name);
134
- const relativePath = path.relative(absolutePath, fullPath);
135
- if (entry.isDirectory()) {
136
- await walk(fullPath);
137
- }
138
- else if (entry.isFile()) {
139
- if (!pattern || matchGlob(relativePath, pattern)) {
140
- files.push(relativePath);
141
- }
142
- }
143
- }
144
- }
145
- catch {
146
- // Skip directories we can't read
147
- }
148
- }
149
- await walk(absolutePath);
150
- return files;
151
- }
152
- /**
153
- * Simple glob matching (supports * and **)
154
- */
155
- export function matchGlob(filePath, pattern) {
156
- // Normalize path separators
157
- const normalizedPath = filePath.replace(/\\/g, '/');
158
- const normalizedPattern = pattern.replace(/\\/g, '/');
159
- // Handle patterns starting with **/ (should match any path depth including root)
160
- if (normalizedPattern.startsWith('**/')) {
161
- // **/ means "anywhere in path", so we match either:
162
- // 1. The file directly at root (just matches the part after **/)
163
- // 2. The file nested in directories (matches .* followed by / then the part after **/)
164
- const rest = normalizedPattern.slice(3); // Remove **/
165
- const restRegex = rest
166
- .replace(/\./g, '\\.')
167
- .replace(/\*\*/g, '.*')
168
- .replace(/\*/g, '[^/]*');
169
- const regex = new RegExp(`^(.*\\/)?${restRegex}$`);
170
- return regex.test(normalizedPath);
171
- }
172
- // Convert glob to regex for other patterns
173
- const regexPattern = normalizedPattern
174
- .replace(/\./g, '\\.') // Escape dots
175
- .replace(/\*\*/g, '.*') // ** matches anything
176
- .replace(/\*/g, '[^/]*'); // * matches anything except /
177
- const regex = new RegExp(`^${regexPattern}$`);
178
- return regex.test(normalizedPath);
179
- }
180
- /**
181
- * Check if a file appears to be binary
182
- */
183
- export async function isBinaryFile(filePath) {
184
- try {
185
- const absolutePath = path.resolve(filePath);
186
- const buffer = Buffer.alloc(512);
187
- const fd = await fs.open(absolutePath, 'r');
188
- const { bytesRead } = await fd.read(buffer, 0, 512, 0);
189
- await fd.close();
190
- // Check for null bytes (common in binary files)
191
- for (let i = 0; i < bytesRead; i++) {
192
- if (buffer[i] === 0) {
193
- return true;
194
- }
195
- }
196
- return false;
197
- }
198
- catch {
199
- return false;
200
- }
201
- }
@@ -1,56 +0,0 @@
1
- // glassware[type="implementation", id="cli-prompts--bbf3cbc1", requirements="requirement-cli-init-ux-2--b69b045f,requirement-cli-init-ux-3--3dde4846,requirement-cli-init-ux-4--140d9249"]
2
- import { select as inquirerSelect, input as inquirerInput, confirm as inquirerConfirm } from '@inquirer/prompts';
3
- /**
4
- * Display a selection prompt with arrow key navigation.
5
- */
6
- export async function select(question, options) {
7
- const result = await inquirerSelect({
8
- message: question,
9
- choices: options.map(opt => ({
10
- name: opt.label,
11
- value: opt.value,
12
- description: opt.description,
13
- })),
14
- });
15
- return result;
16
- }
17
- /**
18
- * Display a text input prompt and return the value.
19
- */
20
- export async function input(question, options) {
21
- const result = await inquirerInput({
22
- message: question,
23
- default: options?.default,
24
- validate: options?.validate
25
- ? (value) => options.validate(value) ?? true
26
- : undefined,
27
- });
28
- return result;
29
- }
30
- /**
31
- * Display a yes/no confirmation prompt.
32
- */
33
- export async function confirm(question, options) {
34
- const result = await inquirerConfirm({
35
- message: question,
36
- default: options?.default ?? true,
37
- });
38
- return result;
39
- }
40
- // glassware[type="implementation", id="impl-validate-workspace-name--6732de6e", requirements="requirement-cli-init-qual-2--4ba7ca9c"]
41
- /**
42
- * Validate a workspace name.
43
- * Returns error message if invalid, null if valid.
44
- */
45
- export function validateWorkspaceName(name) {
46
- if (!name || name.trim().length === 0) {
47
- return 'Workspace name cannot be empty';
48
- }
49
- if (name.length > 100) {
50
- return 'Workspace name must be 100 characters or less';
51
- }
52
- if (!/^[a-zA-Z0-9][a-zA-Z0-9-_ ]*[a-zA-Z0-9]$|^[a-zA-Z0-9]$/.test(name.trim())) {
53
- return 'Workspace name must start and end with alphanumeric characters';
54
- }
55
- return null;
56
- }
@@ -1,213 +0,0 @@
1
- // glassware[type="implementation", id="impl-cli-storage--44a11f2b", specifications="specification-spec-token-storage--26199fe8,specification-spec-config-permissions--18b20291,specification-spec-token-reuse--bfc74a74"]
2
- // glassware[type="implementation", id="impl-cli-storage-distributed--54f8febe", specifications="specification-spec-storage-dir--54adedf5,specification-spec-create-storage-dir--29d5746d,specification-spec-nodefs-adapter--55a00910,specification-spec-storage-path-config--26888df2"]
3
- // glassware[type="implementation", id="impl-cli-ws-storage--bb831726", requirements="requirement-cli-ws-storage-location--5691bb5c,requirement-cli-ws-storage-hash--940b982f,requirement-cli-ws-storage-save--e11dd16d,requirement-cli-ws-storage-get--fbbcc532,requirement-cli-ws-storage-remove--05267ecd,requirement-cli-ws-storage-list--f900c191,requirement-cli-ws-storage-atomic--732eb2e0,requirement-cli-ws-storage-permissions--68c455f2"]
4
- // spec: packages/mod-cli/specs/workspaces.md
5
- import fs from 'fs';
6
- import path from 'path';
7
- import crypto from 'crypto';
8
- import os from 'os';
9
- import { DEFAULT_CONFIG, DEFAULT_SETTINGS } from '../types/config.js';
10
- /**
11
- * Get the path to the ~/.mod/ directory.
12
- */
13
- export function getModDir() {
14
- return path.join(os.homedir(), '.mod');
15
- }
16
- /**
17
- * Ensure the ~/.mod/ directory structure exists.
18
- * Creates: ~/.mod/, ~/.mod/workspaces/, ~/.mod/automerge/, ~/.mod/logs/
19
- */
20
- export function ensureModDir() {
21
- const modDir = getModDir();
22
- const dirs = [
23
- modDir,
24
- path.join(modDir, 'workspaces'),
25
- path.join(modDir, 'automerge'),
26
- path.join(modDir, 'logs'),
27
- ];
28
- for (const dir of dirs) {
29
- if (!fs.existsSync(dir)) {
30
- fs.mkdirSync(dir, { recursive: true, mode: 0o700 });
31
- }
32
- }
33
- }
34
- /**
35
- * Get the path to the config file.
36
- */
37
- export function getConfigPath() {
38
- return path.join(getModDir(), 'config');
39
- }
40
- /**
41
- * Read the global config from ~/.mod/config.
42
- * Returns default config if file doesn't exist or is invalid.
43
- */
44
- export function readConfig() {
45
- const configPath = getConfigPath();
46
- if (!fs.existsSync(configPath)) {
47
- return { ...DEFAULT_CONFIG };
48
- }
49
- try {
50
- const content = fs.readFileSync(configPath, 'utf-8');
51
- const stored = JSON.parse(content);
52
- // Merge with defaults for missing fields
53
- return {
54
- version: stored.version ?? DEFAULT_CONFIG.version,
55
- auth: stored.auth ?? null,
56
- settings: {
57
- ...DEFAULT_SETTINGS,
58
- ...stored.settings,
59
- },
60
- };
61
- }
62
- catch (error) {
63
- // Return defaults if file is corrupted
64
- console.warn('Warning: Could not read ~/.mod/config, using defaults');
65
- return { ...DEFAULT_CONFIG };
66
- }
67
- }
68
- /**
69
- * Write config to ~/.mod/config atomically.
70
- * Creates the file with 0600 permissions (user read/write only).
71
- */
72
- export function writeConfig(config) {
73
- ensureModDir();
74
- const configPath = getConfigPath();
75
- atomicWrite(configPath, JSON.stringify(config, null, 2), 0o600);
76
- }
77
- // glassware[type="implementation", id="impl-cli-ws-storage-hash--7647b959", requirements="requirement-cli-ws-storage-hash--940b982f,requirement-cli-init-app-6--aa3c1ace"]
78
- /**
79
- * Get the path to a workspace connection file for a directory.
80
- * Uses SHA-256 hash of the absolute path (first 16 chars).
81
- */
82
- export function getWorkspaceConnectionPath(directoryPath) {
83
- const absolutePath = path.resolve(directoryPath);
84
- const hash = crypto
85
- .createHash('sha256')
86
- .update(absolutePath)
87
- .digest('hex')
88
- .slice(0, 16);
89
- return path.join(getModDir(), 'workspaces', hash);
90
- }
91
- // glassware[type="implementation", id="impl-cli-ws-storage-get--bce2619c", requirements="requirement-cli-ws-storage-get--fbbcc532"]
92
- /**
93
- * Read the workspace connection for a directory.
94
- * Returns null if no connection exists.
95
- */
96
- export function readWorkspaceConnection(directoryPath) {
97
- const connectionPath = getWorkspaceConnectionPath(directoryPath);
98
- if (!fs.existsSync(connectionPath)) {
99
- return null;
100
- }
101
- try {
102
- const content = fs.readFileSync(connectionPath, 'utf-8');
103
- return JSON.parse(content);
104
- }
105
- catch (error) {
106
- console.warn(`Warning: Could not read workspace connection for ${directoryPath}`);
107
- return null;
108
- }
109
- }
110
- // glassware[type="implementation", id="impl-cli-ws-storage-save--352ee3cc", requirements="requirement-cli-ws-storage-save--e11dd16d,requirement-cli-ws-storage-atomic--732eb2e0,requirement-cli-ws-storage-permissions--68c455f2,requirement-cli-init-qual-3--fc1e8a03"]
111
- /**
112
- * Write a workspace connection for a directory atomically.
113
- */
114
- export function writeWorkspaceConnection(directoryPath, connection) {
115
- ensureModDir();
116
- const connectionPath = getWorkspaceConnectionPath(directoryPath);
117
- atomicWrite(connectionPath, JSON.stringify(connection, null, 2), 0o600);
118
- }
119
- // glassware[type="implementation", id="impl-cli-ws-storage-remove--7923bc01", requirements="requirement-cli-ws-storage-remove--05267ecd"]
120
- /**
121
- * Delete the workspace connection for a directory.
122
- */
123
- export function deleteWorkspaceConnection(directoryPath) {
124
- const connectionPath = getWorkspaceConnectionPath(directoryPath);
125
- if (!fs.existsSync(connectionPath)) {
126
- return false;
127
- }
128
- try {
129
- fs.unlinkSync(connectionPath);
130
- return true;
131
- }
132
- catch (error) {
133
- console.warn(`Warning: Could not delete workspace connection for ${directoryPath}`);
134
- return false;
135
- }
136
- }
137
- // glassware[type="implementation", id="impl-cli-ws-storage-list--00734c67", requirements="requirement-cli-ws-storage-list--f900c191,requirement-cli-init-data-0--d659ea61,requirement-cli-init-int-3--ada97918"]
138
- /**
139
- * List all workspace connections.
140
- * Returns array of connections, filtering out corrupted files.
141
- * Workspace connections map local directories to workspaces (data-0).
142
- * Connection visible to sync daemon for directory lookup (int-3).
143
- */
144
- export function listWorkspaceConnections() {
145
- const workspacesDir = path.join(getModDir(), 'workspaces');
146
- if (!fs.existsSync(workspacesDir)) {
147
- return [];
148
- }
149
- const files = fs.readdirSync(workspacesDir);
150
- const connections = [];
151
- for (const file of files) {
152
- const filePath = path.join(workspacesDir, file);
153
- try {
154
- const content = fs.readFileSync(filePath, 'utf-8');
155
- const connection = JSON.parse(content);
156
- connections.push(connection);
157
- }
158
- catch (error) {
159
- // Skip corrupted files
160
- continue;
161
- }
162
- }
163
- return connections;
164
- }
165
- /**
166
- * Find workspace connection for the current directory or any parent.
167
- * Walks up the directory tree looking for a connection.
168
- */
169
- export function findWorkspaceConnection(startPath) {
170
- let currentPath = path.resolve(startPath);
171
- const root = path.parse(currentPath).root;
172
- while (currentPath !== root) {
173
- const connection = readWorkspaceConnection(currentPath);
174
- if (connection) {
175
- return connection;
176
- }
177
- currentPath = path.dirname(currentPath);
178
- }
179
- return null;
180
- }
181
- /**
182
- * Atomic write: write to temp file then rename.
183
- * Ensures file is never partially written.
184
- */
185
- function atomicWrite(filePath, content, mode) {
186
- const tempPath = `${filePath}.tmp.${process.pid}`;
187
- fs.writeFileSync(tempPath, content, { mode });
188
- fs.renameSync(tempPath, filePath);
189
- }
190
- /**
191
- * Get the path to the sync daemon PID file.
192
- */
193
- export function getPidFilePath() {
194
- return path.join(getModDir(), 'sync.pid');
195
- }
196
- /**
197
- * Get the path to the automerge storage directory.
198
- */
199
- export function getAutomergeStoragePath() {
200
- return path.join(getModDir(), 'automerge');
201
- }
202
- /**
203
- * Get the path to the logs directory.
204
- */
205
- export function getLogsDir() {
206
- return path.join(getModDir(), 'logs');
207
- }
208
- /**
209
- * Get the path to the sync daemon log file.
210
- */
211
- export function getSyncLogPath() {
212
- return path.join(getLogsDir(), 'sync.log');
213
- }