@geminilight/mindos 0.6.25 → 0.6.28

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 (45) hide show
  1. package/README.md +19 -3
  2. package/README_zh.md +19 -3
  3. package/app/app/api/a2a/discover/route.ts +23 -0
  4. package/app/components/CreateSpaceModal.tsx +1 -0
  5. package/app/components/ImportModal.tsx +3 -0
  6. package/app/components/OnboardingView.tsx +1 -0
  7. package/app/components/RightAskPanel.tsx +4 -2
  8. package/app/components/SidebarLayout.tsx +11 -2
  9. package/app/components/agents/AgentDetailContent.tsx +48 -1
  10. package/app/components/agents/AgentsContentPage.tsx +3 -0
  11. package/app/components/agents/AgentsOverviewSection.tsx +11 -0
  12. package/app/components/agents/DiscoverAgentModal.tsx +149 -0
  13. package/app/components/ask/AskContent.tsx +29 -9
  14. package/app/components/ask/SessionTabBar.tsx +70 -0
  15. package/app/components/echo/EchoInsightCollapsible.tsx +4 -0
  16. package/app/components/help/HelpContent.tsx +65 -9
  17. package/app/components/panels/AgentsPanel.tsx +25 -2
  18. package/app/components/panels/AgentsPanelAgentListRow.tsx +10 -1
  19. package/app/components/renderers/workflow/WorkflowRenderer.tsx +5 -0
  20. package/app/components/settings/AiTab.tsx +1 -0
  21. package/app/components/settings/KnowledgeTab.tsx +2 -0
  22. package/app/components/settings/SyncTab.tsx +2 -0
  23. package/app/components/setup/StepDots.tsx +5 -1
  24. package/app/data/skills/mindos/SKILL.md +186 -0
  25. package/app/data/skills/mindos-zh/SKILL.md +185 -0
  26. package/app/hooks/useA2aRegistry.ts +53 -0
  27. package/app/hooks/useAskSession.ts +44 -25
  28. package/app/lib/a2a/a2a-tools.ts +212 -0
  29. package/app/lib/a2a/client.ts +207 -0
  30. package/app/lib/a2a/index.ts +8 -0
  31. package/app/lib/a2a/orchestrator.ts +255 -0
  32. package/app/lib/a2a/types.ts +54 -0
  33. package/app/lib/agent/tools.ts +6 -4
  34. package/app/lib/i18n-en.ts +52 -0
  35. package/app/lib/i18n-zh.ts +52 -0
  36. package/app/next-env.d.ts +1 -1
  37. package/bin/cli.js +180 -171
  38. package/bin/commands/agent.js +110 -18
  39. package/bin/commands/api.js +5 -3
  40. package/bin/commands/ask.js +3 -3
  41. package/bin/commands/file.js +13 -13
  42. package/bin/commands/search.js +2 -2
  43. package/bin/commands/space.js +64 -10
  44. package/bin/lib/command.js +10 -0
  45. package/package.json +1 -1
package/bin/cli.js CHANGED
@@ -54,13 +54,35 @@ import { needsBuild, writeBuildStamp, cleanNextDir, ensureAppDeps } from './lib/
54
54
  import { isPortInUse, assertPortFree } from './lib/port.js';
55
55
  import { savePids, clearPids } from './lib/pid.js';
56
56
  import { stopMindos } from './lib/stop.js';
57
- import { getPlatform, ensureMindosDir, waitForHttp, waitForPortFree, runGatewayCommand } from './lib/gateway.js';
58
57
  import { printStartupInfo, getLocalIP } from './lib/startup.js';
59
58
  import { spawnMcp } from './lib/mcp-spawn.js';
60
- import { ensureMcpBundle } from './lib/mcp-build.js';
61
- import { mcpInstall } from './lib/mcp-install.js';
62
- import { initSync, startSyncDaemon, stopSyncDaemon, getSyncStatus, manualSync, listConflicts, setSyncEnabled } from './lib/sync.js';
63
59
  import { parseArgs } from './lib/command.js';
