@geminilight/mindos 0.5.14 → 0.5.16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +9 -1
- package/README_zh.md +9 -1
- package/app/lib/agent/tools.ts +29 -25
- package/app/package-lock.json +15571 -0
- package/bin/cli.js +160 -11
- package/bin/lib/build.js +7 -6
- package/bin/lib/gateway.js +51 -21
- package/bin/lib/stop.js +2 -2
- package/mcp/package.json +1 -1
- package/package.json +2 -3
- package/scripts/setup.js +20 -4
- package/skills/project-wiki/SKILL.md +186 -9
- package/skills/project-wiki/assets/ref.tmpl.md +34 -0
- package/skills/project-wiki/assets/review.tmpl.md +27 -0
- package/skills/project-wiki/assets/spec.tmpl.md +45 -0
- package/skills/project-wiki/references/file-reference.md +63 -0
package/bin/cli.js
CHANGED
|
@@ -29,6 +29,7 @@
|
|
|
29
29
|
* mindos gateway status — show service status
|
|
30
30
|
* mindos gateway logs — tail service logs
|
|
31
31
|
* mindos doctor — health check (config, ports, build, daemon)
|
|
32
|
+
* mindos uninstall — fully uninstall MindOS (stop, remove daemon, npm uninstall)
|
|
32
33
|
* mindos update — update to latest version
|
|
33
34
|
* mindos logs — tail service logs (~/.mindos/mindos.log)
|
|
34
35
|
* mindos config show — print current config (API keys masked)
|
|
@@ -42,7 +43,7 @@ import { existsSync, readFileSync, writeFileSync, rmSync } from 'node:fs';
|
|
|
42
43
|
import { resolve } from 'node:path';
|
|
43
44
|
import { homedir } from 'node:os';
|
|
44
45
|
|
|
45
|
-
import { ROOT, CONFIG_PATH, BUILD_STAMP, LOG_PATH } from './lib/constants.js';
|
|
46
|
+
import { ROOT, CONFIG_PATH, BUILD_STAMP, LOG_PATH, MINDOS_DIR } from './lib/constants.js';
|
|
46
47
|
import { bold, dim, cyan, green, red, yellow } from './lib/colors.js';
|
|
47
48
|
import { run } from './lib/utils.js';
|
|
48
49
|
import { loadConfig, getStartMode, isDaemonMode } from './lib/config.js';
|
|
@@ -242,7 +243,7 @@ const commands = {
|
|
|
242
243
|
// causing a port-conflict race condition with KeepAlive restart loops.
|
|
243
244
|
console.log(dim(' (First run may take a few minutes to install dependencies and build the app.)'));
|
|
244
245
|
console.log(dim(' Follow live progress with: mindos logs\n'));
|
|
245
|
-
const ready = await waitForHttp(Number(webPort), { retries:
|
|
246
|
+
const ready = await waitForHttp(Number(webPort), { retries: 60, intervalMs: 2000, label: 'Web UI' });
|
|
246
247
|
if (!ready) {
|
|
247
248
|
console.error(red('\n✘ Service started but Web UI did not become ready in time.'));
|
|
248
249
|
console.error(dim(' Check logs with: mindos logs\n'));
|
|
@@ -604,31 +605,178 @@ ${dim('Shortcut: mindos start --daemon → install + start in one step')}
|
|
|
604
605
|
console.log(cyan('\n Daemon is running — restarting to apply the new version...'));
|
|
605
606
|
await runGatewayCommand('stop');
|
|
606
607
|
await runGatewayCommand('install');
|
|
607
|
-
//
|
|
608
|
-
//
|
|
609
|
-
//
|
|
610
|
-
|
|
611
|
-
|
|
608
|
+
// install() starts the service:
|
|
609
|
+
// - systemd: daemon-reload + enable + start
|
|
610
|
+
// - launchd: bootstrap (RunAtLoad=true auto-starts)
|
|
611
|
+
// Do NOT call start() again — on macOS kickstart -k would kill the
|
|
612
|
+
// just-started process, causing a port-conflict race with KeepAlive.
|
|
613
|
+
const updateConfig = (() => {
|
|
614
|
+
try { return JSON.parse(readFileSync(CONFIG_PATH, 'utf-8')); } catch { return {}; }
|
|
612
615
|
})();
|
|
616
|
+
const webPort = updateConfig.port ?? 3456;
|
|
617
|
+
const mcpPort = updateConfig.mcpPort ?? 8781;
|
|
613
618
|
console.log(dim(' (Waiting for Web UI to come back up — first run after update includes a rebuild...)'));
|
|
614
|
-
const ready = await waitForHttp(Number(webPort), { retries:
|
|
619
|
+
const ready = await waitForHttp(Number(webPort), { retries: 60, intervalMs: 2000, label: 'Web UI' });
|
|
615
620
|
if (ready) {
|
|
616
|
-
|
|
621
|
+
const localIP = getLocalIP();
|
|
622
|
+
console.log(`\n${'─'.repeat(53)}`);
|
|
623
|
+
console.log(`${green('✔')} ${bold(`MindOS updated: ${currentVersion} → ${newVersion}`)}\n`);
|
|
624
|
+
console.log(` ${green('●')} Web UI ${cyan(`http://localhost:${webPort}`)}`);
|
|
625
|
+
if (localIP) console.log(` ${cyan(`http://${localIP}:${webPort}`)}`);
|
|
626
|
+
console.log(` ${green('●')} MCP ${cyan(`http://localhost:${mcpPort}/mcp`)}`);
|
|
627
|
+
console.log(`\n ${dim('View changelog:')} ${cyan('https://github.com/GeminiLight/MindOS/releases')}`);
|
|
628
|
+
console.log(`${'─'.repeat(53)}\n`);
|
|
617
629
|
} else {
|
|
618
630
|
console.error(red('✘ MindOS did not come back up in time. Check logs: mindos logs\n'));
|
|
619
631
|
process.exit(1);
|
|
620
632
|
}
|
|
621
633
|
} else {
|
|
622
|
-
console.log(
|
|
634
|
+
console.log(`\n${green('✔')} ${bold(`Updated: ${currentVersion} → ${newVersion}`)}`);
|
|
635
|
+
console.log(dim(' Run `mindos start` — it will rebuild automatically.'));
|
|
636
|
+
console.log(` ${dim('View changelog:')} ${cyan('https://github.com/GeminiLight/MindOS/releases')}\n`);
|
|
623
637
|
}
|
|
624
638
|
},
|
|
625
639
|
|
|
640
|
+
// ── uninstall ──────────────────────────────────────────────────────────────
|
|
641
|
+
uninstall: async () => {
|
|
642
|
+
const readline = await import('node:readline');
|
|
643
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
644
|
+
|
|
645
|
+
// Buffer lines eagerly — readline.question() loses buffered lines when
|
|
646
|
+
// piped stdin delivers multiple lines at once (Node.js known behavior).
|
|
647
|
+
const lineBuffer = [];
|
|
648
|
+
let lineResolve = null;
|
|
649
|
+
rl.on('line', (line) => {
|
|
650
|
+
if (lineResolve) {
|
|
651
|
+
const r = lineResolve;
|
|
652
|
+
lineResolve = null;
|
|
653
|
+
r(line);
|
|
654
|
+
} else {
|
|
655
|
+
lineBuffer.push(line);
|
|
656
|
+
}
|
|
657
|
+
});
|
|
658
|
+
// On EOF with no pending resolve, close gracefully
|
|
659
|
+
rl.on('close', () => {
|
|
660
|
+
if (lineResolve) { lineResolve(''); lineResolve = null; }
|
|
661
|
+
});
|
|
662
|
+
|
|
663
|
+
function prompt(question) {
|
|
664
|
+
process.stdout.write(question + ' ');
|
|
665
|
+
if (lineBuffer.length > 0) return Promise.resolve(lineBuffer.shift());
|
|
666
|
+
return new Promise((resolve) => { lineResolve = resolve; });
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
async function confirm(question) {
|
|
670
|
+
const a = (await prompt(question + ' [y/N]')).trim().toLowerCase();
|
|
671
|
+
return a === 'y' || a === 'yes';
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
async function askInput(question) {
|
|
675
|
+
return (await prompt(question)).trim();
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
async function askPassword(question) {
|
|
679
|
+
// Mute echoed keystrokes
|
|
680
|
+
const stdout = process.stdout;
|
|
681
|
+
const origWrite = stdout.write.bind(stdout);
|
|
682
|
+
stdout.write = (chunk, ...args) => {
|
|
683
|
+
// Suppress everything except the prompt itself
|
|
684
|
+
if (typeof chunk === 'string' && chunk.includes(question)) return origWrite(chunk, ...args);
|
|
685
|
+
return true;
|
|
686
|
+
};
|
|
687
|
+
const answer = await prompt(question);
|
|
688
|
+
stdout.write = origWrite;
|
|
689
|
+
console.log(); // newline after hidden input
|
|
690
|
+
return answer.trim();
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
const done = () => rl.close();
|
|
694
|
+
|
|
695
|
+
console.log(`\n${bold('🗑 MindOS Uninstall')}\n`);
|
|
696
|
+
console.log(' This will:');
|
|
697
|
+
console.log(` ${green('✓')} Stop running MindOS processes`);
|
|
698
|
+
console.log(` ${green('✓')} Remove background service (if installed)`);
|
|
699
|
+
console.log(` ${green('✓')} Uninstall npm package\n`);
|
|
700
|
+
|
|
701
|
+
if (!await confirm('Proceed?')) {
|
|
702
|
+
console.log(dim('\n Aborted.\n'));
|
|
703
|
+
done();
|
|
704
|
+
return;
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
// 1. Stop processes
|
|
708
|
+
console.log(`\n${cyan('Stopping MindOS...')}`);
|
|
709
|
+
try { stopMindos(); } catch { /* may not be running */ }
|
|
710
|
+
|
|
711
|
+
// 2. Remove daemon (skip if platform unsupported)
|
|
712
|
+
if (getPlatform()) {
|
|
713
|
+
try {
|
|
714
|
+
await runGatewayCommand('uninstall');
|
|
715
|
+
} catch {
|
|
716
|
+
// Daemon may not be installed — that's fine
|
|
717
|
+
}
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
// Read config before potentially deleting ~/.mindos/
|
|
721
|
+
let config = {};
|
|
722
|
+
try { config = JSON.parse(readFileSync(CONFIG_PATH, 'utf-8')); } catch {}
|
|
723
|
+
const mindRoot = config.mindRoot?.replace(/^~/, homedir());
|
|
724
|
+
|
|
725
|
+
// 3. Ask to remove ~/.mindos/
|
|
726
|
+
if (existsSync(MINDOS_DIR)) {
|
|
727
|
+
if (await confirm(`Remove config directory (${dim(MINDOS_DIR)})?`)) {
|
|
728
|
+
rmSync(MINDOS_DIR, { recursive: true, force: true });
|
|
729
|
+
console.log(`${green('✔')} Removed ${dim(MINDOS_DIR)}`);
|
|
730
|
+
} else {
|
|
731
|
+
console.log(dim(` Kept ${MINDOS_DIR}`));
|
|
732
|
+
}
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
// 4. Ask to remove knowledge base (triple protection: confirm → type YES → password)
|
|
736
|
+
if (mindRoot && existsSync(mindRoot)) {
|
|
737
|
+
if (await confirm(`Remove knowledge base (${dim(mindRoot)})?`)) {
|
|
738
|
+
const typed = await askInput(`${yellow('⚠ This is irreversible.')} Type ${bold('YES')} to confirm:`);
|
|
739
|
+
if (typed === 'YES') {
|
|
740
|
+
const webPassword = config.webPassword;
|
|
741
|
+
let authorized = true;
|
|
742
|
+
if (webPassword) {
|
|
743
|
+
const pw = await askPassword('Enter web password:');
|
|
744
|
+
if (pw !== webPassword) {
|
|
745
|
+
console.log(red(' Wrong password. Knowledge base kept.'));
|
|
746
|
+
authorized = false;
|
|
747
|
+
}
|
|
748
|
+
}
|
|
749
|
+
if (authorized) {
|
|
750
|
+
rmSync(mindRoot, { recursive: true, force: true });
|
|
751
|
+
console.log(`${green('✔')} Removed ${dim(mindRoot)}`);
|
|
752
|
+
}
|
|
753
|
+
} else {
|
|
754
|
+
console.log(dim(' Knowledge base kept.'));
|
|
755
|
+
}
|
|
756
|
+
} else {
|
|
757
|
+
console.log(dim(` Kept ${mindRoot}`));
|
|
758
|
+
}
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
// 5. npm uninstall -g
|
|
762
|
+
console.log(`\n${cyan('Uninstalling npm package...')}`);
|
|
763
|
+
try {
|
|
764
|
+
execSync('npm uninstall -g @geminilight/mindos', { stdio: ['ignore', 'inherit', 'inherit'] });
|
|
765
|
+
} catch {
|
|
766
|
+
console.log(yellow(' npm uninstall failed — you may need to run manually:'));
|
|
767
|
+
console.log(dim(' npm uninstall -g @geminilight/mindos'));
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
console.log(`\n${green('✔ MindOS uninstalled.')}\n`);
|
|
771
|
+
done();
|
|
772
|
+
},
|
|
773
|
+
|
|
626
774
|
// ── logs ───────────────────────────────────────────────────────────────────
|
|
627
775
|
logs: () => {
|
|
628
776
|
ensureMindosDir();
|
|
629
777
|
if (!existsSync(LOG_PATH)) {
|
|
630
778
|
console.log(dim(`No log file yet at ${LOG_PATH}`));
|
|
631
|
-
console.log(dim('Logs are
|
|
779
|
+
console.log(dim('Logs are created when starting MindOS (mindos start, mindos onboard, or daemon mode).'));
|
|
632
780
|
process.exit(0);
|
|
633
781
|
}
|
|
634
782
|
const noFollow = process.argv.includes('--no-follow');
|
|
@@ -932,6 +1080,7 @@ ${bold('Config & Diagnostics:')}
|
|
|
932
1080
|
${row('mindos config <subcommand>', 'View/update config (show/validate/set/unset)')}
|
|
933
1081
|
${row('mindos doctor', 'Health check (config, ports, build, daemon)')}
|
|
934
1082
|
${row('mindos update', 'Update MindOS to the latest version')}
|
|
1083
|
+
${row('mindos uninstall', 'Fully uninstall MindOS (stop, remove daemon, npm uninstall)')}
|
|
935
1084
|
${row('mindos logs', 'Tail service logs (~/.mindos/mindos.log)')}
|
|
936
1085
|
${row('mindos', 'Start using mode saved in ~/.mindos/config.json')}
|
|
937
1086
|
`);
|
package/bin/lib/build.js
CHANGED
|
@@ -38,14 +38,15 @@ export function cleanNextDir() {
|
|
|
38
38
|
}
|
|
39
39
|
|
|
40
40
|
function depsHash() {
|
|
41
|
-
//
|
|
42
|
-
//
|
|
43
|
-
// dependencies are added/removed/bumped, which is the only case that
|
|
44
|
-
// requires a fresh `npm install`.
|
|
41
|
+
// Hash both package.json and package-lock.json (if present) so that
|
|
42
|
+
// changes to either trigger a fresh npm install.
|
|
45
43
|
const pkgPath = resolve(ROOT, 'app', 'package.json');
|
|
44
|
+
const lockPath = resolve(ROOT, 'app', 'package-lock.json');
|
|
46
45
|
try {
|
|
47
|
-
const
|
|
48
|
-
|
|
46
|
+
const h = createHash('sha256');
|
|
47
|
+
h.update(readFileSync(pkgPath));
|
|
48
|
+
try { h.update(readFileSync(lockPath)); } catch {}
|
|
49
|
+
return h.digest('hex').slice(0, 16);
|
|
49
50
|
} catch {
|
|
50
51
|
return null;
|
|
51
52
|
}
|
package/bin/lib/gateway.js
CHANGED
|
@@ -50,8 +50,19 @@ export async function waitForPortFree(port, { retries = 30, intervalMs = 500 } =
|
|
|
50
50
|
return !(await isPortInUse(port));
|
|
51
51
|
}
|
|
52
52
|
|
|
53
|
-
export async function waitForHttp(port, { retries =
|
|
54
|
-
|
|
53
|
+
export async function waitForHttp(port, { retries = 60, intervalMs = 2000, label = 'service' } = {}) {
|
|
54
|
+
const start = Date.now();
|
|
55
|
+
const elapsed = () => {
|
|
56
|
+
const s = Math.round((Date.now() - start) / 1000);
|
|
57
|
+
return s < 60 ? `${s}s` : `${Math.floor(s / 60)}m${String(s % 60).padStart(2, '0')}s`;
|
|
58
|
+
};
|
|
59
|
+
const phases = [
|
|
60
|
+
{ after: 0, msg: 'installing dependencies' },
|
|
61
|
+
{ after: 15, msg: 'building app' },
|
|
62
|
+
{ after: 60, msg: 'still building (first run takes a while)' },
|
|
63
|
+
];
|
|
64
|
+
let currentPhase = -1;
|
|
65
|
+
|
|
55
66
|
for (let i = 0; i < retries; i++) {
|
|
56
67
|
try {
|
|
57
68
|
const { request } = await import('node:http');
|
|
@@ -62,12 +73,30 @@ export async function waitForHttp(port, { retries = 120, intervalMs = 2000, labe
|
|
|
62
73
|
req.on('timeout', () => { req.destroy(); resolve(false); });
|
|
63
74
|
req.end();
|
|
64
75
|
});
|
|
65
|
-
if (ok) {
|
|
76
|
+
if (ok) {
|
|
77
|
+
// Clear line and print success
|
|
78
|
+
process.stdout.write(`\r\x1b[K`);
|
|
79
|
+
process.stdout.write(` ${green('\u2714')} ${label} ready ${dim(`(${elapsed()})`)}\n`);
|
|
80
|
+
return true;
|
|
81
|
+
}
|
|
66
82
|
} catch { /* not ready yet */ }
|
|
67
|
-
|
|
83
|
+
|
|
84
|
+
// Update phase hint
|
|
85
|
+
const secs = (Date.now() - start) / 1000;
|
|
86
|
+
const nextPhase = phases.reduce((idx, p, i) => secs >= p.after ? i : idx, -1);
|
|
87
|
+
if (nextPhase !== currentPhase) {
|
|
88
|
+
currentPhase = nextPhase;
|
|
89
|
+
}
|
|
90
|
+
const hint = currentPhase >= 0 ? phases[currentPhase].msg : '';
|
|
91
|
+
|
|
92
|
+
// Rewrite the status line in place
|
|
93
|
+
process.stdout.write(`\r\x1b[K`);
|
|
94
|
+
process.stdout.write(cyan(` ⏳ Waiting for ${label}`) + dim(` — ${hint} (${elapsed()})`));
|
|
95
|
+
|
|
68
96
|
await new Promise(r => setTimeout(r, intervalMs));
|
|
69
97
|
}
|
|
70
|
-
process.stdout.write(
|
|
98
|
+
process.stdout.write(`\r\x1b[K`);
|
|
99
|
+
process.stdout.write(` ${red('\u2718')} ${label} did not start ${dim(`(${elapsed()})`)}\n`);
|
|
71
100
|
return false;
|
|
72
101
|
}
|
|
73
102
|
|
|
@@ -107,14 +136,15 @@ const systemd = {
|
|
|
107
136
|
].join('\n');
|
|
108
137
|
writeFileSync(SYSTEMD_UNIT, unit, 'utf-8');
|
|
109
138
|
console.log(green(`\u2714 Wrote ${SYSTEMD_UNIT}`));
|
|
110
|
-
execSync('systemctl --user daemon-reload', { stdio: 'inherit' });
|
|
111
|
-
execSync('systemctl --user enable mindos', { stdio: 'inherit' });
|
|
112
|
-
|
|
139
|
+
execSync('systemctl --user daemon-reload', { stdio: ['ignore', 'inherit', 'inherit'] });
|
|
140
|
+
execSync('systemctl --user enable mindos', { stdio: ['ignore', 'inherit', 'inherit'] });
|
|
141
|
+
execSync('systemctl --user start mindos', { stdio: ['ignore', 'inherit', 'inherit'] });
|
|
142
|
+
console.log(green('\u2714 Service installed and started'));
|
|
113
143
|
},
|
|
114
144
|
|
|
115
145
|
async start() {
|
|
116
146
|
rotateLogs();
|
|
117
|
-
execSync('systemctl --user start mindos', { stdio: 'inherit' });
|
|
147
|
+
execSync('systemctl --user start mindos', { stdio: ['ignore', 'inherit', 'inherit'] });
|
|
118
148
|
const ok = await waitForService(() => {
|
|
119
149
|
try {
|
|
120
150
|
const out = execSync('systemctl --user is-active mindos', { encoding: 'utf-8' }).trim();
|
|
@@ -123,36 +153,36 @@ const systemd = {
|
|
|
123
153
|
});
|
|
124
154
|
if (!ok) {
|
|
125
155
|
console.error(red('\n\u2718 Service failed to start. Last log output:'));
|
|
126
|
-
try { execSync(`journalctl --user -u mindos -n 30 --no-pager`, { stdio: 'inherit' }); } catch {}
|
|
156
|
+
try { execSync(`journalctl --user -u mindos -n 30 --no-pager`, { stdio: ['ignore', 'inherit', 'inherit'] }); } catch {}
|
|
127
157
|
process.exit(1);
|
|
128
158
|
}
|
|
129
159
|
console.log(green('\u2714 Service started'));
|
|
130
160
|
},
|
|
131
161
|
|
|
132
162
|
stop() {
|
|
133
|
-
execSync('systemctl --user stop mindos', { stdio: 'inherit' });
|
|
163
|
+
execSync('systemctl --user stop mindos', { stdio: ['ignore', 'inherit', 'inherit'] });
|
|
134
164
|
console.log(green('\u2714 Service stopped'));
|
|
135
165
|
},
|
|
136
166
|
|
|
137
167
|
status() {
|
|
138
168
|
try {
|
|
139
|
-
execSync('systemctl --user status mindos', { stdio: 'inherit' });
|
|
169
|
+
execSync('systemctl --user status mindos', { stdio: ['ignore', 'inherit', 'inherit'] });
|
|
140
170
|
} catch { /* status exits non-zero when stopped */ }
|
|
141
171
|
},
|
|
142
172
|
|
|
143
173
|
logs() {
|
|
144
|
-
execSync(`journalctl --user -u mindos -f`, { stdio: 'inherit' });
|
|
174
|
+
execSync(`journalctl --user -u mindos -f`, { stdio: ['ignore', 'inherit', 'inherit'] });
|
|
145
175
|
},
|
|
146
176
|
|
|
147
177
|
uninstall() {
|
|
148
178
|
try {
|
|
149
|
-
execSync('systemctl --user disable --now mindos', { stdio: 'inherit' });
|
|
179
|
+
execSync('systemctl --user disable --now mindos', { stdio: ['ignore', 'inherit', 'inherit'] });
|
|
150
180
|
} catch { /* may already be stopped */ }
|
|
151
181
|
if (existsSync(SYSTEMD_UNIT)) {
|
|
152
182
|
rmSync(SYSTEMD_UNIT);
|
|
153
183
|
console.log(green(`\u2714 Removed ${SYSTEMD_UNIT}`));
|
|
154
184
|
}
|
|
155
|
-
execSync('systemctl --user daemon-reload', { stdio: 'inherit' });
|
|
185
|
+
execSync('systemctl --user daemon-reload', { stdio: ['ignore', 'inherit', 'inherit'] });
|
|
156
186
|
console.log(green('\u2714 Service uninstalled'));
|
|
157
187
|
},
|
|
158
188
|
};
|
|
@@ -213,7 +243,7 @@ const launchd = {
|
|
|
213
243
|
|
|
214
244
|
async start() {
|
|
215
245
|
rotateLogs();
|
|
216
|
-
execSync(`launchctl kickstart -k gui/${launchctlUid()}/${LAUNCHD_LABEL}`, { stdio: 'inherit' });
|
|
246
|
+
execSync(`launchctl kickstart -k gui/${launchctlUid()}/${LAUNCHD_LABEL}`, { stdio: ['ignore', 'inherit', 'inherit'] });
|
|
217
247
|
const ok = await waitForService(() => {
|
|
218
248
|
try {
|
|
219
249
|
const out = execSync(`launchctl print gui/${launchctlUid()}/${LAUNCHD_LABEL}`, { encoding: 'utf-8' });
|
|
@@ -222,7 +252,7 @@ const launchd = {
|
|
|
222
252
|
});
|
|
223
253
|
if (!ok) {
|
|
224
254
|
console.error(red('\n\u2718 Service failed to start. Last log output:'));
|
|
225
|
-
try { execSync(`tail -n 30 ${LOG_PATH}`, { stdio: 'inherit' }); } catch {}
|
|
255
|
+
try { execSync(`tail -n 30 ${LOG_PATH}`, { stdio: ['ignore', 'inherit', 'inherit'] }); } catch {}
|
|
226
256
|
process.exit(1);
|
|
227
257
|
}
|
|
228
258
|
console.log(green('\u2714 Service started'));
|
|
@@ -238,7 +268,7 @@ const launchd = {
|
|
|
238
268
|
} catch {}
|
|
239
269
|
|
|
240
270
|
try {
|
|
241
|
-
execSync(`launchctl bootout gui/${launchctlUid()} ${LAUNCHD_PLIST}`, { stdio: 'inherit' });
|
|
271
|
+
execSync(`launchctl bootout gui/${launchctlUid()} ${LAUNCHD_PLIST}`, { stdio: ['ignore', 'inherit', 'inherit'] });
|
|
242
272
|
} catch { /* may not be running */ }
|
|
243
273
|
|
|
244
274
|
// launchctl bootout is async — wait for ports to actually be freed
|
|
@@ -264,19 +294,19 @@ const launchd = {
|
|
|
264
294
|
|
|
265
295
|
status() {
|
|
266
296
|
try {
|
|
267
|
-
execSync(`launchctl print gui/${launchctlUid()}/${LAUNCHD_LABEL}`, { stdio: 'inherit' });
|
|
297
|
+
execSync(`launchctl print gui/${launchctlUid()}/${LAUNCHD_LABEL}`, { stdio: ['ignore', 'inherit', 'inherit'] });
|
|
268
298
|
} catch {
|
|
269
299
|
console.log(dim('Service is not running'));
|
|
270
300
|
}
|
|
271
301
|
},
|
|
272
302
|
|
|
273
303
|
logs() {
|
|
274
|
-
execSync(`tail -f ${LOG_PATH}`, { stdio: 'inherit' });
|
|
304
|
+
execSync(`tail -f ${LOG_PATH}`, { stdio: ['ignore', 'inherit', 'inherit'] });
|
|
275
305
|
},
|
|
276
306
|
|
|
277
307
|
uninstall() {
|
|
278
308
|
try {
|
|
279
|
-
execSync(`launchctl bootout gui/${launchctlUid()} ${LAUNCHD_PLIST}`, { stdio: 'inherit' });
|
|
309
|
+
execSync(`launchctl bootout gui/${launchctlUid()} ${LAUNCHD_PLIST}`, { stdio: ['ignore', 'inherit', 'inherit'] });
|
|
280
310
|
} catch { /* may not be running */ }
|
|
281
311
|
if (existsSync(LAUNCHD_PLIST)) {
|
|
282
312
|
rmSync(LAUNCHD_PLIST);
|
package/bin/lib/stop.js
CHANGED
|
@@ -110,8 +110,8 @@ export function stopMindos(opts = {}) {
|
|
|
110
110
|
|
|
111
111
|
if (!pids.length && portKilled === 0) {
|
|
112
112
|
// Last resort: pattern match (for envs without lsof)
|
|
113
|
-
try { execSync('pkill -f "next start|next dev" 2>/dev/null || true', { stdio: 'inherit' }); } catch {}
|
|
114
|
-
try { execSync('pkill -f "mcp/src/index" 2>/dev/null || true', { stdio: 'inherit' }); } catch {}
|
|
113
|
+
try { execSync('pkill -f "next start|next dev" 2>/dev/null || true', { stdio: ['ignore', 'inherit', 'inherit'] }); } catch {}
|
|
114
|
+
try { execSync('pkill -f "mcp/src/index" 2>/dev/null || true', { stdio: ['ignore', 'inherit', 'inherit'] }); } catch {}
|
|
115
115
|
}
|
|
116
116
|
|
|
117
117
|
if (!pids.length) console.log(green('\u2714 Done'));
|
package/mcp/package.json
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@geminilight/mindos",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.16",
|
|
4
4
|
"description": "MindOS — Human-Agent Collaborative Mind System. Local-first knowledge base that syncs your mind to all AI Agents via MCP.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"mindos",
|
|
@@ -57,8 +57,7 @@
|
|
|
57
57
|
"!assets/images",
|
|
58
58
|
"!mcp/node_modules",
|
|
59
59
|
"!mcp/dist",
|
|
60
|
-
"!mcp/package-lock.json"
|
|
61
|
-
"!app/package-lock.json"
|
|
60
|
+
"!mcp/package-lock.json"
|
|
62
61
|
],
|
|
63
62
|
"scripts": {
|
|
64
63
|
"setup": "node scripts/setup.js",
|
package/scripts/setup.js
CHANGED
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
* Enter confirms
|
|
21
21
|
*/
|
|
22
22
|
|
|
23
|
-
import { existsSync, cpSync, writeFileSync, readFileSync, mkdirSync, createWriteStream, rmSync } from 'node:fs';
|
|
23
|
+
import { existsSync, cpSync, writeFileSync, readFileSync, mkdirSync, createWriteStream, rmSync, openSync } from 'node:fs';
|
|
24
24
|
import { resolve, dirname, join } from 'node:path';
|
|
25
25
|
import { homedir, tmpdir, networkInterfaces } from 'node:os';
|
|
26
26
|
import { fileURLToPath } from 'node:url';
|
|
@@ -36,6 +36,7 @@ const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
|
36
36
|
const ROOT = resolve(__dirname, '..');
|
|
37
37
|
const MINDOS_DIR = resolve(homedir(), '.mindos');
|
|
38
38
|
const CONFIG_PATH = resolve(MINDOS_DIR, 'config.json');
|
|
39
|
+
const LOG_PATH = resolve(MINDOS_DIR, 'mindos.log');
|
|
39
40
|
|
|
40
41
|
// ── i18n ─────────────────────────────────────────────────────────────────────
|
|
41
42
|
|
|
@@ -886,19 +887,34 @@ async function startGuiSetup() {
|
|
|
886
887
|
// Pass MINDOS_WEB_PORT (not PORT) so loadConfig() won't override with the
|
|
887
888
|
// config file port — this is critical when we need a temporary port.
|
|
888
889
|
const cliPath = resolve(__dirname, '../bin/cli.js');
|
|
890
|
+
const logFd = openSync(LOG_PATH, 'a');
|
|
889
891
|
const child = spawn(process.execPath, [cliPath, 'start'], {
|
|
890
892
|
detached: true,
|
|
891
|
-
stdio: 'ignore',
|
|
893
|
+
stdio: ['ignore', logFd, logFd],
|
|
892
894
|
env: { ...process.env, MINDOS_WEB_PORT: String(usePort) },
|
|
893
895
|
});
|
|
894
896
|
child.unref();
|
|
895
897
|
|
|
896
|
-
//
|
|
898
|
+
// First-time install hint
|
|
899
|
+
if (isFirstTime) {
|
|
900
|
+
write(c.dim(' First run: installing dependencies and building app (may take a few minutes)...\n'));
|
|
901
|
+
}
|
|
902
|
+
|
|
903
|
+
// Wait for the server to be ready (120s timeout)
|
|
897
904
|
const { waitForHttp } = await import('../bin/lib/gateway.js');
|
|
898
|
-
const ready = await waitForHttp(usePort, { retries:
|
|
905
|
+
const ready = await waitForHttp(usePort, { retries: 120, intervalMs: 1000, label: 'MindOS' });
|
|
899
906
|
|
|
900
907
|
if (!ready) {
|
|
901
908
|
write(c.red('\n✘ Server failed to start.\n'));
|
|
909
|
+
if (existsSync(LOG_PATH)) {
|
|
910
|
+
write(c.dim(`\n Last log output (${LOG_PATH}):\n`));
|
|
911
|
+
try {
|
|
912
|
+
const lines = readFileSync(LOG_PATH, 'utf-8').trim().split('\n').slice(-15);
|
|
913
|
+
for (const line of lines) write(c.dim(` ${line}\n`));
|
|
914
|
+
} catch {}
|
|
915
|
+
}
|
|
916
|
+
write(c.dim(`\n Full logs: mindos logs\n`));
|
|
917
|
+
write(c.dim(` Manual start: mindos start\n\n`));
|
|
902
918
|
process.exit(1);
|
|
903
919
|
}
|
|
904
920
|
|