mover-os 4.3.1 → 4.3.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/install.js +1610 -168
- package/package.json +1 -1
package/install.js
CHANGED
|
@@ -95,10 +95,18 @@ const LOGO = [
|
|
|
95
95
|
" ╚═════╝ ╚══════╝",
|
|
96
96
|
];
|
|
97
97
|
|
|
98
|
-
|
|
98
|
+
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
99
|
+
|
|
100
|
+
async function printHeader(animate = IS_TTY) {
|
|
99
101
|
ln();
|
|
100
|
-
|
|
101
|
-
|
|
102
|
+
if (animate) {
|
|
103
|
+
// Animated logo reveal — line by line with gradient cascade
|
|
104
|
+
for (let i = 0; i < LOGO.length; i++) {
|
|
105
|
+
ln(gradient(LOGO[i]));
|
|
106
|
+
await sleep(30);
|
|
107
|
+
}
|
|
108
|
+
} else {
|
|
109
|
+
for (const line of LOGO) ln(gradient(line));
|
|
102
110
|
}
|
|
103
111
|
ln();
|
|
104
112
|
ln(` ${dim(`v${VERSION}`)} ${gray("the agentic operating system for obsidian")}`);
|
|
@@ -107,6 +115,60 @@ function printHeader() {
|
|
|
107
115
|
ln();
|
|
108
116
|
}
|
|
109
117
|
|
|
118
|
+
// ─── Enhanced TUI utilities ─────────────────────────────────────────────────
|
|
119
|
+
|
|
120
|
+
// Progress bar with label and percentage
|
|
121
|
+
function progressBar(current, total, width = 30, label = "") {
|
|
122
|
+
const pct = Math.min(1, current / total);
|
|
123
|
+
const filled = Math.round(pct * width);
|
|
124
|
+
const empty = width - filled;
|
|
125
|
+
const bar = `${S.green}${"█".repeat(filled)}${S.gray}${"░".repeat(empty)}${S.reset}`;
|
|
126
|
+
const pctStr = `${Math.round(pct * 100)}%`.padStart(4);
|
|
127
|
+
return `${bar} ${dim(pctStr)}${label ? ` ${dim(label)}` : ""}`;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// Box frame for summaries
|
|
131
|
+
function box(lines, { title = "", color = S.cyan, width = 50 } = {}) {
|
|
132
|
+
const top = title
|
|
133
|
+
? `${color}╭─ ${S.bold}${title}${S.reset}${color} ${"─".repeat(Math.max(0, width - title.length - 4))}╮${S.reset}`
|
|
134
|
+
: `${color}╭${"─".repeat(width)}╮${S.reset}`;
|
|
135
|
+
ln(top);
|
|
136
|
+
for (const l of lines) {
|
|
137
|
+
const stripped = strip(l);
|
|
138
|
+
const pad = Math.max(0, width - 2 - stripped.length);
|
|
139
|
+
ln(`${color}│${S.reset} ${l}${" ".repeat(pad)} ${color}│${S.reset}`);
|
|
140
|
+
}
|
|
141
|
+
ln(`${color}╰${"─".repeat(width)}╯${S.reset}`);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// Typewriter effect for important messages
|
|
145
|
+
async function typewriter(text, delay = 15) {
|
|
146
|
+
if (!IS_TTY) { ln(text); return; }
|
|
147
|
+
for (const ch of text) {
|
|
148
|
+
w(ch);
|
|
149
|
+
if (ch !== " ") await sleep(delay);
|
|
150
|
+
}
|
|
151
|
+
ln();
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// Section header with animated line
|
|
155
|
+
function sectionHeader(text) {
|
|
156
|
+
ln();
|
|
157
|
+
ln(`${S.cyan}┌─${S.reset} ${S.bold}${text}${S.reset}`);
|
|
158
|
+
ln(`${S.cyan}│${S.reset}`);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// Section footer
|
|
162
|
+
function sectionEnd() {
|
|
163
|
+
ln(`${S.cyan}└${S.reset}`);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// Status line with icon
|
|
167
|
+
function statusLine(icon, label, detail = "") {
|
|
168
|
+
const iconMap = { ok: `${S.green}✓${S.reset}`, warn: `${S.yellow}○${S.reset}`, fail: `${S.red}✗${S.reset}`, info: `${S.cyan}●${S.reset}`, dim: `${S.gray}·${S.reset}` };
|
|
169
|
+
barLn(`${iconMap[icon] || icon} ${label}${detail ? ` ${dim(detail)}` : ""}`);
|
|
170
|
+
}
|
|
171
|
+
|
|
110
172
|
// ─── Clack-style frame ──────────────────────────────────────────────────────
|
|
111
173
|
const BAR_COLOR = S.cyan;
|
|
112
174
|
const bar = () => w(`${BAR_COLOR}│${S.reset}`);
|
|
@@ -474,27 +536,65 @@ async function downloadPayload(key) {
|
|
|
474
536
|
}
|
|
475
537
|
|
|
476
538
|
// ─── CLI ─────────────────────────────────────────────────────────────────────
|
|
539
|
+
// ─── CLI Commands ────────────────────────────────────────────────────────────
|
|
540
|
+
const CLI_COMMANDS = {
|
|
541
|
+
install: { desc: "Full interactive install", alias: [] },
|
|
542
|
+
update: { desc: "Update all agents", alias: ["-u"] },
|
|
543
|
+
doctor: { desc: "Health check across all installed agents", alias: [] },
|
|
544
|
+
pulse: { desc: "Terminal dashboard — energy, tasks, streaks",alias: [] },
|
|
545
|
+
warm: { desc: "Pre-warm an AI session with context", alias: [] },
|
|
546
|
+
capture: { desc: "Quick capture — tasks, links, ideas", alias: [] },
|
|
547
|
+
who: { desc: "Entity memory lookup", alias: [] },
|
|
548
|
+
diff: { desc: "Engine file evolution viewer", alias: [] },
|
|
549
|
+
sync: { desc: "Cross-agent synchronization", alias: [] },
|
|
550
|
+
replay: { desc: "Session replay from Daily Notes", alias: [] },
|
|
551
|
+
context: { desc: "Debug what each agent sees", alias: [] },
|
|
552
|
+
settings: { desc: "View/edit config", alias: [] },
|
|
553
|
+
backup: { desc: "Manual backup wizard", alias: [] },
|
|
554
|
+
restore: { desc: "Restore from backup", alias: [] },
|
|
555
|
+
test: { desc: "Run integration tests (dev)", alias: [], hidden: true },
|
|
556
|
+
};
|
|
557
|
+
|
|
477
558
|
function parseArgs() {
|
|
478
559
|
const args = process.argv.slice(2);
|
|
479
|
-
const opts = { vault: "", key: "",
|
|
560
|
+
const opts = { command: "", vault: "", key: "", rest: [] };
|
|
561
|
+
|
|
480
562
|
for (let i = 0; i < args.length; i++) {
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
563
|
+
const a = args[i];
|
|
564
|
+
// Named flags
|
|
565
|
+
if (a === "--vault" && args[i + 1]) { opts.vault = args[++i]; continue; }
|
|
566
|
+
if (a === "--key" && args[i + 1]) { opts.key = args[++i]; continue; }
|
|
567
|
+
// Backward compat: --update / -u → command 'update'
|
|
568
|
+
if (a === "--update" || a === "-u") { opts.command = "update"; continue; }
|
|
569
|
+
if (a === "--help" || a === "-h") {
|
|
570
|
+
ln();
|
|
571
|
+
ln(` ${bold("moveros")} ${dim("— the Mover OS companion CLI")}`);
|
|
485
572
|
ln();
|
|
486
|
-
ln(` ${
|
|
573
|
+
ln(` ${dim("Usage")} moveros [command] [options]`);
|
|
487
574
|
ln();
|
|
488
|
-
ln(` ${dim("
|
|
575
|
+
ln(` ${dim("Commands")}`);
|
|
576
|
+
for (const [cmd, meta] of Object.entries(CLI_COMMANDS)) {
|
|
577
|
+
if (meta.hidden) continue;
|
|
578
|
+
ln(` ${cmd.padEnd(12)}${dim(meta.desc)}`);
|
|
579
|
+
}
|
|
489
580
|
ln();
|
|
490
581
|
ln(` ${dim("Options")}`);
|
|
491
582
|
ln(` --key KEY License key (skip interactive prompt)`);
|
|
492
583
|
ln(` --vault PATH Obsidian vault path (skip detection)`);
|
|
493
|
-
ln(` --update, -u Quick update (
|
|
584
|
+
ln(` --update, -u Quick update (backward compat)`);
|
|
585
|
+
ln();
|
|
586
|
+
ln(` ${dim("Run")} moveros ${dim("with no args for interactive menu")}`);
|
|
494
587
|
ln();
|
|
495
588
|
process.exit(0);
|
|
496
589
|
}
|
|
590
|
+
// Positional: first non-flag arg is the command
|
|
591
|
+
if (!opts.command && !a.startsWith("-") && CLI_COMMANDS[a]) {
|
|
592
|
+
opts.command = a;
|
|
593
|
+
} else {
|
|
594
|
+
opts.rest.push(a);
|
|
595
|
+
}
|
|
497
596
|
}
|
|
597
|
+
|
|
498
598
|
return opts;
|
|
499
599
|
}
|
|
500
600
|
|
|
@@ -915,74 +1015,225 @@ async function runUninstall(vaultPath) {
|
|
|
915
1015
|
}
|
|
916
1016
|
|
|
917
1017
|
// ─── Agent definitions ──────────────────────────────────────────────────────
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
1018
|
+
// ─── Agent Registry ─────────────────────────────────────────────────────────
|
|
1019
|
+
// Data-driven agent configuration. 14 user-selectable agents → 16 install targets.
|
|
1020
|
+
// Each entry defines: detection, rules destination, skills destination,
|
|
1021
|
+
// commands destination + format, hooks config.
|
|
1022
|
+
//
|
|
1023
|
+
// Selection → targets mapping:
|
|
1024
|
+
// "Gemini CLI + Antigravity" = 1 selection → 2 targets (gemini-cli, antigravity)
|
|
1025
|
+
// Everything else = 1 selection → 1 target
|
|
1026
|
+
//
|
|
1027
|
+
// Tiers:
|
|
1028
|
+
// Full = rules + skills + commands + hooks
|
|
1029
|
+
// Enhanced = rules + skills + commands (no hooks)
|
|
1030
|
+
// Basic+ = rules + commands (no skills)
|
|
1031
|
+
// Basic = rules only
|
|
1032
|
+
|
|
1033
|
+
const H = os.homedir();
|
|
1034
|
+
|
|
1035
|
+
const AGENT_REGISTRY = {
|
|
1036
|
+
// ── Full Tier ──────────────────────────────────────────────────────────────
|
|
1037
|
+
"claude-code": {
|
|
921
1038
|
name: "Claude Code",
|
|
922
|
-
tier: "
|
|
923
|
-
|
|
1039
|
+
tier: "full",
|
|
1040
|
+
tierDesc: "Rules, 23 commands, skills, 6 hooks",
|
|
1041
|
+
detect: () => cmdExists("claude") || fs.existsSync(path.join(H, ".claude")),
|
|
1042
|
+
rules: { type: "link", dest: () => path.join(H, ".claude", "CLAUDE.md"), header: "# Mover OS Global Rules" },
|
|
1043
|
+
skills: { dest: () => path.join(H, ".claude", "skills") },
|
|
1044
|
+
commands: { format: "md", dest: () => path.join(H, ".claude", "commands") },
|
|
1045
|
+
hooks: { type: "claude-settings" },
|
|
924
1046
|
},
|
|
925
|
-
{
|
|
926
|
-
id: "cursor",
|
|
1047
|
+
"cursor": {
|
|
927
1048
|
name: "Cursor",
|
|
928
|
-
tier: "
|
|
929
|
-
|
|
1049
|
+
tier: "full",
|
|
1050
|
+
tierDesc: "Rules (.mdc), commands, skills, hooks",
|
|
1051
|
+
detect: () => fs.existsSync(path.join(H, ".cursor")) || cmdExists("cursor"),
|
|
1052
|
+
rules: {
|
|
1053
|
+
type: "mdc",
|
|
1054
|
+
dest: (vault) => path.join(vault || ".", ".cursor", "rules", "mover-os.mdc"),
|
|
1055
|
+
globalDest: () => path.join(H, ".cursor", "rules", "mover-os.mdc"),
|
|
1056
|
+
header: "# Cursor Project Rules",
|
|
1057
|
+
},
|
|
1058
|
+
skills: { dest: () => path.join(H, ".cursor", "skills") },
|
|
1059
|
+
commands: { format: "md", dest: () => path.join(H, ".cursor", "commands") },
|
|
1060
|
+
hooks: { type: "cursor-hooks-json", dest: (vault) => path.join(vault || ".", ".cursor", "hooks.json") },
|
|
930
1061
|
},
|
|
931
|
-
{
|
|
932
|
-
id: "cline",
|
|
1062
|
+
"cline": {
|
|
933
1063
|
name: "Cline",
|
|
934
|
-
tier: "
|
|
935
|
-
|
|
1064
|
+
tier: "full",
|
|
1065
|
+
tierDesc: "Rules, skills, hooks",
|
|
1066
|
+
detect: () => globDirExists(path.join(H, ".vscode", "extensions"), "saoudrizwan.claude-dev-*"),
|
|
1067
|
+
rules: { type: "copy", dest: (vault) => path.join(vault || ".", ".clinerules", "mover-os.md") },
|
|
1068
|
+
skills: { dest: (vault) => path.join(vault || ".", ".cline", "skills") },
|
|
1069
|
+
commands: null,
|
|
1070
|
+
hooks: null,
|
|
936
1071
|
},
|
|
937
|
-
{
|
|
938
|
-
id: "windsurf",
|
|
1072
|
+
"windsurf": {
|
|
939
1073
|
name: "Windsurf",
|
|
940
|
-
tier: "
|
|
941
|
-
|
|
1074
|
+
tier: "full",
|
|
1075
|
+
tierDesc: "Rules, workflows, skills, hooks",
|
|
1076
|
+
detect: () => fs.existsSync(path.join(H, ".codeium")) || cmdExists("windsurf"),
|
|
1077
|
+
rules: {
|
|
1078
|
+
type: "windsurf-rule",
|
|
1079
|
+
dest: (vault) => path.join(vault || ".", ".windsurf", "rules", "mover-os.md"),
|
|
1080
|
+
header: "# Windsurf Rules",
|
|
1081
|
+
},
|
|
1082
|
+
skills: { dest: () => path.join(H, ".codeium", "windsurf", "skills") },
|
|
1083
|
+
commands: { format: "md", dest: (vault) => path.join(vault || ".", ".windsurf", "workflows") },
|
|
1084
|
+
hooks: { type: "windsurf-hooks-json", dest: (vault) => path.join(vault || ".", ".windsurf", "hooks.json") },
|
|
942
1085
|
},
|
|
943
|
-
{
|
|
944
|
-
id: "gemini-cli",
|
|
1086
|
+
"gemini-cli": {
|
|
945
1087
|
name: "Gemini CLI",
|
|
946
|
-
tier: "
|
|
947
|
-
|
|
1088
|
+
tier: "full",
|
|
1089
|
+
tierDesc: "Rules, .toml commands, skills, hooks",
|
|
1090
|
+
detect: () => cmdExists("gemini") || fs.existsSync(path.join(H, ".gemini", "settings.json")),
|
|
1091
|
+
sharedRulesFile: "gemini-md",
|
|
1092
|
+
rules: { type: "link", dest: () => path.join(H, ".gemini", "GEMINI.md"), header: "# Gemini CLI Rules" },
|
|
1093
|
+
skills: { dest: () => path.join(H, ".gemini", "skills") },
|
|
1094
|
+
commands: { format: "toml", dest: () => path.join(H, ".gemini", "commands") },
|
|
1095
|
+
hooks: { type: "gemini-settings-json", dest: () => path.join(H, ".gemini", "settings.json") },
|
|
948
1096
|
},
|
|
949
|
-
{
|
|
950
|
-
id: "copilot",
|
|
1097
|
+
"copilot": {
|
|
951
1098
|
name: "GitHub Copilot",
|
|
952
|
-
tier: "
|
|
1099
|
+
tier: "full",
|
|
1100
|
+
tierDesc: "Rules, .prompt.md commands, skills",
|
|
953
1101
|
detect: () => cmdExists("gh"),
|
|
1102
|
+
rules: { type: "copy", dest: (vault) => path.join(vault || ".", ".github", "copilot-instructions.md"), header: "# Copilot Instructions" },
|
|
1103
|
+
skills: { dest: (vault) => path.join(vault || ".", ".github", "skills") },
|
|
1104
|
+
commands: { format: "prompt-md", dest: (vault) => path.join(vault || ".", ".github", "prompts") },
|
|
1105
|
+
hooks: null,
|
|
954
1106
|
},
|
|
955
|
-
{
|
|
956
|
-
|
|
1107
|
+
"amazon-q": {
|
|
1108
|
+
name: "Amazon Q Developer",
|
|
1109
|
+
tier: "full",
|
|
1110
|
+
tierDesc: "Rules, JSON agent commands, hooks",
|
|
1111
|
+
detect: () => cmdExists("q") || fs.existsSync(path.join(H, ".aws", "amazonq")),
|
|
1112
|
+
rules: { type: "copy", dest: (vault) => path.join(vault || ".", ".amazonq", "rules", "mover-os.md") },
|
|
1113
|
+
skills: null,
|
|
1114
|
+
commands: { format: "amazon-q-json", dest: () => path.join(H, ".aws", "amazonq", "cli-agents") },
|
|
1115
|
+
hooks: null,
|
|
1116
|
+
},
|
|
1117
|
+
"opencode": {
|
|
1118
|
+
name: "OpenCode",
|
|
1119
|
+
tier: "full",
|
|
1120
|
+
tierDesc: "AGENTS.md, agents, commands",
|
|
1121
|
+
detect: () => cmdExists("opencode") || fs.existsSync(path.join(".", "opencode.json")),
|
|
1122
|
+
sharedRulesFile: "agents-md",
|
|
1123
|
+
rules: { type: "agents-md", dest: (vault) => path.join(vault || ".", "AGENTS.md") },
|
|
1124
|
+
skills: { dest: (vault) => path.join(vault || ".", ".opencode", "skills") },
|
|
1125
|
+
commands: { format: "opencode-json", dest: (vault) => path.join(vault || ".", "opencode.json") },
|
|
1126
|
+
hooks: null,
|
|
1127
|
+
},
|
|
1128
|
+
"kilo-code": {
|
|
1129
|
+
name: "Kilo Code",
|
|
1130
|
+
tier: "full",
|
|
1131
|
+
tierDesc: "Rules, skills, commands, modes",
|
|
1132
|
+
detect: () => globDirExists(path.join(H, ".vscode", "extensions"), "kilocode.kilo-code-*"),
|
|
1133
|
+
rules: { type: "copy", dest: (vault) => path.join(vault || ".", ".kilocode", "rules", "mover-os.md") },
|
|
1134
|
+
skills: { dest: (vault) => path.join(vault || ".", ".kilocode", "skills") },
|
|
1135
|
+
commands: { format: "md", dest: (vault) => path.join(vault || ".", ".kilocode", "commands") },
|
|
1136
|
+
hooks: null,
|
|
1137
|
+
},
|
|
1138
|
+
|
|
1139
|
+
// ── Enhanced Tier ──────────────────────────────────────────────────────────
|
|
1140
|
+
"codex": {
|
|
957
1141
|
name: "Codex",
|
|
958
|
-
tier: "
|
|
959
|
-
|
|
1142
|
+
tier: "enhanced",
|
|
1143
|
+
tierDesc: "AGENTS.md, skills (skills = commands)",
|
|
1144
|
+
detect: () => cmdExists("codex") || fs.existsSync(path.join(H, ".codex")),
|
|
1145
|
+
rules: { type: "agents-md", dest: () => path.join(H, ".codex", "AGENTS.md") },
|
|
1146
|
+
skills: { dest: () => path.join(H, ".codex", "skills") },
|
|
1147
|
+
commands: null,
|
|
1148
|
+
hooks: null,
|
|
960
1149
|
},
|
|
961
|
-
|
|
962
|
-
// Vault-root AGENTS.md provides basic rules for these agents.
|
|
963
|
-
{
|
|
964
|
-
id: "antigravity",
|
|
1150
|
+
"antigravity": {
|
|
965
1151
|
name: "Antigravity",
|
|
966
|
-
tier: "
|
|
967
|
-
|
|
1152
|
+
tier: "enhanced",
|
|
1153
|
+
tierDesc: "Rules (shared GEMINI.md), workflows, skills",
|
|
1154
|
+
detect: () => fs.existsSync(path.join(H, ".gemini", "antigravity")),
|
|
1155
|
+
sharedRulesFile: "gemini-md",
|
|
1156
|
+
rules: { type: "link", dest: () => path.join(H, ".gemini", "GEMINI.md"), header: "# Antigravity Rules" },
|
|
1157
|
+
skills: { dest: () => path.join(H, ".gemini", "antigravity", "skills") },
|
|
1158
|
+
commands: { format: "md", dest: () => path.join(H, ".gemini", "antigravity", "global_workflows") },
|
|
1159
|
+
hooks: null,
|
|
968
1160
|
},
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
1161
|
+
"amp": {
|
|
1162
|
+
name: "Amp (Sourcegraph)",
|
|
1163
|
+
tier: "enhanced",
|
|
1164
|
+
tierDesc: "AGENTS.md, skills",
|
|
1165
|
+
detect: () => cmdExists("amp") || fs.existsSync(path.join(H, ".config", "agents")),
|
|
1166
|
+
sharedRulesFile: "agents-md",
|
|
1167
|
+
rules: { type: "agents-md", dest: (vault) => path.join(vault || ".", "AGENTS.md") },
|
|
1168
|
+
skills: { dest: (vault) => path.join(vault || ".", ".agents", "skills") },
|
|
1169
|
+
commands: null,
|
|
1170
|
+
hooks: null,
|
|
976
1171
|
},
|
|
977
|
-
{
|
|
978
|
-
id: "roo-code",
|
|
1172
|
+
"roo-code": {
|
|
979
1173
|
name: "Roo Code",
|
|
980
|
-
tier: "
|
|
981
|
-
|
|
982
|
-
detect: () => globDirExists(path.join(
|
|
1174
|
+
tier: "enhanced",
|
|
1175
|
+
tierDesc: "Rules, skills, commands",
|
|
1176
|
+
detect: () => globDirExists(path.join(H, ".vscode", "extensions"), "rooveterinaryinc.roo-cline-*"),
|
|
1177
|
+
rules: { type: "copy", dest: (vault) => path.join(vault || ".", ".roo", "rules", "mover-os.md") },
|
|
1178
|
+
skills: { dest: (vault) => path.join(vault || ".", ".roo", "skills") },
|
|
1179
|
+
commands: { format: "md", dest: (vault) => path.join(vault || ".", ".roo", "commands") },
|
|
1180
|
+
hooks: null,
|
|
1181
|
+
},
|
|
1182
|
+
|
|
1183
|
+
// ── Basic+ Tier ────────────────────────────────────────────────────────────
|
|
1184
|
+
"continue": {
|
|
1185
|
+
name: "Continue.dev",
|
|
1186
|
+
tier: "basic+",
|
|
1187
|
+
tierDesc: "Rules (.md), .prompt commands",
|
|
1188
|
+
detect: () => fs.existsSync(path.join(H, ".continue")) || fs.existsSync(path.join(".", ".continue")),
|
|
1189
|
+
rules: {
|
|
1190
|
+
type: "continue-rule",
|
|
1191
|
+
dest: (vault) => path.join(vault || ".", ".continue", "rules", "mover-os.md"),
|
|
1192
|
+
},
|
|
1193
|
+
skills: null,
|
|
1194
|
+
commands: { format: "continue-prompt", dest: (vault) => path.join(vault || ".", ".continue", "prompts") },
|
|
1195
|
+
hooks: null,
|
|
983
1196
|
},
|
|
1197
|
+
|
|
1198
|
+
// ── Basic Tier ─────────────────────────────────────────────────────────────
|
|
1199
|
+
"aider": {
|
|
1200
|
+
name: "Aider",
|
|
1201
|
+
tier: "basic",
|
|
1202
|
+
tierDesc: "CONVENTIONS.md (rules only)",
|
|
1203
|
+
detect: () => cmdExists("aider"),
|
|
1204
|
+
rules: { type: "conventions-md", dest: (vault) => path.join(vault || ".", "CONVENTIONS.md") },
|
|
1205
|
+
skills: null,
|
|
1206
|
+
commands: null,
|
|
1207
|
+
hooks: null,
|
|
1208
|
+
},
|
|
1209
|
+
};
|
|
1210
|
+
|
|
1211
|
+
// User-selectable agents (14 selections). Each maps to 1+ install targets.
|
|
1212
|
+
const AGENT_SELECTIONS = [
|
|
1213
|
+
{ id: "claude-code", targets: ["claude-code"], name: "Claude Code" },
|
|
1214
|
+
{ id: "cursor", targets: ["cursor"], name: "Cursor" },
|
|
1215
|
+
{ id: "cline", targets: ["cline"], name: "Cline" },
|
|
1216
|
+
{ id: "windsurf", targets: ["windsurf"], name: "Windsurf" },
|
|
1217
|
+
{ id: "gemini-cli", targets: ["gemini-cli", "antigravity"], name: "Gemini CLI + Antigravity" },
|
|
1218
|
+
{ id: "copilot", targets: ["copilot"], name: "GitHub Copilot" },
|
|
1219
|
+
{ id: "codex", targets: ["codex"], name: "Codex" },
|
|
1220
|
+
{ id: "amazon-q", targets: ["amazon-q"], name: "Amazon Q Developer" },
|
|
1221
|
+
{ id: "opencode", targets: ["opencode"], name: "OpenCode" },
|
|
1222
|
+
{ id: "kilo-code", targets: ["kilo-code"], name: "Kilo Code" },
|
|
1223
|
+
{ id: "amp", targets: ["amp"], name: "Amp (Sourcegraph)" },
|
|
1224
|
+
{ id: "roo-code", targets: ["roo-code"], name: "Roo Code" },
|
|
1225
|
+
{ id: "continue", targets: ["continue"], name: "Continue.dev" },
|
|
1226
|
+
{ id: "aider", targets: ["aider"], name: "Aider" },
|
|
984
1227
|
];
|
|
985
1228
|
|
|
1229
|
+
// Backward compat: AGENTS array used by existing detectChanges() and update flow
|
|
1230
|
+
const AGENTS = AGENT_SELECTIONS.map((s) => ({
|
|
1231
|
+
id: s.id,
|
|
1232
|
+
name: s.name,
|
|
1233
|
+
tier: AGENT_REGISTRY[s.targets[0]].tierDesc,
|
|
1234
|
+
detect: AGENT_REGISTRY[s.targets[0]].detect,
|
|
1235
|
+
}));
|
|
1236
|
+
|
|
986
1237
|
// ─── Utility functions ──────────────────────────────────────────────────────
|
|
987
1238
|
function cmdExists(cmd) {
|
|
988
1239
|
try {
|
|
@@ -1185,41 +1436,10 @@ Sunday: /review-week first
|
|
|
1185
1436
|
New project: /ignite → [WORK]
|
|
1186
1437
|
Stuck: /debug-resistance
|
|
1187
1438
|
\`\`\`
|
|
1188
|
-
`;
|
|
1189
|
-
}
|
|
1190
1439
|
|
|
1191
|
-
|
|
1192
|
-
function generateSoulMd() {
|
|
1193
|
-
return `# SOUL.md — Mover OS
|
|
1440
|
+
## CLI Utility
|
|
1194
1441
|
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
## Core Truths
|
|
1198
|
-
|
|
1199
|
-
- Be genuinely helpful, not performatively helpful. If the answer is "don't build that," say it.
|
|
1200
|
-
- Have opinions. "It depends" is a cop-out. Pick a direction, defend it, change your mind if the user makes a better case.
|
|
1201
|
-
- Be resourceful before asking. Exhaust what you can figure out, then ask sharp questions — not broad ones.
|
|
1202
|
-
- Earn trust through competence, not compliance. Nobody respects a yes-man.
|
|
1203
|
-
|
|
1204
|
-
## The Vibe
|
|
1205
|
-
|
|
1206
|
-
- Talk like a co-founder who's been in the trenches, not a consultant billing by the hour.
|
|
1207
|
-
- One sharp sentence beats three careful ones. Say what you mean.
|
|
1208
|
-
- When you're wrong, don't grovel. Name it, fix it, move. Apologies waste both your time.
|
|
1209
|
-
- Match intensity to stakes. Casual for small tasks. Locked in for architecture. Blunt for bad ideas.
|
|
1210
|
-
|
|
1211
|
-
## Boundaries
|
|
1212
|
-
|
|
1213
|
-
- Never hedge to avoid being wrong. Pick a position.
|
|
1214
|
-
- Never pretend all approaches are equally valid when one is clearly better.
|
|
1215
|
-
- Never pad output to look thorough. Substance only.
|
|
1216
|
-
- Never be artificially enthusiastic about bad ideas.
|
|
1217
|
-
|
|
1218
|
-
## Continuity
|
|
1219
|
-
|
|
1220
|
-
- You start fresh each session but the files are your memory. Read them, trust them, build on them.
|
|
1221
|
-
- If something changed between sessions and you don't understand why, ask — don't assume.
|
|
1222
|
-
- The user corrects you to make you better. Take corrections seriously and encode them.
|
|
1442
|
+
\`moveros\` provides system-level terminal operations: pulse (dashboard), warm (pre-warm sessions), sync (update agents), doctor (health check), who (entity lookup), capture (quick inbox). Use native agent capabilities first — the CLI supplements, never replaces.
|
|
1223
1443
|
`;
|
|
1224
1444
|
}
|
|
1225
1445
|
|
|
@@ -1341,6 +1561,8 @@ function writeMoverConfig(vaultPath, agentIds, licenseKey) {
|
|
|
1341
1561
|
vaultPath: vaultPath,
|
|
1342
1562
|
agents: agentIds,
|
|
1343
1563
|
feedbackWebhook: "https://moveros.dev/api/feedback",
|
|
1564
|
+
track_food: true,
|
|
1565
|
+
track_sleep: true,
|
|
1344
1566
|
installedAt: new Date().toISOString(),
|
|
1345
1567
|
};
|
|
1346
1568
|
if (licenseKey) config.licenseKey = licenseKey;
|
|
@@ -1351,6 +1573,8 @@ function writeMoverConfig(vaultPath, agentIds, licenseKey) {
|
|
|
1351
1573
|
if (existing.installedAt) config.installedAt = existing.installedAt;
|
|
1352
1574
|
if (existing.licenseKey && !licenseKey) config.licenseKey = existing.licenseKey;
|
|
1353
1575
|
if (existing.feedbackWebhook) config.feedbackWebhook = existing.feedbackWebhook;
|
|
1576
|
+
if (existing.track_food !== undefined) config.track_food = existing.track_food;
|
|
1577
|
+
if (existing.track_sleep !== undefined) config.track_sleep = existing.track_sleep;
|
|
1354
1578
|
config.updatedAt = new Date().toISOString();
|
|
1355
1579
|
} catch {}
|
|
1356
1580
|
}
|
|
@@ -1521,6 +1745,122 @@ function installRules(bundleDir, destPath, agentId) {
|
|
|
1521
1745
|
return true;
|
|
1522
1746
|
}
|
|
1523
1747
|
|
|
1748
|
+
// ─── Format Converters (workflow .md → agent-specific formats) ───────────────
|
|
1749
|
+
// Source of truth: src/workflows/*.md (Claude Code format with YAML frontmatter)
|
|
1750
|
+
// Each converter takes workflow metadata and produces the target format.
|
|
1751
|
+
|
|
1752
|
+
function parseWorkflowMd(filePath) {
|
|
1753
|
+
const content = fs.readFileSync(filePath, "utf8");
|
|
1754
|
+
const name = path.basename(filePath, ".md");
|
|
1755
|
+
let description = "";
|
|
1756
|
+
let body = content;
|
|
1757
|
+
|
|
1758
|
+
// Extract YAML frontmatter
|
|
1759
|
+
if (content.startsWith("---")) {
|
|
1760
|
+
const endIdx = content.indexOf("\n---", 3);
|
|
1761
|
+
if (endIdx > 0) {
|
|
1762
|
+
const front = content.substring(3, endIdx).trim();
|
|
1763
|
+
body = content.substring(endIdx + 4).trim();
|
|
1764
|
+
const descMatch = front.match(/description:\s*(.+)/);
|
|
1765
|
+
if (descMatch) description = descMatch[1].trim().replace(/^["']|["']$/g, "");
|
|
1766
|
+
}
|
|
1767
|
+
}
|
|
1768
|
+
|
|
1769
|
+
// Fallback: first non-empty, non-heading line
|
|
1770
|
+
if (!description) {
|
|
1771
|
+
const lines = body.split("\n");
|
|
1772
|
+
for (const l of lines) {
|
|
1773
|
+
const t = l.trim();
|
|
1774
|
+
if (t && !t.startsWith("#") && !t.startsWith(">") && !t.startsWith("---")) {
|
|
1775
|
+
description = t.substring(0, 120);
|
|
1776
|
+
break;
|
|
1777
|
+
}
|
|
1778
|
+
}
|
|
1779
|
+
}
|
|
1780
|
+
|
|
1781
|
+
return { name, description, body, filePath };
|
|
1782
|
+
}
|
|
1783
|
+
|
|
1784
|
+
// Gemini CLI: .md → .toml
|
|
1785
|
+
function mdToToml(wf) {
|
|
1786
|
+
const desc = wf.description.replace(/"/g, '\\"');
|
|
1787
|
+
const prompt = wf.body.replace(/\\/g, "\\\\").replace(/"""/g, '\\"""');
|
|
1788
|
+
return `# Mover OS — /${wf.name}\ndescription = "${desc}"\n\nprompt = """\n${prompt}\n"""\n`;
|
|
1789
|
+
}
|
|
1790
|
+
|
|
1791
|
+
// Copilot: .md → .prompt.md (YAML frontmatter with mode: agent)
|
|
1792
|
+
function mdToCopilotPrompt(wf) {
|
|
1793
|
+
return `---\nmode: agent\ndescription: "${wf.description.replace(/"/g, '\\"')}"\n---\n\n${wf.body}\n`;
|
|
1794
|
+
}
|
|
1795
|
+
|
|
1796
|
+
// Continue.dev: .md → .prompt (YAML frontmatter with invokable: true)
|
|
1797
|
+
function mdToContinuePrompt(wf) {
|
|
1798
|
+
return `---\nname: ${wf.name}\ndescription: "${wf.description.replace(/"/g, '\\"')}"\ninvokable: true\n---\n\n${wf.body}\n`;
|
|
1799
|
+
}
|
|
1800
|
+
|
|
1801
|
+
// Cursor: .md → .mdc (MDC frontmatter format)
|
|
1802
|
+
function mdToMdc(wf) {
|
|
1803
|
+
return `---\ndescription: "${wf.description.replace(/"/g, '\\"')}"\nglobs:\nalwaysApply: false\n---\n\n${wf.body}\n`;
|
|
1804
|
+
}
|
|
1805
|
+
|
|
1806
|
+
// Amazon Q: .md → .json (CLI agent definition)
|
|
1807
|
+
function mdToAmazonQAgent(wf) {
|
|
1808
|
+
return JSON.stringify({
|
|
1809
|
+
name: wf.name,
|
|
1810
|
+
description: wf.description,
|
|
1811
|
+
prompt: wf.body,
|
|
1812
|
+
model: "claude-sonnet-4-20250514",
|
|
1813
|
+
}, null, 2) + "\n";
|
|
1814
|
+
}
|
|
1815
|
+
|
|
1816
|
+
// Codex: workflow .md → SKILL.md (skills ARE commands in Codex)
|
|
1817
|
+
function mdToCodexSkill(wf) {
|
|
1818
|
+
return `---\nname: ${wf.name}\ndescription: "${wf.description.replace(/"/g, '\\"')}"\n---\n\n${wf.body}\n`;
|
|
1819
|
+
}
|
|
1820
|
+
|
|
1821
|
+
// Windsurf: .md rule → .md with trigger_type frontmatter
|
|
1822
|
+
function mdToWindsurfRule(content, name) {
|
|
1823
|
+
const front = `---\ntrigger_type: always_on\ndescription: "Mover OS system rules"\n---\n\n`;
|
|
1824
|
+
return front + content;
|
|
1825
|
+
}
|
|
1826
|
+
|
|
1827
|
+
// Install workflows with format conversion
|
|
1828
|
+
function installWorkflowsConverted(bundleDir, destDir, format, selectedWorkflows) {
|
|
1829
|
+
const srcDir = path.join(bundleDir, "src", "workflows");
|
|
1830
|
+
if (!fs.existsSync(srcDir)) return 0;
|
|
1831
|
+
fs.mkdirSync(destDir, { recursive: true });
|
|
1832
|
+
|
|
1833
|
+
const converters = {
|
|
1834
|
+
"md": null, // identity — handled by installWorkflows()
|
|
1835
|
+
"toml": { fn: mdToToml, ext: ".toml" },
|
|
1836
|
+
"prompt-md": { fn: mdToCopilotPrompt, ext: ".prompt.md" },
|
|
1837
|
+
"continue-prompt": { fn: mdToContinuePrompt, ext: ".prompt" },
|
|
1838
|
+
"amazon-q-json": { fn: mdToAmazonQAgent, ext: ".json" },
|
|
1839
|
+
"codex-skill": { fn: mdToCodexSkill, ext: "/SKILL.md", dir: true },
|
|
1840
|
+
"mdc": { fn: mdToMdc, ext: ".mdc" },
|
|
1841
|
+
};
|
|
1842
|
+
|
|
1843
|
+
const conv = converters[format];
|
|
1844
|
+
if (!conv) return 0;
|
|
1845
|
+
|
|
1846
|
+
let count = 0;
|
|
1847
|
+
for (const file of fs.readdirSync(srcDir).filter((f) => f.endsWith(".md"))) {
|
|
1848
|
+
if (selectedWorkflows && !selectedWorkflows.has(file)) continue;
|
|
1849
|
+
const wf = parseWorkflowMd(path.join(srcDir, file));
|
|
1850
|
+
const converted = conv.fn(wf);
|
|
1851
|
+
if (conv.dir) {
|
|
1852
|
+
// Directory-based output (e.g., Codex: morning/SKILL.md)
|
|
1853
|
+
const skillDir = path.join(destDir, wf.name);
|
|
1854
|
+
fs.mkdirSync(skillDir, { recursive: true });
|
|
1855
|
+
fs.writeFileSync(path.join(skillDir, "SKILL.md"), converted, "utf8");
|
|
1856
|
+
} else {
|
|
1857
|
+
fs.writeFileSync(path.join(destDir, wf.name + conv.ext), converted, "utf8");
|
|
1858
|
+
}
|
|
1859
|
+
count++;
|
|
1860
|
+
}
|
|
1861
|
+
return count;
|
|
1862
|
+
}
|
|
1863
|
+
|
|
1524
1864
|
function computeSkillHash(dirPath) {
|
|
1525
1865
|
const hash = crypto.createHash("sha256");
|
|
1526
1866
|
const entries = fs.readdirSync(dirPath, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name));
|
|
@@ -1716,15 +2056,43 @@ function installCursor(bundleDir, vaultPath, skillOpts) {
|
|
|
1716
2056
|
const steps = [];
|
|
1717
2057
|
|
|
1718
2058
|
if (!skillOpts?.skipRules) {
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
fs.mkdirSync(
|
|
2059
|
+
// V5 FIX: Use .cursor/rules/*.mdc (not legacy .cursorrules)
|
|
2060
|
+
// MDC format: YAML frontmatter with description, globs, alwaysApply
|
|
2061
|
+
const rulesDir = vaultPath
|
|
2062
|
+
? path.join(vaultPath, ".cursor", "rules")
|
|
2063
|
+
: path.join(home, ".cursor", "rules");
|
|
2064
|
+
fs.mkdirSync(rulesDir, { recursive: true });
|
|
1725
2065
|
const src = path.join(bundleDir, "src", "system", "Mover_Global_Rules.md");
|
|
1726
2066
|
if (fs.existsSync(src)) {
|
|
1727
|
-
|
|
2067
|
+
let content = fs.readFileSync(src, "utf8");
|
|
2068
|
+
content = content.replace(/^# Mover OS Global Rules/m, "# Cursor Project Rules");
|
|
2069
|
+
// Strip Claude-specific S10.5
|
|
2070
|
+
content = content.replace(/## 10\.5 Subagents[\s\S]*?(?=\n---\n)/m, "");
|
|
2071
|
+
// Add MDC frontmatter
|
|
2072
|
+
const mdc = `---\ndescription: "Mover OS system rules — coding conventions, workflows, and project integrity"\nglobs:\nalwaysApply: true\n---\n\n${content}`;
|
|
2073
|
+
const destPath = path.join(rulesDir, "mover-os.mdc");
|
|
2074
|
+
// Preserve user customizations
|
|
2075
|
+
const customizations = extractCustomizations(destPath);
|
|
2076
|
+
let finalContent = mdc;
|
|
2077
|
+
if (customizations) {
|
|
2078
|
+
finalContent += `\n${RULES_SENTINEL} — Everything below this line is preserved during updates -->\n\n${customizations}\n`;
|
|
2079
|
+
}
|
|
2080
|
+
fs.writeFileSync(destPath, finalContent, "utf8");
|
|
2081
|
+
steps.push("rules (.mdc)");
|
|
2082
|
+
}
|
|
2083
|
+
|
|
2084
|
+
// Clean legacy .cursorrules if it exists
|
|
2085
|
+
if (vaultPath) {
|
|
2086
|
+
const legacy = path.join(vaultPath, ".cursorrules");
|
|
2087
|
+
if (fs.existsSync(legacy)) {
|
|
2088
|
+
try {
|
|
2089
|
+
const legacyContent = fs.readFileSync(legacy, "utf8");
|
|
2090
|
+
if (legacyContent.includes("Mover OS") || legacyContent.includes("MOVER_OS")) {
|
|
2091
|
+
fs.unlinkSync(legacy);
|
|
2092
|
+
ln(` ${dim("Cleaned legacy .cursorrules")}`);
|
|
2093
|
+
}
|
|
2094
|
+
} catch {}
|
|
2095
|
+
}
|
|
1728
2096
|
}
|
|
1729
2097
|
}
|
|
1730
2098
|
|
|
@@ -1769,12 +2137,16 @@ function installCodex(bundleDir, vaultPath, skillOpts) {
|
|
|
1769
2137
|
|
|
1770
2138
|
const codexDir = path.join(home, ".codex");
|
|
1771
2139
|
fs.mkdirSync(codexDir, { recursive: true });
|
|
1772
|
-
// Codex CLI uses AGENTS.md
|
|
1773
|
-
fs.writeFileSync(path.join(codexDir, "
|
|
1774
|
-
steps.push("
|
|
2140
|
+
// Codex CLI uses AGENTS.md (not instructions.md, not raw Global Rules)
|
|
2141
|
+
fs.writeFileSync(path.join(codexDir, "AGENTS.md"), generateAgentsMd(), "utf8");
|
|
2142
|
+
steps.push("AGENTS.md");
|
|
2143
|
+
|
|
2144
|
+
// V5 FIX: In Codex, skills ARE commands — install workflows as SKILL.md dirs
|
|
2145
|
+
const skillsDir = path.join(codexDir, "skills");
|
|
2146
|
+
const wfCount = installWorkflowsConverted(bundleDir, skillsDir, "codex-skill", skillOpts?.workflows);
|
|
2147
|
+
if (wfCount > 0) steps.push(`${wfCount} commands`);
|
|
1775
2148
|
|
|
1776
2149
|
if (skillOpts && skillOpts.install) {
|
|
1777
|
-
const skillsDir = path.join(codexDir, "skills");
|
|
1778
2150
|
const skCount = installSkillPacks(bundleDir, skillsDir, skillOpts.categories);
|
|
1779
2151
|
if (skCount > 0) steps.push(`${skCount} skills`);
|
|
1780
2152
|
}
|
|
@@ -1786,7 +2158,23 @@ function installWindsurf(bundleDir, vaultPath, skillOpts) {
|
|
|
1786
2158
|
const home = os.homedir();
|
|
1787
2159
|
const steps = [];
|
|
1788
2160
|
if (vaultPath) {
|
|
1789
|
-
|
|
2161
|
+
// V5 FIX: Write to .windsurf/rules/ dir AND legacy .windsurfrules for compatibility
|
|
2162
|
+
const wsDir = path.join(vaultPath, ".windsurf");
|
|
2163
|
+
const rulesDir = path.join(wsDir, "rules");
|
|
2164
|
+
fs.mkdirSync(rulesDir, { recursive: true });
|
|
2165
|
+
const src = path.join(bundleDir, "src", "system", "Mover_Global_Rules.md");
|
|
2166
|
+
if (fs.existsSync(src)) {
|
|
2167
|
+
// Primary: .windsurf/rules/mover-os.md
|
|
2168
|
+
fs.copyFileSync(src, path.join(rulesDir, "mover-os.md"));
|
|
2169
|
+
// Legacy: .windsurfrules (some Windsurf versions still read this)
|
|
2170
|
+
if (installRules(bundleDir, path.join(vaultPath, ".windsurfrules"), "windsurf")) {}
|
|
2171
|
+
steps.push("rules");
|
|
2172
|
+
}
|
|
2173
|
+
|
|
2174
|
+
// V5 FIX: Install workflows as .md commands
|
|
2175
|
+
const wfDir = path.join(wsDir, "workflows");
|
|
2176
|
+
const wfCount = installWorkflows(bundleDir, wfDir, skillOpts?.workflows);
|
|
2177
|
+
if (wfCount > 0) steps.push(`${wfCount} workflows`);
|
|
1790
2178
|
}
|
|
1791
2179
|
|
|
1792
2180
|
if (skillOpts && skillOpts.install) {
|
|
@@ -1798,37 +2186,7 @@ function installWindsurf(bundleDir, vaultPath, skillOpts) {
|
|
|
1798
2186
|
return steps;
|
|
1799
2187
|
}
|
|
1800
2188
|
|
|
1801
|
-
|
|
1802
|
-
const home = os.homedir();
|
|
1803
|
-
const workspace = path.join(home, ".openclaw", "workspace");
|
|
1804
|
-
const steps = [];
|
|
1805
|
-
|
|
1806
|
-
fs.mkdirSync(path.join(workspace, "skills"), { recursive: true });
|
|
1807
|
-
fs.mkdirSync(path.join(workspace, "memory"), { recursive: true });
|
|
1808
|
-
|
|
1809
|
-
fs.writeFileSync(path.join(workspace, "AGENTS.md"), generateAgentsMd(), "utf8");
|
|
1810
|
-
steps.push("AGENTS.md");
|
|
1811
|
-
|
|
1812
|
-
fs.writeFileSync(path.join(workspace, "SOUL.md"), generateSoulMd(), "utf8");
|
|
1813
|
-
steps.push("SOUL.md");
|
|
1814
|
-
|
|
1815
|
-
const userMd = path.join(workspace, "USER.md");
|
|
1816
|
-
if (!fs.existsSync(userMd)) {
|
|
1817
|
-
fs.writeFileSync(
|
|
1818
|
-
userMd,
|
|
1819
|
-
`# USER.md\n\n> Run \`/setup\` in Mover OS to populate this file with your Identity and Strategy.\n\n## Identity\n<!-- Populated by /setup -->\n\n## Strategy\n<!-- Populated by /setup -->\n\n## Assets\n<!-- Populated by /setup -->\n`,
|
|
1820
|
-
"utf8"
|
|
1821
|
-
);
|
|
1822
|
-
steps.push("USER.md");
|
|
1823
|
-
}
|
|
1824
|
-
|
|
1825
|
-
if (skillOpts && skillOpts.install) {
|
|
1826
|
-
const skCount = installSkillPacks(bundleDir, path.join(workspace, "skills"), skillOpts.categories);
|
|
1827
|
-
if (skCount > 0) steps.push(`${skCount} skills`);
|
|
1828
|
-
}
|
|
1829
|
-
|
|
1830
|
-
return steps;
|
|
1831
|
-
}
|
|
2189
|
+
// OpenClaw removed in V5 — unverifiable
|
|
1832
2190
|
|
|
1833
2191
|
function installGeminiCli(bundleDir, vaultPath, skillOpts, writtenFiles) {
|
|
1834
2192
|
const home = os.homedir();
|
|
@@ -1844,6 +2202,11 @@ function installGeminiCli(bundleDir, vaultPath, skillOpts, writtenFiles) {
|
|
|
1844
2202
|
steps.push("rules (shared)");
|
|
1845
2203
|
}
|
|
1846
2204
|
|
|
2205
|
+
// V5 FIX: Install commands as .toml files
|
|
2206
|
+
const cmdsDir = path.join(geminiDir, "commands");
|
|
2207
|
+
const wfCount = installWorkflowsConverted(bundleDir, cmdsDir, "toml", skillOpts?.workflows);
|
|
2208
|
+
if (wfCount > 0) steps.push(`${wfCount} commands`);
|
|
2209
|
+
|
|
1847
2210
|
if (skillOpts && skillOpts.install) {
|
|
1848
2211
|
const skCount = installSkillPacks(bundleDir, path.join(geminiDir, "skills"), skillOpts.categories);
|
|
1849
2212
|
if (skCount > 0) steps.push(`${skCount} skills`);
|
|
@@ -1890,6 +2253,11 @@ function installRooCode(bundleDir, vaultPath, skillOpts) {
|
|
|
1890
2253
|
steps.push("rules");
|
|
1891
2254
|
}
|
|
1892
2255
|
|
|
2256
|
+
// V5 FIX: Install commands as .md files
|
|
2257
|
+
const cmdsDir = path.join(vaultPath, ".roo", "commands");
|
|
2258
|
+
const wfCount = installWorkflows(bundleDir, cmdsDir, skillOpts?.workflows);
|
|
2259
|
+
if (wfCount > 0) steps.push(`${wfCount} commands`);
|
|
2260
|
+
|
|
1893
2261
|
if (skillOpts && skillOpts.install) {
|
|
1894
2262
|
const skillsDir = path.join(vaultPath, ".roo", "skills");
|
|
1895
2263
|
const skCount = installSkillPacks(bundleDir, skillsDir, skillOpts.categories);
|
|
@@ -1910,6 +2278,11 @@ function installCopilot(bundleDir, vaultPath, skillOpts) {
|
|
|
1910
2278
|
steps.push("rules");
|
|
1911
2279
|
}
|
|
1912
2280
|
|
|
2281
|
+
// V5 FIX: Install commands as .prompt.md files
|
|
2282
|
+
const promptsDir = path.join(ghDir, "prompts");
|
|
2283
|
+
const wfCount = installWorkflowsConverted(bundleDir, promptsDir, "prompt-md", skillOpts?.workflows);
|
|
2284
|
+
if (wfCount > 0) steps.push(`${wfCount} commands`);
|
|
2285
|
+
|
|
1913
2286
|
if (skillOpts && skillOpts.install) {
|
|
1914
2287
|
const skillsDir = path.join(ghDir, "skills");
|
|
1915
2288
|
const skCount = installSkillPacks(bundleDir, skillsDir, skillOpts.categories);
|
|
@@ -1925,13 +2298,98 @@ const AGENT_INSTALLERS = {
|
|
|
1925
2298
|
cline: installCline,
|
|
1926
2299
|
codex: installCodex,
|
|
1927
2300
|
windsurf: installWindsurf,
|
|
1928
|
-
openclaw: installOpenClaw,
|
|
1929
2301
|
"gemini-cli": installGeminiCli,
|
|
1930
2302
|
antigravity: installAntigravity,
|
|
1931
2303
|
"roo-code": installRooCode,
|
|
1932
2304
|
copilot: installCopilot,
|
|
2305
|
+
// New agents — use universal installer (delegates to per-agent functions for existing,
|
|
2306
|
+
// falls back to registry-driven install for new agents)
|
|
2307
|
+
"amazon-q": installFromRegistry,
|
|
2308
|
+
"opencode": installFromRegistry,
|
|
2309
|
+
"kilo-code": installFromRegistry,
|
|
2310
|
+
"amp": installFromRegistry,
|
|
2311
|
+
"continue": installFromRegistry,
|
|
2312
|
+
"aider": installFromRegistry,
|
|
1933
2313
|
};
|
|
1934
2314
|
|
|
2315
|
+
// ─── Registry-driven universal installer (for new agents) ────────────────────
|
|
2316
|
+
function installFromRegistry(bundleDir, vaultPath, skillOpts, writtenFiles, id) {
|
|
2317
|
+
const reg = AGENT_REGISTRY[id];
|
|
2318
|
+
if (!reg) return [];
|
|
2319
|
+
const steps = [];
|
|
2320
|
+
|
|
2321
|
+
// Rules
|
|
2322
|
+
if (reg.rules && !skillOpts?.skipRules) {
|
|
2323
|
+
const destPath = reg.rules.dest(vaultPath);
|
|
2324
|
+
if (reg.rules.type === "agents-md") {
|
|
2325
|
+
const sharedKey = reg.sharedRulesFile || id;
|
|
2326
|
+
if (!writtenFiles || !writtenFiles.has(destPath)) {
|
|
2327
|
+
fs.mkdirSync(path.dirname(destPath), { recursive: true });
|
|
2328
|
+
fs.writeFileSync(destPath, generateAgentsMd(), "utf8");
|
|
2329
|
+
if (writtenFiles) writtenFiles.add(destPath);
|
|
2330
|
+
steps.push("AGENTS.md");
|
|
2331
|
+
} else {
|
|
2332
|
+
steps.push("AGENTS.md (shared)");
|
|
2333
|
+
}
|
|
2334
|
+
} else if (reg.rules.type === "conventions-md") {
|
|
2335
|
+
// Aider: shorter rules file
|
|
2336
|
+
fs.mkdirSync(path.dirname(destPath), { recursive: true });
|
|
2337
|
+
const src = path.join(bundleDir, "src", "system", "Mover_Global_Rules.md");
|
|
2338
|
+
if (fs.existsSync(src)) {
|
|
2339
|
+
let content = fs.readFileSync(src, "utf8");
|
|
2340
|
+
// Aider gets a trimmed version — keep sections 0-4 only
|
|
2341
|
+
const s5idx = content.indexOf("\n## 5.");
|
|
2342
|
+
if (s5idx > 0) content = content.substring(0, s5idx);
|
|
2343
|
+
content = "# CONVENTIONS.md\n\n" + content.replace(/^# Mover OS Global Rules/m, "").trim() + "\n";
|
|
2344
|
+
fs.writeFileSync(destPath, content, "utf8");
|
|
2345
|
+
steps.push("CONVENTIONS.md");
|
|
2346
|
+
}
|
|
2347
|
+
} else if (reg.rules.type === "continue-rule") {
|
|
2348
|
+
// Continue.dev: .md with YAML frontmatter (alwaysApply: true)
|
|
2349
|
+
fs.mkdirSync(path.dirname(destPath), { recursive: true });
|
|
2350
|
+
const src = path.join(bundleDir, "src", "system", "Mover_Global_Rules.md");
|
|
2351
|
+
if (fs.existsSync(src)) {
|
|
2352
|
+
let content = fs.readFileSync(src, "utf8");
|
|
2353
|
+
content = content.replace(/^# Mover OS Global Rules/m, "");
|
|
2354
|
+
const front = `---\nname: Mover OS Rules\nglobs: "**/*"\nalwaysApply: true\n---\n\n`;
|
|
2355
|
+
fs.writeFileSync(destPath, front + content.trim() + "\n", "utf8");
|
|
2356
|
+
steps.push("rules");
|
|
2357
|
+
}
|
|
2358
|
+
} else {
|
|
2359
|
+
// Standard copy
|
|
2360
|
+
fs.mkdirSync(path.dirname(destPath), { recursive: true });
|
|
2361
|
+
if (installRules(bundleDir, destPath, id)) steps.push("rules");
|
|
2362
|
+
}
|
|
2363
|
+
}
|
|
2364
|
+
|
|
2365
|
+
// Skills
|
|
2366
|
+
if (reg.skills && skillOpts && skillOpts.install) {
|
|
2367
|
+
const skillsDir = reg.skills.dest(vaultPath);
|
|
2368
|
+
const skCount = installSkillPacks(bundleDir, skillsDir, skillOpts.categories);
|
|
2369
|
+
if (skCount > 0) steps.push(`${skCount} skills`);
|
|
2370
|
+
}
|
|
2371
|
+
|
|
2372
|
+
// Commands (with format conversion)
|
|
2373
|
+
if (reg.commands && !skillOpts?.skipCommands) {
|
|
2374
|
+
const cmdDest = reg.commands.dest(vaultPath);
|
|
2375
|
+
const fmt = reg.commands.format;
|
|
2376
|
+
if (fmt === "md") {
|
|
2377
|
+
// Direct .md copy (same as Claude Code)
|
|
2378
|
+
const wfCount = installWorkflows(bundleDir, cmdDest, skillOpts?.workflows);
|
|
2379
|
+
if (wfCount > 0) steps.push(`${wfCount} commands`);
|
|
2380
|
+
} else if (fmt === "opencode-json") {
|
|
2381
|
+
// OpenCode: commands go into opencode.json (merge, don't overwrite)
|
|
2382
|
+
// Skip for now — requires config file merge logic
|
|
2383
|
+
} else {
|
|
2384
|
+
// All other formats: convert .md → target format
|
|
2385
|
+
const wfCount = installWorkflowsConverted(bundleDir, cmdDest, fmt, skillOpts?.workflows);
|
|
2386
|
+
if (wfCount > 0) steps.push(`${wfCount} commands`);
|
|
2387
|
+
}
|
|
2388
|
+
}
|
|
2389
|
+
|
|
2390
|
+
return steps;
|
|
2391
|
+
}
|
|
2392
|
+
|
|
1935
2393
|
// ─── Pre-flight checks ──────────────────────────────────────────────────────
|
|
1936
2394
|
function preflight() {
|
|
1937
2395
|
const issues = [];
|
|
@@ -1970,6 +2428,936 @@ function preflight() {
|
|
|
1970
2428
|
return issues;
|
|
1971
2429
|
}
|
|
1972
2430
|
|
|
2431
|
+
// ─── CLI Command Handlers (stubs — implemented progressively) ────────────────
|
|
2432
|
+
const CLI_HANDLERS = {
|
|
2433
|
+
pulse: async (opts) => { await cmdPulse(opts); },
|
|
2434
|
+
warm: async (opts) => { await cmdWarm(opts); },
|
|
2435
|
+
capture: async (opts) => { await cmdCapture(opts); },
|
|
2436
|
+
who: async (opts) => { await cmdWho(opts); },
|
|
2437
|
+
diff: async (opts) => { await cmdDiff(opts); },
|
|
2438
|
+
sync: async (opts) => { await cmdSync(opts); },
|
|
2439
|
+
replay: async (opts) => { await cmdReplay(opts); },
|
|
2440
|
+
context: async (opts) => { await cmdContext(opts); },
|
|
2441
|
+
settings: async (opts) => { await cmdSettings(opts); },
|
|
2442
|
+
backup: async (opts) => { await cmdBackup(opts); },
|
|
2443
|
+
restore: async (opts) => { await cmdRestore(opts); },
|
|
2444
|
+
doctor: async (opts) => { await cmdDoctor(opts); },
|
|
2445
|
+
test: async (opts) => { await cmdTest(opts); },
|
|
2446
|
+
};
|
|
2447
|
+
|
|
2448
|
+
// ─── moveros doctor ─────────────────────────────────────────────────────────
|
|
2449
|
+
async function cmdDoctor(opts) {
|
|
2450
|
+
const vault = resolveVaultPath(opts.vault);
|
|
2451
|
+
if (!vault) {
|
|
2452
|
+
barLn(red("No Mover OS vault found. Use: moveros doctor --vault /path"));
|
|
2453
|
+
return;
|
|
2454
|
+
}
|
|
2455
|
+
const home = os.homedir();
|
|
2456
|
+
barLn(bold(" Health Check"));
|
|
2457
|
+
barLn();
|
|
2458
|
+
|
|
2459
|
+
// Vault structure
|
|
2460
|
+
const paraDirs = ["00_Inbox", "01_Projects", "02_Areas", "03_Library", "04_Archives"];
|
|
2461
|
+
let paraOk = 0;
|
|
2462
|
+
for (const d of paraDirs) {
|
|
2463
|
+
if (fs.existsSync(path.join(vault, d))) paraOk++;
|
|
2464
|
+
}
|
|
2465
|
+
statusLine(paraOk === paraDirs.length ? "ok" : "warn", "Vault structure", `${paraOk}/${paraDirs.length} PARA folders`);
|
|
2466
|
+
|
|
2467
|
+
// Engine files
|
|
2468
|
+
const engineFiles = ["Identity_Prime.md", "Strategy.md", "Active_Context.md", "Goals.md", "Mover_Dossier.md", "Auto_Learnings.md"];
|
|
2469
|
+
const engineDir = path.join(vault, "02_Areas", "Engine");
|
|
2470
|
+
let engOk = 0;
|
|
2471
|
+
for (const f of engineFiles) {
|
|
2472
|
+
if (fs.existsSync(path.join(engineDir, f))) engOk++;
|
|
2473
|
+
}
|
|
2474
|
+
statusLine(engOk >= 4 ? "ok" : engOk >= 2 ? "warn" : "fail", "Engine files", `${engOk}/${engineFiles.length} present`);
|
|
2475
|
+
|
|
2476
|
+
// Version
|
|
2477
|
+
const vf = path.join(vault, ".mover-version");
|
|
2478
|
+
const ver = fs.existsSync(vf) ? fs.readFileSync(vf, "utf8").trim() : null;
|
|
2479
|
+
statusLine(ver ? "ok" : "warn", "Version", ver || "not stamped");
|
|
2480
|
+
|
|
2481
|
+
// Config
|
|
2482
|
+
const cfg = path.join(home, ".mover", "config.json");
|
|
2483
|
+
statusLine(fs.existsSync(cfg) ? "ok" : "warn", "Config", fs.existsSync(cfg) ? cfg : "missing");
|
|
2484
|
+
|
|
2485
|
+
// Per-agent checks
|
|
2486
|
+
barLn();
|
|
2487
|
+
barLn(dim(" Agents:"));
|
|
2488
|
+
const cfgData = fs.existsSync(cfg) ? JSON.parse(fs.readFileSync(cfg, "utf8")) : {};
|
|
2489
|
+
const installedAgents = cfgData.agents || [];
|
|
2490
|
+
if (installedAgents.length === 0) {
|
|
2491
|
+
barLn(dim(" No agents recorded in config."));
|
|
2492
|
+
}
|
|
2493
|
+
for (const agentId of installedAgents) {
|
|
2494
|
+
const reg = AGENT_REGISTRY[agentId];
|
|
2495
|
+
if (!reg) { statusLine("warn", ` ${agentId}`, "unknown agent"); continue; }
|
|
2496
|
+
const checks = [];
|
|
2497
|
+
// Rules
|
|
2498
|
+
if (reg.rules) {
|
|
2499
|
+
const rp = reg.rules.dest(vault);
|
|
2500
|
+
checks.push(fs.existsSync(rp) ? "rules" : dim("rules missing"));
|
|
2501
|
+
}
|
|
2502
|
+
// Skills
|
|
2503
|
+
if (reg.skills) {
|
|
2504
|
+
const sp = reg.skills.dest(vault);
|
|
2505
|
+
const hasSkills = fs.existsSync(sp) && fs.readdirSync(sp).length > 0;
|
|
2506
|
+
checks.push(hasSkills ? "skills" : dim("no skills"));
|
|
2507
|
+
}
|
|
2508
|
+
// Commands
|
|
2509
|
+
if (reg.commands) {
|
|
2510
|
+
const cp = reg.commands.dest(vault);
|
|
2511
|
+
const hasCmds = fs.existsSync(cp) && fs.readdirSync(cp).length > 0;
|
|
2512
|
+
checks.push(hasCmds ? "commands" : dim("no commands"));
|
|
2513
|
+
}
|
|
2514
|
+
const allOk = checks.every((c) => !c.includes("missing"));
|
|
2515
|
+
statusLine(allOk ? "ok" : "warn", ` ${reg.name}`, checks.join(", "));
|
|
2516
|
+
}
|
|
2517
|
+
|
|
2518
|
+
// Git
|
|
2519
|
+
barLn();
|
|
2520
|
+
const hasGit = fs.existsSync(path.join(vault, ".git"));
|
|
2521
|
+
statusLine(hasGit ? "ok" : "info", "Git", hasGit ? "initialized" : "not a git repo");
|
|
2522
|
+
|
|
2523
|
+
barLn();
|
|
2524
|
+
barLn(dim(" Run moveros install or moveros update to fix any issues."));
|
|
2525
|
+
}
|
|
2526
|
+
|
|
2527
|
+
// ─── moveros pulse ──────────────────────────────────────────────────────────
|
|
2528
|
+
async function cmdPulse(opts) {
|
|
2529
|
+
const vault = resolveVaultPath(opts.vault);
|
|
2530
|
+
if (!vault) {
|
|
2531
|
+
barLn(red("No vault found. Use: moveros pulse --vault /path"));
|
|
2532
|
+
return;
|
|
2533
|
+
}
|
|
2534
|
+
const engineDir = path.join(vault, "02_Areas", "Engine");
|
|
2535
|
+
|
|
2536
|
+
barLn(bold(" Pulse"));
|
|
2537
|
+
barLn();
|
|
2538
|
+
|
|
2539
|
+
// Energy + Focus from Active_Context
|
|
2540
|
+
const acPath = path.join(engineDir, "Active_Context.md");
|
|
2541
|
+
let energy = "?", focus = "?", blockers = [];
|
|
2542
|
+
if (fs.existsSync(acPath)) {
|
|
2543
|
+
const ac = fs.readFileSync(acPath, "utf8");
|
|
2544
|
+
const energyMatch = ac.match(/\*\*Energy:\*\*\s*(.+)/i) || ac.match(/Energy:\s*(.+)/i);
|
|
2545
|
+
if (energyMatch) energy = energyMatch[1].trim();
|
|
2546
|
+
const focusMatch = ac.match(/\*\*Focus:\*\*\s*(.+)/i) || ac.match(/Single Test:\s*(.+)/i);
|
|
2547
|
+
if (focusMatch) focus = focusMatch[1].trim();
|
|
2548
|
+
// Blockers
|
|
2549
|
+
const blockerSection = ac.match(/##.*Blocker[s]?[\s\S]*?(?=\n##|\n---|\Z)/i);
|
|
2550
|
+
if (blockerSection) {
|
|
2551
|
+
const lines = blockerSection[0].split("\n").filter((l) => l.trim().startsWith("-") || l.trim().startsWith("*"));
|
|
2552
|
+
blockers = lines.map((l) => l.replace(/^[\s*-]+/, "").trim()).filter(Boolean).slice(0, 3);
|
|
2553
|
+
}
|
|
2554
|
+
}
|
|
2555
|
+
statusLine("info", "Energy", energy);
|
|
2556
|
+
statusLine("info", "Focus", focus.substring(0, 60));
|
|
2557
|
+
if (blockers.length > 0) {
|
|
2558
|
+
statusLine("warn", "Blockers", blockers[0]);
|
|
2559
|
+
for (let i = 1; i < blockers.length; i++) barLn(` ${dim(blockers[i])}`);
|
|
2560
|
+
} else {
|
|
2561
|
+
statusLine("ok", "Blockers", "none");
|
|
2562
|
+
}
|
|
2563
|
+
|
|
2564
|
+
// Today's tasks from Daily Note
|
|
2565
|
+
barLn();
|
|
2566
|
+
const now = new Date();
|
|
2567
|
+
const ymd = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, "0")}-${String(now.getDate()).padStart(2, "0")}`;
|
|
2568
|
+
const month = ymd.substring(0, 7);
|
|
2569
|
+
const dailyPath = path.join(engineDir, "Dailies", month, `Daily - ${ymd}.md`);
|
|
2570
|
+
if (fs.existsSync(dailyPath)) {
|
|
2571
|
+
const daily = fs.readFileSync(dailyPath, "utf8");
|
|
2572
|
+
const taskSection = daily.match(/##\s*Tasks[\s\S]*?(?=\n##|\n---)/i);
|
|
2573
|
+
if (taskSection) {
|
|
2574
|
+
const tasks = taskSection[0].split("\n").filter((l) => /^\s*-\s*\[[ x~]\]/.test(l));
|
|
2575
|
+
const done = tasks.filter((t) => /\[x\]|\[~\]/.test(t)).length;
|
|
2576
|
+
const total = tasks.length;
|
|
2577
|
+
barLn(` ${progressBar(done, total, 25, "Tasks")}`);
|
|
2578
|
+
}
|
|
2579
|
+
} else {
|
|
2580
|
+
barLn(dim(" No Daily Note for today."));
|
|
2581
|
+
}
|
|
2582
|
+
|
|
2583
|
+
// Active projects — scan plan.md files
|
|
2584
|
+
barLn();
|
|
2585
|
+
const projectsDir = path.join(vault, "01_Projects");
|
|
2586
|
+
if (fs.existsSync(projectsDir)) {
|
|
2587
|
+
const projects = fs.readdirSync(projectsDir, { withFileTypes: true })
|
|
2588
|
+
.filter((d) => d.isDirectory())
|
|
2589
|
+
.slice(0, 5);
|
|
2590
|
+
if (projects.length > 0) {
|
|
2591
|
+
barLn(dim(" Active Projects:"));
|
|
2592
|
+
for (const p of projects) {
|
|
2593
|
+
const planPath = path.join(projectsDir, p.name, "dev", "plan.md");
|
|
2594
|
+
if (fs.existsSync(planPath)) {
|
|
2595
|
+
const plan = fs.readFileSync(planPath, "utf8");
|
|
2596
|
+
const tasks = plan.match(/^\s*-\s*\[[ x~]\]/gm) || [];
|
|
2597
|
+
const done = (plan.match(/^\s*-\s*\[x\]/gm) || []).length;
|
|
2598
|
+
barLn(` ${p.name.padEnd(25)} ${progressBar(done, tasks.length, 15)}`);
|
|
2599
|
+
} else {
|
|
2600
|
+
barLn(` ${dim(p.name)}`);
|
|
2601
|
+
}
|
|
2602
|
+
}
|
|
2603
|
+
}
|
|
2604
|
+
}
|
|
2605
|
+
|
|
2606
|
+
// Streaks
|
|
2607
|
+
barLn();
|
|
2608
|
+
const dailiesDir = path.join(engineDir, "Dailies");
|
|
2609
|
+
if (fs.existsSync(dailiesDir)) {
|
|
2610
|
+
let streak = 0;
|
|
2611
|
+
const d = new Date();
|
|
2612
|
+
for (let i = 0; i < 30; i++) {
|
|
2613
|
+
const check = new Date(d);
|
|
2614
|
+
check.setDate(check.getDate() - i);
|
|
2615
|
+
const cy = `${check.getFullYear()}-${String(check.getMonth() + 1).padStart(2, "0")}-${String(check.getDate()).padStart(2, "0")}`;
|
|
2616
|
+
const cm = cy.substring(0, 7);
|
|
2617
|
+
if (fs.existsSync(path.join(dailiesDir, cm, `Daily - ${cy}.md`))) {
|
|
2618
|
+
streak++;
|
|
2619
|
+
} else if (i > 0) break; // allow today to be missing
|
|
2620
|
+
}
|
|
2621
|
+
statusLine(streak >= 3 ? "ok" : "info", "Daily streak", `${streak} day${streak !== 1 ? "s" : ""}`);
|
|
2622
|
+
}
|
|
2623
|
+
|
|
2624
|
+
// Last session log time
|
|
2625
|
+
const cfgPath = path.join(os.homedir(), ".mover", "config.json");
|
|
2626
|
+
if (fs.existsSync(cfgPath)) {
|
|
2627
|
+
try {
|
|
2628
|
+
const cfgData = JSON.parse(fs.readFileSync(cfgPath, "utf8"));
|
|
2629
|
+
if (cfgData.lastLog) {
|
|
2630
|
+
const ago = Math.round((Date.now() - new Date(cfgData.lastLog).getTime()) / 3600000);
|
|
2631
|
+
statusLine(ago < 8 ? "ok" : ago < 24 ? "info" : "warn", "Last /log", `${ago}h ago`);
|
|
2632
|
+
}
|
|
2633
|
+
} catch {}
|
|
2634
|
+
}
|
|
2635
|
+
barLn();
|
|
2636
|
+
}
|
|
2637
|
+
|
|
2638
|
+
// ─── moveros warm ───────────────────────────────────────────────────────────
|
|
2639
|
+
async function cmdWarm(opts) {
|
|
2640
|
+
const vault = resolveVaultPath(opts.vault);
|
|
2641
|
+
if (!vault) { barLn(red("No vault found.")); return; }
|
|
2642
|
+
const agent = opts.rest[0] || "claude";
|
|
2643
|
+
const engineDir = path.join(vault, "02_Areas", "Engine");
|
|
2644
|
+
const home = os.homedir();
|
|
2645
|
+
|
|
2646
|
+
// Build context primer
|
|
2647
|
+
const sections = [];
|
|
2648
|
+
sections.push("# Session Context Primer");
|
|
2649
|
+
sections.push(`Generated: ${new Date().toISOString()}\n`);
|
|
2650
|
+
|
|
2651
|
+
// Active Context snapshot
|
|
2652
|
+
const acPath = path.join(engineDir, "Active_Context.md");
|
|
2653
|
+
if (fs.existsSync(acPath)) {
|
|
2654
|
+
const ac = fs.readFileSync(acPath, "utf8");
|
|
2655
|
+
// Extract key sections (first 2000 chars)
|
|
2656
|
+
sections.push("## Active Context\n" + ac.substring(0, 2000));
|
|
2657
|
+
}
|
|
2658
|
+
|
|
2659
|
+
// Current project state
|
|
2660
|
+
const cwd = process.cwd();
|
|
2661
|
+
const planPath = path.join(cwd, "dev", "plan.md");
|
|
2662
|
+
if (fs.existsSync(planPath)) {
|
|
2663
|
+
const plan = fs.readFileSync(planPath, "utf8");
|
|
2664
|
+
// Find last active phase
|
|
2665
|
+
const phases = plan.match(/### Phase \d+[\s\S]*?(?=### Phase|\Z)/g);
|
|
2666
|
+
if (phases) {
|
|
2667
|
+
const active = phases[phases.length - 1].substring(0, 1500);
|
|
2668
|
+
sections.push("## Current Plan (last phase)\n" + active);
|
|
2669
|
+
}
|
|
2670
|
+
}
|
|
2671
|
+
|
|
2672
|
+
// Today's daily note tasks
|
|
2673
|
+
const now = new Date();
|
|
2674
|
+
const ymd = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, "0")}-${String(now.getDate()).padStart(2, "0")}`;
|
|
2675
|
+
const dailyPath = path.join(engineDir, "Dailies", ymd.substring(0, 7), `Daily - ${ymd}.md`);
|
|
2676
|
+
if (fs.existsSync(dailyPath)) {
|
|
2677
|
+
const daily = fs.readFileSync(dailyPath, "utf8");
|
|
2678
|
+
const taskSection = daily.match(/##\s*Tasks[\s\S]*?(?=\n##)/i);
|
|
2679
|
+
if (taskSection) sections.push("## Today's Tasks\n" + taskSection[0]);
|
|
2680
|
+
}
|
|
2681
|
+
|
|
2682
|
+
const primer = sections.join("\n\n---\n\n") + "\n";
|
|
2683
|
+
|
|
2684
|
+
// Write to agent-specific location
|
|
2685
|
+
const targets = {
|
|
2686
|
+
claude: path.join(home, ".claude", "tmp", "session-primer.md"),
|
|
2687
|
+
cursor: path.join(vault, ".cursor", "rules", "session-primer.mdc"),
|
|
2688
|
+
gemini: path.join(home, ".gemini", "session-primer.md"),
|
|
2689
|
+
codex: path.join(home, ".codex", "skills", "session-primer", "SKILL.md"),
|
|
2690
|
+
windsurf: path.join(vault, ".windsurf", "rules", "session-primer.md"),
|
|
2691
|
+
cline: path.join(vault, ".clinerules", "session-primer.md"),
|
|
2692
|
+
};
|
|
2693
|
+
|
|
2694
|
+
const dest = targets[agent] || targets.claude;
|
|
2695
|
+
fs.mkdirSync(path.dirname(dest), { recursive: true });
|
|
2696
|
+
|
|
2697
|
+
let content = primer;
|
|
2698
|
+
if (agent === "cursor") {
|
|
2699
|
+
content = `---\ndescription: "Session context primer — auto-generated by moveros warm"\nglobs:\nalwaysApply: true\n---\n\n${primer}`;
|
|
2700
|
+
} else if (agent === "codex") {
|
|
2701
|
+
content = `---\nname: session-primer\ndescription: "Auto-generated session context from moveros warm"\n---\n\n${primer}`;
|
|
2702
|
+
}
|
|
2703
|
+
|
|
2704
|
+
fs.writeFileSync(dest, content, "utf8");
|
|
2705
|
+
statusLine("ok", "Warm", `${agent} primer written`);
|
|
2706
|
+
barLn(dim(` ${dest}`));
|
|
2707
|
+
barLn();
|
|
2708
|
+
}
|
|
2709
|
+
|
|
2710
|
+
// ─── moveros capture ────────────────────────────────────────────────────────
|
|
2711
|
+
async function cmdCapture(opts) {
|
|
2712
|
+
const vault = resolveVaultPath(opts.vault);
|
|
2713
|
+
if (!vault) { barLn(red("No vault found.")); return; }
|
|
2714
|
+
|
|
2715
|
+
const now = new Date();
|
|
2716
|
+
const ymd = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, "0")}-${String(now.getDate()).padStart(2, "0")}`;
|
|
2717
|
+
const ts = `${String(now.getHours()).padStart(2, "0")}:${String(now.getMinutes()).padStart(2, "0")}`;
|
|
2718
|
+
const capturePath = path.join(vault, "00_Inbox", `Capture - ${ymd}.md`);
|
|
2719
|
+
|
|
2720
|
+
// Determine type from flags
|
|
2721
|
+
let type = null, content = "";
|
|
2722
|
+
const rest = opts.rest;
|
|
2723
|
+
if (rest.includes("--task")) { type = "task"; rest.splice(rest.indexOf("--task"), 1); }
|
|
2724
|
+
else if (rest.includes("--link")) { type = "link"; rest.splice(rest.indexOf("--link"), 1); }
|
|
2725
|
+
else if (rest.includes("--idea")) { type = "idea"; rest.splice(rest.indexOf("--idea"), 1); }
|
|
2726
|
+
else if (rest.includes("--dump")) { type = "dump"; rest.splice(rest.indexOf("--dump"), 1); }
|
|
2727
|
+
|
|
2728
|
+
content = rest.join(" ").trim();
|
|
2729
|
+
|
|
2730
|
+
// Check for stdin pipe
|
|
2731
|
+
if (!content && !process.stdin.isTTY) {
|
|
2732
|
+
content = fs.readFileSync(0, "utf8").trim();
|
|
2733
|
+
if (!type) type = "dump";
|
|
2734
|
+
}
|
|
2735
|
+
|
|
2736
|
+
// Interactive if no content
|
|
2737
|
+
if (!content) {
|
|
2738
|
+
if (!type) {
|
|
2739
|
+
barLn(bold(" Quick Capture"));
|
|
2740
|
+
barLn();
|
|
2741
|
+
type = await interactiveSelect([
|
|
2742
|
+
{ id: "task", name: "Task", tier: "Something to do" },
|
|
2743
|
+
{ id: "idea", name: "Idea", tier: "Something to think about" },
|
|
2744
|
+
{ id: "link", name: "Link", tier: "URL with optional note" },
|
|
2745
|
+
{ id: "dump", name: "Brain dump", tier: "Free-form text" },
|
|
2746
|
+
], { multi: false });
|
|
2747
|
+
}
|
|
2748
|
+
content = await textInput({ label: `Enter ${type}:` });
|
|
2749
|
+
}
|
|
2750
|
+
if (!type) type = "task";
|
|
2751
|
+
|
|
2752
|
+
if (!content) { barLn(yellow("Nothing to capture.")); return; }
|
|
2753
|
+
|
|
2754
|
+
// Format entry
|
|
2755
|
+
let entry;
|
|
2756
|
+
if (type === "task") entry = `- [ ] ${content} *(${ts})*`;
|
|
2757
|
+
else if (type === "link") entry = `- [${content.startsWith("http") ? "Link" : content.split(" ")[0]}](${content.split(" ")[0]}) ${content.split(" ").slice(1).join(" ")} *(${ts})*`;
|
|
2758
|
+
else if (type === "idea") entry = `- **Idea:** ${content} *(${ts})*`;
|
|
2759
|
+
else entry = `- ${content} *(${ts})*`;
|
|
2760
|
+
|
|
2761
|
+
// Append to capture file
|
|
2762
|
+
fs.mkdirSync(path.dirname(capturePath), { recursive: true });
|
|
2763
|
+
if (!fs.existsSync(capturePath)) {
|
|
2764
|
+
fs.writeFileSync(capturePath, `# Capture — ${ymd}\n\n${entry}\n`, "utf8");
|
|
2765
|
+
} else {
|
|
2766
|
+
fs.appendFileSync(capturePath, `${entry}\n`, "utf8");
|
|
2767
|
+
}
|
|
2768
|
+
|
|
2769
|
+
statusLine("ok", "Captured", `${type} → 00_Inbox/`);
|
|
2770
|
+
barLn(dim(` ${entry}`));
|
|
2771
|
+
barLn();
|
|
2772
|
+
}
|
|
2773
|
+
|
|
2774
|
+
// ─── moveros who ────────────────────────────────────────────────────────────
|
|
2775
|
+
async function cmdWho(opts) {
|
|
2776
|
+
const vault = resolveVaultPath(opts.vault);
|
|
2777
|
+
if (!vault) { barLn(red("No vault found.")); return; }
|
|
2778
|
+
|
|
2779
|
+
const name = opts.rest.join(" ").trim();
|
|
2780
|
+
if (!name) { barLn(yellow("Usage: moveros who <name>")); return; }
|
|
2781
|
+
|
|
2782
|
+
const entitiesDir = path.join(vault, "03_Library", "Entities");
|
|
2783
|
+
const subdirs = ["People", "Organizations", "Places"];
|
|
2784
|
+
const results = [];
|
|
2785
|
+
|
|
2786
|
+
for (const sub of subdirs) {
|
|
2787
|
+
const dir = path.join(entitiesDir, sub);
|
|
2788
|
+
if (!fs.existsSync(dir)) continue;
|
|
2789
|
+
for (const file of fs.readdirSync(dir).filter((f) => f.endsWith(".md"))) {
|
|
2790
|
+
if (file.toLowerCase().includes(name.toLowerCase())) {
|
|
2791
|
+
const content = fs.readFileSync(path.join(dir, file), "utf8");
|
|
2792
|
+
results.push({ file, sub, content });
|
|
2793
|
+
}
|
|
2794
|
+
}
|
|
2795
|
+
}
|
|
2796
|
+
|
|
2797
|
+
if (results.length === 0) {
|
|
2798
|
+
barLn(yellow(` No entity found for "${name}".`));
|
|
2799
|
+
barLn();
|
|
2800
|
+
const create = await interactiveSelect([
|
|
2801
|
+
{ id: "yes", name: "Create stub", tier: `Creates ${name}.md in People/` },
|
|
2802
|
+
{ id: "no", name: "Skip", tier: "" },
|
|
2803
|
+
], { multi: false });
|
|
2804
|
+
if (create === "yes") {
|
|
2805
|
+
const peopleDir = path.join(entitiesDir, "People");
|
|
2806
|
+
fs.mkdirSync(peopleDir, { recursive: true });
|
|
2807
|
+
const stub = `# ${name}\n\n**Type:** Person\n**Last Contact:** ${new Date().toISOString().split("T")[0]}\n\n## Context\n\n- \n\n## Notes\n\n- \n`;
|
|
2808
|
+
fs.writeFileSync(path.join(peopleDir, `${name}.md`), stub, "utf8");
|
|
2809
|
+
statusLine("ok", "Created", `${name}.md in Entities/People/`);
|
|
2810
|
+
}
|
|
2811
|
+
return;
|
|
2812
|
+
}
|
|
2813
|
+
|
|
2814
|
+
barLn(bold(` ${name}`));
|
|
2815
|
+
barLn();
|
|
2816
|
+
for (const r of results) {
|
|
2817
|
+
barLn(dim(` ${r.sub}/${r.file}`));
|
|
2818
|
+
// Show first 15 lines of content
|
|
2819
|
+
const lines = r.content.split("\n").slice(0, 15);
|
|
2820
|
+
for (const l of lines) barLn(` ${l}`);
|
|
2821
|
+
barLn();
|
|
2822
|
+
}
|
|
2823
|
+
}
|
|
2824
|
+
|
|
2825
|
+
// ─── moveros diff ───────────────────────────────────────────────────────────
|
|
2826
|
+
async function cmdDiff(opts) {
|
|
2827
|
+
const vault = resolveVaultPath(opts.vault);
|
|
2828
|
+
if (!vault) { barLn(red("No vault found.")); return; }
|
|
2829
|
+
if (!cmdExists("git")) { barLn(red("Git required for moveros diff.")); return; }
|
|
2830
|
+
|
|
2831
|
+
const target = opts.rest[0] || "strategy";
|
|
2832
|
+
const days = parseInt(opts.rest.find((a) => a.startsWith("--days="))?.split("=")[1] || "30", 10);
|
|
2833
|
+
|
|
2834
|
+
const fileMap = {
|
|
2835
|
+
strategy: "02_Areas/Engine/Strategy.md",
|
|
2836
|
+
identity: "02_Areas/Engine/Identity_Prime.md",
|
|
2837
|
+
goals: "02_Areas/Engine/Goals.md",
|
|
2838
|
+
dossier: "02_Areas/Engine/Mover_Dossier.md",
|
|
2839
|
+
context: "02_Areas/Engine/Active_Context.md",
|
|
2840
|
+
};
|
|
2841
|
+
|
|
2842
|
+
const relPath = fileMap[target.toLowerCase()] || target;
|
|
2843
|
+
const fullPath = path.join(vault, relPath);
|
|
2844
|
+
if (!fs.existsSync(fullPath)) {
|
|
2845
|
+
barLn(yellow(` File not found: ${relPath}`));
|
|
2846
|
+
return;
|
|
2847
|
+
}
|
|
2848
|
+
|
|
2849
|
+
barLn(bold(` ${path.basename(relPath)} — last ${days} days`));
|
|
2850
|
+
barLn();
|
|
2851
|
+
|
|
2852
|
+
try {
|
|
2853
|
+
const log = execSync(
|
|
2854
|
+
`git log --since="${days} days ago" --oneline --follow -- "${relPath}"`,
|
|
2855
|
+
{ cwd: vault, encoding: "utf8", timeout: 10000 }
|
|
2856
|
+
).trim();
|
|
2857
|
+
|
|
2858
|
+
if (!log) {
|
|
2859
|
+
barLn(dim(" No changes in the last " + days + " days."));
|
|
2860
|
+
} else {
|
|
2861
|
+
const commits = log.split("\n");
|
|
2862
|
+
for (const c of commits.slice(0, 15)) {
|
|
2863
|
+
const [hash, ...msg] = c.split(" ");
|
|
2864
|
+
barLn(` ${cyan(hash)} ${msg.join(" ")}`);
|
|
2865
|
+
}
|
|
2866
|
+
if (commits.length > 15) barLn(dim(` ...and ${commits.length - 15} more`));
|
|
2867
|
+
}
|
|
2868
|
+
} catch {
|
|
2869
|
+
barLn(dim(" Not a git repo or no history available."));
|
|
2870
|
+
}
|
|
2871
|
+
barLn();
|
|
2872
|
+
}
|
|
2873
|
+
|
|
2874
|
+
// ─── moveros sync ───────────────────────────────────────────────────────────
|
|
2875
|
+
async function cmdSync(opts) {
|
|
2876
|
+
const vault = resolveVaultPath(opts.vault);
|
|
2877
|
+
if (!vault) { barLn(red("No vault found.")); return; }
|
|
2878
|
+
const apply = opts.rest.includes("--apply");
|
|
2879
|
+
const home = os.homedir();
|
|
2880
|
+
|
|
2881
|
+
barLn(bold(" Agent Sync"));
|
|
2882
|
+
barLn();
|
|
2883
|
+
|
|
2884
|
+
// Read installed agents
|
|
2885
|
+
const cfgPath = path.join(home, ".mover", "config.json");
|
|
2886
|
+
if (!fs.existsSync(cfgPath)) { barLn(yellow(" No config found. Run moveros install first.")); return; }
|
|
2887
|
+
const cfg = JSON.parse(fs.readFileSync(cfgPath, "utf8"));
|
|
2888
|
+
const agents = cfg.agents || [];
|
|
2889
|
+
|
|
2890
|
+
if (agents.length === 0) { barLn(dim(" No agents installed.")); return; }
|
|
2891
|
+
|
|
2892
|
+
// Find bundle dir (where src/ lives)
|
|
2893
|
+
let bundleDir = null;
|
|
2894
|
+
const candidates = [
|
|
2895
|
+
process.cwd(),
|
|
2896
|
+
path.join(vault, "01_Projects", "Mover OS Bundle"),
|
|
2897
|
+
__dirname,
|
|
2898
|
+
];
|
|
2899
|
+
for (const c of candidates) {
|
|
2900
|
+
if (fs.existsSync(path.join(c, "src", "workflows"))) { bundleDir = c; break; }
|
|
2901
|
+
}
|
|
2902
|
+
if (!bundleDir) { barLn(yellow(" Can't find Mover OS source. Run from the bundle directory.")); return; }
|
|
2903
|
+
|
|
2904
|
+
// Compare source hash vs installed for each agent
|
|
2905
|
+
const srcRulesHash = (() => {
|
|
2906
|
+
const p = path.join(bundleDir, "src", "system", "Mover_Global_Rules.md");
|
|
2907
|
+
return fs.existsSync(p) ? crypto.createHash("sha256").update(fs.readFileSync(p)).digest("hex").substring(0, 8) : null;
|
|
2908
|
+
})();
|
|
2909
|
+
|
|
2910
|
+
let staleCount = 0;
|
|
2911
|
+
for (const agentId of agents) {
|
|
2912
|
+
const reg = AGENT_REGISTRY[agentId];
|
|
2913
|
+
if (!reg) continue;
|
|
2914
|
+
|
|
2915
|
+
let status = "current";
|
|
2916
|
+
if (reg.rules) {
|
|
2917
|
+
const dest = reg.rules.dest(vault);
|
|
2918
|
+
if (!fs.existsSync(dest)) {
|
|
2919
|
+
status = "missing";
|
|
2920
|
+
} else if (srcRulesHash) {
|
|
2921
|
+
const installed = crypto.createHash("sha256").update(fs.readFileSync(dest)).digest("hex").substring(0, 8);
|
|
2922
|
+
// Rules get transformed, so direct hash won't match — check by file size as heuristic
|
|
2923
|
+
const srcSize = fs.statSync(path.join(bundleDir, "src", "system", "Mover_Global_Rules.md")).size;
|
|
2924
|
+
const dstSize = fs.statSync(dest).size;
|
|
2925
|
+
if (Math.abs(srcSize - dstSize) > srcSize * 0.1) status = "stale";
|
|
2926
|
+
}
|
|
2927
|
+
}
|
|
2928
|
+
|
|
2929
|
+
const icon = status === "current" ? "ok" : status === "stale" ? "warn" : "fail";
|
|
2930
|
+
statusLine(icon, ` ${reg.name}`, status);
|
|
2931
|
+
if (status !== "current") staleCount++;
|
|
2932
|
+
}
|
|
2933
|
+
|
|
2934
|
+
barLn();
|
|
2935
|
+
if (staleCount === 0) {
|
|
2936
|
+
barLn(green(" All agents up to date."));
|
|
2937
|
+
} else if (apply) {
|
|
2938
|
+
barLn(dim(" Updating stale agents..."));
|
|
2939
|
+
const writtenFiles = new Set();
|
|
2940
|
+
const skillOpts = { install: true, categories: null, workflows: null };
|
|
2941
|
+
for (const agentId of agents) {
|
|
2942
|
+
const fn = AGENT_INSTALLERS[agentId];
|
|
2943
|
+
if (fn) fn(bundleDir, vault, skillOpts, writtenFiles, agentId);
|
|
2944
|
+
}
|
|
2945
|
+
barLn(green(` Updated ${staleCount} agent(s).`));
|
|
2946
|
+
} else {
|
|
2947
|
+
barLn(dim(` ${staleCount} agent(s) need updating. Run: moveros sync --apply`));
|
|
2948
|
+
}
|
|
2949
|
+
barLn();
|
|
2950
|
+
}
|
|
2951
|
+
|
|
2952
|
+
// ─── moveros replay ─────────────────────────────────────────────────────────
|
|
2953
|
+
async function cmdReplay(opts) {
|
|
2954
|
+
const vault = resolveVaultPath(opts.vault);
|
|
2955
|
+
if (!vault) { barLn(red("No vault found.")); return; }
|
|
2956
|
+
|
|
2957
|
+
let dateStr = opts.rest.find((a) => a.startsWith("--date="))?.split("=")[1];
|
|
2958
|
+
if (!dateStr) {
|
|
2959
|
+
const now = new Date();
|
|
2960
|
+
dateStr = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, "0")}-${String(now.getDate()).padStart(2, "0")}`;
|
|
2961
|
+
}
|
|
2962
|
+
const month = dateStr.substring(0, 7);
|
|
2963
|
+
const dailyPath = path.join(vault, "02_Areas", "Engine", "Dailies", month, `Daily - ${dateStr}.md`);
|
|
2964
|
+
|
|
2965
|
+
barLn(bold(` Session Replay — ${dateStr}`));
|
|
2966
|
+
barLn();
|
|
2967
|
+
|
|
2968
|
+
if (!fs.existsSync(dailyPath)) {
|
|
2969
|
+
barLn(yellow(` No Daily Note for ${dateStr}.`));
|
|
2970
|
+
return;
|
|
2971
|
+
}
|
|
2972
|
+
|
|
2973
|
+
const daily = fs.readFileSync(dailyPath, "utf8");
|
|
2974
|
+
|
|
2975
|
+
// Extract session log entries
|
|
2976
|
+
const logMatch = daily.match(/##\s*Session Log[\s\S]*?(?=\n## [^#]|\Z)/i);
|
|
2977
|
+
if (!logMatch) {
|
|
2978
|
+
barLn(dim(" No session log found in this Daily Note."));
|
|
2979
|
+
return;
|
|
2980
|
+
}
|
|
2981
|
+
|
|
2982
|
+
// Parse session blocks
|
|
2983
|
+
const sessions = logMatch[0].split(/\n### /).filter(Boolean).slice(1);
|
|
2984
|
+
barLn(dim(` ${sessions.length} session(s) logged`));
|
|
2985
|
+
barLn();
|
|
2986
|
+
|
|
2987
|
+
for (const session of sessions) {
|
|
2988
|
+
const firstLine = session.split("\n")[0];
|
|
2989
|
+
barLn(` ${cyan("###")} ${firstLine}`);
|
|
2990
|
+
const lines = session.split("\n").slice(1, 8);
|
|
2991
|
+
for (const l of lines) {
|
|
2992
|
+
if (l.trim()) barLn(` ${l}`);
|
|
2993
|
+
}
|
|
2994
|
+
barLn();
|
|
2995
|
+
}
|
|
2996
|
+
|
|
2997
|
+
// Task summary
|
|
2998
|
+
const taskSection = daily.match(/##\s*Tasks[\s\S]*?(?=\n##)/i);
|
|
2999
|
+
if (taskSection) {
|
|
3000
|
+
const tasks = taskSection[0].split("\n").filter((l) => /^\s*-\s*\[[ x~]\]/.test(l));
|
|
3001
|
+
const done = tasks.filter((t) => /\[x\]|\[~\]/.test(t)).length;
|
|
3002
|
+
barLn(` ${progressBar(done, tasks.length, 25, "Completion")}`);
|
|
3003
|
+
barLn();
|
|
3004
|
+
}
|
|
3005
|
+
}
|
|
3006
|
+
|
|
3007
|
+
// ─── moveros context ────────────────────────────────────────────────────────
|
|
3008
|
+
async function cmdContext(opts) {
|
|
3009
|
+
const vault = resolveVaultPath(opts.vault);
|
|
3010
|
+
if (!vault) { barLn(red("No vault found.")); return; }
|
|
3011
|
+
|
|
3012
|
+
const target = opts.rest[0];
|
|
3013
|
+
const home = os.homedir();
|
|
3014
|
+
|
|
3015
|
+
if (!target) {
|
|
3016
|
+
// Show all agents
|
|
3017
|
+
barLn(bold(" Agent Context Overview"));
|
|
3018
|
+
barLn();
|
|
3019
|
+
const cfgPath = path.join(home, ".mover", "config.json");
|
|
3020
|
+
const agents = fs.existsSync(cfgPath)
|
|
3021
|
+
? (JSON.parse(fs.readFileSync(cfgPath, "utf8")).agents || [])
|
|
3022
|
+
: [];
|
|
3023
|
+
|
|
3024
|
+
for (const agentId of agents) {
|
|
3025
|
+
const reg = AGENT_REGISTRY[agentId];
|
|
3026
|
+
if (!reg) continue;
|
|
3027
|
+
let totalBytes = 0, fileCount = 0;
|
|
3028
|
+
|
|
3029
|
+
const countDir = (dir) => {
|
|
3030
|
+
if (!fs.existsSync(dir)) return;
|
|
3031
|
+
try {
|
|
3032
|
+
for (const f of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
3033
|
+
if (f.isFile()) { totalBytes += fs.statSync(path.join(dir, f.name)).size; fileCount++; }
|
|
3034
|
+
else if (f.isDirectory()) countDir(path.join(dir, f.name));
|
|
3035
|
+
}
|
|
3036
|
+
} catch {}
|
|
3037
|
+
};
|
|
3038
|
+
|
|
3039
|
+
if (reg.rules) { const rp = reg.rules.dest(vault); if (fs.existsSync(rp)) { totalBytes += fs.statSync(rp).size; fileCount++; } }
|
|
3040
|
+
if (reg.skills) countDir(reg.skills.dest(vault));
|
|
3041
|
+
if (reg.commands) countDir(reg.commands.dest(vault));
|
|
3042
|
+
|
|
3043
|
+
const tokens = Math.round(totalBytes / 4); // rough estimate: 4 bytes per token
|
|
3044
|
+
const kb = (totalBytes / 1024).toFixed(1);
|
|
3045
|
+
barLn(` ${reg.name.padEnd(20)} ${String(fileCount).padStart(3)} files ${String(kb).padStart(6)} KB ~${tokens.toLocaleString()} tokens`);
|
|
3046
|
+
}
|
|
3047
|
+
barLn();
|
|
3048
|
+
return;
|
|
3049
|
+
}
|
|
3050
|
+
|
|
3051
|
+
// Detailed view for specific agent
|
|
3052
|
+
const reg = AGENT_REGISTRY[target] || Object.values(AGENT_REGISTRY).find((r) => r.name.toLowerCase().includes(target.toLowerCase()));
|
|
3053
|
+
if (!reg) { barLn(yellow(` Unknown agent: ${target}`)); return; }
|
|
3054
|
+
|
|
3055
|
+
barLn(bold(` ${reg.name} Context`));
|
|
3056
|
+
barLn();
|
|
3057
|
+
|
|
3058
|
+
if (reg.rules) {
|
|
3059
|
+
const rp = reg.rules.dest(vault);
|
|
3060
|
+
if (fs.existsSync(rp)) {
|
|
3061
|
+
const size = fs.statSync(rp).size;
|
|
3062
|
+
statusLine("ok", "Rules", `${(size / 1024).toFixed(1)} KB ${rp}`);
|
|
3063
|
+
} else {
|
|
3064
|
+
statusLine("fail", "Rules", "not found");
|
|
3065
|
+
}
|
|
3066
|
+
}
|
|
3067
|
+
|
|
3068
|
+
if (reg.skills) {
|
|
3069
|
+
const sp = reg.skills.dest(vault);
|
|
3070
|
+
if (fs.existsSync(sp)) {
|
|
3071
|
+
const skills = fs.readdirSync(sp, { withFileTypes: true }).filter((d) => d.isDirectory());
|
|
3072
|
+
let totalChars = 0;
|
|
3073
|
+
for (const sk of skills) {
|
|
3074
|
+
const sm = path.join(sp, sk.name, "SKILL.md");
|
|
3075
|
+
if (fs.existsSync(sm)) totalChars += fs.statSync(sm).size;
|
|
3076
|
+
}
|
|
3077
|
+
statusLine("ok", "Skills", `${skills.length} packs ${(totalChars / 1024).toFixed(1)} KB descriptions`);
|
|
3078
|
+
} else {
|
|
3079
|
+
statusLine("info", "Skills", "none installed");
|
|
3080
|
+
}
|
|
3081
|
+
}
|
|
3082
|
+
|
|
3083
|
+
if (reg.commands) {
|
|
3084
|
+
const cp = reg.commands.dest(vault);
|
|
3085
|
+
if (fs.existsSync(cp)) {
|
|
3086
|
+
const files = fs.readdirSync(cp).filter((f) => !f.startsWith("."));
|
|
3087
|
+
statusLine("ok", "Commands", `${files.length} files`);
|
|
3088
|
+
} else {
|
|
3089
|
+
statusLine("info", "Commands", "none installed");
|
|
3090
|
+
}
|
|
3091
|
+
}
|
|
3092
|
+
barLn();
|
|
3093
|
+
}
|
|
3094
|
+
|
|
3095
|
+
// ─── moveros settings ───────────────────────────────────────────────────────
|
|
3096
|
+
async function cmdSettings(opts) {
|
|
3097
|
+
const cfgPath = path.join(os.homedir(), ".mover", "config.json");
|
|
3098
|
+
|
|
3099
|
+
if (!fs.existsSync(cfgPath)) {
|
|
3100
|
+
barLn(yellow(" No config found. Run moveros install first."));
|
|
3101
|
+
return;
|
|
3102
|
+
}
|
|
3103
|
+
|
|
3104
|
+
const cfg = JSON.parse(fs.readFileSync(cfgPath, "utf8"));
|
|
3105
|
+
|
|
3106
|
+
// moveros settings set <key> <value>
|
|
3107
|
+
if (opts.rest[0] === "set" && opts.rest[1]) {
|
|
3108
|
+
const key = opts.rest[1];
|
|
3109
|
+
let val = opts.rest.slice(2).join(" ");
|
|
3110
|
+
// Auto-type conversion
|
|
3111
|
+
if (val === "true") val = true;
|
|
3112
|
+
else if (val === "false") val = false;
|
|
3113
|
+
else if (/^\d+$/.test(val)) val = parseInt(val, 10);
|
|
3114
|
+
|
|
3115
|
+
if (!cfg.settings) cfg.settings = {};
|
|
3116
|
+
cfg.settings[key] = val;
|
|
3117
|
+
fs.writeFileSync(cfgPath, JSON.stringify(cfg, null, 2), "utf8");
|
|
3118
|
+
statusLine("ok", "Set", `${key} = ${JSON.stringify(val)}`);
|
|
3119
|
+
return;
|
|
3120
|
+
}
|
|
3121
|
+
|
|
3122
|
+
// Show all settings
|
|
3123
|
+
barLn(bold(" Settings"));
|
|
3124
|
+
barLn();
|
|
3125
|
+
barLn(` ${dim("Vault:")} ${cfg.vaultPath || dim("not set")}`);
|
|
3126
|
+
barLn(` ${dim("Key:")} ${cfg.licenseKey ? cyan(cfg.licenseKey.substring(0, 12) + "...") : dim("not set")}`);
|
|
3127
|
+
barLn(` ${dim("Agents:")} ${(cfg.agents || []).join(", ") || dim("none")}`);
|
|
3128
|
+
barLn(` ${dim("Version:")} ${cfg.version || dim("unknown")}`);
|
|
3129
|
+
barLn();
|
|
3130
|
+
if (cfg.settings) {
|
|
3131
|
+
barLn(dim(" Custom:"));
|
|
3132
|
+
for (const [k, v] of Object.entries(cfg.settings)) {
|
|
3133
|
+
barLn(` ${k}: ${JSON.stringify(v)}`);
|
|
3134
|
+
}
|
|
3135
|
+
barLn();
|
|
3136
|
+
}
|
|
3137
|
+
barLn(dim(" Edit: moveros settings set <key> <value>"));
|
|
3138
|
+
barLn(dim(` File: ${cfgPath}`));
|
|
3139
|
+
barLn();
|
|
3140
|
+
}
|
|
3141
|
+
|
|
3142
|
+
// ─── moveros backup ─────────────────────────────────────────────────────────
|
|
3143
|
+
async function cmdBackup(opts) {
|
|
3144
|
+
const vault = resolveVaultPath(opts.vault);
|
|
3145
|
+
if (!vault) { barLn(red("No vault found.")); return; }
|
|
3146
|
+
const home = os.homedir();
|
|
3147
|
+
|
|
3148
|
+
barLn(bold(" Backup"));
|
|
3149
|
+
barLn();
|
|
3150
|
+
|
|
3151
|
+
const items = [
|
|
3152
|
+
{ id: "engine", name: "Engine files", tier: "Identity, Strategy, Goals, Context, etc." },
|
|
3153
|
+
{ id: "areas", name: "Full Areas folder", tier: "Everything in 02_Areas/" },
|
|
3154
|
+
{ id: "agents", name: "Agent configs", tier: "Rules, skills, commands from all agents" },
|
|
3155
|
+
];
|
|
3156
|
+
|
|
3157
|
+
const choices = await interactiveSelect(items, { multi: true, preSelected: ["engine"] });
|
|
3158
|
+
if (choices.length === 0) { barLn(dim(" Cancelled.")); return; }
|
|
3159
|
+
|
|
3160
|
+
const now = new Date();
|
|
3161
|
+
const ts = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, "0")}-${String(now.getDate()).padStart(2, "0")}_${String(now.getHours()).padStart(2, "0")}${String(now.getMinutes()).padStart(2, "0")}`;
|
|
3162
|
+
const backupDir = path.join(vault, "04_Archives", `Backup_${ts}`);
|
|
3163
|
+
fs.mkdirSync(backupDir, { recursive: true });
|
|
3164
|
+
|
|
3165
|
+
const manifest = { timestamp: now.toISOString(), type: "manual", trigger: "moveros backup", categories: choices, files: {} };
|
|
3166
|
+
|
|
3167
|
+
if (choices.includes("engine")) {
|
|
3168
|
+
const engineDir = path.join(vault, "02_Areas", "Engine");
|
|
3169
|
+
const engBackup = path.join(backupDir, "engine");
|
|
3170
|
+
fs.mkdirSync(engBackup, { recursive: true });
|
|
3171
|
+
let count = 0;
|
|
3172
|
+
manifest.files.engine = [];
|
|
3173
|
+
if (fs.existsSync(engineDir)) {
|
|
3174
|
+
for (const f of fs.readdirSync(engineDir).filter((f) => fs.statSync(path.join(engineDir, f)).isFile())) {
|
|
3175
|
+
fs.copyFileSync(path.join(engineDir, f), path.join(engBackup, f));
|
|
3176
|
+
manifest.files.engine.push(f);
|
|
3177
|
+
count++;
|
|
3178
|
+
}
|
|
3179
|
+
}
|
|
3180
|
+
statusLine("ok", "Engine", `${count} files`);
|
|
3181
|
+
}
|
|
3182
|
+
|
|
3183
|
+
if (choices.includes("areas")) {
|
|
3184
|
+
const areasBackup = path.join(backupDir, "areas");
|
|
3185
|
+
copyDirRecursive(path.join(vault, "02_Areas"), areasBackup);
|
|
3186
|
+
manifest.files.areas = true;
|
|
3187
|
+
statusLine("ok", "Areas", "full copy");
|
|
3188
|
+
}
|
|
3189
|
+
|
|
3190
|
+
if (choices.includes("agents")) {
|
|
3191
|
+
const cfgPath = path.join(home, ".mover", "config.json");
|
|
3192
|
+
const agents = fs.existsSync(cfgPath) ? (JSON.parse(fs.readFileSync(cfgPath, "utf8")).agents || []) : [];
|
|
3193
|
+
const agentsBackup = path.join(backupDir, "agents");
|
|
3194
|
+
manifest.files.agents = {};
|
|
3195
|
+
let agentCount = 0;
|
|
3196
|
+
|
|
3197
|
+
for (const agentId of agents) {
|
|
3198
|
+
const reg = AGENT_REGISTRY[agentId];
|
|
3199
|
+
if (!reg) continue;
|
|
3200
|
+
const agentDir = path.join(agentsBackup, agentId);
|
|
3201
|
+
let backed = false;
|
|
3202
|
+
|
|
3203
|
+
const safeCopy = (src, label) => {
|
|
3204
|
+
try {
|
|
3205
|
+
if (!fs.existsSync(src)) return;
|
|
3206
|
+
const stat = fs.statSync(src);
|
|
3207
|
+
const dst = path.join(agentDir, label);
|
|
3208
|
+
if (stat.isDirectory()) { copyDirRecursive(src, dst); }
|
|
3209
|
+
else { fs.mkdirSync(agentDir, { recursive: true }); fs.copyFileSync(src, dst); }
|
|
3210
|
+
backed = true;
|
|
3211
|
+
} catch {}
|
|
3212
|
+
};
|
|
3213
|
+
|
|
3214
|
+
if (reg.rules) safeCopy(reg.rules.dest(vault), "rules");
|
|
3215
|
+
if (reg.skills) safeCopy(reg.skills.dest(vault), "skills");
|
|
3216
|
+
if (reg.commands) safeCopy(reg.commands.dest(vault), "commands");
|
|
3217
|
+
|
|
3218
|
+
if (backed) { agentCount++; manifest.files.agents[agentId] = true; }
|
|
3219
|
+
}
|
|
3220
|
+
statusLine("ok", "Agents", `${agentCount} agent(s)`);
|
|
3221
|
+
}
|
|
3222
|
+
|
|
3223
|
+
// Write manifest
|
|
3224
|
+
fs.writeFileSync(path.join(backupDir, ".backup-manifest.json"), JSON.stringify(manifest, null, 2), "utf8");
|
|
3225
|
+
|
|
3226
|
+
barLn();
|
|
3227
|
+
barLn(green(` Backup saved to 04_Archives/Backup_${ts}/`));
|
|
3228
|
+
|
|
3229
|
+
// Retention check
|
|
3230
|
+
const archivesDir = path.join(vault, "04_Archives");
|
|
3231
|
+
const backups = fs.readdirSync(archivesDir)
|
|
3232
|
+
.filter((d) => d.startsWith("Backup_") || d.startsWith("Engine_Backup_") || d.startsWith("Areas_Backup_") || d.startsWith("Agent_Backup_"))
|
|
3233
|
+
.sort();
|
|
3234
|
+
if (backups.length > 5) {
|
|
3235
|
+
barLn(dim(` ${backups.length} backups found. Consider cleaning old ones.`));
|
|
3236
|
+
}
|
|
3237
|
+
barLn();
|
|
3238
|
+
}
|
|
3239
|
+
|
|
3240
|
+
// ─── moveros restore ────────────────────────────────────────────────────────
|
|
3241
|
+
async function cmdRestore(opts) {
|
|
3242
|
+
const vault = resolveVaultPath(opts.vault);
|
|
3243
|
+
if (!vault) { barLn(red("No vault found.")); return; }
|
|
3244
|
+
|
|
3245
|
+
const archivesDir = path.join(vault, "04_Archives");
|
|
3246
|
+
if (!fs.existsSync(archivesDir)) { barLn(yellow(" No archives found.")); return; }
|
|
3247
|
+
|
|
3248
|
+
const backups = fs.readdirSync(archivesDir)
|
|
3249
|
+
.filter((d) => {
|
|
3250
|
+
const full = path.join(archivesDir, d);
|
|
3251
|
+
return fs.statSync(full).isDirectory() && (d.startsWith("Backup_") || d.startsWith("Engine_Backup_"));
|
|
3252
|
+
})
|
|
3253
|
+
.sort()
|
|
3254
|
+
.reverse();
|
|
3255
|
+
|
|
3256
|
+
if (backups.length === 0) { barLn(yellow(" No backups found.")); return; }
|
|
3257
|
+
|
|
3258
|
+
barLn(bold(" Restore"));
|
|
3259
|
+
barLn();
|
|
3260
|
+
|
|
3261
|
+
// List available backups
|
|
3262
|
+
const items = backups.slice(0, 10).map((b, i) => {
|
|
3263
|
+
const mPath = path.join(archivesDir, b, ".backup-manifest.json");
|
|
3264
|
+
let detail = "";
|
|
3265
|
+
if (fs.existsSync(mPath)) {
|
|
3266
|
+
try {
|
|
3267
|
+
const m = JSON.parse(fs.readFileSync(mPath, "utf8"));
|
|
3268
|
+
detail = (m.categories || []).join(", ");
|
|
3269
|
+
} catch {}
|
|
3270
|
+
}
|
|
3271
|
+
return { id: b, name: b, tier: detail || dim("legacy backup") };
|
|
3272
|
+
});
|
|
3273
|
+
|
|
3274
|
+
const selected = await interactiveSelect(items, { multi: false });
|
|
3275
|
+
if (!selected) { barLn(dim(" Cancelled.")); return; }
|
|
3276
|
+
|
|
3277
|
+
const backupPath = path.join(archivesDir, selected);
|
|
3278
|
+
|
|
3279
|
+
// Check for engine dir in backup
|
|
3280
|
+
const engPath = path.join(backupPath, "engine");
|
|
3281
|
+
if (fs.existsSync(engPath)) {
|
|
3282
|
+
const engineDir = path.join(vault, "02_Areas", "Engine");
|
|
3283
|
+
let restored = 0;
|
|
3284
|
+
for (const f of fs.readdirSync(engPath).filter((f) => fs.statSync(path.join(engPath, f)).isFile())) {
|
|
3285
|
+
fs.copyFileSync(path.join(engPath, f), path.join(engineDir, f));
|
|
3286
|
+
restored++;
|
|
3287
|
+
}
|
|
3288
|
+
statusLine("ok", "Engine", `${restored} files restored`);
|
|
3289
|
+
} else {
|
|
3290
|
+
// Legacy backup format (files directly in backup dir)
|
|
3291
|
+
const engineDir = path.join(vault, "02_Areas", "Engine");
|
|
3292
|
+
let restored = 0;
|
|
3293
|
+
for (const f of fs.readdirSync(backupPath).filter((f) => f.endsWith(".md") && f !== ".backup-manifest.json")) {
|
|
3294
|
+
fs.copyFileSync(path.join(backupPath, f), path.join(engineDir, f));
|
|
3295
|
+
restored++;
|
|
3296
|
+
}
|
|
3297
|
+
if (restored > 0) statusLine("ok", "Engine", `${restored} files restored`);
|
|
3298
|
+
}
|
|
3299
|
+
|
|
3300
|
+
barLn();
|
|
3301
|
+
barLn(green(` Restored from ${selected}`));
|
|
3302
|
+
barLn();
|
|
3303
|
+
}
|
|
3304
|
+
|
|
3305
|
+
// ─── moveros test (dev only) ────────────────────────────────────────────────
|
|
3306
|
+
async function cmdTest(opts) { barLn(yellow("moveros test — not yet implemented.")); }
|
|
3307
|
+
|
|
3308
|
+
// ─── Interactive Main Menu ────────────────────────────────────────────────────
|
|
3309
|
+
async function cmdMainMenu() {
|
|
3310
|
+
const categories = [
|
|
3311
|
+
{ header: "Setup", cmds: ["install", "update"] },
|
|
3312
|
+
{ header: "Dashboard", cmds: ["pulse", "replay", "diff"] },
|
|
3313
|
+
{ header: "Agents", cmds: ["doctor", "sync", "context", "warm"] },
|
|
3314
|
+
{ header: "Capture", cmds: ["capture", "who"] },
|
|
3315
|
+
{ header: "System", cmds: ["settings", "backup", "restore"] },
|
|
3316
|
+
];
|
|
3317
|
+
|
|
3318
|
+
const menuItems = [];
|
|
3319
|
+
for (const cat of categories) {
|
|
3320
|
+
for (const cmd of cat.cmds) {
|
|
3321
|
+
const meta = CLI_COMMANDS[cmd];
|
|
3322
|
+
if (!meta || meta.hidden) continue;
|
|
3323
|
+
menuItems.push({
|
|
3324
|
+
id: cmd,
|
|
3325
|
+
name: `${cmd.padEnd(12)} ${dim(meta.desc)}`,
|
|
3326
|
+
tier: `${cat.header}`,
|
|
3327
|
+
});
|
|
3328
|
+
}
|
|
3329
|
+
}
|
|
3330
|
+
|
|
3331
|
+
question(`${bold("moveros")} ${dim("— choose a command")}`);
|
|
3332
|
+
barLn();
|
|
3333
|
+
const choice = await interactiveSelect(menuItems);
|
|
3334
|
+
if (!choice) {
|
|
3335
|
+
outro(dim("Cancelled."));
|
|
3336
|
+
process.exit(0);
|
|
3337
|
+
}
|
|
3338
|
+
return choice;
|
|
3339
|
+
}
|
|
3340
|
+
|
|
3341
|
+
// ─── Vault Resolution Helper ─────────────────────────────────────────────────
|
|
3342
|
+
function resolveVaultPath(explicitVault) {
|
|
3343
|
+
if (explicitVault) {
|
|
3344
|
+
let v = explicitVault;
|
|
3345
|
+
if (v.startsWith("~")) v = path.join(os.homedir(), v.slice(1));
|
|
3346
|
+
return path.resolve(v);
|
|
3347
|
+
}
|
|
3348
|
+
// Try config.json
|
|
3349
|
+
const cfgPath = path.join(os.homedir(), ".mover", "config.json");
|
|
3350
|
+
if (fs.existsSync(cfgPath)) {
|
|
3351
|
+
try {
|
|
3352
|
+
const v = JSON.parse(fs.readFileSync(cfgPath, "utf8")).vaultPath;
|
|
3353
|
+
if (v && fs.existsSync(v)) return v;
|
|
3354
|
+
} catch {}
|
|
3355
|
+
}
|
|
3356
|
+
// Try Obsidian detection
|
|
3357
|
+
const obsVaults = detectObsidianVaults();
|
|
3358
|
+
return obsVaults.find((p) => fs.existsSync(path.join(p, ".mover-version"))) || null;
|
|
3359
|
+
}
|
|
3360
|
+
|
|
1973
3361
|
// ─── Main ───────────────────────────────────────────────────────────────────
|
|
1974
3362
|
async function main() {
|
|
1975
3363
|
const opts = parseArgs();
|
|
@@ -1977,9 +3365,27 @@ async function main() {
|
|
|
1977
3365
|
const startTime = Date.now();
|
|
1978
3366
|
|
|
1979
3367
|
// ── Intro ──
|
|
1980
|
-
printHeader();
|
|
3368
|
+
await printHeader();
|
|
3369
|
+
|
|
3370
|
+
// ── Route: no command → interactive menu ──
|
|
3371
|
+
if (!opts.command) {
|
|
3372
|
+
opts.command = await cmdMainMenu();
|
|
3373
|
+
}
|
|
1981
3374
|
|
|
1982
|
-
// ──
|
|
3375
|
+
// ── Route: CLI commands that don't need pre-flight ──
|
|
3376
|
+
const lightCommands = ["pulse", "warm", "capture", "who", "diff", "sync", "replay", "context", "settings", "backup", "restore", "doctor", "test"];
|
|
3377
|
+
if (lightCommands.includes(opts.command)) {
|
|
3378
|
+
const handler = CLI_HANDLERS[opts.command];
|
|
3379
|
+
if (handler) {
|
|
3380
|
+
await handler(opts);
|
|
3381
|
+
} else {
|
|
3382
|
+
barLn(yellow(`Command '${opts.command}' is not yet implemented.`));
|
|
3383
|
+
barLn(dim("Coming soon in a future update."));
|
|
3384
|
+
}
|
|
3385
|
+
return;
|
|
3386
|
+
}
|
|
3387
|
+
|
|
3388
|
+
// ── Pre-flight (install + update only) ──
|
|
1983
3389
|
barLn(gray("Pre-flight"));
|
|
1984
3390
|
barLn();
|
|
1985
3391
|
const checks = preflight();
|
|
@@ -1994,8 +3400,8 @@ async function main() {
|
|
|
1994
3400
|
process.exit(1);
|
|
1995
3401
|
}
|
|
1996
3402
|
|
|
1997
|
-
// ── Headless quick update
|
|
1998
|
-
if (opts.update) {
|
|
3403
|
+
// ── Headless quick update ──
|
|
3404
|
+
if (opts.command === "update") {
|
|
1999
3405
|
// Validate stored key
|
|
2000
3406
|
let updateKey = opts.key;
|
|
2001
3407
|
if (!updateKey) {
|
|
@@ -2070,7 +3476,6 @@ async function main() {
|
|
|
2070
3476
|
// Apply all changes
|
|
2071
3477
|
barLn(bold("Updating..."));
|
|
2072
3478
|
barLn();
|
|
2073
|
-
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
2074
3479
|
|
|
2075
3480
|
// Vault structure
|
|
2076
3481
|
createVaultStructure(vaultPath);
|
|
@@ -2085,10 +3490,7 @@ async function main() {
|
|
|
2085
3490
|
const fn = AGENT_INSTALLERS[agent.id];
|
|
2086
3491
|
if (!fn) continue;
|
|
2087
3492
|
const sp = spinner(agent.name);
|
|
2088
|
-
const
|
|
2089
|
-
const steps = usesWrittenFiles
|
|
2090
|
-
? fn(bundleDir, vaultPath, skillOpts, writtenFiles)
|
|
2091
|
-
: fn(bundleDir, vaultPath, skillOpts);
|
|
3493
|
+
const steps = fn(bundleDir, vaultPath, skillOpts, writtenFiles, agent.id);
|
|
2092
3494
|
await sleep(200);
|
|
2093
3495
|
if (steps.length > 0) {
|
|
2094
3496
|
sp.stop(`${agent.name} ${dim(steps.join(", "))}`);
|
|
@@ -2353,10 +3755,9 @@ async function main() {
|
|
|
2353
3755
|
{ src: path.join(home, ".claude", "hooks"), label: "hooks" },
|
|
2354
3756
|
],
|
|
2355
3757
|
cursor: [
|
|
2356
|
-
{ src: path.join(
|
|
3758
|
+
{ src: path.join(vaultPath, ".cursor", "rules"), label: "rules" },
|
|
2357
3759
|
{ src: path.join(home, ".cursor", "commands"), label: "commands" },
|
|
2358
3760
|
{ src: path.join(home, ".cursor", "skills"), label: "skills" },
|
|
2359
|
-
{ src: path.join(vaultPath, ".cursorrules"), label: ".cursorrules" },
|
|
2360
3761
|
],
|
|
2361
3762
|
cline: [
|
|
2362
3763
|
{ src: path.join(vaultPath, ".clinerules"), label: ".clinerules" },
|
|
@@ -2364,10 +3765,13 @@ async function main() {
|
|
|
2364
3765
|
],
|
|
2365
3766
|
windsurf: [
|
|
2366
3767
|
{ src: path.join(vaultPath, ".windsurfrules"), label: ".windsurfrules" },
|
|
3768
|
+
{ src: path.join(vaultPath, ".windsurf", "rules"), label: "rules" },
|
|
3769
|
+
{ src: path.join(vaultPath, ".windsurf", "workflows"), label: "workflows" },
|
|
2367
3770
|
{ src: path.join(home, ".windsurf", "skills"), label: "skills" },
|
|
2368
3771
|
],
|
|
2369
3772
|
"gemini-cli": [
|
|
2370
3773
|
{ src: path.join(home, ".gemini", "GEMINI.md"), label: "GEMINI.md" },
|
|
3774
|
+
{ src: path.join(home, ".gemini", "commands"), label: "commands" },
|
|
2371
3775
|
{ src: path.join(home, ".gemini", "skills"), label: "skills" },
|
|
2372
3776
|
],
|
|
2373
3777
|
antigravity: [
|
|
@@ -2377,19 +3781,42 @@ async function main() {
|
|
|
2377
3781
|
],
|
|
2378
3782
|
copilot: [
|
|
2379
3783
|
{ src: path.join(vaultPath, ".github", "copilot-instructions.md"), label: "copilot-instructions.md" },
|
|
3784
|
+
{ src: path.join(vaultPath, ".github", "prompts"), label: "prompts" },
|
|
2380
3785
|
{ src: path.join(vaultPath, ".github", "skills"), label: "skills" },
|
|
2381
3786
|
],
|
|
2382
3787
|
codex: [
|
|
2383
|
-
{ src: path.join(home, ".codex", "
|
|
3788
|
+
{ src: path.join(home, ".codex", "AGENTS.md"), label: "AGENTS.md" },
|
|
2384
3789
|
{ src: path.join(home, ".codex", "skills"), label: "skills" },
|
|
2385
3790
|
],
|
|
2386
|
-
openclaw: [
|
|
2387
|
-
{ src: path.join(home, ".openclaw", "workspace"), label: "workspace" },
|
|
2388
|
-
],
|
|
2389
3791
|
"roo-code": [
|
|
2390
3792
|
{ src: path.join(vaultPath, ".roo", "rules"), label: "rules" },
|
|
3793
|
+
{ src: path.join(vaultPath, ".roo", "commands"), label: "commands" },
|
|
2391
3794
|
{ src: path.join(vaultPath, ".roo", "skills"), label: "skills" },
|
|
2392
3795
|
],
|
|
3796
|
+
"amazon-q": [
|
|
3797
|
+
{ src: path.join(vaultPath, ".amazonq", "rules"), label: "rules" },
|
|
3798
|
+
{ src: path.join(home, ".aws", "amazonq", "cli-agents"), label: "cli-agents" },
|
|
3799
|
+
],
|
|
3800
|
+
"kilo-code": [
|
|
3801
|
+
{ src: path.join(vaultPath, ".kilocode", "rules"), label: "rules" },
|
|
3802
|
+
{ src: path.join(vaultPath, ".kilocode", "skills"), label: "skills" },
|
|
3803
|
+
{ src: path.join(vaultPath, ".kilocode", "commands"), label: "commands" },
|
|
3804
|
+
],
|
|
3805
|
+
amp: [
|
|
3806
|
+
{ src: path.join(vaultPath, "AGENTS.md"), label: "AGENTS.md" },
|
|
3807
|
+
{ src: path.join(vaultPath, ".agents", "skills"), label: "skills" },
|
|
3808
|
+
],
|
|
3809
|
+
"continue": [
|
|
3810
|
+
{ src: path.join(vaultPath, ".continue", "rules"), label: "rules" },
|
|
3811
|
+
{ src: path.join(vaultPath, ".continue", "prompts"), label: "prompts" },
|
|
3812
|
+
],
|
|
3813
|
+
opencode: [
|
|
3814
|
+
{ src: path.join(vaultPath, "AGENTS.md"), label: "AGENTS.md" },
|
|
3815
|
+
{ src: path.join(vaultPath, ".opencode", "agents"), label: "agents" },
|
|
3816
|
+
],
|
|
3817
|
+
aider: [
|
|
3818
|
+
{ src: path.join(vaultPath, "CONVENTIONS.md"), label: "CONVENTIONS.md" },
|
|
3819
|
+
],
|
|
2393
3820
|
};
|
|
2394
3821
|
|
|
2395
3822
|
let agentsBacked = 0;
|
|
@@ -2610,7 +4037,6 @@ async function main() {
|
|
|
2610
4037
|
barLn();
|
|
2611
4038
|
|
|
2612
4039
|
let totalSteps = 0;
|
|
2613
|
-
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
2614
4040
|
|
|
2615
4041
|
// 0. Legacy cleanup (remove files from older installer versions)
|
|
2616
4042
|
const legacyPaths = [
|
|
@@ -2665,25 +4091,30 @@ async function main() {
|
|
|
2665
4091
|
const writtenFiles = new Set(); // Track shared files to avoid double-writes (e.g. GEMINI.md)
|
|
2666
4092
|
const skillOpts = { install: installSkills, categories: selectedCategories, statusLine: installStatusLine, workflows: selectedWorkflows, skipHooks, skipRules, skipTemplates };
|
|
2667
4093
|
for (const agent of selectedAgents) {
|
|
2668
|
-
|
|
2669
|
-
|
|
2670
|
-
|
|
2671
|
-
const
|
|
2672
|
-
|
|
2673
|
-
|
|
2674
|
-
|
|
2675
|
-
|
|
2676
|
-
|
|
2677
|
-
|
|
2678
|
-
|
|
2679
|
-
|
|
2680
|
-
|
|
2681
|
-
|
|
4094
|
+
// Expand selections to targets (e.g. "gemini-cli" → ["gemini-cli", "antigravity"])
|
|
4095
|
+
const sel = AGENT_SELECTIONS.find((s) => s.id === agent.id);
|
|
4096
|
+
const targets = sel ? sel.targets : [agent.id];
|
|
4097
|
+
for (const targetId of targets) {
|
|
4098
|
+
const fn = AGENT_INSTALLERS[targetId];
|
|
4099
|
+
if (!fn) continue;
|
|
4100
|
+
const targetReg = AGENT_REGISTRY[targetId];
|
|
4101
|
+
const displayName = targetReg ? targetReg.name : agent.name;
|
|
4102
|
+
sp = spinner(displayName);
|
|
4103
|
+
const steps = fn(bundleDir, vaultPath, skillOpts, writtenFiles, targetId);
|
|
4104
|
+
await sleep(250);
|
|
4105
|
+
if (steps.length > 0) {
|
|
4106
|
+
sp.stop(`${displayName} ${dim(steps.join(", "))}`);
|
|
4107
|
+
totalSteps++;
|
|
4108
|
+
} else {
|
|
4109
|
+
sp.stop(`${displayName} ${dim("configured")}`);
|
|
4110
|
+
totalSteps++;
|
|
4111
|
+
}
|
|
2682
4112
|
}
|
|
2683
4113
|
}
|
|
2684
4114
|
|
|
2685
|
-
// 5. AGENTS.md —
|
|
2686
|
-
const
|
|
4115
|
+
// 5. AGENTS.md at vault root — for agents that share it (Codex writes to ~/.codex/)
|
|
4116
|
+
const agentsMdAtRoot = ["opencode", "amp"];
|
|
4117
|
+
const needsAgentsMd = selectedAgents.some((a) => agentsMdAtRoot.includes(a.id));
|
|
2687
4118
|
if (needsAgentsMd) {
|
|
2688
4119
|
fs.writeFileSync(path.join(vaultPath, "AGENTS.md"), generateAgentsMd(), "utf8");
|
|
2689
4120
|
sp = spinner("AGENTS.md");
|
|
@@ -2769,8 +4200,8 @@ async function main() {
|
|
|
2769
4200
|
ln();
|
|
2770
4201
|
ln(` ${bold("What was installed")}`);
|
|
2771
4202
|
ln();
|
|
2772
|
-
ln(` ${green("▸")} ${bold("
|
|
2773
|
-
ln(` ${green("▸")} ${bold("
|
|
4203
|
+
ln(` ${green("▸")} ${bold("23")} workflows ${dim("slash commands for daily rhythm, projects, strategy")}`);
|
|
4204
|
+
ln(` ${green("▸")} ${bold("63")} skills ${dim("curated packs for dev, marketing, CRO, design")}`);
|
|
2774
4205
|
if (selectedIds.includes("claude-code")) {
|
|
2775
4206
|
ln(` ${green("▸")} ${bold("6")} hooks ${dim("lifecycle guards (engine protection, git safety)")}`);
|
|
2776
4207
|
}
|
|
@@ -2810,6 +4241,17 @@ async function main() {
|
|
|
2810
4241
|
ln();
|
|
2811
4242
|
ln(` ${dim("/morning → [work] → /log → /analyse-day → /plan-tomorrow")}`);
|
|
2812
4243
|
ln();
|
|
4244
|
+
ln(gray(" ─────────────────────────────────────────────"));
|
|
4245
|
+
ln();
|
|
4246
|
+
ln(` ${bold("CLI commands")} ${dim("(run from any terminal)")}`);
|
|
4247
|
+
ln();
|
|
4248
|
+
ln(` ${green("▸")} ${bold("moveros pulse")} ${dim("Dashboard — energy, tasks, streaks")}`);
|
|
4249
|
+
ln(` ${green("▸")} ${bold("moveros doctor")} ${dim("Health check across all agents")}`);
|
|
4250
|
+
ln(` ${green("▸")} ${bold("moveros capture")} ${dim("Quick inbox — tasks, links, ideas")}`);
|
|
4251
|
+
ln(` ${green("▸")} ${bold("moveros warm")} ${dim("Pre-warm your next AI session")}`);
|
|
4252
|
+
ln(` ${green("▸")} ${bold("moveros sync")} ${dim("Update all agents to latest")}`);
|
|
4253
|
+
ln(` ${green("▸")} ${bold("moveros")} ${dim("Full menu with all commands")}`);
|
|
4254
|
+
ln();
|
|
2813
4255
|
}
|
|
2814
4256
|
|
|
2815
4257
|
main().catch((err) => {
|