@geminilight/mindos 0.5.58 → 0.5.59

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 (84) hide show
  1. package/README.md +1 -1
  2. package/README_zh.md +1 -1
  3. package/app/app/api/ask/route.ts +4 -1
  4. package/app/app/api/extract-pdf/route.ts +4 -2
  5. package/app/app/api/file/route.ts +11 -1
  6. package/app/app/api/health/route.ts +3 -1
  7. package/app/app/api/mcp/install-skill/route.ts +4 -2
  8. package/app/app/api/mcp/restart/route.ts +1 -1
  9. package/app/app/api/restart/route.ts +1 -1
  10. package/app/app/api/skills/route.ts +1 -1
  11. package/app/app/api/sync/route.ts +2 -2
  12. package/app/app/api/update/route.ts +1 -1
  13. package/app/app/api/update-check/route.ts +11 -3
  14. package/app/app/globals.css +4 -0
  15. package/app/app/layout.tsx +6 -0
  16. package/app/app/login/page.tsx +12 -1
  17. package/app/app/register-sw.tsx +4 -0
  18. package/app/components/Sidebar.tsx +1 -0
  19. package/app/components/SidebarLayout.tsx +1 -1
  20. package/app/instrumentation.ts +2 -1
  21. package/app/lib/core/list-spaces.ts +58 -0
  22. package/app/lib/project-root.ts +13 -0
  23. package/app/lib/template.ts +13 -6
  24. package/app/next-env.d.ts +1 -1
  25. package/bin/lib/mcp-spawn.js +38 -1
  26. package/mcp/README.md +3 -2
  27. package/mcp/src/index.ts +30 -0
  28. package/package.json +1 -1
  29. package/scripts/setup.js +1 -1
  30. package/skills/mindos/SKILL.md +2 -1
  31. package/skills/mindos/references/README.md +11 -0
  32. package/skills/mindos/references/post-task-hooks.md +53 -0
  33. package/skills/mindos/references/preference-capture.md +41 -0
  34. package/skills/mindos/references/sop-template.md +74 -0
  35. package/skills/mindos-zh/SKILL.md +2 -1
  36. package/skills/mindos-zh/references/README.md +11 -0
  37. package/skills/mindos-zh/references/post-task-hooks.md +53 -0
  38. package/skills/mindos-zh/references/preference-capture.md +41 -0
  39. package/skills/mindos-zh/references/sop-template.md +74 -0
  40. package/templates/empty/CONFIG.json +7 -5
  41. package/templates/empty/INSTRUCTION.md +5 -5
  42. package/templates/empty/README.md +1 -2
  43. package/templates/en/CONFIG.json +7 -5
  44. package/templates/en/INSTRUCTION.md +5 -5
  45. package/templates/en/README.md +1 -2
  46. package/templates/en//360/237/223/232 Resources/README.md" +4 -4
  47. package/templates/en//360/237/223/232 Resources//360/237/247/276 Books.csv" +1 -0
  48. package/templates/en//360/237/223/232 Resources//360/237/247/276 Learning Resources.csv" +1 -0
  49. package/templates/en//360/237/223/232 Resources//360/237/247/276 People to Follow.csv" +1 -0
  50. package/templates/en//360/237/223/232 Resources//360/237/247/276 Tools.csv" +1 -0
  51. package/templates/en//360/237/223/235 Notes/Ideas//360/237/247/252_example_product_idea.md" +9 -12
  52. package/templates/en//360/237/224/204 Workflows/Configurations//360/237/247/252_example_config_update_sop.md" +2 -3
  53. package/templates/en//360/237/224/204 Workflows/INSTRUCTION.md" +13 -5
  54. package/templates/en//360/237/232/200 Projects/Products//360/237/247/252_example_product_project_brief.md" +12 -14
  55. package/templates/template-generation-skill.md +4 -5
  56. package/templates/zh/CONFIG.json +7 -5
  57. package/templates/zh/INSTRUCTION.md +5 -5
  58. package/templates/zh/README.md +1 -2
  59. package/templates/zh//360/237/221/244 /347/224/273/345/203/217/README.md" +4 -4
  60. package/templates/zh//360/237/221/244 /347/224/273/345/203/217//342/232/231/357/270/{217 Preferences.md" → 217 /345/201/217/345/245/275.md" } +1 -1
  61. package/templates/zh//360/237/221/244 /347/224/273/345/203/217//360/237/216/{257 Focus.md" → 257 /350/201/232/347/204/246.md" } +1 -1
  62. package/templates/zh//360/237/221/244 /347/224/273/345/203/217//360/237/221/{244 Identity.md" → 244 /350/272/253/344/273/275.md" } +1 -1
  63. package/templates/zh//360/237/223/232 /350/265/204/346/272/220/README.md" +4 -4
  64. package/templates/zh//360/237/223/232 /350/265/204/346/272/220//360/237/247/276 /344/271/246/345/215/225.csv" +1 -0
  65. package/templates/zh//360/237/223/232 /350/265/204/346/272/220//360/237/247/276 /345/200/274/345/276/227/345/205/263/346/263/250/347/232/204/344/272/272.csv" +1 -0
  66. package/templates/zh//360/237/223/232 /350/265/204/346/272/220//360/237/247/276 /345/255/246/344/271/240/350/265/204/346/272/220.csv" +1 -0
  67. package/templates/zh//360/237/223/232 /350/265/204/346/272/220//360/237/247/276 /345/267/245/345/205/267/346/270/205/345/215/225.csv" +1 -0
  68. package/templates/zh//360/237/223/235 /347/254/224/350/256/260//346/203/263/346/263/225//360/237/247/252_example_/344/272/247/345/223/201/346/203/263/346/263/225.md" +8 -11
  69. package/templates/zh//360/237/224/204 /346/265/201/347/250/213/README.md" +1 -0
  70. package/templates/zh//360/237/224/204 /346/265/201/347/250/213//345/210/233/344/270/232/README.md" +3 -0
  71. package/templates/zh//360/237/224/204 /346/265/201/347/250/213//345/210/233/344/270/232//360/237/247/252_example_/346/257/217/345/221/250/345/210/233/345/247/213/344/272/272/350/277/220/350/220/245/350/212/202/345/245/217.md" +22 -0
  72. package/templates/zh//360/237/224/204 /346/265/201/347/250/213//351/205/215/347/275/256//360/237/247/252_example_/351/205/215/347/275/256/346/233/264/346/226/260/346/265/201/347/250/213.md" +1 -1
  73. package/templates/zh//360/237/232/200 /351/241/271/347/233/256//344/272/247/345/223/201//360/237/247/252_example_/344/272/247/345/223/201/351/241/271/347/233/256/347/256/200/346/212/245.md" +12 -14
  74. package/templates/empty/CONFIG.md +0 -73
  75. package/templates/en/CONFIG.md +0 -73
  76. package/templates/en//360/237/223/232 Resources//360/237/247/276 AI Influencers.csv" +0 -1
  77. package/templates/en//360/237/223/232 Resources//360/237/247/276 AI Products.csv" +0 -1
  78. package/templates/en//360/237/223/232 Resources//360/237/247/276 AI Scholars.csv" +0 -1
  79. package/templates/en//360/237/223/232 Resources//360/237/247/276 AI Tools.csv" +0 -1
  80. package/templates/zh/CONFIG.md +0 -66
  81. package/templates/zh//360/237/223/232 /350/265/204/346/272/220//360/237/247/276 AI Inferencers.csv" +0 -1
  82. package/templates/zh//360/237/223/232 /350/265/204/346/272/220//360/237/247/276 AI /344/272/247/345/223/201.csv" +0 -1
  83. package/templates/zh//360/237/223/232 /350/265/204/346/272/220//360/237/247/276 AI /345/255/246/350/200/205/346/270/205/345/215/225.csv" +0 -1
  84. package/templates/zh//360/237/223/232 /350/265/204/346/272/220//360/237/247/276 AI /345/267/245/345/205/267/346/270/205/345/215/225.csv" +0 -1
