@geminilight/mindos 0.6.71 → 0.6.73

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 (227) hide show
  1. package/_standalone/.mindos-build-version +1 -1
  2. package/_standalone/.next/BUILD_ID +1 -1
  3. package/_standalone/.next/app-path-routes-manifest.json +27 -27
  4. package/_standalone/.next/build-manifest.json +3 -3
  5. package/_standalone/.next/cache/.previewinfo +1 -1
  6. package/_standalone/.next/cache/.rscinfo +1 -1
  7. package/_standalone/.next/cache/config.json +3 -3
  8. package/_standalone/.next/prerender-manifest.json +3 -3
  9. package/_standalone/.next/react-loadable-manifest.json +4 -4
  10. package/_standalone/.next/server/app/.well-known/agent-card.json/route_client-reference-manifest.js +1 -1
  11. package/_standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
  12. package/_standalone/.next/server/app/_global-error.html +2 -2
  13. package/_standalone/.next/server/app/_global-error.rsc +1 -1
  14. package/_standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  15. package/_standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  16. package/_standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  17. package/_standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  18. package/_standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  19. package/_standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  20. package/_standalone/.next/server/app/_not-found/page.js +1 -1
  21. package/_standalone/.next/server/app/_not-found/page.js.nft.json +1 -1
  22. package/_standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  23. package/_standalone/.next/server/app/agents/[agentKey]/page.js +1 -1
  24. package/_standalone/.next/server/app/agents/[agentKey]/page.js.nft.json +1 -1
  25. package/_standalone/.next/server/app/agents/[agentKey]/page_client-reference-manifest.js +1 -1
  26. package/_standalone/.next/server/app/agents/page.js +1 -1
  27. package/_standalone/.next/server/app/agents/page.js.nft.json +1 -1
  28. package/_standalone/.next/server/app/agents/page_client-reference-manifest.js +1 -1
  29. package/_standalone/.next/server/app/api/a2a/agents/route_client-reference-manifest.js +1 -1
  30. package/_standalone/.next/server/app/api/a2a/delegations/route_client-reference-manifest.js +1 -1
  31. package/_standalone/.next/server/app/api/a2a/discover/route_client-reference-manifest.js +1 -1
  32. package/_standalone/.next/server/app/api/a2a/route_client-reference-manifest.js +1 -1
  33. package/_standalone/.next/server/app/api/acp/config/route_client-reference-manifest.js +1 -1
  34. package/_standalone/.next/server/app/api/acp/detect/route_client-reference-manifest.js +1 -1
  35. package/_standalone/.next/server/app/api/acp/install/route_client-reference-manifest.js +1 -1
  36. package/_standalone/.next/server/app/api/acp/registry/route_client-reference-manifest.js +1 -1
  37. package/_standalone/.next/server/app/api/acp/session/route_client-reference-manifest.js +1 -1
  38. package/_standalone/.next/server/app/api/agent-activity/route_client-reference-manifest.js +1 -1
  39. package/_standalone/.next/server/app/api/agents/copy-skill/route_client-reference-manifest.js +1 -1
  40. package/_standalone/.next/server/app/api/agents/custom/detect/route_client-reference-manifest.js +1 -1
  41. package/_standalone/.next/server/app/api/agents/custom/route_client-reference-manifest.js +1 -1
  42. package/_standalone/.next/server/app/api/ask/route_client-reference-manifest.js +1 -1
  43. package/_standalone/.next/server/app/api/ask-sessions/route_client-reference-manifest.js +1 -1
  44. package/_standalone/.next/server/app/api/auth/route_client-reference-manifest.js +1 -1
  45. package/_standalone/.next/server/app/api/backlinks/route_client-reference-manifest.js +1 -1
  46. package/_standalone/.next/server/app/api/bootstrap/route_client-reference-manifest.js +1 -1
  47. package/_standalone/.next/server/app/api/changes/route_client-reference-manifest.js +1 -1
  48. package/_standalone/.next/server/app/api/channels/verify/route_client-reference-manifest.js +1 -1
  49. package/_standalone/.next/server/app/api/connect/route_client-reference-manifest.js +1 -1
  50. package/_standalone/.next/server/app/api/embedding/route_client-reference-manifest.js +1 -1
  51. package/_standalone/.next/server/app/api/export/route_client-reference-manifest.js +1 -1
  52. package/_standalone/.next/server/app/api/extract-pdf/route_client-reference-manifest.js +1 -1
  53. package/_standalone/.next/server/app/api/file/import/route_client-reference-manifest.js +1 -1
  54. package/_standalone/.next/server/app/api/file/raw/route_client-reference-manifest.js +1 -1
  55. package/_standalone/.next/server/app/api/file/route_client-reference-manifest.js +1 -1
  56. package/_standalone/.next/server/app/api/files/route_client-reference-manifest.js +1 -1
  57. package/_standalone/.next/server/app/api/git/route_client-reference-manifest.js +1 -1
  58. package/_standalone/.next/server/app/api/graph/route_client-reference-manifest.js +1 -1
  59. package/_standalone/.next/server/app/api/health/route_client-reference-manifest.js +1 -1
  60. package/_standalone/.next/server/app/api/im/activity/route_client-reference-manifest.js +1 -1
  61. package/_standalone/.next/server/app/api/im/config/route_client-reference-manifest.js +1 -1
  62. package/_standalone/.next/server/app/api/im/status/route_client-reference-manifest.js +1 -1
  63. package/_standalone/.next/server/app/api/im/test/route_client-reference-manifest.js +1 -1
  64. package/_standalone/.next/server/app/api/im/webhook/feishu/route_client-reference-manifest.js +1 -1
  65. package/_standalone/.next/server/app/api/im/webhook-status/route_client-reference-manifest.js +1 -1
  66. package/_standalone/.next/server/app/api/inbox/clip/route_client-reference-manifest.js +1 -1
  67. package/_standalone/.next/server/app/api/inbox/route_client-reference-manifest.js +1 -1
  68. package/_standalone/.next/server/app/api/init/route_client-reference-manifest.js +1 -1
  69. package/_standalone/.next/server/app/api/lint/route_client-reference-manifest.js +1 -1
  70. package/_standalone/.next/server/app/api/mcp/agents/route_client-reference-manifest.js +1 -1
  71. package/_standalone/.next/server/app/api/mcp/direct-tools/route_client-reference-manifest.js +1 -1
  72. package/_standalone/.next/server/app/api/mcp/install/route_client-reference-manifest.js +1 -1
  73. package/_standalone/.next/server/app/api/mcp/install-skill/route_client-reference-manifest.js +1 -1
  74. package/_standalone/.next/server/app/api/mcp/restart/route_client-reference-manifest.js +1 -1
  75. package/_standalone/.next/server/app/api/mcp/status/route_client-reference-manifest.js +1 -1
  76. package/_standalone/.next/server/app/api/mcp/tools/route_client-reference-manifest.js +1 -1
  77. package/_standalone/.next/server/app/api/mcp/uninstall/route_client-reference-manifest.js +1 -1
  78. package/_standalone/.next/server/app/api/monitoring/route_client-reference-manifest.js +1 -1
  79. package/_standalone/.next/server/app/api/recent-files/route_client-reference-manifest.js +1 -1
  80. package/_standalone/.next/server/app/api/restart/route_client-reference-manifest.js +1 -1
  81. package/_standalone/.next/server/app/api/search/route_client-reference-manifest.js +1 -1
  82. package/_standalone/.next/server/app/api/settings/list-models/route_client-reference-manifest.js +1 -1
  83. package/_standalone/.next/server/app/api/settings/reset-token/route_client-reference-manifest.js +1 -1
  84. package/_standalone/.next/server/app/api/settings/route_client-reference-manifest.js +1 -1
  85. package/_standalone/.next/server/app/api/settings/test-key/route_client-reference-manifest.js +1 -1
  86. package/_standalone/.next/server/app/api/setup/check-path/route_client-reference-manifest.js +1 -1
  87. package/_standalone/.next/server/app/api/setup/check-port/route_client-reference-manifest.js +1 -1
  88. package/_standalone/.next/server/app/api/setup/generate-token/route_client-reference-manifest.js +1 -1
  89. package/_standalone/.next/server/app/api/setup/ls/route_client-reference-manifest.js +1 -1
  90. package/_standalone/.next/server/app/api/setup/route_client-reference-manifest.js +1 -1
  91. package/_standalone/.next/server/app/api/skills/route_client-reference-manifest.js +1 -1
  92. package/_standalone/.next/server/app/api/space-overview/route_client-reference-manifest.js +1 -1
  93. package/_standalone/.next/server/app/api/sync/route_client-reference-manifest.js +1 -1
  94. package/_standalone/.next/server/app/api/tree-version/route_client-reference-manifest.js +1 -1
  95. package/_standalone/.next/server/app/api/uninstall/route_client-reference-manifest.js +1 -1
  96. package/_standalone/.next/server/app/api/update/route_client-reference-manifest.js +1 -1
  97. package/_standalone/.next/server/app/api/update-check/route_client-reference-manifest.js +1 -1
  98. package/_standalone/.next/server/app/api/update-status/route_client-reference-manifest.js +1 -1
  99. package/_standalone/.next/server/app/api/workflows/route_client-reference-manifest.js +1 -1
  100. package/_standalone/.next/server/app/changelog/page.js +1 -1
  101. package/_standalone/.next/server/app/changelog/page.js.nft.json +1 -1
  102. package/_standalone/.next/server/app/changelog/page_client-reference-manifest.js +1 -1
  103. package/_standalone/.next/server/app/changes/page.js +1 -1
  104. package/_standalone/.next/server/app/changes/page.js.nft.json +1 -1
  105. package/_standalone/.next/server/app/changes/page_client-reference-manifest.js +1 -1
  106. package/_standalone/.next/server/app/echo/[segment]/page.js +1 -1
  107. package/_standalone/.next/server/app/echo/[segment]/page.js.nft.json +1 -1
  108. package/_standalone/.next/server/app/echo/[segment]/page_client-reference-manifest.js +1 -1
  109. package/_standalone/.next/server/app/echo/page.js +1 -1
  110. package/_standalone/.next/server/app/echo/page.js.nft.json +1 -1
  111. package/_standalone/.next/server/app/echo/page_client-reference-manifest.js +1 -1
  112. package/_standalone/.next/server/app/explore/page.js +1 -1
  113. package/_standalone/.next/server/app/explore/page.js.nft.json +1 -1
  114. package/_standalone/.next/server/app/explore/page_client-reference-manifest.js +1 -1
  115. package/_standalone/.next/server/app/help/page.js +1 -1
  116. package/_standalone/.next/server/app/help/page.js.nft.json +1 -1
  117. package/_standalone/.next/server/app/help/page_client-reference-manifest.js +1 -1
  118. package/_standalone/.next/server/app/inbox/history/page.js +1 -1
  119. package/_standalone/.next/server/app/inbox/history/page.js.nft.json +1 -1
  120. package/_standalone/.next/server/app/inbox/history/page_client-reference-manifest.js +1 -1
  121. package/_standalone/.next/server/app/login/page.js +1 -1
  122. package/_standalone/.next/server/app/login/page.js.nft.json +1 -1
  123. package/_standalone/.next/server/app/login/page_client-reference-manifest.js +1 -1
  124. package/_standalone/.next/server/app/page.js +1 -1
  125. package/_standalone/.next/server/app/page.js.nft.json +1 -1
  126. package/_standalone/.next/server/app/page_client-reference-manifest.js +1 -1
  127. package/_standalone/.next/server/app/setup/page.js +1 -1
  128. package/_standalone/.next/server/app/setup/page.js.nft.json +1 -1
  129. package/_standalone/.next/server/app/setup/page_client-reference-manifest.js +1 -1
  130. package/_standalone/.next/server/app/trash/page.js +2 -2
  131. package/_standalone/.next/server/app/trash/page_client-reference-manifest.js +1 -1
  132. package/_standalone/.next/server/app/view/[...path]/page.js +2 -2
  133. package/_standalone/.next/server/app/view/[...path]/page.js.nft.json +1 -1
  134. package/_standalone/.next/server/app/view/[...path]/page_client-reference-manifest.js +1 -1
  135. package/_standalone/.next/server/app-paths-manifest.json +27 -27
  136. package/_standalone/.next/server/chunks/{3311.js → 2449.js} +2 -2
  137. package/_standalone/.next/server/chunks/5299.js +1 -1
  138. package/_standalone/.next/server/chunks/6022.js +34 -34
  139. package/_standalone/.next/server/middleware-build-manifest.js +1 -1
  140. package/_standalone/.next/server/middleware-react-loadable-manifest.js +1 -1
  141. package/_standalone/.next/server/pages/500.html +2 -2
  142. package/_standalone/.next/server/server-reference-manifest.js +1 -1
  143. package/_standalone/.next/server/server-reference-manifest.json +1 -1
  144. package/_standalone/.next/static/chunks/{7143.879daa87569c5b02.js → 4094.09364c01df411380.js} +1 -1
  145. package/_standalone/.next/static/chunks/{5795.d9099a1afecd6047.js → 5331.c89084fd7f67887d.js} +2 -2
  146. package/_standalone/.next/static/chunks/app/{layout-a344709b8447be75.js → layout-fcbde5bee626d21a.js} +63 -63
  147. package/_standalone/.next/static/chunks/app/trash/page-e623ff0ab35de002.js +1 -0
  148. package/_standalone/.next/static/chunks/app/view/[...path]/page-49c4eff6ffdb5168.js +12 -0
  149. package/_standalone/.next/static/chunks/{webpack-2f2787d3469d3df1.js → webpack-dc486b68118d1328.js} +1 -1
  150. package/_standalone/.next/trace +72 -72
  151. package/_standalone/package-lock.json +2 -2
  152. package/_standalone/package.json +1 -1
  153. package/app/package.json +1 -1
  154. package/package.json +1 -1
  155. package/_standalone/.next/static/chunks/app/trash/page-0907fdd06a4467de.js +0 -1
  156. package/_standalone/.next/static/chunks/app/view/[...path]/page-f53ce199b4a4bbb5.js +0 -12
  157. package/browser-extension/README.md +0 -160
  158. package/browser-extension/build.mjs +0 -63
  159. package/browser-extension/extension/background/service-worker.js +0 -1
  160. package/browser-extension/extension/content/extractor.js +0 -2
  161. package/browser-extension/extension/icons/icon-128.png +0 -0
  162. package/browser-extension/extension/icons/icon-128.svg +0 -4
  163. package/browser-extension/extension/icons/icon-16.png +0 -0
  164. package/browser-extension/extension/icons/icon-16.svg +0 -4
  165. package/browser-extension/extension/icons/icon-32.png +0 -0
  166. package/browser-extension/extension/icons/icon-32.svg +0 -4
  167. package/browser-extension/extension/icons/icon-48.png +0 -0
  168. package/browser-extension/extension/icons/icon-48.svg +0 -4
  169. package/browser-extension/extension/manifest.json +0 -47
  170. package/browser-extension/extension/popup/popup.css +0 -510
  171. package/browser-extension/extension/popup/popup.html +0 -128
  172. package/browser-extension/extension/popup/popup.js +0 -73
  173. package/browser-extension/package-lock.json +0 -617
  174. package/browser-extension/package.json +0 -21
  175. package/browser-extension/scripts/gen-icons.sh +0 -38
  176. package/browser-extension/src/background/service-worker.ts +0 -27
  177. package/browser-extension/src/content/extractor.ts +0 -44
  178. package/browser-extension/src/icons/icon-128.png +0 -0
  179. package/browser-extension/src/icons/icon-128.svg +0 -4
  180. package/browser-extension/src/icons/icon-16.png +0 -0
  181. package/browser-extension/src/icons/icon-16.svg +0 -4
  182. package/browser-extension/src/icons/icon-32.png +0 -0
  183. package/browser-extension/src/icons/icon-32.svg +0 -4
  184. package/browser-extension/src/icons/icon-48.png +0 -0
  185. package/browser-extension/src/icons/icon-48.svg +0 -4
  186. package/browser-extension/src/lib/api.ts +0 -146
  187. package/browser-extension/src/lib/markdown.ts +0 -68
  188. package/browser-extension/src/lib/storage.ts +0 -37
  189. package/browser-extension/src/lib/types.ts +0 -42
  190. package/browser-extension/src/manifest.json +0 -47
  191. package/browser-extension/src/popup/popup.css +0 -510
  192. package/browser-extension/src/popup/popup.html +0 -128
  193. package/browser-extension/src/popup/popup.ts +0 -416
  194. package/browser-extension/tsconfig.json +0 -16
  195. package/tests/e2e/README.md +0 -25
  196. package/tests/e2e/navigation.spec.ts +0 -14
  197. package/tests/e2e/playwright.config.ts +0 -14
  198. package/tests/integration/README.md +0 -25
  199. package/tests/integration/mcp-contract.test.ts +0 -57
  200. package/tests/integration/mcp-transport.test.ts +0 -361
  201. package/tests/integration/package-lock.json +0 -1463
  202. package/tests/integration/package.json +0 -8
  203. package/tests/integration/vitest.config.ts +0 -11
  204. package/tests/security-hardening.test.ts +0 -456
  205. package/tests/unit/build-integrity.test.ts +0 -137
  206. package/tests/unit/cli-build.test.ts +0 -180
  207. package/tests/unit/cli-config.test.ts +0 -257
  208. package/tests/unit/cli-mcp-install-toml.test.ts +0 -586
  209. package/tests/unit/cli-mcp-install.test.ts +0 -123
  210. package/tests/unit/cli-mcp-stdio-default.test.ts +0 -180
  211. package/tests/unit/cli-modules-load.test.ts +0 -64
  212. package/tests/unit/cli-port.test.ts +0 -87
  213. package/tests/unit/cli-skill-auto-copy.test.ts +0 -260
  214. package/tests/unit/cli-smoke.test.ts +0 -88
  215. package/tests/unit/cli-uninstall.test.ts +0 -218
  216. package/tests/unit/cli-update-root.test.ts +0 -89
  217. package/tests/unit/cli-user-flow-sim.test.ts +0 -506
  218. package/tests/unit/cli-wait-hint.test.ts +0 -86
  219. package/tests/unit/custom-agents.test.ts +0 -478
  220. package/tests/unit/dep-safety.test.ts +0 -126
  221. package/tests/unit/detect-system-lang.test.ts +0 -122
  222. package/tests/unit/mcp-build.test.ts +0 -162
  223. package/tests/unit/setup-needs-restart.test.ts +0 -139
  224. package/tests/unit/stop-restart.test.ts +0 -393
  225. package/tests/unit/vitest.config.ts +0 -8
  226. /package/_standalone/.next/static/{w5bqzZbd2_vdoPRB0JQ_I → Dn8EHqUedSzanCfrM8WWS}/_buildManifest.js +0 -0
  227. /package/_standalone/.next/static/{w5bqzZbd2_vdoPRB0JQ_I → Dn8EHqUedSzanCfrM8WWS}/_ssgManifest.js +0 -0
