@geminilight/mindos 0.6.22 → 0.6.25

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (62) hide show
  1. package/README.md +58 -46
  2. package/README_zh.md +58 -46
  3. package/app/app/.well-known/agent-card.json/route.ts +34 -0
  4. package/app/app/api/a2a/route.ts +100 -0
  5. package/app/app/api/file/import/route.ts +0 -2
  6. package/app/app/api/setup/route.ts +2 -0
  7. package/app/components/Backlinks.tsx +2 -2
  8. package/app/components/Breadcrumb.tsx +1 -1
  9. package/app/components/CsvView.tsx +41 -19
  10. package/app/components/DirView.tsx +2 -2
  11. package/app/components/FileTree.tsx +14 -1
  12. package/app/components/GuideCard.tsx +6 -2
  13. package/app/components/HomeContent.tsx +2 -2
  14. package/app/components/RightAskPanel.tsx +17 -10
  15. package/app/components/SearchModal.tsx +3 -3
  16. package/app/components/SidebarLayout.tsx +4 -2
  17. package/app/components/SyncStatusBar.tsx +2 -2
  18. package/app/components/ask/AskContent.tsx +6 -6
  19. package/app/components/ask/FileChip.tsx +1 -1
  20. package/app/components/ask/MentionPopover.tsx +2 -2
  21. package/app/components/ask/MessageList.tsx +1 -1
  22. package/app/components/ask/SlashCommandPopover.tsx +1 -1
  23. package/app/components/explore/UseCaseCard.tsx +2 -2
  24. package/app/components/help/HelpContent.tsx +6 -1
  25. package/app/components/panels/AgentsPanelAgentDetail.tsx +2 -2
  26. package/app/components/panels/DiscoverPanel.tsx +3 -3
  27. package/app/components/panels/PanelNavRow.tsx +2 -2
  28. package/app/components/panels/PluginsPanel.tsx +1 -1
  29. package/app/components/panels/SearchPanel.tsx +3 -3
  30. package/app/components/renderers/summary/SummaryRenderer.tsx +1 -1
  31. package/app/components/settings/AiTab.tsx +4 -4
  32. package/app/components/settings/KnowledgeTab.tsx +1 -1
  33. package/app/components/settings/McpTab.tsx +22 -4
  34. package/app/components/settings/UpdateTab.tsx +1 -1
  35. package/app/components/setup/index.tsx +9 -3
  36. package/app/components/walkthrough/WalkthroughProvider.tsx +2 -2
  37. package/app/hooks/useAskPanel.ts +7 -3
  38. package/app/hooks/useFileImport.ts +1 -1
  39. package/app/lib/a2a/agent-card.ts +107 -0
  40. package/app/lib/a2a/index.ts +23 -0
  41. package/app/lib/a2a/task-handler.ts +228 -0
  42. package/app/lib/a2a/types.ts +158 -0
  43. package/app/lib/agent/tools.ts +1 -1
  44. package/app/lib/core/fs-ops.ts +3 -2
  45. package/app/lib/fs.ts +28 -11
  46. package/app/lib/i18n-en.ts +2 -0
  47. package/app/lib/i18n-zh.ts +2 -0
  48. package/app/lib/settings.ts +1 -1
  49. package/bin/cli.js +48 -20
  50. package/bin/commands/agent.js +18 -0
  51. package/bin/commands/api.js +58 -0
  52. package/bin/commands/ask.js +101 -0
  53. package/bin/commands/file.js +286 -0
  54. package/bin/commands/search.js +51 -0
  55. package/bin/commands/space.js +167 -0
  56. package/bin/commands/status.js +69 -0
  57. package/bin/lib/command.js +156 -0
  58. package/mcp/dist/index.cjs +1 -1
  59. package/mcp/src/index.ts +1 -1
  60. package/package.json +1 -1
  61. package/skills/mindos/SKILL.md +2 -2
  62. package/skills/mindos-zh/SKILL.md +2 -2
package/bin/cli.js CHANGED
@@ -60,6 +60,16 @@ import { spawnMcp } from './lib/mcp-spawn.js';
60
60
  import { ensureMcpBundle } from './lib/mcp-build.js';
61
61
  import { mcpInstall } from './lib/mcp-install.js';
62
62
  import { initSync, startSyncDaemon, stopSyncDaemon, getSyncStatus, manualSync, listConflicts, setSyncEnabled } from './lib/sync.js';