package/README.md CHANGED
@@ -55,7 +55,7 @@ MindOS is where you think, and where your AI agents act — a local-first knowle
55
55
 
56
56
  **1. Global Sync — Breaking Memory Silos**
57
57
 
58
- Switch tools or start a new chat and you're re-transporting context, scattering knowledge. **With a built-in MCP server (20+ tools), MindOS connects all Agents to your core knowledge base with zero config. Record profile and project memory once to empower all AI tools.**
58
+ Switch tools or start a new chat and you're re-transporting context, scattering knowledge. **With a built-in MCP server, MindOS connects all Agents to your core knowledge base with zero config. Record profile and project memory once to empower all AI tools.**
59
59
 
60
60
  **2. Transparent & Controllable — No Black Boxes**
61
61
 
package/README_zh.md CHANGED
@@ -55,7 +55,7 @@ MindOS 是你思考的地方,也是 AI Agent 行动的起点——一个你和
55
55
 
56
56
  **1. 全局同步 — 打破记忆割裂**
57
57
 
58
- 切换工具带来上下文割裂,个人深度背景散落各处,导致知识无法复用。**MindOS 内置 MCP Server (支持 20+ 工具),所有 Agent 零配置直连核心知识库。项目记忆与 SOP 仅需一处记录,全局赋能所有 AI 工具。**
58
+ 切换工具带来上下文割裂,个人深度背景散落各处,导致知识无法复用。**MindOS 内置 MCP Server,所有 Agent 零配置直连核心知识库。项目记忆与 SOP 仅需一处记录,全局赋能所有 AI 工具。**
59
59
 
60
60
  **2. 透明可控 — 消除记忆黑箱**
61
61
 
