@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.
Files changed (79) hide show
  1. package/README.md +2 -0
  2. package/README_zh.md +2 -0
  3. package/app/app/api/mcp/install/route.ts +4 -1
  4. package/app/app/api/setup/check-path/route.ts +2 -7
  5. package/app/app/api/setup/ls/route.ts +3 -9
  6. package/app/app/api/setup/path-utils.ts +8 -0
  7. package/app/app/api/setup/route.ts +2 -7
  8. package/app/app/api/uninstall/route.ts +47 -0
  9. package/app/app/globals.css +11 -0
  10. package/app/components/ActivityBar.tsx +10 -3
  11. package/app/components/AskFab.tsx +7 -3
  12. package/app/components/CreateSpaceModal.tsx +1 -1
  13. package/app/components/DirView.tsx +1 -1
  14. package/app/components/FileTree.tsx +30 -23
  15. package/app/components/GuideCard.tsx +1 -1
  16. package/app/components/HomeContent.tsx +137 -109
  17. package/app/components/ImportModal.tsx +16 -477
  18. package/app/components/MarkdownView.tsx +3 -0
  19. package/app/components/OnboardingView.tsx +1 -1
  20. package/app/components/OrganizeToast.tsx +386 -0
  21. package/app/components/Panel.tsx +23 -2
  22. package/app/components/Sidebar.tsx +1 -1
  23. package/app/components/SidebarLayout.tsx +44 -1
  24. package/app/components/agents/AgentDetailContent.tsx +33 -12
  25. package/app/components/agents/AgentsMcpSection.tsx +1 -1
  26. package/app/components/agents/AgentsOverviewSection.tsx +3 -4
  27. package/app/components/agents/AgentsPrimitives.tsx +2 -2
  28. package/app/components/agents/AgentsSkillsSection.tsx +2 -2
  29. package/app/components/agents/SkillDetailPopover.tsx +24 -8
  30. package/app/components/ask/AskContent.tsx +124 -75
  31. package/app/components/ask/HighlightMatch.tsx +14 -0
  32. package/app/components/ask/MentionPopover.tsx +5 -3
  33. package/app/components/ask/MessageList.tsx +39 -11
  34. package/app/components/ask/SlashCommandPopover.tsx +4 -2
  35. package/app/components/changes/ChangesBanner.tsx +20 -2
  36. package/app/components/changes/ChangesContentPage.tsx +10 -2
  37. package/app/components/echo/EchoHero.tsx +1 -1
  38. package/app/components/echo/EchoInsightCollapsible.tsx +1 -1
  39. package/app/components/echo/EchoPageSections.tsx +1 -1
  40. package/app/components/explore/UseCaseCard.tsx +1 -1
  41. package/app/components/panels/DiscoverPanel.tsx +29 -25
  42. package/app/components/panels/ImportHistoryPanel.tsx +195 -0
  43. package/app/components/panels/PluginsPanel.tsx +2 -2
  44. package/app/components/settings/AiTab.tsx +24 -0
  45. package/app/components/settings/KnowledgeTab.tsx +1 -1
  46. package/app/components/settings/McpSkillCreateForm.tsx +1 -1
  47. package/app/components/settings/McpSkillRow.tsx +1 -1
  48. package/app/components/settings/McpSkillsSection.tsx +2 -2
  49. package/app/components/settings/McpTab.tsx +2 -2
  50. package/app/components/settings/PluginsTab.tsx +1 -1
  51. package/app/components/settings/Primitives.tsx +118 -6
  52. package/app/components/settings/SettingsContent.tsx +5 -2
  53. package/app/components/settings/UninstallTab.tsx +179 -0
  54. package/app/components/settings/UpdateTab.tsx +17 -5
  55. package/app/components/settings/types.ts +2 -1
  56. package/app/components/setup/StepDots.tsx +2 -2
  57. package/app/components/ui/dialog.tsx +1 -1
  58. package/app/hooks/useAiOrganize.ts +122 -10
  59. package/app/hooks/useMention.ts +21 -3
  60. package/app/hooks/useSlashCommand.ts +18 -4
  61. package/app/lib/agent/reconnect.ts +40 -0
  62. package/app/lib/core/backlinks.ts +2 -2
  63. package/app/lib/core/git.ts +14 -10
  64. package/app/lib/fs.ts +2 -1
  65. package/app/lib/i18n-en.ts +46 -2
  66. package/app/lib/i18n-zh.ts +46 -2
  67. package/app/lib/organize-history.ts +74 -0
  68. package/app/lib/settings.ts +2 -0
  69. package/app/lib/types.ts +2 -0
  70. package/app/next.config.ts +23 -5
  71. package/bin/cli.js +6 -9
  72. package/bin/lib/mcp-build.js +74 -0
  73. package/bin/lib/mcp-spawn.js +8 -5
  74. package/bin/lib/port.js +17 -2
  75. package/bin/lib/stop.js +12 -2
  76. package/mcp/dist/index.cjs +43 -43
  77. package/mcp/src/index.ts +58 -12
  78. package/package.json +1 -1
  79. 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
+ }
@@ -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 {
@@ -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: ['github-slugger'],
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: path.join(__dirname),
20
+ outputFileTracingRoot: projectDir,
9
21
  turbopack: {
10
- root: path.join(__dirname),
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
- process.env.HOSTNAME ? undefined : { HOSTNAME: '127.0.0.1' }
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
- const mcpSdk = resolve(ROOT, 'mcp', 'node_modules', '@modelcontextprotocol', 'sdk', 'package.json');
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: 120, intervalMs: 2000, label: 'Web UI', logFile: LOG_PATH });
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: 120, intervalMs: 2000, label: 'Web UI', logFile: LOG_PATH });
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
+ }
@@ -1,17 +1,20 @@
1
1
  import { spawn } from 'node:child_process';
2
- import { existsSync, readFileSync } from 'node:fs';
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
- const mcpBundle = resolve(ROOT, 'mcp', 'dist', 'index.cjs');
12
- if (!existsSync(mcpBundle)) {
12
+ try {
13
+ ensureMcpBundle();
14
+ } catch (err) {
15
+ const message = err instanceof Error ? err.message : String(err);
13
16
  throw new Error(
14
- `MCP bundle not found: ${mcpBundle}\n` +
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, [mcpBundle], {
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 already in use`)} ${dim(`(${name})`)}`);
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
- 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|dist/index)" 2>/dev/null || true', { stdio: ['ignore', 'inherit', 'inherit'] }); } catch {}
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'));