@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
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
import { Conductor } from '../core/conductor.js';
|
|
2
|
+
export type { ToolContext } from '../core/interfaces.js';
|
|
3
|
+
|
|
4
|
+
export interface Plugin {
|
|
5
|
+
name: string;
|
|
6
|
+
description: string;
|
|
7
|
+
version: string;
|
|
8
|
+
initialize(conductor: Conductor): Promise<void>;
|
|
9
|
+
isConfigured(): boolean;
|
|
10
|
+
getTools(): PluginTool[];
|
|
11
|
+
configSchema?: PluginConfigSchema;
|
|
12
|
+
/** Optional: return a short context string for the proactive reasoning cycle. */
|
|
13
|
+
getContext?(): Promise<string | null>;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface PluginConfigSchema {
|
|
17
|
+
fields: {
|
|
18
|
+
key: string;
|
|
19
|
+
label: string;
|
|
20
|
+
type: 'string' | 'password' | 'number' | 'boolean';
|
|
21
|
+
description?: string;
|
|
22
|
+
required: boolean;
|
|
23
|
+
secret?: boolean; // If true, stored in Keychain instead of config.json
|
|
24
|
+
service?: string; // For secret fields, the Keychain service name
|
|
25
|
+
}[];
|
|
26
|
+
setupInstructions?: string;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** The canonical input type for all tool handlers at runtime. */
|
|
30
|
+
export type ToolInput = Record<string, unknown>;
|
|
31
|
+
/** Tool handlers should return a string or a plain-object result. */
|
|
32
|
+
export type ToolOutput = string | Record<string, unknown>;
|
|
33
|
+
|
|
34
|
+
export interface PluginTool {
|
|
35
|
+
name: string;
|
|
36
|
+
description: string;
|
|
37
|
+
inputSchema: Record<string, unknown>;
|
|
38
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
39
|
+
handler: (input: any) => Promise<any>;
|
|
40
|
+
requiresApproval?: boolean;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export interface PluginStatus {
|
|
44
|
+
name: string;
|
|
45
|
+
status: 'ready' | 'not_configured' | 'init_failed' | 'disabled';
|
|
46
|
+
toolCount: number;
|
|
47
|
+
error?: string;
|
|
48
|
+
setupCommand?: string;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export class PluginManager {
|
|
52
|
+
private conductor: Conductor;
|
|
53
|
+
private plugins: Map<string, Plugin> = new Map();
|
|
54
|
+
private initializedPlugins: Set<string> = new Set();
|
|
55
|
+
private initErrors: Map<string, string> = new Map();
|
|
56
|
+
|
|
57
|
+
constructor(conductor: Conductor) {
|
|
58
|
+
this.conductor = conductor;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/** Register a plugin without initializing it. */
|
|
62
|
+
registerPlugin(plugin: Plugin): void {
|
|
63
|
+
this.plugins.set(plugin.name, plugin);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/** Load all builtin plugins (register only, no init). */
|
|
67
|
+
async loadBuiltins(): Promise<void> {
|
|
68
|
+
const builtins = await import('./builtin/index.js');
|
|
69
|
+
for (const plugin of builtins.getAllBuiltinPlugins()) {
|
|
70
|
+
this.registerPlugin(plugin);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/** Get a plugin by name. Lazily initializes on first use. */
|
|
75
|
+
async getPlugin(name: string): Promise<Plugin | undefined> {
|
|
76
|
+
const plugin = this.plugins.get(name);
|
|
77
|
+
if (!plugin) return undefined;
|
|
78
|
+
|
|
79
|
+
if (!this.initializedPlugins.has(name)) {
|
|
80
|
+
try {
|
|
81
|
+
await plugin.initialize(this.conductor);
|
|
82
|
+
this.initializedPlugins.add(name);
|
|
83
|
+
this.initErrors.delete(name);
|
|
84
|
+
} catch (error: any) {
|
|
85
|
+
const msg: string = error.message ?? String(error);
|
|
86
|
+
this.initErrors.set(name, msg);
|
|
87
|
+
process.stderr.write(`[conductor] ✗ ${name}: init failed — ${msg}\n`);
|
|
88
|
+
process.stderr.write(`[conductor] → Run: conductor doctor ${name}\n`);
|
|
89
|
+
return undefined;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return plugin;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/** Get the recorded init error for a plugin, if any. */
|
|
97
|
+
getInitError(name: string): string | undefined {
|
|
98
|
+
return this.initErrors.get(name);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Probe all enabled plugins and return a status summary.
|
|
103
|
+
* Used by the MCP startup summary and conductor doctor.
|
|
104
|
+
*/
|
|
105
|
+
async getStatusSummary(): Promise<PluginStatus[]> {
|
|
106
|
+
const enabledNames = this.conductor.getConfig().get<string[]>('plugins.enabled') ?? [];
|
|
107
|
+
const statuses: PluginStatus[] = [];
|
|
108
|
+
|
|
109
|
+
for (const name of enabledNames) {
|
|
110
|
+
const plugin = this.plugins.get(name);
|
|
111
|
+
if (!plugin) {
|
|
112
|
+
statuses.push({ name, status: 'disabled', toolCount: 0 });
|
|
113
|
+
continue;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
if (!plugin.isConfigured()) {
|
|
117
|
+
statuses.push({
|
|
118
|
+
name,
|
|
119
|
+
status: 'not_configured',
|
|
120
|
+
toolCount: 0,
|
|
121
|
+
setupCommand: `conductor plugins setup ${name}`,
|
|
122
|
+
});
|
|
123
|
+
continue;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Attempt init if not done yet
|
|
127
|
+
if (!this.initializedPlugins.has(name)) {
|
|
128
|
+
await this.getPlugin(name);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const err = this.initErrors.get(name);
|
|
132
|
+
if (err) {
|
|
133
|
+
statuses.push({ name, status: 'init_failed', toolCount: 0, error: err });
|
|
134
|
+
} else {
|
|
135
|
+
const toolCount = plugin.getTools().length;
|
|
136
|
+
statuses.push({ name, status: 'ready', toolCount });
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
return statuses;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/** List all registered plugins. Does NOT initialize them. */
|
|
144
|
+
listPlugins(): Array<{
|
|
145
|
+
name: string;
|
|
146
|
+
description: string;
|
|
147
|
+
version: string;
|
|
148
|
+
enabled: boolean;
|
|
149
|
+
configSchema?: PluginConfigSchema;
|
|
150
|
+
}> {
|
|
151
|
+
const enabledPlugins = this.conductor.getConfig().get<string[]>('plugins.enabled') || [];
|
|
152
|
+
|
|
153
|
+
return Array.from(this.plugins.values()).map((plugin) => ({
|
|
154
|
+
name: plugin.name,
|
|
155
|
+
description: plugin.description,
|
|
156
|
+
version: plugin.version,
|
|
157
|
+
enabled: enabledPlugins.includes(plugin.name),
|
|
158
|
+
configSchema: plugin.configSchema,
|
|
159
|
+
}));
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
isPluginEnabled(name: string): boolean {
|
|
163
|
+
const enabled = this.conductor.getConfig().get<string[]>('plugins.enabled') || [];
|
|
164
|
+
return enabled.includes(name);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
async enablePlugin(name: string): Promise<void> {
|
|
168
|
+
const plugin = this.plugins.get(name);
|
|
169
|
+
if (!plugin) throw new Error(`Plugin not found: ${name}`);
|
|
170
|
+
|
|
171
|
+
// Initialize to verify it works
|
|
172
|
+
if (!this.initializedPlugins.has(name)) {
|
|
173
|
+
try {
|
|
174
|
+
await plugin.initialize(this.conductor);
|
|
175
|
+
this.initializedPlugins.add(name);
|
|
176
|
+
} catch (error: any) {
|
|
177
|
+
throw new Error(`Failed to enable plugin "${name}": initialization failed: ${error.message}`);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
const enabled = this.conductor.getConfig().get<string[]>('plugins.enabled') || [];
|
|
182
|
+
if (!enabled.includes(name)) {
|
|
183
|
+
enabled.push(name);
|
|
184
|
+
await this.conductor.getConfig().set('plugins.enabled', enabled);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
async disablePlugin(name: string): Promise<void> {
|
|
189
|
+
const enabled = this.conductor.getConfig().get<string[]>('plugins.enabled') || [];
|
|
190
|
+
const filtered = enabled.filter((p) => p !== name);
|
|
191
|
+
await this.conductor.getConfig().set('plugins.enabled', filtered);
|
|
192
|
+
this.initializedPlugins.delete(name);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/** Get MCP tools from enabled plugins only. */
|
|
196
|
+
async getEnabledTools(): Promise<PluginTool[]> {
|
|
197
|
+
const tools: PluginTool[] = [];
|
|
198
|
+
const enabledNames = this.conductor.getConfig().get<string[]>('plugins.enabled') || [];
|
|
199
|
+
|
|
200
|
+
for (const name of enabledNames) {
|
|
201
|
+
try {
|
|
202
|
+
const plugin = await this.getPlugin(name);
|
|
203
|
+
if (plugin) {
|
|
204
|
+
tools.push(...plugin.getTools());
|
|
205
|
+
}
|
|
206
|
+
} catch (error: any) {
|
|
207
|
+
process.stderr.write(`Warning: plugin "${name}" failed to load: ${error.message}\n`);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
return tools;
|
|
212
|
+
}
|
|
213
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Zod validation wrapper for plugin tool handlers.
|
|
3
|
+
*
|
|
4
|
+
* Every tool handler input is validated against its inputSchema before execution.
|
|
5
|
+
* Invalid inputs are rejected with clear error messages — no silent failures,
|
|
6
|
+
* no `any` types slipping through.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { z } from 'zod';
|
|
10
|
+
import type { PluginTool } from './manager.js';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Convert an MCP-style JSON Schema inputSchema to a Zod schema.
|
|
14
|
+
* This validates tool inputs at runtime before they reach the handler.
|
|
15
|
+
*/
|
|
16
|
+
function schemaFromInputSchema(schema: Record<string, unknown>): z.ZodType {
|
|
17
|
+
if (schema.type !== 'object') {
|
|
18
|
+
return z.unknown();
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const properties = (schema.properties ?? {}) as Record<string, Record<string, unknown>>;
|
|
22
|
+
const required = new Set<string>((schema.required as string[]) ?? []);
|
|
23
|
+
const shape: Record<string, z.ZodType> = {};
|
|
24
|
+
|
|
25
|
+
for (const [key, propSchema] of Object.entries(properties)) {
|
|
26
|
+
let fieldSchema: z.ZodType;
|
|
27
|
+
|
|
28
|
+
switch (propSchema.type) {
|
|
29
|
+
case 'string':
|
|
30
|
+
fieldSchema = z.string();
|
|
31
|
+
if (propSchema.enum) {
|
|
32
|
+
fieldSchema = z.enum(propSchema.enum as [string, ...string[]]);
|
|
33
|
+
}
|
|
34
|
+
break;
|
|
35
|
+
case 'number':
|
|
36
|
+
case 'integer':
|
|
37
|
+
fieldSchema = z.number();
|
|
38
|
+
break;
|
|
39
|
+
case 'boolean':
|
|
40
|
+
fieldSchema = z.boolean();
|
|
41
|
+
break;
|
|
42
|
+
case 'array':
|
|
43
|
+
fieldSchema = z.array(
|
|
44
|
+
(propSchema.items as { type?: string })?.type === 'string'
|
|
45
|
+
? z.string()
|
|
46
|
+
: (propSchema.items as { type?: string })?.type === 'number'
|
|
47
|
+
? z.number()
|
|
48
|
+
: z.unknown(),
|
|
49
|
+
);
|
|
50
|
+
break;
|
|
51
|
+
case 'object':
|
|
52
|
+
fieldSchema = z.record(z.string(), z.unknown());
|
|
53
|
+
break;
|
|
54
|
+
default:
|
|
55
|
+
fieldSchema = z.unknown();
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if (propSchema.description) {
|
|
59
|
+
fieldSchema = fieldSchema.describe(propSchema.description as string);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
shape[key] = required.has(key) ? fieldSchema : fieldSchema.optional();
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return z.object(shape);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Wrap a plugin tool with Zod validation.
|
|
70
|
+
* Returns a new tool whose handler validates input before execution.
|
|
71
|
+
*/
|
|
72
|
+
export function withValidation(tool: PluginTool): PluginTool {
|
|
73
|
+
const validator = schemaFromInputSchema(tool.inputSchema);
|
|
74
|
+
|
|
75
|
+
return {
|
|
76
|
+
...tool,
|
|
77
|
+
handler: async (input: unknown) => {
|
|
78
|
+
const result = validator.safeParse(input);
|
|
79
|
+
if (!result.success) {
|
|
80
|
+
const errors = result.error.issues.map((e) => `${e.path.join('.')}: ${e.message}`).join('; ');
|
|
81
|
+
throw new Error(`Invalid input for tool "${tool.name}": ${errors}`);
|
|
82
|
+
}
|
|
83
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
84
|
+
return tool.handler(result.data as any);
|
|
85
|
+
},
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Wrap an array of plugin tools with Zod validation.
|
|
91
|
+
*/
|
|
92
|
+
export function validateTools(tools: PluginTool[]): PluginTool[] {
|
|
93
|
+
return tools.map(withValidation);
|
|
94
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# src/security
|
|
2
|
+
|
|
3
|
+
Security layer for Conductor — authentication, keychain management, and credential encryption.
|
|
4
|
+
|
|
5
|
+
## Contents
|
|
6
|
+
|
|
7
|
+
- `keychain.ts` - OS keychain integration for secret storage
|
|
8
|
+
- `auth.ts` - Authentication and authorization utilities
|
|
9
|
+
|
|
10
|
+
## Architecture
|
|
11
|
+
|
|
12
|
+
Secret credentials are AES-256-GCM encrypted with a machine-bound key. The keychain stores secrets in the OS keychain rather than `config.json`. All HTTP endpoints are protected by `express-rate-limit`. The shell plugin uses a whitelist allowlist — no `eval()` or `exec()`.
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import crypto from 'crypto';
|
|
2
|
+
|
|
3
|
+
export interface VerificationCode {
|
|
4
|
+
code: string;
|
|
5
|
+
userId?: number;
|
|
6
|
+
expiresAt: Date;
|
|
7
|
+
used: boolean;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export class AuthManager {
|
|
11
|
+
private codes: Map<string, VerificationCode> = new Map();
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Generate a verification code like: CX-8F2K-9L4P
|
|
15
|
+
*/
|
|
16
|
+
generateVerificationCode(): string {
|
|
17
|
+
const part1 = this.randomString(2);
|
|
18
|
+
const part2 = this.randomString(4);
|
|
19
|
+
const part3 = this.randomString(4);
|
|
20
|
+
|
|
21
|
+
const code = `${part1}-${part2}-${part3}`.toUpperCase();
|
|
22
|
+
|
|
23
|
+
// Store with 5-minute expiry
|
|
24
|
+
this.codes.set(code, {
|
|
25
|
+
code,
|
|
26
|
+
expiresAt: new Date(Date.now() + 5 * 60 * 1000),
|
|
27
|
+
used: false,
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
return code;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Verify a code and mark it as used.
|
|
35
|
+
*/
|
|
36
|
+
verifyCode(code: string, userId: number): boolean {
|
|
37
|
+
const storedCode = this.codes.get(code.toUpperCase());
|
|
38
|
+
|
|
39
|
+
if (!storedCode) return false;
|
|
40
|
+
if (storedCode.used) return false;
|
|
41
|
+
if (storedCode.expiresAt < new Date()) return false;
|
|
42
|
+
|
|
43
|
+
storedCode.used = true;
|
|
44
|
+
storedCode.userId = userId;
|
|
45
|
+
|
|
46
|
+
return true;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Clean up expired codes.
|
|
51
|
+
*/
|
|
52
|
+
cleanupExpiredCodes(): void {
|
|
53
|
+
const now = new Date();
|
|
54
|
+
for (const [code, data] of this.codes.entries()) {
|
|
55
|
+
if (data.expiresAt < now) {
|
|
56
|
+
this.codes.delete(code);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
private randomString(length: number): string {
|
|
62
|
+
const chars = 'ABCDEFGHJKLMNPQRSTUVWXYZ23456789'; // Exclude ambiguous chars
|
|
63
|
+
let result = '';
|
|
64
|
+
const bytes = crypto.randomBytes(length);
|
|
65
|
+
|
|
66
|
+
for (let i = 0; i < length; i++) {
|
|
67
|
+
result += chars[bytes[i] % chars.length];
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return result;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
import crypto from 'crypto';
|
|
2
|
+
import fs from 'fs/promises';
|
|
3
|
+
import { readFileSync, mkdirSync, writeFileSync } from 'fs';
|
|
4
|
+
import { execSync } from 'child_process';
|
|
5
|
+
import path from 'path';
|
|
6
|
+
import os from 'os';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Keychain — encrypted credential storage.
|
|
10
|
+
*
|
|
11
|
+
* Supports two formats:
|
|
12
|
+
* v2 — AES-256-GCM with machine-specific key derivation (written by install.sh)
|
|
13
|
+
* v1 — AES-256-CBC legacy format (written by older versions)
|
|
14
|
+
*
|
|
15
|
+
* New writes always use the v2 format.
|
|
16
|
+
*/
|
|
17
|
+
export class Keychain {
|
|
18
|
+
private keychainDir: string;
|
|
19
|
+
private masterKey: Buffer;
|
|
20
|
+
|
|
21
|
+
constructor(configDir: string) {
|
|
22
|
+
this.keychainDir = path.join(configDir, 'keychain');
|
|
23
|
+
this.masterKey = this.deriveMasterKey();
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Derive a machine-specific master key.
|
|
28
|
+
* Matches the install.sh derivation so credentials saved by either
|
|
29
|
+
* the installer or the app are interchangeable.
|
|
30
|
+
*/
|
|
31
|
+
private deriveMasterKey(): Buffer {
|
|
32
|
+
const machineSecret = this.getMachineSecret();
|
|
33
|
+
const salt = crypto.createHash('sha256').update('conductor:keychain:v1').digest();
|
|
34
|
+
return crypto.scryptSync(machineSecret, salt, 32, { N: 16384, r: 8, p: 1 });
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Get a machine-specific secret for key derivation.
|
|
39
|
+
* Mirrors the logic in install.sh's save_cred function.
|
|
40
|
+
*/
|
|
41
|
+
private getMachineSecret(): string {
|
|
42
|
+
// Linux: /etc/machine-id or /var/lib/dbus/machine-id
|
|
43
|
+
const linuxSources = ['/etc/machine-id', '/var/lib/dbus/machine-id', '/proc/sys/kernel/random/boot_id'];
|
|
44
|
+
|
|
45
|
+
for (const src of linuxSources) {
|
|
46
|
+
try {
|
|
47
|
+
const data = readFileSync(src, 'utf8').trim();
|
|
48
|
+
if (data) return data;
|
|
49
|
+
} catch {
|
|
50
|
+
// Not available on this platform
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// macOS: IOPlatformUUID
|
|
55
|
+
if (process.platform === 'darwin') {
|
|
56
|
+
try {
|
|
57
|
+
const out = execSync("ioreg -rd1 -c IOPlatformExpertDevice | awk '/IOPlatformUUID/{print $NF}'", {
|
|
58
|
+
encoding: 'utf8',
|
|
59
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
60
|
+
})
|
|
61
|
+
.trim()
|
|
62
|
+
.replace(/"/g, '');
|
|
63
|
+
if (out) return out;
|
|
64
|
+
} catch {
|
|
65
|
+
// ioreg not available
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Windows: MachineGuid from registry
|
|
70
|
+
if (process.platform === 'win32') {
|
|
71
|
+
try {
|
|
72
|
+
const out = execSync('reg query "HKLM\\SOFTWARE\\Microsoft\\Cryptography" /v MachineGuid', {
|
|
73
|
+
encoding: 'utf8',
|
|
74
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
75
|
+
});
|
|
76
|
+
const match = out.match(/MachineGuid\s+REG_SZ\s+(.+)/);
|
|
77
|
+
if (match?.[1]?.trim()) return match[1].trim();
|
|
78
|
+
} catch {
|
|
79
|
+
// Registry not accessible
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Persistent fallback: Save a random UUID to a file if hardware IDs fail.
|
|
84
|
+
// This is safer than hostname which can change.
|
|
85
|
+
const fallbackPath = path.join(this.keychainDir, 'machine_secret');
|
|
86
|
+
try {
|
|
87
|
+
if (readFileSync(fallbackPath, 'utf8').trim()) {
|
|
88
|
+
return readFileSync(fallbackPath, 'utf8').trim();
|
|
89
|
+
}
|
|
90
|
+
} catch {
|
|
91
|
+
try {
|
|
92
|
+
const secret = crypto.randomUUID();
|
|
93
|
+
// Use synchronous write since this is inside deriveMasterKey constructor path
|
|
94
|
+
mkdirSync(this.keychainDir, { recursive: true, mode: 0o700 });
|
|
95
|
+
writeFileSync(fallbackPath, secret, { mode: 0o600 });
|
|
96
|
+
return secret;
|
|
97
|
+
} catch {
|
|
98
|
+
// Absolute last resort
|
|
99
|
+
return os.hostname();
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
return os.hostname();
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Store an encrypted credential (v2 format: AES-256-GCM).
|
|
108
|
+
*/
|
|
109
|
+
async set(service: string, key: string, value: string): Promise<void> {
|
|
110
|
+
await fs.mkdir(this.keychainDir, { recursive: true, mode: 0o700 });
|
|
111
|
+
// Enforce 0700 permissions in case dir already existed with wrong perms
|
|
112
|
+
await fs.chmod(this.keychainDir, 0o700).catch(() => {});
|
|
113
|
+
|
|
114
|
+
const iv = crypto.randomBytes(12); // 96-bit IV for GCM
|
|
115
|
+
const cipher = crypto.createCipheriv('aes-256-gcm', this.masterKey, iv);
|
|
116
|
+
|
|
117
|
+
let encrypted = cipher.update(value, 'utf8', 'hex');
|
|
118
|
+
encrypted += cipher.final('hex');
|
|
119
|
+
const tag = cipher.getAuthTag().toString('hex');
|
|
120
|
+
|
|
121
|
+
// Format: v2:iv:tag:ciphertext
|
|
122
|
+
const result = ['v2', iv.toString('hex'), tag, encrypted].join(':');
|
|
123
|
+
|
|
124
|
+
const filepath = path.join(this.keychainDir, `${service}.${key}.enc`);
|
|
125
|
+
await fs.writeFile(filepath, result, { mode: 0o600 });
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Retrieve and decrypt a credential.
|
|
130
|
+
* Handles both v2 (GCM) and legacy v1 (CBC) formats.
|
|
131
|
+
*/
|
|
132
|
+
async get(service: string, key: string): Promise<string | null> {
|
|
133
|
+
try {
|
|
134
|
+
const filepath = path.join(this.keychainDir, `${service}.${key}.enc`);
|
|
135
|
+
const data = await fs.readFile(filepath, 'utf-8');
|
|
136
|
+
return this.decrypt(data.trim());
|
|
137
|
+
} catch {
|
|
138
|
+
return null;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Delete a credential.
|
|
144
|
+
*/
|
|
145
|
+
async delete(service: string, key: string): Promise<void> {
|
|
146
|
+
const filepath = path.join(this.keychainDir, `${service}.${key}.enc`);
|
|
147
|
+
try {
|
|
148
|
+
await fs.unlink(filepath);
|
|
149
|
+
} catch {
|
|
150
|
+
// File doesn't exist — that's fine
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Check if a credential exists.
|
|
156
|
+
*/
|
|
157
|
+
async has(service: string, key: string): Promise<boolean> {
|
|
158
|
+
try {
|
|
159
|
+
const filepath = path.join(this.keychainDir, `${service}.${key}.enc`);
|
|
160
|
+
await fs.access(filepath);
|
|
161
|
+
return true;
|
|
162
|
+
} catch {
|
|
163
|
+
return false;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* List all credential keys for a service.
|
|
169
|
+
*/
|
|
170
|
+
async list(service: string): Promise<string[]> {
|
|
171
|
+
try {
|
|
172
|
+
const files = await fs.readdir(this.keychainDir);
|
|
173
|
+
return files
|
|
174
|
+
.filter((f) => f.startsWith(`${service}.`) && f.endsWith('.enc'))
|
|
175
|
+
.map((f) => f.replace(`${service}.`, '').replace('.enc', ''));
|
|
176
|
+
} catch {
|
|
177
|
+
return [];
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Decrypt a stored value, auto-detecting format version.
|
|
183
|
+
*/
|
|
184
|
+
private decrypt(stored: string): string {
|
|
185
|
+
const parts = stored.split(':');
|
|
186
|
+
|
|
187
|
+
if (parts[0] === 'v2' && parts.length === 4) {
|
|
188
|
+
// v2: AES-256-GCM — iv:tag:ciphertext
|
|
189
|
+
return this.decryptGCM(parts[1], parts[2], parts[3]);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
if (parts.length === 2) {
|
|
193
|
+
// Legacy v1: AES-256-CBC — iv:ciphertext (no version prefix)
|
|
194
|
+
return this.decryptCBC(parts[0], parts[1]);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
throw new Error('Unrecognized credential format');
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* AES-256-GCM decryption (v2 format, matches install.sh).
|
|
202
|
+
*/
|
|
203
|
+
private decryptGCM(ivHex: string, tagHex: string, cipherHex: string): string {
|
|
204
|
+
const iv = Buffer.from(ivHex, 'hex');
|
|
205
|
+
const tag = Buffer.from(tagHex, 'hex');
|
|
206
|
+
const decipher = crypto.createDecipheriv('aes-256-gcm', this.masterKey, iv);
|
|
207
|
+
decipher.setAuthTag(tag);
|
|
208
|
+
|
|
209
|
+
let decrypted = decipher.update(cipherHex, 'hex', 'utf8');
|
|
210
|
+
decrypted += decipher.final('utf8');
|
|
211
|
+
return decrypted;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* AES-256-CBC decryption (legacy v1 format).
|
|
216
|
+
* Kept for backward-compat with credentials written before the v2 migration.
|
|
217
|
+
*/
|
|
218
|
+
private decryptCBC(ivHex: string, cipherHex: string): string {
|
|
219
|
+
const iv = Buffer.from(ivHex, 'hex');
|
|
220
|
+
const decipher = crypto.createDecipheriv('aes-256-cbc', this.masterKey, iv);
|
|
221
|
+
|
|
222
|
+
let decrypted = decipher.update(cipherHex, 'hex', 'utf8');
|
|
223
|
+
decrypted += decipher.final('utf8');
|
|
224
|
+
return decrypted;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# src/utils
|
|
2
|
+
|
|
3
|
+
Shared utility functions used across the Conductor codebase.
|
|
4
|
+
|
|
5
|
+
## Contents
|
|
6
|
+
|
|
7
|
+
- `google-auth.ts` - Google OAuth authentication helpers
|
|
8
|
+
- `retry.ts` - Generic retry logic utilities
|
|
9
|
+
|
|
10
|
+
## Architecture
|
|
11
|
+
|
|
12
|
+
These are cross-cutting utilities consumed by multiple modules. `google-auth.ts` is used by Google Workspace plugins (GCal, GDrive, Gmail). `retry.ts` provides reusable retry logic complementary to the core retry module.
|