@geminilight/mindos 0.6.8 → 0.6.13
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 +2 -0
- package/README_zh.md +2 -0
- package/app/app/api/mcp/install/route.ts +4 -1
- package/app/app/api/setup/check-path/route.ts +2 -7
- package/app/app/api/setup/ls/route.ts +3 -9
- package/app/app/api/setup/path-utils.ts +8 -0
- package/app/app/api/setup/route.ts +2 -7
- package/app/app/api/uninstall/route.ts +47 -0
- package/app/app/globals.css +11 -0
- package/app/components/ActivityBar.tsx +10 -3
- package/app/components/AskFab.tsx +7 -3
- package/app/components/CreateSpaceModal.tsx +1 -1
- package/app/components/DirView.tsx +1 -1
- package/app/components/FileTree.tsx +30 -23
- package/app/components/GuideCard.tsx +1 -1
- package/app/components/HomeContent.tsx +137 -109
- package/app/components/ImportModal.tsx +16 -477
- package/app/components/MarkdownView.tsx +3 -0
- package/app/components/OnboardingView.tsx +1 -1
- package/app/components/OrganizeToast.tsx +386 -0
- package/app/components/Panel.tsx +23 -2
- package/app/components/Sidebar.tsx +1 -1
- package/app/components/SidebarLayout.tsx +44 -1
- package/app/components/agents/AgentDetailContent.tsx +33 -12
- package/app/components/agents/AgentsMcpSection.tsx +1 -1
- package/app/components/agents/AgentsOverviewSection.tsx +3 -4
- package/app/components/agents/AgentsPrimitives.tsx +2 -2
- package/app/components/agents/AgentsSkillsSection.tsx +2 -2
- package/app/components/agents/SkillDetailPopover.tsx +24 -8
- package/app/components/ask/AskContent.tsx +124 -75
- package/app/components/ask/HighlightMatch.tsx +14 -0
- package/app/components/ask/MentionPopover.tsx +5 -3
- package/app/components/ask/MessageList.tsx +39 -11
- package/app/components/ask/SlashCommandPopover.tsx +4 -2
- package/app/components/changes/ChangesBanner.tsx +20 -2
- package/app/components/changes/ChangesContentPage.tsx +10 -2
- package/app/components/echo/EchoHero.tsx +1 -1
- package/app/components/echo/EchoInsightCollapsible.tsx +1 -1
- package/app/components/echo/EchoPageSections.tsx +1 -1
- package/app/components/explore/UseCaseCard.tsx +1 -1
- package/app/components/panels/DiscoverPanel.tsx +29 -25
- package/app/components/panels/ImportHistoryPanel.tsx +195 -0
- package/app/components/panels/PluginsPanel.tsx +2 -2
- package/app/components/settings/AiTab.tsx +24 -0
- package/app/components/settings/KnowledgeTab.tsx +1 -1
- package/app/components/settings/McpSkillCreateForm.tsx +1 -1
- package/app/components/settings/McpSkillRow.tsx +1 -1
- package/app/components/settings/McpSkillsSection.tsx +2 -2
- package/app/components/settings/McpTab.tsx +2 -2
- package/app/components/settings/PluginsTab.tsx +1 -1
- package/app/components/settings/Primitives.tsx +118 -6
- package/app/components/settings/SettingsContent.tsx +5 -2
- package/app/components/settings/UninstallTab.tsx +179 -0
- package/app/components/settings/UpdateTab.tsx +17 -5
- package/app/components/settings/types.ts +2 -1
- package/app/components/setup/StepDots.tsx +2 -2
- package/app/components/ui/dialog.tsx +1 -1
- package/app/hooks/useAiOrganize.ts +122 -10
- package/app/hooks/useMention.ts +21 -3
- package/app/hooks/useSlashCommand.ts +18 -4
- package/app/lib/agent/reconnect.ts +40 -0
- package/app/lib/core/backlinks.ts +2 -2
- package/app/lib/core/git.ts +14 -10
- package/app/lib/fs.ts +2 -1
- package/app/lib/i18n-en.ts +46 -2
- package/app/lib/i18n-zh.ts +46 -2
- package/app/lib/organize-history.ts +74 -0
- package/app/lib/settings.ts +2 -0
- package/app/lib/types.ts +2 -0
- package/app/next.config.ts +23 -5
- package/bin/cli.js +6 -9
- package/bin/lib/mcp-build.js +74 -0
- package/bin/lib/mcp-spawn.js +8 -5
- package/bin/lib/port.js +17 -2
- package/bin/lib/stop.js +12 -2
- package/mcp/dist/index.cjs +43 -43
- package/mcp/src/index.ts +58 -12
- package/package.json +1 -1
- package/scripts/setup.js +2 -2
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
// ---------------------------------------------------------------------------
|
|
4
|
+
// Organize History — localStorage persistence for past AI organize operations
|
|
5
|
+
// ---------------------------------------------------------------------------
|
|
6
|
+
|
|
7
|
+
export interface OrganizeHistoryFile {
|
|
8
|
+
action: 'create' | 'update' | 'unknown';
|
|
9
|
+
path: string;
|
|
10
|
+
ok: boolean;
|
|
11
|
+
/** Undone by user (deleted for create / restored for update) */
|
|
12
|
+
undone?: boolean;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface OrganizeHistoryEntry {
|
|
16
|
+
id: string;
|
|
17
|
+
/** Unix ms */
|
|
18
|
+
timestamp: number;
|
|
19
|
+
/** Original uploaded file names */
|
|
20
|
+
sourceFiles: string[];
|
|
21
|
+
files: OrganizeHistoryFile[];
|
|
22
|
+
status: 'completed' | 'partial' | 'undone';
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const STORAGE_KEY = 'mindos:organize-history';
|
|
26
|
+
const MAX_ENTRIES = 50;
|
|
27
|
+
|
|
28
|
+
export function loadHistory(): OrganizeHistoryEntry[] {
|
|
29
|
+
if (typeof window === 'undefined') return [];
|
|
30
|
+
try {
|
|
31
|
+
const raw = localStorage.getItem(STORAGE_KEY);
|
|
32
|
+
if (!raw) return [];
|
|
33
|
+
return JSON.parse(raw) as OrganizeHistoryEntry[];
|
|
34
|
+
} catch {
|
|
35
|
+
return [];
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function saveHistory(entries: OrganizeHistoryEntry[]): void {
|
|
40
|
+
if (typeof window === 'undefined') return;
|
|
41
|
+
try {
|
|
42
|
+
localStorage.setItem(STORAGE_KEY, JSON.stringify(entries.slice(0, MAX_ENTRIES)));
|
|
43
|
+
} catch { /* quota exceeded — silently drop oldest */ }
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function appendEntry(entry: OrganizeHistoryEntry): OrganizeHistoryEntry[] {
|
|
47
|
+
const all = loadHistory();
|
|
48
|
+
all.unshift(entry);
|
|
49
|
+
const trimmed = all.slice(0, MAX_ENTRIES);
|
|
50
|
+
saveHistory(trimmed);
|
|
51
|
+
return trimmed;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function updateEntry(id: string, patch: Partial<OrganizeHistoryEntry>): OrganizeHistoryEntry[] {
|
|
55
|
+
const all = loadHistory();
|
|
56
|
+
const idx = all.findIndex(e => e.id === id);
|
|
57
|
+
if (idx >= 0) {
|
|
58
|
+
all[idx] = { ...all[idx], ...patch };
|
|
59
|
+
saveHistory(all);
|
|
60
|
+
}
|
|
61
|
+
return all;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export function clearHistory(): void {
|
|
65
|
+
if (typeof window === 'undefined') return;
|
|
66
|
+
try {
|
|
67
|
+
localStorage.removeItem(STORAGE_KEY);
|
|
68
|
+
} catch { /* ignore */ }
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
let _idCounter = 0;
|
|
72
|
+
export function generateEntryId(): string {
|
|
73
|
+
return `org-${Date.now()}-${++_idCounter}`;
|
|
74
|
+
}
|
package/app/lib/settings.ts
CHANGED
|
@@ -23,6 +23,7 @@ export interface AgentConfig {
|
|
|
23
23
|
enableThinking?: boolean; // default false, Anthropic only
|
|
24
24
|
thinkingBudget?: number; // default 5000
|
|
25
25
|
contextStrategy?: 'auto' | 'off'; // default 'auto'
|
|
26
|
+
reconnectRetries?: number; // default 3, range 0-10 (0 = disabled)
|
|
26
27
|
}
|
|
27
28
|
|
|
28
29
|
export interface GuideState {
|
|
@@ -128,6 +129,7 @@ function parseAgent(raw: unknown): AgentConfig | undefined {
|
|
|
128
129
|
if (typeof obj.enableThinking === 'boolean') result.enableThinking = obj.enableThinking;
|
|
129
130
|
if (typeof obj.thinkingBudget === 'number') result.thinkingBudget = Math.min(50000, Math.max(1000, obj.thinkingBudget));
|
|
130
131
|
if (obj.contextStrategy === 'auto' || obj.contextStrategy === 'off') result.contextStrategy = obj.contextStrategy;
|
|
132
|
+
if (typeof obj.reconnectRetries === 'number') result.reconnectRetries = Math.min(10, Math.max(0, obj.reconnectRetries));
|
|
131
133
|
return Object.keys(result).length > 0 ? result : undefined;
|
|
132
134
|
}
|
|
133
135
|
|
package/app/lib/types.ts
CHANGED
|
@@ -41,6 +41,8 @@ export interface Message {
|
|
|
41
41
|
timestamp?: number;
|
|
42
42
|
/** Structured parts for assistant messages (tool calls + text segments) */
|
|
43
43
|
parts?: MessagePart[];
|
|
44
|
+
/** Skill name used for this user message (rendered as a capsule in the UI) */
|
|
45
|
+
skillName?: string;
|
|
44
46
|
}
|
|
45
47
|
|
|
46
48
|
export interface LocalAttachment {
|
package/app/next.config.ts
CHANGED
|
@@ -1,21 +1,39 @@
|
|
|
1
1
|
import type { NextConfig } from "next";
|
|
2
2
|
import path from "path";
|
|
3
3
|
|
|
4
|
+
// When MindOS is installed globally via npm, the entire project lives
|
|
5
|
+
// under node_modules/@geminilight/mindos/. Next.js skips tsconfig path
|
|
6
|
+
// resolution and SWC TypeScript compilation for files inside node_modules.
|
|
7
|
+
// We detect this at config time and apply the necessary overrides.
|
|
8
|
+
const projectDir = path.resolve(__dirname);
|
|
9
|
+
const inNodeModules = projectDir.includes('node_modules');
|
|
10
|
+
|
|
4
11
|
const nextConfig: NextConfig = {
|
|
5
|
-
transpilePackages: [
|
|
12
|
+
transpilePackages: [
|
|
13
|
+
'github-slugger',
|
|
14
|
+
// Self-reference: ensures the SWC loader compiles our own TypeScript
|
|
15
|
+
// when the project is inside node_modules (global npm install).
|
|
16
|
+
...(inNodeModules ? ['@geminilight/mindos'] : []),
|
|
17
|
+
],
|
|
6
18
|
serverExternalPackages: ['chokidar', 'openai', '@mariozechner/pi-ai', '@mariozechner/pi-agent-core', '@mariozechner/pi-coding-agent', 'mcporter'],
|
|
7
19
|
output: 'standalone',
|
|
8
|
-
outputFileTracingRoot:
|
|
20
|
+
outputFileTracingRoot: projectDir,
|
|
9
21
|
turbopack: {
|
|
10
|
-
root:
|
|
22
|
+
root: projectDir,
|
|
11
23
|
},
|
|
12
|
-
// Disable client-side router cache for dynamic layouts so that
|
|
13
|
-
// router.refresh() always fetches a fresh file tree from the server.
|
|
14
24
|
experimental: {
|
|
15
25
|
staleTimes: {
|
|
16
26
|
dynamic: 0,
|
|
17
27
|
},
|
|
18
28
|
},
|
|
29
|
+
webpack: (config) => {
|
|
30
|
+
if (inNodeModules) {
|
|
31
|
+
config.resolve = config.resolve ?? {};
|
|
32
|
+
config.resolve.alias = config.resolve.alias ?? {};
|
|
33
|
+
(config.resolve.alias as Record<string, string>)['@'] = projectDir;
|
|
34
|
+
}
|
|
35
|
+
return config;
|
|
36
|
+
},
|
|
19
37
|
};
|
|
20
38
|
|
|
21
39
|
export default nextConfig;
|
package/bin/cli.js
CHANGED
|
@@ -40,7 +40,7 @@
|
|
|
40
40
|
|
|
41
41
|
import { execSync, spawn as nodeSpawn } from 'node:child_process';
|
|
42
42
|
import { existsSync, readFileSync, writeFileSync, rmSync, cpSync } from 'node:fs';
|
|
43
|
-
import { resolve } from 'node:path';
|
|
43
|
+
import { dirname, resolve } from 'node:path';
|
|
44
44
|
import { homedir } from 'node:os';
|
|
45
45
|
|
|
46
46
|
import { ROOT, CONFIG_PATH, BUILD_STAMP, LOG_PATH, MINDOS_DIR } from './lib/constants.js';
|
|
@@ -57,6 +57,7 @@ import { stopMindos } from './lib/stop.js';
|
|
|
57
57
|
import { getPlatform, ensureMindosDir, waitForHttp, waitForPortFree, runGatewayCommand } from './lib/gateway.js';
|
|
58
58
|
import { printStartupInfo, getLocalIP } from './lib/startup.js';
|
|
59
59
|
import { spawnMcp } from './lib/mcp-spawn.js';
|
|
60
|
+
import { ensureMcpBundle } from './lib/mcp-build.js';
|
|
60
61
|
import { mcpInstall } from './lib/mcp-install.js';
|
|
61
62
|
import { initSync, startSyncDaemon, stopSyncDaemon, getSyncStatus, manualSync, listConflicts, setSyncEnabled } from './lib/sync.js';
|
|
62
63
|
|
|
@@ -403,7 +404,7 @@ const commands = {
|
|
|
403
404
|
run(
|
|
404
405
|
`${NEXT_BIN} start -p ${webPort} ${extra}`,
|
|
405
406
|
resolve(ROOT, 'app'),
|
|
406
|
-
|
|
407
|
+
{ HOSTNAME: '127.0.0.1' }
|
|
407
408
|
);
|
|
408
409
|
},
|
|
409
410
|
|
|
@@ -422,11 +423,7 @@ const commands = {
|
|
|
422
423
|
const hasInstallFlags = restArgs.some(a => ['-g', '--global', '-y', '--yes'].includes(a));
|
|
423
424
|
if (sub === 'install' || hasInstallFlags) { await mcpInstall(); return; }
|
|
424
425
|
loadConfig();
|
|
425
|
-
|
|
426
|
-
if (!existsSync(mcpSdk)) {
|
|
427
|
-
console.log(yellow('Installing MCP dependencies (first run)...\n'));
|
|
428
|
-
npmInstall(resolve(ROOT, 'mcp'), '--no-workspaces');
|
|
429
|
-
}
|
|
426
|
+
ensureMcpBundle();
|
|
430
427
|
// `mindos mcp` is the entry point for MCP clients (Claude Code, Cursor, etc.)
|
|
431
428
|
// which communicate over stdin/stdout. Default to stdio; HTTP is handled by
|
|
432
429
|
// `mindos start` via spawnMcp(). Callers can still override via env.
|
|
@@ -790,7 +787,7 @@ ${dim('Shortcut: mindos start --daemon → install + start in one step')}
|
|
|
790
787
|
const webPort = updateConfig.port ?? 3456;
|
|
791
788
|
const mcpPort = updateConfig.mcpPort ?? 8781;
|
|
792
789
|
console.log(dim(' (Waiting for Web UI to come back up — first run after update includes a rebuild...)'));
|
|
793
|
-
const ready = await waitForHttp(Number(webPort), { retries:
|
|
790
|
+
const ready = await waitForHttp(Number(webPort), { retries: 180, intervalMs: 2000, label: 'Web UI', logFile: LOG_PATH });
|
|
794
791
|
if (ready) {
|
|
795
792
|
const localIP = getLocalIP();
|
|
796
793
|
console.log(`\n${'─'.repeat(53)}`);
|
|
@@ -849,7 +846,7 @@ ${dim('Shortcut: mindos start --daemon → install + start in one step')}
|
|
|
849
846
|
child.unref();
|
|
850
847
|
|
|
851
848
|
console.log(dim(' (Waiting for Web UI to come back up...)'));
|
|
852
|
-
const ready = await waitForHttp(webPort, { retries:
|
|
849
|
+
const ready = await waitForHttp(webPort, { retries: 180, intervalMs: 2000, label: 'Web UI', logFile: LOG_PATH });
|
|
853
850
|
if (ready) {
|
|
854
851
|
const localIP = getLocalIP();
|
|
855
852
|
console.log(`\n${'─'.repeat(53)}`);
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { existsSync, readdirSync, statSync } from 'node:fs';
|
|
2
|
+
import { resolve } from 'node:path';
|
|
3
|
+
import { ROOT } from './constants.js';
|
|
4
|
+
import { yellow } from './colors.js';
|
|
5
|
+
import { run, npmInstall } from './utils.js';
|
|
6
|
+
|
|
7
|
+
export const MCP_DIR = resolve(ROOT, 'mcp');
|
|
8
|
+
export const MCP_SRC_DIR = resolve(MCP_DIR, 'src');
|
|
9
|
+
export const MCP_BUNDLE = resolve(MCP_DIR, 'dist', 'index.cjs');
|
|
10
|
+
|
|
11
|
+
const MCP_PACKAGE_JSON = resolve(MCP_DIR, 'package.json');
|
|
12
|
+
const MCP_PACKAGE_LOCK = resolve(MCP_DIR, 'package-lock.json');
|
|
13
|
+
const MCP_SDK = resolve(MCP_DIR, 'node_modules', '@modelcontextprotocol', 'sdk', 'package.json');
|
|
14
|
+
const MCP_ESBUILD = resolve(MCP_DIR, 'node_modules', 'esbuild', 'package.json');
|
|
15
|
+
|
|
16
|
+
function safeMtime(filePath) {
|
|
17
|
+
try {
|
|
18
|
+
return statSync(filePath).mtimeMs;
|
|
19
|
+
} catch {
|
|
20
|
+
return 0;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function latestTreeMtime(dirPath) {
|
|
25
|
+
if (!existsSync(dirPath)) return 0;
|
|
26
|
+
|
|
27
|
+
let latest = safeMtime(dirPath);
|
|
28
|
+
for (const entry of readdirSync(dirPath, { withFileTypes: true })) {
|
|
29
|
+
const fullPath = resolve(dirPath, entry.name);
|
|
30
|
+
if (entry.isDirectory()) {
|
|
31
|
+
latest = Math.max(latest, latestTreeMtime(fullPath));
|
|
32
|
+
} else {
|
|
33
|
+
latest = Math.max(latest, safeMtime(fullPath));
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return latest;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function hasBuildDeps() {
|
|
40
|
+
return existsSync(MCP_SDK) && existsSync(MCP_ESBUILD);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function needsMcpBuild() {
|
|
44
|
+
if (!existsSync(MCP_BUNDLE)) return true;
|
|
45
|
+
|
|
46
|
+
const bundleMtime = safeMtime(MCP_BUNDLE);
|
|
47
|
+
const sourceMtime = Math.max(
|
|
48
|
+
latestTreeMtime(MCP_SRC_DIR),
|
|
49
|
+
safeMtime(MCP_PACKAGE_JSON),
|
|
50
|
+
safeMtime(MCP_PACKAGE_LOCK),
|
|
51
|
+
);
|
|
52
|
+
|
|
53
|
+
return sourceMtime > bundleMtime;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export function ensureMcpBundle() {
|
|
57
|
+
if (!needsMcpBuild()) return;
|
|
58
|
+
|
|
59
|
+
const hadBundle = existsSync(MCP_BUNDLE);
|
|
60
|
+
|
|
61
|
+
if (!hasBuildDeps()) {
|
|
62
|
+
console.log(yellow('Installing MCP build dependencies...\n'));
|
|
63
|
+
npmInstall(MCP_DIR, '--no-workspaces');
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
console.log(yellow(hadBundle
|
|
67
|
+
? 'Rebuilding MCP bundle (source changed)...\n'
|
|
68
|
+
: 'Building MCP bundle (first run)...\n'));
|
|
69
|
+
run('npm run build', MCP_DIR);
|
|
70
|
+
|
|
71
|
+
if (!existsSync(MCP_BUNDLE)) {
|
|
72
|
+
throw new Error(`MCP bundle build did not produce ${MCP_BUNDLE}`);
|
|
73
|
+
}
|
|
74
|
+
}
|
package/bin/lib/mcp-spawn.js
CHANGED
|
@@ -1,17 +1,20 @@
|
|
|
1
1
|
import { spawn } from 'node:child_process';
|
|
2
|
-
import {
|
|
2
|
+
import { readFileSync } from 'node:fs';
|
|
3
3
|
import { resolve } from 'node:path';
|
|
4
4
|
import { ROOT, CONFIG_PATH } from './constants.js';
|
|
5
5
|
import { bold, red } from './colors.js';
|
|
6
|
+
import { ensureMcpBundle, MCP_BUNDLE } from './mcp-build.js';
|
|
6
7
|
|
|
7
8
|
export function spawnMcp(verbose = false) {
|
|
8
9
|
const mcpPort = process.env.MINDOS_MCP_PORT || '8781';
|
|
9
10
|
const webPort = process.env.MINDOS_WEB_PORT || '3456';
|
|
10
11
|
|
|
11
|
-
|
|
12
|
-
|
|
12
|
+
try {
|
|
13
|
+
ensureMcpBundle();
|
|
14
|
+
} catch (err) {
|
|
15
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
13
16
|
throw new Error(
|
|
14
|
-
|
|
17
|
+
`${message}\n` +
|
|
15
18
|
`This MindOS installation may be corrupted. Try: npm install -g @geminilight/mindos@latest`,
|
|
16
19
|
);
|
|
17
20
|
}
|
|
@@ -31,7 +34,7 @@ export function spawnMcp(verbose = false) {
|
|
|
31
34
|
...(configAuthToken ? { AUTH_TOKEN: configAuthToken } : {}),
|
|
32
35
|
...(verbose ? { MCP_VERBOSE: '1' } : {}),
|
|
33
36
|
};
|
|
34
|
-
const child = spawn(process.execPath, [
|
|
37
|
+
const child = spawn(process.execPath, [MCP_BUNDLE], {
|
|
35
38
|
cwd: resolve(ROOT, 'mcp'),
|
|
36
39
|
stdio: 'inherit',
|
|
37
40
|
env,
|
package/bin/lib/port.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { createConnection } from 'node:net';
|
|
2
|
-
import { bold, dim, red } from './colors.js';
|
|
2
|
+
import { bold, dim, red, yellow } from './colors.js';
|
|
3
3
|
|
|
4
4
|
export function isPortInUse(port) {
|
|
5
5
|
return new Promise((resolve) => {
|
|
@@ -16,10 +16,25 @@ export function isPortInUse(port) {
|
|
|
16
16
|
}
|
|
17
17
|
|
|
18
18
|
export async function assertPortFree(port, name) {
|
|
19
|
+
if (!(await isPortInUse(port))) return;
|
|
20
|
+
|
|
21
|
+
// Port is occupied — try to clean up orphaned processes from a previous
|
|
22
|
+
// installation (e.g. user deleted MindOS.app from Finder without quitting,
|
|
23
|
+
// leaving behind zombie Next.js / MCP processes on the default ports).
|
|
24
|
+
console.warn(`${yellow('⚠')} Port ${port} in use (${name}) — attempting cleanup...`);
|
|
25
|
+
try {
|
|
26
|
+
const { stopMindos } = await import('./stop.js');
|
|
27
|
+
stopMindos();
|
|
28
|
+
// Wait briefly for ports to release after SIGTERM
|
|
29
|
+
await new Promise(r => setTimeout(r, 1500));
|
|
30
|
+
} catch { /* stopMindos may fail if no config exists yet — that's fine */ }
|
|
31
|
+
|
|
32
|
+
// Re-check after cleanup
|
|
19
33
|
if (await isPortInUse(port)) {
|
|
20
|
-
console.error(`\n${red('\u2718')} ${bold(`Port ${port} is
|
|
34
|
+
console.error(`\n${red('\u2718')} ${bold(`Port ${port} is still in use after cleanup`)} ${dim(`(${name})`)}`);
|
|
21
35
|
console.error(`\n ${dim('Stop MindOS:')} mindos stop`);
|
|
22
36
|
console.error(` ${dim('Find the process:')} lsof -i :${port}\n`);
|
|
23
37
|
process.exit(1);
|
|
24
38
|
}
|
|
39
|
+
console.log(` ${dim('Cleaned up orphaned processes on port')} ${port}`);
|
|
25
40
|
}
|
package/bin/lib/stop.js
CHANGED
|
@@ -74,6 +74,13 @@ function killTree(pid) {
|
|
|
74
74
|
* read from the current config file.
|
|
75
75
|
*/
|
|
76
76
|
export function stopMindos(opts = {}) {
|
|
77
|
+
// In test environment, skip all real process killing to avoid
|
|
78
|
+
// destroying dev servers or other unrelated processes.
|
|
79
|
+
if (process.env.NODE_ENV === 'test') {
|
|
80
|
+
console.log('(test mode: skipping real process stop)');
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
|
|
77
84
|
// Read ports from config for port-based cleanup
|
|
78
85
|
let webPort = '3456', mcpPort = '8781';
|
|
79
86
|
try {
|
|
@@ -110,8 +117,11 @@ export function stopMindos(opts = {}) {
|
|
|
110
117
|
|
|
111
118
|
if (!pids.length && portKilled === 0) {
|
|
112
119
|
// Last resort: pattern match (for envs without lsof)
|
|
113
|
-
|
|
114
|
-
|
|
120
|
+
// Skip in test environment to avoid killing unrelated dev servers
|
|
121
|
+
if (process.env.NODE_ENV !== 'test') {
|
|
122
|
+
try { execSync('pkill -f "next start|next dev" 2>/dev/null || true', { stdio: ['ignore', 'inherit', 'inherit'] }); } catch {}
|
|
123
|
+
try { execSync('pkill -f "mcp/(src/index|dist/index)" 2>/dev/null || true', { stdio: ['ignore', 'inherit', 'inherit'] }); } catch {}
|
|
124
|
+
}
|
|
115
125
|
}
|
|
116
126
|
|
|
117
127
|
if (!pids.length) console.log(green('\u2714 Done'));
|