@geminilight/mindos 0.6.40 → 0.6.41
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/_standalone/.mindos-build-version +1 -1
- package/_standalone/.next/BUILD_ID +1 -1
- package/_standalone/.next/app-path-routes-manifest.json +18 -18
- package/_standalone/.next/build-manifest.json +3 -3
- package/_standalone/.next/cache/.previewinfo +1 -1
- package/_standalone/.next/cache/.rscinfo +1 -1
- package/_standalone/.next/cache/config.json +3 -3
- package/_standalone/.next/prerender-manifest.json +3 -3
- package/_standalone/.next/react-loadable-manifest.json +5 -1
- package/_standalone/.next/server/app/.well-known/agent-card.json/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/_global-error.html +2 -2
- package/_standalone/.next/server/app/_global-error.rsc +1 -1
- package/_standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/_standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
- package/_standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
- package/_standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
- package/_standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
- package/_standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/_standalone/.next/server/app/_not-found/page.js +1 -1
- package/_standalone/.next/server/app/_not-found/page.js.nft.json +1 -1
- package/_standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/agents/[agentKey]/page.js +1 -1
- package/_standalone/.next/server/app/agents/[agentKey]/page.js.nft.json +1 -1
- package/_standalone/.next/server/app/agents/[agentKey]/page_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/agents/page.js +2 -2
- package/_standalone/.next/server/app/agents/page.js.nft.json +1 -1
- package/_standalone/.next/server/app/agents/page_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/a2a/agents/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/a2a/delegations/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/a2a/discover/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/a2a/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/acp/config/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/acp/detect/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/acp/install/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/acp/registry/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/acp/session/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/agent-activity/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/ask/route.js +1 -1
- package/_standalone/.next/server/app/api/ask/route.js.nft.json +1 -1
- package/_standalone/.next/server/app/api/ask/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/ask-sessions/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/auth/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/backlinks/route.js.nft.json +1 -1
- package/_standalone/.next/server/app/api/backlinks/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/bootstrap/route.js.nft.json +1 -1
- package/_standalone/.next/server/app/api/bootstrap/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/changes/route.js.nft.json +1 -1
- package/_standalone/.next/server/app/api/changes/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/export/route.js.nft.json +1 -1
- package/_standalone/.next/server/app/api/export/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/extract-pdf/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/file/import/route.js.nft.json +1 -1
- package/_standalone/.next/server/app/api/file/import/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/file/route.js.nft.json +1 -1
- package/_standalone/.next/server/app/api/file/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/files/route.js.nft.json +1 -1
- package/_standalone/.next/server/app/api/files/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/git/route.js.nft.json +1 -1
- package/_standalone/.next/server/app/api/git/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/graph/route.js.nft.json +1 -1
- package/_standalone/.next/server/app/api/graph/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/health/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/inbox/route.js.nft.json +1 -1
- package/_standalone/.next/server/app/api/inbox/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/init/route.js.nft.json +1 -1
- package/_standalone/.next/server/app/api/init/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/mcp/agents/route.js.nft.json +1 -1
- package/_standalone/.next/server/app/api/mcp/agents/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/mcp/install/route.js +1 -1
- package/_standalone/.next/server/app/api/mcp/install/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/mcp/install-skill/route.js.nft.json +1 -1
- package/_standalone/.next/server/app/api/mcp/install-skill/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/mcp/restart/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/mcp/status/route.js +1 -1
- package/_standalone/.next/server/app/api/mcp/status/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/monitoring/route.js.nft.json +1 -1
- package/_standalone/.next/server/app/api/monitoring/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/recent-files/route.js.nft.json +1 -1
- package/_standalone/.next/server/app/api/recent-files/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/restart/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/search/route.js.nft.json +1 -1
- package/_standalone/.next/server/app/api/search/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/settings/list-models/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/settings/reset-token/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/settings/route.js.nft.json +1 -1
- package/_standalone/.next/server/app/api/settings/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/settings/test-key/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/setup/check-path/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/setup/check-port/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/setup/generate-token/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/setup/ls/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/setup/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/skills/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/sync/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/tree-version/route.js.nft.json +1 -1
- package/_standalone/.next/server/app/api/tree-version/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/uninstall/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/update/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/update-check/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/update-status/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/workflows/route.js.nft.json +1 -1
- package/_standalone/.next/server/app/api/workflows/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/changes/page.js +1 -1
- package/_standalone/.next/server/app/changes/page.js.nft.json +1 -1
- package/_standalone/.next/server/app/changes/page_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/echo/[segment]/page.js +1 -1
- package/_standalone/.next/server/app/echo/[segment]/page.js.nft.json +1 -1
- package/_standalone/.next/server/app/echo/[segment]/page_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/echo/page.js +1 -1
- package/_standalone/.next/server/app/echo/page.js.nft.json +1 -1
- package/_standalone/.next/server/app/echo/page_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/explore/page.js +1 -1
- package/_standalone/.next/server/app/explore/page.js.nft.json +1 -1
- package/_standalone/.next/server/app/explore/page_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/help/page.js +2 -2
- package/_standalone/.next/server/app/help/page.js.nft.json +1 -1
- package/_standalone/.next/server/app/help/page_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/login/page.js +1 -1
- package/_standalone/.next/server/app/login/page.js.nft.json +1 -1
- package/_standalone/.next/server/app/login/page_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/page.js +2 -2
- package/_standalone/.next/server/app/page.js.nft.json +1 -1
- package/_standalone/.next/server/app/page_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/setup/page.js +2 -2
- package/_standalone/.next/server/app/setup/page.js.nft.json +1 -1
- package/_standalone/.next/server/app/setup/page_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/trash/page.js +3 -3
- package/_standalone/.next/server/app/trash/page.js.nft.json +1 -1
- package/_standalone/.next/server/app/trash/page_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/view/[...path]/page.js +3 -3
- package/_standalone/.next/server/app/view/[...path]/page.js.nft.json +1 -1
- package/_standalone/.next/server/app/view/[...path]/page_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app-paths-manifest.json +18 -18
- package/_standalone/.next/server/chunks/1550.js +1 -1
- package/_standalone/.next/server/chunks/2190.js +11 -0
- package/_standalone/.next/server/chunks/{6365.js → 2536.js} +2 -2
- package/_standalone/.next/server/chunks/5648.js +2 -0
- package/_standalone/.next/server/chunks/8388.js +1 -1
- package/_standalone/.next/server/chunks/953.js +1 -1
- package/_standalone/.next/server/chunks/9539.js +219 -0
- package/_standalone/.next/server/middleware-build-manifest.js +1 -1
- package/_standalone/.next/server/middleware-react-loadable-manifest.js +1 -1
- package/_standalone/.next/server/next-font-manifest.js +1 -1
- package/_standalone/.next/server/next-font-manifest.json +1 -1
- package/_standalone/.next/server/pages/500.html +2 -2
- package/_standalone/.next/server/server-reference-manifest.js +1 -1
- package/_standalone/.next/server/server-reference-manifest.json +1 -1
- package/_standalone/.next/static/chunks/1053-b70535785cc5aaee.js +29 -0
- package/_standalone/.next/static/chunks/{8663-de911d2d395622be.js → 1880-c2a9e76201841c86.js} +1 -1
- package/_standalone/.next/static/chunks/3637.0541ac2d0ea7de1f.js +1 -0
- package/_standalone/.next/static/chunks/4563-b2a2ce80aff845af.js +6 -0
- package/_standalone/.next/static/chunks/6981-3d7dcac2d12a5670.js +1 -0
- package/_standalone/.next/static/chunks/7144-5febf62f1a79fe64.js +1 -0
- package/_standalone/.next/static/chunks/app/agents/[agentKey]/page-773071a99c4daac2.js +1 -0
- package/_standalone/.next/static/chunks/app/agents/page-6102a884b2cb3cfe.js +5 -0
- package/_standalone/.next/static/chunks/app/help/page-2325d25b6846ca07.js +1 -0
- package/_standalone/.next/static/chunks/app/{layout-9378c1c8d3e5761b.js → layout-42cdbce19f404567.js} +34 -34
- package/_standalone/.next/static/chunks/app/{page-9bae420fbbdc5fff.js → page-8c9643b649e01735.js} +1 -1
- package/_standalone/.next/static/chunks/app/setup/page-d158b8cb533feb1e.js +1 -0
- package/_standalone/.next/static/chunks/app/trash/{page-b61ef2d5cd4f8d73.js → page-e9ab74ffeb96af41.js} +1 -1
- package/_standalone/.next/static/chunks/app/view/[...path]/page-764a69a1c8bd4eef.js +12 -0
- package/_standalone/.next/static/chunks/{webpack-c28c55d0a6021a6b.js → webpack-7b276daaa930d480.js} +1 -1
- package/_standalone/.next/static/css/bc9179074eaf65ae.css +1 -0
- package/_standalone/.next/trace +63 -63
- package/_standalone/__tests__/api/mcp-install.test.ts +23 -0
- package/_standalone/__tests__/cli/agent-routing.test.ts +232 -0
- package/_standalone/__tests__/cli/file-subcommands.test.ts +379 -0
- package/_standalone/__tests__/core/tools.test.ts +3 -6
- package/_standalone/components/FileTree.tsx +3 -2
- package/_standalone/components/MarkdownView.tsx +30 -15
- package/_standalone/components/RightAskPanel.tsx +36 -6
- package/_standalone/components/Sidebar.tsx +3 -3
- package/_standalone/components/agents/AgentsMcpSection.tsx +3 -0
- package/_standalone/components/settings/McpAgentInstall.tsx +94 -27
- package/_standalone/components/settings/McpSkillsSection.tsx +1 -1
- package/_standalone/components/settings/McpTab.tsx +484 -340
- package/_standalone/components/settings/SettingsContent.tsx +12 -6
- package/_standalone/components/settings/types.ts +3 -0
- package/_standalone/components/setup/StepAgents.tsx +113 -47
- package/_standalone/components/setup/StepReview.tsx +14 -27
- package/_standalone/components/setup/types.ts +6 -0
- package/_standalone/data/skills/mindos/SKILL.md +92 -92
- package/_standalone/data/skills/mindos/references/write-supplement.md +119 -0
- package/_standalone/data/skills/mindos-zh/SKILL.md +100 -104
- package/_standalone/data/skills/mindos-zh/references/write-supplement.md +119 -0
- package/_standalone/lib/i18n/modules/features.ts +4 -4
- package/_standalone/lib/i18n/modules/knowledge.ts +4 -0
- package/_standalone/lib/i18n/modules/onboarding.ts +40 -30
- package/_standalone/lib/i18n/modules/settings.ts +78 -6
- package/_standalone/lib/mcp-snippets.ts +5 -1
- package/_standalone/tsconfig.tsbuildinfo +1 -1
- package/app/app/api/ask/route.ts +3 -2
- package/app/app/api/mcp/install/route.ts +2 -1
- package/app/app/api/mcp/status/route.ts +14 -6
- package/app/app/view/[...path]/ViewPageClient.tsx +12 -27
- package/app/components/FileTree.tsx +3 -2
- package/app/components/MarkdownView.tsx +30 -15
- package/app/components/RightAskPanel.tsx +36 -6
- package/app/components/Sidebar.tsx +3 -3
- package/app/components/agents/AgentsMcpSection.tsx +3 -0
- package/app/components/help/HelpContent.tsx +1 -0
- package/app/components/settings/McpAgentInstall.tsx +94 -27
- package/app/components/settings/McpSkillsSection.tsx +1 -1
- package/app/components/settings/McpTab.tsx +484 -340
- package/app/components/settings/SettingsContent.tsx +12 -6
- package/app/components/settings/types.ts +3 -0
- package/app/components/setup/StepAgents.tsx +113 -47
- package/app/components/setup/StepReview.tsx +14 -27
- package/app/components/setup/index.tsx +12 -11
- package/app/components/setup/types.ts +6 -0
- package/app/data/skills/mindos/SKILL.md +92 -92
- package/app/data/skills/mindos/references/write-supplement.md +119 -0
- package/app/data/skills/mindos-zh/SKILL.md +100 -104
- package/app/data/skills/mindos-zh/references/write-supplement.md +119 -0
- package/app/lib/fs.ts +0 -6
- package/app/lib/i18n/modules/features.ts +4 -4
- package/app/lib/i18n/modules/knowledge.ts +4 -0
- package/app/lib/i18n/modules/onboarding.ts +40 -30
- package/app/lib/i18n/modules/settings.ts +78 -6
- package/app/lib/mcp-agents.ts +1 -2
- package/app/lib/mcp-snippets.ts +5 -1
- package/app/lib/renderers/index.ts +2 -1
- package/bin/cli.js +168 -1404
- package/bin/commands/agent.js +156 -20
- package/bin/commands/api.js +14 -11
- package/bin/commands/ask.js +79 -68
- package/bin/commands/build.js +26 -0
- package/bin/commands/config.js +170 -0
- package/bin/commands/dev.js +58 -0
- package/bin/commands/doctor.js +205 -0
- package/bin/commands/file.js +551 -36
- package/bin/commands/gateway.js +42 -0
- package/bin/commands/init-skills.js +56 -0
- package/bin/commands/logs.js +32 -0
- package/bin/commands/mcp-cmd.js +57 -0
- package/bin/commands/onboard.js +25 -0
- package/bin/commands/open.js +41 -0
- package/bin/commands/restart.js +48 -0
- package/bin/commands/search.js +16 -14
- package/bin/commands/space.js +96 -25
- package/bin/commands/start.js +262 -0
- package/bin/commands/status.js +2 -2
- package/bin/commands/stop.js +14 -0
- package/bin/commands/sync-cmd.js +134 -0
- package/bin/commands/token.js +98 -0
- package/bin/commands/uninstall.js +154 -0
- package/bin/commands/update.js +286 -0
- package/bin/lib/build.js +1 -1
- package/bin/lib/colors.js +8 -7
- package/bin/lib/command.js +37 -96
- package/bin/lib/config.js +5 -0
- package/bin/lib/csv.js +19 -0
- package/bin/lib/jsonc.js +12 -0
- package/bin/lib/markdown.js +69 -0
- package/bin/lib/mcp-agents.js +1 -6
- package/bin/lib/mcp-build.js +1 -1
- package/bin/lib/mcp-install.js +2 -1
- package/bin/lib/one-shot.js +88 -0
- package/bin/lib/path-expand.js +9 -0
- package/bin/lib/remote.js +65 -0
- package/bin/lib/repl.js +167 -0
- package/bin/lib/{utils.js → shell.js} +10 -26
- package/bin/lib/skill-check.js +1 -1
- package/bin/lib/sse-stream.js +167 -0
- package/package.json +2 -2
- package/scripts/setup.js +182 -120
- package/skills/mindos/SKILL.md +92 -92
- package/skills/mindos-zh/SKILL.md +100 -104
- package/_standalone/.next/server/chunks/1955.js +0 -11
- package/_standalone/.next/server/chunks/3680.js +0 -1
- package/_standalone/.next/server/chunks/4497.js +0 -219
- package/_standalone/.next/server/chunks/5560.js +0 -2
- package/_standalone/.next/static/chunks/1053-0adaccc98a752a58.js +0 -29
- package/_standalone/.next/static/chunks/3637.f9a42cca59fd5bb5.js +0 -1
- package/_standalone/.next/static/chunks/4563-c2afaeacb241d1d0.js +0 -6
- package/_standalone/.next/static/chunks/6090-c98268ca726a68d3.js +0 -1
- package/_standalone/.next/static/chunks/9371-575600301da5d6bb.js +0 -1
- package/_standalone/.next/static/chunks/app/agents/[agentKey]/page-3e08abb495ecd5fd.js +0 -1
- package/_standalone/.next/static/chunks/app/agents/page-e7e0f87ad3d765ac.js +0 -5
- package/_standalone/.next/static/chunks/app/help/page-3d0e1ceaa4abc243.js +0 -1
- package/_standalone/.next/static/chunks/app/setup/page-99ed3d1bb6b8f4ef.js +0 -1
- package/_standalone/.next/static/chunks/app/view/[...path]/page-44fa78cbea613a78.js +0 -12
- package/_standalone/.next/static/css/d300701f384db50d.css +0 -1
- package/_standalone/components/renderers/agent-inspector/manifest.ts +0 -16
- /package/_standalone/.next/static/{rZLs1krFuduixvcVNe6q3 → Ij3PFh-a0zi5K_ANoSAW0}/_buildManifest.js +0 -0
- /package/_standalone/.next/static/{rZLs1krFuduixvcVNe6q3 → Ij3PFh-a0zi5K_ANoSAW0}/_ssgManifest.js +0 -0
package/bin/cli.js
CHANGED
|
@@ -1,178 +1,68 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
* MindOS CLI
|
|
4
|
+
* MindOS CLI — thin router.
|
|
5
5
|
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* mindos onboard --install-daemon — setup + install & start as background OS service
|
|
9
|
-
* mindos start — start app + MCP server (production, auto-rebuilds if needed)
|
|
10
|
-
* mindos start --daemon — install + start as background OS service
|
|
11
|
-
* mindos start --verbose — start with verbose MCP logging
|
|
12
|
-
* mindos dev — start app + MCP server (dev mode)
|
|
13
|
-
* mindos dev --turbopack — start with Turbopack (faster HMR)
|
|
14
|
-
* mindos build — build the app for production
|
|
15
|
-
* mindos mcp — start MCP server only
|
|
16
|
-
* mindos stop — stop running MindOS processes
|
|
17
|
-
* mindos restart — stop then start
|
|
18
|
-
* mindos open — open Web UI in the default browser
|
|
19
|
-
* mindos token — show current auth token and MCP config snippet
|
|
20
|
-
* mindos sync — show sync status
|
|
21
|
-
* mindos sync init — configure remote git repo for sync
|
|
22
|
-
* mindos sync now — manual trigger sync
|
|
23
|
-
* mindos sync conflicts — list conflict files
|
|
24
|
-
* mindos sync on|off — enable/disable auto-sync
|
|
25
|
-
* mindos gateway install — install background service (systemd/launchd)
|
|
26
|
-
* mindos gateway uninstall — remove background service
|
|
27
|
-
* mindos gateway start — start the background service
|
|
28
|
-
* mindos gateway stop — stop the background service
|
|
29
|
-
* mindos gateway status — show service status
|
|
30
|
-
* mindos gateway logs — tail service logs
|
|
31
|
-
* mindos doctor — health check (config, ports, build, daemon)
|
|
32
|
-
* mindos uninstall — fully uninstall MindOS (stop, remove daemon, npm uninstall)
|
|
33
|
-
* mindos update — update to latest version
|
|
34
|
-
* mindos logs — tail service logs (~/.mindos/mindos.log)
|
|
35
|
-
* mindos config show — print current config (API keys masked)
|
|
36
|
-
* mindos config set <key> <val> — update a single config field
|
|
37
|
-
* mindos config unset <key> — remove a config field
|
|
38
|
-
* mindos config validate — validate config file
|
|
6
|
+
* All command logic lives in bin/commands/*.js.
|
|
7
|
+
* This file only parses args, dispatches commands, and renders global help.
|
|
39
8
|
*/
|
|
40
9
|
|
|
41
|
-
import {
|
|
42
|
-
import {
|
|
43
|
-
import { dirname, resolve } from 'node:path';
|
|
44
|
-
import { homedir } from 'node:os';
|
|
10
|
+
import { readFileSync } from 'node:fs';
|
|
11
|
+
import { resolve } from 'node:path';
|
|
45
12
|
|
|
46
|
-
import { ROOT
|
|
47
|
-
import { bold, dim, cyan
|
|
13
|
+
import { ROOT } from './lib/constants.js';
|
|
14
|
+
import { bold, dim, cyan } from './lib/colors.js';
|
|
15
|
+
import { parseArgs, printCommandHelp } from './lib/command.js';
|
|
48
16
|
|
|
49
|
-
//
|
|
50
|
-
|
|
51
|
-
import
|
|
52
|
-
import { loadConfig, getStartMode, isDaemonMode } from './lib/config.js';
|
|
53
|
-
import { needsBuild, writeBuildStamp, cleanNextDir, ensureAppDeps, hasPrebuiltStandalone } from './lib/build.js';
|
|
54
|
-
import { isPortInUse, assertPortFree } from './lib/port.js';
|
|
55
|
-
import { savePids, clearPids } from './lib/pid.js';
|
|
56
|
-
import { stopMindos, killByPort } from './lib/stop.js';
|
|
57
|
-
import { printStartupInfo, getLocalIP } from './lib/startup.js';
|
|
58
|
-
import { spawnMcp } from './lib/mcp-spawn.js';
|
|
59
|
-
import { parseArgs, EXIT } from './lib/command.js';
|
|
60
|
-
import { MCP_AGENTS, detectAgentPresence } from './lib/mcp-agents.js';
|
|
61
|
-
|
|
62
|
-
// Heavy modules — loaded lazily inside command handlers to speed up CLI cold start
|
|
63
|
-
// gateway.js (426 lines), mcp-install.js (335), sync.js (472), mcp-build.js (74)
|
|
64
|
-
const lazy = {
|
|
65
|
-
gateway: () => import('./lib/gateway.js'),
|
|
66
|
-
mcpInstall: () => import('./lib/mcp-install.js'),
|
|
67
|
-
sync: () => import('./lib/sync.js'),
|
|
68
|
-
mcpBuild: () => import('./lib/mcp-build.js'),
|
|
69
|
-
};
|
|
70
|
-
|
|
71
|
-
// Thin wrappers for lazy modules — same API as before, but loaded on first call
|
|
72
|
-
async function getPlatform() { return (await lazy.gateway()).getPlatform(); }
|
|
73
|
-
async function runGatewayCommand(sub) { return (await lazy.gateway()).runGatewayCommand(sub); }
|
|
74
|
-
async function waitForHttp(...a) { return (await lazy.gateway()).waitForHttp(...a); }
|
|
75
|
-
async function waitForPortFree(...a) { return (await lazy.gateway()).waitForPortFree(...a); }
|
|
76
|
-
async function ensureMindosDir() { return (await lazy.gateway()).ensureMindosDir(); }
|
|
77
|
-
async function ensureMcpBundle() { return (await lazy.mcpBuild()).ensureMcpBundle(); }
|
|
78
|
-
async function mcpInstall() { return (await lazy.mcpInstall()).mcpInstall(); }
|
|
79
|
-
async function initSync(...a) { return (await lazy.sync()).initSync(...a); }
|
|
80
|
-
async function startSyncDaemon(...a) { return (await lazy.sync()).startSyncDaemon(...a); }
|
|
81
|
-
async function stopSyncDaemon() { return (await lazy.sync()).stopSyncDaemon(); }
|
|
82
|
-
async function getSyncStatus(...a) { return (await lazy.sync()).getSyncStatus(...a); }
|
|
83
|
-
async function manualSync(...a) { return (await lazy.sync()).manualSync(...a); }
|
|
84
|
-
async function listConflicts(...a) { return (await lazy.sync()).listConflicts(...a); }
|
|
85
|
-
async function setSyncEnabled(...a) { return (await lazy.sync()).setSyncEnabled(...a); }
|
|
86
|
-
|
|
87
|
-
// ── New modular commands ──────────────────────────────────────────────────────
|
|
17
|
+
// ── Modular commands ──────────────────────────────────────────────────────────
|
|
18
|
+
import * as agentCmd from './commands/agent.js';
|
|
19
|
+
import * as askCmd from './commands/ask.js';
|
|
88
20
|
import * as fileCmd from './commands/file.js';
|
|
89
21
|
import * as spaceCmd from './commands/space.js';
|
|
90
|
-
import * as
|
|
22
|
+
import * as searchCmd from './commands/search.js';
|
|
23
|
+
import * as startCmd from './commands/start.js';
|
|
24
|
+
import * as devCmd from './commands/dev.js';
|
|
25
|
+
import * as stopCmd from './commands/stop.js';
|
|
26
|
+
import * as restartCmd from './commands/restart.js';
|
|
27
|
+
import * as buildCmd from './commands/build.js';
|
|
91
28
|
import * as statusCmd from './commands/status.js';
|
|
29
|
+
import * as openCmd from './commands/open.js';
|
|
30
|
+
import * as mcpCmd from './commands/mcp-cmd.js';
|
|
31
|
+
import * as tokenCmd from './commands/token.js';
|
|
32
|
+
import * as syncCmd from './commands/sync-cmd.js';
|
|
33
|
+
import * as gatewayCmd from './commands/gateway.js';
|
|
34
|
+
import * as onboardCmd from './commands/onboard.js';
|
|
35
|
+
import * as configCmd from './commands/config.js';
|
|
36
|
+
import * as doctorCmd from './commands/doctor.js';
|
|
37
|
+
import * as updateCmd from './commands/update.js';
|
|
38
|
+
import * as uninstallCmd from './commands/uninstall.js';
|
|
39
|
+
import * as logsCmd from './commands/logs.js';
|
|
92
40
|
import * as apiCmd from './commands/api.js';
|
|
93
|
-
import * as
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
} catch {
|
|
114
|
-
cliPath = mindosBin;
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
if (cliPath) {
|
|
118
|
-
// cliPath is like /path/to/node_modules/@geminilight/mindos/bin/cli.js
|
|
119
|
-
// ROOT is /path/to/node_modules/@geminilight/mindos
|
|
120
|
-
return resolve(dirname(cliPath), '..');
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
} catch {}
|
|
124
|
-
// Fallback to static ROOT
|
|
125
|
-
return ROOT;
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
/**
|
|
129
|
-
* Build the app in the given root if the build stamp doesn't match the package version.
|
|
130
|
-
* Used by `mindos update` to pre-build before restarting the daemon.
|
|
131
|
-
*/
|
|
132
|
-
function buildIfNeeded(newRoot) {
|
|
133
|
-
// Check for prebuilt standalone first (npm package ships with it)
|
|
134
|
-
const standaloneServer = resolve(newRoot, '_standalone', 'server.js');
|
|
135
|
-
const standaloneStamp = resolve(newRoot, '_standalone', '.mindos-build-version');
|
|
136
|
-
if (existsSync(standaloneServer)) {
|
|
137
|
-
try {
|
|
138
|
-
const builtVersion = readFileSync(standaloneStamp, 'utf-8').trim();
|
|
139
|
-
const pkgVersion = JSON.parse(readFileSync(resolve(newRoot, 'package.json'), 'utf-8')).version;
|
|
140
|
-
if (builtVersion === pkgVersion) return; // prebuilt standalone matches, no build needed
|
|
141
|
-
} catch { /* fall through to legacy build */ }
|
|
41
|
+
import * as initSkillsCmd from './commands/init-skills.js';
|
|
42
|
+
|
|
43
|
+
// ── Command registry ──────────────────────────────────────────────────────────
|
|
44
|
+
|
|
45
|
+
const modules = [
|
|
46
|
+
agentCmd, askCmd,
|
|
47
|
+
fileCmd, spaceCmd, searchCmd,
|
|
48
|
+
startCmd, devCmd, stopCmd, restartCmd, buildCmd, statusCmd, openCmd,
|
|
49
|
+
mcpCmd, tokenCmd,
|
|
50
|
+
syncCmd,
|
|
51
|
+
gatewayCmd,
|
|
52
|
+
onboardCmd, configCmd, doctorCmd, updateCmd, uninstallCmd, logsCmd, apiCmd,
|
|
53
|
+
initSkillsCmd,
|
|
54
|
+
];
|
|
55
|
+
|
|
56
|
+
const commands = {};
|
|
57
|
+
for (const mod of modules) {
|
|
58
|
+
commands[mod.meta.name] = mod;
|
|
59
|
+
if (mod.meta.aliases) {
|
|
60
|
+
for (const alias of mod.meta.aliases) commands[alias] = mod;
|
|
142
61
|
}
|
|
143
|
-
|
|
144
|
-
const newBuildStamp = resolve(newRoot, 'app', '.next', '.mindos-build-version');
|
|
145
|
-
const newNextBin = resolve(newRoot, 'app', 'node_modules', '.bin', 'next');
|
|
146
|
-
|
|
147
|
-
let needBuild = true;
|
|
148
|
-
try {
|
|
149
|
-
const builtVersion = readFileSync(newBuildStamp, 'utf-8').trim();
|
|
150
|
-
const pkgVersion = JSON.parse(readFileSync(resolve(newRoot, 'package.json'), 'utf-8')).version;
|
|
151
|
-
needBuild = builtVersion !== pkgVersion;
|
|
152
|
-
} catch {
|
|
153
|
-
needBuild = true;
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
if (!needBuild) return;
|
|
157
|
-
|
|
158
|
-
console.log(yellow('\n Building MindOS (version change detected)...\n'));
|
|
159
|
-
const appPkg = resolve(newRoot, 'app', 'package.json');
|
|
160
|
-
if (existsSync(appPkg)) {
|
|
161
|
-
run('npm install', resolve(newRoot, 'app'));
|
|
162
|
-
}
|
|
163
|
-
const nextDir = resolve(newRoot, 'app', '.next');
|
|
164
|
-
if (existsSync(nextDir)) {
|
|
165
|
-
run(`rm -rf "${nextDir}"`, newRoot);
|
|
166
|
-
}
|
|
167
|
-
run('node scripts/gen-renderer-index.js', newRoot);
|
|
168
|
-
run(`${newNextBin} build`, resolve(newRoot, 'app'));
|
|
169
|
-
const version = JSON.parse(readFileSync(resolve(newRoot, 'package.json'), 'utf-8')).version;
|
|
170
|
-
writeFileSync(newBuildStamp, version, 'utf-8');
|
|
171
62
|
}
|
|
172
63
|
|
|
173
|
-
// ──
|
|
64
|
+
// ── Parse args ────────────────────────────────────────────────────────────────
|
|
174
65
|
|
|
175
|
-
// ── Unified arg parsing ────────────────────────────────────────────────────
|
|
176
66
|
const { command: cmd, args: cliArgs, flags: cliFlags } = parseArgs(process.argv.slice(2));
|
|
177
67
|
|
|
178
68
|
if (cliFlags.version || cliFlags.v) {
|
|
@@ -181,1264 +71,138 @@ if (cliFlags.version || cliFlags.v) {
|
|
|
181
71
|
process.exit(0);
|
|
182
72
|
}
|
|
183
73
|
|
|
184
|
-
//
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
console.log(dim('No auth token set. Run `mindos onboard` to configure one.'));
|
|
236
|
-
process.exit(0);
|
|
237
|
-
}
|
|
238
|
-
const mcpPort = config.mcpPort || 8781;
|
|
239
|
-
const localIP = getLocalIP();
|
|
240
|
-
const localUrl = `http://localhost:${mcpPort}/mcp`;
|
|
241
|
-
|
|
242
|
-
if (cliFlags.json) {
|
|
243
|
-
const data = { token, mcpPort, localUrl, remoteUrl: localIP ? `http://${localIP}:${mcpPort}/mcp` : null };
|
|
244
|
-
console.log(JSON.stringify(data, null, 2));
|
|
245
|
-
return;
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
const sep = dim('━'.repeat(40));
|
|
249
|
-
const snippet = (url) => JSON.stringify({
|
|
250
|
-
mcpServers: { mindos: { url, headers: { Authorization: `Bearer ${token}` } } },
|
|
251
|
-
}, null, 2);
|
|
252
|
-
|
|
253
|
-
console.log(`\n${bold('Auth token:')} ${cyan(token)}\n`);
|
|
254
|
-
|
|
255
|
-
// Show installed agents, then up to 3 uninstalled popular ones
|
|
256
|
-
const installed = [];
|
|
257
|
-
const others = [];
|
|
258
|
-
for (const [key, agent] of Object.entries(MCP_AGENTS)) {
|
|
259
|
-
(detectAgentPresence(key) ? installed : others).push([key, agent]);
|
|
260
|
-
}
|
|
261
|
-
const toShow = [...installed.slice(0, 8), ...others.slice(0, Math.max(0, 3 - installed.length))];
|
|
262
|
-
|
|
263
|
-
for (const [key, agent] of toShow) {
|
|
264
|
-
console.log(sep);
|
|
265
|
-
console.log(bold(agent.name));
|
|
266
|
-
console.log(dim('Install:') + ` mindos mcp install ${key} -g -y`);
|
|
267
|
-
if (agent.global) console.log(dim(`Config: ${agent.global}`));
|
|
268
|
-
console.log(snippet(localUrl));
|
|
269
|
-
console.log();
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
if (localIP) {
|
|
273
|
-
console.log(sep);
|
|
274
|
-
console.log(bold('Remote (other devices)'));
|
|
275
|
-
console.log(`URL: ${cyan(`http://${localIP}:${mcpPort}/mcp`)}`);
|
|
276
|
-
console.log(snippet(`http://${localIP}:${mcpPort}/mcp`));
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
if (toShow.length < installed.length) {
|
|
280
|
-
console.log(dim(`\n +${installed.length - toShow.length} more agents detected. Run \`mindos agent list\` to see all.`));
|
|
281
|
-
}
|
|
282
|
-
console.log(dim('\nRun `mindos onboard` to regenerate.\n'));
|
|
283
|
-
},
|
|
284
|
-
|
|
285
|
-
// ── dev ────────────────────────────────────────────────────────────────────
|
|
286
|
-
dev: async () => {
|
|
287
|
-
loadConfig();
|
|
288
|
-
if (!process.env.MINDOS_WEB_PORT) process.env.MINDOS_WEB_PORT = '3456';
|
|
289
|
-
if (!process.env.MINDOS_MCP_PORT) process.env.MINDOS_MCP_PORT = '8781';
|
|
290
|
-
process.env.MINDOS_CLI_PATH = resolve(ROOT, 'bin', 'cli.js');
|
|
291
|
-
process.env.MINDOS_NODE_BIN = process.execPath;
|
|
292
|
-
const webPort = process.env.MINDOS_WEB_PORT;
|
|
293
|
-
const mcpPort = process.env.MINDOS_MCP_PORT;
|
|
294
|
-
await assertPortFree(Number(webPort), 'web');
|
|
295
|
-
await assertPortFree(Number(mcpPort), 'mcp');
|
|
296
|
-
ensureAppDeps({ force: true }); // dev mode always needs app/node_modules
|
|
297
|
-
const mcp = spawnMcp(isVerbose);
|
|
298
|
-
savePids(process.pid, mcp.pid);
|
|
299
|
-
process.on('exit', () => { stopSyncDaemon().catch(() => {}); clearPids(); });
|
|
300
|
-
const devMindRoot = process.env.MIND_ROOT;
|
|
301
|
-
if (devMindRoot) {
|
|
302
|
-
startSyncDaemon(devMindRoot).catch(() => {});
|
|
303
|
-
}
|
|
304
|
-
await printStartupInfo(webPort, mcpPort);
|
|
305
|
-
run(`${NEXT_BIN} dev -p ${webPort} ${extra}`, resolve(ROOT, 'app'));
|
|
306
|
-
},
|
|
307
|
-
|
|
308
|
-
// ── start ──────────────────────────────────────────────────────────────────
|
|
309
|
-
start: async () => {
|
|
310
|
-
// Check for incomplete setup
|
|
311
|
-
if (existsSync(CONFIG_PATH)) {
|
|
312
|
-
try {
|
|
313
|
-
const cfg = JSON.parse(readFileSync(CONFIG_PATH, 'utf-8'));
|
|
314
|
-
if (cfg.setupPending === true) {
|
|
315
|
-
console.log(`\n ${yellow('⚠ Setup was not completed.')} Run ${cyan('mindos onboard')} to finish, or ${cyan('mindos config set setupPending false')} to dismiss.\n`);
|
|
316
|
-
}
|
|
317
|
-
} catch {}
|
|
318
|
-
}
|
|
319
|
-
if (isDaemon) {
|
|
320
|
-
const platform = await getPlatform();
|
|
321
|
-
if (!platform) {
|
|
322
|
-
console.warn(yellow('Warning: daemon mode not supported on this platform. Falling back to foreground.'));
|
|
323
|
-
} else {
|
|
324
|
-
loadConfig();
|
|
325
|
-
if (!process.env.MINDOS_WEB_PORT) process.env.MINDOS_WEB_PORT = '3456';
|
|
326
|
-
if (!process.env.MINDOS_MCP_PORT) process.env.MINDOS_MCP_PORT = '8781';
|
|
327
|
-
const webPort = process.env.MINDOS_WEB_PORT;
|
|
328
|
-
const mcpPort = process.env.MINDOS_MCP_PORT;
|
|
329
|
-
console.log(cyan(`Installing MindOS as a background service (${platform})...`));
|
|
330
|
-
await runGatewayCommand('install');
|
|
331
|
-
// install() already starts the service via launchctl bootstrap + RunAtLoad=true.
|
|
332
|
-
// Do NOT call start() here — kickstart -k would kill the just-started process,
|
|
333
|
-
// causing a port-conflict race condition with KeepAlive restart loops.
|
|
334
|
-
console.log(dim(' (First run may take a few minutes to install dependencies and build the app.)'));
|
|
335
|
-
const ready = await waitForHttp(Number(webPort), { retries: 180, intervalMs: 2000, label: 'Web UI', logFile: LOG_PATH });
|
|
336
|
-
if (!ready) {
|
|
337
|
-
console.error(red('\n✘ Service started but Web UI did not become ready in time.'));
|
|
338
|
-
console.error(dim(' Check logs with: mindos logs\n'));
|
|
339
|
-
process.exit(EXIT.ERROR);
|
|
340
|
-
}
|
|
341
|
-
await printStartupInfo(webPort, mcpPort);
|
|
342
|
-
// System notification
|
|
343
|
-
try {
|
|
344
|
-
if (process.platform === 'darwin') {
|
|
345
|
-
execSync(`osascript -e 'display notification "http://localhost:${webPort}" with title "MindOS Ready"'`, { stdio: 'ignore' });
|
|
346
|
-
} else if (process.platform === 'linux') {
|
|
347
|
-
execSync(`notify-send "MindOS Ready" "http://localhost:${webPort}"`, { stdio: 'ignore' });
|
|
348
|
-
}
|
|
349
|
-
} catch { /* notification is best-effort */ }
|
|
350
|
-
console.log(`${green('✔ MindOS is running as a background service')}`);
|
|
351
|
-
console.log(dim(' View logs: mindos logs'));
|
|
352
|
-
console.log(dim(' Stop: mindos gateway stop'));
|
|
353
|
-
console.log(dim(' Uninstall: mindos gateway uninstall\n'));
|
|
354
|
-
return;
|
|
355
|
-
}
|
|
356
|
-
}
|
|
357
|
-
loadConfig();
|
|
358
|
-
if (!process.env.MINDOS_WEB_PORT) process.env.MINDOS_WEB_PORT = '3456';
|
|
359
|
-
if (!process.env.MINDOS_MCP_PORT) process.env.MINDOS_MCP_PORT = '8781';
|
|
360
|
-
const webPort = process.env.MINDOS_WEB_PORT;
|
|
361
|
-
const mcpPort = process.env.MINDOS_MCP_PORT;
|
|
362
|
-
|
|
363
|
-
// Clean up zombie processes from an abandoned GUI setup session.
|
|
364
|
-
// setup.js records a temporary port (setupPort) in config; if the user
|
|
365
|
-
// closed the browser without completing setup, that process is still
|
|
366
|
-
// running. Kill it before we proceed.
|
|
367
|
-
// Also read config for auto-migration below (avoids double readFileSync).
|
|
368
|
-
let startupCfg = {};
|
|
369
|
-
try { startupCfg = JSON.parse(readFileSync(CONFIG_PATH, 'utf-8')); } catch {}
|
|
370
|
-
if (startupCfg.setupPort && Number(startupCfg.setupPort) !== Number(webPort)) {
|
|
371
|
-
killByPort(Number(startupCfg.setupPort));
|
|
372
|
-
}
|
|
373
|
-
|
|
374
|
-
// ── Auto-migrate user preferences → .mindos/user-preferences.md ────────
|
|
375
|
-
try {
|
|
376
|
-
const mr = startupCfg.mindRoot;
|
|
377
|
-
if (mr && existsSync(mr)) {
|
|
378
|
-
const mindosDir = resolve(mr, '.mindos');
|
|
379
|
-
const newPath = resolve(mindosDir, 'user-preferences.md');
|
|
380
|
-
|
|
381
|
-
if (!existsSync(newPath)) {
|
|
382
|
-
// Ensure .mindos/ directory exists
|
|
383
|
-
if (!existsSync(mindosDir)) mkdirSync(mindosDir, { recursive: true });
|
|
384
|
-
|
|
385
|
-
// Try migrate from previous locations (newest → oldest)
|
|
386
|
-
const prevPaths = [
|
|
387
|
-
resolve(mindosDir, 'user-rules.md'), // v0.6.x interim
|
|
388
|
-
resolve(mr, 'user-skill-rules.md'), // v0.5.x root
|
|
389
|
-
];
|
|
390
|
-
let migrated = false;
|
|
391
|
-
for (const prev of prevPaths) {
|
|
392
|
-
if (existsSync(prev)) {
|
|
393
|
-
cpSync(prev, newPath);
|
|
394
|
-
unlinkSync(prev);
|
|
395
|
-
console.log(` ${green('✓')} ${dim(`Migrated ${prev.split('/').pop()} → .mindos/user-preferences.md`)}`);
|
|
396
|
-
migrated = true;
|
|
397
|
-
break;
|
|
398
|
-
}
|
|
399
|
-
}
|
|
400
|
-
if (!migrated) {
|
|
401
|
-
// Try legacy location (.agents/skills/{name}/user-rules.md)
|
|
402
|
-
const isZh = startupCfg.disabledSkills?.includes('mindos');
|
|
403
|
-
const sName = isZh ? 'mindos-zh' : 'mindos';
|
|
404
|
-
const oldPath = resolve(mr, '.agents', 'skills', sName, 'user-rules.md');
|
|
405
|
-
if (existsSync(oldPath)) {
|
|
406
|
-
cpSync(oldPath, newPath);
|
|
407
|
-
console.log(` ${green('✓')} ${dim('Migrated .agents/skills/ user-rules.md → .mindos/user-preferences.md')}`);
|
|
408
|
-
}
|
|
409
|
-
}
|
|
410
|
-
}
|
|
411
|
-
}
|
|
412
|
-
} catch { /* best-effort, don't block startup */ }
|
|
413
|
-
|
|
414
|
-
// When launched by a daemon manager (launchd/systemd), wait for ports to
|
|
415
|
-
// free instead of exiting immediately — the previous instance may still be
|
|
416
|
-
// shutting down after a restart/update.
|
|
417
|
-
const launchedByDaemon = process.env.LAUNCHED_BY_LAUNCHD === '1'
|
|
418
|
-
|| !!process.env.INVOCATION_ID; /* systemd sets INVOCATION_ID */
|
|
419
|
-
|
|
420
|
-
if (launchedByDaemon) {
|
|
421
|
-
const webOk = await waitForPortFree(Number(webPort), { retries: 60, intervalMs: 500 });
|
|
422
|
-
const mcpOk = await waitForPortFree(Number(mcpPort), { retries: 60, intervalMs: 500 });
|
|
423
|
-
if (!webOk || !mcpOk) {
|
|
424
|
-
console.error('Ports still in use after 30s, exiting.');
|
|
425
|
-
process.exit(EXIT.ERROR); // KeepAlive will retry after ThrottleInterval
|
|
426
|
-
}
|
|
427
|
-
} else {
|
|
428
|
-
await assertPortFree(Number(webPort), 'web');
|
|
429
|
-
await assertPortFree(Number(mcpPort), 'mcp');
|
|
430
|
-
}
|
|
431
|
-
process.env.MINDOS_CLI_PATH = resolve(ROOT, 'bin', 'cli.js');
|
|
432
|
-
process.env.MINDOS_NODE_BIN = process.execPath;
|
|
433
|
-
ensureAppDeps();
|
|
434
|
-
if (needsBuild()) {
|
|
435
|
-
console.log(yellow('Building MindOS (first run or new version detected)...\n'));
|
|
436
|
-
cleanNextDir();
|
|
437
|
-
run('node scripts/gen-renderer-index.js', ROOT);
|
|
438
|
-
run(`${NEXT_BIN} build --webpack`, resolve(ROOT, 'app'));
|
|
439
|
-
writeBuildStamp();
|
|
440
|
-
}
|
|
441
|
-
const mcp = spawnMcp(isVerbose);
|
|
442
|
-
savePids(process.pid, mcp.pid);
|
|
443
|
-
process.on('exit', () => { stopSyncDaemon().catch(() => {}); clearPids(); });
|
|
444
|
-
// Start sync daemon if enabled
|
|
445
|
-
const mindRoot = process.env.MIND_ROOT;
|
|
446
|
-
if (mindRoot) {
|
|
447
|
-
startSyncDaemon(mindRoot).catch(() => {});
|
|
448
|
-
}
|
|
449
|
-
await printStartupInfo(webPort, mcpPort);
|
|
450
|
-
// Prefer prebuilt standalone server (shipped with npm package) over next start.
|
|
451
|
-
// Standalone includes its own traced node_modules — no app/node_modules needed.
|
|
452
|
-
if (hasPrebuiltStandalone()) {
|
|
453
|
-
const standaloneServer = resolve(ROOT, '_standalone', 'server.js');
|
|
454
|
-
try {
|
|
455
|
-
execFileSync(process.execPath, [standaloneServer], {
|
|
456
|
-
cwd: resolve(ROOT, '_standalone'),
|
|
457
|
-
stdio: 'inherit',
|
|
458
|
-
env: { ...process.env, NODE_ENV: 'production', HOSTNAME: process.env.MINDOS_WEB_HOST || '0.0.0.0', PORT: webPort },
|
|
459
|
-
});
|
|
460
|
-
} catch (err) {
|
|
461
|
-
process.exit(err.status || 1);
|
|
462
|
-
}
|
|
463
|
-
} else {
|
|
464
|
-
run(
|
|
465
|
-
`${NEXT_BIN} start -p ${webPort} ${extra}`,
|
|
466
|
-
resolve(ROOT, 'app'),
|
|
467
|
-
{ HOSTNAME: process.env.MINDOS_WEB_HOST || '0.0.0.0' }
|
|
468
|
-
);
|
|
469
|
-
}
|
|
470
|
-
},
|
|
471
|
-
|
|
472
|
-
// ── build ──────────────────────────────────────────────────────────────────
|
|
473
|
-
build: () => {
|
|
474
|
-
ensureAppDeps({ force: true }); // build always needs app/node_modules
|
|
475
|
-
cleanNextDir();
|
|
476
|
-
run('node scripts/gen-renderer-index.js', ROOT);
|
|
477
|
-
run(`${NEXT_BIN} build --webpack ${extra}`, resolve(ROOT, 'app'));
|
|
478
|
-
writeBuildStamp();
|
|
479
|
-
},
|
|
480
|
-
|
|
481
|
-
mcp: async () => {
|
|
482
|
-
const sub = cliArgs[0];
|
|
483
|
-
const restArgs = cliArgs;
|
|
484
|
-
const hasInstallFlags = restArgs.some(a => ['-g', '--global', '-y', '--yes'].includes(a));
|
|
485
|
-
if (sub === 'install' || hasInstallFlags) { await mcpInstall(); return; }
|
|
486
|
-
loadConfig();
|
|
487
|
-
await ensureMcpBundle();
|
|
488
|
-
// `mindos mcp` is the entry point for MCP clients (Claude Code, Cursor, etc.)
|
|
489
|
-
// which communicate over stdin/stdout. Default to stdio; HTTP is handled by
|
|
490
|
-
// `mindos start` via spawnMcp(). Callers can still override via env.
|
|
491
|
-
if (!process.env.MCP_TRANSPORT) {
|
|
492
|
-
process.env.MCP_TRANSPORT = 'stdio';
|
|
493
|
-
}
|
|
494
|
-
const webPort = process.env.MINDOS_WEB_PORT || '3456';
|
|
495
|
-
process.env.MINDOS_URL = process.env.MINDOS_URL || `http://localhost:${webPort}`;
|
|
496
|
-
// Only set MCP_PORT for HTTP mode (stdio doesn't bind any port)
|
|
497
|
-
if (process.env.MCP_TRANSPORT === 'http') {
|
|
498
|
-
process.env.MCP_PORT = process.env.MINDOS_MCP_PORT || '8781';
|
|
499
|
-
}
|
|
500
|
-
run(`node dist/index.cjs`, resolve(ROOT, 'mcp'));
|
|
501
|
-
},
|
|
502
|
-
|
|
503
|
-
// ── stop / restart ─────────────────────────────────────────────────────────
|
|
504
|
-
stop: () => stopMindos(),
|
|
505
|
-
|
|
506
|
-
restart: async () => {
|
|
507
|
-
// Capture old ports BEFORE loadConfig overwrites env vars, so we can
|
|
508
|
-
// clean up processes that are still listening on the previous ports
|
|
509
|
-
// (e.g. user changed ports in the GUI and config was already saved).
|
|
510
|
-
// Sources: (1) MINDOS_OLD_* set by /api/restart when it strips the
|
|
511
|
-
// current env, (2) current MINDOS_*_PORT env vars.
|
|
512
|
-
const oldWebPort = process.env.MINDOS_OLD_WEB_PORT || process.env.MINDOS_WEB_PORT;
|
|
513
|
-
const oldMcpPort = process.env.MINDOS_OLD_MCP_PORT || process.env.MINDOS_MCP_PORT;
|
|
514
|
-
|
|
515
|
-
loadConfig();
|
|
516
|
-
|
|
517
|
-
// After loadConfig, env vars reflect the NEW config (or old if unchanged).
|
|
518
|
-
const newWebPort = Number(process.env.MINDOS_WEB_PORT || '3456');
|
|
519
|
-
const newMcpPort = Number(process.env.MINDOS_MCP_PORT || '8781');
|
|
520
|
-
|
|
521
|
-
// Collect old ports that differ from new ones — processes may still be
|
|
522
|
-
// listening there even though config already points to the new ports.
|
|
523
|
-
const extraPorts = [];
|
|
524
|
-
if (oldWebPort && Number(oldWebPort) !== newWebPort) extraPorts.push(oldWebPort);
|
|
525
|
-
if (oldMcpPort && Number(oldMcpPort) !== newMcpPort) extraPorts.push(oldMcpPort);
|
|
526
|
-
|
|
527
|
-
stopMindos({ extraPorts });
|
|
528
|
-
|
|
529
|
-
// Wait until ALL ports (old + new) are actually free (up to 15s)
|
|
530
|
-
const allPorts = new Set([newWebPort, newMcpPort]);
|
|
531
|
-
for (const p of extraPorts) allPorts.add(Number(p));
|
|
532
|
-
|
|
533
|
-
const deadline = Date.now() + 15_000;
|
|
534
|
-
while (Date.now() < deadline) {
|
|
535
|
-
let anyBusy = false;
|
|
536
|
-
for (const p of allPorts) {
|
|
537
|
-
if (await isPortInUse(p)) { anyBusy = true; break; }
|
|
538
|
-
}
|
|
539
|
-
if (!anyBusy) break;
|
|
540
|
-
await new Promise((r) => setTimeout(r, 500));
|
|
541
|
-
}
|
|
542
|
-
await commands[getStartMode()]();
|
|
543
|
-
},
|
|
544
|
-
|
|
545
|
-
// ── gateway ────────────────────────────────────────────────────────────────
|
|
546
|
-
gateway: async () => {
|
|
547
|
-
const sub = cliArgs[0];
|
|
548
|
-
if (!sub) {
|
|
549
|
-
const row = (c, d) => ` ${cyan(c.padEnd(32))}${dim(d)}`;
|
|
550
|
-
console.log(`
|
|
551
|
-
${bold('mindos gateway')} — manage MindOS as a background OS service
|
|
552
|
-
|
|
553
|
-
${bold('Subcommands:')}
|
|
554
|
-
${row('mindos gateway install', 'Install and enable the service (systemd/launchd)')}
|
|
555
|
-
${row('mindos gateway uninstall', 'Disable and remove the service')}
|
|
556
|
-
${row('mindos gateway start', 'Start the service')}
|
|
557
|
-
${row('mindos gateway stop', 'Stop the service')}
|
|
558
|
-
${row('mindos gateway status', 'Show service status')}
|
|
559
|
-
${row('mindos gateway logs', 'Tail service logs')}
|
|
560
|
-
|
|
561
|
-
${dim('Shortcut: mindos start --daemon → install + start in one step')}
|
|
562
|
-
`);
|
|
563
|
-
return;
|
|
564
|
-
}
|
|
565
|
-
await runGatewayCommand(sub);
|
|
566
|
-
},
|
|
567
|
-
|
|
568
|
-
// ── init-skills ──────────────────────────────────────────────────────────
|
|
569
|
-
'init-skills': async () => {
|
|
570
|
-
console.log(`\n${bold('📦 Initialize Skill Rules')}\n`);
|
|
571
|
-
|
|
572
|
-
if (!existsSync(CONFIG_PATH)) {
|
|
573
|
-
console.log(` ${red('✘')} Config not found. Run ${cyan('mindos onboard')} first.\n`);
|
|
574
|
-
process.exit(EXIT.ERROR);
|
|
575
|
-
}
|
|
576
|
-
let config;
|
|
577
|
-
try {
|
|
578
|
-
config = JSON.parse(readFileSync(CONFIG_PATH, 'utf-8'));
|
|
579
|
-
} catch {
|
|
580
|
-
console.log(` ${red('✘')} Failed to parse config at ${dim(CONFIG_PATH)}\n`);
|
|
581
|
-
process.exit(EXIT.ERROR);
|
|
582
|
-
}
|
|
583
|
-
const mindRoot = config.mindRoot;
|
|
584
|
-
if (!mindRoot || !existsSync(mindRoot)) {
|
|
585
|
-
console.log(` ${red('✘')} Knowledge base not found: ${dim(mindRoot || '(not set)')}\n`);
|
|
586
|
-
process.exit(EXIT.ERROR);
|
|
587
|
-
}
|
|
588
|
-
|
|
589
|
-
// Skill operating rules are now built into SKILL.md (shipped with the app).
|
|
590
|
-
// This command only initializes .mindos/user-preferences.md for personalization.
|
|
591
|
-
const mindosDir = resolve(mindRoot, '.mindos');
|
|
592
|
-
const dest = resolve(mindosDir, 'user-preferences.md');
|
|
593
|
-
if (existsSync(dest)) {
|
|
594
|
-
console.log(` ${dim('skip')} .mindos/user-preferences.md (already exists)\n`);
|
|
595
|
-
} else {
|
|
596
|
-
if (!existsSync(mindosDir)) mkdirSync(mindosDir, { recursive: true });
|
|
597
|
-
const isZh = config.disabledSkills?.includes('mindos');
|
|
598
|
-
const lang = isZh ? 'zh' : 'en';
|
|
599
|
-
const src = resolve(ROOT, 'templates', 'skill-rules', lang, 'user-rules.md');
|
|
600
|
-
if (existsSync(src)) {
|
|
601
|
-
cpSync(src, dest);
|
|
602
|
-
console.log(` ${green('✓')} .mindos/user-preferences.md created at ${dim(mindosDir)}\n`);
|
|
603
|
-
} else {
|
|
604
|
-
console.log(` ${dim('skip')} Template not found, create .mindos/user-preferences.md manually if needed.\n`);
|
|
605
|
-
}
|
|
606
|
-
}
|
|
607
|
-
console.log(` ${dim('Note: Operating rules are now built into the app. No install needed.')}\n`);
|
|
608
|
-
},
|
|
609
|
-
|
|
610
|
-
// ── doctor ─────────────────────────────────────────────────────────────────
|
|
611
|
-
doctor: async () => {
|
|
612
|
-
const jsonMode = cliFlags.json === true;
|
|
613
|
-
const checks = [];
|
|
614
|
-
const ok = (msg, key) => { checks.push({ status: 'ok', key, msg }); if (!jsonMode) console.log(` ${green('✔')} ${msg}`); };
|
|
615
|
-
const err = (msg, key) => { checks.push({ status: 'error', key, msg }); if (!jsonMode) console.log(` ${red('✘')} ${msg}`); };
|
|
616
|
-
const warn= (msg, key) => { checks.push({ status: 'warn', key, msg }); if (!jsonMode) console.log(` ${yellow('!')} ${msg}`); };
|
|
617
|
-
|
|
618
|
-
if (!jsonMode) console.log(`\n${bold('MindOS Doctor')}\n`);
|
|
619
|
-
let hasError = false;
|
|
620
|
-
|
|
621
|
-
// 1. config file
|
|
622
|
-
if (!existsSync(CONFIG_PATH)) {
|
|
623
|
-
err(`Config not found at ${dim(CONFIG_PATH)}`, 'config');
|
|
624
|
-
if (!jsonMode) { console.log(`\n ${dim('Run `mindos onboard` to create it.')}\n`); }
|
|
625
|
-
if (jsonMode) { console.log(JSON.stringify({ ok: false, checks }, null, 2)); }
|
|
626
|
-
process.exit(EXIT.ERROR);
|
|
627
|
-
}
|
|
628
|
-
let config;
|
|
629
|
-
try {
|
|
630
|
-
config = JSON.parse(readFileSync(CONFIG_PATH, 'utf-8'));
|
|
631
|
-
ok(`Config file found and valid JSON ${dim(CONFIG_PATH)}`, 'config');
|
|
632
|
-
} catch {
|
|
633
|
-
err(`Config file exists but failed to parse ${dim(CONFIG_PATH)}`, 'config');
|
|
634
|
-
hasError = true;
|
|
635
|
-
}
|
|
636
|
-
|
|
637
|
-
// 2. mindRoot
|
|
638
|
-
if (config) {
|
|
639
|
-
const mindRoot = config.mindRoot;
|
|
640
|
-
if (!mindRoot) {
|
|
641
|
-
err('Config missing required field: mindRoot');
|
|
642
|
-
hasError = true;
|
|
643
|
-
} else if (!existsSync(mindRoot.replace(/^~/, homedir()))) {
|
|
644
|
-
warn(`mindRoot path does not exist: ${dim(mindRoot)} (will be created on first start)`);
|
|
645
|
-
} else {
|
|
646
|
-
ok(`Knowledge base path exists ${dim(mindRoot)}`);
|
|
647
|
-
}
|
|
648
|
-
}
|
|
649
|
-
|
|
650
|
-
// 3. AI config
|
|
651
|
-
if (config) {
|
|
652
|
-
const provider = config.ai?.provider;
|
|
653
|
-
const providers = config.ai?.providers;
|
|
654
|
-
const hasAnthropic = providers?.anthropic?.apiKey || config.ai?.anthropicApiKey;
|
|
655
|
-
const hasOpenai = providers?.openai?.apiKey || config.ai?.openaiApiKey;
|
|
656
|
-
if (!provider) {
|
|
657
|
-
warn('AI provider not configured (run `mindos onboard` to set up)');
|
|
658
|
-
} else if (provider === 'anthropic' && !hasAnthropic) {
|
|
659
|
-
err('AI provider is "anthropic" but no API key found');
|
|
660
|
-
hasError = true;
|
|
661
|
-
} else if (provider === 'openai' && !hasOpenai) {
|
|
662
|
-
err('AI provider is "openai" but no API key found');
|
|
663
|
-
hasError = true;
|
|
664
|
-
} else {
|
|
665
|
-
ok(`AI provider configured ${dim(provider)}`);
|
|
666
|
-
}
|
|
667
|
-
}
|
|
668
|
-
|
|
669
|
-
// 4. Node version
|
|
670
|
-
const nodeVersion = process.versions.node;
|
|
671
|
-
const [nodeMajor] = nodeVersion.split('.').map(Number);
|
|
672
|
-
if (nodeMajor < 18) {
|
|
673
|
-
err(`Node.js ${nodeVersion} is below minimum required (18+)`);
|
|
674
|
-
hasError = true;
|
|
675
|
-
} else {
|
|
676
|
-
ok(`Node.js ${nodeVersion}`);
|
|
677
|
-
}
|
|
678
|
-
|
|
679
|
-
// 4b. npm reachable from /bin/sh
|
|
680
|
-
try {
|
|
681
|
-
const npmVersion = execSync('npm --version', { stdio: 'pipe' }).toString().trim();
|
|
682
|
-
ok(`npm ${npmVersion} reachable`);
|
|
683
|
-
} catch {
|
|
684
|
-
err('npm not found in PATH — app dependencies cannot be installed');
|
|
685
|
-
console.log(dim(' Node.js may be installed via nvm/fnm/volta and not visible to /bin/sh.'));
|
|
686
|
-
console.log(dim(' Fix: add your Node.js bin path to ~/.profile so non-interactive shells can find it.'));
|
|
687
|
-
hasError = true;
|
|
688
|
-
}
|
|
689
|
-
|
|
690
|
-
// 5. Build
|
|
691
|
-
if (hasPrebuiltStandalone()) {
|
|
692
|
-
ok('Production build is up to date (prebuilt standalone)');
|
|
693
|
-
} else if (!existsSync(resolve(ROOT, 'app', '.next'))) {
|
|
694
|
-
warn(`App not built yet — will build automatically on next ${dim('mindos start')}`);
|
|
695
|
-
} else if (needsBuild()) {
|
|
696
|
-
warn(`Build is outdated — will rebuild automatically on next ${dim('mindos start')}`);
|
|
697
|
-
} else {
|
|
698
|
-
ok('Production build is up to date');
|
|
699
|
-
}
|
|
700
|
-
|
|
701
|
-
// 6. Ports
|
|
702
|
-
const webPort = Number(config?.port || process.env.MINDOS_WEB_PORT || 3456);
|
|
703
|
-
const mcpPort = Number(config?.mcpPort || process.env.MINDOS_MCP_PORT || 8781);
|
|
704
|
-
const webInUse = await isPortInUse(webPort);
|
|
705
|
-
const mcpInUse = await isPortInUse(mcpPort);
|
|
706
|
-
if (webInUse) {
|
|
707
|
-
ok(`Web server is listening on port ${webPort}`);
|
|
708
|
-
} else {
|
|
709
|
-
warn(`Web server is not running on port ${webPort}`);
|
|
710
|
-
}
|
|
711
|
-
if (mcpInUse) {
|
|
712
|
-
ok(`MCP server is listening on port ${mcpPort}`);
|
|
713
|
-
} else {
|
|
714
|
-
warn(`MCP server is not running on port ${mcpPort}`);
|
|
715
|
-
}
|
|
716
|
-
|
|
717
|
-
// 7. Daemon status
|
|
718
|
-
const platform = await getPlatform();
|
|
719
|
-
if (platform === 'systemd') {
|
|
720
|
-
try {
|
|
721
|
-
execSync('systemctl --user is-active mindos', { stdio: 'pipe' });
|
|
722
|
-
ok('Systemd service mindos is active');
|
|
723
|
-
} catch {
|
|
724
|
-
warn('Systemd service mindos is not active (run `mindos gateway start` to start)');
|
|
725
|
-
}
|
|
726
|
-
} else if (platform === 'launchd') {
|
|
727
|
-
try {
|
|
728
|
-
const uid = execSync('id -u').toString().trim();
|
|
729
|
-
execSync(`launchctl print gui/${uid}/com.mindos.app`, { stdio: 'pipe' });
|
|
730
|
-
ok('LaunchAgent com.mindos.app is loaded');
|
|
731
|
-
} catch {
|
|
732
|
-
warn('LaunchAgent com.mindos.app is not loaded (run `mindos gateway start` to start)');
|
|
733
|
-
}
|
|
734
|
-
}
|
|
735
|
-
|
|
736
|
-
// 8. Sync status
|
|
737
|
-
if (config?.mindRoot) {
|
|
738
|
-
try {
|
|
739
|
-
const syncStatus = await getSyncStatus(config.mindRoot);
|
|
740
|
-
if (!syncStatus.enabled) {
|
|
741
|
-
warn(`Cross-device sync is not configured ${dim('(run `mindos sync init` to set up)')}`);
|
|
742
|
-
} else if (syncStatus.lastError) {
|
|
743
|
-
err(`Sync error: ${syncStatus.lastError}`);
|
|
744
|
-
hasError = true;
|
|
745
|
-
} else if (syncStatus.conflicts && syncStatus.conflicts.length > 0) {
|
|
746
|
-
warn(`Sync has ${syncStatus.conflicts.length} unresolved conflict(s) ${dim('(run `mindos sync conflicts` to view)')}`);
|
|
747
|
-
} else {
|
|
748
|
-
const unpushed = parseInt(syncStatus.unpushed || '0', 10);
|
|
749
|
-
const extra = unpushed > 0 ? ` ${dim(`(${unpushed} unpushed commit(s))`)}` : '';
|
|
750
|
-
ok(`Sync enabled ${dim(syncStatus.remote || 'origin')}${extra}`);
|
|
751
|
-
}
|
|
752
|
-
} catch {
|
|
753
|
-
warn('Could not check sync status');
|
|
754
|
-
}
|
|
755
|
-
}
|
|
756
|
-
|
|
757
|
-
// 9. Update check
|
|
758
|
-
try {
|
|
759
|
-
const { checkForUpdate } = await import('./lib/update-check.js');
|
|
760
|
-
const latestVersion = await Promise.race([
|
|
761
|
-
checkForUpdate(),
|
|
762
|
-
new Promise(r => setTimeout(() => r(null), 4000)),
|
|
763
|
-
]);
|
|
764
|
-
if (latestVersion) {
|
|
765
|
-
const currentVersion = (() => { try { return JSON.parse(readFileSync(resolve(ROOT, 'package.json'), 'utf-8')).version; } catch { return '?'; } })();
|
|
766
|
-
warn(`Update available: v${currentVersion} → ${bold(`v${latestVersion}`)} ${dim('run `mindos update`')}`);
|
|
767
|
-
} else {
|
|
768
|
-
ok('MindOS is up to date');
|
|
769
|
-
}
|
|
770
|
-
} catch {
|
|
771
|
-
warn('Could not check for updates');
|
|
772
|
-
}
|
|
773
|
-
|
|
774
|
-
if (jsonMode) {
|
|
775
|
-
const hasErr = checks.some(c => c.status === 'error');
|
|
776
|
-
console.log(JSON.stringify({ ok: !hasErr, checks }, null, 2));
|
|
777
|
-
} else {
|
|
778
|
-
console.log(hasError
|
|
779
|
-
? `\n${red('Some checks failed.')} Run ${cyan('mindos onboard')} to reconfigure.\n`
|
|
780
|
-
: `\n${green('All checks passed.')}\n`);
|
|
781
|
-
}
|
|
782
|
-
if (hasError) process.exit(EXIT.ERROR);
|
|
783
|
-
},
|
|
784
|
-
|
|
785
|
-
// ── update ─────────────────────────────────────────────────────────────────
|
|
786
|
-
update: async () => {
|
|
787
|
-
const { writeUpdateStatus, writeUpdateFailed, clearUpdateStatus } = await import('./lib/update-status.js');
|
|
788
|
-
const currentVersion = (() => {
|
|
789
|
-
try { return JSON.parse(readFileSync(resolve(ROOT, 'package.json'), 'utf-8')).version; } catch { return '?'; }
|
|
790
|
-
})();
|
|
791
|
-
console.log(`\n${bold('⬆ Updating MindOS...')} ${dim(`(current: ${currentVersion})`)}\n`);
|
|
792
|
-
|
|
793
|
-
// Stage 1: Download
|
|
794
|
-
writeUpdateStatus('downloading', { fromVersion: currentVersion });
|
|
795
|
-
try {
|
|
796
|
-
execSync('npm install -g @geminilight/mindos@latest', { stdio: 'inherit' });
|
|
797
|
-
} catch {
|
|
798
|
-
writeUpdateFailed('downloading', 'npm install failed', { fromVersion: currentVersion });
|
|
799
|
-
console.error(red('Update failed. Try: npm install -g @geminilight/mindos@latest'));
|
|
800
|
-
process.exit(EXIT.ERROR);
|
|
801
|
-
}
|
|
802
|
-
if (existsSync(BUILD_STAMP)) rmSync(BUILD_STAMP);
|
|
803
|
-
|
|
804
|
-
// Resolve the new installation path (after npm install -g, ROOT is stale)
|
|
805
|
-
const updatedRoot = getUpdatedRoot();
|
|
806
|
-
const newVersion = (() => {
|
|
807
|
-
try { return JSON.parse(readFileSync(resolve(updatedRoot, 'package.json'), 'utf-8')).version; } catch { return '?'; }
|
|
808
|
-
})();
|
|
809
|
-
const vOpts = { fromVersion: currentVersion, toVersion: newVersion };
|
|
810
|
-
|
|
811
|
-
// Stage 2: Skills
|
|
812
|
-
writeUpdateStatus('skills', vOpts);
|
|
813
|
-
try {
|
|
814
|
-
const { checkSkillVersions, updateSkill } = await import('./lib/skill-check.js');
|
|
815
|
-
const mismatches = checkSkillVersions(updatedRoot);
|
|
816
|
-
for (const m of mismatches) {
|
|
817
|
-
updateSkill(m.bundledPath, m.installPath);
|
|
818
|
-
console.log(` ${green('✓')} ${dim(`Skill ${m.name}: v${m.installed} → v${m.bundled}`)}`);
|
|
819
|
-
}
|
|
820
|
-
} catch { /* best-effort */ }
|
|
821
|
-
|
|
822
|
-
if (newVersion !== currentVersion) {
|
|
823
|
-
console.log(`\n${green(`✔ Updated ${currentVersion} → ${newVersion}`)}`);
|
|
824
|
-
} else {
|
|
825
|
-
console.log(`\n${green('✔ Already on the latest version')} ${dim(`(${currentVersion})`)}\n`);
|
|
826
|
-
return;
|
|
827
|
-
}
|
|
828
|
-
|
|
829
|
-
const updatePlatform = await getPlatform();
|
|
830
|
-
let daemonRunning = false;
|
|
831
|
-
if (updatePlatform === 'systemd') {
|
|
832
|
-
try { execSync('systemctl --user is-active mindos', { stdio: 'pipe' }); daemonRunning = true; } catch {}
|
|
833
|
-
} else if (updatePlatform === 'launchd') {
|
|
834
|
-
try {
|
|
835
|
-
const uid = execSync('id -u').toString().trim();
|
|
836
|
-
execSync(`launchctl print gui/${uid}/com.mindos.app`, { stdio: 'pipe' });
|
|
837
|
-
daemonRunning = true;
|
|
838
|
-
} catch {}
|
|
839
|
-
}
|
|
840
|
-
|
|
841
|
-
if (daemonRunning) {
|
|
842
|
-
console.log(cyan('\n Daemon is running — stopping to apply the new version...'));
|
|
843
|
-
await runGatewayCommand('stop');
|
|
844
|
-
|
|
845
|
-
// Stage 3: Rebuild
|
|
846
|
-
writeUpdateStatus('rebuilding', vOpts);
|
|
847
|
-
let daemonBuildFailed = '';
|
|
848
|
-
try {
|
|
849
|
-
buildIfNeeded(updatedRoot);
|
|
850
|
-
} catch (err) {
|
|
851
|
-
daemonBuildFailed = err instanceof Error ? err.message : String(err);
|
|
852
|
-
console.error(yellow(`\n Pre-build failed: ${daemonBuildFailed}`));
|
|
853
|
-
console.error(yellow(' Daemon will attempt to rebuild on startup...\n'));
|
|
854
|
-
}
|
|
855
|
-
|
|
856
|
-
// Stage 4: Restart — always attempt, even if pre-build failed
|
|
857
|
-
// (daemon has auto-restart; `mindos start` retries the build)
|
|
858
|
-
writeUpdateStatus('restarting', vOpts);
|
|
859
|
-
await runGatewayCommand('install');
|
|
860
|
-
const updateConfig = (() => {
|
|
861
|
-
try { return JSON.parse(readFileSync(CONFIG_PATH, 'utf-8')); } catch { return {}; }
|
|
862
|
-
})();
|
|
863
|
-
const webPort = updateConfig.port ?? 3456;
|
|
864
|
-
const mcpPort = updateConfig.mcpPort ?? 8781;
|
|
865
|
-
console.log(dim(' (Waiting for Web UI to come back up — first run after update includes a rebuild...)'));
|
|
866
|
-
const ready = await waitForHttp(Number(webPort), { retries: 450, intervalMs: 2000, label: 'Web UI', logFile: LOG_PATH });
|
|
867
|
-
if (ready) {
|
|
868
|
-
const localIP = getLocalIP();
|
|
869
|
-
console.log(`\n${'─'.repeat(53)}`);
|
|
870
|
-
console.log(`${green('✔')} ${bold(`MindOS updated: ${currentVersion} → ${newVersion}`)}\n`);
|
|
871
|
-
console.log(` ${green('●')} Web UI ${cyan(`http://localhost:${webPort}`)}`);
|
|
872
|
-
if (localIP) console.log(` ${cyan(`http://${localIP}:${webPort}`)}`);
|
|
873
|
-
console.log(` ${green('●')} MCP ${cyan(`http://localhost:${mcpPort}/mcp`)}`);
|
|
874
|
-
console.log(`\n ${dim('View changelog:')} ${cyan('https://github.com/GeminiLight/MindOS/releases')}`);
|
|
875
|
-
console.log(`${'─'.repeat(53)}\n`);
|
|
876
|
-
writeUpdateStatus('done', vOpts);
|
|
877
|
-
} else {
|
|
878
|
-
const failMsg = daemonBuildFailed
|
|
879
|
-
? `Build failed (${daemonBuildFailed}), server did not come back up`
|
|
880
|
-
: 'Server did not come back up in time';
|
|
881
|
-
writeUpdateFailed('restarting', failMsg, vOpts);
|
|
882
|
-
console.error(red(`✘ ${failMsg}. Check logs: mindos logs\n`));
|
|
883
|
-
process.exit(EXIT.ERROR);
|
|
884
|
-
}
|
|
885
|
-
} else {
|
|
886
|
-
// Non-daemon mode: check if a MindOS instance is currently running
|
|
887
|
-
// (e.g. user started via `mindos start`, or GUI triggered this update).
|
|
888
|
-
// If so, stop it and restart from the NEW installation path.
|
|
889
|
-
const updateConfig = (() => {
|
|
890
|
-
try { return JSON.parse(readFileSync(CONFIG_PATH, 'utf-8')); } catch { return {}; }
|
|
891
|
-
})();
|
|
892
|
-
const webPort = Number(updateConfig.port ?? 3456);
|
|
893
|
-
const mcpPort = Number(updateConfig.mcpPort ?? 8781);
|
|
894
|
-
|
|
895
|
-
const wasRunning = await isPortInUse(webPort) || await isPortInUse(mcpPort);
|
|
896
|
-
|
|
897
|
-
if (wasRunning) {
|
|
898
|
-
console.log(cyan('\n MindOS is running — restarting to apply the new version...'));
|
|
899
|
-
stopMindos();
|
|
900
|
-
// Wait for ports to free (up to 15s)
|
|
901
|
-
const deadline = Date.now() + 15_000;
|
|
902
|
-
while (Date.now() < deadline) {
|
|
903
|
-
const busy = await isPortInUse(webPort) || await isPortInUse(mcpPort);
|
|
904
|
-
if (!busy) break;
|
|
905
|
-
await new Promise((r) => setTimeout(r, 500));
|
|
906
|
-
}
|
|
907
|
-
|
|
908
|
-
// Stage 3: Rebuild
|
|
909
|
-
writeUpdateStatus('rebuilding', vOpts);
|
|
910
|
-
let buildFailed = '';
|
|
911
|
-
try {
|
|
912
|
-
buildIfNeeded(updatedRoot);
|
|
913
|
-
} catch (err) {
|
|
914
|
-
buildFailed = err instanceof Error ? err.message : String(err);
|
|
915
|
-
console.error(yellow(`\n Pre-build failed: ${buildFailed}`));
|
|
916
|
-
console.error(yellow(' Starting server anyway (it will retry the build)...\n'));
|
|
917
|
-
}
|
|
918
|
-
|
|
919
|
-
// Stage 4: Restart — always attempt, even if pre-build failed
|
|
920
|
-
// (`mindos start` has its own build-on-startup logic)
|
|
921
|
-
writeUpdateStatus('restarting', vOpts);
|
|
922
|
-
const newCliPath = resolve(updatedRoot, 'bin', 'cli.js');
|
|
923
|
-
const childEnv = { ...process.env };
|
|
924
|
-
delete childEnv.MINDOS_WEB_PORT;
|
|
925
|
-
delete childEnv.MINDOS_MCP_PORT;
|
|
926
|
-
delete childEnv.MIND_ROOT;
|
|
927
|
-
delete childEnv.AUTH_TOKEN;
|
|
928
|
-
delete childEnv.WEB_PASSWORD;
|
|
929
|
-
const child = nodeSpawn(
|
|
930
|
-
process.execPath, [newCliPath, 'start'],
|
|
931
|
-
{ detached: true, stdio: 'ignore', env: childEnv },
|
|
932
|
-
);
|
|
933
|
-
child.unref();
|
|
934
|
-
|
|
935
|
-
console.log(dim(' (Waiting for Web UI to come back up...)'));
|
|
936
|
-
const ready = await waitForHttp(webPort, { retries: 180, intervalMs: 2000, label: 'Web UI', logFile: LOG_PATH });
|
|
937
|
-
if (ready) {
|
|
938
|
-
const localIP = getLocalIP();
|
|
939
|
-
console.log(`\n${'─'.repeat(53)}`);
|
|
940
|
-
console.log(`${green('✔')} ${bold(`MindOS updated: ${currentVersion} → ${newVersion}`)}\n`);
|
|
941
|
-
console.log(` ${green('●')} Web UI ${cyan(`http://localhost:${webPort}`)}`);
|
|
942
|
-
if (localIP) console.log(` ${cyan(`http://${localIP}:${webPort}`)}`);
|
|
943
|
-
console.log(` ${green('●')} MCP ${cyan(`http://localhost:${mcpPort}/mcp`)}`);
|
|
944
|
-
console.log(`\n ${dim('View changelog:')} ${cyan('https://github.com/GeminiLight/MindOS/releases')}`);
|
|
945
|
-
console.log(`${'─'.repeat(53)}\n`);
|
|
946
|
-
writeUpdateStatus('done', vOpts);
|
|
947
|
-
} else {
|
|
948
|
-
const failMsg = buildFailed
|
|
949
|
-
? `Build failed (${buildFailed}), server did not come back up`
|
|
950
|
-
: 'Server did not come back up in time';
|
|
951
|
-
writeUpdateFailed('restarting', failMsg, vOpts);
|
|
952
|
-
console.error(red(`✘ ${failMsg}. Check logs: mindos logs\n`));
|
|
953
|
-
process.exit(EXIT.ERROR);
|
|
954
|
-
}
|
|
955
|
-
} else {
|
|
956
|
-
// No running instance — just build and tell user to start manually
|
|
957
|
-
try {
|
|
958
|
-
buildIfNeeded(updatedRoot);
|
|
959
|
-
} catch (err) {
|
|
960
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
961
|
-
console.error(yellow(`\n Pre-build failed: ${msg}`));
|
|
962
|
-
console.error(dim(' The build will be retried when you run `mindos start`.'));
|
|
963
|
-
}
|
|
964
|
-
console.log(`\n${green('✔')} ${bold(`Updated: ${currentVersion} → ${newVersion}`)}`);
|
|
965
|
-
console.log(dim(' Run `mindos start` to start the updated version.'));
|
|
966
|
-
console.log(` ${dim('View changelog:')} ${cyan('https://github.com/GeminiLight/MindOS/releases')}\n`);
|
|
967
|
-
}
|
|
968
|
-
}
|
|
969
|
-
},
|
|
970
|
-
|
|
971
|
-
// ── uninstall ───────────────────────────────────────────────────────────────
|
|
972
|
-
uninstall: async () => {
|
|
973
|
-
const readline = await import('node:readline');
|
|
974
|
-
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
975
|
-
|
|
976
|
-
// Buffer lines eagerly — readline.question() loses buffered lines when
|
|
977
|
-
// piped stdin delivers multiple lines at once (Node.js known behavior).
|
|
978
|
-
const lineBuffer = [];
|
|
979
|
-
let lineResolve = null;
|
|
980
|
-
rl.on('line', (line) => {
|
|
981
|
-
if (lineResolve) {
|
|
982
|
-
const r = lineResolve;
|
|
983
|
-
lineResolve = null;
|
|
984
|
-
r(line);
|
|
985
|
-
} else {
|
|
986
|
-
lineBuffer.push(line);
|
|
987
|
-
}
|
|
988
|
-
});
|
|
989
|
-
// On EOF with no pending resolve, close gracefully
|
|
990
|
-
rl.on('close', () => {
|
|
991
|
-
if (lineResolve) { lineResolve(''); lineResolve = null; }
|
|
992
|
-
});
|
|
993
|
-
|
|
994
|
-
function prompt(question) {
|
|
995
|
-
process.stdout.write(question + ' ');
|
|
996
|
-
if (lineBuffer.length > 0) return Promise.resolve(lineBuffer.shift());
|
|
997
|
-
return new Promise((resolve) => { lineResolve = resolve; });
|
|
998
|
-
}
|
|
999
|
-
|
|
1000
|
-
async function confirm(question) {
|
|
1001
|
-
const a = (await prompt(question + ' [y/N]')).trim().toLowerCase();
|
|
1002
|
-
return a === 'y' || a === 'yes';
|
|
1003
|
-
}
|
|
1004
|
-
|
|
1005
|
-
async function askInput(question) {
|
|
1006
|
-
return (await prompt(question)).trim();
|
|
1007
|
-
}
|
|
1008
|
-
|
|
1009
|
-
async function askPassword(question) {
|
|
1010
|
-
// Mute echoed keystrokes
|
|
1011
|
-
const stdout = process.stdout;
|
|
1012
|
-
const origWrite = stdout.write.bind(stdout);
|
|
1013
|
-
stdout.write = (chunk, ...args) => {
|
|
1014
|
-
// Suppress everything except the prompt itself
|
|
1015
|
-
if (typeof chunk === 'string' && chunk.includes(question)) return origWrite(chunk, ...args);
|
|
1016
|
-
return true;
|
|
1017
|
-
};
|
|
1018
|
-
const answer = await prompt(question);
|
|
1019
|
-
stdout.write = origWrite;
|
|
1020
|
-
console.log(); // newline after hidden input
|
|
1021
|
-
return answer.trim();
|
|
1022
|
-
}
|
|
1023
|
-
|
|
1024
|
-
const done = () => rl.close();
|
|
1025
|
-
|
|
1026
|
-
console.log(`\n${bold('🗑 MindOS Uninstall')}\n`);
|
|
1027
|
-
console.log(' This will:');
|
|
1028
|
-
console.log(` ${green('✓')} Stop running MindOS processes`);
|
|
1029
|
-
console.log(` ${green('✓')} Remove background service (if installed)`);
|
|
1030
|
-
console.log(` ${green('✓')} Uninstall npm package\n`);
|
|
1031
|
-
|
|
1032
|
-
if (!await confirm('Proceed?')) {
|
|
1033
|
-
console.log(dim('\n Aborted.\n'));
|
|
1034
|
-
done();
|
|
1035
|
-
return;
|
|
1036
|
-
}
|
|
1037
|
-
|
|
1038
|
-
// 1. Stop processes
|
|
1039
|
-
console.log(`\n${cyan('Stopping MindOS...')}`);
|
|
1040
|
-
try { stopMindos(); } catch { /* may not be running */ }
|
|
1041
|
-
|
|
1042
|
-
// 2. Remove daemon (skip if platform unsupported)
|
|
1043
|
-
if (await getPlatform()) {
|
|
1044
|
-
try {
|
|
1045
|
-
await runGatewayCommand('uninstall');
|
|
1046
|
-
} catch {
|
|
1047
|
-
// Daemon may not be installed — that's fine
|
|
1048
|
-
}
|
|
1049
|
-
}
|
|
1050
|
-
|
|
1051
|
-
// Read config before potentially deleting ~/.mindos/
|
|
1052
|
-
let config = {};
|
|
1053
|
-
try { config = JSON.parse(readFileSync(CONFIG_PATH, 'utf-8')); } catch {}
|
|
1054
|
-
const mindRoot = config.mindRoot?.replace(/^~/, homedir());
|
|
1055
|
-
|
|
1056
|
-
// 3. Ask to remove ~/.mindos/
|
|
1057
|
-
if (existsSync(MINDOS_DIR)) {
|
|
1058
|
-
if (await confirm(`Remove config directory (${dim(MINDOS_DIR)})?`)) {
|
|
1059
|
-
rmSync(MINDOS_DIR, { recursive: true, force: true });
|
|
1060
|
-
console.log(`${green('✔')} Removed ${dim(MINDOS_DIR)}`);
|
|
1061
|
-
} else {
|
|
1062
|
-
console.log(dim(` Kept ${MINDOS_DIR}`));
|
|
1063
|
-
}
|
|
1064
|
-
}
|
|
1065
|
-
|
|
1066
|
-
// 4. Ask to remove knowledge base (triple protection: confirm → type YES → password)
|
|
1067
|
-
if (mindRoot && existsSync(mindRoot)) {
|
|
1068
|
-
if (await confirm(`Remove knowledge base (${dim(mindRoot)})?`)) {
|
|
1069
|
-
const typed = await askInput(`${yellow('⚠ This is irreversible.')} Type ${bold('YES')} to confirm:`);
|
|
1070
|
-
if (typed === 'YES') {
|
|
1071
|
-
const webPassword = config.webPassword;
|
|
1072
|
-
let authorized = true;
|
|
1073
|
-
if (webPassword) {
|
|
1074
|
-
const pw = await askPassword('Enter web password:');
|
|
1075
|
-
if (pw !== webPassword) {
|
|
1076
|
-
console.log(red(' Wrong password. Knowledge base kept.'));
|
|
1077
|
-
authorized = false;
|
|
1078
|
-
}
|
|
1079
|
-
}
|
|
1080
|
-
if (authorized) {
|
|
1081
|
-
rmSync(mindRoot, { recursive: true, force: true });
|
|
1082
|
-
console.log(`${green('✔')} Removed ${dim(mindRoot)}`);
|
|
1083
|
-
}
|
|
1084
|
-
} else {
|
|
1085
|
-
console.log(dim(' Knowledge base kept.'));
|
|
1086
|
-
}
|
|
1087
|
-
} else {
|
|
1088
|
-
console.log(dim(` Kept ${mindRoot}`));
|
|
1089
|
-
}
|
|
1090
|
-
}
|
|
1091
|
-
|
|
1092
|
-
// 5. npm uninstall -g
|
|
1093
|
-
console.log(`\n${cyan('Uninstalling npm package...')}`);
|
|
1094
|
-
try {
|
|
1095
|
-
execSync('npm uninstall -g @geminilight/mindos', { stdio: ['ignore', 'inherit', 'inherit'] });
|
|
1096
|
-
} catch {
|
|
1097
|
-
console.log(yellow(' npm uninstall failed — you may need to run manually:'));
|
|
1098
|
-
console.log(dim(' npm uninstall -g @geminilight/mindos'));
|
|
1099
|
-
}
|
|
1100
|
-
|
|
1101
|
-
console.log(`\n${green('✔ MindOS uninstalled.')}\n`);
|
|
1102
|
-
done();
|
|
1103
|
-
},
|
|
1104
|
-
|
|
1105
|
-
// ── logs ───────────────────────────────────────────────────────────────────
|
|
1106
|
-
logs: async () => {
|
|
1107
|
-
await ensureMindosDir();
|
|
1108
|
-
if (!existsSync(LOG_PATH)) {
|
|
1109
|
-
console.log(dim(`No log file yet at ${LOG_PATH}`));
|
|
1110
|
-
console.log(dim('Logs are created when starting MindOS (mindos start, mindos onboard, or daemon mode).'));
|
|
1111
|
-
process.exit(0);
|
|
1112
|
-
}
|
|
1113
|
-
const noFollow = cliFlags['no-follow'] === true;
|
|
1114
|
-
if (noFollow) {
|
|
1115
|
-
execSync(`tail -n 100 ${LOG_PATH}`, { stdio: 'inherit' });
|
|
1116
|
-
} else {
|
|
1117
|
-
execSync(`tail -f ${LOG_PATH}`, { stdio: 'inherit' });
|
|
1118
|
-
}
|
|
1119
|
-
},
|
|
1120
|
-
|
|
1121
|
-
// ── config ─────────────────────────────────────────────────────────────────
|
|
1122
|
-
config: () => {
|
|
1123
|
-
const sub = cliArgs[0];
|
|
1124
|
-
|
|
1125
|
-
function maskKey(val) {
|
|
1126
|
-
if (!val) return val;
|
|
1127
|
-
if (val.length <= 8) return '****';
|
|
1128
|
-
return val.slice(0, 6) + '****';
|
|
1129
|
-
}
|
|
1130
|
-
|
|
1131
|
-
if (sub === 'show') {
|
|
1132
|
-
if (!existsSync(CONFIG_PATH)) {
|
|
1133
|
-
console.error(red('No config found. Run `mindos onboard` first.'));
|
|
1134
|
-
process.exit(EXIT.ERROR);
|
|
1135
|
-
}
|
|
1136
|
-
let config;
|
|
1137
|
-
try { config = JSON.parse(readFileSync(CONFIG_PATH, 'utf-8')); } catch {
|
|
1138
|
-
console.error(red('Failed to parse config file.'));
|
|
1139
|
-
process.exit(EXIT.ERROR);
|
|
1140
|
-
}
|
|
1141
|
-
const display = JSON.parse(JSON.stringify(config));
|
|
1142
|
-
if (display.ai?.providers?.anthropic?.apiKey)
|
|
1143
|
-
display.ai.providers.anthropic.apiKey = maskKey(display.ai.providers.anthropic.apiKey);
|
|
1144
|
-
if (display.ai?.providers?.openai?.apiKey)
|
|
1145
|
-
display.ai.providers.openai.apiKey = maskKey(display.ai.providers.openai.apiKey);
|
|
1146
|
-
if (display.ai?.anthropicApiKey)
|
|
1147
|
-
display.ai.anthropicApiKey = maskKey(display.ai.anthropicApiKey);
|
|
1148
|
-
if (display.ai?.openaiApiKey)
|
|
1149
|
-
display.ai.openaiApiKey = maskKey(display.ai.openaiApiKey);
|
|
1150
|
-
if (display.authToken)
|
|
1151
|
-
display.authToken = maskKey(display.authToken);
|
|
1152
|
-
if (display.webPassword)
|
|
1153
|
-
display.webPassword = maskKey(display.webPassword);
|
|
1154
|
-
if (cliFlags.json) {
|
|
1155
|
-
console.log(JSON.stringify(display, null, 2));
|
|
1156
|
-
return;
|
|
1157
|
-
}
|
|
1158
|
-
console.log(`\n${bold('MindOS Config')} ${dim(`v${(() => { try { return JSON.parse(readFileSync(resolve(ROOT, 'package.json'), 'utf-8')).version; } catch { return '?'; } })()}`)} ${dim(CONFIG_PATH)}\n`);
|
|
1159
|
-
console.log(JSON.stringify(display, null, 2));
|
|
1160
|
-
console.log();
|
|
1161
|
-
return;
|
|
1162
|
-
}
|
|
1163
|
-
|
|
1164
|
-
if (sub === 'validate') {
|
|
1165
|
-
if (!existsSync(CONFIG_PATH)) {
|
|
1166
|
-
console.error(red('No config found. Run `mindos onboard` first.'));
|
|
1167
|
-
process.exit(EXIT.ERROR);
|
|
1168
|
-
}
|
|
1169
|
-
let config;
|
|
1170
|
-
try {
|
|
1171
|
-
config = JSON.parse(readFileSync(CONFIG_PATH, 'utf-8'));
|
|
1172
|
-
} catch (e) {
|
|
1173
|
-
console.error(red(`✘ Invalid JSON: ${e.message}`));
|
|
1174
|
-
process.exit(EXIT.ERROR);
|
|
1175
|
-
}
|
|
1176
|
-
const issues = [];
|
|
1177
|
-
if (!config.mindRoot) issues.push('missing required field: mindRoot');
|
|
1178
|
-
if (!config.ai?.provider) issues.push('missing field: ai.provider');
|
|
1179
|
-
if (config.ai?.provider === 'anthropic') {
|
|
1180
|
-
const key = config.ai?.providers?.anthropic?.apiKey || config.ai?.anthropicApiKey;
|
|
1181
|
-
if (!key) issues.push('ai.provider is "anthropic" but no API key found');
|
|
1182
|
-
}
|
|
1183
|
-
if (config.ai?.provider === 'openai') {
|
|
1184
|
-
const key = config.ai?.providers?.openai?.apiKey || config.ai?.openaiApiKey;
|
|
1185
|
-
if (!key) issues.push('ai.provider is "openai" but no API key found');
|
|
1186
|
-
}
|
|
1187
|
-
if (issues.length) {
|
|
1188
|
-
console.error(`\n${red('✘ Config has issues:')}`);
|
|
1189
|
-
issues.forEach(i => console.error(` ${red('•')} ${i}`));
|
|
1190
|
-
console.error(`\n ${dim('Run `mindos onboard` to fix.\n')}`);
|
|
1191
|
-
process.exit(EXIT.ERROR);
|
|
1192
|
-
}
|
|
1193
|
-
console.log(`\n${green('✔ Config is valid')}\n`);
|
|
1194
|
-
return;
|
|
1195
|
-
}
|
|
1196
|
-
|
|
1197
|
-
if (sub === 'set') {
|
|
1198
|
-
const key = cliArgs[1];
|
|
1199
|
-
const val = cliArgs[2];
|
|
1200
|
-
if (!key || val === undefined) {
|
|
1201
|
-
console.error(red('Usage: mindos config set <key> <value>'));
|
|
1202
|
-
console.error(dim(' Examples:'));
|
|
1203
|
-
console.error(dim(' mindos config set port 3002'));
|
|
1204
|
-
console.error(dim(' mindos config set mcpPort 8788'));
|
|
1205
|
-
console.error(dim(' mindos config set ai.provider openai'));
|
|
1206
|
-
process.exit(EXIT.ARGS);
|
|
1207
|
-
}
|
|
1208
|
-
if (!existsSync(CONFIG_PATH)) {
|
|
1209
|
-
console.error(red('No config found. Run `mindos onboard` first.'));
|
|
1210
|
-
process.exit(EXIT.ERROR);
|
|
1211
|
-
}
|
|
1212
|
-
let config;
|
|
1213
|
-
try { config = JSON.parse(readFileSync(CONFIG_PATH, 'utf-8')); } catch {
|
|
1214
|
-
console.error(red('Failed to parse config file.'));
|
|
1215
|
-
process.exit(EXIT.ERROR);
|
|
1216
|
-
}
|
|
1217
|
-
const parts = key.split('.');
|
|
1218
|
-
let obj = config;
|
|
1219
|
-
for (let i = 0; i < parts.length - 1; i++) {
|
|
1220
|
-
if (typeof obj[parts[i]] !== 'object' || !obj[parts[i]]) obj[parts[i]] = {};
|
|
1221
|
-
obj = obj[parts[i]];
|
|
1222
|
-
}
|
|
1223
|
-
// Coerce string values to appropriate types
|
|
1224
|
-
function coerceValue(v) {
|
|
1225
|
-
if (v === 'true') return true;
|
|
1226
|
-
if (v === 'false') return false;
|
|
1227
|
-
if (v === 'null') return null;
|
|
1228
|
-
if (v === '""' || v === "''") return '';
|
|
1229
|
-
if (v.trim() !== '' && !isNaN(Number(v))) return Number(v);
|
|
1230
|
-
return v;
|
|
1231
|
-
}
|
|
1232
|
-
const coerced = coerceValue(val);
|
|
1233
|
-
obj[parts[parts.length - 1]] = coerced;
|
|
1234
|
-
writeFileSync(CONFIG_PATH, JSON.stringify(config, null, 2), 'utf-8');
|
|
1235
|
-
console.log(`${green('✔')} Set ${cyan(key)} = ${bold(String(coerced))}`);
|
|
1236
|
-
return;
|
|
1237
|
-
}
|
|
1238
|
-
|
|
1239
|
-
if (sub === 'unset') {
|
|
1240
|
-
const key = cliArgs[1];
|
|
1241
|
-
if (!key) {
|
|
1242
|
-
console.error(red('Usage: mindos config unset <key>'));
|
|
1243
|
-
process.exit(EXIT.ARGS);
|
|
1244
|
-
}
|
|
1245
|
-
if (!existsSync(CONFIG_PATH)) {
|
|
1246
|
-
console.error(red('No config found. Run `mindos onboard` first.'));
|
|
1247
|
-
process.exit(EXIT.ERROR);
|
|
1248
|
-
}
|
|
1249
|
-
let config;
|
|
1250
|
-
try { config = JSON.parse(readFileSync(CONFIG_PATH, 'utf-8')); } catch {
|
|
1251
|
-
console.error(red('Failed to parse config file.'));
|
|
1252
|
-
process.exit(EXIT.ERROR);
|
|
1253
|
-
}
|
|
1254
|
-
const parts = key.split('.');
|
|
1255
|
-
let obj = config;
|
|
1256
|
-
for (let i = 0; i < parts.length - 1; i++) {
|
|
1257
|
-
if (!obj[parts[i]]) { console.log(dim(`Key "${key}" not found`)); return; }
|
|
1258
|
-
obj = obj[parts[i]];
|
|
1259
|
-
}
|
|
1260
|
-
if (!(parts[parts.length - 1] in obj)) { console.log(dim(`Key "${key}" not found`)); return; }
|
|
1261
|
-
delete obj[parts[parts.length - 1]];
|
|
1262
|
-
writeFileSync(CONFIG_PATH, JSON.stringify(config, null, 2), 'utf-8');
|
|
1263
|
-
console.log(`${green('✔')} Removed ${cyan(key)}`);
|
|
1264
|
-
return;
|
|
1265
|
-
}
|
|
1266
|
-
|
|
1267
|
-
// no subcommand or unknown → show help
|
|
1268
|
-
const row = (c, d) => ` ${cyan(c.padEnd(32))}${dim(d)}`;
|
|
1269
|
-
console.log(`
|
|
1270
|
-
${bold('mindos config')} — view and update MindOS configuration
|
|
1271
|
-
|
|
1272
|
-
${bold('Subcommands:')}
|
|
1273
|
-
${row('mindos config show', 'Print current config (API keys masked)')}
|
|
1274
|
-
${row('mindos config validate', 'Validate config file')}
|
|
1275
|
-
${row('mindos config set <key> <v>', 'Update a single field (dot-notation supported)')}
|
|
1276
|
-
${row('mindos config unset <key>', 'Remove a config field')}
|
|
1277
|
-
|
|
1278
|
-
${bold('Examples:')}
|
|
1279
|
-
${dim('mindos config set port 3002')}
|
|
1280
|
-
${dim('mindos config set ai.provider openai')}
|
|
1281
|
-
${dim('mindos config set setupPending false')}
|
|
1282
|
-
${dim('mindos config unset webPassword')}
|
|
1283
|
-
`);
|
|
1284
|
-
},
|
|
1285
|
-
|
|
1286
|
-
// ── sync ──────────────────────────────────────────────────────────────────
|
|
1287
|
-
sync: async () => {
|
|
1288
|
-
const sub = cliArgs[0];
|
|
1289
|
-
loadConfig();
|
|
1290
|
-
const mindRoot = process.env.MIND_ROOT;
|
|
1291
|
-
|
|
1292
|
-
if (sub === 'init') {
|
|
1293
|
-
// Flags are already parsed by parseArgs into cliFlags
|
|
1294
|
-
const nonInteractive = cliFlags['non-interactive'] === true;
|
|
1295
|
-
|
|
1296
|
-
if (nonInteractive) {
|
|
1297
|
-
await initSync(mindRoot, {
|
|
1298
|
-
nonInteractive: true,
|
|
1299
|
-
remote: typeof cliFlags.remote === 'string' ? cliFlags.remote : '',
|
|
1300
|
-
token: typeof cliFlags.token === 'string' ? cliFlags.token : '',
|
|
1301
|
-
branch: (typeof cliFlags.branch === 'string' ? cliFlags.branch : '') || 'main',
|
|
1302
|
-
});
|
|
1303
|
-
} else {
|
|
1304
|
-
await initSync(mindRoot);
|
|
1305
|
-
}
|
|
1306
|
-
return;
|
|
1307
|
-
}
|
|
1308
|
-
|
|
1309
|
-
if (sub === 'now') {
|
|
1310
|
-
try {
|
|
1311
|
-
console.log(dim('Pulling...'));
|
|
1312
|
-
await manualSync(mindRoot);
|
|
1313
|
-
console.log(green('✔ Sync complete'));
|
|
1314
|
-
} catch (err) {
|
|
1315
|
-
console.error(red(err.message));
|
|
1316
|
-
process.exit(EXIT.ERROR);
|
|
1317
|
-
}
|
|
1318
|
-
return;
|
|
1319
|
-
}
|
|
74
|
+
// ── Help generation ───────────────────────────────────────────────────────────
|
|
75
|
+
// Summaries come from each module's meta.summary — single source of truth.
|
|
76
|
+
// [displayName, module] — displayName may differ from meta.name (e.g. init → onboard).
|
|
77
|
+
|
|
78
|
+
const coreEntries = [
|
|
79
|
+
['agent', agentCmd],
|
|
80
|
+
['ask', askCmd],
|
|
81
|
+
['start', startCmd],
|
|
82
|
+
['stop', stopCmd],
|
|
83
|
+
['status', statusCmd],
|
|
84
|
+
['open', openCmd],
|
|
85
|
+
['file', fileCmd],
|
|
86
|
+
['space', spaceCmd],
|
|
87
|
+
['search', searchCmd],
|
|
88
|
+
['mcp', mcpCmd],
|
|
89
|
+
['init', onboardCmd],
|
|
90
|
+
['config', configCmd],
|
|
91
|
+
['doctor', doctorCmd],
|
|
92
|
+
['update', updateCmd],
|
|
93
|
+
];
|
|
94
|
+
|
|
95
|
+
const additionalEntries = [
|
|
96
|
+
['dev', devCmd],
|
|
97
|
+
['build', buildCmd],
|
|
98
|
+
['restart', restartCmd],
|
|
99
|
+
['sync', syncCmd],
|
|
100
|
+
['gateway', gatewayCmd],
|
|
101
|
+
['token', tokenCmd],
|
|
102
|
+
['logs', logsCmd],
|
|
103
|
+
['api', apiCmd],
|
|
104
|
+
['init-skills', initSkillsCmd],
|
|
105
|
+
['uninstall', uninstallCmd],
|
|
106
|
+
];
|
|
107
|
+
|
|
108
|
+
function showGlobalHelp(showAll = false) {
|
|
109
|
+
const pkgVersion = (() => {
|
|
110
|
+
try { return JSON.parse(readFileSync(resolve(ROOT, 'package.json'), 'utf-8')).version; }
|
|
111
|
+
catch { return '?'; }
|
|
112
|
+
})();
|
|
113
|
+
const row = ([name, mod]) => ` ${cyan(name.padEnd(14))}${dim(mod.meta.summary)}`;
|
|
114
|
+
|
|
115
|
+
const lines = [
|
|
116
|
+
'',
|
|
117
|
+
`${bold('MindOS CLI')} ${dim(`v${pkgVersion}`)}`,
|
|
118
|
+
'',
|
|
119
|
+
`${bold('USAGE')}`,
|
|
120
|
+
` ${cyan('mindos <command> [flags]')}`,
|
|
121
|
+
'',
|
|
122
|
+
`${bold('COMMANDS')}`,
|
|
123
|
+
...coreEntries.map(row),
|
|
124
|
+
];
|
|
1320
125
|
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
126
|
+
if (showAll) {
|
|
127
|
+
lines.push('', `${bold('ADDITIONAL COMMANDS')}`);
|
|
128
|
+
lines.push(...additionalEntries.map(row));
|
|
129
|
+
}
|
|
1325
130
|
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
}
|
|
131
|
+
const flagRow = (f, d) => ` ${cyan(f.padEnd(14))}${dim(d)}`;
|
|
132
|
+
lines.push(
|
|
133
|
+
'',
|
|
134
|
+
`${bold('FLAGS')}`,
|
|
135
|
+
flagRow('--help, -h', 'Show help'),
|
|
136
|
+
flagRow('--version, -v', 'Show version'),
|
|
137
|
+
flagRow('--json', 'Output as JSON'),
|
|
138
|
+
'',
|
|
139
|
+
` ${dim('Run')} ${cyan('mindos <command> --help')} ${dim('for details on any command.')}`,
|
|
140
|
+
);
|
|
141
|
+
|
|
142
|
+
if (!showAll) {
|
|
143
|
+
lines.push(` ${dim('Run')} ${cyan('mindos --all')} ${dim('to see all commands.')}`);
|
|
144
|
+
}
|
|
1330
145
|
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
return;
|
|
1335
|
-
}
|
|
146
|
+
lines.push('');
|
|
147
|
+
console.log(lines.join('\n'));
|
|
148
|
+
}
|
|
1336
149
|
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
150
|
+
/**
|
|
151
|
+
* Show help for a specific command.
|
|
152
|
+
* Delegates to the command's own printHelp() if it exists,
|
|
153
|
+
* otherwise auto-generates from meta.
|
|
154
|
+
*/
|
|
155
|
+
function showCommandHelp(mod) {
|
|
156
|
+
if (typeof mod.printHelp === 'function') {
|
|
157
|
+
mod.printHelp();
|
|
158
|
+
} else {
|
|
159
|
+
printCommandHelp(mod);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
1346
162
|
|
|
1347
|
-
|
|
1348
|
-
const status = await getSyncStatus(mindRoot);
|
|
163
|
+
// ── Entry ─────────────────────────────────────────────────────────────────────
|
|
1349
164
|
|
|
1350
|
-
|
|
1351
|
-
console.log(JSON.stringify(status, null, 2));
|
|
1352
|
-
return;
|
|
1353
|
-
}
|
|
165
|
+
const showAll = cliFlags.all === true || cliFlags.a === true;
|
|
1354
166
|
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
return;
|
|
1359
|
-
}
|
|
1360
|
-
const ago = status.lastSync
|
|
1361
|
-
? (() => {
|
|
1362
|
-
const diff = Date.now() - new Date(status.lastSync).getTime();
|
|
1363
|
-
if (diff < 60000) return 'just now';
|
|
1364
|
-
if (diff < 3600000) return `${Math.floor(diff / 60000)} minutes ago`;
|
|
1365
|
-
return `${Math.floor(diff / 3600000)} hours ago`;
|
|
1366
|
-
})()
|
|
1367
|
-
: 'never';
|
|
167
|
+
// --help can be boolean (--help) or string (--help agent)
|
|
168
|
+
const helpValue = cliFlags.help || cliFlags.h;
|
|
169
|
+
const hasHelp = helpValue !== undefined && helpValue !== false;
|
|
1368
170
|
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
console.log(` ${dim('Conflicts:')} ${status.conflicts.length ? yellow(`${status.conflicts.length} file(s)`) : green('none')}`);
|
|
1375
|
-
console.log(` ${dim('Auto-sync:')} ${green('● enabled')} ${dim(`(commit: ${status.autoCommitInterval}s, pull: ${status.autoPullInterval / 60}min)`)}`);
|
|
1376
|
-
if (status.lastError) {
|
|
1377
|
-
console.log(` ${dim('Last error:')} ${red(status.lastError)}`);
|
|
1378
|
-
}
|
|
1379
|
-
console.log();
|
|
1380
|
-
},
|
|
171
|
+
// `mindos --all` → show full help
|
|
172
|
+
if (showAll && !cmd) {
|
|
173
|
+
showGlobalHelp(true);
|
|
174
|
+
process.exit(0);
|
|
175
|
+
}
|
|
1381
176
|
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
177
|
+
// `mindos help <cmd>` → show help for <cmd>
|
|
178
|
+
if (cmd === 'help') {
|
|
179
|
+
const target = cliArgs[0];
|
|
180
|
+
if (target && commands[target]) {
|
|
181
|
+
showCommandHelp(commands[target]);
|
|
182
|
+
} else {
|
|
183
|
+
showGlobalHelp(showAll);
|
|
184
|
+
}
|
|
185
|
+
process.exit(0);
|
|
186
|
+
}
|
|
1391
187
|
|
|
1392
|
-
//
|
|
188
|
+
// `mindos --help <cmd>` → parseArgs puts <cmd> as the value of --help
|
|
189
|
+
if (hasHelp && typeof helpValue === 'string' && commands[helpValue]) {
|
|
190
|
+
showCommandHelp(commands[helpValue]);
|
|
191
|
+
process.exit(0);
|
|
192
|
+
}
|
|
1393
193
|
|
|
1394
|
-
|
|
194
|
+
// Resolve which command to run (or show global help)
|
|
195
|
+
const resolvedCmd = (hasHelp && !cmd) ? null : (cmd || null);
|
|
1395
196
|
|
|
1396
197
|
if (!resolvedCmd || !commands[resolvedCmd]) {
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
// Command registry — help is generated entirely from this array.
|
|
1401
|
-
// Modular commands provide meta via their exports; inline commands define meta here.
|
|
1402
|
-
const helpRegistry = [
|
|
1403
|
-
{ group: 'Core', usage: 'mindos onboard', summary: 'Interactive setup (aliases: init, setup)' },
|
|
1404
|
-
{ group: 'Core', usage: 'mindos start', summary: 'Start app + MCP server (production)' },
|
|
1405
|
-
{ group: 'Core', usage: 'mindos start --daemon', summary: 'Start as background OS service' },
|
|
1406
|
-
{ group: 'Core', usage: 'mindos dev', summary: 'Start in dev mode' },
|
|
1407
|
-
{ group: 'Core', usage: 'mindos stop', summary: 'Stop running processes' },
|
|
1408
|
-
{ group: 'Core', usage: 'mindos restart', summary: 'Stop then start again' },
|
|
1409
|
-
{ group: 'Core', usage: 'mindos build', summary: 'Build for production' },
|
|
1410
|
-
statusCmd.meta,
|
|
1411
|
-
{ group: 'Core', usage: 'mindos open', summary: 'Open Web UI in browser' },
|
|
1412
|
-
fileCmd.meta, spaceCmd.meta, searchCmd.meta, askCmd.meta, agentCmd.meta, apiCmd.meta,
|
|
1413
|
-
{ group: 'MCP', usage: 'mindos mcp', summary: 'Start MCP server only' },
|
|
1414
|
-
{ group: 'MCP', usage: 'mindos mcp install [agent]', summary: 'Install MCP config into Agent' },
|
|
1415
|
-
{ group: 'MCP', usage: 'mindos token', summary: 'Show auth token and MCP config' },
|
|
1416
|
-
{ group: 'Sync', usage: 'mindos sync', summary: 'Show sync status (init/now/conflicts/on/off)' },
|
|
1417
|
-
{ group: 'Gateway', usage: 'mindos gateway <sub>', summary: 'Manage service (install/start/stop/status/logs)' },
|
|
1418
|
-
{ group: 'Config', usage: 'mindos config <sub>', summary: 'View/update config (show/set/unset/validate)' },
|
|
1419
|
-
{ group: 'Config', usage: 'mindos doctor', summary: 'Health check' },
|
|
1420
|
-
{ group: 'Config', usage: 'mindos update', summary: 'Update to latest version' },
|
|
1421
|
-
{ group: 'Config', usage: 'mindos uninstall', summary: 'Fully uninstall MindOS' },
|
|
1422
|
-
{ group: 'Config', usage: 'mindos logs', summary: 'Tail service logs' },
|
|
1423
|
-
];
|
|
1424
|
-
|
|
1425
|
-
const groupLabels = [
|
|
1426
|
-
['Core', 'Core'], ['Knowledge', 'Knowledge'], ['MCP', 'MCP'],
|
|
1427
|
-
['Sync', 'Sync'], ['Gateway', 'Gateway (Background Service)'], ['Config', 'Config & Diagnostics'],
|
|
1428
|
-
];
|
|
1429
|
-
const groups = {};
|
|
1430
|
-
for (const e of helpRegistry) {
|
|
1431
|
-
const g = e.group || 'Other';
|
|
1432
|
-
if (!groups[g]) groups[g] = [];
|
|
1433
|
-
groups[g].push(row(e.usage || `mindos ${e.name}`, e.summary));
|
|
1434
|
-
}
|
|
1435
|
-
const sections = groupLabels
|
|
1436
|
-
.filter(([k]) => groups[k])
|
|
1437
|
-
.map(([k, label]) => `${bold(`${label}:`)}\n${groups[k].join('\n')}`);
|
|
198
|
+
showGlobalHelp(showAll);
|
|
199
|
+
process.exit((cmd && !hasHelp) ? 1 : 0);
|
|
200
|
+
}
|
|
1438
201
|
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
202
|
+
// `mindos <cmd> --help` → show help for <cmd> instead of executing it
|
|
203
|
+
if (hasHelp) {
|
|
204
|
+
showCommandHelp(commands[resolvedCmd]);
|
|
205
|
+
process.exit(0);
|
|
1442
206
|
}
|
|
1443
207
|
|
|
1444
|
-
commands[resolvedCmd]();
|
|
208
|
+
commands[resolvedCmd].run(cliArgs, cliFlags);
|