@geminilight/mindos 0.1.0
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/.env.local.example +38 -0
- package/LICENSE +21 -0
- package/README.md +423 -0
- package/README_zh.md +423 -0
- package/app/README.md +152 -0
- package/app/app/api/ask/route.ts +170 -0
- package/app/app/api/ask-sessions/route.ts +90 -0
- package/app/app/api/auth/route.ts +37 -0
- package/app/app/api/backlinks/route.ts +22 -0
- package/app/app/api/bootstrap/route.ts +37 -0
- package/app/app/api/extract-pdf/route.ts +82 -0
- package/app/app/api/file/route.ts +138 -0
- package/app/app/api/files/route.ts +12 -0
- package/app/app/api/git/route.ts +42 -0
- package/app/app/api/graph/route.ts +113 -0
- package/app/app/api/recent-files/route.ts +10 -0
- package/app/app/api/search/route.ts +17 -0
- package/app/app/api/settings/reset-token/route.ts +21 -0
- package/app/app/api/settings/route.ts +123 -0
- package/app/app/error.tsx +33 -0
- package/app/app/globals.css +368 -0
- package/app/app/icon.svg +35 -0
- package/app/app/layout.tsx +103 -0
- package/app/app/login/page.tsx +120 -0
- package/app/app/page.tsx +12 -0
- package/app/app/view/[...path]/ViewPageClient.tsx +343 -0
- package/app/app/view/[...path]/error.tsx +33 -0
- package/app/app/view/[...path]/loading.tsx +15 -0
- package/app/app/view/[...path]/page.tsx +93 -0
- package/app/components/AskFab.tsx +59 -0
- package/app/components/AskModal.tsx +398 -0
- package/app/components/Backlinks.tsx +75 -0
- package/app/components/Breadcrumb.tsx +31 -0
- package/app/components/CsvView.tsx +325 -0
- package/app/components/DirView.tsx +138 -0
- package/app/components/Editor.tsx +124 -0
- package/app/components/EditorWrapper.tsx +17 -0
- package/app/components/ErrorBoundary.tsx +53 -0
- package/app/components/FileTree.tsx +369 -0
- package/app/components/HomeContent.tsx +262 -0
- package/app/components/JsonView.tsx +27 -0
- package/app/components/MarkdownEditor.tsx +95 -0
- package/app/components/MarkdownView.tsx +118 -0
- package/app/components/SearchModal.tsx +193 -0
- package/app/components/SettingsModal.tsx +237 -0
- package/app/components/Sidebar.tsx +136 -0
- package/app/components/SidebarLayout.tsx +36 -0
- package/app/components/TableOfContents.tsx +150 -0
- package/app/components/ThemeToggle.tsx +34 -0
- package/app/components/WysiwygEditor.tsx +75 -0
- package/app/components/ask/FileChip.tsx +30 -0
- package/app/components/ask/MentionPopover.tsx +52 -0
- package/app/components/ask/MessageList.tsx +126 -0
- package/app/components/ask/SessionHistory.tsx +49 -0
- package/app/components/renderers/AgentInspectorRenderer.tsx +277 -0
- package/app/components/renderers/BacklinksRenderer.tsx +147 -0
- package/app/components/renderers/ConfigRenderer.tsx +236 -0
- package/app/components/renderers/CsvRenderer.tsx +77 -0
- package/app/components/renderers/DiffRenderer.tsx +310 -0
- package/app/components/renderers/GraphRenderer.tsx +428 -0
- package/app/components/renderers/SummaryRenderer.tsx +251 -0
- package/app/components/renderers/TimelineRenderer.tsx +213 -0
- package/app/components/renderers/TodoRenderer.tsx +474 -0
- package/app/components/renderers/WorkflowRenderer.tsx +404 -0
- package/app/components/renderers/csv/BoardView.tsx +146 -0
- package/app/components/renderers/csv/ConfigPanel.tsx +117 -0
- package/app/components/renderers/csv/EditableCell.tsx +43 -0
- package/app/components/renderers/csv/GalleryView.tsx +40 -0
- package/app/components/renderers/csv/TableView.tsx +164 -0
- package/app/components/renderers/csv/types.ts +87 -0
- package/app/components/settings/AiTab.tsx +111 -0
- package/app/components/settings/AppearanceTab.tsx +101 -0
- package/app/components/settings/KnowledgeTab.tsx +157 -0
- package/app/components/settings/PluginsTab.tsx +82 -0
- package/app/components/settings/Primitives.tsx +60 -0
- package/app/components/settings/ShortcutsTab.tsx +22 -0
- package/app/components/settings/types.ts +41 -0
- package/app/components/ui/button.tsx +60 -0
- package/app/components/ui/dialog.tsx +157 -0
- package/app/components/ui/input.tsx +20 -0
- package/app/components/ui/scroll-area.tsx +55 -0
- package/app/components/ui/toggle.tsx +44 -0
- package/app/components/ui/tooltip.tsx +66 -0
- package/app/components.json +25 -0
- package/app/data/pages/home-dark.png +0 -0
- package/app/data/pages/home-mobile-crop.png +0 -0
- package/app/data/pages/home-mobile.png +0 -0
- package/app/data/pages/home.png +0 -0
- package/app/data/pages/view-dir.png +0 -0
- package/app/data/pages/view-file-bot.png +0 -0
- package/app/data/pages/view-file-dark-crop.png +0 -0
- package/app/data/pages/view-file-dark.png +0 -0
- package/app/data/pages/view-file-mobile.png +0 -0
- package/app/data/pages/view-file-sm.png +0 -0
- package/app/data/pages/view-file-top.png +0 -0
- package/app/data/pages/view-file.png +0 -0
- package/app/eslint.config.mjs +18 -0
- package/app/hooks/useAskSession.ts +181 -0
- package/app/hooks/useFileUpload.ts +126 -0
- package/app/hooks/useMention.ts +65 -0
- package/app/lib/LocaleContext.tsx +40 -0
- package/app/lib/actions.ts +40 -0
- package/app/lib/agent/index.ts +3 -0
- package/app/lib/agent/model.ts +18 -0
- package/app/lib/agent/prompt.ts +32 -0
- package/app/lib/agent/tools.ts +151 -0
- package/app/lib/api.ts +55 -0
- package/app/lib/core/backlinks.ts +40 -0
- package/app/lib/core/csv.ts +28 -0
- package/app/lib/core/fs-ops.ts +118 -0
- package/app/lib/core/git.ts +50 -0
- package/app/lib/core/index.ts +58 -0
- package/app/lib/core/lines.ts +89 -0
- package/app/lib/core/search.ts +79 -0
- package/app/lib/core/security.ts +43 -0
- package/app/lib/core/tree.ts +113 -0
- package/app/lib/core/types.ts +40 -0
- package/app/lib/fs.ts +467 -0
- package/app/lib/i18n.ts +300 -0
- package/app/lib/jwt.ts +58 -0
- package/app/lib/renderers/index.ts +79 -0
- package/app/lib/renderers/registry.ts +70 -0
- package/app/lib/settings.ts +150 -0
- package/app/lib/types.ts +32 -0
- package/app/lib/utils.ts +34 -0
- package/app/next-env.d.ts +6 -0
- package/app/next.config.ts +10 -0
- package/app/package-lock.json +15306 -0
- package/app/package.json +71 -0
- package/app/postcss.config.mjs +7 -0
- package/app/proxy.ts +64 -0
- package/app/public/file.svg +1 -0
- package/app/public/globe.svg +1 -0
- package/app/public/landing/index.html +353 -0
- package/app/public/landing/style.css +216 -0
- package/app/public/logo-square.svg +37 -0
- package/app/public/logo.svg +37 -0
- package/app/public/next.svg +1 -0
- package/app/public/vercel.svg +1 -0
- package/app/public/window.svg +1 -0
- package/app/scripts/extract-pdf.cjs +56 -0
- package/app/tsconfig.json +34 -0
- package/app/vitest.config.ts +14 -0
- package/assets/demo-flow-zh.html +622 -0
- package/assets/images/demo-flow-dark.png +0 -0
- package/assets/images/demo-flow-light.png +0 -0
- package/assets/images/demo-flow-zh-dark.png +0 -0
- package/assets/images/demo-flow-zh-light.png +0 -0
- package/assets/images/gui-sync-cv.png +0 -0
- package/assets/logo-square.svg +37 -0
- package/bin/cli.js +894 -0
- package/mcp/README.md +113 -0
- package/mcp/package-lock.json +1717 -0
- package/mcp/package.json +18 -0
- package/mcp/src/index.ts +494 -0
- package/mcp/tsconfig.json +13 -0
- package/package.json +49 -0
- package/scripts/setup.js +675 -0
- package/scripts/upgrade-prompt.md +147 -0
- package/skills/mindos/SKILL.md +319 -0
- package/skills/mindos-zh/SKILL.md +318 -0
- package/templates/README.md +31 -0
- package/templates/empty/CHANGELOG.md +9 -0
- package/templates/empty/CONFIG.json +197 -0
- package/templates/empty/CONFIG.md +73 -0
- package/templates/empty/INSTRUCTION.md +177 -0
- package/templates/empty/README.md +27 -0
- package/templates/en/CHANGELOG.md +9 -0
- package/templates/en/CONFIG.json +197 -0
- package/templates/en/CONFIG.md +73 -0
- package/templates/en/INSTRUCTION.md +177 -0
- package/templates/en/README.md +27 -0
- package/templates/en/TODO.md +13 -0
- package/templates/en//360/237/221/244 Profile/INSTRUCTION.md" +21 -0
- package/templates/en//360/237/221/244 Profile/README.md" +15 -0
- package/templates/en//360/237/221/244 Profile//342/232/231/357/270/217 Preferences.md" +21 -0
- package/templates/en//360/237/221/244 Profile//360/237/216/257 Focus.md" +31 -0
- package/templates/en//360/237/221/244 Profile//360/237/221/244 Identity.md" +22 -0
- package/templates/en//360/237/223/232 Resources/INSTRUCTION.md" +29 -0
- package/templates/en//360/237/223/232 Resources/README.md" +21 -0
- package/templates/en//360/237/223/232 Resources//360/237/247/276 AI Influencers.csv" +1 -0
- package/templates/en//360/237/223/232 Resources//360/237/247/276 AI Products.csv" +1 -0
- package/templates/en//360/237/223/232 Resources//360/237/247/276 AI Scholars.csv" +1 -0
- package/templates/en//360/237/223/232 Resources//360/237/247/276 AI Tools.csv" +1 -0
- package/templates/en//360/237/223/235 Notes/INSTRUCTION.md" +31 -0
- package/templates/en//360/237/223/235 Notes/Ideas/README.md" +8 -0
- package/templates/en//360/237/223/235 Notes/Ideas//360/237/247/252_example_product_idea.md" +16 -0
- package/templates/en//360/237/223/235 Notes/Inbox/README.md" +8 -0
- package/templates/en//360/237/223/235 Notes/Inbox//360/237/247/252_example_quick_capture.md" +14 -0
- package/templates/en//360/237/223/235 Notes/Meetings/README.md" +8 -0
- package/templates/en//360/237/223/235 Notes/Meetings//360/237/247/252_example_meeting_note.md" +17 -0
- package/templates/en//360/237/223/235 Notes/README.md" +24 -0
- package/templates/en//360/237/223/235 Notes/Waiting/README.md" +8 -0
- package/templates/en//360/237/223/235 Notes/Waiting//360/237/247/252_example_blocked_item.md" +16 -0
- package/templates/en//360/237/224/204 Workflows/Configurations/README.md" +3 -0
- package/templates/en//360/237/224/204 Workflows/Configurations//360/237/247/252_example_config_update_sop.md" +14 -0
- package/templates/en//360/237/224/204 Workflows/INSTRUCTION.md" +21 -0
- package/templates/en//360/237/224/204 Workflows/Information/README.md" +16 -0
- package/templates/en//360/237/224/204 Workflows/Information//360/237/247/252_example_info_capture_sop.md" +13 -0
- package/templates/en//360/237/224/204 Workflows/Media/README.md" +16 -0
- package/templates/en//360/237/224/204 Workflows/Media//360/237/247/252_example_content_publish_sop.md" +13 -0
- package/templates/en//360/237/224/204 Workflows/README.md" +22 -0
- package/templates/en//360/237/224/204 Workflows/Research/README.md" +16 -0
- package/templates/en//360/237/224/204 Workflows/Research//360/237/247/252_example_lit_review_sop.md" +16 -0
- package/templates/en//360/237/224/204 Workflows/Startup/README.md" +3 -0
- package/templates/en//360/237/224/204 Workflows/Startup//360/237/247/252_example_weekly_founder_ops.md" +22 -0
- package/templates/en//360/237/224/227 Connections/Classmates/README.md" +11 -0
- package/templates/en//360/237/224/227 Connections/Classmates//360/237/247/252_example_leo_chen.md" +16 -0
- package/templates/en//360/237/224/227 Connections/Colleagues/README.md" +11 -0
- package/templates/en//360/237/224/227 Connections/Colleagues//360/237/247/252_example_ethan_zhao.md" +16 -0
- package/templates/en//360/237/224/227 Connections/Connections Overview.csv" +5 -0
- package/templates/en//360/237/224/227 Connections/Family/README.md" +11 -0
- package/templates/en//360/237/224/227 Connections/Family//360/237/247/252_example_james_wang.md" +16 -0
- package/templates/en//360/237/224/227 Connections/Friends/README.md" +11 -0
- package/templates/en//360/237/224/227 Connections/Friends//360/237/247/252_example_lily_lin.md" +16 -0
- package/templates/en//360/237/224/227 Connections/INSTRUCTION.md" +56 -0
- package/templates/en//360/237/224/227 Connections/README.md" +20 -0
- package/templates/en//360/237/232/200 Projects/Archived/README.md" +9 -0
- package/templates/en//360/237/232/200 Projects/Archived//360/237/247/252_example_archived_project_note.md" +14 -0
- package/templates/en//360/237/232/200 Projects/INSTRUCTION.md" +29 -0
- package/templates/en//360/237/232/200 Projects/Products/README.md" +16 -0
- package/templates/en//360/237/232/200 Projects/Products//360/237/247/252_example_product_project_brief.md" +20 -0
- package/templates/en//360/237/232/200 Projects/README.md" +21 -0
- package/templates/en//360/237/232/200 Projects/Research/README.md" +16 -0
- package/templates/en//360/237/232/200 Projects/Research//360/237/247/252_example_research_project_brief.md" +16 -0
- package/templates/template-generation-skill.md +79 -0
- package/templates/zh/CHANGELOG.md +9 -0
- package/templates/zh/CONFIG.json +197 -0
- package/templates/zh/CONFIG.md +66 -0
- package/templates/zh/INSTRUCTION.md +177 -0
- package/templates/zh/README.md +27 -0
- package/templates/zh/TODO.md +13 -0
- package/templates/zh//360/237/221/244 /347/224/273/345/203/217/INSTRUCTION.md" +28 -0
- package/templates/zh//360/237/221/244 /347/224/273/345/203/217/README.md" +20 -0
- package/templates/zh//360/237/221/244 /347/224/273/345/203/217//342/232/231/357/270/217 Preferences.md" +21 -0
- package/templates/zh//360/237/221/244 /347/224/273/345/203/217//360/237/216/257 Focus.md" +31 -0
- package/templates/zh//360/237/221/244 /347/224/273/345/203/217//360/237/221/244 Identity.md" +22 -0
- package/templates/zh//360/237/223/232 /350/265/204/346/272/220/INSTRUCTION.md" +29 -0
- package/templates/zh//360/237/223/232 /350/265/204/346/272/220/README.md" +21 -0
- package/templates/zh//360/237/223/232 /350/265/204/346/272/220//360/237/247/276 AI Inferencers.csv" +1 -0
- package/templates/zh//360/237/223/232 /350/265/204/346/272/220//360/237/247/276 AI /344/272/247/345/223/201.csv" +1 -0
- package/templates/zh//360/237/223/232 /350/265/204/346/272/220//360/237/247/276 AI /345/255/246/350/200/205/346/270/205/345/215/225.csv" +1 -0
- package/templates/zh//360/237/223/232 /350/265/204/346/272/220//360/237/247/276 AI /345/267/245/345/205/267/346/270/205/345/215/225.csv" +1 -0
- package/templates/zh//360/237/223/235 /347/254/224/350/256/260/INSTRUCTION.md" +31 -0
- package/templates/zh//360/237/223/235 /347/254/224/350/256/260/README.md" +24 -0
- package/templates/zh//360/237/223/235 /347/254/224/350/256/260//344/274/232/350/256/256/README.md" +8 -0
- package/templates/zh//360/237/223/235 /347/254/224/350/256/260//344/274/232/350/256/256//360/237/247/252_example_/344/274/232/350/256/256/347/272/252/350/246/201.md" +17 -0
- package/templates/zh//360/237/223/235 /347/254/224/350/256/260//345/276/205/345/217/215/351/246/210/README.md" +8 -0
- package/templates/zh//360/237/223/235 /347/254/224/350/256/260//345/276/205/345/217/215/351/246/210//360/237/247/252_example_/345/276/205/345/217/215/351/246/210/344/272/213/351/241/271.md" +16 -0
- package/templates/zh//360/237/223/235 /347/254/224/350/256/260//346/203/263/346/263/225/README.md" +8 -0
- package/templates/zh//360/237/223/235 /347/254/224/350/256/260//346/203/263/346/263/225//360/237/247/252_example_/344/272/247/345/223/201/346/203/263/346/263/225.md" +16 -0
- package/templates/zh//360/237/223/235 /347/254/224/350/256/260//346/224/266/344/273/266/347/256/261/README.md" +8 -0
- package/templates/zh//360/237/223/235 /347/254/224/350/256/260//346/224/266/344/273/266/347/256/261//360/237/247/252_example_/344/270/264/346/227/266/351/200/237/350/256/260.md" +13 -0
- package/templates/zh//360/237/224/204 /346/265/201/347/250/213/INSTRUCTION.md" +29 -0
- package/templates/zh//360/237/224/204 /346/265/201/347/250/213/README.md" +21 -0
- package/templates/zh//360/237/224/204 /346/265/201/347/250/213//344/277/241/346/201/257/README.md" +16 -0
- package/templates/zh//360/237/224/204 /346/265/201/347/250/213//344/277/241/346/201/257//360/237/247/252_example_/344/277/241/346/201/257/351/207/207/351/233/206/346/265/201/347/250/213.md" +13 -0
- package/templates/zh//360/237/224/204 /346/265/201/347/250/213//345/252/222/344/275/223/README.md" +16 -0
- package/templates/zh//360/237/224/204 /346/265/201/347/250/213//345/252/222/344/275/223//360/237/247/252_example_/345/206/205/345/256/271/345/217/221/345/270/203/346/265/201/347/250/213.md" +13 -0
- package/templates/zh//360/237/224/204 /346/265/201/347/250/213//347/247/221/347/240/224/README.md" +16 -0
- package/templates/zh//360/237/224/204 /346/265/201/347/250/213//347/247/221/347/240/224//360/237/247/252_example_/346/226/207/347/214/256/347/273/274/350/277/260/346/265/201/347/250/213.md" +16 -0
- package/templates/zh//360/237/224/204 /346/265/201/347/250/213//351/205/215/347/275/256/README.md" +3 -0
- package/templates/zh//360/237/224/204 /346/265/201/347/250/213//351/205/215/347/275/256//360/237/247/252_example_/351/205/215/347/275/256/346/233/264/346/226/260/346/265/201/347/250/213.md" +26 -0
- package/templates/zh//360/237/224/227 /345/205/263/347/263/273/INSTRUCTION.md" +62 -0
- package/templates/zh//360/237/224/227 /345/205/263/347/263/273/README.md" +20 -0
- package/templates/zh//360/237/224/227 /345/205/263/347/263/273//345/205/263/347/263/273/346/200/273/350/247/210.csv" +5 -0
- package/templates/zh//360/237/224/227 /345/205/263/347/263/273//345/220/214/344/272/213/README.md" +11 -0
- package/templates/zh//360/237/224/227 /345/205/263/347/263/273//345/220/214/344/272/213//360/237/247/252_example_/345/220/214/344/272/213/350/265/265/344/270/200/350/276/260.md" +16 -0
- package/templates/zh//360/237/224/227 /345/205/263/347/263/273//345/220/214/345/255/246/README.md" +11 -0
- package/templates/zh//360/237/224/227 /345/205/263/347/263/273//345/220/214/345/255/246//360/237/247/252_example_/345/220/214/345/255/246/351/231/210/347/253/213/346/254/247.md" +16 -0
- package/templates/zh//360/237/224/227 /345/205/263/347/263/273//345/256/266/344/272/272/README.md" +11 -0
- package/templates/zh//360/237/224/227 /345/205/263/347/263/273//345/256/266/344/272/272//360/237/247/252_example_/345/256/266/344/272/272/347/216/213/345/273/272/345/233/275.md" +16 -0
- package/templates/zh//360/237/224/227 /345/205/263/347/263/273//346/234/213/345/217/213/README.md" +11 -0
- package/templates/zh//360/237/224/227 /345/205/263/347/263/273//346/234/213/345/217/213//360/237/247/252_example_/346/234/213/345/217/213/346/236/227/345/260/217/344/270/275.md" +16 -0
- package/templates/zh//360/237/232/200 /351/241/271/347/233/256/INSTRUCTION.md" +31 -0
- package/templates/zh//360/237/232/200 /351/241/271/347/233/256/README.md" +21 -0
- package/templates/zh//360/237/232/200 /351/241/271/347/233/256//344/272/247/345/223/201/README.md" +16 -0
- package/templates/zh//360/237/232/200 /351/241/271/347/233/256//344/272/247/345/223/201//360/237/247/252_example_/344/272/247/345/223/201/351/241/271/347/233/256/347/256/200/346/212/245.md" +20 -0
- package/templates/zh//360/237/232/200 /351/241/271/347/233/256//345/267/262/345/275/222/346/241/243/README.md" +9 -0
- package/templates/zh//360/237/232/200 /351/241/271/347/233/256//345/267/262/345/275/222/346/241/243//360/237/247/252_example_/345/275/222/346/241/243/351/241/271/347/233/256/350/256/260/345/275/225.md" +15 -0
- package/templates/zh//360/237/232/200 /351/241/271/347/233/256//347/247/221/347/240/224/README.md" +16 -0
- package/templates/zh//360/237/232/200 /351/241/271/347/233/256//347/247/221/347/240/224//360/237/247/252_example_/347/247/221/347/240/224/351/241/271/347/233/256/347/256/200/346/212/245.md" +16 -0
package/scripts/setup.js
ADDED
|
@@ -0,0 +1,675 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* MindOS interactive setup script
|
|
5
|
+
*
|
|
6
|
+
* Usage: npm run setup OR mindos onboard
|
|
7
|
+
*
|
|
8
|
+
* Steps:
|
|
9
|
+
* 1. Choose knowledge base path → default ~/.mindos/my-mind
|
|
10
|
+
* 2. Choose template (en / zh / empty / custom) → copy to knowledge base path
|
|
11
|
+
* 3. Choose ports (web + mcp) — checked for conflicts upfront
|
|
12
|
+
* 4. Auth token (auto-generated or passphrase-seeded)
|
|
13
|
+
* 5. Web UI password (optional)
|
|
14
|
+
* 6. Choose AI provider + API Key → write ~/.mindos/config.json
|
|
15
|
+
* 7. Print next steps
|
|
16
|
+
*
|
|
17
|
+
* Language switching:
|
|
18
|
+
* ← → keys switch UI language (en/zh) at any prompt
|
|
19
|
+
* ↑ ↓ keys navigate select options
|
|
20
|
+
* Enter confirms
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
import { existsSync, cpSync, writeFileSync, readFileSync, mkdirSync, createWriteStream, rmSync } from 'node:fs';
|
|
24
|
+
import { resolve, dirname, join } from 'node:path';
|
|
25
|
+
import { homedir, tmpdir, networkInterfaces } from 'node:os';
|
|
26
|
+
import { fileURLToPath } from 'node:url';
|
|
27
|
+
import { createInterface } from 'node:readline';
|
|
28
|
+
import { pipeline } from 'node:stream/promises';
|
|
29
|
+
import { execSync } from 'node:child_process';
|
|
30
|
+
import { randomBytes, createHash } from 'node:crypto';
|
|
31
|
+
import { createConnection } from 'node:net';
|
|
32
|
+
|
|
33
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
34
|
+
const ROOT = resolve(__dirname, '..');
|
|
35
|
+
const MINDOS_DIR = resolve(homedir(), '.mindos');
|
|
36
|
+
const CONFIG_PATH = resolve(MINDOS_DIR, 'config.json');
|
|
37
|
+
|
|
38
|
+
// ── i18n ─────────────────────────────────────────────────────────────────────
|
|
39
|
+
|
|
40
|
+
const T = {
|
|
41
|
+
title: { en: '🧠 MindOS Setup', zh: '🧠 MindOS 初始化' },
|
|
42
|
+
langHint: { en: ' ← → switch language / 切换语言 ↑ ↓ navigate Enter confirm', zh: ' ← → switch language / 切换语言 ↑ ↓ 上下切换 Enter 确认' },
|
|
43
|
+
|
|
44
|
+
// step labels
|
|
45
|
+
step: { en: (n, total) => `Step ${n}/${total}`, zh: (n, total) => `步骤 ${n}/${total}` },
|
|
46
|
+
stepTitles: {
|
|
47
|
+
en: ['Knowledge Base', 'Template', 'Ports', 'Auth Token', 'Web Password', 'AI Provider'],
|
|
48
|
+
zh: ['知识库', '模板', '端口', 'Auth Token', 'Web 密码', 'AI 服务商'],
|
|
49
|
+
},
|
|
50
|
+
|
|
51
|
+
// path
|
|
52
|
+
pathPrompt: { en: 'Folder name or absolute path', zh: '文件夹名或绝对路径' },
|
|
53
|
+
pathHintInline: { en: (base) => `stored under ${base + '/'}`, zh: (base) => `存储在 ${base + '/'}` },
|
|
54
|
+
pathResolved: { en: (p) => ` → ${c.dim(p)}`, zh: (p) => ` → ${c.dim(p)}` },
|
|
55
|
+
|
|
56
|
+
// existing kb
|
|
57
|
+
kbExists: { en: (p) => ` ${c.yellow('⚠')} Directory already exists: ${c.dim(p)}`, zh: (p) => ` ${c.yellow('⚠')} 目录已存在:${c.dim(p)}` },
|
|
58
|
+
kbExistsFiles: { en: 'Contents', zh: '目录内容' },
|
|
59
|
+
kbExistsOpts: { en: ['Use this directory', 'Choose a different path'], zh: ['使用此目录', '重新选择路径'] },
|
|
60
|
+
kbExistsVals: ['use', 'reselect'],
|
|
61
|
+
kbCreated: { en: '✔ Knowledge base initialized', zh: '✔ 知识库已初始化' },
|
|
62
|
+
|
|
63
|
+
// template
|
|
64
|
+
tplPrompt: { en: 'Template', zh: '模板' },
|
|
65
|
+
tplOptions: { en: ['en — English template', 'zh — Chinese template', 'empty — blank files only', 'custom — local path or URL'], zh: ['en — 英文模板', 'zh — 中文模板', 'empty — 仅基础文件', 'custom — 本地路径或 URL'] },
|
|
66
|
+
tplValues: ['en', 'zh', 'empty', 'custom'],
|
|
67
|
+
tplNotFound: { en: '✘ Template not found', zh: '✘ 模板目录不存在' },
|
|
68
|
+
customPrompt: { en: 'Path or URL', zh: '路径或 URL' },
|
|
69
|
+
customEmpty: { en: ' Path cannot be empty, please try again', zh: ' 路径不能为空,请重新输入' },
|
|
70
|
+
downloading: { en: '⏳ Downloading template...', zh: '⏳ 正在下载模板...' },
|
|
71
|
+
dlDone: { en: '✔ Template downloaded', zh: '✔ 模板下载完成' },
|
|
72
|
+
|
|
73
|
+
// ports
|
|
74
|
+
webPortPrompt: { en: 'Web UI port', zh: 'Web UI 端口' },
|
|
75
|
+
mcpPortPrompt: { en: 'MCP server port', zh: 'MCP 服务端口' },
|
|
76
|
+
portInUse: { en: (p) => ` ⚠ Port ${p} is already in use, choose another`, zh: (p) => ` ⚠ 端口 ${p} 已被占用,请换一个` },
|
|
77
|
+
portInvalid: { en: (p) => ` ⚠ Invalid port "${p}", must be 1024–65535`, zh: (p) => ` ⚠ 端口 "${p}" 无效,需在 1024–65535 之间` },
|
|
78
|
+
|
|
79
|
+
// auth
|
|
80
|
+
authPrompt: { en: 'Auth token seed (Enter to auto-generate)', zh: 'Auth token 种子(回车自动生成)' },
|
|
81
|
+
tokenGenerated: { en: '✔ Auth token', zh: '✔ Auth token' },
|
|
82
|
+
|
|
83
|
+
// web password
|
|
84
|
+
webPassPrompt: { en: 'Web UI password (leave empty = no password protection)', zh: 'Web UI 访问密码(留空 = 不设密码)' },
|
|
85
|
+
webPassWarn: { en: ' ⚠ No Web UI password — anyone on the network can access the UI', zh: ' ⚠ 未设置 Web UI 密码,局域网内任何人均可访问' },
|
|
86
|
+
webPassSkip: { en: 'Skip password protection anyway?', zh: '确认不设密码继续?' },
|
|
87
|
+
|
|
88
|
+
// provider
|
|
89
|
+
providerPrompt: { en: 'AI Provider', zh: 'AI 服务商' },
|
|
90
|
+
providerOpts: { en: ['anthropic', 'openai', 'skip — configure later via `mindos config set`'], zh: ['anthropic', 'openai', 'skip — 稍后通过 `mindos config set` 配置'] },
|
|
91
|
+
providerVals: ['anthropic', 'openai', 'skip'],
|
|
92
|
+
providerSkip: { en: ' → AI provider skipped. Run `mindos config set ai.provider <anthropic|openai>` later.', zh: ' → 已跳过 AI 配置,稍后运行 `mindos config set ai.provider <anthropic|openai>` 补填。' },
|
|
93
|
+
anthropicKey: { en: 'Anthropic API Key (sk-ant-...)', zh: 'Anthropic API Key (sk-ant-...)' },
|
|
94
|
+
openaiKey: { en: 'OpenAI API Key (sk-...)', zh: 'OpenAI API Key (sk-...)' },
|
|
95
|
+
openaiBase: { en: 'OpenAI Base URL (leave empty for default)', zh: 'OpenAI Base URL(留空使用默认)' },
|
|
96
|
+
apiKeyWarn: { en: ' ⚠ No API key entered — AI features will not work until you add one to ~/.mindos/config.json', zh: ' ⚠ 未填写 API Key,AI 功能将无法使用,可后续在 ~/.mindos/config.json 中补填' },
|
|
97
|
+
|
|
98
|
+
// config
|
|
99
|
+
cfgExists: { en: (p) => `${p} already exists. Overwrite?`, zh: (p) => `${p} 已存在,是否覆盖?` },
|
|
100
|
+
cfgKept: { en: '✔ Keeping existing config', zh: '✔ 保留现有配置' },
|
|
101
|
+
cfgKeptNote: { en: ' Settings from this session were not saved', zh: ' 本次填写的设置未保存' },
|
|
102
|
+
cfgSaved: { en: '✔ Config saved', zh: '✔ 配置已保存' },
|
|
103
|
+
yesNo: { en: '[y/N]', zh: '[y/N]' },
|
|
104
|
+
yesNoDefault: { en: '[Y/n]', zh: '[Y/n]' },
|
|
105
|
+
startNow: { en: 'Start MindOS now?', zh: '现在启动 MindOS?' },
|
|
106
|
+
|
|
107
|
+
// next steps (onboard — keep it minimal, details shown on `mindos start`)
|
|
108
|
+
nextSteps: {
|
|
109
|
+
en: (cmd) => [
|
|
110
|
+
'─────────────────────────────────────────────',
|
|
111
|
+
'🚀 Setup complete! Start MindOS:\n',
|
|
112
|
+
` ${c.cyan(cmd)}`,
|
|
113
|
+
` ${c.dim('MCP config, auth token, and skills info will be shown on startup.')}\n`,
|
|
114
|
+
'─────────────────────────────────────────────',
|
|
115
|
+
],
|
|
116
|
+
zh: (cmd) => [
|
|
117
|
+
'─────────────────────────────────────────────',
|
|
118
|
+
'🚀 初始化完成!启动 MindOS:\n',
|
|
119
|
+
` ${c.cyan(cmd)}`,
|
|
120
|
+
` ${c.dim('MCP 配置、Auth token、Skills 信息将在启动后显示。')}\n`,
|
|
121
|
+
'─────────────────────────────────────────────',
|
|
122
|
+
],
|
|
123
|
+
},
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
// ── Terminal helpers ──────────────────────────────────────────────────────────
|
|
127
|
+
|
|
128
|
+
const ESC = '\x1b';
|
|
129
|
+
const CLEAR_LINE = '\r\x1b[K';
|
|
130
|
+
const CURSOR_UP = (n) => n > 0 ? `\x1b[${n}A` : '';
|
|
131
|
+
const HIDE_CURSOR = '\x1b[?25l';
|
|
132
|
+
const SHOW_CURSOR = '\x1b[?25h';
|
|
133
|
+
|
|
134
|
+
const c = process.stdout.isTTY ? {
|
|
135
|
+
bold: (s) => `\x1b[1m${s}\x1b[0m`,
|
|
136
|
+
dim: (s) => `\x1b[2m${s}\x1b[0m`,
|
|
137
|
+
cyan: (s) => `\x1b[36m${s}\x1b[0m`,
|
|
138
|
+
green: (s) => `\x1b[32m${s}\x1b[0m`,
|
|
139
|
+
red: (s) => `\x1b[31m${s}\x1b[0m`,
|
|
140
|
+
yellow: (s) => `\x1b[33m${s}\x1b[0m`,
|
|
141
|
+
} : { bold: s=>s, dim: s=>s, cyan: s=>s, green: s=>s, red: s=>s, yellow: s=>s };
|
|
142
|
+
|
|
143
|
+
function write(s) { process.stdout.write(s); }
|
|
144
|
+
|
|
145
|
+
// ── State ─────────────────────────────────────────────────────────────────────
|
|
146
|
+
|
|
147
|
+
let uiLang = 'en';
|
|
148
|
+
const t = (key) => T[key]?.[uiLang] ?? T[key]?.en ?? '';
|
|
149
|
+
const tf = (key, ...args) => {
|
|
150
|
+
const v = T[key]?.[uiLang] ?? T[key]?.en;
|
|
151
|
+
return typeof v === 'function' ? v(...args) : v ?? '';
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
// ── Step header ───────────────────────────────────────────────────────────────
|
|
155
|
+
|
|
156
|
+
const TOTAL_STEPS = 6;
|
|
157
|
+
function stepHeader(n) {
|
|
158
|
+
const title = T.stepTitles[uiLang][n - 1] ?? T.stepTitles.en[n - 1];
|
|
159
|
+
const stepLabel = tf('step', n, TOTAL_STEPS);
|
|
160
|
+
console.log(`\n${c.bold(title)} ${c.dim(stepLabel)}`);
|
|
161
|
+
console.log(c.dim('─'.repeat(44)));
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// ── Raw-mode key reader ───────────────────────────────────────────────────────
|
|
165
|
+
|
|
166
|
+
function readKey() {
|
|
167
|
+
return new Promise((resolve) => {
|
|
168
|
+
const { stdin } = process;
|
|
169
|
+
if (!stdin.isTTY) return resolve(null);
|
|
170
|
+
stdin.setRawMode(true);
|
|
171
|
+
stdin.resume();
|
|
172
|
+
stdin.setEncoding('utf8');
|
|
173
|
+
const onData = (chunk) => {
|
|
174
|
+
stdin.setRawMode(false);
|
|
175
|
+
stdin.pause();
|
|
176
|
+
stdin.removeListener('data', onData);
|
|
177
|
+
resolve(chunk);
|
|
178
|
+
};
|
|
179
|
+
stdin.on('data', onData);
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// ── Select prompt ─────────────────────────────────────────────────────────────
|
|
184
|
+
|
|
185
|
+
async function select(labelKey, optionsKey, valuesKey = null) {
|
|
186
|
+
let idx = 0;
|
|
187
|
+
let lastLineCount = 0;
|
|
188
|
+
|
|
189
|
+
const render = (first = false) => {
|
|
190
|
+
const opts = T[optionsKey][uiLang];
|
|
191
|
+
const lines = [
|
|
192
|
+
`${c.bold(t(labelKey) + ':')}`,
|
|
193
|
+
...opts.map((o, i) => {
|
|
194
|
+
const [val, ...rest] = o.split(' — ');
|
|
195
|
+
const desc = rest.join(' — ');
|
|
196
|
+
const item = desc ? `${c.cyan(val)} ${c.dim('—')} ${c.dim(desc)}` : c.cyan(val);
|
|
197
|
+
return i === idx
|
|
198
|
+
? ` ${c.cyan('❯')} ${item}`
|
|
199
|
+
: ` ${c.dim(val)}${desc ? c.dim(' — ' + desc) : ''}`;
|
|
200
|
+
}),
|
|
201
|
+
];
|
|
202
|
+
if (!first && lastLineCount > 0) {
|
|
203
|
+
write(`${CURSOR_UP(lastLineCount)}\r\x1b[J`);
|
|
204
|
+
}
|
|
205
|
+
lastLineCount = lines.length;
|
|
206
|
+
write(lines.join('\n') + '\n');
|
|
207
|
+
};
|
|
208
|
+
|
|
209
|
+
write(HIDE_CURSOR);
|
|
210
|
+
render(true);
|
|
211
|
+
|
|
212
|
+
while (true) {
|
|
213
|
+
const key = await readKey();
|
|
214
|
+
if (key === null) {
|
|
215
|
+
write(SHOW_CURSOR);
|
|
216
|
+
return (valuesKey ? T[valuesKey] : T[optionsKey][uiLang])[0];
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
const opts = T[optionsKey][uiLang];
|
|
220
|
+
|
|
221
|
+
if (key === '\r' || key === '\n') {
|
|
222
|
+
write(SHOW_CURSOR);
|
|
223
|
+
write(`${CURSOR_UP(lastLineCount)}\r\x1b[J`);
|
|
224
|
+
const displayLabel = opts[idx].split(' — ')[0];
|
|
225
|
+
write(`${c.bold(t(labelKey) + ':')} ${c.cyan(displayLabel)}\n`);
|
|
226
|
+
return valuesKey ? T[valuesKey][idx] : opts[idx];
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
if (key === `${ESC}[A`) { idx = (idx - 1 + opts.length) % opts.length; render(); }
|
|
230
|
+
else if (key === `${ESC}[B`) { idx = (idx + 1) % opts.length; render(); }
|
|
231
|
+
else if (key === `${ESC}[C` || key === `${ESC}[D`) {
|
|
232
|
+
uiLang = uiLang === 'en' ? 'zh' : 'en';
|
|
233
|
+
idx = Math.min(idx, T[optionsKey][uiLang].length - 1);
|
|
234
|
+
render();
|
|
235
|
+
}
|
|
236
|
+
else if (key === '\x03') { write(SHOW_CURSOR); process.exit(1); }
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// ── Text input prompt ─────────────────────────────────────────────────────────
|
|
241
|
+
|
|
242
|
+
async function askText(labelKey, defaultValue = '', hintKey = '', ...hintArgs) {
|
|
243
|
+
const buildPrompt = () => {
|
|
244
|
+
const label = c.bold(t(labelKey));
|
|
245
|
+
const def = defaultValue ? ` ${c.dim('[' + defaultValue + ']')}` : '';
|
|
246
|
+
const hintStr = hintKey ? tf(hintKey, ...hintArgs) : '';
|
|
247
|
+
const h = hintStr ? ` ${c.dim('← ' + hintStr)}` : '';
|
|
248
|
+
return `${label}${def}${h}: `;
|
|
249
|
+
};
|
|
250
|
+
|
|
251
|
+
if (!process.stdin.isTTY) {
|
|
252
|
+
return new Promise((resolve) => {
|
|
253
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
254
|
+
rl.question(buildPrompt(), (ans) => { rl.close(); resolve(ans.trim() || defaultValue); });
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
const render = (buf, first = false) => {
|
|
259
|
+
if (!first) write(`\r\x1b[K`);
|
|
260
|
+
write(`${buildPrompt()}${c.cyan(buf)}`);
|
|
261
|
+
};
|
|
262
|
+
|
|
263
|
+
write(HIDE_CURSOR);
|
|
264
|
+
let buf = '';
|
|
265
|
+
render(buf, true);
|
|
266
|
+
write(SHOW_CURSOR);
|
|
267
|
+
|
|
268
|
+
process.stdin.setRawMode(true);
|
|
269
|
+
process.stdin.resume();
|
|
270
|
+
process.stdin.setEncoding('utf8');
|
|
271
|
+
|
|
272
|
+
return new Promise((resolve) => {
|
|
273
|
+
const onData = (chunk) => {
|
|
274
|
+
if (chunk === '\x03') { write('\n'); process.exit(1); }
|
|
275
|
+
|
|
276
|
+
if (chunk === '\r' || chunk === '\n') {
|
|
277
|
+
process.stdin.setRawMode(false);
|
|
278
|
+
process.stdin.pause();
|
|
279
|
+
process.stdin.removeListener('data', onData);
|
|
280
|
+
write('\n');
|
|
281
|
+
resolve(buf || defaultValue);
|
|
282
|
+
return;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
if (chunk === `${ESC}[C` || chunk === `${ESC}[D`) {
|
|
286
|
+
uiLang = uiLang === 'en' ? 'zh' : 'en';
|
|
287
|
+
render(buf);
|
|
288
|
+
return;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
if (chunk === '\x7f' || chunk === '\b') {
|
|
292
|
+
if (buf.length > 0) { buf = buf.slice(0, -1); render(buf); }
|
|
293
|
+
return;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
if (chunk.startsWith(ESC)) return;
|
|
297
|
+
buf += chunk;
|
|
298
|
+
render(buf);
|
|
299
|
+
};
|
|
300
|
+
process.stdin.on('data', onData);
|
|
301
|
+
});
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
// ── Yes/No prompt ─────────────────────────────────────────────────────────────
|
|
305
|
+
|
|
306
|
+
async function askYesNo(labelKey, arg = '', defaultYes = false) {
|
|
307
|
+
const label = typeof T[labelKey][uiLang] === 'function' ? T[labelKey][uiLang](arg) : t(labelKey);
|
|
308
|
+
const hintKey = defaultYes ? 'yesNoDefault' : 'yesNo';
|
|
309
|
+
if (!process.stdin.isTTY) {
|
|
310
|
+
return new Promise((resolve) => {
|
|
311
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
312
|
+
rl.question(`${c.bold(label)} ${c.dim(t(hintKey))}: `, (ans) => {
|
|
313
|
+
const v = ans.trim().toLowerCase();
|
|
314
|
+
rl.close(); resolve(defaultYes ? v !== 'n' : v === 'y');
|
|
315
|
+
});
|
|
316
|
+
});
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
write(HIDE_CURSOR);
|
|
320
|
+
const render = (buf = '', first = false) => {
|
|
321
|
+
if (!first) write(`\r\x1b[K`);
|
|
322
|
+
write(`${c.bold(label)} ${c.dim(t(hintKey))}: ${c.cyan(buf)}`);
|
|
323
|
+
};
|
|
324
|
+
render('', true);
|
|
325
|
+
write(SHOW_CURSOR);
|
|
326
|
+
|
|
327
|
+
process.stdin.setRawMode(true);
|
|
328
|
+
process.stdin.resume();
|
|
329
|
+
process.stdin.setEncoding('utf8');
|
|
330
|
+
|
|
331
|
+
return new Promise((resolve) => {
|
|
332
|
+
let buf = '';
|
|
333
|
+
const onData = (chunk) => {
|
|
334
|
+
if (chunk === '\x03') { write('\n'); process.exit(1); }
|
|
335
|
+
|
|
336
|
+
if (chunk === '\r' || chunk === '\n') {
|
|
337
|
+
process.stdin.setRawMode(false);
|
|
338
|
+
process.stdin.pause();
|
|
339
|
+
process.stdin.removeListener('data', onData);
|
|
340
|
+
write('\n');
|
|
341
|
+
const v = buf.toLowerCase();
|
|
342
|
+
resolve(defaultYes ? v !== 'n' : v === 'y');
|
|
343
|
+
return;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
if (chunk === `${ESC}[C` || chunk === `${ESC}[D`) {
|
|
347
|
+
uiLang = uiLang === 'en' ? 'zh' : 'en';
|
|
348
|
+
render(buf);
|
|
349
|
+
return;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
if (chunk === '\x7f' || chunk === '\b') {
|
|
353
|
+
if (buf.length > 0) { buf = buf.slice(0, -1); render(buf); }
|
|
354
|
+
return;
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
if (chunk.startsWith(ESC)) return;
|
|
358
|
+
buf += chunk;
|
|
359
|
+
render(buf);
|
|
360
|
+
};
|
|
361
|
+
process.stdin.on('data', onData);
|
|
362
|
+
});
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
const askYesNoDefault = (labelKey, arg = '') => askYesNo(labelKey, arg, true);
|
|
366
|
+
|
|
367
|
+
// ── Port helpers ──────────────────────────────────────────────────────────────
|
|
368
|
+
|
|
369
|
+
function isPortInUse(port) {
|
|
370
|
+
return new Promise((resolve) => {
|
|
371
|
+
const sock = createConnection({ port, host: '127.0.0.1' });
|
|
372
|
+
sock.once('connect', () => { sock.destroy(); resolve(true); });
|
|
373
|
+
sock.once('error', () => { sock.destroy(); resolve(false); });
|
|
374
|
+
});
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
async function findFreePort(from) {
|
|
378
|
+
let p = from;
|
|
379
|
+
while (p <= 65535 && await isPortInUse(p)) p++;
|
|
380
|
+
return p;
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
async function askPort(labelKey, defaultPort) {
|
|
384
|
+
let port = await findFreePort(defaultPort);
|
|
385
|
+
while (true) {
|
|
386
|
+
const input = (await askText(labelKey, String(port))).trim();
|
|
387
|
+
const parsed = parseInt(input, 10);
|
|
388
|
+
if (!parsed || parsed < 1024 || parsed > 65535) {
|
|
389
|
+
write(c.yellow(tf('portInvalid', input) + '\n'));
|
|
390
|
+
continue;
|
|
391
|
+
}
|
|
392
|
+
if (await isPortInUse(parsed)) {
|
|
393
|
+
const next = await findFreePort(parsed + 1);
|
|
394
|
+
write(c.yellow(tf('portInUse', parsed) + '\n'));
|
|
395
|
+
port = next;
|
|
396
|
+
continue;
|
|
397
|
+
}
|
|
398
|
+
return parsed;
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
// ── Token generation ──────────────────────────────────────────────────────────
|
|
403
|
+
|
|
404
|
+
function generateToken(passphrase = '') {
|
|
405
|
+
let bytes;
|
|
406
|
+
if (passphrase) {
|
|
407
|
+
const salt = randomBytes(16).toString('hex');
|
|
408
|
+
bytes = createHash('sha256').update(passphrase + salt).digest();
|
|
409
|
+
} else {
|
|
410
|
+
bytes = randomBytes(24);
|
|
411
|
+
}
|
|
412
|
+
const hex = bytes.toString('hex').slice(0, 24);
|
|
413
|
+
return hex.match(/.{4}/g).join('-');
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
// ── Template handler ──────────────────────────────────────────────────────────
|
|
417
|
+
|
|
418
|
+
function parseGithubDir(url) {
|
|
419
|
+
const m = url.match(/github\.com\/([^/]+)\/([^/]+)\/tree\/([^/]+)\/?(.*)$/);
|
|
420
|
+
if (!m) return null;
|
|
421
|
+
const [, owner, repo, ref, subdir] = m;
|
|
422
|
+
return { tarball: `https://api.github.com/repos/${owner}/${repo}/tarball/${ref}`, subdir: subdir || '' };
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
async function downloadAndExtract(url, destDir) {
|
|
426
|
+
const tmp = join(tmpdir(), `mindos-tpl-${Date.now()}`);
|
|
427
|
+
mkdirSync(tmp, { recursive: true });
|
|
428
|
+
const tarPath = join(tmp, 'tpl.tar.gz');
|
|
429
|
+
|
|
430
|
+
let fetchUrl = url;
|
|
431
|
+
let subdir = '';
|
|
432
|
+
const gh = parseGithubDir(url);
|
|
433
|
+
if (gh) { fetchUrl = gh.tarball; subdir = gh.subdir; }
|
|
434
|
+
|
|
435
|
+
const res = await fetch(fetchUrl, { headers: { 'User-Agent': 'mindos-init' }, redirect: 'follow' });
|
|
436
|
+
if (!res.ok) throw new Error(`HTTP ${res.status} ${res.statusText}`);
|
|
437
|
+
await pipeline(res.body, createWriteStream(tarPath));
|
|
438
|
+
|
|
439
|
+
const extractDir = join(tmp, 'extracted');
|
|
440
|
+
mkdirSync(extractDir, { recursive: true });
|
|
441
|
+
execSync(`tar -xzf "${tarPath}" -C "${extractDir}"`);
|
|
442
|
+
|
|
443
|
+
const { readdirSync, statSync } = await import('node:fs');
|
|
444
|
+
let contentRoot = extractDir;
|
|
445
|
+
const entries = readdirSync(extractDir);
|
|
446
|
+
if (entries.length === 1 && statSync(join(extractDir, entries[0])).isDirectory()) {
|
|
447
|
+
contentRoot = join(extractDir, entries[0]);
|
|
448
|
+
}
|
|
449
|
+
if (subdir) contentRoot = join(contentRoot, subdir);
|
|
450
|
+
|
|
451
|
+
cpSync(contentRoot, destDir, { recursive: true, filter: (src) => !src.endsWith('.gitkeep') });
|
|
452
|
+
rmSync(tmp, { recursive: true, force: true });
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
async function applyTemplate(tpl, mindDir) {
|
|
456
|
+
if (tpl === 'custom') {
|
|
457
|
+
let source = '';
|
|
458
|
+
while (!source) {
|
|
459
|
+
source = (await askText('customPrompt')).trim();
|
|
460
|
+
if (!source) write(c.yellow(t('customEmpty') + '\n'));
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
const isUrl = source.startsWith('http://') || source.startsWith('https://');
|
|
464
|
+
if (isUrl) {
|
|
465
|
+
write(c.yellow(t('downloading') + '\n'));
|
|
466
|
+
await downloadAndExtract(source, mindDir);
|
|
467
|
+
console.log(`${c.green(t('dlDone'))}: ${c.dim(mindDir)}`);
|
|
468
|
+
} else {
|
|
469
|
+
const localPath = resolve(source);
|
|
470
|
+
if (!existsSync(localPath)) {
|
|
471
|
+
console.error(c.red(`${t('tplNotFound')}: ${localPath}`));
|
|
472
|
+
process.exit(1);
|
|
473
|
+
}
|
|
474
|
+
cpSync(localPath, mindDir, { recursive: true, filter: (src) => !src.endsWith('.gitkeep') });
|
|
475
|
+
console.log(`${c.green(t('kbCreated'))}: ${c.dim(mindDir)}`);
|
|
476
|
+
}
|
|
477
|
+
} else {
|
|
478
|
+
const tplDir = resolve(ROOT, 'templates', tpl);
|
|
479
|
+
if (!existsSync(tplDir)) {
|
|
480
|
+
console.error(c.red(`${t('tplNotFound')}: ${tplDir}`));
|
|
481
|
+
process.exit(1);
|
|
482
|
+
}
|
|
483
|
+
cpSync(tplDir, mindDir, { recursive: true, filter: (src) => !src.endsWith('.gitkeep') });
|
|
484
|
+
console.log(`${c.green(t('kbCreated'))}: ${c.dim(mindDir)}`);
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
// ── Main ──────────────────────────────────────────────────────────────────────
|
|
489
|
+
|
|
490
|
+
async function main() {
|
|
491
|
+
console.log(`\n${c.bold(t('title'))}\n\n${c.dim(t('langHint'))}\n`);
|
|
492
|
+
|
|
493
|
+
// ── Step 1: Knowledge base path ───────────────────────────────────────────
|
|
494
|
+
stepHeader(1);
|
|
495
|
+
|
|
496
|
+
const { readdirSync } = await import('node:fs');
|
|
497
|
+
let mindDir;
|
|
498
|
+
|
|
499
|
+
while (true) {
|
|
500
|
+
const input = (await askText('pathPrompt', 'my-mind', 'pathHintInline', MINDOS_DIR)).trim();
|
|
501
|
+
const resolved = input.startsWith('/') ? input : resolve(MINDOS_DIR, input);
|
|
502
|
+
write(tf('pathResolved', resolved) + '\n');
|
|
503
|
+
mindDir = resolved;
|
|
504
|
+
|
|
505
|
+
if (existsSync(mindDir)) {
|
|
506
|
+
// show contents
|
|
507
|
+
let entries = [];
|
|
508
|
+
try { entries = readdirSync(mindDir).filter(e => !e.startsWith('.')); } catch {}
|
|
509
|
+
write('\n');
|
|
510
|
+
write(tf('kbExists', mindDir) + '\n');
|
|
511
|
+
if (entries.length) {
|
|
512
|
+
const label = T.kbExistsFiles[uiLang] ?? T.kbExistsFiles.en;
|
|
513
|
+
write(` ${c.dim(label + ':')} ${entries.slice(0, 8).map(e => c.dim(e)).join(' ')}${entries.length > 8 ? c.dim(' …') : ''}\n`);
|
|
514
|
+
} else {
|
|
515
|
+
write(` ${c.dim('(empty)')}\n`);
|
|
516
|
+
}
|
|
517
|
+
write('\n');
|
|
518
|
+
|
|
519
|
+
const choice = await select('kbExistsFiles', 'kbExistsOpts', 'kbExistsVals');
|
|
520
|
+
if (choice === 'reselect') { write('\n'); continue; }
|
|
521
|
+
break;
|
|
522
|
+
} else {
|
|
523
|
+
// ── Step 2: Template ────────────────────────────────────────────────
|
|
524
|
+
write('\n');
|
|
525
|
+
stepHeader(2);
|
|
526
|
+
const tpl = await select('tplPrompt', 'tplOptions', 'tplValues');
|
|
527
|
+
mkdirSync(mindDir, { recursive: true });
|
|
528
|
+
await applyTemplate(tpl, mindDir);
|
|
529
|
+
break;
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
// ── Step 3: Ports ─────────────────────────────────────────────────────────
|
|
534
|
+
write('\n');
|
|
535
|
+
stepHeader(3);
|
|
536
|
+
const webPort = await askPort('webPortPrompt', 3000);
|
|
537
|
+
const mcpPort = await askPort('mcpPortPrompt', 8787);
|
|
538
|
+
|
|
539
|
+
// ── Step 4: Auth token ────────────────────────────────────────────────────
|
|
540
|
+
write('\n');
|
|
541
|
+
stepHeader(4);
|
|
542
|
+
const authSeed = await askText('authPrompt');
|
|
543
|
+
const authToken = generateToken(authSeed);
|
|
544
|
+
console.log(`${c.green(t('tokenGenerated'))}: ${c.cyan(authToken)}`);
|
|
545
|
+
|
|
546
|
+
// ── Step 5: Web UI password ───────────────────────────────────────────────
|
|
547
|
+
write('\n');
|
|
548
|
+
stepHeader(5);
|
|
549
|
+
let webPassword = '';
|
|
550
|
+
while (true) {
|
|
551
|
+
webPassword = await askText('webPassPrompt');
|
|
552
|
+
if (webPassword) break;
|
|
553
|
+
write(c.yellow(t('webPassWarn') + '\n'));
|
|
554
|
+
const confirmed = await askYesNo('webPassSkip');
|
|
555
|
+
if (confirmed) break;
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
// ── Step 6: AI Provider + API Key ─────────────────────────────────────────
|
|
559
|
+
write('\n');
|
|
560
|
+
stepHeader(6);
|
|
561
|
+
|
|
562
|
+
// if config exists, ask about overwrite first
|
|
563
|
+
if (existsSync(CONFIG_PATH)) {
|
|
564
|
+
const overwrite = await askYesNo('cfgExists', CONFIG_PATH);
|
|
565
|
+
if (!overwrite) {
|
|
566
|
+
let existingMode = 'start';
|
|
567
|
+
let existingMcpPort = 8787;
|
|
568
|
+
let existingAuth = '';
|
|
569
|
+
let existingMindRoot = mindDir;
|
|
570
|
+
try {
|
|
571
|
+
const existing = JSON.parse(readFileSync(CONFIG_PATH, 'utf-8'));
|
|
572
|
+
existingMode = existing.startMode || 'start';
|
|
573
|
+
existingMcpPort = existing.mcpPort || 8787;
|
|
574
|
+
existingAuth = existing.authToken || '';
|
|
575
|
+
existingMindRoot = existing.mindRoot || mindDir;
|
|
576
|
+
} catch { /* ignore */ }
|
|
577
|
+
console.log(`\n${c.green(t('cfgKept'))} ${c.dim(CONFIG_PATH)}`);
|
|
578
|
+
write(c.dim(t('cfgKeptNote') + '\n'));
|
|
579
|
+
finish(existingMindRoot, existingMode, existingMcpPort, existingAuth);
|
|
580
|
+
return;
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
const provider = await select('providerPrompt', 'providerOpts', 'providerVals');
|
|
585
|
+
const isSkip = provider === 'skip';
|
|
586
|
+
const isAnthropic = provider === 'anthropic';
|
|
587
|
+
|
|
588
|
+
// preserve existing provider configs
|
|
589
|
+
let existingProviders = {
|
|
590
|
+
anthropic: { apiKey: '', model: 'claude-sonnet-4-6' },
|
|
591
|
+
openai: { apiKey: '', model: 'gpt-5.4', baseUrl: '' },
|
|
592
|
+
};
|
|
593
|
+
let existingAiProvider = 'anthropic';
|
|
594
|
+
try {
|
|
595
|
+
const existing = JSON.parse(readFileSync(CONFIG_PATH, 'utf-8'));
|
|
596
|
+
if (existing.ai?.providers) {
|
|
597
|
+
existingProviders = { ...existingProviders, ...existing.ai.providers };
|
|
598
|
+
} else if (existing.ai?.anthropicApiKey) {
|
|
599
|
+
existingProviders.anthropic = { apiKey: existing.ai.anthropicApiKey || '', model: existing.ai.anthropicModel || 'claude-sonnet-4-6' };
|
|
600
|
+
existingProviders.openai = { apiKey: existing.ai.openaiApiKey || '', model: existing.ai.openaiModel || 'gpt-5.4', baseUrl: existing.ai.openaiBaseUrl || '' };
|
|
601
|
+
}
|
|
602
|
+
if (existing.ai?.provider) existingAiProvider = existing.ai.provider;
|
|
603
|
+
} catch { /* ignore */ }
|
|
604
|
+
|
|
605
|
+
if (isSkip) {
|
|
606
|
+
write(c.dim(t('providerSkip') + '\n'));
|
|
607
|
+
} else {
|
|
608
|
+
let apiKey = '';
|
|
609
|
+
let baseUrl = '';
|
|
610
|
+
if (isAnthropic) {
|
|
611
|
+
apiKey = await askText('anthropicKey');
|
|
612
|
+
} else {
|
|
613
|
+
apiKey = await askText('openaiKey');
|
|
614
|
+
baseUrl = await askText('openaiBase');
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
if (!apiKey) {
|
|
618
|
+
write(c.yellow(t('apiKeyWarn') + '\n'));
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
if (isAnthropic) {
|
|
622
|
+
existingProviders.anthropic = { apiKey, model: existingProviders.anthropic?.model || 'claude-sonnet-4-6' };
|
|
623
|
+
} else {
|
|
624
|
+
existingProviders.openai = { apiKey, model: existingProviders.openai?.model || 'gpt-5.4', baseUrl: baseUrl || '' };
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
const config = {
|
|
629
|
+
mindRoot: mindDir,
|
|
630
|
+
port: webPort,
|
|
631
|
+
mcpPort: mcpPort,
|
|
632
|
+
authToken: authToken,
|
|
633
|
+
webPassword: webPassword || '',
|
|
634
|
+
startMode: 'start',
|
|
635
|
+
ai: {
|
|
636
|
+
provider: isSkip ? existingAiProvider : (isAnthropic ? 'anthropic' : 'openai'),
|
|
637
|
+
providers: existingProviders,
|
|
638
|
+
},
|
|
639
|
+
};
|
|
640
|
+
|
|
641
|
+
mkdirSync(MINDOS_DIR, { recursive: true });
|
|
642
|
+
writeFileSync(CONFIG_PATH, JSON.stringify(config, null, 2) + '\n');
|
|
643
|
+
console.log(`\n${c.green(t('cfgSaved'))}: ${c.dim(CONFIG_PATH)}`);
|
|
644
|
+
|
|
645
|
+
finish(mindDir, config.startMode, config.mcpPort, config.authToken);
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
function getLocalIP() {
|
|
649
|
+
for (const ifaces of Object.values(networkInterfaces())) {
|
|
650
|
+
for (const iface of ifaces) {
|
|
651
|
+
if (iface.family === 'IPv4' && !iface.internal) return iface.address;
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
return null;
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
async function finish(mindDir, startMode = 'start', mcpPort = 8787, authToken = '') {
|
|
658
|
+
const startCmd = startMode === 'dev' ? 'mindos dev' : 'mindos start';
|
|
659
|
+
const lines = T.nextSteps[uiLang](startCmd);
|
|
660
|
+
console.log('');
|
|
661
|
+
lines.forEach((l) => console.log(l));
|
|
662
|
+
|
|
663
|
+
const doStart = await askYesNoDefault('startNow');
|
|
664
|
+
if (doStart) {
|
|
665
|
+
const { execSync } = await import('node:child_process');
|
|
666
|
+
const cliPath = resolve(__dirname, '../bin/cli.js');
|
|
667
|
+
execSync(`node "${cliPath}" ${startMode}`, { stdio: 'inherit' });
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
main().catch((err) => {
|
|
672
|
+
write(SHOW_CURSOR);
|
|
673
|
+
console.error(err);
|
|
674
|
+
process.exit(1);
|
|
675
|
+
});
|