task-summary-extractor 8.1.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/ARCHITECTURE.md +605 -0
- package/EXPLORATION.md +451 -0
- package/QUICK_START.md +272 -0
- package/README.md +544 -0
- package/bin/taskex.js +64 -0
- package/package.json +63 -0
- package/process_and_upload.js +107 -0
- package/prompt.json +265 -0
- package/setup.js +505 -0
- package/src/config.js +327 -0
- package/src/logger.js +355 -0
- package/src/pipeline.js +2006 -0
- package/src/renderers/markdown.js +968 -0
- package/src/services/firebase.js +106 -0
- package/src/services/gemini.js +779 -0
- package/src/services/git.js +329 -0
- package/src/services/video.js +305 -0
- package/src/utils/adaptive-budget.js +266 -0
- package/src/utils/change-detector.js +466 -0
- package/src/utils/cli.js +415 -0
- package/src/utils/context-manager.js +499 -0
- package/src/utils/cost-tracker.js +156 -0
- package/src/utils/deep-dive.js +549 -0
- package/src/utils/diff-engine.js +315 -0
- package/src/utils/dynamic-mode.js +567 -0
- package/src/utils/focused-reanalysis.js +317 -0
- package/src/utils/format.js +32 -0
- package/src/utils/fs.js +39 -0
- package/src/utils/global-config.js +315 -0
- package/src/utils/health-dashboard.js +216 -0
- package/src/utils/inject-cli-flags.js +58 -0
- package/src/utils/json-parser.js +245 -0
- package/src/utils/learning-loop.js +301 -0
- package/src/utils/progress-updater.js +451 -0
- package/src/utils/progress.js +166 -0
- package/src/utils/prompt.js +32 -0
- package/src/utils/quality-gate.js +429 -0
- package/src/utils/retry.js +129 -0
package/src/utils/cli.js
ADDED
|
@@ -0,0 +1,415 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CLI argument parser — simple, zero-dependency flag parser.
|
|
3
|
+
*
|
|
4
|
+
* Supports:
|
|
5
|
+
* --flag Boolean flag
|
|
6
|
+
* --key=value Key-value pairs
|
|
7
|
+
* --key value Key-value (next arg)
|
|
8
|
+
* positional args Collected separately
|
|
9
|
+
*
|
|
10
|
+
* Also includes interactive folder selection for when no folder arg is provided.
|
|
11
|
+
*
|
|
12
|
+
* Usage:
|
|
13
|
+
* const { flags, positional } = parseArgs(process.argv.slice(2));
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
'use strict';
|
|
17
|
+
|
|
18
|
+
const fs = require('fs');
|
|
19
|
+
const path = require('path');
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Parse command-line arguments into flags and positional args.
|
|
23
|
+
*
|
|
24
|
+
* @param {string[]} argv - Arguments (typically process.argv.slice(2))
|
|
25
|
+
* @returns {{ flags: object, positional: string[] }}
|
|
26
|
+
*/
|
|
27
|
+
function parseArgs(argv) {
|
|
28
|
+
const flags = {};
|
|
29
|
+
const positional = [];
|
|
30
|
+
|
|
31
|
+
// Boolean flags that should never consume the next argument as a value
|
|
32
|
+
const BOOLEAN_FLAGS = new Set([
|
|
33
|
+
'help', 'h', 'version', 'v',
|
|
34
|
+
'skip-upload', 'force-upload', 'no-storage-url',
|
|
35
|
+
'skip-compression', 'skip-gemini',
|
|
36
|
+
'resume', 'reanalyze', 'dry-run',
|
|
37
|
+
'dynamic', 'deep-dive', 'update-progress',
|
|
38
|
+
'no-focused-pass', 'no-learning', 'no-diff',
|
|
39
|
+
]);
|
|
40
|
+
|
|
41
|
+
for (let i = 0; i < argv.length; i++) {
|
|
42
|
+
const arg = argv[i];
|
|
43
|
+
|
|
44
|
+
if (arg.startsWith('--')) {
|
|
45
|
+
const eqIdx = arg.indexOf('=');
|
|
46
|
+
if (eqIdx !== -1) {
|
|
47
|
+
// --key=value
|
|
48
|
+
const key = arg.slice(2, eqIdx);
|
|
49
|
+
flags[key] = arg.slice(eqIdx + 1);
|
|
50
|
+
} else {
|
|
51
|
+
const key = arg.slice(2);
|
|
52
|
+
// Boolean flags never consume the next argument
|
|
53
|
+
if (BOOLEAN_FLAGS.has(key)) {
|
|
54
|
+
flags[key] = true;
|
|
55
|
+
} else if (i + 1 < argv.length && !argv[i + 1].startsWith('--')) {
|
|
56
|
+
// Value flag: consume next argument
|
|
57
|
+
flags[key] = argv[i + 1];
|
|
58
|
+
i++;
|
|
59
|
+
} else {
|
|
60
|
+
flags[key] = true;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
} else if (arg.startsWith('-') && arg.length === 2) {
|
|
64
|
+
// Short flag: -v, -q, etc.
|
|
65
|
+
const key = arg.slice(1);
|
|
66
|
+
flags[key] = true;
|
|
67
|
+
} else {
|
|
68
|
+
positional.push(arg);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return { flags, positional };
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// ======================== INTERACTIVE FOLDER SELECTOR ========================
|
|
76
|
+
|
|
77
|
+
/** Directories to exclude when scanning for call/project folders */
|
|
78
|
+
const SKIP_FOLDER_NAMES = new Set([
|
|
79
|
+
'node_modules', '.git', 'src', 'logs', 'gemini_runs', 'compressed', 'runs',
|
|
80
|
+
]);
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Discover folders in the project root that look like call/project folders.
|
|
84
|
+
* A valid folder is any directory that is NOT a known infrastructure folder
|
|
85
|
+
* and contains at least one file (video, doc, or subdirectory with docs).
|
|
86
|
+
*
|
|
87
|
+
* @param {string} projectRoot - Root directory of the tool
|
|
88
|
+
* @returns {Array<{name: string, absPath: string, hasVideo: boolean, docCount: number, description: string}>}
|
|
89
|
+
*/
|
|
90
|
+
function discoverFolders(projectRoot) {
|
|
91
|
+
const VIDEO_EXTS = new Set(['.mp4', '.mkv', '.avi', '.mov', '.webm']);
|
|
92
|
+
const DOC_EXTS = new Set(['.vtt', '.txt', '.pdf', '.docx', '.doc', '.srt', '.csv', '.md']);
|
|
93
|
+
const folders = [];
|
|
94
|
+
|
|
95
|
+
let entries;
|
|
96
|
+
try {
|
|
97
|
+
entries = fs.readdirSync(projectRoot, { withFileTypes: true });
|
|
98
|
+
} catch {
|
|
99
|
+
return folders;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
for (const entry of entries) {
|
|
103
|
+
if (!entry.isDirectory()) continue;
|
|
104
|
+
if (entry.name.startsWith('.') || SKIP_FOLDER_NAMES.has(entry.name)) continue;
|
|
105
|
+
|
|
106
|
+
const absPath = path.join(projectRoot, entry.name);
|
|
107
|
+
let hasVideo = false;
|
|
108
|
+
let docCount = 0;
|
|
109
|
+
let hasRuns = false;
|
|
110
|
+
|
|
111
|
+
// Quick scan top level + one depth
|
|
112
|
+
const scan = (dir, depth = 0) => {
|
|
113
|
+
let items;
|
|
114
|
+
try { items = fs.readdirSync(dir, { withFileTypes: true }); } catch { return; }
|
|
115
|
+
for (const item of items) {
|
|
116
|
+
if (item.isFile()) {
|
|
117
|
+
const ext = path.extname(item.name).toLowerCase();
|
|
118
|
+
if (VIDEO_EXTS.has(ext)) hasVideo = true;
|
|
119
|
+
if (DOC_EXTS.has(ext)) docCount++;
|
|
120
|
+
} else if (item.isDirectory() && depth === 0) {
|
|
121
|
+
if (item.name === 'runs') hasRuns = true;
|
|
122
|
+
if (!SKIP_FOLDER_NAMES.has(item.name) && item.name !== 'runs') {
|
|
123
|
+
scan(path.join(dir, item.name), depth + 1);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
};
|
|
128
|
+
scan(absPath);
|
|
129
|
+
|
|
130
|
+
// Only include folders with at least some content
|
|
131
|
+
if (hasVideo || docCount > 0) {
|
|
132
|
+
const parts = [];
|
|
133
|
+
if (hasVideo) parts.push('video');
|
|
134
|
+
if (docCount > 0) parts.push(`${docCount} doc(s)`);
|
|
135
|
+
if (hasRuns) parts.push('has runs');
|
|
136
|
+
folders.push({
|
|
137
|
+
name: entry.name,
|
|
138
|
+
absPath,
|
|
139
|
+
hasVideo,
|
|
140
|
+
docCount,
|
|
141
|
+
hasRuns,
|
|
142
|
+
description: parts.join(', '),
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
return folders;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Interactive folder selection — shows discovered folders and lets user pick.
|
|
152
|
+
* Returns the selected folder name as a string, or null if cancelled.
|
|
153
|
+
*
|
|
154
|
+
* @param {string} projectRoot - Root directory
|
|
155
|
+
* @returns {Promise<string|null>} - Folder name or null
|
|
156
|
+
*/
|
|
157
|
+
async function selectFolder(projectRoot) {
|
|
158
|
+
const readline = require('readline');
|
|
159
|
+
const folders = discoverFolders(projectRoot);
|
|
160
|
+
|
|
161
|
+
if (folders.length === 0) {
|
|
162
|
+
console.log('\n No call/project folders found in the current directory.');
|
|
163
|
+
console.log(' Create a folder with your recording or documents, then run again.\n');
|
|
164
|
+
return null;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
console.log('');
|
|
168
|
+
console.log(' Available folders:');
|
|
169
|
+
console.log(' ─────────────────');
|
|
170
|
+
folders.forEach((f, i) => {
|
|
171
|
+
const icon = f.hasVideo ? '🎥' : '📄';
|
|
172
|
+
const mode = f.hasVideo ? '' : ' (docs only → use --dynamic)';
|
|
173
|
+
console.log(` [${i + 1}] ${icon} ${f.name} — ${f.description}${mode}`);
|
|
174
|
+
});
|
|
175
|
+
console.log('');
|
|
176
|
+
|
|
177
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
178
|
+
return new Promise(resolve => {
|
|
179
|
+
rl.question(' Select folder (number, or type a path): ', answer => {
|
|
180
|
+
rl.close();
|
|
181
|
+
const trimmed = (answer || '').trim();
|
|
182
|
+
if (!trimmed) { resolve(null); return; }
|
|
183
|
+
|
|
184
|
+
// Number selection
|
|
185
|
+
const num = parseInt(trimmed, 10);
|
|
186
|
+
if (!isNaN(num) && num >= 1 && num <= folders.length) {
|
|
187
|
+
resolve(folders[num - 1].name);
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// Direct path input
|
|
192
|
+
resolve(trimmed);
|
|
193
|
+
});
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// ======================== INTERACTIVE MODEL SELECTOR ========================
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Format a token count as a human-readable context window size.
|
|
201
|
+
* @param {number} tokens
|
|
202
|
+
* @returns {string}
|
|
203
|
+
*/
|
|
204
|
+
function fmtContext(tokens) {
|
|
205
|
+
if (tokens >= 1_000_000) return `${(tokens / 1_000_000).toFixed(tokens % 1_000_000 === 0 ? 0 : 1)}M`;
|
|
206
|
+
if (tokens >= 1_000) return `${(tokens / 1_000).toFixed(0)}K`;
|
|
207
|
+
return String(tokens);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Interactive model selector — shows all available Gemini models with
|
|
212
|
+
* context window sizes, pricing, and descriptions. Returns the model ID.
|
|
213
|
+
*
|
|
214
|
+
* @param {object} GEMINI_MODELS - Model registry from config.js
|
|
215
|
+
* @param {string} currentModel - Currently active model ID (shown as default)
|
|
216
|
+
* @returns {Promise<string>} Selected model ID
|
|
217
|
+
*/
|
|
218
|
+
async function selectModel(GEMINI_MODELS, currentModel) {
|
|
219
|
+
const readline = require('readline');
|
|
220
|
+
const modelIds = Object.keys(GEMINI_MODELS);
|
|
221
|
+
|
|
222
|
+
// Group by tier for organized display
|
|
223
|
+
const tiers = {
|
|
224
|
+
premium: { label: 'Premium (highest quality)', icon: '🏆', models: [] },
|
|
225
|
+
balanced: { label: 'Balanced (recommended)', icon: '⚡', models: [] },
|
|
226
|
+
economy: { label: 'Economy (lowest cost)', icon: '💰', models: [] },
|
|
227
|
+
};
|
|
228
|
+
|
|
229
|
+
let idx = 0;
|
|
230
|
+
const indexMap = {}; // index → modelId
|
|
231
|
+
for (const id of modelIds) {
|
|
232
|
+
const m = GEMINI_MODELS[id];
|
|
233
|
+
const tier = tiers[m.tier] || tiers.fast;
|
|
234
|
+
idx++;
|
|
235
|
+
indexMap[idx] = id;
|
|
236
|
+
tier.models.push({ idx, id, ...m });
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
console.log('');
|
|
240
|
+
console.log(' ┌──────────────────────────────────────────────────────────────────────────────┐');
|
|
241
|
+
console.log(' │ 🤖 Gemini Model Selection │');
|
|
242
|
+
console.log(' └──────────────────────────────────────────────────────────────────────────────┘');
|
|
243
|
+
|
|
244
|
+
for (const [, tier] of Object.entries(tiers)) {
|
|
245
|
+
if (tier.models.length === 0) continue;
|
|
246
|
+
console.log('');
|
|
247
|
+
console.log(` ${tier.icon} ${tier.label}`);
|
|
248
|
+
console.log(' ' + '─'.repeat(76));
|
|
249
|
+
|
|
250
|
+
for (const m of tier.models) {
|
|
251
|
+
const isDefault = m.id === currentModel;
|
|
252
|
+
const marker = isDefault ? ' ← default' : '';
|
|
253
|
+
const thinkTag = m.thinking ? ' [thinking]' : '';
|
|
254
|
+
|
|
255
|
+
// Line 1: number, name, description
|
|
256
|
+
console.log(` [${m.idx}] ${m.name}${thinkTag}${marker}`);
|
|
257
|
+
console.log(` ${m.description}`);
|
|
258
|
+
|
|
259
|
+
// Line 2: specs
|
|
260
|
+
const ctxStr = fmtContext(m.contextWindow);
|
|
261
|
+
const outStr = fmtContext(m.maxOutput);
|
|
262
|
+
const inPrice = `$${m.pricing.inputPerM.toFixed(m.pricing.inputPerM < 0.1 ? 4 : 2)}/1M in`;
|
|
263
|
+
const outPrice = `$${m.pricing.outputPerM.toFixed(m.pricing.outputPerM < 1 ? 2 : 2)}/1M out`;
|
|
264
|
+
const thinkPrice = m.thinking ? ` · $${m.pricing.thinkingPerM.toFixed(2)}/1M think` : '';
|
|
265
|
+
console.log(` Context: ${ctxStr} tokens · Max output: ${outStr} · ${m.costEstimate}`);
|
|
266
|
+
console.log(` Pricing: ${inPrice} · ${outPrice}${thinkPrice}`);
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
console.log('');
|
|
271
|
+
|
|
272
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
273
|
+
return new Promise(resolve => {
|
|
274
|
+
rl.question(` Select model [1-${idx}] (Enter = keep default): `, answer => {
|
|
275
|
+
rl.close();
|
|
276
|
+
const trimmed = (answer || '').trim();
|
|
277
|
+
|
|
278
|
+
// Enter = keep default
|
|
279
|
+
if (!trimmed) {
|
|
280
|
+
console.log(` → Using ${GEMINI_MODELS[currentModel].name}`);
|
|
281
|
+
resolve(currentModel);
|
|
282
|
+
return;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
// Number selection
|
|
286
|
+
const num = parseInt(trimmed, 10);
|
|
287
|
+
if (!isNaN(num) && indexMap[num]) {
|
|
288
|
+
const chosen = indexMap[num];
|
|
289
|
+
console.log(` → Selected ${GEMINI_MODELS[chosen].name}`);
|
|
290
|
+
resolve(chosen);
|
|
291
|
+
return;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
// Direct model ID input
|
|
295
|
+
if (GEMINI_MODELS[trimmed]) {
|
|
296
|
+
console.log(` → Selected ${GEMINI_MODELS[trimmed].name}`);
|
|
297
|
+
resolve(trimmed);
|
|
298
|
+
return;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
// Fuzzy match: partial name
|
|
302
|
+
const lower = trimmed.toLowerCase();
|
|
303
|
+
const match = modelIds.find(id =>
|
|
304
|
+
id.toLowerCase().includes(lower) ||
|
|
305
|
+
GEMINI_MODELS[id].name.toLowerCase().includes(lower)
|
|
306
|
+
);
|
|
307
|
+
if (match) {
|
|
308
|
+
console.log(` → Matched ${GEMINI_MODELS[match].name}`);
|
|
309
|
+
resolve(match);
|
|
310
|
+
return;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
console.log(` ⚠ Unknown selection "${trimmed}" — using default (${currentModel})`);
|
|
314
|
+
resolve(currentModel);
|
|
315
|
+
});
|
|
316
|
+
});
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
/**
|
|
320
|
+
* Display help text and signal an early exit by throwing.
|
|
321
|
+
* Callers should catch this and exit cleanly (no process.exit in library code).
|
|
322
|
+
*/
|
|
323
|
+
function showHelp() {
|
|
324
|
+
console.log(`
|
|
325
|
+
Usage: taskex [options] [folder]
|
|
326
|
+
taskex config [--show | --clear]
|
|
327
|
+
node process_and_upload.js [options] [folder]
|
|
328
|
+
|
|
329
|
+
AI-powered meeting analysis & document generation pipeline.
|
|
330
|
+
If no folder is specified, shows an interactive folder selector.
|
|
331
|
+
If you cd into a folder, just run: taskex
|
|
332
|
+
|
|
333
|
+
Subcommands:
|
|
334
|
+
config Interactive global config setup (~/.taskexrc)
|
|
335
|
+
config --show Show saved config (masked secrets)
|
|
336
|
+
config --clear Remove global config file
|
|
337
|
+
|
|
338
|
+
Arguments:
|
|
339
|
+
[folder] Path to the call/project folder (optional — interactive if omitted)
|
|
340
|
+
|
|
341
|
+
Modes:
|
|
342
|
+
(default) Video analysis — compress, analyze, extract, compile
|
|
343
|
+
--dynamic Document-only mode — no video required, generates docs from context + request
|
|
344
|
+
--update-progress Track item completion via git since last analysis
|
|
345
|
+
--deep-dive (after video analysis) Generate explanatory docs per topic discussed
|
|
346
|
+
|
|
347
|
+
Core Options:
|
|
348
|
+
--name <name> Your name (skips interactive prompt)
|
|
349
|
+
--model <id> Gemini model to use (skips interactive selector)
|
|
350
|
+
Models: gemini-3.1-pro-preview, gemini-3-flash-preview,
|
|
351
|
+
gemini-2.5-pro, gemini-2.5-flash (default), gemini-2.5-flash-lite
|
|
352
|
+
--skip-upload Skip all Firebase Storage uploads
|
|
353
|
+
--force-upload Re-upload files even if they already exist in Storage
|
|
354
|
+
--no-storage-url Disable Storage URL optimization (force Gemini File API)
|
|
355
|
+
--skip-compression Skip video compression (use existing segments)
|
|
356
|
+
--skip-gemini Skip Gemini AI analysis
|
|
357
|
+
--resume Resume from last checkpoint (skip completed steps)
|
|
358
|
+
--reanalyze Force re-analysis of all segments
|
|
359
|
+
--dry-run Show what would be done without executing
|
|
360
|
+
|
|
361
|
+
Dynamic Mode:
|
|
362
|
+
--dynamic Enable document-only mode (no video required)
|
|
363
|
+
--request <text> What to generate — e.g. "Plan migration from X to Y"
|
|
364
|
+
(prompted interactively if omitted)
|
|
365
|
+
|
|
366
|
+
Progress Tracking:
|
|
367
|
+
--repo <path> Path to the project git repo (for change detection)
|
|
368
|
+
|
|
369
|
+
Configuration:
|
|
370
|
+
--gemini-key <key> Gemini API key (overrides .env / ~/.taskexrc)
|
|
371
|
+
--firebase-key <key> Firebase API key (overrides .env / ~/.taskexrc)
|
|
372
|
+
--firebase-project <id> Firebase project ID (overrides .env / ~/.taskexrc)
|
|
373
|
+
--firebase-bucket <bucket> Firebase storage bucket (overrides .env / ~/.taskexrc)
|
|
374
|
+
--firebase-domain <domain> Firebase auth domain (overrides .env / ~/.taskexrc)
|
|
375
|
+
|
|
376
|
+
Config resolution (highest wins):
|
|
377
|
+
CLI flags → env vars → CWD .env → ~/.taskexrc → package .env
|
|
378
|
+
|
|
379
|
+
Tuning:
|
|
380
|
+
--parallel <n> Max parallel uploads (default: 3)
|
|
381
|
+
--parallel-analysis <n> Concurrent segment analysis batches (default: 2)
|
|
382
|
+
--thinking-budget <n> Thinking token budget per segment (default: 24576)
|
|
383
|
+
--compilation-thinking-budget <n> Thinking tokens for final compilation (default: 10240)
|
|
384
|
+
--log-level <level> Log level: debug, info, warn, error (default: info)
|
|
385
|
+
--output <dir> Custom output directory for results
|
|
386
|
+
--no-focused-pass Disable focused re-analysis for weak segments
|
|
387
|
+
--no-learning Disable learning loop (historical budget adjustments)
|
|
388
|
+
--no-diff Disable diff comparison against previous runs
|
|
389
|
+
|
|
390
|
+
Info:
|
|
391
|
+
--help, -h Show this help message
|
|
392
|
+
--version, -v Show version
|
|
393
|
+
|
|
394
|
+
Examples:
|
|
395
|
+
taskex Interactive (cd into folder first)
|
|
396
|
+
taskex "call 1" Analyze a call (with video)
|
|
397
|
+
taskex --name "Jane" --skip-upload "call 1" Skip Firebase, set name
|
|
398
|
+
taskex --gemini-key "AIza..." --skip-upload "call 1" Pass API key inline (no .env)
|
|
399
|
+
taskex --model gemini-2.5-pro "call 1" Use Gemini 2.5 Pro model
|
|
400
|
+
taskex --resume "call 1" Resume interrupted run
|
|
401
|
+
taskex --deep-dive "call 1" Video analysis + deep dive docs
|
|
402
|
+
taskex --dynamic "my-project" Doc-only mode (prompted for request)
|
|
403
|
+
taskex --dynamic --request "Plan API migration" "specs" Dynamic with request
|
|
404
|
+
taskex --update-progress --repo "C:\\my-project" "call 1" Progress tracking via git
|
|
405
|
+
|
|
406
|
+
First-time setup:
|
|
407
|
+
taskex config Save API keys globally (~/.taskexrc)
|
|
408
|
+
taskex config --show View saved config
|
|
409
|
+
taskex config --clear Remove saved config
|
|
410
|
+
`);
|
|
411
|
+
// Signal early exit — pipeline checks for help flag before calling this
|
|
412
|
+
throw Object.assign(new Error('HELP_SHOWN'), { code: 'HELP_SHOWN' });
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
module.exports = { parseArgs, showHelp, discoverFolders, selectFolder, selectModel };
|