63
+ import { parseArgs } from './lib/command.js';
64
+
65
+ // ── New modular commands ──────────────────────────────────────────────────────
66
+ import * as fileCmd from './commands/file.js';
67
+ import * as spaceCmd from './commands/space.js';
68
+ import * as askCmd from './commands/ask.js';
69
+ import * as statusCmd from './commands/status.js';
70
+ import * as apiCmd from './commands/api.js';
71
+ import * as agentCmd from './commands/agent.js';
72
+ import * as searchCmd from './commands/search.js';
63
73
 
64
74
  // ── Helpers ───────────────────────────────────────────────────────────────────
65
75
 
@@ -1298,6 +1308,15 @@ ${bold('Examples:')}
1298
1308
  }
1299
1309
  console.log();
1300
1310
  },
1311
+
1312
+ // ── New modular commands (knowledge operations) ──────────────────────────
1313
+ file: async () => { const p = parseArgs(process.argv.slice(3)); await fileCmd.run([p.command, ...p.args].filter(Boolean), p.flags); },
1314
+ space: async () => { const p = parseArgs(process.argv.slice(3)); await spaceCmd.run([p.command, ...p.args].filter(Boolean), p.flags); },
1315
+ ask: async () => { const p = parseArgs(process.argv.slice(3)); await askCmd.run([p.command, ...p.args].filter(Boolean), p.flags); },
1316
+ status: async () => { const p = parseArgs(process.argv.slice(3)); await statusCmd.run([p.command, ...p.args].filter(Boolean), p.flags); },
1317
+ api: async () => { const p = parseArgs(process.argv.slice(3)); await apiCmd.run([p.command, ...p.args].filter(Boolean), p.flags); },
1318
+ agent: async () => { const p = parseArgs(process.argv.slice(3)); await agentCmd.run([p.command, ...p.args].filter(Boolean), p.flags); },
1319
+ search: async () => { const p = parseArgs(process.argv.slice(3)); await searchCmd.run([p.command, ...p.args].filter(Boolean), p.flags); },
1301
1320
  };
1302
1321
 
1303
1322
  // ── Entry ─────────────────────────────────────────────────────────────────────