60
+ import { MCP_AGENTS, detectAgentPresence } from './lib/mcp-agents.js';
61
+
62
+ // Heavy modules — loaded lazily inside command handlers to speed up CLI cold start
63
+ // gateway.js (426 lines), mcp-install.js (335), sync.js (472), mcp-build.js (74)
64
+ const lazy = {
65
+ gateway: () => import('./lib/gateway.js'),
66
+ mcpInstall: () => import('./lib/mcp-install.js'),
67
+ sync: () => import('./lib/sync.js'),
68
+ mcpBuild: () => import('./lib/mcp-build.js'),
69
+ };
70
+
71
+ // Thin wrappers for lazy modules — same API as before, but loaded on first call
72
+ async function getPlatform() { return (await lazy.gateway()).getPlatform(); }
73
+ async function runGatewayCommand(sub) { return (await lazy.gateway()).runGatewayCommand(sub); }
74
+ async function waitForHttp(...a) { return (await lazy.gateway()).waitForHttp(...a); }
75
+ async function waitForPortFree(...a) { return (await lazy.gateway()).waitForPortFree(...a); }
76
+ async function ensureMindosDir() { return (await lazy.gateway()).ensureMindosDir(); }
77
+ async function ensureMcpBundle() { return (await lazy.mcpBuild()).ensureMcpBundle(); }
78
+ async function mcpInstall() { return (await lazy.mcpInstall()).mcpInstall(); }
79
+ async function initSync(...a) { return (await lazy.sync()).initSync(...a); }
80
+ async function startSyncDaemon(...a) { return (await lazy.sync()).startSyncDaemon(...a); }
81
+ async function stopSyncDaemon() { return (await lazy.sync()).stopSyncDaemon(); }
82
+ async function getSyncStatus(...a) { return (await lazy.sync()).getSyncStatus(...a); }
83
+ async function manualSync(...a) { return (await lazy.sync()).manualSync(...a); }
84
+ async function listConflicts(...a) { return (await lazy.sync()).listConflicts(...a); }
85
+ async function setSyncEnabled(...a) { return (await lazy.sync()).setSyncEnabled(...a); }
64
86
 
65
87
  // ── New modular commands ──────────────────────────────────────────────────────
66
88
  import * as fileCmd from './commands/file.js';