@@ -1,478 +0,0 @@
1
- import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
2
- import fs from 'fs';
3
- import os from 'os';
4
- import path from 'path';
5
-
6
- /**
7
- * Tests for app/lib/custom-agents.ts — slugify, inferDefaults, toAgentDef,
8
- * generateUniqueKey, validateCustomAgentInput, detectBaseDir.
9
- */
10
-
11
- // We need to mock mcp-agents to avoid importing the full agent registry
12
- vi.mock('../../app/lib/mcp-agents', () => ({
13
- expandHome: (p: string) => p.replace(/^~/, os.homedir()),
14
- MCP_AGENTS: {
15
- cursor: { name: 'Cursor', project: '.cursor/mcp.json', global: '~/.cursor/mcp.json', key: 'mcpServers', preferredTransport: 'stdio' },
16
- 'claude-code': { name: 'Claude Code', project: '.mcp.json', global: '~/.claude.json', key: 'mcpServers', preferredTransport: 'stdio' },
17
- },
18
- SKILL_AGENT_REGISTRY: {},
19
- }));
20
-
21
- // Mock settings to avoid file system dependency
22
- let mockCustomAgents: unknown[] = [];
23
- vi.mock('../../app/lib/settings', () => ({
24
- readSettings: () => ({ customAgents: mockCustomAgents, ai: {}, mindRoot: '' }),
25
- writeSettings: vi.fn((settings: Record<string, unknown>) => {
26
- mockCustomAgents = (settings.customAgents as unknown[]) ?? [];
27
- }),
28
- }));
29
-
30
- // Dynamic import after mocks are set up
31
- let slugify: typeof import('../../app/lib/custom-agents').slugify;
32
- let generateUniqueKey: typeof import('../../app/lib/custom-agents').generateUniqueKey;
33
- let inferDefaults: typeof import('../../app/lib/custom-agents').inferDefaults;
34
- let toAgentDef: typeof import('../../app/lib/custom-agents').toAgentDef;
35
- let validateCustomAgentInput: typeof import('../../app/lib/custom-agents').validateCustomAgentInput;
36
- let detectBaseDir: typeof import('../../app/lib/custom-agents').detectBaseDir;
37
- let loadCustomAgents: typeof import('../../app/lib/custom-agents').loadCustomAgents;
38
- let saveCustomAgents: typeof import('../../app/lib/custom-agents').saveCustomAgents;
39
- let getAllAgents: typeof import('../../app/lib/custom-agents').getAllAgents;
40
- let scanCustomAgentSkills: typeof import('../../app/lib/custom-agents').scanCustomAgentSkills;
41
-
42
- beforeEach(async () => {
43
- mockCustomAgents = [];
44
- const mod = await import('../../app/lib/custom-agents');
45
- slugify = mod.slugify;
46
- generateUniqueKey = mod.generateUniqueKey;
47
- inferDefaults = mod.inferDefaults;
48
- toAgentDef = mod.toAgentDef;
49
- validateCustomAgentInput = mod.validateCustomAgentInput;
50
- detectBaseDir = mod.detectBaseDir;
51
- loadCustomAgents = mod.loadCustomAgents;
52
- saveCustomAgents = mod.saveCustomAgents;
53
- getAllAgents = mod.getAllAgents;
54
- scanCustomAgentSkills = mod.scanCustomAgentSkills;
55
- });
56
-
57
- /* ─── slugify ─── */
58
-
59
- describe('slugify', () => {
60
- it('converts normal names to lowercase kebab-case', () => {
61
- expect(slugify('QC Law Pro 3.0')).toBe('qc-law-pro-30');
62
- });
63
-
64
- it('handles underscores and spaces', () => {
65
- expect(slugify('Work_Buddy Test')).toBe('work-buddy-test');
66
- });
67
-
68
- it('strips non-ASCII characters (CJK)', () => {
69
- expect(slugify('工作助手')).toBe('');
70
- });
71
-
72
- it('strips emoji', () => {
73
- expect(slugify('My🚀Agent')).toBe('myagent');
74
- });
75
-
76
- it('collapses multiple hyphens', () => {
77
- expect(slugify('a - - b')).toBe('a-b');
78
- });
79
-
80
- it('trims leading/trailing hyphens', () => {
81
- expect(slugify(' -hello- ')).toBe('hello');
82
- });
83
-
84
- it('handles empty string', () => {
85
- expect(slugify('')).toBe('');
86
- });
87
-
88
- it('handles string with only special characters', () => {
89
- expect(slugify('!@#$%^')).toBe('');
90
- });
91
-
92
- it('preserves numbers', () => {
93
- expect(slugify('Agent 42')).toBe('agent-42');
94
- });
95
- });
96
-
97
- /* ─── generateUniqueKey ─── */
98
-
99
- describe('generateUniqueKey', () => {
100
- it('returns slug when no conflict', () => {
101
- expect(generateUniqueKey('QCLaw', new Set())).toBe('qclaw');
102
- });
103
-
104
- it('appends suffix on collision', () => {
105
- expect(generateUniqueKey('QCLaw', new Set(['qclaw']))).toBe('qclaw-2');
106
- });
107
-
108
- it('increments suffix on multiple collisions', () => {
109
- expect(generateUniqueKey('QCLaw', new Set(['qclaw', 'qclaw-2']))).toBe('qclaw-3');
110
- });
111
-
112
- it('falls back to custom-N for empty slug (CJK name)', () => {
113
- expect(generateUniqueKey('工作助手', new Set())).toBe('custom-1');
114
- });
115
-
116
- it('increments custom-N on collision', () => {
117
- expect(generateUniqueKey('工作', new Set(['custom-1']))).toBe('custom-2');
118
- });
119
- });
120
-
121
- /* ─── inferDefaults ─── */
122
-
123
- describe('inferDefaults', () => {
124
- it('generates correct defaults from name and baseDir', () => {
125
- const result = inferDefaults('QCLaw', '~/.qclaw');
126
- expect(result.name).toBe('QCLaw');
127
- expect(result.baseDir).toBe('~/.qclaw/');
128
- expect(result.global).toBe('~/.qclaw/mcp.json');
129
- expect(result.project).toBeNull();
130
- expect(result.configKey).toBe('mcpServers');
131
- expect(result.format).toBe('json');
132
- expect(result.preferredTransport).toBe('stdio');
133
- expect(result.presenceDirs).toEqual(['~/.qclaw/']);
134
- });
135
-
136
- it('preserves trailing slash in baseDir', () => {
137
- const result = inferDefaults('Test', '~/.test/');
138
- expect(result.baseDir).toBe('~/.test/');
139
- expect(result.global).toBe('~/.test/mcp.json');
140
- });
141
-
142
- it('sets skillDir to baseDir + skills/', () => {
143
- const result = inferDefaults('QCLaw', '~/.qclaw');
144
- expect(result.skillDir).toBe('~/.qclaw/skills/');
145
- });
146
-
147
- it('preserves trailing slash for skillDir', () => {
148
- const result = inferDefaults('Test', '~/.test/');
149
- expect(result.skillDir).toBe('~/.test/skills/');
150
- });
151
- });
152
-
153
- /* ─── toAgentDef ─── */
154
-
155
- describe('toAgentDef', () => {
156
- it('converts CustomAgentDef to AgentDef correctly', () => {
157
- const custom = {
158
- name: 'QCLaw',
159
- key: 'qclaw',
160
- baseDir: '~/.qclaw/',
161
- global: '~/.qclaw/mcp.json',
162
- configKey: 'mcpServers',
163
- format: 'json' as const,
164
- preferredTransport: 'stdio' as const,
165
- presenceDirs: ['~/.qclaw/'],
166
- };
167
-
168
- const def = toAgentDef(custom);
169
- expect(def.name).toBe('QCLaw');
170
- expect(def.key).toBe('mcpServers'); // AgentDef.key = configKey
171
- expect(def.global).toBe('~/.qclaw/mcp.json');
172
- expect(def.project).toBeNull();
173
- expect(def.preferredTransport).toBe('stdio');
174
- expect(def.format).toBe('json');
175
- expect(def.presenceDirs).toEqual(['~/.qclaw/']);
176
- });
177
-
178
- it('handles optional fields', () => {
179
- const custom = {
180
- name: 'Test',
181
- key: 'test',
182
- baseDir: '~/.test/',
183
- global: '~/.test/config.toml',
184
- configKey: 'mcp_servers',
185
- format: 'toml' as const,
186
- preferredTransport: 'http' as const,
187
- presenceDirs: ['~/.test/'],
188
- presenceCli: 'test-cli',
189
- globalNestedKey: 'mcp.servers',
190
- };
191
-
192
- const def = toAgentDef(custom);
193
- expect(def.format).toBe('toml');
194
- expect(def.preferredTransport).toBe('http');
195
- expect(def.presenceCli).toBe('test-cli');
196
- expect(def.globalNestedKey).toBe('mcp.servers');
197
- });
198
- });
199
-
200
- /* ─── validateCustomAgentInput ─── */
201
-
202
- describe('validateCustomAgentInput', () => {
203
- it('returns null for valid input', () => {
204
- const err = validateCustomAgentInput(
205
- { name: 'QCLaw', baseDir: '~/.qclaw/' },
206
- new Set(),
207
- );
208
- expect(err).toBeNull();
209
- });
210
-
211
- it('rejects empty name', () => {
212
- const err = validateCustomAgentInput(
213
- { name: '', baseDir: '~/.qclaw/' },
214
- new Set(),
215
- );
216
- expect(err).toBe('Agent name is required');
217
- });
218
-
219
- it('rejects empty baseDir', () => {
220
- const err = validateCustomAgentInput(
221
- { name: 'Test', baseDir: '' },
222
- new Set(),
223
- );
224
- expect(err).toBe('Config directory is required');
225
- });
226
-
227
- it('rejects relative path', () => {
228
- const err = validateCustomAgentInput(
229
- { name: 'Test', baseDir: 'relative/path' },
230
- new Set(),
231
- );
232
- expect(err).toContain('absolute path');
233
- });
234
-
235
- it('rejects key conflict with built-in agent', () => {
236
- const err = validateCustomAgentInput(
237
- { name: 'Cursor', baseDir: '~/.my-cursor/' },
238
- new Set(),
239
- );
240
- expect(err).toContain('Conflicts with built-in agent');
241
- });
242
-
243
- it('rejects key conflict with existing custom agent', () => {
244
- const err = validateCustomAgentInput(
245
- { name: 'QCLaw', baseDir: '~/.qclaw/' },
246
- new Set(['qclaw']),
247
- );
248
- expect(err).toContain('already exists');
249
- });
250
-
251
- it('skips key conflict check in edit mode', () => {
252
- const err = validateCustomAgentInput(
253
- { name: 'QCLaw', baseDir: '~/.qclaw/' },
254
- new Set(['qclaw']),
255
- true,
256
- );
257
- expect(err).toBeNull();
258
- });
259
-
260
- it('accepts absolute path starting with /', () => {
261
- const err = validateCustomAgentInput(
262
- { name: 'Test', baseDir: '/opt/test/' },
263
- new Set(),
264
- );
265
- expect(err).toBeNull();
266
- });
267
- });
268
-
269
- /* ─── loadCustomAgents / saveCustomAgents ─── */
270
-
271
- describe('loadCustomAgents', () => {
272
- it('returns empty array when no customAgents in config', () => {
273
- mockCustomAgents = [];
274
- const result = loadCustomAgents();
275
- expect(result).toEqual([]);
276
- });
277
-
278
- it('filters out invalid entries', () => {
279
- mockCustomAgents = [
280
- { name: 'Valid', key: 'valid', baseDir: '~/.valid/' },
281
- { invalid: true },
282
- null,
283
- 'string',
284
- ];
285
- const result = loadCustomAgents();
286
- expect(result).toHaveLength(1);
287
- expect(result[0].name).toBe('Valid');
288
- });
289
- });
290
-
291
- /* ─── getAllAgents ─── */
292
-
293
- describe('getAllAgents', () => {
294
- it('returns built-in agents when no custom agents exist', () => {
295
- mockCustomAgents = [];
296
- const all = getAllAgents();
297
- expect('cursor' in all).toBe(true);
298
- expect('claude-code' in all).toBe(true);
299
- });
300
-
301
- it('merges custom agents with built-in ones', () => {
302
- mockCustomAgents = [
303
- {
304
- name: 'QCLaw',
305
- key: 'qclaw',
306
- baseDir: '~/.qclaw/',
307
- global: '~/.qclaw/mcp.json',
308
- configKey: 'mcpServers',
309
- format: 'json',
310
- preferredTransport: 'stdio',
311
- presenceDirs: ['~/.qclaw/'],
312
- },
313
- ];
314
- const all = getAllAgents();
315
- expect('qclaw' in all).toBe(true);
316
- expect(all.qclaw.name).toBe('QCLaw');
317
- });
318
-
319
- it('built-in agents take priority on key collision', () => {
320
- mockCustomAgents = [
321
- {
322
- name: 'My Cursor',
323
- key: 'cursor',
324
- baseDir: '~/.my-cursor/',
325
- global: '~/.my-cursor/mcp.json',
326
- configKey: 'mcpServers',
327
- format: 'json',
328
- preferredTransport: 'stdio',
329
- presenceDirs: ['~/.my-cursor/'],
330
- },
331
- ];
332
- const all = getAllAgents();
333
- expect(all.cursor.name).toBe('Cursor'); // built-in wins
334
- });
335
- });
336
-
337
- /* ─── detectBaseDir ─── */
338
-
339
- describe('detectBaseDir', () => {
340
- let tempDir: string;
341
-
342
- beforeEach(() => {
343
- tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'mindos-detect-test-'));
344
- });
345
-
346
- afterEach(() => {
347
- fs.rmSync(tempDir, { recursive: true, force: true });
348
- });
349
-
350
- it('returns exists=false for non-existent directory', () => {
351
- const result = detectBaseDir('/tmp/nonexistent-dir-' + Date.now());
352
- expect(result.exists).toBe(false);
353
- expect(result.hasSkillsDir).toBe(false);
354
- });
355
-
356
- it('returns exists=true for existing directory', () => {
357
- const result = detectBaseDir(tempDir);
358
- expect(result.exists).toBe(true);
359
- });
360
-
361
- it('detects JSON config with mcpServers key', () => {
362
- fs.writeFileSync(
363
- path.join(tempDir, 'mcp.json'),
364
- JSON.stringify({ mcpServers: {} }),
365
- );
366
- const result = detectBaseDir(tempDir);
367
- expect(result.exists).toBe(true);
368
- expect(result.detectedFormat).toBe('json');
369
- expect(result.detectedConfigKey).toBe('mcpServers');
370
- expect(result.detectedConfig).toContain('mcp.json');
371
- });
372
-
373
- it('detects JSON config with servers key', () => {
374
- fs.writeFileSync(
375
- path.join(tempDir, 'settings.json'),
376
- JSON.stringify({ servers: {} }),
377
- );
378
- const result = detectBaseDir(tempDir);
379
- expect(result.detectedFormat).toBe('json');
380
- expect(result.detectedConfigKey).toBe('servers');
381
- });
382
-
383
- it('detects skills/ subdirectory', () => {
384
- fs.mkdirSync(path.join(tempDir, 'skills'));
385
- const result = detectBaseDir(tempDir);
386
- expect(result.hasSkillsDir).toBe(true);
387
- expect(result.detectedSkillDir).toBeDefined();
388
- expect(result.skillCount).toBe(0);
389
- });
390
-
391
- it('counts skills inside skills/ directory', () => {
392
- const skillsDir = path.join(tempDir, 'skills');
393
- fs.mkdirSync(skillsDir);
394
- fs.mkdirSync(path.join(skillsDir, 'my-skill-a'));
395
- fs.mkdirSync(path.join(skillsDir, 'my-skill-b'));
396
- fs.writeFileSync(path.join(skillsDir, 'not-a-skill.txt'), '');
397
- const result = detectBaseDir(tempDir);
398
- expect(result.hasSkillsDir).toBe(true);
399
- expect(result.skillCount).toBe(2);
400
- expect(result.skillNames).toEqual(['my-skill-a', 'my-skill-b']);
401
- });
402
-
403
- it('ignores hidden directories in skills/', () => {
404
- const skillsDir = path.join(tempDir, 'skills');
405
- fs.mkdirSync(skillsDir);
406
- fs.mkdirSync(path.join(skillsDir, 'visible-skill'));
407
- fs.mkdirSync(path.join(skillsDir, '.hidden-skill'));
408
- const result = detectBaseDir(tempDir);
409
- expect(result.skillCount).toBe(1);
410
- expect(result.skillNames).toEqual(['visible-skill']);
411
- });
412
-
413
- it('detects TOML config with mcp_servers section', () => {
414
- fs.writeFileSync(
415
- path.join(tempDir, 'config.toml'),
416
- '[mcp_servers]\nmindos = { command = "npx" }\n',
417
- );
418
- const result = detectBaseDir(tempDir);
419
- expect(result.detectedFormat).toBe('toml');
420
- expect(result.detectedConfigKey).toBe('mcp_servers');
421
- });
422
-
423
- it('handles empty directory', () => {
424
- const result = detectBaseDir(tempDir);
425
- expect(result.exists).toBe(true);
426
- expect(result.detectedConfig).toBeUndefined();
427
- expect(result.hasSkillsDir).toBe(false);
428
- });
429
-
430
- it('suggests name from directory name', () => {
431
- const namedDir = path.join(tempDir, 'qclaw');
432
- fs.mkdirSync(namedDir);
433
- const result = detectBaseDir(namedDir);
434
- expect(result.suggestedName).toBe('Qclaw');
435
- });
436
- });
437
-
438
- /* ─── scanCustomAgentSkills ─── */
439
-
440
- describe('scanCustomAgentSkills', () => {
441
- let tempDir: string;
442
-
443
- beforeEach(() => {
444
- tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'mindos-skill-scan-'));
445
- });
446
-
447
- afterEach(() => {
448
- fs.rmSync(tempDir, { recursive: true, force: true });
449
- });
450
-
451
- it('returns empty when skillDir does not exist', () => {
452
- const custom = { ...inferDefaults('Test', tempDir), key: 'test', skillDir: tempDir + '/skills/' };
453
- fs.rmSync(path.join(tempDir), { recursive: true, force: true });
454
- const result = scanCustomAgentSkills(custom);
455
- expect(result.skills).toEqual([]);
456
- });
457
-
458
- it('scans skills from skillDir', () => {
459
- const skillsDir = path.join(tempDir, 'skills');
460
- fs.mkdirSync(skillsDir);
461
- fs.mkdirSync(path.join(skillsDir, 'alpha'));
462
- fs.mkdirSync(path.join(skillsDir, 'beta'));
463
- const custom = { ...inferDefaults('Test', tempDir + '/'), key: 'test', skillDir: tempDir + '/skills/' };
464
- const result = scanCustomAgentSkills(custom);
465
- expect(result.skills).toEqual(['alpha', 'beta']);
466
- expect(result.sourcePath).toContain('skills');
467
- });
468
-
469
- it('uses baseDir + skills/ when skillDir is not set', () => {
470
- const skillsDir = path.join(tempDir, 'skills');
471
- fs.mkdirSync(skillsDir);
472
- fs.mkdirSync(path.join(skillsDir, 'gamma'));
473
- const custom = { ...inferDefaults('Test', tempDir + '/'), key: 'test' };
474
- delete (custom as Record<string, unknown>).skillDir;
475
- const result = scanCustomAgentSkills(custom);
476
- expect(result.skills).toEqual(['gamma']);
477
- });
478
- });
@@ -1,126 +0,0 @@
1
- import { describe, it, expect } from 'vitest';
2
- import fs from 'fs';
3
- import path from 'path';
4
-
5
- /**
6
- * Dependency safety tests — catch version-range vs actual-import mismatches
7
- * before they reach users.
8
- *
9
- * Motivation: @modelcontextprotocol/sdk declared ^1.6.1 but code imported
10
- * server/express.js which only exists since 1.25.0. The lockfile masked the
11
- * bug locally; fresh installs on new machines crashed at runtime.
12
- */
13
-
14
- const ROOT = path.resolve(__dirname, '..', '..');
15
- const MCP_DIR = path.join(ROOT, 'mcp');
16
- const MCP_SRC = path.join(MCP_DIR, 'src', 'index.ts');
17
- const MCP_NODE_MODULES = path.join(MCP_DIR, 'node_modules');
18
-
19
- const hasMcpSrc = fs.existsSync(MCP_SRC);
20
- const hasMcpModules = fs.existsSync(MCP_NODE_MODULES);
21
-
22
- describe.skipIf(!hasMcpSrc)('MCP dependency safety', () => {
23
- /** Extract all bare-specifier imports from a TS/JS file */
24
- function extractImports(filePath: string): string[] {
25
- const content = fs.readFileSync(filePath, 'utf-8');
26
- const imports: string[] = [];
27
- // Match: import ... from "pkg" / import ... from "pkg/sub/path.js"
28
- const re = /import\s+(?:[^'"]+)\s+from\s+['"]([^'"]+)['"]/g;
29
- let m: RegExpExecArray | null;
30
- while ((m = re.exec(content)) !== null) {
31
- // Skip node: built-ins and relative imports
32
- if (!m[1].startsWith('.') && !m[1].startsWith('node:')) {
33
- imports.push(m[1]);
34
- }
35
- }
36
- return imports;
37
- }
38
-
39
- it('all MCP source imports resolve to existing files in node_modules', () => {
40
- if (!hasMcpModules) return; // skip if deps not installed
41
-
42
- const imports = extractImports(MCP_SRC);
43
- expect(imports.length).toBeGreaterThan(0);
44
-
45
- const missing: string[] = [];
46
- for (const specifier of imports) {
47
- // e.g. "@modelcontextprotocol/sdk/server/express.js" or "zod"
48
- try {
49
- require.resolve(specifier, { paths: [MCP_DIR] });
50
- } catch {
51
- missing.push(specifier);
52
- }
53
- }
54
-
55
- expect(missing, `Imports not resolvable in node_modules: ${missing.join(', ')}`).toEqual([]);
56
- });
57
-
58
- it('package.json version range lower bound has required subpath exports', () => {
59
- // Read package.json to get declared deps
60
- const pkgPath = path.join(MCP_DIR, 'package.json');
61
- const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
62
- const sdkRange = pkg.dependencies?.['@modelcontextprotocol/sdk'];
63
- if (!sdkRange) return;
64
-
65
- // Extract lower bound from semver range (^1.25.0 → 1.25.0)
66
- const lowerMatch = sdkRange.match(/(\d+\.\d+\.\d+)/);
67
- if (!lowerMatch) return;
68
- const lowerBound = lowerMatch[1];
69
- const [major, minor] = lowerBound.split('.').map(Number);
70
-
71
- // The express.js subpath was added in 1.25.0 — ensure our lower bound is >= 1.25.0
72
- // This is a specific guard for the known breaking change
73
- const imports = extractImports(MCP_SRC);
74
- const usesExpress = imports.some(i => i.includes('express'));
75
- if (usesExpress) {
76
- expect(
77
- major > 1 || (major === 1 && minor >= 25),
78
- `SDK version range "${sdkRange}" allows <1.25.0 which lacks server/express.js`,
79
- ).toBe(true);
80
- }
81
- });
82
- });
83
-
84
- describe('npm install patterns', () => {
85
- it('no raw --prefer-offline without fallback in CLI scripts', () => {
86
- // Scan bin/ for direct --prefer-offline usage that bypasses npmInstall()
87
- const binDir = path.join(ROOT, 'bin');
88
- const files = collectJsFiles(binDir);
89
-
90
- const violations: string[] = [];
91
- for (const file of files) {
92
- // utils.js and shell.js define npmInstall() which legitimately uses --prefer-offline
93
- if (path.basename(file) === 'utils.js' || path.basename(file) === 'shell.js') continue;
94
-
95
- const content = fs.readFileSync(file, 'utf-8');
96
- const lines = content.split('\n');
97
- for (let i = 0; i < lines.length; i++) {
98
- const line = lines[i];
99
- // Flag: execSync/run with --prefer-offline (should use npmInstall instead)
100
- if (line.includes('--prefer-offline') && !line.trimStart().startsWith('//') && !line.trimStart().startsWith('*')) {
101
- violations.push(`${path.relative(ROOT, file)}:${i + 1}: ${line.trim()}`);
102
- }
103
- }
104
- }
105
-
106
- expect(
107
- violations,
108
- `Found raw --prefer-offline usage (use npmInstall() from utils.js instead):\n${violations.join('\n')}`,
109
- ).toEqual([]);
110
- });
111
- });
112
-
113
- /** Collect .js files recursively */
114
- function collectJsFiles(dir: string): string[] {
115
- const results: string[] = [];
116
- if (!fs.existsSync(dir)) return results;
117
- for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
118
- const full = path.join(dir, entry.name);
119
- if (entry.isDirectory() && entry.name !== 'node_modules') {
120
- results.push(...collectJsFiles(full));
121
- } else if (entry.name.endsWith('.js') || entry.name.endsWith('.ts')) {
122
- results.push(full);
123
- }
124
- }
125
- return results;
126
- }