@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
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { resolveSafe } from './security';
|
|
4
|
+
import { collectAllFiles } from './tree';
|
|
5
|
+
import { readFile } from './fs-ops';
|
|
6
|
+
import type { SearchResult, SearchOptions } from './types';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Core literal search — used by MCP tools via REST API.
|
|
10
|
+
*
|
|
11
|
+
* This is a **case-insensitive literal string match** with occurrence-density scoring.
|
|
12
|
+
* It supports scope, file_type, and modified_after filters that MCP tools expose.
|
|
13
|
+
*
|
|
14
|
+
* NOTE: The App also has a separate Fuse.js fuzzy search in `lib/fs.ts` for the
|
|
15
|
+
* browser `⌘K` search overlay. The two coexist intentionally:
|
|
16
|
+
* - Core search (here): exact literal match, supports filters, used by MCP/API
|
|
17
|
+
* - App search (lib/fs.ts): Fuse.js fuzzy match with CJK support, used by frontend
|
|
18
|
+
*/
|
|
19
|
+
export function searchFiles(mindRoot: string, query: string, opts: SearchOptions = {}): SearchResult[] {
|
|
20
|
+
if (!query.trim()) return [];
|
|
21
|
+
const { limit = 20, scope, file_type = 'all', modified_after } = opts;
|
|
22
|
+
|
|
23
|
+
let allFiles = collectAllFiles(mindRoot);
|
|
24
|
+
|
|
25
|
+
// Filter by scope (directory prefix)
|
|
26
|
+
if (scope) {
|
|
27
|
+
const normalizedScope = scope.endsWith('/') ? scope : scope + '/';
|
|
28
|
+
allFiles = allFiles.filter(f => f.startsWith(normalizedScope) || f === scope);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Filter by file type
|
|
32
|
+
if (file_type !== 'all') {
|
|
33
|
+
const ext = `.${file_type}`;
|
|
34
|
+
allFiles = allFiles.filter(f => f.endsWith(ext));
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Filter by modification time
|
|
38
|
+
let mtimeThreshold = 0;
|
|
39
|
+
if (modified_after) {
|
|
40
|
+
mtimeThreshold = new Date(modified_after).getTime();
|
|
41
|
+
if (isNaN(mtimeThreshold)) mtimeThreshold = 0;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const results: SearchResult[] = [];
|
|
45
|
+
const lowerQuery = query.toLowerCase();
|
|
46
|
+
const escapedQuery = lowerQuery.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
47
|
+
|
|
48
|
+
for (const filePath of allFiles) {
|
|
49
|
+
// Check mtime filter before reading content
|
|
50
|
+
if (mtimeThreshold > 0) {
|
|
51
|
+
try {
|
|
52
|
+
const abs = path.join(mindRoot, filePath);
|
|
53
|
+
const stat = fs.statSync(abs);
|
|
54
|
+
if (stat.mtimeMs < mtimeThreshold) continue;
|
|
55
|
+
} catch { continue; }
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
let content: string;
|
|
59
|
+
try { content = readFile(mindRoot, filePath); } catch { continue; }
|
|
60
|
+
|
|
61
|
+
const lowerContent = content.toLowerCase();
|
|
62
|
+
const index = lowerContent.indexOf(lowerQuery);
|
|
63
|
+
if (index === -1) continue;
|
|
64
|
+
|
|
65
|
+
const snippetStart = Math.max(0, index - 60);
|
|
66
|
+
const snippetEnd = Math.min(content.length, index + query.length + 60);
|
|
67
|
+
let snippet = content.slice(snippetStart, snippetEnd).replace(/\n/g, ' ').trim();
|
|
68
|
+
if (snippetStart > 0) snippet = '...' + snippet;
|
|
69
|
+
if (snippetEnd < content.length) snippet += '...';
|
|
70
|
+
|
|
71
|
+
const occurrences = (lowerContent.match(new RegExp(escapedQuery, 'g')) ?? []).length;
|
|
72
|
+
const score = occurrences / content.length;
|
|
73
|
+
|
|
74
|
+
results.push({ path: filePath, snippet, score, occurrences });
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
results.sort((a, b) => b.score - a.score);
|
|
78
|
+
return results.slice(0, limit);
|
|
79
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import path from 'path';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Asserts that a resolved path is within the given root.
|
|
5
|
+
*/
|
|
6
|
+
export function assertWithinRoot(resolved: string, root: string): void {
|
|
7
|
+
if (!resolved.startsWith(root + path.sep) && resolved !== root) {
|
|
8
|
+
throw new Error('Access denied: path outside MIND_ROOT');
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Resolves a relative file path against mindRoot and validates it is within bounds.
|
|
14
|
+
* Returns the resolved absolute path.
|
|
15
|
+
*/
|
|
16
|
+
export function resolveSafe(mindRoot: string, filePath: string): string {
|
|
17
|
+
const root = path.resolve(mindRoot);
|
|
18
|
+
const resolved = path.resolve(path.join(root, filePath));
|
|
19
|
+
assertWithinRoot(resolved, root);
|
|
20
|
+
return resolved;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const ROOT_PROTECTED_FILES = new Set(['INSTRUCTION.md']);
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Checks if a relative file path refers to a root-level protected file.
|
|
27
|
+
*/
|
|
28
|
+
export function isRootProtected(filePath: string): boolean {
|
|
29
|
+
const normalized = path.normalize(filePath);
|
|
30
|
+
return ROOT_PROTECTED_FILES.has(normalized);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Throws if the file is protected and cannot be modified via automated tools.
|
|
35
|
+
*/
|
|
36
|
+
export function assertNotProtected(filePath: string, operation: string): void {
|
|
37
|
+
if (isRootProtected(filePath)) {
|
|
38
|
+
throw new Error(
|
|
39
|
+
`Protected file: root "${filePath}" cannot be ${operation} via MCP. ` +
|
|
40
|
+
`This is a system kernel file (§7 of INSTRUCTION.md). Edit it manually or use a dedicated confirmation workflow.`
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import type { FileNode } from './types';
|
|
4
|
+
|
|
5
|
+
const DEFAULT_IGNORED_DIRS = new Set(['.git', 'node_modules', 'app', '.next', '.DS_Store', 'mcp']);
|
|
6
|
+
const DEFAULT_ALLOWED_EXTENSIONS = new Set(['.md', '.csv']);
|
|
7
|
+
|
|
8
|
+
export interface TreeOptions {
|
|
9
|
+
ignoredDirs?: Set<string>;
|
|
10
|
+
allowedExtensions?: Set<string>;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Builds a recursive file tree from dirPath.
|
|
15
|
+
* Only includes files with allowed extensions and non-ignored directories.
|
|
16
|
+
*/
|
|
17
|
+
export function getFileTree(
|
|
18
|
+
mindRoot: string,
|
|
19
|
+
dirPath?: string,
|
|
20
|
+
opts: TreeOptions = {}
|
|
21
|
+
): FileNode[] {
|
|
22
|
+
const root = path.resolve(mindRoot);
|
|
23
|
+
const dir = dirPath ?? root;
|
|
24
|
+
const ignoredDirs = opts.ignoredDirs ?? DEFAULT_IGNORED_DIRS;
|
|
25
|
+
const allowedExtensions = opts.allowedExtensions ?? DEFAULT_ALLOWED_EXTENSIONS;
|
|
26
|
+
|
|
27
|
+
let entries: fs.Dirent[];
|
|
28
|
+
try {
|
|
29
|
+
entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
30
|
+
} catch {
|
|
31
|
+
return [];
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const nodes: FileNode[] = [];
|
|
35
|
+
for (const entry of entries) {
|
|
36
|
+
const fullPath = path.join(dir, entry.name);
|
|
37
|
+
const relativePath = path.relative(root, fullPath);
|
|
38
|
+
|
|
39
|
+
if (entry.isDirectory()) {
|
|
40
|
+
if (ignoredDirs.has(entry.name)) continue;
|
|
41
|
+
const children = getFileTree(mindRoot, fullPath, opts);
|
|
42
|
+
if (children.length > 0) {
|
|
43
|
+
nodes.push({ name: entry.name, path: relativePath, type: 'directory', children });
|
|
44
|
+
}
|
|
45
|
+
} else if (entry.isFile()) {
|
|
46
|
+
const ext = path.extname(entry.name).toLowerCase();
|
|
47
|
+
if (allowedExtensions.has(ext)) {
|
|
48
|
+
nodes.push({ name: entry.name, path: relativePath, type: 'file', extension: ext });
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
nodes.sort((a, b) => {
|
|
54
|
+
if (a.type !== b.type) return a.type === 'directory' ? -1 : 1;
|
|
55
|
+
return a.name.localeCompare(b.name);
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
return nodes;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Collects all file paths (relative to mindRoot) with allowed extensions.
|
|
63
|
+
*/
|
|
64
|
+
export function collectAllFiles(
|
|
65
|
+
mindRoot: string,
|
|
66
|
+
dirPath?: string,
|
|
67
|
+
opts: TreeOptions = {}
|
|
68
|
+
): string[] {
|
|
69
|
+
const root = path.resolve(mindRoot);
|
|
70
|
+
const dir = dirPath ?? root;
|
|
71
|
+
const ignoredDirs = opts.ignoredDirs ?? DEFAULT_IGNORED_DIRS;
|
|
72
|
+
const allowedExtensions = opts.allowedExtensions ?? DEFAULT_ALLOWED_EXTENSIONS;
|
|
73
|
+
|
|
74
|
+
let entries: fs.Dirent[];
|
|
75
|
+
try {
|
|
76
|
+
entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
77
|
+
} catch {
|
|
78
|
+
return [];
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const files: string[] = [];
|
|
82
|
+
for (const entry of entries) {
|
|
83
|
+
const fullPath = path.join(dir, entry.name);
|
|
84
|
+
if (entry.isDirectory()) {
|
|
85
|
+
if (ignoredDirs.has(entry.name)) continue;
|
|
86
|
+
files.push(...collectAllFiles(mindRoot, fullPath, opts));
|
|
87
|
+
} else if (entry.isFile()) {
|
|
88
|
+
const ext = path.extname(entry.name).toLowerCase();
|
|
89
|
+
if (allowedExtensions.has(ext)) {
|
|
90
|
+
files.push(path.relative(root, fullPath));
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
return files;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Renders a file tree as an ASCII tree string.
|
|
99
|
+
*/
|
|
100
|
+
export function renderTree(nodes: FileNode[], indent = ''): string {
|
|
101
|
+
const lines: string[] = [];
|
|
102
|
+
for (let i = 0; i < nodes.length; i++) {
|
|
103
|
+
const node = nodes[i];
|
|
104
|
+
const isLast = i === nodes.length - 1;
|
|
105
|
+
const prefix = indent + (isLast ? '└── ' : '├── ');
|
|
106
|
+
const childIndent = indent + (isLast ? ' ' : '│ ');
|
|
107
|
+
lines.push(prefix + node.name + (node.type === 'directory' ? '/' : ''));
|
|
108
|
+
if (node.children?.length) {
|
|
109
|
+
lines.push(renderTree(node.children, childIndent));
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
return lines.join('\n');
|
|
113
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
export interface FileNode {
|
|
2
|
+
name: string;
|
|
3
|
+
path: string;
|
|
4
|
+
type: 'file' | 'directory';
|
|
5
|
+
children?: FileNode[];
|
|
6
|
+
extension?: string;
|
|
7
|
+
mtime?: number;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export interface SearchResult {
|
|
11
|
+
path: string;
|
|
12
|
+
snippet: string;
|
|
13
|
+
score: number;
|
|
14
|
+
occurrences: number;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface BacklinkEntry {
|
|
18
|
+
source: string;
|
|
19
|
+
line: number;
|
|
20
|
+
context: string;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface SearchOptions {
|
|
24
|
+
limit?: number;
|
|
25
|
+
scope?: string;
|
|
26
|
+
file_type?: 'md' | 'csv' | 'all';
|
|
27
|
+
modified_after?: string;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export interface GitLogEntry {
|
|
31
|
+
hash: string;
|
|
32
|
+
date: string;
|
|
33
|
+
message: string;
|
|
34
|
+
author: string;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface MoveResult {
|
|
38
|
+
newPath: string;
|
|
39
|
+
affectedFiles: string[];
|
|
40
|
+
}
|