@cubedot/cli 0.1.1 → 0.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +457 -131
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -3,12 +3,12 @@
|
|
|
3
3
|
// src/cli.ts
|
|
4
4
|
import { Command } from "commander";
|
|
5
5
|
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
6
|
-
import { dirname as dirname4, join as
|
|
7
|
-
import { readFileSync as
|
|
6
|
+
import { dirname as dirname4, join as join10 } from "path";
|
|
7
|
+
import { readFileSync as readFileSync10 } from "fs";
|
|
8
8
|
|
|
9
9
|
// src/commands/init.ts
|
|
10
10
|
import pc from "picocolors";
|
|
11
|
-
import { readFileSync as readFileSync3 } from "fs";
|
|
11
|
+
import { readFileSync as readFileSync3, existsSync as existsSync3 } from "fs";
|
|
12
12
|
import { join as join3 } from "path";
|
|
13
13
|
|
|
14
14
|
// src/mcp.ts
|
|
@@ -83,7 +83,10 @@ import {
|
|
|
83
83
|
copyFileSync,
|
|
84
84
|
existsSync,
|
|
85
85
|
mkdirSync,
|
|
86
|
+
readdirSync,
|
|
86
87
|
readFileSync,
|
|
88
|
+
rmdirSync,
|
|
89
|
+
unlinkSync,
|
|
87
90
|
writeFileSync
|
|
88
91
|
} from "fs";
|
|
89
92
|
import { dirname, join } from "path";
|
|
@@ -101,13 +104,15 @@ function writeMcpJson(cwd, mcpUrl, projectId, key) {
|
|
|
101
104
|
} catch {
|
|
102
105
|
}
|
|
103
106
|
}
|
|
107
|
+
const mcpEndpoint = new URL(mcpUrl);
|
|
108
|
+
mcpEndpoint.searchParams.set("project", projectId);
|
|
104
109
|
const config = {
|
|
105
110
|
...existing,
|
|
106
111
|
mcpServers: {
|
|
107
112
|
...existing.mcpServers ?? {},
|
|
108
113
|
cubedot: {
|
|
109
114
|
type: "http",
|
|
110
|
-
url:
|
|
115
|
+
url: mcpEndpoint.toString(),
|
|
111
116
|
headers: { Authorization: `ApiKey ${key}` }
|
|
112
117
|
}
|
|
113
118
|
}
|
|
@@ -176,17 +181,18 @@ var GITIGNORE_ENTRIES = [
|
|
|
176
181
|
function updateGitignore(cwd) {
|
|
177
182
|
const path = join(cwd, ".gitignore");
|
|
178
183
|
let content = existsSync(path) ? readFileSync(path, "utf8") : "";
|
|
179
|
-
const
|
|
180
|
-
|
|
184
|
+
const existing = content.split("\n");
|
|
185
|
+
const added = [];
|
|
181
186
|
for (const entry of GITIGNORE_ENTRIES) {
|
|
182
|
-
if (!
|
|
187
|
+
if (!existing.some((l) => l.trim() === entry)) {
|
|
183
188
|
content = content.endsWith("\n") ? content + entry + "\n" : content + "\n" + entry + "\n";
|
|
184
|
-
|
|
189
|
+
added.push(entry);
|
|
185
190
|
}
|
|
186
191
|
}
|
|
187
|
-
if (
|
|
192
|
+
if (added.length > 0) {
|
|
188
193
|
writeFileSync(path, content, "utf8");
|
|
189
194
|
}
|
|
195
|
+
return added;
|
|
190
196
|
}
|
|
191
197
|
function readHashes(cwd) {
|
|
192
198
|
const path = join(cwd, ".cubedot", "sync", "hashes.json");
|
|
@@ -206,8 +212,9 @@ function enableMcpServerInSettings(cwd, serverName = "cubedot") {
|
|
|
206
212
|
const dir = join(cwd, ".claude");
|
|
207
213
|
if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
|
|
208
214
|
const path = join(dir, "settings.json");
|
|
215
|
+
const existed = existsSync(path);
|
|
209
216
|
let settings = {};
|
|
210
|
-
if (
|
|
217
|
+
if (existed) {
|
|
211
218
|
try {
|
|
212
219
|
settings = JSON.parse(readFileSync(path, "utf8"));
|
|
213
220
|
} catch {
|
|
@@ -218,6 +225,7 @@ function enableMcpServerInSettings(cwd, serverName = "cubedot") {
|
|
|
218
225
|
settings["enabledMcpjsonServers"] = [...cur, serverName];
|
|
219
226
|
}
|
|
220
227
|
writeFileSync(path, JSON.stringify(settings, null, 2) + "\n", "utf8");
|
|
228
|
+
return { created: !existed };
|
|
221
229
|
}
|
|
222
230
|
function readLinks(cwd) {
|
|
223
231
|
const path = join(cwd, ".cubedot", "links.json");
|
|
@@ -228,6 +236,76 @@ function readLinks(cwd) {
|
|
|
228
236
|
return {};
|
|
229
237
|
}
|
|
230
238
|
}
|
|
239
|
+
var MANIFEST_PATH = ".cubedot/install-manifest.json";
|
|
240
|
+
function readInstallManifest(cwd) {
|
|
241
|
+
const path = join(cwd, MANIFEST_PATH);
|
|
242
|
+
if (!existsSync(path)) return null;
|
|
243
|
+
try {
|
|
244
|
+
return JSON.parse(readFileSync(path, "utf8"));
|
|
245
|
+
} catch {
|
|
246
|
+
return null;
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
function writeInstallManifest(cwd, manifest) {
|
|
250
|
+
writeFileSync(join(cwd, MANIFEST_PATH), JSON.stringify(manifest, null, 2) + "\n", "utf8");
|
|
251
|
+
}
|
|
252
|
+
function stripGitignoreLines(cwd, lines) {
|
|
253
|
+
const path = join(cwd, ".gitignore");
|
|
254
|
+
if (!existsSync(path)) return;
|
|
255
|
+
const content = readFileSync(path, "utf8");
|
|
256
|
+
const kept = content.split("\n").filter((l) => !lines.includes(l.trim()));
|
|
257
|
+
while (kept.length > 0 && !kept[kept.length - 1]?.trim()) kept.pop();
|
|
258
|
+
if (kept.length === 0) {
|
|
259
|
+
unlinkSync(path);
|
|
260
|
+
} else {
|
|
261
|
+
writeFileSync(path, kept.join("\n") + "\n", "utf8");
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
function removeCubedotFromSettings(cwd) {
|
|
265
|
+
const path = join(cwd, ".claude", "settings.json");
|
|
266
|
+
if (!existsSync(path)) return;
|
|
267
|
+
let settings;
|
|
268
|
+
try {
|
|
269
|
+
settings = JSON.parse(readFileSync(path, "utf8"));
|
|
270
|
+
} catch {
|
|
271
|
+
return;
|
|
272
|
+
}
|
|
273
|
+
if (!Array.isArray(settings["enabledMcpjsonServers"])) return;
|
|
274
|
+
const filtered = settings["enabledMcpjsonServers"].filter(
|
|
275
|
+
(s) => s !== "cubedot"
|
|
276
|
+
);
|
|
277
|
+
if (filtered.length === 0) {
|
|
278
|
+
delete settings["enabledMcpjsonServers"];
|
|
279
|
+
} else {
|
|
280
|
+
settings["enabledMcpjsonServers"] = filtered;
|
|
281
|
+
}
|
|
282
|
+
if (Object.keys(settings).length === 0) {
|
|
283
|
+
unlinkSync(path);
|
|
284
|
+
} else {
|
|
285
|
+
writeFileSync(path, JSON.stringify(settings, null, 2) + "\n", "utf8");
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
function stripCubedotMarkers(filePath) {
|
|
289
|
+
if (!existsSync(filePath)) return;
|
|
290
|
+
const content = readFileSync(filePath, "utf8");
|
|
291
|
+
const stripped = content.replace(
|
|
292
|
+
/<!-- CUBEDOT:START:(\w+) -->[\s\S]*?<!-- CUBEDOT:END:\1 -->\n?/g,
|
|
293
|
+
""
|
|
294
|
+
);
|
|
295
|
+
const cleaned = stripped.replace(/\n{3,}/g, "\n\n").trimEnd();
|
|
296
|
+
writeFileSync(filePath, cleaned ? cleaned + "\n" : "", "utf8");
|
|
297
|
+
}
|
|
298
|
+
function tryRmdir(dirPath) {
|
|
299
|
+
if (!existsSync(dirPath)) return false;
|
|
300
|
+
try {
|
|
301
|
+
const entries = readdirSync(dirPath);
|
|
302
|
+
if (entries.length > 0) return false;
|
|
303
|
+
rmdirSync(dirPath);
|
|
304
|
+
return true;
|
|
305
|
+
} catch {
|
|
306
|
+
return false;
|
|
307
|
+
}
|
|
308
|
+
}
|
|
231
309
|
|
|
232
310
|
// src/hotmemory.ts
|
|
233
311
|
import { existsSync as existsSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "fs";
|
|
@@ -394,6 +472,25 @@ function decodeToken(token) {
|
|
|
394
472
|
}
|
|
395
473
|
async function initCommand(opts) {
|
|
396
474
|
const cwd = process.cwd();
|
|
475
|
+
const ownPkgPath = join3(cwd, "package.json");
|
|
476
|
+
if (existsSync3(ownPkgPath) && !opts.force) {
|
|
477
|
+
try {
|
|
478
|
+
const ownPkg = JSON.parse(readFileSync3(ownPkgPath, "utf8"));
|
|
479
|
+
if (ownPkg.name === "@cubedot/cli") {
|
|
480
|
+
console.error(
|
|
481
|
+
pc.red(
|
|
482
|
+
"Error: This looks like the Cubedot CLI source repo, not a project to connect.\nRun `cubedot init` inside your target project directory, or pass --force to override."
|
|
483
|
+
)
|
|
484
|
+
);
|
|
485
|
+
process.exit(1);
|
|
486
|
+
}
|
|
487
|
+
} catch {
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
const claudeMdExisted = existsSync3(join3(cwd, "CLAUDE.md"));
|
|
491
|
+
const agentsMdExisted = existsSync3(join3(cwd, "AGENTS.md"));
|
|
492
|
+
const fieldNotesExisted = existsSync3(join3(cwd, ".cubedot", "field-notes.md"));
|
|
493
|
+
const linksJsonExisted = existsSync3(join3(cwd, ".cubedot", "links.json"));
|
|
397
494
|
let projectId;
|
|
398
495
|
let key;
|
|
399
496
|
let mcpUrl;
|
|
@@ -483,7 +580,7 @@ async function initCommand(opts) {
|
|
|
483
580
|
await client.close();
|
|
484
581
|
}
|
|
485
582
|
writeMcpJson(cwd, mcpUrl, projectId, key);
|
|
486
|
-
enableMcpServerInSettings(cwd);
|
|
583
|
+
const { created: settingsCreated } = enableMcpServerInSettings(cwd);
|
|
487
584
|
writeCubedotConfig(cwd, {
|
|
488
585
|
projectId,
|
|
489
586
|
mcpUrl,
|
|
@@ -496,7 +593,33 @@ async function initCommand(opts) {
|
|
|
496
593
|
generateMemoryFiles(cwd, identity, conventions, registry);
|
|
497
594
|
installAgentAssets(cwd);
|
|
498
595
|
scaffoldCubedotDirs(cwd);
|
|
499
|
-
updateGitignore(cwd);
|
|
596
|
+
const gitignoreAdded = updateGitignore(cwd);
|
|
597
|
+
const manifest = {
|
|
598
|
+
cliVersion: pkg.version,
|
|
599
|
+
at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
600
|
+
createdFiles: [
|
|
601
|
+
".mcp.json",
|
|
602
|
+
".cubedot/config.json",
|
|
603
|
+
".cubedot/fia-operating-manual.md",
|
|
604
|
+
".claude/agents/CubedotVerifier.md",
|
|
605
|
+
...!fieldNotesExisted ? [".cubedot/field-notes.md"] : [],
|
|
606
|
+
...!linksJsonExisted ? [".cubedot/links.json"] : []
|
|
607
|
+
],
|
|
608
|
+
createdDirs: [
|
|
609
|
+
".cubedot/progress",
|
|
610
|
+
".cubedot/reports",
|
|
611
|
+
".cubedot/docs",
|
|
612
|
+
".cubedot/sync",
|
|
613
|
+
".cubedot",
|
|
614
|
+
".claude/agents",
|
|
615
|
+
".claude"
|
|
616
|
+
],
|
|
617
|
+
claudeMd: claudeMdExisted ? "inserted" : "created",
|
|
618
|
+
agentsMd: agentsMdExisted ? "inserted" : "created",
|
|
619
|
+
settingsCreated,
|
|
620
|
+
gitignoreAdded
|
|
621
|
+
};
|
|
622
|
+
writeInstallManifest(cwd, manifest);
|
|
500
623
|
const projectName = orientText.split("\n").find((l) => l.trim())?.replace(/^#+\s*/, "") ?? projectId;
|
|
501
624
|
console.log("\n" + pc.green("\u2713") + " Cubedot connected: " + pc.bold(projectName));
|
|
502
625
|
console.log(pc.dim(` Project ID : ${projectId}`));
|
|
@@ -508,6 +631,8 @@ async function initCommand(opts) {
|
|
|
508
631
|
const written = [
|
|
509
632
|
".mcp.json (gitignored \u2014 holds your key)",
|
|
510
633
|
".cubedot/config.json (gitignored)",
|
|
634
|
+
".cubedot/install-manifest.json",
|
|
635
|
+
".claude/settings.json (enabledMcpjsonServers)",
|
|
511
636
|
"AGENTS.md",
|
|
512
637
|
"CLAUDE.md",
|
|
513
638
|
".claude/agents/CubedotVerifier.md",
|
|
@@ -521,24 +646,224 @@ async function initCommand(opts) {
|
|
|
521
646
|
console.log("\nOpen Claude Code in this directory to start building. \u2728");
|
|
522
647
|
}
|
|
523
648
|
|
|
524
|
-
// src/commands/
|
|
649
|
+
// src/commands/uninstall.ts
|
|
525
650
|
import pc2 from "picocolors";
|
|
651
|
+
import { existsSync as existsSync4, readFileSync as readFileSync4, rmSync as rmSync2, unlinkSync as unlinkSync2 } from "fs";
|
|
652
|
+
import { join as join4 } from "path";
|
|
653
|
+
async function uninstallCommand(opts) {
|
|
654
|
+
const cwd = process.cwd();
|
|
655
|
+
const hasCubedot = existsSync4(join4(cwd, ".mcp.json")) || existsSync4(join4(cwd, ".cubedot")) || existsSync4(join4(cwd, ".claude", "agents", "CubedotVerifier.md"));
|
|
656
|
+
if (!hasCubedot) {
|
|
657
|
+
console.log("Nothing to uninstall here.");
|
|
658
|
+
return;
|
|
659
|
+
}
|
|
660
|
+
const manifest = readInstallManifest(cwd);
|
|
661
|
+
if (manifest) {
|
|
662
|
+
await runManifestUninstall(cwd, manifest, opts.yes ?? false);
|
|
663
|
+
} else {
|
|
664
|
+
await runHeuristicUninstall(cwd, opts.yes ?? false);
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
async function confirm(skipConfirm) {
|
|
668
|
+
if (skipConfirm) return true;
|
|
669
|
+
if (!process.stdin.isTTY) {
|
|
670
|
+
console.error(pc2.red("Non-interactive shell: pass --yes / -y to skip confirmation."));
|
|
671
|
+
process.exit(1);
|
|
672
|
+
}
|
|
673
|
+
const { default: input } = await import("@inquirer/input");
|
|
674
|
+
const answer = await input({ message: "Continue? [y/N]" });
|
|
675
|
+
return answer.trim().toLowerCase() === "y";
|
|
676
|
+
}
|
|
677
|
+
function execute(actions) {
|
|
678
|
+
for (const a of actions) {
|
|
679
|
+
try {
|
|
680
|
+
a.fn();
|
|
681
|
+
} catch (err) {
|
|
682
|
+
console.warn(pc2.yellow(` \u26A0 Could not complete "${a.desc}": ${err.message}`));
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
}
|
|
686
|
+
async function runManifestUninstall(cwd, manifest, skipConfirm) {
|
|
687
|
+
const actions = [];
|
|
688
|
+
for (const rel of manifest.createdFiles) {
|
|
689
|
+
const abs = join4(cwd, rel);
|
|
690
|
+
if (existsSync4(abs)) {
|
|
691
|
+
actions.push({ desc: `delete ${rel}`, fn: () => unlinkSync2(abs) });
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
const claudePath = join4(cwd, "CLAUDE.md");
|
|
695
|
+
if (existsSync4(claudePath)) {
|
|
696
|
+
if (manifest.claudeMd === "created") {
|
|
697
|
+
actions.push({
|
|
698
|
+
desc: "delete CLAUDE.md (created by cubedot init)",
|
|
699
|
+
fn: () => unlinkSync2(claudePath)
|
|
700
|
+
});
|
|
701
|
+
} else {
|
|
702
|
+
actions.push({
|
|
703
|
+
desc: "CLAUDE.md \u2014 remove CUBEDOT marker blocks (preserves your content)",
|
|
704
|
+
fn: () => stripCubedotMarkers(claudePath)
|
|
705
|
+
});
|
|
706
|
+
}
|
|
707
|
+
}
|
|
708
|
+
const agentsPath = join4(cwd, "AGENTS.md");
|
|
709
|
+
if (existsSync4(agentsPath)) {
|
|
710
|
+
if (manifest.agentsMd === "created") {
|
|
711
|
+
actions.push({
|
|
712
|
+
desc: "delete AGENTS.md (created by cubedot init)",
|
|
713
|
+
fn: () => unlinkSync2(agentsPath)
|
|
714
|
+
});
|
|
715
|
+
} else {
|
|
716
|
+
actions.push({
|
|
717
|
+
desc: "AGENTS.md \u2014 remove CUBEDOT marker blocks (preserves your content)",
|
|
718
|
+
fn: () => stripCubedotMarkers(agentsPath)
|
|
719
|
+
});
|
|
720
|
+
}
|
|
721
|
+
}
|
|
722
|
+
const settingsPath = join4(cwd, ".claude", "settings.json");
|
|
723
|
+
if (existsSync4(settingsPath)) {
|
|
724
|
+
if (manifest.settingsCreated) {
|
|
725
|
+
actions.push({
|
|
726
|
+
desc: "delete .claude/settings.json (created by cubedot init)",
|
|
727
|
+
fn: () => unlinkSync2(settingsPath)
|
|
728
|
+
});
|
|
729
|
+
} else {
|
|
730
|
+
actions.push({
|
|
731
|
+
desc: '.claude/settings.json \u2014 remove "cubedot" from enabledMcpjsonServers',
|
|
732
|
+
fn: () => removeCubedotFromSettings(cwd)
|
|
733
|
+
});
|
|
734
|
+
}
|
|
735
|
+
}
|
|
736
|
+
if (manifest.gitignoreAdded.length > 0 && existsSync4(join4(cwd, ".gitignore"))) {
|
|
737
|
+
actions.push({
|
|
738
|
+
desc: `.gitignore \u2014 remove lines: ${manifest.gitignoreAdded.join(", ")}`,
|
|
739
|
+
fn: () => stripGitignoreLines(cwd, manifest.gitignoreAdded)
|
|
740
|
+
});
|
|
741
|
+
}
|
|
742
|
+
const manifestAbs = join4(cwd, MANIFEST_PATH);
|
|
743
|
+
if (existsSync4(manifestAbs)) {
|
|
744
|
+
actions.push({
|
|
745
|
+
desc: `delete ${MANIFEST_PATH}`,
|
|
746
|
+
fn: () => unlinkSync2(manifestAbs)
|
|
747
|
+
});
|
|
748
|
+
}
|
|
749
|
+
if (actions.length === 0) {
|
|
750
|
+
console.log("Nothing to uninstall here.");
|
|
751
|
+
return;
|
|
752
|
+
}
|
|
753
|
+
printPlan(actions, true);
|
|
754
|
+
if (!await confirm(skipConfirm)) {
|
|
755
|
+
console.log("Aborted.");
|
|
756
|
+
return;
|
|
757
|
+
}
|
|
758
|
+
execute(actions);
|
|
759
|
+
const dirsToTry = [...manifest.createdDirs].reverse();
|
|
760
|
+
for (const rel of dirsToTry) {
|
|
761
|
+
tryRmdir(join4(cwd, rel));
|
|
762
|
+
}
|
|
763
|
+
console.log("\n" + pc2.green("\u2713") + " Uninstall complete. This folder is no longer connected to Cubedot.");
|
|
764
|
+
}
|
|
765
|
+
async function runHeuristicUninstall(cwd, skipConfirm) {
|
|
766
|
+
const actions = [];
|
|
767
|
+
const mcpPath = join4(cwd, ".mcp.json");
|
|
768
|
+
if (existsSync4(mcpPath)) {
|
|
769
|
+
actions.push({ desc: "delete .mcp.json", fn: () => unlinkSync2(mcpPath) });
|
|
770
|
+
}
|
|
771
|
+
const cubedotDir = join4(cwd, ".cubedot");
|
|
772
|
+
if (existsSync4(cubedotDir)) {
|
|
773
|
+
actions.push({
|
|
774
|
+
desc: "delete .cubedot/ (entire directory)",
|
|
775
|
+
fn: () => rmSync2(cubedotDir, { recursive: true, force: true })
|
|
776
|
+
});
|
|
777
|
+
}
|
|
778
|
+
const verifierPath = join4(cwd, ".claude", "agents", "CubedotVerifier.md");
|
|
779
|
+
if (existsSync4(verifierPath)) {
|
|
780
|
+
actions.push({
|
|
781
|
+
desc: "delete .claude/agents/CubedotVerifier.md",
|
|
782
|
+
fn: () => unlinkSync2(verifierPath)
|
|
783
|
+
});
|
|
784
|
+
}
|
|
785
|
+
const settingsPath = join4(cwd, ".claude", "settings.json");
|
|
786
|
+
if (existsSync4(settingsPath)) {
|
|
787
|
+
actions.push({
|
|
788
|
+
desc: '.claude/settings.json \u2014 remove "cubedot" from enabledMcpjsonServers',
|
|
789
|
+
fn: () => removeCubedotFromSettings(cwd)
|
|
790
|
+
});
|
|
791
|
+
}
|
|
792
|
+
const gitignorePath = join4(cwd, ".gitignore");
|
|
793
|
+
if (existsSync4(gitignorePath)) {
|
|
794
|
+
const content = readFileSync4(gitignorePath, "utf8");
|
|
795
|
+
const lines = content.split("\n");
|
|
796
|
+
if (GITIGNORE_ENTRIES.some((e) => lines.some((l) => l.trim() === e))) {
|
|
797
|
+
actions.push({
|
|
798
|
+
desc: ".gitignore \u2014 remove cubedot lines",
|
|
799
|
+
fn: () => stripGitignoreLines(cwd, GITIGNORE_ENTRIES)
|
|
800
|
+
});
|
|
801
|
+
}
|
|
802
|
+
}
|
|
803
|
+
const claudePath = join4(cwd, "CLAUDE.md");
|
|
804
|
+
if (existsSync4(claudePath)) {
|
|
805
|
+
const content = readFileSync4(claudePath, "utf8");
|
|
806
|
+
if (content.includes("<!-- CUBEDOT:START:")) {
|
|
807
|
+
actions.push({
|
|
808
|
+
desc: "delete CLAUDE.md (contains cubedot markers \u2014 treated as generated)",
|
|
809
|
+
fn: () => unlinkSync2(claudePath)
|
|
810
|
+
});
|
|
811
|
+
}
|
|
812
|
+
}
|
|
813
|
+
const agentsPath = join4(cwd, "AGENTS.md");
|
|
814
|
+
if (existsSync4(agentsPath)) {
|
|
815
|
+
const content = readFileSync4(agentsPath, "utf8");
|
|
816
|
+
if (content.includes("<!-- CUBEDOT:START:")) {
|
|
817
|
+
actions.push({
|
|
818
|
+
desc: "delete AGENTS.md (contains cubedot markers \u2014 treated as generated)",
|
|
819
|
+
fn: () => unlinkSync2(agentsPath)
|
|
820
|
+
});
|
|
821
|
+
}
|
|
822
|
+
}
|
|
823
|
+
if (actions.length === 0) {
|
|
824
|
+
console.log("Nothing to uninstall here.");
|
|
825
|
+
return;
|
|
826
|
+
}
|
|
827
|
+
printPlan(actions, false);
|
|
828
|
+
if (!await confirm(skipConfirm)) {
|
|
829
|
+
console.log("Aborted.");
|
|
830
|
+
return;
|
|
831
|
+
}
|
|
832
|
+
execute(actions);
|
|
833
|
+
tryRmdir(join4(cwd, ".claude", "agents"));
|
|
834
|
+
tryRmdir(join4(cwd, ".claude"));
|
|
835
|
+
console.log("\n" + pc2.green("\u2713") + " Uninstall complete. This folder is no longer connected to Cubedot.");
|
|
836
|
+
}
|
|
837
|
+
function printPlan(actions, hasManifest) {
|
|
838
|
+
const mode = hasManifest ? "manifest-based (precise)" : "heuristic (no manifest found)";
|
|
839
|
+
console.log(pc2.bold(`
|
|
840
|
+
Cubedot uninstall \u2014 ${mode}
|
|
841
|
+
`));
|
|
842
|
+
console.log("The following changes will be made:\n");
|
|
843
|
+
for (const a of actions) {
|
|
844
|
+
console.log(" " + pc2.dim("\u2022") + " " + a.desc);
|
|
845
|
+
}
|
|
846
|
+
console.log("");
|
|
847
|
+
}
|
|
848
|
+
|
|
849
|
+
// src/commands/sync.ts
|
|
850
|
+
import pc3 from "picocolors";
|
|
526
851
|
import { createHash } from "crypto";
|
|
527
|
-
import { existsSync as
|
|
528
|
-
import { join as
|
|
852
|
+
import { existsSync as existsSync6, readFileSync as readFileSync6 } from "fs";
|
|
853
|
+
import { join as join6 } from "path";
|
|
529
854
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
530
855
|
import { dirname as dirname3 } from "path";
|
|
531
856
|
|
|
532
857
|
// src/ledger.ts
|
|
533
858
|
import {
|
|
534
|
-
existsSync as
|
|
859
|
+
existsSync as existsSync5,
|
|
535
860
|
mkdirSync as mkdirSync2,
|
|
536
|
-
readFileSync as
|
|
537
|
-
readdirSync,
|
|
861
|
+
readFileSync as readFileSync5,
|
|
862
|
+
readdirSync as readdirSync2,
|
|
538
863
|
renameSync,
|
|
539
864
|
writeFileSync as writeFileSync3
|
|
540
865
|
} from "fs";
|
|
541
|
-
import { join as
|
|
866
|
+
import { join as join5 } from "path";
|
|
542
867
|
import { randomUUID } from "crypto";
|
|
543
868
|
var FN_CODE_REGEX = /^FN-\d{3}-\d{2,}$/;
|
|
544
869
|
function validateFnCode(code) {
|
|
@@ -549,13 +874,13 @@ function validateFnCode(code) {
|
|
|
549
874
|
}
|
|
550
875
|
}
|
|
551
876
|
function getLedgerPath(cwd, code) {
|
|
552
|
-
return
|
|
877
|
+
return join5(cwd, ".cubedot", "progress", `${code}.json`);
|
|
553
878
|
}
|
|
554
879
|
function readLedger(cwd, code) {
|
|
555
880
|
const path = getLedgerPath(cwd, code);
|
|
556
|
-
if (!
|
|
881
|
+
if (!existsSync5(path)) return null;
|
|
557
882
|
try {
|
|
558
|
-
return JSON.parse(
|
|
883
|
+
return JSON.parse(readFileSync5(path, "utf8"));
|
|
559
884
|
} catch {
|
|
560
885
|
throw new Error(
|
|
561
886
|
`Failed to read ledger for ${code}: file exists but contains malformed JSON.`
|
|
@@ -563,11 +888,11 @@ function readLedger(cwd, code) {
|
|
|
563
888
|
}
|
|
564
889
|
}
|
|
565
890
|
function readAllLedgers(cwd) {
|
|
566
|
-
const dir =
|
|
567
|
-
if (!
|
|
568
|
-
return
|
|
891
|
+
const dir = join5(cwd, ".cubedot", "progress");
|
|
892
|
+
if (!existsSync5(dir)) return [];
|
|
893
|
+
return readdirSync2(dir).filter((f) => f.endsWith(".json")).map((f) => {
|
|
569
894
|
try {
|
|
570
|
-
return JSON.parse(
|
|
895
|
+
return JSON.parse(readFileSync5(join5(dir, f), "utf8"));
|
|
571
896
|
} catch {
|
|
572
897
|
return null;
|
|
573
898
|
}
|
|
@@ -575,8 +900,8 @@ function readAllLedgers(cwd) {
|
|
|
575
900
|
}
|
|
576
901
|
function writeLedger(cwd, record) {
|
|
577
902
|
const path = getLedgerPath(cwd, record.code);
|
|
578
|
-
const dir =
|
|
579
|
-
if (!
|
|
903
|
+
const dir = join5(cwd, ".cubedot", "progress");
|
|
904
|
+
if (!existsSync5(dir)) mkdirSync2(dir, { recursive: true });
|
|
580
905
|
const tmp = `${path}.${randomUUID()}.tmp`;
|
|
581
906
|
writeFileSync3(tmp, JSON.stringify(record, null, 2) + "\n", "utf8");
|
|
582
907
|
renameSync(tmp, path);
|
|
@@ -669,12 +994,12 @@ function doneFn(cwd, code, actor = "human", force = false, discrepancies = []) {
|
|
|
669
994
|
var __filename2 = fileURLToPath3(import.meta.url);
|
|
670
995
|
var __dirname3 = dirname3(__filename2);
|
|
671
996
|
var pkg2 = JSON.parse(
|
|
672
|
-
|
|
997
|
+
readFileSync6(join6(__dirname3, "../package.json"), "utf8")
|
|
673
998
|
);
|
|
674
999
|
function fileHash(filePath) {
|
|
675
|
-
if (!
|
|
1000
|
+
if (!existsSync6(filePath)) return "";
|
|
676
1001
|
try {
|
|
677
|
-
return createHash("sha256").update(
|
|
1002
|
+
return createHash("sha256").update(readFileSync6(filePath)).digest("hex");
|
|
678
1003
|
} catch {
|
|
679
1004
|
return "";
|
|
680
1005
|
}
|
|
@@ -684,7 +1009,7 @@ async function syncCommand() {
|
|
|
684
1009
|
const config = readCubedotConfig(cwd);
|
|
685
1010
|
if (!config) {
|
|
686
1011
|
console.error(
|
|
687
|
-
|
|
1012
|
+
pc3.red(
|
|
688
1013
|
"Error: .cubedot/config.json not found. Run `cubedot init` first."
|
|
689
1014
|
)
|
|
690
1015
|
);
|
|
@@ -695,26 +1020,26 @@ async function syncCommand() {
|
|
|
695
1020
|
const ageDays = (Date.now() - new Date(config.lastSynced).getTime()) / 864e5;
|
|
696
1021
|
if (ageDays >= STALE_DAYS) {
|
|
697
1022
|
console.log(
|
|
698
|
-
|
|
1023
|
+
pc3.yellow(
|
|
699
1024
|
`\u26A0 Last synced ${Math.floor(ageDays)} day(s) ago \u2014 refreshing now.`
|
|
700
1025
|
)
|
|
701
1026
|
);
|
|
702
1027
|
}
|
|
703
1028
|
}
|
|
704
1029
|
const connectUrl = buildConnectUrl(config.mcpUrl, config.projectId);
|
|
705
|
-
console.log(
|
|
1030
|
+
console.log(pc3.dim(`Connecting to ${connectUrl} \u2026`));
|
|
706
1031
|
let client;
|
|
707
1032
|
try {
|
|
708
1033
|
client = await createMcpClient(connectUrl, await readKeyFromMcpJson(cwd));
|
|
709
1034
|
} catch (err) {
|
|
710
|
-
console.error(
|
|
1035
|
+
console.error(pc3.red(`Error: ${categorizeError(err)}`));
|
|
711
1036
|
process.exit(1);
|
|
712
1037
|
}
|
|
713
1038
|
try {
|
|
714
1039
|
await validateTools(client);
|
|
715
1040
|
} catch (err) {
|
|
716
1041
|
await client.close();
|
|
717
|
-
console.error(
|
|
1042
|
+
console.error(pc3.red(`Error: ${err.message}`));
|
|
718
1043
|
process.exit(1);
|
|
719
1044
|
}
|
|
720
1045
|
let orientText = "";
|
|
@@ -732,7 +1057,7 @@ async function syncCommand() {
|
|
|
732
1057
|
} catch (err) {
|
|
733
1058
|
await client.close();
|
|
734
1059
|
console.error(
|
|
735
|
-
|
|
1060
|
+
pc3.red(`Error fetching project data: ${err.message}`)
|
|
736
1061
|
);
|
|
737
1062
|
process.exit(1);
|
|
738
1063
|
} finally {
|
|
@@ -759,7 +1084,7 @@ async function syncCommand() {
|
|
|
759
1084
|
const skipOrphanDetection = specFnCodes.size === 0 && ledgers.length > 0;
|
|
760
1085
|
if (skipOrphanDetection) {
|
|
761
1086
|
console.log(
|
|
762
|
-
|
|
1087
|
+
pc3.yellow(
|
|
763
1088
|
"\u26A0 Spec tree returned no functionalities \u2014 skipping orphan detection this sync."
|
|
764
1089
|
)
|
|
765
1090
|
);
|
|
@@ -775,7 +1100,7 @@ async function syncCommand() {
|
|
|
775
1100
|
if (record.state === "done") {
|
|
776
1101
|
const files = links[record.code] ?? [];
|
|
777
1102
|
for (const filePath of files) {
|
|
778
|
-
const absPath =
|
|
1103
|
+
const absPath = join6(cwd, filePath);
|
|
779
1104
|
const newHash = fileHash(absPath);
|
|
780
1105
|
const oldHash = prevHashes[filePath] ?? "";
|
|
781
1106
|
nextHashes[filePath] = newHash;
|
|
@@ -793,35 +1118,35 @@ async function syncCommand() {
|
|
|
793
1118
|
}
|
|
794
1119
|
for (const [code, files] of Object.entries(links)) {
|
|
795
1120
|
for (const filePath of files) {
|
|
796
|
-
const absPath =
|
|
1121
|
+
const absPath = join6(cwd, filePath);
|
|
797
1122
|
nextHashes[filePath] = fileHash(absPath);
|
|
798
1123
|
}
|
|
799
1124
|
}
|
|
800
1125
|
writeHashes(cwd, nextHashes);
|
|
801
|
-
console.log(
|
|
1126
|
+
console.log(pc3.green("\u2713") + " Sync complete.");
|
|
802
1127
|
if (orphaned.length > 0) {
|
|
803
1128
|
console.log(
|
|
804
|
-
|
|
1129
|
+
pc3.yellow(` Orphaned (removed from spec): ${orphaned.join(", ")}`)
|
|
805
1130
|
);
|
|
806
1131
|
}
|
|
807
1132
|
if (needsReverification.length > 0) {
|
|
808
1133
|
console.log(
|
|
809
|
-
|
|
1134
|
+
pc3.yellow(
|
|
810
1135
|
` Needs re-verification (spec changed after done): ${needsReverification.join(", ")}`
|
|
811
1136
|
)
|
|
812
1137
|
);
|
|
813
1138
|
}
|
|
814
1139
|
if (orphaned.length === 0 && needsReverification.length === 0) {
|
|
815
|
-
console.log(
|
|
1140
|
+
console.log(pc3.dim(" No drift detected."));
|
|
816
1141
|
}
|
|
817
|
-
console.log(
|
|
1142
|
+
console.log(pc3.dim(" AGENTS.md and CLAUDE.md updated."));
|
|
818
1143
|
}
|
|
819
1144
|
async function readKeyFromMcpJson(cwd) {
|
|
820
|
-
const mcpPath =
|
|
821
|
-
if (!
|
|
1145
|
+
const mcpPath = join6(cwd, ".mcp.json");
|
|
1146
|
+
if (!existsSync6(mcpPath)) {
|
|
822
1147
|
throw new Error(".mcp.json not found. Run `cubedot init` first.");
|
|
823
1148
|
}
|
|
824
|
-
const mcp = JSON.parse(
|
|
1149
|
+
const mcp = JSON.parse(readFileSync6(mcpPath, "utf8"));
|
|
825
1150
|
const headers = mcp.mcpServers?.["cubedot"]?.headers ?? {};
|
|
826
1151
|
const authHeader = headers["Authorization"] ?? headers["authorization"] ?? "";
|
|
827
1152
|
const key = authHeader.replace(/^ApiKey\s+/i, "").trim();
|
|
@@ -834,90 +1159,90 @@ async function readKeyFromMcpJson(cwd) {
|
|
|
834
1159
|
}
|
|
835
1160
|
|
|
836
1161
|
// src/commands/status.ts
|
|
837
|
-
import
|
|
838
|
-
import { existsSync as
|
|
839
|
-
import { join as
|
|
1162
|
+
import pc4 from "picocolors";
|
|
1163
|
+
import { existsSync as existsSync7, readFileSync as readFileSync7 } from "fs";
|
|
1164
|
+
import { join as join7 } from "path";
|
|
840
1165
|
async function statusCommand() {
|
|
841
1166
|
const cwd = process.cwd();
|
|
842
1167
|
let ok = true;
|
|
843
1168
|
const config = readCubedotConfig(cwd);
|
|
844
1169
|
if (!config) {
|
|
845
|
-
console.log(
|
|
1170
|
+
console.log(pc4.red("\u2717") + " .cubedot/config.json \u2014 missing (run `cubedot init`)");
|
|
846
1171
|
ok = false;
|
|
847
1172
|
} else {
|
|
848
|
-
console.log(
|
|
849
|
-
console.log(
|
|
1173
|
+
console.log(pc4.green("\u2713") + " .cubedot/config.json");
|
|
1174
|
+
console.log(pc4.dim(` projectId: ${config.projectId}`));
|
|
850
1175
|
if (config.lastSynced) {
|
|
851
1176
|
const ageDays = (Date.now() - new Date(config.lastSynced).getTime()) / 864e5;
|
|
852
1177
|
const ageStr = ageDays < 1 ? "today" : `${Math.floor(ageDays)} day(s) ago`;
|
|
853
1178
|
const stale = ageDays >= 3;
|
|
854
1179
|
console.log(
|
|
855
|
-
(stale ?
|
|
1180
|
+
(stale ? pc4.yellow("\u26A0 ") : pc4.dim(" ")) + ` Last synced: ${ageStr}${stale ? " \u2014 consider running `cubedot sync`" : ""}`
|
|
856
1181
|
);
|
|
857
1182
|
}
|
|
858
1183
|
}
|
|
859
|
-
const mcpPath =
|
|
860
|
-
if (!
|
|
861
|
-
console.log(
|
|
1184
|
+
const mcpPath = join7(cwd, ".mcp.json");
|
|
1185
|
+
if (!existsSync7(mcpPath)) {
|
|
1186
|
+
console.log(pc4.red("\u2717") + " .mcp.json \u2014 missing");
|
|
862
1187
|
ok = false;
|
|
863
1188
|
} else {
|
|
864
1189
|
try {
|
|
865
|
-
const mcp = JSON.parse(
|
|
1190
|
+
const mcp = JSON.parse(readFileSync7(mcpPath, "utf8"));
|
|
866
1191
|
const headers = mcp.mcpServers?.["cubedot"]?.headers ?? {};
|
|
867
1192
|
const authHeader = headers["Authorization"] ?? headers["authorization"] ?? "";
|
|
868
1193
|
const keyRaw = authHeader.replace(/^ApiKey\s+/i, "").trim();
|
|
869
1194
|
const keyTail = keyRaw ? keyRaw.slice(-8) : "(empty)";
|
|
870
|
-
console.log(
|
|
1195
|
+
console.log(pc4.green("\u2713") + ` .mcp.json \u2014 key tail: \u2026${keyTail}`);
|
|
871
1196
|
} catch {
|
|
872
|
-
console.log(
|
|
1197
|
+
console.log(pc4.red("\u2717") + " .mcp.json \u2014 malformed JSON");
|
|
873
1198
|
ok = false;
|
|
874
1199
|
}
|
|
875
1200
|
}
|
|
876
1201
|
for (const file of ["AGENTS.md", "CLAUDE.md"]) {
|
|
877
|
-
const path =
|
|
878
|
-
if (!
|
|
879
|
-
console.log(
|
|
1202
|
+
const path = join7(cwd, file);
|
|
1203
|
+
if (!existsSync7(path)) {
|
|
1204
|
+
console.log(pc4.red("\u2717") + ` ${file} \u2014 missing`);
|
|
880
1205
|
ok = false;
|
|
881
1206
|
} else {
|
|
882
|
-
const content =
|
|
1207
|
+
const content = readFileSync7(path, "utf8");
|
|
883
1208
|
const markers = hasAllMarkers(content);
|
|
884
1209
|
console.log(
|
|
885
|
-
(markers ?
|
|
1210
|
+
(markers ? pc4.green("\u2713") : pc4.yellow("\u26A0")) + ` ${file}${markers ? "" : " \u2014 markers missing or malformed (run `cubedot sync`)"}`
|
|
886
1211
|
);
|
|
887
1212
|
}
|
|
888
1213
|
}
|
|
889
|
-
const verifier =
|
|
890
|
-
const manual =
|
|
1214
|
+
const verifier = join7(cwd, ".claude", "agents", "CubedotVerifier.md");
|
|
1215
|
+
const manual = join7(cwd, ".cubedot", "fia-operating-manual.md");
|
|
891
1216
|
console.log(
|
|
892
|
-
(
|
|
1217
|
+
(existsSync7(verifier) ? pc4.green("\u2713") : pc4.red("\u2717")) + " .claude/agents/CubedotVerifier.md"
|
|
893
1218
|
);
|
|
894
1219
|
console.log(
|
|
895
|
-
(
|
|
1220
|
+
(existsSync7(manual) ? pc4.green("\u2713") : pc4.red("\u2717")) + " .cubedot/fia-operating-manual.md"
|
|
896
1221
|
);
|
|
897
1222
|
const ledgers = readAllLedgers(cwd);
|
|
898
1223
|
if (ledgers.length === 0) {
|
|
899
|
-
console.log(
|
|
1224
|
+
console.log(pc4.dim(" Ledger: no entries yet"));
|
|
900
1225
|
} else {
|
|
901
1226
|
const counts = {};
|
|
902
1227
|
for (const l of ledgers) counts[l.state] = (counts[l.state] ?? 0) + 1;
|
|
903
1228
|
const summary = Object.entries(counts).map(([s, n]) => `${n} ${s}`).join(", ");
|
|
904
|
-
console.log(
|
|
1229
|
+
console.log(pc4.dim(` Ledger: ${ledgers.length} entries (${summary})`));
|
|
905
1230
|
}
|
|
906
1231
|
if (config) {
|
|
907
|
-
console.log(
|
|
1232
|
+
console.log(pc4.dim("\nChecking MCP connectivity \u2026"));
|
|
908
1233
|
try {
|
|
909
1234
|
const key = await readKeyFromMcpJson(cwd);
|
|
910
1235
|
const connectUrl = buildConnectUrl(config.mcpUrl, config.projectId);
|
|
911
1236
|
const client = await createMcpClient(connectUrl, key);
|
|
912
1237
|
try {
|
|
913
1238
|
await validateTools(client);
|
|
914
|
-
console.log(
|
|
1239
|
+
console.log(pc4.green("\u2713") + " MCP server reachable \u2014 all 7 tools present");
|
|
915
1240
|
} finally {
|
|
916
1241
|
await client.close();
|
|
917
1242
|
}
|
|
918
1243
|
} catch (err) {
|
|
919
1244
|
console.log(
|
|
920
|
-
|
|
1245
|
+
pc4.red("\u2717") + " MCP server: " + categorizeError(err)
|
|
921
1246
|
);
|
|
922
1247
|
ok = false;
|
|
923
1248
|
}
|
|
@@ -926,9 +1251,9 @@ async function statusCommand() {
|
|
|
926
1251
|
}
|
|
927
1252
|
|
|
928
1253
|
// src/commands/check.ts
|
|
929
|
-
import
|
|
930
|
-
import { existsSync as
|
|
931
|
-
import { join as
|
|
1254
|
+
import pc5 from "picocolors";
|
|
1255
|
+
import { existsSync as existsSync8, readFileSync as readFileSync8 } from "fs";
|
|
1256
|
+
import { join as join8 } from "path";
|
|
932
1257
|
async function checkCommand() {
|
|
933
1258
|
const cwd = process.cwd();
|
|
934
1259
|
const issues = [];
|
|
@@ -936,12 +1261,12 @@ async function checkCommand() {
|
|
|
936
1261
|
if (!config) {
|
|
937
1262
|
issues.push(".cubedot/config.json missing \u2014 run `cubedot init`");
|
|
938
1263
|
}
|
|
939
|
-
const mcpPath =
|
|
940
|
-
if (!
|
|
1264
|
+
const mcpPath = join8(cwd, ".mcp.json");
|
|
1265
|
+
if (!existsSync8(mcpPath)) {
|
|
941
1266
|
issues.push(".mcp.json missing \u2014 run `cubedot init`");
|
|
942
1267
|
} else {
|
|
943
1268
|
try {
|
|
944
|
-
const mcp = JSON.parse(
|
|
1269
|
+
const mcp = JSON.parse(readFileSync8(mcpPath, "utf8"));
|
|
945
1270
|
if (!mcp.mcpServers?.["cubedot"]) {
|
|
946
1271
|
issues.push(
|
|
947
1272
|
".mcp.json missing mcpServers.cubedot \u2014 run `cubedot init`"
|
|
@@ -951,106 +1276,106 @@ async function checkCommand() {
|
|
|
951
1276
|
issues.push(".mcp.json contains malformed JSON");
|
|
952
1277
|
}
|
|
953
1278
|
}
|
|
954
|
-
const agentsPath =
|
|
955
|
-
if (!
|
|
1279
|
+
const agentsPath = join8(cwd, "AGENTS.md");
|
|
1280
|
+
if (!existsSync8(agentsPath)) {
|
|
956
1281
|
issues.push("AGENTS.md missing \u2014 run `cubedot init`");
|
|
957
1282
|
} else {
|
|
958
|
-
const content =
|
|
1283
|
+
const content = readFileSync8(agentsPath, "utf8");
|
|
959
1284
|
if (!hasAllMarkers(content)) {
|
|
960
1285
|
issues.push(
|
|
961
1286
|
"AGENTS.md is missing or has malformed CUBEDOT markers \u2014 run `cubedot sync`"
|
|
962
1287
|
);
|
|
963
1288
|
}
|
|
964
1289
|
}
|
|
965
|
-
const claudePath =
|
|
966
|
-
if (!
|
|
1290
|
+
const claudePath = join8(cwd, "CLAUDE.md");
|
|
1291
|
+
if (!existsSync8(claudePath)) {
|
|
967
1292
|
issues.push("CLAUDE.md missing \u2014 run `cubedot init`");
|
|
968
1293
|
} else {
|
|
969
|
-
const content =
|
|
1294
|
+
const content = readFileSync8(claudePath, "utf8");
|
|
970
1295
|
if (!hasAllMarkers(content)) {
|
|
971
1296
|
issues.push(
|
|
972
1297
|
"CLAUDE.md is missing or has malformed CUBEDOT markers \u2014 run `cubedot sync`"
|
|
973
1298
|
);
|
|
974
1299
|
}
|
|
975
1300
|
}
|
|
976
|
-
const verifier =
|
|
977
|
-
if (!
|
|
1301
|
+
const verifier = join8(cwd, ".claude", "agents", "CubedotVerifier.md");
|
|
1302
|
+
if (!existsSync8(verifier)) {
|
|
978
1303
|
issues.push(
|
|
979
1304
|
".claude/agents/CubedotVerifier.md missing \u2014 run `cubedot init`"
|
|
980
1305
|
);
|
|
981
1306
|
}
|
|
982
|
-
const manual =
|
|
983
|
-
if (!
|
|
1307
|
+
const manual = join8(cwd, ".cubedot", "fia-operating-manual.md");
|
|
1308
|
+
if (!existsSync8(manual)) {
|
|
984
1309
|
issues.push(
|
|
985
1310
|
".cubedot/fia-operating-manual.md missing \u2014 run `cubedot init`"
|
|
986
1311
|
);
|
|
987
1312
|
}
|
|
988
1313
|
for (const dir of [".cubedot/progress", ".cubedot/reports"]) {
|
|
989
|
-
if (!
|
|
1314
|
+
if (!existsSync8(join8(cwd, dir))) {
|
|
990
1315
|
issues.push(`${dir}/ missing \u2014 run 'cubedot init'`);
|
|
991
1316
|
}
|
|
992
1317
|
}
|
|
993
|
-
if (
|
|
994
|
-
const content =
|
|
995
|
-
if (content.includes("fia-operating-manual") && !
|
|
1318
|
+
if (existsSync8(claudePath)) {
|
|
1319
|
+
const content = readFileSync8(claudePath, "utf8");
|
|
1320
|
+
if (content.includes("fia-operating-manual") && !existsSync8(join8(cwd, ".cubedot", "fia-operating-manual.md"))) {
|
|
996
1321
|
issues.push(
|
|
997
1322
|
"CLAUDE.md references .cubedot/fia-operating-manual.md but it is missing"
|
|
998
1323
|
);
|
|
999
1324
|
}
|
|
1000
1325
|
}
|
|
1001
1326
|
if (issues.length === 0) {
|
|
1002
|
-
console.log(
|
|
1327
|
+
console.log(pc5.green("\u2713") + " All checks passed.");
|
|
1003
1328
|
process.exit(0);
|
|
1004
1329
|
} else {
|
|
1005
|
-
console.log(
|
|
1330
|
+
console.log(pc5.red(`\u2717 ${issues.length} issue(s) found:
|
|
1006
1331
|
`));
|
|
1007
1332
|
for (const issue of issues) {
|
|
1008
|
-
console.log(" " +
|
|
1333
|
+
console.log(" " + pc5.red("\u2022") + " " + issue);
|
|
1009
1334
|
}
|
|
1010
1335
|
process.exit(1);
|
|
1011
1336
|
}
|
|
1012
1337
|
}
|
|
1013
1338
|
|
|
1014
1339
|
// src/commands/start.ts
|
|
1015
|
-
import
|
|
1340
|
+
import pc6 from "picocolors";
|
|
1016
1341
|
function startCommand(fn, opts) {
|
|
1017
1342
|
const cwd = process.cwd();
|
|
1018
1343
|
const actor = opts.actor === "human" ? "human" : "agent";
|
|
1019
1344
|
try {
|
|
1020
1345
|
const record = startFn(cwd, fn, actor);
|
|
1021
1346
|
console.log(
|
|
1022
|
-
|
|
1347
|
+
pc6.green("\u2713") + ` ${fn} \u2192 in_progress (actor: ${record.actor}, at: ${record.updatedAt})`
|
|
1023
1348
|
);
|
|
1024
|
-
console.log(
|
|
1349
|
+
console.log(pc6.dim(` Ledger: .cubedot/progress/${fn}.json`));
|
|
1025
1350
|
} catch (err) {
|
|
1026
|
-
console.error(
|
|
1351
|
+
console.error(pc6.red(`Error: ${err.message}`));
|
|
1027
1352
|
process.exit(1);
|
|
1028
1353
|
}
|
|
1029
1354
|
}
|
|
1030
1355
|
|
|
1031
1356
|
// src/commands/complete.ts
|
|
1032
|
-
import
|
|
1357
|
+
import pc7 from "picocolors";
|
|
1033
1358
|
function completeCommand(fn, opts) {
|
|
1034
1359
|
const cwd = process.cwd();
|
|
1035
1360
|
const actor = opts.actor === "human" ? "human" : "agent";
|
|
1036
1361
|
try {
|
|
1037
1362
|
const record = completeFn(cwd, fn, actor);
|
|
1038
1363
|
console.log(
|
|
1039
|
-
|
|
1364
|
+
pc7.green("\u2713") + ` ${fn} \u2192 code-complete (actor: ${record.actor}, at: ${record.updatedAt})`
|
|
1040
1365
|
);
|
|
1041
1366
|
console.log(
|
|
1042
|
-
|
|
1367
|
+
pc7.dim(
|
|
1043
1368
|
` Next: spawn CubedotVerifier, then ask the developer to run \`cubedot done ${fn}\`.`
|
|
1044
1369
|
)
|
|
1045
1370
|
);
|
|
1046
1371
|
} catch (err) {
|
|
1047
|
-
console.error(
|
|
1372
|
+
console.error(pc7.red(`Error: ${err.message}`));
|
|
1048
1373
|
process.exit(1);
|
|
1049
1374
|
}
|
|
1050
1375
|
}
|
|
1051
1376
|
|
|
1052
1377
|
// src/commands/done.ts
|
|
1053
|
-
import
|
|
1378
|
+
import pc8 from "picocolors";
|
|
1054
1379
|
function doneCommand(fn, opts) {
|
|
1055
1380
|
const cwd = process.cwd();
|
|
1056
1381
|
const actor = opts.actor === "agent" ? "agent" : "human";
|
|
@@ -1062,43 +1387,43 @@ function doneCommand(fn, opts) {
|
|
|
1062
1387
|
try {
|
|
1063
1388
|
const record = doneFn(cwd, fn, actor, force, discrepancies);
|
|
1064
1389
|
console.log(
|
|
1065
|
-
|
|
1390
|
+
pc8.green("\u2713") + ` ${fn} \u2192 done (actor: ${record.actor}, at: ${record.updatedAt})`
|
|
1066
1391
|
);
|
|
1067
1392
|
if (record.overrides.length > 0) {
|
|
1068
1393
|
console.log(
|
|
1069
|
-
|
|
1394
|
+
pc8.yellow(
|
|
1070
1395
|
` \u26A0 Override recorded: ${record.overrides[record.overrides.length - 1].condition}`
|
|
1071
1396
|
)
|
|
1072
1397
|
);
|
|
1073
1398
|
}
|
|
1074
1399
|
if (discrepancies.length > 0) {
|
|
1075
1400
|
console.log(
|
|
1076
|
-
|
|
1401
|
+
pc8.yellow(
|
|
1077
1402
|
` \u26A0 ${discrepancies.length} discrepancy(ies) recorded (criteria accepted unmet):`
|
|
1078
1403
|
)
|
|
1079
1404
|
);
|
|
1080
1405
|
for (const d of discrepancies) {
|
|
1081
|
-
console.log(
|
|
1406
|
+
console.log(pc8.yellow(` - ${d.criterion}`));
|
|
1082
1407
|
}
|
|
1083
1408
|
}
|
|
1084
1409
|
} catch (err) {
|
|
1085
|
-
console.error(
|
|
1410
|
+
console.error(pc8.red(`Error: ${err.message}`));
|
|
1086
1411
|
process.exit(1);
|
|
1087
1412
|
}
|
|
1088
1413
|
}
|
|
1089
1414
|
|
|
1090
1415
|
// src/commands/progress.ts
|
|
1091
|
-
import
|
|
1092
|
-
import { existsSync as
|
|
1093
|
-
import { join as
|
|
1416
|
+
import pc9 from "picocolors";
|
|
1417
|
+
import { existsSync as existsSync9, readFileSync as readFileSync9 } from "fs";
|
|
1418
|
+
import { join as join9 } from "path";
|
|
1094
1419
|
function progressCommand() {
|
|
1095
1420
|
const cwd = process.cwd();
|
|
1096
1421
|
const ledgers = readAllLedgers(cwd);
|
|
1097
1422
|
let fnNames = /* @__PURE__ */ new Map();
|
|
1098
1423
|
for (const file of ["AGENTS.md", "CLAUDE.md"]) {
|
|
1099
|
-
const path =
|
|
1100
|
-
if (
|
|
1101
|
-
fnNames = parseRegistryBlock(
|
|
1424
|
+
const path = join9(cwd, file);
|
|
1425
|
+
if (existsSync9(path)) {
|
|
1426
|
+
fnNames = parseRegistryBlock(readFileSync9(path, "utf8"));
|
|
1102
1427
|
if (fnNames.size > 0) break;
|
|
1103
1428
|
}
|
|
1104
1429
|
}
|
|
@@ -1117,24 +1442,24 @@ function progressCommand() {
|
|
|
1117
1442
|
const ccCount = byState["code-complete"].length;
|
|
1118
1443
|
const ipCount = byState["in_progress"].length;
|
|
1119
1444
|
const todoCount = Math.max(0, total - doneCount - ccCount - ipCount);
|
|
1120
|
-
console.log("\n" +
|
|
1445
|
+
console.log("\n" + pc9.bold("Progress Summary"));
|
|
1121
1446
|
console.log("\u2501".repeat(40));
|
|
1122
|
-
console.log(` Done: ${
|
|
1447
|
+
console.log(` Done: ${pc9.green(String(doneCount))}/${total}`);
|
|
1123
1448
|
if (ccCount > 0)
|
|
1124
|
-
console.log(` Code-complete: ${
|
|
1449
|
+
console.log(` Code-complete: ${pc9.cyan(String(ccCount))}/${total}`);
|
|
1125
1450
|
if (ipCount > 0)
|
|
1126
|
-
console.log(` In progress: ${
|
|
1451
|
+
console.log(` In progress: ${pc9.yellow(String(ipCount))}/${total}`);
|
|
1127
1452
|
if (todoCount > 0)
|
|
1128
|
-
console.log(` Todo: ${
|
|
1453
|
+
console.log(` Todo: ${pc9.dim(String(todoCount))}/${total}`);
|
|
1129
1454
|
const active = [
|
|
1130
1455
|
...byState["in_progress"],
|
|
1131
1456
|
...byState["code-complete"]
|
|
1132
1457
|
];
|
|
1133
1458
|
if (active.length > 0) {
|
|
1134
|
-
console.log("\n" +
|
|
1459
|
+
console.log("\n" + pc9.bold("Active:"));
|
|
1135
1460
|
for (const l of active) {
|
|
1136
1461
|
const name = fnNames.get(l.code) ? ` \u2014 ${fnNames.get(l.code)}` : "";
|
|
1137
|
-
const stateLabel = l.state === "in_progress" ?
|
|
1462
|
+
const stateLabel = l.state === "in_progress" ? pc9.yellow("in_progress") : pc9.cyan("code-complete");
|
|
1138
1463
|
console.log(` ${l.code}${name} [${stateLabel}]`);
|
|
1139
1464
|
}
|
|
1140
1465
|
}
|
|
@@ -1143,21 +1468,21 @@ function progressCommand() {
|
|
|
1143
1468
|
const registryOrder = Array.from(fnNames.keys());
|
|
1144
1469
|
const nextCode = registryOrder.find((code) => !ledgerCodes.has(code));
|
|
1145
1470
|
if (nextCode) {
|
|
1146
|
-
console.log("\n" +
|
|
1471
|
+
console.log("\n" + pc9.bold("Next unblocked:"));
|
|
1147
1472
|
console.log(` ${nextCode} \u2014 ${fnNames.get(nextCode) ?? ""}`);
|
|
1148
1473
|
}
|
|
1149
1474
|
}
|
|
1150
1475
|
if (flagged.length > 0) {
|
|
1151
|
-
console.log("\n" +
|
|
1476
|
+
console.log("\n" + pc9.bold(pc9.yellow("Flagged:")));
|
|
1152
1477
|
for (const l of flagged) {
|
|
1153
1478
|
console.log(` ${l.code} [${l.flags.join(", ")}]`);
|
|
1154
1479
|
}
|
|
1155
1480
|
}
|
|
1156
1481
|
if (byState["done"].length > 0) {
|
|
1157
|
-
console.log("\n" +
|
|
1482
|
+
console.log("\n" + pc9.bold("Done:"));
|
|
1158
1483
|
for (const l of byState["done"]) {
|
|
1159
1484
|
const name = fnNames.get(l.code) ? ` \u2014 ${fnNames.get(l.code)}` : "";
|
|
1160
|
-
console.log(` ${
|
|
1485
|
+
console.log(` ${pc9.green("\u2713")} ${l.code}${name}`);
|
|
1161
1486
|
}
|
|
1162
1487
|
}
|
|
1163
1488
|
console.log("");
|
|
@@ -1167,15 +1492,16 @@ function progressCommand() {
|
|
|
1167
1492
|
var __filename3 = fileURLToPath4(import.meta.url);
|
|
1168
1493
|
var __dirname4 = dirname4(__filename3);
|
|
1169
1494
|
var pkg3 = JSON.parse(
|
|
1170
|
-
|
|
1495
|
+
readFileSync10(join10(__dirname4, "../package.json"), "utf8")
|
|
1171
1496
|
);
|
|
1172
1497
|
var program = new Command();
|
|
1173
1498
|
var collect = (value, prev) => [...prev, value];
|
|
1174
1499
|
program.name("cubedot").description(
|
|
1175
1500
|
"Connect your repo to the Cubedot MCP and drive the spec-first build loop."
|
|
1176
1501
|
).version(pkg3.version);
|
|
1177
|
-
program.command("init").description("Connect and scaffold the repo (validates MCP, writes config + hot-memory files).").option("--token <token>", "Base64url connect token {v:1, projectId, key, mcpUrl}").option("--project <projectId>", "Project ID (alternative to --token)").option("--key <key>", "MCP API key (alternative to --token)").option("--url <url>", "MCP URL (alternative to --token)").action(initCommand);
|
|
1502
|
+
program.command("init").description("Connect and scaffold the repo (validates MCP, writes config + hot-memory files).").option("--token <token>", "Base64url connect token {v:1, projectId, key, mcpUrl}").option("--project <projectId>", "Project ID (alternative to --token)").option("--key <key>", "MCP API key (alternative to --token)").option("--url <url>", "MCP URL (alternative to --token)").option("--force", "Override the self-install guard (run init inside the CLI source repo).").action(initCommand);
|
|
1178
1503
|
program.command("sync").description("Refresh local projection (AGENTS.md/CLAUDE.md markers) and detect drift.").action(syncCommand);
|
|
1504
|
+
program.command("uninstall").description("Remove all cubedot scaffold files from this repo and disconnect from the MCP.").option("-y, --yes", "Skip the confirmation prompt.").action(uninstallCommand);
|
|
1179
1505
|
program.command("status").description("Connectivity ping + file-health check (read-only).").action(statusCommand);
|
|
1180
1506
|
program.command("check").description("Validate markers, config, and server name (read-only; CI-friendly).").action(checkCommand);
|
|
1181
1507
|
program.command("start <fn>").description("Set a functionality to in_progress.").option("--actor <actor>", "Actor performing the action", "agent").action(startCommand);
|