@@ -139,24 +161,24 @@ function buildIfNeeded(newRoot) {
139
161
 
140
162
  // ── Commands ──────────────────────────────────────────────────────────────────
141
163
 
142
- const cmd = process.argv[2];
164
+ // ── Unified arg parsing ────────────────────────────────────────────────────
165
+ const { command: cmd, args: cliArgs, flags: cliFlags } = parseArgs(process.argv.slice(2));
143
166
 
144
- // ── --version / -v ──────────────────────────────────────────────────────────
145
- // --help / -h is handled at entry section (resolvedCmd = null → help block)
146
- if (cmd === '--version' || cmd === '-v') {
167
+ if (cliFlags.version || cliFlags.v) {
147
168
  const version = JSON.parse(readFileSync(resolve(ROOT, 'package.json'), 'utf-8')).version;
148
169
  console.log(`mindos/${version} node/${process.version} ${process.platform}-${process.arch}`);
149
170
  process.exit(0);
150
171
  }
151
172
 
152
- const isDaemon = process.argv.includes('--daemon') || (!cmd && isDaemonMode());
153
- const isVerbose = process.argv.includes('--verbose');
154
- const extra = process.argv.slice(3).filter(a => a !== '--daemon' && a !== '--verbose' && a !== '--turbo').join(' ');
173
+ // Backward compat: derive legacy variables from unified flags
174
+ const isDaemon = cliFlags.daemon === true || (!cmd && isDaemonMode());
175
+ const isVerbose = cliFlags.verbose === true;
176
+ const extra = cliArgs.filter(a => !a.startsWith('-')).join(' ');
155
177
 
156
178
  const commands = {
157
179
  // ── onboard ────────────────────────────────────────────────────────────────
158
180
  onboard: async () => {
159
- const daemonFlag = process.argv.includes('--install-daemon') ? ' --install-daemon' : '';
181
+ const daemonFlag = cliFlags['install-daemon'] ? ' --install-daemon' : '';
160
182
  run(`node ${resolve(ROOT, 'scripts/setup.js')}${daemonFlag}`);
161
183
  },
162
184
  init: async () => commands.onboard(),
@@ -204,74 +226,48 @@ const commands = {
204
226
  }
205
227
  const mcpPort = config.mcpPort || 8781;
206
228
  const localIP = getLocalIP();
207
-
208
229
  const localUrl = `http://localhost:${mcpPort}/mcp`;
209
- const sep = '━'.repeat(40);
210
-
211
- console.log(`\n${bold('🔑 Auth token:')} ${cyan(token)}\n`);
212
-
213
- // Claude Code
214
- console.log(`${sep}`);
215
- console.log(`${bold('Claude Code')}`);
216
- console.log(`${sep}`);
217
- console.log(dim('Quick install:') + ` mindos mcp install claude-code -g -y`);
218
- console.log(dim('\nManual config (~/.claude.json):'));
219
- console.log(JSON.stringify({
220
- mcpServers: {
221
- mindos: {
222
- url: localUrl,
223
- headers: { Authorization: `Bearer ${token}` },
224
- },
225
- },
226
- }, null, 2));
227
-
228
- // CodeBuddy (Claude Code Internal)
229
- console.log(`\n${sep}`);
230
- console.log(`${bold('CodeBuddy (Claude Code Internal)')}`);
231
- console.log(`${sep}`);
232
- console.log(dim('Quick install:') + ` mindos mcp install codebuddy -g -y`);
233
- console.log(dim('\nManual config (~/.claude-internal/.claude.json):'));
234
- console.log(JSON.stringify({
235
- mcpServers: {
236
- mindos: {
237
- url: localUrl,
238
- headers: { Authorization: `Bearer ${token}` },
239
- },
240
- },
241
- }, null, 2));
242
-
243
- // Cursor
244
- console.log(`\n${sep}`);
245
- console.log(`${bold('Cursor')}`);
246
- console.log(`${sep}`);
247
- console.log(dim('Quick install:') + ` mindos mcp install cursor -g -y`);
248
- console.log(dim('\nManual config (~/.cursor/mcp.json):'));
249
- console.log(JSON.stringify({
250
- mcpServers: {
251
- mindos: {
252
- url: localUrl,
253
- headers: { Authorization: `Bearer ${token}` },
254
- },
255
- },
256
- }, null, 2));
257
-
258
- // Remote
230
+
231
+ if (cliFlags.json) {
232
+ const data = { token, mcpPort, localUrl, remoteUrl: localIP ? `http://${localIP}:${mcpPort}/mcp` : null };
233
+ console.log(JSON.stringify(data, null, 2));
234
+ return;
235
+ }
236
+
237
+ const sep = dim('━'.repeat(40));
238
+ const snippet = (url) => JSON.stringify({
239
+ mcpServers: { mindos: { url, headers: { Authorization: `Bearer ${token}` } } },
240
+ }, null, 2);
241
+
242
+ console.log(`\n${bold('Auth token:')} ${cyan(token)}\n`);
243
+
244
+ // Show installed agents, then up to 3 uninstalled popular ones
245
+ const installed = [];
246
+ const others = [];
247
+ for (const [key, agent] of Object.entries(MCP_AGENTS)) {
248
+ (detectAgentPresence(key) ? installed : others).push([key, agent]);
249
+ }
250
+ const toShow = [...installed.slice(0, 8), ...others.slice(0, Math.max(0, 3 - installed.length))];
251
+
252
+ for (const [key, agent] of toShow) {
253
+ console.log(sep);
254
+ console.log(bold(agent.name));
255
+ console.log(dim('Install:') + ` mindos mcp install ${key} -g -y`);
256
+ if (agent.global) console.log(dim(`Config: ${agent.global}`));
257
+ console.log(snippet(localUrl));
258
+ console.log();
259
+ }
260
+
259
261
  if (localIP) {
260
- const remoteUrl = `http://${localIP}:${mcpPort}/mcp`;
261
- console.log(`\n${sep}`);
262
- console.log(`${bold('Remote (other devices)')}`);
263
- console.log(`${sep}`);
264
- console.log(`URL: ${cyan(remoteUrl)}`);
265
- console.log(JSON.stringify({
266
- mcpServers: {
267
- mindos: {
268
- url: remoteUrl,
269
- headers: { Authorization: `Bearer ${token}` },
270
- },
271
- },
272
- }, null, 2));
262
+ console.log(sep);
263
+ console.log(bold('Remote (other devices)'));
264
+ console.log(`URL: ${cyan(`http://${localIP}:${mcpPort}/mcp`)}`);
265
+ console.log(snippet(`http://${localIP}:${mcpPort}/mcp`));
273
266
  }
274
267
 
268
+ if (toShow.length < installed.length) {
269
+ console.log(dim(`\n +${installed.length - toShow.length} more agents detected. Run \`mindos agent list\` to see all.`));
270
+ }
275
271
  console.log(dim('\nRun `mindos onboard` to regenerate.\n'));
276
272
  },
277
273
 
@@ -289,8 +285,7 @@ const commands = {
289
285
  ensureAppDeps();
290
286
  const mcp = spawnMcp(isVerbose);
291
287
  savePids(process.pid, mcp.pid);
292
- process.on('exit', () => { stopSyncDaemon(); clearPids(); });
293
- // Start sync daemon if enabled
288
+ process.on('exit', () => { stopSyncDaemon().catch(() => {}); clearPids(); });
294
289
  const devMindRoot = process.env.MIND_ROOT;
295
290
  if (devMindRoot) {
296
291
  startSyncDaemon(devMindRoot).catch(() => {});
@@ -311,7 +306,7 @@ const commands = {
311
306
  } catch {}
312
307
  }
313
308
  if (isDaemon) {
314
- const platform = getPlatform();
309
+ const platform = await getPlatform();
315
310
  if (!platform) {
316
311
  console.warn(yellow('Warning: daemon mode not supported on this platform. Falling back to foreground.'));
317
312
  } else {
@@ -404,7 +399,7 @@ const commands = {
404
399
  }
405
400
  const mcp = spawnMcp(isVerbose);
406
401
  savePids(process.pid, mcp.pid);
407
- process.on('exit', () => { stopSyncDaemon(); clearPids(); });
402
+ process.on('exit', () => { stopSyncDaemon().catch(() => {}); clearPids(); });
408
403
  // Start sync daemon if enabled
409
404
  const mindRoot = process.env.MIND_ROOT;
410
405
  if (mindRoot) {
@@ -428,12 +423,12 @@ const commands = {
428
423
  },
429
424
 
430
425
  mcp: async () => {
431
- const sub = process.argv[3];
432
- const restArgs = process.argv.slice(3);
426
+ const sub = cliArgs[0];
427
+ const restArgs = cliArgs;
433
428
  const hasInstallFlags = restArgs.some(a => ['-g', '--global', '-y', '--yes'].includes(a));
434
429
  if (sub === 'install' || hasInstallFlags) { await mcpInstall(); return; }
435
430
  loadConfig();
436
- ensureMcpBundle();
431
+ await ensureMcpBundle();
437
432
  // `mindos mcp` is the entry point for MCP clients (Claude Code, Cursor, etc.)
438
433
  // which communicate over stdin/stdout. Default to stdio; HTTP is handled by
439
434
  // `mindos start` via spawnMcp(). Callers can still override via env.
@@ -493,7 +488,7 @@ const commands = {
493
488
 
494
489
  // ── gateway ────────────────────────────────────────────────────────────────
495
490
  gateway: async () => {
496
- const sub = process.argv[3];
491
+ const sub = cliArgs[0];
497
492
  if (!sub) {
498
493
  const row = (c, d) => ` ${cyan(c.padEnd(32))}${dim(d)}`;
499
494
  console.log(`
@@ -556,25 +551,28 @@ ${dim('Shortcut: mindos start --daemon → install + start in one step')}
556
551
 
557
552
  // ── doctor ─────────────────────────────────────────────────────────────────
558
553
  doctor: async () => {
559
- const ok = (msg) => console.log(` ${green('✔')} ${msg}`);
560
- const err = (msg) => console.log(` ${red('✘')} ${msg}`);
561
- const warn= (msg) => console.log(` ${yellow('!')} ${msg}`);
554
+ const jsonMode = cliFlags.json === true;
555
+ const checks = [];
556
+ const ok = (msg, key) => { checks.push({ status: 'ok', key, msg }); if (!jsonMode) console.log(` ${green('')} ${msg}`); };
557
+ const err = (msg, key) => { checks.push({ status: 'error', key, msg }); if (!jsonMode) console.log(` ${red('✘')} ${msg}`); };
558
+ const warn= (msg, key) => { checks.push({ status: 'warn', key, msg }); if (!jsonMode) console.log(` ${yellow('!')} ${msg}`); };
562
559
 
563
- console.log(`\n${bold('🩺 MindOS Doctor')}\n`);
560
+ if (!jsonMode) console.log(`\n${bold('MindOS Doctor')}\n`);
564
561
  let hasError = false;
565
562
 
566
563
  // 1. config file
567
564
  if (!existsSync(CONFIG_PATH)) {
568
- err(`Config not found at ${dim(CONFIG_PATH)}`);
569
- console.log(`\n ${dim('Run `mindos onboard` to create it.')}\n`);
565
+ err(`Config not found at ${dim(CONFIG_PATH)}`, 'config');
566
+ if (!jsonMode) { console.log(`\n ${dim('Run `mindos onboard` to create it.')}\n`); }
567
+ if (jsonMode) { console.log(JSON.stringify({ ok: false, checks }, null, 2)); }
570
568
  process.exit(1);
571
569
  }
572
570
  let config;
573
571
  try {
574
572
  config = JSON.parse(readFileSync(CONFIG_PATH, 'utf-8'));
575
- ok(`Config file found and valid JSON ${dim(CONFIG_PATH)}`);
573
+ ok(`Config file found and valid JSON ${dim(CONFIG_PATH)}`, 'config');
576
574
  } catch {
577
- err(`Config file exists but failed to parse ${dim(CONFIG_PATH)}`);
575
+ err(`Config file exists but failed to parse ${dim(CONFIG_PATH)}`, 'config');
578
576
  hasError = true;
579
577
  }
580
578
 
@@ -657,7 +655,7 @@ ${dim('Shortcut: mindos start --daemon → install + start in one step')}
657
655
  }
658
656
 
659
657
  // 7. Daemon status
660
- const platform = getPlatform();
658
+ const platform = await getPlatform();
661
659
  if (platform === 'systemd') {
662
660
  try {
663
661
  execSync('systemctl --user is-active mindos', { stdio: 'pipe' });
@@ -678,7 +676,7 @@ ${dim('Shortcut: mindos start --daemon → install + start in one step')}
678
676
  // 8. Sync status
679
677
  if (config?.mindRoot) {
680
678
  try {
681
- const syncStatus = getSyncStatus(config.mindRoot);
679
+ const syncStatus = await getSyncStatus(config.mindRoot);
682
680
  if (!syncStatus.enabled) {
683
681
  warn(`Cross-device sync is not configured ${dim('(run `mindos sync init` to set up)')}`);
684
682
  } else if (syncStatus.lastError) {
@@ -713,9 +711,14 @@ ${dim('Shortcut: mindos start --daemon → install + start in one step')}
713
711
  warn('Could not check for updates');
714
712
  }
715
713
 
716
- console.log(hasError
717
- ? `\n${red('Some checks failed.')} Run ${cyan('mindos onboard')} to reconfigure.\n`
718
- : `\n${green('All checks passed.')}\n`);
714
+ if (jsonMode) {
715
+ const hasErr = checks.some(c => c.status === 'error');
716
+ console.log(JSON.stringify({ ok: !hasErr, checks }, null, 2));
717
+ } else {
718
+ console.log(hasError
719
+ ? `\n${red('Some checks failed.')} Run ${cyan('mindos onboard')} to reconfigure.\n`
720
+ : `\n${green('All checks passed.')}\n`);
721
+ }
719
722
  if (hasError) process.exit(1);
720
723
  },
721
724
 
@@ -763,7 +766,7 @@ ${dim('Shortcut: mindos start --daemon → install + start in one step')}
763
766
  return;
764
767
  }
765
768
 
766
- const updatePlatform = getPlatform();
769
+ const updatePlatform = await getPlatform();
767
770
  let daemonRunning = false;
768
771
  if (updatePlatform === 'systemd') {
769
772
  try { execSync('systemctl --user is-active mindos', { stdio: 'pipe' }); daemonRunning = true; } catch {}
@@ -977,7 +980,7 @@ ${dim('Shortcut: mindos start --daemon → install + start in one step')}
977
980
  try { stopMindos(); } catch { /* may not be running */ }
978
981
 
979
982
  // 2. Remove daemon (skip if platform unsupported)
980
- if (getPlatform()) {
983
+ if (await getPlatform()) {
981
984
  try {
982
985
  await runGatewayCommand('uninstall');
983
986
  } catch {
@@ -1040,14 +1043,14 @@ ${dim('Shortcut: mindos start --daemon → install + start in one step')}
1040
1043
  },
1041
1044
 
1042
1045
  // ── logs ───────────────────────────────────────────────────────────────────
1043
- logs: () => {
1044
- ensureMindosDir();
1046
+ logs: async () => {
1047
+ await ensureMindosDir();
1045
1048
  if (!existsSync(LOG_PATH)) {
1046
1049
  console.log(dim(`No log file yet at ${LOG_PATH}`));
1047
1050
  console.log(dim('Logs are created when starting MindOS (mindos start, mindos onboard, or daemon mode).'));
1048
1051
  process.exit(0);
1049
1052
  }
1050
- const noFollow = process.argv.includes('--no-follow');
1053
+ const noFollow = cliFlags['no-follow'] === true;
1051
1054
  if (noFollow) {
1052
1055
  execSync(`tail -n 100 ${LOG_PATH}`, { stdio: 'inherit' });
1053
1056
  } else {
@@ -1057,7 +1060,7 @@ ${dim('Shortcut: mindos start --daemon → install + start in one step')}
1057
1060
 
1058
1061
  // ── config ─────────────────────────────────────────────────────────────────
1059
1062
  config: () => {
1060
- const sub = process.argv[3];
1063
+ const sub = cliArgs[0];
1061
1064
 
1062
1065
  function maskKey(val) {
1063
1066
  if (!val) return val;
@@ -1088,7 +1091,11 @@ ${dim('Shortcut: mindos start --daemon → install + start in one step')}
1088
1091
  display.authToken = maskKey(display.authToken);
1089
1092
  if (display.webPassword)
1090
1093
  display.webPassword = maskKey(display.webPassword);
1091
- console.log(`\n${bold('📋 MindOS Config')} ${dim(`v${(() => { try { return JSON.parse(readFileSync(resolve(ROOT, 'package.json'), 'utf-8')).version; } catch { return '?'; } })()}`)} ${dim(CONFIG_PATH)}\n`);
1094
+ if (cliFlags.json) {
1095
+ console.log(JSON.stringify(display, null, 2));
1096
+ return;
1097
+ }
1098
+ console.log(`\n${bold('MindOS Config')} ${dim(`v${(() => { try { return JSON.parse(readFileSync(resolve(ROOT, 'package.json'), 'utf-8')).version; } catch { return '?'; } })()}`)} ${dim(CONFIG_PATH)}\n`);
1092
1099
  console.log(JSON.stringify(display, null, 2));
1093
1100
  console.log();
1094
1101
  return;
@@ -1128,8 +1135,8 @@ ${dim('Shortcut: mindos start --daemon → install + start in one step')}
1128
1135
  }
1129
1136
 
1130
1137
  if (sub === 'set') {
1131
- const key = process.argv[4];
1132
- const val = process.argv[5];
1138
+ const key = cliArgs[1];
1139
+ const val = cliArgs[2];
1133
1140
  if (!key || val === undefined) {
1134
1141
  console.error(red('Usage: mindos config set <key> <value>'));
1135
1142
  console.error(dim(' Examples:'));
@@ -1170,7 +1177,7 @@ ${dim('Shortcut: mindos start --daemon → install + start in one step')}
1170
1177
  }
1171
1178
 
1172
1179
  if (sub === 'unset') {
1173
- const key = process.argv[4];
1180
+ const key = cliArgs[1];
1174
1181
  if (!key) {
1175
1182
  console.error(red('Usage: mindos config unset <key>'));
1176
1183
  process.exit(1);
@@ -1218,13 +1225,13 @@ ${bold('Examples:')}
1218
1225
 
1219
1226
  // ── sync ──────────────────────────────────────────────────────────────────
1220
1227
  sync: async () => {
1221
- const sub = process.argv[3];
1228
+ const sub = cliArgs[0];
1222
1229
  loadConfig();
1223
1230
  const mindRoot = process.env.MIND_ROOT;
1224
1231
 
1225
1232
  if (sub === 'init') {
1226
1233
  // Parse --non-interactive --remote <url> --branch <branch> --token <token>
1227
- const args = process.argv.slice(4);
1234
+ const args = cliArgs.slice(1);
1228
1235
  const flagIdx = (flag) => args.indexOf(flag);
1229
1236
  const flagVal = (flag) => { const i = flagIdx(flag); return i >= 0 && i + 1 < args.length ? args[i + 1] : ''; };
1230
1237
  const nonInteractive = args.includes('--non-interactive');
@@ -1245,7 +1252,7 @@ ${bold('Examples:')}
1245
1252
  if (sub === 'now') {
1246
1253
  try {
1247
1254
  console.log(dim('Pulling...'));
1248
- manualSync(mindRoot);
1255
+ await manualSync(mindRoot);
1249
1256
  console.log(green('✔ Sync complete'));
1250
1257
  } catch (err) {
1251
1258
  console.error(red(err.message));
@@ -1255,18 +1262,18 @@ ${bold('Examples:')}
1255
1262
  }
1256
1263
 
1257
1264
  if (sub === 'conflicts') {
1258
- listConflicts(mindRoot);
1265
+ await listConflicts(mindRoot);
1259
1266
  return;
1260
1267
  }
1261
1268
 
1262
1269
  if (sub === 'on') {
1263
- setSyncEnabled(true);
1270
+ await setSyncEnabled(true);
1264
1271
  return;
1265
1272
  }
1266
1273
 
1267
1274
  if (sub === 'off') {
1268
- setSyncEnabled(false);
1269
- stopSyncDaemon();
1275
+ await setSyncEnabled(false);
1276
+ await stopSyncDaemon();
1270
1277
  return;
1271
1278
  }
1272
1279
 
@@ -1281,9 +1288,15 @@ ${bold('Examples:')}
1281
1288
  }
1282
1289
 
1283
1290
  // default: sync status
1284
- const status = getSyncStatus(mindRoot);
1291
+ const status = await getSyncStatus(mindRoot);
1292
+
1293
+ if (cliFlags.json) {
1294
+ console.log(JSON.stringify(status, null, 2));
1295
+ return;
1296
+ }
1297
+
1285
1298
  if (!status.enabled) {
1286
- console.log(`\n${bold('🔄 Sync Status')}`);
1299
+ console.log(`\n${bold('Sync Status')}`);
1287
1300
  console.log(dim(' Not configured. Run `mindos sync init` to set up.\n'));
1288
1301
  return;
1289
1302
  }
@@ -1296,7 +1309,7 @@ ${bold('Examples:')}
1296
1309
  })()
1297
1310
  : 'never';
1298
1311
 
1299
- console.log(`\n${bold('🔄 Sync Status')}`);
1312
+ console.log(`\n${bold('Sync Status')}`);
1300
1313
  console.log(` ${dim('Provider:')} ${cyan(`${status.provider} (${status.remote})`)}`);
1301
1314
  console.log(` ${dim('Branch:')} ${cyan(status.branch)}`);
1302
1315
  console.log(` ${dim('Last sync:')} ${ago}`);
@@ -1310,68 +1323,64 @@ ${bold('Examples:')}
1310
1323
  },
1311
1324
 
1312
1325
  // ── 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); },
1326
+ file: async () => fileCmd.run(cliArgs, cliFlags),
1327
+ space: async () => spaceCmd.run(cliArgs, cliFlags),
1328
+ ask: async () => askCmd.run(cliArgs, cliFlags),
1329
+ status: async () => statusCmd.run(cliArgs, cliFlags),
1330
+ api: async () => apiCmd.run(cliArgs, cliFlags),
1331
+ agent: async () => agentCmd.run(cliArgs, cliFlags),
1332
+ search: async () => searchCmd.run(cliArgs, cliFlags),
1320
1333
  };
1321
1334
 
1322
1335
  // ── Entry ─────────────────────────────────────────────────────────────────────
1323
1336
 
1324
- const resolvedCmd = (cmd === '--help' || cmd === '-h') ? null : (cmd || (existsSync(CONFIG_PATH) ? getStartMode() : null));
1337
+ const resolvedCmd = (cliFlags.help || cliFlags.h) ? null : (cmd || (existsSync(CONFIG_PATH) ? getStartMode() : null));
1325
1338
 
1326
1339
  if (!resolvedCmd || !commands[resolvedCmd]) {
1327
1340
  const pkgVersion = (() => { try { return JSON.parse(readFileSync(resolve(ROOT, 'package.json'), 'utf-8')).version; } catch { return '?'; } })();
1328
1341
  const row = (c, d) => ` ${cyan(c.padEnd(36))}${dim(d)}`;
1329
- console.log(`
1330
- ${bold('MindOS CLI')} ${dim(`v${pkgVersion}`)}
1331
-
1332
- ${bold('Core:')}
1333
- ${row('mindos onboard', 'Interactive setup (aliases: init, setup)')}
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')}
1338
- ${row('mindos restart', 'Stop then start again')}
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)')}
1350
-
1351
- ${bold('MCP:')}
1352
- ${row('mindos mcp', 'Start MCP server only')}
1353
- ${row('mindos mcp install [agent]', 'Install MCP config into Agent')}
1354
- ${row('mindos token', 'Show auth token and MCP config')}
1355
-
1356
- ${bold('Sync:')}
1357
- ${row('mindos sync', 'Show sync status (init/now/conflicts/on/off)')}
1358
-
1359
- ${bold('Gateway (Background Service):')}
1360
- ${row('mindos gateway <sub>', 'Manage service (install/start/stop/status/logs)')}
1361
-
1362
- ${bold('Config & Diagnostics:')}
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')}
1373
- `);
1374
- const isHelp = (cmd === '--help' || cmd === '-h');
1342
+
1343
+ // Command registry — help is generated entirely from this array.
1344
+ // Modular commands provide meta via their exports; inline commands define meta here.
1345
+ const helpRegistry = [
1346
+ { group: 'Core', usage: 'mindos onboard', summary: 'Interactive setup (aliases: init, setup)' },
1347
+ { group: 'Core', usage: 'mindos start', summary: 'Start app + MCP server (production)' },
1348
+ { group: 'Core', usage: 'mindos start --daemon', summary: 'Start as background OS service' },
1349
+ { group: 'Core', usage: 'mindos dev', summary: 'Start in dev mode' },
1350
+ { group: 'Core', usage: 'mindos stop', summary: 'Stop running processes' },
1351
+ { group: 'Core', usage: 'mindos restart', summary: 'Stop then start again' },
1352
+ { group: 'Core', usage: 'mindos build', summary: 'Build for production' },
1353
+ statusCmd.meta,
1354
+ { group: 'Core', usage: 'mindos open', summary: 'Open Web UI in browser' },
1355
+ fileCmd.meta, spaceCmd.meta, searchCmd.meta, askCmd.meta, agentCmd.meta, apiCmd.meta,
1356
+ { group: 'MCP', usage: 'mindos mcp', summary: 'Start MCP server only' },
1357
+ { group: 'MCP', usage: 'mindos mcp install [agent]', summary: 'Install MCP config into Agent' },
1358
+ { group: 'MCP', usage: 'mindos token', summary: 'Show auth token and MCP config' },
1359
+ { group: 'Sync', usage: 'mindos sync', summary: 'Show sync status (init/now/conflicts/on/off)' },
1360
+ { group: 'Gateway', usage: 'mindos gateway <sub>', summary: 'Manage service (install/start/stop/status/logs)' },
1361
+ { group: 'Config', usage: 'mindos config <sub>', summary: 'View/update config (show/set/unset/validate)' },
1362
+ { group: 'Config', usage: 'mindos doctor', summary: 'Health check' },
1363
+ { group: 'Config', usage: 'mindos update', summary: 'Update to latest version' },
1364
+ { group: 'Config', usage: 'mindos uninstall', summary: 'Fully uninstall MindOS' },
1365
+ { group: 'Config', usage: 'mindos logs', summary: 'Tail service logs' },
1366
+ ];
1367
+
1368
+ const groupLabels = [
1369
+ ['Core', 'Core'], ['Knowledge', 'Knowledge'], ['MCP', 'MCP'],
1370
+ ['Sync', 'Sync'], ['Gateway', 'Gateway (Background Service)'], ['Config', 'Config & Diagnostics'],
1371
+ ];
1372
+ const groups = {};
1373
+ for (const e of helpRegistry) {
1374
+ const g = e.group || 'Other';
1375
+ if (!groups[g]) groups[g] = [];
1376
+ groups[g].push(row(e.usage || `mindos ${e.name}`, e.summary));
1377
+ }
1378
+ const sections = groupLabels
1379
+ .filter(([k]) => groups[k])
1380
+ .map(([k, label]) => `${bold(`${label}:`)}\n${groups[k].join('\n')}`);
1381
+
1382
+ console.log(`\n${bold('MindOS CLI')} ${dim(`v${pkgVersion}`)}\n\n${sections.join('\n\n')}\n\n${bold('Global Flags:')}\n${row('--json', 'Output in JSON (for AI agents)')}\n${row('--help, -h', 'Show help')}\n${row('--version, -v', 'Show version')}\n`);
1383
+ const isHelp = cliFlags.help || cliFlags.h;
1375
1384
  process.exit((cmd && !isHelp) ? 1 : 0);
1376
1385
  }
1377
1386