@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/install.sh ADDED
@@ -0,0 +1,1119 @@
1
+ #!/usr/bin/env bash
2
+ # Conductor — The AI Tool Hub
3
+ # install.sh: one-line installer
4
+ # Usage: curl -fsSL https://conductor.thealxlabs.ca/install.sh | bash
5
+ # or: curl -fsSL https://raw.githubusercontent.com/thegreatalxx/conductor/main/install.sh | bash
6
+
7
+ set -euo pipefail
8
+
9
+ BOLD='\033[1m'
10
+ DIM='\033[2m'
11
+ ORANGE='\033[38;5;208m'
12
+ GREEN='\033[32m'
13
+ YELLOW='\033[33m'
14
+ RED='\033[31m'
15
+ RESET='\033[0m'
16
+
17
+ NPM_PACKAGE="@conductor/cli"
18
+ MIN_NODE_MAJOR=18
19
+ UPGRADE_MODE=false
20
+
21
+ # ── Helpers ───────────────────────────────────────────────────────────────────
22
+
23
+ info() { echo -e " ${DIM}${*}${RESET}"; }
24
+ success() { echo -e " ${GREEN}✓${RESET} ${*}"; }
25
+ warn() { echo -e " ${YELLOW}⚠${RESET} ${*}"; }
26
+ die() { echo -e " ${RED}✗${RESET} ${*}" >&2; echo "" ; exit 1; }
27
+
28
+ print_header() {
29
+ echo ""
30
+ echo -e "${BOLD} ┌─────────────────────────────────────────┐${RESET}"
31
+ echo -e "${BOLD} │${RESET}${ORANGE} ♦ Conductor — The AI Tool Hub ${RESET}${BOLD}│${RESET}"
32
+ echo -e "${BOLD} │${RESET}${DIM} One MCP server. 100+ tools. Any AI. ${RESET}${BOLD}│${RESET}"
33
+ echo -e "${BOLD} └─────────────────────────────────────────┘${RESET}"
34
+ echo ""
35
+ }
36
+
37
+ # ── Platform detection ────────────────────────────────────────────────────────
38
+
39
+ detect_platform() {
40
+ OS="unknown"
41
+ case "$(uname -s)" in
42
+ Linux*)
43
+ if grep -qi microsoft /proc/version 2>/dev/null; then
44
+ OS="wsl"
45
+ else
46
+ OS="linux"
47
+ fi
48
+ ;;
49
+ Darwin*) OS="macos" ;;
50
+ CYGWIN*|MINGW*|MSYS*) OS="windows" ;;
51
+ esac
52
+ [ "$OS" = "unknown" ] && OS="linux"
53
+ }
54
+
55
+ # ── Dependency checks ─────────────────────────────────────────────────────────
56
+
57
+ check_node() {
58
+ if ! command -v node &>/dev/null; then
59
+ echo ""
60
+ die "Node.js is not installed. Install Node.js ${MIN_NODE_MAJOR}+ from https://nodejs.org"
61
+ fi
62
+
63
+ NODE_VERSION_RAW="$(node --version)"
64
+ NODE_VERSION_CLEAN="${NODE_VERSION_RAW#v}"
65
+ NODE_MAJOR="${NODE_VERSION_CLEAN%%.*}"
66
+
67
+ if [ "${NODE_MAJOR}" -lt "${MIN_NODE_MAJOR}" ]; then
68
+ die "Node.js ${NODE_VERSION_RAW} found, but ${MIN_NODE_MAJOR}+ required. Upgrade at https://nodejs.org"
69
+ fi
70
+
71
+ success "Node.js ${NODE_VERSION_RAW}"
72
+ }
73
+
74
+ check_npm() {
75
+ if ! command -v npm &>/dev/null; then
76
+ die "npm not found. Reinstall Node.js from https://nodejs.org"
77
+ fi
78
+ success "npm v$(npm --version)"
79
+ }
80
+
81
+ # ── Already installed? ────────────────────────────────────────────────────────
82
+
83
+ check_existing() {
84
+ if command -v conductor &>/dev/null; then
85
+ EXISTING_VERSION="$(conductor --version 2>/dev/null || echo 'unknown')"
86
+ echo ""
87
+ warn "Conductor ${EXISTING_VERSION} is already installed."
88
+
89
+ if [ -t 0 ]; then
90
+ printf " Upgrade to latest? [Y/n] "
91
+ read -r REPLY
92
+ echo ""
93
+ case "${REPLY:-Y}" in
94
+ [Nn]*) info "Skipping upgrade."; echo ""; exit 0 ;;
95
+ esac
96
+ else
97
+ info "Running non-interactively — upgrading automatically."
98
+ echo ""
99
+ fi
100
+
101
+ UPGRADE_MODE=true
102
+ fi
103
+ }
104
+
105
+ # ── Installation ──────────────────────────────────────────────────────────────
106
+
107
+ install_conductor() {
108
+ echo ""
109
+ if [ "${UPGRADE_MODE}" = true ]; then
110
+ info "Upgrading ${NPM_PACKAGE} to latest..."
111
+ else
112
+ info "Installing ${NPM_PACKAGE}..."
113
+ fi
114
+ echo ""
115
+
116
+ NPM_PREFIX="$(npm config get prefix 2>/dev/null || echo "")"
117
+
118
+ if [[ "$NPM_PREFIX" == /usr* ]] && [ "$(id -u)" -ne 0 ]; then
119
+ if command -v sudo &>/dev/null; then
120
+ info "Global npm prefix is ${NPM_PREFIX} — using sudo"
121
+ sudo npm install -g "${NPM_PACKAGE}" 2>&1 | grep -v "^npm warn" | tail -3 || \
122
+ die "Installation failed. Try: sudo npm install -g ${NPM_PACKAGE}"
123
+ else
124
+ die "Cannot write to ${NPM_PREFIX}. Run: sudo npm install -g ${NPM_PACKAGE}"
125
+ fi
126
+ else
127
+ npm install -g "${NPM_PACKAGE}" 2>&1 | grep -v "^npm warn" | tail -3 || \
128
+ die "Installation failed. Check npm output above for details."
129
+ fi
130
+ }
131
+
132
+ verify_installation() {
133
+ if ! command -v conductor &>/dev/null; then
134
+ echo ""
135
+ warn "conductor binary not found in PATH after install."
136
+ NPM_BIN="$(npm config get prefix 2>/dev/null)/bin"
137
+ echo ""
138
+ info "Add npm's global bin directory to your PATH:"
139
+ echo -e " ${BOLD}export PATH=\"${NPM_BIN}:\$PATH\"${RESET}"
140
+ echo ""
141
+ info "Then reload your shell and run: conductor init"
142
+ echo ""
143
+ return 1
144
+ fi
145
+
146
+ INSTALLED_VERSION="$(conductor --version 2>/dev/null || echo 'unknown')"
147
+ success "conductor v${INSTALLED_VERSION} is ready"
148
+ return 0
149
+ }
150
+
151
+ # ── Completion message ────────────────────────────────────────────────────────
152
+
153
+ print_next_steps() {
154
+ echo ""
155
+ echo -e "${GREEN}${BOLD} ✓ Conductor installed successfully!${RESET}"
156
+ echo ""
157
+ echo -e " ${BOLD}Get started in under 2 minutes:${RESET}"
158
+ echo ""
159
+ echo -e " ${BOLD}conductor init${RESET}"
160
+ echo -e " ${DIM}Interactive setup — AI provider, plugins, and MCP client config${RESET}"
161
+ echo ""
162
+ echo -e " ${DIM}Other commands:${RESET}"
163
+ echo -e " ${DIM}conductor onboard${RESET} Pick and configure plugins"
164
+ echo -e " ${DIM}conductor mcp setup${RESET} Auto-configure Claude Desktop / Cursor / Cline"
165
+ echo -e " ${DIM}conductor mcp start${RESET} Start the MCP server (stdio)"
166
+ echo -e " ${DIM}conductor doctor${RESET} Diagnose issues"
167
+ echo -e " ${DIM}conductor dashboard${RESET} Open web dashboard"
168
+ echo ""
169
+ echo -e " ${DIM}Docs: https://conductor.thealxlabs.ca${RESET}"
170
+ echo ""
171
+ }
172
+
173
+ # ── Main ──────────────────────────────────────────────────────────────────────
174
+
175
+ main() {
176
+ print_header
177
+ detect_platform
178
+ check_node
179
+ check_npm
180
+ check_existing
181
+ install_conductor
182
+ verify_installation
183
+ print_next_steps
184
+ }
185
+
186
+ main "$@"
187
+
188
+ # ─────────────────────────────────────────────────────────────────────────────
189
+ # The section below is LEGACY — kept for reference / local dev installs.
190
+ # The main() above handles all standard installs via npm.
191
+ # To use the legacy source-based install, set CONDUCTOR_LEGACY=1 before running.
192
+ # ─────────────────────────────────────────────────────────────────────────────
193
+ [ "${CONDUCTOR_LEGACY:-0}" = "1" ] || exit 0
194
+
195
+ IFS=$'\n\t'
196
+
197
+ # ── Terminal colours ──────────────────────────────────────────────────────────
198
+ if [[ -t 1 ]] && command -v tput &>/dev/null; then
199
+ RED=$(tput setaf 1) GREEN=$(tput setaf 2) YELLOW=$(tput setaf 3)
200
+ BLUE=$(tput setaf 4) CYAN=$(tput setaf 6) BOLD=$(tput bold)
201
+ DIM=$(tput dim 2>/dev/null || printf '') ITALIC=''
202
+ RESET=$(tput sgr0)
203
+ else
204
+ RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m'
205
+ BLUE='\033[0;34m' CYAN='\033[0;36m' BOLD='\033[1m'
206
+ DIM='\033[2m' ITALIC='\033[3m' RESET='\033[0m'
207
+ fi
208
+
209
+ # ── Logging ───────────────────────────────────────────────────────────────────
210
+ info() { echo -e " ${BLUE}▶${RESET} $*"; }
211
+ success() { echo -e " ${GREEN}✓${RESET} $*"; }
212
+ warn() { echo -e " ${YELLOW}⚠${RESET} $*" >&2; }
213
+ fail() { echo -e "\n ${RED}✗ FATAL:${RESET} $*\n" >&2; exit 1; }
214
+ hint() { echo -e " ${DIM}${ITALIC}$*${RESET}"; }
215
+
216
+ step() {
217
+ echo ""
218
+ echo -e " ${BOLD}${CYAN}┌──────────────────────────────────────────────${RESET}"
219
+ echo -e " ${BOLD}${CYAN}│ $*${RESET}"
220
+ echo -e " ${BOLD}${CYAN}└──────────────────────────────────────────────${RESET}"
221
+ }
222
+
223
+ # ── Cleanup & signal handling ─────────────────────────────────────────────────
224
+ _SPINNER_PID=""
225
+ _TMPFILES=()
226
+
227
+ cleanup() {
228
+ local exit_code=$?
229
+ if [[ -n "$_SPINNER_PID" ]] && kill -0 "$_SPINNER_PID" 2>/dev/null; then
230
+ kill "$_SPINNER_PID" 2>/dev/null || true
231
+ wait "$_SPINNER_PID" 2>/dev/null || true
232
+ fi
233
+ printf '\r\033[K' 2>/dev/null || true
234
+ # Securely wipe any temp files that may contain credentials
235
+ for f in "${_TMPFILES[@]:-}"; do
236
+ if [[ -f "$f" ]]; then
237
+ # Overwrite with zeros before removing
238
+ dd if=/dev/zero of="$f" bs=1 count="$(wc -c < "$f")" 2>/dev/null || true
239
+ rm -f "$f" 2>/dev/null || true
240
+ fi
241
+ done
242
+ if [[ $exit_code -ne 0 ]]; then
243
+ echo ""
244
+ warn "Installation did not complete (exit $exit_code). Re-run to retry — it is idempotent."
245
+ fi
246
+ }
247
+ trap cleanup EXIT
248
+ trap 'exit 130' INT
249
+ trap 'exit 143' TERM
250
+
251
+ # ── Prompt helpers (always /dev/tty so piped installs work) ───────────────────
252
+ prompt_input() {
253
+ local prompt="$1" varname="$2" default="${3:-}"
254
+ if [[ -n "$default" ]]; then
255
+ printf " ${CYAN}?${RESET} ${BOLD}%s${RESET} ${DIM}[%s]${RESET}: " "$prompt" "$default" >/dev/tty
256
+ else
257
+ printf " ${CYAN}?${RESET} ${BOLD}%s${RESET}: " "$prompt" >/dev/tty
258
+ fi
259
+ local _val; IFS= read -r _val </dev/tty || _val=""
260
+ printf -v "$varname" '%s' "${_val:-$default}"
261
+ }
262
+
263
+ prompt_yn() {
264
+ local prompt="$1" varname="$2" default="${3:-y}"
265
+ local hint_str="Y/n"; [[ "$default" == "n" ]] && hint_str="y/N"
266
+ printf " ${CYAN}?${RESET} ${BOLD}%s${RESET} ${DIM}[%s]${RESET}: " "$prompt" "$hint_str" >/dev/tty
267
+ local _val; IFS= read -r _val </dev/tty || _val=""
268
+ _val="${_val:-$default}"
269
+ if [[ "$_val" =~ ^[Yy] ]]; then printf -v "$varname" '%s' "true"
270
+ else printf -v "$varname" '%s' "false"; fi
271
+ }
272
+
273
+ prompt_secret() {
274
+ local prompt="$1" varname="$2"
275
+ printf " ${CYAN}?${RESET} ${BOLD}%s${RESET}: " "$prompt" >/dev/tty
276
+ local _sec; IFS= read -rs _sec </dev/tty || _sec=""
277
+ echo "" >/dev/tty
278
+ printf -v "$varname" '%s' "$_sec"
279
+ }
280
+
281
+ # ── Spinner ───────────────────────────────────────────────────────────────────
282
+ spinner() {
283
+ local pid=$1 label="${2:-Working}"
284
+ local frames=('⠋' '⠙' '⠹' '⠸' '⠼' '⠴' '⠦' '⠧' '⠇' '⠏')
285
+ local i=0
286
+ while kill -0 "$pid" 2>/dev/null; do
287
+ printf "\r ${CYAN}%s${RESET} ${DIM}%s...${RESET}" "${frames[$i]}" "$label" >/dev/tty
288
+ i=$(( (i + 1) % ${#frames[@]} ))
289
+ sleep 0.1
290
+ done
291
+ printf '\r\033[K' >/dev/tty
292
+ }
293
+
294
+ run_step() {
295
+ local label="$1"; shift
296
+ local log_file; log_file=$(mktemp)
297
+ _TMPFILES+=("$log_file")
298
+ "$@" >"$log_file" 2>&1 &
299
+ local pid=$!
300
+ spinner "$pid" "$label"
301
+ local rc=0; wait "$pid" || rc=$?
302
+ if [[ $rc -ne 0 ]]; then
303
+ echo ""; warn "Command failed (exit $rc): $*"
304
+ warn "Last output:"; tail -20 "$log_file" | sed 's/^/ /' >&2
305
+ rm -f "$log_file"; return $rc
306
+ fi
307
+ rm -f "$log_file"; return 0
308
+ }
309
+
310
+ # ── Config helpers ────────────────────────────────────────────────────────────
311
+ CONFIG_DIR="$HOME/.conductor"
312
+ CONFIG_FILE="$CONFIG_DIR/config.json"
313
+
314
+ ensure_dirs() {
315
+ mkdir -p "$CONFIG_DIR"/{keychain,plugins,logs}
316
+ chmod 700 "$CONFIG_DIR/keychain"
317
+ chmod 700 "$CONFIG_DIR"
318
+ }
319
+
320
+ update_config() {
321
+ local json_str="$1"
322
+ local tmp_file; tmp_file=$(mktemp "${CONFIG_FILE}.XXXXXX")
323
+ _TMPFILES+=("$tmp_file")
324
+ python3 -c "
325
+ import json, sys, os
326
+ config_path, tmp_path, new_json = sys.argv[1], sys.argv[2], sys.argv[3]
327
+ existing = {}
328
+ if os.path.exists(config_path):
329
+ try:
330
+ with open(config_path) as f: existing = json.load(f)
331
+ except Exception: pass
332
+ def merge(a, b):
333
+ for k, v in b.items():
334
+ if k in a and isinstance(a[k], dict) and isinstance(v, dict): merge(a[k], v)
335
+ else: a[k] = v
336
+ return a
337
+ with open(tmp_path, 'w') as f:
338
+ json.dump(merge(existing, json.loads(new_json)), f, indent=2)
339
+ f.flush(); os.fsync(f.fileno())
340
+ os.replace(tmp_path, config_path)
341
+ " "$CONFIG_FILE" "$tmp_file" "$json_str"
342
+ }
343
+
344
+ # ── save_cred: reads value from stdin to avoid ps aux exposure ────────────────
345
+ # Usage: echo "myvalue" | save_cred "service" "key"
346
+ save_cred() {
347
+ local service="$1" key="$2"
348
+ # Read value from stdin — never passed as argument (audit fix: prevents ps aux leakage)
349
+ local val
350
+ val=$(cat)
351
+ node - "$CONFIG_DIR" "$service" "$key" << JSEOF
352
+ const crypto=require('crypto'),fs=require('fs'),path=require('path'),os=require('os'),{execSync}=require('child_process');
353
+ const [,,configDir,service,key]=process.argv;
354
+ // Read value from stdin
355
+ let val='';
356
+ try{val=fs.readFileSync('/dev/stdin','utf8').trim();}catch{process.exit(1);}
357
+ const kd=path.join(configDir,'keychain'); fs.mkdirSync(kd,{recursive:true,mode:0o700});
358
+ function ms(){
359
+ for(const s of['/etc/machine-id','/var/lib/dbus/machine-id'])try{const d=fs.readFileSync(s,'utf8').trim();if(d)return d}catch{}
360
+ if(process.platform==='darwin')try{const o=execSync("ioreg -rd1 -c IOPlatformExpertDevice|awk '/IOPlatformUUID/{print \$NF}'",{encoding:'utf8',stdio:['pipe','pipe','pipe']}).trim().replace(/"/g,'');if(o)return o}catch{}
361
+ const f=path.join(kd,'machine_secret');
362
+ try{const d=fs.readFileSync(f,'utf8').trim();if(d)return d}catch{}
363
+ try{const s=crypto.randomUUID();fs.writeFileSync(f,s,{mode:0o600});return s}catch{}
364
+ // Do not fall back to hostname — fail loudly
365
+ throw new Error('Cannot derive machine ID for keychain encryption');
366
+ }
367
+ const salt=crypto.createHash('sha256').update('conductor:keychain:v1').digest();
368
+ const mk=crypto.scryptSync(ms(),salt,32,{N:16384,r:8,p:1});
369
+ const iv=crypto.randomBytes(12),c=crypto.createCipheriv('aes-256-gcm',mk,iv);
370
+ let e=c.update(val,'utf8','hex'); e+=c.final('hex');
371
+ const t=c.getAuthTag().toString('hex');
372
+ const out=['v2',iv.toString('hex'),t,e].join(':');
373
+ const fp=path.join(kd,\`\${service}.\${key}.enc\`); const tmp=fp+'.tmp';
374
+ fs.writeFileSync(tmp,out,{mode:0o600}); fs.renameSync(tmp,fp);
375
+ JSEOF
376
+ printf '%s' "$val" | node - "$CONFIG_DIR" "$service" "$key"
377
+ }
378
+
379
+ # Convenience wrapper: save_cred_val "service" "key" "value"
380
+ # Pipes value through stdin so it never appears in process list
381
+ save_cred_val() {
382
+ printf '%s' "$3" | save_cred "$1" "$2"
383
+ }
384
+
385
+ add_plugin() {
386
+ python3 -c "
387
+ import json, sys, os
388
+ p, pl = sys.argv[1], sys.argv[2]
389
+ c = {}
390
+ if os.path.exists(p):
391
+ try:
392
+ with open(p) as f: c = json.load(f)
393
+ except Exception: pass
394
+ for k in ['installed','enabled']:
395
+ lst = c.get('plugins',{}).get(k,[])
396
+ if pl not in lst: lst.append(pl)
397
+ c.setdefault('plugins',{})[k] = lst
398
+ with open(p,'w') as f: json.dump(c, f, indent=2)
399
+ " "$CONFIG_FILE" "$1"
400
+ }
401
+
402
+ version_gte() {
403
+ local IFS='.'
404
+ read -ra A <<< "$1"; read -ra B <<< "$2"
405
+ for i in 0 1 2; do
406
+ local a="${A[$i]:-0}" b="${B[$i]:-0}"
407
+ (( a > b )) && return 0; (( a < b )) && return 1
408
+ done
409
+ return 0
410
+ }
411
+
412
+ # ── HEADER ────────────────────────────────────────────────────────────────────
413
+ clear 2>/dev/null || true
414
+ echo ""
415
+ printf "${BOLD}${CYAN}"
416
+ cat << 'BANNER'
417
+ ██████╗ ██████╗ ███╗ ██╗██████╗ ██╗ ██╗ ██████╗████████╗ ██████╗ ██████╗
418
+ ██╔════╝██╔═══██╗████╗ ██║██╔══██╗██║ ██║██╔════╝╚══██╔══╝██╔═══██╗██╔══██╗
419
+ ██║ ██║ ██║██╔██╗ ██║██║ ██║██║ ██║██║ ██║ ██║ ██║██████╔╝
420
+ ██║ ██║ ██║██║╚██╗██║██║ ██║██║ ██║██║ ██║ ██║ ██║██╔══██╗
421
+ ╚██████╗╚██████╔╝██║ ╚████║██████╔╝╚██████╔╝╚██████╗ ██║ ╚██████╔╝██║ ██║
422
+ ╚═════╝ ╚═════╝ ╚═╝ ╚═══╝╚═════╝ ╚═════╝ ╚═════╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝
423
+ BANNER
424
+ printf "${RESET}\n"
425
+ echo -e " ${DIM}Your AI Integration Hub · by ${CYAN}TheAlxLabs${RESET}"
426
+ echo -e " ${DIM}────────────────────────────────────────────────────────────${RESET}"
427
+ echo ""
428
+ echo -e " ${ITALIC}Connect your services. Talk to your tools. Let AI handle the rest.${RESET}"
429
+ echo ""
430
+
431
+ # ── STEP 1: PREFLIGHT ─────────────────────────────────────────────────────────
432
+ step "01 / Preflight Check"
433
+
434
+ OS="$(uname -s 2>/dev/null || echo Unknown)"
435
+ ARCH="$(uname -m 2>/dev/null || echo Unknown)"
436
+ case "$OS" in
437
+ Darwin) PLATFORM="macos" ;;
438
+ Linux) PLATFORM="linux" ;;
439
+ MINGW*|MSYS*|CYGWIN*) PLATFORM="windows" ;;
440
+ *) PLATFORM="unknown"; warn "Unknown OS: $OS" ;;
441
+ esac
442
+ info "Platform: ${BOLD}$OS${RESET} ($ARCH)"
443
+
444
+ command -v node &>/dev/null || fail "Node.js not found. Install v18+ from https://nodejs.org"
445
+ NODE_RAW=$(node --version 2>/dev/null || echo "v0")
446
+ NODE_VER="${NODE_RAW#v}"; NODE_MAJOR="${NODE_VER%%.*}"
447
+ # Audit fix: guard against empty NODE_MAJOR
448
+ [[ -z "$NODE_MAJOR" ]] && fail "Could not determine Node.js version from: $NODE_RAW"
449
+ [[ "$NODE_MAJOR" =~ ^[0-9]+$ ]] && (( NODE_MAJOR >= 18 )) || \
450
+ fail "Node.js v18+ required (found $NODE_RAW). Upgrade at https://nodejs.org"
451
+ success "Node.js $NODE_RAW"
452
+
453
+ command -v npm &>/dev/null || fail "npm not found. Reinstall Node.js from https://nodejs.org"
454
+ success "npm $(npm --version)"
455
+
456
+ command -v python3 &>/dev/null || fail "Python 3 not found. Install python3 and re-run."
457
+ PY_VER=$(python3 -c 'import sys; print(".".join(map(str,sys.version_info[:2])))' 2>/dev/null || echo "0.0")
458
+ version_gte "$PY_VER" "3.6" || fail "Python 3.6+ required (found $PY_VER)."
459
+ success "Python $PY_VER"
460
+
461
+ command -v git &>/dev/null && success "git $(git --version | awk '{print $3}')" || warn "git not found"
462
+ command -v curl &>/dev/null && success "curl $(curl --version | head -1 | awk '{print $2}')" || \
463
+ fail "curl not found — required for installation"
464
+
465
+ ensure_dirs
466
+ success "Config dirs ready ($CONFIG_DIR)"
467
+
468
+ # ── STEP 2: INSTALL & BUILD ───────────────────────────────────────────────────
469
+ step "02 / Install & Build"
470
+
471
+ if [[ -f "package.json" ]] && grep -q '"conductor-hub"' package.json 2>/dev/null; then
472
+ CONDUCTOR_DIR="$(pwd)"
473
+ info "Using current directory: $CONDUCTOR_DIR"
474
+ else
475
+ CONDUCTOR_DIR="$HOME/.conductor-src"
476
+ if [[ -d "$CONDUCTOR_DIR/.git" ]]; then
477
+ info "Updating existing source..."
478
+ command -v git &>/dev/null && \
479
+ (cd "$CONDUCTOR_DIR" && git pull --ff-only --quiet 2>/dev/null) || \
480
+ warn "git pull failed — continuing with existing source"
481
+ elif command -v git &>/dev/null; then
482
+ info "Cloning from GitHub..."
483
+ run_step "Cloning conductor" \
484
+ git clone --depth=1 --quiet https://github.com/thealxlabs/conductor.git "$CONDUCTOR_DIR" || \
485
+ fail "Clone failed. Check your internet or run:\n git clone https://github.com/thealxlabs/conductor.git"
486
+ else
487
+ fail "git not found. Clone manually:\n git clone https://github.com/thealxlabs/conductor.git && cd conductor && bash install.sh"
488
+ fi
489
+ fi
490
+
491
+ cd "$CONDUCTOR_DIR"
492
+
493
+ info "Installing dependencies..."
494
+ run_step "Installing dependencies" npm install --no-fund --no-audit || \
495
+ fail "npm install failed. Run 'npm install' in $CONDUCTOR_DIR to see the error."
496
+ success "Dependencies installed"
497
+
498
+ info "Building TypeScript..."
499
+ run_step "Compiling" npm run build || \
500
+ fail "Build failed. Run 'npm run build' in $CONDUCTOR_DIR to see the error."
501
+
502
+ chmod +x "$CONDUCTOR_DIR/dist/cli/index.js" 2>/dev/null || true
503
+ [[ -d "dist" ]] || fail "dist/ not found after build."
504
+ success "Build complete"
505
+
506
+ chmod +x dist/cli/index.js 2>/dev/null || true
507
+
508
+ info "Linking conductor command..."
509
+ CONDUCTOR_BIN=""
510
+
511
+ _try_npm_link() {
512
+ npm link --silent 2>/dev/null || return 1
513
+ local linked; linked=$(command -v conductor 2>/dev/null || true)
514
+ [[ -z "$linked" ]] && return 1
515
+ chmod +x "$linked" 2>/dev/null || true
516
+ local real; real=$(readlink -f "$linked" 2>/dev/null || readlink "$linked" 2>/dev/null || true)
517
+ [[ -n "$real" ]] && chmod +x "$real" 2>/dev/null || true
518
+ CONDUCTOR_BIN="$linked"; return 0
519
+ }
520
+
521
+ _try_global_install() {
522
+ npm install -g . --silent 2>/dev/null || \
523
+ sudo npm install -g . --silent 2>/dev/null || return 1
524
+ CONDUCTOR_BIN=$(command -v conductor 2>/dev/null || true)
525
+ [[ -n "$CONDUCTOR_BIN" ]] || return 1; return 0
526
+ }
527
+
528
+ _fallback_local_bin() {
529
+ local BIN="$HOME/.local/bin"; mkdir -p "$BIN"
530
+ printf '#!/usr/bin/env bash\nexec node "%s/dist/cli/index.js" "$@"\n' "$CONDUCTOR_DIR" > "$BIN/conductor"
531
+ chmod +x "$BIN/conductor"
532
+ for RC in "$HOME/.zshrc" "$HOME/.bashrc" "$HOME/.profile"; do
533
+ if [[ -f "$RC" ]] && ! grep -qF '.local/bin' "$RC" 2>/dev/null; then
534
+ printf '\nexport PATH="$HOME/.local/bin:$PATH"\n' >> "$RC"; break
535
+ fi
536
+ done
537
+ export PATH="$BIN:$PATH"
538
+ CONDUCTOR_BIN="$BIN/conductor"
539
+ hint "Added ~/.local/bin to PATH — restart your shell or: export PATH=\"\$HOME/.local/bin:\$PATH\""
540
+ return 0
541
+ }
542
+
543
+ if _try_npm_link; then
544
+ success "Linked via npm link"
545
+ elif _try_global_install; then
546
+ success "Installed globally via npm"
547
+ elif _fallback_local_bin; then
548
+ success "Installed to ~/.local/bin/conductor"
549
+ else
550
+ fail "Could not install conductor. Run manually:\n node $CONDUCTOR_DIR/dist/cli/index.js"
551
+ fi
552
+
553
+ echo ""
554
+ echo -e " ${BOLD}${GREEN}conductor${RESET} ${DIM}→ $CONDUCTOR_BIN${RESET}"
555
+
556
+ # ── STEP 3: AI PROVIDER ───────────────────────────────────────────────────────
557
+ step "03 / AI Provider ${DIM}powers natural language${RESET}"
558
+ echo ""
559
+ echo -e " ${CYAN}1${RESET} ${BOLD}Claude${RESET} ${DIM}· Anthropic · console.anthropic.com/settings/keys${RESET}
560
+ ${CYAN}2${RESET} ${BOLD}OpenAI${RESET} ${DIM}· GPT-4o · platform.openai.com/api-keys${RESET}
561
+ ${CYAN}3${RESET} ${BOLD}Gemini${RESET} ${DIM}· Google · aistudio.google.com/apikey${RESET}
562
+ ${CYAN}4${RESET} ${BOLD}OpenRouter${RESET} ${DIM}· Multi-model · openrouter.ai/keys${RESET}
563
+ ${CYAN}5${RESET} ${BOLD}Ollama${RESET} ${DIM}· Local · no API key needed${RESET}
564
+ ${CYAN}6${RESET} ${BOLD}Skip${RESET} ${DIM}· configure later: conductor ai setup${RESET}
565
+ "
566
+ echo ""
567
+
568
+ prompt_input "Choose" AI_CHOICE "6"
569
+ AI_PROVIDER_SET=""
570
+
571
+ case "$AI_CHOICE" in
572
+ 1)
573
+ prompt_secret "Anthropic API key" API_KEY
574
+ if [[ -n "$API_KEY" ]]; then
575
+ save_cred_val "anthropic" "api_key" "$API_KEY"
576
+ update_config '{"ai":{"provider":"claude"}}'
577
+ success "Claude configured"; AI_PROVIDER_SET="claude"
578
+ else warn "Skipped — run later: conductor ai setup"; fi ;;
579
+ 2)
580
+ prompt_secret "OpenAI API key" API_KEY
581
+ if [[ -n "$API_KEY" ]]; then
582
+ save_cred_val "openai" "api_key" "$API_KEY"
583
+ update_config '{"ai":{"provider":"openai"}}'
584
+ success "OpenAI configured"
585
+ hint "This key is also used by the Memory plugin for semantic embeddings."
586
+ AI_PROVIDER_SET="openai"
587
+ else warn "Skipped — run later: conductor ai setup"; fi ;;
588
+ 3)
589
+ prompt_secret "Gemini API key" API_KEY
590
+ if [[ -n "$API_KEY" ]]; then
591
+ save_cred_val "gemini" "api_key" "$API_KEY"
592
+ update_config '{"ai":{"provider":"gemini"}}'
593
+ success "Gemini configured"; AI_PROVIDER_SET="gemini"
594
+ else warn "Skipped — run later: conductor ai setup"; fi ;;
595
+ 4)
596
+ prompt_secret "OpenRouter API key" API_KEY
597
+ if [[ -n "$API_KEY" ]]; then
598
+ save_cred_val "openrouter" "api_key" "$API_KEY"
599
+ update_config '{"ai":{"provider":"openrouter"}}'
600
+ success "OpenRouter configured"
601
+ AI_PROVIDER_SET="openrouter"
602
+ else warn "Skipped — run later: conductor ai setup"; fi ;;
603
+ 5)
604
+ prompt_input "Ollama model" OLLAMA_MODEL "llama3.2"
605
+ # Audit fix: use python3 to safely JSON-encode the model name
606
+ OLLAMA_JSON=$(python3 -c "import json,sys; print(json.dumps(sys.argv[1]))" "$OLLAMA_MODEL")
607
+ update_config "{\"ai\":{\"provider\":\"ollama\",\"model\":${OLLAMA_JSON},\"local_config\":{\"endpoint\":\"http://localhost:11434\"}}}"
608
+ success "Ollama configured with $OLLAMA_MODEL"
609
+ hint "Make sure Ollama is running: ollama serve"
610
+ AI_PROVIDER_SET="ollama" ;;
611
+ *)
612
+ warn "Skipped — run later: conductor ai setup" ;;
613
+ esac
614
+
615
+ # ── STEP 4: MEMORY PLUGIN ─────────────────────────────────────────────────────
616
+ step "04 / Memory Plugin ${DIM}long-term context across conversations${RESET}"
617
+ hint "Stores facts, preferences, and decisions — recalled automatically by the AI"
618
+ hint "Works in keyword mode without OpenAI; semantic search requires an OpenAI key"
619
+ echo ""
620
+
621
+ if [[ "$AI_PROVIDER_SET" == "openai" ]]; then
622
+ hint "Your OpenAI key (already set) will power semantic memory search."
623
+ update_config '{"plugins":{"memory":{"enabled":true}}}'
624
+ add_plugin "memory"
625
+ success "Memory plugin enabled with semantic search"
626
+ else
627
+ prompt_yn "Enable memory plugin?" SETUP_MEM "y"
628
+ if [[ "$SETUP_MEM" == "true" ]]; then
629
+ if [[ "$AI_PROVIDER_SET" != "openai" ]]; then
630
+ prompt_yn "Add an OpenAI key for semantic search? (optional — keyword search otherwise)" WANT_OAI "n"
631
+ if [[ "$WANT_OAI" == "true" ]]; then
632
+ prompt_secret "OpenAI API key" OAI_KEY
633
+ if [[ -n "$OAI_KEY" ]]; then
634
+ save_cred_val "openai" "api_key" "$OAI_KEY"
635
+ success "OpenAI key saved for embeddings"
636
+ else
637
+ warn "No key entered — memory will use keyword search"
638
+ fi
639
+ fi
640
+ fi
641
+ update_config '{"plugins":{"memory":{"enabled":true}}}'
642
+ add_plugin "memory"
643
+ success "Memory plugin enabled"
644
+ else
645
+ warn "Skipped — enable later: conductor plugins enable memory"
646
+ fi
647
+ fi
648
+
649
+ # ── STEP 5: GOOGLE SERVICES ───────────────────────────────────────────────────
650
+ step "05 / Google Services ${DIM}Gmail · Calendar · Drive${RESET}"
651
+ hint "Uses Google OAuth — powered by Conductor's shared OAuth app"
652
+ echo ""
653
+
654
+ prompt_yn "Enable Gmail, Google Calendar, and Google Drive plugins?" SETUP_GOOGLE "y"
655
+
656
+ if [[ "$SETUP_GOOGLE" == "true" ]]; then
657
+ HAVE_GOOGLE_TOKEN="false"
658
+
659
+ if [[ "${AI_PROVIDER_SET:-}" == "gemini" ]]; then
660
+ HAVE_GOOGLE_TOKEN="true"
661
+ hint "Using your Gemini Google token for Gmail/Calendar/Drive."
662
+ else
663
+ STORED=$(node -e "
664
+ const p=require('path'),fs=require('fs');
665
+ const f=p.join(process.env.HOME,'.conductor','keychain','google.access_token.enc');
666
+ process.stdout.write(fs.existsSync(f)?'yes':'no');
667
+ " 2>/dev/null || echo "no")
668
+ [[ "$STORED" == "yes" ]] && HAVE_GOOGLE_TOKEN="true"
669
+ fi
670
+
671
+ # Fetch Google OAuth credentials from Vercel (secret stored server-side, never in repo)
672
+ info "Fetching Google OAuth configuration..."
673
+ OAUTH_JSON=""
674
+ if command -v curl &>/dev/null; then
675
+ OAUTH_JSON=$(curl -fsSL --max-time 10 \
676
+ -H "User-Agent: Conductor-Installer/1.0" \
677
+ -H "X-Conductor-Install: true" \
678
+ "https://conductor.thealxlabs.ca/api/oauth-config" 2>/dev/null || echo "")
679
+ fi
680
+
681
+ if [[ -n "$OAUTH_JSON" ]]; then
682
+ GOOGLE_CLIENT_ID=$(python3 -c "import json,sys; d=json.loads(sys.argv[1]); print(d.get('client_id',''))" "$OAUTH_JSON" 2>/dev/null || echo "")
683
+ GOOGLE_CLIENT_SECRET=$(python3 -c "import json,sys; d=json.loads(sys.argv[1]); print(d.get('client_secret',''))" "$OAUTH_JSON" 2>/dev/null || echo "")
684
+
685
+ if [[ -n "$GOOGLE_CLIENT_ID" && -n "$GOOGLE_CLIENT_SECRET" ]]; then
686
+ save_cred_val "google_oauth" "client_id" "$GOOGLE_CLIENT_ID"
687
+ save_cred_val "google_oauth" "client_secret" "$GOOGLE_CLIENT_SECRET"
688
+ success "Google OAuth app credentials saved to keychain"
689
+ # Unset vars immediately — don't leave secrets in shell environment
690
+ unset GOOGLE_CLIENT_ID GOOGLE_CLIENT_SECRET OAUTH_JSON
691
+ else
692
+ warn "Could not parse OAuth credentials — Google features may not work"
693
+ warn "Run later: conductor auth google"
694
+ unset OAUTH_JSON
695
+ fi
696
+ else
697
+ warn "Could not fetch Google OAuth config (no internet or service unavailable)"
698
+ warn "Run later: conductor auth google"
699
+ fi
700
+
701
+ if [[ "$HAVE_GOOGLE_TOKEN" != "true" ]]; then
702
+ echo ""
703
+ echo -e " ${DIM}After installation, authenticate with: ${CYAN}conductor auth google${RESET}"
704
+ echo -e " ${DIM}This opens your browser — no extra setup needed.${RESET}"
705
+ echo ""
706
+ fi
707
+
708
+ update_config '{"plugins":{"gmail":{"enabled":true},"gcal":{"enabled":true},"gdrive":{"enabled":true}}}'
709
+ add_plugin "gmail"; add_plugin "gcal"; add_plugin "gdrive"
710
+ success "Gmail, Calendar, and Drive plugins enabled"
711
+ [[ "$HAVE_GOOGLE_TOKEN" != "true" ]] && hint "Authenticate with: conductor auth google"
712
+
713
+ else
714
+ warn "Skipped — enable later: conductor plugins enable gmail gcal gdrive"
715
+ fi
716
+
717
+ # ── STEP 6: NOTION ────────────────────────────────────────────────────────────
718
+ step "06 / Notion ${DIM}optional${RESET}"
719
+ hint "Read and write Notion pages and databases"
720
+ hint "Get your integration token at: https://www.notion.so/my-integrations"
721
+ echo ""
722
+
723
+ prompt_yn "Set up Notion?" SETUP_NOTION "n"
724
+ if [[ "$SETUP_NOTION" == "true" ]]; then
725
+ prompt_secret "Notion Integration Token (ntn_...)" NOTION_TOKEN
726
+ if [[ -n "$NOTION_TOKEN" ]]; then
727
+ info "Verifying token..."
728
+ NOTION_STATUS="unknown"
729
+ if command -v curl &>/dev/null; then
730
+ NOTION_STATUS=$(curl -sf --max-time 8 \
731
+ -X POST "https://api.notion.com/v1/search" \
732
+ -H "Authorization: Bearer ${NOTION_TOKEN}" \
733
+ -H "Notion-Version: 2022-06-28" \
734
+ -H "Content-Type: application/json" \
735
+ -d '{"query":"","page_size":1}' 2>/dev/null \
736
+ | python3 -c "import json,sys; d=json.load(sys.stdin); print('ok' if 'results' in d else 'fail')" 2>/dev/null \
737
+ || echo "fail")
738
+ fi
739
+ if [[ "$NOTION_STATUS" == "ok" ]]; then
740
+ save_cred_val "notion" "api_key" "$NOTION_TOKEN"
741
+ update_config '{"plugins":{"notion":{"enabled":true}}}'
742
+ add_plugin "notion"
743
+ success "Notion connected"
744
+ hint "Share pages with your integration in Notion's connection settings."
745
+ else
746
+ warn "Verification failed (bad token or no internet)."
747
+ prompt_yn "Save token anyway?" SAVE_NOTION "n"
748
+ if [[ "$SAVE_NOTION" == "true" ]]; then
749
+ save_cred_val "notion" "api_key" "$NOTION_TOKEN"
750
+ update_config '{"plugins":{"notion":{"enabled":true}}}'
751
+ add_plugin "notion"
752
+ warn "Saved unverified."
753
+ fi
754
+ fi
755
+ else
756
+ warn "Skipped — run later: conductor plugins config notion token <TOKEN>"
757
+ fi
758
+ fi
759
+
760
+ # ── STEP 7: X (TWITTER) ───────────────────────────────────────────────────────
761
+ step "07 / X ${DIM}optional${RESET}"
762
+ hint "Search tweets, get timelines, post — requires X Developer account"
763
+ hint "Get credentials at: https://developer.x.com"
764
+ echo ""
765
+
766
+ prompt_yn "Set up X?" SETUP_X "n"
767
+ if [[ "$SETUP_X" == "true" ]]; then
768
+ echo ""
769
+ echo -e " ${CYAN}1${RESET} ${BOLD}Read-only${RESET} ${DIM}· Bearer Token only · search + timelines${RESET}"
770
+ echo -e " ${CYAN}2${RESET} ${BOLD}Full access${RESET} ${DIM}· Bearer + OAuth 1.0a · also post/like/delete${RESET}"
771
+ echo ""
772
+ prompt_input "Choose" X_LEVEL "1"
773
+ prompt_secret "Bearer Token" X_BEARER
774
+ if [[ -n "$X_BEARER" ]]; then
775
+ info "Verifying bearer token..."
776
+ X_OK="fail"
777
+ if command -v curl &>/dev/null; then
778
+ X_OK=$(curl -sf --max-time 8 \
779
+ "https://api.twitter.com/2/users/by/username/x" \
780
+ -H "Authorization: Bearer ${X_BEARER}" 2>/dev/null \
781
+ | python3 -c "import json,sys; d=json.load(sys.stdin); print('ok' if 'data' in d else 'fail')" 2>/dev/null \
782
+ || echo "fail")
783
+ fi
784
+ save_cred_val "x" "bearer_token" "$X_BEARER"
785
+ [[ "$X_OK" == "ok" ]] && success "X Bearer Token verified" || warn "Could not verify token — saved anyway"
786
+
787
+ if [[ "$X_LEVEL" == "2" ]]; then
788
+ echo ""; hint "OAuth 1.0a credentials for write access:"
789
+ prompt_secret "API Key (Consumer Key)" X_API_KEY
790
+ prompt_secret "API Secret (Consumer Secret)" X_API_SECRET
791
+ prompt_secret "Access Token" X_ACCESS_TOKEN
792
+ prompt_secret "Access Token Secret" X_ACCESS_SECRET
793
+ if [[ -n "$X_API_KEY" ]]; then save_cred_val "x" "api_key" "$X_API_KEY"; fi
794
+ if [[ -n "$X_API_SECRET" ]]; then save_cred_val "x" "api_secret" "$X_API_SECRET"; fi
795
+ if [[ -n "$X_ACCESS_TOKEN" ]]; then save_cred_val "x" "access_token" "$X_ACCESS_TOKEN"; fi
796
+ if [[ -n "$X_ACCESS_SECRET" ]]; then save_cred_val "x" "access_secret" "$X_ACCESS_SECRET"; fi
797
+ [[ -n "$X_API_KEY" && -n "$X_API_SECRET" && -n "$X_ACCESS_TOKEN" && -n "$X_ACCESS_SECRET" ]] && \
798
+ success "X write credentials saved" || warn "Some fields empty — write access may not work"
799
+ fi
800
+
801
+ update_config '{"plugins":{"x":{"enabled":true}}}'
802
+ add_plugin "x"
803
+ success "X plugin enabled"
804
+ else
805
+ warn "Skipped — run later: conductor plugins config x bearer_token <TOKEN>"
806
+ fi
807
+ fi
808
+
809
+ # ── STEP 8: SPOTIFY ───────────────────────────────────────────────────────────
810
+ step "08 / Spotify ${DIM}optional${RESET}"
811
+ hint "Playback control, search, playlists, recommendations"
812
+ hint "Get credentials at: https://developer.spotify.com/dashboard"
813
+ echo ""
814
+
815
+ prompt_yn "Set up Spotify?" SETUP_SPOTIFY "n"
816
+ if [[ "$SETUP_SPOTIFY" == "true" ]]; then
817
+ echo ""
818
+ echo -e " ${DIM}Create an app at ${CYAN}https://developer.spotify.com/dashboard${RESET}"
819
+ echo -e " ${DIM}Add redirect URI: ${CYAN}http://localhost:4839/spotify/callback${RESET}"
820
+ echo -e " ${DIM}Copy the Client ID and Access Token from your app dashboard${RESET}"
821
+ echo ""
822
+ prompt_secret "Spotify Client ID" SPOTIFY_CLIENT_ID
823
+ prompt_secret "Spotify Access Token" SPOTIFY_TOKEN
824
+ if [[ -n "$SPOTIFY_TOKEN" ]]; then
825
+ SPOTIFY_OK="fail"
826
+ if command -v curl &>/dev/null && [[ -n "$SPOTIFY_TOKEN" ]]; then
827
+ SPOTIFY_OK=$(curl -sf --max-time 8 \
828
+ "https://api.spotify.com/v1/me" \
829
+ -H "Authorization: Bearer ${SPOTIFY_TOKEN}" 2>/dev/null \
830
+ | python3 -c "import json,sys; d=json.load(sys.stdin); print('ok' if 'id' in d else 'fail')" 2>/dev/null \
831
+ || echo "fail")
832
+ fi
833
+ save_cred_val "spotify" "access_token" "$SPOTIFY_TOKEN"
834
+ [[ -n "$SPOTIFY_CLIENT_ID" ]] && save_cred_val "spotify" "client_id" "$SPOTIFY_CLIENT_ID"
835
+ [[ "$SPOTIFY_OK" == "ok" ]] && success "Spotify connected" || warn "Token saved (could not verify — may need refresh)"
836
+ update_config '{"plugins":{"spotify":{"enabled":true}}}'
837
+ add_plugin "spotify"
838
+ hint "Tokens expire after 1hr. Re-run to refresh: conductor plugins config spotify access_token <NEW_TOKEN>"
839
+ else
840
+ warn "Skipped — run later: conductor plugins config spotify access_token <TOKEN>"
841
+ fi
842
+ fi
843
+
844
+ # ── STEP 9: GITHUB ACTIONS ────────────────────────────────────────────────────
845
+ step "09 / GitHub Actions & CI ${DIM}optional${RESET}"
846
+ hint "PRs, issues, workflow runs, releases, notifications — needs PAT"
847
+ hint "Create token: https://github.com/settings/tokens"
848
+ echo ""
849
+
850
+ GH_TOKEN_STORED=$(node -e "
851
+ const p=require('path'),fs=require('fs');
852
+ const f=p.join(process.env.HOME,'.conductor','keychain','github.token.enc');
853
+ process.stdout.write(fs.existsSync(f)?'yes':'no');
854
+ " 2>/dev/null || echo "no")
855
+
856
+ if [[ "$GH_TOKEN_STORED" == "yes" ]]; then
857
+ info "GitHub token already stored — enabling GitHub Actions plugin"
858
+ update_config '{"plugins":{"github_actions":{"enabled":true}}}'
859
+ add_plugin "github_actions"
860
+ success "GitHub Actions plugin enabled"
861
+ else
862
+ prompt_yn "Set up GitHub Actions plugin?" SETUP_GH_ACTIONS "n"
863
+ if [[ "$SETUP_GH_ACTIONS" == "true" ]]; then
864
+ echo ""
865
+ echo -e " ${DIM}Scopes needed: ${CYAN}repo, workflow, notifications, read:user${RESET}"
866
+ echo ""
867
+ prompt_secret "GitHub Personal Access Token" GH_PAT
868
+ if [[ -n "$GH_PAT" ]]; then
869
+ GH_OK="fail"
870
+ if command -v curl &>/dev/null; then
871
+ GH_OK=$(curl -sf --max-time 8 \
872
+ "https://api.github.com/user" \
873
+ -H "Authorization: Bearer ${GH_PAT}" \
874
+ -H "Accept: application/vnd.github+json" 2>/dev/null \
875
+ | python3 -c "import json,sys; d=json.load(sys.stdin); print('ok' if 'login' in d else 'fail')" 2>/dev/null \
876
+ || echo "fail")
877
+ fi
878
+ if [[ "$GH_OK" == "ok" ]]; then
879
+ save_cred_val "github" "token" "$GH_PAT"
880
+ success "GitHub token verified and saved"
881
+ else
882
+ warn "Verification failed — saving anyway"
883
+ save_cred_val "github" "token" "$GH_PAT"
884
+ fi
885
+ update_config '{"plugins":{"github_actions":{"enabled":true}}}'
886
+ add_plugin "github_actions"
887
+ success "GitHub Actions plugin enabled"
888
+ else
889
+ warn "Skipped — run later: conductor plugins config github_actions token <TOKEN>"
890
+ fi
891
+ fi
892
+ fi
893
+
894
+ # ── STEP 10: VERCEL ───────────────────────────────────────────────────────────
895
+ step "10 / Vercel ${DIM}optional${RESET}"
896
+ hint "Deployments, projects, domains, env vars, logs"
897
+ hint "Get token: https://vercel.com/account/tokens"
898
+ echo ""
899
+
900
+ prompt_yn "Set up Vercel?" SETUP_VERCEL "n"
901
+ if [[ "$SETUP_VERCEL" == "true" ]]; then
902
+ prompt_secret "Vercel Token" VERCEL_TOKEN
903
+ if [[ -n "$VERCEL_TOKEN" ]]; then
904
+ VERCEL_OK="fail"
905
+ if command -v curl &>/dev/null; then
906
+ VERCEL_OK=$(curl -sf --max-time 8 \
907
+ "https://api.vercel.com/v2/user" \
908
+ -H "Authorization: Bearer ${VERCEL_TOKEN}" 2>/dev/null \
909
+ | python3 -c "import json,sys; d=json.load(sys.stdin); print('ok' if 'user' in d or 'id' in d else 'fail')" 2>/dev/null \
910
+ || echo "fail")
911
+ fi
912
+ save_cred_val "vercel" "token" "$VERCEL_TOKEN"
913
+ [[ "$VERCEL_OK" == "ok" ]] && success "Vercel connected" || warn "Token saved (could not verify)"
914
+ prompt_yn "Are you on a Vercel team? (configure team scope)" VERCEL_TEAM "n"
915
+ if [[ "$VERCEL_TEAM" == "true" ]]; then
916
+ prompt_input "Team ID or slug" VERCEL_TEAM_ID ""
917
+ [[ -n "$VERCEL_TEAM_ID" ]] && save_cred_val "vercel" "team_id" "$VERCEL_TEAM_ID"
918
+ fi
919
+ update_config '{"plugins":{"vercel":{"enabled":true}}}'
920
+ add_plugin "vercel"
921
+ success "Vercel plugin enabled"
922
+ else
923
+ warn "Skipped — run later: conductor plugins config vercel token <TOKEN>"
924
+ fi
925
+ fi
926
+
927
+ # ── STEP 11: N8N ──────────────────────────────────────────────────────────────
928
+ step "11 / n8n Automation ${DIM}optional${RESET}"
929
+ hint "Trigger workflows, inspect executions, fire webhooks"
930
+ hint "Works with self-hosted and n8n Cloud"
931
+ echo ""
932
+
933
+ prompt_yn "Set up n8n?" SETUP_N8N "n"
934
+ if [[ "$SETUP_N8N" == "true" ]]; then
935
+ prompt_input "n8n instance URL" N8N_URL "http://localhost:5678"
936
+ prompt_secret "n8n API Key (Settings → API → Create Key)" N8N_KEY
937
+ if [[ -n "$N8N_KEY" ]]; then
938
+ N8N_BASE="${N8N_URL%/}/api/v1"
939
+ N8N_OK="fail"
940
+ if command -v curl &>/dev/null; then
941
+ N8N_OK=$(curl -sf --max-time 10 \
942
+ "${N8N_BASE}/health" \
943
+ -H "X-N8N-API-KEY: ${N8N_KEY}" 2>/dev/null \
944
+ | python3 -c "import json,sys; d=json.load(sys.stdin); print('ok' if d.get('status')=='ok' or 'status' in d else 'fail')" 2>/dev/null \
945
+ || echo "fail")
946
+ fi
947
+ save_cred_val "n8n" "api_key" "$N8N_KEY"
948
+ save_cred_val "n8n" "base_url" "$N8N_URL"
949
+ [[ "$N8N_OK" == "ok" ]] && success "n8n connected at ${N8N_URL}" || warn "Saved (could not verify — check URL and key)"
950
+ update_config '{"plugins":{"n8n":{"enabled":true}}}'
951
+ add_plugin "n8n"
952
+ success "n8n plugin enabled"
953
+ else
954
+ warn "Skipped — run later:"
955
+ hint " conductor plugins config n8n api_key <KEY>"
956
+ hint " conductor plugins config n8n base_url <URL>"
957
+ fi
958
+ fi
959
+
960
+ # ── STEP 12: NOTES & SCHEDULER ────────────────────────────────────────────────
961
+ step "12 / Notes & Scheduler ${DIM}local, no API keys${RESET}"
962
+ hint "Notes: local markdown notes the AI can read/write/search"
963
+ hint "Scheduler: natural language cron — 'every day at 9am', 'in 30 minutes'"
964
+ echo ""
965
+
966
+ update_config '{"plugins":{"notes":{"enabled":true},"cron":{"enabled":true}}}'
967
+ add_plugin "notes"; add_plugin "cron"
968
+ success "Notes and Scheduler enabled (stored in ~/.conductor/notes/ and ~/.conductor/scheduler.json)"
969
+
970
+ # ── STEP 13: TELEGRAM ─────────────────────────────────────────────────────────
971
+ step "13 / Telegram Bot ${DIM}optional${RESET}"
972
+ hint "Chat with your AI from Telegram — @BotFather → /newbot to get a token"
973
+ echo ""
974
+
975
+ prompt_yn "Set up Telegram?" SETUP_TG "n"
976
+
977
+ if [[ "$SETUP_TG" == "true" ]]; then
978
+ prompt_secret "Bot token" TG_TOKEN
979
+ if [[ -n "$TG_TOKEN" ]]; then
980
+ TG_VERIFIED="false"
981
+ if command -v curl &>/dev/null; then
982
+ info "Verifying token..."
983
+ TG_RESP=$(curl -sf --max-time 8 --retry 2 --retry-delay 1 \
984
+ "https://api.telegram.org/bot${TG_TOKEN}/getMe" 2>/dev/null || echo '{"ok":false}')
985
+ TG_OK=$(python3 -c "import json,sys; d=json.loads(sys.argv[1]); print('y' if d.get('ok') else 'n')" "$TG_RESP" 2>/dev/null || echo "n")
986
+ TG_NAME=$(python3 -c "import json,sys; d=json.loads(sys.argv[1]); print(d.get('result',{}).get('username',''))" "$TG_RESP" 2>/dev/null || echo "")
987
+ if [[ "$TG_OK" == "y" ]]; then
988
+ save_cred_val "telegram" "bot_token" "$TG_TOKEN"
989
+ update_config '{"telegram":{"enabled":true}}'
990
+ success "Telegram connected — @${TG_NAME}"
991
+ TG_VERIFIED="true"
992
+ else
993
+ warn "Token verification failed (bad token or no internet)."
994
+ prompt_yn "Save token anyway?" SAVE_TG_ANYWAY "n"
995
+ if [[ "$SAVE_TG_ANYWAY" == "true" ]]; then
996
+ save_cred_val "telegram" "bot_token" "$TG_TOKEN"
997
+ update_config '{"telegram":{"enabled":true}}'
998
+ warn "Token saved unverified — run: conductor telegram start"
999
+ fi
1000
+ fi
1001
+ else
1002
+ save_cred_val "telegram" "bot_token" "$TG_TOKEN"
1003
+ update_config '{"telegram":{"enabled":true}}'
1004
+ warn "Saved without verification (curl not found) — run: conductor telegram start"
1005
+ fi
1006
+ else
1007
+ warn "Skipped — run later: conductor telegram setup"
1008
+ fi
1009
+ fi
1010
+
1011
+ # ── STEP 14: SLACK BOT ────────────────────────────────────────────────────────
1012
+ step "14 / Slack Bot ${DIM}optional${RESET}"
1013
+ hint "Socket Mode — api.slack.com/apps → Create App → Event Subscriptions"
1014
+ echo ""
1015
+
1016
+ prompt_yn "Set up Slack Bot?" SETUP_SLACK "n"
1017
+ if [[ "$SETUP_SLACK" == "true" ]]; then
1018
+ echo -e " To get tokens, go to ${BOLD}api.slack.com/apps${RESET}:"
1019
+ echo -e " 1. ${DIM}Create App (From Scratch)${RESET}"
1020
+ echo -e " 2. ${DIM}Bot User OAuth Token (xoxb-...) in 'OAuth & Permissions'${RESET}"
1021
+ echo -e " 3. ${DIM}App-Level Token (xapp-...) in 'Basic Information' -> 'App-Level Tokens'${RESET}"
1022
+ echo ""
1023
+ prompt_secret "Slack Bot OAuth Token (xoxb-)" SLACK_BOT_TOKEN
1024
+ prompt_secret "Slack App-Level Token (xapp-)" SLACK_APP_TOKEN
1025
+ if [[ -n "$SLACK_BOT_TOKEN" && -n "$SLACK_APP_TOKEN" ]]; then
1026
+ save_cred_val "slack" "bot_token" "$SLACK_BOT_TOKEN"
1027
+ save_cred_val "slack" "app_token" "$SLACK_APP_TOKEN"
1028
+ update_config '{"plugins":{"slack":{"enabled":true}}}'
1029
+ success "Slack tokens saved"
1030
+ else
1031
+ warn "Missing tokens — skipped Slack setup"
1032
+ fi
1033
+ else
1034
+ warn "Skipped — run later: conductor slack setup"
1035
+ fi
1036
+
1037
+ # ── STEP 15: MCP ──────────────────────────────────────────────────────────────
1038
+ step "15 / MCP Server ${DIM}Claude Desktop integration${RESET}"
1039
+
1040
+ prompt_yn "Configure MCP for Claude Desktop?" SETUP_MCP "y"
1041
+
1042
+ if [[ "$SETUP_MCP" == "true" ]]; then
1043
+ case "$PLATFORM" in
1044
+ macos) MCP_CONFIG="$HOME/Library/Application Support/Claude/claude_desktop_config.json" ;;
1045
+ linux) MCP_CONFIG="$HOME/.config/Claude/claude_desktop_config.json" ;;
1046
+ windows) MCP_CONFIG="${APPDATA:-$HOME/AppData/Roaming}/Claude/claude_desktop_config.json" ;;
1047
+ *) MCP_CONFIG="" ;;
1048
+ esac
1049
+
1050
+ if [[ -n "$MCP_CONFIG" ]]; then
1051
+ mkdir -p "$(dirname "$MCP_CONFIG")"
1052
+ CONDUCTOR_CLI="${CONDUCTOR_BIN:-conductor}"
1053
+
1054
+ if [[ -f "$MCP_CONFIG" ]]; then
1055
+ cp "$MCP_CONFIG" "${MCP_CONFIG}.bak" 2>/dev/null && \
1056
+ hint "Backed up existing Claude Desktop config to ${MCP_CONFIG}.bak"
1057
+ fi
1058
+
1059
+ node -e "
1060
+ const fs = require('fs');
1061
+ const [,, configPath, conductorCmd] = process.argv;
1062
+ let config = {};
1063
+ try { config = JSON.parse(fs.readFileSync(configPath, 'utf8')); } catch {}
1064
+ if (!config.mcpServers) config.mcpServers = {};
1065
+ if (conductorCmd.startsWith('node ')) {
1066
+ const scriptPath = conductorCmd.split(' ').slice(1).join(' ');
1067
+ config.mcpServers.conductor = { command: 'node', args: [scriptPath, 'mcp', 'start'] };
1068
+ } else {
1069
+ config.mcpServers.conductor = { command: conductorCmd, args: ['mcp', 'start'] };
1070
+ }
1071
+ const tmp = configPath + '.tmp';
1072
+ fs.writeFileSync(tmp, JSON.stringify(config, null, 2));
1073
+ fs.renameSync(tmp, configPath);
1074
+ " "$MCP_CONFIG" "$CONDUCTOR_CLI" && \
1075
+ success "MCP configured for Claude Desktop" && \
1076
+ hint "Restart Claude Desktop to connect" || \
1077
+ warn "MCP config update failed — run manually: conductor mcp setup"
1078
+ else
1079
+ warn "Could not detect Claude Desktop config path for: $PLATFORM"
1080
+ hint "Run manually: conductor mcp setup"
1081
+ fi
1082
+ fi
1083
+
1084
+ # ── Dashboard ─────────────────────────────────────────────────────────────────
1085
+ echo ""
1086
+ prompt_yn "Open the Conductor dashboard now?" OPEN_DASHBOARD "y"
1087
+ if [[ "$OPEN_DASHBOARD" == "true" ]]; then
1088
+ info "Launching dashboard…"
1089
+ "$CONDUCTOR_CLI" dashboard &
1090
+ DASH_PID=$!
1091
+ sleep 1
1092
+ if kill -0 "$DASH_PID" 2>/dev/null; then
1093
+ success "Dashboard running at http://localhost:4242"
1094
+ hint "Stop it with: kill $DASH_PID (or Ctrl+C in its terminal)"
1095
+ else
1096
+ warn "Dashboard did not start — run manually: conductor dashboard"
1097
+ fi
1098
+ else
1099
+ hint "Run later: conductor dashboard"
1100
+ fi
1101
+
1102
+ # ── DONE ──────────────────────────────────────────────────────────────────────
1103
+ echo ""
1104
+ echo -e " ${BOLD}${CYAN}┌──────────────────────────────────────────────${RESET}"
1105
+ echo -e " ${BOLD}${CYAN}│ ${GREEN}✓ Installation Complete${RESET}"
1106
+ echo -e " ${BOLD}${CYAN}└──────────────────────────────────────────────${RESET}"
1107
+ echo ""
1108
+ echo -e " ${BOLD}Get started:${RESET}"
1109
+ echo ""
1110
+ echo -e " ${CYAN}conductor dashboard${RESET} — Open web dashboard"
1111
+ echo -e " ${CYAN}conductor status${RESET} — Check your setup"
1112
+ echo -e " ${CYAN}conductor ai test${RESET} — Test AI provider"
1113
+ echo -e " ${CYAN}conductor auth google${RESET} — Connect Gmail/Calendar/Drive"
1114
+ echo -e " ${CYAN}conductor telegram start${RESET} — Start Telegram bot"
1115
+ echo -e " ${CYAN}conductor slack start${RESET} — Start Slack bot"
1116
+ echo -e " ${CYAN}conductor mcp start${RESET} — Start MCP server"
1117
+ echo ""
1118
+ echo -e " ${DIM}Docs: https://conductor.thealxlabs.ca${RESET}"
1119
+ echo ""