mover-os 4.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of mover-os might be problematic. Click here for more details.
- package/README.md +201 -0
- package/install.js +1424 -0
- package/package.json +35 -0
- package/src/hooks/context-staleness.sh +46 -0
- package/src/hooks/dirty-tree-guard.sh +33 -0
- package/src/hooks/engine-protection.sh +43 -0
- package/src/hooks/git-safety.sh +47 -0
- package/src/hooks/pre-compact-backup.sh +177 -0
- package/src/hooks/session-log-reminder.sh +64 -0
- package/src/skills/THIRD-PARTY-LICENSES.md +53 -0
- package/src/skills/agent-code-reviewer/SKILL.md +41 -0
- package/src/skills/agent-content-researcher/SKILL.md +59 -0
- package/src/skills/agent-research-analyst/SKILL.md +53 -0
- package/src/skills/agent-security-auditor/SKILL.md +42 -0
- package/src/skills/agent-strategy-analyst/SKILL.md +54 -0
- package/src/skills/defuddle/SKILL.md +41 -0
- package/src/skills/find-bugs/SKILL.md +75 -0
- package/src/skills/find-skills/SKILL.md +133 -0
- package/src/skills/frontend-design/LICENSE.txt +177 -0
- package/src/skills/frontend-design/SKILL.md +42 -0
- package/src/skills/human-writer/SKILL.md +185 -0
- package/src/skills/json-canvas/SKILL.md +656 -0
- package/src/skills/marketingskills/.claude-plugin/marketplace.json +45 -0
- package/src/skills/marketingskills/README.md +204 -0
- package/src/skills/marketingskills/skills/ab-test-setup/SKILL.md +508 -0
- package/src/skills/marketingskills/skills/analytics-tracking/SKILL.md +539 -0
- package/src/skills/marketingskills/skills/competitor-alternatives/SKILL.md +750 -0
- package/src/skills/marketingskills/skills/copy-editing/SKILL.md +439 -0
- package/src/skills/marketingskills/skills/copywriting/SKILL.md +455 -0
- package/src/skills/marketingskills/skills/email-sequence/SKILL.md +925 -0
- package/src/skills/marketingskills/skills/form-cro/SKILL.md +425 -0
- package/src/skills/marketingskills/skills/free-tool-strategy/SKILL.md +576 -0
- package/src/skills/marketingskills/skills/launch-strategy/SKILL.md +344 -0
- package/src/skills/marketingskills/skills/marketing-ideas/SKILL.md +565 -0
- package/src/skills/marketingskills/skills/marketing-psychology/SKILL.md +451 -0
- package/src/skills/marketingskills/skills/onboarding-cro/SKILL.md +433 -0
- package/src/skills/marketingskills/skills/page-cro/SKILL.md +334 -0
- package/src/skills/marketingskills/skills/paid-ads/SKILL.md +551 -0
- package/src/skills/marketingskills/skills/paywall-upgrade-cro/SKILL.md +570 -0
- package/src/skills/marketingskills/skills/popup-cro/SKILL.md +449 -0
- package/src/skills/marketingskills/skills/pricing-strategy/SKILL.md +710 -0
- package/src/skills/marketingskills/skills/programmatic-seo/SKILL.md +626 -0
- package/src/skills/marketingskills/skills/referral-program/SKILL.md +602 -0
- package/src/skills/marketingskills/skills/schema-markup/SKILL.md +596 -0
- package/src/skills/marketingskills/skills/seo-audit/SKILL.md +384 -0
- package/src/skills/marketingskills/skills/signup-flow-cro/SKILL.md +355 -0
- package/src/skills/marketingskills/skills/social-content/SKILL.md +807 -0
- package/src/skills/obsidian-bases/SKILL.md +651 -0
- package/src/skills/obsidian-cli/SKILL.md +103 -0
- package/src/skills/obsidian-markdown/SKILL.md +620 -0
- package/src/skills/react-best-practices/SKILL.md +136 -0
- package/src/skills/refactoring/SKILL.md +119 -0
- package/src/skills/seo-content-writer/SKILL.md +661 -0
- package/src/skills/seo-content-writer/references/content-structure-templates.md +875 -0
- package/src/skills/seo-content-writer/references/title-formulas.md +339 -0
- package/src/skills/skill-creator/LICENSE.txt +202 -0
- package/src/skills/skill-creator/SKILL.md +357 -0
- package/src/skills/skill-creator/references/output-patterns.md +82 -0
- package/src/skills/skill-creator/references/workflows.md +28 -0
- package/src/skills/skill-creator/scripts/init_skill.py +303 -0
- package/src/skills/skill-creator/scripts/package_skill.py +110 -0
- package/src/skills/skill-creator/scripts/quick_validate.py +103 -0
- package/src/skills/systematic-debugging/CREATION-LOG.md +119 -0
- package/src/skills/systematic-debugging/SKILL.md +296 -0
- package/src/skills/systematic-debugging/condition-based-waiting-example.ts +158 -0
- package/src/skills/systematic-debugging/condition-based-waiting.md +115 -0
- package/src/skills/systematic-debugging/defense-in-depth.md +122 -0
- package/src/skills/systematic-debugging/find-polluter.sh +63 -0
- package/src/skills/systematic-debugging/root-cause-tracing.md +169 -0
- package/src/skills/systematic-debugging/test-academic.md +14 -0
- package/src/skills/systematic-debugging/test-pressure-1.md +58 -0
- package/src/skills/systematic-debugging/test-pressure-2.md +68 -0
- package/src/skills/systematic-debugging/test-pressure-3.md +69 -0
- package/src/skills/ui-ux-pro-max/SKILL.md +386 -0
- package/src/skills/webhook-handler-patterns/SKILL.md +81 -0
- package/src/skills/webhook-handler-patterns/references/error-handling.md +299 -0
- package/src/skills/webhook-handler-patterns/references/frameworks/express.md +295 -0
- package/src/skills/webhook-handler-patterns/references/frameworks/fastapi.md +415 -0
- package/src/skills/webhook-handler-patterns/references/frameworks/nextjs.md +331 -0
- package/src/skills/webhook-handler-patterns/references/handler-sequence.md +51 -0
- package/src/skills/webhook-handler-patterns/references/idempotency.md +227 -0
- package/src/skills/webhook-handler-patterns/references/retry-logic.md +261 -0
- package/src/structure/00_Inbox/.gitkeep +0 -0
- package/src/structure/00_Inbox/Quick_Capture.md +5 -0
- package/src/structure/01_Projects/.gitkeep +0 -0
- package/src/structure/01_Projects/_Template Project/plan.md +55 -0
- package/src/structure/01_Projects/_Template Project/project_brief.md +45 -0
- package/src/structure/01_Projects/_Template Project/project_state.md +19 -0
- package/src/structure/02_Areas/Engine/Active_Context.md +126 -0
- package/src/structure/02_Areas/Engine/Auto_Learnings.md +36 -0
- package/src/structure/02_Areas/Engine/Dailies/.gitkeep +0 -0
- package/src/structure/02_Areas/Engine/Daily_Template.md +130 -0
- package/src/structure/02_Areas/Engine/Goals.md +65 -0
- package/src/structure/02_Areas/Engine/Identity_Prime_template.md +86 -0
- package/src/structure/02_Areas/Engine/Metrics_Log.md +45 -0
- package/src/structure/02_Areas/Engine/Monthly Reviews/.gitkeep +0 -0
- package/src/structure/02_Areas/Engine/Mover_Dossier.md +120 -0
- package/src/structure/02_Areas/Engine/Quarterly Reviews/.gitkeep +0 -0
- package/src/structure/02_Areas/Engine/Someday_Maybe.md +51 -0
- package/src/structure/02_Areas/Engine/Strategy_template.md +65 -0
- package/src/structure/02_Areas/Engine/Weekly Reviews/.gitkeep +0 -0
- package/src/structure/03_Library/Cheatsheets/.gitkeep +0 -0
- package/src/structure/03_Library/Entities/Decisions/_TEMPLATE.md +38 -0
- package/src/structure/03_Library/Entities/Entities MOC.md +49 -0
- package/src/structure/03_Library/Entities/Organizations/_TEMPLATE.md +28 -0
- package/src/structure/03_Library/Entities/People/_TEMPLATE.md +31 -0
- package/src/structure/03_Library/Entities/Places/_TEMPLATE.md +26 -0
- package/src/structure/03_Library/Inputs/.gitkeep +0 -0
- package/src/structure/03_Library/Inputs/Archive/.gitkeep +0 -0
- package/src/structure/03_Library/Inputs/Articles/.gitkeep +0 -0
- package/src/structure/03_Library/Inputs/Articles/_TEMPLATE.md +29 -0
- package/src/structure/03_Library/Inputs/Books/.gitkeep +0 -0
- package/src/structure/03_Library/Inputs/Books/_TEMPLATE.md +41 -0
- package/src/structure/03_Library/Inputs/Inputs MOC.md +40 -0
- package/src/structure/03_Library/Inputs/Videos/.gitkeep +0 -0
- package/src/structure/03_Library/Inputs/Videos/_TEMPLATE.md +29 -0
- package/src/structure/03_Library/MOCs/.gitkeep +0 -0
- package/src/structure/03_Library/Misc/.gitkeep +0 -0
- package/src/structure/03_Library/Principles/.gitkeep +0 -0
- package/src/structure/03_Library/Principles/Naval_Leverage.md +65 -0
- package/src/structure/03_Library/SOPs/Mover_OS_Architecture.md +74 -0
- package/src/structure/03_Library/SOPs/Tech_Doctrine.md +123 -0
- package/src/structure/03_Library/SOPs/Tech_Stack.md +55 -0
- package/src/structure/03_Library/SOPs/Zone_Operating.md +58 -0
- package/src/structure/04_Archives/.gitkeep +0 -0
- package/src/system/AI_INSTALL_MANIFEST.md +219 -0
- package/src/system/Mover_Global_Rules.md +646 -0
- package/src/system/V4_CONTEXT.md +223 -0
- package/src/workflows/analyse-day.md +376 -0
- package/src/workflows/context.md +24 -0
- package/src/workflows/debrief.md +199 -0
- package/src/workflows/debug-resistance.md +181 -0
- package/src/workflows/harvest.md +240 -0
- package/src/workflows/history.md +247 -0
- package/src/workflows/ignite.md +696 -0
- package/src/workflows/init-plan.md +16 -0
- package/src/workflows/log.md +314 -0
- package/src/workflows/morning.md +209 -0
- package/src/workflows/overview.md +203 -0
- package/src/workflows/pivot-strategy.md +218 -0
- package/src/workflows/plan-tomorrow.md +286 -0
- package/src/workflows/primer.md +209 -0
- package/src/workflows/project-notes.md +17 -0
- package/src/workflows/reboot.md +201 -0
- package/src/workflows/refactor-plan.md +135 -0
- package/src/workflows/review-week.md +537 -0
- package/src/workflows/setup.md +387 -0
- package/src/workflows/update.md +411 -0
- package/src/workflows/walkthrough.md +259 -0
package/install.js
ADDED
|
@@ -0,0 +1,1424 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// ══════════════════════════════════════════════════════════════════════════════
|
|
3
|
+
// Mover OS — Interactive TUI Installer
|
|
4
|
+
// Zero dependencies. Node.js 18+.
|
|
5
|
+
//
|
|
6
|
+
// Usage:
|
|
7
|
+
// npx install-mover-os
|
|
8
|
+
// npx install-mover-os --key YOUR_KEY --vault ~/vault
|
|
9
|
+
// ══════════════════════════════════════════════════════════════════════════════
|
|
10
|
+
|
|
11
|
+
const readline = require("readline");
|
|
12
|
+
const fs = require("fs");
|
|
13
|
+
const path = require("path");
|
|
14
|
+
const os = require("os");
|
|
15
|
+
const { execSync } = require("child_process");
|
|
16
|
+
|
|
17
|
+
const VERSION = "4";
|
|
18
|
+
|
|
19
|
+
// ─── ANSI ────────────────────────────────────────────────────────────────────
|
|
20
|
+
const IS_TTY = process.stdout.isTTY && process.stdin.isTTY;
|
|
21
|
+
const S = IS_TTY
|
|
22
|
+
? {
|
|
23
|
+
reset: "\x1b[0m",
|
|
24
|
+
bold: "\x1b[1m",
|
|
25
|
+
dim: "\x1b[2m",
|
|
26
|
+
italic: "\x1b[3m",
|
|
27
|
+
underline: "\x1b[4m",
|
|
28
|
+
green: "\x1b[32m",
|
|
29
|
+
yellow: "\x1b[33m",
|
|
30
|
+
cyan: "\x1b[36m",
|
|
31
|
+
red: "\x1b[31m",
|
|
32
|
+
magenta: "\x1b[35m",
|
|
33
|
+
blue: "\x1b[34m",
|
|
34
|
+
white: "\x1b[37m",
|
|
35
|
+
gray: "\x1b[90m",
|
|
36
|
+
inverse: "\x1b[7m",
|
|
37
|
+
hide: "\x1b[?25l",
|
|
38
|
+
show: "\x1b[?25h",
|
|
39
|
+
// 256-color for gradient
|
|
40
|
+
fg: (n) => `\x1b[38;5;${n}m`,
|
|
41
|
+
}
|
|
42
|
+
: {
|
|
43
|
+
reset: "", bold: "", dim: "", italic: "", underline: "",
|
|
44
|
+
green: "", yellow: "", cyan: "", red: "", magenta: "", blue: "",
|
|
45
|
+
white: "", gray: "", inverse: "", hide: "", show: "",
|
|
46
|
+
fg: () => "",
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
const w = (s) => process.stdout.write(s);
|
|
50
|
+
const ln = (s = "") => w(s + "\n");
|
|
51
|
+
const bold = (s) => `${S.bold}${s}${S.reset}`;
|
|
52
|
+
const dim = (s) => `${S.dim}${s}${S.reset}`;
|
|
53
|
+
const green = (s) => `${S.green}${s}${S.reset}`;
|
|
54
|
+
const yellow = (s) => `${S.yellow}${s}${S.reset}`;
|
|
55
|
+
const cyan = (s) => `${S.cyan}${s}${S.reset}`;
|
|
56
|
+
const red = (s) => `${S.red}${s}${S.reset}`;
|
|
57
|
+
const gray = (s) => `${S.gray}${s}${S.reset}`;
|
|
58
|
+
const strip = (s) => s.replace(/\x1b\[[0-9;]*m/g, "");
|
|
59
|
+
|
|
60
|
+
// Gradient text using 256-color palette (white → gray, matching website monochrome theme)
|
|
61
|
+
const GRADIENT = [255, 255, 254, 253, 252, 251, 250, 249, 248, 247];
|
|
62
|
+
function gradient(text) {
|
|
63
|
+
if (!IS_TTY) return text;
|
|
64
|
+
const chars = [...text];
|
|
65
|
+
return chars.map((ch, i) => {
|
|
66
|
+
if (ch === " ") return ch;
|
|
67
|
+
const idx = Math.floor((i / Math.max(chars.length - 1, 1)) * (GRADIENT.length - 1));
|
|
68
|
+
return `${S.fg(GRADIENT[idx])}${ch}`;
|
|
69
|
+
}).join("") + S.reset;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// ─── Terminal cleanup ────────────────────────────────────────────────────────
|
|
73
|
+
function cleanup() {
|
|
74
|
+
w(S.show);
|
|
75
|
+
try { if (process.stdin.isTTY && process.stdin.isRaw) process.stdin.setRawMode(false); } catch {}
|
|
76
|
+
}
|
|
77
|
+
process.on("exit", cleanup);
|
|
78
|
+
process.on("SIGINT", () => { cleanup(); ln(); process.exit(0); });
|
|
79
|
+
process.on("SIGTERM", () => { cleanup(); process.exit(0); });
|
|
80
|
+
|
|
81
|
+
// ─── Branded header ─────────────────────────────────────────────────────────
|
|
82
|
+
const LOGO = [
|
|
83
|
+
" ╔╦╗╔═╗╦ ╦╔═╗╦═╗ ╔═╗╔═╗",
|
|
84
|
+
" ║║║║ ║╚╗╔╝║╣ ╠╦╝ ║ ║╚═╗",
|
|
85
|
+
" ╩ ╩╚═╝ ╚╝ ╚═╝╩╚═ ╚═╝╚═╝",
|
|
86
|
+
];
|
|
87
|
+
|
|
88
|
+
function printHeader() {
|
|
89
|
+
ln();
|
|
90
|
+
for (const line of LOGO) {
|
|
91
|
+
ln(gradient(line));
|
|
92
|
+
}
|
|
93
|
+
ln();
|
|
94
|
+
ln(` ${dim(`v${VERSION}`)} ${gray("the self-improving OS for AI agents")}`);
|
|
95
|
+
ln();
|
|
96
|
+
ln(gray(" ─────────────────────────────────────"));
|
|
97
|
+
ln();
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// ─── Clack-style frame ──────────────────────────────────────────────────────
|
|
101
|
+
const BAR_COLOR = S.cyan;
|
|
102
|
+
const bar = () => w(`${BAR_COLOR}│${S.reset}`);
|
|
103
|
+
const barLn = (text = "") => ln(`${BAR_COLOR}│${S.reset} ${text}`);
|
|
104
|
+
const result = (text) => ln(`${S.green}◇${S.reset} ${dim(text)}`);
|
|
105
|
+
const question = (text) => ln(`${BAR_COLOR}◆${S.reset} ${text}`);
|
|
106
|
+
const outro = (text) => { ln(`${BAR_COLOR}└${S.reset} ${text}`); ln(); };
|
|
107
|
+
|
|
108
|
+
// ─── Animated spinner ───────────────────────────────────────────────────────
|
|
109
|
+
const SPIN_FRAMES = ["◐", "◓", "◑", "◒"];
|
|
110
|
+
function spinner(label) {
|
|
111
|
+
if (!IS_TTY) {
|
|
112
|
+
return { stop: (finalLabel) => { ln(`${BAR_COLOR}│${S.reset} ${S.green}\u2713${S.reset} ${finalLabel || label}`); } };
|
|
113
|
+
}
|
|
114
|
+
let i = 0;
|
|
115
|
+
w(S.hide);
|
|
116
|
+
w(`${BAR_COLOR}│${S.reset} ${S.cyan}${SPIN_FRAMES[0]}${S.reset} ${label}`);
|
|
117
|
+
const timer = setInterval(() => {
|
|
118
|
+
i = (i + 1) % SPIN_FRAMES.length;
|
|
119
|
+
w(`\x1b[2K\r${BAR_COLOR}│${S.reset} ${S.cyan}${SPIN_FRAMES[i]}${S.reset} ${label}`);
|
|
120
|
+
}, 80);
|
|
121
|
+
return {
|
|
122
|
+
stop: (finalLabel) => {
|
|
123
|
+
clearInterval(timer);
|
|
124
|
+
w(`\x1b[2K\r${BAR_COLOR}│${S.reset} ${S.green}\u2713${S.reset} ${finalLabel || label}\n`);
|
|
125
|
+
w(S.show);
|
|
126
|
+
},
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// ─── UI: Text input (raw mode) ──────────────────────────────────────────────
|
|
131
|
+
function textInput({ label = "", initial = "", mask = null, placeholder = "" }) {
|
|
132
|
+
return new Promise((resolve) => {
|
|
133
|
+
question(label);
|
|
134
|
+
|
|
135
|
+
if (!IS_TTY) {
|
|
136
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
137
|
+
rl.question(`${BAR_COLOR}│${S.reset} `, (ans) => { rl.close(); resolve(ans || initial); });
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const { stdin } = process;
|
|
142
|
+
stdin.setRawMode(true);
|
|
143
|
+
stdin.resume();
|
|
144
|
+
stdin.setEncoding("utf8");
|
|
145
|
+
|
|
146
|
+
let value = initial;
|
|
147
|
+
let pos = value.length;
|
|
148
|
+
|
|
149
|
+
const render = () => {
|
|
150
|
+
w("\x1b[2K\r");
|
|
151
|
+
const prefix = `${BAR_COLOR}│${S.reset} `;
|
|
152
|
+
if (value.length === 0 && placeholder) {
|
|
153
|
+
w(prefix + dim(placeholder));
|
|
154
|
+
} else if (mask) {
|
|
155
|
+
w(prefix + mask.repeat(value.length));
|
|
156
|
+
} else {
|
|
157
|
+
w(prefix + value);
|
|
158
|
+
}
|
|
159
|
+
// Position cursor
|
|
160
|
+
if (!mask) {
|
|
161
|
+
const back = value.length - pos;
|
|
162
|
+
if (back > 0) w(`\x1b[${back}D`);
|
|
163
|
+
}
|
|
164
|
+
};
|
|
165
|
+
|
|
166
|
+
render();
|
|
167
|
+
|
|
168
|
+
const handler = (data) => {
|
|
169
|
+
if (data === "\r" || data === "\n") {
|
|
170
|
+
stdin.removeListener("data", handler);
|
|
171
|
+
stdin.setRawMode(false);
|
|
172
|
+
stdin.pause();
|
|
173
|
+
ln();
|
|
174
|
+
barLn();
|
|
175
|
+
if (mask) {
|
|
176
|
+
result("Entered");
|
|
177
|
+
} else {
|
|
178
|
+
result(value || initial);
|
|
179
|
+
}
|
|
180
|
+
resolve(value || initial);
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
if (data === "\x03") { cleanup(); ln(); process.exit(0); }
|
|
184
|
+
|
|
185
|
+
if (data === "\x1b[C") { pos = Math.min(pos + 1, value.length); }
|
|
186
|
+
else if (data === "\x1b[D") { pos = Math.max(0, pos - 1); }
|
|
187
|
+
else if (data === "\x1b[H" || data === "\x01") { pos = 0; }
|
|
188
|
+
else if (data === "\x1b[F" || data === "\x05") { pos = value.length; }
|
|
189
|
+
else if (data === "\x15") { value = ""; pos = 0; }
|
|
190
|
+
else if (data === "\x7f" || data === "\x08") {
|
|
191
|
+
if (pos > 0) {
|
|
192
|
+
value = value.slice(0, pos - 1) + value.slice(pos);
|
|
193
|
+
pos--;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
else if (data === "\x1b[3~") {
|
|
197
|
+
if (pos < value.length) {
|
|
198
|
+
value = value.slice(0, pos) + value.slice(pos + 1);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
else if (data.startsWith("\x1b")) { /* ignore escape sequences */ }
|
|
202
|
+
else {
|
|
203
|
+
for (const ch of data) {
|
|
204
|
+
if (ch.charCodeAt(0) >= 32) {
|
|
205
|
+
value = value.slice(0, pos) + ch + value.slice(pos);
|
|
206
|
+
pos++;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
render();
|
|
211
|
+
};
|
|
212
|
+
|
|
213
|
+
stdin.on("data", handler);
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// ─── UI: Interactive select (raw mode) ───────────────────────────────────────
|
|
218
|
+
function interactiveSelect(items, { multi = false, preSelected = [], defaultIndex = 0 } = {}) {
|
|
219
|
+
return new Promise((resolve) => {
|
|
220
|
+
if (!IS_TTY) {
|
|
221
|
+
resolve(multi ? preSelected : items[defaultIndex]?.id);
|
|
222
|
+
return;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
const { stdin } = process;
|
|
226
|
+
stdin.setRawMode(true);
|
|
227
|
+
stdin.resume();
|
|
228
|
+
stdin.setEncoding("utf8");
|
|
229
|
+
w(S.hide);
|
|
230
|
+
|
|
231
|
+
let cursor = defaultIndex;
|
|
232
|
+
const selected = new Set(preSelected);
|
|
233
|
+
let prevLines = 0;
|
|
234
|
+
|
|
235
|
+
const render = () => {
|
|
236
|
+
if (prevLines > 0) w(`\x1b[${prevLines}A`);
|
|
237
|
+
|
|
238
|
+
let lines = 0;
|
|
239
|
+
for (let i = 0; i < items.length; i++) {
|
|
240
|
+
const item = items[i];
|
|
241
|
+
const active = i === cursor;
|
|
242
|
+
const checked = selected.has(item.id);
|
|
243
|
+
|
|
244
|
+
const icon = multi
|
|
245
|
+
? (checked ? green("◼") : dim("◻"))
|
|
246
|
+
: (active ? cyan("●") : dim("○"));
|
|
247
|
+
const label = active ? bold(item.name) : item.name;
|
|
248
|
+
const padded = strip(label).padEnd(20);
|
|
249
|
+
const styledPadded = active ? bold(padded) : padded;
|
|
250
|
+
// Color-coded tier badges
|
|
251
|
+
let tierLabel = "";
|
|
252
|
+
if (item.tier === "Full") tierLabel = `${S.green}Full${S.reset}`;
|
|
253
|
+
else if (item.tier === "Enhanced") tierLabel = `${S.cyan}Enhanced${S.reset}`;
|
|
254
|
+
else if (item.tier === "Basic+" || item.tier === "Basic") tierLabel = `${S.gray}${item.tier}${S.reset}`;
|
|
255
|
+
else if (item.tier === "recommended") tierLabel = `${S.green}recommended${S.reset}`;
|
|
256
|
+
else if (item.tier) tierLabel = dim(item.tier);
|
|
257
|
+
|
|
258
|
+
const tierStr = tierLabel ? strip(tierLabel).length : 0;
|
|
259
|
+
const tierPad = tierLabel ? tierLabel + " ".repeat(Math.max(0, 12 - tierStr)) : " ".repeat(12);
|
|
260
|
+
const tag = item._detected ? dim("(detected)") : "";
|
|
261
|
+
|
|
262
|
+
w(`\x1b[2K${BAR_COLOR}│${S.reset} ${icon} ${styledPadded}${tierPad}${tag}\n`);
|
|
263
|
+
lines++;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
w(`\x1b[2K${BAR_COLOR}│${S.reset}\n`);
|
|
267
|
+
lines++;
|
|
268
|
+
|
|
269
|
+
const hint = multi
|
|
270
|
+
? dim(" ↑↓ navigate space select a all enter confirm")
|
|
271
|
+
: dim(" ↑↓ navigate enter select");
|
|
272
|
+
w(`\x1b[2K${BAR_COLOR}│${S.reset}${hint}\n`);
|
|
273
|
+
lines++;
|
|
274
|
+
|
|
275
|
+
prevLines = lines;
|
|
276
|
+
};
|
|
277
|
+
|
|
278
|
+
render();
|
|
279
|
+
|
|
280
|
+
const handler = (data) => {
|
|
281
|
+
if (data === "\x1b[A") { cursor = (cursor - 1 + items.length) % items.length; }
|
|
282
|
+
else if (data === "\x1b[B") { cursor = (cursor + 1) % items.length; }
|
|
283
|
+
else if (data === " " && multi) {
|
|
284
|
+
const id = items[cursor].id;
|
|
285
|
+
if (selected.has(id)) selected.delete(id);
|
|
286
|
+
else selected.add(id);
|
|
287
|
+
}
|
|
288
|
+
else if ((data === "a" || data === "A") && multi) {
|
|
289
|
+
if (selected.size === items.length) selected.clear();
|
|
290
|
+
else items.forEach((i) => selected.add(i.id));
|
|
291
|
+
}
|
|
292
|
+
else if (data === "\r" || data === "\n") {
|
|
293
|
+
stdin.removeListener("data", handler);
|
|
294
|
+
stdin.setRawMode(false);
|
|
295
|
+
stdin.pause();
|
|
296
|
+
|
|
297
|
+
// Clear rendered lines
|
|
298
|
+
if (prevLines > 0) {
|
|
299
|
+
w(`\x1b[${prevLines}A`);
|
|
300
|
+
for (let i = 0; i < prevLines; i++) w("\x1b[2K\n");
|
|
301
|
+
w(`\x1b[${prevLines}A`);
|
|
302
|
+
}
|
|
303
|
+
w(S.show);
|
|
304
|
+
|
|
305
|
+
if (multi) {
|
|
306
|
+
const names = items.filter((i) => selected.has(i.id)).map((i) => i.name);
|
|
307
|
+
barLn();
|
|
308
|
+
result(names.length > 0 ? names.join(", ") : "None");
|
|
309
|
+
resolve([...selected]);
|
|
310
|
+
} else {
|
|
311
|
+
barLn();
|
|
312
|
+
result(items[cursor].name);
|
|
313
|
+
resolve(items[cursor].id);
|
|
314
|
+
}
|
|
315
|
+
return;
|
|
316
|
+
}
|
|
317
|
+
else if (data === "\x03") { cleanup(); ln(); process.exit(0); }
|
|
318
|
+
|
|
319
|
+
render();
|
|
320
|
+
};
|
|
321
|
+
|
|
322
|
+
stdin.on("data", handler);
|
|
323
|
+
});
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
// ─── License validation ─────────────────────────────────────────────────────
|
|
327
|
+
const VALID_KEYS = new Set(["MOVER-BETA-2026", "MOVER-FOUNDER-001"]);
|
|
328
|
+
function validateKey(key) {
|
|
329
|
+
if (!key) return false;
|
|
330
|
+
return VALID_KEYS.has(key.toUpperCase().trim());
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
// ─── CLI ─────────────────────────────────────────────────────────────────────
|
|
334
|
+
function parseArgs() {
|
|
335
|
+
const args = process.argv.slice(2);
|
|
336
|
+
const opts = { vault: "", key: "" };
|
|
337
|
+
for (let i = 0; i < args.length; i++) {
|
|
338
|
+
if (args[i] === "--vault" && args[i + 1]) opts.vault = args[++i];
|
|
339
|
+
else if (args[i] === "--key" && args[i + 1]) opts.key = args[++i];
|
|
340
|
+
else if (args[i] === "--help" || args[i] === "-h") {
|
|
341
|
+
ln();
|
|
342
|
+
ln(` ${bold("mover os")} ${dim("installer")}`);
|
|
343
|
+
ln();
|
|
344
|
+
ln(` ${dim("Usage")} npx install-mover-os`);
|
|
345
|
+
ln();
|
|
346
|
+
ln(` ${dim("Options")}`);
|
|
347
|
+
ln(` --key KEY License key (skip interactive prompt)`);
|
|
348
|
+
ln(` --vault PATH Obsidian vault path (skip detection)`);
|
|
349
|
+
ln();
|
|
350
|
+
process.exit(0);
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
return opts;
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
// ─── Engine file detection ──────────────────────────────────────────────────
|
|
357
|
+
function detectEngineFiles(vaultPath) {
|
|
358
|
+
const engineDir = path.join(vaultPath, "02_Areas", "Engine");
|
|
359
|
+
if (!fs.existsSync(engineDir)) return { exists: false, files: [] };
|
|
360
|
+
const coreFiles = [
|
|
361
|
+
"Identity_Prime.md", "Strategy.md", "Active_Context.md",
|
|
362
|
+
"Mover_Dossier.md", "Auto_Learnings.md", "Goals.md",
|
|
363
|
+
];
|
|
364
|
+
const found = coreFiles.filter((f) => {
|
|
365
|
+
const p = path.join(engineDir, f);
|
|
366
|
+
if (!fs.existsSync(p)) return false;
|
|
367
|
+
// Template files have placeholder content — check if user has actually written to them
|
|
368
|
+
try {
|
|
369
|
+
const content = fs.readFileSync(p, "utf8");
|
|
370
|
+
return content.length > 200; // Templates are ~100-200 chars of scaffolding
|
|
371
|
+
} catch { return false; }
|
|
372
|
+
});
|
|
373
|
+
return { exists: found.length > 0, files: found };
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
// ─── Uninstall ──────────────────────────────────────────────────────────────
|
|
377
|
+
async function runUninstall(vaultPath) {
|
|
378
|
+
ln();
|
|
379
|
+
ln(gradient(" ╔╦╗╔═╗╦ ╦╔═╗╦═╗ ╔═╗╔═╗"));
|
|
380
|
+
ln(gradient(" ║║║║ ║╚╗╔╝║╣ ╠╦╝ ║ ║╚═╗"));
|
|
381
|
+
ln(gradient(" ╩ ╩╚═╝ ╚╝ ╚═╝╩╚═ ╚═╝╚═╝"));
|
|
382
|
+
ln();
|
|
383
|
+
barLn(dim("uninstalling agent configs..."));
|
|
384
|
+
barLn();
|
|
385
|
+
|
|
386
|
+
const home = os.homedir();
|
|
387
|
+
let removed = 0;
|
|
388
|
+
|
|
389
|
+
// Agent configs
|
|
390
|
+
const agentPaths = [
|
|
391
|
+
{ label: "Claude Code rules", path: path.join(home, ".claude", "CLAUDE.md") },
|
|
392
|
+
{ label: "Claude Code commands", path: path.join(home, ".claude", "commands"), dir: true },
|
|
393
|
+
{ label: "Claude Code skills", path: path.join(home, ".claude", "skills"), dir: true, keepBuiltins: true },
|
|
394
|
+
{ label: "Cursor rules", path: path.join(home, ".cursor", "rules", "mover-os.mdc") },
|
|
395
|
+
{ label: "Cursor commands", path: path.join(home, ".cursor", "commands"), dir: true },
|
|
396
|
+
{ label: "Cursor skills", path: path.join(home, ".cursor", "skills"), dir: true },
|
|
397
|
+
{ label: "Antigravity rules", path: path.join(home, ".gemini", "GEMINI.md") },
|
|
398
|
+
{ label: "Antigravity workflows", path: path.join(home, ".gemini", "antigravity", "global_workflows"), dir: true },
|
|
399
|
+
{ label: "Codex instructions", path: path.join(home, ".codex", "instructions.md") },
|
|
400
|
+
{ label: "Claude Code hooks", path: path.join(home, ".claude", "hooks"), dir: true },
|
|
401
|
+
];
|
|
402
|
+
|
|
403
|
+
for (const item of agentPaths) {
|
|
404
|
+
if (!fs.existsSync(item.path)) continue;
|
|
405
|
+
try {
|
|
406
|
+
if (item.dir) {
|
|
407
|
+
if (item.keepBuiltins) {
|
|
408
|
+
// Only remove skill directories that match our installed skills
|
|
409
|
+
for (const entry of fs.readdirSync(item.path, { withFileTypes: true })) {
|
|
410
|
+
if (entry.isDirectory() && fs.existsSync(path.join(item.path, entry.name, "SKILL.md"))) {
|
|
411
|
+
fs.rmSync(path.join(item.path, entry.name), { recursive: true, force: true });
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
} else {
|
|
415
|
+
fs.rmSync(item.path, { recursive: true, force: true });
|
|
416
|
+
}
|
|
417
|
+
} else {
|
|
418
|
+
fs.unlinkSync(item.path);
|
|
419
|
+
}
|
|
420
|
+
barLn(`${green("\u2713")} ${dim(item.label)}`);
|
|
421
|
+
removed++;
|
|
422
|
+
} catch {}
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
// Vault-specific files (only if vault path given)
|
|
426
|
+
if (vaultPath && fs.existsSync(vaultPath)) {
|
|
427
|
+
const vaultFiles = [
|
|
428
|
+
{ label: "AGENTS.md", path: path.join(vaultPath, "AGENTS.md") },
|
|
429
|
+
{ label: "SOUL.md", path: path.join(vaultPath, "SOUL.md") },
|
|
430
|
+
{ label: ".mover-version", path: path.join(vaultPath, ".mover-version") },
|
|
431
|
+
{ label: "Claude settings", path: path.join(vaultPath, ".claude", "settings.json") },
|
|
432
|
+
];
|
|
433
|
+
|
|
434
|
+
const vaultAgentFiles = [
|
|
435
|
+
{ label: ".cursorrules", path: path.join(vaultPath, ".cursorrules") },
|
|
436
|
+
{ label: ".clinerules", path: path.join(vaultPath, ".clinerules"), dir: true },
|
|
437
|
+
{ label: ".windsurfrules", path: path.join(vaultPath, ".windsurfrules") },
|
|
438
|
+
{ label: "AMP.md", path: path.join(vaultPath, "AMP.md") },
|
|
439
|
+
{ label: ".aider.conf.yml", path: path.join(vaultPath, ".aider.conf.yml") },
|
|
440
|
+
];
|
|
441
|
+
|
|
442
|
+
for (const item of [...vaultFiles, ...vaultAgentFiles]) {
|
|
443
|
+
if (!fs.existsSync(item.path)) continue;
|
|
444
|
+
try {
|
|
445
|
+
if (item.dir) {
|
|
446
|
+
fs.rmSync(item.path, { recursive: true, force: true });
|
|
447
|
+
} else {
|
|
448
|
+
fs.unlinkSync(item.path);
|
|
449
|
+
}
|
|
450
|
+
barLn(`${green("\u2713")} ${dim(item.label)}`);
|
|
451
|
+
removed++;
|
|
452
|
+
} catch {}
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
barLn();
|
|
457
|
+
if (removed > 0) {
|
|
458
|
+
barLn(dim("Engine files and vault structure left intact."));
|
|
459
|
+
barLn(dim("Your Identity, Strategy, and Daily Notes are untouched."));
|
|
460
|
+
} else {
|
|
461
|
+
barLn(dim("Nothing to remove — Mover OS not detected."));
|
|
462
|
+
}
|
|
463
|
+
barLn();
|
|
464
|
+
outro(`${green("Done.")} ${removed} items removed.`);
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
// ─── Agent definitions ──────────────────────────────────────────────────────
|
|
468
|
+
const AGENTS = [
|
|
469
|
+
{
|
|
470
|
+
id: "claude-code",
|
|
471
|
+
name: "Claude Code",
|
|
472
|
+
tier: "Full",
|
|
473
|
+
detect: () => cmdExists("claude") || fs.existsSync(path.join(os.homedir(), ".claude")),
|
|
474
|
+
},
|
|
475
|
+
{
|
|
476
|
+
id: "cursor",
|
|
477
|
+
name: "Cursor",
|
|
478
|
+
tier: "Full",
|
|
479
|
+
detect: () => fs.existsSync(path.join(os.homedir(), ".cursor")) || cmdExists("cursor"),
|
|
480
|
+
},
|
|
481
|
+
{
|
|
482
|
+
id: "cline",
|
|
483
|
+
name: "Cline",
|
|
484
|
+
tier: "Full",
|
|
485
|
+
detect: () => globDirExists(path.join(os.homedir(), ".vscode", "extensions"), "saoudrizwan.claude-dev-*"),
|
|
486
|
+
},
|
|
487
|
+
{
|
|
488
|
+
id: "codex",
|
|
489
|
+
name: "Codex CLI",
|
|
490
|
+
tier: "Enhanced",
|
|
491
|
+
detect: () => cmdExists("codex") || fs.existsSync(path.join(os.homedir(), ".codex")),
|
|
492
|
+
},
|
|
493
|
+
{
|
|
494
|
+
id: "windsurf",
|
|
495
|
+
name: "Windsurf",
|
|
496
|
+
tier: "Enhanced",
|
|
497
|
+
detect: () => fs.existsSync(path.join(os.homedir(), ".windsurf")) || cmdExists("windsurf"),
|
|
498
|
+
},
|
|
499
|
+
{
|
|
500
|
+
id: "openclaw",
|
|
501
|
+
name: "OpenClaw",
|
|
502
|
+
tier: "Enhanced",
|
|
503
|
+
detect: () => fs.existsSync(path.join(os.homedir(), ".openclaw")) || cmdExists("openclaw"),
|
|
504
|
+
},
|
|
505
|
+
{
|
|
506
|
+
id: "antigravity",
|
|
507
|
+
name: "Antigravity",
|
|
508
|
+
tier: "Basic+",
|
|
509
|
+
detect: () => fs.existsSync(path.join(os.homedir(), ".gemini")) || cmdExists("gemini"),
|
|
510
|
+
},
|
|
511
|
+
{
|
|
512
|
+
id: "roo-code",
|
|
513
|
+
name: "Roo Code",
|
|
514
|
+
tier: "Basic",
|
|
515
|
+
detect: () => globDirExists(path.join(os.homedir(), ".vscode", "extensions"), "rooveterinaryinc.roo-cline-*"),
|
|
516
|
+
},
|
|
517
|
+
{
|
|
518
|
+
id: "copilot",
|
|
519
|
+
name: "GitHub Copilot",
|
|
520
|
+
tier: "Basic",
|
|
521
|
+
detect: () => cmdExists("gh"),
|
|
522
|
+
},
|
|
523
|
+
{
|
|
524
|
+
id: "amp",
|
|
525
|
+
name: "Amp",
|
|
526
|
+
tier: "Basic",
|
|
527
|
+
detect: () => cmdExists("amp") || fs.existsSync(path.join(os.homedir(), ".amp")),
|
|
528
|
+
},
|
|
529
|
+
{
|
|
530
|
+
id: "aider",
|
|
531
|
+
name: "Aider",
|
|
532
|
+
tier: "Basic",
|
|
533
|
+
detect: () => cmdExists("aider"),
|
|
534
|
+
},
|
|
535
|
+
];
|
|
536
|
+
|
|
537
|
+
// ─── Utility functions ──────────────────────────────────────────────────────
|
|
538
|
+
function cmdExists(cmd) {
|
|
539
|
+
try {
|
|
540
|
+
execSync(process.platform === "win32" ? `where ${cmd}` : `command -v ${cmd}`, { stdio: "ignore" });
|
|
541
|
+
return true;
|
|
542
|
+
} catch {
|
|
543
|
+
return false;
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
function globDirExists(dir, pattern) {
|
|
548
|
+
if (!fs.existsSync(dir)) return false;
|
|
549
|
+
const prefix = pattern.replace("*", "");
|
|
550
|
+
try {
|
|
551
|
+
return fs.readdirSync(dir).some((e) => e.startsWith(prefix));
|
|
552
|
+
} catch {
|
|
553
|
+
return false;
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
function linkOrCopy(src, dest) {
|
|
558
|
+
try {
|
|
559
|
+
fs.mkdirSync(path.dirname(dest), { recursive: true });
|
|
560
|
+
if (fs.existsSync(dest)) fs.unlinkSync(dest);
|
|
561
|
+
fs.linkSync(src, dest);
|
|
562
|
+
return "linked";
|
|
563
|
+
} catch {
|
|
564
|
+
try {
|
|
565
|
+
fs.copyFileSync(src, dest);
|
|
566
|
+
return "copied";
|
|
567
|
+
} catch {
|
|
568
|
+
return null;
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
function copyDirRecursive(src, dest) {
|
|
574
|
+
fs.mkdirSync(dest, { recursive: true });
|
|
575
|
+
for (const entry of fs.readdirSync(src, { withFileTypes: true })) {
|
|
576
|
+
const s = path.join(src, entry.name);
|
|
577
|
+
const d = path.join(dest, entry.name);
|
|
578
|
+
if (entry.isDirectory()) {
|
|
579
|
+
copyDirRecursive(s, d);
|
|
580
|
+
} else {
|
|
581
|
+
fs.copyFileSync(s, d);
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
function findSkills(bundleDir) {
|
|
587
|
+
const skillsDir = path.join(bundleDir, "src", "skills");
|
|
588
|
+
if (!fs.existsSync(skillsDir)) return [];
|
|
589
|
+
const skills = [];
|
|
590
|
+
const walk = (dir) => {
|
|
591
|
+
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
592
|
+
const full = path.join(dir, entry.name);
|
|
593
|
+
if (entry.isDirectory()) {
|
|
594
|
+
if (fs.existsSync(path.join(full, "SKILL.md"))) {
|
|
595
|
+
skills.push({ name: entry.name, path: full });
|
|
596
|
+
} else {
|
|
597
|
+
walk(full);
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
};
|
|
602
|
+
walk(skillsDir);
|
|
603
|
+
return skills;
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
// ─── AGENTS.md generator ────────────────────────────────────────────────────
|
|
607
|
+
function generateAgentsMd() {
|
|
608
|
+
return `# AGENTS.md
|
|
609
|
+
|
|
610
|
+
> Mover OS — The Agentic Operating System for Obsidian
|
|
611
|
+
|
|
612
|
+
## What This Is
|
|
613
|
+
|
|
614
|
+
You are operating within **Mover OS**, an AI-powered execution engine built on Obsidian. You audit behavior against stated strategy, extract reusable knowledge, and evolve via user corrections.
|
|
615
|
+
|
|
616
|
+
## Core Rules
|
|
617
|
+
|
|
618
|
+
1. **Read before acting.** Load Engine files (02_Areas/Engine/) before any strategic operation.
|
|
619
|
+
2. **Cite sources.** Every claim needs \`[file:line]\` or \`[INFERRED: reason]\`. Confidence < 3 = ask.
|
|
620
|
+
3. **Append-only.** Never delete from plan.md, Auto_Learnings.md, or Daily Notes.
|
|
621
|
+
4. **Grounded output.** No fabricated answers. "I don't know" beats a guess.
|
|
622
|
+
5. **Engine protection.** Never overwrite Identity_Prime.md, Strategy.md, or other Engine files without explicit approval.
|
|
623
|
+
|
|
624
|
+
## Project Structure
|
|
625
|
+
|
|
626
|
+
\`\`\`
|
|
627
|
+
00_Inbox/ # Quick capture
|
|
628
|
+
01_Projects/ # Active projects with plan.md + project_state.md
|
|
629
|
+
02_Areas/Engine/ # THE CORE — Identity, Strategy, Context, Dossier
|
|
630
|
+
03_Library/ # Permanent knowledge (MOCs, SOPs, Principles)
|
|
631
|
+
04_Archives/ # Completed projects
|
|
632
|
+
\`\`\`
|
|
633
|
+
|
|
634
|
+
## Engine Files (Priority Order)
|
|
635
|
+
|
|
636
|
+
| Priority | File | Purpose |
|
|
637
|
+
|----------|------|---------|
|
|
638
|
+
| P0 | Auto_Learnings.md | AI corrections & behavioral patterns |
|
|
639
|
+
| P1 | Active_Context.md | Current state, blockers, energy |
|
|
640
|
+
| P2 | Strategy.md | Business/life hypothesis being tested |
|
|
641
|
+
| P3 | Identity_Prime.md | User persona, values, anti-identity |
|
|
642
|
+
| P4 | project_state.md | Project-specific knowledge |
|
|
643
|
+
| P5 | Yesterday's Daily Note | Recent session history |
|
|
644
|
+
|
|
645
|
+
## Behavior
|
|
646
|
+
|
|
647
|
+
- Be direct. No hedging, no "Great question!", no emdashes.
|
|
648
|
+
- Think like a co-founder, not a consultant.
|
|
649
|
+
- When wrong, name it, fix it, move on.
|
|
650
|
+
- Comment WHY, not WHAT.
|
|
651
|
+
- Have opinions. "It depends" is a cop-out.
|
|
652
|
+
|
|
653
|
+
## Workflows
|
|
654
|
+
|
|
655
|
+
Available slash commands: /setup, /walkthrough, /plan-tomorrow, /analyse-day, /log, /review-week, /morning, /overview, /ignite, /harvest, /debug-resistance, /pivot-strategy, /reboot, /refactor-plan
|
|
656
|
+
|
|
657
|
+
Each workflow hands off to the next. Follow the breadcrumbs.
|
|
658
|
+
|
|
659
|
+
## Daily Flow
|
|
660
|
+
|
|
661
|
+
\`\`\`
|
|
662
|
+
/morning → [WORK] → /log → /analyse-day → /plan-tomorrow
|
|
663
|
+
Sunday: /review-week first
|
|
664
|
+
New project: /ignite → [WORK]
|
|
665
|
+
Stuck: /debug-resistance
|
|
666
|
+
\`\`\`
|
|
667
|
+
`;
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
// ─── SOUL.md generator (OpenClaw) ───────────────────────────────────────────
|
|
671
|
+
function generateSoulMd() {
|
|
672
|
+
return `# SOUL.md — Mover OS
|
|
673
|
+
|
|
674
|
+
> You are not a chatbot. You are a co-founder who happens to live inside a terminal.
|
|
675
|
+
|
|
676
|
+
## Core Truths
|
|
677
|
+
|
|
678
|
+
- Be genuinely helpful, not performatively helpful. If the answer is "don't build that," say it.
|
|
679
|
+
- Have opinions. "It depends" is a cop-out. Pick a direction, defend it, change your mind if the user makes a better case.
|
|
680
|
+
- Be resourceful before asking. Exhaust what you can figure out, then ask sharp questions — not broad ones.
|
|
681
|
+
- Earn trust through competence, not compliance. Nobody respects a yes-man.
|
|
682
|
+
|
|
683
|
+
## The Vibe
|
|
684
|
+
|
|
685
|
+
- Talk like a co-founder who's been in the trenches, not a consultant billing by the hour.
|
|
686
|
+
- One sharp sentence beats three careful ones. Say what you mean.
|
|
687
|
+
- When you're wrong, don't grovel. Name it, fix it, move. Apologies waste both your time.
|
|
688
|
+
- Match intensity to stakes. Casual for small tasks. Locked in for architecture. Blunt for bad ideas.
|
|
689
|
+
|
|
690
|
+
## Boundaries
|
|
691
|
+
|
|
692
|
+
- Never hedge to avoid being wrong. Pick a position.
|
|
693
|
+
- Never pretend all approaches are equally valid when one is clearly better.
|
|
694
|
+
- Never pad output to look thorough. Substance only.
|
|
695
|
+
- Never be artificially enthusiastic about bad ideas.
|
|
696
|
+
|
|
697
|
+
## Continuity
|
|
698
|
+
|
|
699
|
+
- You start fresh each session but the files are your memory. Read them, trust them, build on them.
|
|
700
|
+
- If something changed between sessions and you don't understand why, ask — don't assume.
|
|
701
|
+
- The user corrects you to make you better. Take corrections seriously and encode them.
|
|
702
|
+
`;
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
// ─── Claude Code hooks (settings.json) ──────────────────────────────────────
|
|
706
|
+
function generateClaudeSettings() {
|
|
707
|
+
return JSON.stringify(
|
|
708
|
+
{
|
|
709
|
+
hooks: {
|
|
710
|
+
Stop: [
|
|
711
|
+
{
|
|
712
|
+
hooks: [
|
|
713
|
+
{ type: "command", command: 'bash "$HOME/.claude/hooks/session-log-reminder.sh"', timeout: 10 },
|
|
714
|
+
{ type: "command", command: 'bash "$HOME/.claude/hooks/dirty-tree-guard.sh"', timeout: 10 },
|
|
715
|
+
{ type: "command", command: 'bash "$HOME/.claude/hooks/context-staleness.sh"', timeout: 5 },
|
|
716
|
+
],
|
|
717
|
+
},
|
|
718
|
+
],
|
|
719
|
+
PreToolUse: [
|
|
720
|
+
{
|
|
721
|
+
matcher: "Write|Edit",
|
|
722
|
+
hooks: [{ type: "command", command: 'bash "$HOME/.claude/hooks/engine-protection.sh"', timeout: 5 }],
|
|
723
|
+
},
|
|
724
|
+
{
|
|
725
|
+
matcher: "Bash",
|
|
726
|
+
hooks: [{ type: "command", command: 'bash "$HOME/.claude/hooks/git-safety.sh"', timeout: 5 }],
|
|
727
|
+
},
|
|
728
|
+
],
|
|
729
|
+
PreCompact: [
|
|
730
|
+
{
|
|
731
|
+
hooks: [{ type: "command", command: 'bash "$HOME/.claude/hooks/pre-compact-backup.sh"', timeout: 15 }],
|
|
732
|
+
},
|
|
733
|
+
],
|
|
734
|
+
},
|
|
735
|
+
},
|
|
736
|
+
null,
|
|
737
|
+
2
|
|
738
|
+
);
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
// ─── .gitignore ─────────────────────────────────────────────────────────────
|
|
742
|
+
function generateGitignore() {
|
|
743
|
+
return `# Mover OS — protected from git
|
|
744
|
+
02_Areas/Engine/Dailies/
|
|
745
|
+
02_Areas/Engine/Weekly Reviews/
|
|
746
|
+
.obsidian/
|
|
747
|
+
.trash/
|
|
748
|
+
dev/
|
|
749
|
+
`;
|
|
750
|
+
}
|
|
751
|
+
|
|
752
|
+
// ─── Install functions ──────────────────────────────────────────────────────
|
|
753
|
+
function createVaultStructure(vaultPath) {
|
|
754
|
+
const dirs = [
|
|
755
|
+
"00_Inbox",
|
|
756
|
+
"01_Projects",
|
|
757
|
+
"01_Projects/_Template Project",
|
|
758
|
+
"02_Areas/Engine",
|
|
759
|
+
"02_Areas/Engine/Dailies",
|
|
760
|
+
"02_Areas/Engine/Weekly Reviews",
|
|
761
|
+
"03_Library/Cheatsheets",
|
|
762
|
+
"03_Library/Inputs",
|
|
763
|
+
"03_Library/MOCs",
|
|
764
|
+
"03_Library/Principles",
|
|
765
|
+
"03_Library/Scripts",
|
|
766
|
+
"03_Library/SOPs",
|
|
767
|
+
"04_Archives",
|
|
768
|
+
"Templates",
|
|
769
|
+
];
|
|
770
|
+
|
|
771
|
+
let created = 0;
|
|
772
|
+
for (const dir of dirs) {
|
|
773
|
+
const full = path.join(vaultPath, dir);
|
|
774
|
+
if (!fs.existsSync(full)) {
|
|
775
|
+
fs.mkdirSync(full, { recursive: true });
|
|
776
|
+
created++;
|
|
777
|
+
}
|
|
778
|
+
}
|
|
779
|
+
return created;
|
|
780
|
+
}
|
|
781
|
+
|
|
782
|
+
function installTemplateFiles(bundleDir, vaultPath) {
|
|
783
|
+
const structDir = path.join(bundleDir, "src", "structure");
|
|
784
|
+
if (!fs.existsSync(structDir)) return 0;
|
|
785
|
+
|
|
786
|
+
let count = 0;
|
|
787
|
+
const walk = (dir, rel) => {
|
|
788
|
+
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
789
|
+
const srcPath = path.join(dir, entry.name);
|
|
790
|
+
const destPath = path.join(vaultPath, rel, entry.name);
|
|
791
|
+
|
|
792
|
+
if (entry.isDirectory()) {
|
|
793
|
+
walk(srcPath, path.join(rel, entry.name));
|
|
794
|
+
} else {
|
|
795
|
+
const relNorm = rel.replace(/\\/g, "/");
|
|
796
|
+
if (relNorm.includes("02_Areas") && relNorm.includes("Engine") && fs.existsSync(destPath)) {
|
|
797
|
+
continue;
|
|
798
|
+
}
|
|
799
|
+
fs.mkdirSync(path.dirname(destPath), { recursive: true });
|
|
800
|
+
if (!fs.existsSync(destPath)) {
|
|
801
|
+
fs.copyFileSync(srcPath, destPath);
|
|
802
|
+
count++;
|
|
803
|
+
}
|
|
804
|
+
}
|
|
805
|
+
}
|
|
806
|
+
};
|
|
807
|
+
walk(structDir, "");
|
|
808
|
+
return count;
|
|
809
|
+
}
|
|
810
|
+
|
|
811
|
+
function installWorkflows(bundleDir, destDir) {
|
|
812
|
+
const srcDir = path.join(bundleDir, "src", "workflows");
|
|
813
|
+
if (!fs.existsSync(srcDir)) return 0;
|
|
814
|
+
|
|
815
|
+
fs.mkdirSync(destDir, { recursive: true });
|
|
816
|
+
let count = 0;
|
|
817
|
+
for (const file of fs.readdirSync(srcDir).filter((f) => f.endsWith(".md"))) {
|
|
818
|
+
if (linkOrCopy(path.join(srcDir, file), path.join(destDir, file))) count++;
|
|
819
|
+
}
|
|
820
|
+
return count;
|
|
821
|
+
}
|
|
822
|
+
|
|
823
|
+
function installRules(bundleDir, destPath) {
|
|
824
|
+
const src = path.join(bundleDir, "src", "system", "Mover_Global_Rules.md");
|
|
825
|
+
if (!fs.existsSync(src)) return false;
|
|
826
|
+
return linkOrCopy(src, destPath) !== null;
|
|
827
|
+
}
|
|
828
|
+
|
|
829
|
+
function installSkillPacks(bundleDir, destDir) {
|
|
830
|
+
const skills = findSkills(bundleDir);
|
|
831
|
+
fs.mkdirSync(destDir, { recursive: true });
|
|
832
|
+
let count = 0;
|
|
833
|
+
for (const skill of skills) {
|
|
834
|
+
const dest = path.join(destDir, skill.name);
|
|
835
|
+
if (fs.existsSync(dest)) fs.rmSync(dest, { recursive: true, force: true });
|
|
836
|
+
copyDirRecursive(skill.path, dest);
|
|
837
|
+
count++;
|
|
838
|
+
}
|
|
839
|
+
return count;
|
|
840
|
+
}
|
|
841
|
+
|
|
842
|
+
function installHooksForClaude(bundleDir, vaultPath) {
|
|
843
|
+
const hooksSrc = path.join(bundleDir, "src", "hooks");
|
|
844
|
+
const hooksDst = path.join(os.homedir(), ".claude", "hooks");
|
|
845
|
+
const settingsDir = path.join(vaultPath, ".claude");
|
|
846
|
+
|
|
847
|
+
if (!fs.existsSync(hooksSrc)) return 0;
|
|
848
|
+
|
|
849
|
+
fs.mkdirSync(hooksDst, { recursive: true });
|
|
850
|
+
fs.mkdirSync(settingsDir, { recursive: true });
|
|
851
|
+
|
|
852
|
+
let count = 0;
|
|
853
|
+
for (const file of fs.readdirSync(hooksSrc).filter((f) => f.endsWith(".sh"))) {
|
|
854
|
+
fs.copyFileSync(path.join(hooksSrc, file), path.join(hooksDst, file));
|
|
855
|
+
count++;
|
|
856
|
+
}
|
|
857
|
+
|
|
858
|
+
const settingsPath = path.join(settingsDir, "settings.json");
|
|
859
|
+
if (!fs.existsSync(settingsPath)) {
|
|
860
|
+
fs.writeFileSync(settingsPath, generateClaudeSettings(), "utf8");
|
|
861
|
+
}
|
|
862
|
+
|
|
863
|
+
return count;
|
|
864
|
+
}
|
|
865
|
+
|
|
866
|
+
// ─── Per-agent install orchestrators ────────────────────────────────────────
|
|
867
|
+
function installClaudeCode(bundleDir, vaultPath) {
|
|
868
|
+
const home = os.homedir();
|
|
869
|
+
const steps = [];
|
|
870
|
+
|
|
871
|
+
const claudeDir = path.join(home, ".claude");
|
|
872
|
+
fs.mkdirSync(claudeDir, { recursive: true });
|
|
873
|
+
if (installRules(bundleDir, path.join(claudeDir, "CLAUDE.md"))) steps.push("rules");
|
|
874
|
+
|
|
875
|
+
const cmdsDir = path.join(claudeDir, "commands");
|
|
876
|
+
const wfCount = installWorkflows(bundleDir, cmdsDir);
|
|
877
|
+
if (wfCount > 0) steps.push(`${wfCount} commands`);
|
|
878
|
+
|
|
879
|
+
const skillsDir = path.join(claudeDir, "skills");
|
|
880
|
+
const skCount = installSkillPacks(bundleDir, skillsDir);
|
|
881
|
+
if (skCount > 0) steps.push(`${skCount} skills`);
|
|
882
|
+
|
|
883
|
+
if (vaultPath) {
|
|
884
|
+
const hkCount = installHooksForClaude(bundleDir, vaultPath);
|
|
885
|
+
if (hkCount > 0) steps.push(`${hkCount} hooks`);
|
|
886
|
+
}
|
|
887
|
+
|
|
888
|
+
return steps;
|
|
889
|
+
}
|
|
890
|
+
|
|
891
|
+
function installCursor(bundleDir, vaultPath) {
|
|
892
|
+
const home = os.homedir();
|
|
893
|
+
const steps = [];
|
|
894
|
+
|
|
895
|
+
if (vaultPath) {
|
|
896
|
+
if (installRules(bundleDir, path.join(vaultPath, ".cursorrules"))) steps.push("rules");
|
|
897
|
+
}
|
|
898
|
+
|
|
899
|
+
const cursorRulesDir = path.join(home, ".cursor", "rules");
|
|
900
|
+
fs.mkdirSync(cursorRulesDir, { recursive: true });
|
|
901
|
+
const src = path.join(bundleDir, "src", "system", "Mover_Global_Rules.md");
|
|
902
|
+
if (fs.existsSync(src)) {
|
|
903
|
+
fs.copyFileSync(src, path.join(cursorRulesDir, "mover-os.mdc"));
|
|
904
|
+
}
|
|
905
|
+
|
|
906
|
+
const cmdsDir = path.join(home, ".cursor", "commands");
|
|
907
|
+
const wfCount = installWorkflows(bundleDir, cmdsDir);
|
|
908
|
+
if (wfCount > 0) steps.push(`${wfCount} commands`);
|
|
909
|
+
|
|
910
|
+
const skillsDir = path.join(home, ".cursor", "skills");
|
|
911
|
+
const skCount = installSkillPacks(bundleDir, skillsDir);
|
|
912
|
+
if (skCount > 0) steps.push(`${skCount} skills`);
|
|
913
|
+
|
|
914
|
+
return steps;
|
|
915
|
+
}
|
|
916
|
+
|
|
917
|
+
function installCline(bundleDir, vaultPath) {
|
|
918
|
+
const steps = [];
|
|
919
|
+
|
|
920
|
+
if (vaultPath) {
|
|
921
|
+
const rulesDir = path.join(vaultPath, ".clinerules");
|
|
922
|
+
fs.mkdirSync(rulesDir, { recursive: true });
|
|
923
|
+
const src = path.join(bundleDir, "src", "system", "Mover_Global_Rules.md");
|
|
924
|
+
if (fs.existsSync(src)) {
|
|
925
|
+
fs.copyFileSync(src, path.join(rulesDir, "mover-os.md"));
|
|
926
|
+
steps.push("rules");
|
|
927
|
+
}
|
|
928
|
+
|
|
929
|
+
const skillsDir = path.join(vaultPath, ".cline", "skills");
|
|
930
|
+
const skCount = installSkillPacks(bundleDir, skillsDir);
|
|
931
|
+
if (skCount > 0) steps.push(`${skCount} skills`);
|
|
932
|
+
}
|
|
933
|
+
|
|
934
|
+
return steps;
|
|
935
|
+
}
|
|
936
|
+
|
|
937
|
+
function installCodex(bundleDir, vaultPath) {
|
|
938
|
+
const home = os.homedir();
|
|
939
|
+
const steps = [];
|
|
940
|
+
|
|
941
|
+
if (vaultPath) {
|
|
942
|
+
fs.writeFileSync(path.join(vaultPath, "AGENTS.md"), generateAgentsMd(), "utf8");
|
|
943
|
+
steps.push("AGENTS.md");
|
|
944
|
+
}
|
|
945
|
+
|
|
946
|
+
const codexDir = path.join(home, ".codex");
|
|
947
|
+
fs.mkdirSync(codexDir, { recursive: true });
|
|
948
|
+
if (installRules(bundleDir, path.join(codexDir, "instructions.md"))) steps.push("global instructions");
|
|
949
|
+
|
|
950
|
+
return steps;
|
|
951
|
+
}
|
|
952
|
+
|
|
953
|
+
function installWindsurf(bundleDir, vaultPath) {
|
|
954
|
+
const steps = [];
|
|
955
|
+
if (vaultPath) {
|
|
956
|
+
if (installRules(bundleDir, path.join(vaultPath, ".windsurfrules"))) steps.push("rules");
|
|
957
|
+
}
|
|
958
|
+
return steps;
|
|
959
|
+
}
|
|
960
|
+
|
|
961
|
+
function installOpenClaw(bundleDir, vaultPath) {
|
|
962
|
+
const home = os.homedir();
|
|
963
|
+
const workspace = path.join(home, ".openclaw", "workspace");
|
|
964
|
+
const steps = [];
|
|
965
|
+
|
|
966
|
+
fs.mkdirSync(path.join(workspace, "skills"), { recursive: true });
|
|
967
|
+
fs.mkdirSync(path.join(workspace, "memory"), { recursive: true });
|
|
968
|
+
|
|
969
|
+
fs.writeFileSync(path.join(workspace, "AGENTS.md"), generateAgentsMd(), "utf8");
|
|
970
|
+
steps.push("AGENTS.md");
|
|
971
|
+
|
|
972
|
+
fs.writeFileSync(path.join(workspace, "SOUL.md"), generateSoulMd(), "utf8");
|
|
973
|
+
steps.push("SOUL.md");
|
|
974
|
+
|
|
975
|
+
const userMd = path.join(workspace, "USER.md");
|
|
976
|
+
if (!fs.existsSync(userMd)) {
|
|
977
|
+
fs.writeFileSync(
|
|
978
|
+
userMd,
|
|
979
|
+
`# 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`,
|
|
980
|
+
"utf8"
|
|
981
|
+
);
|
|
982
|
+
steps.push("USER.md");
|
|
983
|
+
}
|
|
984
|
+
|
|
985
|
+
const skCount = installSkillPacks(bundleDir, path.join(workspace, "skills"));
|
|
986
|
+
if (skCount > 0) steps.push(`${skCount} skills`);
|
|
987
|
+
|
|
988
|
+
return steps;
|
|
989
|
+
}
|
|
990
|
+
|
|
991
|
+
function installAntigravity(bundleDir) {
|
|
992
|
+
const home = os.homedir();
|
|
993
|
+
const steps = [];
|
|
994
|
+
|
|
995
|
+
const geminiDir = path.join(home, ".gemini");
|
|
996
|
+
fs.mkdirSync(geminiDir, { recursive: true });
|
|
997
|
+
if (installRules(bundleDir, path.join(geminiDir, "GEMINI.md"))) steps.push("rules");
|
|
998
|
+
|
|
999
|
+
const wfDir = path.join(geminiDir, "antigravity", "global_workflows");
|
|
1000
|
+
const wfCount = installWorkflows(bundleDir, wfDir);
|
|
1001
|
+
if (wfCount > 0) steps.push(`${wfCount} workflows`);
|
|
1002
|
+
|
|
1003
|
+
return steps;
|
|
1004
|
+
}
|
|
1005
|
+
|
|
1006
|
+
function installRooCode(bundleDir, vaultPath) {
|
|
1007
|
+
const steps = [];
|
|
1008
|
+
if (vaultPath) {
|
|
1009
|
+
const rulesDir = path.join(vaultPath, ".roo", "rules");
|
|
1010
|
+
fs.mkdirSync(rulesDir, { recursive: true });
|
|
1011
|
+
const src = path.join(bundleDir, "src", "system", "Mover_Global_Rules.md");
|
|
1012
|
+
if (fs.existsSync(src)) {
|
|
1013
|
+
fs.copyFileSync(src, path.join(rulesDir, "mover-os.md"));
|
|
1014
|
+
steps.push("rules");
|
|
1015
|
+
}
|
|
1016
|
+
}
|
|
1017
|
+
return steps;
|
|
1018
|
+
}
|
|
1019
|
+
|
|
1020
|
+
function installCopilot(bundleDir, vaultPath) {
|
|
1021
|
+
const steps = [];
|
|
1022
|
+
if (vaultPath) {
|
|
1023
|
+
const ghDir = path.join(vaultPath, ".github");
|
|
1024
|
+
fs.mkdirSync(ghDir, { recursive: true });
|
|
1025
|
+
const src = path.join(bundleDir, "src", "system", "Mover_Global_Rules.md");
|
|
1026
|
+
if (fs.existsSync(src)) {
|
|
1027
|
+
fs.copyFileSync(src, path.join(ghDir, "copilot-instructions.md"));
|
|
1028
|
+
steps.push("rules");
|
|
1029
|
+
}
|
|
1030
|
+
}
|
|
1031
|
+
return steps;
|
|
1032
|
+
}
|
|
1033
|
+
|
|
1034
|
+
function installAmp(bundleDir, vaultPath) {
|
|
1035
|
+
const steps = [];
|
|
1036
|
+
if (vaultPath) {
|
|
1037
|
+
fs.writeFileSync(path.join(vaultPath, "AGENTS.md"), generateAgentsMd(), "utf8");
|
|
1038
|
+
steps.push("AGENTS.md");
|
|
1039
|
+
if (installRules(bundleDir, path.join(vaultPath, "AMP.md"))) steps.push("AMP.md");
|
|
1040
|
+
}
|
|
1041
|
+
return steps;
|
|
1042
|
+
}
|
|
1043
|
+
|
|
1044
|
+
function installAider(bundleDir, vaultPath) {
|
|
1045
|
+
const steps = [];
|
|
1046
|
+
if (vaultPath) {
|
|
1047
|
+
const agentsMdPath = path.join(vaultPath, "AGENTS.md");
|
|
1048
|
+
if (!fs.existsSync(agentsMdPath)) {
|
|
1049
|
+
fs.writeFileSync(agentsMdPath, generateAgentsMd(), "utf8");
|
|
1050
|
+
}
|
|
1051
|
+
steps.push("AGENTS.md");
|
|
1052
|
+
|
|
1053
|
+
const aiderConf = path.join(vaultPath, ".aider.conf.yml");
|
|
1054
|
+
if (!fs.existsSync(aiderConf)) {
|
|
1055
|
+
fs.writeFileSync(aiderConf, "# Mover OS — Aider configuration\nread:\n - AGENTS.md\n", "utf8");
|
|
1056
|
+
steps.push(".aider.conf.yml");
|
|
1057
|
+
}
|
|
1058
|
+
}
|
|
1059
|
+
return steps;
|
|
1060
|
+
}
|
|
1061
|
+
|
|
1062
|
+
const AGENT_INSTALLERS = {
|
|
1063
|
+
"claude-code": installClaudeCode,
|
|
1064
|
+
cursor: installCursor,
|
|
1065
|
+
cline: installCline,
|
|
1066
|
+
codex: installCodex,
|
|
1067
|
+
windsurf: installWindsurf,
|
|
1068
|
+
openclaw: installOpenClaw,
|
|
1069
|
+
antigravity: installAntigravity,
|
|
1070
|
+
"roo-code": installRooCode,
|
|
1071
|
+
copilot: installCopilot,
|
|
1072
|
+
amp: installAmp,
|
|
1073
|
+
aider: installAider,
|
|
1074
|
+
};
|
|
1075
|
+
|
|
1076
|
+
// ─── Pre-flight checks ──────────────────────────────────────────────────────
|
|
1077
|
+
function preflight() {
|
|
1078
|
+
const issues = [];
|
|
1079
|
+
|
|
1080
|
+
// Node version
|
|
1081
|
+
const nodeVer = parseInt(process.versions.node.split(".")[0], 10);
|
|
1082
|
+
if (nodeVer < 18) {
|
|
1083
|
+
issues.push({ label: "Node.js", status: "fail", detail: `v${process.versions.node} (need 18+)` });
|
|
1084
|
+
} else {
|
|
1085
|
+
issues.push({ label: "Node.js", status: "ok", detail: `v${process.versions.node}` });
|
|
1086
|
+
}
|
|
1087
|
+
|
|
1088
|
+
// Git
|
|
1089
|
+
const hasGit = cmdExists("git");
|
|
1090
|
+
issues.push({ label: "git", status: hasGit ? "ok" : "warn", detail: hasGit ? "found" : "not found (optional)" });
|
|
1091
|
+
|
|
1092
|
+
// OS
|
|
1093
|
+
const plat = process.platform === "win32" ? "Windows" : process.platform === "darwin" ? "macOS" : "Linux";
|
|
1094
|
+
issues.push({ label: "Platform", status: "ok", detail: plat });
|
|
1095
|
+
|
|
1096
|
+
return issues;
|
|
1097
|
+
}
|
|
1098
|
+
|
|
1099
|
+
// ─── Main ───────────────────────────────────────────────────────────────────
|
|
1100
|
+
async function main() {
|
|
1101
|
+
const opts = parseArgs();
|
|
1102
|
+
const bundleDir = path.resolve(__dirname);
|
|
1103
|
+
const startTime = Date.now();
|
|
1104
|
+
|
|
1105
|
+
// ── Intro ──
|
|
1106
|
+
printHeader();
|
|
1107
|
+
|
|
1108
|
+
// ── Pre-flight ──
|
|
1109
|
+
barLn(gray("Pre-flight"));
|
|
1110
|
+
barLn();
|
|
1111
|
+
const checks = preflight();
|
|
1112
|
+
for (const c of checks) {
|
|
1113
|
+
const icon = c.status === "ok" ? green("\u2713") : c.status === "warn" ? yellow("\u25CB") : red("\u2717");
|
|
1114
|
+
barLn(`${icon} ${dim(`${c.label} ${c.detail}`)}`);
|
|
1115
|
+
}
|
|
1116
|
+
barLn();
|
|
1117
|
+
|
|
1118
|
+
if (checks.some((c) => c.status === "fail")) {
|
|
1119
|
+
outro(red("Pre-flight failed. Fix the issues above."));
|
|
1120
|
+
process.exit(1);
|
|
1121
|
+
}
|
|
1122
|
+
|
|
1123
|
+
// ── License key ──
|
|
1124
|
+
let key = opts.key;
|
|
1125
|
+
|
|
1126
|
+
if (!key) {
|
|
1127
|
+
let attempts = 0;
|
|
1128
|
+
while (attempts < 3) {
|
|
1129
|
+
key = await textInput({
|
|
1130
|
+
label: "Enter your license key",
|
|
1131
|
+
mask: "\u25AA",
|
|
1132
|
+
placeholder: "MOVER-XXXX-XXXX",
|
|
1133
|
+
});
|
|
1134
|
+
|
|
1135
|
+
if (validateKey(key)) break;
|
|
1136
|
+
|
|
1137
|
+
attempts++;
|
|
1138
|
+
if (attempts < 3) {
|
|
1139
|
+
barLn(red("Invalid key. Try again."));
|
|
1140
|
+
barLn();
|
|
1141
|
+
key = "";
|
|
1142
|
+
}
|
|
1143
|
+
}
|
|
1144
|
+
|
|
1145
|
+
if (!validateKey(key)) {
|
|
1146
|
+
barLn(red("Invalid license key."));
|
|
1147
|
+
barLn();
|
|
1148
|
+
barLn(dim("Get a key at https://moveros.com"));
|
|
1149
|
+
outro("Cancelled.");
|
|
1150
|
+
process.exit(1);
|
|
1151
|
+
}
|
|
1152
|
+
} else {
|
|
1153
|
+
if (!validateKey(key)) {
|
|
1154
|
+
barLn(red("Invalid license key."));
|
|
1155
|
+
barLn();
|
|
1156
|
+
barLn(dim("Get a key at https://moveros.com"));
|
|
1157
|
+
outro("Cancelled.");
|
|
1158
|
+
process.exit(1);
|
|
1159
|
+
}
|
|
1160
|
+
barLn(green("License verified."));
|
|
1161
|
+
barLn();
|
|
1162
|
+
}
|
|
1163
|
+
|
|
1164
|
+
// ── Vault path ──
|
|
1165
|
+
let vaultPath = opts.vault;
|
|
1166
|
+
|
|
1167
|
+
if (!vaultPath) {
|
|
1168
|
+
const candidates = [
|
|
1169
|
+
path.join(os.homedir(), "Documents", "Obsidian"),
|
|
1170
|
+
path.join(os.homedir(), "Obsidian"),
|
|
1171
|
+
path.join(os.homedir(), "Documents", "Mover-OS"),
|
|
1172
|
+
];
|
|
1173
|
+
|
|
1174
|
+
const detected = candidates.find((p) => {
|
|
1175
|
+
try {
|
|
1176
|
+
return fs.existsSync(p) && (fs.existsSync(path.join(p, ".obsidian")) || fs.existsSync(path.join(p, "02_Areas")));
|
|
1177
|
+
} catch {
|
|
1178
|
+
return false;
|
|
1179
|
+
}
|
|
1180
|
+
});
|
|
1181
|
+
|
|
1182
|
+
const defaultPath = detected || path.join(os.homedir(), "Mover-OS");
|
|
1183
|
+
|
|
1184
|
+
vaultPath = await textInput({
|
|
1185
|
+
label: `Where is your Obsidian vault?${detected ? dim(" (detected)") : ""}`,
|
|
1186
|
+
initial: defaultPath,
|
|
1187
|
+
});
|
|
1188
|
+
} else {
|
|
1189
|
+
barLn(dim(`Vault: ${vaultPath}`));
|
|
1190
|
+
barLn();
|
|
1191
|
+
}
|
|
1192
|
+
|
|
1193
|
+
if (vaultPath.startsWith("~")) vaultPath = path.join(os.homedir(), vaultPath.slice(1));
|
|
1194
|
+
vaultPath = path.resolve(vaultPath);
|
|
1195
|
+
|
|
1196
|
+
// ── Detect existing install → show mode menu ──
|
|
1197
|
+
const engine = detectEngineFiles(vaultPath);
|
|
1198
|
+
const versionFile = path.join(vaultPath, ".mover-version");
|
|
1199
|
+
const hasExistingInstall = fs.existsSync(versionFile) || engine.exists;
|
|
1200
|
+
|
|
1201
|
+
let installMode = "fresh"; // fresh | update | uninstall
|
|
1202
|
+
|
|
1203
|
+
if (hasExistingInstall) {
|
|
1204
|
+
if (engine.exists) {
|
|
1205
|
+
barLn(yellow("Existing Mover OS vault detected."));
|
|
1206
|
+
barLn(dim(` Engine files: ${engine.files.join(", ")}`));
|
|
1207
|
+
} else {
|
|
1208
|
+
barLn(yellow("Mover OS installed, but no Engine data yet."));
|
|
1209
|
+
}
|
|
1210
|
+
barLn();
|
|
1211
|
+
|
|
1212
|
+
question("What would you like to do?");
|
|
1213
|
+
barLn();
|
|
1214
|
+
|
|
1215
|
+
installMode = await interactiveSelect(
|
|
1216
|
+
[
|
|
1217
|
+
{ id: "update", name: "Update", tier: "recommended" },
|
|
1218
|
+
{ id: "fresh", name: "Fresh Install", tier: "overwrites templates" },
|
|
1219
|
+
{ id: "uninstall", name: "Uninstall", tier: "removes agent configs" },
|
|
1220
|
+
],
|
|
1221
|
+
{ multi: false, defaultIndex: 0 }
|
|
1222
|
+
);
|
|
1223
|
+
}
|
|
1224
|
+
|
|
1225
|
+
// ── Uninstall flow ──
|
|
1226
|
+
if (installMode === "uninstall") {
|
|
1227
|
+
await runUninstall(vaultPath);
|
|
1228
|
+
return;
|
|
1229
|
+
}
|
|
1230
|
+
|
|
1231
|
+
const updateMode = installMode === "update";
|
|
1232
|
+
|
|
1233
|
+
if (!fs.existsSync(vaultPath)) fs.mkdirSync(vaultPath, { recursive: true });
|
|
1234
|
+
|
|
1235
|
+
// ── Agent selection ──
|
|
1236
|
+
const detectedIds = AGENTS.filter((a) => a.detect()).map((a) => a.id);
|
|
1237
|
+
const agentItems = AGENTS.map((a) => ({
|
|
1238
|
+
...a,
|
|
1239
|
+
_detected: detectedIds.includes(a.id),
|
|
1240
|
+
}));
|
|
1241
|
+
|
|
1242
|
+
question(`Select your AI agents${detectedIds.length > 0 ? dim(" (detected agents pre-selected)") : ""}`);
|
|
1243
|
+
barLn();
|
|
1244
|
+
|
|
1245
|
+
const selectedIds = await interactiveSelect(agentItems, {
|
|
1246
|
+
multi: true,
|
|
1247
|
+
preSelected: detectedIds,
|
|
1248
|
+
});
|
|
1249
|
+
const selectedAgents = AGENTS.filter((a) => selectedIds.includes(a.id));
|
|
1250
|
+
|
|
1251
|
+
if (selectedAgents.length === 0) {
|
|
1252
|
+
barLn(yellow("No agents selected."));
|
|
1253
|
+
outro("Cancelled.");
|
|
1254
|
+
process.exit(0);
|
|
1255
|
+
}
|
|
1256
|
+
|
|
1257
|
+
// ── Skills ──
|
|
1258
|
+
const allSkills = findSkills(bundleDir);
|
|
1259
|
+
let installSkills = false;
|
|
1260
|
+
|
|
1261
|
+
if (allSkills.length > 0 && selectedAgents.some((a) => ["Full", "Enhanced"].includes(a.tier))) {
|
|
1262
|
+
question(`Install ${bold(String(allSkills.length))} skill packs? ${dim("code review, marketing, security...")}`);
|
|
1263
|
+
barLn();
|
|
1264
|
+
|
|
1265
|
+
const skillChoice = await interactiveSelect(
|
|
1266
|
+
[
|
|
1267
|
+
{ id: "yes", name: "Yes" },
|
|
1268
|
+
{ id: "no", name: "No" },
|
|
1269
|
+
],
|
|
1270
|
+
{ multi: false, defaultIndex: 0 }
|
|
1271
|
+
);
|
|
1272
|
+
installSkills = skillChoice === "yes";
|
|
1273
|
+
}
|
|
1274
|
+
|
|
1275
|
+
// ── Install with animated spinners ──
|
|
1276
|
+
barLn();
|
|
1277
|
+
question(updateMode ? bold("Updating...") : bold("Installing..."));
|
|
1278
|
+
barLn();
|
|
1279
|
+
|
|
1280
|
+
let totalSteps = 0;
|
|
1281
|
+
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
1282
|
+
|
|
1283
|
+
// 1. Vault structure
|
|
1284
|
+
let sp = spinner("Vault structure");
|
|
1285
|
+
const dirsCreated = createVaultStructure(vaultPath);
|
|
1286
|
+
await sleep(200);
|
|
1287
|
+
sp.stop(`Vault structure${dirsCreated > 0 ? dim(` ${dirsCreated} folders`) : dim(" verified")}`);
|
|
1288
|
+
totalSteps++;
|
|
1289
|
+
|
|
1290
|
+
// 2. Template files (skip on update)
|
|
1291
|
+
if (!updateMode) {
|
|
1292
|
+
sp = spinner("Engine templates");
|
|
1293
|
+
const templatesInstalled = installTemplateFiles(bundleDir, vaultPath);
|
|
1294
|
+
await sleep(200);
|
|
1295
|
+
sp.stop(`Engine templates${templatesInstalled > 0 ? dim(` ${templatesInstalled} files`) : dim(" up to date")}`);
|
|
1296
|
+
totalSteps++;
|
|
1297
|
+
}
|
|
1298
|
+
|
|
1299
|
+
// 3. CLAUDE.md (skip on update)
|
|
1300
|
+
if (!updateMode) {
|
|
1301
|
+
const vaultClaudeMd = path.join(vaultPath, "CLAUDE.md");
|
|
1302
|
+
const bundleClaudeMd = path.join(bundleDir, "CLAUDE.md");
|
|
1303
|
+
if (!fs.existsSync(vaultClaudeMd) && fs.existsSync(bundleClaudeMd)) {
|
|
1304
|
+
fs.copyFileSync(bundleClaudeMd, vaultClaudeMd);
|
|
1305
|
+
sp = spinner("CLAUDE.md");
|
|
1306
|
+
await sleep(150);
|
|
1307
|
+
sp.stop("CLAUDE.md");
|
|
1308
|
+
totalSteps++;
|
|
1309
|
+
}
|
|
1310
|
+
}
|
|
1311
|
+
|
|
1312
|
+
// 4. Per-agent installation
|
|
1313
|
+
for (const agent of selectedAgents) {
|
|
1314
|
+
const fn = AGENT_INSTALLERS[agent.id];
|
|
1315
|
+
if (!fn) continue;
|
|
1316
|
+
sp = spinner(agent.name);
|
|
1317
|
+
const steps = agent.id === "antigravity" ? fn(bundleDir) : fn(bundleDir, vaultPath);
|
|
1318
|
+
await sleep(250);
|
|
1319
|
+
if (steps.length > 0) {
|
|
1320
|
+
sp.stop(`${agent.name} ${dim(steps.join(", "))}`);
|
|
1321
|
+
totalSteps++;
|
|
1322
|
+
} else {
|
|
1323
|
+
sp.stop(`${agent.name} ${dim("configured")}`);
|
|
1324
|
+
totalSteps++;
|
|
1325
|
+
}
|
|
1326
|
+
}
|
|
1327
|
+
|
|
1328
|
+
// 5. AGENTS.md — only for agents that need it
|
|
1329
|
+
const needsAgentsMd = selectedAgents.some((a) => ["codex", "amp", "aider", "openclaw"].includes(a.id));
|
|
1330
|
+
if (needsAgentsMd) {
|
|
1331
|
+
fs.writeFileSync(path.join(vaultPath, "AGENTS.md"), generateAgentsMd(), "utf8");
|
|
1332
|
+
sp = spinner("AGENTS.md");
|
|
1333
|
+
await sleep(150);
|
|
1334
|
+
sp.stop("AGENTS.md");
|
|
1335
|
+
totalSteps++;
|
|
1336
|
+
}
|
|
1337
|
+
|
|
1338
|
+
// 6. .gitignore + git init (fresh install only)
|
|
1339
|
+
if (!updateMode) {
|
|
1340
|
+
const hasGit = cmdExists("git");
|
|
1341
|
+
if (hasGit) {
|
|
1342
|
+
const gitignorePath = path.join(vaultPath, ".gitignore");
|
|
1343
|
+
if (!fs.existsSync(gitignorePath)) {
|
|
1344
|
+
fs.writeFileSync(gitignorePath, generateGitignore(), "utf8");
|
|
1345
|
+
sp = spinner(".gitignore");
|
|
1346
|
+
await sleep(100);
|
|
1347
|
+
sp.stop(".gitignore");
|
|
1348
|
+
totalSteps++;
|
|
1349
|
+
}
|
|
1350
|
+
if (!fs.existsSync(path.join(vaultPath, ".git"))) {
|
|
1351
|
+
sp = spinner("Git repository");
|
|
1352
|
+
try {
|
|
1353
|
+
execSync("git init", { cwd: vaultPath, stdio: "ignore" });
|
|
1354
|
+
execSync("git add -A", { cwd: vaultPath, stdio: "ignore" });
|
|
1355
|
+
execSync('git commit -m "Initial commit — Mover OS v' + VERSION + '"', { cwd: vaultPath, stdio: "ignore" });
|
|
1356
|
+
await sleep(300);
|
|
1357
|
+
sp.stop("Git initialized");
|
|
1358
|
+
totalSteps++;
|
|
1359
|
+
} catch {
|
|
1360
|
+
sp.stop(dim("Git skipped"));
|
|
1361
|
+
}
|
|
1362
|
+
}
|
|
1363
|
+
}
|
|
1364
|
+
}
|
|
1365
|
+
|
|
1366
|
+
// 7. Version stamp
|
|
1367
|
+
fs.writeFileSync(path.join(vaultPath, ".mover-version"), `V${VERSION}\n`, "utf8");
|
|
1368
|
+
|
|
1369
|
+
barLn();
|
|
1370
|
+
|
|
1371
|
+
// ── Done ──
|
|
1372
|
+
const elapsed = ((Date.now() - startTime) / 1000).toFixed(1);
|
|
1373
|
+
const verb = updateMode ? "updated" : "installed";
|
|
1374
|
+
outro(`${green("Done.")} Mover OS v${VERSION} ${verb}. ${dim(`${totalSteps} steps in ${elapsed}s`)}`);
|
|
1375
|
+
|
|
1376
|
+
// How to open the vault in each agent
|
|
1377
|
+
const agentOpen = {
|
|
1378
|
+
"claude-code": { cmd: `cd "${vaultPath}" && claude`, name: "Claude Code" },
|
|
1379
|
+
cursor: { cmd: `Open Cursor → File → Open Folder → select vault`, name: "Cursor" },
|
|
1380
|
+
cline: { cmd: `Open VS Code → File → Open Folder → select vault`, name: "Cline (VS Code)" },
|
|
1381
|
+
codex: { cmd: `cd "${vaultPath}" && codex`, name: "Codex" },
|
|
1382
|
+
windsurf: { cmd: `Open Windsurf → File → Open Folder → select vault`, name: "Windsurf" },
|
|
1383
|
+
openclaw: { cmd: `cd "${vaultPath}" && openclaw`, name: "OpenClaw" },
|
|
1384
|
+
antigravity: { cmd: `cd "${vaultPath}" && gemini`, name: "Antigravity" },
|
|
1385
|
+
"roo-code": { cmd: `Open VS Code → File → Open Folder → select vault`, name: "Roo Code" },
|
|
1386
|
+
copilot: { cmd: `Open VS Code → File → Open Folder → select vault`, name: "Copilot" },
|
|
1387
|
+
amp: { cmd: `cd "${vaultPath}" && amp`, name: "Amp" },
|
|
1388
|
+
aider: { cmd: `cd "${vaultPath}" && aider`, name: "Aider" },
|
|
1389
|
+
};
|
|
1390
|
+
|
|
1391
|
+
const primaryAgent = selectedAgents[0];
|
|
1392
|
+
const command = updateMode ? "/update" : "/setup";
|
|
1393
|
+
const agent = agentOpen[primaryAgent.id] || { cmd: "Open your AI agent in the vault", name: "your agent" };
|
|
1394
|
+
|
|
1395
|
+
// Box-drawn summary panel
|
|
1396
|
+
const boxW = 58;
|
|
1397
|
+
const pad = (s, w) => {
|
|
1398
|
+
const visible = strip(s).length;
|
|
1399
|
+
return visible < w ? s + " ".repeat(w - visible) : s;
|
|
1400
|
+
};
|
|
1401
|
+
|
|
1402
|
+
ln(gray(` ┌${"─".repeat(boxW)}┐`));
|
|
1403
|
+
ln(gray(` │`) + pad(` ${bold("Next steps")}`, boxW) + gray(`│`));
|
|
1404
|
+
ln(gray(` │${"─".repeat(boxW)}│`));
|
|
1405
|
+
ln(gray(` │`) + pad(` ${cyan("1")} Open your vault in ${bold("Obsidian")} ${dim("(for viewing files)")}`, boxW) + gray(`│`));
|
|
1406
|
+
ln(gray(` │`) + pad(` ${dim(vaultPath.length > 48 ? "..." + vaultPath.slice(-45) : vaultPath)}`, boxW) + gray(`│`));
|
|
1407
|
+
ln(gray(` │`) + pad(` ${cyan("2")} Open the vault in ${bold(agent.name)} ${dim("(your AI agent)")}`, boxW) + gray(`│`));
|
|
1408
|
+
ln(gray(` │`) + pad(` ${dim(agent.cmd.length > 48 ? agent.cmd.slice(0, 48) + "..." : agent.cmd)}`, boxW) + gray(`│`));
|
|
1409
|
+
ln(gray(` │`) + pad(` ${cyan("3")} Run ${bold(command)} in ${agent.name}`, boxW) + gray(`│`));
|
|
1410
|
+
ln(gray(` │`) + pad(` ${dim(updateMode ? "Syncs your Engine with the latest version" : "Builds your Identity, Strategy, and Goals")}`, boxW) + gray(`│`));
|
|
1411
|
+
ln(gray(` │${" ".repeat(boxW)}│`));
|
|
1412
|
+
ln(gray(` │`) + pad(` ${dim("Obsidian = view your files")}`, boxW) + gray(`│`));
|
|
1413
|
+
ln(gray(` │`) + pad(` ${dim(agent.name + " = where you work with the AI")}`, boxW) + gray(`│`));
|
|
1414
|
+
ln(gray(` │${" ".repeat(boxW)}│`));
|
|
1415
|
+
ln(gray(` │`) + pad(` ${dim("/morning → [work] → /log → /analyse-day → /plan-tomorrow")}`, boxW) + gray(`│`));
|
|
1416
|
+
ln(gray(` └${"─".repeat(boxW)}┘`));
|
|
1417
|
+
ln();
|
|
1418
|
+
}
|
|
1419
|
+
|
|
1420
|
+
main().catch((err) => {
|
|
1421
|
+
cleanup();
|
|
1422
|
+
console.error(red(`\n Error: ${err.message}`));
|
|
1423
|
+
process.exit(1);
|
|
1424
|
+
});
|