@geminilight/mindos 0.5.13 → 0.5.15
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/app/api/restart/route.ts +5 -4
- package/app/app/api/sync/route.ts +4 -3
- package/app/lib/agent/tools.ts +29 -25
- package/app/package-lock.json +15571 -0
- package/bin/cli.js +164 -11
- package/bin/lib/build.js +7 -6
- package/bin/lib/gateway.js +51 -21
- package/bin/lib/stop.js +2 -2
- package/bin/lib/sync.js +3 -1
- package/package.json +2 -3
- package/scripts/setup.js +20 -4
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';
|
|
@@ -197,6 +198,8 @@ const commands = {
|
|
|
197
198
|
// ── dev ────────────────────────────────────────────────────────────────────
|
|
198
199
|
dev: async () => {
|
|
199
200
|
loadConfig();
|
|
201
|
+
process.env.MINDOS_CLI_PATH = resolve(ROOT, 'bin', 'cli.js');
|
|
202
|
+
process.env.MINDOS_NODE_BIN = process.execPath;
|
|
200
203
|
const webPort = process.env.MINDOS_WEB_PORT || '3456';
|
|
201
204
|
const mcpPort = process.env.MINDOS_MCP_PORT || '8781';
|
|
202
205
|
await assertPortFree(Number(webPort), 'web');
|
|
@@ -240,7 +243,7 @@ const commands = {
|
|
|
240
243
|
// causing a port-conflict race condition with KeepAlive restart loops.
|
|
241
244
|
console.log(dim(' (First run may take a few minutes to install dependencies and build the app.)'));
|
|
242
245
|
console.log(dim(' Follow live progress with: mindos logs\n'));
|
|
243
|
-
const ready = await waitForHttp(Number(webPort), { retries:
|
|
246
|
+
const ready = await waitForHttp(Number(webPort), { retries: 60, intervalMs: 2000, label: 'Web UI' });
|
|
244
247
|
if (!ready) {
|
|
245
248
|
console.error(red('\n✘ Service started but Web UI did not become ready in time.'));
|
|
246
249
|
console.error(dim(' Check logs with: mindos logs\n'));
|
|
@@ -283,6 +286,8 @@ const commands = {
|
|
|
283
286
|
await assertPortFree(Number(webPort), 'web');
|
|
284
287
|
await assertPortFree(Number(mcpPort), 'mcp');
|
|
285
288
|
}
|
|
289
|
+
process.env.MINDOS_CLI_PATH = resolve(ROOT, 'bin', 'cli.js');
|
|
290
|
+
process.env.MINDOS_NODE_BIN = process.execPath;
|
|
286
291
|
ensureAppDeps();
|
|
287
292
|
if (needsBuild()) {
|
|
288
293
|
console.log(yellow('Building MindOS (first run or new version detected)...\n'));
|
|
@@ -600,31 +605,178 @@ ${dim('Shortcut: mindos start --daemon → install + start in one step')}
|
|
|
600
605
|
console.log(cyan('\n Daemon is running — restarting to apply the new version...'));
|
|
601
606
|
await runGatewayCommand('stop');
|
|
602
607
|
await runGatewayCommand('install');
|
|
603
|
-
//
|
|
604
|
-
//
|
|
605
|
-
//
|
|
606
|
-
|
|
607
|
-
|
|
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 {}; }
|
|
608
615
|
})();
|
|
616
|
+
const webPort = updateConfig.port ?? 3456;
|
|
617
|
+
const mcpPort = updateConfig.mcpPort ?? 8781;
|
|
609
618
|
console.log(dim(' (Waiting for Web UI to come back up — first run after update includes a rebuild...)'));
|
|
610
|
-
const ready = await waitForHttp(Number(webPort), { retries:
|
|
619
|
+
const ready = await waitForHttp(Number(webPort), { retries: 60, intervalMs: 2000, label: 'Web UI' });
|
|
611
620
|
if (ready) {
|
|
612
|
-
|
|
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`);
|
|
613
629
|
} else {
|
|
614
630
|
console.error(red('✘ MindOS did not come back up in time. Check logs: mindos logs\n'));
|
|
615
631
|
process.exit(1);
|
|
616
632
|
}
|
|
617
633
|
} else {
|
|
618
|
-
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`);
|
|
619
637
|
}
|
|
620
638
|
},
|
|
621
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
|
+
|
|
622
774
|
// ── logs ───────────────────────────────────────────────────────────────────
|
|
623
775
|
logs: () => {
|
|
624
776
|
ensureMindosDir();
|
|
625
777
|
if (!existsSync(LOG_PATH)) {
|
|
626
778
|
console.log(dim(`No log file yet at ${LOG_PATH}`));
|
|
627
|
-
console.log(dim('Logs are
|
|
779
|
+
console.log(dim('Logs are created when starting MindOS (mindos start, mindos onboard, or daemon mode).'));
|
|
628
780
|
process.exit(0);
|
|
629
781
|
}
|
|
630
782
|
const noFollow = process.argv.includes('--no-follow');
|
|
@@ -928,6 +1080,7 @@ ${bold('Config & Diagnostics:')}
|
|
|
928
1080
|
${row('mindos config <subcommand>', 'View/update config (show/validate/set/unset)')}
|
|
929
1081
|
${row('mindos doctor', 'Health check (config, ports, build, daemon)')}
|
|
930
1082
|
${row('mindos update', 'Update MindOS to the latest version')}
|
|
1083
|
+
${row('mindos uninstall', 'Fully uninstall MindOS (stop, remove daemon, npm uninstall)')}
|
|
931
1084
|
${row('mindos logs', 'Tail service logs (~/.mindos/mindos.log)')}
|
|
932
1085
|
${row('mindos', 'Start using mode saved in ~/.mindos/config.json')}
|
|
933
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/bin/lib/sync.js
CHANGED
|
@@ -286,7 +286,9 @@ export async function initSync(mindRoot, opts = {}) {
|
|
|
286
286
|
// 5. Test connection
|
|
287
287
|
if (!nonInteractive) console.log(dim('Testing connection...'));
|
|
288
288
|
try {
|
|
289
|
-
|
|
289
|
+
// `git ls-remote --exit-code origin` returns non-zero for an empty remote,
|
|
290
|
+
// which breaks first-time setup against a freshly created repository.
|
|
291
|
+
execFileSync('git', ['ls-remote', 'origin'], { cwd: mindRoot, stdio: 'pipe', timeout: 15000 });
|
|
290
292
|
if (!nonInteractive) console.log(green('✔ Connection successful'));
|
|
291
293
|
} catch (lsErr) {
|
|
292
294
|
const detail = lsErr.stderr ? lsErr.stderr.toString().trim() : '';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@geminilight/mindos",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.15",
|
|
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
|
|