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/setup.js ADDED
@@ -0,0 +1,505 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Task Summary Extractor — Complete Setup
4
+ *
5
+ * Sets up everything needed from zero to working:
6
+ * 1. Checks prerequisites (Node.js, ffmpeg, git)
7
+ * 2. Installs npm dependencies
8
+ * 3. Creates .env with API key
9
+ * 4. Sets up .gitignore for local data
10
+ * 5. Creates sample call folder structure
11
+ * 6. Optionally creates a local working branch
12
+ * 7. Validates the pipeline loads
13
+ *
14
+ * Usage:
15
+ * node setup.js Full interactive setup
16
+ * node setup.js --check Validation only (no changes)
17
+ * node setup.js --silent Non-interactive (skip prompts, use defaults)
18
+ */
19
+
20
+ 'use strict';
21
+
22
+ const { execSync } = require('child_process');
23
+ const fs = require('fs');
24
+ const path = require('path');
25
+ const readline = require('readline');
26
+
27
+ // ─── Constants ────────────────────────────────────────────────────────────────
28
+
29
+ const ROOT = __dirname;
30
+ const ENV_FILE = path.join(ROOT, '.env');
31
+ const GITIGNORE_FILE = path.join(ROOT, '.gitignore');
32
+ const NODE_MODULES = path.join(ROOT, 'node_modules');
33
+ const SAMPLE_CALL = path.join(ROOT, 'sample-call');
34
+
35
+ const CHECK_ONLY = process.argv.includes('--check');
36
+ const SILENT = process.argv.includes('--silent');
37
+ const REQUIRED_NODE_VERSION = 18;
38
+
39
+ const ENV_TEMPLATE = `# ─── Required ──────────────────────────────────────────
40
+ GEMINI_API_KEY=YOUR_GEMINI_API_KEY_HERE
41
+
42
+ # ─── Optional: Firebase (skip if using --skip-upload) ──
43
+ # Get these from Firebase Console → Project Settings → General
44
+ # FIREBASE_API_KEY=
45
+ # FIREBASE_AUTH_DOMAIN=
46
+ # FIREBASE_PROJECT_ID=
47
+ # FIREBASE_STORAGE_BUCKET=
48
+ # FIREBASE_MESSAGING_SENDER_ID=
49
+ # FIREBASE_APP_ID=
50
+ # FIREBASE_MEASUREMENT_ID=
51
+
52
+ # ─── Optional: Tuning ─────────────────────────────────
53
+ # GEMINI_MODEL=gemini-2.5-flash
54
+ # VIDEO_SPEED=1.5
55
+ # VIDEO_SEGMENT_TIME=280
56
+ # VIDEO_PRESET=slow
57
+ # THINKING_BUDGET=24576
58
+ # COMPILATION_THINKING_BUDGET=10240
59
+ # LOG_LEVEL=info
60
+ # MAX_PARALLEL_UPLOADS=3
61
+ `;
62
+
63
+ const GITIGNORE_CONTENT = `# ─── Local Data (call folders, results, logs) ─────────
64
+ call */
65
+ logs/
66
+ gemini_runs/
67
+
68
+ # ─── Video Files ──────────────────────────────────────
69
+ **/*.mp4
70
+ **/*.mkv
71
+ **/*.avi
72
+ **/*.mov
73
+ **/*.webm
74
+
75
+ # ─── Environment & Dependencies ───────────────────────
76
+ .env
77
+ .env.local
78
+ node_modules/
79
+
80
+ # ─── OS / Editor ──────────────────────────────────────
81
+ .DS_Store
82
+ Thumbs.db
83
+ *.swp
84
+ *.swo
85
+ .vscode/settings.json
86
+ `;
87
+
88
+ const SAMPLE_README = `# Sample Meeting Folder
89
+
90
+ This is a sample folder. Replace with your actual recording.
91
+
92
+ ## How to use
93
+
94
+ 1. Drop your video file here (e.g., \`Meeting Recording.mp4\`)
95
+ 2. Add subtitles if available (e.g., \`Meeting Recording.vtt\`)
96
+ 3. Add any relevant docs in subfolders — the pipeline scans ALL subfolders recursively
97
+ 4. Run: \`taskex --name "Your Name" "sample-call"\`
98
+
99
+ ## Folder structure
100
+
101
+ \`\`\`
102
+ sample-call/
103
+ ├── your-video.mp4 ← Required: your recording
104
+ ├── your-video.vtt ← Recommended: subtitles
105
+ ├── agenda.md ← Optional: loose docs at root work too
106
+
107
+ ├── .tasks/ ← Optional: gets highest priority in AI prompt
108
+ │ ├── code-map.md
109
+ │ └── team.csv
110
+ ├── specs/ ← Any folder name — all are scanned
111
+ │ └── requirements.md
112
+ ├── notes/ ← Add as many context folders as you need
113
+ │ └── previous-decisions.md
114
+
115
+ ├── compressed/ ← Auto-generated: compressed segments
116
+ └── runs/ ← Auto-generated: analysis results
117
+ \`\`\`
118
+
119
+ ## Use case examples
120
+
121
+ - **Dev call**: .tasks/code-map.md, .tasks/current-sprint.md, docs/tech-debt.md
122
+ - **Client meeting**: requirements/scope.md, contracts/sow.md, .tasks/stakeholders.csv
123
+ - **Interview**: role/job-description.md, role/evaluation-rubric.md
124
+ - **Incident review**: systems/architecture.md, runbooks/service-x.md
125
+ `;
126
+
127
+ // ─── Helpers ──────────────────────────────────────────────────────────────────
128
+
129
+ const ICONS = { ok: '\u2705', fail: '\u274c', warn: '\u26a0\ufe0f', info: '\u2139\ufe0f', gear: '\u2699\ufe0f', rocket: '\ud83d\ude80', folder: '\ud83d\udcc1' };
130
+
131
+ function print(icon, msg) { console.log(` ${icon} ${msg}`); }
132
+ function printSub(msg) { console.log(` ${msg}`); }
133
+
134
+ function header(num, title) {
135
+ console.log(`\n${'─'.repeat(60)}`);
136
+ console.log(` Step ${num}: ${title}`);
137
+ console.log(`${'─'.repeat(60)}\n`);
138
+ }
139
+
140
+ function commandExists(cmd) {
141
+ try {
142
+ const where = process.platform === 'win32' ? 'where' : 'which';
143
+ execSync(`${where} ${cmd}`, { stdio: 'pipe' });
144
+ return true;
145
+ } catch { return false; }
146
+ }
147
+
148
+ function getVersion(cmd, flag = '--version') {
149
+ try {
150
+ return execSync(`${cmd} ${flag}`, { stdio: 'pipe' }).toString().trim().split('\n')[0];
151
+ } catch { return null; }
152
+ }
153
+
154
+ function ask(question) {
155
+ if (SILENT) return Promise.resolve('');
156
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
157
+ return new Promise(resolve => rl.question(question, a => { rl.close(); resolve(a.trim()); }));
158
+ }
159
+
160
+ async function confirm(question, defaultYes = true) {
161
+ if (SILENT) return defaultYes;
162
+ const suffix = defaultYes ? '[Y/n]' : '[y/N]';
163
+ const answer = await ask(` ${question} ${suffix} `);
164
+ if (answer === '') return defaultYes;
165
+ return answer.toLowerCase().startsWith('y');
166
+ }
167
+
168
+ // ─── Tracking ─────────────────────────────────────────────────────────────────
169
+
170
+ const counts = { pass: 0, fail: 0, warn: 0, action: 0 };
171
+ function pass(msg) { counts.pass++; print(ICONS.ok, msg); }
172
+ function fail(msg, hint) { counts.fail++; print(ICONS.fail, msg); if (hint) printSub(`→ ${hint}`); }
173
+ function warn(msg, hint) { counts.warn++; print(ICONS.warn, msg); if (hint) printSub(`→ ${hint}`); }
174
+ function action(msg) { counts.action++; print(ICONS.gear, msg); }
175
+
176
+ // ─── Step 1: Prerequisites ───────────────────────────────────────────────────
177
+
178
+ function checkPrerequisites() {
179
+ header(1, 'Prerequisites');
180
+
181
+ // Node.js
182
+ const nodeMajor = parseInt(process.versions.node.split('.')[0], 10);
183
+ if (nodeMajor >= REQUIRED_NODE_VERSION) {
184
+ pass(`Node.js v${process.versions.node} (requires ≥${REQUIRED_NODE_VERSION})`);
185
+ } else {
186
+ fail(`Node.js v${process.versions.node} — requires ≥${REQUIRED_NODE_VERSION}`, 'Download from https://nodejs.org/');
187
+ }
188
+
189
+ // ffmpeg
190
+ if (commandExists('ffmpeg')) {
191
+ const ver = getVersion('ffmpeg', '-version');
192
+ const short = ver ? ver.replace(/^ffmpeg version\s+/i, '').split(' ')[0] : 'found';
193
+ pass(`ffmpeg ${short}`);
194
+ } else if (process.platform === 'win32' && fs.existsSync('C:\\ffmpeg\\bin\\ffmpeg.exe')) {
195
+ pass('ffmpeg found at C:\\ffmpeg\\bin\\');
196
+ } else {
197
+ fail('ffmpeg not found in PATH',
198
+ process.platform === 'win32'
199
+ ? 'Download: https://www.gyan.dev/ffmpeg/builds/ → add to PATH or C:\\ffmpeg\\bin\\'
200
+ : 'Install: brew install ffmpeg (macOS) / apt install ffmpeg (Linux)');
201
+ }
202
+
203
+ // Git
204
+ if (commandExists('git')) {
205
+ const ver = getVersion('git') || 'found';
206
+ pass(`Git ${ver.replace('git version ', '')}`);
207
+ } else {
208
+ warn('Git not found — --update-progress feature will be unavailable',
209
+ 'Install from https://git-scm.com/downloads');
210
+ }
211
+ }
212
+
213
+ // ─── Step 2: Dependencies ────────────────────────────────────────────────────
214
+
215
+ async function installDependencies() {
216
+ header(2, 'Dependencies');
217
+
218
+ if (fs.existsSync(NODE_MODULES)) {
219
+ // Check if key packages exist
220
+ const hasGenai = fs.existsSync(path.join(NODE_MODULES, '@google', 'genai'));
221
+ const hasFirebase = fs.existsSync(path.join(NODE_MODULES, 'firebase'));
222
+ const hasDotenv = fs.existsSync(path.join(NODE_MODULES, 'dotenv'));
223
+
224
+ if (hasGenai && hasFirebase && hasDotenv) {
225
+ pass('All dependencies installed');
226
+ return;
227
+ }
228
+ warn('node_modules exists but some packages are missing');
229
+ }
230
+
231
+ if (CHECK_ONLY) {
232
+ fail('Dependencies not fully installed', 'Run: node setup.js');
233
+ return;
234
+ }
235
+
236
+ action('Running npm install...');
237
+ try {
238
+ execSync('npm install', { cwd: ROOT, stdio: 'inherit' });
239
+ pass('npm install completed');
240
+ } catch {
241
+ fail('npm install failed', 'Try: rm -rf node_modules && npm install');
242
+ }
243
+ }
244
+
245
+ // ─── Step 3: Environment ─────────────────────────────────────────────────────
246
+
247
+ async function setupEnvironment() {
248
+ header(3, 'Environment Configuration');
249
+
250
+ if (fs.existsSync(ENV_FILE)) {
251
+ const content = fs.readFileSync(ENV_FILE, 'utf8');
252
+ const hasKey = /GEMINI_API_KEY\s*=\s*\S+/.test(content) && !content.includes('YOUR_GEMINI_API_KEY_HERE');
253
+
254
+ if (hasKey) {
255
+ pass('.env file with GEMINI_API_KEY configured');
256
+ return;
257
+ }
258
+ warn('.env exists but GEMINI_API_KEY not set', 'Edit .env and add your key from https://aistudio.google.com/apikey');
259
+ return;
260
+ }
261
+
262
+ if (CHECK_ONLY) {
263
+ fail('.env file not found', 'Run: node setup.js');
264
+ return;
265
+ }
266
+
267
+ action('Creating .env from template...');
268
+ let content = ENV_TEMPLATE;
269
+
270
+ const key = await ask(' Enter your Gemini API key (or Enter to skip): ');
271
+ if (key) {
272
+ content = content.replace('YOUR_GEMINI_API_KEY_HERE', key);
273
+ pass('Gemini API key saved to .env');
274
+ } else {
275
+ warn('Gemini API key not set — edit .env later', 'Get key: https://aistudio.google.com/apikey');
276
+ }
277
+
278
+ fs.writeFileSync(ENV_FILE, content, 'utf8');
279
+ pass('.env file created');
280
+ }
281
+
282
+ // ─── Step 4: Git Ignore ──────────────────────────────────────────────────────
283
+
284
+ function setupGitignore() {
285
+ header(4, 'Git Configuration');
286
+
287
+ if (fs.existsSync(GITIGNORE_FILE)) {
288
+ const content = fs.readFileSync(GITIGNORE_FILE, 'utf8');
289
+
290
+ // Check for essential patterns
291
+ const hasCallFolders = content.includes('call */') || content.includes('call*/');
292
+ const hasEnv = content.includes('.env');
293
+ const hasLogs = content.includes('logs/');
294
+ const hasGeminiRuns = content.includes('gemini_runs/');
295
+ const hasNodeModules = content.includes('node_modules');
296
+
297
+ if (hasCallFolders && hasEnv && hasLogs && hasGeminiRuns && hasNodeModules) {
298
+ pass('.gitignore properly configured');
299
+ return;
300
+ }
301
+ }
302
+
303
+ if (CHECK_ONLY) {
304
+ warn('.gitignore needs updating', 'Run: node setup.js');
305
+ return;
306
+ }
307
+
308
+ action('Writing .gitignore (local data excluded from repo)...');
309
+ fs.writeFileSync(GITIGNORE_FILE, GITIGNORE_CONTENT, 'utf8');
310
+ pass('.gitignore configured — call folders, .env, logs, videos excluded');
311
+ }
312
+
313
+ // ─── Step 5: Sample Meeting Folder ───────────────────────────────────────────
314
+
315
+ async function setupSampleFolder() {
316
+ header(5, 'Sample Meeting Folder');
317
+
318
+ // Check if any call/meeting folder exists
319
+ const entries = fs.readdirSync(ROOT);
320
+ const hasCallFolder = entries.some(e => {
321
+ const fullPath = path.join(ROOT, e);
322
+ return fs.statSync(fullPath).isDirectory() &&
323
+ (e.startsWith('call') || e === 'sample-call') &&
324
+ !['node_modules', '.git', 'src', 'logs', 'gemini_runs'].includes(e);
325
+ });
326
+
327
+ if (hasCallFolder) {
328
+ pass('Meeting folder already exists');
329
+ return;
330
+ }
331
+
332
+ if (CHECK_ONLY) {
333
+ warn('No meeting folder found', 'Create one or run: node setup.js');
334
+ return;
335
+ }
336
+
337
+ const create = await confirm('Create a sample meeting folder with README?');
338
+ if (!create) {
339
+ warn('Skipped — create a meeting folder manually when ready');
340
+ return;
341
+ }
342
+
343
+ // Create sample-call structure
344
+ fs.mkdirSync(SAMPLE_CALL, { recursive: true });
345
+ fs.mkdirSync(path.join(SAMPLE_CALL, '.tasks'), { recursive: true });
346
+ fs.writeFileSync(path.join(SAMPLE_CALL, 'README.md'), SAMPLE_README, 'utf8');
347
+ fs.writeFileSync(path.join(SAMPLE_CALL, '.tasks', 'context.md'),
348
+ '# Project Context\n\nDescribe your project, team, or meeting context here.\nThe AI uses this to better understand what\'s discussed in the recording.\n\n## Examples by Use Case\n\n### Dev Project\n- `src/api/` — REST API endpoints\n- `src/models/` — Database models\n- Sprint: TICKET-123 (auth), TICKET-456 (payments)\n\n### Client Project\n- Deliverable: Phase 1 UI redesign\n- Stakeholders: Jane (PM), Ahmed (Design Lead)\n- Contract scope: 6 screens, responsive, Q1 deadline\n\n### General\n- Meeting purpose: [describe]\n- Key participants: [list]\n- Relevant docs: [reference]\n', 'utf8');
349
+
350
+ pass('sample-call/ created with README and .tasks/ templates');
351
+ printSub('Drop your video file in sample-call/ to get started');
352
+ printSub('See README.md for .tasks/ examples by use case (dev, client, interview, etc.)');
353
+ }
354
+
355
+ // ─── Step 6: Local Branch ────────────────────────────────────────────────────
356
+
357
+ async function setupBranch() {
358
+ header(6, 'Working Branch');
359
+
360
+ if (!commandExists('git')) {
361
+ warn('Git not available — skipping branch setup');
362
+ return;
363
+ }
364
+
365
+ // Check if we're in a git repo
366
+ try {
367
+ execSync('git rev-parse --git-dir', { cwd: ROOT, stdio: 'pipe' });
368
+ } catch {
369
+ warn('Not a git repository — skipping branch setup');
370
+ return;
371
+ }
372
+
373
+ // Check current branch
374
+ let currentBranch;
375
+ try {
376
+ currentBranch = execSync('git branch --show-current', { cwd: ROOT, stdio: 'pipe' }).toString().trim();
377
+ } catch {
378
+ currentBranch = 'unknown';
379
+ }
380
+
381
+ if (currentBranch.startsWith('local/')) {
382
+ pass(`Already on local branch: ${currentBranch}`);
383
+ return;
384
+ }
385
+
386
+ if (CHECK_ONLY) {
387
+ warn(`On branch ${currentBranch} — consider creating a local branch`,
388
+ 'Run: git checkout -b local/my-workspace');
389
+ return;
390
+ }
391
+
392
+ print(ICONS.info, `Current branch: ${currentBranch}`);
393
+ printSub('Recommended: create a local branch to separate your data from tool code.');
394
+ printSub('This lets you pull updates from main without conflicts.');
395
+ console.log('');
396
+
397
+ const create = await confirm('Create local/my-workspace branch?');
398
+ if (!create) {
399
+ warn('Skipped — you can create it later: git checkout -b local/my-workspace');
400
+ return;
401
+ }
402
+
403
+ try {
404
+ execSync('git checkout -b local/my-workspace', { cwd: ROOT, stdio: 'pipe' });
405
+ pass('Created and switched to branch: local/my-workspace');
406
+ } catch (err) {
407
+ const msg = err.stderr ? err.stderr.toString().trim() : '';
408
+ if (msg.includes('already exists')) {
409
+ try {
410
+ execSync('git checkout local/my-workspace', { cwd: ROOT, stdio: 'pipe' });
411
+ pass('Switched to existing branch: local/my-workspace');
412
+ } catch {
413
+ warn('Branch local/my-workspace exists but could not switch', 'Run: git checkout local/my-workspace');
414
+ }
415
+ } else {
416
+ warn('Could not create branch', msg || 'Run: git checkout -b local/my-workspace');
417
+ }
418
+ }
419
+ }
420
+
421
+ // ─── Step 7: Validation ──────────────────────────────────────────────────────
422
+
423
+ function validate() {
424
+ header(7, 'Validation');
425
+
426
+ // Check pipeline loads
427
+ if (!fs.existsSync(NODE_MODULES)) {
428
+ warn('Skipping pipeline validation — no node_modules');
429
+ return;
430
+ }
431
+
432
+ try {
433
+ execSync('node -e "require(\'./src/pipeline\')"', { cwd: ROOT, stdio: 'pipe', timeout: 15000 });
434
+ pass('Pipeline module loads successfully');
435
+ } catch (err) {
436
+ const msg = err.stderr ? err.stderr.toString().split('\n')[0] : '';
437
+ fail('Pipeline failed to load', msg || 'Run: node -e "require(\'./src/pipeline\')" for details');
438
+ }
439
+
440
+ // Check version
441
+ try {
442
+ const version = execSync('node process_and_upload.js --version', { cwd: ROOT, stdio: 'pipe', timeout: 10000 })
443
+ .toString().trim().split('\n').pop();
444
+ pass(`Version: ${version}`);
445
+ } catch {
446
+ warn('Could not read version');
447
+ }
448
+ }
449
+
450
+ // ─── Main ─────────────────────────────────────────────────────────────────────
451
+
452
+ async function main() {
453
+ console.log('');
454
+ console.log(' ╔══════════════════════════════════════════╗');
455
+ console.log(' ║ Task Summary Extractor — Setup v8.0.0 ║');
456
+ console.log(' ╚══════════════════════════════════════════╝');
457
+
458
+ if (CHECK_ONLY) {
459
+ console.log('\n Mode: Validation only (--check)\n');
460
+ }
461
+
462
+ // Run all steps
463
+ checkPrerequisites();
464
+ await installDependencies();
465
+ await setupEnvironment();
466
+ setupGitignore();
467
+ await setupSampleFolder();
468
+ await setupBranch();
469
+ validate();
470
+
471
+ // ── Summary ──────────────────────────────────────────────────────────
472
+ console.log(`\n${'═'.repeat(60)}`);
473
+ console.log(' Summary');
474
+ console.log(`${'═'.repeat(60)}\n`);
475
+
476
+ console.log(` ${ICONS.ok} Passed: ${counts.pass}`);
477
+ if (counts.warn > 0) console.log(` ${ICONS.warn} Warnings: ${counts.warn}`);
478
+ if (counts.fail > 0) console.log(` ${ICONS.fail} Failed: ${counts.fail}`);
479
+ if (counts.action > 0) console.log(` ${ICONS.gear} Actions taken: ${counts.action}`);
480
+ console.log('');
481
+
482
+ if (counts.fail === 0) {
483
+ console.log(` ${ICONS.rocket} Setup complete! You're ready to go.\n`);
484
+ console.log(' Next steps:');
485
+ console.log(' ──────────');
486
+ console.log(' 1. Drop a video in your call folder');
487
+ console.log(' 2. Run: taskex --name "Your Name" "call 1"');
488
+ console.log(' or: node process_and_upload.js --name "Your Name" "call 1"');
489
+ console.log(' 3. Open: call 1/runs/{timestamp}/results.md');
490
+ console.log('');
491
+ console.log(' Docs: README.md · QUICK_START.md · ARCHITECTURE.md');
492
+ console.log('');
493
+ } else {
494
+ console.log(` ${ICONS.fail} ${counts.fail} issue(s) need fixing.\n`);
495
+ console.log(' Fix the issues above, then re-run:');
496
+ console.log(' node setup.js');
497
+ console.log('');
498
+ process.exit(1);
499
+ }
500
+ }
501
+
502
+ main().catch(err => {
503
+ console.error(`\nSetup failed: ${err.message}`);
504
+ process.exit(1);
505
+ });