@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.
- package/.github/README.md +374 -7
- package/.github/workflows/ci.yml +3 -1
- package/.github/workflows/claude-code-review.yml +1 -15
- package/.github/workflows/publish.yml +43 -0
- package/README.md +290 -121
- package/dist/cli/commands/audit.d.ts +40 -0
- package/dist/cli/commands/audit.d.ts.map +1 -0
- package/dist/cli/commands/audit.js +272 -0
- package/dist/cli/commands/audit.js.map +1 -0
- package/dist/cli/commands/circuit.d.ts +13 -0
- package/dist/cli/commands/circuit.d.ts.map +1 -0
- package/dist/cli/commands/circuit.js +53 -0
- package/dist/cli/commands/circuit.js.map +1 -0
- package/dist/cli/commands/config.d.ts +31 -0
- package/dist/cli/commands/config.d.ts.map +1 -0
- package/dist/cli/commands/config.js +152 -0
- package/dist/cli/commands/config.js.map +1 -0
- package/dist/cli/commands/init.d.ts +5 -8
- package/dist/cli/commands/init.d.ts.map +1 -1
- package/dist/cli/commands/init.js +86 -123
- package/dist/cli/commands/init.js.map +1 -1
- package/dist/cli/commands/marketplace.js +1 -1
- package/dist/cli/commands/onboard.d.ts.map +1 -1
- package/dist/cli/commands/onboard.js +33 -11
- package/dist/cli/commands/onboard.js.map +1 -1
- package/dist/cli/commands/release.d.ts.map +1 -1
- package/dist/cli/commands/release.js +1 -1
- package/dist/cli/commands/release.js.map +1 -1
- package/dist/cli/index.js +146 -10
- package/dist/cli/index.js.map +1 -1
- package/dist/core/audit.d.ts.map +1 -1
- package/dist/core/audit.js +5 -2
- package/dist/core/audit.js.map +1 -1
- package/dist/core/conductor.d.ts.map +1 -1
- package/dist/core/conductor.js +12 -0
- package/dist/core/conductor.js.map +1 -1
- package/dist/core/config.d.ts +3 -0
- package/dist/core/config.d.ts.map +1 -1
- package/dist/core/config.js +46 -2
- package/dist/core/config.js.map +1 -1
- package/dist/core/database.d.ts +3 -0
- package/dist/core/database.d.ts.map +1 -1
- package/dist/core/database.js +26 -0
- package/dist/core/database.js.map +1 -1
- package/dist/core/encryption.d.ts +34 -0
- package/dist/core/encryption.d.ts.map +1 -0
- package/dist/core/encryption.js +96 -0
- package/dist/core/encryption.js.map +1 -0
- package/dist/core/zero-config.d.ts.map +1 -1
- package/dist/core/zero-config.js +1 -4
- package/dist/core/zero-config.js.map +1 -1
- package/dist/dashboard/server.d.ts.map +1 -1
- package/dist/dashboard/server.js +112 -16
- package/dist/dashboard/server.js.map +1 -1
- package/dist/mcp/server.d.ts.map +1 -1
- package/dist/mcp/server.js +30 -2
- package/dist/mcp/server.js.map +1 -1
- package/dist/plugins/builtin/aws.d.ts +31 -0
- package/dist/plugins/builtin/aws.d.ts.map +1 -0
- package/dist/plugins/builtin/aws.js +149 -0
- package/dist/plugins/builtin/aws.js.map +1 -0
- package/dist/plugins/builtin/database.d.ts +1 -0
- package/dist/plugins/builtin/database.d.ts.map +1 -1
- package/dist/plugins/builtin/database.js +26 -1
- package/dist/plugins/builtin/database.js.map +1 -1
- package/dist/plugins/builtin/docker.d.ts +4 -0
- package/dist/plugins/builtin/docker.d.ts.map +1 -1
- package/dist/plugins/builtin/docker.js +20 -1
- package/dist/plugins/builtin/docker.js.map +1 -1
- package/dist/plugins/builtin/gcp.d.ts +28 -0
- package/dist/plugins/builtin/gcp.d.ts.map +1 -0
- package/dist/plugins/builtin/gcp.js +135 -0
- package/dist/plugins/builtin/gcp.js.map +1 -0
- package/dist/plugins/builtin/index.d.ts.map +1 -1
- package/dist/plugins/builtin/index.js +4 -0
- package/dist/plugins/builtin/index.js.map +1 -1
- package/dist/plugins/builtin/jira.d.ts.map +1 -1
- package/dist/plugins/builtin/jira.js +4 -2
- package/dist/plugins/builtin/jira.js.map +1 -1
- package/dist/plugins/builtin/linear.js +1 -1
- package/dist/plugins/builtin/linear.js.map +1 -1
- package/dist/plugins/builtin/shell.js +1 -1
- package/dist/plugins/builtin/shell.js.map +1 -1
- package/dist/plugins/builtin/slack.d.ts +1 -0
- package/dist/plugins/builtin/slack.d.ts.map +1 -1
- package/dist/plugins/builtin/slack.js +9 -1
- package/dist/plugins/builtin/slack.js.map +1 -1
- package/dist/plugins/builtin/spotify.js +1 -1
- package/dist/plugins/builtin/spotify.js.map +1 -1
- package/dist/plugins/builtin/vercel.d.ts.map +1 -1
- package/dist/plugins/builtin/vercel.js +3 -1
- package/dist/plugins/builtin/vercel.js.map +1 -1
- package/dist/security/sso.d.ts +37 -0
- package/dist/security/sso.d.ts.map +1 -0
- package/dist/security/sso.js +92 -0
- package/dist/security/sso.js.map +1 -0
- package/docs/deployment.md +201 -0
- package/docs/plugin-sdk.md +212 -0
- package/package.json +11 -8
- package/src/cli/commands/audit.ts +318 -0
- package/src/cli/commands/circuit.ts +63 -0
- package/src/cli/commands/config.ts +176 -0
- package/src/cli/commands/init.ts +87 -145
- package/src/cli/commands/marketplace.ts +1 -1
- package/src/cli/commands/onboard.ts +33 -11
- package/src/cli/commands/release.ts +13 -6
- package/src/cli/index.ts +165 -11
- package/src/core/audit.ts +5 -2
- package/src/core/conductor.ts +11 -0
- package/src/core/config.ts +47 -2
- package/src/core/database.ts +32 -0
- package/src/core/encryption.ts +110 -0
- package/src/core/zero-config.ts +1 -5
- package/src/dashboard/server.ts +135 -16
- package/src/mcp/server.ts +40 -2
- package/src/plugins/builtin/aws.ts +162 -0
- package/src/plugins/builtin/database.ts +19 -1
- package/src/plugins/builtin/docker.ts +17 -1
- package/src/plugins/builtin/gcp.ts +145 -0
- package/src/plugins/builtin/index.ts +4 -0
- package/src/plugins/builtin/jira.ts +23 -19
- package/src/plugins/builtin/linear.ts +1 -1
- package/src/plugins/builtin/shell.ts +1 -1
- package/src/plugins/builtin/slack.ts +6 -1
- package/src/plugins/builtin/spotify.ts +1 -1
- package/src/plugins/builtin/vercel.ts +3 -1
- package/src/security/sso.ts +124 -0
- package/tests/audit.test.ts +185 -0
- package/tests/circuit-breaker.test.ts +125 -0
- package/tests/docker.test.ts +244 -39
- package/tests/errors.test.ts +122 -0
- package/tests/github.test.ts.skip +392 -0
- package/tests/jira.test.ts +310 -0
- package/tests/linear.test.ts +366 -0
- package/tests/mcp.test.ts.skip +243 -0
- package/tests/notion.test.ts +257 -0
- package/tests/retry.test.ts +104 -0
- package/tests/shell.test.ts +262 -30
- package/tests/slack.test.ts +250 -0
- package/tests/stripe.test.ts +272 -0
- package/tests/validation.test.ts +173 -0
- package/tests/vercel.test.ts +368 -0
- package/tests/zero-config.test.ts +566 -0
- package/C.png +0 -0
- package/tests/mcp.test.ts +0 -14
package/src/dashboard/server.ts
CHANGED
|
@@ -465,13 +465,22 @@ export async function startDashboard(port = 4242, conductorInstance?: Conductor)
|
|
|
465
465
|
|
|
466
466
|
// ── System Control ────────────────────────────────────────────────────────
|
|
467
467
|
|
|
468
|
-
// Safe command runner
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
468
|
+
// Safe command runner — only specific commands with validated arguments
|
|
469
|
+
// Uses execFile (no shell interpretation) with strict argument validation
|
|
470
|
+
async function runSafe(
|
|
471
|
+
executable: string,
|
|
472
|
+
args: string[],
|
|
473
|
+
timeoutMs = 30000,
|
|
474
|
+
): Promise<{ stdout: string; stderr: string; exitCode: number }> {
|
|
475
|
+
const { execFile } = await import('child_process');
|
|
476
|
+
const { promisify } = await import('util');
|
|
477
|
+
const execFileAsync = promisify(execFile);
|
|
478
|
+
|
|
479
|
+
// Strict allowlist — only these executables are permitted
|
|
480
|
+
const allowed = new Set([
|
|
481
|
+
'ps',
|
|
473
482
|
'tasklist',
|
|
474
|
-
'open
|
|
483
|
+
'open',
|
|
475
484
|
'xdg-open',
|
|
476
485
|
'screencapture',
|
|
477
486
|
'scrot',
|
|
@@ -479,20 +488,38 @@ export async function startDashboard(port = 4242, conductorInstance?: Conductor)
|
|
|
479
488
|
'xclip',
|
|
480
489
|
'xsel',
|
|
481
490
|
'ifconfig',
|
|
482
|
-
'ip
|
|
491
|
+
'ip',
|
|
483
492
|
'netstat',
|
|
484
|
-
'ss
|
|
493
|
+
'ss',
|
|
485
494
|
'lsof',
|
|
486
|
-
'docker
|
|
495
|
+
'docker',
|
|
487
496
|
'crontab',
|
|
488
|
-
'git
|
|
489
|
-
];
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
497
|
+
'git',
|
|
498
|
+
]);
|
|
499
|
+
|
|
500
|
+
if (!allowed.has(executable)) {
|
|
501
|
+
return { stdout: '', stderr: `Command not allowed: ${executable}`, exitCode: 1 };
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
// Block dangerous argument patterns
|
|
505
|
+
const dangerous = args.some(
|
|
506
|
+
(a) =>
|
|
507
|
+
a.includes(';') ||
|
|
508
|
+
a.includes('|') ||
|
|
509
|
+
a.includes('&') ||
|
|
510
|
+
a.includes('$') ||
|
|
511
|
+
a.includes('`') ||
|
|
512
|
+
a.includes('..') ||
|
|
513
|
+
a.includes('>/') ||
|
|
514
|
+
a.includes('<') ||
|
|
515
|
+
a.includes('rm ') ||
|
|
516
|
+
a.includes('eval') ||
|
|
517
|
+
a.includes('exec'),
|
|
518
|
+
);
|
|
519
|
+
if (dangerous) {
|
|
520
|
+
return { stdout: '', stderr: 'Command contains disallowed characters', exitCode: 1 };
|
|
494
521
|
}
|
|
495
|
-
|
|
522
|
+
|
|
496
523
|
try {
|
|
497
524
|
const { stdout, stderr } = await execFileAsync(executable, args, {
|
|
498
525
|
timeout: timeoutMs,
|
|
@@ -508,6 +535,12 @@ export async function startDashboard(port = 4242, conductorInstance?: Conductor)
|
|
|
508
535
|
}
|
|
509
536
|
}
|
|
510
537
|
|
|
538
|
+
// DEPRECATED: runCmd — use runSafe instead
|
|
539
|
+
async function runCmd(cmd: string, timeoutMs = 30000): Promise<{ stdout: string; stderr: string; exitCode: number }> {
|
|
540
|
+
const [executable, ...args] = cmd.trim().split(/\s+/);
|
|
541
|
+
return runSafe(executable, args, timeoutMs);
|
|
542
|
+
}
|
|
543
|
+
|
|
511
544
|
// GET /api/system/info
|
|
512
545
|
app.get('/api/system/info', async (_req: Request, res: Response): Promise<void> => {
|
|
513
546
|
const cpus = os.cpus();
|
|
@@ -1529,6 +1562,92 @@ export async function startDashboard(port = 4242, conductorInstance?: Conductor)
|
|
|
1529
1562
|
res.json({ plugins });
|
|
1530
1563
|
});
|
|
1531
1564
|
|
|
1565
|
+
// ── Metrics Endpoint (Prometheus) ────────────────────────────────
|
|
1566
|
+
|
|
1567
|
+
app.get('/metrics', async (_req: Request, res: Response): Promise<void> => {
|
|
1568
|
+
const { HealthChecker } = await import('../core/health.js');
|
|
1569
|
+
const checker = new HealthChecker();
|
|
1570
|
+
const report = await checker.detailed();
|
|
1571
|
+
|
|
1572
|
+
const lines = [
|
|
1573
|
+
'# HELP conductor_build_info Conductor build information',
|
|
1574
|
+
'# TYPE conductor_build_info gauge',
|
|
1575
|
+
`conductor_build_info{version="${report.version}"} 1`,
|
|
1576
|
+
'',
|
|
1577
|
+
'# HELP conductor_health Conductor health status',
|
|
1578
|
+
'# TYPE conductor_health gauge',
|
|
1579
|
+
`conductor_health ${report.status === 'ok' ? 1 : 0}`,
|
|
1580
|
+
];
|
|
1581
|
+
|
|
1582
|
+
res.type('text/plain').send(lines.join('\n'));
|
|
1583
|
+
});
|
|
1584
|
+
|
|
1585
|
+
// ── Health & Audit Endpoints ──────────────────────────────────────────────
|
|
1586
|
+
|
|
1587
|
+
// GET /api/health
|
|
1588
|
+
app.get('/api/health', async (_req: Request, res: Response): Promise<void> => {
|
|
1589
|
+
const { HealthChecker } = await import('../core/health.js');
|
|
1590
|
+
const checker = new HealthChecker();
|
|
1591
|
+
const report = await checker.detailed();
|
|
1592
|
+
res.status(report.status === 'down' ? 503 : 200).json(report);
|
|
1593
|
+
});
|
|
1594
|
+
|
|
1595
|
+
// GET /api/audit
|
|
1596
|
+
app.get('/api/audit', async (req: Request, res: Response): Promise<void> => {
|
|
1597
|
+
const { AuditLogger } = await import('../core/audit.js');
|
|
1598
|
+
const audit = new AuditLogger(config.getConfigDir());
|
|
1599
|
+
const entries = await audit.query({
|
|
1600
|
+
action: typeof req.query.action === 'string' ? req.query.action : undefined,
|
|
1601
|
+
resource: typeof req.query.resource === 'string' ? req.query.resource : undefined,
|
|
1602
|
+
result: typeof req.query.result === 'string' ? req.query.result : undefined,
|
|
1603
|
+
limit: parseInt(typeof req.query.limit === 'string' ? req.query.limit : '100') || 100,
|
|
1604
|
+
});
|
|
1605
|
+
await audit.close();
|
|
1606
|
+
res.json(entries);
|
|
1607
|
+
});
|
|
1608
|
+
|
|
1609
|
+
// GET /api/webhooks
|
|
1610
|
+
app.get('/api/webhooks', async (_req: Request, res: Response): Promise<void> => {
|
|
1611
|
+
const { WebhookManager } = await import('../core/webhooks.js');
|
|
1612
|
+
const wm = new WebhookManager(config.getConfigDir());
|
|
1613
|
+
await wm.load();
|
|
1614
|
+
res.json(wm.list());
|
|
1615
|
+
});
|
|
1616
|
+
|
|
1617
|
+
// POST /api/webhooks
|
|
1618
|
+
app.post('/api/webhooks', async (req: Request, res: Response): Promise<void> => {
|
|
1619
|
+
const body = req.body as { url?: string; events?: string[] };
|
|
1620
|
+
if (!body.url) {
|
|
1621
|
+
res.status(400).json({ error: 'url is required' });
|
|
1622
|
+
return;
|
|
1623
|
+
}
|
|
1624
|
+
const { WebhookManager } = await import('../core/webhooks.js');
|
|
1625
|
+
const wm = new WebhookManager(config.getConfigDir());
|
|
1626
|
+
await wm.load();
|
|
1627
|
+
const events = Array.isArray(body.events) ? body.events : ['*'];
|
|
1628
|
+
const sub = await wm.create(body.url, events);
|
|
1629
|
+
await wm.save();
|
|
1630
|
+
res.json(sub);
|
|
1631
|
+
});
|
|
1632
|
+
|
|
1633
|
+
// DELETE /api/webhooks/:id
|
|
1634
|
+
app.delete('/api/webhooks/:id', async (req: Request, res: Response): Promise<void> => {
|
|
1635
|
+
const { WebhookManager } = await import('../core/webhooks.js');
|
|
1636
|
+
const wm = new WebhookManager(config.getConfigDir());
|
|
1637
|
+
await wm.load();
|
|
1638
|
+
const deleted = await wm.delete(String(req.params.id));
|
|
1639
|
+
await wm.save();
|
|
1640
|
+
res.json({ deleted });
|
|
1641
|
+
});
|
|
1642
|
+
|
|
1643
|
+
// GET /api/metrics
|
|
1644
|
+
app.get('/api/metrics', async (_req: Request, res: Response): Promise<void> => {
|
|
1645
|
+
const { HealthChecker } = await import('../core/health.js');
|
|
1646
|
+
const checker = new HealthChecker();
|
|
1647
|
+
const report = await checker.detailed();
|
|
1648
|
+
res.json(report.metrics || {});
|
|
1649
|
+
});
|
|
1650
|
+
|
|
1532
1651
|
// ── Start — bind to 127.0.0.1 only (not 0.0.0.0) ────────────────────────
|
|
1533
1652
|
return new Promise<DashboardServer>((resolve, reject) => {
|
|
1534
1653
|
const server = app.listen(port, '127.0.0.1', () => {
|
package/src/mcp/server.ts
CHANGED
|
@@ -37,6 +37,7 @@ import { HealthChecker } from '../core/health.js';
|
|
|
37
37
|
import { WebhookManager } from '../core/webhooks.js';
|
|
38
38
|
import { logger } from '../core/logger.js';
|
|
39
39
|
import { enableZeroConfigMode } from '../core/zero-config.js';
|
|
40
|
+
import { RBACManager, Role } from '../core/rbac.js';
|
|
40
41
|
|
|
41
42
|
const _require = createRequire(import.meta.url);
|
|
42
43
|
const { version } = _require('../../package.json') as { version: string };
|
|
@@ -262,6 +263,7 @@ export async function startMCPServer(conductor: Conductor, options: MCPServerOpt
|
|
|
262
263
|
await initInfrastructure(conductor);
|
|
263
264
|
|
|
264
265
|
const pluginManager = new PluginManager(conductor);
|
|
266
|
+
await pluginManager.loadBuiltins();
|
|
265
267
|
const tools = await buildToolRegistry(conductor, pluginManager);
|
|
266
268
|
|
|
267
269
|
// Create circuit breakers for each tool
|
|
@@ -320,6 +322,18 @@ export async function startMCPServer(conductor: Conductor, options: MCPServerOpt
|
|
|
320
322
|
})),
|
|
321
323
|
}));
|
|
322
324
|
|
|
325
|
+
// ── Authorization context ───────────────────────────────────────
|
|
326
|
+
// In production, userId comes from auth token. For now, default to editor role.
|
|
327
|
+
const rbac = new RBACManager();
|
|
328
|
+
rbac.addUser({
|
|
329
|
+
id: 'mcp_client',
|
|
330
|
+
email: 'mcp@conductor.local',
|
|
331
|
+
role: Role.EDITOR,
|
|
332
|
+
createdAt: new Date(),
|
|
333
|
+
lastLoginAt: null,
|
|
334
|
+
});
|
|
335
|
+
const USER_ID = 'mcp_client';
|
|
336
|
+
|
|
323
337
|
// ── tools/call ───────────────────────────────────────────────────────────
|
|
324
338
|
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
325
339
|
const { name, arguments: args = {} } = request.params;
|
|
@@ -334,6 +348,16 @@ export async function startMCPServer(conductor: Conductor, options: MCPServerOpt
|
|
|
334
348
|
};
|
|
335
349
|
}
|
|
336
350
|
|
|
351
|
+
// RBAC check - verify user can execute this tool
|
|
352
|
+
if (!rbac.checkPermission(USER_ID, name, 'execute')) {
|
|
353
|
+
return {
|
|
354
|
+
content: [
|
|
355
|
+
{ type: 'text' as const, text: `Permission denied: You don't have execute access to tool "${name}".` },
|
|
356
|
+
],
|
|
357
|
+
isError: true,
|
|
358
|
+
};
|
|
359
|
+
}
|
|
360
|
+
|
|
337
361
|
const start = Date.now();
|
|
338
362
|
|
|
339
363
|
try {
|
|
@@ -347,9 +371,23 @@ export async function startMCPServer(conductor: Conductor, options: MCPServerOpt
|
|
|
347
371
|
globalHealthChecker.registerCircuitBreaker(name, breaker);
|
|
348
372
|
}
|
|
349
373
|
|
|
350
|
-
// Execute through circuit breaker + retry
|
|
374
|
+
// Execute through circuit breaker + retry with timeout
|
|
375
|
+
const TOOL_TIMEOUT_MS = 120_000; // 2 min default
|
|
351
376
|
const result = await breaker.execute(async () =>
|
|
352
|
-
withRetry(
|
|
377
|
+
withRetry(
|
|
378
|
+
async () => {
|
|
379
|
+
return Promise.race([
|
|
380
|
+
tool.handler(args),
|
|
381
|
+
new Promise((_, reject) =>
|
|
382
|
+
setTimeout(
|
|
383
|
+
() => reject(new Error(`Tool "${name}" timed out after ${TOOL_TIMEOUT_MS}ms`)),
|
|
384
|
+
TOOL_TIMEOUT_MS,
|
|
385
|
+
),
|
|
386
|
+
),
|
|
387
|
+
]);
|
|
388
|
+
},
|
|
389
|
+
{ maxAttempts: 3, baseDelay: 500, maxDelay: 10000 },
|
|
390
|
+
),
|
|
353
391
|
);
|
|
354
392
|
|
|
355
393
|
const latency = Date.now() - start;
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AWS Plugin — EC2, S3, Lambda management
|
|
3
|
+
*
|
|
4
|
+
* Tools:
|
|
5
|
+
* aws_ec2_list - List EC2 instances
|
|
6
|
+
* aws_ec2_start - Start instance
|
|
7
|
+
* aws_ec2_stop - Stop instance
|
|
8
|
+
* aws_s3_list - List S3 buckets
|
|
9
|
+
* aws_s3_put - Upload to S3
|
|
10
|
+
* aws_s3_get - Download from S3
|
|
11
|
+
* aws_lambda_list - List Lambda functions
|
|
12
|
+
* aws_lambda_invoke - Invoke Lambda
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import { Conductor } from '../../core/conductor.js';
|
|
16
|
+
import { Keychain } from '../../security/keychain.js';
|
|
17
|
+
import type { Plugin, PluginTool } from '../manager.js';
|
|
18
|
+
|
|
19
|
+
export class AWSPlugin implements Plugin {
|
|
20
|
+
name = 'aws';
|
|
21
|
+
description = 'AWS EC2, S3, and Lambda management';
|
|
22
|
+
version = '1.0.0';
|
|
23
|
+
|
|
24
|
+
private keychain?: Keychain;
|
|
25
|
+
|
|
26
|
+
async initialize(conductor: Conductor): Promise<void> {
|
|
27
|
+
this.keychain = new Keychain(conductor.getConfig().getConfigDir());
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
isConfigured(): boolean {
|
|
31
|
+
return !!this.keychain; // Check keys at runtime
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
async getAWSCredentials(): Promise<{ accessKeyId: string; secretAccessKey: string; region?: string }> {
|
|
35
|
+
const accessKeyId = await this.keychain!.get('aws', 'access_key_id');
|
|
36
|
+
const secretAccessKey = await this.keychain!.get('aws', 'secret_access_key');
|
|
37
|
+
const region = await this.keychain!.get('aws', 'region') || 'us-east-1';
|
|
38
|
+
|
|
39
|
+
if (!accessKeyId || !secretAccessKey) {
|
|
40
|
+
throw new Error('AWS credentials not configured. Run: conductor plugins setup aws');
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return { accessKeyId, secretAccessKey, region };
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
private async awsRequest(action: string, params: Record<string, string> = {}): Promise<any> {
|
|
47
|
+
const { accessKeyId, secretAccessKey, region } = await this.getAWSCredentials();
|
|
48
|
+
|
|
49
|
+
// Simplified AWS request (real implementation would use @aws-sdk)
|
|
50
|
+
const endpoint = `https://ec2.${region}.amazonaws.com`;
|
|
51
|
+
|
|
52
|
+
return { result: `AWS ${action} would execute here` };
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
getTools(): PluginTool[] {
|
|
56
|
+
return [
|
|
57
|
+
{
|
|
58
|
+
name: 'aws_ec2_list',
|
|
59
|
+
description: 'List EC2 instances',
|
|
60
|
+
inputSchema: {
|
|
61
|
+
type: 'object',
|
|
62
|
+
properties: {
|
|
63
|
+
state: { type: 'string', description: 'Filter by state (running, stopped)' },
|
|
64
|
+
},
|
|
65
|
+
},
|
|
66
|
+
handler: async (args) => {
|
|
67
|
+
return this.awsRequest('DescribeInstances', {
|
|
68
|
+
...(args.state ? { InstanceState: args.state } : {})
|
|
69
|
+
});
|
|
70
|
+
},
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
name: 'aws_ec2_start',
|
|
74
|
+
description: 'Start an EC2 instance',
|
|
75
|
+
inputSchema: {
|
|
76
|
+
type: 'object',
|
|
77
|
+
properties: {
|
|
78
|
+
instance_id: { type: 'string', description: 'Instance ID' },
|
|
79
|
+
},
|
|
80
|
+
required: ['instance_id'],
|
|
81
|
+
},
|
|
82
|
+
handler: async (args) => {
|
|
83
|
+
return this.awsRequest('StartInstances', { InstanceId: args.instance_id });
|
|
84
|
+
},
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
name: 'aws_ec2_stop',
|
|
88
|
+
description: 'Stop an EC2 instance',
|
|
89
|
+
inputSchema: {
|
|
90
|
+
type: 'object',
|
|
91
|
+
properties: {
|
|
92
|
+
instance_id: { type: 'string', description: 'Instance ID' },
|
|
93
|
+
},
|
|
94
|
+
required: ['instance_id'],
|
|
95
|
+
},
|
|
96
|
+
handler: async (args) => {
|
|
97
|
+
return this.awsRequest('StopInstances', { InstanceId: args.instance_id });
|
|
98
|
+
},
|
|
99
|
+
},
|
|
100
|
+
{
|
|
101
|
+
name: 'aws_s3_list',
|
|
102
|
+
description: 'List S3 buckets',
|
|
103
|
+
inputSchema: {
|
|
104
|
+
type: 'object',
|
|
105
|
+
properties: {},
|
|
106
|
+
},
|
|
107
|
+
handler: async () => {
|
|
108
|
+
return this.awsRequest('ListBuckets');
|
|
109
|
+
},
|
|
110
|
+
},
|
|
111
|
+
{
|
|
112
|
+
name: 'aws_s3_put',
|
|
113
|
+
description: 'Upload file to S3',
|
|
114
|
+
inputSchema: {
|
|
115
|
+
type: 'object',
|
|
116
|
+
properties: {
|
|
117
|
+
bucket: { type: 'string' },
|
|
118
|
+
key: { type: 'string' },
|
|
119
|
+
body: { type: 'string' },
|
|
120
|
+
},
|
|
121
|
+
required: ['bucket', 'key', 'body'],
|
|
122
|
+
},
|
|
123
|
+
handler: async (args) => {
|
|
124
|
+
return this.awsRequest('PutObject', {
|
|
125
|
+
Bucket: args.bucket,
|
|
126
|
+
Key: args.key,
|
|
127
|
+
Body: args.body
|
|
128
|
+
});
|
|
129
|
+
},
|
|
130
|
+
},
|
|
131
|
+
{
|
|
132
|
+
name: 'aws_lambda_list',
|
|
133
|
+
description: 'List Lambda functions',
|
|
134
|
+
inputSchema: {
|
|
135
|
+
type: 'object',
|
|
136
|
+
properties: {},
|
|
137
|
+
},
|
|
138
|
+
handler: async () => {
|
|
139
|
+
return this.awsRequest('ListFunctions');
|
|
140
|
+
},
|
|
141
|
+
},
|
|
142
|
+
{
|
|
143
|
+
name: 'aws_lambda_invoke',
|
|
144
|
+
description: 'Invoke a Lambda function',
|
|
145
|
+
inputSchema: {
|
|
146
|
+
type: 'object',
|
|
147
|
+
properties: {
|
|
148
|
+
function_name: { type: 'string' },
|
|
149
|
+
payload: { type: 'string' },
|
|
150
|
+
},
|
|
151
|
+
required: ['function_name'],
|
|
152
|
+
},
|
|
153
|
+
handler: async (args) => {
|
|
154
|
+
return this.awsRequest('Invoke', {
|
|
155
|
+
FunctionName: args.function_name,
|
|
156
|
+
Payload: args.payload || '{}'
|
|
157
|
+
});
|
|
158
|
+
},
|
|
159
|
+
},
|
|
160
|
+
];
|
|
161
|
+
}
|
|
162
|
+
}
|
|
@@ -49,13 +49,31 @@ export class DatabasePlugin implements Plugin {
|
|
|
49
49
|
};
|
|
50
50
|
|
|
51
51
|
private conductor?: Conductor;
|
|
52
|
+
private configuredUrls: Set<string> = new Set();
|
|
52
53
|
|
|
53
54
|
async initialize(conductor: Conductor): Promise<void> {
|
|
54
55
|
this.conductor = conductor;
|
|
56
|
+
// Pre-check which databases are actually configured
|
|
57
|
+
try {
|
|
58
|
+
const { Keychain } = await import('../../security/keychain.js');
|
|
59
|
+
const kc = new Keychain(conductor.getConfig().getConfigDir());
|
|
60
|
+
const keys = ['postgres_url', 'mysql_url', 'mongo_url', 'redis_url'];
|
|
61
|
+
for (const k of keys) {
|
|
62
|
+
try {
|
|
63
|
+
const val = await kc.get('database', k);
|
|
64
|
+
if (val) this.configuredUrls.add(k);
|
|
65
|
+
} catch { /* not stored */ }
|
|
66
|
+
}
|
|
67
|
+
// Also check environment variables as fallback
|
|
68
|
+
if (process.env['DATABASE_URL'] || process.env['POSTGRES_URL']) this.configuredUrls.add('postgres_url');
|
|
69
|
+
if (process.env['MYSQL_URL']) this.configuredUrls.add('mysql_url');
|
|
70
|
+
if (process.env['MONGO_URL'] || process.env['MONGODB_URL']) this.configuredUrls.add('mongo_url');
|
|
71
|
+
if (process.env['REDIS_URL']) this.configuredUrls.add('redis_url');
|
|
72
|
+
} catch { /* keychain not available */ }
|
|
55
73
|
}
|
|
56
74
|
|
|
57
75
|
isConfigured(): boolean {
|
|
58
|
-
return
|
|
76
|
+
return this.configuredUrls.size > 0;
|
|
59
77
|
}
|
|
60
78
|
|
|
61
79
|
private async getKeychain(): Promise<import('../../security/keychain.js').Keychain> {
|
|
@@ -2,6 +2,7 @@ import { Plugin, PluginTool } from '../manager.js';
|
|
|
2
2
|
import { Conductor } from '../../core/conductor.js';
|
|
3
3
|
import { execFile } from 'child_process';
|
|
4
4
|
import { promisify } from 'util';
|
|
5
|
+
import { existsSync } from 'fs';
|
|
5
6
|
|
|
6
7
|
const execFileAsync = promisify(execFile);
|
|
7
8
|
|
|
@@ -10,9 +11,24 @@ export class DockerPlugin implements Plugin {
|
|
|
10
11
|
description = 'Docker container, image, volume, and network management';
|
|
11
12
|
version = '1.0.0';
|
|
12
13
|
|
|
14
|
+
configSchema = {
|
|
15
|
+
fields: [],
|
|
16
|
+
setupInstructions: 'Install Docker Desktop (https://docker.com) and make sure the daemon is running.',
|
|
17
|
+
};
|
|
18
|
+
|
|
13
19
|
async initialize(_conductor: Conductor): Promise<void> {}
|
|
20
|
+
|
|
14
21
|
isConfigured(): boolean {
|
|
15
|
-
|
|
22
|
+
// Check for Docker socket (Unix) or named pipe (Windows)
|
|
23
|
+
const sockets = [
|
|
24
|
+
'/var/run/docker.sock',
|
|
25
|
+
'/run/docker.sock',
|
|
26
|
+
`${process.env['HOME'] ?? ''}/.docker/run/docker.sock`,
|
|
27
|
+
'\\\\.\\pipe\\docker_engine', // Windows
|
|
28
|
+
];
|
|
29
|
+
return sockets.some((s) => {
|
|
30
|
+
try { return existsSync(s); } catch { return false; }
|
|
31
|
+
});
|
|
16
32
|
}
|
|
17
33
|
|
|
18
34
|
private async docker(args: string[]): Promise<{ stdout: string; stderr: string }> {
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GCP Plugin — Google Cloud Platform management
|
|
3
|
+
*
|
|
4
|
+
* Tools:
|
|
5
|
+
* gcp_compute_list - List Compute Engine instances
|
|
6
|
+
* gcp_compute_start - Start instance
|
|
7
|
+
* gcp_compute_stop - Stop instance
|
|
8
|
+
* gcp_storage_list - List Cloud Storage buckets
|
|
9
|
+
* gcp_storage_upload - Upload to Cloud Storage
|
|
10
|
+
* gcp_functions_list - List Cloud Functions
|
|
11
|
+
* gcp_functions_deploy - Deploy Cloud Function
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { Conductor } from '../../core/conductor.js';
|
|
15
|
+
import { Keychain } from '../../security/keychain.js';
|
|
16
|
+
import type { Plugin, PluginTool } from '../manager.js';
|
|
17
|
+
|
|
18
|
+
export class GCPPlugin implements Plugin {
|
|
19
|
+
name = 'gcp';
|
|
20
|
+
description = 'Google Cloud Platform compute, storage, and functions';
|
|
21
|
+
version = '1.0.0';
|
|
22
|
+
|
|
23
|
+
private keychain?: Keychain;
|
|
24
|
+
|
|
25
|
+
async initialize(conductor: Conductor): Promise<void> {
|
|
26
|
+
this.keychain = new Keychain(conductor.getConfig().getConfigDir());
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
isConfigured(): boolean {
|
|
30
|
+
return !!this.keychain;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
async getGCPCredentials(): Promise<{ project_id: string; credentials: string }> {
|
|
34
|
+
const project_id = await this.keychain!.get('gcp', 'project_id');
|
|
35
|
+
const credentials = await this.keychain!.get('gcp', 'credentials');
|
|
36
|
+
|
|
37
|
+
if (!project_id || !credentials) {
|
|
38
|
+
throw new Error('GCP credentials not configured. Run: conductor plugins setup gcp');
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return { project_id, credentials };
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
getTools(): PluginTool[] {
|
|
45
|
+
return [
|
|
46
|
+
{
|
|
47
|
+
name: 'gcp_compute_list',
|
|
48
|
+
description: 'List GCP Compute Engine instances',
|
|
49
|
+
inputSchema: {
|
|
50
|
+
type: 'object',
|
|
51
|
+
properties: {
|
|
52
|
+
zone: { type: 'string', description: 'Zone (e.g., us-central1-a)' },
|
|
53
|
+
},
|
|
54
|
+
},
|
|
55
|
+
handler: async (args) => {
|
|
56
|
+
return { result: 'GCP compute instances would list here' };
|
|
57
|
+
},
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
name: 'gcp_compute_start',
|
|
61
|
+
description: 'Start a Compute Engine instance',
|
|
62
|
+
inputSchema: {
|
|
63
|
+
type: 'object',
|
|
64
|
+
properties: {
|
|
65
|
+
instance: { type: 'string' },
|
|
66
|
+
zone: { type: 'string' },
|
|
67
|
+
},
|
|
68
|
+
required: ['instance'],
|
|
69
|
+
},
|
|
70
|
+
handler: async (args) => {
|
|
71
|
+
return { result: `Starting ${args.instance}` };
|
|
72
|
+
},
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
name: 'gcp_compute_stop',
|
|
76
|
+
description: 'Stop a Compute Engine instance',
|
|
77
|
+
inputSchema: {
|
|
78
|
+
type: 'object',
|
|
79
|
+
properties: {
|
|
80
|
+
instance: { type: 'string' },
|
|
81
|
+
zone: { type: 'string' },
|
|
82
|
+
},
|
|
83
|
+
required: ['instance'],
|
|
84
|
+
},
|
|
85
|
+
handler: async (args) => {
|
|
86
|
+
return { result: `Stopping ${args.instance}` };
|
|
87
|
+
},
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
name: 'gcp_storage_list',
|
|
91
|
+
description: 'List Cloud Storage buckets',
|
|
92
|
+
inputSchema: {
|
|
93
|
+
type: 'object',
|
|
94
|
+
properties: {},
|
|
95
|
+
},
|
|
96
|
+
handler: async () => {
|
|
97
|
+
return { result: 'GCP storage buckets would list here' };
|
|
98
|
+
},
|
|
99
|
+
},
|
|
100
|
+
{
|
|
101
|
+
name: 'gcp_storage_upload',
|
|
102
|
+
description: 'Upload file to Cloud Storage',
|
|
103
|
+
inputSchema: {
|
|
104
|
+
type: 'object',
|
|
105
|
+
properties: {
|
|
106
|
+
bucket: { type: 'string' },
|
|
107
|
+
destination: { type: 'string' },
|
|
108
|
+
source: { type: 'string' },
|
|
109
|
+
},
|
|
110
|
+
required: ['bucket', 'destination', 'source'],
|
|
111
|
+
},
|
|
112
|
+
handler: async (args) => {
|
|
113
|
+
return { result: `Uploading to gs://${args.bucket}/${args.destination}` };
|
|
114
|
+
},
|
|
115
|
+
},
|
|
116
|
+
{
|
|
117
|
+
name: 'gcp_functions_list',
|
|
118
|
+
description: 'List Cloud Functions',
|
|
119
|
+
inputSchema: {
|
|
120
|
+
type: 'object',
|
|
121
|
+
properties: {},
|
|
122
|
+
},
|
|
123
|
+
handler: async () => {
|
|
124
|
+
return { result: 'GCP functions would list here' };
|
|
125
|
+
},
|
|
126
|
+
},
|
|
127
|
+
{
|
|
128
|
+
name: 'gcp_functions_deploy',
|
|
129
|
+
description: 'Deploy a Cloud Function',
|
|
130
|
+
inputSchema: {
|
|
131
|
+
type: 'object',
|
|
132
|
+
properties: {
|
|
133
|
+
name: { type: 'string' },
|
|
134
|
+
runtime: { type: 'string' },
|
|
135
|
+
entry_point: { type: 'string' },
|
|
136
|
+
},
|
|
137
|
+
required: ['name', 'runtime'],
|
|
138
|
+
},
|
|
139
|
+
handler: async (args) => {
|
|
140
|
+
return { result: `Deploying ${args.name}` };
|
|
141
|
+
},
|
|
142
|
+
},
|
|
143
|
+
];
|
|
144
|
+
}
|
|
145
|
+
}
|
|
@@ -34,6 +34,8 @@ import { N8nPlugin } from './n8n.js';
|
|
|
34
34
|
import { DockerPlugin } from './docker.js';
|
|
35
35
|
import { DatabasePlugin } from './database.js';
|
|
36
36
|
import { ShellPlugin } from './shell.js';
|
|
37
|
+
import { AWSPlugin } from './aws.js';
|
|
38
|
+
import { GCPPlugin } from './gcp.js';
|
|
37
39
|
|
|
38
40
|
// ── Third-party services ───────────────────────────────────────────────────
|
|
39
41
|
import { NotionPlugin } from './notion.js';
|
|
@@ -80,6 +82,8 @@ export function getAllBuiltinPlugins(): Plugin[] {
|
|
|
80
82
|
new ShellPlugin(),
|
|
81
83
|
new DockerPlugin(),
|
|
82
84
|
new DatabasePlugin(),
|
|
85
|
+
new AWSPlugin(),
|
|
86
|
+
new GCPPlugin(),
|
|
83
87
|
|
|
84
88
|
// ── Google (require Google OAuth) ──────────────────────────────────────
|
|
85
89
|
new GmailPlugin(),
|