@useconductor/conductor 1.0.0 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (145) hide show
  1. package/.github/README.md +374 -7
  2. package/.github/workflows/ci.yml +3 -1
  3. package/.github/workflows/claude-code-review.yml +1 -15
  4. package/.github/workflows/publish.yml +43 -0
  5. package/README.md +290 -121
  6. package/dist/cli/commands/audit.d.ts +40 -0
  7. package/dist/cli/commands/audit.d.ts.map +1 -0
  8. package/dist/cli/commands/audit.js +272 -0
  9. package/dist/cli/commands/audit.js.map +1 -0
  10. package/dist/cli/commands/circuit.d.ts +13 -0
  11. package/dist/cli/commands/circuit.d.ts.map +1 -0
  12. package/dist/cli/commands/circuit.js +53 -0
  13. package/dist/cli/commands/circuit.js.map +1 -0
  14. package/dist/cli/commands/config.d.ts +31 -0
  15. package/dist/cli/commands/config.d.ts.map +1 -0
  16. package/dist/cli/commands/config.js +152 -0
  17. package/dist/cli/commands/config.js.map +1 -0
  18. package/dist/cli/commands/init.d.ts +5 -8
  19. package/dist/cli/commands/init.d.ts.map +1 -1
  20. package/dist/cli/commands/init.js +86 -123
  21. package/dist/cli/commands/init.js.map +1 -1
  22. package/dist/cli/commands/marketplace.js +1 -1
  23. package/dist/cli/commands/onboard.d.ts.map +1 -1
  24. package/dist/cli/commands/onboard.js +33 -11
  25. package/dist/cli/commands/onboard.js.map +1 -1
  26. package/dist/cli/commands/release.d.ts.map +1 -1
  27. package/dist/cli/commands/release.js +1 -1
  28. package/dist/cli/commands/release.js.map +1 -1
  29. package/dist/cli/index.js +146 -10
  30. package/dist/cli/index.js.map +1 -1
  31. package/dist/core/audit.d.ts.map +1 -1
  32. package/dist/core/audit.js +5 -2
  33. package/dist/core/audit.js.map +1 -1
  34. package/dist/core/conductor.d.ts.map +1 -1
  35. package/dist/core/conductor.js +12 -0
  36. package/dist/core/conductor.js.map +1 -1
  37. package/dist/core/config.d.ts +3 -0
  38. package/dist/core/config.d.ts.map +1 -1
  39. package/dist/core/config.js +46 -2
  40. package/dist/core/config.js.map +1 -1
  41. package/dist/core/database.d.ts +3 -0
  42. package/dist/core/database.d.ts.map +1 -1
  43. package/dist/core/database.js +26 -0
  44. package/dist/core/database.js.map +1 -1
  45. package/dist/core/encryption.d.ts +34 -0
  46. package/dist/core/encryption.d.ts.map +1 -0
  47. package/dist/core/encryption.js +96 -0
  48. package/dist/core/encryption.js.map +1 -0
  49. package/dist/core/zero-config.d.ts.map +1 -1
  50. package/dist/core/zero-config.js +1 -4
  51. package/dist/core/zero-config.js.map +1 -1
  52. package/dist/dashboard/server.d.ts.map +1 -1
  53. package/dist/dashboard/server.js +112 -16
  54. package/dist/dashboard/server.js.map +1 -1
  55. package/dist/mcp/server.d.ts.map +1 -1
  56. package/dist/mcp/server.js +30 -2
  57. package/dist/mcp/server.js.map +1 -1
  58. package/dist/plugins/builtin/aws.d.ts +31 -0
  59. package/dist/plugins/builtin/aws.d.ts.map +1 -0
  60. package/dist/plugins/builtin/aws.js +149 -0
  61. package/dist/plugins/builtin/aws.js.map +1 -0
  62. package/dist/plugins/builtin/database.d.ts +1 -0
  63. package/dist/plugins/builtin/database.d.ts.map +1 -1
  64. package/dist/plugins/builtin/database.js +26 -1
  65. package/dist/plugins/builtin/database.js.map +1 -1
  66. package/dist/plugins/builtin/docker.d.ts +4 -0
  67. package/dist/plugins/builtin/docker.d.ts.map +1 -1
  68. package/dist/plugins/builtin/docker.js +20 -1
  69. package/dist/plugins/builtin/docker.js.map +1 -1
  70. package/dist/plugins/builtin/gcp.d.ts +28 -0
  71. package/dist/plugins/builtin/gcp.d.ts.map +1 -0
  72. package/dist/plugins/builtin/gcp.js +135 -0
  73. package/dist/plugins/builtin/gcp.js.map +1 -0
  74. package/dist/plugins/builtin/index.d.ts.map +1 -1
  75. package/dist/plugins/builtin/index.js +4 -0
  76. package/dist/plugins/builtin/index.js.map +1 -1
  77. package/dist/plugins/builtin/jira.d.ts.map +1 -1
  78. package/dist/plugins/builtin/jira.js +4 -2
  79. package/dist/plugins/builtin/jira.js.map +1 -1
  80. package/dist/plugins/builtin/linear.js +1 -1
  81. package/dist/plugins/builtin/linear.js.map +1 -1
  82. package/dist/plugins/builtin/shell.js +1 -1
  83. package/dist/plugins/builtin/shell.js.map +1 -1
  84. package/dist/plugins/builtin/slack.d.ts +1 -0
  85. package/dist/plugins/builtin/slack.d.ts.map +1 -1
  86. package/dist/plugins/builtin/slack.js +9 -1
  87. package/dist/plugins/builtin/slack.js.map +1 -1
  88. package/dist/plugins/builtin/spotify.js +1 -1
  89. package/dist/plugins/builtin/spotify.js.map +1 -1
  90. package/dist/plugins/builtin/vercel.d.ts.map +1 -1
  91. package/dist/plugins/builtin/vercel.js +3 -1
  92. package/dist/plugins/builtin/vercel.js.map +1 -1
  93. package/dist/security/sso.d.ts +37 -0
  94. package/dist/security/sso.d.ts.map +1 -0
  95. package/dist/security/sso.js +92 -0
  96. package/dist/security/sso.js.map +1 -0
  97. package/docs/deployment.md +201 -0
  98. package/docs/plugin-sdk.md +212 -0
  99. package/package.json +11 -8
  100. package/src/cli/commands/audit.ts +318 -0
  101. package/src/cli/commands/circuit.ts +63 -0
  102. package/src/cli/commands/config.ts +176 -0
  103. package/src/cli/commands/init.ts +87 -145
  104. package/src/cli/commands/marketplace.ts +1 -1
  105. package/src/cli/commands/onboard.ts +33 -11
  106. package/src/cli/commands/release.ts +13 -6
  107. package/src/cli/index.ts +165 -11
  108. package/src/core/audit.ts +5 -2
  109. package/src/core/conductor.ts +11 -0
  110. package/src/core/config.ts +47 -2
  111. package/src/core/database.ts +32 -0
  112. package/src/core/encryption.ts +110 -0
  113. package/src/core/zero-config.ts +1 -5
  114. package/src/dashboard/server.ts +135 -16
  115. package/src/mcp/server.ts +40 -2
  116. package/src/plugins/builtin/aws.ts +162 -0
  117. package/src/plugins/builtin/database.ts +19 -1
  118. package/src/plugins/builtin/docker.ts +17 -1
  119. package/src/plugins/builtin/gcp.ts +145 -0
  120. package/src/plugins/builtin/index.ts +4 -0
  121. package/src/plugins/builtin/jira.ts +23 -19
  122. package/src/plugins/builtin/linear.ts +1 -1
  123. package/src/plugins/builtin/shell.ts +1 -1
  124. package/src/plugins/builtin/slack.ts +6 -1
  125. package/src/plugins/builtin/spotify.ts +1 -1
  126. package/src/plugins/builtin/vercel.ts +3 -1
  127. package/src/security/sso.ts +124 -0
  128. package/tests/audit.test.ts +185 -0
  129. package/tests/circuit-breaker.test.ts +125 -0
  130. package/tests/docker.test.ts +244 -39
  131. package/tests/errors.test.ts +122 -0
  132. package/tests/github.test.ts.skip +392 -0
  133. package/tests/jira.test.ts +310 -0
  134. package/tests/linear.test.ts +366 -0
  135. package/tests/mcp.test.ts.skip +243 -0
  136. package/tests/notion.test.ts +257 -0
  137. package/tests/retry.test.ts +104 -0
  138. package/tests/shell.test.ts +262 -30
  139. package/tests/slack.test.ts +250 -0
  140. package/tests/stripe.test.ts +272 -0
  141. package/tests/validation.test.ts +173 -0
  142. package/tests/vercel.test.ts +368 -0
  143. package/tests/zero-config.test.ts +566 -0
  144. package/C.png +0 -0
  145. package/tests/mcp.test.ts +0 -14
