@useconductor/conductor 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (504) hide show
  1. package/.claude-plugin/marketplace.json +33 -0
  2. package/.claude-plugin/plugin.json +23 -0
  3. package/.eslintrc.json +23 -0
  4. package/.gitattributes +6 -0
  5. package/.github/FUNDING.yml +15 -0
  6. package/.github/ISSUE_TEMPLATE/bug_report.yml +91 -0
  7. package/.github/ISSUE_TEMPLATE/config.yml +8 -0
  8. package/.github/ISSUE_TEMPLATE/feature_request.yml +63 -0
  9. package/.github/ISSUE_TEMPLATE/plugin_request.yml +71 -0
  10. package/.github/README.md +13 -0
  11. package/.github/workflows/README.md +22 -0
  12. package/.github/workflows/auto-release.yml +112 -0
  13. package/.github/workflows/ci.yml +49 -0
  14. package/.github/workflows/claude-code-review.yml +44 -0
  15. package/.github/workflows/claude.yml +36 -0
  16. package/.github/workflows/sync-install.yml +47 -0
  17. package/.mcp.json +9 -0
  18. package/.prettierrc.json +7 -0
  19. package/C.png +0 -0
  20. package/CHANGELOG.md +74 -0
  21. package/CLAUDE.md +118 -0
  22. package/CONTRIBUTING.md +231 -0
  23. package/LICENSE +201 -0
  24. package/README.md +179 -0
  25. package/SECURITY.md +47 -0
  26. package/commands/conductor-setup.md +11 -0
  27. package/commands/conductor-status.md +7 -0
  28. package/dist/ai/base.d.ts +44 -0
  29. package/dist/ai/base.d.ts.map +1 -0
  30. package/dist/ai/base.js +47 -0
  31. package/dist/ai/base.js.map +1 -0
  32. package/dist/ai/claude.d.ts +11 -0
  33. package/dist/ai/claude.d.ts.map +1 -0
  34. package/dist/ai/claude.js +149 -0
  35. package/dist/ai/claude.js.map +1 -0
  36. package/dist/ai/gemini.d.ts +15 -0
  37. package/dist/ai/gemini.d.ts.map +1 -0
  38. package/dist/ai/gemini.js +156 -0
  39. package/dist/ai/gemini.js.map +1 -0
  40. package/dist/ai/maestro.d.ts +22 -0
  41. package/dist/ai/maestro.d.ts.map +1 -0
  42. package/dist/ai/maestro.js +142 -0
  43. package/dist/ai/maestro.js.map +1 -0
  44. package/dist/ai/manager.d.ts +47 -0
  45. package/dist/ai/manager.d.ts.map +1 -0
  46. package/dist/ai/manager.js +450 -0
  47. package/dist/ai/manager.js.map +1 -0
  48. package/dist/ai/ollama.d.ts +16 -0
  49. package/dist/ai/ollama.d.ts.map +1 -0
  50. package/dist/ai/ollama.js +151 -0
  51. package/dist/ai/ollama.js.map +1 -0
  52. package/dist/ai/openai.d.ts +11 -0
  53. package/dist/ai/openai.d.ts.map +1 -0
  54. package/dist/ai/openai.js +132 -0
  55. package/dist/ai/openai.js.map +1 -0
  56. package/dist/ai/openrouter.d.ts +11 -0
  57. package/dist/ai/openrouter.d.ts.map +1 -0
  58. package/dist/ai/openrouter.js +139 -0
  59. package/dist/ai/openrouter.js.map +1 -0
  60. package/dist/bot/slack.d.ts +17 -0
  61. package/dist/bot/slack.d.ts.map +1 -0
  62. package/dist/bot/slack.js +144 -0
  63. package/dist/bot/slack.js.map +1 -0
  64. package/dist/bot/telegram.d.ts +19 -0
  65. package/dist/bot/telegram.d.ts.map +1 -0
  66. package/dist/bot/telegram.js +157 -0
  67. package/dist/bot/telegram.js.map +1 -0
  68. package/dist/cli/commands/ai.d.ts +4 -0
  69. package/dist/cli/commands/ai.d.ts.map +1 -0
  70. package/dist/cli/commands/ai.js +161 -0
  71. package/dist/cli/commands/ai.js.map +1 -0
  72. package/dist/cli/commands/doctor.d.ts +18 -0
  73. package/dist/cli/commands/doctor.d.ts.map +1 -0
  74. package/dist/cli/commands/doctor.js +213 -0
  75. package/dist/cli/commands/doctor.js.map +1 -0
  76. package/dist/cli/commands/init.d.ts +15 -0
  77. package/dist/cli/commands/init.d.ts.map +1 -0
  78. package/dist/cli/commands/init.js +281 -0
  79. package/dist/cli/commands/init.js.map +1 -0
  80. package/dist/cli/commands/install.d.ts +16 -0
  81. package/dist/cli/commands/install.d.ts.map +1 -0
  82. package/dist/cli/commands/install.js +750 -0
  83. package/dist/cli/commands/install.js.map +1 -0
  84. package/dist/cli/commands/lifecycle.d.ts +4 -0
  85. package/dist/cli/commands/lifecycle.d.ts.map +1 -0
  86. package/dist/cli/commands/lifecycle.js +84 -0
  87. package/dist/cli/commands/lifecycle.js.map +1 -0
  88. package/dist/cli/commands/marketplace.d.ts +13 -0
  89. package/dist/cli/commands/marketplace.d.ts.map +1 -0
  90. package/dist/cli/commands/marketplace.js +197 -0
  91. package/dist/cli/commands/marketplace.js.map +1 -0
  92. package/dist/cli/commands/mcp.d.ts +6 -0
  93. package/dist/cli/commands/mcp.d.ts.map +1 -0
  94. package/dist/cli/commands/mcp.js +83 -0
  95. package/dist/cli/commands/mcp.js.map +1 -0
  96. package/dist/cli/commands/onboard.d.ts +10 -0
  97. package/dist/cli/commands/onboard.d.ts.map +1 -0
  98. package/dist/cli/commands/onboard.js +207 -0
  99. package/dist/cli/commands/onboard.js.map +1 -0
  100. package/dist/cli/commands/plugin-create.d.ts +13 -0
  101. package/dist/cli/commands/plugin-create.d.ts.map +1 -0
  102. package/dist/cli/commands/plugin-create.js +122 -0
  103. package/dist/cli/commands/plugin-create.js.map +1 -0
  104. package/dist/cli/commands/plugins.d.ts +5 -0
  105. package/dist/cli/commands/plugins.d.ts.map +1 -0
  106. package/dist/cli/commands/plugins.js +30 -0
  107. package/dist/cli/commands/plugins.js.map +1 -0
  108. package/dist/cli/commands/release.d.ts +13 -0
  109. package/dist/cli/commands/release.d.ts.map +1 -0
  110. package/dist/cli/commands/release.js +243 -0
  111. package/dist/cli/commands/release.js.map +1 -0
  112. package/dist/cli/commands/telegram.d.ts +3 -0
  113. package/dist/cli/commands/telegram.d.ts.map +1 -0
  114. package/dist/cli/commands/telegram.js +20 -0
  115. package/dist/cli/commands/telegram.js.map +1 -0
  116. package/dist/cli/index.d.ts +3 -0
  117. package/dist/cli/index.d.ts.map +1 -0
  118. package/dist/cli/index.js +402 -0
  119. package/dist/cli/index.js.map +1 -0
  120. package/dist/config/oauth.d.ts +8 -0
  121. package/dist/config/oauth.d.ts.map +1 -0
  122. package/dist/config/oauth.js +13 -0
  123. package/dist/config/oauth.js.map +1 -0
  124. package/dist/core/audit.d.ts +91 -0
  125. package/dist/core/audit.d.ts.map +1 -0
  126. package/dist/core/audit.js +233 -0
  127. package/dist/core/audit.js.map +1 -0
  128. package/dist/core/circuit-breaker.d.ts +56 -0
  129. package/dist/core/circuit-breaker.d.ts.map +1 -0
  130. package/dist/core/circuit-breaker.js +107 -0
  131. package/dist/core/circuit-breaker.js.map +1 -0
  132. package/dist/core/conductor.d.ts +44 -0
  133. package/dist/core/conductor.d.ts.map +1 -0
  134. package/dist/core/conductor.js +200 -0
  135. package/dist/core/conductor.js.map +1 -0
  136. package/dist/core/config.d.ts +66 -0
  137. package/dist/core/config.d.ts.map +1 -0
  138. package/dist/core/config.js +86 -0
  139. package/dist/core/config.js.map +1 -0
  140. package/dist/core/database.d.ts +59 -0
  141. package/dist/core/database.d.ts.map +1 -0
  142. package/dist/core/database.js +342 -0
  143. package/dist/core/database.js.map +1 -0
  144. package/dist/core/errors.d.ts +231 -0
  145. package/dist/core/errors.d.ts.map +1 -0
  146. package/dist/core/errors.js +254 -0
  147. package/dist/core/errors.js.map +1 -0
  148. package/dist/core/health.d.ts +72 -0
  149. package/dist/core/health.d.ts.map +1 -0
  150. package/dist/core/health.js +116 -0
  151. package/dist/core/health.js.map +1 -0
  152. package/dist/core/interfaces.d.ts +62 -0
  153. package/dist/core/interfaces.d.ts.map +1 -0
  154. package/dist/core/interfaces.js +8 -0
  155. package/dist/core/interfaces.js.map +1 -0
  156. package/dist/core/logger.d.ts +15 -0
  157. package/dist/core/logger.d.ts.map +1 -0
  158. package/dist/core/logger.js +30 -0
  159. package/dist/core/logger.js.map +1 -0
  160. package/dist/core/rbac.d.ts +132 -0
  161. package/dist/core/rbac.d.ts.map +1 -0
  162. package/dist/core/rbac.js +230 -0
  163. package/dist/core/rbac.js.map +1 -0
  164. package/dist/core/retry.d.ts +22 -0
  165. package/dist/core/retry.d.ts.map +1 -0
  166. package/dist/core/retry.js +41 -0
  167. package/dist/core/retry.js.map +1 -0
  168. package/dist/core/webhooks.d.ts +92 -0
  169. package/dist/core/webhooks.d.ts.map +1 -0
  170. package/dist/core/webhooks.js +176 -0
  171. package/dist/core/webhooks.js.map +1 -0
  172. package/dist/core/zero-config.d.ts +22 -0
  173. package/dist/core/zero-config.d.ts.map +1 -0
  174. package/dist/core/zero-config.js +59 -0
  175. package/dist/core/zero-config.js.map +1 -0
  176. package/dist/dashboard/cli.d.ts +6 -0
  177. package/dist/dashboard/cli.d.ts.map +1 -0
  178. package/dist/dashboard/cli.js +42 -0
  179. package/dist/dashboard/cli.js.map +1 -0
  180. package/dist/dashboard/index.html +3426 -0
  181. package/dist/dashboard/server.d.ts +7 -0
  182. package/dist/dashboard/server.d.ts.map +1 -0
  183. package/dist/dashboard/server.js +1427 -0
  184. package/dist/dashboard/server.js.map +1 -0
  185. package/dist/mcp/server.d.ts +27 -0
  186. package/dist/mcp/server.d.ts.map +1 -0
  187. package/dist/mcp/server.js +380 -0
  188. package/dist/mcp/server.js.map +1 -0
  189. package/dist/mcp/tools/misc.d.ts +15 -0
  190. package/dist/mcp/tools/misc.d.ts.map +1 -0
  191. package/dist/mcp/tools/misc.js +49 -0
  192. package/dist/mcp/tools/misc.js.map +1 -0
  193. package/dist/plugins/builtin/calculator.d.ts +11 -0
  194. package/dist/plugins/builtin/calculator.d.ts.map +1 -0
  195. package/dist/plugins/builtin/calculator.js +166 -0
  196. package/dist/plugins/builtin/calculator.js.map +1 -0
  197. package/dist/plugins/builtin/colors.d.ts +15 -0
  198. package/dist/plugins/builtin/colors.d.ts.map +1 -0
  199. package/dist/plugins/builtin/colors.js +193 -0
  200. package/dist/plugins/builtin/colors.js.map +1 -0
  201. package/dist/plugins/builtin/cron.d.ts +40 -0
  202. package/dist/plugins/builtin/cron.d.ts.map +1 -0
  203. package/dist/plugins/builtin/cron.js +578 -0
  204. package/dist/plugins/builtin/cron.js.map +1 -0
  205. package/dist/plugins/builtin/crypto.d.ts +11 -0
  206. package/dist/plugins/builtin/crypto.d.ts.map +1 -0
  207. package/dist/plugins/builtin/crypto.js +83 -0
  208. package/dist/plugins/builtin/crypto.js.map +1 -0
  209. package/dist/plugins/builtin/database.d.ts +29 -0
  210. package/dist/plugins/builtin/database.d.ts.map +1 -0
  211. package/dist/plugins/builtin/database.js +230 -0
  212. package/dist/plugins/builtin/database.js.map +1 -0
  213. package/dist/plugins/builtin/docker.d.ts +12 -0
  214. package/dist/plugins/builtin/docker.d.ts.map +1 -0
  215. package/dist/plugins/builtin/docker.js +436 -0
  216. package/dist/plugins/builtin/docker.js.map +1 -0
  217. package/dist/plugins/builtin/fun.d.ts +11 -0
  218. package/dist/plugins/builtin/fun.d.ts.map +1 -0
  219. package/dist/plugins/builtin/fun.js +114 -0
  220. package/dist/plugins/builtin/fun.js.map +1 -0
  221. package/dist/plugins/builtin/gcal.d.ts +38 -0
  222. package/dist/plugins/builtin/gcal.d.ts.map +1 -0
  223. package/dist/plugins/builtin/gcal.js +280 -0
  224. package/dist/plugins/builtin/gcal.js.map +1 -0
  225. package/dist/plugins/builtin/gdrive.d.ts +26 -0
  226. package/dist/plugins/builtin/gdrive.d.ts.map +1 -0
  227. package/dist/plugins/builtin/gdrive.js +295 -0
  228. package/dist/plugins/builtin/gdrive.js.map +1 -0
  229. package/dist/plugins/builtin/github-actions.d.ts +38 -0
  230. package/dist/plugins/builtin/github-actions.d.ts.map +1 -0
  231. package/dist/plugins/builtin/github-actions.js +629 -0
  232. package/dist/plugins/builtin/github-actions.js.map +1 -0
  233. package/dist/plugins/builtin/github.d.ts +26 -0
  234. package/dist/plugins/builtin/github.d.ts.map +1 -0
  235. package/dist/plugins/builtin/github.js +800 -0
  236. package/dist/plugins/builtin/github.js.map +1 -0
  237. package/dist/plugins/builtin/gmail.d.ts +50 -0
  238. package/dist/plugins/builtin/gmail.d.ts.map +1 -0
  239. package/dist/plugins/builtin/gmail.js +445 -0
  240. package/dist/plugins/builtin/gmail.js.map +1 -0
  241. package/dist/plugins/builtin/hash.d.ts +11 -0
  242. package/dist/plugins/builtin/hash.d.ts.map +1 -0
  243. package/dist/plugins/builtin/hash.js +95 -0
  244. package/dist/plugins/builtin/hash.js.map +1 -0
  245. package/dist/plugins/builtin/homekit.d.ts +53 -0
  246. package/dist/plugins/builtin/homekit.d.ts.map +1 -0
  247. package/dist/plugins/builtin/homekit.js +341 -0
  248. package/dist/plugins/builtin/homekit.js.map +1 -0
  249. package/dist/plugins/builtin/index.d.ts +4 -0
  250. package/dist/plugins/builtin/index.d.ts.map +1 -0
  251. package/dist/plugins/builtin/index.js +96 -0
  252. package/dist/plugins/builtin/index.js.map +1 -0
  253. package/dist/plugins/builtin/jira.d.ts +50 -0
  254. package/dist/plugins/builtin/jira.d.ts.map +1 -0
  255. package/dist/plugins/builtin/jira.js +353 -0
  256. package/dist/plugins/builtin/jira.js.map +1 -0
  257. package/dist/plugins/builtin/linear.d.ts +35 -0
  258. package/dist/plugins/builtin/linear.d.ts.map +1 -0
  259. package/dist/plugins/builtin/linear.js +397 -0
  260. package/dist/plugins/builtin/linear.js.map +1 -0
  261. package/dist/plugins/builtin/lumen.d.ts +21 -0
  262. package/dist/plugins/builtin/lumen.d.ts.map +1 -0
  263. package/dist/plugins/builtin/lumen.js +404 -0
  264. package/dist/plugins/builtin/lumen.js.map +1 -0
  265. package/dist/plugins/builtin/memory.d.ts +22 -0
  266. package/dist/plugins/builtin/memory.d.ts.map +1 -0
  267. package/dist/plugins/builtin/memory.js +184 -0
  268. package/dist/plugins/builtin/memory.js.map +1 -0
  269. package/dist/plugins/builtin/n8n.d.ts +60 -0
  270. package/dist/plugins/builtin/n8n.d.ts.map +1 -0
  271. package/dist/plugins/builtin/n8n.js +519 -0
  272. package/dist/plugins/builtin/n8n.js.map +1 -0
  273. package/dist/plugins/builtin/network.d.ts +11 -0
  274. package/dist/plugins/builtin/network.d.ts.map +1 -0
  275. package/dist/plugins/builtin/network.js +88 -0
  276. package/dist/plugins/builtin/network.js.map +1 -0
  277. package/dist/plugins/builtin/notes.d.ts +47 -0
  278. package/dist/plugins/builtin/notes.d.ts.map +1 -0
  279. package/dist/plugins/builtin/notes.js +641 -0
  280. package/dist/plugins/builtin/notes.js.map +1 -0
  281. package/dist/plugins/builtin/notion.d.ts +47 -0
  282. package/dist/plugins/builtin/notion.d.ts.map +1 -0
  283. package/dist/plugins/builtin/notion.js +317 -0
  284. package/dist/plugins/builtin/notion.js.map +1 -0
  285. package/dist/plugins/builtin/shell.d.ts +12 -0
  286. package/dist/plugins/builtin/shell.d.ts.map +1 -0
  287. package/dist/plugins/builtin/shell.js +310 -0
  288. package/dist/plugins/builtin/shell.js.map +1 -0
  289. package/dist/plugins/builtin/slack.d.ts +31 -0
  290. package/dist/plugins/builtin/slack.d.ts.map +1 -0
  291. package/dist/plugins/builtin/slack.js +295 -0
  292. package/dist/plugins/builtin/slack.js.map +1 -0
  293. package/dist/plugins/builtin/spotify.d.ts +55 -0
  294. package/dist/plugins/builtin/spotify.d.ts.map +1 -0
  295. package/dist/plugins/builtin/spotify.js +623 -0
  296. package/dist/plugins/builtin/spotify.js.map +1 -0
  297. package/dist/plugins/builtin/stripe.d.ts +35 -0
  298. package/dist/plugins/builtin/stripe.d.ts.map +1 -0
  299. package/dist/plugins/builtin/stripe.js +376 -0
  300. package/dist/plugins/builtin/stripe.js.map +1 -0
  301. package/dist/plugins/builtin/system.d.ts +11 -0
  302. package/dist/plugins/builtin/system.d.ts.map +1 -0
  303. package/dist/plugins/builtin/system.js +91 -0
  304. package/dist/plugins/builtin/system.js.map +1 -0
  305. package/dist/plugins/builtin/text-tools.d.ts +11 -0
  306. package/dist/plugins/builtin/text-tools.d.ts.map +1 -0
  307. package/dist/plugins/builtin/text-tools.js +146 -0
  308. package/dist/plugins/builtin/text-tools.js.map +1 -0
  309. package/dist/plugins/builtin/timezone.d.ts +13 -0
  310. package/dist/plugins/builtin/timezone.d.ts.map +1 -0
  311. package/dist/plugins/builtin/timezone.js +164 -0
  312. package/dist/plugins/builtin/timezone.js.map +1 -0
  313. package/dist/plugins/builtin/todoist.d.ts +49 -0
  314. package/dist/plugins/builtin/todoist.d.ts.map +1 -0
  315. package/dist/plugins/builtin/todoist.js +540 -0
  316. package/dist/plugins/builtin/todoist.js.map +1 -0
  317. package/dist/plugins/builtin/translate.d.ts +11 -0
  318. package/dist/plugins/builtin/translate.d.ts.map +1 -0
  319. package/dist/plugins/builtin/translate.js +42 -0
  320. package/dist/plugins/builtin/translate.js.map +1 -0
  321. package/dist/plugins/builtin/url-tools.d.ts +11 -0
  322. package/dist/plugins/builtin/url-tools.d.ts.map +1 -0
  323. package/dist/plugins/builtin/url-tools.js +70 -0
  324. package/dist/plugins/builtin/url-tools.js.map +1 -0
  325. package/dist/plugins/builtin/vercel.d.ts +55 -0
  326. package/dist/plugins/builtin/vercel.d.ts.map +1 -0
  327. package/dist/plugins/builtin/vercel.js +514 -0
  328. package/dist/plugins/builtin/vercel.js.map +1 -0
  329. package/dist/plugins/builtin/weather.d.ts +13 -0
  330. package/dist/plugins/builtin/weather.d.ts.map +1 -0
  331. package/dist/plugins/builtin/weather.js +103 -0
  332. package/dist/plugins/builtin/weather.js.map +1 -0
  333. package/dist/plugins/builtin/x.d.ts +54 -0
  334. package/dist/plugins/builtin/x.d.ts.map +1 -0
  335. package/dist/plugins/builtin/x.js +402 -0
  336. package/dist/plugins/builtin/x.js.map +1 -0
  337. package/dist/plugins/manager.d.ts +77 -0
  338. package/dist/plugins/manager.d.ts.map +1 -0
  339. package/dist/plugins/manager.js +141 -0
  340. package/dist/plugins/manager.js.map +1 -0
  341. package/dist/plugins/validation.d.ts +18 -0
  342. package/dist/plugins/validation.d.ts.map +1 -0
  343. package/dist/plugins/validation.js +81 -0
  344. package/dist/plugins/validation.js.map +1 -0
  345. package/dist/security/auth.d.ts +23 -0
  346. package/dist/security/auth.d.ts.map +1 -0
  347. package/dist/security/auth.js +56 -0
  348. package/dist/security/auth.js.map +1 -0
  349. package/dist/security/keychain.d.ts +60 -0
  350. package/dist/security/keychain.d.ts.map +1 -0
  351. package/dist/security/keychain.js +213 -0
  352. package/dist/security/keychain.js.map +1 -0
  353. package/dist/utils/google-auth.d.ts +21 -0
  354. package/dist/utils/google-auth.d.ts.map +1 -0
  355. package/dist/utils/google-auth.js +135 -0
  356. package/dist/utils/google-auth.js.map +1 -0
  357. package/dist/utils/retry.d.ts +5 -0
  358. package/dist/utils/retry.d.ts.map +1 -0
  359. package/dist/utils/retry.js +34 -0
  360. package/dist/utils/retry.js.map +1 -0
  361. package/docs/README.md +13 -0
  362. package/docs/api.md +210 -0
  363. package/docs/getting-started.md +100 -0
  364. package/docs/plugins.md +306 -0
  365. package/docs-site/.vitepress/config.ts +59 -0
  366. package/docs-site/README.md +12 -0
  367. package/docs-site/index.md +30 -0
  368. package/eslint.config.js +29 -0
  369. package/install.ps1 +334 -0
  370. package/install.sh +1119 -0
  371. package/local-install.sh +304 -0
  372. package/package.json +90 -0
  373. package/packages/README.md +11 -0
  374. package/packages/plugin-sdk/README.md +12 -0
  375. package/packages/plugin-sdk/package.json +22 -0
  376. package/packages/plugin-sdk/src/README.md +11 -0
  377. package/packages/plugin-sdk/src/index.ts +191 -0
  378. package/sdks/README.md +26 -0
  379. package/sdks/csharp/ConductorClient.cs +65 -0
  380. package/sdks/csharp/README.md +11 -0
  381. package/sdks/go/README.md +11 -0
  382. package/sdks/go/conductor.go +257 -0
  383. package/sdks/java/ConductorClient.java +27 -0
  384. package/sdks/java/README.md +11 -0
  385. package/sdks/php/README.md +11 -0
  386. package/sdks/php/src/Client.php +72 -0
  387. package/sdks/python/README.md +12 -0
  388. package/sdks/python/conductor/__init__.py +227 -0
  389. package/sdks/python/pyproject.toml +30 -0
  390. package/sdks/ruby/README.md +11 -0
  391. package/sdks/ruby/lib/conductor.rb +46 -0
  392. package/sdks/rust/Cargo.toml +14 -0
  393. package/sdks/rust/README.md +11 -0
  394. package/sdks/swift/README.md +11 -0
  395. package/sdks/swift/Sources/Conductor/ConductorClient.swift +65 -0
  396. package/skills/conductor-mcp/SKILL.md +38 -0
  397. package/src/README.md +20 -0
  398. package/src/ai/README.md +18 -0
  399. package/src/ai/base.ts +93 -0
  400. package/src/ai/claude.ts +162 -0
  401. package/src/ai/gemini.ts +188 -0
  402. package/src/ai/maestro.ts +168 -0
  403. package/src/ai/manager.ts +537 -0
  404. package/src/ai/ollama.ts +186 -0
  405. package/src/ai/openai.ts +147 -0
  406. package/src/ai/openrouter.ts +152 -0
  407. package/src/bot/README.md +12 -0
  408. package/src/bot/slack.ts +164 -0
  409. package/src/bot/telegram.ts +185 -0
  410. package/src/cli/README.md +24 -0
  411. package/src/cli/commands/README.md +20 -0
  412. package/src/cli/commands/ai.ts +170 -0
  413. package/src/cli/commands/doctor.ts +221 -0
  414. package/src/cli/commands/init.ts +348 -0
  415. package/src/cli/commands/install.ts +792 -0
  416. package/src/cli/commands/lifecycle.ts +95 -0
  417. package/src/cli/commands/marketplace.ts +253 -0
  418. package/src/cli/commands/mcp.ts +92 -0
  419. package/src/cli/commands/onboard.ts +248 -0
  420. package/src/cli/commands/plugin-create.ts +130 -0
  421. package/src/cli/commands/plugins.ts +36 -0
  422. package/src/cli/commands/release.ts +251 -0
  423. package/src/cli/commands/telegram.ts +25 -0
  424. package/src/cli/index.ts +450 -0
  425. package/src/config/README.md +11 -0
  426. package/src/config/oauth.ts +26 -0
  427. package/src/core/README.md +22 -0
  428. package/src/core/audit.ts +291 -0
  429. package/src/core/circuit-breaker.ts +129 -0
  430. package/src/core/conductor.ts +240 -0
  431. package/src/core/config.ts +149 -0
  432. package/src/core/database.ts +411 -0
  433. package/src/core/errors.ts +275 -0
  434. package/src/core/health.ts +159 -0
  435. package/src/core/interfaces.ts +75 -0
  436. package/src/core/logger.ts +33 -0
  437. package/src/core/rbac.ts +321 -0
  438. package/src/core/retry.ts +61 -0
  439. package/src/core/webhooks.ts +234 -0
  440. package/src/core/zero-config.ts +72 -0
  441. package/src/dashboard/README.md +15 -0
  442. package/src/dashboard/cli.ts +48 -0
  443. package/src/dashboard/index.html +3426 -0
  444. package/src/dashboard/server.ts +1544 -0
  445. package/src/mcp/README.md +20 -0
  446. package/src/mcp/server.ts +475 -0
  447. package/src/mcp/tools/README.md +11 -0
  448. package/src/mcp/tools/misc.ts +61 -0
  449. package/src/plugins/README.md +28 -0
  450. package/src/plugins/builtin/README.md +23 -0
  451. package/src/plugins/builtin/calculator.ts +178 -0
  452. package/src/plugins/builtin/colors.ts +201 -0
  453. package/src/plugins/builtin/cron.ts +649 -0
  454. package/src/plugins/builtin/crypto.ts +85 -0
  455. package/src/plugins/builtin/database.ts +235 -0
  456. package/src/plugins/builtin/docker.ts +426 -0
  457. package/src/plugins/builtin/fun.ts +118 -0
  458. package/src/plugins/builtin/gcal.ts +305 -0
  459. package/src/plugins/builtin/gdrive.ts +326 -0
  460. package/src/plugins/builtin/github-actions.ts +666 -0
  461. package/src/plugins/builtin/github.ts +912 -0
  462. package/src/plugins/builtin/gmail.ts +492 -0
  463. package/src/plugins/builtin/hash.ts +98 -0
  464. package/src/plugins/builtin/homekit.ts +389 -0
  465. package/src/plugins/builtin/index.ts +116 -0
  466. package/src/plugins/builtin/jira.ts +380 -0
  467. package/src/plugins/builtin/linear.ts +448 -0
  468. package/src/plugins/builtin/lumen.ts +497 -0
  469. package/src/plugins/builtin/memory.ts +200 -0
  470. package/src/plugins/builtin/n8n.ts +565 -0
  471. package/src/plugins/builtin/network.ts +92 -0
  472. package/src/plugins/builtin/notes.ts +689 -0
  473. package/src/plugins/builtin/notion.ts +348 -0
  474. package/src/plugins/builtin/shell.ts +334 -0
  475. package/src/plugins/builtin/slack.ts +327 -0
  476. package/src/plugins/builtin/spotify.ts +665 -0
  477. package/src/plugins/builtin/stripe.ts +388 -0
  478. package/src/plugins/builtin/system.ts +93 -0
  479. package/src/plugins/builtin/text-tools.ts +150 -0
  480. package/src/plugins/builtin/timezone.ts +173 -0
  481. package/src/plugins/builtin/todoist.ts +625 -0
  482. package/src/plugins/builtin/translate.ts +47 -0
  483. package/src/plugins/builtin/url-tools.ts +73 -0
  484. package/src/plugins/builtin/vercel.ts +546 -0
  485. package/src/plugins/builtin/weather.ts +112 -0
  486. package/src/plugins/builtin/x.ts +440 -0
  487. package/src/plugins/manager.ts +213 -0
  488. package/src/plugins/validation.ts +94 -0
  489. package/src/security/README.md +12 -0
  490. package/src/security/auth.ts +72 -0
  491. package/src/security/keychain.ts +226 -0
  492. package/src/utils/README.md +12 -0
  493. package/src/utils/google-auth.ts +159 -0
  494. package/src/utils/retry.ts +41 -0
  495. package/test-all.mjs +1256 -0
  496. package/test.mjs +633 -0
  497. package/tests/README.md +19 -0
  498. package/tests/calculator.test.ts +54 -0
  499. package/tests/docker.test.ts +42 -0
  500. package/tests/load.test.ts +129 -0
  501. package/tests/mcp.test.ts +14 -0
  502. package/tests/shell.test.ts +42 -0
  503. package/tsconfig.json +21 -0
  504. package/vitest.config.ts +14 -0
