mkctx 3.0.0 → 4.0.1
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 +173 -146
- package/bin/mkctx.js +510 -395
- package/package.json +1 -1
package/bin/mkctx.js
CHANGED
|
@@ -1,20 +1,21 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
'use strict';
|
|
4
|
+
|
|
5
|
+
const fs = require('fs');
|
|
4
6
|
const path = require('path');
|
|
5
7
|
|
|
6
8
|
// ============================================
|
|
7
|
-
// LAZY
|
|
9
|
+
// LAZY-LOADED DEPENDENCIES
|
|
8
10
|
// ============================================
|
|
9
11
|
|
|
10
12
|
let inquirer, chalk, ora;
|
|
11
13
|
|
|
12
14
|
function loadDependencies() {
|
|
13
|
-
if (
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
}
|
|
15
|
+
if (inquirer) return;
|
|
16
|
+
inquirer = require('inquirer');
|
|
17
|
+
chalk = require('chalk');
|
|
18
|
+
ora = require('ora');
|
|
18
19
|
}
|
|
19
20
|
|
|
20
21
|
// ============================================
|
|
@@ -23,29 +24,29 @@ function loadDependencies() {
|
|
|
23
24
|
|
|
24
25
|
const CONFIG_FILE = 'mkctx.config.json';
|
|
25
26
|
|
|
26
|
-
const
|
|
27
|
-
src:
|
|
28
|
-
ignore:
|
|
29
|
-
output:
|
|
30
|
-
first_comment:
|
|
31
|
-
last_comment:
|
|
27
|
+
const DEFAULT_CONFIG = {
|
|
28
|
+
src: '.',
|
|
29
|
+
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',
|
|
30
|
+
output: './mkctx',
|
|
31
|
+
first_comment: '/* Project Context */',
|
|
32
|
+
last_comment: '/* End of Context */',
|
|
32
33
|
};
|
|
33
34
|
|
|
34
|
-
|
|
35
|
+
const VALID_FORMATS = ['json', 'md', 'toon', 'xml'];
|
|
36
|
+
|
|
35
37
|
const LANG_MAP = {
|
|
36
|
-
js: 'javascript', ts: 'typescript', jsx: 'jsx',
|
|
37
|
-
py: 'python',
|
|
38
|
-
java: 'java',
|
|
39
|
-
c: 'c',
|
|
40
|
-
sh: 'bash',
|
|
41
|
-
sql: 'sql',
|
|
42
|
-
sass: 'sass',
|
|
43
|
-
yaml: 'yaml',
|
|
38
|
+
js: 'javascript', ts: 'typescript', jsx: 'jsx', tsx: 'tsx',
|
|
39
|
+
py: 'python', rb: 'ruby', go: 'go', rs: 'rust',
|
|
40
|
+
java: 'java', kt: 'kotlin', cs: 'csharp', cpp: 'cpp',
|
|
41
|
+
c: 'c', h: 'c', hpp: 'cpp', php: 'php',
|
|
42
|
+
sh: 'bash', bash: 'bash', zsh: 'bash', ps1: 'powershell',
|
|
43
|
+
sql: 'sql', html: 'html', css: 'css', scss: 'scss',
|
|
44
|
+
sass: 'sass', less: 'less', json: 'json', xml: 'xml',
|
|
45
|
+
yaml: 'yaml', yml: 'yaml', md: 'markdown', vue: 'vue',
|
|
44
46
|
svelte: 'svelte', dockerfile: 'dockerfile', makefile: 'makefile',
|
|
45
|
-
toml: 'toml',
|
|
47
|
+
toml: 'toml', ini: 'ini', cfg: 'ini', env: 'bash',
|
|
46
48
|
};
|
|
47
49
|
|
|
48
|
-
// Text file extensions
|
|
49
50
|
const TEXT_EXTENSIONS = new Set([
|
|
50
51
|
'.js', '.ts', '.jsx', '.tsx', '.mjs', '.cjs',
|
|
51
52
|
'.py', '.pyw', '.rb', '.rake', '.go', '.rs',
|
|
@@ -65,7 +66,7 @@ const TEXT_EXTENSIONS = new Set([
|
|
|
65
66
|
'.ex', '.exs', '.erl', '.hrl', '.clj', '.cljs', '.cljc',
|
|
66
67
|
'.hs', '.lhs', '.elm', '.pug', '.jade',
|
|
67
68
|
'.ejs', '.hbs', '.handlebars', '.twig', '.blade.php',
|
|
68
|
-
'.astro', '.prisma', '.sol'
|
|
69
|
+
'.astro', '.prisma', '.sol',
|
|
69
70
|
]);
|
|
70
71
|
|
|
71
72
|
const KNOWN_FILES = new Set([
|
|
@@ -73,376 +74,517 @@ const KNOWN_FILES = new Set([
|
|
|
73
74
|
'procfile', 'vagrantfile', 'jenkinsfile',
|
|
74
75
|
'.gitignore', '.gitattributes', '.editorconfig',
|
|
75
76
|
'.eslintrc', '.prettierrc', '.babelrc',
|
|
76
|
-
'.env', '.env.example', '.env.local'
|
|
77
|
+
'.env', '.env.example', '.env.local',
|
|
78
|
+
'readme.md', 'readme.txt', 'readme',
|
|
79
|
+
'license', 'license.md', 'license.txt',
|
|
77
80
|
]);
|
|
78
81
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
+
const SYSTEM_IGNORES = [
|
|
83
|
+
'.git', '.DS_Store', 'Thumbs.db', 'node_modules',
|
|
84
|
+
'.svn', '.hg', '__pycache__', '.pytest_cache',
|
|
85
|
+
'.mypy_cache', '.vscode', '.idea',
|
|
86
|
+
];
|
|
82
87
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
88
|
+
// Flags that take a value (not boolean)
|
|
89
|
+
const VALUE_FLAGS = new Set([
|
|
90
|
+
'src', 'output', 'format', 'ignore', 'name', 'first-comment', 'last-comment',
|
|
91
|
+
's', 'o', 'f', 'n',
|
|
92
|
+
]);
|
|
93
|
+
|
|
94
|
+
// Flags that trigger non-interactive mode
|
|
95
|
+
const NON_INTERACTIVE_FLAGS = new Set([
|
|
96
|
+
'src', 's', 'output', 'o', 'format', 'f',
|
|
97
|
+
'ignore', 'name', 'n', 'first-comment', 'last-comment',
|
|
98
|
+
]);
|
|
86
99
|
|
|
87
100
|
// ============================================
|
|
88
|
-
//
|
|
101
|
+
// CLI ARGUMENT PARSING
|
|
89
102
|
// ============================================
|
|
90
103
|
|
|
91
|
-
function
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
104
|
+
function parseArgs(argv) {
|
|
105
|
+
const args = argv.slice(2);
|
|
106
|
+
const flags = {};
|
|
107
|
+
let command = null;
|
|
108
|
+
|
|
109
|
+
if (args.length > 0 && !args[0].startsWith('-')) {
|
|
110
|
+
command = args[0];
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
for (let i = 0; i < args.length; i++) {
|
|
114
|
+
const arg = args[i];
|
|
115
|
+
|
|
116
|
+
if (arg.startsWith('--') && arg.includes('=')) {
|
|
117
|
+
const eq = arg.indexOf('=');
|
|
118
|
+
flags[arg.slice(2, eq)] = arg.slice(eq + 1);
|
|
119
|
+
continue;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
if (arg.startsWith('-') && !arg.startsWith('--') && arg.includes('=')) {
|
|
123
|
+
const eq = arg.indexOf('=');
|
|
124
|
+
flags[arg.slice(1, eq)] = arg.slice(eq + 1);
|
|
125
|
+
continue;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
if (arg.startsWith('--')) {
|
|
129
|
+
const key = arg.slice(2);
|
|
130
|
+
const next = args[i + 1];
|
|
131
|
+
if (VALUE_FLAGS.has(key) && next !== undefined && !isFlagToken(next)) {
|
|
132
|
+
flags[key] = next;
|
|
133
|
+
i++;
|
|
134
|
+
} else {
|
|
135
|
+
flags[key] = true;
|
|
136
|
+
}
|
|
137
|
+
continue;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
if (arg.startsWith('-') && arg.length === 2) {
|
|
141
|
+
const key = arg.slice(1);
|
|
142
|
+
const next = args[i + 1];
|
|
143
|
+
if (VALUE_FLAGS.has(key) && next !== undefined && !isFlagToken(next)) {
|
|
144
|
+
flags[key] = next;
|
|
145
|
+
i++;
|
|
146
|
+
} else {
|
|
147
|
+
flags[key] = true;
|
|
148
|
+
}
|
|
99
149
|
}
|
|
100
150
|
}
|
|
101
|
-
|
|
151
|
+
|
|
152
|
+
return { command, flags };
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function isFlagToken(str) {
|
|
156
|
+
return /^--[a-zA-Z]/.test(str) || /^-[a-zA-Z]/.test(str);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function isNonInteractiveMode(flags) {
|
|
160
|
+
return Object.keys(flags).some(f => NON_INTERACTIVE_FLAGS.has(f));
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// Resolve short aliases to canonical names
|
|
164
|
+
function resolveFlags(flags) {
|
|
165
|
+
return {
|
|
166
|
+
src: flags.src || flags.s,
|
|
167
|
+
output: flags.output || flags.o,
|
|
168
|
+
format: flags.format || flags.f,
|
|
169
|
+
name: flags.name || flags.n,
|
|
170
|
+
ignore: flags.ignore,
|
|
171
|
+
firstComment: flags['first-comment'],
|
|
172
|
+
lastComment: flags['last-comment'],
|
|
173
|
+
};
|
|
102
174
|
}
|
|
103
175
|
|
|
104
|
-
|
|
176
|
+
// ============================================
|
|
177
|
+
// CONFIG MANAGEMENT
|
|
178
|
+
// ============================================
|
|
179
|
+
|
|
180
|
+
function configFileExists() {
|
|
105
181
|
return fs.existsSync(CONFIG_FILE);
|
|
106
182
|
}
|
|
107
183
|
|
|
108
|
-
function
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
184
|
+
function loadConfig() {
|
|
185
|
+
if (!configFileExists()) return null;
|
|
186
|
+
try {
|
|
187
|
+
const raw = JSON.parse(fs.readFileSync(CONFIG_FILE, 'utf-8'));
|
|
188
|
+
return { ...DEFAULT_CONFIG, ...raw };
|
|
189
|
+
} catch {
|
|
190
|
+
console.log(chalk.yellow('⚠️ Error parsing config file, using defaults'));
|
|
191
|
+
return { ...DEFAULT_CONFIG };
|
|
113
192
|
}
|
|
193
|
+
}
|
|
114
194
|
|
|
115
|
-
|
|
116
|
-
|
|
195
|
+
function buildConfig(cliFlags = {}) {
|
|
196
|
+
const base = configFileExists() ? loadConfig() : { ...DEFAULT_CONFIG };
|
|
197
|
+
if (cliFlags.src) base.src = cliFlags.src;
|
|
198
|
+
if (cliFlags.output) base.output = cliFlags.output;
|
|
199
|
+
if (cliFlags.ignore) base.ignore = cliFlags.ignore;
|
|
200
|
+
if (cliFlags.firstComment) base.first_comment = cliFlags.firstComment;
|
|
201
|
+
if (cliFlags.lastComment) base.last_comment = cliFlags.lastComment;
|
|
202
|
+
return base;
|
|
203
|
+
}
|
|
117
204
|
|
|
205
|
+
function createConfigFile() {
|
|
206
|
+
loadDependencies();
|
|
207
|
+
fs.mkdirSync('mkctx', { recursive: true });
|
|
208
|
+
fs.writeFileSync(CONFIG_FILE, JSON.stringify(DEFAULT_CONFIG, null, 2));
|
|
209
|
+
appendToGitignore();
|
|
118
210
|
console.log(chalk.green('\n✅ Configuration created:'));
|
|
119
211
|
console.log(chalk.white(' - mkctx.config.json'));
|
|
120
212
|
console.log(chalk.white(' - mkctx/ folder'));
|
|
121
213
|
console.log(chalk.white(' - Entry in .gitignore\n'));
|
|
122
214
|
}
|
|
123
215
|
|
|
124
|
-
function
|
|
216
|
+
function appendToGitignore() {
|
|
125
217
|
const gitignorePath = '.gitignore';
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
218
|
+
const current = fs.existsSync(gitignorePath)
|
|
219
|
+
? fs.readFileSync(gitignorePath, 'utf-8')
|
|
220
|
+
: '';
|
|
221
|
+
if (!current.includes('mkctx/')) {
|
|
222
|
+
fs.writeFileSync(gitignorePath, current + '\n# mkctx - generated context\nmkctx/\n');
|
|
130
223
|
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// ============================================
|
|
227
|
+
// FORMAT RESOLUTION
|
|
228
|
+
// ============================================
|
|
229
|
+
|
|
230
|
+
function resolveFormats(formatArg) {
|
|
231
|
+
if (!formatArg) return ['md'];
|
|
232
|
+
if (formatArg === 'all') return [...VALID_FORMATS];
|
|
233
|
+
|
|
234
|
+
const requested = formatArg.split(',').map(f => f.trim().toLowerCase());
|
|
235
|
+
const invalid = requested.filter(f => !VALID_FORMATS.includes(f));
|
|
131
236
|
|
|
132
|
-
if (
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
fs.writeFileSync(gitignorePath, content);
|
|
237
|
+
if (invalid.length > 0) {
|
|
238
|
+
console.error(`❌ Invalid format(s): ${invalid.join(', ')}. Valid: ${VALID_FORMATS.join(', ')}, all`);
|
|
239
|
+
process.exit(1);
|
|
136
240
|
}
|
|
241
|
+
|
|
242
|
+
return requested;
|
|
137
243
|
}
|
|
138
244
|
|
|
139
245
|
// ============================================
|
|
140
|
-
// UTILITY
|
|
246
|
+
// UTILITY HELPERS
|
|
141
247
|
// ============================================
|
|
142
248
|
|
|
143
249
|
function formatSize(bytes) {
|
|
144
|
-
const
|
|
250
|
+
const units = ['B', 'KB', 'MB', 'GB'];
|
|
145
251
|
if (bytes === 0) return '0 B';
|
|
146
252
|
const i = Math.floor(Math.log(bytes) / Math.log(1024));
|
|
147
|
-
return `${(bytes / Math.pow(1024, i)).toFixed(2)} ${
|
|
253
|
+
return `${(bytes / Math.pow(1024, i)).toFixed(2)} ${units[i]}`;
|
|
148
254
|
}
|
|
149
255
|
|
|
150
256
|
function estimateTokens(text) {
|
|
151
257
|
return Math.ceil(text.length / 4);
|
|
152
258
|
}
|
|
153
259
|
|
|
260
|
+
function toUnixPath(filePath) {
|
|
261
|
+
return filePath.replace(/\\/g, '/');
|
|
262
|
+
}
|
|
263
|
+
|
|
154
264
|
// ============================================
|
|
155
|
-
// FILE
|
|
265
|
+
// FILE FILTERING
|
|
156
266
|
// ============================================
|
|
157
267
|
|
|
158
268
|
function isTextFile(filename) {
|
|
159
|
-
const ext
|
|
269
|
+
const ext = path.extname(filename).toLowerCase();
|
|
160
270
|
const basename = path.basename(filename).toLowerCase();
|
|
161
|
-
|
|
162
|
-
if (KNOWN_FILES.has(basename)) return true;
|
|
163
|
-
if (ext && TEXT_EXTENSIONS.has(ext)) return true;
|
|
164
|
-
|
|
165
|
-
return false;
|
|
271
|
+
return KNOWN_FILES.has(basename) || (!!ext && TEXT_EXTENSIONS.has(ext));
|
|
166
272
|
}
|
|
167
273
|
|
|
168
274
|
function getLanguage(filename) {
|
|
169
|
-
const ext
|
|
275
|
+
const ext = path.extname(filename).slice(1).toLowerCase();
|
|
170
276
|
const basename = path.basename(filename).toLowerCase();
|
|
171
|
-
|
|
172
|
-
if (basename === '
|
|
173
|
-
if (basename
|
|
174
|
-
if (basename.startsWith('.env')) return 'bash';
|
|
175
|
-
|
|
277
|
+
if (basename === 'dockerfile') return 'dockerfile';
|
|
278
|
+
if (basename === 'makefile') return 'makefile';
|
|
279
|
+
if (basename.startsWith('.env')) return 'bash';
|
|
176
280
|
return LANG_MAP[ext] || ext || 'text';
|
|
177
281
|
}
|
|
178
282
|
|
|
179
283
|
function parseIgnorePatterns(ignoreString) {
|
|
180
284
|
if (!ignoreString) return [];
|
|
181
|
-
return ignoreString
|
|
182
|
-
.split(',')
|
|
183
|
-
.map(p => p.trim())
|
|
184
|
-
.filter(Boolean);
|
|
285
|
+
return ignoreString.split(',').map(p => p.trim()).filter(Boolean);
|
|
185
286
|
}
|
|
186
287
|
|
|
187
|
-
function matchWildcard(pattern,
|
|
188
|
-
const
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
288
|
+
function matchWildcard(pattern, subject) {
|
|
289
|
+
const regex = new RegExp(
|
|
290
|
+
'^' + pattern
|
|
291
|
+
.replace(/\./g, '\\.')
|
|
292
|
+
.replace(/\*\*/g, '.*')
|
|
293
|
+
.replace(/\*/g, '[^/]*') + '$',
|
|
294
|
+
'i'
|
|
295
|
+
);
|
|
296
|
+
return regex.test(subject);
|
|
194
297
|
}
|
|
195
298
|
|
|
196
299
|
function shouldIgnore(fullPath, name, relativePath, patterns) {
|
|
197
|
-
const
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
return true;
|
|
208
|
-
}
|
|
209
|
-
}
|
|
300
|
+
const normFull = toUnixPath(fullPath);
|
|
301
|
+
const normRelative = toUnixPath(relativePath);
|
|
302
|
+
|
|
303
|
+
const inSystemIgnore = SYSTEM_IGNORES.some(ig =>
|
|
304
|
+
normFull.includes(`/${ig}/`) ||
|
|
305
|
+
normFull.includes(`/${ig}`) ||
|
|
306
|
+
normFull.endsWith(`/${ig}`) ||
|
|
307
|
+
name === ig
|
|
308
|
+
);
|
|
309
|
+
if (inSystemIgnore) return true;
|
|
210
310
|
|
|
211
|
-
|
|
311
|
+
return patterns.some(pattern => {
|
|
212
312
|
if (pattern.includes('*')) {
|
|
213
|
-
|
|
214
|
-
if (matchWildcard(pattern, relativePath)) return true;
|
|
313
|
+
return matchWildcard(pattern, name) || matchWildcard(pattern, normRelative);
|
|
215
314
|
}
|
|
216
|
-
|
|
217
315
|
if (pattern.endsWith('/')) {
|
|
218
316
|
const dir = pattern.slice(0, -1);
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
return true;
|
|
223
|
-
}
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
if (relativePath === pattern || name === pattern) {
|
|
227
|
-
return true;
|
|
317
|
+
return normFull.includes(`/${dir}/`) ||
|
|
318
|
+
normFull.endsWith(`/${dir}`) ||
|
|
319
|
+
name === dir;
|
|
228
320
|
}
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
return false;
|
|
321
|
+
return normRelative === pattern || name === pattern;
|
|
322
|
+
});
|
|
232
323
|
}
|
|
233
324
|
|
|
234
|
-
|
|
235
|
-
|
|
325
|
+
// ============================================
|
|
326
|
+
// FILE SCANNING
|
|
327
|
+
// ============================================
|
|
328
|
+
|
|
329
|
+
function scanFiles(srcPath, config) {
|
|
236
330
|
const ignorePatterns = parseIgnorePatterns(config.ignore);
|
|
331
|
+
const files = [];
|
|
332
|
+
const stats = { files: 0, totalSize: 0, totalLines: 0, filesByExt: {} };
|
|
237
333
|
|
|
238
334
|
function walk(dir) {
|
|
239
335
|
if (!fs.existsSync(dir)) return;
|
|
240
336
|
|
|
241
337
|
let entries;
|
|
242
|
-
try {
|
|
243
|
-
|
|
244
|
-
} catch (err) {
|
|
245
|
-
return;
|
|
246
|
-
}
|
|
338
|
+
try { entries = fs.readdirSync(dir, { withFileTypes: true }); }
|
|
339
|
+
catch { return; }
|
|
247
340
|
|
|
248
341
|
for (const entry of entries) {
|
|
249
|
-
const fullPath
|
|
342
|
+
const fullPath = path.join(dir, entry.name);
|
|
250
343
|
const relativePath = path.relative(srcPath, fullPath);
|
|
251
344
|
|
|
252
|
-
if (shouldIgnore(fullPath, entry.name, relativePath, ignorePatterns))
|
|
253
|
-
continue;
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
if (entry.isDirectory()) {
|
|
257
|
-
walk(fullPath);
|
|
258
|
-
} else if (entry.isFile() && isTextFile(entry.name)) {
|
|
259
|
-
files.push({
|
|
260
|
-
fullPath,
|
|
261
|
-
relativePath,
|
|
262
|
-
name: entry.name,
|
|
263
|
-
ext: path.extname(entry.name).slice(1).toLowerCase()
|
|
264
|
-
});
|
|
265
|
-
}
|
|
266
|
-
}
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
walk(srcPath);
|
|
270
|
-
return files.sort((a, b) => a.relativePath.localeCompare(b.relativePath));
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
function buildContextContent(files, config, srcPath) {
|
|
274
|
-
let content = '';
|
|
275
|
-
let totalSize = 0;
|
|
276
|
-
let totalLines = 0;
|
|
277
|
-
const filesByExt = {};
|
|
278
|
-
|
|
279
|
-
if (config.first_comment) {
|
|
280
|
-
content += config.first_comment + '\n\n';
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
content += '## Project Structure\n\n```\n';
|
|
284
|
-
const dirs = new Set();
|
|
285
|
-
files.forEach(f => {
|
|
286
|
-
const dir = path.dirname(f.relativePath);
|
|
287
|
-
if (dir !== '.') dirs.add(dir);
|
|
288
|
-
});
|
|
289
|
-
dirs.forEach(d => content += `📁 ${d}/\n`);
|
|
290
|
-
content += `\n${files.length} files total\n\`\`\`\n\n`;
|
|
291
|
-
|
|
292
|
-
content += '## Source Files\n\n';
|
|
345
|
+
if (shouldIgnore(fullPath, entry.name, relativePath, ignorePatterns)) continue;
|
|
293
346
|
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
try {
|
|
297
|
-
fileContent = fs.readFileSync(file.fullPath, 'utf-8');
|
|
298
|
-
} catch (err) {
|
|
299
|
-
console.log(chalk.yellow(`⚠️ Could not read: ${file.relativePath}`));
|
|
300
|
-
continue;
|
|
301
|
-
}
|
|
347
|
+
if (entry.isDirectory()) { walk(fullPath); continue; }
|
|
348
|
+
if (!entry.isFile() || !isTextFile(entry.name)) continue;
|
|
302
349
|
|
|
303
|
-
|
|
304
|
-
|
|
350
|
+
let content;
|
|
351
|
+
try { content = fs.readFileSync(fullPath, 'utf-8'); }
|
|
352
|
+
catch { continue; }
|
|
305
353
|
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
354
|
+
const ext = path.extname(entry.name).slice(1).toLowerCase() || null;
|
|
355
|
+
const lines = content.split('\n').length;
|
|
356
|
+
const size = Buffer.byteLength(content, 'utf-8');
|
|
357
|
+
const language = getLanguage(entry.name);
|
|
309
358
|
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
359
|
+
stats.totalSize += size;
|
|
360
|
+
stats.totalLines += lines;
|
|
361
|
+
stats.filesByExt[ext || 'other'] = (stats.filesByExt[ext || 'other'] || 0) + 1;
|
|
313
362
|
|
|
314
|
-
|
|
315
|
-
content += '\n';
|
|
363
|
+
files.push({ path: toUnixPath(relativePath), name: entry.name, extension: ext, language, lines, size, content });
|
|
316
364
|
}
|
|
317
|
-
|
|
318
|
-
content += '```\n\n';
|
|
319
365
|
}
|
|
320
366
|
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
contextStats = {
|
|
326
|
-
files: files.length,
|
|
327
|
-
totalSize,
|
|
328
|
-
totalLines,
|
|
329
|
-
filesByExt,
|
|
330
|
-
estimatedTokens: estimateTokens(content)
|
|
331
|
-
};
|
|
367
|
+
walk(srcPath);
|
|
368
|
+
files.sort((a, b) => a.path.localeCompare(b.path));
|
|
369
|
+
stats.files = files.length;
|
|
332
370
|
|
|
333
|
-
return
|
|
371
|
+
return { files, stats };
|
|
334
372
|
}
|
|
335
373
|
|
|
336
374
|
// ============================================
|
|
337
|
-
//
|
|
375
|
+
// OUTPUT FORMATTERS
|
|
338
376
|
// ============================================
|
|
339
377
|
|
|
340
|
-
|
|
341
|
-
|
|
378
|
+
function toJson(files) {
|
|
379
|
+
return JSON.stringify(files, null, 2);
|
|
380
|
+
}
|
|
342
381
|
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
382
|
+
function toMarkdown(files, config) {
|
|
383
|
+
const dirs = [...new Set(
|
|
384
|
+
files.map(f => path.dirname(f.path)).filter(d => d !== '.')
|
|
385
|
+
)].sort();
|
|
386
|
+
|
|
387
|
+
const structure = dirs.map(d => `📁 ${d}/`).join('\n');
|
|
388
|
+
|
|
389
|
+
const sources = files.map(file => {
|
|
390
|
+
const body = file.content.endsWith('\n') ? file.content : file.content + '\n';
|
|
391
|
+
return `### ${file.path}\n\n\`\`\`${file.language}\n${body}\`\`\`\n`;
|
|
392
|
+
}).join('\n');
|
|
393
|
+
|
|
394
|
+
return [
|
|
395
|
+
config.first_comment,
|
|
396
|
+
'',
|
|
397
|
+
'## Project Structure',
|
|
398
|
+
'',
|
|
399
|
+
'```',
|
|
400
|
+
structure,
|
|
401
|
+
'',
|
|
402
|
+
`${files.length} files total`,
|
|
403
|
+
'```',
|
|
404
|
+
'',
|
|
405
|
+
'## Source Files',
|
|
406
|
+
'',
|
|
407
|
+
sources,
|
|
408
|
+
config.last_comment,
|
|
409
|
+
].join('\n');
|
|
410
|
+
}
|
|
357
411
|
|
|
358
|
-
|
|
359
|
-
|
|
412
|
+
function toToon(files, stats) {
|
|
413
|
+
const header = [
|
|
414
|
+
'# Project Context',
|
|
415
|
+
`# Generated: ${new Date().toISOString()}`,
|
|
416
|
+
`# Files: ${files.length}`,
|
|
417
|
+
`# Lines: ${stats.totalLines}`,
|
|
418
|
+
`# Size: ${stats.totalSize} bytes`,
|
|
419
|
+
'',
|
|
420
|
+
`files[${files.length}]{path,name,extension,language,lines,size}:`,
|
|
421
|
+
...files.map(f =>
|
|
422
|
+
` ${escapeToon(f.path)},${escapeToon(f.name)},${f.extension || ''},${f.language},${f.lines},${f.size}`
|
|
423
|
+
),
|
|
424
|
+
'',
|
|
425
|
+
].join('\n');
|
|
426
|
+
|
|
427
|
+
const bodies = files.map((f, i) => [
|
|
428
|
+
'---',
|
|
429
|
+
`[${i}] ${f.path}`,
|
|
430
|
+
`language: ${f.language}`,
|
|
431
|
+
'content:',
|
|
432
|
+
...f.content.split('\n').map(l => ` ${l}`),
|
|
433
|
+
].join('\n')).join('\n');
|
|
434
|
+
|
|
435
|
+
return header + bodies;
|
|
360
436
|
}
|
|
361
437
|
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
const
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
438
|
+
function escapeToon(value) {
|
|
439
|
+
if (value == null) return '';
|
|
440
|
+
const str = String(value);
|
|
441
|
+
const needsQuotes = str.includes(',') || str.includes('\n') || str.includes('"')
|
|
442
|
+
|| str.startsWith(' ') || str.endsWith(' ');
|
|
443
|
+
return needsQuotes
|
|
444
|
+
? `"${str.replace(/"/g, '""').replace(/\n/g, '\\n')}"`
|
|
445
|
+
: str;
|
|
446
|
+
}
|
|
370
447
|
|
|
371
|
-
|
|
448
|
+
function toXml(files) {
|
|
449
|
+
const fileEntries = files.map(f => [
|
|
450
|
+
' <file>',
|
|
451
|
+
` <path>${escapeXml(f.path)}</path>`,
|
|
452
|
+
` <name>${escapeXml(f.name)}</name>`,
|
|
453
|
+
` <extension>${escapeXml(f.extension || '')}</extension>`,
|
|
454
|
+
` <language>${escapeXml(f.language)}</language>`,
|
|
455
|
+
` <lines>${f.lines}</lines>`,
|
|
456
|
+
` <size>${f.size}</size>`,
|
|
457
|
+
` <content><![CDATA[\n${f.content}${f.content.endsWith('\n') ? '' : '\n'}]]></content>`,
|
|
458
|
+
' </file>',
|
|
459
|
+
].join('\n')).join('\n');
|
|
460
|
+
|
|
461
|
+
return `<?xml version="1.0" encoding="UTF-8"?>\n<context>\n${fileEntries}\n</context>\n`;
|
|
372
462
|
}
|
|
373
463
|
|
|
374
|
-
function
|
|
375
|
-
|
|
464
|
+
function escapeXml(str) {
|
|
465
|
+
if (str == null) return '';
|
|
466
|
+
return String(str)
|
|
467
|
+
.replace(/&/g, '&')
|
|
468
|
+
.replace(/</g, '<')
|
|
469
|
+
.replace(/>/g, '>')
|
|
470
|
+
.replace(/"/g, '"')
|
|
471
|
+
.replace(/'/g, ''');
|
|
472
|
+
}
|
|
376
473
|
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
return
|
|
474
|
+
function renderFormat(format, files, stats, config) {
|
|
475
|
+
switch (format) {
|
|
476
|
+
case 'json': return { content: toJson(files), ext: 'json' };
|
|
477
|
+
case 'md': return { content: toMarkdown(files, config), ext: 'md' };
|
|
478
|
+
case 'toon': return { content: toToon(files, stats), ext: 'toon' };
|
|
479
|
+
case 'xml': return { content: toXml(files), ext: 'xml' };
|
|
380
480
|
}
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
// ============================================
|
|
484
|
+
// CONTEXT GENERATION
|
|
485
|
+
// ============================================
|
|
381
486
|
|
|
382
|
-
|
|
487
|
+
function generateContext(config) {
|
|
488
|
+
const spinner = ora(`Scanning ${config.src}...`).start();
|
|
383
489
|
|
|
384
|
-
if (
|
|
385
|
-
spinner.fail(`
|
|
490
|
+
if (!fs.existsSync(config.src)) {
|
|
491
|
+
spinner.fail(`Source path does not exist: ${config.src}`);
|
|
386
492
|
return null;
|
|
387
493
|
}
|
|
388
494
|
|
|
389
|
-
|
|
390
|
-
generatedContext = buildContextContent(contextFiles, config, srcPath);
|
|
495
|
+
const { files, stats } = scanFiles(config.src, config);
|
|
391
496
|
|
|
392
|
-
|
|
497
|
+
if (files.length === 0) {
|
|
498
|
+
spinner.fail(`No files found in: ${config.src}`);
|
|
499
|
+
return null;
|
|
500
|
+
}
|
|
393
501
|
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
502
|
+
spinner.succeed(`Context built: ${chalk.yellow(files.length)} files, ${chalk.yellow(formatSize(stats.totalSize))}`);
|
|
503
|
+
return { files, stats, config };
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
function printSummary(stats) {
|
|
507
|
+
console.log(chalk.cyan('\n📊 Context Summary:'));
|
|
508
|
+
console.log(chalk.white(` Files: ${stats.files}`));
|
|
509
|
+
console.log(chalk.white(` Lines: ${stats.totalLines.toLocaleString()}`));
|
|
510
|
+
console.log(chalk.white(` Size: ${formatSize(stats.totalSize)}`));
|
|
400
511
|
}
|
|
401
512
|
|
|
402
513
|
// ============================================
|
|
403
514
|
// SAVE CONTEXT
|
|
404
515
|
// ============================================
|
|
405
516
|
|
|
406
|
-
async function saveContext(result) {
|
|
517
|
+
async function saveContext(result, formats, fileName) {
|
|
407
518
|
loadDependencies();
|
|
408
519
|
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
type: 'input',
|
|
417
|
-
name: 'savePath',
|
|
418
|
-
message: 'Enter output directory:',
|
|
419
|
-
default: './mkctx'
|
|
420
|
-
}
|
|
421
|
-
]);
|
|
422
|
-
outputPath = savePath;
|
|
520
|
+
if (!fileName) {
|
|
521
|
+
const answer = await inquirer.prompt([{
|
|
522
|
+
type: 'input', name: 'fileName',
|
|
523
|
+
message: 'Enter a name for the output files:',
|
|
524
|
+
default: 'context',
|
|
525
|
+
}]);
|
|
526
|
+
fileName = answer.fileName;
|
|
423
527
|
}
|
|
424
528
|
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
}
|
|
529
|
+
const outputDir = result.config.output || './mkctx';
|
|
530
|
+
fs.mkdirSync(outputDir, { recursive: true });
|
|
428
531
|
|
|
429
|
-
const
|
|
430
|
-
fs.writeFileSync(outputFile, result.content);
|
|
532
|
+
const savedFiles = [];
|
|
431
533
|
|
|
432
|
-
|
|
433
|
-
|
|
534
|
+
for (const format of formats) {
|
|
535
|
+
const { content, ext } = renderFormat(format, result.files, result.stats, result.config);
|
|
536
|
+
const outputPath = path.join(outputDir, `${fileName}.${ext}`);
|
|
537
|
+
fs.writeFileSync(outputPath, content);
|
|
538
|
+
savedFiles.push({
|
|
539
|
+
format,
|
|
540
|
+
file: outputPath,
|
|
541
|
+
size: Buffer.byteLength(content, 'utf-8'),
|
|
542
|
+
tokens: estimateTokens(content),
|
|
543
|
+
});
|
|
544
|
+
}
|
|
434
545
|
|
|
435
|
-
|
|
546
|
+
console.log(chalk.green('\n✅ Context saved:\n'));
|
|
547
|
+
for (const { format, file, size, tokens } of savedFiles) {
|
|
548
|
+
console.log(chalk.white(` ${chalk.cyan(format.toUpperCase().padEnd(4))} → ${chalk.yellow(file)}`));
|
|
549
|
+
console.log(chalk.gray(` ${formatSize(size)} | ~${tokens.toLocaleString()} tokens\n`));
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
return savedFiles;
|
|
436
553
|
}
|
|
437
554
|
|
|
438
555
|
// ============================================
|
|
439
|
-
//
|
|
556
|
+
// INTERACTIVE PROMPTS
|
|
440
557
|
// ============================================
|
|
441
558
|
|
|
442
|
-
async function
|
|
443
|
-
|
|
559
|
+
async function promptSrcPath() {
|
|
560
|
+
const { srcPath } = await inquirer.prompt([{
|
|
561
|
+
type: 'input', name: 'srcPath',
|
|
562
|
+
message: 'Enter the source path to analyze:',
|
|
563
|
+
default: '.',
|
|
564
|
+
validate: input => fs.existsSync(input) || `Path does not exist: ${input}`,
|
|
565
|
+
}]);
|
|
566
|
+
return srcPath;
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
async function promptFormat() {
|
|
570
|
+
const { format } = await inquirer.prompt([{
|
|
571
|
+
type: 'list', name: 'format',
|
|
572
|
+
message: 'Select output format:',
|
|
573
|
+
default: 'all',
|
|
574
|
+
choices: [
|
|
575
|
+
{ name: chalk.magenta('📦 All formats (MD, JSON, TOON, XML)'), value: 'all' },
|
|
576
|
+
new inquirer.Separator(),
|
|
577
|
+
{ name: chalk.blue('📝 Markdown (.md)'), value: 'md' },
|
|
578
|
+
{ name: chalk.green('🔧 JSON (.json) - Simple array'), value: 'json' },
|
|
579
|
+
{ name: chalk.yellow('🎒 TOON (.toon) - Token-optimized'), value: 'toon' },
|
|
580
|
+
{ name: chalk.red('📄 XML (.xml)'), value: 'xml' },
|
|
581
|
+
],
|
|
582
|
+
}]);
|
|
583
|
+
return resolveFormats(format);
|
|
584
|
+
}
|
|
444
585
|
|
|
445
|
-
|
|
586
|
+
async function promptMainMenu() {
|
|
587
|
+
const hasConfig = configFileExists();
|
|
446
588
|
|
|
447
589
|
console.log(chalk.cyan('\n╔════════════════════════════════════════╗'));
|
|
448
590
|
console.log(chalk.cyan('║') + chalk.cyan.bold(' 📄 mkctx - Make Context ') + chalk.cyan('║'));
|
|
@@ -451,155 +593,96 @@ async function showMainMenu() {
|
|
|
451
593
|
const choices = [];
|
|
452
594
|
|
|
453
595
|
if (hasConfig) {
|
|
454
|
-
choices.push({
|
|
455
|
-
name: chalk.green('📁 Generate from config file'),
|
|
456
|
-
value: 'from-config'
|
|
457
|
-
});
|
|
596
|
+
choices.push({ name: chalk.green('📁 Generate from config file'), value: 'from-config' });
|
|
458
597
|
}
|
|
459
598
|
|
|
460
599
|
choices.push(
|
|
461
|
-
{
|
|
462
|
-
name: chalk.blue('🔍 Generate dynamically (choose path)'),
|
|
463
|
-
value: 'dynamic'
|
|
464
|
-
},
|
|
600
|
+
{ name: chalk.blue('🔍 Generate dynamically (choose path)'), value: 'dynamic' },
|
|
465
601
|
new inquirer.Separator(),
|
|
466
602
|
{
|
|
467
|
-
name:
|
|
468
|
-
|
|
469
|
-
: chalk.yellow('⚙️ Create configuration file'),
|
|
470
|
-
value: 'config'
|
|
603
|
+
name: hasConfig ? chalk.gray('⚙️ View configuration') : chalk.yellow('⚙️ Create configuration file'),
|
|
604
|
+
value: 'config',
|
|
471
605
|
},
|
|
472
606
|
new inquirer.Separator(),
|
|
473
|
-
{
|
|
474
|
-
name: chalk.red('❌ Exit'),
|
|
475
|
-
value: 'exit'
|
|
476
|
-
}
|
|
607
|
+
{ name: chalk.red('❌ Exit'), value: 'exit' },
|
|
477
608
|
);
|
|
478
609
|
|
|
479
|
-
const { action } = await inquirer.prompt([
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
choices
|
|
485
|
-
}
|
|
486
|
-
]);
|
|
487
|
-
|
|
488
|
-
return action;
|
|
489
|
-
}
|
|
490
|
-
|
|
491
|
-
async function showPostGenerationMenu(result) {
|
|
492
|
-
loadDependencies();
|
|
493
|
-
|
|
494
|
-
console.log(chalk.cyan('\n📊 Context Summary:'));
|
|
495
|
-
console.log(chalk.white(` Files: ${result.stats.files}`));
|
|
496
|
-
console.log(chalk.white(` Lines: ${result.stats.totalLines.toLocaleString()}`));
|
|
497
|
-
console.log(chalk.white(` Size: ${formatSize(result.stats.totalSize)}`));
|
|
498
|
-
console.log(chalk.white(` Est. tokens: ~${result.stats.estimatedTokens.toLocaleString()}`));
|
|
499
|
-
|
|
500
|
-
const { action } = await inquirer.prompt([
|
|
501
|
-
{
|
|
502
|
-
type: 'list',
|
|
503
|
-
name: 'action',
|
|
504
|
-
message: 'What would you like to do with this context?',
|
|
505
|
-
choices: [
|
|
506
|
-
{
|
|
507
|
-
name: chalk.blue('💾 Save context to file'),
|
|
508
|
-
value: 'save'
|
|
509
|
-
},
|
|
510
|
-
new inquirer.Separator(),
|
|
511
|
-
{
|
|
512
|
-
name: chalk.gray('🔙 Back to main menu'),
|
|
513
|
-
value: 'back'
|
|
514
|
-
},
|
|
515
|
-
{
|
|
516
|
-
name: chalk.red('❌ Exit'),
|
|
517
|
-
value: 'exit'
|
|
518
|
-
}
|
|
519
|
-
]
|
|
520
|
-
}
|
|
521
|
-
]);
|
|
610
|
+
const { action } = await inquirer.prompt([{
|
|
611
|
+
type: 'list', name: 'action',
|
|
612
|
+
message: 'What would you like to do?',
|
|
613
|
+
choices,
|
|
614
|
+
}]);
|
|
522
615
|
|
|
523
616
|
return action;
|
|
524
617
|
}
|
|
525
618
|
|
|
526
619
|
// ============================================
|
|
527
|
-
//
|
|
620
|
+
// EXECUTION FLOWS
|
|
528
621
|
// ============================================
|
|
529
622
|
|
|
530
|
-
async function
|
|
531
|
-
|
|
623
|
+
async function runNonInteractive(rawFlags) {
|
|
624
|
+
loadDependencies();
|
|
532
625
|
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
626
|
+
const cli = resolveFlags(rawFlags);
|
|
627
|
+
const config = buildConfig(cli);
|
|
628
|
+
const formats = resolveFormats(cli.format);
|
|
629
|
+
const fileName = cli.name || 'context';
|
|
537
630
|
|
|
538
|
-
if (
|
|
539
|
-
|
|
540
|
-
|
|
631
|
+
if (!fs.existsSync(config.src)) {
|
|
632
|
+
console.error(`❌ Source path does not exist: ${config.src}`);
|
|
633
|
+
process.exit(1);
|
|
541
634
|
}
|
|
542
635
|
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
636
|
+
const result = generateContext(config);
|
|
637
|
+
if (!result) process.exit(1);
|
|
638
|
+
|
|
639
|
+
printSummary(result.stats);
|
|
640
|
+
await saveContext(result, formats, fileName);
|
|
641
|
+
console.log(chalk.yellow('👋 Done!\n'));
|
|
642
|
+
}
|
|
548
643
|
|
|
644
|
+
async function runInteractive() {
|
|
549
645
|
loadDependencies();
|
|
550
646
|
|
|
551
647
|
let running = true;
|
|
552
648
|
|
|
553
649
|
while (running) {
|
|
554
|
-
const action = await
|
|
650
|
+
const action = await promptMainMenu();
|
|
555
651
|
|
|
556
652
|
switch (action) {
|
|
557
653
|
case 'from-config': {
|
|
558
|
-
const
|
|
559
|
-
if (
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
}
|
|
568
|
-
|
|
569
|
-
if (postAction === 'exit') {
|
|
570
|
-
running = false;
|
|
571
|
-
}
|
|
572
|
-
}
|
|
654
|
+
const config = loadConfig();
|
|
655
|
+
if (!config) { console.log(chalk.yellow('\n⚠️ No config file found.')); break; }
|
|
656
|
+
const result = generateContext(config);
|
|
657
|
+
if (!result) break;
|
|
658
|
+
printSummary(result.stats);
|
|
659
|
+
const formats = await promptFormat();
|
|
660
|
+
await saveContext(result, formats);
|
|
661
|
+
console.log(chalk.yellow('👋 Done!\n'));
|
|
662
|
+
running = false;
|
|
573
663
|
break;
|
|
574
664
|
}
|
|
575
665
|
|
|
576
666
|
case 'dynamic': {
|
|
577
|
-
const
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
}
|
|
587
|
-
|
|
588
|
-
if (postAction === 'exit') {
|
|
589
|
-
running = false;
|
|
590
|
-
}
|
|
591
|
-
}
|
|
667
|
+
const srcPath = await promptSrcPath();
|
|
668
|
+
const config = { ...DEFAULT_CONFIG, src: srcPath };
|
|
669
|
+
const result = generateContext(config);
|
|
670
|
+
if (!result) break;
|
|
671
|
+
printSummary(result.stats);
|
|
672
|
+
const formats = await promptFormat();
|
|
673
|
+
await saveContext(result, formats);
|
|
674
|
+
console.log(chalk.yellow('👋 Done!\n'));
|
|
675
|
+
running = false;
|
|
592
676
|
break;
|
|
593
677
|
}
|
|
594
678
|
|
|
595
679
|
case 'config':
|
|
596
|
-
if (
|
|
680
|
+
if (configFileExists()) {
|
|
597
681
|
console.log(chalk.cyan('\n📄 Current configuration:\n'));
|
|
598
|
-
|
|
599
|
-
console.log(JSON.stringify(config, null, 2));
|
|
682
|
+
console.log(JSON.stringify(loadConfig(), null, 2));
|
|
600
683
|
console.log(chalk.gray(`\n Edit ${CONFIG_FILE} to modify settings.\n`));
|
|
601
684
|
} else {
|
|
602
|
-
|
|
685
|
+
createConfigFile();
|
|
603
686
|
}
|
|
604
687
|
break;
|
|
605
688
|
|
|
@@ -611,9 +694,12 @@ async function main() {
|
|
|
611
694
|
}
|
|
612
695
|
}
|
|
613
696
|
|
|
697
|
+
// ============================================
|
|
698
|
+
// HELP & VERSION
|
|
699
|
+
// ============================================
|
|
700
|
+
|
|
614
701
|
function showHelp() {
|
|
615
702
|
loadDependencies();
|
|
616
|
-
|
|
617
703
|
console.log(chalk.cyan(`
|
|
618
704
|
╔════════════════════════════════════════════════════════════╗
|
|
619
705
|
║ 📄 mkctx - Make Context for AI Code Analysis ║
|
|
@@ -622,17 +708,34 @@ function showHelp() {
|
|
|
622
708
|
${chalk.white('Generate context files from your codebase for AI analysis.')}
|
|
623
709
|
|
|
624
710
|
${chalk.yellow('Usage:')}
|
|
625
|
-
mkctx
|
|
626
|
-
mkctx config
|
|
627
|
-
mkctx help
|
|
628
|
-
mkctx version
|
|
629
|
-
|
|
630
|
-
${chalk.yellow('
|
|
631
|
-
src
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
711
|
+
mkctx Interactive mode
|
|
712
|
+
mkctx config Create configuration file
|
|
713
|
+
mkctx help / --help Show this help message
|
|
714
|
+
mkctx version / --version Show version
|
|
715
|
+
|
|
716
|
+
${chalk.yellow('Non-interactive flags (skip all prompts):')}
|
|
717
|
+
--src <path> Source directory (default: .)
|
|
718
|
+
--output <path> Output directory (default: ./mkctx)
|
|
719
|
+
--format <fmt> md, json, toon, xml, all, or comma-separated
|
|
720
|
+
--name <filename> Output file base name (default: context)
|
|
721
|
+
--ignore <patterns> Comma-separated ignore patterns
|
|
722
|
+
--first-comment <text> Override first comment header
|
|
723
|
+
--last-comment <text> Override last comment footer
|
|
724
|
+
|
|
725
|
+
${chalk.yellow('Short aliases:')}
|
|
726
|
+
-s --src -o --output -f --format -n --name
|
|
727
|
+
|
|
728
|
+
${chalk.yellow('Examples:')}
|
|
729
|
+
mkctx --src ./src
|
|
730
|
+
mkctx --src . --format all --name my-project --output ./docs
|
|
731
|
+
mkctx --src ./app --format md,json --ignore "*.test.ts,__tests__/"
|
|
732
|
+
mkctx -s ./src -f toon -n snapshot
|
|
733
|
+
|
|
734
|
+
${chalk.yellow('Output Formats:')}
|
|
735
|
+
${chalk.green('JSON')} Simple array of file objects
|
|
736
|
+
${chalk.blue('MD')} Markdown with code blocks
|
|
737
|
+
${chalk.yellow('TOON')} Token-Oriented Object Notation (LLM optimized)
|
|
738
|
+
${chalk.red('XML')} XML with CDATA sections
|
|
636
739
|
|
|
637
740
|
${chalk.gray('More info: https://github.com/pnkkzero/mkctx')}
|
|
638
741
|
`));
|
|
@@ -643,18 +746,30 @@ function showVersion() {
|
|
|
643
746
|
const pkg = require('./package.json');
|
|
644
747
|
console.log(`mkctx v${pkg.version}`);
|
|
645
748
|
} catch {
|
|
646
|
-
console.log('mkctx
|
|
749
|
+
console.log('mkctx v4.0.0');
|
|
647
750
|
}
|
|
648
751
|
}
|
|
649
752
|
|
|
650
753
|
// ============================================
|
|
651
|
-
//
|
|
754
|
+
// ENTRY POINT
|
|
652
755
|
// ============================================
|
|
653
756
|
|
|
654
|
-
main()
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
757
|
+
async function main() {
|
|
758
|
+
const { command, flags } = parseArgs(process.argv);
|
|
759
|
+
|
|
760
|
+
if (flags.help || flags.h || command === 'help') return showHelp();
|
|
761
|
+
if (flags.version || flags.v || command === 'version') return showVersion();
|
|
762
|
+
if (command === 'config') { loadDependencies(); return createConfigFile(); }
|
|
763
|
+
|
|
764
|
+
if (isNonInteractiveMode(flags)) {
|
|
765
|
+
await runNonInteractive(flags);
|
|
766
|
+
} else {
|
|
767
|
+
await runInteractive();
|
|
658
768
|
}
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
main().catch(err => {
|
|
772
|
+
console.error(`\n❌ Error: ${err.message}`);
|
|
773
|
+
if (process.env.DEBUG) console.error(err.stack);
|
|
659
774
|
process.exit(1);
|
|
660
775
|
});
|