@geminilight/mindos 0.5.63 → 0.5.65

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 (104) hide show
  1. package/README.md +4 -0
  2. package/README_zh.md +4 -0
  3. package/app/app/api/ask/route.ts +12 -0
  4. package/app/app/api/changes/route.ts +7 -1
  5. package/app/app/api/file/route.ts +9 -0
  6. package/app/app/api/mcp/agents/route.ts +27 -1
  7. package/app/app/api/mcp/install-skill/route.ts +9 -24
  8. package/app/app/api/skills/route.ts +18 -2
  9. package/app/app/api/tree-version/route.ts +8 -0
  10. package/app/app/layout.tsx +1 -0
  11. package/app/app/page.tsx +1 -2
  12. package/app/app/view/[...path]/ViewPageClient.tsx +0 -1
  13. package/app/components/ActivityBar.tsx +2 -2
  14. package/app/components/Backlinks.tsx +5 -5
  15. package/app/components/CreateSpaceModal.tsx +3 -2
  16. package/app/components/DirPicker.tsx +1 -1
  17. package/app/components/DirView.tsx +2 -3
  18. package/app/components/EditorWrapper.tsx +3 -3
  19. package/app/components/FileTree.tsx +25 -10
  20. package/app/components/GuideCard.tsx +4 -4
  21. package/app/components/HomeContent.tsx +44 -14
  22. package/app/components/MarkdownView.tsx +2 -2
  23. package/app/components/OnboardingView.tsx +1 -1
  24. package/app/components/Panel.tsx +1 -1
  25. package/app/components/RightAgentDetailPanel.tsx +2 -1
  26. package/app/components/RightAskPanel.tsx +1 -1
  27. package/app/components/SearchModal.tsx +10 -2
  28. package/app/components/SidebarLayout.tsx +36 -10
  29. package/app/components/ThemeToggle.tsx +1 -1
  30. package/app/components/agents/AgentDetailContent.tsx +454 -59
  31. package/app/components/agents/AgentsContentPage.tsx +89 -20
  32. package/app/components/agents/AgentsMcpSection.tsx +513 -85
  33. package/app/components/agents/AgentsOverviewSection.tsx +418 -59
  34. package/app/components/agents/AgentsPrimitives.tsx +335 -0
  35. package/app/components/agents/AgentsSkillsSection.tsx +746 -105
  36. package/app/components/agents/SkillDetailPopover.tsx +416 -0
  37. package/app/components/agents/agents-content-model.ts +308 -10
  38. package/app/components/ask/AskContent.tsx +34 -5
  39. package/app/components/ask/FileChip.tsx +1 -0
  40. package/app/components/ask/MentionPopover.tsx +13 -1
  41. package/app/components/ask/MessageList.tsx +5 -7
  42. package/app/components/ask/ToolCallBlock.tsx +4 -4
  43. package/app/components/changes/ChangesBanner.tsx +89 -13
  44. package/app/components/changes/ChangesContentPage.tsx +134 -51
  45. package/app/components/echo/EchoHero.tsx +10 -24
  46. package/app/components/echo/EchoInsightCollapsible.tsx +52 -43
  47. package/app/components/echo/EchoPageSections.tsx +13 -9
  48. package/app/components/echo/EchoSegmentNav.tsx +14 -11
  49. package/app/components/echo/EchoSegmentPageClient.tsx +64 -43
  50. package/app/components/explore/ExploreContent.tsx +3 -7
  51. package/app/components/explore/UseCaseCard.tsx +4 -15
  52. package/app/components/panels/AgentsPanel.tsx +22 -128
  53. package/app/components/panels/AgentsPanelAgentDetail.tsx +7 -6
  54. package/app/components/panels/AgentsPanelAgentGroups.tsx +8 -13
  55. package/app/components/panels/AgentsPanelAgentListRow.tsx +39 -16
  56. package/app/components/panels/AgentsPanelHubNav.tsx +12 -12
  57. package/app/components/panels/EchoPanel.tsx +8 -10
  58. package/app/components/panels/PanelNavRow.tsx +9 -2
  59. package/app/components/panels/PluginsPanel.tsx +5 -5
  60. package/app/components/renderers/agent-inspector/AgentInspectorRenderer.tsx +30 -8
  61. package/app/components/renderers/agent-inspector/manifest.ts +5 -3
  62. package/app/components/renderers/config/manifest.ts +1 -0
  63. package/app/components/renderers/csv/manifest.ts +1 -0
  64. package/app/components/renderers/todo/manifest.ts +1 -0
  65. package/app/components/settings/AiTab.tsx +3 -3
  66. package/app/components/settings/AppearanceTab.tsx +2 -2
  67. package/app/components/settings/KnowledgeTab.tsx +3 -3
  68. package/app/components/settings/McpAgentInstall.tsx +3 -6
  69. package/app/components/settings/McpSkillCreateForm.tsx +2 -3
  70. package/app/components/settings/McpSkillRow.tsx +2 -3
  71. package/app/components/settings/McpSkillsSection.tsx +2 -2
  72. package/app/components/settings/McpTab.tsx +12 -13
  73. package/app/components/settings/MonitoringTab.tsx +13 -13
  74. package/app/components/settings/PluginsTab.tsx +6 -5
  75. package/app/components/settings/Primitives.tsx +3 -4
  76. package/app/components/settings/SettingsContent.tsx +3 -3
  77. package/app/components/settings/SyncTab.tsx +11 -17
  78. package/app/components/settings/UpdateTab.tsx +18 -21
  79. package/app/components/settings/types.ts +14 -0
  80. package/app/components/setup/StepKB.tsx +1 -1
  81. package/app/hooks/useMcpData.tsx +7 -4
  82. package/app/hooks/useMention.ts +25 -8
  83. package/app/lib/agent/log.ts +15 -18
  84. package/app/lib/agent/stream-consumer.ts +3 -0
  85. package/app/lib/agent/to-agent-messages.ts +6 -4
  86. package/app/lib/core/agent-audit-log.ts +280 -0
  87. package/app/lib/core/content-changes.ts +148 -8
  88. package/app/lib/core/index.ts +11 -0
  89. package/app/lib/fs.ts +16 -1
  90. package/app/lib/i18n-en.ts +317 -36
  91. package/app/lib/i18n-zh.ts +316 -35
  92. package/app/lib/mcp-agents.ts +273 -2
  93. package/app/lib/renderers/index.ts +1 -2
  94. package/app/lib/renderers/registry.ts +10 -0
  95. package/app/lib/types.ts +2 -0
  96. package/app/next-env.d.ts +1 -1
  97. package/bin/lib/mcp-agents.js +38 -13
  98. package/package.json +1 -1
  99. package/scripts/migrate-agent-audit-log.js +170 -0
  100. package/scripts/migrate-agent-diff.js +146 -0
  101. package/scripts/setup.js +12 -17
  102. package/skills/plugin-core-builtin-migration/SKILL.md +178 -0
  103. package/app/components/renderers/diff/DiffRenderer.tsx +0 -311
  104. package/app/components/renderers/diff/manifest.ts +0 -14
