mkctx 2.0.1 ā 4.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/README.md +146 -135
- package/bin/mkctx.js +720 -374
- package/package.json +15 -6
package/bin/mkctx.js
CHANGED
|
@@ -2,461 +2,807 @@
|
|
|
2
2
|
|
|
3
3
|
const fs = require('fs');
|
|
4
4
|
const path = require('path');
|
|
5
|
-
const readline = require('readline');
|
|
6
5
|
|
|
7
|
-
|
|
6
|
+
// ============================================
|
|
7
|
+
// LAZY LOAD DEPENDENCIES (for faster startup)
|
|
8
|
+
// ============================================
|
|
9
|
+
|
|
10
|
+
let inquirer, chalk, ora;
|
|
8
11
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
"dynamic": false
|
|
12
|
+
function loadDependencies() {
|
|
13
|
+
if (!inquirer) {
|
|
14
|
+
inquirer = require('inquirer');
|
|
15
|
+
chalk = require('chalk');
|
|
16
|
+
ora = require('ora');
|
|
17
|
+
}
|
|
16
18
|
}
|
|
17
19
|
|
|
18
|
-
//
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
cs: 'csharp',
|
|
31
|
-
cpp: 'cpp',
|
|
32
|
-
c: 'c',
|
|
33
|
-
h: 'c',
|
|
34
|
-
hpp: 'cpp',
|
|
35
|
-
php: 'php',
|
|
36
|
-
sh: 'bash',
|
|
37
|
-
bash: 'bash',
|
|
38
|
-
zsh: 'bash',
|
|
39
|
-
ps1: 'powershell',
|
|
40
|
-
sql: 'sql',
|
|
41
|
-
html: 'html',
|
|
42
|
-
css: 'css',
|
|
43
|
-
scss: 'scss',
|
|
44
|
-
sass: 'sass',
|
|
45
|
-
less: 'less',
|
|
46
|
-
json: 'json',
|
|
47
|
-
xml: 'xml',
|
|
48
|
-
yaml: 'yaml',
|
|
49
|
-
yml: 'yaml',
|
|
50
|
-
md: 'markdown',
|
|
51
|
-
vue: 'vue',
|
|
52
|
-
svelte: 'svelte',
|
|
53
|
-
dockerfile: 'dockerfile',
|
|
54
|
-
makefile: 'makefile',
|
|
55
|
-
toml: 'toml',
|
|
56
|
-
ini: 'ini',
|
|
57
|
-
cfg: 'ini',
|
|
58
|
-
env: 'bash'
|
|
20
|
+
// ============================================
|
|
21
|
+
// CONSTANTS
|
|
22
|
+
// ============================================
|
|
23
|
+
|
|
24
|
+
const CONFIG_FILE = 'mkctx.config.json';
|
|
25
|
+
|
|
26
|
+
const DEFAULT_PROJECT_CONFIG = {
|
|
27
|
+
src: ".",
|
|
28
|
+
ignore: "mkctx.config.json, pnpm-lock.yaml, **/.titan/, mkctx/, node_modules/, .git/, dist/, build/, target/, .next/, out/, .cache, package-lock.json, *.log, temp/, tmp/, coverage/, .nyc_output, .env, .env.local, .env.development.local, .env.test.local, .env.production.local, npm-debug.log*, yarn-debug.log*, yarn-error.log*, .npm, .yarn-integrity, .parcel-cache, .vuepress/dist, .svelte-kit, **/*.rs.bk, .idea/, .vscode/, .DS_Store, Thumbs.db, *.swp, *.swo, .~lock.*, Cargo.lock, .cargo/registry/, .cargo/git/, .rustup/, *.pdb, *.dSYM/, *.so, *.dll, *.dylib, *.exe, *.lib, *.a, *.o, *.rlib, *.d, *.tmp, *.bak, *.orig, *.rej, *.pyc, *.pyo, *.class, *.jar, *.war, *.ear, *.zip, *.tar.gz, *.rar, *.7z, *.iso, *.img, *.dmg, *.pdf, *.doc, *.docx, *.xls, *.xlsx, *.ppt, *.pptx",
|
|
29
|
+
output: "./mkctx",
|
|
30
|
+
first_comment: "/* Project Context */",
|
|
31
|
+
last_comment: "/* End of Context */"
|
|
59
32
|
};
|
|
60
33
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
case 'version':
|
|
75
|
-
case '--version':
|
|
76
|
-
case '-v':
|
|
77
|
-
showVersion();
|
|
78
|
-
break;
|
|
79
|
-
default:
|
|
80
|
-
await generateContext();
|
|
81
|
-
}
|
|
82
|
-
}
|
|
34
|
+
// Language mapping for syntax highlighting
|
|
35
|
+
const LANG_MAP = {
|
|
36
|
+
js: 'javascript', ts: 'typescript', jsx: 'jsx', tsx: 'tsx',
|
|
37
|
+
py: 'python', rb: 'ruby', go: 'go', rs: 'rust',
|
|
38
|
+
java: 'java', kt: 'kotlin', cs: 'csharp', cpp: 'cpp',
|
|
39
|
+
c: 'c', h: 'c', hpp: 'cpp', php: 'php',
|
|
40
|
+
sh: 'bash', bash: 'bash', zsh: 'bash', ps1: 'powershell',
|
|
41
|
+
sql: 'sql', html: 'html', css: 'css', scss: 'scss',
|
|
42
|
+
sass: 'sass', less: 'less', json: 'json', xml: 'xml',
|
|
43
|
+
yaml: 'yaml', yml: 'yaml', md: 'markdown', vue: 'vue',
|
|
44
|
+
svelte: 'svelte', dockerfile: 'dockerfile', makefile: 'makefile',
|
|
45
|
+
toml: 'toml', ini: 'ini', cfg: 'ini', env: 'bash'
|
|
46
|
+
};
|
|
83
47
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
48
|
+
// Text file extensions
|
|
49
|
+
const TEXT_EXTENSIONS = new Set([
|
|
50
|
+
'.js', '.ts', '.jsx', '.tsx', '.mjs', '.cjs',
|
|
51
|
+
'.py', '.pyw', '.rb', '.rake', '.go', '.rs',
|
|
52
|
+
'.java', '.kt', '.kts', '.scala', '.cs', '.fs', '.vb',
|
|
53
|
+
'.cpp', '.c', '.h', '.hpp', '.cc', '.cxx',
|
|
54
|
+
'.php', '.phtml', '.sh', '.bash', '.zsh', '.fish', '.ps1', '.bat', '.cmd',
|
|
55
|
+
'.sql', '.html', '.htm', '.xhtml',
|
|
56
|
+
'.css', '.scss', '.sass', '.less', '.styl',
|
|
57
|
+
'.json', '.json5', '.xml', '.xsl', '.xslt',
|
|
58
|
+
'.yaml', '.yml', '.md', '.markdown', '.mdx',
|
|
59
|
+
'.txt', '.text', '.vue', '.svelte',
|
|
60
|
+
'.dockerfile', '.makefile', '.toml', '.ini', '.cfg', '.conf',
|
|
61
|
+
'.env', '.env.example', '.gitignore', '.gitattributes', '.editorconfig',
|
|
62
|
+
'.eslintrc', '.prettierrc', '.babelrc',
|
|
63
|
+
'.graphql', '.gql', '.proto', '.tf', '.tfvars',
|
|
64
|
+
'.lua', '.r', '.R', '.swift', '.m', '.mm',
|
|
65
|
+
'.ex', '.exs', '.erl', '.hrl', '.clj', '.cljs', '.cljc',
|
|
66
|
+
'.hs', '.lhs', '.elm', '.pug', '.jade',
|
|
67
|
+
'.ejs', '.hbs', '.handlebars', '.twig', '.blade.php',
|
|
68
|
+
'.astro', '.prisma', '.sol'
|
|
69
|
+
]);
|
|
70
|
+
|
|
71
|
+
const KNOWN_FILES = new Set([
|
|
72
|
+
'dockerfile', 'makefile', 'gemfile', 'rakefile',
|
|
73
|
+
'procfile', 'vagrantfile', 'jenkinsfile',
|
|
74
|
+
'.gitignore', '.gitattributes', '.editorconfig',
|
|
75
|
+
'.eslintrc', '.prettierrc', '.babelrc',
|
|
76
|
+
'.env', '.env.example', '.env.local',
|
|
77
|
+
'readme.md', 'readme.txt', 'readme',
|
|
78
|
+
'license', 'license.md', 'license.txt'
|
|
79
|
+
]);
|
|
80
|
+
|
|
81
|
+
// ============================================
|
|
82
|
+
// CONFIGURATION MANAGEMENT
|
|
83
|
+
// ============================================
|
|
84
|
+
|
|
85
|
+
function loadProjectConfig() {
|
|
86
|
+
if (fs.existsSync(CONFIG_FILE)) {
|
|
87
|
+
try {
|
|
88
|
+
const config = JSON.parse(fs.readFileSync(CONFIG_FILE, 'utf-8'));
|
|
89
|
+
return { ...DEFAULT_PROJECT_CONFIG, ...config };
|
|
90
|
+
} catch (err) {
|
|
91
|
+
console.log(chalk.yellow('ā ļø Error parsing config file, using defaults'));
|
|
92
|
+
return { ...DEFAULT_PROJECT_CONFIG };
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
return null;
|
|
110
96
|
}
|
|
111
97
|
|
|
112
|
-
function
|
|
113
|
-
|
|
114
|
-
console.log(`mkctx v${pkg.version}`);
|
|
98
|
+
function hasProjectConfig() {
|
|
99
|
+
return fs.existsSync(CONFIG_FILE);
|
|
115
100
|
}
|
|
116
101
|
|
|
117
|
-
function
|
|
118
|
-
|
|
119
|
-
if (!fs.existsSync('mkctx')) {
|
|
120
|
-
fs.mkdirSync('mkctx', { recursive: true });
|
|
121
|
-
}
|
|
102
|
+
function createProjectConfig() {
|
|
103
|
+
loadDependencies();
|
|
122
104
|
|
|
123
|
-
|
|
124
|
-
|
|
105
|
+
if (!fs.existsSync('mkctx')) {
|
|
106
|
+
fs.mkdirSync('mkctx', { recursive: true });
|
|
107
|
+
}
|
|
125
108
|
|
|
126
|
-
|
|
127
|
-
|
|
109
|
+
fs.writeFileSync(CONFIG_FILE, JSON.stringify(DEFAULT_PROJECT_CONFIG, null, 2));
|
|
110
|
+
updateGitignore();
|
|
128
111
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
112
|
+
console.log(chalk.green('\nā
Configuration created:'));
|
|
113
|
+
console.log(chalk.white(' - mkctx.config.json'));
|
|
114
|
+
console.log(chalk.white(' - mkctx/ folder'));
|
|
115
|
+
console.log(chalk.white(' - Entry in .gitignore\n'));
|
|
133
116
|
}
|
|
134
117
|
|
|
135
118
|
function updateGitignore() {
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
if (fs.existsSync(gitignorePath)) {
|
|
140
|
-
content = fs.readFileSync(gitignorePath, 'utf-8');
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
if (!content.includes('mkctx/')) {
|
|
144
|
-
const entry = '\n# mkctx - generated context\nmkctx/\n';
|
|
145
|
-
content += entry;
|
|
146
|
-
fs.writeFileSync(gitignorePath, content);
|
|
147
|
-
}
|
|
148
|
-
}
|
|
119
|
+
const gitignorePath = '.gitignore';
|
|
120
|
+
let content = '';
|
|
149
121
|
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
122
|
+
if (fs.existsSync(gitignorePath)) {
|
|
123
|
+
content = fs.readFileSync(gitignorePath, 'utf-8');
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
if (!content.includes('mkctx/')) {
|
|
127
|
+
const entry = '\n# mkctx - generated context\nmkctx/\n';
|
|
128
|
+
content += entry;
|
|
129
|
+
fs.writeFileSync(gitignorePath, content);
|
|
158
130
|
}
|
|
159
|
-
}
|
|
160
|
-
// Sin config, activar dynamic por defecto
|
|
161
|
-
return { ...defaultConfig, dynamic: true };
|
|
162
131
|
}
|
|
163
132
|
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
133
|
+
// ============================================
|
|
134
|
+
// UTILITY FUNCTIONS
|
|
135
|
+
// ============================================
|
|
136
|
+
|
|
137
|
+
function formatSize(bytes) {
|
|
138
|
+
const sizes = ['B', 'KB', 'MB', 'GB'];
|
|
139
|
+
if (bytes === 0) return '0 B';
|
|
140
|
+
const i = Math.floor(Math.log(bytes) / Math.log(1024));
|
|
141
|
+
return `${(bytes / Math.pow(1024, i)).toFixed(2)} ${sizes[i]}`;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function estimateTokens(text) {
|
|
145
|
+
return Math.ceil(text.length / 4);
|
|
146
|
+
}
|
|
169
147
|
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
148
|
+
// Normalize path to always use forward slashes
|
|
149
|
+
function normalizePath(filePath) {
|
|
150
|
+
return filePath.replace(/\\/g, '/');
|
|
151
|
+
}
|
|
173
152
|
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
153
|
+
// ============================================
|
|
154
|
+
// FILE OPERATIONS
|
|
155
|
+
// ============================================
|
|
177
156
|
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
}
|
|
157
|
+
function isTextFile(filename) {
|
|
158
|
+
const ext = path.extname(filename).toLowerCase();
|
|
159
|
+
const basename = path.basename(filename).toLowerCase();
|
|
182
160
|
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
resolve(defaultPath);
|
|
186
|
-
return;
|
|
187
|
-
}
|
|
161
|
+
if (KNOWN_FILES.has(basename)) return true;
|
|
162
|
+
if (ext && TEXT_EXTENSIONS.has(ext)) return true;
|
|
188
163
|
|
|
189
|
-
|
|
190
|
-
});
|
|
191
|
-
});
|
|
164
|
+
return false;
|
|
192
165
|
}
|
|
193
166
|
|
|
194
|
-
|
|
195
|
-
|
|
167
|
+
function getLanguage(filename) {
|
|
168
|
+
const ext = path.extname(filename).slice(1).toLowerCase();
|
|
169
|
+
const basename = path.basename(filename).toLowerCase();
|
|
196
170
|
|
|
197
|
-
|
|
171
|
+
if (basename === 'dockerfile') return 'dockerfile';
|
|
172
|
+
if (basename === 'makefile') return 'makefile';
|
|
173
|
+
if (basename.startsWith('.env')) return 'bash';
|
|
198
174
|
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
srcPath = await askForPath(srcPath);
|
|
202
|
-
} else if (config.src === '.' || config.src === '') {
|
|
203
|
-
srcPath = await askForPath('.');
|
|
204
|
-
}
|
|
175
|
+
return LANG_MAP[ext] || ext || 'text';
|
|
176
|
+
}
|
|
205
177
|
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
178
|
+
function parseIgnorePatterns(ignoreString) {
|
|
179
|
+
if (!ignoreString) return [];
|
|
180
|
+
return ignoreString
|
|
181
|
+
.split(',')
|
|
182
|
+
.map(p => p.trim())
|
|
183
|
+
.filter(Boolean);
|
|
184
|
+
}
|
|
211
185
|
|
|
212
|
-
|
|
186
|
+
function matchWildcard(pattern, filename) {
|
|
187
|
+
const regexPattern = pattern
|
|
188
|
+
.replace(/\./g, '\\.')
|
|
189
|
+
.replace(/\*\*/g, '.*')
|
|
190
|
+
.replace(/\*/g, '[^/]*');
|
|
191
|
+
const regex = new RegExp(`^${regexPattern}$`, 'i');
|
|
192
|
+
return regex.test(filename);
|
|
193
|
+
}
|
|
213
194
|
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
195
|
+
function shouldIgnore(fullPath, name, relativePath, patterns) {
|
|
196
|
+
// Normalize paths for comparison
|
|
197
|
+
const normalizedFull = normalizePath(fullPath);
|
|
198
|
+
const normalizedRelative = normalizePath(relativePath);
|
|
199
|
+
|
|
200
|
+
const systemIgnores = [
|
|
201
|
+
'.git', '.DS_Store', 'Thumbs.db', 'node_modules',
|
|
202
|
+
'.svn', '.hg', '__pycache__', '.pytest_cache',
|
|
203
|
+
'.mypy_cache', '.vscode', '.idea'
|
|
204
|
+
];
|
|
205
|
+
|
|
206
|
+
for (const ignore of systemIgnores) {
|
|
207
|
+
if (normalizedFull.includes('/' + ignore + '/') ||
|
|
208
|
+
normalizedFull.includes('/' + ignore) ||
|
|
209
|
+
normalizedFull.endsWith('/' + ignore) ||
|
|
210
|
+
name === ignore) {
|
|
211
|
+
return true;
|
|
212
|
+
}
|
|
213
|
+
}
|
|
218
214
|
|
|
219
|
-
|
|
215
|
+
for (const pattern of patterns) {
|
|
216
|
+
if (pattern.includes('*')) {
|
|
217
|
+
if (matchWildcard(pattern, name)) return true;
|
|
218
|
+
if (matchWildcard(pattern, normalizedRelative)) return true;
|
|
219
|
+
}
|
|
220
220
|
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
221
|
+
if (pattern.endsWith('/')) {
|
|
222
|
+
const dir = pattern.slice(0, -1);
|
|
223
|
+
if (normalizedFull.includes('/' + dir + '/') ||
|
|
224
|
+
normalizedFull.endsWith('/' + dir) ||
|
|
225
|
+
name === dir) {
|
|
226
|
+
return true;
|
|
227
|
+
}
|
|
228
|
+
}
|
|
225
229
|
|
|
226
|
-
|
|
227
|
-
|
|
230
|
+
if (normalizedRelative === pattern || name === pattern) {
|
|
231
|
+
return true;
|
|
232
|
+
}
|
|
233
|
+
}
|
|
228
234
|
|
|
229
|
-
|
|
230
|
-
console.log(` š Source: ${srcPath}`);
|
|
231
|
-
console.log(` š Files included: ${files.length}`);
|
|
235
|
+
return false;
|
|
232
236
|
}
|
|
233
237
|
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
238
|
+
// ============================================
|
|
239
|
+
// SCAN AND BUILD JSON IN ONE PASS
|
|
240
|
+
// ============================================
|
|
241
|
+
|
|
242
|
+
function scanAndBuildJson(srcPath, config) {
|
|
243
|
+
const jsonArray = [];
|
|
244
|
+
const ignorePatterns = parseIgnorePatterns(config.ignore);
|
|
245
|
+
let totalSize = 0;
|
|
246
|
+
let totalLines = 0;
|
|
247
|
+
const filesByExt = {};
|
|
248
|
+
|
|
249
|
+
function walk(dir) {
|
|
250
|
+
if (!fs.existsSync(dir)) return;
|
|
251
|
+
|
|
252
|
+
let entries;
|
|
253
|
+
try {
|
|
254
|
+
entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
255
|
+
} catch (err) {
|
|
256
|
+
return;
|
|
257
|
+
}
|
|
237
258
|
|
|
238
|
-
|
|
239
|
-
|
|
259
|
+
for (const entry of entries) {
|
|
260
|
+
const fullPath = path.join(dir, entry.name);
|
|
261
|
+
const relativePath = path.relative(srcPath, fullPath);
|
|
262
|
+
|
|
263
|
+
if (shouldIgnore(fullPath, entry.name, relativePath, ignorePatterns)) {
|
|
264
|
+
continue;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
if (entry.isDirectory()) {
|
|
268
|
+
walk(fullPath);
|
|
269
|
+
} else if (entry.isFile() && isTextFile(entry.name)) {
|
|
270
|
+
// Read file immediately when found
|
|
271
|
+
let content;
|
|
272
|
+
try {
|
|
273
|
+
content = fs.readFileSync(fullPath, 'utf-8');
|
|
274
|
+
} catch (err) {
|
|
275
|
+
// Skip files that can't be read
|
|
276
|
+
continue;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
const ext = path.extname(entry.name).slice(1).toLowerCase() || null;
|
|
280
|
+
const lines = content.split('\n').length;
|
|
281
|
+
const size = Buffer.byteLength(content, 'utf-8');
|
|
282
|
+
const language = getLanguage(entry.name);
|
|
283
|
+
|
|
284
|
+
// Update stats
|
|
285
|
+
totalSize += size;
|
|
286
|
+
totalLines += lines;
|
|
287
|
+
filesByExt[ext || 'other'] = (filesByExt[ext || 'other'] || 0) + 1;
|
|
288
|
+
|
|
289
|
+
// Add to JSON array immediately
|
|
290
|
+
jsonArray.push({
|
|
291
|
+
path: normalizePath(relativePath),
|
|
292
|
+
name: entry.name,
|
|
293
|
+
extension: ext,
|
|
294
|
+
language: language,
|
|
295
|
+
lines: lines,
|
|
296
|
+
size: size,
|
|
297
|
+
content: content
|
|
298
|
+
});
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
}
|
|
240
302
|
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
303
|
+
walk(srcPath);
|
|
304
|
+
|
|
305
|
+
// Sort by path for consistency
|
|
306
|
+
jsonArray.sort((a, b) => a.path.localeCompare(b.path));
|
|
307
|
+
|
|
308
|
+
const stats = {
|
|
309
|
+
files: jsonArray.length,
|
|
310
|
+
totalSize,
|
|
311
|
+
totalLines,
|
|
312
|
+
filesByExt
|
|
313
|
+
};
|
|
314
|
+
|
|
315
|
+
return { jsonArray, stats };
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
// ============================================
|
|
319
|
+
// FORMAT CONVERTERS (from base JSON)
|
|
320
|
+
// ============================================
|
|
321
|
+
|
|
322
|
+
function toJson(baseJson) {
|
|
323
|
+
return JSON.stringify(baseJson, null, 2);
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
function toMarkdown(baseJson, config) {
|
|
327
|
+
let content = '';
|
|
328
|
+
|
|
329
|
+
if (config.first_comment) {
|
|
330
|
+
content += config.first_comment + '\n\n';
|
|
247
331
|
}
|
|
248
332
|
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
333
|
+
// Project structure
|
|
334
|
+
content += '## Project Structure\n\n```\n';
|
|
335
|
+
const dirs = new Set();
|
|
336
|
+
baseJson.forEach(f => {
|
|
337
|
+
const dir = path.dirname(f.path);
|
|
338
|
+
if (dir !== '.') dirs.add(dir);
|
|
339
|
+
});
|
|
340
|
+
Array.from(dirs).sort().forEach(d => content += `š ${d}/\n`);
|
|
341
|
+
content += `\n${baseJson.length} files total\n\`\`\`\n\n`;
|
|
342
|
+
|
|
343
|
+
// Source files
|
|
344
|
+
content += '## Source Files\n\n';
|
|
345
|
+
|
|
346
|
+
for (const file of baseJson) {
|
|
347
|
+
content += `### ${file.path}\n\n`;
|
|
348
|
+
content += '```' + file.language + '\n';
|
|
349
|
+
content += file.content;
|
|
350
|
+
if (!file.content.endsWith('\n')) {
|
|
351
|
+
content += '\n';
|
|
352
|
+
}
|
|
353
|
+
content += '```\n\n';
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
if (config.last_comment) {
|
|
357
|
+
content += config.last_comment;
|
|
358
|
+
}
|
|
252
359
|
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
360
|
+
return content;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
function toToon(baseJson, stats) {
|
|
364
|
+
let content = '';
|
|
365
|
+
|
|
366
|
+
// Meta header
|
|
367
|
+
content += `# Project Context\n`;
|
|
368
|
+
content += `# Generated: ${new Date().toISOString()}\n`;
|
|
369
|
+
content += `# Files: ${baseJson.length}\n`;
|
|
370
|
+
content += `# Lines: ${stats.totalLines}\n`;
|
|
371
|
+
content += `# Size: ${stats.totalSize} bytes\n\n`;
|
|
372
|
+
|
|
373
|
+
// Files table (compact tabular format - TOON's strength)
|
|
374
|
+
content += `files[${baseJson.length}]{path,name,extension,language,lines,size}:\n`;
|
|
375
|
+
for (const file of baseJson) {
|
|
376
|
+
const ext = file.extension || '';
|
|
377
|
+
content += ` ${escapeToonValue(file.path)},${escapeToonValue(file.name)},${ext},${file.language},${file.lines},${file.size}\n`;
|
|
378
|
+
}
|
|
256
379
|
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
380
|
+
content += '\n';
|
|
381
|
+
|
|
382
|
+
// File contents
|
|
383
|
+
for (let i = 0; i < baseJson.length; i++) {
|
|
384
|
+
const file = baseJson[i];
|
|
385
|
+
content += `---\n`;
|
|
386
|
+
content += `[${i}] ${file.path}\n`;
|
|
387
|
+
content += `language: ${file.language}\n`;
|
|
388
|
+
content += `content:\n`;
|
|
389
|
+
// Indent each line with 2 spaces
|
|
390
|
+
const lines = file.content.split('\n');
|
|
391
|
+
for (const line of lines) {
|
|
392
|
+
content += ` ${line}\n`;
|
|
263
393
|
}
|
|
264
|
-
}
|
|
265
394
|
}
|
|
266
|
-
}
|
|
267
395
|
|
|
268
|
-
|
|
269
|
-
return files.sort(); // Ordenar alfabƩticamente
|
|
396
|
+
return content;
|
|
270
397
|
}
|
|
271
398
|
|
|
272
|
-
function
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
.
|
|
276
|
-
|
|
277
|
-
|
|
399
|
+
function escapeToonValue(value) {
|
|
400
|
+
if (value === null || value === undefined) return '';
|
|
401
|
+
const str = String(value);
|
|
402
|
+
if (str.includes(',') || str.includes('\n') || str.includes('"') ||
|
|
403
|
+
str.startsWith(' ') || str.endsWith(' ')) {
|
|
404
|
+
return '"' + str.replace(/"/g, '""').replace(/\n/g, '\\n') + '"';
|
|
405
|
+
}
|
|
406
|
+
return str;
|
|
278
407
|
}
|
|
279
408
|
|
|
280
|
-
function
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
'*.pyc',
|
|
295
|
-
'*.pyo',
|
|
296
|
-
'.env.local',
|
|
297
|
-
'.env.*.local'
|
|
298
|
-
];
|
|
299
|
-
|
|
300
|
-
for (const ignore of systemIgnores) {
|
|
301
|
-
if (ignore.includes('*')) {
|
|
302
|
-
if (matchWildcard(ignore, name)) return true;
|
|
303
|
-
} else {
|
|
304
|
-
if (fullPath.includes(ignore) || name === ignore) return true;
|
|
409
|
+
function toXml(baseJson) {
|
|
410
|
+
let content = '<?xml version="1.0" encoding="UTF-8"?>\n';
|
|
411
|
+
content += '<context>\n';
|
|
412
|
+
|
|
413
|
+
for (const file of baseJson) {
|
|
414
|
+
content += ` <file>\n`;
|
|
415
|
+
content += ` <path>${escapeXml(file.path)}</path>\n`;
|
|
416
|
+
content += ` <name>${escapeXml(file.name)}</name>\n`;
|
|
417
|
+
content += ` <extension>${escapeXml(file.extension || '')}</extension>\n`;
|
|
418
|
+
content += ` <language>${escapeXml(file.language)}</language>\n`;
|
|
419
|
+
content += ` <lines>${file.lines}</lines>\n`;
|
|
420
|
+
content += ` <size>${file.size}</size>\n`;
|
|
421
|
+
content += ` <content><![CDATA[\n${file.content}${file.content.endsWith('\n') ? '' : '\n'}]]></content>\n`;
|
|
422
|
+
content += ` </file>\n`;
|
|
305
423
|
}
|
|
306
|
-
}
|
|
307
424
|
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
425
|
+
content += '</context>\n';
|
|
426
|
+
return content;
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
function escapeXml(str) {
|
|
430
|
+
if (str === null || str === undefined) return '';
|
|
431
|
+
return String(str)
|
|
432
|
+
.replace(/&/g, '&')
|
|
433
|
+
.replace(/</g, '<')
|
|
434
|
+
.replace(/>/g, '>')
|
|
435
|
+
.replace(/"/g, '"')
|
|
436
|
+
.replace(/'/g, ''');
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
// ============================================
|
|
440
|
+
// CONTEXT GENERATION
|
|
441
|
+
// ============================================
|
|
442
|
+
|
|
443
|
+
async function generateContextDynamic() {
|
|
444
|
+
loadDependencies();
|
|
445
|
+
|
|
446
|
+
const { srcPath } = await inquirer.prompt([
|
|
447
|
+
{
|
|
448
|
+
type: 'input',
|
|
449
|
+
name: 'srcPath',
|
|
450
|
+
message: 'Enter the source path to analyze:',
|
|
451
|
+
default: '.',
|
|
452
|
+
validate: (input) => {
|
|
453
|
+
if (!fs.existsSync(input)) {
|
|
454
|
+
return `Path does not exist: ${input}`;
|
|
455
|
+
}
|
|
456
|
+
return true;
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
]);
|
|
460
|
+
|
|
461
|
+
const config = { ...DEFAULT_PROJECT_CONFIG, src: srcPath };
|
|
462
|
+
return generateContext(config, srcPath);
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
async function generateContextFromConfigFile() {
|
|
466
|
+
loadDependencies();
|
|
467
|
+
|
|
468
|
+
const config = loadProjectConfig();
|
|
469
|
+
if (!config) {
|
|
470
|
+
console.log(chalk.yellow('\nā ļø No config file found.'));
|
|
471
|
+
return null;
|
|
313
472
|
}
|
|
314
473
|
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
474
|
+
return generateContext(config, config.src);
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
function generateContext(config, srcPath) {
|
|
478
|
+
const spinner = ora(`Scanning and reading files from ${srcPath}...`).start();
|
|
479
|
+
|
|
480
|
+
if (!fs.existsSync(srcPath)) {
|
|
481
|
+
spinner.fail(`Source path does not exist: ${srcPath}`);
|
|
482
|
+
return null;
|
|
323
483
|
}
|
|
324
484
|
|
|
325
|
-
//
|
|
326
|
-
|
|
327
|
-
|
|
485
|
+
// Single pass: scan AND build JSON at the same time
|
|
486
|
+
const { jsonArray, stats } = scanAndBuildJson(srcPath, config);
|
|
487
|
+
|
|
488
|
+
if (jsonArray.length === 0) {
|
|
489
|
+
spinner.fail(`No files found in: ${srcPath}`);
|
|
490
|
+
return null;
|
|
328
491
|
}
|
|
329
|
-
}
|
|
330
492
|
|
|
331
|
-
|
|
332
|
-
}
|
|
493
|
+
spinner.succeed(`Context built: ${chalk.yellow(jsonArray.length)} files, ${chalk.yellow(formatSize(stats.totalSize))}`);
|
|
333
494
|
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
const regex = new RegExp(`^${regexPattern}$`, 'i');
|
|
340
|
-
return regex.test(filename);
|
|
495
|
+
return {
|
|
496
|
+
baseJson: jsonArray,
|
|
497
|
+
stats: stats,
|
|
498
|
+
config
|
|
499
|
+
};
|
|
341
500
|
}
|
|
342
501
|
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
// Archivos sin extensión pero conocidos
|
|
394
|
-
const knownFiles = [
|
|
395
|
-
'dockerfile', 'makefile', 'gemfile', 'rakefile',
|
|
396
|
-
'procfile', 'vagrantfile', 'jenkinsfile',
|
|
397
|
-
'.gitignore', '.gitattributes', '.editorconfig',
|
|
398
|
-
'.eslintrc', '.prettierrc', '.babelrc',
|
|
399
|
-
'.env', '.env.example', '.env.local'
|
|
400
|
-
];
|
|
502
|
+
// ============================================
|
|
503
|
+
// SAVE CONTEXT
|
|
504
|
+
// ============================================
|
|
505
|
+
|
|
506
|
+
// ============================================
|
|
507
|
+
// SAVE CONTEXT
|
|
508
|
+
// ============================================
|
|
509
|
+
|
|
510
|
+
async function saveContext(result, formats) {
|
|
511
|
+
loadDependencies();
|
|
512
|
+
|
|
513
|
+
const { fileName } = await inquirer.prompt([
|
|
514
|
+
{
|
|
515
|
+
type: 'input',
|
|
516
|
+
name: 'fileName',
|
|
517
|
+
message: 'Enter a name for the output files:',
|
|
518
|
+
default: 'context'
|
|
519
|
+
}
|
|
520
|
+
]);
|
|
521
|
+
|
|
522
|
+
let outputPath = result.config.output || './mkctx';
|
|
523
|
+
|
|
524
|
+
if (!fs.existsSync(outputPath)) {
|
|
525
|
+
fs.mkdirSync(outputPath, { recursive: true });
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
const savedFiles = [];
|
|
529
|
+
|
|
530
|
+
for (const format of formats) {
|
|
531
|
+
let content;
|
|
532
|
+
let filename;
|
|
533
|
+
|
|
534
|
+
switch (format) {
|
|
535
|
+
case 'json':
|
|
536
|
+
content = toJson(result.baseJson);
|
|
537
|
+
filename = `${fileName}.json`;
|
|
538
|
+
break;
|
|
539
|
+
case 'md':
|
|
540
|
+
content = toMarkdown(result.baseJson, result.config);
|
|
541
|
+
filename = `${fileName}.md`;
|
|
542
|
+
break;
|
|
543
|
+
case 'toon':
|
|
544
|
+
content = toToon(result.baseJson, result.stats);
|
|
545
|
+
filename = `${fileName}.toon`;
|
|
546
|
+
break;
|
|
547
|
+
case 'xml':
|
|
548
|
+
content = toXml(result.baseJson);
|
|
549
|
+
filename = `${fileName}.xml`;
|
|
550
|
+
break;
|
|
551
|
+
}
|
|
401
552
|
|
|
402
|
-
|
|
403
|
-
|
|
553
|
+
const outputFile = path.join(outputPath, filename);
|
|
554
|
+
fs.writeFileSync(outputFile, content);
|
|
555
|
+
const size = Buffer.byteLength(content, 'utf-8');
|
|
556
|
+
const tokens = estimateTokens(content);
|
|
557
|
+
savedFiles.push({ format, file: outputFile, size, tokens });
|
|
558
|
+
}
|
|
404
559
|
|
|
405
|
-
|
|
560
|
+
console.log(chalk.green('\nā
Context saved:\n'));
|
|
561
|
+
for (const { format, file, size, tokens } of savedFiles) {
|
|
562
|
+
console.log(chalk.white(` ${chalk.cyan(format.toUpperCase().padEnd(4))} ā ${chalk.yellow(file)}`));
|
|
563
|
+
console.log(chalk.gray(` ${formatSize(size)} | ~${tokens.toLocaleString()} tokens\n`));
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
return savedFiles;
|
|
406
567
|
}
|
|
407
568
|
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
569
|
+
// ============================================
|
|
570
|
+
// FORMAT SELECTION
|
|
571
|
+
// ============================================
|
|
572
|
+
|
|
573
|
+
async function selectFormat() {
|
|
574
|
+
loadDependencies();
|
|
575
|
+
|
|
576
|
+
const { format } = await inquirer.prompt([
|
|
577
|
+
{
|
|
578
|
+
type: 'list',
|
|
579
|
+
name: 'format',
|
|
580
|
+
message: 'Select output format:',
|
|
581
|
+
default: 'all',
|
|
582
|
+
choices: [
|
|
583
|
+
{
|
|
584
|
+
name: chalk.magenta('š¦ All formats (MD, JSON, TOON, XML)'),
|
|
585
|
+
value: 'all'
|
|
586
|
+
},
|
|
587
|
+
new inquirer.Separator(),
|
|
588
|
+
{
|
|
589
|
+
name: chalk.blue('š Markdown (.md)'),
|
|
590
|
+
value: 'md'
|
|
591
|
+
},
|
|
592
|
+
{
|
|
593
|
+
name: chalk.green('š§ JSON (.json) - Simple array'),
|
|
594
|
+
value: 'json'
|
|
595
|
+
},
|
|
596
|
+
{
|
|
597
|
+
name: chalk.yellow('š TOON (.toon) - Token-optimized'),
|
|
598
|
+
value: 'toon'
|
|
599
|
+
},
|
|
600
|
+
{
|
|
601
|
+
name: chalk.red('š XML (.xml)'),
|
|
602
|
+
value: 'xml'
|
|
603
|
+
}
|
|
604
|
+
]
|
|
605
|
+
}
|
|
606
|
+
]);
|
|
411
607
|
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
if (basename.startsWith('.env')) return 'bash';
|
|
608
|
+
if (format === 'all') {
|
|
609
|
+
return ['json', 'md', 'toon', 'xml'];
|
|
610
|
+
}
|
|
416
611
|
|
|
417
|
-
|
|
612
|
+
return [format];
|
|
418
613
|
}
|
|
419
614
|
|
|
420
|
-
|
|
421
|
-
|
|
615
|
+
// ============================================
|
|
616
|
+
// MAIN MENU
|
|
617
|
+
// ============================================
|
|
422
618
|
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
}
|
|
619
|
+
async function showMainMenu() {
|
|
620
|
+
loadDependencies();
|
|
426
621
|
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
622
|
+
const hasConfig = hasProjectConfig();
|
|
623
|
+
|
|
624
|
+
console.log(chalk.cyan('\nāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā'));
|
|
625
|
+
console.log(chalk.cyan('ā') + chalk.cyan.bold(' š mkctx - Make Context ') + chalk.cyan('ā'));
|
|
626
|
+
console.log(chalk.cyan('āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā\n'));
|
|
627
|
+
|
|
628
|
+
const choices = [];
|
|
629
|
+
|
|
630
|
+
if (hasConfig) {
|
|
631
|
+
choices.push({
|
|
632
|
+
name: chalk.green('š Generate from config file'),
|
|
633
|
+
value: 'from-config'
|
|
634
|
+
});
|
|
434
635
|
}
|
|
435
636
|
|
|
436
|
-
|
|
437
|
-
|
|
637
|
+
choices.push(
|
|
638
|
+
{
|
|
639
|
+
name: chalk.blue('š Generate dynamically (choose path)'),
|
|
640
|
+
value: 'dynamic'
|
|
641
|
+
},
|
|
642
|
+
new inquirer.Separator(),
|
|
643
|
+
{
|
|
644
|
+
name: hasConfig
|
|
645
|
+
? chalk.gray('āļø View configuration')
|
|
646
|
+
: chalk.yellow('āļø Create configuration file'),
|
|
647
|
+
value: 'config'
|
|
648
|
+
},
|
|
649
|
+
new inquirer.Separator(),
|
|
650
|
+
{
|
|
651
|
+
name: chalk.red('ā Exit'),
|
|
652
|
+
value: 'exit'
|
|
653
|
+
}
|
|
654
|
+
);
|
|
655
|
+
|
|
656
|
+
const { action } = await inquirer.prompt([
|
|
657
|
+
{
|
|
658
|
+
type: 'list',
|
|
659
|
+
name: 'action',
|
|
660
|
+
message: 'What would you like to do?',
|
|
661
|
+
choices
|
|
662
|
+
}
|
|
663
|
+
]);
|
|
438
664
|
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
content += fileContent;
|
|
665
|
+
return action;
|
|
666
|
+
}
|
|
442
667
|
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
668
|
+
// ============================================
|
|
669
|
+
// MAIN APPLICATION
|
|
670
|
+
// ============================================
|
|
671
|
+
|
|
672
|
+
async function main() {
|
|
673
|
+
const args = process.argv.slice(2);
|
|
674
|
+
|
|
675
|
+
if (args.includes('--help') || args.includes('-h') || args[0] === 'help') {
|
|
676
|
+
showHelp();
|
|
677
|
+
return;
|
|
446
678
|
}
|
|
447
679
|
|
|
448
|
-
|
|
449
|
-
|
|
680
|
+
if (args.includes('--version') || args.includes('-v') || args[0] === 'version') {
|
|
681
|
+
showVersion();
|
|
682
|
+
return;
|
|
683
|
+
}
|
|
450
684
|
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
685
|
+
if (args[0] === 'config') {
|
|
686
|
+
loadDependencies();
|
|
687
|
+
createProjectConfig();
|
|
688
|
+
return;
|
|
689
|
+
}
|
|
454
690
|
|
|
455
|
-
|
|
691
|
+
loadDependencies();
|
|
692
|
+
|
|
693
|
+
let running = true;
|
|
694
|
+
|
|
695
|
+
while (running) {
|
|
696
|
+
const action = await showMainMenu();
|
|
697
|
+
|
|
698
|
+
switch (action) {
|
|
699
|
+
case 'from-config': {
|
|
700
|
+
const result = await generateContextFromConfigFile();
|
|
701
|
+
if (result) {
|
|
702
|
+
console.log(chalk.cyan('\nš Context Summary:'));
|
|
703
|
+
console.log(chalk.white(` Files: ${result.stats.files}`));
|
|
704
|
+
console.log(chalk.white(` Lines: ${result.stats.totalLines.toLocaleString()}`));
|
|
705
|
+
console.log(chalk.white(` Size: ${formatSize(result.stats.totalSize)}`));
|
|
706
|
+
|
|
707
|
+
const formats = await selectFormat();
|
|
708
|
+
await saveContext(result, formats);
|
|
709
|
+
|
|
710
|
+
console.log(chalk.yellow('š Done!\n'));
|
|
711
|
+
running = false;
|
|
712
|
+
}
|
|
713
|
+
break;
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
case 'dynamic': {
|
|
717
|
+
const result = await generateContextDynamic();
|
|
718
|
+
if (result) {
|
|
719
|
+
console.log(chalk.cyan('\nš Context Summary:'));
|
|
720
|
+
console.log(chalk.white(` Files: ${result.stats.files}`));
|
|
721
|
+
console.log(chalk.white(` Lines: ${result.stats.totalLines.toLocaleString()}`));
|
|
722
|
+
console.log(chalk.white(` Size: ${formatSize(result.stats.totalSize)}`));
|
|
723
|
+
|
|
724
|
+
const formats = await selectFormat();
|
|
725
|
+
await saveContext(result, formats);
|
|
726
|
+
|
|
727
|
+
console.log(chalk.yellow('š Done!\n'));
|
|
728
|
+
running = false;
|
|
729
|
+
}
|
|
730
|
+
break;
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
case 'config':
|
|
734
|
+
if (hasProjectConfig()) {
|
|
735
|
+
console.log(chalk.cyan('\nš Current configuration:\n'));
|
|
736
|
+
const config = loadProjectConfig();
|
|
737
|
+
console.log(JSON.stringify(config, null, 2));
|
|
738
|
+
console.log(chalk.gray(`\n Edit ${CONFIG_FILE} to modify settings.\n`));
|
|
739
|
+
} else {
|
|
740
|
+
createProjectConfig();
|
|
741
|
+
}
|
|
742
|
+
break;
|
|
743
|
+
|
|
744
|
+
case 'exit':
|
|
745
|
+
running = false;
|
|
746
|
+
console.log(chalk.yellow('\nš Goodbye!\n'));
|
|
747
|
+
break;
|
|
748
|
+
}
|
|
749
|
+
}
|
|
750
|
+
}
|
|
751
|
+
|
|
752
|
+
function showHelp() {
|
|
753
|
+
loadDependencies();
|
|
754
|
+
|
|
755
|
+
console.log(chalk.cyan(`
|
|
756
|
+
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
757
|
+
ā š mkctx - Make Context for AI Code Analysis ā
|
|
758
|
+
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
759
|
+
|
|
760
|
+
${chalk.white('Generate context files from your codebase for AI analysis.')}
|
|
761
|
+
|
|
762
|
+
${chalk.yellow('Usage:')}
|
|
763
|
+
mkctx Interactive mode (recommended)
|
|
764
|
+
mkctx config Create configuration file
|
|
765
|
+
mkctx help Show this help message
|
|
766
|
+
mkctx version Show version
|
|
767
|
+
|
|
768
|
+
${chalk.yellow('Output Formats:')}
|
|
769
|
+
${chalk.green('JSON')} Simple array of file objects (base format)
|
|
770
|
+
${chalk.blue('MD')} Markdown with code blocks
|
|
771
|
+
${chalk.yellow('TOON')} Token-Oriented Object Notation (LLM optimized)
|
|
772
|
+
${chalk.red('XML')} XML with CDATA sections
|
|
773
|
+
|
|
774
|
+
${chalk.yellow('JSON Structure:')}
|
|
775
|
+
[{
|
|
776
|
+
"path": "src/index.ts",
|
|
777
|
+
"name": "index.ts",
|
|
778
|
+
"extension": "ts",
|
|
779
|
+
"language": "typescript",
|
|
780
|
+
"lines": 150,
|
|
781
|
+
"size": 4096,
|
|
782
|
+
"content": "..."
|
|
783
|
+
}]
|
|
784
|
+
|
|
785
|
+
${chalk.gray('More info: https://github.com/pnkkzero/mkctx')}
|
|
786
|
+
`));
|
|
456
787
|
}
|
|
457
788
|
|
|
458
|
-
|
|
789
|
+
function showVersion() {
|
|
790
|
+
try {
|
|
791
|
+
const pkg = require('./package.json');
|
|
792
|
+
console.log(`mkctx v${pkg.version}`);
|
|
793
|
+
} catch {
|
|
794
|
+
console.log('mkctx v4.0.0');
|
|
795
|
+
}
|
|
796
|
+
}
|
|
797
|
+
|
|
798
|
+
// ============================================
|
|
799
|
+
// RUN
|
|
800
|
+
// ============================================
|
|
801
|
+
|
|
459
802
|
main().catch((err) => {
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
803
|
+
console.error(`\nā Error: ${err.message}`);
|
|
804
|
+
if (process.env.DEBUG) {
|
|
805
|
+
console.error(err.stack);
|
|
806
|
+
}
|
|
807
|
+
process.exit(1);
|
|
808
|
+
});
|