@@ -0,0 +1,176 @@
1
+ /**
2
+ * conductor config — read and write configuration keys.
3
+ *
4
+ * Commands:
5
+ * conductor config list — show all config keys and values
6
+ * conductor config get <key> — get a specific key
7
+ * conductor config set <key> <val> — set a key
8
+ * conductor config path — print config file path
9
+ * conductor config export — dump config as JSON
10
+ * conductor config reset — reset to defaults (with confirmation)
11
+ * conductor config validate — validate config structure
12
+ */
13
+
14
+ import type { Conductor } from '../../core/conductor.js';
15
+
16
+ function flattenConfig(obj: unknown, prefix = ''): Array<[string, unknown]> {
17
+ if (typeof obj !== 'object' || obj === null) {
18
+ return [[prefix, obj]];
19
+ }
20
+ const result: Array<[string, unknown]> = [];
21
+ for (const [key, val] of Object.entries(obj as Record<string, unknown>)) {
22
+ const fullKey = prefix ? `${prefix}.${key}` : key;
23
+ if (typeof val === 'object' && val !== null && !Array.isArray(val)) {
24
+ result.push(...flattenConfig(val, fullKey));
25
+ } else {
26
+ result.push([fullKey, val]);
27
+ }
28
+ }
29
+ return result;
30
+ }
31
+
32
+ function maskSecret(key: string, value: unknown): unknown {
33
+ if (/token|secret|password|api_key|key_stored/i.test(key)) {
34
+ if (typeof value === 'string' && value.length > 0) return '***';
35
+ }
36
+ return value;
37
+ }
38
+
39
+ export async function configList(
40
+ conductor: Conductor,
41
+ opts: { json?: boolean; show_secrets?: boolean },
42
+ ): Promise<void> {
43
+ await conductor.initialize();
44
+ const config = conductor.getConfig().getConfig();
45
+
46
+ if (opts.json) {
47
+ console.log(JSON.stringify(config, null, 2));
48
+ return;
49
+ }
50
+
51
+ const pairs = flattenConfig(config);
52
+
53
+ console.log('');
54
+ console.log(' ⚙️ Configuration\n');
55
+ console.log(` ${'KEY'.padEnd(40)} VALUE`);
56
+ console.log(' ' + '─'.repeat(70));
57
+ for (const [key, val] of pairs) {
58
+ const display = opts.show_secrets ? val : maskSecret(key, val);
59
+ const str = Array.isArray(val) ? `[${(val as unknown[]).join(', ')}]` : String(display ?? '');
60
+ console.log(` ${key.padEnd(40)} ${str}`);
61
+ }
62
+ console.log('');
63
+ console.log(` Config file: ${conductor.getConfig().getConfigDir()}/config.json\n`);
64
+ }
65
+
66
+ export async function configGet(conductor: Conductor, key: string, opts: { json?: boolean }): Promise<void> {
67
+ await conductor.initialize();
68
+ const value = conductor.getConfig().get(key);
69
+
70
+ if (value === undefined) {
71
+ console.error(`\n ❌ Key "${key}" not found.\n`);
72
+ console.log(' Run: conductor config list to see all keys.\n');
73
+ process.exit(1);
74
+ }
75
+
76
+ if (opts.json) {
77
+ console.log(JSON.stringify({ key, value }));
78
+ return;
79
+ }
80
+
81
+ console.log(`\n ${key}: ${JSON.stringify(value)}\n`);
82
+ }
83
+
84
+ export async function configSet(conductor: Conductor, key: string, value: string): Promise<void> {
85
+ await conductor.initialize();
86
+
87
+ // Try to parse as JSON, fall back to string
88
+ let parsed: unknown;
89
+ try {
90
+ parsed = JSON.parse(value);
91
+ } catch {
92
+ parsed = value;
93
+ }
94
+
95
+ await conductor.getConfig().set(key, parsed);
96
+ console.log(`\n ✅ Set ${key} = ${JSON.stringify(parsed)}\n`);
97
+ }
98
+
99
+ export async function configPath(conductor: Conductor): Promise<void> {
100
+ await conductor.initialize();
101
+ const dir = conductor.getConfig().getConfigDir();
102
+ console.log(`\n ${dir}/config.json\n`);
103
+ }
104
+
105
+ export async function configExport(conductor: Conductor, opts: { output?: string; pretty?: boolean }): Promise<void> {
106
+ await conductor.initialize();
107
+ const config = conductor.getConfig().getConfig();
108
+ const content = opts.pretty === false ? JSON.stringify(config) : JSON.stringify(config, null, 2);
109
+
110
+ if (opts.output) {
111
+ const { writeFile } = await import('fs/promises');
112
+ await writeFile(opts.output, content + '\n', 'utf-8');
113
+ console.log(`\n ✅ Config exported to: ${opts.output}\n`);
114
+ } else {
115
+ console.log(content);
116
+ }
117
+ }
118
+
119
+ export async function configReset(conductor: Conductor, opts: { yes?: boolean }): Promise<void> {
120
+ if (!opts.yes) {
121
+ const { default: inquirer } = await import('inquirer');
122
+ const { confirm } = await inquirer.prompt([
123
+ {
124
+ type: 'confirm',
125
+ name: 'confirm',
126
+ message: 'Reset configuration to defaults? This cannot be undone.',
127
+ default: false,
128
+ },
129
+ ]);
130
+ if (!confirm) {
131
+ console.log('\n Cancelled.\n');
132
+ return;
133
+ }
134
+ }
135
+
136
+ await conductor.initialize();
137
+ const dir = conductor.getConfig().getConfigDir();
138
+
139
+ // Write an empty config to trigger re-initialization to defaults
140
+ const { writeFile } = await import('fs/promises');
141
+ await writeFile(`${dir}/config.json`, '{}', 'utf-8');
142
+
143
+ console.log('\n ✅ Configuration reset to defaults.\n');
144
+ console.log(' Run: conductor init to set up from scratch.\n');
145
+ }
146
+
147
+ export async function configValidate(conductor: Conductor): Promise<void> {
148
+ await conductor.initialize();
149
+ const config = conductor.getConfig().getConfig();
150
+ const issues: string[] = [];
151
+
152
+ // Basic structural checks
153
+ if (config.ai?.provider && !['claude', 'openai', 'gemini', 'ollama'].includes(config.ai.provider)) {
154
+ issues.push(`ai.provider "${config.ai.provider}" is not a recognized provider`);
155
+ }
156
+
157
+ if (config.plugins?.enabled && !Array.isArray(config.plugins.enabled)) {
158
+ issues.push('plugins.enabled must be an array');
159
+ }
160
+
161
+ if (config.plugins?.installed && !Array.isArray(config.plugins.installed)) {
162
+ issues.push('plugins.installed must be an array');
163
+ }
164
+
165
+ console.log('');
166
+ if (issues.length === 0) {
167
+ console.log(' ✅ Configuration is valid.\n');
168
+ } else {
169
+ console.log(' ❌ Configuration issues found:\n');
170
+ for (const issue of issues) {
171
+ console.log(` • ${issue}`);
172
+ }
173
+ console.log('');
174
+ process.exit(1);
175
+ }
176
+ }
@@ -1,17 +1,13 @@
1
1
  /**
2
2
  * conductor init — First-run setup wizard
3
3
  *
4
- * Takes a brand-new user from zero to a fully working MCP server
5
- * in under 2 minutes. Orchestrates all other setup flows in sequence:
6
- *
7
- * 1. Welcome banner + ask user name
8
- * 2. AI provider setup (Claude / OpenAI / Gemini / Ollama / skip)
9
- * 3. Plugin onboard TUI (calls the existing onboard() function)
10
- * 4. MCP client config (Claude Desktop / Cursor / Cline / skip)
11
- * 5. Final instructions
4
+ * Gets a new user to a working MCP server in under 2 minutes:
5
+ * 1. AI provider setup (Claude / OpenAI / Gemini / Ollama / skip)
6
+ * 2. Plugin onboard TUI
7
+ * 3. MCP client config (Claude Desktop / Cursor / Cline / skip)
8
+ * 4. Final instructions
12
9
  */
