@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.
- package/.claude-plugin/marketplace.json +33 -0
- package/.claude-plugin/plugin.json +23 -0
- package/.eslintrc.json +23 -0
- package/.gitattributes +6 -0
- package/.github/FUNDING.yml +15 -0
- package/.github/ISSUE_TEMPLATE/bug_report.yml +91 -0
- package/.github/ISSUE_TEMPLATE/config.yml +8 -0
- package/.github/ISSUE_TEMPLATE/feature_request.yml +63 -0
- package/.github/ISSUE_TEMPLATE/plugin_request.yml +71 -0
- package/.github/README.md +13 -0
- package/.github/workflows/README.md +22 -0
- package/.github/workflows/auto-release.yml +112 -0
- package/.github/workflows/ci.yml +49 -0
- package/.github/workflows/claude-code-review.yml +44 -0
- package/.github/workflows/claude.yml +36 -0
- package/.github/workflows/sync-install.yml +47 -0
- package/.mcp.json +9 -0
- package/.prettierrc.json +7 -0
- package/C.png +0 -0
- package/CHANGELOG.md +74 -0
- package/CLAUDE.md +118 -0
- package/CONTRIBUTING.md +231 -0
- package/LICENSE +201 -0
- package/README.md +179 -0
- package/SECURITY.md +47 -0
- package/commands/conductor-setup.md +11 -0
- package/commands/conductor-status.md +7 -0
- package/dist/ai/base.d.ts +44 -0
- package/dist/ai/base.d.ts.map +1 -0
- package/dist/ai/base.js +47 -0
- package/dist/ai/base.js.map +1 -0
- package/dist/ai/claude.d.ts +11 -0
- package/dist/ai/claude.d.ts.map +1 -0
- package/dist/ai/claude.js +149 -0
- package/dist/ai/claude.js.map +1 -0
- package/dist/ai/gemini.d.ts +15 -0
- package/dist/ai/gemini.d.ts.map +1 -0
- package/dist/ai/gemini.js +156 -0
- package/dist/ai/gemini.js.map +1 -0
- package/dist/ai/maestro.d.ts +22 -0
- package/dist/ai/maestro.d.ts.map +1 -0
- package/dist/ai/maestro.js +142 -0
- package/dist/ai/maestro.js.map +1 -0
- package/dist/ai/manager.d.ts +47 -0
- package/dist/ai/manager.d.ts.map +1 -0
- package/dist/ai/manager.js +450 -0
- package/dist/ai/manager.js.map +1 -0
- package/dist/ai/ollama.d.ts +16 -0
- package/dist/ai/ollama.d.ts.map +1 -0
- package/dist/ai/ollama.js +151 -0
- package/dist/ai/ollama.js.map +1 -0
- package/dist/ai/openai.d.ts +11 -0
- package/dist/ai/openai.d.ts.map +1 -0
- package/dist/ai/openai.js +132 -0
- package/dist/ai/openai.js.map +1 -0
- package/dist/ai/openrouter.d.ts +11 -0
- package/dist/ai/openrouter.d.ts.map +1 -0
- package/dist/ai/openrouter.js +139 -0
- package/dist/ai/openrouter.js.map +1 -0
- package/dist/bot/slack.d.ts +17 -0
- package/dist/bot/slack.d.ts.map +1 -0
- package/dist/bot/slack.js +144 -0
- package/dist/bot/slack.js.map +1 -0
- package/dist/bot/telegram.d.ts +19 -0
- package/dist/bot/telegram.d.ts.map +1 -0
- package/dist/bot/telegram.js +157 -0
- package/dist/bot/telegram.js.map +1 -0
- package/dist/cli/commands/ai.d.ts +4 -0
- package/dist/cli/commands/ai.d.ts.map +1 -0
- package/dist/cli/commands/ai.js +161 -0
- package/dist/cli/commands/ai.js.map +1 -0
- package/dist/cli/commands/doctor.d.ts +18 -0
- package/dist/cli/commands/doctor.d.ts.map +1 -0
- package/dist/cli/commands/doctor.js +213 -0
- package/dist/cli/commands/doctor.js.map +1 -0
- package/dist/cli/commands/init.d.ts +15 -0
- package/dist/cli/commands/init.d.ts.map +1 -0
- package/dist/cli/commands/init.js +281 -0
- package/dist/cli/commands/init.js.map +1 -0
- package/dist/cli/commands/install.d.ts +16 -0
- package/dist/cli/commands/install.d.ts.map +1 -0
- package/dist/cli/commands/install.js +750 -0
- package/dist/cli/commands/install.js.map +1 -0
- package/dist/cli/commands/lifecycle.d.ts +4 -0
- package/dist/cli/commands/lifecycle.d.ts.map +1 -0
- package/dist/cli/commands/lifecycle.js +84 -0
- package/dist/cli/commands/lifecycle.js.map +1 -0
- package/dist/cli/commands/marketplace.d.ts +13 -0
- package/dist/cli/commands/marketplace.d.ts.map +1 -0
- package/dist/cli/commands/marketplace.js +197 -0
- package/dist/cli/commands/marketplace.js.map +1 -0
- package/dist/cli/commands/mcp.d.ts +6 -0
- package/dist/cli/commands/mcp.d.ts.map +1 -0
- package/dist/cli/commands/mcp.js +83 -0
- package/dist/cli/commands/mcp.js.map +1 -0
- package/dist/cli/commands/onboard.d.ts +10 -0
- package/dist/cli/commands/onboard.d.ts.map +1 -0
- package/dist/cli/commands/onboard.js +207 -0
- package/dist/cli/commands/onboard.js.map +1 -0
- package/dist/cli/commands/plugin-create.d.ts +13 -0
- package/dist/cli/commands/plugin-create.d.ts.map +1 -0
- package/dist/cli/commands/plugin-create.js +122 -0
- package/dist/cli/commands/plugin-create.js.map +1 -0
- package/dist/cli/commands/plugins.d.ts +5 -0
- package/dist/cli/commands/plugins.d.ts.map +1 -0
- package/dist/cli/commands/plugins.js +30 -0
- package/dist/cli/commands/plugins.js.map +1 -0
- package/dist/cli/commands/release.d.ts +13 -0
- package/dist/cli/commands/release.d.ts.map +1 -0
- package/dist/cli/commands/release.js +243 -0
- package/dist/cli/commands/release.js.map +1 -0
- package/dist/cli/commands/telegram.d.ts +3 -0
- package/dist/cli/commands/telegram.d.ts.map +1 -0
- package/dist/cli/commands/telegram.js +20 -0
- package/dist/cli/commands/telegram.js.map +1 -0
- package/dist/cli/index.d.ts +3 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +402 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/config/oauth.d.ts +8 -0
- package/dist/config/oauth.d.ts.map +1 -0
- package/dist/config/oauth.js +13 -0
- package/dist/config/oauth.js.map +1 -0
- package/dist/core/audit.d.ts +91 -0
- package/dist/core/audit.d.ts.map +1 -0
- package/dist/core/audit.js +233 -0
- package/dist/core/audit.js.map +1 -0
- package/dist/core/circuit-breaker.d.ts +56 -0
- package/dist/core/circuit-breaker.d.ts.map +1 -0
- package/dist/core/circuit-breaker.js +107 -0
- package/dist/core/circuit-breaker.js.map +1 -0
- package/dist/core/conductor.d.ts +44 -0
- package/dist/core/conductor.d.ts.map +1 -0
- package/dist/core/conductor.js +200 -0
- package/dist/core/conductor.js.map +1 -0
- package/dist/core/config.d.ts +66 -0
- package/dist/core/config.d.ts.map +1 -0
- package/dist/core/config.js +86 -0
- package/dist/core/config.js.map +1 -0
- package/dist/core/database.d.ts +59 -0
- package/dist/core/database.d.ts.map +1 -0
- package/dist/core/database.js +342 -0
- package/dist/core/database.js.map +1 -0
- package/dist/core/errors.d.ts +231 -0
- package/dist/core/errors.d.ts.map +1 -0
- package/dist/core/errors.js +254 -0
- package/dist/core/errors.js.map +1 -0
- package/dist/core/health.d.ts +72 -0
- package/dist/core/health.d.ts.map +1 -0
- package/dist/core/health.js +116 -0
- package/dist/core/health.js.map +1 -0
- package/dist/core/interfaces.d.ts +62 -0
- package/dist/core/interfaces.d.ts.map +1 -0
- package/dist/core/interfaces.js +8 -0
- package/dist/core/interfaces.js.map +1 -0
- package/dist/core/logger.d.ts +15 -0
- package/dist/core/logger.d.ts.map +1 -0
- package/dist/core/logger.js +30 -0
- package/dist/core/logger.js.map +1 -0
- package/dist/core/rbac.d.ts +132 -0
- package/dist/core/rbac.d.ts.map +1 -0
- package/dist/core/rbac.js +230 -0
- package/dist/core/rbac.js.map +1 -0
- package/dist/core/retry.d.ts +22 -0
- package/dist/core/retry.d.ts.map +1 -0
- package/dist/core/retry.js +41 -0
- package/dist/core/retry.js.map +1 -0
- package/dist/core/webhooks.d.ts +92 -0
- package/dist/core/webhooks.d.ts.map +1 -0
- package/dist/core/webhooks.js +176 -0
- package/dist/core/webhooks.js.map +1 -0
- package/dist/core/zero-config.d.ts +22 -0
- package/dist/core/zero-config.d.ts.map +1 -0
- package/dist/core/zero-config.js +59 -0
- package/dist/core/zero-config.js.map +1 -0
- package/dist/dashboard/cli.d.ts +6 -0
- package/dist/dashboard/cli.d.ts.map +1 -0
- package/dist/dashboard/cli.js +42 -0
- package/dist/dashboard/cli.js.map +1 -0
- package/dist/dashboard/index.html +3426 -0
- package/dist/dashboard/server.d.ts +7 -0
- package/dist/dashboard/server.d.ts.map +1 -0
- package/dist/dashboard/server.js +1427 -0
- package/dist/dashboard/server.js.map +1 -0
- package/dist/mcp/server.d.ts +27 -0
- package/dist/mcp/server.d.ts.map +1 -0
- package/dist/mcp/server.js +380 -0
- package/dist/mcp/server.js.map +1 -0
- package/dist/mcp/tools/misc.d.ts +15 -0
- package/dist/mcp/tools/misc.d.ts.map +1 -0
- package/dist/mcp/tools/misc.js +49 -0
- package/dist/mcp/tools/misc.js.map +1 -0
- package/dist/plugins/builtin/calculator.d.ts +11 -0
- package/dist/plugins/builtin/calculator.d.ts.map +1 -0
- package/dist/plugins/builtin/calculator.js +166 -0
- package/dist/plugins/builtin/calculator.js.map +1 -0
- package/dist/plugins/builtin/colors.d.ts +15 -0
- package/dist/plugins/builtin/colors.d.ts.map +1 -0
- package/dist/plugins/builtin/colors.js +193 -0
- package/dist/plugins/builtin/colors.js.map +1 -0
- package/dist/plugins/builtin/cron.d.ts +40 -0
- package/dist/plugins/builtin/cron.d.ts.map +1 -0
- package/dist/plugins/builtin/cron.js +578 -0
- package/dist/plugins/builtin/cron.js.map +1 -0
- package/dist/plugins/builtin/crypto.d.ts +11 -0
- package/dist/plugins/builtin/crypto.d.ts.map +1 -0
- package/dist/plugins/builtin/crypto.js +83 -0
- package/dist/plugins/builtin/crypto.js.map +1 -0
- package/dist/plugins/builtin/database.d.ts +29 -0
- package/dist/plugins/builtin/database.d.ts.map +1 -0
- package/dist/plugins/builtin/database.js +230 -0
- package/dist/plugins/builtin/database.js.map +1 -0
- package/dist/plugins/builtin/docker.d.ts +12 -0
- package/dist/plugins/builtin/docker.d.ts.map +1 -0
- package/dist/plugins/builtin/docker.js +436 -0
- package/dist/plugins/builtin/docker.js.map +1 -0
- package/dist/plugins/builtin/fun.d.ts +11 -0
- package/dist/plugins/builtin/fun.d.ts.map +1 -0
- package/dist/plugins/builtin/fun.js +114 -0
- package/dist/plugins/builtin/fun.js.map +1 -0
- package/dist/plugins/builtin/gcal.d.ts +38 -0
- package/dist/plugins/builtin/gcal.d.ts.map +1 -0
- package/dist/plugins/builtin/gcal.js +280 -0
- package/dist/plugins/builtin/gcal.js.map +1 -0
- package/dist/plugins/builtin/gdrive.d.ts +26 -0
- package/dist/plugins/builtin/gdrive.d.ts.map +1 -0
- package/dist/plugins/builtin/gdrive.js +295 -0
- package/dist/plugins/builtin/gdrive.js.map +1 -0
- package/dist/plugins/builtin/github-actions.d.ts +38 -0
- package/dist/plugins/builtin/github-actions.d.ts.map +1 -0
- package/dist/plugins/builtin/github-actions.js +629 -0
- package/dist/plugins/builtin/github-actions.js.map +1 -0
- package/dist/plugins/builtin/github.d.ts +26 -0
- package/dist/plugins/builtin/github.d.ts.map +1 -0
- package/dist/plugins/builtin/github.js +800 -0
- package/dist/plugins/builtin/github.js.map +1 -0
- package/dist/plugins/builtin/gmail.d.ts +50 -0
- package/dist/plugins/builtin/gmail.d.ts.map +1 -0
- package/dist/plugins/builtin/gmail.js +445 -0
- package/dist/plugins/builtin/gmail.js.map +1 -0
- package/dist/plugins/builtin/hash.d.ts +11 -0
- package/dist/plugins/builtin/hash.d.ts.map +1 -0
- package/dist/plugins/builtin/hash.js +95 -0
- package/dist/plugins/builtin/hash.js.map +1 -0
- package/dist/plugins/builtin/homekit.d.ts +53 -0
- package/dist/plugins/builtin/homekit.d.ts.map +1 -0
- package/dist/plugins/builtin/homekit.js +341 -0
- package/dist/plugins/builtin/homekit.js.map +1 -0
- package/dist/plugins/builtin/index.d.ts +4 -0
- package/dist/plugins/builtin/index.d.ts.map +1 -0
- package/dist/plugins/builtin/index.js +96 -0
- package/dist/plugins/builtin/index.js.map +1 -0
- package/dist/plugins/builtin/jira.d.ts +50 -0
- package/dist/plugins/builtin/jira.d.ts.map +1 -0
- package/dist/plugins/builtin/jira.js +353 -0
- package/dist/plugins/builtin/jira.js.map +1 -0
- package/dist/plugins/builtin/linear.d.ts +35 -0
- package/dist/plugins/builtin/linear.d.ts.map +1 -0
- package/dist/plugins/builtin/linear.js +397 -0
- package/dist/plugins/builtin/linear.js.map +1 -0
- package/dist/plugins/builtin/lumen.d.ts +21 -0
- package/dist/plugins/builtin/lumen.d.ts.map +1 -0
- package/dist/plugins/builtin/lumen.js +404 -0
- package/dist/plugins/builtin/lumen.js.map +1 -0
- package/dist/plugins/builtin/memory.d.ts +22 -0
- package/dist/plugins/builtin/memory.d.ts.map +1 -0
- package/dist/plugins/builtin/memory.js +184 -0
- package/dist/plugins/builtin/memory.js.map +1 -0
- package/dist/plugins/builtin/n8n.d.ts +60 -0
- package/dist/plugins/builtin/n8n.d.ts.map +1 -0
- package/dist/plugins/builtin/n8n.js +519 -0
- package/dist/plugins/builtin/n8n.js.map +1 -0
- package/dist/plugins/builtin/network.d.ts +11 -0
- package/dist/plugins/builtin/network.d.ts.map +1 -0
- package/dist/plugins/builtin/network.js +88 -0
- package/dist/plugins/builtin/network.js.map +1 -0
- package/dist/plugins/builtin/notes.d.ts +47 -0
- package/dist/plugins/builtin/notes.d.ts.map +1 -0
- package/dist/plugins/builtin/notes.js +641 -0
- package/dist/plugins/builtin/notes.js.map +1 -0
- package/dist/plugins/builtin/notion.d.ts +47 -0
- package/dist/plugins/builtin/notion.d.ts.map +1 -0
- package/dist/plugins/builtin/notion.js +317 -0
- package/dist/plugins/builtin/notion.js.map +1 -0
- package/dist/plugins/builtin/shell.d.ts +12 -0
- package/dist/plugins/builtin/shell.d.ts.map +1 -0
- package/dist/plugins/builtin/shell.js +310 -0
- package/dist/plugins/builtin/shell.js.map +1 -0
- package/dist/plugins/builtin/slack.d.ts +31 -0
- package/dist/plugins/builtin/slack.d.ts.map +1 -0
- package/dist/plugins/builtin/slack.js +295 -0
- package/dist/plugins/builtin/slack.js.map +1 -0
- package/dist/plugins/builtin/spotify.d.ts +55 -0
- package/dist/plugins/builtin/spotify.d.ts.map +1 -0
- package/dist/plugins/builtin/spotify.js +623 -0
- package/dist/plugins/builtin/spotify.js.map +1 -0
- package/dist/plugins/builtin/stripe.d.ts +35 -0
- package/dist/plugins/builtin/stripe.d.ts.map +1 -0
- package/dist/plugins/builtin/stripe.js +376 -0
- package/dist/plugins/builtin/stripe.js.map +1 -0
- package/dist/plugins/builtin/system.d.ts +11 -0
- package/dist/plugins/builtin/system.d.ts.map +1 -0
- package/dist/plugins/builtin/system.js +91 -0
- package/dist/plugins/builtin/system.js.map +1 -0
- package/dist/plugins/builtin/text-tools.d.ts +11 -0
- package/dist/plugins/builtin/text-tools.d.ts.map +1 -0
- package/dist/plugins/builtin/text-tools.js +146 -0
- package/dist/plugins/builtin/text-tools.js.map +1 -0
- package/dist/plugins/builtin/timezone.d.ts +13 -0
- package/dist/plugins/builtin/timezone.d.ts.map +1 -0
- package/dist/plugins/builtin/timezone.js +164 -0
- package/dist/plugins/builtin/timezone.js.map +1 -0
- package/dist/plugins/builtin/todoist.d.ts +49 -0
- package/dist/plugins/builtin/todoist.d.ts.map +1 -0
- package/dist/plugins/builtin/todoist.js +540 -0
- package/dist/plugins/builtin/todoist.js.map +1 -0
- package/dist/plugins/builtin/translate.d.ts +11 -0
- package/dist/plugins/builtin/translate.d.ts.map +1 -0
- package/dist/plugins/builtin/translate.js +42 -0
- package/dist/plugins/builtin/translate.js.map +1 -0
- package/dist/plugins/builtin/url-tools.d.ts +11 -0
- package/dist/plugins/builtin/url-tools.d.ts.map +1 -0
- package/dist/plugins/builtin/url-tools.js +70 -0
- package/dist/plugins/builtin/url-tools.js.map +1 -0
- package/dist/plugins/builtin/vercel.d.ts +55 -0
- package/dist/plugins/builtin/vercel.d.ts.map +1 -0
- package/dist/plugins/builtin/vercel.js +514 -0
- package/dist/plugins/builtin/vercel.js.map +1 -0
- package/dist/plugins/builtin/weather.d.ts +13 -0
- package/dist/plugins/builtin/weather.d.ts.map +1 -0
- package/dist/plugins/builtin/weather.js +103 -0
- package/dist/plugins/builtin/weather.js.map +1 -0
- package/dist/plugins/builtin/x.d.ts +54 -0
- package/dist/plugins/builtin/x.d.ts.map +1 -0
- package/dist/plugins/builtin/x.js +402 -0
- package/dist/plugins/builtin/x.js.map +1 -0
- package/dist/plugins/manager.d.ts +77 -0
- package/dist/plugins/manager.d.ts.map +1 -0
- package/dist/plugins/manager.js +141 -0
- package/dist/plugins/manager.js.map +1 -0
- package/dist/plugins/validation.d.ts +18 -0
- package/dist/plugins/validation.d.ts.map +1 -0
- package/dist/plugins/validation.js +81 -0
- package/dist/plugins/validation.js.map +1 -0
- package/dist/security/auth.d.ts +23 -0
- package/dist/security/auth.d.ts.map +1 -0
- package/dist/security/auth.js +56 -0
- package/dist/security/auth.js.map +1 -0
- package/dist/security/keychain.d.ts +60 -0
- package/dist/security/keychain.d.ts.map +1 -0
- package/dist/security/keychain.js +213 -0
- package/dist/security/keychain.js.map +1 -0
- package/dist/utils/google-auth.d.ts +21 -0
- package/dist/utils/google-auth.d.ts.map +1 -0
- package/dist/utils/google-auth.js +135 -0
- package/dist/utils/google-auth.js.map +1 -0
- package/dist/utils/retry.d.ts +5 -0
- package/dist/utils/retry.d.ts.map +1 -0
- package/dist/utils/retry.js +34 -0
- package/dist/utils/retry.js.map +1 -0
- package/docs/README.md +13 -0
- package/docs/api.md +210 -0
- package/docs/getting-started.md +100 -0
- package/docs/plugins.md +306 -0
- package/docs-site/.vitepress/config.ts +59 -0
- package/docs-site/README.md +12 -0
- package/docs-site/index.md +30 -0
- package/eslint.config.js +29 -0
- package/install.ps1 +334 -0
- package/install.sh +1119 -0
- package/local-install.sh +304 -0
- package/package.json +90 -0
- package/packages/README.md +11 -0
- package/packages/plugin-sdk/README.md +12 -0
- package/packages/plugin-sdk/package.json +22 -0
- package/packages/plugin-sdk/src/README.md +11 -0
- package/packages/plugin-sdk/src/index.ts +191 -0
- package/sdks/README.md +26 -0
- package/sdks/csharp/ConductorClient.cs +65 -0
- package/sdks/csharp/README.md +11 -0
- package/sdks/go/README.md +11 -0
- package/sdks/go/conductor.go +257 -0
- package/sdks/java/ConductorClient.java +27 -0
- package/sdks/java/README.md +11 -0
- package/sdks/php/README.md +11 -0
- package/sdks/php/src/Client.php +72 -0
- package/sdks/python/README.md +12 -0
- package/sdks/python/conductor/__init__.py +227 -0
- package/sdks/python/pyproject.toml +30 -0
- package/sdks/ruby/README.md +11 -0
- package/sdks/ruby/lib/conductor.rb +46 -0
- package/sdks/rust/Cargo.toml +14 -0
- package/sdks/rust/README.md +11 -0
- package/sdks/swift/README.md +11 -0
- package/sdks/swift/Sources/Conductor/ConductorClient.swift +65 -0
- package/skills/conductor-mcp/SKILL.md +38 -0
- package/src/README.md +20 -0
- package/src/ai/README.md +18 -0
- package/src/ai/base.ts +93 -0
- package/src/ai/claude.ts +162 -0
- package/src/ai/gemini.ts +188 -0
- package/src/ai/maestro.ts +168 -0
- package/src/ai/manager.ts +537 -0
- package/src/ai/ollama.ts +186 -0
- package/src/ai/openai.ts +147 -0
- package/src/ai/openrouter.ts +152 -0
- package/src/bot/README.md +12 -0
- package/src/bot/slack.ts +164 -0
- package/src/bot/telegram.ts +185 -0
- package/src/cli/README.md +24 -0
- package/src/cli/commands/README.md +20 -0
- package/src/cli/commands/ai.ts +170 -0
- package/src/cli/commands/doctor.ts +221 -0
- package/src/cli/commands/init.ts +348 -0
- package/src/cli/commands/install.ts +792 -0
- package/src/cli/commands/lifecycle.ts +95 -0
- package/src/cli/commands/marketplace.ts +253 -0
- package/src/cli/commands/mcp.ts +92 -0
- package/src/cli/commands/onboard.ts +248 -0
- package/src/cli/commands/plugin-create.ts +130 -0
- package/src/cli/commands/plugins.ts +36 -0
- package/src/cli/commands/release.ts +251 -0
- package/src/cli/commands/telegram.ts +25 -0
- package/src/cli/index.ts +450 -0
- package/src/config/README.md +11 -0
- package/src/config/oauth.ts +26 -0
- package/src/core/README.md +22 -0
- package/src/core/audit.ts +291 -0
- package/src/core/circuit-breaker.ts +129 -0
- package/src/core/conductor.ts +240 -0
- package/src/core/config.ts +149 -0
- package/src/core/database.ts +411 -0
- package/src/core/errors.ts +275 -0
- package/src/core/health.ts +159 -0
- package/src/core/interfaces.ts +75 -0
- package/src/core/logger.ts +33 -0
- package/src/core/rbac.ts +321 -0
- package/src/core/retry.ts +61 -0
- package/src/core/webhooks.ts +234 -0
- package/src/core/zero-config.ts +72 -0
- package/src/dashboard/README.md +15 -0
- package/src/dashboard/cli.ts +48 -0
- package/src/dashboard/index.html +3426 -0
- package/src/dashboard/server.ts +1544 -0
- package/src/mcp/README.md +20 -0
- package/src/mcp/server.ts +475 -0
- package/src/mcp/tools/README.md +11 -0
- package/src/mcp/tools/misc.ts +61 -0
- package/src/plugins/README.md +28 -0
- package/src/plugins/builtin/README.md +23 -0
- package/src/plugins/builtin/calculator.ts +178 -0
- package/src/plugins/builtin/colors.ts +201 -0
- package/src/plugins/builtin/cron.ts +649 -0
- package/src/plugins/builtin/crypto.ts +85 -0
- package/src/plugins/builtin/database.ts +235 -0
- package/src/plugins/builtin/docker.ts +426 -0
- package/src/plugins/builtin/fun.ts +118 -0
- package/src/plugins/builtin/gcal.ts +305 -0
- package/src/plugins/builtin/gdrive.ts +326 -0
- package/src/plugins/builtin/github-actions.ts +666 -0
- package/src/plugins/builtin/github.ts +912 -0
- package/src/plugins/builtin/gmail.ts +492 -0
- package/src/plugins/builtin/hash.ts +98 -0
- package/src/plugins/builtin/homekit.ts +389 -0
- package/src/plugins/builtin/index.ts +116 -0
- package/src/plugins/builtin/jira.ts +380 -0
- package/src/plugins/builtin/linear.ts +448 -0
- package/src/plugins/builtin/lumen.ts +497 -0
- package/src/plugins/builtin/memory.ts +200 -0
- package/src/plugins/builtin/n8n.ts +565 -0
- package/src/plugins/builtin/network.ts +92 -0
- package/src/plugins/builtin/notes.ts +689 -0
- package/src/plugins/builtin/notion.ts +348 -0
- package/src/plugins/builtin/shell.ts +334 -0
- package/src/plugins/builtin/slack.ts +327 -0
- package/src/plugins/builtin/spotify.ts +665 -0
- package/src/plugins/builtin/stripe.ts +388 -0
- package/src/plugins/builtin/system.ts +93 -0
- package/src/plugins/builtin/text-tools.ts +150 -0
- package/src/plugins/builtin/timezone.ts +173 -0
- package/src/plugins/builtin/todoist.ts +625 -0
- package/src/plugins/builtin/translate.ts +47 -0
- package/src/plugins/builtin/url-tools.ts +73 -0
- package/src/plugins/builtin/vercel.ts +546 -0
- package/src/plugins/builtin/weather.ts +112 -0
- package/src/plugins/builtin/x.ts +440 -0
- package/src/plugins/manager.ts +213 -0
- package/src/plugins/validation.ts +94 -0
- package/src/security/README.md +12 -0
- package/src/security/auth.ts +72 -0
- package/src/security/keychain.ts +226 -0
- package/src/utils/README.md +12 -0
- package/src/utils/google-auth.ts +159 -0
- package/src/utils/retry.ts +41 -0
- package/test-all.mjs +1256 -0
- package/test.mjs +633 -0
- package/tests/README.md +19 -0
- package/tests/calculator.test.ts +54 -0
- package/tests/docker.test.ts +42 -0
- package/tests/load.test.ts +129 -0
- package/tests/mcp.test.ts +14 -0
- package/tests/shell.test.ts +42 -0
- package/tsconfig.json +21 -0
- 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 ""
|