task-summary-extractor 9.0.1 → 9.2.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/package.json +1 -1
- package/src/phases/init.js +158 -7
- package/src/phases/output.js +5 -6
- package/src/pipeline.js +1 -1
- package/src/utils/cli.js +264 -4
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "task-summary-extractor",
|
|
3
|
-
"version": "9.0
|
|
3
|
+
"version": "9.2.0",
|
|
4
4
|
"description": "AI-powered meeting analysis & document generation CLI — video + document processing, deep dive docs, dynamic mode, interactive CLI with model selection, confidence scoring, learning loop, git progress tracking",
|
|
5
5
|
"main": "process_and_upload.js",
|
|
6
6
|
"bin": {
|
package/src/phases/init.js
CHANGED
|
@@ -12,7 +12,7 @@ const {
|
|
|
12
12
|
|
|
13
13
|
// --- Utils ---
|
|
14
14
|
const { c } = require('../utils/colors');
|
|
15
|
-
const { parseArgs, showHelp, selectFolder, selectModel } = require('../utils/cli');
|
|
15
|
+
const { parseArgs, showHelp, selectFolder, selectModel, selectRunMode, selectFormats, selectConfidence } = require('../utils/cli');
|
|
16
16
|
const { promptForKey } = require('../utils/global-config');
|
|
17
17
|
const Logger = require('../logger');
|
|
18
18
|
const Progress = require('../utils/checkpoint');
|
|
@@ -73,9 +73,78 @@ async function phaseInit() {
|
|
|
73
73
|
repoPath: flags.repo || null,
|
|
74
74
|
model: typeof flags.model === 'string' ? flags.model : null,
|
|
75
75
|
minConfidence: typeof flags['min-confidence'] === 'string' ? flags['min-confidence'].toLowerCase() : null,
|
|
76
|
-
format: typeof flags.format === 'string' ? flags.format.toLowerCase() :
|
|
76
|
+
format: typeof flags.format === 'string' ? flags.format.toLowerCase() : null,
|
|
77
|
+
runMode: null, // will be populated by interactive selector or inferred
|
|
77
78
|
};
|
|
78
79
|
|
|
80
|
+
// --- Determine if user provided enough flags to skip interactive mode ---
|
|
81
|
+
const hasExplicitMode = opts.model || flags['no-focused-pass'] || flags['no-learning'] || flags['no-diff'] || opts.format;
|
|
82
|
+
const isNonInteractive = !process.stdin.isTTY;
|
|
83
|
+
|
|
84
|
+
// --- Interactive Run-Mode selector (only when TTY and no explicit flags) ---
|
|
85
|
+
if (!hasExplicitMode && !isNonInteractive && !opts.skipGemini) {
|
|
86
|
+
// Show the welcome banner
|
|
87
|
+
const pkg = JSON.parse(fs.readFileSync(path.join(PKG_ROOT, 'package.json'), 'utf8'));
|
|
88
|
+
console.log('');
|
|
89
|
+
console.log(c.heading(' ┌──────────────────────────────────────────────────────────────────────────────┐'));
|
|
90
|
+
console.log(c.heading(` │ ${c.bold('taskex')} ${c.dim(`v${pkg.version}`)} — AI-powered meeting analysis │`));
|
|
91
|
+
console.log(c.heading(' └──────────────────────────────────────────────────────────────────────────────┘'));
|
|
92
|
+
|
|
93
|
+
const mode = await selectRunMode();
|
|
94
|
+
opts.runMode = mode;
|
|
95
|
+
|
|
96
|
+
if (mode !== 'custom') {
|
|
97
|
+
// Apply preset overrides
|
|
98
|
+
const { selectRunMode: _ignore, ...cliModule } = require('../utils/cli');
|
|
99
|
+
// Access RUN_PRESETS from the module
|
|
100
|
+
const presetOverrides = {
|
|
101
|
+
fast: {
|
|
102
|
+
disableFocusedPass: true,
|
|
103
|
+
disableLearning: true,
|
|
104
|
+
disableDiff: true,
|
|
105
|
+
format: 'md,json',
|
|
106
|
+
formats: new Set(['md', 'json']),
|
|
107
|
+
modelTier: 'economy',
|
|
108
|
+
},
|
|
109
|
+
balanced: {
|
|
110
|
+
disableFocusedPass: false,
|
|
111
|
+
disableLearning: false,
|
|
112
|
+
disableDiff: false,
|
|
113
|
+
format: 'all',
|
|
114
|
+
formats: new Set(['md', 'html', 'json', 'pdf', 'docx']),
|
|
115
|
+
modelTier: 'balanced',
|
|
116
|
+
},
|
|
117
|
+
detailed: {
|
|
118
|
+
disableFocusedPass: false,
|
|
119
|
+
disableLearning: false,
|
|
120
|
+
disableDiff: false,
|
|
121
|
+
format: 'all',
|
|
122
|
+
formats: new Set(['md', 'html', 'json', 'pdf', 'docx']),
|
|
123
|
+
modelTier: 'premium',
|
|
124
|
+
},
|
|
125
|
+
};
|
|
126
|
+
const preset = presetOverrides[mode];
|
|
127
|
+
if (preset) {
|
|
128
|
+
opts.disableFocusedPass = preset.disableFocusedPass;
|
|
129
|
+
opts.disableLearning = preset.disableLearning;
|
|
130
|
+
opts.disableDiff = preset.disableDiff;
|
|
131
|
+
opts.format = preset.format;
|
|
132
|
+
opts.formats = preset.formats;
|
|
133
|
+
opts._modelTier = preset.modelTier; // used later for model auto-selection
|
|
134
|
+
}
|
|
135
|
+
} else {
|
|
136
|
+
// Custom mode: show interactive pickers for format & confidence
|
|
137
|
+
const chosenFormats = await selectFormats();
|
|
138
|
+
opts.formats = chosenFormats;
|
|
139
|
+
opts.format = chosenFormats.size === 5 ? 'all' : [...chosenFormats].join(',');
|
|
140
|
+
|
|
141
|
+
const chosenConfidence = await selectConfidence();
|
|
142
|
+
if (chosenConfidence) {
|
|
143
|
+
opts.minConfidence = chosenConfidence;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
79
148
|
// --- Validate min-confidence level ---
|
|
80
149
|
if (opts.minConfidence) {
|
|
81
150
|
const { validateConfidenceLevel } = require('../utils/confidence-filter');
|
|
@@ -86,10 +155,22 @@ async function phaseInit() {
|
|
|
86
155
|
opts.minConfidence = check.normalised.toLowerCase();
|
|
87
156
|
}
|
|
88
157
|
|
|
89
|
-
// --- Validate --format flag ---
|
|
90
|
-
|
|
91
|
-
if (!
|
|
92
|
-
|
|
158
|
+
// --- Validate --format flag (supports comma-separated: md,html,pdf) ---
|
|
159
|
+
// If format wasn't set by interactive picker or flag, default to 'all'
|
|
160
|
+
if (!opts.format) opts.format = 'all';
|
|
161
|
+
if (!opts.formats) {
|
|
162
|
+
const VALID_FORMATS = new Set(['md', 'html', 'json', 'pdf', 'docx', 'all']);
|
|
163
|
+
const requestedFormats = opts.format.split(',').map(f => f.trim()).filter(Boolean);
|
|
164
|
+
const invalidFormats = requestedFormats.filter(f => !VALID_FORMATS.has(f));
|
|
165
|
+
if (invalidFormats.length > 0) {
|
|
166
|
+
throw new Error(`Invalid --format "${invalidFormats.join(', ')}". Valid: md, html, json, pdf, docx, all`);
|
|
167
|
+
}
|
|
168
|
+
// Normalise: "all" or set of specific formats
|
|
169
|
+
opts.formats = requestedFormats.includes('all')
|
|
170
|
+
? new Set(['md', 'html', 'json', 'pdf', 'docx'])
|
|
171
|
+
: new Set(requestedFormats);
|
|
172
|
+
// Keep opts.format as the original string for backwards compatibility
|
|
173
|
+
opts.format = requestedFormats.includes('all') ? 'all' : requestedFormats.join(',');
|
|
93
174
|
}
|
|
94
175
|
|
|
95
176
|
// --- Resolve folder: positional arg or interactive selection ---
|
|
@@ -178,6 +259,21 @@ async function phaseInit() {
|
|
|
178
259
|
// CLI flag: --model <id> — validate and activate
|
|
179
260
|
setActiveModel(opts.model);
|
|
180
261
|
log.step(`Model set via flag: ${config.GEMINI_MODEL}`);
|
|
262
|
+
} else if (opts._modelTier) {
|
|
263
|
+
// Preset-driven: auto-select the best model for the chosen tier
|
|
264
|
+
const modelIds = Object.keys(GEMINI_MODELS);
|
|
265
|
+
const tierModel = modelIds.find(id => GEMINI_MODELS[id].tier === opts._modelTier);
|
|
266
|
+
if (tierModel) {
|
|
267
|
+
setActiveModel(tierModel);
|
|
268
|
+
console.log(c.success(`Model auto-selected: ${GEMINI_MODELS[tierModel].name} (${opts._modelTier} tier)`));
|
|
269
|
+
log.step(`Model auto-selected for ${opts._modelTier} tier: ${config.GEMINI_MODEL}`);
|
|
270
|
+
} else {
|
|
271
|
+
// Fallback to interactive if tier not found
|
|
272
|
+
const chosenModel = await selectModel(GEMINI_MODELS, config.GEMINI_MODEL);
|
|
273
|
+
setActiveModel(chosenModel);
|
|
274
|
+
log.step(`Model selected: ${config.GEMINI_MODEL}`);
|
|
275
|
+
}
|
|
276
|
+
delete opts._modelTier;
|
|
181
277
|
} else {
|
|
182
278
|
// Interactive model selection
|
|
183
279
|
const chosenModel = await selectModel(GEMINI_MODELS, config.GEMINI_MODEL);
|
|
@@ -185,6 +281,9 @@ async function phaseInit() {
|
|
|
185
281
|
log.step(`Model selected: ${config.GEMINI_MODEL}`);
|
|
186
282
|
}
|
|
187
283
|
|
|
284
|
+
// --- Print run summary ---
|
|
285
|
+
_printRunSummary(opts, config.GEMINI_MODEL, GEMINI_MODELS, targetDir);
|
|
286
|
+
|
|
188
287
|
// --- Initialize progress tracking ---
|
|
189
288
|
const progress = new Progress(targetDir);
|
|
190
289
|
const costTracker = new CostTracker(getActiveModelPricing());
|
|
@@ -196,4 +295,56 @@ async function phaseInit() {
|
|
|
196
295
|
return { opts, targetDir, progress, costTracker, progressBar };
|
|
197
296
|
}
|
|
198
297
|
|
|
199
|
-
|
|
298
|
+
/**
|
|
299
|
+
* Print a compact run summary with all active settings.
|
|
300
|
+
*/
|
|
301
|
+
function _printRunSummary(opts, modelId, models, targetDir) {
|
|
302
|
+
const modelName = (models[modelId] || {}).name || modelId;
|
|
303
|
+
const tier = (models[modelId] || {}).tier || '?';
|
|
304
|
+
const cost = (models[modelId] || {}).costEstimate || '';
|
|
305
|
+
|
|
306
|
+
console.log('');
|
|
307
|
+
console.log(c.heading(' ┌──────────────────────────────────────────────────────────────────────────────┐'));
|
|
308
|
+
console.log(c.heading(' │ 📋 Run Summary │'));
|
|
309
|
+
console.log(c.heading(' └──────────────────────────────────────────────────────────────────────────────┘'));
|
|
310
|
+
console.log('');
|
|
311
|
+
console.log(` ${c.dim('Folder:')} ${c.bold(path.basename(targetDir))}`);
|
|
312
|
+
console.log(` ${c.dim('Model:')} ${c.bold(modelName)} ${c.dim(`(${tier})`)} ${cost ? c.dim(cost) : ''}`);
|
|
313
|
+
console.log(` ${c.dim('Formats:')} ${c.bold(opts.format === 'all' ? 'all (md, html, json, pdf, docx)' : opts.format)}`);
|
|
314
|
+
|
|
315
|
+
if (opts.minConfidence) {
|
|
316
|
+
console.log(` ${c.dim('Confidence:')} ${c.bold(opts.minConfidence)}+`);
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
// Feature toggles
|
|
320
|
+
const features = [];
|
|
321
|
+
if (!opts.disableFocusedPass) features.push(c.green('focused-pass'));
|
|
322
|
+
if (!opts.disableLearning) features.push(c.green('learning'));
|
|
323
|
+
if (!opts.disableDiff) features.push(c.green('diff'));
|
|
324
|
+
if (opts.deepDive) features.push(c.cyan('deep-dive'));
|
|
325
|
+
if (opts.dynamic) features.push(c.cyan('dynamic'));
|
|
326
|
+
if (opts.resume) features.push(c.yellow('resume'));
|
|
327
|
+
if (opts.dryRun) features.push(c.yellow('dry-run'));
|
|
328
|
+
if (opts.skipUpload) features.push(c.dim('skip-upload'));
|
|
329
|
+
|
|
330
|
+
const disabled = [];
|
|
331
|
+
if (opts.disableFocusedPass) disabled.push(c.dim('no-focused'));
|
|
332
|
+
if (opts.disableLearning) disabled.push(c.dim('no-learning'));
|
|
333
|
+
if (opts.disableDiff) disabled.push(c.dim('no-diff'));
|
|
334
|
+
|
|
335
|
+
if (features.length > 0) {
|
|
336
|
+
console.log(` ${c.dim('Features:')} ${features.join(c.dim(' · '))}`);
|
|
337
|
+
}
|
|
338
|
+
if (disabled.length > 0) {
|
|
339
|
+
console.log(` ${c.dim('Disabled:')} ${disabled.join(c.dim(' · '))}`);
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
if (opts.runMode) {
|
|
343
|
+
console.log(` ${c.dim('Run mode:')} ${c.bold(opts.runMode)}`);
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
console.log(c.dim(' ' + '─'.repeat(78)));
|
|
347
|
+
console.log('');
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
module.exports = phaseInit;
|
package/src/phases/output.js
CHANGED
|
@@ -25,6 +25,8 @@ const { getLog, phaseTimer, PROJECT_ROOT } = require('./_shared');
|
|
|
25
25
|
|
|
26
26
|
/** Check whether a given output type should be rendered. */
|
|
27
27
|
function shouldRender(opts, type) {
|
|
28
|
+
if (opts.formats) return opts.formats.has(type);
|
|
29
|
+
// Fallback for legacy callers
|
|
28
30
|
if (opts.format === 'all') return true;
|
|
29
31
|
return opts.format === type;
|
|
30
32
|
}
|
|
@@ -59,14 +61,11 @@ async function phaseOutput(ctx, results, compiledAnalysis, compilationRun, compi
|
|
|
59
61
|
// Attach cost summary to results
|
|
60
62
|
results.costSummary = costTracker.getSummary();
|
|
61
63
|
|
|
62
|
-
// Write results JSON (always written
|
|
64
|
+
// Write results JSON (always written; logged only when JSON format is requested)
|
|
63
65
|
const jsonPath = path.join(runDir, 'results.json');
|
|
64
|
-
|
|
65
|
-
|
|
66
|
+
fs.writeFileSync(jsonPath, JSON.stringify(results, null, 2), 'utf8');
|
|
67
|
+
if (shouldRender(opts, 'json')) {
|
|
66
68
|
log.step(`Results JSON saved → ${jsonPath}`);
|
|
67
|
-
} else {
|
|
68
|
-
// Still write JSON internally (needed for uploads/diffs) but don't advertise
|
|
69
|
-
fs.writeFileSync(jsonPath, JSON.stringify(results, null, 2), 'utf8');
|
|
70
69
|
}
|
|
71
70
|
|
|
72
71
|
// Generate Markdown
|
package/src/pipeline.js
CHANGED
|
@@ -370,7 +370,7 @@ async function runDocOnly(ctx) {
|
|
|
370
370
|
const jsonPath = path.join(runDir, 'results.json');
|
|
371
371
|
fs.writeFileSync(jsonPath, JSON.stringify(results, null, 2), 'utf8');
|
|
372
372
|
|
|
373
|
-
const shouldRender = (type) => opts.format === 'all' || opts.format === type;
|
|
373
|
+
const shouldRender = (type) => opts.formats ? opts.formats.has(type) : (opts.format === 'all' || opts.format === type);
|
|
374
374
|
|
|
375
375
|
if (compiledAnalysis) {
|
|
376
376
|
const mdMeta = {
|
package/src/utils/cli.js
CHANGED
|
@@ -354,6 +354,11 @@ function showHelp() {
|
|
|
354
354
|
${c.bold('taskex setup')} ${c.dim('[--check | --silent]')}
|
|
355
355
|
${c.bold('taskex config')} ${c.dim('[--show | --clear]')}
|
|
356
356
|
|
|
357
|
+
${h('INTERACTIVE MODE')}
|
|
358
|
+
${f('taskex', 'Launch interactive wizard — choose run mode, model, format, confidence')}
|
|
359
|
+
${f2('Run modes: ⚡ Fast · ⚖️ Balanced · 🔬 Detailed · ⚙️ Custom')}
|
|
360
|
+
${f2('When flags are provided, interactive prompts are skipped automatically.')}
|
|
361
|
+
|
|
357
362
|
${h('SUBCOMMANDS')}
|
|
358
363
|
${f('setup', 'Full interactive setup (prerequisites, deps, .env)')}
|
|
359
364
|
${f('setup --check', 'Validation only — verify environment')}
|
|
@@ -370,7 +375,7 @@ ${f('--deep-dive', 'Generate explanatory docs per topic')}
|
|
|
370
375
|
${h('CORE OPTIONS')}
|
|
371
376
|
${f('--name <name>', 'Your name (skip interactive prompt)')}
|
|
372
377
|
${f('--model <id>', 'Gemini model (skip interactive selector)')}
|
|
373
|
-
${f('--format <type>', 'Output
|
|
378
|
+
${f('--format <type>', 'Output: md, html, json, pdf, docx, all — comma-separated (default: all)')}
|
|
374
379
|
${f('--min-confidence <level>', 'Filter: high, medium, low (default: all)')}
|
|
375
380
|
${f('--output <dir>', 'Custom output directory for results')}
|
|
376
381
|
${f('--skip-upload', 'Skip Firebase Storage uploads')}
|
|
@@ -417,13 +422,16 @@ ${f('--help, -h', 'Show this help message')}
|
|
|
417
422
|
${f('--version, -v', 'Show version')}
|
|
418
423
|
|
|
419
424
|
${h('EXAMPLES')}
|
|
420
|
-
${c.dim('$')} taskex ${c.dim('# Interactive mode')}
|
|
421
|
-
${c.dim('$')} taskex "call 1" ${c.dim('# Analyze a call')}
|
|
425
|
+
${c.dim('$')} taskex ${c.dim('# Interactive wizard — choose mode, model, format')}
|
|
426
|
+
${c.dim('$')} taskex "call 1" ${c.dim('# Analyze a call (interactive wizard)')}
|
|
427
|
+
${c.dim('$')} taskex --model gemini-2.5-pro "call 1" ${c.dim('# Skip wizard, use specific model')}
|
|
428
|
+
${c.dim('$')} taskex --format md,html "call 1" ${c.dim('# Skip wizard, specific formats')}
|
|
422
429
|
${c.dim('$')} taskex --name "Jane" --skip-upload "call 1"
|
|
423
430
|
${c.dim('$')} taskex --model gemini-2.5-pro --deep-dive "call 1"
|
|
424
431
|
${c.dim('$')} taskex --dynamic --request "Plan API migration" "specs"
|
|
425
432
|
${c.dim('$')} taskex --min-confidence medium "call 1" ${c.dim('# Filter low-confidence')}
|
|
426
433
|
${c.dim('$')} taskex --format md "call 1" ${c.dim('# Markdown only')}
|
|
434
|
+
${c.dim('$')} taskex --format md,html,pdf "call 1" ${c.dim('# Multiple formats')}
|
|
427
435
|
${c.dim('$')} taskex --format pdf "call 1" ${c.dim('# PDF report')}
|
|
428
436
|
${c.dim('$')} taskex --format docx "call 1" ${c.dim('# Word document')}
|
|
429
437
|
${c.dim('$')} taskex --resume "call 1" ${c.dim('# Resume interrupted run')}
|
|
@@ -433,7 +441,10 @@ ${f('--version, -v', 'Show version')}
|
|
|
433
441
|
throw Object.assign(new Error('HELP_SHOWN'), { code: 'HELP_SHOWN' });
|
|
434
442
|
}
|
|
435
443
|
|
|
436
|
-
module.exports = {
|
|
444
|
+
module.exports = {
|
|
445
|
+
parseArgs, showHelp, discoverFolders, selectFolder, selectModel,
|
|
446
|
+
promptUser, promptUserText, selectRunMode, selectFormats, selectConfidence,
|
|
447
|
+
};
|
|
437
448
|
|
|
438
449
|
// ======================== INTERACTIVE PROMPTS ========================
|
|
439
450
|
|
|
@@ -461,3 +472,252 @@ function promptUserText(question) {
|
|
|
461
472
|
});
|
|
462
473
|
});
|
|
463
474
|
}
|
|
475
|
+
|
|
476
|
+
// ======================== RUN MODE SELECTOR ========================
|
|
477
|
+
|
|
478
|
+
/**
|
|
479
|
+
* Run-mode presets. Each key maps to a set of opts overrides.
|
|
480
|
+
* 'custom' triggers the interactive per-setting prompts.
|
|
481
|
+
*/
|
|
482
|
+
const RUN_PRESETS = {
|
|
483
|
+
fast: {
|
|
484
|
+
label: 'Fast',
|
|
485
|
+
icon: '⚡',
|
|
486
|
+
description: 'Economy model, skip extras, Markdown + JSON only — fastest & cheapest',
|
|
487
|
+
overrides: {
|
|
488
|
+
disableFocusedPass: true,
|
|
489
|
+
disableLearning: true,
|
|
490
|
+
disableDiff: true,
|
|
491
|
+
format: 'md,json',
|
|
492
|
+
formats: new Set(['md', 'json']),
|
|
493
|
+
modelTier: 'economy',
|
|
494
|
+
},
|
|
495
|
+
},
|
|
496
|
+
balanced: {
|
|
497
|
+
label: 'Balanced',
|
|
498
|
+
icon: '⚖️',
|
|
499
|
+
description: 'Balanced model, learning enabled, all formats — recommended default',
|
|
500
|
+
overrides: {
|
|
501
|
+
disableFocusedPass: false,
|
|
502
|
+
disableLearning: false,
|
|
503
|
+
disableDiff: false,
|
|
504
|
+
format: 'all',
|
|
505
|
+
formats: new Set(['md', 'html', 'json', 'pdf', 'docx']),
|
|
506
|
+
modelTier: 'balanced',
|
|
507
|
+
},
|
|
508
|
+
},
|
|
509
|
+
detailed: {
|
|
510
|
+
label: 'Detailed',
|
|
511
|
+
icon: '🔬',
|
|
512
|
+
description: 'Premium model, all features, all formats — highest quality analysis',
|
|
513
|
+
overrides: {
|
|
514
|
+
disableFocusedPass: false,
|
|
515
|
+
disableLearning: false,
|
|
516
|
+
disableDiff: false,
|
|
517
|
+
format: 'all',
|
|
518
|
+
formats: new Set(['md', 'html', 'json', 'pdf', 'docx']),
|
|
519
|
+
modelTier: 'premium',
|
|
520
|
+
},
|
|
521
|
+
},
|
|
522
|
+
custom: {
|
|
523
|
+
label: 'Custom',
|
|
524
|
+
icon: '⚙️',
|
|
525
|
+
description: 'Choose each setting interactively',
|
|
526
|
+
overrides: {},
|
|
527
|
+
},
|
|
528
|
+
};
|
|
529
|
+
|
|
530
|
+
/**
|
|
531
|
+
* Interactive run-mode selector. Shows preset options and returns the chosen
|
|
532
|
+
* preset key. The caller applies overrides to opts.
|
|
533
|
+
*
|
|
534
|
+
* @returns {Promise<string>} Preset key: 'fast' | 'balanced' | 'detailed' | 'custom'
|
|
535
|
+
*/
|
|
536
|
+
async function selectRunMode() {
|
|
537
|
+
const readline = require('readline');
|
|
538
|
+
const presetKeys = Object.keys(RUN_PRESETS);
|
|
539
|
+
|
|
540
|
+
console.log('');
|
|
541
|
+
console.log(c.heading(' ┌──────────────────────────────────────────────────────────────────────────────┐'));
|
|
542
|
+
console.log(c.heading(' │ 🚀 Run Mode │'));
|
|
543
|
+
console.log(c.heading(' └──────────────────────────────────────────────────────────────────────────────┘'));
|
|
544
|
+
console.log('');
|
|
545
|
+
|
|
546
|
+
presetKeys.forEach((key, i) => {
|
|
547
|
+
const p = RUN_PRESETS[key];
|
|
548
|
+
const num = c.cyan(`[${i + 1}]`);
|
|
549
|
+
const label = c.bold(`${p.icon} ${p.label}`);
|
|
550
|
+
const desc = c.dim(p.description);
|
|
551
|
+
const marker = key === 'balanced' ? c.green(' ← default') : '';
|
|
552
|
+
console.log(` ${num} ${label}${marker}`);
|
|
553
|
+
console.log(` ${desc}`);
|
|
554
|
+
});
|
|
555
|
+
console.log('');
|
|
556
|
+
|
|
557
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
558
|
+
return new Promise(resolve => {
|
|
559
|
+
rl.question(' Select run mode [1-4] (Enter = balanced): ', answer => {
|
|
560
|
+
rl.close();
|
|
561
|
+
const trimmed = (answer || '').trim();
|
|
562
|
+
|
|
563
|
+
if (!trimmed) {
|
|
564
|
+
console.log(c.success('Using balanced mode'));
|
|
565
|
+
resolve('balanced');
|
|
566
|
+
return;
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
const num = parseInt(trimmed, 10);
|
|
570
|
+
if (num >= 1 && num <= presetKeys.length) {
|
|
571
|
+
const key = presetKeys[num - 1];
|
|
572
|
+
console.log(c.success(`Using ${RUN_PRESETS[key].label} mode`));
|
|
573
|
+
resolve(key);
|
|
574
|
+
return;
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
// Try matching by name
|
|
578
|
+
const lower = trimmed.toLowerCase();
|
|
579
|
+
const match = presetKeys.find(k => k === lower || RUN_PRESETS[k].label.toLowerCase() === lower);
|
|
580
|
+
if (match) {
|
|
581
|
+
console.log(c.success(`Using ${RUN_PRESETS[match].label} mode`));
|
|
582
|
+
resolve(match);
|
|
583
|
+
return;
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
console.log(c.warn(`Unknown "${trimmed}" — using balanced mode`));
|
|
587
|
+
resolve('balanced');
|
|
588
|
+
});
|
|
589
|
+
});
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
// ======================== FORMAT PICKER ========================
|
|
593
|
+
|
|
594
|
+
const ALL_FORMATS = [
|
|
595
|
+
{ key: 'md', icon: '📝', label: 'Markdown', desc: 'Human-readable report' },
|
|
596
|
+
{ key: 'html', icon: '🌐', label: 'HTML', desc: 'Styled web page' },
|
|
597
|
+
{ key: 'pdf', icon: '📄', label: 'PDF', desc: 'Portable document' },
|
|
598
|
+
{ key: 'docx', icon: '📘', label: 'Word', desc: 'Editable Word document' },
|
|
599
|
+
{ key: 'json', icon: '🔧', label: 'JSON', desc: 'Machine-readable data' },
|
|
600
|
+
];
|
|
601
|
+
|
|
602
|
+
/**
|
|
603
|
+
* Interactive format picker — user enters comma-separated numbers or "all".
|
|
604
|
+
* Returns a Set of chosen format keys.
|
|
605
|
+
*
|
|
606
|
+
* @returns {Promise<Set<string>>}
|
|
607
|
+
*/
|
|
608
|
+
async function selectFormats() {
|
|
609
|
+
const readline = require('readline');
|
|
610
|
+
|
|
611
|
+
console.log('');
|
|
612
|
+
console.log(` ${c.bold('📦 Output Formats')} ${c.dim('(select one or more)')}`);
|
|
613
|
+
console.log(c.dim(' ' + '─'.repeat(50)));
|
|
614
|
+
|
|
615
|
+
ALL_FORMATS.forEach((f, i) => {
|
|
616
|
+
const num = c.cyan(`[${i + 1}]`);
|
|
617
|
+
console.log(` ${num} ${f.icon} ${c.bold(f.label.padEnd(12))} ${c.dim(f.desc)}`);
|
|
618
|
+
});
|
|
619
|
+
console.log(` ${c.cyan('[A]')} ${c.bold('All formats')}`);
|
|
620
|
+
console.log('');
|
|
621
|
+
|
|
622
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
623
|
+
return new Promise(resolve => {
|
|
624
|
+
rl.question(' Formats (e.g. 1,2,3 or A for all) [Enter = all]: ', answer => {
|
|
625
|
+
rl.close();
|
|
626
|
+
const trimmed = (answer || '').trim().toLowerCase();
|
|
627
|
+
|
|
628
|
+
if (!trimmed || trimmed === 'a' || trimmed === 'all') {
|
|
629
|
+
const all = new Set(ALL_FORMATS.map(f => f.key));
|
|
630
|
+
console.log(c.success('All formats selected'));
|
|
631
|
+
resolve(all);
|
|
632
|
+
return;
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
const parts = trimmed.split(/[\s,]+/).filter(Boolean);
|
|
636
|
+
const chosen = new Set();
|
|
637
|
+
for (const p of parts) {
|
|
638
|
+
const num = parseInt(p, 10);
|
|
639
|
+
if (num >= 1 && num <= ALL_FORMATS.length) {
|
|
640
|
+
chosen.add(ALL_FORMATS[num - 1].key);
|
|
641
|
+
} else {
|
|
642
|
+
// Try matching format key by name
|
|
643
|
+
const match = ALL_FORMATS.find(f => f.key === p || f.label.toLowerCase() === p);
|
|
644
|
+
if (match) chosen.add(match.key);
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
if (chosen.size === 0) {
|
|
649
|
+
console.log(c.warn('No valid formats selected — using all'));
|
|
650
|
+
resolve(new Set(ALL_FORMATS.map(f => f.key)));
|
|
651
|
+
return;
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
const labels = [...chosen].map(k => ALL_FORMATS.find(f => f.key === k)?.label || k);
|
|
655
|
+
console.log(c.success(`Formats: ${labels.join(', ')}`));
|
|
656
|
+
resolve(chosen);
|
|
657
|
+
});
|
|
658
|
+
});
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
// ======================== CONFIDENCE PICKER ========================
|
|
662
|
+
|
|
663
|
+
/**
|
|
664
|
+
* Interactive confidence-level selector.
|
|
665
|
+
* Returns null (keep all) or a string like 'high' or 'medium'.
|
|
666
|
+
*
|
|
667
|
+
* @returns {Promise<string|null>}
|
|
668
|
+
*/
|
|
669
|
+
async function selectConfidence() {
|
|
670
|
+
const readline = require('readline');
|
|
671
|
+
|
|
672
|
+
const levels = [
|
|
673
|
+
{ key: null, icon: '🌐', label: 'All', desc: 'Keep everything — no filtering' },
|
|
674
|
+
{ key: 'low', icon: '🟡', label: 'Low+', desc: 'Keep low, medium & high confidence' },
|
|
675
|
+
{ key: 'medium', icon: '🟠', label: 'Medium+', desc: 'Keep medium & high confidence' },
|
|
676
|
+
{ key: 'high', icon: '🔴', label: 'High', desc: 'Only high-confidence items' },
|
|
677
|
+
];
|
|
678
|
+
|
|
679
|
+
console.log('');
|
|
680
|
+
console.log(` ${c.bold('🎯 Confidence Filter')}`);
|
|
681
|
+
console.log(c.dim(' ' + '─'.repeat(50)));
|
|
682
|
+
|
|
683
|
+
levels.forEach((l, i) => {
|
|
684
|
+
const num = c.cyan(`[${i + 1}]`);
|
|
685
|
+
const marker = i === 0 ? c.green(' ← default') : '';
|
|
686
|
+
console.log(` ${num} ${l.icon} ${c.bold(l.label.padEnd(10))} ${c.dim(l.desc)}${marker}`);
|
|
687
|
+
});
|
|
688
|
+
console.log('');
|
|
689
|
+
|
|
690
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
691
|
+
return new Promise(resolve => {
|
|
692
|
+
rl.question(' Confidence filter [1-4] (Enter = keep all): ', answer => {
|
|
693
|
+
rl.close();
|
|
694
|
+
const trimmed = (answer || '').trim();
|
|
695
|
+
|
|
696
|
+
if (!trimmed) {
|
|
697
|
+
console.log(c.success('Keeping all confidence levels'));
|
|
698
|
+
resolve(null);
|
|
699
|
+
return;
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
const num = parseInt(trimmed, 10);
|
|
703
|
+
if (num >= 1 && num <= levels.length) {
|
|
704
|
+
const chosen = levels[num - 1];
|
|
705
|
+
console.log(c.success(`Confidence filter: ${chosen.label}`));
|
|
706
|
+
resolve(chosen.key);
|
|
707
|
+
return;
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
// Try matching by name
|
|
711
|
+
const lower = trimmed.toLowerCase();
|
|
712
|
+
const match = levels.find(l => l.key === lower || l.label.toLowerCase() === lower);
|
|
713
|
+
if (match) {
|
|
714
|
+
console.log(c.success(`Confidence filter: ${match.label}`));
|
|
715
|
+
resolve(match.key);
|
|
716
|
+
return;
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
console.log(c.warn(`Unknown "${trimmed}" — keeping all`));
|
|
720
|
+
resolve(null);
|
|
721
|
+
});
|
|
722
|
+
});
|
|
723
|
+
}
|