13
10
 
14
- import chalk from 'chalk';
15
11
  import inquirer from 'inquirer';
16
12
  import { Conductor } from '../../core/conductor.js';
17
13
  import { AIManager } from '../../ai/manager.js';
@@ -19,65 +15,57 @@ import fs from 'fs/promises';
19
15
  import path from 'path';
20
16
  import { homedir } from 'os';
21
17
 
22
- // ── Banner ─────────────────────────────────────────────────────────────────
18
+ // ── Terminal helpers (b/w only) ────────────────────────────────────────────
23
19
 
24
- function printBanner(): void {
25
- console.log('');
26
- console.log(chalk.bold.white(' ╔══════════════════════════════════════════════╗'));
27
- console.log(
28
- chalk.bold.white(' ║') +
29
- chalk.bold.hex('#FF8C00')(' ♦ Conductor — The AI Tool Hub ') +
30
- chalk.bold.white('║'),
31
- );
32
- console.log(
33
- chalk.bold.white(' ║') + chalk.dim(' One MCP server. 100+ tools. Any AI. ') + chalk.bold.white('║'),
34
- );
35
- console.log(chalk.bold.white(' ╚══════════════════════════════════════════════╝'));
36
- console.log('');
37
- console.log(chalk.dim(' This wizard will get you up and running in under 2 minutes.'));
38
- console.log(chalk.dim(' Press Ctrl+C at any time to exit.'));
39
- console.log('');
40
- }
20
+ const B = '\x1b[1m';
21
+ const D = '\x1b[2m';
22
+ const R = '\x1b[0m';
41
23
 
