@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,187 +0,0 @@
1
- import fs from 'fs';
2
- import path from 'path';
3
- const FEATURE_DIR = '.mod';
4
- const FEATURE_FILE = 'feature-flags.json';
5
- const FLAG_LIST_ENV = 'MOD_FLAGS';
6
- const FEATURE_FILE_ENV = 'MOD_FEATURE_FLAGS_PATH';
7
- const FEATURE_DEFINITIONS = {
8
- 'tasks-panel': {
9
- description: 'Enable the Tasks view in the interactive TUI (/tasks).',
10
- defaultValue: true,
11
- },
12
- 'directory-view': {
13
- description: 'Enable the project file browser (/files).',
14
- defaultValue: true,
15
- },
16
- 'background-watch': {
17
- description: 'Allow automatic workspace file watching & background sync.',
18
- defaultValue: true,
19
- },
20
- 'agents-run-command': {
21
- description: 'Expose the experimental agents-run command.',
22
- defaultValue: true,
23
- },
24
- 'full-ui': {
25
- description: 'Enable full CLI functionality beyond alpha-safe commands.',
26
- defaultValue: false,
27
- },
28
- };
29
- const FLAG_KEYS = Object.keys(FEATURE_DEFINITIONS);
30
- let cachedFlags = null;
31
- const TRUE_VALUES = new Set(['1', 'true', 'yes', 'on', 'enable', 'enabled']);
32
- const FALSE_VALUES = new Set(['0', 'false', 'no', 'off', 'disable', 'disabled']);
33
- function canonicalizeFlagName(name) {
34
- if (!name)
35
- return null;
36
- const normalized = name
37
- .trim()
38
- .toLowerCase()
39
- .replace(/[^a-z0-9]+/g, '-')
40
- .replace(/^-+/, '')
41
- .replace(/-+$/, '');
42
- return (FLAG_KEYS.find(flag => flag === normalized) ?? null);
43
- }
44
- function parseBoolean(value) {
45
- if (typeof value === 'boolean')
46
- return value;
47
- if (typeof value === 'number')
48
- return value !== 0;
49
- if (typeof value === 'string') {
50
- const normalized = value.trim().toLowerCase();
51
- if (TRUE_VALUES.has(normalized))
52
- return true;
53
- if (FALSE_VALUES.has(normalized))
54
- return false;
55
- }
56
- return undefined;
57
- }
58
- function getFeatureFlagsFilePath() {
59
- const overridePath = process.env[FEATURE_FILE_ENV];
60
- if (overridePath && overridePath.trim().length > 0) {
61
- return path.resolve(overridePath.trim());
62
- }
63
- return path.join(process.cwd(), FEATURE_DIR, FEATURE_FILE);
64
- }
65
- function getPackageLocalConfigPath() {
66
- try {
67
- const __filename = new URL(import.meta.url).pathname;
68
- const __dirname = path.dirname(__filename);
69
- // From dist/services -> ../config/feature-flags.json
70
- return path.join(__dirname, '../../config/feature-flags.json');
71
- }
72
- catch {
73
- return '';
74
- }
75
- }
76
- function parseConfigFile(filePath) {
77
- if (!fs.existsSync(filePath))
78
- return {};
79
- try {
80
- const raw = JSON.parse(fs.readFileSync(filePath, 'utf8'));
81
- const overrides = {};
82
- for (const [name, value] of Object.entries(raw)) {
83
- const key = canonicalizeFlagName(name);
84
- if (!key)
85
- continue;
86
- if (typeof value === 'boolean') {
87
- overrides[key] = value;
88
- }
89
- else if (typeof value === 'string') {
90
- const parsed = parseBoolean(value);
91
- if (typeof parsed === 'boolean')
92
- overrides[key] = parsed;
93
- }
94
- }
95
- return overrides;
96
- }
97
- catch {
98
- return {};
99
- }
100
- }
101
- function readJsonOverrides() {
102
- const globalOverrides = parseConfigFile(getFeatureFlagsFilePath());
103
- const packageOverrides = parseConfigFile(getPackageLocalConfigPath());
104
- // Package-local overrides take precedence over global ones
105
- return { ...globalOverrides, ...packageOverrides };
106
- }
107
- function parseListEnvOverrides() {
108
- const value = process.env[FLAG_LIST_ENV];
109
- if (!value)
110
- return {};
111
- const overrides = {};
112
- const tokens = value.split(/[, ]+/g).map(token => token.trim()).filter(Boolean);
113
- for (const token of tokens) {
114
- let enabled = true;
115
- let name = token;
116
- if (name.startsWith('!') || name.startsWith('-')) {
117
- enabled = false;
118
- name = name.slice(1);
119
- }
120
- else if (name.startsWith('+')) {
121
- name = name.slice(1);
122
- }
123
- const key = canonicalizeFlagName(name);
124
- if (key)
125
- overrides[key] = enabled;
126
- }
127
- return overrides;
128
- }
129
- function parsePerFlagEnvOverrides() {
130
- const overrides = {};
131
- for (const key of FLAG_KEYS) {
132
- const envName = `MOD_FLAG_${key.replace(/[^A-Z0-9]/gi, '_').toUpperCase()}`;
133
- const raw = process.env[envName];
134
- const parsed = parseBoolean(raw);
135
- if (typeof parsed === 'boolean') {
136
- overrides[key] = parsed;
137
- }
138
- }
139
- const fullUiEnv = process.env['MOD_CLI_FULL_UI'];
140
- const fullUiParsed = parseBoolean(fullUiEnv);
141
- if (typeof fullUiParsed === 'boolean') {
142
- overrides['full-ui'] = fullUiParsed;
143
- }
144
- return overrides;
145
- }
146
- function buildFlags() {
147
- const overrides = {
148
- ...readJsonOverrides(),
149
- ...parseListEnvOverrides(),
150
- ...parsePerFlagEnvOverrides(),
151
- };
152
- const resolved = {};
153
- for (const key of FLAG_KEYS) {
154
- resolved[key] = overrides[key] ?? FEATURE_DEFINITIONS[key].defaultValue;
155
- }
156
- return resolved;
157
- }
158
- export function getFeatureFlags(options) {
159
- if (!cachedFlags || options?.refresh) {
160
- cachedFlags = buildFlags();
161
- }
162
- return cachedFlags;
163
- }
164
- export function isFeatureEnabled(flag) {
165
- return getFeatureFlags()[flag];
166
- }
167
- export function describeFeatureFlags() {
168
- const flags = getFeatureFlags();
169
- return FLAG_KEYS.map(key => ({
170
- key,
171
- description: FEATURE_DEFINITIONS[key].description,
172
- defaultValue: FEATURE_DEFINITIONS[key].defaultValue,
173
- enabled: flags[key],
174
- }));
175
- }
176
- export function shouldEnableBackgroundWatch() {
177
- const env = process.env.MOD_CLI_AUTO_WATCH;
178
- if (env === '1')
179
- return true;
180
- if (env === '0')
181
- return false;
182
- return isFeatureEnabled('background-watch');
183
- }
184
- export function shouldExposeFullUi() {
185
- return isFeatureEnabled('full-ui');
186
- }
187
- export const FEATURE_FLAGS_FILE_NAME = FEATURE_FILE;
@@ -1,283 +0,0 @@
1
- import fs from 'fs';
2
- import path from 'path';
3
- import crypto from 'crypto';
4
- import { createModWorkspace } from '@mod/mod-core/mod-workspace';
5
- import { detectMimeType, mimeTypeToCanvasType } from '@mod/mod-core';
6
- import { ModIgnoreService } from './modignore-service.js';
7
- import { readModConfig } from './mod-config.js';
8
- export class FileImportService {
9
- constructor(repo) {
10
- this.folderCache = new Map(); // path -> folderId
11
- this.repo = repo;
12
- }
13
- async previewImport(options = {}) {
14
- console.log('🔍 Scanning for importable files...');
15
- const workingDir = options.workingDirectory || process.cwd();
16
- const preview = await this.scanFiles(workingDir, options);
17
- console.log('\n📋 Import Preview:');
18
- console.log(` Total files scanned: ${preview.totalFilesScanned}`);
19
- console.log(` Files to import: ${preview.filteredFiles.length}`);
20
- console.log(` Files excluded: ${preview.excludedFiles.length}`);
21
- console.log(` Estimated size: ${this.formatBytes(preview.estimatedSize)}`);
22
- console.log(` Estimated time: ${preview.estimatedDuration}ms`);
23
- if (options.verbose) {
24
- console.log('\n📁 Files to import:');
25
- preview.filteredFiles.slice(0, 20).forEach(file => {
26
- console.log(` ${file}`);
27
- });
28
- if (preview.filteredFiles.length > 20) {
29
- console.log(` ... and ${preview.filteredFiles.length - 20} more files`);
30
- }
31
- }
32
- return preview;
33
- }
34
- async promptImport(options = {}) {
35
- const preview = await this.previewImport(options);
36
- if (preview.filteredFiles.length === 0) {
37
- console.log('❌ No files found to import');
38
- return false;
39
- }
40
- // Simple confirmation for now - could enhance with readline interface
41
- console.log('\n❓ Proceed with import? (This will create documents in your workspace)');
42
- console.log(' Type "y" to continue or any other key to cancel...');
43
- // For CLI implementation, we'll return true for now
44
- // In a full implementation, this would use readline to get user input
45
- return true;
46
- }
47
- async executeImport(options = {}) {
48
- const startTime = Date.now();
49
- const workingDir = options.workingDirectory || process.cwd();
50
- const batchSize = options.batchSize || 50;
51
- console.log('🚀 Starting file import...');
52
- // Clear folder cache for fresh import
53
- this.folderCache.clear();
54
- const config = readModConfig();
55
- if (!config || !config.workspaceId) {
56
- throw new Error('No active workspace configured. Run `mod workspace create <name>` first.');
57
- }
58
- const modWorkspace = createModWorkspace(this.repo);
59
- const workspaceHandle = await modWorkspace.openWorkspace(config.workspaceId);
60
- const preview = await this.scanFiles(workingDir, options);
61
- const result = {
62
- importedFiles: [],
63
- skippedFiles: [],
64
- errors: [],
65
- totalDuration: 0
66
- };
67
- const batches = this.chunkArray(preview.filteredFiles, batchSize);
68
- for (let i = 0; i < batches.length; i++) {
69
- const batch = batches[i];
70
- console.log(`📦 Processing batch ${i + 1}/${batches.length} (${batch.length} files)...`);
71
- await Promise.all(batch.map(async (filePath) => {
72
- try {
73
- await this.importFile(filePath, workspaceHandle, workingDir);
74
- result.importedFiles.push(filePath);
75
- if (options.verbose) {
76
- console.log(` ✓ ${filePath}`);
77
- }
78
- }
79
- catch (error) {
80
- result.errors.push({
81
- file: filePath,
82
- error: error instanceof Error ? error.message : String(error)
83
- });
84
- if (options.verbose) {
85
- console.log(` ❌ ${filePath}: ${error}`);
86
- }
87
- }
88
- }));
89
- // Small delay between batches to prevent overwhelming the system
90
- if (i < batches.length - 1) {
91
- await new Promise(resolve => setTimeout(resolve, 100));
92
- }
93
- }
94
- result.totalDuration = Date.now() - startTime;
95
- console.log('\n✅ Import complete!');
96
- console.log(` Imported: ${result.importedFiles.length} files`);
97
- console.log(` Skipped: ${result.skippedFiles.length} files`);
98
- console.log(` Errors: ${result.errors.length} files`);
99
- console.log(` Duration: ${result.totalDuration}ms`);
100
- if (result.errors.length > 0) {
101
- console.log('\n❌ Errors occurred:');
102
- result.errors.slice(0, 5).forEach(error => {
103
- console.log(` ${error.file}: ${error.error}`);
104
- });
105
- if (result.errors.length > 5) {
106
- console.log(` ... and ${result.errors.length - 5} more errors`);
107
- }
108
- }
109
- return result;
110
- }
111
- async scanFiles(workingDir, options) {
112
- this.modIgnoreService = new ModIgnoreService(workingDir);
113
- const scannedFiles = [];
114
- const filteredFiles = [];
115
- const excludedFiles = [];
116
- let totalSize = 0;
117
- await this.scanDirectory(workingDir, workingDir, scannedFiles, filteredFiles, excludedFiles, options);
118
- // Calculate size estimates
119
- for (const file of filteredFiles) {
120
- try {
121
- const stats = await fs.promises.stat(file);
122
- totalSize += stats.size;
123
- }
124
- catch (error) {
125
- // File might have been deleted during scan, skip
126
- }
127
- }
128
- return {
129
- totalFilesScanned: scannedFiles.length,
130
- filteredFiles,
131
- excludedFiles,
132
- estimatedSize: totalSize,
133
- estimatedDuration: filteredFiles.length * 10 // Rough estimate: 10ms per file
134
- };
135
- }
136
- async scanDirectory(rootDir, currentDir, scannedFiles, filteredFiles, excludedFiles, options) {
137
- try {
138
- const entries = await fs.promises.readdir(currentDir, { withFileTypes: true });
139
- for (const entry of entries) {
140
- const fullPath = path.join(currentDir, entry.name);
141
- const relativePath = path.relative(rootDir, fullPath);
142
- if (entry.isDirectory()) {
143
- // Check if directory should be excluded
144
- if (this.modIgnoreService?.shouldIgnore(fullPath, rootDir)) {
145
- excludedFiles.push(relativePath);
146
- continue;
147
- }
148
- // Recurse into directory
149
- await this.scanDirectory(rootDir, fullPath, scannedFiles, filteredFiles, excludedFiles, options);
150
- }
151
- else if (entry.isFile()) {
152
- scannedFiles.push(relativePath);
153
- // Check if file should be excluded
154
- if (this.modIgnoreService?.shouldIgnore(fullPath, rootDir)) {
155
- excludedFiles.push(relativePath);
156
- continue;
157
- }
158
- // Check file extension filter
159
- if (this.isTrackableFile(fullPath)) {
160
- // Apply pattern filters if specified
161
- if (options.patterns && options.patterns.length > 0) {
162
- const matches = options.patterns.some(pattern => relativePath.includes(pattern) || fullPath.includes(pattern));
163
- if (!matches) {
164
- excludedFiles.push(relativePath);
165
- continue;
166
- }
167
- }
168
- filteredFiles.push(fullPath);
169
- }
170
- else {
171
- excludedFiles.push(relativePath);
172
- }
173
- }
174
- }
175
- }
176
- catch (error) {
177
- // Directory might not be accessible, skip
178
- }
179
- }
180
- isTrackableFile(filePath) {
181
- const ext = path.extname(filePath).toLowerCase();
182
- const trackableExtensions = [
183
- '.md', '.txt', '.js', '.ts', '.jsx', '.tsx', '.json', '.yaml', '.yml',
184
- '.py', '.java', '.cpp', '.c', '.h', '.css', '.scss', '.html', '.xml',
185
- '.sql', '.go', '.rs', '.php', '.rb', '.swift', '.kt', '.scala', '.sh',
186
- '.ps1', '.dockerfile', '.tf', '.hcl', '.vue', '.svelte', '.astro'
187
- ];
188
- return trackableExtensions.includes(ext);
189
- }
190
- async importFile(filePath, workspaceHandle, workingDir) {
191
- try {
192
- const content = await fs.promises.readFile(filePath, 'utf-8');
193
- const relativePath = path.relative(workingDir, filePath);
194
- const contentHash = crypto.createHash('sha256').update(content).digest('hex');
195
- // Parse directory structure to create hierarchical folders
196
- const pathParts = relativePath.split(path.sep);
197
- const fileName = pathParts.pop();
198
- const dirPath = pathParts.join('/');
199
- let folderId = null;
200
- // Create folder hierarchy if the file is in a subdirectory
201
- if (dirPath) {
202
- folderId = await this.ensureFolderHierarchy(dirPath, workspaceHandle);
203
- }
204
- // Create document via workspace handle with proper folder association
205
- const ext = path.extname(fileName).toLowerCase();
206
- const mimeType = this.getMimeTypeForExtension(ext);
207
- const canvasType = mimeTypeToCanvasType(mimeType);
208
- const documentData = {
209
- text: content,
210
- metadata: {
211
- type: canvasType, // Required for workspace container routing
212
- originalFilename: fileName,
213
- tags: [`imported:${new Date().toISOString()}`, `hash:${contentHash}`],
214
- typeData: {
215
- originalPath: filePath,
216
- relativePath,
217
- contentHash
218
- }
219
- }
220
- };
221
- const fileDoc = await workspaceHandle.file.create(documentData, {
222
- name: fileName, // Use just the filename, not the full path
223
- mimeType,
224
- folderId: folderId, // Associate with the appropriate folder (cast to handle DocumentId type)
225
- });
226
- // This should be handled automatically by the workspace handle
227
- }
228
- catch (error) {
229
- throw new Error(`Failed to import ${filePath}: ${error instanceof Error ? error.message : String(error)}`);
230
- }
231
- }
232
- async ensureFolderHierarchy(dirPath, workspaceHandle) {
233
- // Check cache first to avoid creating duplicate folders
234
- if (this.folderCache.has(dirPath)) {
235
- return this.folderCache.get(dirPath);
236
- }
237
- const pathParts = dirPath.split('/').filter(Boolean);
238
- let currentPath = '';
239
- let parentFolderId = null;
240
- // Create folders hierarchically from root to deepest level
241
- for (const folderName of pathParts) {
242
- currentPath = currentPath ? `${currentPath}/${folderName}` : folderName;
243
- if (this.folderCache.has(currentPath)) {
244
- parentFolderId = this.folderCache.get(currentPath);
245
- continue;
246
- }
247
- // Create folder using workspace handle - we'll use the folder path approach
248
- // since the workspace handle doesn't expose the direct service createFolder method
249
- const folderRef = await workspaceHandle.folder.create(currentPath, {
250
- name: folderName,
251
- metadata: {
252
- type: 'folder',
253
- path: currentPath,
254
- createdAt: new Date().toISOString()
255
- }
256
- });
257
- // Cache the folder ID for reuse
258
- this.folderCache.set(currentPath, folderRef.id);
259
- parentFolderId = folderRef.id;
260
- }
261
- return parentFolderId;
262
- }
263
- chunkArray(array, chunkSize) {
264
- const chunks = [];
265
- for (let i = 0; i < array.length; i += chunkSize) {
266
- chunks.push(array.slice(i, i + chunkSize));
267
- }
268
- return chunks;
269
- }
270
- getMimeTypeForExtension(ext) {
271
- // Use centralized MIME type detection from mod-core
272
- const fileName = `file${ext}`;
273
- return detectMimeType(fileName);
274
- }
275
- formatBytes(bytes) {
276
- if (bytes === 0)
277
- return '0 Bytes';
278
- const k = 1024;
279
- const sizes = ['Bytes', 'KB', 'MB', 'GB'];
280
- const i = Math.floor(Math.log(bytes) / Math.log(k));
281
- return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
282
- }
283
- }
@@ -1,218 +0,0 @@
1
- import path from 'path';
2
- export class FileTransformationService {
3
- constructor() {
4
- this.languageExtensionMap = {
5
- // JavaScript/TypeScript
6
- javascript: '.js',
7
- typescript: '.ts',
8
- tsx: '.tsx',
9
- jsx: '.jsx',
10
- // Python
11
- python: '.py',
12
- // Java/Kotlin/Scala
13
- java: '.java',
14
- kotlin: '.kt',
15
- scala: '.scala',
16
- // C/C++
17
- c: '.c',
18
- cpp: '.cpp',
19
- 'c++': '.cpp',
20
- cc: '.cc',
21
- cxx: '.cxx',
22
- // C#
23
- csharp: '.cs',
24
- 'c#': '.cs',
25
- // Go
26
- go: '.go',
27
- golang: '.go',
28
- // Rust
29
- rust: '.rs',
30
- // PHP
31
- php: '.php',
32
- // Ruby
33
- ruby: '.rb',
34
- // Swift
35
- swift: '.swift',
36
- // Dart
37
- dart: '.dart',
38
- // Shell/Bash
39
- shell: '.sh',
40
- bash: '.sh',
41
- zsh: '.zsh',
42
- fish: '.fish',
43
- // Web technologies
44
- html: '.html',
45
- css: '.css',
46
- scss: '.scss',
47
- sass: '.sass',
48
- less: '.less',
49
- // Config/Data
50
- json: '.json',
51
- yaml: '.yml',
52
- yml: '.yml',
53
- toml: '.toml',
54
- xml: '.xml',
55
- // SQL
56
- sql: '.sql',
57
- mysql: '.sql',
58
- postgresql: '.sql',
59
- sqlite: '.sql',
60
- // Other
61
- dockerfile: 'Dockerfile',
62
- makefile: 'Makefile',
63
- r: '.r',
64
- matlab: '.m',
65
- lua: '.lua',
66
- perl: '.pl',
67
- // Assembly
68
- assembly: '.asm',
69
- asm: '.asm',
70
- // Functional languages
71
- haskell: '.hs',
72
- clojure: '.clj',
73
- erlang: '.erl',
74
- elixir: '.ex',
75
- ocaml: '.ml',
76
- fsharp: '.fs',
77
- // Other popular languages
78
- groovy: '.groovy',
79
- powershell: '.ps1',
80
- vim: '.vim',
81
- // Markup
82
- markdown: '.md',
83
- tex: '.tex',
84
- latex: '.tex',
85
- };
86
- }
87
- /**
88
- * Transform a file based on its metadata
89
- */
90
- transformFile(originalFileName, content, metadata) {
91
- const result = {
92
- fileName: originalFileName,
93
- content,
94
- transformed: false,
95
- originalType: metadata.type,
96
- };
97
- if (metadata.type === 'text') {
98
- // Transform text files to markdown
99
- result.fileName = this.ensureExtension(originalFileName, '.md');
100
- result.transformed = result.fileName !== originalFileName;
101
- result.targetType = 'markdown';
102
- }
103
- else if (metadata.type === 'code') {
104
- // Transform code files based on language
105
- const language = this.detectLanguage(metadata, originalFileName);
106
- if (language) {
107
- const extension = this.languageExtensionMap[language.toLowerCase()];
108
- if (extension) {
109
- result.fileName = this.replaceExtension(originalFileName, extension);
110
- result.transformed = result.fileName !== originalFileName;
111
- result.targetType = `code (${language})`;
112
- }
113
- }
114
- // If no language detected or mapped, default to .txt
115
- if (!result.transformed) {
116
- result.fileName = this.ensureExtension(originalFileName, '.txt');
117
- result.transformed = result.fileName !== originalFileName;
118
- result.targetType = 'text (unknown language)';
119
- }
120
- }
121
- return result;
122
- }
123
- /**
124
- * Detect the programming language from metadata or filename
125
- */
126
- detectLanguage(metadata, fileName) {
127
- // First, check metadata for explicit language
128
- if (metadata.language) {
129
- return metadata.language;
130
- }
131
- // Try to infer from filename extension
132
- const ext = path.extname(fileName).toLowerCase();
133
- const extToLanguage = {
134
- '.js': 'javascript',
135
- '.ts': 'typescript',
136
- '.tsx': 'tsx',
137
- '.jsx': 'jsx',
138
- '.py': 'python',
139
- '.java': 'java',
140
- '.kt': 'kotlin',
141
- '.scala': 'scala',
142
- '.c': 'c',
143
- '.cpp': 'cpp',
144
- '.cc': 'cpp',
145
- '.cxx': 'cpp',
146
- '.cs': 'csharp',
147
- '.go': 'go',
148
- '.rs': 'rust',
149
- '.php': 'php',
150
- '.rb': 'ruby',
151
- '.swift': 'swift',
152
- '.dart': 'dart',
153
- '.sh': 'shell',
154
- '.bash': 'bash',
155
- '.zsh': 'zsh',
156
- '.fish': 'fish',
157
- '.html': 'html',
158
- '.css': 'css',
159
- '.scss': 'scss',
160
- '.sass': 'sass',
161
- '.less': 'less',
162
- '.json': 'json',
163
- '.yml': 'yaml',
164
- '.yaml': 'yaml',
165
- '.toml': 'toml',
166
- '.xml': 'xml',
167
- '.sql': 'sql',
168
- '.r': 'r',
169
- '.m': 'matlab',
170
- '.lua': 'lua',
171
- '.pl': 'perl',
172
- '.asm': 'assembly',
173
- '.hs': 'haskell',
174
- '.clj': 'clojure',
175
- '.erl': 'erlang',
176
- '.ex': 'elixir',
177
- '.ml': 'ocaml',
178
- '.fs': 'fsharp',
179
- '.groovy': 'groovy',
180
- '.ps1': 'powershell',
181
- '.vim': 'vim',
182
- '.md': 'markdown',
183
- '.tex': 'latex',
184
- };
185
- return extToLanguage[ext] || null;
186
- }
187
- /**
188
- * Ensure a filename has a specific extension
189
- */
190
- ensureExtension(fileName, extension) {
191
- const currentExt = path.extname(fileName);
192
- if (currentExt === extension) {
193
- return fileName;
194
- }
195
- const baseName = currentExt ? fileName.slice(0, -currentExt.length) : fileName;
196
- return baseName + extension;
197
- }
198
- /**
199
- * Replace the extension of a filename
200
- */
201
- replaceExtension(fileName, newExtension) {
202
- const currentExt = path.extname(fileName);
203
- const baseName = currentExt ? fileName.slice(0, -currentExt.length) : fileName;
204
- return baseName + newExtension;
205
- }
206
- /**
207
- * Get the mapping of languages to extensions (for documentation/debugging)
208
- */
209
- getLanguageExtensionMap() {
210
- return { ...this.languageExtensionMap };
211
- }
212
- /**
213
- * Add or update a language extension mapping
214
- */
215
- setLanguageExtension(language, extension) {
216
- this.languageExtensionMap[language.toLowerCase()] = extension;
217
- }
218
- }