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/index.js
ADDED
|
@@ -0,0 +1,978 @@
|
|
|
1
|
+
// src/commands/init.ts
|
|
2
|
+
import { existsSync as existsSync3, mkdirSync as mkdirSync2 } from "fs";
|
|
3
|
+
import { createInterface } from "readline";
|
|
4
|
+
|
|
5
|
+
// src/lib/config.ts
|
|
6
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
7
|
+
import { homedir } from "os";
|
|
8
|
+
import { join } from "path";
|
|
9
|
+
|
|
10
|
+
// src/lib/machine-id.ts
|
|
11
|
+
import { createHash } from "crypto";
|
|
12
|
+
import { hostname, userInfo } from "os";
|
|
13
|
+
import { execSync } from "child_process";
|
|
14
|
+
function getMachineId() {
|
|
15
|
+
const host = hostname();
|
|
16
|
+
const user = userInfo().username;
|
|
17
|
+
let gitEmail = "";
|
|
18
|
+
try {
|
|
19
|
+
gitEmail = execSync("git config user.email", { encoding: "utf8" }).trim();
|
|
20
|
+
} catch {
|
|
21
|
+
}
|
|
22
|
+
const hash = createHash("sha256").update(`${host}-${user}-${gitEmail}`).digest("hex").substring(0, 8);
|
|
23
|
+
const name = `${user}-${host}`.toLowerCase().replace(/[^a-z0-9-]/g, "-");
|
|
24
|
+
return { name, id: hash };
|
|
25
|
+
}
|
|
26
|
+
function getTimestamp() {
|
|
27
|
+
const now = /* @__PURE__ */ new Date();
|
|
28
|
+
return now.toISOString().replace(/:/g, "-").replace(/\.\d{3}Z$/, "");
|
|
29
|
+
}
|
|
30
|
+
function generateUpdateFilename() {
|
|
31
|
+
const { name, id } = getMachineId();
|
|
32
|
+
const timestamp = getTimestamp();
|
|
33
|
+
return `${timestamp}_${name}_${id}.json`;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// src/lib/config.ts
|
|
37
|
+
var MINDCONTEXT_DIR = join(homedir(), ".mindcontext");
|
|
38
|
+
var CONFIG_FILE = join(MINDCONTEXT_DIR, "config.json");
|
|
39
|
+
var REPO_DIR = join(MINDCONTEXT_DIR, "repo");
|
|
40
|
+
var PENDING_FILE = join(MINDCONTEXT_DIR, "pending.json");
|
|
41
|
+
function getMindcontextDir() {
|
|
42
|
+
return MINDCONTEXT_DIR;
|
|
43
|
+
}
|
|
44
|
+
function getRepoDir() {
|
|
45
|
+
return REPO_DIR;
|
|
46
|
+
}
|
|
47
|
+
function isInitialized() {
|
|
48
|
+
return existsSync(CONFIG_FILE) && existsSync(REPO_DIR);
|
|
49
|
+
}
|
|
50
|
+
function createDefaultConfig() {
|
|
51
|
+
const machine = getMachineId();
|
|
52
|
+
return {
|
|
53
|
+
version: "1.0",
|
|
54
|
+
dashboard_repo: "",
|
|
55
|
+
dashboard_url: "",
|
|
56
|
+
projects: {},
|
|
57
|
+
machine
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
function readConfig() {
|
|
61
|
+
if (!existsSync(CONFIG_FILE)) {
|
|
62
|
+
return null;
|
|
63
|
+
}
|
|
64
|
+
try {
|
|
65
|
+
const content = readFileSync(CONFIG_FILE, "utf8");
|
|
66
|
+
return JSON.parse(content);
|
|
67
|
+
} catch {
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
function writeConfig(config) {
|
|
72
|
+
if (!existsSync(MINDCONTEXT_DIR)) {
|
|
73
|
+
mkdirSync(MINDCONTEXT_DIR, { recursive: true });
|
|
74
|
+
}
|
|
75
|
+
writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2));
|
|
76
|
+
}
|
|
77
|
+
function readPending() {
|
|
78
|
+
if (!existsSync(PENDING_FILE)) {
|
|
79
|
+
return [];
|
|
80
|
+
}
|
|
81
|
+
try {
|
|
82
|
+
const content = readFileSync(PENDING_FILE, "utf8");
|
|
83
|
+
return JSON.parse(content);
|
|
84
|
+
} catch {
|
|
85
|
+
return [];
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
function writePending(pending) {
|
|
89
|
+
writeFileSync(PENDING_FILE, JSON.stringify(pending, null, 2));
|
|
90
|
+
}
|
|
91
|
+
function addPending(message) {
|
|
92
|
+
const pending = readPending();
|
|
93
|
+
pending.push({
|
|
94
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
95
|
+
message
|
|
96
|
+
});
|
|
97
|
+
writePending(pending);
|
|
98
|
+
}
|
|
99
|
+
function clearPending() {
|
|
100
|
+
if (existsSync(PENDING_FILE)) {
|
|
101
|
+
writeFileSync(PENDING_FILE, "[]");
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
function getProjectDir(projectName) {
|
|
105
|
+
return join(REPO_DIR, "projects", projectName, "updates");
|
|
106
|
+
}
|
|
107
|
+
function ensureProjectDir(projectName) {
|
|
108
|
+
const dir = getProjectDir(projectName);
|
|
109
|
+
if (!existsSync(dir)) {
|
|
110
|
+
mkdirSync(dir, { recursive: true });
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// src/lib/git-ops.ts
|
|
115
|
+
import { execSync as execSync2 } from "child_process";
|
|
116
|
+
import { existsSync as existsSync2 } from "fs";
|
|
117
|
+
var execOptions = {
|
|
118
|
+
encoding: "utf8",
|
|
119
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
120
|
+
};
|
|
121
|
+
function cloneRepo(repoUrl, targetDir) {
|
|
122
|
+
execSync2(`git clone "${repoUrl}" "${targetDir}"`, execOptions);
|
|
123
|
+
}
|
|
124
|
+
function pull() {
|
|
125
|
+
const repoDir = getRepoDir();
|
|
126
|
+
if (!existsSync2(repoDir)) {
|
|
127
|
+
return { success: false, message: "Repository not initialized" };
|
|
128
|
+
}
|
|
129
|
+
try {
|
|
130
|
+
const output = execSync2("git pull --rebase origin main", {
|
|
131
|
+
...execOptions,
|
|
132
|
+
cwd: repoDir
|
|
133
|
+
});
|
|
134
|
+
return { success: true, message: output.trim() };
|
|
135
|
+
} catch (error) {
|
|
136
|
+
const message = error instanceof Error ? error.message : "Pull failed";
|
|
137
|
+
return { success: false, message };
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
function stageAll() {
|
|
141
|
+
const repoDir = getRepoDir();
|
|
142
|
+
execSync2("git add -A", { ...execOptions, cwd: repoDir });
|
|
143
|
+
}
|
|
144
|
+
function commit(message) {
|
|
145
|
+
const repoDir = getRepoDir();
|
|
146
|
+
try {
|
|
147
|
+
const status = execSync2("git status --porcelain", {
|
|
148
|
+
...execOptions,
|
|
149
|
+
cwd: repoDir
|
|
150
|
+
});
|
|
151
|
+
if (!status.trim()) {
|
|
152
|
+
return { success: true, message: "Nothing to commit" };
|
|
153
|
+
}
|
|
154
|
+
stageAll();
|
|
155
|
+
execSync2(`git commit -m "${message.replace(/"/g, '\\"')}"`, {
|
|
156
|
+
...execOptions,
|
|
157
|
+
cwd: repoDir
|
|
158
|
+
});
|
|
159
|
+
return { success: true, message: "Committed" };
|
|
160
|
+
} catch (error) {
|
|
161
|
+
const msg = error instanceof Error ? error.message : "Commit failed";
|
|
162
|
+
return { success: false, message: msg };
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
function push() {
|
|
166
|
+
const repoDir = getRepoDir();
|
|
167
|
+
try {
|
|
168
|
+
execSync2("git push origin main", {
|
|
169
|
+
...execOptions,
|
|
170
|
+
cwd: repoDir,
|
|
171
|
+
timeout: 3e4
|
|
172
|
+
});
|
|
173
|
+
return { success: true, message: "Pushed to remote" };
|
|
174
|
+
} catch (error) {
|
|
175
|
+
const message = error instanceof Error ? error.message : "Push failed";
|
|
176
|
+
return { success: false, message };
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
function sync(commitMessage) {
|
|
180
|
+
const pending = readPending();
|
|
181
|
+
if (pending.length > 0) {
|
|
182
|
+
const pushResult2 = push();
|
|
183
|
+
if (pushResult2.success) {
|
|
184
|
+
clearPending();
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
const commitResult = commit(commitMessage);
|
|
188
|
+
if (!commitResult.success) {
|
|
189
|
+
return {
|
|
190
|
+
committed: false,
|
|
191
|
+
pushed: false,
|
|
192
|
+
message: commitResult.message
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
if (commitResult.message === "Nothing to commit") {
|
|
196
|
+
return {
|
|
197
|
+
committed: false,
|
|
198
|
+
pushed: false,
|
|
199
|
+
message: "No changes to sync"
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
const pushResult = push();
|
|
203
|
+
if (!pushResult.success) {
|
|
204
|
+
addPending(commitMessage);
|
|
205
|
+
return {
|
|
206
|
+
committed: true,
|
|
207
|
+
pushed: false,
|
|
208
|
+
message: "Committed locally. Push pending (offline)"
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
return {
|
|
212
|
+
committed: true,
|
|
213
|
+
pushed: true,
|
|
214
|
+
message: "Synced successfully"
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
function getRecentCommits(projectPath, limit = 5) {
|
|
218
|
+
try {
|
|
219
|
+
const output = execSync2(
|
|
220
|
+
`git log --oneline -${limit} --format="%s"`,
|
|
221
|
+
{ ...execOptions, cwd: projectPath }
|
|
222
|
+
);
|
|
223
|
+
return output.trim().split("\n").filter((line) => line.length > 0);
|
|
224
|
+
} catch {
|
|
225
|
+
return [];
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// src/commands/init.ts
|
|
230
|
+
async function prompt(question) {
|
|
231
|
+
const rl = createInterface({
|
|
232
|
+
input: process.stdin,
|
|
233
|
+
output: process.stdout
|
|
234
|
+
});
|
|
235
|
+
return new Promise((resolve5) => {
|
|
236
|
+
rl.question(question, (answer) => {
|
|
237
|
+
rl.close();
|
|
238
|
+
resolve5(answer.trim());
|
|
239
|
+
});
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
async function init(options = {}) {
|
|
243
|
+
const mindcontextDir = getMindcontextDir();
|
|
244
|
+
const repoDir = getRepoDir();
|
|
245
|
+
if (isInitialized()) {
|
|
246
|
+
const config2 = readConfig();
|
|
247
|
+
if (!options.quiet) {
|
|
248
|
+
console.log("MindContext is already initialized.");
|
|
249
|
+
console.log(` Directory: ${mindcontextDir}`);
|
|
250
|
+
console.log(` Dashboard: ${config2?.dashboard_url || "Not configured"}`);
|
|
251
|
+
console.log(` Projects: ${Object.keys(config2?.projects || {}).length}`);
|
|
252
|
+
console.log("");
|
|
253
|
+
console.log('Run "mc reset" to start fresh.');
|
|
254
|
+
}
|
|
255
|
+
return;
|
|
256
|
+
}
|
|
257
|
+
if (!options.quiet) {
|
|
258
|
+
console.log("Initializing MindContext...\n");
|
|
259
|
+
}
|
|
260
|
+
if (!existsSync3(mindcontextDir)) {
|
|
261
|
+
mkdirSync2(mindcontextDir, { recursive: true });
|
|
262
|
+
}
|
|
263
|
+
let dashboardRepo = "";
|
|
264
|
+
if (!options.quiet) {
|
|
265
|
+
console.log("Enter your dashboard repository URL.");
|
|
266
|
+
console.log("(Create one from https://github.com/tmsjngx0/mindcontext-template)\n");
|
|
267
|
+
dashboardRepo = await prompt("Dashboard repo URL (git@github.com:user/repo.git): ");
|
|
268
|
+
}
|
|
269
|
+
if (!dashboardRepo) {
|
|
270
|
+
if (!options.quiet) {
|
|
271
|
+
console.log("\nNo dashboard URL provided. You can configure it later with:");
|
|
272
|
+
console.log(" mc config --dashboard-repo <url>");
|
|
273
|
+
}
|
|
274
|
+
const config2 = createDefaultConfig();
|
|
275
|
+
writeConfig(config2);
|
|
276
|
+
if (!options.quiet) {
|
|
277
|
+
console.log(`
|
|
278
|
+
Created: ${mindcontextDir}/config.json`);
|
|
279
|
+
}
|
|
280
|
+
return;
|
|
281
|
+
}
|
|
282
|
+
if (!options.quiet) {
|
|
283
|
+
console.log(`
|
|
284
|
+
Cloning dashboard repository...`);
|
|
285
|
+
}
|
|
286
|
+
try {
|
|
287
|
+
cloneRepo(dashboardRepo, repoDir);
|
|
288
|
+
} catch (error) {
|
|
289
|
+
console.error("Failed to clone repository:", error instanceof Error ? error.message : error);
|
|
290
|
+
process.exit(1);
|
|
291
|
+
}
|
|
292
|
+
let dashboardUrl = "";
|
|
293
|
+
const match = dashboardRepo.match(/github\.com[:/]([^/]+)\/([^/.]+)/);
|
|
294
|
+
if (match) {
|
|
295
|
+
dashboardUrl = `https://${match[1]}.github.io/${match[2]}`;
|
|
296
|
+
}
|
|
297
|
+
const config = createDefaultConfig();
|
|
298
|
+
config.dashboard_repo = dashboardRepo;
|
|
299
|
+
config.dashboard_url = dashboardUrl;
|
|
300
|
+
writeConfig(config);
|
|
301
|
+
if (!options.quiet) {
|
|
302
|
+
console.log("\n\u2713 MindContext initialized!");
|
|
303
|
+
console.log(` Directory: ${mindcontextDir}`);
|
|
304
|
+
console.log(` Dashboard: ${dashboardUrl}`);
|
|
305
|
+
console.log("");
|
|
306
|
+
console.log("Next steps:");
|
|
307
|
+
console.log(" cd <your-project>");
|
|
308
|
+
console.log(" mc connect");
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
// src/commands/connect.ts
|
|
313
|
+
import { basename as basename2, resolve, join as join3 } from "path";
|
|
314
|
+
import { existsSync as existsSync5, mkdirSync as mkdirSync3, writeFileSync as writeFileSync2 } from "fs";
|
|
315
|
+
|
|
316
|
+
// src/parsers/openspec.ts
|
|
317
|
+
import { existsSync as existsSync4, readdirSync, readFileSync as readFileSync2 } from "fs";
|
|
318
|
+
import { join as join2, basename } from "path";
|
|
319
|
+
function hasOpenSpec(projectPath) {
|
|
320
|
+
const openspecDir = join2(projectPath, "openspec");
|
|
321
|
+
return existsSync4(openspecDir) && existsSync4(join2(openspecDir, "changes"));
|
|
322
|
+
}
|
|
323
|
+
function parseTasksFile(filepath) {
|
|
324
|
+
if (!existsSync4(filepath)) {
|
|
325
|
+
return { total: 0, complete: 0 };
|
|
326
|
+
}
|
|
327
|
+
const content = readFileSync2(filepath, "utf8");
|
|
328
|
+
const lines = content.split("\n");
|
|
329
|
+
let total = 0;
|
|
330
|
+
let complete = 0;
|
|
331
|
+
for (const line of lines) {
|
|
332
|
+
if (/^\s*-\s*\[\s*\]\s/.test(line)) {
|
|
333
|
+
total++;
|
|
334
|
+
} else if (/^\s*-\s*\[x\]\s/i.test(line)) {
|
|
335
|
+
total++;
|
|
336
|
+
complete++;
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
return { total, complete };
|
|
340
|
+
}
|
|
341
|
+
function determineStatus(tasksTotal, tasksComplete) {
|
|
342
|
+
if (tasksTotal === 0) {
|
|
343
|
+
return "proposed";
|
|
344
|
+
}
|
|
345
|
+
if (tasksComplete === 0) {
|
|
346
|
+
return "proposed";
|
|
347
|
+
}
|
|
348
|
+
if (tasksComplete === tasksTotal) {
|
|
349
|
+
return "done";
|
|
350
|
+
}
|
|
351
|
+
return "in_progress";
|
|
352
|
+
}
|
|
353
|
+
function parseChange(changePath) {
|
|
354
|
+
const id = basename(changePath);
|
|
355
|
+
if (id === "archive") {
|
|
356
|
+
return null;
|
|
357
|
+
}
|
|
358
|
+
const tasksFile = join2(changePath, "tasks.md");
|
|
359
|
+
const { total, complete } = parseTasksFile(tasksFile);
|
|
360
|
+
return {
|
|
361
|
+
id,
|
|
362
|
+
tasksTotal: total,
|
|
363
|
+
tasksComplete: complete,
|
|
364
|
+
status: determineStatus(total, complete)
|
|
365
|
+
};
|
|
366
|
+
}
|
|
367
|
+
function parseOpenSpec(projectPath) {
|
|
368
|
+
if (!hasOpenSpec(projectPath)) {
|
|
369
|
+
return { found: false, changes: [], activeChange: null };
|
|
370
|
+
}
|
|
371
|
+
const changesDir = join2(projectPath, "openspec", "changes");
|
|
372
|
+
const entries = readdirSync(changesDir, { withFileTypes: true });
|
|
373
|
+
const changes = [];
|
|
374
|
+
for (const entry of entries) {
|
|
375
|
+
if (entry.isDirectory()) {
|
|
376
|
+
const change = parseChange(join2(changesDir, entry.name));
|
|
377
|
+
if (change) {
|
|
378
|
+
changes.push(change);
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
let activeChange = null;
|
|
383
|
+
for (const change of changes) {
|
|
384
|
+
if (change.status === "in_progress") {
|
|
385
|
+
activeChange = change;
|
|
386
|
+
break;
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
if (!activeChange) {
|
|
390
|
+
for (const change of changes) {
|
|
391
|
+
if (change.status === "review") {
|
|
392
|
+
activeChange = change;
|
|
393
|
+
break;
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
if (!activeChange) {
|
|
398
|
+
for (const change of changes) {
|
|
399
|
+
if (change.status === "proposed" && change.tasksTotal > 0) {
|
|
400
|
+
activeChange = change;
|
|
401
|
+
break;
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
return { found: true, changes, activeChange };
|
|
406
|
+
}
|
|
407
|
+
function getOpenSpecProgress(projectPath) {
|
|
408
|
+
const result = parseOpenSpec(projectPath);
|
|
409
|
+
if (!result.found || !result.activeChange) {
|
|
410
|
+
return {
|
|
411
|
+
source: "manual",
|
|
412
|
+
tasks_done: 0,
|
|
413
|
+
tasks_total: 0
|
|
414
|
+
};
|
|
415
|
+
}
|
|
416
|
+
return {
|
|
417
|
+
source: "openspec",
|
|
418
|
+
change: result.activeChange.id,
|
|
419
|
+
tasks_done: result.activeChange.tasksComplete,
|
|
420
|
+
tasks_total: result.activeChange.tasksTotal
|
|
421
|
+
};
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
// src/lib/templates.ts
|
|
425
|
+
var COMMAND_TEMPLATES = {
|
|
426
|
+
"prime.md": `---
|
|
427
|
+
description: Load context and show what to work on
|
|
428
|
+
---
|
|
429
|
+
|
|
430
|
+
# Prime Context
|
|
431
|
+
|
|
432
|
+
Load project context at the start of your session.
|
|
433
|
+
|
|
434
|
+
## Step 1: Get Current Context
|
|
435
|
+
|
|
436
|
+
\`\`\`bash
|
|
437
|
+
mc context --json
|
|
438
|
+
\`\`\`
|
|
439
|
+
|
|
440
|
+
## Step 2: Load Last Session Notes
|
|
441
|
+
|
|
442
|
+
From the context output, identify:
|
|
443
|
+
- **Last session summary** - What was accomplished
|
|
444
|
+
- **Next tasks** - What was planned for this session
|
|
445
|
+
- **Current change** - The OpenSpec change being worked on
|
|
446
|
+
- **Progress** - Tasks completed vs total
|
|
447
|
+
|
|
448
|
+
## Step 3: Show Context Summary
|
|
449
|
+
|
|
450
|
+
Display to the user:
|
|
451
|
+
|
|
452
|
+
\`\`\`
|
|
453
|
+
SESSION CONTEXT LOADED
|
|
454
|
+
\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
|
|
455
|
+
|
|
456
|
+
Project: [project name]
|
|
457
|
+
Change: [current openspec change]
|
|
458
|
+
Progress: [tasks_done]/[tasks_total] ([percentage]%)
|
|
459
|
+
|
|
460
|
+
Last Session:
|
|
461
|
+
- [notes from last update]
|
|
462
|
+
|
|
463
|
+
Planned for This Session:
|
|
464
|
+
- [next tasks from last update]
|
|
465
|
+
\`\`\`
|
|
466
|
+
|
|
467
|
+
## Step 4: Suggest Next Action
|
|
468
|
+
|
|
469
|
+
Based on context, suggest what to work on next.
|
|
470
|
+
`,
|
|
471
|
+
"update.md": `---
|
|
472
|
+
description: Save session context and sync progress
|
|
473
|
+
---
|
|
474
|
+
|
|
475
|
+
# Update Context
|
|
476
|
+
|
|
477
|
+
Save your session progress with auto-generated context.
|
|
478
|
+
|
|
479
|
+
## Step 1: Generate Session Summary
|
|
480
|
+
|
|
481
|
+
From your conversation context, summarize what was accomplished:
|
|
482
|
+
- Recent code changes and commits
|
|
483
|
+
- Tasks completed
|
|
484
|
+
- Features implemented or bugs fixed
|
|
485
|
+
|
|
486
|
+
Create 3-5 concise bullet points.
|
|
487
|
+
|
|
488
|
+
## Step 2: Generate Next Tasks
|
|
489
|
+
|
|
490
|
+
From OpenSpec and conversation context, identify what should be done next:
|
|
491
|
+
- Remaining tasks from current OpenSpec change
|
|
492
|
+
- Blockers or pending items mentioned
|
|
493
|
+
|
|
494
|
+
Create 2-4 actionable next task items.
|
|
495
|
+
|
|
496
|
+
## Step 3: Run mc sync
|
|
497
|
+
|
|
498
|
+
\`\`\`bash
|
|
499
|
+
mc sync --notes "First accomplishment
|
|
500
|
+
Second accomplishment" --next "Next task 1
|
|
501
|
+
Next task 2"
|
|
502
|
+
\`\`\`
|
|
503
|
+
|
|
504
|
+
Use multiline strings - each line becomes one item.
|
|
505
|
+
|
|
506
|
+
## Step 4: Show Confirmation
|
|
507
|
+
|
|
508
|
+
\`\`\`
|
|
509
|
+
PROGRESS SYNCED
|
|
510
|
+
\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
|
|
511
|
+
|
|
512
|
+
Session Summary:
|
|
513
|
+
- [generated notes]
|
|
514
|
+
|
|
515
|
+
Next Session:
|
|
516
|
+
- [generated next tasks]
|
|
517
|
+
\`\`\`
|
|
518
|
+
`,
|
|
519
|
+
"progress.md": `---
|
|
520
|
+
description: Show progress
|
|
521
|
+
---
|
|
522
|
+
|
|
523
|
+
Run \`mc progress\` to see current progress in the terminal.
|
|
524
|
+
|
|
525
|
+
Options:
|
|
526
|
+
- \`--web\` - Open the web dashboard in your browser
|
|
527
|
+
|
|
528
|
+
This aggregates all update files to show:
|
|
529
|
+
- Current change and task completion
|
|
530
|
+
- Recent activity across machines
|
|
531
|
+
- Team member status (if collaborative)
|
|
532
|
+
`,
|
|
533
|
+
"context.md": `---
|
|
534
|
+
description: Output current context
|
|
535
|
+
---
|
|
536
|
+
|
|
537
|
+
Run \`mc context\` to output the current project context.
|
|
538
|
+
|
|
539
|
+
Options:
|
|
540
|
+
- \`--json\` - Output as JSON (for integration with other tools)
|
|
541
|
+
|
|
542
|
+
This shows:
|
|
543
|
+
- Project connection status
|
|
544
|
+
- Current OpenSpec change and progress
|
|
545
|
+
- Last update details
|
|
546
|
+
- Team activity summary
|
|
547
|
+
`
|
|
548
|
+
};
|
|
549
|
+
var HOOK_TEMPLATES = {
|
|
550
|
+
"session-end.js": `const { execSync } = require('child_process');
|
|
551
|
+
|
|
552
|
+
module.exports = async function() {
|
|
553
|
+
try {
|
|
554
|
+
execSync('mc sync --quiet', {
|
|
555
|
+
stdio: 'inherit',
|
|
556
|
+
timeout: 10000
|
|
557
|
+
});
|
|
558
|
+
} catch (e) {
|
|
559
|
+
// Sync failed silently - don't block session end
|
|
560
|
+
}
|
|
561
|
+
};
|
|
562
|
+
`
|
|
563
|
+
};
|
|
564
|
+
|
|
565
|
+
// src/commands/connect.ts
|
|
566
|
+
async function connect(options = {}) {
|
|
567
|
+
if (!isInitialized()) {
|
|
568
|
+
console.error('MindContext is not initialized. Run "mc init" first.');
|
|
569
|
+
process.exit(1);
|
|
570
|
+
}
|
|
571
|
+
const config = readConfig();
|
|
572
|
+
if (!config) {
|
|
573
|
+
console.error("Failed to read config.");
|
|
574
|
+
process.exit(1);
|
|
575
|
+
}
|
|
576
|
+
const projectPath = resolve(process.cwd());
|
|
577
|
+
const projectName = options.name || basename2(projectPath);
|
|
578
|
+
if (config.projects[projectName]) {
|
|
579
|
+
if (!options.quiet) {
|
|
580
|
+
console.log(`Project "${projectName}" is already connected.`);
|
|
581
|
+
console.log(` Path: ${config.projects[projectName].path}`);
|
|
582
|
+
console.log(` Category: ${config.projects[projectName].category}`);
|
|
583
|
+
console.log(` OpenSpec: ${config.projects[projectName].openspec ? "Yes" : "No"}`);
|
|
584
|
+
}
|
|
585
|
+
return;
|
|
586
|
+
}
|
|
587
|
+
const openspec = hasOpenSpec(projectPath);
|
|
588
|
+
config.projects[projectName] = {
|
|
589
|
+
path: projectPath,
|
|
590
|
+
openspec,
|
|
591
|
+
category: options.category || "default"
|
|
592
|
+
};
|
|
593
|
+
writeConfig(config);
|
|
594
|
+
ensureProjectDir(projectName);
|
|
595
|
+
const claudeDir = join3(projectPath, ".claude");
|
|
596
|
+
const commandsDir = join3(claudeDir, "commands", "mc");
|
|
597
|
+
if (!existsSync5(commandsDir)) {
|
|
598
|
+
mkdirSync3(commandsDir, { recursive: true });
|
|
599
|
+
for (const [filename, content] of Object.entries(COMMAND_TEMPLATES)) {
|
|
600
|
+
writeFileSync2(join3(commandsDir, filename), content);
|
|
601
|
+
}
|
|
602
|
+
if (!options.quiet) {
|
|
603
|
+
console.log(`\u2713 Created .claude/commands/mc/`);
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
if (options.withHooks) {
|
|
607
|
+
const hooksDir = join3(claudeDir, "hooks");
|
|
608
|
+
if (!existsSync5(hooksDir)) {
|
|
609
|
+
mkdirSync3(hooksDir, { recursive: true });
|
|
610
|
+
for (const [filename, content] of Object.entries(HOOK_TEMPLATES)) {
|
|
611
|
+
writeFileSync2(join3(hooksDir, filename), content);
|
|
612
|
+
}
|
|
613
|
+
if (!options.quiet) {
|
|
614
|
+
console.log(`\u2713 Created .claude/hooks/`);
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
if (!options.quiet) {
|
|
619
|
+
console.log(`\u2713 Connected "${projectName}"`);
|
|
620
|
+
console.log(` Path: ${projectPath}`);
|
|
621
|
+
console.log(` Category: ${options.category || "default"}`);
|
|
622
|
+
console.log(` OpenSpec: ${openspec ? "Detected" : "Not found"}`);
|
|
623
|
+
console.log("");
|
|
624
|
+
console.log('Next: Run "mc sync" to create your first update.');
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
// src/commands/sync.ts
|
|
629
|
+
import { basename as basename3, resolve as resolve2 } from "path";
|
|
630
|
+
|
|
631
|
+
// src/lib/update-file.ts
|
|
632
|
+
import { existsSync as existsSync6, readdirSync as readdirSync2, readFileSync as readFileSync3, writeFileSync as writeFileSync3 } from "fs";
|
|
633
|
+
import { join as join4 } from "path";
|
|
634
|
+
function createUpdateFile(projectName, progress2, context2, recentCommits) {
|
|
635
|
+
const { name, id } = getMachineId();
|
|
636
|
+
const filename = generateUpdateFilename();
|
|
637
|
+
const filepath = join4(getProjectDir(projectName), filename);
|
|
638
|
+
const update = {
|
|
639
|
+
version: "1.0",
|
|
640
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
641
|
+
machine: name,
|
|
642
|
+
machine_id: id,
|
|
643
|
+
project: projectName,
|
|
644
|
+
progress: progress2,
|
|
645
|
+
context: context2,
|
|
646
|
+
recent_commits: recentCommits
|
|
647
|
+
};
|
|
648
|
+
ensureProjectDir(projectName);
|
|
649
|
+
writeFileSync3(filepath, JSON.stringify(update, null, 2));
|
|
650
|
+
return filepath;
|
|
651
|
+
}
|
|
652
|
+
function readUpdateFiles(projectName) {
|
|
653
|
+
const dir = getProjectDir(projectName);
|
|
654
|
+
if (!existsSync6(dir)) {
|
|
655
|
+
return [];
|
|
656
|
+
}
|
|
657
|
+
const files = readdirSync2(dir).filter((f) => f.endsWith(".json"));
|
|
658
|
+
const updates = [];
|
|
659
|
+
for (const file of files) {
|
|
660
|
+
try {
|
|
661
|
+
const content = readFileSync3(join4(dir, file), "utf8");
|
|
662
|
+
updates.push(JSON.parse(content));
|
|
663
|
+
} catch {
|
|
664
|
+
}
|
|
665
|
+
}
|
|
666
|
+
return updates.sort(
|
|
667
|
+
(a, b) => new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime()
|
|
668
|
+
);
|
|
669
|
+
}
|
|
670
|
+
function getLatestUpdate(projectName) {
|
|
671
|
+
const updates = readUpdateFiles(projectName);
|
|
672
|
+
return updates[0] || null;
|
|
673
|
+
}
|
|
674
|
+
function getLatestByMachine(projectName) {
|
|
675
|
+
const updates = readUpdateFiles(projectName);
|
|
676
|
+
const byMachine = /* @__PURE__ */ new Map();
|
|
677
|
+
for (const update of updates) {
|
|
678
|
+
if (!byMachine.has(update.machine_id)) {
|
|
679
|
+
byMachine.set(update.machine_id, update);
|
|
680
|
+
}
|
|
681
|
+
}
|
|
682
|
+
return byMachine;
|
|
683
|
+
}
|
|
684
|
+
function getRecentUpdates(projectName, limit = 10) {
|
|
685
|
+
const updates = readUpdateFiles(projectName);
|
|
686
|
+
return updates.slice(0, limit);
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
// src/commands/sync.ts
|
|
690
|
+
async function sync2(options = {}) {
|
|
691
|
+
if (!isInitialized()) {
|
|
692
|
+
if (!options.quiet) {
|
|
693
|
+
console.error('MindContext is not initialized. Run "mc init" first.');
|
|
694
|
+
}
|
|
695
|
+
process.exit(1);
|
|
696
|
+
}
|
|
697
|
+
const config = readConfig();
|
|
698
|
+
if (!config) {
|
|
699
|
+
if (!options.quiet) {
|
|
700
|
+
console.error("Failed to read config.");
|
|
701
|
+
}
|
|
702
|
+
process.exit(1);
|
|
703
|
+
}
|
|
704
|
+
const projectPath = resolve2(process.cwd());
|
|
705
|
+
const projectName = basename3(projectPath);
|
|
706
|
+
const project = config.projects[projectName];
|
|
707
|
+
if (!project) {
|
|
708
|
+
if (!options.quiet) {
|
|
709
|
+
console.error(`Project "${projectName}" is not connected.`);
|
|
710
|
+
console.error('Run "mc connect" first.');
|
|
711
|
+
}
|
|
712
|
+
process.exit(1);
|
|
713
|
+
}
|
|
714
|
+
const progress2 = getOpenSpecProgress(projectPath);
|
|
715
|
+
const context2 = {
|
|
716
|
+
current_task: progress2.change ? `Working on ${progress2.change}` : void 0,
|
|
717
|
+
status: options.status || (progress2.tasks_done > 0 ? "in_progress" : "idle"),
|
|
718
|
+
notes: options.notes || [],
|
|
719
|
+
next: options.next || []
|
|
720
|
+
};
|
|
721
|
+
if (!options.quiet) {
|
|
722
|
+
console.log(`Syncing "${projectName}"...`);
|
|
723
|
+
if (progress2.source === "openspec") {
|
|
724
|
+
console.log(` Change: ${progress2.change}`);
|
|
725
|
+
console.log(` Progress: ${progress2.tasks_done}/${progress2.tasks_total}`);
|
|
726
|
+
}
|
|
727
|
+
}
|
|
728
|
+
const recentCommits = getRecentCommits(projectPath, 5);
|
|
729
|
+
if (options.dryRun) {
|
|
730
|
+
if (!options.quiet) {
|
|
731
|
+
console.log("\n[Dry run] Would create update file:");
|
|
732
|
+
console.log(JSON.stringify({ progress: progress2, context: context2, recent_commits: recentCommits }, null, 2));
|
|
733
|
+
}
|
|
734
|
+
return;
|
|
735
|
+
}
|
|
736
|
+
const filepath = createUpdateFile(projectName, progress2, context2, recentCommits);
|
|
737
|
+
if (!options.quiet) {
|
|
738
|
+
console.log(` Created: ${filepath}`);
|
|
739
|
+
}
|
|
740
|
+
const commitMessage = `chore(progress): sync ${config.machine.name}`;
|
|
741
|
+
const result = sync(commitMessage);
|
|
742
|
+
if (!options.quiet) {
|
|
743
|
+
if (result.committed && result.pushed) {
|
|
744
|
+
console.log("\u2713 Synced successfully");
|
|
745
|
+
} else if (result.committed) {
|
|
746
|
+
console.log("\u2713 Committed locally (push pending)");
|
|
747
|
+
console.log(' Run "mc push" when online to push changes.');
|
|
748
|
+
} else {
|
|
749
|
+
console.log(` ${result.message}`);
|
|
750
|
+
}
|
|
751
|
+
}
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
// src/commands/pull.ts
|
|
755
|
+
async function pull2(options = {}) {
|
|
756
|
+
if (!isInitialized()) {
|
|
757
|
+
if (!options.quiet) {
|
|
758
|
+
console.error('MindContext is not initialized. Run "mc init" first.');
|
|
759
|
+
}
|
|
760
|
+
process.exit(1);
|
|
761
|
+
}
|
|
762
|
+
if (!options.quiet) {
|
|
763
|
+
console.log("Pulling latest updates...");
|
|
764
|
+
}
|
|
765
|
+
const result = pull();
|
|
766
|
+
if (!options.quiet) {
|
|
767
|
+
if (result.success) {
|
|
768
|
+
console.log("\u2713 Updated");
|
|
769
|
+
if (result.message && result.message !== "Already up to date.") {
|
|
770
|
+
console.log(` ${result.message}`);
|
|
771
|
+
}
|
|
772
|
+
} else {
|
|
773
|
+
console.error("\u2717 Failed to pull");
|
|
774
|
+
console.error(` ${result.message}`);
|
|
775
|
+
}
|
|
776
|
+
}
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
// src/commands/context.ts
|
|
780
|
+
import { basename as basename4, resolve as resolve3 } from "path";
|
|
781
|
+
async function context(options = {}) {
|
|
782
|
+
const projectPath = resolve3(process.cwd());
|
|
783
|
+
const projectName = basename4(projectPath);
|
|
784
|
+
const initialized = isInitialized();
|
|
785
|
+
const config = initialized ? readConfig() : null;
|
|
786
|
+
const project = config?.projects[projectName];
|
|
787
|
+
const progress2 = getOpenSpecProgress(projectPath);
|
|
788
|
+
const percentage = progress2.tasks_total > 0 ? Math.round(progress2.tasks_done / progress2.tasks_total * 100) : 0;
|
|
789
|
+
const output = {
|
|
790
|
+
project: projectName,
|
|
791
|
+
connected: !!project,
|
|
792
|
+
progress: {
|
|
793
|
+
...progress2,
|
|
794
|
+
percentage
|
|
795
|
+
},
|
|
796
|
+
team: []
|
|
797
|
+
};
|
|
798
|
+
if (project) {
|
|
799
|
+
const latestUpdate = getLatestUpdate(projectName);
|
|
800
|
+
if (latestUpdate) {
|
|
801
|
+
output.lastUpdate = {
|
|
802
|
+
timestamp: latestUpdate.timestamp,
|
|
803
|
+
machine: latestUpdate.machine,
|
|
804
|
+
status: latestUpdate.context.status,
|
|
805
|
+
notes: latestUpdate.context.notes,
|
|
806
|
+
next: latestUpdate.context.next
|
|
807
|
+
};
|
|
808
|
+
}
|
|
809
|
+
const byMachine = getLatestByMachine(projectName);
|
|
810
|
+
for (const [_machineId, update] of byMachine) {
|
|
811
|
+
output.team.push({
|
|
812
|
+
machine: update.machine,
|
|
813
|
+
timestamp: update.timestamp,
|
|
814
|
+
status: update.context.status
|
|
815
|
+
});
|
|
816
|
+
}
|
|
817
|
+
}
|
|
818
|
+
if (options.json) {
|
|
819
|
+
console.log(JSON.stringify(output, null, 2));
|
|
820
|
+
return;
|
|
821
|
+
}
|
|
822
|
+
if (!options.quiet) {
|
|
823
|
+
console.log(`Project: ${projectName}`);
|
|
824
|
+
console.log(`Connected: ${output.connected ? "Yes" : "No"}`);
|
|
825
|
+
console.log("");
|
|
826
|
+
if (progress2.source === "openspec" && progress2.change) {
|
|
827
|
+
console.log(`Change: ${progress2.change}`);
|
|
828
|
+
console.log(`Progress: ${progress2.tasks_done}/${progress2.tasks_total} (${percentage}%)`);
|
|
829
|
+
} else {
|
|
830
|
+
console.log("Progress: No active change");
|
|
831
|
+
}
|
|
832
|
+
if (output.lastUpdate) {
|
|
833
|
+
console.log("");
|
|
834
|
+
console.log(`Last Update: ${output.lastUpdate.timestamp}`);
|
|
835
|
+
console.log(` Machine: ${output.lastUpdate.machine}`);
|
|
836
|
+
console.log(` Status: ${output.lastUpdate.status}`);
|
|
837
|
+
if (output.lastUpdate.notes.length > 0) {
|
|
838
|
+
console.log(` Notes: ${output.lastUpdate.notes.join(", ")}`);
|
|
839
|
+
}
|
|
840
|
+
if (output.lastUpdate.next.length > 0) {
|
|
841
|
+
console.log(` Next: ${output.lastUpdate.next.join(", ")}`);
|
|
842
|
+
}
|
|
843
|
+
}
|
|
844
|
+
if (output.team.length > 1) {
|
|
845
|
+
console.log("");
|
|
846
|
+
console.log("Team Activity:");
|
|
847
|
+
for (const member of output.team) {
|
|
848
|
+
console.log(` ${member.machine}: ${member.status} (${member.timestamp})`);
|
|
849
|
+
}
|
|
850
|
+
}
|
|
851
|
+
}
|
|
852
|
+
}
|
|
853
|
+
|
|
854
|
+
// src/commands/progress.ts
|
|
855
|
+
import { execSync as execSync3 } from "child_process";
|
|
856
|
+
import { basename as basename5, resolve as resolve4 } from "path";
|
|
857
|
+
async function progress(options = {}) {
|
|
858
|
+
if (!isInitialized()) {
|
|
859
|
+
if (!options.quiet) {
|
|
860
|
+
console.error('MindContext is not initialized. Run "mc init" first.');
|
|
861
|
+
}
|
|
862
|
+
process.exit(1);
|
|
863
|
+
}
|
|
864
|
+
const config = readConfig();
|
|
865
|
+
if (!config) {
|
|
866
|
+
if (!options.quiet) {
|
|
867
|
+
console.error("Failed to read config.");
|
|
868
|
+
}
|
|
869
|
+
process.exit(1);
|
|
870
|
+
}
|
|
871
|
+
if (options.web) {
|
|
872
|
+
if (!config.dashboard_url) {
|
|
873
|
+
if (!options.quiet) {
|
|
874
|
+
console.error("No dashboard URL configured.");
|
|
875
|
+
console.error("Configure with: mc config --dashboard-url <url>");
|
|
876
|
+
}
|
|
877
|
+
process.exit(1);
|
|
878
|
+
}
|
|
879
|
+
if (!options.quiet) {
|
|
880
|
+
console.log(`Opening: ${config.dashboard_url}`);
|
|
881
|
+
}
|
|
882
|
+
try {
|
|
883
|
+
const platform = process.platform;
|
|
884
|
+
if (platform === "darwin") {
|
|
885
|
+
execSync3(`open "${config.dashboard_url}"`);
|
|
886
|
+
} else if (platform === "win32") {
|
|
887
|
+
execSync3(`start "${config.dashboard_url}"`);
|
|
888
|
+
} else {
|
|
889
|
+
execSync3(`xdg-open "${config.dashboard_url}"`);
|
|
890
|
+
}
|
|
891
|
+
} catch {
|
|
892
|
+
if (!options.quiet) {
|
|
893
|
+
console.log(`Please open: ${config.dashboard_url}`);
|
|
894
|
+
}
|
|
895
|
+
}
|
|
896
|
+
return;
|
|
897
|
+
}
|
|
898
|
+
const projectPath = resolve4(process.cwd());
|
|
899
|
+
const projectName = basename5(projectPath);
|
|
900
|
+
const project = config.projects[projectName];
|
|
901
|
+
if (!project) {
|
|
902
|
+
console.log("MindContext Progress\n");
|
|
903
|
+
console.log("Projects:");
|
|
904
|
+
for (const [name, proj] of Object.entries(config.projects)) {
|
|
905
|
+
const openspecProgress2 = getOpenSpecProgress(proj.path);
|
|
906
|
+
const percentage2 = openspecProgress2.tasks_total > 0 ? Math.round(openspecProgress2.tasks_done / openspecProgress2.tasks_total * 100) : 0;
|
|
907
|
+
const bar = generateProgressBar(percentage2);
|
|
908
|
+
console.log(` ${name}`);
|
|
909
|
+
console.log(` ${bar} ${percentage2}%`);
|
|
910
|
+
if (openspecProgress2.change) {
|
|
911
|
+
console.log(` Current: ${openspecProgress2.change}`);
|
|
912
|
+
}
|
|
913
|
+
}
|
|
914
|
+
if (Object.keys(config.projects).length === 0) {
|
|
915
|
+
console.log(" (No projects connected)");
|
|
916
|
+
console.log("");
|
|
917
|
+
console.log('Run "mc connect" in a project directory to connect it.');
|
|
918
|
+
}
|
|
919
|
+
if (config.dashboard_url) {
|
|
920
|
+
console.log("");
|
|
921
|
+
console.log(`Dashboard: ${config.dashboard_url}`);
|
|
922
|
+
console.log('Run "mc progress --web" to open in browser.');
|
|
923
|
+
}
|
|
924
|
+
return;
|
|
925
|
+
}
|
|
926
|
+
const openspecProgress = getOpenSpecProgress(projectPath);
|
|
927
|
+
const percentage = openspecProgress.tasks_total > 0 ? Math.round(openspecProgress.tasks_done / openspecProgress.tasks_total * 100) : 0;
|
|
928
|
+
console.log(`${projectName}
|
|
929
|
+
`);
|
|
930
|
+
if (openspecProgress.source === "openspec" && openspecProgress.change) {
|
|
931
|
+
console.log(`Change: ${openspecProgress.change}`);
|
|
932
|
+
console.log(`Progress: ${openspecProgress.tasks_done}/${openspecProgress.tasks_total}`);
|
|
933
|
+
console.log(generateProgressBar(percentage, 30) + ` ${percentage}%`);
|
|
934
|
+
} else {
|
|
935
|
+
console.log("No active OpenSpec change.");
|
|
936
|
+
}
|
|
937
|
+
const recentUpdates = getRecentUpdates(projectName, 5);
|
|
938
|
+
if (recentUpdates.length > 0) {
|
|
939
|
+
console.log("\nRecent Activity:");
|
|
940
|
+
for (const update of recentUpdates) {
|
|
941
|
+
const date = new Date(update.timestamp).toLocaleDateString();
|
|
942
|
+
const time = new Date(update.timestamp).toLocaleTimeString();
|
|
943
|
+
console.log(` ${date} ${time} - ${update.machine}`);
|
|
944
|
+
if (update.context.notes.length > 0) {
|
|
945
|
+
console.log(` Notes: ${update.context.notes.join(", ")}`);
|
|
946
|
+
}
|
|
947
|
+
}
|
|
948
|
+
}
|
|
949
|
+
}
|
|
950
|
+
function generateProgressBar(percentage, width = 20) {
|
|
951
|
+
const filled = Math.round(percentage / 100 * width);
|
|
952
|
+
const empty = width - filled;
|
|
953
|
+
return "\u2588".repeat(filled) + "\u2591".repeat(empty);
|
|
954
|
+
}
|
|
955
|
+
export {
|
|
956
|
+
connect,
|
|
957
|
+
context,
|
|
958
|
+
createUpdateFile,
|
|
959
|
+
generateUpdateFilename,
|
|
960
|
+
getLatestByMachine,
|
|
961
|
+
getLatestUpdate,
|
|
962
|
+
getMachineId,
|
|
963
|
+
getMindcontextDir,
|
|
964
|
+
getOpenSpecProgress,
|
|
965
|
+
getRepoDir,
|
|
966
|
+
getTimestamp,
|
|
967
|
+
hasOpenSpec,
|
|
968
|
+
init,
|
|
969
|
+
isInitialized,
|
|
970
|
+
parseOpenSpec,
|
|
971
|
+
progress,
|
|
972
|
+
pull2 as pull,
|
|
973
|
+
readConfig,
|
|
974
|
+
readUpdateFiles,
|
|
975
|
+
sync2 as sync,
|
|
976
|
+
writeConfig
|
|
977
|
+
};
|
|
978
|
+
//# sourceMappingURL=index.js.map
|