@@ -1308,40 +1327,49 @@ if (!resolvedCmd || !commands[resolvedCmd]) {
1308
1327
  const pkgVersion = (() => { try { return JSON.parse(readFileSync(resolve(ROOT, 'package.json'), 'utf-8')).version; } catch { return '?'; } })();
1309
1328
  const row = (c, d) => ` ${cyan(c.padEnd(36))}${dim(d)}`;
1310
1329
  console.log(`
1311
- ${bold('🧠 MindOS CLI')} ${dim(`v${pkgVersion}`)}
1330
+ ${bold('MindOS CLI')} ${dim(`v${pkgVersion}`)}
1312
1331
 
1313
1332
  ${bold('Core:')}
1314
1333
  ${row('mindos onboard', 'Interactive setup (aliases: init, setup)')}
1315
- ${row('mindos onboard --install-daemon', 'Setup + install & start as background OS service')}
1316
- ${row('mindos start', 'Start app + MCP server (production, auto-rebuilds if needed)')}
1317
- ${row('mindos start --daemon', 'Install + start as background OS service (survives terminal close)')}
1318
- ${row('mindos start --verbose', 'Start with verbose MCP logging')}
1319
- ${row('mindos dev', 'Start app + MCP server (dev mode)')}
1320
- ${row('mindos dev --turbopack', 'Start with Turbopack (faster HMR)')}
1321
- ${row('mindos stop', 'Stop running MindOS processes')}
1334
+ ${row('mindos start', 'Start app + MCP server (production)')}
1335
+ ${row('mindos start --daemon', 'Start as background OS service')}
1336
+ ${row('mindos dev', 'Start in dev mode')}
1337
+ ${row('mindos stop', 'Stop running processes')}
1322
1338
  ${row('mindos restart', 'Stop then start again')}
1323
- ${row('mindos build', 'Build the app for production')}
1324
- ${row('mindos open', 'Open Web UI in the default browser')}
1339
+ ${row('mindos build', 'Build for production')}
1340
+ ${row('mindos status', 'Show service status overview')}
1341
+ ${row('mindos open', 'Open Web UI in browser')}
1342
+
1343
+ ${bold('Knowledge:')}
1344
+ ${row('mindos file <sub>', 'File operations (list/read/create/delete/search)')}
1345
+ ${row('mindos space <sub>', 'Space management (list/create/info)')}
1346
+ ${row('mindos search "<query>"', 'Search knowledge base via API')}
1347
+ ${row('mindos ask "<question>"', 'Ask AI using your knowledge base')}
1348
+ ${row('mindos agent <sub>', 'AI Agent management (list/info)')}
1349
+ ${row('mindos api <METHOD> <path>', 'Raw API passthrough (for developers/agents)')}
1325
1350
 
1326
1351
  ${bold('MCP:')}
1327
1352
  ${row('mindos mcp', 'Start MCP server only')}
1328
- ${row('mindos mcp install [agent]', 'Install MindOS MCP config into Agent (claude-code/cursor/windsurf/…) [-g]')}
1329
- ${row('mindos token', 'Show current auth token and MCP config snippet')}
1353
+ ${row('mindos mcp install [agent]', 'Install MCP config into Agent')}
1354
+ ${row('mindos token', 'Show auth token and MCP config')}
1330
1355
 
1331
1356
  ${bold('Sync:')}
1332
1357
  ${row('mindos sync', 'Show sync status (init/now/conflicts/on/off)')}
1333
1358
 
1334
1359
  ${bold('Gateway (Background Service):')}
1335
- ${row('mindos gateway <subcommand>', 'Manage background service (install/uninstall/start/stop/status/logs)')}
1360
+ ${row('mindos gateway <sub>', 'Manage service (install/start/stop/status/logs)')}
1336
1361
 
1337
1362
  ${bold('Config & Diagnostics:')}
1338
- ${row('mindos config <subcommand>', 'View/update config (show/validate/set/unset)')}
1339
- ${row('mindos doctor', 'Health check (config, ports, build, daemon)')}
1340
- ${row('mindos init-skills', 'Create user-skill-rules.md for personalization')}
1341
- ${row('mindos update', 'Update MindOS to the latest version')}
1342
- ${row('mindos uninstall', 'Fully uninstall MindOS (stop, remove daemon, npm uninstall)')}
1343
- ${row('mindos logs', 'Tail service logs (~/.mindos/mindos.log)')}
1344
- ${row('mindos', 'Start using mode saved in ~/.mindos/config.json')}
1363
+ ${row('mindos config <sub>', 'View/update config (show/set/unset/validate)')}
1364
+ ${row('mindos doctor', 'Health check')}
1365
+ ${row('mindos update', 'Update to latest version')}
1366
+ ${row('mindos uninstall', 'Fully uninstall MindOS')}
1367
+ ${row('mindos logs', 'Tail service logs')}
1368
+
1369
+ ${bold('Global Flags:')}
1370
+ ${row('--json', 'Output in JSON (for AI agents)')}
1371
+ ${row('--help, -h', 'Show help')}
1372
+ ${row('--version, -v', 'Show version')}
1345
1373
  `);
1346
1374
  const isHelp = (cmd === '--help' || cmd === '-h');
1347
1375
  process.exit((cmd && !isHelp) ? 1 : 0);
@@ -0,0 +1,18 @@
1
+ import{bold,dim,cyan,green,red,yellow}from"../lib/colors.js";import{MCP_AGENTS,detectAgentPresence}from"../lib/mcp-agents.js";import{existsSync,readFileSync}from"node:fs";import{resolve}from"node:path";import{homedir}from"node:os";import{output,isJsonMode}from"../lib/command.js";function expandHome(p){return p.startsWith("~/")?resolve(homedir(),p.slice(2)):p}export const meta={name:"agent",group:"Knowledge",summary:"AI Agent management (list/info)",usage:"mindos agent <subcommand>"};export async function run(a,f){const s=a[0];if(!s||f.help||f.h){console.log(bold("mindos agent")+" — AI Agent management
2
+
3
+ Subcommands:
4
+ list List detected AI agents
5
+ info Show agent details
6
+
7
+ Agent Keys: "+Object.keys(MCP_AGENTS).join(", "));return}if(s==="list"||s==="ls")return agentList(f);if(s==="info")return agentInfo(a[1],f);console.error(red("Unknown: "+s));process.exit(1)}function hasMindosConfig(agent){const paths=[agent.global,agent.project].filter(Boolean).map(expandHome);for(const p of paths){try{if(!existsSync(p))continue;const raw=readFileSync(p,"utf-8").replace(///.*$/gm,"").replace(//*[\s\S]*?*//g,"");const data=JSON.parse(raw);const servers=data[agent.key]||{};if(Object.keys(servers).some(k=>k.toLowerCase().includes("mindos")))return true}catch{}}return false}function agentList(f){const agents=[];for(const[key,agent]of Object.entries(MCP_AGENTS)){if(!detectAgentPresence(key))continue;agents.push({key,name:agent.name,installed:true,mindosConnected:hasMindosConfig(agent)})}if(isJsonMode(f)){output({count:agents.length,agents},f);return}if(agents.length===0){console.log(dim("No AI agents detected."));return}console.log("
8
+ "+bold("Detected Agents ("+agents.length+"):")+"
9
+ ");for(const a of agents){console.log(" "+a.name.padEnd(20)+" "+(a.mindosConnected?green("● connected"):dim("○ not connected")))}console.log("
10
+ "+dim("Connect: mindos mcp install <agent-key>")+"
11
+ ")}function agentInfo(key,f){if(!key){console.error(red("Usage: mindos agent info <key>"));process.exit(1)}const agent=MCP_AGENTS[key];if(!agent){console.error(red("Unknown: "+key));process.exit(1)}const installed=detectAgentPresence(key);const connected=installed?hasMindosConfig(agent):false;const info={key,name:agent.name,installed,mindosConnected:connected,transport:agent.preferredTransport};if(isJsonMode(f)){output(info,f);return}console.log("
12
+ "+bold(agent.name)+"
13
+ Key: "+key+"
14
+ Installed: "+(installed?green("yes"):red("no"))+"
15
+ MindOS: "+(connected?green("connected"):yellow("not connected"))+"
16
+ Transport: "+agent.preferredTransport+(agent.global?"
17
+ Config: "+expandHome(agent.global):"")+"
18
+ ")}
@@ -0,0 +1,58 @@
1
+ import { bold, dim, cyan, red } from '../lib/colors.js';
2
+ import { loadConfig } from '../lib/config.js';
3
+
4
+ export const meta = {
5
+ name: 'api',
6
+ group: 'Knowledge',
7
+ summary: 'Raw API passthrough (GET/POST/PUT/DELETE any endpoint)',
8
+ usage: 'mindos api <METHOD> <path>',
9
+ };
10
+
11
+ export async function run(args, flags) {
12
+ if (args.length < 2 || flags.help || flags.h) {
13
+ console.log(bold('mindos api') + ' — Raw API passthrough\n');
14
+ console.log('Usage: mindos api <METHOD> <path> [--body <json>]');
15
+ console.log('Methods: GET, POST, PUT, PATCH, DELETE\n');
16
+ console.log('Examples:');
17
+ console.log(' mindos api GET /api/health');
18
+ console.log(' mindos api GET /api/files');
19
+ console.log(' mindos api POST /api/ask --body \'{"question":"..."}\'');
20
+ return;
21
+ }
22
+
23
+ const method = args[0].toUpperCase();
24
+ if (!['GET', 'POST', 'PUT', 'PATCH', 'DELETE'].includes(method)) {
25
+ console.error(red('Invalid method: ' + args[0]));
26
+ process.exit(1);
27
+ }
28
+
29
+ let apiPath = args[1];
30
+ if (!apiPath.startsWith('/')) apiPath = '/' + apiPath;
31
+
32
+ loadConfig();
33
+ const port = flags.port || process.env.MINDOS_WEB_PORT || '3456';
34
+ const token = process.env.MINDOS_AUTH_TOKEN || '';
35
+ const headers = { 'Content-Type': 'application/json' };
36
+ if (token) headers['Authorization'] = 'Bearer ' + token;
37
+
38
+ const fetchOpts = { method, headers };
39
+ if (flags.body && method !== 'GET') fetchOpts.body = flags.body;
40
+
41
+ try {
42
+ const res = await fetch('http://localhost:' + port + apiPath, fetchOpts);
43
+ const ct = res.headers.get('content-type') || '';
44
+ if (ct.includes('json')) {
45
+ console.log(JSON.stringify(await res.json(), null, 2));
46
+ } else {
47
+ console.log(await res.text());
48
+ }
49
+ if (!res.ok) process.exit(1);
50
+ } catch (err) {
51
+ if (err.cause && err.cause.code === 'ECONNREFUSED') {
52
+ console.error(red('Connection refused. Start with: mindos start'));
53
+ } else {
54
+ console.error(red('Request failed: ' + err.message));
55
+ }
56
+ process.exit(1);
57
+ }
58
+ }
@@ -0,0 +1,101 @@
1
+ /**
2
+ * mindos ask — AI question answering via local MindOS API
3
+ */
4
+
5
+ import { bold, dim, cyan, green, red } from '../lib/colors.js';
6
+ import { loadConfig } from '../lib/config.js';
7
+ import { output, isJsonMode } from '../lib/command.js';
8
+
9
+ export const meta = {
10
+ name: 'ask',
11
+ group: 'Knowledge',
12
+ summary: 'Ask AI a question using your knowledge base',
13
+ usage: 'mindos ask "<question>"',
14
+ flags: {
15
+ '--json': 'Output as JSON',
16
+ '--port <port>': 'MindOS web port (default: 3456)',
17
+ },
18
+ examples: [
19
+ 'mindos ask "Summarize my meeting notes from today"',
20
+ 'mindos ask "What are the key points in my RAG research?"',
21
+ 'mindos ask "List all TODOs across my notes" --json',
22
+ ],
23
+ };
24
+
25
+ export async function run(args, flags) {
26
+ const question = args.join(' ');
27
+
28
+ if (!question || flags.help || flags.h) {
29
+ console.log(`
30
+ ${bold('mindos ask')} — Ask AI using your knowledge base
31
+
32
+ ${bold('Usage:')}
33
+ ${cyan('mindos ask "<question>"')}
34
+
35
+ ${bold('Examples:')}
36
+ ${dim('mindos ask "Summarize my meeting notes"')}
37
+ ${dim('mindos ask "What are the key insights from my research?" --json')}
38
+
39
+ ${bold('Note:')} MindOS must be running (mindos start).
40
+ `);
41
+ return;
42
+ }
43
+
44
+ loadConfig();
45
+ const port = flags.port || process.env.MINDOS_WEB_PORT || '3456';
46
+ const token = process.env.MINDOS_AUTH_TOKEN || '';
47
+ const baseUrl = `http://localhost:${port}`;
48
+
49
+ // Check if MindOS is running
50
+ try {
51
+ const healthRes = await fetch(`${baseUrl}/api/health`);
52
+ if (!healthRes.ok) throw new Error();
53
+ } catch {
54
+ console.error(red('MindOS is not running. Start it with: mindos start'));
55
+ process.exit(1);
56
+ }
57
+
58
+ if (!isJsonMode(flags)) {
59
+ process.stdout.write(dim('Thinking...'));
60
+ }
61
+
62
+ try {
63
+ const headers = { 'Content-Type': 'application/json' };
64
+ if (token) headers['Authorization'] = `Bearer ${token}`;
65
+
66
+ const res = await fetch(`${baseUrl}/api/ask`, {
67
+ method: 'POST',
68
+ headers,
69
+ body: JSON.stringify({ question }),
70
+ });
71
+
72
+ if (!res.ok) {
73
+ const errText = await res.text();
74
+ throw new Error(`API error (${res.status}): ${errText}`);
75
+ }
76
+
77
+ const data = await res.json();
78
+
79
+ if (isJsonMode(flags)) {
80
+ output(data, flags);
81
+ return;
82
+ }
83
+
84
+ // Clear "Thinking..." line
85
+ process.stdout.write('\r' + ' '.repeat(40) + '\r');
86
+
87
+ if (data.answer) {
88
+ console.log(data.answer);
89
+ } else if (data.text) {
90
+ console.log(data.text);
91
+ } else {
92
+ console.log(JSON.stringify(data, null, 2));
93
+ }
94
+ } catch (err) {
95
+ if (!isJsonMode(flags)) {
96
+ process.stdout.write('\r' + ' '.repeat(40) + '\r');
97
+ }
98
+ console.error(red(err.message));
99
+ process.exit(1);
100
+ }
101
+ }
@@ -0,0 +1,286 @@
1
+ /**
2
+ * mindos file — Knowledge base file operations
3
+ *
4
+ * Subcommands: list, read, create, delete, rename, move, search
5
+ * Supports --json for agent consumption
6
+ */
7
+
8
+ import { existsSync, readFileSync, writeFileSync, unlinkSync, renameSync, mkdirSync, readdirSync, statSync } from 'node:fs';
9
+ import { resolve, basename, dirname, relative } from 'node:path';
10
+ import { bold, dim, cyan, green, red, yellow } from '../lib/colors.js';
11
+ import { loadConfig } from '../lib/config.js';
12
+ import { output, isJsonMode } from '../lib/command.js';
13
+
14
+ function getMindRoot() {
15
+ loadConfig();
16
+ const root = process.env.MIND_ROOT;
17
+ if (!root || !existsSync(root)) {
18
+ console.error(red('Mind root not configured. Run `mindos onboard` first.'));
19
+ process.exit(1);
20
+ }
21
+ return root;
22
+ }
23
+
24
+ function resolvePath(root, filePath) {
25
+ // Accept both relative (to mind root) and absolute paths
26
+ if (filePath.startsWith('/')) return filePath;
27
+ return resolve(root, filePath);
28
+ }
29
+
30
+ export const meta = {
31
+ name: 'file',
32
+ group: 'Knowledge',
33
+ summary: 'Knowledge base file operations (list, read, create, delete, search)',
34
+ usage: 'mindos file <subcommand>',
35
+ flags: {
36
+ '--space <name>': 'Filter by space name',
37
+ '--json': 'Output as JSON',
38
+ '--recursive, -r': 'Recursive listing',
39
+ },
40
+ examples: [
41
+ 'mindos file list',
42
+ 'mindos file list --space "Work"',
43
+ 'mindos file read "notes/meeting.md"',
44
+ 'mindos file create "notes/idea.md" --content "# My Idea"',
45
+ 'mindos file search "RAG implementation"',
46
+ 'mindos file delete "notes/old.md"',
47
+ ],
48
+ };
49
+
50
+ export async function run(args, flags) {
51
+ const sub = args[0];
52
+ const root = getMindRoot();
53
+
54
+ if (!sub || flags.help || flags.h) {
55
+ printFileHelp();
56
+ return;
57
+ }
58
+
59
+ switch (sub) {
60
+ case 'list': return fileList(root, args.slice(1), flags);
61
+ case 'ls': return fileList(root, args.slice(1), flags);
62
+ case 'read': return fileRead(root, args[1], flags);
63
+ case 'cat': return fileRead(root, args[1], flags);
64
+ case 'create': return fileCreate(root, args[1], flags);
65
+ case 'delete': return fileDelete(root, args[1], flags);
66
+ case 'rm': return fileDelete(root, args[1], flags);
67
+ case 'rename': return fileRename(root, args[1], args[2], flags);
68
+ case 'mv': return fileRename(root, args[1], args[2], flags);
69
+ case 'move': return fileRename(root, args[1], args[2], flags);
70
+ case 'search': return fileSearch(root, args.slice(1).join(' '), flags);
71
+ default:
72
+ console.error(red(`Unknown subcommand: ${sub}`));
73
+ console.error(dim('Available: list, read, create, delete, rename, move, search'));
74
+ process.exit(1);
75
+ }
76
+ }
77
+
78
+ function printFileHelp() {
79
+ console.log(`
80
+ ${bold('mindos file')} — Knowledge base file operations
81
+
82
+ ${bold('Subcommands:')}
83
+ ${cyan('list'.padEnd(20))}${dim('List files in knowledge base')}
84
+ ${cyan('read <path>'.padEnd(20))}${dim('Read file content')}
85
+ ${cyan('create <path>'.padEnd(20))}${dim('Create a new file (--content "...")')}
86
+ ${cyan('delete <path>'.padEnd(20))}${dim('Delete a file')}
87
+ ${cyan('rename <old> <new>'.padEnd(20))}${dim('Rename or move a file')}
88
+ ${cyan('search <query>'.padEnd(20))}${dim('Search files by content')}
89
+
90
+ ${bold('Aliases:')} ls=list, cat=read, rm=delete, mv=rename
91
+
92
+ ${bold('Examples:')}
93
+ ${dim('mindos file list')}
94
+ ${dim('mindos file list --json')}
95
+ ${dim('mindos file read "notes/meeting.md"')}
96
+ ${dim('mindos file create "ideas/new.md" --content "# New Idea"')}
97
+ ${dim('mindos file search "machine learning"')}
98
+ `);
99
+ }
100
+
101
+ function walkFiles(dir, root, opts = {}) {
102
+ const { recursive = true, space = null } = opts;
103
+ const results = [];
104
+ let entries;
105
+ try { entries = readdirSync(dir, { withFileTypes: true }); } catch { return results; }
106
+
107
+ for (const entry of entries) {
108
+ if (entry.name.startsWith('.')) continue;
109
+ const full = resolve(dir, entry.name);
110
+ const rel = relative(root, full);
111
+
112
+ if (entry.isDirectory()) {
113
+ if (space && dirname(rel) === '.' && entry.name !== space) continue;
114
+ if (recursive) results.push(...walkFiles(full, root, { ...opts, space: null }));
115
+ } else if (entry.isFile()) {
116
+ results.push({
117
+ path: rel,
118
+ name: entry.name,
119
+ size: statSync(full).size,
120
+ });
121
+ }
122
+ }
123
+ return results;
124
+ }
125
+
126
+ function fileList(root, _args, flags) {
127
+ const files = walkFiles(root, root, {
128
+ recursive: flags.recursive || flags.r || true,
129
+ space: flags.space || null,
130
+ });
131
+
132
+ if (isJsonMode(flags)) {
133
+ output({ count: files.length, files }, flags);
134
+ return;
135
+ }
136
+
137
+ if (files.length === 0) {
138
+ console.log(dim('No files found.'));
139
+ return;
140
+ }
141
+
142
+ console.log(`\n${bold(`Files (${files.length}):`)}\n`);
143
+ for (const f of files) {
144
+ const sizeStr = f.size < 1024 ? `${f.size}B` : `${(f.size / 1024).toFixed(1)}K`;
145
+ console.log(` ${f.path} ${dim(sizeStr)}`);
146
+ }
147
+ console.log();
148
+ }
149
+
150
+ function fileRead(root, filePath, flags) {
151
+ if (!filePath) {
152
+ console.error(red('Usage: mindos file read <path>'));
153
+ process.exit(1);
154
+ }
155
+ const full = resolvePath(root, filePath);
156
+ if (!existsSync(full)) {
157
+ console.error(red(`File not found: ${filePath}`));
158
+ process.exit(1);
159
+ }
160
+ const content = readFileSync(full, 'utf-8');
161
+
162
+ if (isJsonMode(flags)) {
163
+ output({ path: filePath, size: content.length, content }, flags);
164
+ return;
165
+ }
166
+ console.log(content);
167
+ }
168
+
169
+ function fileCreate(root, filePath, flags) {
170
+ if (!filePath) {
171
+ console.error(red('Usage: mindos file create <path> --content "..."'));
172
+ process.exit(1);
173
+ }
174
+ const full = resolvePath(root, filePath);
175
+ if (existsSync(full) && !flags.force) {
176
+ console.error(red(`File already exists: ${filePath}`));
177
+ console.error(dim('Use --force to overwrite.'));
178
+ process.exit(1);
179
+ }
180
+
181
+ const content = flags.content || `# ${basename(filePath, '.md')}\n`;
182
+ mkdirSync(dirname(full), { recursive: true });
183
+ writeFileSync(full, content, 'utf-8');
184
+
185
+ if (isJsonMode(flags)) {
186
+ output({ ok: true, path: filePath, size: content.length }, flags);
187
+ return;
188
+ }
189
+ console.log(`${green('✔')} Created: ${cyan(filePath)}`);
190
+ }
191
+
192
+ function fileDelete(root, filePath, flags) {
193
+ if (!filePath) {
194
+ console.error(red('Usage: mindos file delete <path>'));
195
+ process.exit(1);
196
+ }
197
+ const full = resolvePath(root, filePath);
198
+ if (!existsSync(full)) {
199
+ console.error(red(`File not found: ${filePath}`));
200
+ process.exit(1);
201
+ }
202
+
203
+ unlinkSync(full);
204
+
205
+ if (isJsonMode(flags)) {
206
+ output({ ok: true, deleted: filePath }, flags);
207
+ return;
208
+ }
209
+ console.log(`${green('✔')} Deleted: ${filePath}`);
210
+ }
211
+
212
+ function fileRename(root, oldPath, newPath, flags) {
213
+ if (!oldPath || !newPath) {
214
+ console.error(red('Usage: mindos file rename <old-path> <new-path>'));
215
+ process.exit(1);
216
+ }
217
+ const fullOld = resolvePath(root, oldPath);
218
+ const fullNew = resolvePath(root, newPath);
219
+
220
+ if (!existsSync(fullOld)) {
221
+ console.error(red(`File not found: ${oldPath}`));
222
+ process.exit(1);
223
+ }
224
+ if (existsSync(fullNew) && !flags.force) {
225
+ console.error(red(`Target already exists: ${newPath}`));
226
+ process.exit(1);
227
+ }
228
+
229
+ mkdirSync(dirname(fullNew), { recursive: true });
230
+ renameSync(fullOld, fullNew);
231
+
232
+ if (isJsonMode(flags)) {
233
+ output({ ok: true, from: oldPath, to: newPath }, flags);
234
+ return;
235
+ }
236
+ console.log(`${green('✔')} Renamed: ${oldPath} → ${cyan(newPath)}`);
237
+ }
238
+
239
+ function fileSearch(root, query, flags) {
240
+ if (!query) {
241
+ console.error(red('Usage: mindos file search <query>'));
242
+ process.exit(1);
243
+ }
244
+
245
+ const files = walkFiles(root, root);
246
+ const results = [];
247
+ const queryLower = query.toLowerCase();
248
+
249
+ for (const f of files) {
250
+ try {
251
+ const content = readFileSync(resolve(root, f.path), 'utf-8');
252
+ const lines = content.split('\n');
253
+ const matches = [];
254
+ for (let i = 0; i < lines.length; i++) {
255
+ if (lines[i].toLowerCase().includes(queryLower)) {
256
+ matches.push({ line: i + 1, text: lines[i].trim().slice(0, 120) });
257
+ }
258
+ }
259
+ if (matches.length > 0 || f.name.toLowerCase().includes(queryLower)) {
260
+ results.push({ path: f.path, matches });
261
+ }
262
+ } catch { /* skip unreadable files */ }
263
+ }
264
+
265
+ if (isJsonMode(flags)) {
266
+ output({ query, count: results.length, results }, flags);
267
+ return;
268
+ }
269
+
270
+ if (results.length === 0) {
271
+ console.log(dim(`No results for "${query}"`));
272
+ return;
273
+ }
274
+
275
+ console.log(`\n${bold(`Search: "${query}" (${results.length} files)`)}\n`);
276
+ for (const r of results) {
277
+ console.log(` ${cyan(r.path)}`);
278
+ for (const m of r.matches.slice(0, 3)) {
279
+ console.log(` ${dim(`L${m.line}:`)} ${m.text}`);
280
+ }
281
+ if (r.matches.length > 3) {
282
+ console.log(` ${dim(`...and ${r.matches.length - 3} more`)}`);
283
+ }
284
+ }
285
+ console.log();
286
+ }
@@ -0,0 +1,51 @@
1
+ import { bold, dim, cyan, red } from '../lib/colors.js';
2
+ import { loadConfig } from '../lib/config.js';
3
+ import { output, isJsonMode } from '../lib/command.js';
4
+
5
+ export const meta = {
6
+ name: 'search', group: 'Knowledge',
7
+ summary: 'Search knowledge base via API',
8
+ usage: 'mindos search "<query>"',
9
+ examples: ['mindos search "meeting notes"', 'mindos search "RAG" --limit 5 --json'],
10
+ };
11
+
12
+ export async function run(args, flags) {
13
+ const query = args.join(' ');
14
+ if (!query || flags.help || flags.h) {
15
+ console.log(bold('mindos search') + ' — Knowledge base search');
16
+ console.log('');
17
+ console.log('Usage: mindos search "<query>"');
18
+ console.log('Flags: --limit <n> (default 20), --json');
19
+ console.log('Note: MindOS must be running. Offline: mindos file search "<query>"');
20
+ return;
21
+ }
22
+ loadConfig();
23
+ const port = flags.port || process.env.MINDOS_WEB_PORT || '3456';
24
+ const token = process.env.MINDOS_AUTH_TOKEN || '';
25
+ const limit = parseInt(flags.limit) || 20;
26
+ const headers = {};
27
+ if (token) headers['Authorization'] = 'Bearer ' + token;
28
+ try {
29
+ const res = await fetch('http://localhost:' + port + '/api/search?q=' + encodeURIComponent(query) + '&limit=' + limit, { headers });
30
+ if (!res.ok) throw new Error('API error (' + res.status + ')');
31
+ const data = await res.json();
32
+ const results = data.results || data || [];
33
+ if (isJsonMode(flags)) { output({ query, count: results.length, results }, flags); return; }
34
+ if (results.length === 0) { console.log(dim('No results for "' + query + '"')); return; }
35
+ console.log('');
36
+ console.log(bold('Search: "' + query + '" (' + results.length + ' results)'));
37
+ console.log('');
38
+ for (const r of results) {
39
+ const path = r.path || r.filePath || r.name || 'unknown';
40
+ const snippet = r.snippet || r.preview || r.excerpt || '';
41
+ const score = r.score ? dim(' (' + (r.score * 100).toFixed(0) + '%)') : '';
42
+ console.log(' ' + cyan(path) + score);
43
+ if (snippet) { for (const line of snippet.split('\n').slice(0, 2)) { console.log(' ' + dim(line.trim().slice(0, 100))); } }
44
+ }
45
+ console.log('');
46
+ } catch (err) {
47
+ if (err.cause && err.cause.code === 'ECONNREFUSED') { console.error(red('MindOS not running. Offline: mindos file search "' + query + '"')); }
48
+ else { console.error(red(err.message)); }
49
+ process.exit(1);
50
+ }
51
+ }