mindcontext-cli 0.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/dist/cli.js ADDED
@@ -0,0 +1,1443 @@
1
+ #!/usr/bin/env node
2
+
3
+
4
+ // src/commands/init.ts
5
+ import { existsSync as existsSync3, mkdirSync as mkdirSync2 } from "fs";
6
+ import { createInterface } from "readline";
7
+
8
+ // src/lib/config.ts
9
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
10
+ import { homedir } from "os";
11
+ import { join } from "path";
12
+
13
+ // src/lib/machine-id.ts
14
+ import { createHash } from "crypto";
15
+ import { hostname, userInfo } from "os";
16
+ import { execSync } from "child_process";
17
+ function getMachineId() {
18
+ const host = hostname();
19
+ const user = userInfo().username;
20
+ let gitEmail = "";
21
+ try {
22
+ gitEmail = execSync("git config user.email", { encoding: "utf8" }).trim();
23
+ } catch {
24
+ }
25
+ const hash = createHash("sha256").update(`${host}-${user}-${gitEmail}`).digest("hex").substring(0, 8);
26
+ const name = `${user}-${host}`.toLowerCase().replace(/[^a-z0-9-]/g, "-");
27
+ return { name, id: hash };
28
+ }
29
+ function getTimestamp() {
30
+ const now = /* @__PURE__ */ new Date();
31
+ return now.toISOString().replace(/:/g, "-").replace(/\.\d{3}Z$/, "");
32
+ }
33
+ function generateUpdateFilename() {
34
+ const { name, id } = getMachineId();
35
+ const timestamp = getTimestamp();
36
+ return `${timestamp}_${name}_${id}.json`;
37
+ }
38
+
39
+ // src/lib/config.ts
40
+ var MINDCONTEXT_DIR = join(homedir(), ".mindcontext");
41
+ var CONFIG_FILE = join(MINDCONTEXT_DIR, "config.json");
42
+ var REPO_DIR = join(MINDCONTEXT_DIR, "repo");
43
+ var PENDING_FILE = join(MINDCONTEXT_DIR, "pending.json");
44
+ function getMindcontextDir() {
45
+ return MINDCONTEXT_DIR;
46
+ }
47
+ function getRepoDir() {
48
+ return REPO_DIR;
49
+ }
50
+ function isInitialized() {
51
+ return existsSync(CONFIG_FILE) && existsSync(REPO_DIR);
52
+ }
53
+ function createDefaultConfig() {
54
+ const machine = getMachineId();
55
+ return {
56
+ version: "1.0",
57
+ dashboard_repo: "",
58
+ dashboard_url: "",
59
+ projects: {},
60
+ machine
61
+ };
62
+ }
63
+ function readConfig() {
64
+ if (!existsSync(CONFIG_FILE)) {
65
+ return null;
66
+ }
67
+ try {
68
+ const content = readFileSync(CONFIG_FILE, "utf8");
69
+ return JSON.parse(content);
70
+ } catch {
71
+ return null;
72
+ }
73
+ }
74
+ function writeConfig(config2) {
75
+ if (!existsSync(MINDCONTEXT_DIR)) {
76
+ mkdirSync(MINDCONTEXT_DIR, { recursive: true });
77
+ }
78
+ writeFileSync(CONFIG_FILE, JSON.stringify(config2, null, 2));
79
+ }
80
+ function readPending() {
81
+ if (!existsSync(PENDING_FILE)) {
82
+ return [];
83
+ }
84
+ try {
85
+ const content = readFileSync(PENDING_FILE, "utf8");
86
+ return JSON.parse(content);
87
+ } catch {
88
+ return [];
89
+ }
90
+ }
91
+ function writePending(pending) {
92
+ writeFileSync(PENDING_FILE, JSON.stringify(pending, null, 2));
93
+ }
94
+ function addPending(message) {
95
+ const pending = readPending();
96
+ pending.push({
97
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
98
+ message
99
+ });
100
+ writePending(pending);
101
+ }
102
+ function clearPending() {
103
+ if (existsSync(PENDING_FILE)) {
104
+ writeFileSync(PENDING_FILE, "[]");
105
+ }
106
+ }
107
+ function getProjectDir(projectName) {
108
+ return join(REPO_DIR, "projects", projectName, "updates");
109
+ }
110
+ function ensureProjectDir(projectName) {
111
+ const dir = getProjectDir(projectName);
112
+ if (!existsSync(dir)) {
113
+ mkdirSync(dir, { recursive: true });
114
+ }
115
+ }
116
+
117
+ // src/lib/git-ops.ts
118
+ import { execSync as execSync2 } from "child_process";
119
+ import { existsSync as existsSync2 } from "fs";
120
+ var execOptions = {
121
+ encoding: "utf8",
122
+ stdio: ["pipe", "pipe", "pipe"]
123
+ };
124
+ function cloneRepo(repoUrl, targetDir) {
125
+ execSync2(`git clone "${repoUrl}" "${targetDir}"`, execOptions);
126
+ }
127
+ function pull() {
128
+ const repoDir = getRepoDir();
129
+ if (!existsSync2(repoDir)) {
130
+ return { success: false, message: "Repository not initialized" };
131
+ }
132
+ try {
133
+ const output = execSync2("git pull --rebase origin main", {
134
+ ...execOptions,
135
+ cwd: repoDir
136
+ });
137
+ return { success: true, message: output.trim() };
138
+ } catch (error) {
139
+ const message = error instanceof Error ? error.message : "Pull failed";
140
+ return { success: false, message };
141
+ }
142
+ }
143
+ function stageAll() {
144
+ const repoDir = getRepoDir();
145
+ execSync2("git add -A", { ...execOptions, cwd: repoDir });
146
+ }
147
+ function commit(message) {
148
+ const repoDir = getRepoDir();
149
+ try {
150
+ const status = execSync2("git status --porcelain", {
151
+ ...execOptions,
152
+ cwd: repoDir
153
+ });
154
+ if (!status.trim()) {
155
+ return { success: true, message: "Nothing to commit" };
156
+ }
157
+ stageAll();
158
+ execSync2(`git commit -m "${message.replace(/"/g, '\\"')}"`, {
159
+ ...execOptions,
160
+ cwd: repoDir
161
+ });
162
+ return { success: true, message: "Committed" };
163
+ } catch (error) {
164
+ const msg = error instanceof Error ? error.message : "Commit failed";
165
+ return { success: false, message: msg };
166
+ }
167
+ }
168
+ function push() {
169
+ const repoDir = getRepoDir();
170
+ try {
171
+ execSync2("git push origin main", {
172
+ ...execOptions,
173
+ cwd: repoDir,
174
+ timeout: 3e4
175
+ });
176
+ return { success: true, message: "Pushed to remote" };
177
+ } catch (error) {
178
+ const message = error instanceof Error ? error.message : "Push failed";
179
+ return { success: false, message };
180
+ }
181
+ }
182
+ function sync(commitMessage) {
183
+ const pending = readPending();
184
+ if (pending.length > 0) {
185
+ const pushResult2 = push();
186
+ if (pushResult2.success) {
187
+ clearPending();
188
+ }
189
+ }
190
+ const commitResult = commit(commitMessage);
191
+ if (!commitResult.success) {
192
+ return {
193
+ committed: false,
194
+ pushed: false,
195
+ message: commitResult.message
196
+ };
197
+ }
198
+ if (commitResult.message === "Nothing to commit") {
199
+ return {
200
+ committed: false,
201
+ pushed: false,
202
+ message: "No changes to sync"
203
+ };
204
+ }
205
+ const pushResult = push();
206
+ if (!pushResult.success) {
207
+ addPending(commitMessage);
208
+ return {
209
+ committed: true,
210
+ pushed: false,
211
+ message: "Committed locally. Push pending (offline)"
212
+ };
213
+ }
214
+ return {
215
+ committed: true,
216
+ pushed: true,
217
+ message: "Synced successfully"
218
+ };
219
+ }
220
+ function getRecentCommits(projectPath, limit = 5) {
221
+ try {
222
+ const output = execSync2(
223
+ `git log --oneline -${limit} --format="%s"`,
224
+ { ...execOptions, cwd: projectPath }
225
+ );
226
+ return output.trim().split("\n").filter((line) => line.length > 0);
227
+ } catch {
228
+ return [];
229
+ }
230
+ }
231
+
232
+ // src/commands/init.ts
233
+ async function prompt(question) {
234
+ const rl = createInterface({
235
+ input: process.stdin,
236
+ output: process.stdout
237
+ });
238
+ return new Promise((resolve5) => {
239
+ rl.question(question, (answer) => {
240
+ rl.close();
241
+ resolve5(answer.trim());
242
+ });
243
+ });
244
+ }
245
+ async function init(options = {}) {
246
+ const mindcontextDir = getMindcontextDir();
247
+ const repoDir = getRepoDir();
248
+ if (isInitialized()) {
249
+ const config3 = readConfig();
250
+ if (!options.quiet) {
251
+ console.log("MindContext is already initialized.");
252
+ console.log(` Directory: ${mindcontextDir}`);
253
+ console.log(` Dashboard: ${config3?.dashboard_url || "Not configured"}`);
254
+ console.log(` Projects: ${Object.keys(config3?.projects || {}).length}`);
255
+ console.log("");
256
+ console.log('Run "mc reset" to start fresh.');
257
+ }
258
+ return;
259
+ }
260
+ if (!options.quiet) {
261
+ console.log("Initializing MindContext...\n");
262
+ }
263
+ if (!existsSync3(mindcontextDir)) {
264
+ mkdirSync2(mindcontextDir, { recursive: true });
265
+ }
266
+ let dashboardRepo = "";
267
+ if (!options.quiet) {
268
+ console.log("Enter your dashboard repository URL.");
269
+ console.log("(Create one from https://github.com/tmsjngx0/mindcontext-template)\n");
270
+ dashboardRepo = await prompt("Dashboard repo URL (git@github.com:user/repo.git): ");
271
+ }
272
+ if (!dashboardRepo) {
273
+ if (!options.quiet) {
274
+ console.log("\nNo dashboard URL provided. You can configure it later with:");
275
+ console.log(" mc config --dashboard-repo <url>");
276
+ }
277
+ const config3 = createDefaultConfig();
278
+ writeConfig(config3);
279
+ if (!options.quiet) {
280
+ console.log(`
281
+ Created: ${mindcontextDir}/config.json`);
282
+ }
283
+ return;
284
+ }
285
+ if (!options.quiet) {
286
+ console.log(`
287
+ Cloning dashboard repository...`);
288
+ }
289
+ try {
290
+ cloneRepo(dashboardRepo, repoDir);
291
+ } catch (error) {
292
+ console.error("Failed to clone repository:", error instanceof Error ? error.message : error);
293
+ process.exit(1);
294
+ }
295
+ let dashboardUrl = "";
296
+ const match = dashboardRepo.match(/github\.com[:/]([^/]+)\/([^/.]+)/);
297
+ if (match) {
298
+ dashboardUrl = `https://${match[1]}.github.io/${match[2]}`;
299
+ }
300
+ const config2 = createDefaultConfig();
301
+ config2.dashboard_repo = dashboardRepo;
302
+ config2.dashboard_url = dashboardUrl;
303
+ writeConfig(config2);
304
+ if (!options.quiet) {
305
+ console.log("\n\u2713 MindContext initialized!");
306
+ console.log(` Directory: ${mindcontextDir}`);
307
+ console.log(` Dashboard: ${dashboardUrl}`);
308
+ console.log("");
309
+ console.log("Next steps:");
310
+ console.log(" cd <your-project>");
311
+ console.log(" mc connect");
312
+ }
313
+ }
314
+
315
+ // src/commands/connect.ts
316
+ import { basename as basename2, resolve, join as join3 } from "path";
317
+ import { existsSync as existsSync5, mkdirSync as mkdirSync3, writeFileSync as writeFileSync2 } from "fs";
318
+
319
+ // src/parsers/openspec.ts
320
+ import { existsSync as existsSync4, readdirSync, readFileSync as readFileSync2 } from "fs";
321
+ import { join as join2, basename } from "path";
322
+ function hasOpenSpec(projectPath) {
323
+ const openspecDir = join2(projectPath, "openspec");
324
+ return existsSync4(openspecDir) && existsSync4(join2(openspecDir, "changes"));
325
+ }
326
+ function parseTasksFile(filepath) {
327
+ if (!existsSync4(filepath)) {
328
+ return { total: 0, complete: 0 };
329
+ }
330
+ const content = readFileSync2(filepath, "utf8");
331
+ const lines = content.split("\n");
332
+ let total = 0;
333
+ let complete = 0;
334
+ for (const line of lines) {
335
+ if (/^\s*-\s*\[\s*\]\s/.test(line)) {
336
+ total++;
337
+ } else if (/^\s*-\s*\[x\]\s/i.test(line)) {
338
+ total++;
339
+ complete++;
340
+ }
341
+ }
342
+ return { total, complete };
343
+ }
344
+ function determineStatus(tasksTotal, tasksComplete) {
345
+ if (tasksTotal === 0) {
346
+ return "proposed";
347
+ }
348
+ if (tasksComplete === 0) {
349
+ return "proposed";
350
+ }
351
+ if (tasksComplete === tasksTotal) {
352
+ return "done";
353
+ }
354
+ return "in_progress";
355
+ }
356
+ function parseChange(changePath) {
357
+ const id = basename(changePath);
358
+ if (id === "archive") {
359
+ return null;
360
+ }
361
+ const tasksFile = join2(changePath, "tasks.md");
362
+ const { total, complete } = parseTasksFile(tasksFile);
363
+ return {
364
+ id,
365
+ tasksTotal: total,
366
+ tasksComplete: complete,
367
+ status: determineStatus(total, complete)
368
+ };
369
+ }
370
+ function parseOpenSpec(projectPath) {
371
+ if (!hasOpenSpec(projectPath)) {
372
+ return { found: false, changes: [], activeChange: null };
373
+ }
374
+ const changesDir = join2(projectPath, "openspec", "changes");
375
+ const entries = readdirSync(changesDir, { withFileTypes: true });
376
+ const changes = [];
377
+ for (const entry of entries) {
378
+ if (entry.isDirectory()) {
379
+ const change = parseChange(join2(changesDir, entry.name));
380
+ if (change) {
381
+ changes.push(change);
382
+ }
383
+ }
384
+ }
385
+ let activeChange = null;
386
+ for (const change of changes) {
387
+ if (change.status === "in_progress") {
388
+ activeChange = change;
389
+ break;
390
+ }
391
+ }
392
+ if (!activeChange) {
393
+ for (const change of changes) {
394
+ if (change.status === "review") {
395
+ activeChange = change;
396
+ break;
397
+ }
398
+ }
399
+ }
400
+ if (!activeChange) {
401
+ for (const change of changes) {
402
+ if (change.status === "proposed" && change.tasksTotal > 0) {
403
+ activeChange = change;
404
+ break;
405
+ }
406
+ }
407
+ }
408
+ return { found: true, changes, activeChange };
409
+ }
410
+ function getOpenSpecProgress(projectPath) {
411
+ const result = parseOpenSpec(projectPath);
412
+ if (!result.found || !result.activeChange) {
413
+ return {
414
+ source: "manual",
415
+ tasks_done: 0,
416
+ tasks_total: 0
417
+ };
418
+ }
419
+ return {
420
+ source: "openspec",
421
+ change: result.activeChange.id,
422
+ tasks_done: result.activeChange.tasksComplete,
423
+ tasks_total: result.activeChange.tasksTotal
424
+ };
425
+ }
426
+
427
+ // src/lib/templates.ts
428
+ var COMMAND_TEMPLATES = {
429
+ "prime.md": `---
430
+ description: Load context and show what to work on
431
+ ---
432
+
433
+ # Prime Context
434
+
435
+ Load project context at the start of your session.
436
+
437
+ ## Step 1: Get Current Context
438
+
439
+ \`\`\`bash
440
+ mc context --json
441
+ \`\`\`
442
+
443
+ ## Step 2: Load Last Session Notes
444
+
445
+ From the context output, identify:
446
+ - **Last session summary** - What was accomplished
447
+ - **Next tasks** - What was planned for this session
448
+ - **Current change** - The OpenSpec change being worked on
449
+ - **Progress** - Tasks completed vs total
450
+
451
+ ## Step 3: Show Context Summary
452
+
453
+ Display to the user:
454
+
455
+ \`\`\`
456
+ SESSION CONTEXT LOADED
457
+ \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
458
+
459
+ Project: [project name]
460
+ Change: [current openspec change]
461
+ Progress: [tasks_done]/[tasks_total] ([percentage]%)
462
+
463
+ Last Session:
464
+ - [notes from last update]
465
+
466
+ Planned for This Session:
467
+ - [next tasks from last update]
468
+ \`\`\`
469
+
470
+ ## Step 4: Suggest Next Action
471
+
472
+ Based on context, suggest what to work on next.
473
+ `,
474
+ "update.md": `---
475
+ description: Save session context and sync progress
476
+ ---
477
+
478
+ # Update Context
479
+
480
+ Save your session progress with auto-generated context.
481
+
482
+ ## Step 1: Generate Session Summary
483
+
484
+ From your conversation context, summarize what was accomplished:
485
+ - Recent code changes and commits
486
+ - Tasks completed
487
+ - Features implemented or bugs fixed
488
+
489
+ Create 3-5 concise bullet points.
490
+
491
+ ## Step 2: Generate Next Tasks
492
+
493
+ From OpenSpec and conversation context, identify what should be done next:
494
+ - Remaining tasks from current OpenSpec change
495
+ - Blockers or pending items mentioned
496
+
497
+ Create 2-4 actionable next task items.
498
+
499
+ ## Step 3: Run mc sync
500
+
501
+ \`\`\`bash
502
+ mc sync --notes "First accomplishment
503
+ Second accomplishment" --next "Next task 1
504
+ Next task 2"
505
+ \`\`\`
506
+
507
+ Use multiline strings - each line becomes one item.
508
+
509
+ ## Step 4: Show Confirmation
510
+
511
+ \`\`\`
512
+ PROGRESS SYNCED
513
+ \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
514
+
515
+ Session Summary:
516
+ - [generated notes]
517
+
518
+ Next Session:
519
+ - [generated next tasks]
520
+ \`\`\`
521
+ `,
522
+ "progress.md": `---
523
+ description: Show progress
524
+ ---
525
+
526
+ Run \`mc progress\` to see current progress in the terminal.
527
+
528
+ Options:
529
+ - \`--web\` - Open the web dashboard in your browser
530
+
531
+ This aggregates all update files to show:
532
+ - Current change and task completion
533
+ - Recent activity across machines
534
+ - Team member status (if collaborative)
535
+ `,
536
+ "context.md": `---
537
+ description: Output current context
538
+ ---
539
+
540
+ Run \`mc context\` to output the current project context.
541
+
542
+ Options:
543
+ - \`--json\` - Output as JSON (for integration with other tools)
544
+
545
+ This shows:
546
+ - Project connection status
547
+ - Current OpenSpec change and progress
548
+ - Last update details
549
+ - Team activity summary
550
+ `
551
+ };
552
+ var HOOK_TEMPLATES = {
553
+ "session-end.js": `const { execSync } = require('child_process');
554
+
555
+ module.exports = async function() {
556
+ try {
557
+ execSync('mc sync --quiet', {
558
+ stdio: 'inherit',
559
+ timeout: 10000
560
+ });
561
+ } catch (e) {
562
+ // Sync failed silently - don't block session end
563
+ }
564
+ };
565
+ `
566
+ };
567
+
568
+ // src/commands/connect.ts
569
+ async function connect(options = {}) {
570
+ if (!isInitialized()) {
571
+ console.error('MindContext is not initialized. Run "mc init" first.');
572
+ process.exit(1);
573
+ }
574
+ const config2 = readConfig();
575
+ if (!config2) {
576
+ console.error("Failed to read config.");
577
+ process.exit(1);
578
+ }
579
+ const projectPath = resolve(process.cwd());
580
+ const projectName = options.name || basename2(projectPath);
581
+ if (config2.projects[projectName]) {
582
+ if (!options.quiet) {
583
+ console.log(`Project "${projectName}" is already connected.`);
584
+ console.log(` Path: ${config2.projects[projectName].path}`);
585
+ console.log(` Category: ${config2.projects[projectName].category}`);
586
+ console.log(` OpenSpec: ${config2.projects[projectName].openspec ? "Yes" : "No"}`);
587
+ }
588
+ return;
589
+ }
590
+ const openspec = hasOpenSpec(projectPath);
591
+ config2.projects[projectName] = {
592
+ path: projectPath,
593
+ openspec,
594
+ category: options.category || "default"
595
+ };
596
+ writeConfig(config2);
597
+ ensureProjectDir(projectName);
598
+ const claudeDir = join3(projectPath, ".claude");
599
+ const commandsDir = join3(claudeDir, "commands", "mc");
600
+ if (!existsSync5(commandsDir)) {
601
+ mkdirSync3(commandsDir, { recursive: true });
602
+ for (const [filename, content] of Object.entries(COMMAND_TEMPLATES)) {
603
+ writeFileSync2(join3(commandsDir, filename), content);
604
+ }
605
+ if (!options.quiet) {
606
+ console.log(`\u2713 Created .claude/commands/mc/`);
607
+ }
608
+ }
609
+ if (options.withHooks) {
610
+ const hooksDir = join3(claudeDir, "hooks");
611
+ if (!existsSync5(hooksDir)) {
612
+ mkdirSync3(hooksDir, { recursive: true });
613
+ for (const [filename, content] of Object.entries(HOOK_TEMPLATES)) {
614
+ writeFileSync2(join3(hooksDir, filename), content);
615
+ }
616
+ if (!options.quiet) {
617
+ console.log(`\u2713 Created .claude/hooks/`);
618
+ }
619
+ }
620
+ }
621
+ if (!options.quiet) {
622
+ console.log(`\u2713 Connected "${projectName}"`);
623
+ console.log(` Path: ${projectPath}`);
624
+ console.log(` Category: ${options.category || "default"}`);
625
+ console.log(` OpenSpec: ${openspec ? "Detected" : "Not found"}`);
626
+ console.log("");
627
+ console.log('Next: Run "mc sync" to create your first update.');
628
+ }
629
+ }
630
+
631
+ // src/commands/sync.ts
632
+ import { basename as basename3, resolve as resolve2 } from "path";
633
+
634
+ // src/lib/update-file.ts
635
+ import { existsSync as existsSync6, readdirSync as readdirSync2, readFileSync as readFileSync3, writeFileSync as writeFileSync3 } from "fs";
636
+ import { join as join4 } from "path";
637
+ function createUpdateFile(projectName, progress2, context2, recentCommits) {
638
+ const { name, id } = getMachineId();
639
+ const filename = generateUpdateFilename();
640
+ const filepath = join4(getProjectDir(projectName), filename);
641
+ const update = {
642
+ version: "1.0",
643
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
644
+ machine: name,
645
+ machine_id: id,
646
+ project: projectName,
647
+ progress: progress2,
648
+ context: context2,
649
+ recent_commits: recentCommits
650
+ };
651
+ ensureProjectDir(projectName);
652
+ writeFileSync3(filepath, JSON.stringify(update, null, 2));
653
+ return filepath;
654
+ }
655
+ function readUpdateFiles(projectName) {
656
+ const dir = getProjectDir(projectName);
657
+ if (!existsSync6(dir)) {
658
+ return [];
659
+ }
660
+ const files = readdirSync2(dir).filter((f) => f.endsWith(".json"));
661
+ const updates = [];
662
+ for (const file of files) {
663
+ try {
664
+ const content = readFileSync3(join4(dir, file), "utf8");
665
+ updates.push(JSON.parse(content));
666
+ } catch {
667
+ }
668
+ }
669
+ return updates.sort(
670
+ (a, b) => new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime()
671
+ );
672
+ }
673
+ function getLatestUpdate(projectName) {
674
+ const updates = readUpdateFiles(projectName);
675
+ return updates[0] || null;
676
+ }
677
+ function getLatestByMachine(projectName) {
678
+ const updates = readUpdateFiles(projectName);
679
+ const byMachine = /* @__PURE__ */ new Map();
680
+ for (const update of updates) {
681
+ if (!byMachine.has(update.machine_id)) {
682
+ byMachine.set(update.machine_id, update);
683
+ }
684
+ }
685
+ return byMachine;
686
+ }
687
+ function getRecentUpdates(projectName, limit = 10) {
688
+ const updates = readUpdateFiles(projectName);
689
+ return updates.slice(0, limit);
690
+ }
691
+
692
+ // src/commands/sync.ts
693
+ async function sync2(options = {}) {
694
+ if (!isInitialized()) {
695
+ if (!options.quiet) {
696
+ console.error('MindContext is not initialized. Run "mc init" first.');
697
+ }
698
+ process.exit(1);
699
+ }
700
+ const config2 = readConfig();
701
+ if (!config2) {
702
+ if (!options.quiet) {
703
+ console.error("Failed to read config.");
704
+ }
705
+ process.exit(1);
706
+ }
707
+ const projectPath = resolve2(process.cwd());
708
+ const projectName = basename3(projectPath);
709
+ const project = config2.projects[projectName];
710
+ if (!project) {
711
+ if (!options.quiet) {
712
+ console.error(`Project "${projectName}" is not connected.`);
713
+ console.error('Run "mc connect" first.');
714
+ }
715
+ process.exit(1);
716
+ }
717
+ const progress2 = getOpenSpecProgress(projectPath);
718
+ const context2 = {
719
+ current_task: progress2.change ? `Working on ${progress2.change}` : void 0,
720
+ status: options.status || (progress2.tasks_done > 0 ? "in_progress" : "idle"),
721
+ notes: options.notes || [],
722
+ next: options.next || []
723
+ };
724
+ if (!options.quiet) {
725
+ console.log(`Syncing "${projectName}"...`);
726
+ if (progress2.source === "openspec") {
727
+ console.log(` Change: ${progress2.change}`);
728
+ console.log(` Progress: ${progress2.tasks_done}/${progress2.tasks_total}`);
729
+ }
730
+ }
731
+ const recentCommits = getRecentCommits(projectPath, 5);
732
+ if (options.dryRun) {
733
+ if (!options.quiet) {
734
+ console.log("\n[Dry run] Would create update file:");
735
+ console.log(JSON.stringify({ progress: progress2, context: context2, recent_commits: recentCommits }, null, 2));
736
+ }
737
+ return;
738
+ }
739
+ const filepath = createUpdateFile(projectName, progress2, context2, recentCommits);
740
+ if (!options.quiet) {
741
+ console.log(` Created: ${filepath}`);
742
+ }
743
+ const commitMessage = `chore(progress): sync ${config2.machine.name}`;
744
+ const result = sync(commitMessage);
745
+ if (!options.quiet) {
746
+ if (result.committed && result.pushed) {
747
+ console.log("\u2713 Synced successfully");
748
+ } else if (result.committed) {
749
+ console.log("\u2713 Committed locally (push pending)");
750
+ console.log(' Run "mc push" when online to push changes.');
751
+ } else {
752
+ console.log(` ${result.message}`);
753
+ }
754
+ }
755
+ }
756
+
757
+ // src/commands/pull.ts
758
+ async function pull2(options = {}) {
759
+ if (!isInitialized()) {
760
+ if (!options.quiet) {
761
+ console.error('MindContext is not initialized. Run "mc init" first.');
762
+ }
763
+ process.exit(1);
764
+ }
765
+ if (!options.quiet) {
766
+ console.log("Pulling latest updates...");
767
+ }
768
+ const result = pull();
769
+ if (!options.quiet) {
770
+ if (result.success) {
771
+ console.log("\u2713 Updated");
772
+ if (result.message && result.message !== "Already up to date.") {
773
+ console.log(` ${result.message}`);
774
+ }
775
+ } else {
776
+ console.error("\u2717 Failed to pull");
777
+ console.error(` ${result.message}`);
778
+ }
779
+ }
780
+ }
781
+
782
+ // src/commands/context.ts
783
+ import { basename as basename4, resolve as resolve3 } from "path";
784
+ async function context(options = {}) {
785
+ const projectPath = resolve3(process.cwd());
786
+ const projectName = basename4(projectPath);
787
+ const initialized = isInitialized();
788
+ const config2 = initialized ? readConfig() : null;
789
+ const project = config2?.projects[projectName];
790
+ const progress2 = getOpenSpecProgress(projectPath);
791
+ const percentage = progress2.tasks_total > 0 ? Math.round(progress2.tasks_done / progress2.tasks_total * 100) : 0;
792
+ const output = {
793
+ project: projectName,
794
+ connected: !!project,
795
+ progress: {
796
+ ...progress2,
797
+ percentage
798
+ },
799
+ team: []
800
+ };
801
+ if (project) {
802
+ const latestUpdate = getLatestUpdate(projectName);
803
+ if (latestUpdate) {
804
+ output.lastUpdate = {
805
+ timestamp: latestUpdate.timestamp,
806
+ machine: latestUpdate.machine,
807
+ status: latestUpdate.context.status,
808
+ notes: latestUpdate.context.notes,
809
+ next: latestUpdate.context.next
810
+ };
811
+ }
812
+ const byMachine = getLatestByMachine(projectName);
813
+ for (const [_machineId, update] of byMachine) {
814
+ output.team.push({
815
+ machine: update.machine,
816
+ timestamp: update.timestamp,
817
+ status: update.context.status
818
+ });
819
+ }
820
+ }
821
+ if (options.json) {
822
+ console.log(JSON.stringify(output, null, 2));
823
+ return;
824
+ }
825
+ if (!options.quiet) {
826
+ console.log(`Project: ${projectName}`);
827
+ console.log(`Connected: ${output.connected ? "Yes" : "No"}`);
828
+ console.log("");
829
+ if (progress2.source === "openspec" && progress2.change) {
830
+ console.log(`Change: ${progress2.change}`);
831
+ console.log(`Progress: ${progress2.tasks_done}/${progress2.tasks_total} (${percentage}%)`);
832
+ } else {
833
+ console.log("Progress: No active change");
834
+ }
835
+ if (output.lastUpdate) {
836
+ console.log("");
837
+ console.log(`Last Update: ${output.lastUpdate.timestamp}`);
838
+ console.log(` Machine: ${output.lastUpdate.machine}`);
839
+ console.log(` Status: ${output.lastUpdate.status}`);
840
+ if (output.lastUpdate.notes.length > 0) {
841
+ console.log(` Notes: ${output.lastUpdate.notes.join(", ")}`);
842
+ }
843
+ if (output.lastUpdate.next.length > 0) {
844
+ console.log(` Next: ${output.lastUpdate.next.join(", ")}`);
845
+ }
846
+ }
847
+ if (output.team.length > 1) {
848
+ console.log("");
849
+ console.log("Team Activity:");
850
+ for (const member of output.team) {
851
+ console.log(` ${member.machine}: ${member.status} (${member.timestamp})`);
852
+ }
853
+ }
854
+ }
855
+ }
856
+
857
+ // src/commands/progress.ts
858
+ import { execSync as execSync3 } from "child_process";
859
+ import { basename as basename5, resolve as resolve4 } from "path";
860
+ async function progress(options = {}) {
861
+ if (!isInitialized()) {
862
+ if (!options.quiet) {
863
+ console.error('MindContext is not initialized. Run "mc init" first.');
864
+ }
865
+ process.exit(1);
866
+ }
867
+ const config2 = readConfig();
868
+ if (!config2) {
869
+ if (!options.quiet) {
870
+ console.error("Failed to read config.");
871
+ }
872
+ process.exit(1);
873
+ }
874
+ if (options.web) {
875
+ if (!config2.dashboard_url) {
876
+ if (!options.quiet) {
877
+ console.error("No dashboard URL configured.");
878
+ console.error("Configure with: mc config --dashboard-url <url>");
879
+ }
880
+ process.exit(1);
881
+ }
882
+ if (!options.quiet) {
883
+ console.log(`Opening: ${config2.dashboard_url}`);
884
+ }
885
+ try {
886
+ const platform = process.platform;
887
+ if (platform === "darwin") {
888
+ execSync3(`open "${config2.dashboard_url}"`);
889
+ } else if (platform === "win32") {
890
+ execSync3(`start "${config2.dashboard_url}"`);
891
+ } else {
892
+ execSync3(`xdg-open "${config2.dashboard_url}"`);
893
+ }
894
+ } catch {
895
+ if (!options.quiet) {
896
+ console.log(`Please open: ${config2.dashboard_url}`);
897
+ }
898
+ }
899
+ return;
900
+ }
901
+ const projectPath = resolve4(process.cwd());
902
+ const projectName = basename5(projectPath);
903
+ const project = config2.projects[projectName];
904
+ if (!project) {
905
+ console.log("MindContext Progress\n");
906
+ console.log("Projects:");
907
+ for (const [name, proj] of Object.entries(config2.projects)) {
908
+ const openspecProgress2 = getOpenSpecProgress(proj.path);
909
+ const percentage2 = openspecProgress2.tasks_total > 0 ? Math.round(openspecProgress2.tasks_done / openspecProgress2.tasks_total * 100) : 0;
910
+ const bar = generateProgressBar(percentage2);
911
+ console.log(` ${name}`);
912
+ console.log(` ${bar} ${percentage2}%`);
913
+ if (openspecProgress2.change) {
914
+ console.log(` Current: ${openspecProgress2.change}`);
915
+ }
916
+ }
917
+ if (Object.keys(config2.projects).length === 0) {
918
+ console.log(" (No projects connected)");
919
+ console.log("");
920
+ console.log('Run "mc connect" in a project directory to connect it.');
921
+ }
922
+ if (config2.dashboard_url) {
923
+ console.log("");
924
+ console.log(`Dashboard: ${config2.dashboard_url}`);
925
+ console.log('Run "mc progress --web" to open in browser.');
926
+ }
927
+ return;
928
+ }
929
+ const openspecProgress = getOpenSpecProgress(projectPath);
930
+ const percentage = openspecProgress.tasks_total > 0 ? Math.round(openspecProgress.tasks_done / openspecProgress.tasks_total * 100) : 0;
931
+ console.log(`${projectName}
932
+ `);
933
+ if (openspecProgress.source === "openspec" && openspecProgress.change) {
934
+ console.log(`Change: ${openspecProgress.change}`);
935
+ console.log(`Progress: ${openspecProgress.tasks_done}/${openspecProgress.tasks_total}`);
936
+ console.log(generateProgressBar(percentage, 30) + ` ${percentage}%`);
937
+ } else {
938
+ console.log("No active OpenSpec change.");
939
+ }
940
+ const recentUpdates = getRecentUpdates(projectName, 5);
941
+ if (recentUpdates.length > 0) {
942
+ console.log("\nRecent Activity:");
943
+ for (const update of recentUpdates) {
944
+ const date = new Date(update.timestamp).toLocaleDateString();
945
+ const time = new Date(update.timestamp).toLocaleTimeString();
946
+ console.log(` ${date} ${time} - ${update.machine}`);
947
+ if (update.context.notes.length > 0) {
948
+ console.log(` Notes: ${update.context.notes.join(", ")}`);
949
+ }
950
+ }
951
+ }
952
+ }
953
+ function generateProgressBar(percentage, width = 20) {
954
+ const filled = Math.round(percentage / 100 * width);
955
+ const empty = width - filled;
956
+ return "\u2588".repeat(filled) + "\u2591".repeat(empty);
957
+ }
958
+
959
+ // src/commands/config.ts
960
+ async function config(options) {
961
+ if (!isInitialized()) {
962
+ throw new Error('Mindcontext not initialized. Run "mc init" first.');
963
+ }
964
+ const currentConfig = readConfig();
965
+ if (!currentConfig) {
966
+ throw new Error("Failed to read config file.");
967
+ }
968
+ if (options.get) {
969
+ const key = options.get;
970
+ const value = currentConfig[key];
971
+ if (!options.quiet) {
972
+ if (typeof value === "object") {
973
+ console.log(JSON.stringify(value, null, 2));
974
+ } else {
975
+ console.log(value);
976
+ }
977
+ }
978
+ return value;
979
+ }
980
+ let updated = false;
981
+ if (options.dashboardRepo !== void 0) {
982
+ currentConfig.dashboard_repo = options.dashboardRepo;
983
+ updated = true;
984
+ }
985
+ if (options.dashboardUrl !== void 0) {
986
+ currentConfig.dashboard_url = options.dashboardUrl;
987
+ updated = true;
988
+ }
989
+ if (updated) {
990
+ writeConfig(currentConfig);
991
+ if (!options.quiet) {
992
+ console.log("\u2713 Config updated");
993
+ }
994
+ return currentConfig;
995
+ }
996
+ if (!options.quiet) {
997
+ console.log("Mindcontext Configuration:\n");
998
+ console.log(` Dashboard Repo: ${currentConfig.dashboard_repo || "(not set)"}`);
999
+ console.log(` Dashboard URL: ${currentConfig.dashboard_url || "(not set)"}`);
1000
+ console.log(` Machine: ${currentConfig.machine.name} (${currentConfig.machine.id})`);
1001
+ console.log(` Projects: ${Object.keys(currentConfig.projects).length} registered`);
1002
+ }
1003
+ return currentConfig;
1004
+ }
1005
+
1006
+ // src/commands/cleanup.ts
1007
+ import { readdirSync as readdirSync3, statSync, unlinkSync, existsSync as existsSync7 } from "fs";
1008
+ import { join as join5 } from "path";
1009
+ var DEFAULT_OLDER_THAN_DAYS = 30;
1010
+ async function cleanup(options) {
1011
+ if (!isInitialized()) {
1012
+ throw new Error('Mindcontext not initialized. Run "mc init" first.');
1013
+ }
1014
+ const config2 = readConfig();
1015
+ if (!config2) {
1016
+ throw new Error("Failed to read config file.");
1017
+ }
1018
+ const olderThanDays = options.olderThan ?? DEFAULT_OLDER_THAN_DAYS;
1019
+ const cutoffTime = Date.now() - olderThanDays * 24 * 60 * 60 * 1e3;
1020
+ const repoDir = getRepoDir();
1021
+ let deleted = 0;
1022
+ let wouldDelete = 0;
1023
+ for (const projectName of Object.keys(config2.projects)) {
1024
+ const updatesDir = join5(repoDir, "projects", projectName, "updates");
1025
+ if (!existsSync7(updatesDir)) {
1026
+ continue;
1027
+ }
1028
+ const files = readdirSync3(updatesDir);
1029
+ for (const file of files) {
1030
+ if (!file.endsWith(".json")) {
1031
+ continue;
1032
+ }
1033
+ const filePath = join5(updatesDir, file);
1034
+ const stats = statSync(filePath);
1035
+ const fileAge = stats.mtime.getTime();
1036
+ if (fileAge < cutoffTime) {
1037
+ if (options.dryRun) {
1038
+ wouldDelete++;
1039
+ if (!options.quiet) {
1040
+ console.log(`Would delete: ${projectName}/${file}`);
1041
+ }
1042
+ } else {
1043
+ unlinkSync(filePath);
1044
+ deleted++;
1045
+ if (!options.quiet) {
1046
+ console.log(`Deleted: ${projectName}/${file}`);
1047
+ }
1048
+ }
1049
+ }
1050
+ }
1051
+ }
1052
+ if (!options.quiet) {
1053
+ if (options.dryRun) {
1054
+ console.log(`
1055
+ ${wouldDelete} file(s) would be deleted.`);
1056
+ } else {
1057
+ console.log(`
1058
+ \u2713 Cleaned up ${deleted} file(s).`);
1059
+ }
1060
+ }
1061
+ return { deleted, wouldDelete };
1062
+ }
1063
+
1064
+ // src/commands/reset.ts
1065
+ import { existsSync as existsSync8, rmSync } from "fs";
1066
+ async function reset(options) {
1067
+ const mindcontextDir = getMindcontextDir();
1068
+ if (!existsSync8(mindcontextDir)) {
1069
+ if (!options.quiet) {
1070
+ console.log("Nothing to remove. Mindcontext directory does not exist.");
1071
+ }
1072
+ return { success: true, removed: false };
1073
+ }
1074
+ if (options.dryRun) {
1075
+ if (!options.quiet) {
1076
+ console.log(`Would remove: ${mindcontextDir}`);
1077
+ console.log("\nThis will delete:");
1078
+ console.log(" - config.json (global configuration)");
1079
+ console.log(" - repo/ (dashboard repository clone)");
1080
+ console.log(" - All cached data and pending pushes");
1081
+ console.log("\nRun with --force to actually remove.");
1082
+ }
1083
+ return { success: true, removed: false, wouldRemove: true };
1084
+ }
1085
+ if (!options.force) {
1086
+ if (!options.quiet) {
1087
+ console.log("Reset requires --force flag to confirm.");
1088
+ console.log(`
1089
+ This will remove: ${mindcontextDir}`);
1090
+ console.log("\nRun: mc reset --force");
1091
+ console.log("Or preview with: mc reset --dry-run");
1092
+ }
1093
+ return { success: false, removed: false };
1094
+ }
1095
+ try {
1096
+ rmSync(mindcontextDir, { recursive: true, force: true });
1097
+ if (!options.quiet) {
1098
+ console.log(`\u2713 Removed ${mindcontextDir}`);
1099
+ console.log('\nRun "mc init" to set up mindcontext again.');
1100
+ }
1101
+ return { success: true, removed: true };
1102
+ } catch (error) {
1103
+ if (!options.quiet) {
1104
+ console.error(`Failed to remove: ${error instanceof Error ? error.message : error}`);
1105
+ }
1106
+ return { success: false, removed: false };
1107
+ }
1108
+ }
1109
+
1110
+ // src/commands/migrate.ts
1111
+ import { existsSync as existsSync9, readdirSync as readdirSync4, readFileSync as readFileSync4, writeFileSync as writeFileSync4, statSync as statSync2 } from "fs";
1112
+ import { join as join6, relative } from "path";
1113
+ function listFilesRecursively(dir, baseDir = dir) {
1114
+ const files = [];
1115
+ if (!existsSync9(dir)) {
1116
+ return files;
1117
+ }
1118
+ const entries = readdirSync4(dir);
1119
+ for (const entry of entries) {
1120
+ const fullPath = join6(dir, entry);
1121
+ const stat = statSync2(fullPath);
1122
+ if (stat.isDirectory()) {
1123
+ files.push(...listFilesRecursively(fullPath, baseDir));
1124
+ } else {
1125
+ files.push(relative(baseDir, fullPath));
1126
+ }
1127
+ }
1128
+ return files;
1129
+ }
1130
+ function detectMigrationSources(projectPath) {
1131
+ const projectDir = join6(projectPath, ".project");
1132
+ const focusJsonPath = join6(projectPath, ".claude", "focus.json");
1133
+ const hasProjectDir = existsSync9(projectDir);
1134
+ const hasFocusJson = existsSync9(focusJsonPath);
1135
+ const projectFiles = hasProjectDir ? listFilesRecursively(projectDir) : [];
1136
+ return {
1137
+ hasProjectDir,
1138
+ hasFocusJson,
1139
+ projectFiles,
1140
+ focusJsonPath: hasFocusJson ? focusJsonPath : void 0
1141
+ };
1142
+ }
1143
+ function convertFocusToUpdate(focusJson, projectName, machine) {
1144
+ const timestamp = focusJson.timestamp || (/* @__PURE__ */ new Date()).toISOString();
1145
+ return {
1146
+ version: "1.0",
1147
+ timestamp,
1148
+ machine: machine.name,
1149
+ machine_id: machine.id,
1150
+ project: projectName,
1151
+ progress: {
1152
+ source: "migration",
1153
+ change: "migrated-from-focus",
1154
+ tasks_done: 0,
1155
+ tasks_total: 0
1156
+ },
1157
+ context: {
1158
+ current_task: focusJson.current_focus || "",
1159
+ status: "migrated",
1160
+ notes: focusJson.session_summary ? [focusJson.session_summary] : [],
1161
+ next: focusJson.next_session_tasks || []
1162
+ }
1163
+ };
1164
+ }
1165
+ async function migrate(options) {
1166
+ if (!isInitialized()) {
1167
+ throw new Error('Mindcontext not initialized. Run "mc init" first.');
1168
+ }
1169
+ const config2 = readConfig();
1170
+ if (!config2) {
1171
+ throw new Error("Failed to read config file.");
1172
+ }
1173
+ const sources = detectMigrationSources(options.projectPath);
1174
+ if (!sources.hasProjectDir && !sources.hasFocusJson) {
1175
+ if (!options.quiet) {
1176
+ console.log("Nothing to migrate.");
1177
+ console.log(" - No .project/ directory found");
1178
+ console.log(" - No .claude/focus.json found");
1179
+ }
1180
+ return {
1181
+ sources,
1182
+ migrated: false,
1183
+ focusMigrated: false,
1184
+ projectDirMigrated: false
1185
+ };
1186
+ }
1187
+ if (!options.quiet) {
1188
+ console.log("Migration sources detected:\n");
1189
+ if (sources.hasProjectDir) {
1190
+ console.log(" .project/ directory:");
1191
+ for (const file of sources.projectFiles) {
1192
+ console.log(` - ${file}`);
1193
+ }
1194
+ }
1195
+ if (sources.hasFocusJson) {
1196
+ console.log(" .claude/focus.json: Found");
1197
+ }
1198
+ console.log("");
1199
+ }
1200
+ if (options.dryRun) {
1201
+ if (!options.quiet) {
1202
+ console.log("Dry-run mode - no changes made.");
1203
+ if (sources.hasFocusJson) {
1204
+ console.log("\nWould migrate focus.json to update file.");
1205
+ }
1206
+ if (sources.hasProjectDir) {
1207
+ console.log("\nWould prompt for .project/ file destinations.");
1208
+ }
1209
+ }
1210
+ return {
1211
+ sources,
1212
+ migrated: false,
1213
+ focusMigrated: false,
1214
+ projectDirMigrated: false
1215
+ };
1216
+ }
1217
+ let focusMigrated = false;
1218
+ let projectDirMigrated = false;
1219
+ if (sources.hasFocusJson && !options.skipFocusJson) {
1220
+ if (!options.quiet) {
1221
+ console.log("Migrating focus.json...");
1222
+ }
1223
+ try {
1224
+ const projectName = Object.keys(config2.projects).find(
1225
+ (name) => config2.projects[name].path === options.projectPath
1226
+ ) || "unknown-project";
1227
+ const focusContent = readFileSync4(sources.focusJsonPath, "utf8");
1228
+ const focusJson = JSON.parse(focusContent);
1229
+ const update = convertFocusToUpdate(focusJson, projectName, config2.machine);
1230
+ ensureProjectDir(projectName);
1231
+ const filename = generateUpdateFilename();
1232
+ const updatesDir = getProjectDir(projectName);
1233
+ const updatePath = join6(updatesDir, filename);
1234
+ writeFileSync4(updatePath, JSON.stringify(update, null, 2));
1235
+ if (!options.quiet) {
1236
+ console.log(` \u2713 Created update file: ${filename}`);
1237
+ }
1238
+ focusMigrated = true;
1239
+ } catch (error) {
1240
+ if (!options.quiet) {
1241
+ console.error(` \u2717 Failed to migrate focus.json: ${error instanceof Error ? error.message : error}`);
1242
+ }
1243
+ }
1244
+ }
1245
+ if (sources.hasProjectDir && !options.skipProjectDir) {
1246
+ if (options.autoConfirm) {
1247
+ if (!options.quiet) {
1248
+ console.log("\n.project/ migration requires interactive mode.");
1249
+ console.log("Run without --auto-confirm to migrate .project/ files.");
1250
+ }
1251
+ } else if (!options.quiet) {
1252
+ console.log("\n.project/ migration: Interactive prompts not yet implemented.");
1253
+ console.log("Files remain in .project/ - manual migration recommended.");
1254
+ }
1255
+ }
1256
+ const migrated = focusMigrated || projectDirMigrated;
1257
+ if (!options.quiet && migrated) {
1258
+ console.log("\n\u2713 Migration complete.");
1259
+ }
1260
+ return {
1261
+ sources,
1262
+ migrated,
1263
+ focusMigrated,
1264
+ projectDirMigrated
1265
+ };
1266
+ }
1267
+
1268
+ // src/cli.ts
1269
+ var args = process.argv.slice(2);
1270
+ var command = args[0];
1271
+ var flags = {};
1272
+ var positional = [];
1273
+ for (let i = 1; i < args.length; i++) {
1274
+ const arg = args[i];
1275
+ if (arg.startsWith("--")) {
1276
+ const eqIndex = arg.indexOf("=");
1277
+ if (eqIndex > -1) {
1278
+ const key = arg.slice(2, eqIndex);
1279
+ const value = arg.slice(eqIndex + 1);
1280
+ flags[key] = value;
1281
+ } else {
1282
+ const key = arg.slice(2);
1283
+ const nextArg = args[i + 1];
1284
+ if (nextArg && !nextArg.startsWith("-")) {
1285
+ flags[key] = nextArg;
1286
+ i++;
1287
+ } else {
1288
+ flags[key] = true;
1289
+ }
1290
+ }
1291
+ } else if (arg.startsWith("-")) {
1292
+ flags[arg.slice(1)] = true;
1293
+ } else {
1294
+ positional.push(arg);
1295
+ }
1296
+ }
1297
+ async function main() {
1298
+ try {
1299
+ switch (command) {
1300
+ case "init":
1301
+ await init({ quiet: !!flags.quiet || !!flags.q });
1302
+ break;
1303
+ case "connect":
1304
+ await connect({
1305
+ category: flags.category,
1306
+ name: positional[0],
1307
+ quiet: !!flags.quiet || !!flags.q,
1308
+ withHooks: !!flags["with-hooks"]
1309
+ });
1310
+ break;
1311
+ case "sync":
1312
+ await sync2({
1313
+ quiet: !!flags.quiet || !!flags.q,
1314
+ dryRun: !!flags["dry-run"],
1315
+ notes: flags.notes ? String(flags.notes).split("\n").filter(Boolean) : void 0,
1316
+ next: flags.next ? String(flags.next).split("\n").filter(Boolean) : void 0
1317
+ });
1318
+ break;
1319
+ case "pull":
1320
+ await pull2({ quiet: !!flags.quiet || !!flags.q });
1321
+ break;
1322
+ case "context":
1323
+ await context({
1324
+ json: !!flags.json,
1325
+ quiet: !!flags.quiet || !!flags.q
1326
+ });
1327
+ break;
1328
+ case "progress":
1329
+ await progress({
1330
+ web: !!flags.web,
1331
+ quiet: !!flags.quiet || !!flags.q
1332
+ });
1333
+ break;
1334
+ case "config":
1335
+ await config({
1336
+ show: !flags["dashboard-repo"] && !flags["dashboard-url"] && !flags.get,
1337
+ get: flags.get,
1338
+ dashboardRepo: flags["dashboard-repo"],
1339
+ dashboardUrl: flags["dashboard-url"],
1340
+ quiet: !!flags.quiet || !!flags.q
1341
+ });
1342
+ break;
1343
+ case "cleanup":
1344
+ await cleanup({
1345
+ olderThan: flags["older-than"] ? parseInt(flags["older-than"], 10) : void 0,
1346
+ dryRun: !!flags["dry-run"],
1347
+ quiet: !!flags.quiet || !!flags.q
1348
+ });
1349
+ break;
1350
+ case "reset":
1351
+ await reset({
1352
+ force: !!flags.force,
1353
+ dryRun: !!flags["dry-run"],
1354
+ quiet: !!flags.quiet || !!flags.q
1355
+ });
1356
+ break;
1357
+ case "migrate":
1358
+ await migrate({
1359
+ projectPath: process.cwd(),
1360
+ dryRun: !!flags["dry-run"],
1361
+ quiet: !!flags.quiet || !!flags.q,
1362
+ skipProjectDir: !!flags["skip-project"],
1363
+ skipFocusJson: !!flags["skip-focus"],
1364
+ autoConfirm: !!flags.yes || !!flags.y
1365
+ });
1366
+ break;
1367
+ case "help":
1368
+ case "--help":
1369
+ case "-h":
1370
+ case void 0:
1371
+ printHelp();
1372
+ break;
1373
+ case "version":
1374
+ case "--version":
1375
+ case "-v":
1376
+ console.log("mindcontext v0.1.0");
1377
+ break;
1378
+ default:
1379
+ console.error(`Unknown command: ${command}`);
1380
+ console.error('Run "mc help" for available commands.');
1381
+ process.exit(1);
1382
+ }
1383
+ } catch (error) {
1384
+ console.error("Error:", error instanceof Error ? error.message : error);
1385
+ process.exit(1);
1386
+ }
1387
+ }
1388
+ function printHelp() {
1389
+ console.log(`
1390
+ mindcontext - Git-based project progress tracker
1391
+
1392
+ USAGE:
1393
+ mc <command> [options]
1394
+
1395
+ COMMANDS:
1396
+ init Initialize mindcontext (~/.mindcontext/)
1397
+ connect Connect current project to mindcontext
1398
+ sync Create progress update and push
1399
+ pull Pull latest updates from remote
1400
+ context Output current context (for integration)
1401
+ progress Show progress (or --web to open dashboard)
1402
+ config View or update configuration
1403
+ cleanup Remove old update files
1404
+ reset Remove ~/.mindcontext/ and start fresh
1405
+ migrate Migrate from .project/ and focus.json
1406
+ help Show this help message
1407
+
1408
+ OPTIONS:
1409
+ --quiet, -q Suppress output
1410
+ --json Output as JSON (context command)
1411
+ --web Open web dashboard (progress command)
1412
+ --category Set project category (connect command)
1413
+ --with-hooks Generate session-end hook (connect command)
1414
+ --dry-run Show what would be done (sync command)
1415
+ --dashboard-repo Set dashboard git repository (config command)
1416
+ --dashboard-url Set dashboard web URL (config command)
1417
+ --get <key> Get specific config value (config command)
1418
+ --older-than <d> Days threshold for cleanup (default: 30)
1419
+ --force Confirm destructive action (reset command)
1420
+ --yes, -y Auto-confirm prompts (migrate command)
1421
+ --skip-project Skip .project/ migration
1422
+ --skip-focus Skip focus.json migration
1423
+
1424
+ EXAMPLES:
1425
+ # First-time setup
1426
+ mc init
1427
+
1428
+ # Connect a project
1429
+ cd my-project
1430
+ mc connect --category work
1431
+
1432
+ # Daily usage
1433
+ mc sync # Push progress update
1434
+ mc progress # View progress in terminal
1435
+ mc progress --web # Open dashboard in browser
1436
+ mc pull # Get team updates
1437
+
1438
+ DOCUMENTATION:
1439
+ https://github.com/tmsjngx0/mindcontext
1440
+ `);
1441
+ }
1442
+ main();
1443
+ //# sourceMappingURL=cli.js.map