42
- // ── Step header ────────────────────────────────────────────────────────────
24
+ function hr(width = 50) {
25
+ process.stdout.write(' ' + '─'.repeat(width) + '\n');
26
+ }
43
27
 
44
28
  function stepHeader(n: number, total: number, label: string): void {
45
29
  console.log('');
46
- console.log(
47
- chalk.bold.white(` ── Step ${n}/${total}: ${label} `) + chalk.dim('─'.repeat(Math.max(0, 38 - label.length))),
48
- );
30
+ console.log(` ${B}── Step ${n}/${total}: ${label}${R}`);
49
31
  console.log('');
50
32
  }
51
33
 
52
- // ── Step 1: User name ──────────────────────────────────────────────────────
53
-
54
- async function setupUserName(conductor: Conductor): Promise<void> {
55
- stepHeader(1, 4, 'Your name');
56
-
57
- const existing = conductor.getConfig().get<string>('user.name') || '';
34
+ // ── Banner ─────────────────────────────────────────────────────────────────
58
35
 
59
- const { name } = await inquirer.prompt<{ name: string }>([
60
- {
61
- type: 'input',
62
- name: 'name',
63
- message: 'What should I call you?',
64
- default: existing || undefined,
65
- validate: (v: string) => (v.trim().length > 0 ? true : 'Name cannot be empty'),
66
- },
67
- ]);
36
+ function printBanner(): void {
37
+ const W = 50;
38
+ const top = '' + '─'.repeat(W) + '┐';
39
+ const bot = '' + '─'.repeat(W) + '┘';
40
+ const blank = ' │' + ' '.repeat(W) + '│';
41
+ const line = (text: string) => {
42
+ const pad = W - text.length - 1;
43
+ return ` │ ${B}${text}${R}` + ' '.repeat(Math.max(0, pad)) + '│';
44
+ };
45
+ const dim = (text: string) => {
46
+ const pad = W - text.length - 1;
47
+ return ` │ ${D}${text}${R}` + ' '.repeat(Math.max(0, pad)) + '│';
48
+ };
68
49
 
69
- await conductor.getConfig().set('user.name', name.trim());
70
50
  console.log('');
71
- console.log(chalk.green(` ✓ Hi, ${name.trim()}!`));
51
+ console.log(top);
52
+ console.log(blank);
53
+ console.log(line('Conductor — The AI Tool Hub'));
54
+ console.log(blank);
55
+ console.log(dim('One MCP server. 100+ tools. Any AI agent.'));
56
+ console.log(dim('Setup takes under 2 minutes.'));
57
+ console.log(blank);
58
+ console.log(bot);
59
+ console.log('');
72
60
  }
73
61
 
74
- // ── Step 2: AI Provider ────────────────────────────────────────────────────
62
+ // ── Step 1: AI Provider ────────────────────────────────────────────────────
75
63
 
76
64
  async function setupAIProvider(conductor: Conductor): Promise<void> {
77
- stepHeader(2, 4, 'AI Provider');
65
+ stepHeader(1, 3, 'AI Provider');
78
66
 
79
- console.log(chalk.dim(' Pick the AI provider Conductor will use for its own reasoning.'));
80
- console.log(chalk.dim(' (This is separate from the AI agent that calls Conductor via MCP.)'));
67
+ console.log(` ${D}Pick the AI provider Conductor uses for its own reasoning.${R}`);
68
+ console.log(` ${D}(Separate from the AI agent that calls Conductor via MCP.)${R}`);
81
69
  console.log('');
82
70
 
83
71
  const { provider } = await inquirer.prompt<{ provider: string }>([
@@ -90,14 +78,14 @@ async function setupAIProvider(conductor: Conductor): Promise<void> {
90
78
  { name: 'OpenAI (GPT-4o) — popular & capable', value: 'openai' },
91
79
  { name: 'Gemini (Google) — fast & free tier', value: 'gemini' },
92
80
  { name: 'Ollama (local, private) — no API key needed', value: 'ollama' },
93
- { name: chalk.dim('Skip — configure later with: conductor ai setup'), value: 'skip' },
81
+ { name: `${D}Skip — configure later with: conductor ai setup${R}`, value: 'skip' },
94
82
  ],
95
83
  },
96
84
  ]);
97
85
 
98
86
  if (provider === 'skip') {
99
87
  console.log('');
100
- console.log(chalk.dim(' Skipped. Run: conductor ai setup'));
88
+ console.log(` ${D}Skipped. Run: conductor ai setup${R}`);
101
89
  return;
102
90
  }
103
91
 
@@ -106,77 +94,54 @@ async function setupAIProvider(conductor: Conductor): Promise<void> {
106
94
  switch (provider) {
107
95
  case 'claude': {
108
96
  console.log('');
109
- console.log(chalk.dim(' Get your key at: https://console.anthropic.com'));
97
+ console.log(` ${D}Get your key at: https://console.anthropic.com${R}`);
110
98
  const { apiKey } = await inquirer.prompt<{ apiKey: string }>([
111
- {
112
- type: 'password',
113
- name: 'apiKey',
114
- message: 'Anthropic API key:',
115
- mask: '*',
116
- validate: (v: string) => v.trim().length > 0 || 'API key is required',
117
- },
99
+ { type: 'password', name: 'apiKey', message: 'Anthropic API key:', mask: '*',
100
+ validate: (v: string) => v.trim().length > 0 || 'API key is required' },
118
101
  ]);
119
102
  await aiManager.setupClaude(apiKey.trim());
120
- console.log(chalk.green(' ✓ Claude configured'));
103
+ console.log(` ✓ Claude configured`);
121
104
  break;
122
105
  }
123
-
124
106
  case 'openai': {
125
107
  console.log('');
126
- console.log(chalk.dim(' Get your key at: https://platform.openai.com/api-keys'));
108
+ console.log(` ${D}Get your key at: https://platform.openai.com/api-keys${R}`);
127
109
  const { apiKey } = await inquirer.prompt<{ apiKey: string }>([
128
- {
129
- type: 'password',
130
- name: 'apiKey',
131
- message: 'OpenAI API key:',
132
- mask: '*',
133
- validate: (v: string) => v.trim().length > 0 || 'API key is required',
134
- },
110
+ { type: 'password', name: 'apiKey', message: 'OpenAI API key:', mask: '*',
111
+ validate: (v: string) => v.trim().length > 0 || 'API key is required' },
135
112
  ]);
136
113
  await aiManager.setupOpenAI(apiKey.trim());
137
- console.log(chalk.green(' ✓ OpenAI configured'));
114
+ console.log(` ✓ OpenAI configured`);
138
115
  break;
139
116
  }
140
-
141
117
  case 'gemini': {
142
118
  console.log('');
143
- console.log(chalk.dim(' Get your key at: https://aistudio.google.com/app/apikey'));
119
+ console.log(` ${D}Get your key at: https://aistudio.google.com/app/apikey${R}`);
144
120
  const { apiKey } = await inquirer.prompt<{ apiKey: string }>([
145
- {
146
- type: 'password',
147
- name: 'apiKey',
148
- message: 'Gemini API key:',
149
- mask: '*',
150
- validate: (v: string) => v.trim().length > 0 || 'API key is required',
151
- },
121
+ { type: 'password', name: 'apiKey', message: 'Gemini API key:', mask: '*',
122
+ validate: (v: string) => v.trim().length > 0 || 'API key is required' },
152
123
  ]);
153
124
  await aiManager.setupGemini(apiKey.trim());
154
- console.log(chalk.green(' ✓ Gemini configured'));
125
+ console.log(` ✓ Gemini configured`);
155
126
  break;
156
127
  }
157
-
158
128
  case 'ollama': {
159
129
  console.log('');
160
- console.log(chalk.dim(' Make sure Ollama is running: https://ollama.ai'));
130
+ console.log(` ${D}Make sure Ollama is running: https://ollama.ai${R}`);
161
131
  const { model } = await inquirer.prompt<{ model: string }>([
162
- {
163
- type: 'input',
164
- name: 'model',
165
- message: 'Which Ollama model?',
166
- default: 'llama3.2',
167
- },
132
+ { type: 'input', name: 'model', message: 'Which Ollama model?', default: 'llama3.2' },
168
133
  ]);
169
134
  await aiManager.setupOllama(model.trim());
170
- console.log(chalk.green(` ✓ Ollama configured (${model.trim()})`));
135
+ console.log(` ✓ Ollama configured (${model.trim()})`);
171
136
  break;
172
137
  }
173
138
  }
174
139
  }
175
140
 
176
- // ── Step 3: Plugins ────────────────────────────────────────────────────────
141
+ // ── Step 2: Plugins ────────────────────────────────────────────────────────
177
142
 
178
143
  async function setupPlugins(conductor: Conductor): Promise<void> {
179
- stepHeader(3, 4, 'Plugins');
144
+ stepHeader(2, 3, 'Plugins');
180
145
 
181
146
  const { doOnboard } = await inquirer.prompt<{ doOnboard: boolean }>([
182
147
  {
@@ -189,18 +154,16 @@ async function setupPlugins(conductor: Conductor): Promise<void> {
189
154
 
190
155
  if (!doOnboard) {
191
156
  console.log('');
192
- console.log(chalk.dim(' Skipped. Run: conductor onboard'));
157
+ console.log(` ${D}Skipped. Run: conductor onboard${R}`);
193
158
  return;
194
159
  }
195
160
 
196
- // Dynamically import onboard to avoid circular deps
197
161
  const { onboard } = await import('./onboard.js');
198
162
  await onboard(conductor);
199
163
  }
200
164
 
201
- // ── Step 4: MCP client config ──────────────────────────────────────────────
165
+ // ── Step 3: MCP client config ──────────────────────────────────────────────
202
166
 
203
- // Config file paths per client
204
167
  const MCP_CONFIG_PATHS: Record<string, string> = {
205
168
  claude: path.join(
206
169
  homedir(),
@@ -227,8 +190,8 @@ const MCP_CONFIG_PATHS: Record<string, string> = {
227
190
  };
228
191
 
229
192
  async function writeClientConfig(configPath: string): Promise<void> {
230
- const conductorBin = process.argv[1];
231
- const entry = { command: 'node', args: [conductorBin, 'mcp', 'start'] };
193
+ // Use the conductor binary by name — robust across npm reinstalls and upgrades.
194
+ const entry = { command: 'conductor', args: ['mcp', 'start'] };
232
195
 
233
196
  let config: Record<string, unknown> = {};
234
197
  try {
@@ -238,7 +201,6 @@ async function writeClientConfig(configPath: string): Promise<void> {
238
201
  // File doesn't exist — start fresh
239
202
  }
240
203
 
241
- // Both Claude Desktop and Cursor use mcpServers; Cline too
242
204
  const servers = (config['mcpServers'] ?? {}) as Record<string, unknown>;
243
205
  servers['conductor'] = entry;
244
206
  config['mcpServers'] = servers;
@@ -248,9 +210,9 @@ async function writeClientConfig(configPath: string): Promise<void> {
248
210
  }
249
211
 
250
212
  async function setupMCPClient(conductor: Conductor): Promise<void> {
251
- stepHeader(4, 4, 'Connect your AI client');
213
+ stepHeader(3, 3, 'Connect your AI client');
252
214
 
253
- console.log(chalk.dim(' Conductor will auto-write the MCP server config for your chosen client.'));
215
+ console.log(` ${D}Conductor will write the MCP server entry into your client's config.${R}`);
254
216
  console.log('');
255
217
 
256
218
  const { client } = await inquirer.prompt<{ client: string }>([
@@ -262,14 +224,15 @@ async function setupMCPClient(conductor: Conductor): Promise<void> {
262
224
  { name: 'Claude Desktop', value: 'claude' },
263
225
  { name: 'Cursor', value: 'cursor' },
264
226
  { name: 'Cline (VS Code extension)', value: 'cline' },
265
- { name: chalk.dim("Skip — I'll configure manually"), value: 'skip' },
227
+ { name: `${D}Skip — I'll configure manually${R}`, value: 'skip' },
266
228
  ],
267
229
  },
268
230
  ]);
269
231
 
270
232
  if (client === 'skip') {
271
233
  console.log('');
272
- console.log(chalk.dim(' Skipped. Run: conductor mcp setup'));
234
+ console.log(` ${D}Skipped. Run: conductor mcp setup${R}`);
235
+ void conductor;
273
236
  return;
274
237
  }
275
238
 
@@ -277,49 +240,38 @@ async function setupMCPClient(conductor: Conductor): Promise<void> {
277
240
 
278
241
  try {
279
242
  await writeClientConfig(configPath);
243
+ const clientName = client === 'claude' ? 'Claude Desktop' : client === 'cursor' ? 'Cursor' : 'Cline';
280
244
  console.log('');
281
- console.log(
282
- chalk.green(
283
- ` ✓ ${client === 'claude' ? 'Claude Desktop' : client === 'cursor' ? 'Cursor' : 'Cline'} configured`,
284
- ),
285
- );
286
- console.log(chalk.dim(` Config: ${configPath}`));
245
+ console.log(` ✓ ${clientName} configured`);
246
+ console.log(` ${D} Config: ${configPath}${R}`);
287
247
  } catch (err: unknown) {
288
248
  const msg = err instanceof Error ? err.message : String(err);
289
249
  console.log('');
290
- console.log(chalk.yellow(` Could not write config: ${msg}`));
291
- console.log(chalk.dim(' Run: conductor mcp setup'));
250
+ console.log(` ! Could not write config: ${msg}`);
251
+ console.log(` ${D} Run: conductor mcp setup${R}`);
292
252
  }
293
-
294
- // Silence the conductor parameter — used for future expansion
295
- void conductor;
296
253
  }
297
254
 
298
255
  // ── Final instructions ─────────────────────────────────────────────────────
299
256
 
300
- function printFinalInstructions(userName: string): void {
257
+ function printFinalInstructions(): void {
258
+ const W = 50;
259
+ console.log('');
260
+ hr(W);
301
261
  console.log('');
302
- console.log(chalk.bold.white(' ╔══════════════════════════════════════════════╗'));
303
- console.log(
304
- chalk.bold.white(' ║') +
305
- chalk.bold.green(" ✓ You're all set, " + (userName || 'there') + '!') +
306
- ' '.repeat(Math.max(0, 24 - (userName || 'there').length)) +
307
- chalk.bold.white('║'),
308
- );
309
- console.log(chalk.bold.white(' ╚══════════════════════════════════════════════╝'));
262
+ console.log(` ${B}You're all set.${R}`);
310
263
  console.log('');
311
- console.log(chalk.dim(' Start the MCP server:'));
312
- console.log(` ${chalk.cyan('conductor mcp start')}`);
264
+ console.log(` Start the MCP server:`);
265
+ console.log(` ${B}conductor mcp start${R}`);
313
266
  console.log('');
314
- console.log(chalk.dim(' Then restart your AI client (Claude Desktop / Cursor / Cline)'));
315
- console.log(chalk.dim(' and Conductor will appear as a connected MCP server.'));
267
+ console.log(` ${D}Then restart your AI client Conductor will appear`);
268
+ console.log(` as a connected MCP server with 100+ tools available.${R}`);
316
269
  console.log('');
317
- console.log(chalk.dim(' Explore:'));
318
- console.log(` ${chalk.cyan('conductor dashboard')} ${chalk.dim('web UI with metrics and audit log')}`);
319
- console.log(` ${chalk.cyan('conductor doctor')} ${chalk.dim('diagnose issues')}`);
320
- console.log(` ${chalk.cyan('conductor health')} ${chalk.dim('— system health status')}`);
270
+ console.log(` ${D}conductor dashboard — web UI with metrics and audit log${R}`);
271
+ console.log(` ${D}conductor doctor diagnose issues${R}`);
272
+ console.log(` ${D}conductor healthsystem health status${R}`);
321
273
  console.log('');
322
- console.log(chalk.dim(' Docs & support: https://conductor.thealxlabs.ca'));
274
+ hr(W);
323
275
  console.log('');
324
276
  }
325
277
 
@@ -330,19 +282,9 @@ export async function init(conductor: Conductor): Promise<void> {
330
282
 
331
283
  printBanner();
332
284
 
333
- // Step 1 — user name
334
- await setupUserName(conductor);
335
-
336
- // Step 2 — AI provider
337
285
  await setupAIProvider(conductor);
338
-
339
- // Step 3 — plugins
340
286
  await setupPlugins(conductor);
341
-
342
- // Step 4 — MCP client
343
287
  await setupMCPClient(conductor);
344
288
 
345
- // Done!
346
- const userName = conductor.getConfig().get<string>('user.name') || '';
347
- printFinalInstructions(userName);
289
+ printFinalInstructions();
348
290
  }
@@ -3,7 +3,7 @@ import path from 'path';
3
3
  import { Conductor } from '../../core/conductor.js';
4
4
 
5
5
  const REGISTRY_URLS = [
6
- 'https://raw.githubusercontent.com/thegreatalxx/conductor-plugins/main/registry.json',
6
+ 'https://raw.githubusercontent.com/useconductor/conductor-plugins/main/registry.json',
7
7
  'https://conductor.thealxlabs.ca/registry.json',
8
8
  ];
9
9
  const GITHUB_RAW = 'https://raw.githubusercontent.com';
@@ -22,25 +22,47 @@ interface _PluginEntry {
22
22
 
23
23
  // Category groupings for the TUI picker
24
24
  const CATEGORIES: Record<string, string[]> = {
25
- 'Developer Tools': [
26
- 'shell', 'docker', 'github', 'git', 'github-actions', 'vercel', 'n8n', 'linear', 'jira',
27
- ],
28
- 'Communication': ['slack', 'telegram'],
25
+ 'Developer Tools': ['shell', 'docker', 'github', 'git', 'github-actions', 'vercel', 'n8n', 'linear', 'jira'],
26
+ Communication: ['slack', 'telegram'],
29
27
  'Google Workspace': ['gmail', 'google-calendar', 'google-drive'],
30
- 'Productivity': ['notes', 'memory', 'notion', 'todoist'],
28
+ Productivity: ['notes', 'memory', 'notion', 'todoist'],
31
29
  'Finance & Commerce': ['stripe'],
32
- 'Utilities': [
33
- 'calculator', 'colors', 'crypto', 'hash', 'text-tools',
34
- 'timezone', 'network', 'url-tools', 'fun', 'system', 'cron', 'weather', 'translate',
30
+ Utilities: [
31
+ 'calculator',
32
+ 'colors',
33
+ 'crypto',
34
+ 'hash',
35
+ 'text-tools',
36
+ 'timezone',
37
+ 'network',
38
+ 'url-tools',
39
+ 'fun',
40
+ 'system',
41
+ 'cron',
42
+ 'weather',
43
+ 'translate',
35
44
  ],
36
45
  'Media & Social': ['spotify', 'x'],
37
46
  'Smart Home': ['homekit'],
38
47
  };
39
48
 
40
49
  const ZERO_CONFIG_SET = new Set([
41
- 'calculator', 'colors', 'hash', 'text-tools', 'timezone',
42
- 'network', 'url-tools', 'fun', 'system', 'notes', 'memory',
43
- 'cron', 'shell', 'docker', 'github', 'translate',
50
+ 'calculator',
51
+ 'colors',
52
+ 'hash',
53
+ 'text-tools',
54
+ 'timezone',
55
+ 'network',
56
+ 'url-tools',
57
+ 'fun',
58
+ 'system',
59
+ 'notes',
60
+ 'memory',
61
+ 'cron',
62
+ 'shell',
63
+ 'docker',
64
+ 'github',
65
+ 'translate',
44
66
  'weather',
45
67
  'crypto',
46
68
  ]);