@geminilight/mindos 0.3.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/app/app/api/mcp/agents/route.ts +72 -0
- package/app/app/api/mcp/install/route.ts +95 -0
- package/app/app/api/mcp/status/route.ts +47 -0
- package/app/app/api/skills/route.ts +208 -0
- package/app/app/api/sync/route.ts +54 -3
- package/app/app/api/update-check/route.ts +52 -0
- package/app/app/globals.css +12 -0
- package/app/app/layout.tsx +4 -2
- package/app/app/login/page.tsx +20 -13
- package/app/app/page.tsx +17 -2
- package/app/app/view/[...path]/ViewPageClient.tsx +47 -21
- package/app/app/view/[...path]/loading.tsx +1 -1
- package/app/app/view/[...path]/not-found.tsx +101 -0
- package/app/components/AskFab.tsx +1 -1
- package/app/components/AskModal.tsx +1 -1
- package/app/components/Backlinks.tsx +1 -1
- package/app/components/Breadcrumb.tsx +13 -3
- package/app/components/CsvView.tsx +5 -6
- package/app/components/DirView.tsx +42 -21
- package/app/components/FindInPage.tsx +211 -0
- package/app/components/HomeContent.tsx +97 -44
- package/app/components/JsonView.tsx +1 -2
- package/app/components/MarkdownEditor.tsx +1 -2
- package/app/components/OnboardingView.tsx +6 -7
- package/app/components/SettingsModal.tsx +5 -2
- package/app/components/SetupWizard.tsx +4 -4
- package/app/components/Sidebar.tsx +1 -1
- package/app/components/UpdateBanner.tsx +101 -0
- package/app/components/renderers/{AgentInspectorRenderer.tsx → agent-inspector/AgentInspectorRenderer.tsx} +13 -11
- package/app/components/renderers/agent-inspector/manifest.ts +14 -0
- package/app/components/renderers/{BacklinksRenderer.tsx → backlinks/BacklinksRenderer.tsx} +6 -6
- package/app/components/renderers/backlinks/manifest.ts +14 -0
- package/app/components/renderers/config/manifest.ts +14 -0
- package/app/components/renderers/csv/BoardView.tsx +12 -12
- package/app/components/renderers/csv/ConfigPanel.tsx +7 -8
- package/app/components/renderers/{CsvRenderer.tsx → csv/CsvRenderer.tsx} +8 -9
- package/app/components/renderers/csv/GalleryView.tsx +3 -3
- package/app/components/renderers/csv/TableView.tsx +4 -5
- package/app/components/renderers/csv/manifest.ts +14 -0
- package/app/components/renderers/{DiffRenderer.tsx → diff/DiffRenderer.tsx} +10 -9
- package/app/components/renderers/diff/manifest.ts +14 -0
- package/app/components/renderers/{GraphRenderer.tsx → graph/GraphRenderer.tsx} +4 -5
- package/app/components/renderers/graph/manifest.ts +14 -0
- package/app/components/renderers/{SummaryRenderer.tsx → summary/SummaryRenderer.tsx} +6 -6
- package/app/components/renderers/summary/manifest.ts +14 -0
- package/app/components/renderers/{TimelineRenderer.tsx → timeline/TimelineRenderer.tsx} +6 -6
- package/app/components/renderers/timeline/manifest.ts +14 -0
- package/app/components/renderers/{TodoRenderer.tsx → todo/TodoRenderer.tsx} +2 -2
- package/app/components/renderers/todo/manifest.ts +14 -0
- package/app/components/renderers/{WorkflowRenderer.tsx → workflow/WorkflowRenderer.tsx} +13 -13
- package/app/components/renderers/workflow/manifest.ts +14 -0
- package/app/components/settings/McpTab.tsx +549 -0
- package/app/components/settings/SyncTab.tsx +139 -50
- package/app/components/settings/types.ts +1 -1
- package/app/data/pages/home.png +0 -0
- package/app/lib/i18n.ts +178 -10
- package/app/lib/renderers/index.ts +20 -89
- package/app/lib/renderers/registry.ts +4 -1
- package/app/lib/settings.ts +3 -0
- package/app/package.json +1 -0
- package/app/types/semver.d.ts +8 -0
- package/bin/cli.js +137 -24
- package/bin/lib/build.js +53 -18
- package/bin/lib/colors.js +3 -1
- package/bin/lib/config.js +4 -0
- package/bin/lib/constants.js +2 -0
- package/bin/lib/debug.js +10 -0
- package/bin/lib/startup.js +21 -20
- package/bin/lib/stop.js +41 -3
- package/bin/lib/sync.js +65 -53
- package/bin/lib/update-check.js +94 -0
- package/bin/lib/utils.js +2 -2
- package/package.json +1 -1
- package/scripts/gen-renderer-index.js +57 -0
- package/scripts/setup.js +24 -0
- /package/app/components/renderers/{ConfigRenderer.tsx → config/ConfigRenderer.tsx} +0 -0
package/app/package.json
CHANGED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
declare module 'semver' {
|
|
2
|
+
export function gt(v1: string, v2: string): boolean;
|
|
3
|
+
export function lt(v1: string, v2: string): boolean;
|
|
4
|
+
export function gte(v1: string, v2: string): boolean;
|
|
5
|
+
export function lte(v1: string, v2: string): boolean;
|
|
6
|
+
export function eq(v1: string, v2: string): boolean;
|
|
7
|
+
export function valid(v: string | null): string | null;
|
|
8
|
+
}
|
package/bin/cli.js
CHANGED
|
@@ -33,6 +33,7 @@
|
|
|
33
33
|
* mindos logs — tail service logs (~/.mindos/mindos.log)
|
|
34
34
|
* mindos config show — print current config (API keys masked)
|
|
35
35
|
* mindos config set <key> <val> — update a single config field
|
|
36
|
+
* mindos config unset <key> — remove a config field
|
|
36
37
|
* mindos config validate — validate config file
|
|
37
38
|
*/
|
|
38
39
|
|
|
@@ -58,6 +59,15 @@ import { initSync, startSyncDaemon, stopSyncDaemon, getSyncStatus, manualSync, l
|
|
|
58
59
|
// ── Commands ──────────────────────────────────────────────────────────────────
|
|
59
60
|
|
|
60
61
|
const cmd = process.argv[2];
|
|
62
|
+
|
|
63
|
+
// ── --version / -v ──────────────────────────────────────────────────────────
|
|
64
|
+
// --help / -h is handled at entry section (resolvedCmd = null → help block)
|
|
65
|
+
if (cmd === '--version' || cmd === '-v') {
|
|
66
|
+
const version = JSON.parse(readFileSync(resolve(ROOT, 'package.json'), 'utf-8')).version;
|
|
67
|
+
console.log(`mindos/${version} node/${process.version} ${process.platform}-${process.arch}`);
|
|
68
|
+
process.exit(0);
|
|
69
|
+
}
|
|
70
|
+
|
|
61
71
|
const isDaemon = process.argv.includes('--daemon') || (!cmd && isDaemonMode());
|
|
62
72
|
const isVerbose = process.argv.includes('--verbose');
|
|
63
73
|
const extra = process.argv.slice(3).filter(a => a !== '--daemon' && a !== '--verbose').join(' ');
|
|
@@ -68,8 +78,8 @@ const commands = {
|
|
|
68
78
|
const daemonFlag = process.argv.includes('--install-daemon') ? ' --install-daemon' : '';
|
|
69
79
|
run(`node ${resolve(ROOT, 'scripts/setup.js')}${daemonFlag}`);
|
|
70
80
|
},
|
|
71
|
-
init: () =>
|
|
72
|
-
setup: () =>
|
|
81
|
+
init: async () => commands.onboard(),
|
|
82
|
+
setup: async () => commands.onboard(),
|
|
73
83
|
|
|
74
84
|
// ── open ───────────────────────────────────────────────────────────────────
|
|
75
85
|
open: () => {
|
|
@@ -123,8 +133,8 @@ const commands = {
|
|
|
123
133
|
console.log(`${sep}`);
|
|
124
134
|
console.log(`${bold('Claude Code')}`);
|
|
125
135
|
console.log(`${sep}`);
|
|
126
|
-
console.log(dim('
|
|
127
|
-
console.log(dim('\
|
|
136
|
+
console.log(dim('Quick install:') + ` mindos mcp install claude-code -g -y`);
|
|
137
|
+
console.log(dim('\nManual config (~/.claude.json):'));
|
|
128
138
|
console.log(JSON.stringify({
|
|
129
139
|
mcpServers: {
|
|
130
140
|
mindos: {
|
|
@@ -138,8 +148,8 @@ const commands = {
|
|
|
138
148
|
console.log(`\n${sep}`);
|
|
139
149
|
console.log(`${bold('CodeBuddy (Claude Code Internal)')}`);
|
|
140
150
|
console.log(`${sep}`);
|
|
141
|
-
console.log(dim('
|
|
142
|
-
console.log(dim('\
|
|
151
|
+
console.log(dim('Quick install:') + ` mindos mcp install codebuddy -g -y`);
|
|
152
|
+
console.log(dim('\nManual config (~/.claude-internal/.claude.json):'));
|
|
143
153
|
console.log(JSON.stringify({
|
|
144
154
|
mcpServers: {
|
|
145
155
|
mindos: {
|
|
@@ -153,8 +163,8 @@ const commands = {
|
|
|
153
163
|
console.log(`\n${sep}`);
|
|
154
164
|
console.log(`${bold('Cursor')}`);
|
|
155
165
|
console.log(`${sep}`);
|
|
156
|
-
console.log(dim('
|
|
157
|
-
console.log(dim('\
|
|
166
|
+
console.log(dim('Quick install:') + ` mindos mcp install cursor -g -y`);
|
|
167
|
+
console.log(dim('\nManual config (~/.cursor/mcp.json):'));
|
|
158
168
|
console.log(JSON.stringify({
|
|
159
169
|
mcpServers: {
|
|
160
170
|
mindos: {
|
|
@@ -168,7 +178,7 @@ const commands = {
|
|
|
168
178
|
if (localIP) {
|
|
169
179
|
const remoteUrl = `http://${localIP}:${mcpPort}/mcp`;
|
|
170
180
|
console.log(`\n${sep}`);
|
|
171
|
-
console.log(`${bold('Remote (
|
|
181
|
+
console.log(`${bold('Remote (other devices)')}`);
|
|
172
182
|
console.log(`${sep}`);
|
|
173
183
|
console.log(`URL: ${cyan(remoteUrl)}`);
|
|
174
184
|
console.log(JSON.stringify({
|
|
@@ -200,12 +210,21 @@ const commands = {
|
|
|
200
210
|
if (devMindRoot) {
|
|
201
211
|
startSyncDaemon(devMindRoot).catch(() => {});
|
|
202
212
|
}
|
|
203
|
-
printStartupInfo(webPort, mcpPort);
|
|
213
|
+
await printStartupInfo(webPort, mcpPort);
|
|
204
214
|
run(`npx next dev -p ${webPort} ${extra}`, resolve(ROOT, 'app'));
|
|
205
215
|
},
|
|
206
216
|
|
|
207
217
|
// ── start ──────────────────────────────────────────────────────────────────
|
|
208
218
|
start: async () => {
|
|
219
|
+
// Check for incomplete setup
|
|
220
|
+
if (existsSync(CONFIG_PATH)) {
|
|
221
|
+
try {
|
|
222
|
+
const cfg = JSON.parse(readFileSync(CONFIG_PATH, 'utf-8'));
|
|
223
|
+
if (cfg.setupPending === true) {
|
|
224
|
+
console.log(`\n ${yellow('⚠ Setup was not completed.')} Run ${cyan('mindos onboard')} to finish, or ${cyan('mindos config set setupPending false')} to dismiss.\n`);
|
|
225
|
+
}
|
|
226
|
+
} catch {}
|
|
227
|
+
}
|
|
209
228
|
if (isDaemon) {
|
|
210
229
|
const platform = getPlatform();
|
|
211
230
|
if (!platform) {
|
|
@@ -225,13 +244,13 @@ const commands = {
|
|
|
225
244
|
console.error(dim(' Check logs with: mindos logs\n'));
|
|
226
245
|
process.exit(1);
|
|
227
246
|
}
|
|
228
|
-
printStartupInfo(webPort, mcpPort);
|
|
247
|
+
await printStartupInfo(webPort, mcpPort);
|
|
229
248
|
// System notification
|
|
230
249
|
try {
|
|
231
250
|
if (process.platform === 'darwin') {
|
|
232
|
-
execSync(`osascript -e 'display notification "http://localhost:${webPort}" with title "MindOS
|
|
251
|
+
execSync(`osascript -e 'display notification "http://localhost:${webPort}" with title "MindOS Ready"'`, { stdio: 'ignore' });
|
|
233
252
|
} else if (process.platform === 'linux') {
|
|
234
|
-
execSync(`notify-send "MindOS
|
|
253
|
+
execSync(`notify-send "MindOS Ready" "http://localhost:${webPort}"`, { stdio: 'ignore' });
|
|
235
254
|
}
|
|
236
255
|
} catch { /* notification is best-effort */ }
|
|
237
256
|
console.log(`${green('✔ MindOS is running as a background service')}`);
|
|
@@ -250,6 +269,7 @@ const commands = {
|
|
|
250
269
|
if (needsBuild()) {
|
|
251
270
|
console.log(yellow('Building MindOS (first run or new version detected)...\n'));
|
|
252
271
|
cleanNextDir();
|
|
272
|
+
run('node scripts/gen-renderer-index.js', ROOT);
|
|
253
273
|
run('npx next build', resolve(ROOT, 'app'));
|
|
254
274
|
writeBuildStamp();
|
|
255
275
|
}
|
|
@@ -261,7 +281,7 @@ const commands = {
|
|
|
261
281
|
if (mindRoot) {
|
|
262
282
|
startSyncDaemon(mindRoot).catch(() => {});
|
|
263
283
|
}
|
|
264
|
-
printStartupInfo(webPort, mcpPort);
|
|
284
|
+
await printStartupInfo(webPort, mcpPort);
|
|
265
285
|
run(`npx next start -p ${webPort} ${extra}`, resolve(ROOT, 'app'));
|
|
266
286
|
},
|
|
267
287
|
|
|
@@ -269,6 +289,7 @@ const commands = {
|
|
|
269
289
|
build: () => {
|
|
270
290
|
ensureAppDeps();
|
|
271
291
|
cleanNextDir();
|
|
292
|
+
run('node scripts/gen-renderer-index.js', ROOT);
|
|
272
293
|
run(`npx next build ${extra}`, resolve(ROOT, 'app'));
|
|
273
294
|
writeBuildStamp();
|
|
274
295
|
},
|
|
@@ -466,6 +487,23 @@ ${dim('Shortcut: mindos start --daemon → install + start in one step')}
|
|
|
466
487
|
}
|
|
467
488
|
}
|
|
468
489
|
|
|
490
|
+
// 9. Update check
|
|
491
|
+
try {
|
|
492
|
+
const { checkForUpdate } = await import('./lib/update-check.js');
|
|
493
|
+
const latestVersion = await Promise.race([
|
|
494
|
+
checkForUpdate(),
|
|
495
|
+
new Promise(r => setTimeout(() => r(null), 4000)),
|
|
496
|
+
]);
|
|
497
|
+
if (latestVersion) {
|
|
498
|
+
const currentVersion = (() => { try { return JSON.parse(readFileSync(resolve(ROOT, 'package.json'), 'utf-8')).version; } catch { return '?'; } })();
|
|
499
|
+
warn(`Update available: v${currentVersion} → ${bold(`v${latestVersion}`)} ${dim('run `mindos update`')}`);
|
|
500
|
+
} else {
|
|
501
|
+
ok('MindOS is up to date');
|
|
502
|
+
}
|
|
503
|
+
} catch {
|
|
504
|
+
warn('Could not check for updates');
|
|
505
|
+
}
|
|
506
|
+
|
|
469
507
|
console.log(hasError
|
|
470
508
|
? `\n${red('Some checks failed.')} Run ${cyan('mindos onboard')} to reconfigure.\n`
|
|
471
509
|
: `\n${green('All checks passed.')}\n`);
|
|
@@ -575,7 +613,7 @@ ${dim('Shortcut: mindos start --daemon → install + start in one step')}
|
|
|
575
613
|
display.authToken = maskKey(display.authToken);
|
|
576
614
|
if (display.webPassword)
|
|
577
615
|
display.webPassword = maskKey(display.webPassword);
|
|
578
|
-
console.log(`\n${bold('📋 MindOS Config')} ${dim(CONFIG_PATH)}\n`);
|
|
616
|
+
console.log(`\n${bold('📋 MindOS Config')} ${dim(`v${(() => { try { return JSON.parse(readFileSync(resolve(ROOT, 'package.json'), 'utf-8')).version; } catch { return '?'; } })()}`)} ${dim(CONFIG_PATH)}\n`);
|
|
579
617
|
console.log(JSON.stringify(display, null, 2));
|
|
580
618
|
console.log();
|
|
581
619
|
return;
|
|
@@ -640,13 +678,50 @@ ${dim('Shortcut: mindos start --daemon → install + start in one step')}
|
|
|
640
678
|
if (typeof obj[parts[i]] !== 'object' || !obj[parts[i]]) obj[parts[i]] = {};
|
|
641
679
|
obj = obj[parts[i]];
|
|
642
680
|
}
|
|
643
|
-
|
|
681
|
+
// Coerce string values to appropriate types
|
|
682
|
+
function coerceValue(v) {
|
|
683
|
+
if (v === 'true') return true;
|
|
684
|
+
if (v === 'false') return false;
|
|
685
|
+
if (v === 'null') return null;
|
|
686
|
+
if (v === '""' || v === "''") return '';
|
|
687
|
+
if (v.trim() !== '' && !isNaN(Number(v))) return Number(v);
|
|
688
|
+
return v;
|
|
689
|
+
}
|
|
690
|
+
const coerced = coerceValue(val);
|
|
644
691
|
obj[parts[parts.length - 1]] = coerced;
|
|
645
692
|
writeFileSync(CONFIG_PATH, JSON.stringify(config, null, 2), 'utf-8');
|
|
646
693
|
console.log(`${green('✔')} Set ${cyan(key)} = ${bold(String(coerced))}`);
|
|
647
694
|
return;
|
|
648
695
|
}
|
|
649
696
|
|
|
697
|
+
if (sub === 'unset') {
|
|
698
|
+
const key = process.argv[4];
|
|
699
|
+
if (!key) {
|
|
700
|
+
console.error(red('Usage: mindos config unset <key>'));
|
|
701
|
+
process.exit(1);
|
|
702
|
+
}
|
|
703
|
+
if (!existsSync(CONFIG_PATH)) {
|
|
704
|
+
console.error(red('No config found. Run `mindos onboard` first.'));
|
|
705
|
+
process.exit(1);
|
|
706
|
+
}
|
|
707
|
+
let config;
|
|
708
|
+
try { config = JSON.parse(readFileSync(CONFIG_PATH, 'utf-8')); } catch {
|
|
709
|
+
console.error(red('Failed to parse config file.'));
|
|
710
|
+
process.exit(1);
|
|
711
|
+
}
|
|
712
|
+
const parts = key.split('.');
|
|
713
|
+
let obj = config;
|
|
714
|
+
for (let i = 0; i < parts.length - 1; i++) {
|
|
715
|
+
if (!obj[parts[i]]) { console.log(dim(`Key "${key}" not found`)); return; }
|
|
716
|
+
obj = obj[parts[i]];
|
|
717
|
+
}
|
|
718
|
+
if (!(parts[parts.length - 1] in obj)) { console.log(dim(`Key "${key}" not found`)); return; }
|
|
719
|
+
delete obj[parts[parts.length - 1]];
|
|
720
|
+
writeFileSync(CONFIG_PATH, JSON.stringify(config, null, 2), 'utf-8');
|
|
721
|
+
console.log(`${green('✔')} Removed ${cyan(key)}`);
|
|
722
|
+
return;
|
|
723
|
+
}
|
|
724
|
+
|
|
650
725
|
// no subcommand or unknown → show help
|
|
651
726
|
const row = (c, d) => ` ${cyan(c.padEnd(32))}${dim(d)}`;
|
|
652
727
|
console.log(`
|
|
@@ -656,10 +731,13 @@ ${bold('Subcommands:')}
|
|
|
656
731
|
${row('mindos config show', 'Print current config (API keys masked)')}
|
|
657
732
|
${row('mindos config validate', 'Validate config file')}
|
|
658
733
|
${row('mindos config set <key> <v>', 'Update a single field (dot-notation supported)')}
|
|
734
|
+
${row('mindos config unset <key>', 'Remove a config field')}
|
|
659
735
|
|
|
660
736
|
${bold('Examples:')}
|
|
661
737
|
${dim('mindos config set port 3002')}
|
|
662
738
|
${dim('mindos config set ai.provider openai')}
|
|
739
|
+
${dim('mindos config set setupPending false')}
|
|
740
|
+
${dim('mindos config unset webPassword')}
|
|
663
741
|
`);
|
|
664
742
|
},
|
|
665
743
|
|
|
@@ -670,7 +748,22 @@ ${bold('Examples:')}
|
|
|
670
748
|
const mindRoot = process.env.MIND_ROOT;
|
|
671
749
|
|
|
672
750
|
if (sub === 'init') {
|
|
673
|
-
|
|
751
|
+
// Parse --non-interactive --remote <url> --branch <branch> --token <token>
|
|
752
|
+
const args = process.argv.slice(4);
|
|
753
|
+
const flagIdx = (flag) => args.indexOf(flag);
|
|
754
|
+
const flagVal = (flag) => { const i = flagIdx(flag); return i >= 0 && i + 1 < args.length ? args[i + 1] : ''; };
|
|
755
|
+
const nonInteractive = args.includes('--non-interactive');
|
|
756
|
+
|
|
757
|
+
if (nonInteractive) {
|
|
758
|
+
await initSync(mindRoot, {
|
|
759
|
+
nonInteractive: true,
|
|
760
|
+
remote: flagVal('--remote'),
|
|
761
|
+
token: flagVal('--token'),
|
|
762
|
+
branch: flagVal('--branch') || 'main',
|
|
763
|
+
});
|
|
764
|
+
} else {
|
|
765
|
+
await initSync(mindRoot);
|
|
766
|
+
}
|
|
674
767
|
return;
|
|
675
768
|
}
|
|
676
769
|
|
|
@@ -695,6 +788,16 @@ ${bold('Examples:')}
|
|
|
695
788
|
return;
|
|
696
789
|
}
|
|
697
790
|
|
|
791
|
+
// Unknown subcommand check
|
|
792
|
+
if (sub) {
|
|
793
|
+
const validSubs = ['init', 'now', 'conflicts', 'on', 'off'];
|
|
794
|
+
if (!validSubs.includes(sub)) {
|
|
795
|
+
console.error(red(`Unknown sync subcommand: ${sub}`));
|
|
796
|
+
console.error(dim(`Available: ${validSubs.join(' | ')}`));
|
|
797
|
+
process.exit(1);
|
|
798
|
+
}
|
|
799
|
+
}
|
|
800
|
+
|
|
698
801
|
// default: sync status
|
|
699
802
|
const status = getSyncStatus(mindRoot);
|
|
700
803
|
if (!status.enabled) {
|
|
@@ -727,15 +830,16 @@ ${bold('Examples:')}
|
|
|
727
830
|
|
|
728
831
|
// ── Entry ─────────────────────────────────────────────────────────────────────
|
|
729
832
|
|
|
730
|
-
const resolvedCmd = cmd || (existsSync(CONFIG_PATH) ? getStartMode() : null);
|
|
833
|
+
const resolvedCmd = (cmd === '--help' || cmd === '-h') ? null : (cmd || (existsSync(CONFIG_PATH) ? getStartMode() : null));
|
|
731
834
|
|
|
732
835
|
if (!resolvedCmd || !commands[resolvedCmd]) {
|
|
836
|
+
const pkgVersion = (() => { try { return JSON.parse(readFileSync(resolve(ROOT, 'package.json'), 'utf-8')).version; } catch { return '?'; } })();
|
|
733
837
|
const row = (c, d) => ` ${cyan(c.padEnd(36))}${dim(d)}`;
|
|
734
838
|
console.log(`
|
|
735
|
-
${bold('🧠 MindOS CLI')}
|
|
839
|
+
${bold('🧠 MindOS CLI')} ${dim(`v${pkgVersion}`)}
|
|
736
840
|
|
|
737
|
-
${bold('
|
|
738
|
-
${row('mindos onboard', 'Interactive setup (
|
|
841
|
+
${bold('Core:')}
|
|
842
|
+
${row('mindos onboard', 'Interactive setup (aliases: init, setup)')}
|
|
739
843
|
${row('mindos onboard --install-daemon', 'Setup + install & start as background OS service')}
|
|
740
844
|
${row('mindos start', 'Start app + MCP server (production, auto-rebuilds if needed)')}
|
|
741
845
|
${row('mindos start --daemon', 'Install + start as background OS service (survives terminal close)')}
|
|
@@ -745,19 +849,28 @@ ${row('mindos dev --turbopack', 'Start with Turbopack (faster HMR)')}
|
|
|
745
849
|
${row('mindos stop', 'Stop running MindOS processes')}
|
|
746
850
|
${row('mindos restart', 'Stop then start again')}
|
|
747
851
|
${row('mindos build', 'Build the app for production')}
|
|
852
|
+
${row('mindos open', 'Open Web UI in the default browser')}
|
|
853
|
+
|
|
854
|
+
${bold('MCP:')}
|
|
748
855
|
${row('mindos mcp', 'Start MCP server only')}
|
|
749
856
|
${row('mindos mcp install [agent]', 'Install MindOS MCP config into Agent (claude-code/cursor/windsurf/…) [-g]')}
|
|
750
|
-
${row('mindos open', 'Open Web UI in the default browser')}
|
|
751
857
|
${row('mindos token', 'Show current auth token and MCP config snippet')}
|
|
858
|
+
|
|
859
|
+
${bold('Sync:')}
|
|
752
860
|
${row('mindos sync', 'Show sync status (init/now/conflicts/on/off)')}
|
|
861
|
+
|
|
862
|
+
${bold('Gateway (Background Service):')}
|
|
753
863
|
${row('mindos gateway <subcommand>', 'Manage background service (install/uninstall/start/stop/status/logs)')}
|
|
864
|
+
|
|
865
|
+
${bold('Config & Diagnostics:')}
|
|
866
|
+
${row('mindos config <subcommand>', 'View/update config (show/validate/set/unset)')}
|
|
754
867
|
${row('mindos doctor', 'Health check (config, ports, build, daemon)')}
|
|
755
868
|
${row('mindos update', 'Update MindOS to the latest version')}
|
|
756
869
|
${row('mindos logs', 'Tail service logs (~/.mindos/mindos.log)')}
|
|
757
|
-
${row('mindos config <subcommand>', 'View/update config (show/validate/set)')}
|
|
758
870
|
${row('mindos', 'Start using mode saved in ~/.mindos/config.json')}
|
|
759
871
|
`);
|
|
760
|
-
|
|
872
|
+
const isHelp = (cmd === '--help' || cmd === '-h');
|
|
873
|
+
process.exit((cmd && !isHelp) ? 1 : 0);
|
|
761
874
|
}
|
|
762
875
|
|
|
763
876
|
commands[resolvedCmd]();
|
package/bin/lib/build.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { execSync } from 'node:child_process';
|
|
2
2
|
import { existsSync, readFileSync, writeFileSync, rmSync } from 'node:fs';
|
|
3
|
+
import { createHash } from 'node:crypto';
|
|
3
4
|
import { resolve } from 'node:path';
|
|
4
|
-
import { ROOT, BUILD_STAMP } from './constants.js';
|
|
5
|
+
import { ROOT, BUILD_STAMP, DEPS_STAMP } from './constants.js';
|
|
5
6
|
import { red, dim, yellow } from './colors.js';
|
|
6
7
|
import { run } from './utils.js';
|
|
7
8
|
|
|
@@ -36,24 +37,58 @@ export function cleanNextDir() {
|
|
|
36
37
|
}
|
|
37
38
|
}
|
|
38
39
|
|
|
40
|
+
function depsHash() {
|
|
41
|
+
const lockPath = resolve(ROOT, 'app', 'package-lock.json');
|
|
42
|
+
try {
|
|
43
|
+
const content = readFileSync(lockPath);
|
|
44
|
+
return createHash('sha256').update(content).digest('hex').slice(0, 16);
|
|
45
|
+
} catch {
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function depsChanged() {
|
|
51
|
+
const currentHash = depsHash();
|
|
52
|
+
if (!currentHash) return true;
|
|
53
|
+
try {
|
|
54
|
+
const savedHash = readFileSync(DEPS_STAMP, 'utf-8').trim();
|
|
55
|
+
return savedHash !== currentHash;
|
|
56
|
+
} catch {
|
|
57
|
+
return true;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function writeDepsStamp() {
|
|
62
|
+
const hash = depsHash();
|
|
63
|
+
if (hash) {
|
|
64
|
+
try { writeFileSync(DEPS_STAMP, hash, 'utf-8'); } catch {}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
39
68
|
export function ensureAppDeps() {
|
|
40
69
|
const appNext = resolve(ROOT, 'app', 'node_modules', 'next', 'package.json');
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
run('npm install --prefer-offline --no-workspaces', resolve(ROOT, 'app'));
|
|
70
|
+
const needsInstall = !existsSync(appNext) || depsChanged();
|
|
71
|
+
if (!needsInstall) return;
|
|
72
|
+
|
|
73
|
+
try {
|
|
74
|
+
execSync('npm --version', { stdio: 'pipe' });
|
|
75
|
+
} catch {
|
|
76
|
+
console.error(red('\n\u2718 npm not found in PATH.\n'));
|
|
77
|
+
console.error(' MindOS needs npm to install its app dependencies on first run.');
|
|
78
|
+
console.error(' This usually means Node.js is installed via a version manager (nvm, fnm, volta, etc.)');
|
|
79
|
+
console.error(' that only loads in interactive shells, but not in /bin/sh.\n');
|
|
80
|
+
console.error(' Fix: add your Node.js bin directory to a profile that /bin/sh reads (~/.profile).');
|
|
81
|
+
console.error(' Example:');
|
|
82
|
+
console.error(dim(' echo \'export PATH="$HOME/.nvm/versions/node/$(node --version)/bin:$PATH"\' >> ~/.profile'));
|
|
83
|
+
console.error(dim(' source ~/.profile\n'));
|
|
84
|
+
console.error(' Then run `mindos start` again.\n');
|
|
85
|
+
process.exit(1);
|
|
58
86
|
}
|
|
87
|
+
|
|
88
|
+
const label = existsSync(appNext)
|
|
89
|
+
? 'Updating app dependencies (package-lock.json changed)...\n'
|
|
90
|
+
: 'Installing app dependencies (first run)...\n';
|
|
91
|
+
console.log(yellow(label));
|
|
92
|
+
run('npm install --prefer-offline --no-workspaces', resolve(ROOT, 'app'));
|
|
93
|
+
writeDepsStamp();
|
|
59
94
|
}
|
package/bin/lib/colors.js
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
|
|
1
|
+
const noColor = 'NO_COLOR' in process.env;
|
|
2
|
+
const forceColor = process.env.FORCE_COLOR !== undefined && process.env.FORCE_COLOR !== '0';
|
|
3
|
+
export const isTTY = noColor ? false : (forceColor || process.stdout.isTTY);
|
|
2
4
|
export const bold = (s) => isTTY ? `\x1b[1m${s}\x1b[0m` : s;
|
|
3
5
|
export const dim = (s) => isTTY ? `\x1b[2m${s}\x1b[0m` : s;
|
|
4
6
|
export const cyan = (s) => isTTY ? `\x1b[36m${s}\x1b[0m` : s;
|
package/bin/lib/config.js
CHANGED
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
import { existsSync, readFileSync } from 'node:fs';
|
|
2
2
|
import { CONFIG_PATH } from './constants.js';
|
|
3
3
|
|
|
4
|
+
let loaded = false;
|
|
5
|
+
|
|
4
6
|
export function loadConfig() {
|
|
7
|
+
if (loaded) return;
|
|
8
|
+
loaded = true;
|
|
5
9
|
if (!existsSync(CONFIG_PATH)) return;
|
|
6
10
|
let config;
|
|
7
11
|
try {
|
package/bin/lib/constants.js
CHANGED
|
@@ -11,3 +11,5 @@ export const MINDOS_DIR = resolve(homedir(), '.mindos');
|
|
|
11
11
|
export const LOG_PATH = resolve(MINDOS_DIR, 'mindos.log');
|
|
12
12
|
export const CLI_PATH = resolve(__dirname, '..', 'cli.js');
|
|
13
13
|
export const NODE_BIN = process.execPath;
|
|
14
|
+
export const UPDATE_CHECK_PATH = resolve(MINDOS_DIR, 'update-check.json');
|
|
15
|
+
export const DEPS_STAMP = resolve(MINDOS_DIR, 'deps-hash');
|
package/bin/lib/debug.js
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { dim } from './colors.js';
|
|
2
|
+
|
|
3
|
+
const enabled = process.env.MINDOS_DEBUG === '1' || process.argv.includes('--verbose');
|
|
4
|
+
|
|
5
|
+
export function debug(...args) {
|
|
6
|
+
if (enabled) {
|
|
7
|
+
const ts = new Date().toISOString().slice(11, 23);
|
|
8
|
+
console.error(dim(`[${ts}]`), ...args);
|
|
9
|
+
}
|
|
10
|
+
}
|
package/bin/lib/startup.js
CHANGED
|
@@ -3,6 +3,7 @@ import { networkInterfaces } from 'node:os';
|
|
|
3
3
|
import { CONFIG_PATH } from './constants.js';
|
|
4
4
|
import { bold, dim, cyan, green, yellow } from './colors.js';
|
|
5
5
|
import { getSyncStatus } from './sync.js';
|
|
6
|
+
import { checkForUpdate, printUpdateHint } from './update-check.js';
|
|
6
7
|
|
|
7
8
|
export function getLocalIP() {
|
|
8
9
|
try {
|
|
@@ -15,39 +16,32 @@ export function getLocalIP() {
|
|
|
15
16
|
return null;
|
|
16
17
|
}
|
|
17
18
|
|
|
18
|
-
export function printStartupInfo(webPort, mcpPort) {
|
|
19
|
+
export async function printStartupInfo(webPort, mcpPort) {
|
|
20
|
+
// Fire update check immediately (non-blocking)
|
|
21
|
+
const updatePromise = checkForUpdate().catch(() => null);
|
|
22
|
+
|
|
19
23
|
let config = {};
|
|
20
24
|
try { config = JSON.parse(readFileSync(CONFIG_PATH, 'utf-8')); } catch { /* ignore */ }
|
|
21
25
|
const authToken = config.authToken || '';
|
|
22
26
|
const localIP = getLocalIP();
|
|
23
27
|
|
|
24
|
-
const auth = authToken
|
|
25
|
-
? `,\n "headers": { "Authorization": "Bearer ${authToken}" }`
|
|
26
|
-
: '';
|
|
27
|
-
const block = (host) =>
|
|
28
|
-
` {\n "mcpServers": {\n "mindos": {\n "url": "http://${host}:${mcpPort}/mcp"${auth}\n }\n }\n }`;
|
|
29
|
-
|
|
30
28
|
console.log(`\n${'─'.repeat(53)}`);
|
|
31
29
|
console.log(`${bold('🧠 MindOS is starting')}\n`);
|
|
32
30
|
console.log(` ${green('●')} Web UI ${cyan(`http://localhost:${webPort}`)}`);
|
|
33
31
|
if (localIP) console.log(` ${cyan(`http://${localIP}:${webPort}`)}`);
|
|
34
32
|
console.log(` ${green('●')} MCP ${cyan(`http://localhost:${mcpPort}/mcp`)}`);
|
|
35
33
|
if (localIP) console.log(` ${cyan(`http://${localIP}:${mcpPort}/mcp`)}`);
|
|
36
|
-
|
|
37
|
-
console.log();
|
|
38
|
-
console.log(bold('Configure MCP in your Agent:'));
|
|
39
|
-
console.log(dim(' Local (same machine):'));
|
|
40
|
-
console.log(block('localhost'));
|
|
41
|
-
if (localIP) {
|
|
42
|
-
console.log(dim('\n Remote (other device):'));
|
|
43
|
-
console.log(block(localIP));
|
|
44
|
-
}
|
|
34
|
+
|
|
45
35
|
if (authToken) {
|
|
46
|
-
|
|
47
|
-
console.log(dim('
|
|
36
|
+
const maskedToken = authToken.length > 8 ? authToken.slice(0, 8) + '····' : (authToken.length > 4 ? authToken.slice(0, 4) + '····' : '····');
|
|
37
|
+
console.log(` ${green('●')} Auth ${cyan(maskedToken)} ${dim('(run `mindos token` for full config)')}`);
|
|
48
38
|
}
|
|
49
|
-
|
|
50
|
-
|
|
39
|
+
|
|
40
|
+
// MCP quick-connect hint
|
|
41
|
+
console.log(`\n ${dim('Quick connect:')} ${cyan('mindos mcp install claude-code -g -y')}`);
|
|
42
|
+
console.log(` ${dim('Full config:')} ${cyan('mindos token')}`);
|
|
43
|
+
|
|
44
|
+
if (localIP) console.log(dim(`\n 💡 Remote? SSH port forwarding: ssh -L ${webPort}:localhost:${webPort} -L ${mcpPort}:localhost:${mcpPort} user@${localIP}`));
|
|
51
45
|
|
|
52
46
|
// Sync status
|
|
53
47
|
const mindRoot = config.mindRoot;
|
|
@@ -70,5 +64,12 @@ export function printStartupInfo(webPort, mcpPort) {
|
|
|
70
64
|
} catch { /* sync check is best-effort */ }
|
|
71
65
|
}
|
|
72
66
|
|
|
67
|
+
// Wait for update check result (max 4s, then give up)
|
|
68
|
+
const latestVersion = await Promise.race([
|
|
69
|
+
updatePromise,
|
|
70
|
+
new Promise(r => setTimeout(() => r(null), 4000)),
|
|
71
|
+
]);
|
|
72
|
+
if (latestVersion) printUpdateHint(latestVersion);
|
|
73
|
+
|
|
73
74
|
console.log(`${'─'.repeat(53)}\n`);
|
|
74
75
|
}
|
package/bin/lib/stop.js
CHANGED
|
@@ -1,13 +1,51 @@
|
|
|
1
1
|
import { execSync } from 'node:child_process';
|
|
2
|
+
import { readFileSync } from 'node:fs';
|
|
2
3
|
import { green, yellow, dim } from './colors.js';
|
|
3
4
|
import { loadPids, clearPids } from './pid.js';
|
|
5
|
+
import { CONFIG_PATH } from './constants.js';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Kill processes listening on the given port.
|
|
9
|
+
* Returns number of processes killed.
|
|
10
|
+
*/
|
|
11
|
+
function killByPort(port) {
|
|
12
|
+
let killed = 0;
|
|
13
|
+
try {
|
|
14
|
+
const output = execSync(`lsof -ti :${port} 2>/dev/null`, { encoding: 'utf-8' }).trim();
|
|
15
|
+
if (output) {
|
|
16
|
+
for (const p of output.split('\n')) {
|
|
17
|
+
const pid = Number(p);
|
|
18
|
+
if (pid > 0) {
|
|
19
|
+
try { process.kill(pid, 'SIGTERM'); killed++; } catch {}
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
} catch {
|
|
24
|
+
// lsof not available or no processes found
|
|
25
|
+
}
|
|
26
|
+
return killed;
|
|
27
|
+
}
|
|
4
28
|
|
|
5
29
|
export function stopMindos() {
|
|
6
30
|
const pids = loadPids();
|
|
7
31
|
if (!pids.length) {
|
|
8
|
-
console.log(yellow('No PID file found, trying
|
|
9
|
-
|
|
10
|
-
|
|
32
|
+
console.log(yellow('No PID file found, trying port-based stop...'));
|
|
33
|
+
// Read ports from config
|
|
34
|
+
let webPort = '3000', mcpPort = '8787';
|
|
35
|
+
try {
|
|
36
|
+
const config = JSON.parse(readFileSync(CONFIG_PATH, 'utf-8'));
|
|
37
|
+
if (config.port) webPort = String(config.port);
|
|
38
|
+
if (config.mcpPort) mcpPort = String(config.mcpPort);
|
|
39
|
+
} catch {}
|
|
40
|
+
let stopped = 0;
|
|
41
|
+
for (const port of [webPort, mcpPort]) {
|
|
42
|
+
stopped += killByPort(port);
|
|
43
|
+
}
|
|
44
|
+
if (stopped === 0) {
|
|
45
|
+
// Fallback: pkill pattern match (for envs without lsof)
|
|
46
|
+
try { execSync('pkill -f "next start|next dev" 2>/dev/null || true', { stdio: 'inherit' }); } catch {}
|
|
47
|
+
try { execSync('pkill -f "mcp/src/index" 2>/dev/null || true', { stdio: 'inherit' }); } catch {}
|
|
48
|
+
}
|
|
11
49
|
console.log(green('\u2714 Done'));
|
|
12
50
|
return;
|
|
13
51
|
}
|