@@ -182,7 +182,10 @@ export async function POST(req: NextRequest) {
182
182
  // 2. user-skill-rules.md — user's personalized rules from KB root (if exists)
183
183
  const isZh = serverSettings.disabledSkills?.includes('mindos') ?? false;
184
184
  const skillDirName = isZh ? 'mindos-zh' : 'mindos';
185
- const skillPath = path.resolve(process.cwd(), `data/skills/${skillDirName}/SKILL.md`);
185
+ const appDir = process.env.MINDOS_PROJECT_ROOT
186
+ ? path.join(process.env.MINDOS_PROJECT_ROOT, 'app')
187
+ : process.cwd();
188
+ const skillPath = path.join(appDir, `data/skills/${skillDirName}/SKILL.md`);
186
189
  const skill = readAbsoluteFile(skillPath);
187
190
 
188
191
  const mindRoot = getMindRoot();
@@ -27,8 +27,10 @@ function extractPdf(buf: Buffer): { text: string; pages: number } {
27
27
  // Write PDF to a temp file so the child script can read it.
28
28
  const tmpDir = os.tmpdir();
29
29
  const tmpPdf = path.join(tmpDir, `pdf-extract-${Date.now()}.pdf`);
30
- // Dynamic path construction to prevent Turbopack static analysis
31
- const scriptPath = [process.cwd(), 'scripts', 'extract-pdf.cjs'].join(path.sep);
30
+ const appDir = process.env.MINDOS_PROJECT_ROOT
31
+ ? path.join(process.env.MINDOS_PROJECT_ROOT, 'app')
32
+ : process.cwd();
33
+ const scriptPath = path.join(appDir, 'scripts', 'extract-pdf.cjs');
32
34
 
33
35
  fs.writeFileSync(tmpPdf, buf);
34
36
  try {
@@ -18,6 +18,7 @@ import {
18
18
  appendCsvRow,
19
19
  getMindRoot,
20
20
  invalidateCache,
21
+ listMindSpaces,
21
22
  } from '@/lib/fs';
22
23
  import { createSpaceFilesystem } from '@/lib/core/create-space';
23
24
 
@@ -25,10 +26,19 @@ function err(msg: string, status = 400) {
25
26
  return NextResponse.json({ error: msg }, { status });
26
27
  }
27
28
 
28
- // GET /api/file?path=foo.md&op=read_file|read_lines
29
+ // GET /api/file?path=foo.md&op=read_file|read_lines | GET ?op=list_spaces (no path)
29
30
  export async function GET(req: NextRequest) {
30
31
  const filePath = req.nextUrl.searchParams.get('path');
31
32
  const op = req.nextUrl.searchParams.get('op') ?? 'read_file';
33
+
34
+ if (op === 'list_spaces') {
35
+ try {
36
+ return NextResponse.json({ spaces: listMindSpaces() });
37
+ } catch (e) {
38
+ return err((e as Error).message, 500);
39
+ }
40
+ }
41
+
32
42
  if (!filePath) return err('missing path');
33
43
 
34
44
  try {
@@ -11,8 +11,10 @@ function readVersion(): string {
11
11
  // 1. Env var set by CLI (most reliable)
12
12
  if (process.env.npm_package_version) return process.env.npm_package_version;
13
13
 
14
- // 2. Try known relative paths from Next.js app directory
14
+ // 2. Try known relative paths MINDOS_PROJECT_ROOT is reliable in standalone mode
15
+ const projRoot = process.env.MINDOS_PROJECT_ROOT;
15
16
  const candidates = [
17
+ ...(projRoot ? [join(projRoot, 'package.json')] : []),
16
18
  join(process.cwd(), '..', 'package.json'), // dev: app/ → root
17
19
  join(process.cwd(), 'package.json'), // if cwd is root
18
20
  join(dirname(process.cwd()), 'package.json'), // standalone edge case
@@ -36,9 +36,11 @@ const AGENT_NAME_MAP: Record<string, string> = {
36
36
 
37
37
  /** Fallback: find local skills directory for offline installs */
38
38
  function findLocalSkillsDir(): string | null {
39
+ const projRoot = process.env.MINDOS_PROJECT_ROOT || path.resolve(process.cwd(), '..');
39
40
  const candidates = [
40
- path.resolve(process.cwd(), 'data/skills'), // app/data/skills/
41
- path.resolve(process.cwd(), '..', 'skills'), // project-root/skills/
41
+ path.resolve(process.cwd(), 'data/skills'), // app/data/skills/
42
+ path.join(projRoot, 'skills'), // project-root/skills/
43
+ path.join(projRoot, 'app', 'data', 'skills'), // standalone fallback
42
44
  ];
43
45
  for (const dir of candidates) {
44
46
  if (fs.existsSync(dir)) return dir;
@@ -57,7 +57,7 @@ export async function POST() {
57
57
  await new Promise(r => setTimeout(r, 1000));
58
58
 
59
59
  // Step 3: Spawn new MCP server
60
- const root = resolve(process.cwd(), '..');
60
+ const root = process.env.MINDOS_PROJECT_ROOT || resolve(process.cwd(), '..');
61
61
  const mcpDir = resolve(root, 'mcp');
62
62
 
63
63
  if (!existsSync(resolve(mcpDir, 'node_modules'))) {
@@ -5,7 +5,7 @@ import { resolve } from 'node:path';
5
5
 
6
6
  export async function POST() {
7
7
  try {
8
- const cliPath = process.env.MINDOS_CLI_PATH || resolve(process.cwd(), '..', 'bin', 'cli.js');
8
+ const cliPath = process.env.MINDOS_CLI_PATH || resolve(process.env.MINDOS_PROJECT_ROOT || process.cwd(), '..', 'bin', 'cli.js');
9
9
  const nodeBin = process.env.MINDOS_NODE_BIN || process.execPath;
10
10
  // Use 'restart' (stop all → wait for ports free → start) instead of bare
11
11
  // 'start' which would fail assertPortFree because the current process and
@@ -5,7 +5,7 @@ import path from 'path';
5
5
  import os from 'os';
6
6
  import { readSettings, writeSettings } from '@/lib/settings';
7
7
 
8
- const PROJECT_ROOT = path.resolve(process.cwd(), '..');
8
+ const PROJECT_ROOT = process.env.MINDOS_PROJECT_ROOT || path.resolve(process.cwd(), '..');
9
9
 
10
10
  function getMindRoot(): string {
11
11
  const s = readSettings();
@@ -37,9 +37,9 @@ function isGitRepo(dir: string) {
37
37
  return existsSync(join(dir, '.git'));
38
38
  }
39
39
 
40
- /** Resolve path to bin/cli.js — prefer env var set by CLI launcher, fall back to cwd. */
40
+ /** Resolve path to bin/cli.js — prefer env var set by CLI launcher, fall back to project root. */
41
41
  function getCliPath() {
42
- return process.env.MINDOS_CLI_PATH || resolve(process.cwd(), '..', 'bin', 'cli' + '.js');
42
+ return process.env.MINDOS_CLI_PATH || resolve(process.env.MINDOS_PROJECT_ROOT || process.cwd(), '..', 'bin', 'cli' + '.js');
43
43
  }
44
44
 
45
45
  /** Run CLI command via execFile — avoids shell injection by passing args as array */
@@ -12,7 +12,7 @@ import { resolve } from 'node:path';
12
12
  */
13
13
  export async function POST() {
14
14
  try {
15
- const cliPath = process.env.MINDOS_CLI_PATH || resolve(process.cwd(), '..', 'bin', 'cli.js');
15
+ const cliPath = process.env.MINDOS_CLI_PATH || resolve(process.env.MINDOS_PROJECT_ROOT || process.cwd(), '..', 'bin', 'cli.js');
16
16
  const nodeBin = process.env.MINDOS_NODE_BIN || process.execPath;
17
17
 
18
18
  // Strip MINDOS_* env vars so the child reads fresh config
@@ -7,9 +7,17 @@ import { resolve } from 'path';
7
7
  // Read version from package.json (not process.env.npm_package_version — unavailable in daemon mode)
8
8
  let current = '0.0.0';
9
9
  try {
10
- const pkgPath = resolve(process.cwd(), '..', 'package.json');
11
- const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8'));
12
- current = pkg.version;
10
+ const projRoot = process.env.MINDOS_PROJECT_ROOT;
11
+ const candidates = [
12
+ ...(projRoot ? [resolve(projRoot, 'package.json')] : []),
13
+ resolve(process.cwd(), '..', 'package.json'),
14
+ ];
15
+ for (const p of candidates) {
16
+ try {
17
+ const pkg = JSON.parse(readFileSync(p, 'utf-8'));
18
+ if (pkg.version) { current = pkg.version; break; }
19
+ } catch { /* try next */ }
20
+ }
13
21
  } catch {}
14
22
 
15
23
  // npm registry sources: prefer China mirror, fallback to official
@@ -422,3 +422,7 @@ a:focus-visible,
422
422
  .wysiwyg-wrapper:focus-within {
423
423
  outline: none;
424
424
  }
425
+
426
+ /* macOS Electron: traffic-light safe zone. Actual CSS is injected by Electron
427
+ main process via webContents.insertCSS(); this is a no-op fallback. */
428
+ .electron-mac-titlebar-pad { display: none; }
@@ -88,6 +88,12 @@ export default async function RootLayout({
88
88
  __html: `(function(){if(typeof Node!=='undefined'){var o=Node.prototype.removeChild;Node.prototype.removeChild=function(c){if(c.parentNode!==this){try{return o.call(c.parentNode,c)}catch(e){return c}}return o.call(this,c)};var i=Node.prototype.insertBefore;Node.prototype.insertBefore=function(n,r){if(r&&r.parentNode!==this){try{return i.call(r.parentNode,n,r)}catch(e){return i.call(this,n,null)}}return i.call(this,n,r)}}})();`,
89
89
  }}
90
90
  />
91
+ {/* Electron macOS: set data-electron-mac before first paint so sidebar clears traffic lights */}
92
+ <script
93
+ dangerouslySetInnerHTML={{
94
+ __html: `(function(){try{if(/electron/i.test(navigator.userAgent)&&/macintosh/i.test(navigator.userAgent)){document.documentElement.setAttribute('data-electron-mac','')}}catch(e){}})();`,
95
+ }}
96
+ />
91
97
  {/* Apply user appearance settings before first paint, preventing flash */}
92
98
  <script
93
99
  dangerouslySetInnerHTML={{
@@ -118,9 +118,20 @@ function LoginForm() {
118
118
  );
119
119
  }
120
120
 
121
+ function LoginFallback() {
122
+ return (
123
+ <div className="min-h-screen bg-background flex items-center justify-center px-4">
124
+ <div className="flex flex-col items-center gap-3 text-muted-foreground">
125
+ <Loader2 className="h-8 w-8 animate-spin text-[var(--amber)]" aria-hidden />
126
+ <p className="text-sm">Loading…</p>
127
+ </div>
128
+ </div>
129
+ );
130
+ }
131
+
121
132
  export default function LoginPage() {
122
133
  return (
123
- <Suspense>
134
+ <Suspense fallback={<LoginFallback />}>
124
135
  <LoginForm />
125
136
  </Suspense>
126
137
  );
@@ -4,6 +4,10 @@ import { useEffect } from 'react';
4
4
 
5
5
  export default function RegisterSW() {
6
6
  useEffect(() => {
7
+ // Electron embedded Chromium: SW can leave the UI blank or stall hydration; skip.
8
+ if (typeof navigator !== 'undefined' && /electron/i.test(navigator.userAgent)) {
9
+ return;
10
+ }
7
11
  if ('serviceWorker' in navigator) {
8
12
  navigator.serviceWorker.register('/sw.js').catch((err) => {
9
13
  console.warn('[SW] Registration failed:', err);
@@ -90,6 +90,7 @@ export default function Sidebar({ fileTree, collapsed = false, onCollapse, onExp
90
90
 
91
91
  const sidebarContent = (
92
92
  <div className="flex flex-col h-full">
93
+ <div className="shrink-0 electron-mac-titlebar-pad" />
93
94
  <div className="flex items-center justify-between px-4 py-4 border-b border-border shrink-0">
94
95
  <Link href="/" className="flex items-center gap-2 hover:opacity-80 transition-opacity">
95
96
  <Logo id="desktop" />
@@ -376,7 +376,7 @@ export default function SidebarLayout({ fileTree, children }: SidebarLayoutProps
376
376
  #main-content {
377
377
  padding-left: ${lp.panelOpen && lp.panelMaximized ? '100vw' : `${lp.panelOpen ? lp.railWidth + lp.effectivePanelWidth : lp.railWidth}px`} !important;
378
378
  padding-right: calc(var(--right-panel-width) + var(--right-agent-detail-width)) !important;
379
- padding-top: 0 !important;
379
+ padding-top: 0;
380
380
  }
381
381
  }
382
382
  `}</style>
@@ -11,7 +11,8 @@ export async function register() {
11
11
  // createRequire() calls. The only way to load a runtime-computed path
12
12
  // is to hide the require call inside a Function constructor, which is
13
13
  // opaque to bundler static analysis.
14
- const syncModule = resolve(process.cwd(), '..', 'bin', 'lib', 'sync.js');
14
+ const projRoot = process.env.MINDOS_PROJECT_ROOT || resolve(process.cwd(), '..');
15
+ const syncModule = resolve(projRoot, 'bin', 'lib', 'sync.js');
15
16
  // eslint-disable-next-line @typescript-eslint/no-implied-eval
16
17
  const dynamicRequire = new Function('id', 'return require(id)') as (id: string) => any;
17
18
  const { startSyncDaemon } = dynamicRequire(syncModule);
@@ -0,0 +1,58 @@
1
+ import path from 'path';
2
+ import type { FileNode } from './types';
3
+ import { readFile } from './fs-ops';
4
+
5
+ /** One top-level Mind Space (aligned with home page Spaces grid). */
6
+ export interface MindSpaceSummary {
7
+ /** Directory entry name as on disk (may include leading emoji). */
8
+ name: string;
9
+ /** Relative path to the space directory, forward slashes only. */
10
+ path: string;
11
+ /** Count of .md/.csv files under this space (recursive). */
12
+ fileCount: number;
13
+ /** First non-empty body line from README.md after the title, or empty. */
14
+ description: string;
15
+ }
16
+
17
+ function countFiles(node: FileNode): number {
18
+ if (node.type === 'file') return 1;
19
+ return (node.children ?? []).reduce((sum, c) => sum + countFiles(c), 0);
20
+ }
21
+
22
+ function toPosixRel(p: string): string {
23
+ return p.split(path.sep).join('/');
24
+ }
25
+
26
+ function extractDescription(mindRoot: string, dirRelPosix: string): string {
27
+ const readmeRel = `${dirRelPosix}/README.md`;
28
+ try {
29
+ const content = readFile(mindRoot, readmeRel);
30
+ const lines = content.split('\n');
31
+ for (const line of lines) {
32
+ const trimmed = line.trim();
33
+ if (!trimmed || trimmed.startsWith('#')) continue;
34
+ return trimmed;
35
+ }
36
+ } catch {
37
+ /* no README */
38
+ }
39
+ return '';
40
+ }
41
+
42
+ /**
43
+ * Build summaries from the **app file tree** (same nodes as home Spaces).
44
+ * Caller passes `getFileTree()` so ignore rules match the UI cache.
45
+ */
46
+ export function summarizeTopLevelSpaces(mindRoot: string, tree: FileNode[]): MindSpaceSummary[] {
47
+ return tree
48
+ .filter((n) => n.type === 'directory' && !n.name.startsWith('.'))
49
+ .map((n) => {
50
+ const posix = toPosixRel(n.path);
51
+ return {
52
+ name: n.name,
53
+ path: posix,
54
+ fileCount: countFiles(n),
55
+ description: extractDescription(mindRoot, posix),
56
+ };
57
+ });
58
+ }
@@ -0,0 +1,13 @@
1
+ import path from 'path';
2
+
3
+ /**
4
+ * Resolve the MindOS project root directory.
5
+ *
6
+ * In standalone mode (`node .next/standalone/server.js`), `process.cwd()` points to
7
+ * `.next/standalone/` — NOT the app or project root. `MINDOS_PROJECT_ROOT` is injected
8
+ * by Desktop ProcessManager; for CLI launches it defaults to `cwd/..` which is correct
9
+ * when cwd is `app/`.
10
+ */
11
+ export function getProjectRoot(): string {
12
+ return process.env.MINDOS_PROJECT_ROOT || path.resolve(process.cwd(), '..');
13
+ }
@@ -1,5 +1,6 @@
1
1
  import fs from 'fs';
2
2
  import path from 'path';
3
+ import { getProjectRoot } from './project-root';
3
4
 
4
5
  /**
5
6
  * Recursively copy `src` to `dest`, skipping files that already exist in dest.
@@ -29,12 +30,18 @@ export function applyTemplate(template: string, destDir: string): void {
29
30
  throw new Error(`Invalid template: ${template}`);
30
31
  }
31
32
 
32
- // templates/ is at the repo root (sibling of app/)
33
- const repoRoot = path.resolve(process.cwd(), '..');
34
- const templateDir = path.join(repoRoot, 'templates', template);
35
-
36
- if (!fs.existsSync(templateDir)) {
37
- throw new Error(`Template "${template}" not found at ${templateDir}`);
33
+ // templates/ lives at the repo/project root (sibling of app/).
34
+ // In standalone mode process.cwd() is .next/standalone/ — unreliable for relative paths.
35
+ // MINDOS_PROJECT_ROOT is set by Desktop ProcessManager and CLI startup.
36
+ const projectRoot = getProjectRoot();
37
+ const candidates = [
38
+ path.join(projectRoot, 'templates', template),
39
+ path.resolve(process.cwd(), '..', 'templates', template),
40
+ path.resolve(process.cwd(), 'templates', template),
41
+ ];
42
+ const templateDir = candidates.find((d) => fs.existsSync(d));
43
+ if (!templateDir) {
44
+ throw new Error(`Template "${template}" not found at ${candidates.join(', ')}`);
38
45
  }
39
46
 
40
47
  if (!fs.existsSync(destDir)) {
package/app/next-env.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  /// <reference types="next" />
2
2
  /// <reference types="next/image-types/global" />
3
- import "./.next/types/routes.d.ts";
3
+ import "./.next/dev/types/routes.d.ts";
4
4
 
5
5
  // NOTE: This file should not be edited
6
6
  // see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
@@ -1,10 +1,46 @@
1
1
  import { execSync, spawn } from 'node:child_process';
2
- import { existsSync, readFileSync } from 'node:fs';
2
+ import { existsSync, readFileSync, readdirSync, rmSync, writeFileSync } from 'node:fs';
3
3
  import { resolve } from 'node:path';
4
+ import { arch, platform } from 'node:os';
4
5
  import { ROOT, CONFIG_PATH } from './constants.js';
5
6
  import { bold, red, yellow } from './colors.js';
6
7
  import { npmInstall } from './utils.js';
7
8
 
9
+ /**
10
+ * If mcp/node_modules was installed on a different platform (e.g. Linux CI → macOS user),
11
+ * native packages like esbuild will crash. Detect via stamp or @esbuild heuristics, then reinstall.
12
+ */
13
+ function ensureMcpNativeDeps() {
14
+ const mcpDir = resolve(ROOT, 'mcp');
15
+ const nm = resolve(mcpDir, 'node_modules');
16
+ if (!existsSync(nm)) return;
17
+
18
+ const host = `${platform()}-${arch()}`;
19
+ const stamp = resolve(mcpDir, '.mindos-npm-ci-platform');
20
+ let needsReinstall = false;
21
+
22
+ if (existsSync(stamp)) {
23
+ try {
24
+ needsReinstall = readFileSync(stamp, 'utf-8').trim() !== host;
25
+ } catch { /* fall through */ }
26
+ } else {
27
+ const esbuildDir = resolve(nm, '@esbuild');
28
+ if (existsSync(esbuildDir)) {
29
+ try {
30
+ const names = readdirSync(esbuildDir);
31
+ if (names.length > 0 && !names.includes(host)) needsReinstall = true;
32
+ } catch { /* ignore */ }
33
+ }
34
+ }
35
+
36
+ if (!needsReinstall) return;
37
+
38
+ console.log(yellow('MCP dependencies were built for another platform — reinstalling...'));
39
+ rmSync(nm, { recursive: true, force: true });
40
+ npmInstall(mcpDir, '--no-workspaces');
41
+ try { writeFileSync(stamp, host, 'utf-8'); } catch { /* non-fatal */ }
42
+ }
43
+
8
44
  export function spawnMcp(verbose = false) {
9
45
  const mcpPort = process.env.MINDOS_MCP_PORT || '8781';
10
46
  const webPort = process.env.MINDOS_WEB_PORT || '3456';
@@ -14,6 +50,7 @@ export function spawnMcp(verbose = false) {
14
50
  console.log(yellow('Installing MCP dependencies (first run)...\n'));
15
51
  npmInstall(resolve(ROOT, 'mcp'), '--no-workspaces');
16
52
  }
53
+ ensureMcpNativeDeps();
17
54
 
18
55
  // Read AUTH_TOKEN directly from config to avoid stale system env overriding
19
56
  // the user's configured token. Config is the source of truth for auth.
package/mcp/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # MindOS MCP Server
2
2
 
3
- Pure HTTP client wrapper that maps 22 MCP tools to the App REST API via `fetch`. Zero business logic — all operations are delegated to the App.
3
+ Pure HTTP client wrapper that maps MCP tools to the App REST API via `fetch`. Zero business logic — all operations are delegated to the App.
4
4
 
5
5
  ## Architecture
6
6
 
@@ -41,11 +41,12 @@ MCP_TRANSPORT=stdio mindos mcp # stdio mode
41
41
  | `MCP_PORT` | `8781` | HTTP listen port (configurable via `mindos onboard`) |
42
42
  | `MCP_ENDPOINT` | `/mcp` | HTTP endpoint path |
43
43
 
44
- ## MCP Tools (22)
44
+ ## MCP Tools
45
45
 
46
46
  | Tool | App API | Description |
47
47
  |------|---------|-------------|
48
48
  | `mindos_list_files` | `GET /api/files` | List all files in the knowledge base |
49
+ | `mindos_list_spaces` | `GET /api/file?op=list_spaces` | List top-level Mind Spaces (name, path, counts, README blurb) |
49
50
  | `mindos_read_file` | `GET /api/file?path=...` | Read file content (with offset/limit pagination) |
50
51
  | `mindos_write_file` | `POST /api/file` op=save_file | Overwrite file content |
51
52
  | `mindos_create_file` | `POST /api/file` op=create_file | Create a new .md or .csv file |
package/mcp/src/index.ts CHANGED
@@ -114,6 +114,36 @@ server.registerTool("mindos_list_files", {
114
114
  } catch (e) { logOp("mindos_list_files", { response_format }, "error", String(e)); return error(String(e)); }
115
115
  });
116
116
 
117
+ // ── mindos_list_spaces ──────────────────────────────────────────────────────
118
+
119
+ server.registerTool("mindos_list_spaces", {
120
+ title: "List Mind Spaces",
121
+ description:
122
+ "List top-level Mind Spaces (same as home Spaces grid): name, path, file count, and README blurb. Only spaces that appear in the file tree (at least one .md/.csv under the folder) are included.",
123
+ inputSchema: z.object({
124
+ response_format: z.enum(["markdown", "json"]).default("json"),
125
+ }),
126
+ annotations: { readOnlyHint: true },
127
+ }, async ({ response_format }) => {
128
+ try {
129
+ const json = await get("/api/file", { op: "list_spaces" });
130
+ const spaces = json.spaces as Array<{ name: string; path: string; fileCount: number; description: string }>;
131
+ if (response_format === "json") {
132
+ logOp("mindos_list_spaces", { response_format }, "ok", `${spaces?.length ?? 0} spaces`);
133
+ return ok(JSON.stringify({ spaces: spaces ?? [] }, null, 2));
134
+ }
135
+ const lines = (spaces ?? []).map(
136
+ (s) => `- **${s.name}** (\`${s.path}/\`) — ${s.fileCount} file(s)${s.description ? ` — ${s.description}` : ""}`,
137
+ );
138
+ const text = lines.length ? lines.join("\n") : "(no top-level spaces in tree)";
139
+ logOp("mindos_list_spaces", { response_format }, "ok", `${spaces?.length ?? 0} spaces`);
140
+ return ok(text);
141
+ } catch (e) {
142
+ logOp("mindos_list_spaces", { response_format }, "error", String(e));
143
+ return error(String(e));
144
+ }
145
+ });
146
+
117
147
  // ── mindos_read_file ────────────────────────────────────────────────────────
118
148
 
119
149
  server.registerTool("mindos_read_file", {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@geminilight/mindos",
3
- "version": "0.5.58",
3
+ "version": "0.5.59",
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",
package/scripts/setup.js CHANGED
@@ -598,7 +598,7 @@ function expandHomePath(p) {
598
598
  return p.startsWith('~/') ? resolve(homedir(), p.slice(2)) : p;
599
599
  }
600
600
 
601
- /** Parse JSONC (JSON with // and /* */ comments) for VS Code-based editor configs */
601
+ /** Parse JSONC: JSON plus line // comments and slash-star block comments (e.g. VS Code configs). */
602
602
  function parseJsonc(text) {
603
603
  let stripped = text.replace(/\\"|"(?:\\"|[^"])*"|(\/\/.*$)/gm, (m, g) => g ? '' : m);
604
604
  stripped = stripped.replace(/\/\*[\s\S]*?\*\//g, '');
@@ -52,7 +52,7 @@ Run this sequence before substantive edits:
52
52
  - If unavailable, read root `INSTRUCTION.md` and root `README.md` directly.
53
53
 
54
54
  2. Discover current structure dynamically.
55
- - Use `mindos_list_files` and targeted `mindos_search_notes`.
55
+ - Use `mindos_list_spaces` (top-level zones + README blurbs), `mindos_list_files`, and targeted `mindos_search_notes` as needed.
56
56
  - Do not assume fixed top-level directory names.
57
57
 
58
58
  3. Load local guidance around target paths.
@@ -96,6 +96,7 @@ Before any non-trivial write, confirm all checks:
96
96
 
97
97
  - `mindos_bootstrap`: Load startup context.
98
98
  - `mindos_list_files`: Inspect file tree.
99
+ - `mindos_list_spaces`: Top-level Mind Spaces with README blurbs (lighter than full tree).
99
100
  - `mindos_search_notes`: Locate relevant files by keyword/scope/type/date. **When searching, always issue multiple parallel searches with different keywords upfront** — synonyms, abbreviations, English/Chinese variants, and broader/narrower terms. A single keyword is fragile; casting a wider net on the first try avoids wasted rounds.
100
101
  - `mindos_get_recent`: Inspect latest activity.
101
102
  - `mindos_get_backlinks`: Assess impact before rename/move/delete.
@@ -0,0 +1,11 @@
1
+ # MindOS skill — reference bundle
2
+
3
+ Long-form supplements for `../SKILL.md`. Load a file only when the task needs it.
4
+
5
+ | File | When to read |
6
+ |------|----------------|
7
+ | [sop-template.md](./sop-template.md) | Creating or rewriting a workflow SOP in the KB |
8
+ | [post-task-hooks.md](./post-task-hooks.md) | After a write/multi-step task; proposing follow-ups |
9
+ | [preference-capture.md](./preference-capture.md) | User expresses standing preferences to persist |
10
+
11
+ Chinese mirror: `skills/mindos-zh/references/` (same filenames).
@@ -0,0 +1,53 @@
1
+ # Post-task hooks
2
+
3
+ After completing a task, check the conditions below. If one matches, make a **one-line** proposal to the user. If none match, end quietly.
4
+
5
+ ## Discipline
6
+
7
+ 1. Do not propose after simple operations (rename, append one line, read-only queries).
8
+ 2. **At most 1 proposal per task** — pick the highest priority match.
9
+ 3. One sentence + specific target file/path. Only expand if the user says yes.
10
+ 4. Check `user-skill-rules.md` suppression section first — skip any suppressed hook.
11
+ 5. If the user asked for **no suggestions / quiet mode** for this turn or session, skip all hooks.
12
+
13
+ ## Default hooks
14
+
15
+ ### Experience capture (priority: high)
16
+ - **Condition**: task involved debugging, troubleshooting, or took multiple rounds to resolve.
17
+ - **Propose**: "Record this experience to {related experience file}?"
18
+ - **Format**: problem → cause → solution → rule
19
+
20
+ ### Consistency sync (priority: high)
21
+ - **Condition**: edited file A, and A is referenced by other files (check via `get_backlinks`).
22
+ - **Propose**: "{B} references what you just changed — sync it?"
23
+
24
+ ### Linked update (priority: medium)
25
+ - **Condition**: changed a CSV/TODO item status, and related docs exist.
26
+ - **Propose**: "Sync the corresponding info in {related doc}?"
27
+
28
+ ### Structure classification (priority: medium)
29
+ - **Condition**: created a new file in a temporary location or inbox.
30
+ - **Propose**: "Move this to {recommended directory}?"
31
+
32
+ ### Pattern extraction (priority: low)
33
+ - **Condition**: 3+ structurally similar operations in the current session.
34
+ - **Propose**: "This operation repeated multiple times — create an SOP?"
35
+
36
+ ### SOP drift (priority: medium)
37
+ - **Condition**: task was executed following an existing SOP, but actual steps diverged from documented steps.
38
+ - **Propose**: "Execution diverged from {SOP file} — update the SOP?"
39
+ - **Action**: update divergent steps, append new pitfalls, set `<!-- last-used: -->` to today.
40
+
41
+ ### Conversation retrospective (priority: low)
42
+ - **Condition**: session >10 turns and involved decisions, trade-offs, or lessons.
43
+ - **Propose**: "This conversation had some decisions worth capturing — do a retrospective?"
44
+
45
+ ## User-defined hooks
46
+
47
+ Add your own below, same format:
48
+
49
+ ```markdown
50
+ ### Weekly report material (priority: medium)
51
+ - Condition: updated a Project-related file.
52
+ - Propose: "Record this change in the weekly report?"
53
+ ```