ctxman 3.0.0
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/.ctxman/custom-profiles.example.json +20 -0
- package/.ctxman/llm-profiles.json +131 -0
- package/.ctxman/logs/ctxman-2026-07-02.log +156 -0
- package/.ctxman/self-knowledge/documents.lance/_transactions/0-b3dd1558-f95b-44b9-a154-18e8c330ae7a.txn +0 -0
- package/.ctxman/self-knowledge/documents.lance/_transactions/1-44bff6d4-e552-4010-a769-e09ad822698d.txn +0 -0
- package/.ctxman/self-knowledge/documents.lance/_transactions/10-37260794-5947-4ecb-b4c8-d732f58f40f1.txn +0 -0
- package/.ctxman/self-knowledge/documents.lance/_transactions/11-0d52a883-5518-463c-9b6d-986c3af89dfd.txn +0 -0
- package/.ctxman/self-knowledge/documents.lance/_transactions/12-ae354646-52f2-49c9-90fb-2e6ed8932663.txn +0 -0
- package/.ctxman/self-knowledge/documents.lance/_transactions/13-5d7d5e57-27da-498b-b734-091b9d38da13.txn +0 -0
- package/.ctxman/self-knowledge/documents.lance/_transactions/14-558ce928-810b-4252-a364-fdd1ee6f8a25.txn +0 -0
- package/.ctxman/self-knowledge/documents.lance/_transactions/15-27e3a58f-8b64-43b8-a9f4-e3369446968b.txn +0 -0
- package/.ctxman/self-knowledge/documents.lance/_transactions/16-a110c390-e5c8-4464-b976-bc7f5195fa15.txn +0 -0
- package/.ctxman/self-knowledge/documents.lance/_transactions/17-97513225-53de-4216-b61a-6337bd0cc4ad.txn +0 -0
- package/.ctxman/self-knowledge/documents.lance/_transactions/18-5ba450f0-c527-459c-bf4f-6d79b5e4285f.txn +0 -0
- package/.ctxman/self-knowledge/documents.lance/_transactions/19-600d3a63-5225-49e9-b8b7-30e037b564ab.txn +0 -0
- package/.ctxman/self-knowledge/documents.lance/_transactions/2-48a61b4e-92cf-4342-8376-914c1b99a9f9.txn +0 -0
- package/.ctxman/self-knowledge/documents.lance/_transactions/20-121dd912-5410-410e-84ff-82c7a23604a0.txn +0 -0
- package/.ctxman/self-knowledge/documents.lance/_transactions/3-bc6844ab-7ea3-4bb3-9ee1-538ce5664a9c.txn +0 -0
- package/.ctxman/self-knowledge/documents.lance/_transactions/4-d5e1754f-dbb5-454e-a9be-2a7da8d7bcf9.txn +0 -0
- package/.ctxman/self-knowledge/documents.lance/_transactions/5-4fae93df-0568-4b6f-8acf-8bef4bba6047.txn +0 -0
- package/.ctxman/self-knowledge/documents.lance/_transactions/6-4b1b8b87-068e-49a3-ba2b-d8dae628b252.txn +0 -0
- package/.ctxman/self-knowledge/documents.lance/_transactions/7-63d040fb-d2fa-4c0f-bed0-53411918a7b0.txn +0 -0
- package/.ctxman/self-knowledge/documents.lance/_transactions/8-c5628cf6-264c-420f-aab8-bc96a9083591.txn +0 -0
- package/.ctxman/self-knowledge/documents.lance/_transactions/9-c94570aa-5e53-4fcb-971f-cecc1d1d5492.txn +0 -0
- package/.ctxman/self-knowledge/documents.lance/_versions/1.manifest +0 -0
- package/.ctxman/self-knowledge/documents.lance/_versions/10.manifest +0 -0
- package/.ctxman/self-knowledge/documents.lance/_versions/11.manifest +0 -0
- package/.ctxman/self-knowledge/documents.lance/_versions/12.manifest +0 -0
- package/.ctxman/self-knowledge/documents.lance/_versions/13.manifest +0 -0
- package/.ctxman/self-knowledge/documents.lance/_versions/14.manifest +0 -0
- package/.ctxman/self-knowledge/documents.lance/_versions/15.manifest +0 -0
- package/.ctxman/self-knowledge/documents.lance/_versions/16.manifest +0 -0
- package/.ctxman/self-knowledge/documents.lance/_versions/17.manifest +0 -0
- package/.ctxman/self-knowledge/documents.lance/_versions/18.manifest +0 -0
- package/.ctxman/self-knowledge/documents.lance/_versions/19.manifest +0 -0
- package/.ctxman/self-knowledge/documents.lance/_versions/2.manifest +0 -0
- package/.ctxman/self-knowledge/documents.lance/_versions/20.manifest +0 -0
- package/.ctxman/self-knowledge/documents.lance/_versions/21.manifest +0 -0
- package/.ctxman/self-knowledge/documents.lance/_versions/3.manifest +0 -0
- package/.ctxman/self-knowledge/documents.lance/_versions/4.manifest +0 -0
- package/.ctxman/self-knowledge/documents.lance/_versions/5.manifest +0 -0
- package/.ctxman/self-knowledge/documents.lance/_versions/6.manifest +0 -0
- package/.ctxman/self-knowledge/documents.lance/_versions/7.manifest +0 -0
- package/.ctxman/self-knowledge/documents.lance/_versions/8.manifest +0 -0
- package/.ctxman/self-knowledge/documents.lance/_versions/9.manifest +0 -0
- package/.ctxman/self-knowledge/documents.lance/data/000010010011011101000010de8621405d9d5cb06540dbf02c.lance +0 -0
- package/.ctxman/self-knowledge/documents.lance/data/0000101001011100011000003fb62a4aa8978974075c144f1c.lance +0 -0
- package/.ctxman/self-knowledge/documents.lance/data/000011010000100111001011c6cc824d628f3bb1f4e04cd5d0.lance +0 -0
- package/.ctxman/self-knowledge/documents.lance/data/001000001000011001011100d8f74349ada090b332cd1df669.lance +0 -0
- package/.ctxman/self-knowledge/documents.lance/data/01000001111010001000010045ab44467bb1e7e4d8ae06bd58.lance +0 -0
- package/.ctxman/self-knowledge/documents.lance/data/01000101101110111110011131ef904241a89e1ffb6ddaf8e3.lance +0 -0
- package/.ctxman/self-knowledge/documents.lance/data/010010110000010000011010fbfcc145c6a8a827b28057a07e.lance +0 -0
- package/.ctxman/self-knowledge/documents.lance/data/1000011000101010001110003fb4f045c3835982efa1405324.lance +0 -0
- package/.ctxman/self-knowledge/documents.lance/data/100111011101110111101011f4396b410fa06af3b028d204e6.lance +0 -0
- package/.ctxman/self-knowledge/documents.lance/data/100111100111101000001100596b174e00995d6cc2969cc8dc.lance +0 -0
- package/.ctxman/self-knowledge/documents.lance/data/1010001100011000110010102ea16b484d887c7c9725feffd6.lance +0 -0
- package/.ctxman/self-knowledge/documents.lance/data/101101100010101111110111c5230944d9b3af7301b6392dda.lance +0 -0
- package/.ctxman/self-knowledge/documents.lance/data/1011011100100111111110105892bf41b5a530fedd7cf516f1.lance +0 -0
- package/.ctxman/self-knowledge/documents.lance/data/10111110000010001011110164e8774742bade87b483c10f36.lance +0 -0
- package/.ctxman/self-knowledge/documents.lance/data/1100000100010110001110103ffaca40a2a42c85067ebaf9ae.lance +0 -0
- package/.ctxman/self-knowledge/documents.lance/data/110000010001110110101110664bde4c76a78dfc14b05e83b8.lance +0 -0
- package/.ctxman/self-knowledge/documents.lance/data/11001100111000010100110050645c4281b1f35b2022234f7f.lance +0 -0
- package/.ctxman/self-knowledge/documents.lance/data/1101111011001100110111017214924a368f5719eab2246619.lance +0 -0
- package/.ctxman/self-knowledge/documents.lance/data/1110000111001010001000002150c24dcab653d39d07032e63.lance +0 -0
- package/.ctxman/self-knowledge/documents.lance/data/1110100000000100000011014d20534967a18b5c87721659ff.lance +0 -0
- package/.ctxman/self-knowledge/documents.lance/data/11110101111010000101011135a0114ef2b35dc6f1297a3381.lance +0 -0
- package/.ctxman/wizard-profiles/code-review/.contextignore +41 -0
- package/.ctxman/wizard-profiles/code-review/.contextinclude +30 -0
- package/.ctxman/wizard-profiles/code-review/.methodignore +26 -0
- package/.ctxman/wizard-profiles/code-review/.methodinclude +47 -0
- package/.ctxman/wizard-profiles/code-review/profile.json +26 -0
- package/.ctxman/wizard-profiles/documentation/.contextignore +21 -0
- package/.ctxman/wizard-profiles/documentation/.contextinclude +24 -0
- package/.ctxman/wizard-profiles/documentation/.methodignore +19 -0
- package/.ctxman/wizard-profiles/documentation/.methodinclude +44 -0
- package/.ctxman/wizard-profiles/documentation/profile.json +27 -0
- package/.ctxman/wizard-profiles/full/.contextignore +23 -0
- package/.ctxman/wizard-profiles/full/.contextinclude +38 -0
- package/.ctxman/wizard-profiles/full/.methodignore +5 -0
- package/.ctxman/wizard-profiles/full/.methodinclude +5 -0
- package/.ctxman/wizard-profiles/full/profile.json +28 -0
- package/.ctxman/wizard-profiles/llm-explain/.contextignore +25 -0
- package/.ctxman/wizard-profiles/llm-explain/.contextinclude +33 -0
- package/.ctxman/wizard-profiles/llm-explain/.methodignore +19 -0
- package/.ctxman/wizard-profiles/llm-explain/.methodinclude +48 -0
- package/.ctxman/wizard-profiles/llm-explain/profile.json +27 -0
- package/.ctxman/wizard-profiles/minimal/.contextignore +27 -0
- package/.ctxman/wizard-profiles/minimal/.contextinclude +14 -0
- package/.ctxman/wizard-profiles/minimal/.methodignore +22 -0
- package/.ctxman/wizard-profiles/minimal/.methodinclude +18 -0
- package/.ctxman/wizard-profiles/minimal/profile.json +26 -0
- package/.ctxman/wizard-profiles/security-audit/.contextignore +36 -0
- package/.ctxman/wizard-profiles/security-audit/.contextinclude +37 -0
- package/.ctxman/wizard-profiles/security-audit/.methodignore +29 -0
- package/.ctxman/wizard-profiles/security-audit/.methodinclude +60 -0
- package/.ctxman/wizard-profiles/security-audit/profile.json +34 -0
- package/README.md +1167 -0
- package/bin/cli.js +650 -0
- package/bin/cm-ask.js +95 -0
- package/bin/cm-gitingest.js +134 -0
- package/bin/cm-update.js +175 -0
- package/bin/mcp-server.js +31 -0
- package/ctxman.js +236 -0
- package/examples/README.md +298 -0
- package/examples/custom-llm-profiles.example.json +19 -0
- package/examples/wizard-profiles/code-review/.contextignore +41 -0
- package/examples/wizard-profiles/code-review/.contextinclude +30 -0
- package/examples/wizard-profiles/code-review/.methodignore +26 -0
- package/examples/wizard-profiles/code-review/.methodinclude +47 -0
- package/examples/wizard-profiles/code-review/profile.json +26 -0
- package/examples/wizard-profiles/documentation/.contextignore +21 -0
- package/examples/wizard-profiles/documentation/.contextinclude +24 -0
- package/examples/wizard-profiles/documentation/.methodignore +19 -0
- package/examples/wizard-profiles/documentation/.methodinclude +44 -0
- package/examples/wizard-profiles/documentation/profile.json +27 -0
- package/examples/wizard-profiles/full/.contextignore +23 -0
- package/examples/wizard-profiles/full/.contextinclude +38 -0
- package/examples/wizard-profiles/full/.methodignore +5 -0
- package/examples/wizard-profiles/full/.methodinclude +5 -0
- package/examples/wizard-profiles/full/profile.json +28 -0
- package/examples/wizard-profiles/llm-explain/.contextignore +25 -0
- package/examples/wizard-profiles/llm-explain/.contextinclude +33 -0
- package/examples/wizard-profiles/llm-explain/.methodignore +19 -0
- package/examples/wizard-profiles/llm-explain/.methodinclude +48 -0
- package/examples/wizard-profiles/llm-explain/profile.json +27 -0
- package/examples/wizard-profiles/minimal/.contextignore +27 -0
- package/examples/wizard-profiles/minimal/.contextinclude +14 -0
- package/examples/wizard-profiles/minimal/.methodignore +22 -0
- package/examples/wizard-profiles/minimal/.methodinclude +18 -0
- package/examples/wizard-profiles/minimal/profile.json +26 -0
- package/examples/wizard-profiles/security-audit/.contextignore +36 -0
- package/examples/wizard-profiles/security-audit/.contextinclude +37 -0
- package/examples/wizard-profiles/security-audit/.methodignore +29 -0
- package/examples/wizard-profiles/security-audit/.methodinclude +60 -0
- package/examples/wizard-profiles/security-audit/profile.json +34 -0
- package/index.js +68 -0
- package/lib/agents/pr-failure-agent.js +201 -0
- package/lib/analyzers/go-method-analyzer.js +126 -0
- package/lib/analyzers/method-analyzer.js +457 -0
- package/lib/analyzers/token-calculator.js +577 -0
- package/lib/api/mcp/prompts.js +319 -0
- package/lib/api/mcp/resources.js +269 -0
- package/lib/api/mcp/server.js +339 -0
- package/lib/api/mcp/transports/HttpSseTransport.js +63 -0
- package/lib/api/rest/server.js +405 -0
- package/lib/cache/CacheManager.js +388 -0
- package/lib/core/Analyzer.js +180 -0
- package/lib/core/ContextBuilder.js +325 -0
- package/lib/core/Reporter.js +270 -0
- package/lib/core/Scanner.js +129 -0
- package/lib/formatters/format-registry.js +419 -0
- package/lib/formatters/gitingest-formatter.js +746 -0
- package/lib/formatters/toon-formatter-v1.3.js +731 -0
- package/lib/formatters/toon-formatter.js +291 -0
- package/lib/integrations/git/BlameTracker.js +142 -0
- package/lib/integrations/git/DiffAnalyzer.js +260 -0
- package/lib/integrations/git/GitClient.js +336 -0
- package/lib/parsers/gitignore-parser.js +127 -0
- package/lib/parsers/method-filter-parser.js +50 -0
- package/lib/plugins/ExporterPlugin.js +102 -0
- package/lib/plugins/LanguagePlugin.js +100 -0
- package/lib/plugins/PluginManager.js +302 -0
- package/lib/rag/DataSourcePlugin.js +31 -0
- package/lib/rag/EmbeddingProvider.js +75 -0
- package/lib/rag/Indexer.js +83 -0
- package/lib/rag/VectorStoreFactory.js +24 -0
- package/lib/rag/sources/FileDataSource.js +70 -0
- package/lib/rag/stores/BaseVectorStore.js +43 -0
- package/lib/rag/stores/ChromaDBStore.js +85 -0
- package/lib/rag/stores/LanceDBStore.js +109 -0
- package/lib/rag/stores/PineconeStore.js +74 -0
- package/lib/ui/dashboard.js +109 -0
- package/lib/ui/index.js +15 -0
- package/lib/ui/progress-bar.js +110 -0
- package/lib/ui/select-input.js +51 -0
- package/lib/ui/wizard.js +359 -0
- package/lib/utils/clipboard-utils.js +66 -0
- package/lib/utils/config-utils.js +94 -0
- package/lib/utils/error-handler.js +145 -0
- package/lib/utils/file-utils.js +89 -0
- package/lib/utils/format-converter.js +325 -0
- package/lib/utils/git-utils.js +337 -0
- package/lib/utils/llm-detector.js +409 -0
- package/lib/utils/logger.js +278 -0
- package/lib/utils/token-utils.js +200 -0
- package/lib/utils/tokenizer-adapter.js +454 -0
- package/lib/utils/updater.js +565 -0
- package/lib/watch/FileWatcher.js +208 -0
- package/lib/watch/IncrementalAnalyzer.js +206 -0
- package/package.json +118 -0
package/lib/ui/wizard.js
ADDED
|
@@ -0,0 +1,359 @@
|
|
|
1
|
+
import React, { useState, useCallback, useMemo } from 'react';
|
|
2
|
+
import { Box, Text } from 'ink';
|
|
3
|
+
import SelectInput from './select-input.js';
|
|
4
|
+
import { LLMDetector } from '../utils/llm-detector.js';
|
|
5
|
+
import fs from 'fs';
|
|
6
|
+
import path from 'path';
|
|
7
|
+
import { fileURLToPath } from 'url';
|
|
8
|
+
|
|
9
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
10
|
+
const __dirname = path.dirname(__filename);
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Interactive Configuration Wizard
|
|
14
|
+
* Guides users through context generation setup
|
|
15
|
+
* v2.3.8 - Use case templates with configuration copying
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Discover available wizard profiles from .ctxman/wizard-profiles/
|
|
20
|
+
*/
|
|
21
|
+
function discoverProfiles() {
|
|
22
|
+
const profilesDir = path.join(__dirname, '../../.ctxman/wizard-profiles');
|
|
23
|
+
const profiles = [];
|
|
24
|
+
|
|
25
|
+
try {
|
|
26
|
+
if (fs.existsSync(profilesDir)) {
|
|
27
|
+
const dirs = fs.readdirSync(profilesDir, { withFileTypes: true });
|
|
28
|
+
|
|
29
|
+
dirs.forEach(dirent => {
|
|
30
|
+
if (dirent.isDirectory()) {
|
|
31
|
+
const profilePath = path.join(profilesDir, dirent.name);
|
|
32
|
+
const metadataPath = path.join(profilePath, 'profile.json');
|
|
33
|
+
|
|
34
|
+
if (fs.existsSync(metadataPath)) {
|
|
35
|
+
try {
|
|
36
|
+
const metadata = JSON.parse(fs.readFileSync(metadataPath, 'utf-8'));
|
|
37
|
+
profiles.push({
|
|
38
|
+
label: `${metadata.icon} ${metadata.name}`,
|
|
39
|
+
value: metadata.id,
|
|
40
|
+
path: profilePath,
|
|
41
|
+
metadata: metadata
|
|
42
|
+
});
|
|
43
|
+
} catch (error) {
|
|
44
|
+
console.error(`Error reading profile metadata ${dirent.name}:`, error);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
} catch (error) {
|
|
51
|
+
console.error('Error discovering profiles:', error);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Add custom option at the end
|
|
55
|
+
profiles.push({
|
|
56
|
+
label: '⚙️ Custom (use existing root config)',
|
|
57
|
+
value: 'custom',
|
|
58
|
+
path: null,
|
|
59
|
+
metadata: null
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
return profiles;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Copy profile configuration files to project root with profile name suffix
|
|
67
|
+
*/
|
|
68
|
+
function copyProfileFiles(profilePath, profileId, projectRoot) {
|
|
69
|
+
const filesToCopy = [
|
|
70
|
+
'.contextinclude',
|
|
71
|
+
'.contextignore',
|
|
72
|
+
'.methodinclude',
|
|
73
|
+
'.methodignore'
|
|
74
|
+
];
|
|
75
|
+
const copiedFiles = [];
|
|
76
|
+
|
|
77
|
+
filesToCopy.forEach(fileName => {
|
|
78
|
+
const sourcePath = path.join(profilePath, fileName);
|
|
79
|
+
// Copy with profile name suffix: .contextinclude-code-review
|
|
80
|
+
const destFileName = `${fileName}-${profileId}`;
|
|
81
|
+
const destPath = path.join(projectRoot, destFileName);
|
|
82
|
+
|
|
83
|
+
if (fs.existsSync(sourcePath)) {
|
|
84
|
+
try {
|
|
85
|
+
fs.copyFileSync(sourcePath, destPath);
|
|
86
|
+
copiedFiles.push(destFileName);
|
|
87
|
+
} catch (error) {
|
|
88
|
+
console.error(`Error copying ${fileName}:`, error);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
// Create/update symlinks for active config
|
|
94
|
+
// .contextinclude -> .contextinclude-code-review
|
|
95
|
+
const symlinkFiles = [
|
|
96
|
+
['.contextinclude', `.contextinclude-${profileId}`],
|
|
97
|
+
['.contextignore', `.contextignore-${profileId}`],
|
|
98
|
+
['.methodinclude', `.methodinclude-${profileId}`],
|
|
99
|
+
['.methodignore', `.methodignore-${profileId}`]
|
|
100
|
+
];
|
|
101
|
+
|
|
102
|
+
symlinkFiles.forEach(([linkName, targetName]) => {
|
|
103
|
+
const linkPath = path.join(projectRoot, linkName);
|
|
104
|
+
const targetPath = path.join(projectRoot, targetName);
|
|
105
|
+
|
|
106
|
+
try {
|
|
107
|
+
// Remove existing symlink/file
|
|
108
|
+
if (fs.existsSync(linkPath)) {
|
|
109
|
+
fs.unlinkSync(linkPath);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Create copy instead of symlink for better compatibility
|
|
113
|
+
if (fs.existsSync(targetPath)) {
|
|
114
|
+
fs.copyFileSync(targetPath, linkPath);
|
|
115
|
+
}
|
|
116
|
+
} catch (error) {
|
|
117
|
+
console.error(`Error creating link ${linkName}:`, error);
|
|
118
|
+
}
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
return copiedFiles;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export default function Wizard({ onComplete }) {
|
|
125
|
+
const [step, setStep] = useState('profile');
|
|
126
|
+
const [answers, setAnswers] = useState({});
|
|
127
|
+
|
|
128
|
+
// Dynamic profile discovery
|
|
129
|
+
const profileItems = useMemo(() => discoverProfiles(), []);
|
|
130
|
+
|
|
131
|
+
// Dynamic LLM model list from JSON config
|
|
132
|
+
const targetModelItems = useMemo(() => {
|
|
133
|
+
const models = LLMDetector.getModelList();
|
|
134
|
+
const items = [
|
|
135
|
+
{ label: '✨ Auto-detect from environment', value: 'auto' }
|
|
136
|
+
];
|
|
137
|
+
|
|
138
|
+
// Add models grouped by vendor
|
|
139
|
+
const anthropicModels = models.filter(m => m.vendor === 'Anthropic');
|
|
140
|
+
const openaiModels = models.filter(m => m.vendor === 'OpenAI');
|
|
141
|
+
const googleModels = models.filter(m => m.vendor === 'Google');
|
|
142
|
+
const otherModels = models.filter(m => !['Anthropic', 'OpenAI', 'Google'].includes(m.vendor));
|
|
143
|
+
|
|
144
|
+
// Anthropic (Claude)
|
|
145
|
+
anthropicModels.forEach(model => {
|
|
146
|
+
const contextK = Math.floor(model.contextWindow / 1000);
|
|
147
|
+
items.push({
|
|
148
|
+
label: `${model.name} (${contextK}k context)`,
|
|
149
|
+
value: model.id
|
|
150
|
+
});
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
// OpenAI (GPT-4)
|
|
154
|
+
openaiModels.forEach(model => {
|
|
155
|
+
const contextK = Math.floor(model.contextWindow / 1000);
|
|
156
|
+
items.push({
|
|
157
|
+
label: `${model.name} (${contextK}k context)`,
|
|
158
|
+
value: model.id
|
|
159
|
+
});
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
// Google (Gemini)
|
|
163
|
+
googleModels.forEach(model => {
|
|
164
|
+
const contextK = model.contextWindow >= 1000000
|
|
165
|
+
? Math.floor(model.contextWindow / 1000000) + 'M'
|
|
166
|
+
: Math.floor(model.contextWindow / 1000) + 'k';
|
|
167
|
+
items.push({
|
|
168
|
+
label: `${model.name} (${contextK} context)`,
|
|
169
|
+
value: model.id
|
|
170
|
+
});
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
// Others
|
|
174
|
+
otherModels.forEach(model => {
|
|
175
|
+
const contextK = Math.floor(model.contextWindow / 1000);
|
|
176
|
+
items.push({
|
|
177
|
+
label: `${model.name} (${contextK}k context)`,
|
|
178
|
+
value: model.id
|
|
179
|
+
});
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
return items;
|
|
183
|
+
}, []);
|
|
184
|
+
|
|
185
|
+
const outputFormatItems = [
|
|
186
|
+
{ label: 'TOON (most efficient, 40-50% reduction)', value: 'toon' },
|
|
187
|
+
{ label: 'JSON (standard format)', value: 'json' },
|
|
188
|
+
{ label: 'GitIngest (single file)', value: 'gitingest' },
|
|
189
|
+
{ label: 'Markdown (documentation)', value: 'markdown' },
|
|
190
|
+
{ label: 'YAML (human-readable)', value: 'yaml' },
|
|
191
|
+
{ label: 'CSV (spreadsheet)', value: 'csv' }
|
|
192
|
+
];
|
|
193
|
+
|
|
194
|
+
const handleProfileSelect = useCallback((item) => {
|
|
195
|
+
// Copy profile files if not custom
|
|
196
|
+
if (item.value !== 'custom' && item.path) {
|
|
197
|
+
const projectRoot = process.cwd();
|
|
198
|
+
const copiedFiles = copyProfileFiles(item.path, item.value, projectRoot);
|
|
199
|
+
|
|
200
|
+
setAnswers(prev => ({
|
|
201
|
+
...prev,
|
|
202
|
+
profile: item.value,
|
|
203
|
+
copiedFiles: copiedFiles,
|
|
204
|
+
profileMetadata: item.metadata
|
|
205
|
+
}));
|
|
206
|
+
} else {
|
|
207
|
+
// Custom - use existing root config files
|
|
208
|
+
setAnswers(prev => ({
|
|
209
|
+
...prev,
|
|
210
|
+
profile: 'custom',
|
|
211
|
+
copiedFiles: [],
|
|
212
|
+
profileMetadata: null
|
|
213
|
+
}));
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
setStep('target-model');
|
|
217
|
+
}, []);
|
|
218
|
+
|
|
219
|
+
const handleTargetModelSelect = useCallback((item) => {
|
|
220
|
+
let selectedModel = item.value;
|
|
221
|
+
|
|
222
|
+
// Handle auto-detect
|
|
223
|
+
if (selectedModel === 'auto') {
|
|
224
|
+
selectedModel = LLMDetector.detect();
|
|
225
|
+
if (selectedModel === 'unknown') {
|
|
226
|
+
// Fallback to default if detection fails
|
|
227
|
+
selectedModel = 'claude-sonnet-4.5';
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
setAnswers(prev => ({ ...prev, targetModel: selectedModel }));
|
|
232
|
+
setStep('output-format');
|
|
233
|
+
}, []);
|
|
234
|
+
|
|
235
|
+
const handleOutputFormatSelect = useCallback((item) => {
|
|
236
|
+
const finalAnswers = { ...answers, outputFormat: item.value };
|
|
237
|
+
setAnswers(finalAnswers);
|
|
238
|
+
setStep('complete');
|
|
239
|
+
|
|
240
|
+
// Call completion callback
|
|
241
|
+
if (onComplete) {
|
|
242
|
+
onComplete(finalAnswers);
|
|
243
|
+
}
|
|
244
|
+
}, [answers, onComplete]);
|
|
245
|
+
|
|
246
|
+
// Build children array and filter out falsy values
|
|
247
|
+
const children = [
|
|
248
|
+
React.createElement(Box, { key: 'title', marginBottom: 1 },
|
|
249
|
+
React.createElement(Text, { bold: true, color: 'cyan' }, '🧙 Context Generation Wizard')
|
|
250
|
+
)
|
|
251
|
+
];
|
|
252
|
+
|
|
253
|
+
// Step 1: Profile Selection
|
|
254
|
+
if (step === 'profile') {
|
|
255
|
+
children.push(
|
|
256
|
+
React.createElement(Box, { key: 'profile-step', flexDirection: 'column' },
|
|
257
|
+
React.createElement(Box, { key: 'question-box', marginBottom: 1 },
|
|
258
|
+
React.createElement(Text, null, 'Select analysis profile:')
|
|
259
|
+
),
|
|
260
|
+
React.createElement(SelectInput, {
|
|
261
|
+
key: 'select-input',
|
|
262
|
+
items: profileItems,
|
|
263
|
+
onSelect: handleProfileSelect
|
|
264
|
+
}),
|
|
265
|
+
React.createElement(Box, { key: 'help-box', marginTop: 1 },
|
|
266
|
+
React.createElement(Text, { dimColor: true }, '[↑↓] Navigate [Enter] Select [Esc] Cancel')
|
|
267
|
+
)
|
|
268
|
+
)
|
|
269
|
+
);
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
// Step 2: Target Model
|
|
273
|
+
if (step === 'target-model') {
|
|
274
|
+
children.push(
|
|
275
|
+
React.createElement(Box, { key: 'target-model-step', flexDirection: 'column' },
|
|
276
|
+
React.createElement(Box, { key: 'question-box-2', marginBottom: 1 },
|
|
277
|
+
React.createElement(Text, null, 'Target LLM?')
|
|
278
|
+
),
|
|
279
|
+
React.createElement(SelectInput, {
|
|
280
|
+
key: 'select-input-2',
|
|
281
|
+
items: targetModelItems,
|
|
282
|
+
onSelect: handleTargetModelSelect
|
|
283
|
+
}),
|
|
284
|
+
React.createElement(Box, { key: 'help-box-2', marginTop: 1 },
|
|
285
|
+
React.createElement(Text, { dimColor: true }, '[↑↓] Navigate [Enter] Select [Esc] Cancel')
|
|
286
|
+
)
|
|
287
|
+
)
|
|
288
|
+
);
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
// Step 3: Output Format
|
|
292
|
+
if (step === 'output-format') {
|
|
293
|
+
children.push(
|
|
294
|
+
React.createElement(Box, { key: 'output-format-step', flexDirection: 'column' },
|
|
295
|
+
React.createElement(Box, { key: 'question-box-3', marginBottom: 1 },
|
|
296
|
+
React.createElement(Text, null, 'Output format?')
|
|
297
|
+
),
|
|
298
|
+
React.createElement(SelectInput, {
|
|
299
|
+
key: 'select-input-3',
|
|
300
|
+
items: outputFormatItems,
|
|
301
|
+
onSelect: handleOutputFormatSelect
|
|
302
|
+
}),
|
|
303
|
+
React.createElement(Box, { key: 'help-box-3', marginTop: 1 },
|
|
304
|
+
React.createElement(Text, { dimColor: true }, '[↑↓] Navigate [Enter] Select [Esc] Cancel')
|
|
305
|
+
)
|
|
306
|
+
)
|
|
307
|
+
);
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
// Complete
|
|
311
|
+
if (step === 'complete') {
|
|
312
|
+
const completeBoxChildren = [
|
|
313
|
+
React.createElement(Box, { key: 'complete-title', marginBottom: 1 },
|
|
314
|
+
React.createElement(Text, { bold: true, color: 'green' }, '✓ Configuration Complete!')
|
|
315
|
+
),
|
|
316
|
+
React.createElement(Box, { key: 'complete-info', flexDirection: 'column', marginBottom: 1 },
|
|
317
|
+
React.createElement(Text, null, `Profile: ${answers.profile}`),
|
|
318
|
+
React.createElement(Text, null, `Target Model: ${answers.targetModel}`),
|
|
319
|
+
React.createElement(Text, null, `Output Format: ${answers.outputFormat}`)
|
|
320
|
+
)
|
|
321
|
+
];
|
|
322
|
+
|
|
323
|
+
// Show copied files if any
|
|
324
|
+
if (answers.copiedFiles && answers.copiedFiles.length > 0) {
|
|
325
|
+
completeBoxChildren.push(
|
|
326
|
+
React.createElement(Box, { key: 'copied-files', flexDirection: 'column', marginTop: 1 },
|
|
327
|
+
React.createElement(Text, { color: 'cyan' }, 'Profile configuration files:'),
|
|
328
|
+
...answers.copiedFiles.slice(0, 4).map((file, idx) =>
|
|
329
|
+
React.createElement(Text, { key: `file-${idx}`, color: 'gray' }, ` ✓ ${file}`)
|
|
330
|
+
)
|
|
331
|
+
)
|
|
332
|
+
);
|
|
333
|
+
|
|
334
|
+
completeBoxChildren.push(
|
|
335
|
+
React.createElement(Box, { key: 'active-files', flexDirection: 'column', marginTop: 1 },
|
|
336
|
+
React.createElement(Text, { color: 'green' }, 'Active configuration (symlinked):'),
|
|
337
|
+
React.createElement(Text, { color: 'gray' }, ' → .contextinclude'),
|
|
338
|
+
React.createElement(Text, { color: 'gray' }, ' → .contextignore'),
|
|
339
|
+
React.createElement(Text, { color: 'gray' }, ' → .methodinclude'),
|
|
340
|
+
React.createElement(Text, { color: 'gray' }, ' → .methodignore')
|
|
341
|
+
)
|
|
342
|
+
);
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
children.push(
|
|
346
|
+
React.createElement(Box, { key: 'complete-step', flexDirection: 'column' },
|
|
347
|
+
...completeBoxChildren
|
|
348
|
+
)
|
|
349
|
+
);
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
return React.createElement(Box, {
|
|
353
|
+
flexDirection: 'column',
|
|
354
|
+
padding: 1,
|
|
355
|
+
borderStyle: 'round',
|
|
356
|
+
borderColor: 'cyan',
|
|
357
|
+
width: 76
|
|
358
|
+
}, ...children);
|
|
359
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cross-platform Clipboard Utilities
|
|
3
|
+
* Handles clipboard operations for macOS, Linux, and Windows
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { execSync } from 'child_process';
|
|
7
|
+
|
|
8
|
+
class ClipboardUtils {
|
|
9
|
+
/**
|
|
10
|
+
* Copy text to system clipboard
|
|
11
|
+
* @param {string} text - Text to copy
|
|
12
|
+
* @returns {boolean} Success status
|
|
13
|
+
*/
|
|
14
|
+
static copy(text) {
|
|
15
|
+
try {
|
|
16
|
+
if (process.platform === 'darwin') {
|
|
17
|
+
// macOS
|
|
18
|
+
execSync('pbcopy', { input: text, encoding: 'utf8' });
|
|
19
|
+
} else if (process.platform === 'linux') {
|
|
20
|
+
// Linux - try xclip first, then xsel
|
|
21
|
+
try {
|
|
22
|
+
execSync('xclip -selection clipboard', { input: text, encoding: 'utf8' });
|
|
23
|
+
} catch {
|
|
24
|
+
execSync('xsel --clipboard --input', { input: text, encoding: 'utf8' });
|
|
25
|
+
}
|
|
26
|
+
} else if (process.platform === 'win32') {
|
|
27
|
+
// Windows
|
|
28
|
+
execSync('clip', { input: text, encoding: 'utf8' });
|
|
29
|
+
} else {
|
|
30
|
+
throw new Error('Unsupported platform for clipboard');
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
console.log('✅ Context copied to clipboard!');
|
|
34
|
+
console.log(`📋 ${text.length} characters copied`);
|
|
35
|
+
return true;
|
|
36
|
+
|
|
37
|
+
} catch (error) {
|
|
38
|
+
console.error('❌ Failed to copy to clipboard:', error.message);
|
|
39
|
+
return false;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Check if clipboard is available on current platform
|
|
45
|
+
* @returns {boolean}
|
|
46
|
+
*/
|
|
47
|
+
static isAvailable() {
|
|
48
|
+
const platform = process.platform;
|
|
49
|
+
return ['darwin', 'linux', 'win32'].includes(platform);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Get clipboard command for current platform
|
|
54
|
+
* @returns {string} Command name
|
|
55
|
+
*/
|
|
56
|
+
static getCommand() {
|
|
57
|
+
const commands = {
|
|
58
|
+
darwin: 'pbcopy',
|
|
59
|
+
linux: 'xclip',
|
|
60
|
+
win32: 'clip'
|
|
61
|
+
};
|
|
62
|
+
return commands[process.platform] || 'unknown';
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export default ClipboardUtils;
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Configuration File Utilities
|
|
3
|
+
* Handles discovery and initialization of configuration files
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import fs from 'fs';
|
|
7
|
+
import path from 'path';
|
|
8
|
+
import { fileURLToPath } from 'url';
|
|
9
|
+
import { dirname } from 'path';
|
|
10
|
+
import MethodFilterParser from '../parsers/method-filter-parser.js';
|
|
11
|
+
import GitIgnoreParser from '../parsers/gitignore-parser.js';
|
|
12
|
+
|
|
13
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
14
|
+
const __dirname = dirname(__filename);
|
|
15
|
+
|
|
16
|
+
class ConfigUtils {
|
|
17
|
+
/**
|
|
18
|
+
* Find configuration file in standard locations
|
|
19
|
+
* @param {string} projectRoot - Project root directory
|
|
20
|
+
* @param {string} filename - Config filename
|
|
21
|
+
* @returns {string|undefined} Config file path or undefined
|
|
22
|
+
*/
|
|
23
|
+
static findConfigFile(projectRoot, filename) {
|
|
24
|
+
const locations = [
|
|
25
|
+
path.join(__dirname, '..', '..', filename), // Package root
|
|
26
|
+
path.join(projectRoot, filename) // Project root
|
|
27
|
+
];
|
|
28
|
+
return locations.find(loc => fs.existsSync(loc));
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Initialize method filter parser
|
|
33
|
+
* @param {string} projectRoot - Project root directory
|
|
34
|
+
* @returns {object|null} MethodFilterParser instance or null
|
|
35
|
+
*/
|
|
36
|
+
static initMethodFilter(projectRoot) {
|
|
37
|
+
const paths = {
|
|
38
|
+
methodInclude: this.findConfigFile(projectRoot, '.methodinclude'),
|
|
39
|
+
methodIgnore: this.findConfigFile(projectRoot, '.methodignore')
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
if (!paths.methodInclude && !paths.methodIgnore) {
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return new MethodFilterParser(paths.methodInclude, paths.methodIgnore);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Detect if method filtering is enabled
|
|
51
|
+
* @param {string} projectRoot - Project root directory
|
|
52
|
+
* @returns {boolean}
|
|
53
|
+
*/
|
|
54
|
+
static detectMethodFilters(projectRoot) {
|
|
55
|
+
const methodIncludePath = this.findConfigFile(projectRoot, '.methodinclude');
|
|
56
|
+
const methodIgnorePath = this.findConfigFile(projectRoot, '.methodignore');
|
|
57
|
+
return !!(methodIncludePath || methodIgnorePath);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Initialize git ignore parser
|
|
62
|
+
* @param {string} projectRoot - Project root directory
|
|
63
|
+
* @returns {object} GitIgnoreParser instance
|
|
64
|
+
*/
|
|
65
|
+
static initGitIgnore(projectRoot) {
|
|
66
|
+
const paths = {
|
|
67
|
+
contextIgnore: this.findConfigFile(projectRoot, '.contextignore'),
|
|
68
|
+
contextInclude: this.findConfigFile(projectRoot, '.contextinclude')
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
return new GitIgnoreParser(
|
|
72
|
+
path.join(projectRoot, '.gitignore'),
|
|
73
|
+
paths.contextIgnore,
|
|
74
|
+
paths.contextInclude
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Get all config file paths
|
|
80
|
+
* @param {string} projectRoot - Project root directory
|
|
81
|
+
* @returns {object} Config paths
|
|
82
|
+
*/
|
|
83
|
+
static getConfigPaths(projectRoot) {
|
|
84
|
+
return {
|
|
85
|
+
gitignore: path.join(projectRoot, '.gitignore'),
|
|
86
|
+
contextIgnore: this.findConfigFile(projectRoot, '.contextignore'),
|
|
87
|
+
contextInclude: this.findConfigFile(projectRoot, '.contextinclude'),
|
|
88
|
+
methodInclude: this.findConfigFile(projectRoot, '.methodinclude'),
|
|
89
|
+
methodIgnore: this.findConfigFile(projectRoot, '.methodignore')
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export default ConfigUtils;
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Error Handler Utilities
|
|
3
|
+
* v2.3.4 - Enhanced error handling and user-friendly messages
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import fs from 'fs';
|
|
7
|
+
|
|
8
|
+
class ErrorHandler {
|
|
9
|
+
constructor(options = {}) {
|
|
10
|
+
this.verbose = options.verbose || false;
|
|
11
|
+
this.logFile = options.logFile || null;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Handle format errors
|
|
16
|
+
*/
|
|
17
|
+
handleFormatError(error, format) {
|
|
18
|
+
const message = `Format error (${format}): ${error.message}`;
|
|
19
|
+
|
|
20
|
+
if (this.verbose) {
|
|
21
|
+
console.error('❌ ' + message);
|
|
22
|
+
console.error(' Stack:', error.stack);
|
|
23
|
+
} else {
|
|
24
|
+
console.error('❌ ' + message);
|
|
25
|
+
console.error(' Use --verbose for detailed error information');
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
this.logError(message, error);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Handle file errors
|
|
33
|
+
*/
|
|
34
|
+
handleFileError(error, filePath) {
|
|
35
|
+
const message = `File error (${filePath}): ${error.message}`;
|
|
36
|
+
|
|
37
|
+
console.error('❌ ' + message);
|
|
38
|
+
|
|
39
|
+
// Provide helpful suggestions
|
|
40
|
+
if (error.code === 'ENOENT') {
|
|
41
|
+
console.error(' Suggestion: Check if the file path is correct');
|
|
42
|
+
} else if (error.code === 'EACCES') {
|
|
43
|
+
console.error(' Suggestion: Check file permissions');
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
this.logError(message, error);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Handle parse errors
|
|
51
|
+
*/
|
|
52
|
+
handleParseError(error, format, content) {
|
|
53
|
+
const message = `Parse error (${format}): ${error.message}`;
|
|
54
|
+
|
|
55
|
+
console.error('❌ ' + message);
|
|
56
|
+
|
|
57
|
+
if (this.verbose && content) {
|
|
58
|
+
console.error(' Content preview:', content.substring(0, 200) + '...');
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
this.logError(message, error);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Handle validation errors
|
|
66
|
+
*/
|
|
67
|
+
handleValidationError(errors, context) {
|
|
68
|
+
console.error(`❌ Validation failed (${context}):`);
|
|
69
|
+
|
|
70
|
+
for (const error of errors) {
|
|
71
|
+
console.error(` • ${error}`);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
this.logError(`Validation errors in ${context}`, { errors });
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Log error to file (if configured)
|
|
79
|
+
*/
|
|
80
|
+
logError(message, error) {
|
|
81
|
+
if (!this.logFile) return;
|
|
82
|
+
|
|
83
|
+
try {
|
|
84
|
+
const timestamp = new Date().toISOString();
|
|
85
|
+
const logEntry = `[${timestamp}] ${message}\n${error.stack || ''}\n\n`;
|
|
86
|
+
|
|
87
|
+
fs.appendFileSync(this.logFile, logEntry, 'utf8');
|
|
88
|
+
} catch (logError) {
|
|
89
|
+
// Silently fail if logging fails
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Wrap async function with error handling
|
|
95
|
+
*/
|
|
96
|
+
wrapAsync(fn, context) {
|
|
97
|
+
return async (...args) => {
|
|
98
|
+
try {
|
|
99
|
+
return await fn(...args);
|
|
100
|
+
} catch (error) {
|
|
101
|
+
console.error(`❌ Error in ${context}:`, error.message);
|
|
102
|
+
if (this.verbose) {
|
|
103
|
+
console.error(' Stack:', error.stack);
|
|
104
|
+
}
|
|
105
|
+
this.logError(`Error in ${context}`, error);
|
|
106
|
+
throw error;
|
|
107
|
+
}
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Validate format support
|
|
113
|
+
*/
|
|
114
|
+
validateFormat(format, supportedFormats) {
|
|
115
|
+
if (!supportedFormats.includes(format)) {
|
|
116
|
+
const message = `Unsupported format: ${format}`;
|
|
117
|
+
const suggestion = `Supported formats: ${supportedFormats.join(', ')}`;
|
|
118
|
+
|
|
119
|
+
console.error(`❌ ${message}`);
|
|
120
|
+
console.error(` ${suggestion}`);
|
|
121
|
+
|
|
122
|
+
throw new Error(message);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Create user-friendly error message
|
|
128
|
+
*/
|
|
129
|
+
createUserMessage(error, context = '') {
|
|
130
|
+
const prefix = context ? `${context}: ` : '';
|
|
131
|
+
|
|
132
|
+
// Map common errors to user-friendly messages
|
|
133
|
+
const errorMap = {
|
|
134
|
+
'ENOENT': 'File not found',
|
|
135
|
+
'EACCES': 'Permission denied',
|
|
136
|
+
'EISDIR': 'Expected file, got directory',
|
|
137
|
+
'ENOTDIR': 'Expected directory, got file'
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
const userMessage = errorMap[error.code] || error.message;
|
|
141
|
+
return `${prefix}${userMessage}`;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
export default ErrorHandler;
|