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/README.md +342 -0
- package/dist/cli.js +1443 -0
- package/dist/cli.js.map +1 -0
- package/dist/index.d.ts +180 -0
- package/dist/index.js +978 -0
- package/dist/index.js.map +1 -0
- package/package.json +53 -0
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
|