package/test-all.mjs ADDED
@@ -0,0 +1,1256 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Conductor Full System Test Suite
4
+ * Tests absolutely everything — CLI, MCP, security, config, plugins, file structure.
5
+ *
6
+ * Usage:
7
+ * node test-all.mjs # Everything
8
+ * node test-all.mjs --skip-auth # Skip tests needing API tokens
9
+ * node test-all.mjs --write # Include write operations
10
+ * node test-all.mjs --suite cli # One suite only: cli, mcp, security, config, plugins, files
11
+ * node test-all.mjs --verbose # Print full output
12
+ */
13
+
14
+ import { execSync, spawn } from 'child_process';
15
+ import { existsSync, mkdirSync, writeFileSync, readFileSync, rmSync, readdirSync, statSync, lstatSync } from 'fs';
16
+ import { homedir, platform, cpus, totalmem } from 'os';
17
+ import { join, resolve, dirname } from 'path';
18
+ import { fileURLToPath } from 'url';
19
+ import { parseArgs } from 'util';
20
+
21
+ const __dir = dirname(fileURLToPath(import.meta.url));
22
+
23
+ // ── Args ──────────────────────────────────────────────────────────────────────
24
+
25
+ const { values: args } = parseArgs({
26
+ options: {
27
+ suite: { type: 'string' },
28
+ 'skip-auth': { type: 'boolean', default: false },
29
+ write: { type: 'boolean', default: false },
30
+ verbose: { type: 'boolean', short: 'v', default: false },
31
+ help: { type: 'boolean', short: 'h', default: false },
32
+ },
33
+ allowPositionals: true,
34
+ });
35
+
36
+ if (args.help) {
37
+ console.log(`
38
+ Conductor Full System Test Suite
39
+
40
+ node test-all.mjs Run everything
41
+ node test-all.mjs --skip-auth Skip auth plugin tests
42
+ node test-all.mjs --write Include write operations
43
+ node test-all.mjs --suite cli Run one suite only
44
+ node test-all.mjs --verbose Show full output
45
+
46
+ Suites: cli, mcp, security, config, files, plugins
47
+ `);
48
+ process.exit(0);
49
+ }
50
+
51
+ // ── Colours ───────────────────────────────────────────────────────────────────
52
+
53
+ const c = {
54
+ reset: '\x1b[0m', bold: '\x1b[1m', dim: '\x1b[2m',
55
+ green: '\x1b[32m', red: '\x1b[31m', yellow: '\x1b[33m',
56
+ cyan: '\x1b[36m', blue: '\x1b[34m', magenta: '\x1b[35m', gray: '\x1b[90m',
57
+ };
58
+
59
+ // ── Tracking ──────────────────────────────────────────────────────────────────
60
+
61
+ const totals = { passed: 0, failed: 0, skipped: 0, errors: [] };
62
+ let currentSuite = '';
63
+
64
+ function suiteHeader(name, icon = '▶') {
65
+ currentSuite = name;
66
+ console.log(`\n${c.bold}${c.cyan}${icon} ${name}${c.reset}`);
67
+ }
68
+
69
+ function pass(name, detail = '') {
70
+ totals.passed++;
71
+ const det = detail && args.verbose ? ` ${c.gray}${String(detail).slice(0, 100)}${c.reset}` : '';
72
+ console.log(` ${c.green}✓${c.reset} ${name}${det}`);
73
+ }
74
+
75
+ function fail(name, err) {
76
+ totals.failed++;
77
+ const msg = err?.message ?? String(err);
78
+ totals.errors.push({ suite: currentSuite, test: name, error: msg });
79
+ console.log(` ${c.red}✗${c.reset} ${name} ${c.red}${msg.split('\n')[0]}${c.reset}`);
80
+ }
81
+
82
+ function skip(name, reason) {
83
+ totals.skipped++;
84
+ console.log(` ${c.dim}○ ${name} — ${reason}${c.reset}`);
85
+ }
86
+
87
+ async function test(name, fn, { skipIf = false, skipReason = '' } = {}) {
88
+ if (skipIf) return skip(name, skipReason);
89
+ try {
90
+ const result = await fn();
91
+ pass(name, result);
92
+ } catch (err) {
93
+ fail(name, err);
94
+ }
95
+ }
96
+
97
+ function cli(cmd) {
98
+ return execSync(`node ${join(__dir, 'dist/cli/index.js')} ${cmd}`, {
99
+ encoding: 'utf8',
100
+ timeout: 10000,
101
+ cwd: __dir,
102
+ });
103
+ }
104
+
105
+ // ══════════════════════════════════════════════════════════════════════════════
106
+ // SUITE 1: File Structure
107
+ // ══════════════════════════════════════════════════════════════════════════════
108
+
109
+ async function testFiles() {
110
+ if (args.suite && args.suite !== 'files') return;
111
+ suiteHeader('File Structure', '📁');
112
+
113
+ const required = [
114
+ 'package.json',
115
+ 'tsconfig.json',
116
+ 'install.sh',
117
+ 'install.ps1',
118
+ 'README.md',
119
+ 'SECURITY.md',
120
+ 'LICENSE',
121
+ '.gitignore',
122
+ 'test.mjs',
123
+ 'test-all.mjs',
124
+ 'src/cli/index.ts',
125
+ 'src/core/conductor.ts',
126
+ 'src/core/config.ts',
127
+ 'src/ai/manager.ts',
128
+ 'src/ai/claude.ts',
129
+ 'src/ai/openai.ts',
130
+ 'src/ai/gemini.ts',
131
+ 'src/ai/ollama.ts',
132
+ 'src/ai/openrouter.ts',
133
+ 'src/bot/telegram.ts',
134
+ 'src/mcp/server.ts',
135
+ 'src/security/keychain.ts',
136
+ 'src/config/oauth.ts',
137
+ 'src/plugins/manager.ts',
138
+ 'src/plugins/builtin/index.ts',
139
+ 'dist/cli/index.js',
140
+ 'dist/plugins/builtin/index.js',
141
+ 'dist/core/conductor.js',
142
+ 'dist/mcp/server.js',
143
+ ];
144
+
145
+ for (const f of required) {
146
+ await test(`exists: ${f}`, () => {
147
+ if (!existsSync(join(__dir, f))) throw new Error(`Missing: ${f}`);
148
+ return true;
149
+ });
150
+ }
151
+
152
+ // Check all builtin plugin source files exist
153
+ const pluginsDir = join(__dir, 'src/plugins/builtin');
154
+ const pluginFolders = readdirSync(pluginsDir)
155
+ .filter(f => lstatSync(join(pluginsDir, f)).isDirectory());
156
+ const expectedPluginCount = pluginFolders.length;
157
+
158
+ suiteHeader('File Structure', '📁');
159
+ const plugins = pluginFolders;
160
+ for (const p of plugins) {
161
+ await test(`plugin src: ${p}.ts`, () => {
162
+ const f = join(__dir, `src/plugins/builtin/${p}.ts`);
163
+ if (!existsSync(f)) throw new Error(`Missing: ${f}`);
164
+ const size = readFileSync(f).length;
165
+ if (size < 500) throw new Error(`${p}.ts is suspiciously small (${size} bytes)`);
166
+ return `${Math.round(size / 1024)}kb`;
167
+ });
168
+ }
169
+
170
+ await test('package.json valid JSON', () => {
171
+ const pkg = JSON.parse(readFileSync(join(__dir, 'package.json'), 'utf8'));
172
+ if (!pkg.name) throw new Error('Missing name');
173
+ if (!pkg.version) throw new Error('Missing version');
174
+ if (!pkg.scripts?.build) throw new Error('Missing build script');
175
+ if (!pkg.bin?.conductor) throw new Error('Missing bin entry');
176
+ return `${pkg.name}@${pkg.version}`;
177
+ });
178
+
179
+ await test('tsconfig.json valid', () => {
180
+ const tsconfig = JSON.parse(readFileSync(join(__dir, 'tsconfig.json'), 'utf8'));
181
+ if (!tsconfig.compilerOptions) throw new Error('Missing compilerOptions');
182
+ if (tsconfig.compilerOptions.strict !== true) throw new Error('strict mode should be enabled');
183
+ return 'strict mode on';
184
+ });
185
+
186
+ await test('install.sh is executable text', () => {
187
+ const sh = readFileSync(join(__dir, 'install.sh'), 'utf8');
188
+ if (!sh.startsWith('#!/')) throw new Error('Missing shebang');
189
+ if (sh.length < 10000) throw new Error(`install.sh too small: ${sh.length} bytes`);
190
+ const steps = (sh.match(/^step "/gm) || []).length;
191
+ if (steps < 10) throw new Error(`Expected 10+ steps, found ${steps}`);
192
+ return `${steps} steps, ${Math.round(sh.length / 1024)}kb`;
193
+ });
194
+
195
+ await test('README.md complete', () => {
196
+ const md = readFileSync(join(__dir, 'README.md'), 'utf8');
197
+ const checks = ['## Install', '## Plugins', '## Security', 'TheAlxLabs'];
198
+ for (const check of checks) {
199
+ if (!md.includes(check)) throw new Error(`README missing: ${check}`);
200
+ }
201
+ return `${Math.round(md.length / 1024)}kb`;
202
+ });
203
+
204
+ await test('SECURITY.md exists and complete', () => {
205
+ const md = readFileSync(join(__dir, 'SECURITY.md'), 'utf8');
206
+ if (!md.includes('Reporting')) throw new Error('Missing Reporting section');
207
+ if (!md.includes('AES-256')) throw new Error('Missing encryption details');
208
+ return `${Math.round(md.length / 1024)}kb`;
209
+ });
210
+
211
+ await test('.gitignore has node_modules and dist', () => {
212
+ const gi = readFileSync(join(__dir, '.gitignore'), 'utf8');
213
+ if (!gi.includes('node_modules')) throw new Error('Missing node_modules');
214
+ if (!gi.includes('dist/')) throw new Error('Missing dist/');
215
+ if (!gi.includes('keychain')) throw new Error('Missing keychain (security risk!)');
216
+ return 'ok';
217
+ });
218
+ }
219
+
220
+ // ══════════════════════════════════════════════════════════════════════════════
221
+ // SUITE 2: Build & TypeScript
222
+ // ══════════════════════════════════════════════════════════════════════════════
223
+
224
+ async function testBuild() {
225
+ if (args.suite && args.suite !== 'build') return;
226
+ suiteHeader('Build & TypeScript', '🔨');
227
+
228
+ await test('TypeScript compiles with zero errors', () => {
229
+ const result = execSync('npm run build 2>&1', { encoding: 'utf8', cwd: __dir });
230
+ if (result.includes('error TS')) {
231
+ const errors = result.split('\n').filter(l => l.includes('error TS'));
232
+ throw new Error(`${errors.length} TypeScript errors:\n${errors.slice(0, 3).join('\n')}`);
233
+ }
234
+ return 'zero errors';
235
+ });
236
+
237
+ await test('dist/cli/index.js exists after build', () => {
238
+ if (!existsSync(join(__dir, 'dist/cli/index.js')))
239
+ throw new Error('dist/cli/index.js not found — build failed');
240
+ return true;
241
+ });
242
+
243
+ await test('dist has correct structure', () => {
244
+ const expected = ['cli', 'core', 'ai', 'bot', 'mcp', 'plugins', 'security', 'config'];
245
+ const missing = expected.filter(d => !existsSync(join(__dir, 'dist', d)));
246
+ if (missing.length) throw new Error(`Missing dist dirs: ${missing.join(', ')}`);
247
+ return `${expected.length} directories`;
248
+ });
249
+
250
+ await test('all plugin source files compile', () => {
251
+ const distDir = join(__dir, 'dist/plugins/builtin');
252
+ const plugins = ['calculator', 'colors', 'cron', 'crypto', 'fun', 'gcal', 'gdrive',
253
+ 'github', 'github-actions', 'gmail', 'hash', 'memory', 'n8n', 'network',
254
+ 'notes', 'notion', 'spotify', 'system', 'text-tools', 'timezone',
255
+ 'translate', 'url-tools', 'vercel', 'weather', 'x'];
256
+ const missing = plugins.filter(p => !existsSync(join(distDir, `${p}.js`)));
257
+ if (missing.length) throw new Error(`Missing compiled plugins: ${missing.join(', ')}`);
258
+ return `${plugins.length} plugins compiled`;
259
+ });
260
+ }
261
+
262
+ // ══════════════════════════════════════════════════════════════════════════════
263
+ // SUITE 3: CLI
264
+ // ══════════════════════════════════════════════════════════════════════════════
265
+
266
+ async function testCLI() {
267
+ if (args.suite && args.suite !== 'cli') return;
268
+ suiteHeader('CLI', '⌨️');
269
+
270
+ await test('--help prints usage', () => {
271
+ const out = cli('--help');
272
+ if (!out.includes('conductor')) throw new Error('No conductor in --help output');
273
+ if (!out.includes('Commands')) throw new Error('No Commands section');
274
+ return 'ok';
275
+ });
276
+
277
+ await test('--version returns semver', () => {
278
+ const out = cli('--version').trim();
279
+ if (!/^\d+\.\d+\.\d+/.test(out)) throw new Error(`Bad version: ${out}`);
280
+ return out;
281
+ });
282
+
283
+ await test('version command works', () => {
284
+ const out = cli('version');
285
+ if (!out.includes('0.')) throw new Error('No version number');
286
+ return out.trim();
287
+ });
288
+
289
+ await test('status command runs', () => {
290
+ const out = cli('status');
291
+ // Should output something about the setup
292
+ if (out.length < 10) throw new Error('Status output too short');
293
+ return 'ok';
294
+ });
295
+
296
+ await test('plugins list --help', () => {
297
+ const out = cli('plugins --help');
298
+ if (!out.includes('list') && !out.includes('enable')) throw new Error('Missing plugin subcommands');
299
+ return 'ok';
300
+ });
301
+
302
+ await test('ai --help', () => {
303
+ const out = cli('ai --help');
304
+ if (!out.includes('setup') && !out.includes('test')) throw new Error('Missing ai subcommands');
305
+ return 'ok';
306
+ });
307
+
308
+ await test('mcp --help', () => {
309
+ const out = cli('mcp --help');
310
+ if (!out.includes('start') && !out.includes('setup')) throw new Error('Missing mcp subcommands');
311
+ return 'ok';
312
+ });
313
+
314
+ await test('telegram --help', () => {
315
+ const out = cli('telegram --help');
316
+ if (!out.includes('start') && !out.includes('setup')) throw new Error('Missing telegram subcommands');
317
+ return 'ok';
318
+ });
319
+
320
+ await test('logs --help', () => {
321
+ const out = cli('logs --help');
322
+ if (out.length < 10) throw new Error('logs --help too short');
323
+ return 'ok';
324
+ });
325
+
326
+ await test('unknown command exits non-zero', () => {
327
+ try {
328
+ cli('this-command-does-not-exist');
329
+ throw new Error('Should have thrown');
330
+ } catch (err) {
331
+ // execSync throws on non-zero exit — that's what we want
332
+ if (err.message === 'Should have thrown') throw err;
333
+ return 'exited non-zero as expected';
334
+ }
335
+ });
336
+ }
337
+
338
+ // ══════════════════════════════════════════════════════════════════════════════
339
+ // SUITE 4: Security & Keychain
340
+ // ══════════════════════════════════════════════════════════════════════════════
341
+
342
+ async function testSecurity() {
343
+ if (args.suite && args.suite !== 'security') return;
344
+ suiteHeader('Security & Keychain', '🔐');
345
+
346
+ const tmpDir = join(homedir(), '.conductor-test-' + Date.now());
347
+ mkdirSync(join(tmpDir, 'keychain'), { recursive: true });
348
+
349
+ try {
350
+ const { Keychain } = await import('./dist/security/keychain.js');
351
+
352
+ await test('Keychain initializes', () => {
353
+ const kc = new Keychain(tmpDir);
354
+ if (!kc) throw new Error('Keychain constructor failed');
355
+ return 'ok';
356
+ });
357
+
358
+ await test('store and retrieve a credential', async () => {
359
+ const kc = new Keychain(tmpDir);
360
+ await kc.set('test-service', 'test-key', 'super-secret-value-123');
361
+ const val = await kc.get('test-service', 'test-key');
362
+ if (val !== 'super-secret-value-123') throw new Error(`Got: ${val}`);
363
+ return 'encrypt → decrypt roundtrip ok';
364
+ });
365
+
366
+ await test('different service/key combinations are isolated', async () => {
367
+ const kc = new Keychain(tmpDir);
368
+ await kc.set('service-a', 'key1', 'value-a1');
369
+ await kc.set('service-b', 'key1', 'value-b1');
370
+ const a = await kc.get('service-a', 'key1');
371
+ const b = await kc.get('service-b', 'key1');
372
+ if (a !== 'value-a1') throw new Error(`service-a got: ${a}`);
373
+ if (b !== 'value-b1') throw new Error(`service-b got: ${b}`);
374
+ return 'isolation ok';
375
+ });
376
+
377
+ await test('missing key returns null', async () => {
378
+ const kc = new Keychain(tmpDir);
379
+ const val = await kc.get('nonexistent', 'key');
380
+ if (val !== null) throw new Error(`Expected null, got: ${val}`);
381
+ return 'null returned';
382
+ });
383
+
384
+ await test('delete removes a credential', async () => {
385
+ const kc = new Keychain(tmpDir);
386
+ await kc.set('del-test', 'key', 'to-delete');
387
+ await kc.delete('del-test', 'key');
388
+ const val = await kc.get('del-test', 'key');
389
+ if (val !== null) throw new Error(`Still exists: ${val}`);
390
+ return 'deleted ok';
391
+ });
392
+
393
+ await test('encrypted file is not plaintext', async () => {
394
+ const kc = new Keychain(tmpDir);
395
+ await kc.set('plaintext-check', 'secret', 'my-secret-api-key-abc123');
396
+ // Find the written file and verify the raw contents don't contain the secret
397
+ const keychainDir = join(tmpDir, 'keychain');
398
+ const { readdirSync } = await import('fs');
399
+ const files = readdirSync(keychainDir);
400
+ for (const file of files) {
401
+ const raw = readFileSync(join(keychainDir, file), 'utf8');
402
+ if (raw.includes('my-secret-api-key-abc123')) {
403
+ throw new Error(`Plaintext secret found in ${file}!`);
404
+ }
405
+ }
406
+ return 'no plaintext secrets on disk';
407
+ });
408
+
409
+ await test('credential file has restricted permissions (non-Windows)', async () => {
410
+ if (platform() === 'win32') return 'skipped on Windows';
411
+ const keychainDir = join(tmpDir, 'keychain');
412
+ const stat = statSync(keychainDir);
413
+ const mode = (stat.mode & 0o777).toString(8);
414
+ // Should be 0700 (owner only)
415
+ if (mode !== '700') throw new Error(`Expected 700, got ${mode}`);
416
+ return `mode: ${mode}`;
417
+ });
418
+
419
+ await test('special characters in credentials', async () => {
420
+ const kc = new Keychain(tmpDir);
421
+ const special = 'p@$$w0rd!#%^&*()_+-=[]{}|;\':",./<>?`~\\n\\t';
422
+ await kc.set('special', 'chars', special);
423
+ const val = await kc.get('special', 'chars');
424
+ if (val !== special) throw new Error('Special chars mangled');
425
+ return 'special chars preserved';
426
+ });
427
+
428
+ await test('long credential value', async () => {
429
+ const kc = new Keychain(tmpDir);
430
+ const long = 'x'.repeat(4096);
431
+ await kc.set('long', 'value', long);
432
+ const val = await kc.get('long', 'value');
433
+ if (val !== long) throw new Error('Long value mangled');
434
+ return `${long.length} chars preserved`;
435
+ });
436
+
437
+ } finally {
438
+ // Clean up temp keychain
439
+ rmSync(tmpDir, { recursive: true, force: true });
440
+ }
441
+ }
442
+
443
+ // ══════════════════════════════════════════════════════════════════════════════
444
+ // SUITE 5: Config
445
+ // ══════════════════════════════════════════════════════════════════════════════
446
+
447
+ async function testConfig() {
448
+ if (args.suite && args.suite !== 'config') return;
449
+ suiteHeader('Config', '⚙️');
450
+
451
+ const { Conductor } = await import('./dist/core/conductor.js');
452
+ const conductor = new Conductor();
453
+
454
+ await test('Conductor initializes', () => {
455
+ if (!conductor) throw new Error('Conductor failed to init');
456
+ return 'ok';
457
+ });
458
+
459
+ await test('getConfig() returns config object', () => {
460
+ const config = conductor.getConfig();
461
+ if (!config) throw new Error('No config returned');
462
+ if (typeof config.getConfigDir !== 'function') throw new Error('Missing getConfigDir()');
463
+ return 'ok';
464
+ });
465
+
466
+ await test('config dir exists or is creatable', () => {
467
+ const dir = conductor.getConfig().getConfigDir();
468
+ if (!dir) throw new Error('No config dir returned');
469
+ mkdirSync(dir, { recursive: true });
470
+ if (!existsSync(dir)) throw new Error(`Could not create: ${dir}`);
471
+ return dir;
472
+ });
473
+
474
+ await test('OAuth config module loads', async () => {
475
+ const { getOAuthCredentials } = await import('./dist/config/oauth.js');
476
+ if (typeof getOAuthCredentials !== 'function') throw new Error('getOAuthCredentials not exported');
477
+ return 'OAuth config module ok';
478
+ });
479
+
480
+ await test('OAuth redirect URI defaults to localhost', async () => {
481
+ const { getOAuthCredentials } = await import('./dist/config/oauth.js');
482
+ // Set dummy env vars for test
483
+ process.env.CONDUCTOR_GOOGLE_CLIENT_ID = 'test-id';
484
+ process.env.CONDUCTOR_GOOGLE_CLIENT_SECRET = 'test-secret';
485
+
486
+ const creds = getOAuthCredentials(conductor, 'google');
487
+ if (!creds.redirectUri.includes('localhost')) {
488
+ throw new Error('Expected localhost redirect URI');
489
+ }
490
+
491
+ delete process.env.CONDUCTOR_GOOGLE_CLIENT_ID;
492
+ delete process.env.CONDUCTOR_GOOGLE_CLIENT_SECRET;
493
+ // Do not return the redirect URI itself to avoid logging potentially sensitive data
494
+ return 'ok';
495
+ });
496
+ }
497
+
498
+ // ══════════════════════════════════════════════════════════════════════════════
499
+ // SUITE 6: Plugin Manager
500
+ // ══════════════════════════════════════════════════════════════════════════════
501
+
502
+ async function testPluginManager() {
503
+ if (args.suite && args.suite !== 'plugins') return;
504
+ suiteHeader('Plugin Manager', '🔌');
505
+
506
+ const { Conductor } = await import('./dist/core/conductor.js');
507
+ const { PluginManager } = await import('./dist/plugins/manager.js');
508
+ const conductor = new Conductor();
509
+ const manager = new PluginManager(conductor);
510
+
511
+ await test('PluginManager initializes', () => {
512
+ if (!manager) throw new Error('PluginManager failed');
513
+ return 'ok';
514
+ });
515
+
516
+ await test('getAllBuiltinPlugins returns a consistent count', async () => {
517
+ const { getAllBuiltinPlugins } = await import('./dist/plugins/builtin/index.js');
518
+ const plugins = getAllBuiltinPlugins();
519
+ const expectedPluginCount = plugins.length; // Dynamically get the count
520
+ if (plugins.length < 10) throw new Error(`Expected at least 10 plugins, got ${plugins.length}`);
521
+ return `${plugins.length} plugins`;
522
+ });
523
+
524
+ await test('all plugins implement Plugin interface', async () => {
525
+ const { getAllBuiltinPlugins } = await import('./dist/plugins/builtin/index.js');
526
+ const plugins = getAllBuiltinPlugins();
527
+ const required = ['name', 'description', 'version'];
528
+ const bad = [];
529
+ for (const p of plugins) {
530
+ for (const field of required) {
531
+ if (!p[field]) bad.push(`${p.name || '?'} missing ${field}`);
532
+ }
533
+ if (typeof p.getTools !== 'function') bad.push(`${p.name} missing getTools()`);
534
+ if (typeof p.initialize !== 'function') bad.push(`${p.name} missing initialize()`);
535
+ }
536
+ if (bad.length) throw new Error(bad.join(', '));
537
+ return `${plugins.length} plugins valid`;
538
+ });
539
+
540
+ await test('total tool count is consistent', async () => {
541
+ const { getAllBuiltinPlugins } = await import('./dist/plugins/builtin/index.js');
542
+ const all = getAllBuiltinPlugins();
543
+ let total = 0;
544
+ for (const p of all) total += p.getTools().length;
545
+ // We expect at least a minimum set of tools, but it will grow
546
+ if (total < 100) throw new Error(`Got ${total}, expected ≥ 100`);
547
+ return `${total} tools`;
548
+ });
549
+
550
+ await test('all tool names follow snake_case', async () => {
551
+ const { getAllBuiltinPlugins } = await import('./dist/plugins/builtin/index.js');
552
+ const plugins = getAllBuiltinPlugins();
553
+ const bad = plugins.flatMap(p =>
554
+ p.getTools()
555
+ .filter(t => !/^[a-z][a-z0-9_]*$/.test(t.name))
556
+ .map(t => t.name)
557
+ );
558
+ if (bad.length) throw new Error(`Bad names: ${bad.join(', ')}`);
559
+ return 'all snake_case';
560
+ });
561
+
562
+ await test('all tools have descriptions', async () => {
563
+ const { getAllBuiltinPlugins } = await import('./dist/plugins/builtin/index.js');
564
+ const plugins = getAllBuiltinPlugins();
565
+ const bad = plugins.flatMap(p =>
566
+ p.getTools()
567
+ .filter(t => !t.description || t.description.length < 10)
568
+ .map(t => `${p.name}/${t.name}`)
569
+ );
570
+ if (bad.length) throw new Error(`Missing/short descriptions: ${bad.join(', ')}`);
571
+ return 'all described';
572
+ });
573
+
574
+ await test('all tool handlers return objects (not primitives)', async () => {
575
+ // Test a sample of synchronous tools that return immediately
576
+ const { getAllBuiltinPlugins } = await import('./dist/plugins/builtin/index.js');
577
+ const conductor = new Conductor();
578
+ const plugins = getAllBuiltinPlugins();
579
+ const fakeConductor = { getConfig: () => ({ getConfigDir: () => join(homedir(), '.conductor'), get: () => null }) };
580
+
581
+ for (const plugin of plugins) {
582
+ try {
583
+ await plugin.initialize(fakeConductor);
584
+ } catch {
585
+ // Some plugins (e.g. lumen) throw on init if their backend is unavailable.
586
+ // This is expected — we just skip those plugins for this test.
587
+ }
588
+ }
589
+
590
+ const instantTools = [
591
+ ['hash', 'generate_uuid', {}],
592
+ ['hash', 'generate_password', { length: 16 }],
593
+ ['calculator', 'calc_math', { expression: '1+1' }],
594
+ ['colors', 'color_convert', { color: '#ff0000', to: 'rgb' }],
595
+ ];
596
+
597
+ for (const [pluginName, toolName, input] of instantTools) {
598
+ const plugin = plugins.find(p => p.name === pluginName);
599
+ const tool = plugin?.getTools().find(t => t.name === toolName);
600
+ if (!tool) throw new Error(`${pluginName}/${toolName} not found`);
601
+ const result = await tool.handler(input);
602
+ if (typeof result !== 'object' || result === null) {
603
+ throw new Error(`${toolName} returned ${typeof result}`);
604
+ }
605
+ }
606
+ return `${instantTools.length} handler return types verified`;
607
+ });
608
+ }
609
+
610
+ // ══════════════════════════════════════════════════════════════════════════════
611
+ // SUITE 7: MCP Protocol
612
+ // ══════════════════════════════════════════════════════════════════════════════
613
+
614
+ async function testMCP() {
615
+ if (args.suite && args.suite !== 'mcp') return;
616
+ suiteHeader('MCP Protocol', '🔗');
617
+
618
+ await test('MCP server module loads', async () => {
619
+ const mod = await import('./dist/mcp/server.js');
620
+ if (typeof mod.startMCPServer !== 'function')
621
+ throw new Error('startMCPServer not exported');
622
+ return 'ok';
623
+ });
624
+
625
+ await test('MCP server responds to initialize request', async () => {
626
+ return new Promise((resolve, reject) => {
627
+ const proc = spawn('node', [join(__dir, 'dist/cli/index.js'), 'mcp', 'start'], {
628
+ stdio: ['pipe', 'pipe', 'pipe'],
629
+ cwd: __dir,
630
+ });
631
+
632
+ const initRequest = JSON.stringify({
633
+ jsonrpc: '2.0',
634
+ id: 1,
635
+ method: 'initialize',
636
+ params: {
637
+ protocolVersion: '2024-11-05',
638
+ capabilities: {},
639
+ clientInfo: { name: 'test', version: '1.0' },
640
+ },
641
+ }) + '\n';
642
+
643
+ let stdout = '';
644
+ let timedOut = false;
645
+
646
+ const timeout = setTimeout(() => {
647
+ timedOut = true;
648
+ proc.kill();
649
+ reject(new Error('MCP server timed out (5s)'));
650
+ }, 5000);
651
+
652
+ proc.stdout.on('data', (data) => {
653
+ stdout += data.toString();
654
+ try {
655
+ const lines = stdout.trim().split('\n');
656
+ for (const line of lines) {
657
+ if (!line.trim()) continue;
658
+ const response = JSON.parse(line);
659
+ if (response.id === 1 && response.result) {
660
+ clearTimeout(timeout);
661
+ proc.kill();
662
+ resolve(`protocolVersion: ${response.result.protocolVersion}`);
663
+ return;
664
+ }
665
+ }
666
+ } catch { }
667
+ });
668
+
669
+ proc.on('error', (err) => {
670
+ if (!timedOut) { clearTimeout(timeout); reject(err); }
671
+ });
672
+
673
+ proc.stdin.write(initRequest);
674
+ });
675
+ });
676
+
677
+ await test('MCP server responds to tools/list', async () => {
678
+ return new Promise((resolve, reject) => {
679
+ const proc = spawn('node', [join(__dir, 'dist/cli/index.js'), 'mcp', 'start'], {
680
+ stdio: ['pipe', 'pipe', 'pipe'],
681
+ cwd: __dir,
682
+ });
683
+
684
+ const messages = [
685
+ {
686
+ jsonrpc: '2.0', id: 1, method: 'initialize',
687
+ params: { protocolVersion: '2024-11-05', capabilities: {}, clientInfo: { name: 'test', version: '1.0' } }
688
+ },
689
+ { jsonrpc: '2.0', method: 'notifications/initialized', params: {} },
690
+ { jsonrpc: '2.0', id: 2, method: 'tools/list', params: {} },
691
+ ];
692
+
693
+ let stdout = '';
694
+ let timedOut = false;
695
+
696
+ const timeout = setTimeout(() => {
697
+ timedOut = true;
698
+ proc.kill();
699
+ reject(new Error('MCP tools/list timed out'));
700
+ }, 8000);
701
+
702
+ proc.stdout.on('data', (data) => {
703
+ stdout += data.toString();
704
+ try {
705
+ const lines = stdout.split('\n');
706
+ for (const line of lines) {
707
+ if (!line.trim()) continue;
708
+ try {
709
+ const response = JSON.parse(line);
710
+ if (response.id === 2 && response.result?.tools !== undefined) {
711
+ const toolCount = response.result.tools.length;
712
+ clearTimeout(timeout);
713
+ proc.kill();
714
+ // Tool count depends on which plugins are enabled in config —
715
+ // a fresh install may have zero enabled. Just verify the response shape.
716
+ resolve(`${toolCount} tools exposed via MCP (enable plugins with: conductor plugins enable <name>)`);
717
+ return;
718
+ }
719
+ } catch { }
720
+ }
721
+ } catch { }
722
+ });
723
+
724
+ proc.on('error', (err) => {
725
+ if (!timedOut) { clearTimeout(timeout); reject(err); }
726
+ });
727
+
728
+ for (const msg of messages) {
729
+ proc.stdin.write(JSON.stringify(msg) + '\n');
730
+ }
731
+ });
732
+ });
733
+
734
+ await test('MCP tool call returns valid JSON-RPC response', async () => {
735
+ // Tool availability depends on enabled plugins. Test the protocol layer directly:
736
+ // send an unknown tool and verify we get a proper JSON-RPC error back (not a crash).
737
+ return new Promise((resolve, reject) => {
738
+ const proc = spawn('node', [join(__dir, 'dist/cli/index.js'), 'mcp', 'start'], {
739
+ stdio: ['pipe', 'pipe', 'pipe'],
740
+ cwd: __dir,
741
+ });
742
+
743
+ const messages = [
744
+ {
745
+ jsonrpc: '2.0', id: 1, method: 'initialize',
746
+ params: { protocolVersion: '2024-11-05', capabilities: {}, clientInfo: { name: 'test', version: '1.0' } }
747
+ },
748
+ { jsonrpc: '2.0', method: 'notifications/initialized', params: {} },
749
+ {
750
+ jsonrpc: '2.0', id: 3, method: 'tools/call',
751
+ params: { name: '__nonexistent_tool__', arguments: {} }
752
+ },
753
+ ];
754
+
755
+ let stdout = '';
756
+ let timedOut = false;
757
+
758
+ const timeout = setTimeout(() => {
759
+ timedOut = true;
760
+ proc.kill();
761
+ reject(new Error('MCP tool call timed out'));
762
+ }, 8000);
763
+
764
+ proc.stdout.on('data', (data) => {
765
+ stdout += data.toString();
766
+ const lines = stdout.split('\n');
767
+ for (const line of lines) {
768
+ if (!line.trim()) continue;
769
+ try {
770
+ const response = JSON.parse(line);
771
+ if (response.id === 3) {
772
+ clearTimeout(timeout);
773
+ proc.kill();
774
+ // Should return a structured JSON-RPC error, not crash
775
+ if (response.error && response.error.code && response.error.message) {
776
+ resolve(`JSON-RPC error returned correctly: ${response.error.message}`);
777
+ } else if (response.result) {
778
+ resolve('Tool returned result (unexpected but valid JSON-RPC)');
779
+ } else {
780
+ reject(new Error(`Unexpected response shape: ${JSON.stringify(response)}`));
781
+ }
782
+ return;
783
+ }
784
+ } catch { }
785
+ }
786
+ });
787
+
788
+ proc.on('error', (err) => {
789
+ if (!timedOut) { clearTimeout(timeout); reject(err); }
790
+ });
791
+
792
+ for (const msg of messages) {
793
+ proc.stdin.write(JSON.stringify(msg) + '\n');
794
+ }
795
+ });
796
+ });
797
+ }
798
+
799
+ // ══════════════════════════════════════════════════════════════════════════════
800
+ // SUITE 8: AI Provider Modules
801
+ // ══════════════════════════════════════════════════════════════════════════════
802
+
803
+ async function testAIModules() {
804
+ if (args.suite && args.suite !== 'ai') return;
805
+ suiteHeader('AI Provider Modules', '🤖');
806
+
807
+ const providers = ['claude', 'openai', 'gemini', 'ollama', 'openrouter'];
808
+
809
+ for (const provider of providers) {
810
+ await test(`${provider} module loads`, async () => {
811
+ const mod = await import(`./dist/ai/${provider}.js`);
812
+ // Each should export a class
813
+ const Cls = Object.values(mod).find(v => typeof v === 'function');
814
+ if (!Cls) throw new Error(`No class exported from ${provider}.js`);
815
+ return 'ok';
816
+ });
817
+ }
818
+
819
+ await test('AIManager loads all providers', async () => {
820
+ const { AIManager } = await import('./dist/ai/manager.js');
821
+ if (!AIManager) throw new Error('AIManager not exported');
822
+ return 'ok';
823
+ });
824
+
825
+ await test('AIManager lists supported providers', async () => {
826
+ const { AIManager } = await import('./dist/ai/manager.js');
827
+ // Just verify the module exports the class correctly
828
+ if (typeof AIManager !== 'function') throw new Error('AIManager is not a class');
829
+ const supported = ['claude', 'openai', 'gemini', 'ollama', 'openrouter'];
830
+ return `${supported.length} providers defined`;
831
+ });
832
+ }
833
+
834
+ // ══════════════════════════════════════════════════════════════════════════════
835
+ // SUITE 9: Plugins (delegates to test.mjs logic)
836
+ // ══════════════════════════════════════════════════════════════════════════════
837
+
838
+ async function testPlugins() {
839
+ if (args.suite && args.suite !== 'plugins') return;
840
+ suiteHeader('Plugin Functionality', '🧩');
841
+ console.log(` ${c.dim}Running plugin tests via test.mjs...${c.reset}\n`);
842
+
843
+ // Run the plugin test suite as a subprocess so it inherits the full environment
844
+ const flags = [
845
+ args['skip-auth'] ? '--skip-auth' : '',
846
+ args.write ? '--write' : '',
847
+ args.verbose ? '--verbose' : '',
848
+ ].filter(Boolean);
849
+
850
+ try {
851
+ const out = execSync(`node ${join(__dir, 'test.mjs')} ${flags.join(' ')}`, {
852
+ encoding: 'utf8',
853
+ cwd: __dir,
854
+ timeout: 60000,
855
+ // Don't throw on non-zero exit — we'll parse failures from stdout
856
+ stdio: ['pipe', 'pipe', 'pipe'],
857
+ });
858
+
859
+ // Parse results from output
860
+ const passMatch = out.match(/✓ (\d+) passed/);
861
+ const skipMatch = out.match(/○ (\d+) skipped/);
862
+ const failMatch = out.match(/✗ (\d+) failed/);
863
+
864
+ const passed = passMatch ? parseInt(passMatch[1]) : 0;
865
+ const skipped = skipMatch ? parseInt(skipMatch[1]) : 0;
866
+ const failed = failMatch ? parseInt(failMatch[1]) : 0;
867
+
868
+ totals.passed += passed;
869
+ totals.skipped += skipped;
870
+
871
+ if (failed > 0) {
872
+ totals.failed += failed;
873
+ // Extract failure details from output
874
+ const failureLines = out.split('\n')
875
+ .filter(l => l.includes('✗'))
876
+ .slice(0, 5)
877
+ .map(l => l.trim());
878
+ throw new Error(`${failed} plugin tests failed:\n ${failureLines.join('\n ')}`);
879
+ }
880
+
881
+ console.log(` ${c.green}✓${c.reset} ${passed} plugin tests passed, ${skipped} skipped`);
882
+ } catch (err) {
883
+ // execSync throws on non-zero exit — capture stdout from the error object
884
+ const out = err.stdout ?? '';
885
+ const passMatch = out.match(/✓ (\d+) passed/);
886
+ const skipMatch = out.match(/○ (\d+) skipped/);
887
+ const failMatch = out.match(/✗ (\d+) failed/);
888
+ const passed = passMatch ? parseInt(passMatch[1]) : 0;
889
+ const skipped = skipMatch ? parseInt(skipMatch[1]) : 0;
890
+ const failed = failMatch ? parseInt(failMatch[1]) : 0;
891
+ totals.passed += passed;
892
+ totals.skipped += skipped;
893
+ totals.failed += failed;
894
+ if (failed > 0) {
895
+ const failureLines = out.split('\n').filter(l => l.includes('✗')).slice(0, 5).map(l => l.replace(/\x1b\[[0-9;]*m/g, '').trim());
896
+ console.log(` ${c.red}✗${c.reset} ${failed} plugin test(s) failed — ${passed} passed, ${skipped} skipped`);
897
+ for (const line of failureLines) console.log(` ${c.dim}${line}${c.reset}`);
898
+ totals.errors.push({ suite: 'Plugin Functionality', test: 'plugin tests', error: `${failed} failed` });
899
+ } else if (!err.message.includes('plugin tests failed')) {
900
+ fail('plugin test runner', err);
901
+ }
902
+ }
903
+ }
904
+
905
+ // ══════════════════════════════════════════════════════════════════════════════
906
+ // SUITE 10: Environment
907
+ // ══════════════════════════════════════════════════════════════════════════════
908
+
909
+ async function testEnvironment() {
910
+ if (args.suite && args.suite !== 'env') return;
911
+ suiteHeader('Environment', '🖥️');
912
+
913
+ await test('Node.js version ≥ 18', () => {
914
+ const ver = process.version; // e.g. v20.11.0
915
+ const major = parseInt(ver.slice(1).split('.')[0]);
916
+ if (major < 18) throw new Error(`Node ${ver} is too old — need v18+`);
917
+ return ver;
918
+ });
919
+
920
+ await test('npm is available', () => {
921
+ const ver = execSync('npm --version', { encoding: 'utf8' }).trim();
922
+ return `npm ${ver}`;
923
+ });
924
+
925
+ await test('running on supported platform', () => {
926
+ const plat = platform();
927
+ if (!['darwin', 'linux', 'win32'].includes(plat))
928
+ throw new Error(`Unknown platform: ${plat}`);
929
+ return plat;
930
+ });
931
+
932
+ await test('sufficient memory (≥ 512MB)', () => {
933
+ const mb = Math.round(totalmem() / 1024 / 1024);
934
+ if (mb < 512) throw new Error(`Only ${mb}MB RAM`);
935
+ return `${mb}MB available`;
936
+ });
937
+
938
+ await test('home directory is accessible', () => {
939
+ const home = homedir();
940
+ if (!existsSync(home)) throw new Error(`Home not found: ${home}`);
941
+ return home;
942
+ });
943
+
944
+ await test('~/.conductor dir exists or is creatable', () => {
945
+ const dir = join(homedir(), '.conductor');
946
+ mkdirSync(dir, { recursive: true });
947
+ if (!existsSync(dir)) throw new Error('Could not create ~/.conductor');
948
+ return dir;
949
+ });
950
+
951
+ await test('no credential leaks in dist output', () => {
952
+ // Scan compiled JS for anything that looks like a secret
953
+ const distDir = join(__dir, 'dist');
954
+ const dangerPatterns = [
955
+ /sk-[a-zA-Z0-9]{32,}/, // OpenAI keys
956
+ /AIza[a-zA-Z0-9]{30,}/, // Google API keys
957
+ /ghp_[a-zA-Z0-9]{30,}/, // GitHub PATs
958
+ ];
959
+
960
+ function scanDir(dir) {
961
+ for (const entry of readdirSync(dir)) {
962
+ const full = join(dir, entry);
963
+ if (statSync(full).isDirectory()) {
964
+ scanDir(full);
965
+ } else if (entry.endsWith('.js')) {
966
+ const content = readFileSync(full, 'utf8');
967
+ for (const pattern of dangerPatterns) {
968
+ if (pattern.test(content)) {
969
+ throw new Error(`Potential credential leak in ${full}`);
970
+ }
971
+ }
972
+ }
973
+ }
974
+ }
975
+ scanDir(distDir);
976
+ return 'no credential patterns found';
977
+ });
978
+ }
979
+
980
+ // ══════════════════════════════════════════════════════════════════════════════
981
+ // SUITE 11: New Plugin Tests (Phase 5)
982
+ // ══════════════════════════════════════════════════════════════════════════════
983
+
984
+ async function testNewPlugins() {
985
+ if (args.suite && args.suite !== 'newplugins') return;
986
+ suiteHeader('New Plugin Tests (Phase 5)', '🔌');
987
+
988
+ // ── Keychain ESM fix verification ──────────────────────────────────────────
989
+ await test('keychain.ts uses ESM imports (no require)', () => {
990
+ const src = readFileSync(join(__dir, 'src/security/keychain.ts'), 'utf8');
991
+ if (src.includes("require('fs')")) throw new Error('Found CJS require("fs") — ESM fix not applied');
992
+ if (!src.includes("import { readFileSync, mkdirSync, writeFileSync }")) {
993
+ throw new Error('Missing ESM fs imports');
994
+ }
995
+ return 'ESM imports confirmed';
996
+ });
997
+
998
+ // ── Lumen requiresApproval verification ────────────────────────────────────
999
+ await test('lumen shell tools have requiresApproval', () => {
1000
+ const src = readFileSync(join(__dir, 'src/plugins/builtin/lumen.ts'), 'utf8');
1001
+ const shellTools = ['lumen_ask', 'lumen_shell', 'lumen_write_file', 'lumen_fix_bug'];
1002
+ for (const t of shellTools) {
1003
+ const idx = src.indexOf(`name: '${t}'`);
1004
+ if (idx === -1) throw new Error(`Tool ${t} not found`);
1005
+ // Look up to 2000 chars ahead (handlers can be large)
1006
+ const snippet = src.slice(idx, idx + 2000);
1007
+ if (!snippet.includes('requiresApproval: true')) {
1008
+ throw new Error(`${t} missing requiresApproval: true`);
1009
+ }
1010
+ }
1011
+ return `${shellTools.length} tools verified`;
1012
+ });
1013
+
1014
+ // ── Memory plugin userId isolation ─────────────────────────────────────────
1015
+ await test('memory plugin has setUserId method', () => {
1016
+ const src = readFileSync(join(__dir, 'src/plugins/builtin/memory.ts'), 'utf8');
1017
+ if (!src.includes('setUserId')) throw new Error('setUserId method missing');
1018
+ if (src.includes("searchMessages('%'")) throw new Error("Still uses '%' wildcard");
1019
+ return 'userId isolation confirmed';
1020
+ });
1021
+
1022
+ // ── Todoist plugin has Zod validation ──────────────────────────────────────
1023
+ await test('todoist plugin uses Zod validation', () => {
1024
+ const src = readFileSync(join(__dir, 'src/plugins/builtin/todoist.ts'), 'utf8');
1025
+ if (!src.includes("from 'zod'")) throw new Error('Zod not imported');
1026
+ if (!src.includes('schema.parse')) throw new Error('schema.parse not found');
1027
+ const parseCount = (src.match(/schema\.parse/g) || []).length;
1028
+ if (parseCount < 5) throw new Error(`Expected at least 5 schema.parse calls, found ${parseCount}`);
1029
+ return `${parseCount} Zod validations`;
1030
+ });
1031
+
1032
+ // ── Dashboard auth token injection ─────────────────────────────────────────
1033
+ await test('dashboard server injects auth token meta tag', () => {
1034
+ const src = readFileSync(join(__dir, 'src/dashboard/server.ts'), 'utf8');
1035
+ if (!src.includes('dashboard-token')) throw new Error('dashboard-token meta injection missing');
1036
+ if (!src.includes('127.0.0.1')) throw new Error('Server not binding to 127.0.0.1');
1037
+ if (!src.includes('Authorization: Bearer')) throw new Error('Auth middleware missing');
1038
+ return 'Auth injection confirmed';
1039
+ });
1040
+
1041
+ // ── Dashboard HTML reads token ──────────────────────────────────────────────
1042
+ await test('dashboard HTML reads token from meta tag', () => {
1043
+ const html = readFileSync(join(__dir, 'src/dashboard/index.html'), 'utf8');
1044
+ if (!html.includes('dashboard-token')) throw new Error('dashboard-token meta read missing');
1045
+ if (!html.includes("'Authorization'")) throw new Error('Authorization header not sent in api()');
1046
+ return 'Token auth in HTML confirmed';
1047
+ });
1048
+
1049
+ // ── Database debounce ──────────────────────────────────────────────────────
1050
+ await test('database uses write debouncing', () => {
1051
+ const src = readFileSync(join(__dir, 'src/core/database.ts'), 'utf8');
1052
+ if (!src.includes('scheduleFlush')) throw new Error('scheduleFlush not found');
1053
+ if (!src.includes('DEBOUNCE_MS')) throw new Error('DEBOUNCE_MS constant missing');
1054
+ if (!src.includes('flush()')) throw new Error('flush() method missing');
1055
+ // Count remaining await this.save() in write methods (should only be in createTables and save())
1056
+ const awaitSaveCount = (src.match(/await this\.save\(\)/g) || []).length;
1057
+ if (awaitSaveCount > 2) throw new Error(`Too many direct await this.save() calls: ${awaitSaveCount}`);
1058
+ return `Debounce confirmed, ${awaitSaveCount} direct save calls remaining`;
1059
+ });
1060
+
1061
+ // ── Proactive cycle mutex ──────────────────────────────────────────────────
1062
+ await test('conductor proactive cycle has mutex guard', () => {
1063
+ const src = readFileSync(join(__dir, 'src/core/conductor.ts'), 'utf8');
1064
+ if (!src.includes('_cycleRunning')) throw new Error('_cycleRunning flag missing');
1065
+ if (!src.includes('Skipping cycle — previous cycle still running')) {
1066
+ throw new Error('Mutex warning message missing');
1067
+ }
1068
+ return 'Mutex guard confirmed';
1069
+ });
1070
+
1071
+ // ── Version from package.json ──────────────────────────────────────────────
1072
+ await test('CLI uses dynamic version from package.json', () => {
1073
+ const src = readFileSync(join(__dir, 'src/cli/index.ts'), 'utf8');
1074
+ if (src.includes(".version('0.1.0')")) throw new Error('Hardcoded version 0.1.0 still present');
1075
+ if (!src.includes('pkgVersion')) throw new Error('pkgVersion variable missing');
1076
+ return 'Dynamic version confirmed';
1077
+ });
1078
+
1079
+ // ── Plugin getContext() interface ──────────────────────────────────────────
1080
+ await test('Plugin interface has getContext() method', () => {
1081
+ const src = readFileSync(join(__dir, 'src/plugins/manager.ts'), 'utf8');
1082
+ if (!src.includes('getContext?()')) throw new Error('getContext() not in Plugin interface');
1083
+ return 'getContext() in interface confirmed';
1084
+ });
1085
+
1086
+ // ── GmailPlugin has getContext ────────────────────────────────────────────
1087
+ await test('GmailPlugin implements getContext()', () => {
1088
+ const src = readFileSync(join(__dir, 'src/plugins/builtin/gmail.ts'), 'utf8');
1089
+ if (!src.includes('async getContext()')) throw new Error('getContext() not in GmailPlugin');
1090
+ return 'GmailPlugin.getContext() confirmed';
1091
+ });
1092
+
1093
+ // ── Todoist has getContext ────────────────────────────────────────────────
1094
+ await test('TodoistPlugin implements getContext()', () => {
1095
+ const src = readFileSync(join(__dir, 'src/plugins/builtin/todoist.ts'), 'utf8');
1096
+ if (!src.includes('async getContext()')) throw new Error('getContext() not in TodoistPlugin');
1097
+ return 'TodoistPlugin.getContext() confirmed';
1098
+ });
1099
+
1100
+ // ── Interfaces file exists ─────────────────────────────────────────────────
1101
+ await test('src/core/interfaces.ts exists with required interfaces', () => {
1102
+ const src = readFileSync(join(__dir, 'src/core/interfaces.ts'), 'utf8');
1103
+ if (!src.includes('IConfig')) throw new Error('IConfig not found');
1104
+ if (!src.includes('IDatabase')) throw new Error('IDatabase not found');
1105
+ if (!src.includes('IPluginRegistry')) throw new Error('IPluginRegistry not found');
1106
+ if (!src.includes('ToolContext')) throw new Error('ToolContext not found');
1107
+ if (!src.includes('ConductorNotification')) throw new Error('ConductorNotification not found');
1108
+ return 'All interfaces confirmed';
1109
+ });
1110
+
1111
+ // ── .gitignore has google-creds.json ──────────────────────────────────────
1112
+ await test('.gitignore includes google-creds.json', () => {
1113
+ const src = readFileSync(join(__dir, '.gitignore'), 'utf8');
1114
+ if (!src.includes('google-creds.json')) throw new Error('google-creds.json not in .gitignore');
1115
+ return '.gitignore verified';
1116
+ });
1117
+
1118
+ // ── .env.example exists ────────────────────────────────────────────────────
1119
+ await test('.env.example exists', () => {
1120
+ if (!existsSync(join(__dir, '.env.example'))) throw new Error('.env.example not found');
1121
+ const content = readFileSync(join(__dir, '.env.example'), 'utf8');
1122
+ if (!content.includes('GOOGLE_CLIENT_SECRET')) throw new Error('GOOGLE_CLIENT_SECRET not documented');
1123
+ return '.env.example verified';
1124
+ });
1125
+
1126
+ // ── ConductorNotification replaces raw text ───────────────────────────────
1127
+ await test('conductor uses ConductorNotification type', () => {
1128
+ const src = readFileSync(join(__dir, 'src/core/conductor.ts'), 'utf8');
1129
+ if (!src.includes('ConductorNotification')) throw new Error('ConductorNotification not imported');
1130
+ if (src.includes('notifyUser(`')) throw new Error('notifyUser still called with template string');
1131
+ return 'Structured notifications confirmed';
1132
+ });
1133
+
1134
+ // ── MCP server uses SDK ────────────────────────────────────────────────────
1135
+ await test('MCP server uses @modelcontextprotocol/sdk', () => {
1136
+ const src = readFileSync(join(__dir, 'src/mcp/server.ts'), 'utf8');
1137
+ if (!src.includes('@modelcontextprotocol/sdk')) throw new Error('SDK not imported');
1138
+ if (!src.includes('StdioServerTransport')) throw new Error('StdioServerTransport not used');
1139
+ if (!src.includes('ListToolsRequestSchema')) throw new Error('ListToolsRequestSchema not used');
1140
+ return 'MCP SDK integration confirmed';
1141
+ });
1142
+ }
1143
+
1144
+ // ══════════════════════════════════════════════════════════════════════════════
1145
+ // SUITE 12: Performance Benchmarks (Phase 5.4)
1146
+ // ══════════════════════════════════════════════════════════════════════════════
1147
+
1148
+ async function testBenchmarks() {
1149
+ if (args.suite && args.suite !== 'bench') return;
1150
+ suiteHeader('Performance Benchmarks', 'P');
1151
+
1152
+ const WARN_THRESHOLD_MS = 2000;
1153
+
1154
+ await test('config initialization is fast', async () => {
1155
+ const { ConfigManager } = await import('./dist/core/config.js');
1156
+ const start = Date.now();
1157
+ const cfg = new ConfigManager();
1158
+ await cfg.initialize();
1159
+ const elapsed = Date.now() - start;
1160
+ if (elapsed > WARN_THRESHOLD_MS) {
1161
+ console.log(` WARNING: config init took ${elapsed}ms (> ${WARN_THRESHOLD_MS}ms threshold)`);
1162
+ }
1163
+ return `${elapsed}ms`;
1164
+ });
1165
+
1166
+ await test('database init + write + read cycle', async () => {
1167
+ const { DatabaseManager } = await import('./dist/core/database.js');
1168
+ const tmpDir = join(homedir(), '.conductor-bench-test');
1169
+ mkdirSync(tmpDir, { recursive: true });
1170
+ const db = new DatabaseManager(tmpDir);
1171
+ const start = Date.now();
1172
+ await db.initialize();
1173
+ await db.logActivity('bench', 'test_write', 'benchmark');
1174
+ const activity = await db.getRecentActivity(1);
1175
+ const elapsed = Date.now() - start;
1176
+ await db.close();
1177
+ // Cleanup
1178
+ try { rmSync(join(tmpDir, 'conductor.db'), { force: true }); } catch {}
1179
+ if (activity.length === 0) throw new Error('Write+read cycle produced no results');
1180
+ if (elapsed > WARN_THRESHOLD_MS) {
1181
+ console.log(` WARNING: db cycle took ${elapsed}ms (> ${WARN_THRESHOLD_MS}ms threshold)`);
1182
+ }
1183
+ return `${elapsed}ms, 1 record`;
1184
+ });
1185
+
1186
+ await test('plugin loading benchmark (builtin index)', async () => {
1187
+ const start = Date.now();
1188
+ const { getAllBuiltinPlugins } = await import('./dist/plugins/builtin/index.js');
1189
+ const plugins = getAllBuiltinPlugins();
1190
+ const elapsed = Date.now() - start;
1191
+ if (elapsed > WARN_THRESHOLD_MS) {
1192
+ console.log(` WARNING: plugin load took ${elapsed}ms (> ${WARN_THRESHOLD_MS}ms threshold)`);
1193
+ }
1194
+ return `${elapsed}ms, ${plugins.length} plugins loaded`;
1195
+ });
1196
+ }
1197
+
1198
+ // ══════════════════════════════════════════════════════════════════════════════
1199
+ // MAIN
1200
+ // ══════════════════════════════════════════════════════════════════════════════
1201
+
1202
+ async function main() {
1203
+ console.log(`\n${c.bold}${c.cyan}╔══════════════════════════════════════════════════╗${c.reset}`);
1204
+ console.log(`${c.bold}${c.cyan}║ Conductor Full System Test Suite ║${c.reset}`);
1205
+ console.log(`${c.bold}${c.cyan}║ CLI · MCP · Security · Config · Plugins · Files║${c.reset}`);
1206
+ console.log(`${c.bold}${c.cyan}╚══════════════════════════════════════════════════╝${c.reset}`);
1207
+
1208
+ if (args['skip-auth']) console.log(`\n${c.yellow} ⚠ --skip-auth: skipping auth plugin tests${c.reset}`);
1209
+ if (!args.write) console.log(`${c.dim} --write not set: skipping destructive write tests${c.reset}`);
1210
+ if (args.suite) console.log(`${c.dim} --suite: running ${args.suite} only${c.reset}`);
1211
+
1212
+ const start = Date.now();
1213
+
1214
+ await testEnvironment();
1215
+ await testFiles();
1216
+ await testBuild();
1217
+ await testSecurity();
1218
+ await testConfig();
1219
+ await testAIModules();
1220
+ await testPluginManager();
1221
+ await testCLI();
1222
+ await testMCP();
1223
+ await testPlugins();
1224
+ await testNewPlugins();
1225
+ await testBenchmarks();
1226
+
1227
+ const elapsed = ((Date.now() - start) / 1000).toFixed(1);
1228
+ const total = totals.passed + totals.failed + totals.skipped;
1229
+
1230
+ console.log(`\n${c.bold}${'═'.repeat(60)}${c.reset}`);
1231
+ console.log(`${c.bold}Full System Results${c.reset} ${elapsed}s · ${total} tests`);
1232
+ console.log(` ${c.green}✓ ${totals.passed} passed${c.reset}`);
1233
+ if (totals.skipped) console.log(` ${c.dim}○ ${totals.skipped} skipped${c.reset}`);
1234
+ if (totals.failed) console.log(` ${c.red}✗ ${totals.failed} failed${c.reset}`);
1235
+
1236
+ if (totals.errors.length > 0) {
1237
+ console.log(`\n${c.bold}${c.red}Failures:${c.reset}`);
1238
+ for (const e of totals.errors) {
1239
+ console.log(` ${c.red}✗${c.reset} ${c.bold}[${e.suite}]${c.reset} ${e.test}`);
1240
+ console.log(` ${c.dim}${e.error.split('\n')[0]}${c.reset}`);
1241
+ }
1242
+ }
1243
+
1244
+ const allGood = totals.failed === 0;
1245
+ console.log(`\n${allGood
1246
+ ? `${c.bold}${c.green} ✅ All systems go.${c.reset}`
1247
+ : `${c.bold}${c.red} ❌ ${totals.failed} test(s) need attention.${c.reset}`
1248
+ }\n`);
1249
+
1250
+ process.exit(allGood ? 0 : 1);
1251
+ }
1252
+
1253
+ main().catch(err => {
1254
+ console.error(`\n${c.red}Fatal:${c.reset}`, err);
1255
+ process.exit(1);
1256
+ });