@@ -33,6 +33,13 @@ export interface AgentDef {
33
33
  presenceDirs?: string[];
34
34
  }
35
35
 
36
+ export type SkillInstallMode = 'universal' | 'additional' | 'unsupported';
37
+ export interface SkillAgentRegistration {
38
+ mode: SkillInstallMode;
39
+ /** npx skills `-a` value for additional agents. */
40
+ skillAgentName?: string;
41
+ }
42
+
36
43
  export const MCP_AGENTS: Record<string, AgentDef> = {
37
44
  'claude-code': {
38
45
  name: 'Claude Code',
@@ -161,6 +168,15 @@ export const MCP_AGENTS: Record<string, AgentDef> = {
161
168
  presenceCli: 'qwen',
162
169
  presenceDirs: ['~/.qwen/'],
163
170
  },
171
+ 'qoder': {
172
+ name: 'Qoder',
173
+ project: null,
174
+ global: '~/.qoder.json',
175
+ key: 'mcpServers',
176
+ preferredTransport: 'stdio',
177
+ presenceCli: 'qoder',
178
+ presenceDirs: ['~/.qoder/', '~/.qoder.json'],
179
+ },
164
180
  'trae-cn': {
165
181
  name: 'Trae CN',
166
182
  project: '.trae/mcp.json',
@@ -215,6 +231,259 @@ export const MCP_AGENTS: Record<string, AgentDef> = {
215
231
  },
216
232
  };
217
233
 
234
+ /**
235
+ * Skill-install registry keyed by MCP agent key.
236
+ * Keep in sync with docs and bin/lib/mcp-agents.js.
237
+ */
238
+ export const SKILL_AGENT_REGISTRY: Record<string, SkillAgentRegistration> = {
239
+ 'claude-code': { mode: 'additional', skillAgentName: 'claude-code' },
240
+ 'cursor': { mode: 'universal' },
241
+ 'windsurf': { mode: 'additional', skillAgentName: 'windsurf' },
242
+ 'cline': { mode: 'universal' },
243
+ 'trae': { mode: 'additional', skillAgentName: 'trae' },
244
+ 'gemini-cli': { mode: 'universal' },
245
+ 'openclaw': { mode: 'additional', skillAgentName: 'openclaw' },
246
+ 'codebuddy': { mode: 'additional', skillAgentName: 'codebuddy' },
247
+ 'iflow-cli': { mode: 'additional', skillAgentName: 'iflow-cli' },
248
+ 'kimi-cli': { mode: 'universal' },
249
+ 'opencode': { mode: 'universal' },
250
+ 'pi': { mode: 'additional', skillAgentName: 'pi' },
251
+ 'augment': { mode: 'additional', skillAgentName: 'augment' },
252
+ 'qwen-code': { mode: 'additional', skillAgentName: 'qwen-code' },
253
+ 'qoder': { mode: 'additional', skillAgentName: 'qoder' },
254
+ 'trae-cn': { mode: 'additional', skillAgentName: 'trae-cn' },
255
+ 'roo': { mode: 'additional', skillAgentName: 'roo' },
256
+ 'vscode': { mode: 'universal' },
257
+ 'codex': { mode: 'universal' },
258
+ };
259
+
260
+ export interface SkillWorkspaceProfile {
261
+ mode: SkillInstallMode;
262
+ skillAgentName?: string;
263
+ workspacePath: string;
264
+ }
265
+
266
+ export interface AgentRuntimeSignals {
267
+ hiddenRootPath: string;
268
+ hiddenRootPresent: boolean;
269
+ conversationSignal: boolean;
270
+ usageSignal: boolean;
271
+ lastActivityAt?: string;
272
+ }
273
+
274
+ export interface AgentConfiguredMcpServers {
275
+ servers: string[];
276
+ sources: string[];
277
+ }
278
+
279
+ export interface AgentInstalledSkills {
280
+ skills: string[];
281
+ sourcePath: string;
282
+ }
283
+
284
+ function resolveHiddenRootPath(agent: AgentDef): string {
285
+ const dirs = agent.presenceDirs ?? [];
286
+ for (const entry of dirs) {
287
+ const abs = expandHome(entry);
288
+ if (!fs.existsSync(abs)) continue;
289
+ try {
290
+ const stat = fs.statSync(abs);
291
+ if (stat.isDirectory()) return abs;
292
+ if (stat.isFile()) return path.dirname(abs);
293
+ } catch {
294
+ continue;
295
+ }
296
+ }
297
+ return path.dirname(expandHome(agent.global));
298
+ }
299
+
300
+ function readDirectoryEntries(dir: string): fs.Dirent[] {
301
+ try {
302
+ return fs.readdirSync(dir, { withFileTypes: true });
303
+ } catch {
304
+ return [];
305
+ }
306
+ }
307
+
308
+ function detectSignalsFromName(name: string): { conversation: boolean; usage: boolean } {
309
+ const lowered = name.toLowerCase();
310
+ return {
311
+ conversation: /(session|history|conversation|chat|transcript)/.test(lowered),
312
+ usage: /(usage|token|cost|billing|metric|analytics)/.test(lowered),
313
+ };
314
+ }
315
+
316
+ function readNestedRecord(obj: Record<string, unknown>, nestedPath: string): Record<string, unknown> | null {
317
+ const parts = nestedPath.split('.').filter(Boolean);
318
+ let current: unknown = obj;
319
+ for (const part of parts) {
320
+ if (!current || typeof current !== 'object') return null;
321
+ current = (current as Record<string, unknown>)[part];
322
+ }
323
+ if (!current || typeof current !== 'object') return null;
324
+ return current as Record<string, unknown>;
325
+ }
326
+
327
+ function parseJsonServerNames(content: string, configKey: string, globalNestedKey?: string): string[] {
328
+ try {
329
+ const config = parseJsonc(content) as Record<string, unknown>;
330
+ const section = globalNestedKey
331
+ ? readNestedRecord(config, globalNestedKey)
332
+ : (config[configKey] as unknown);
333
+ if (!section || typeof section !== 'object') return [];
334
+ return Object.keys(section as Record<string, unknown>);
335
+ } catch {
336
+ return [];
337
+ }
338
+ }
339
+
340
+ function parseTomlServerNames(content: string, sectionKey: string): string[] {
341
+ const names = new Set<string>();
342
+ const lines = content.split('\n');
343
+ let inRootSection = false;
344
+ const sectionPrefix = `${sectionKey}.`;
345
+ for (const line of lines) {
346
+ const trimmed = line.trim();
347
+ if (!trimmed || trimmed.startsWith('#')) continue;
348
+ if (trimmed.startsWith('[') && trimmed.endsWith(']')) {
349
+ const section = trimmed.slice(1, -1).trim();
350
+ inRootSection = section === sectionKey;
351
+ if (section.startsWith(sectionPrefix)) {
352
+ const name = section.slice(sectionPrefix.length).split('.')[0]?.trim();
353
+ if (name) names.add(name);
354
+ }
355
+ continue;
356
+ }
357
+ if (!inRootSection) continue;
358
+ const kv = trimmed.match(/^([a-zA-Z0-9_-]+)\s*=\s*/);
359
+ if (!kv) continue;
360
+ const name = kv[1]?.trim();
361
+ if (name) names.add(name);
362
+ }
363
+ return [...names];
364
+ }
365
+
366
+ export function resolveSkillWorkspaceProfile(agentKey: string): SkillWorkspaceProfile {
367
+ const registration = SKILL_AGENT_REGISTRY[agentKey] ?? { mode: 'unsupported' as const };
368
+ if (registration.mode === 'universal') {
369
+ return { mode: registration.mode, workspacePath: expandHome('~/.agents/skills') };
370
+ }
371
+ const agent = MCP_AGENTS[agentKey];
372
+ const root = agent ? resolveHiddenRootPath(agent) : expandHome('~/.agents');
373
+ const workspacePath = path.join(root, 'skills');
374
+ return {
375
+ mode: registration.mode,
376
+ skillAgentName: registration.skillAgentName,
377
+ workspacePath,
378
+ };
379
+ }
380
+
381
+ export function detectAgentConfiguredMcpServers(agentKey: string): AgentConfiguredMcpServers {
382
+ const agent = MCP_AGENTS[agentKey];
383
+ if (!agent) return { servers: [], sources: [] };
384
+ const serverSet = new Set<string>();
385
+ const sources: string[] = [];
386
+ for (const [scopeType, cfgPath] of [['global', agent.global], ['project', agent.project]] as Array<[string, string | null]>) {
387
+ if (!cfgPath) continue;
388
+ const absPath = expandHome(cfgPath);
389
+ if (!fs.existsSync(absPath)) continue;
390
+ try {
391
+ const content = fs.readFileSync(absPath, 'utf-8');
392
+ const nestedPath = scopeType === 'global' ? agent.globalNestedKey : undefined;
393
+ const names =
394
+ agent.format === 'toml'
395
+ ? parseTomlServerNames(content, agent.key)
396
+ : parseJsonServerNames(content, agent.key, nestedPath);
397
+ for (const name of names) serverSet.add(name);
398
+ sources.push(`${scopeType}:${cfgPath}`);
399
+ } catch {
400
+ continue;
401
+ }
402
+ }
403
+ return {
404
+ servers: [...serverSet].sort((a, b) => a.localeCompare(b)),
405
+ sources,
406
+ };
407
+ }
408
+
409
+ export function detectAgentInstalledSkills(agentKey: string): AgentInstalledSkills {
410
+ const profile = resolveSkillWorkspaceProfile(agentKey);
411
+ const sourcePath = profile.workspacePath;
412
+ if (!fs.existsSync(sourcePath)) return { skills: [], sourcePath };
413
+ let entries: fs.Dirent[] = [];
414
+ try {
415
+ entries = fs.readdirSync(sourcePath, { withFileTypes: true });
416
+ } catch {
417
+ return { skills: [], sourcePath };
418
+ }
419
+ const skills = entries
420
+ .filter((entry) => (entry.isDirectory() || entry.isSymbolicLink()) && !entry.name.startsWith('.'))
421
+ .map((entry) => entry.name)
422
+ .sort((a, b) => a.localeCompare(b));
423
+ return { skills, sourcePath };
424
+ }
425
+
426
+ export function detectAgentRuntimeSignals(agentKey: string): AgentRuntimeSignals {
427
+ const agent = MCP_AGENTS[agentKey];
428
+ if (!agent) {
429
+ return {
430
+ hiddenRootPath: '',
431
+ hiddenRootPresent: false,
432
+ conversationSignal: false,
433
+ usageSignal: false,
434
+ };
435
+ }
436
+ const hiddenRootPath = resolveHiddenRootPath(agent);
437
+ if (!fs.existsSync(hiddenRootPath)) {
438
+ return {
439
+ hiddenRootPath,
440
+ hiddenRootPresent: false,
441
+ conversationSignal: false,
442
+ usageSignal: false,
443
+ };
444
+ }
445
+
446
+ const maxDepth = 3;
447
+ const maxEntries = 300;
448
+ let scanned = 0;
449
+ let conversationSignal = false;
450
+ let usageSignal = false;
451
+ let latestMtime = 0;
452
+ const queue: Array<{ dir: string; depth: number }> = [{ dir: hiddenRootPath, depth: 0 }];
453
+
454
+ while (queue.length > 0 && scanned < maxEntries) {
455
+ const current = queue.shift();
456
+ if (!current) break;
457
+ const entries = readDirectoryEntries(current.dir);
458
+ for (const entry of entries) {
459
+ if (scanned >= maxEntries) break;
460
+ scanned += 1;
461
+ if (entry.name === 'node_modules' || entry.name === '.git') continue;
462
+ const fullPath = path.join(current.dir, entry.name);
463
+ try {
464
+ const stat = fs.statSync(fullPath);
465
+ if (stat.mtimeMs > latestMtime) latestMtime = stat.mtimeMs;
466
+ const signals = detectSignalsFromName(entry.name);
467
+ if (signals.conversation) conversationSignal = true;
468
+ if (signals.usage) usageSignal = true;
469
+ if (entry.isDirectory() && current.depth < maxDepth) {
470
+ queue.push({ dir: fullPath, depth: current.depth + 1 });
471
+ }
472
+ } catch {
473
+ continue;
474
+ }
475
+ }
476
+ }
477
+
478
+ return {
479
+ hiddenRootPath,
480
+ hiddenRootPresent: true,
481
+ conversationSignal,
482
+ usageSignal,
483
+ lastActivityAt: latestMtime > 0 ? new Date(latestMtime).toISOString() : undefined,
484
+ };
485
+ }
486
+
218
487
  /* ── MindOS MCP Install Detection ──────────────────────────────────────── */
219
488
 
220
489
  export function detectInstalled(agentKey: string): { installed: boolean; scope?: string; transport?: string; configPath?: string } {
@@ -238,9 +507,11 @@ export function detectInstalled(agentKey: string): { installed: boolean; scope?:
238
507
  } else {
239
508
  // JSON format (default)
240
509
  const config = parseJsonc(content);
241
- const servers = config[agent.key];
510
+ const servers = scopeType === 'global' && agent.globalNestedKey
511
+ ? readNestedRecord(config as Record<string, unknown>, agent.globalNestedKey)
512
+ : (config[agent.key] as Record<string, unknown> | undefined);
242
513
  if (servers?.mindos) {
243
- const entry = servers.mindos;
514
+ const entry = servers.mindos as Record<string, unknown>;
244
515
  const transport = entry.type === 'stdio' ? 'stdio' : entry.url ? 'http' : 'unknown';
245
516
  return { installed: true, scope: scopeType, transport, configPath: cfgPath };
246
517
  }
@@ -7,7 +7,6 @@ import { manifest as agentInspector } from '@/components/renderers/agent-inspect
7
7
  import { manifest as backlinks } from '@/components/renderers/backlinks/manifest';
8
8
  import { manifest as config } from '@/components/renderers/config/manifest';
9
9
  import { manifest as csv } from '@/components/renderers/csv/manifest';
10
- import { manifest as diff } from '@/components/renderers/diff/manifest';
11
10
  import { manifest as summary } from '@/components/renderers/summary/manifest';
12
11
  import { manifest as timeline } from '@/components/renderers/timeline/manifest';
13
12
  import { manifest as todo } from '@/components/renderers/todo/manifest';
@@ -15,7 +14,7 @@ import { manifest as workflow } from '@/components/renderers/workflow/manifest';
15
14
  import { manifest as graph } from '@/components/renderers/graph/manifest';
16
15
 
17
16
  const manifests = [
18
- agentInspector, backlinks, config, csv, diff, summary, timeline, todo, workflow, graph,
17
+ agentInspector, backlinks, config, csv, summary, timeline, todo, workflow, graph,
19
18
  ];
20
19
 
21
20
  for (const m of manifests) {
@@ -16,6 +16,11 @@ export interface RendererDefinition {
16
16
  tags: string[];
17
17
  builtin: boolean; // true = ships with MindOS; false = user-installed (future)
18
18
  core?: boolean; // true = default renderer for a file type, cannot be disabled by user
19
+ /**
20
+ * App-builtin feature (not a user-facing plugin).
21
+ * When true, keep renderer functional but hide from Plugins surfaces.
22
+ */
23
+ appBuiltinFeature?: boolean;
19
24
  entryPath?: string; // canonical entry file shown on home page (e.g. 'TODO.md')
20
25
  match: (ctx: Pick<RendererContext, 'filePath' | 'extension'>) => boolean;
21
26
  // Provide either `component` (eager) or `load` (lazy). Prefer `load` for code-splitting.
@@ -78,3 +83,8 @@ export function resolveRenderer(
78
83
  export function getAllRenderers(): RendererDefinition[] {
79
84
  return registry;
80
85
  }
86
+
87
+ /** User-facing plugins only (exclude app-builtin features like CSV). */
88
+ export function getPluginRenderers(): RendererDefinition[] {
89
+ return registry.filter((r) => !r.appBuiltinFeature);
90
+ }
package/app/lib/types.ts CHANGED
@@ -37,6 +37,8 @@ export type MessagePart = TextPart | ToolCallPart | ReasoningPart;
37
37
  export interface Message {
38
38
  role: 'user' | 'assistant';
39
39
  content: string;
40
+ /** Unix timestamp in milliseconds when this message was created */
41
+ timestamp?: number;
40
42
  /** Structured parts for assistant messages (tool calls + text segments) */
41
43
  parts?: MessagePart[];
42
44
  }
package/app/next-env.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  /// <reference types="next" />
2
2
  /// <reference types="next/image-types/global" />
3
- import "./.next/dev/types/routes.d.ts";
3
+ import "./.next/types/routes.d.ts";
4
4
 
5
5
  // NOTE: This file should not be edited
6
6
  // see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
@@ -26,16 +26,6 @@ export const MCP_AGENTS = {
26
26
  presenceCli: 'claude',
27
27
  presenceDirs: ['~/.claude/'],
28
28
  },
29
- 'claude-desktop': {
30
- name: 'Claude Desktop',
31
- project: null,
32
- global: process.platform === 'darwin'
33
- ? '~/Library/Application Support/Claude/claude_desktop_config.json'
34
- : '~/.config/Claude/claude_desktop_config.json',
35
- key: 'mcpServers',
36
- preferredTransport: 'http',
37
- presenceDirs: ['~/Library/Application Support/Claude/', '~/.config/Claude/'],
38
- },
39
29
  'cursor': {
40
30
  name: 'Cursor',
41
31
  project: '.cursor/mcp.json',
@@ -94,11 +84,11 @@ export const MCP_AGENTS = {
94
84
  'codebuddy': {
95
85
  name: 'CodeBuddy',
96
86
  project: null,
97
- global: '~/.claude-internal/.claude.json',
87
+ global: '~/.codebuddy/mcp.json',
98
88
  key: 'mcpServers',
99
89
  preferredTransport: 'stdio',
100
- presenceCli: 'claude-internal',
101
- presenceDirs: ['~/.claude-internal/'],
90
+ presenceCli: 'codebuddy',
91
+ presenceDirs: ['~/.codebuddy/'],
102
92
  },
103
93
  'iflow-cli': {
104
94
  name: 'iFlow CLI',
@@ -154,6 +144,15 @@ export const MCP_AGENTS = {
154
144
  presenceCli: 'qwen',
155
145
  presenceDirs: ['~/.qwen/'],
156
146
  },
147
+ 'qoder': {
148
+ name: 'Qoder',
149
+ project: null,
150
+ global: '~/.qoder.json',
151
+ key: 'mcpServers',
152
+ preferredTransport: 'stdio',
153
+ presenceCli: 'qoder',
154
+ presenceDirs: ['~/.qoder/', '~/.qoder.json'],
155
+ },
157
156
  'trae-cn': {
158
157
  name: 'Trae CN',
159
158
  project: '.trae/mcp.json',
@@ -183,6 +182,32 @@ export const MCP_AGENTS = {
183
182
  },
184
183
  };
185
184
 
185
+ /**
186
+ * Skill-install registry keyed by MCP agent key.
187
+ * Keep in sync with app/lib/mcp-agents.ts.
188
+ */
189
+ export const SKILL_AGENT_REGISTRY = {
190
+ 'claude-code': { mode: 'additional', skillAgentName: 'claude-code' },
191
+ 'cursor': { mode: 'universal' },
192
+ 'windsurf': { mode: 'additional', skillAgentName: 'windsurf' },
193
+ 'cline': { mode: 'universal' },
194
+ 'trae': { mode: 'additional', skillAgentName: 'trae' },
195
+ 'gemini-cli': { mode: 'universal' },
196
+ 'openclaw': { mode: 'additional', skillAgentName: 'openclaw' },
197
+ 'codebuddy': { mode: 'additional', skillAgentName: 'codebuddy' },
198
+ 'iflow-cli': { mode: 'additional', skillAgentName: 'iflow-cli' },
199
+ 'kimi-cli': { mode: 'universal' },
200
+ 'opencode': { mode: 'universal' },
201
+ 'pi': { mode: 'additional', skillAgentName: 'pi' },
202
+ 'augment': { mode: 'additional', skillAgentName: 'augment' },
203
+ 'qwen-code': { mode: 'additional', skillAgentName: 'qwen-code' },
204
+ 'qoder': { mode: 'additional', skillAgentName: 'qoder' },
205
+ 'trae-cn': { mode: 'additional', skillAgentName: 'trae-cn' },
206
+ 'roo': { mode: 'additional', skillAgentName: 'roo' },
207
+ 'vscode': { mode: 'universal' },
208
+ 'codex': { mode: 'universal' },
209
+ };
210
+
186
211
  export function detectAgentPresence(agentKey) {
187
212
  const agent = MCP_AGENTS[agentKey];
188
213
  if (!agent) return false;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@geminilight/mindos",
3
- "version": "0.5.63",
3
+ "version": "0.5.65",
4
4
  "description": "MindOS — Human-Agent Collaborative Mind System. Local-first knowledge base that syncs your mind to all AI Agents via MCP.",
5
5
  "keywords": [
6
6
  "mindos",
@@ -0,0 +1,170 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * One-shot migration helper:
4
+ * Import legacy Agent-Audit.md and .agent-log.json into .mindos/agent-audit-log.json.
5
+ *
6
+ * Usage:
7
+ * node scripts/migrate-agent-audit-log.js --mind-root /abs/path/to/mindRoot
8
+ */
9
+
10
+ import fs from 'fs';
11
+ import path from 'path';
12
+
13
+ const MAX_EVENTS = 1000;
14
+
15
+ function parseArgs(argv) {
16
+ let mindRoot = '';
17
+ for (let i = 0; i < argv.length; i++) {
18
+ if (argv[i] === '--mind-root') {
19
+ mindRoot = argv[i + 1] || '';
20
+ i += 1;
21
+ }
22
+ }
23
+ return { mindRoot };
24
+ }
25
+
26
+ function nowIso() {
27
+ return new Date().toISOString();
28
+ }
29
+
30
+ function validIso(ts) {
31
+ if (typeof ts !== 'string') return nowIso();
32
+ const ms = new Date(ts).getTime();
33
+ return Number.isFinite(ms) ? new Date(ms).toISOString() : nowIso();
34
+ }
35
+
36
+ function normalizeMessage(message) {
37
+ if (typeof message !== 'string') return undefined;
38
+ return message.slice(0, 2000);
39
+ }
40
+
41
+ function readJson(file, fallback) {
42
+ try {
43
+ if (!fs.existsSync(file)) return fallback;
44
+ return JSON.parse(fs.readFileSync(file, 'utf-8'));
45
+ } catch {
46
+ return fallback;
47
+ }
48
+ }
49
+
50
+ function parseMdBlocks(raw) {
51
+ const blocks = [];
52
+ const re = /```agent-op\s*\n([\s\S]*?)```/g;
53
+ let match;
54
+ while ((match = re.exec(raw)) !== null) {
55
+ try {
56
+ blocks.push(JSON.parse(match[1].trim()));
57
+ } catch {
58
+ // Keep migration best-effort.
59
+ }
60
+ }
61
+ return blocks;
62
+ }
63
+
64
+ function parseJsonLines(raw) {
65
+ const lines = [];
66
+ for (const line of raw.split('\n')) {
67
+ const trimmed = line.trim();
68
+ if (!trimmed || trimmed.startsWith('#') || trimmed.startsWith('//')) continue;
69
+ try {
70
+ lines.push(JSON.parse(trimmed));
71
+ } catch {
72
+ // Ignore malformed line.
73
+ }
74
+ }
75
+ return lines;
76
+ }
77
+
78
+ function toEvent(entry, op, idx) {
79
+ const tool = typeof entry?.tool === 'string' && entry.tool.trim() ? entry.tool.trim() : 'unknown-tool';
80
+ return {
81
+ id: `legacy-script-${Date.now().toString(36)}-${idx.toString(36)}`,
82
+ ts: validIso(entry?.ts),
83
+ tool,
84
+ params: entry?.params && typeof entry.params === 'object' ? entry.params : {},
85
+ result: entry?.result === 'error' ? 'error' : 'ok',
86
+ message: normalizeMessage(entry?.message),
87
+ durationMs: typeof entry?.durationMs === 'number' ? entry.durationMs : undefined,
88
+ op,
89
+ };
90
+ }
91
+
92
+ function main() {
93
+ const { mindRoot } = parseArgs(process.argv.slice(2));
94
+ if (!mindRoot) {
95
+ console.error('Missing --mind-root');
96
+ process.exit(1);
97
+ }
98
+ const root = path.resolve(mindRoot);
99
+ if (!fs.existsSync(root) || !fs.statSync(root).isDirectory()) {
100
+ console.error('Invalid mind root:', root);
101
+ process.exit(1);
102
+ }
103
+
104
+ const legacyMd = path.join(root, 'Agent-Audit.md');
105
+ const legacyJsonl = path.join(root, '.agent-log.json');
106
+ if (!fs.existsSync(legacyMd) && !fs.existsSync(legacyJsonl)) {
107
+ console.log('No legacy Agent-Audit.md or .agent-log.json found. Nothing to migrate.');
108
+ return;
109
+ }
110
+
111
+ const logDir = path.join(root, '.mindos');
112
+ const logFile = path.join(logDir, 'agent-audit-log.json');
113
+ const state = readJson(logFile, {
114
+ version: 1,
115
+ events: [],
116
+ legacy: { mdImportedCount: 0, jsonlImportedCount: 0, lastImportedAt: null },
117
+ });
118
+ const baseEvents = Array.isArray(state.events) ? state.events : [];
119
+ const legacy = {
120
+ mdImportedCount: Number(state?.legacy?.mdImportedCount || 0),
121
+ jsonlImportedCount: Number(state?.legacy?.jsonlImportedCount || 0),
122
+ lastImportedAt: typeof state?.legacy?.lastImportedAt === 'string' ? state.legacy.lastImportedAt : null,
123
+ };
124
+
125
+ const imported = [];
126
+
127
+ if (fs.existsSync(legacyMd)) {
128
+ const blocks = parseMdBlocks(fs.readFileSync(legacyMd, 'utf-8'));
129
+ if (blocks.length > legacy.mdImportedCount) {
130
+ const incoming = blocks.slice(legacy.mdImportedCount);
131
+ imported.push(...incoming.map((entry, idx) => toEvent(entry, 'legacy_agent_audit_md_import', idx)));
132
+ legacy.mdImportedCount = blocks.length;
133
+ legacy.lastImportedAt = nowIso();
134
+ }
135
+ if (blocks.length > 0) fs.rmSync(legacyMd, { force: true });
136
+ }
137
+
138
+ if (fs.existsSync(legacyJsonl)) {
139
+ const lines = parseJsonLines(fs.readFileSync(legacyJsonl, 'utf-8'));
140
+ if (lines.length > legacy.jsonlImportedCount) {
141
+ const incoming = lines.slice(legacy.jsonlImportedCount);
142
+ imported.push(...incoming.map((entry, idx) => toEvent(entry, 'legacy_agent_log_jsonl_import', idx)));
143
+ legacy.jsonlImportedCount = lines.length;
144
+ legacy.lastImportedAt = nowIso();
145
+ }
146
+ if (lines.length > 0) fs.rmSync(legacyJsonl, { force: true });
147
+ }
148
+
149
+ if (imported.length === 0) {
150
+ console.log('No new legacy entries to import.');
151
+ return;
152
+ }
153
+
154
+ const merged = [...baseEvents, ...imported]
155
+ .sort((a, b) => new Date(b.ts).getTime() - new Date(a.ts).getTime())
156
+ .slice(0, MAX_EVENTS);
157
+
158
+ const next = {
159
+ version: 1,
160
+ events: merged,
161
+ legacy,
162
+ };
163
+
164
+ fs.mkdirSync(logDir, { recursive: true });
165
+ fs.writeFileSync(logFile, JSON.stringify(next, null, 2), 'utf-8');
166
+ console.log(`Imported ${imported.length} legacy entry(s) into ${logFile}`);
167
+ }
168
+
169
+ main();
170
+