diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5e47a02..7d9c4cf 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -6,24 +6,30 @@ on: pull_request: branches: [main] +permissions: + contents: read + +concurrency: + group: ci-${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + jobs: test: runs-on: ubuntu-latest strategy: matrix: - # Node 18 reached EOL April 2025. ESLint 10 and Vitest/rolldown require - # util.styleText which was added in Node 20.12. Min supported: Node 20. - node-version: [20, 22] + node-version: [22, 24] steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Setup Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v4 + uses: actions/setup-node@v6 with: node-version: ${{ matrix.node-version }} - cache: 'npm' + cache: npm + cache-dependency-path: package-lock.json - name: Install dependencies run: npm ci @@ -44,8 +50,8 @@ jobs: run: npm test - name: Upload coverage - uses: codecov/codecov-action@v4 - if: matrix.node-version == 22 + uses: codecov/codecov-action@v6 + if: matrix.node-version == 24 with: files: ./coverage/lcov.info fail_ci_if_error: false diff --git a/src/cli/commands/cloud.ts b/src/cli/commands/cloud.ts index 0a2f861..8f2dfd5 100644 --- a/src/cli/commands/cloud.ts +++ b/src/cli/commands/cloud.ts @@ -1,6 +1,6 @@ /** * conductor cloud - Cloud sync and account management - * + * * Commands: * cloud login - Log in to Conductor Cloud and pair device * cloud sync - Sync credentials from cloud @@ -51,13 +51,13 @@ async function apiRequest( method?: string; body?: unknown; sessionId?: string; - } = {} + } = {}, ): Promise> { const url = `${serverUrl}${endpoint}`; const headers: Record = { 'Content-Type': 'application/json', }; - + if (options.sessionId) { headers['Authorization'] = `Bearer ${options.sessionId}`; } @@ -68,17 +68,15 @@ async function apiRequest( headers, body: options.body ? JSON.stringify(options.body) : undefined, }); - - return await response.json() as Record; + + return (await response.json()) as Record; } catch (error) { return { success: false, error: String(error) }; } } export function registerCloudCommands(program: Command) { - const cloud = program - .command('cloud') - .description('Conductor Cloud - sync credentials across devices'); + const cloud = program.command('cloud').description('Conductor Cloud - sync credentials across devices'); // cloud login cloud @@ -101,7 +99,7 @@ export function registerCloudCommands(program: Command) { // Generate device credentials const deviceId = crypto.randomUUID(); const deviceName = process.env.HOSTNAME || 'My Computer'; - + // Create pairing request console.log(chalk.gray(' Creating device pairing request...')); const pairResult = await apiRequest(serverUrl, '/device/pair', { @@ -111,10 +109,10 @@ export function registerCloudCommands(program: Command) { if (!pairResult.success) { console.log(chalk.red(` ✗ Failed to create pairing request: ${pairResult.error}`)); - + // For demo mode, simulate success console.log(chalk.yellow(' ⚠ Running in demo mode (simulated)')); - + const config: CloudConfig = { connected: true, deviceId, @@ -124,11 +122,13 @@ export function registerCloudCommands(program: Command) { lastSync: Date.now(), sessionId: `demo_session_${Date.now()}`, }; - + await saveCloudConfig(config); - + console.log(chalk.green(' ✓ Connected to Conductor Cloud!')); - console.log(chalk.gray(' Run ') + chalk.white('conductor cloud sync') + chalk.gray(' to download credentials.\n')); + console.log( + chalk.gray(' Run ') + chalk.white('conductor cloud sync') + chalk.gray(' to download credentials.\n'), + ); return; } @@ -151,21 +151,21 @@ export function registerCloudCommands(program: Command) { // Poll for approval let attempts = 0; const maxAttempts = 60; - + while (attempts < maxAttempts) { - await new Promise(r => setTimeout(r, 2000)); - + await new Promise((r) => setTimeout(r, 2000)); + const verifyResult = await apiRequest(serverUrl, `/device/pairing?requestId=${requestId}`, { sessionId: requestId, }); - + if (verifyResult.success && (verifyResult.requests as unknown[])?.length === 0) { // Device approved! break; } - + attempts++; - + if (attempts % 10 === 0) { console.log(chalk.gray(` Waiting for approval... (${attempts * 2}s)`)); } @@ -185,11 +185,13 @@ export function registerCloudCommands(program: Command) { lastSync: Date.now(), sessionId: requestId, }; - + await saveCloudConfig(config); console.log(chalk.green(' ✓ Device paired successfully!')); - console.log(chalk.gray(' Run ') + chalk.white('conductor cloud sync') + chalk.gray(' to download credentials.\n')); + console.log( + chalk.gray(' Run ') + chalk.white('conductor cloud sync') + chalk.gray(' to download credentials.\n'), + ); }); // cloud sync @@ -197,7 +199,7 @@ export function registerCloudCommands(program: Command) { .command('sync') .description('Sync credentials from Conductor Cloud') .option('--force', 'Force full sync') - .action(async (opts: { force?: boolean }) => { + .action(async (_opts: { force?: boolean }) => { const config = await loadCloudConfig(); if (!config.connected || !config.serverUrl) { @@ -283,7 +285,7 @@ export function registerCloudCommands(program: Command) { console.log(chalk.gray(' Server: ') + chalk.white(config.serverUrl || 'cloud.conductor.sh')); console.log(chalk.gray(' Email: ') + chalk.white(config.email || 'N/A')); console.log(chalk.gray(' Device: ') + chalk.white(config.deviceId?.substring(0, 8) || 'N/A')); - + if (config.lastSync) { console.log(chalk.gray(' Last sync: ') + chalk.white(new Date(config.lastSync).toLocaleString())); } @@ -312,7 +314,7 @@ export function registerCloudCommands(program: Command) { console.log(chalk.gray(' ' + '─'.repeat(50))); const devices = (result.devices as unknown[]) || []; - + if (devices.length === 0) { console.log(chalk.gray(' Only this device connected.\n')); return; @@ -348,4 +350,4 @@ export function registerCloudCommands(program: Command) { console.log(chalk.gray(' Server: ') + chalk.white(config.serverUrl || 'N/A')); console.log(''); }); -} \ No newline at end of file +} diff --git a/src/cli/commands/doctor.ts b/src/cli/commands/doctor.ts index 105c976..536fc38 100644 --- a/src/cli/commands/doctor.ts +++ b/src/cli/commands/doctor.ts @@ -119,7 +119,6 @@ export async function doctor(conductor: Conductor): Promise { // Disk space const homeDir = os.homedir(); try { - const si = await import('systeminformation'); const fsInfo = await si.default.fsSize(); const homeFs = fsInfo.find((f: any) => homeDir.startsWith(f.mount)); diff --git a/src/cli/commands/init.ts b/src/cli/commands/init.ts index 1afc50c..a249ca1 100644 --- a/src/cli/commands/init.ts +++ b/src/cli/commands/init.ts @@ -37,9 +37,9 @@ function stepHeader(n: number, total: number, label: string): void { function printBanner(): void { const W = 50; - const top = ' ┌' + '─'.repeat(W) + '┐'; - const bot = ' └' + '─'.repeat(W) + '┘'; - const blank = ' │' + ' '.repeat(W) + '│'; + const top = ' ┌' + '─'.repeat(W) + '┐'; + const bot = ' └' + '─'.repeat(W) + '┘'; + const blank = ' │' + ' '.repeat(W) + '│'; const line = (text: string) => { const pad = W - text.length - 1; return ` │ ${B}${text}${R}` + ' '.repeat(Math.max(0, pad)) + '│'; @@ -159,8 +159,13 @@ async function setupAIProvider(conductor: Conductor): Promise { console.log(''); console.log(` ${D}Get your key at: https://console.anthropic.com${R}`); const { apiKey } = await inquirer.prompt<{ apiKey: string }>([ - { type: 'password', name: 'apiKey', message: 'Anthropic API key:', mask: '*', - validate: (v: string) => v.trim().length > 0 || 'API key is required' }, + { + type: 'password', + name: 'apiKey', + message: 'Anthropic API key:', + mask: '*', + validate: (v: string) => v.trim().length > 0 || 'API key is required', + }, ]); await aiManager.setupClaude(apiKey.trim()); console.log(` ${G}✓${R} Claude configured`); @@ -170,8 +175,13 @@ async function setupAIProvider(conductor: Conductor): Promise { console.log(''); console.log(` ${D}Get your key at: https://platform.openai.com/api-keys${R}`); const { apiKey } = await inquirer.prompt<{ apiKey: string }>([ - { type: 'password', name: 'apiKey', message: 'OpenAI API key:', mask: '*', - validate: (v: string) => v.trim().length > 0 || 'API key is required' }, + { + type: 'password', + name: 'apiKey', + message: 'OpenAI API key:', + mask: '*', + validate: (v: string) => v.trim().length > 0 || 'API key is required', + }, ]); await aiManager.setupOpenAI(apiKey.trim()); console.log(` ${G}✓${R} OpenAI configured`); @@ -181,8 +191,13 @@ async function setupAIProvider(conductor: Conductor): Promise { console.log(''); console.log(` ${D}Get your key at: https://aistudio.google.com/app/apikey${R}`); const { apiKey } = await inquirer.prompt<{ apiKey: string }>([ - { type: 'password', name: 'apiKey', message: 'Gemini API key:', mask: '*', - validate: (v: string) => v.trim().length > 0 || 'API key is required' }, + { + type: 'password', + name: 'apiKey', + message: 'Gemini API key:', + mask: '*', + validate: (v: string) => v.trim().length > 0 || 'API key is required', + }, ]); await aiManager.setupGemini(apiKey.trim()); console.log(` ${G}✓${R} Gemini configured`); @@ -357,4 +372,4 @@ export async function init(conductor: Conductor): Promise { await setupMCPClient(conductor); printFinalInstructions(); } -} \ No newline at end of file +} diff --git a/src/cloud/index.ts b/src/cloud/index.ts index 1d7aa58..543fa17 100644 --- a/src/cloud/index.ts +++ b/src/cloud/index.ts @@ -1,6 +1,6 @@ /** * Conductor Cloud - Zero-Knowledge Backend - * + * * Architecture: * - User auth via GitHub/Google OAuth * - Credentials encrypted client-side (AES-256-GCM) @@ -48,6 +48,7 @@ export interface DevicePairingRequest { deviceId: string; deviceName: string; publicKey: string; + userId?: string; expiresAt: Date; } @@ -73,9 +74,9 @@ export async function createUser(params: { providerId: string; }): Promise { const existingUser = Array.from(users.values()).find( - u => u.provider === params.provider && u.providerId === params.providerId + (u) => u.provider === params.provider && u.providerId === params.providerId, ); - + if (existingUser) { return existingUser; } @@ -98,9 +99,7 @@ export function getUserById(id: string): User | undefined { } export function getUserByProvider(provider: 'github' | 'google', providerId: string): User | undefined { - return Array.from(users.values()).find( - u => u.provider === provider && u.providerId === providerId - ); + return Array.from(users.values()).find((u) => u.provider === provider && u.providerId === providerId); } // ───────────────────────────────────────────────────────────── @@ -111,6 +110,7 @@ export async function createPairingRequest(params: { deviceId: string; deviceName: string; publicKey: string; + userId?: string; }): Promise<{ code: string; requestId: string }> { const code = Math.random().toString(36).substring(2, 8).toUpperCase(); const requestId = randomUUID(); @@ -120,6 +120,7 @@ export async function createPairingRequest(params: { deviceId: params.deviceId, deviceName: params.deviceName, publicKey: params.publicKey, + userId: params.userId, expiresAt: new Date(Date.now() + 5 * 60 * 1000), // 5 minutes }; @@ -129,7 +130,7 @@ export async function createPairingRequest(params: { export async function approvePairing(requestId: string, userId: string): Promise { const request = pairingRequests.get(requestId); - + if (!request) { throw new Error('Pairing request not found'); } @@ -155,7 +156,7 @@ export async function approvePairing(requestId: string, userId: string): Promise } export function getPendingPairingRequests(userId: string): DevicePairingRequest[] { - return Array.from(pairingRequests.values()).filter(r => r.expiresAt > new Date()); + return Array.from(pairingRequests.values()).filter((r) => r.expiresAt > new Date() && r.userId === userId); } export function getDevice(id: string): Device | undefined { @@ -163,7 +164,7 @@ export function getDevice(id: string): Device | undefined { } export function getUserDevices(userId: string): Device[] { - return Array.from(devices.values()).filter(d => d.userId === userId && d.approved); + return Array.from(devices.values()).filter((d) => d.userId === userId && d.approved); } export function revokeDevice(deviceId: string): void { @@ -184,7 +185,7 @@ export async function storeCredential(params: { }): Promise { // Find existing or create new const existing = Array.from(credentials.values()).find( - c => c.userId === params.userId && c.plugin === params.plugin + (c) => c.userId === params.userId && c.plugin === params.plugin, ); const credential: EncryptedCredential = { @@ -204,7 +205,7 @@ export async function storeCredential(params: { } export function getUserCredentials(userId: string): EncryptedCredential[] { - return Array.from(credentials.values()).filter(c => c.userId === userId); + return Array.from(credentials.values()).filter((c) => c.userId === userId); } export function getCredentialsForDevice(deviceId: string): EncryptedCredential[] { @@ -214,9 +215,7 @@ export function getCredentialsForDevice(deviceId: string): EncryptedCredential[] } export async function deleteCredential(userId: string, plugin: string): Promise { - const credential = Array.from(credentials.values()).find( - c => c.userId === userId && c.plugin === plugin - ); + const credential = Array.from(credentials.values()).find((c) => c.userId === userId && c.plugin === plugin); if (credential) { credentials.delete(credential.id); } @@ -258,8 +257,7 @@ export function getCredentialsForSync(deviceId: string, since: number): Encrypte const device = devices.get(deviceId); if (!device || !device.approved) return []; - return Array.from(credentials.values()) - .filter(c => c.userId === device.userId && c.updatedAt.getTime() > since); + return Array.from(credentials.values()).filter((c) => c.userId === device.userId && c.updatedAt.getTime() > since); } // ───────────────────────────────────────────────────────────── @@ -279,11 +277,11 @@ export function createCloudApp(): express.Express { // Auth callback (called by OAuth provider) app.post('/auth/callback', async (req, res) => { try { - const { provider, providerId, email, accessToken } = req.body; - + const { provider, providerId, email, accessToken: _accessToken } = req.body; + // In production, verify the access token with the provider // For now, trust the client (should verify in production!) - + const user = await createUser({ email, provider: provider as 'github' | 'google', @@ -291,7 +289,7 @@ export function createCloudApp(): express.Express { }); const sessionId = createSession(user.id); - + res.json({ success: true, sessionId, @@ -306,29 +304,36 @@ export function createCloudApp(): express.Express { // Create device pairing request app.post('/device/pair', async (req, res) => { try { - const { deviceId, deviceName, publicKey } = req.body; - const result = await createPairingRequest({ deviceId, deviceName, publicKey }); + const { deviceId, deviceName, publicKey, userId } = req.body; + const result = await createPairingRequest({ deviceId, deviceName, publicKey, userId }); res.json({ success: true, ...result }); } catch (error) { res.status(500).json({ success: false, error: String(error) }); } }); - // Get pending pairing requests for user + // Get pending pairing requests for user (or check device request status) app.get('/device/pairing', async (req, res) => { try { - const sessionId = req.headers.authorization?.replace('Bearer ', ''); - if (!sessionId) { + const auth = req.headers.authorization?.replace('Bearer ', ''); + if (!auth) { return res.status(401).json({ success: false, error: 'Unauthorized' }); } - const session = validateSession(sessionId); - if (!session) { - return res.status(401).json({ success: false, error: 'Invalid session' }); + // Check if this is a session or a requestId + const session = validateSession(auth); + if (session) { + const requests = getPendingPairingRequests(session.userId); + return res.json({ success: true, requests }); } - const requests = getPendingPairingRequests(session.userId); - res.json({ success: true, requests }); + // Fallback: check if it's a requestId for device polling + const request = pairingRequests.get(auth); + if (request) { + return res.json({ success: true, requests: [request] }); + } + + return res.status(401).json({ success: false, error: 'Invalid session' }); } catch (error) { res.status(500).json({ success: false, error: String(error) }); } @@ -452,15 +457,15 @@ export function createCloudApp(): express.Express { } const userCredentials = getUserCredentials(session.userId); - res.json({ - success: true, - credentials: userCredentials.map(c => ({ + res.json({ + success: true, + credentials: userCredentials.map((c) => ({ id: c.id, plugin: c.plugin, createdAt: c.createdAt, updatedAt: c.updatedAt, // Don't send encrypted data - client will fetch on demand - })) + })), }); } catch (error) { res.status(500).json({ success: false, error: String(error) }); @@ -489,20 +494,20 @@ export function createCloudApp(): express.Express { } const userCredentials = getUserCredentials(session.userId); - const credential = userCredentials.find(c => c.plugin === req.params.plugin); - + const credential = userCredentials.find((c) => c.plugin === req.params.plugin); + if (!credential) { return res.status(404).json({ success: false, error: 'Credential not found' }); } - res.json({ - success: true, + res.json({ + success: true, credential: { plugin: credential.plugin, encryptedData: credential.encryptedData, iv: credential.iv, authTag: credential.authTag, - } + }, }); } catch (error) { res.status(500).json({ success: false, error: String(error) }); @@ -549,16 +554,16 @@ export function createCloudApp(): express.Express { const since = parseInt(req.query.since as string) || 0; const creds = getCredentialsForSync(session.deviceId, since); - - res.json({ - success: true, - credentials: creds.map(c => ({ + + res.json({ + success: true, + credentials: creds.map((c) => ({ plugin: c.plugin, encryptedData: c.encryptedData, iv: c.iv, authTag: c.authTag, updatedAt: c.updatedAt, - })) + })), }); } catch (error) { res.status(500).json({ success: false, error: String(error) }); @@ -598,7 +603,7 @@ export class CloudManager { async login(): Promise { // Generate device credentials const deviceId = randomUUID(); - const { publicKey, privateKey } = crypto.generateKeyPairSync('rsa', { + const { publicKey, privateKey: _privateKey } = crypto.generateKeyPairSync('rsa', { modulusLength: 2048, publicKeyEncoding: { type: 'spki', format: 'pem' }, privateKeyEncoding: { type: 'pkcs8', format: 'pem' }, @@ -619,8 +624,8 @@ export class CloudManager { throw new Error('Failed to create pairing request'); } - const data = await response.json() as { code: string; requestId: string }; - const { code, requestId } = data; + const data = (await response.json()) as { code: string; requestId: string }; + const { code, requestId } = data; console.log('\n ╔═══════════════════════════════════════════╗'); console.log(' ║ CONDUCTOR CLOUD PAIRING ║'); @@ -633,13 +638,13 @@ export class CloudManager { // Poll for approval let attempts = 0; while (attempts < 60) { - await new Promise(r => setTimeout(r, 2000)); - + await new Promise((r) => setTimeout(r, 2000)); + const verifyRes = await fetch(`${this.baseUrl}/device/pairing`, { - headers: { 'Authorization': `Bearer ${requestId}` }, + headers: { Authorization: `Bearer ${requestId}` }, }); - - const verifyData = await verifyRes.json() as { requests?: unknown[] }; + + const verifyData = (await verifyRes.json()) as { requests?: unknown[] }; if (verifyData.requests?.length === 0) { // Approved! break; @@ -667,4 +672,4 @@ export class CloudManager { } } -export default { createCloudApp, CloudManager }; \ No newline at end of file +export default { createCloudApp, CloudManager }; diff --git a/src/core/conductor.ts b/src/core/conductor.ts index 7a1cc01..4f44e51 100644 --- a/src/core/conductor.ts +++ b/src/core/conductor.ts @@ -42,7 +42,7 @@ export class Conductor { await this.config.initialize(); await this.db.initialize(); await this.plugins.loadBuiltins(); - + // Reset audit log on fresh init (avoid tampered chain issues) const { default: fs } = await import('fs/promises'); const auditLog = path.join(this.config.getConfigDir(), 'audit', 'audit.log'); diff --git a/src/core/config.ts b/src/core/config.ts index de57daa..150951c 100644 --- a/src/core/config.ts +++ b/src/core/config.ts @@ -127,7 +127,7 @@ export class ConfigManager { try { const data = await fs.readFile(this.configPath, 'utf-8'); const parsed = JSON.parse(data); - this.config = await this.decryptSensitive(parsed) as ConductorConfig; + this.config = (await this.decryptSensitive(parsed)) as ConductorConfig; } catch { // Config doesn't exist yet — use defaults this.config = { diff --git a/src/core/encryption.ts b/src/core/encryption.ts index d095e63..c2bdc93 100644 --- a/src/core/encryption.ts +++ b/src/core/encryption.ts @@ -54,10 +54,7 @@ export class EncryptionManager { const iv = crypto.randomBytes(IV_LENGTH); const cipher = crypto.createCipheriv(ALGORITHM, key, iv); - const encrypted = Buffer.concat([ - cipher.update(plaintext, 'utf8'), - cipher.final(), - ]); + const encrypted = Buffer.concat([cipher.update(plaintext, 'utf8'), cipher.final()]); const authTag = cipher.getAuthTag(); // Format: iv + authTag + ciphertext (all base64) @@ -79,10 +76,7 @@ export class EncryptionManager { const decipher = crypto.createDecipheriv(ALGORITHM, key, iv); decipher.setAuthTag(authTag); - const decrypted = Buffer.concat([ - decipher.update(encrypted), - decipher.final(), - ]); + const decrypted = Buffer.concat([decipher.update(encrypted), decipher.final()]); return decrypted.toString('utf8'); } @@ -106,4 +100,4 @@ export class EncryptionManager { export function looksEncrypted(value: string): boolean { // Base64 strings that look like encryption output return /^[A-Za-z0-9+/]{40,}={0,2}$/.test(value); -} \ No newline at end of file +} diff --git a/src/core/interfaces.ts b/src/core/interfaces.ts index 8ac87e9..5d7a4e5 100644 --- a/src/core/interfaces.ts +++ b/src/core/interfaces.ts @@ -30,7 +30,7 @@ export interface IDatabase { /** Full-text search across a user's messages */ searchMessages(userId: string, query: string, limit?: number): Promise; /** Record an activity log entry */ - + logActivity(userId: string, action: string, plugin?: string, details?: string, success?: boolean): Promise; /** Recent activity entries for the dashboard / proactive context */ // eslint-disable-next-line @typescript-eslint/no-explicit-any diff --git a/src/dashboard/server.ts b/src/dashboard/server.ts index 3d93c74..2c93081 100644 --- a/src/dashboard/server.ts +++ b/src/dashboard/server.ts @@ -1564,7 +1564,7 @@ export async function startDashboard(port = 4242, conductorInstance?: Conductor) const { HealthChecker } = await import('../core/health.js'); const checker = new HealthChecker(); const report = await checker.detailed(); - + const lines = [ '# HELP conductor_build_info Conductor build information', '# TYPE conductor_build_info gauge', @@ -1574,7 +1574,7 @@ export async function startDashboard(port = 4242, conductorInstance?: Conductor) '# TYPE conductor_health gauge', `conductor_health ${report.status === 'ok' ? 1 : 0}`, ]; - + res.type('text/plain').send(lines.join('\n')); }); diff --git a/src/plugins/builtin/aws.ts b/src/plugins/builtin/aws.ts index b154993..3455871 100644 --- a/src/plugins/builtin/aws.ts +++ b/src/plugins/builtin/aws.ts @@ -1,6 +1,6 @@ /** * AWS Plugin — EC2, S3, Lambda management - * + * * Tools: * aws_ec2_list - List EC2 instances * aws_ec2_start - Start instance @@ -20,7 +20,7 @@ export class AWSPlugin implements Plugin { name = 'aws'; description = 'AWS EC2, S3, and Lambda management'; version = '1.0.0'; - + private keychain?: Keychain; async initialize(conductor: Conductor): Promise { @@ -34,12 +34,12 @@ export class AWSPlugin implements Plugin { async getAWSCredentials(): Promise<{ _accessKeyId: string; _secretAccessKey: string; region?: string }> { const _accessKeyId = await this.keychain!.get('aws', 'access_key_id'); const _secretAccessKey = await this.keychain!.get('aws', 'secret_access_key'); - const region = await this.keychain!.get('aws', 'region') || 'us-east-1'; - + const region = (await this.keychain!.get('aws', 'region')) || 'us-east-1'; + if (!_accessKeyId || !_secretAccessKey) { throw new Error('AWS credentials not configured. Run: conductor plugins setup aws'); } - + return { _accessKeyId, _secretAccessKey, region }; } @@ -60,8 +60,8 @@ export class AWSPlugin implements Plugin { }, }, handler: async (args) => { - return this.awsRequest('DescribeInstances', { - ...(args.state ? { InstanceState: args.state } : {}) + return this.awsRequest('DescribeInstances', { + ...(args.state ? { InstanceState: args.state } : {}), }); }, }, @@ -117,10 +117,10 @@ export class AWSPlugin implements Plugin { required: ['bucket', 'key', 'body'], }, handler: async (args) => { - return this.awsRequest('PutObject', { - Bucket: args.bucket, - Key: args.key, - Body: args.body + return this.awsRequest('PutObject', { + Bucket: args.bucket, + Key: args.key, + Body: args.body, }); }, }, @@ -147,12 +147,12 @@ export class AWSPlugin implements Plugin { required: ['function_name'], }, handler: async (args) => { - return this.awsRequest('Invoke', { - FunctionName: args.function_name, - Payload: args.payload || '{}' + return this.awsRequest('Invoke', { + FunctionName: args.function_name, + Payload: args.payload || '{}', }); }, }, ]; } -} \ No newline at end of file +} diff --git a/src/plugins/builtin/database.ts b/src/plugins/builtin/database.ts index 5184a8d..8163f45 100644 --- a/src/plugins/builtin/database.ts +++ b/src/plugins/builtin/database.ts @@ -62,14 +62,18 @@ export class DatabasePlugin implements Plugin { try { const val = await kc.get('database', k); if (val) this.configuredUrls.add(k); - } catch { /* not stored */ } + } catch { + /* not stored */ + } } // Also check environment variables as fallback if (process.env['DATABASE_URL'] || process.env['POSTGRES_URL']) this.configuredUrls.add('postgres_url'); if (process.env['MYSQL_URL']) this.configuredUrls.add('mysql_url'); if (process.env['MONGO_URL'] || process.env['MONGODB_URL']) this.configuredUrls.add('mongo_url'); if (process.env['REDIS_URL']) this.configuredUrls.add('redis_url'); - } catch { /* keychain not available */ } + } catch { + /* keychain not available */ + } } isConfigured(): boolean { diff --git a/src/plugins/builtin/docker.ts b/src/plugins/builtin/docker.ts index 48f3585..d4eb8c3 100644 --- a/src/plugins/builtin/docker.ts +++ b/src/plugins/builtin/docker.ts @@ -27,7 +27,11 @@ export class DockerPlugin implements Plugin { '\\\\.\\pipe\\docker_engine', // Windows ]; return sockets.some((s) => { - try { return existsSync(s); } catch { return false; } + try { + return existsSync(s); + } catch { + return false; + } }); } diff --git a/src/plugins/builtin/gcp.ts b/src/plugins/builtin/gcp.ts index db21063..4663d84 100644 --- a/src/plugins/builtin/gcp.ts +++ b/src/plugins/builtin/gcp.ts @@ -1,6 +1,6 @@ /** * GCP Plugin — Google Cloud Platform management - * + * * Tools: * gcp_compute_list - List Compute Engine instances * gcp_compute_start - Start instance @@ -19,7 +19,7 @@ export class GCPPlugin implements Plugin { name = 'gcp'; description = 'Google Cloud Platform compute, storage, and functions'; version = '1.0.0'; - + private keychain?: Keychain; async initialize(conductor: Conductor): Promise { @@ -33,11 +33,11 @@ export class GCPPlugin implements Plugin { async getGCPCredentials(): Promise<{ project_id: string; credentials: string }> { const project_id = await this.keychain!.get('gcp', 'project_id'); const credentials = await this.keychain!.get('gcp', 'credentials'); - + if (!project_id || !credentials) { throw new Error('GCP credentials not configured. Run: conductor plugins setup gcp'); } - + return { project_id, credentials }; } @@ -142,4 +142,4 @@ export class GCPPlugin implements Plugin { }, ]; } -} \ No newline at end of file +} diff --git a/src/plugins/builtin/slack.ts b/src/plugins/builtin/slack.ts index b2c6ceb..227f923 100644 --- a/src/plugins/builtin/slack.ts +++ b/src/plugins/builtin/slack.ts @@ -33,7 +33,9 @@ export class SlackPlugin implements Plugin { try { const t = await this.keychain.get('slack', 'bot_token'); this.hasToken = !!t; - } catch { this.hasToken = false; } + } catch { + this.hasToken = false; + } } isConfigured(): boolean { diff --git a/src/security/sso.ts b/src/security/sso.ts index 310a61c..21ae499 100644 --- a/src/security/sso.ts +++ b/src/security/sso.ts @@ -1,10 +1,10 @@ /** * Enterprise SSO/OIDC Auth Middleware - * + * * Supports: * - OIDC (Okta, Auth0, Google Workspace, Azure AD) * - Custom JWT - * + * * Usage: * conductor config set security.auth.provider oidc * conductor config set security.auth.clientId @@ -80,14 +80,12 @@ export class AuthMiddleware { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded', - 'Authorization': `Basic ${Buffer.from( - `${this.config.clientId}:${this.config.clientSecret}` - ).toString('base64')}`, + Authorization: `Basic ${Buffer.from(`${this.config.clientId}:${this.config.clientSecret}`).toString('base64')}`, }, body: `token=${token}`, }); - const data = await response.json() as any; + const data = (await response.json()) as any; if (!data.active) throw new Error('Token inactive'); return { @@ -105,9 +103,7 @@ export function createAuthMiddleware(config: any) { return async (req: any, res: any, next: any) => { if (!auth.isEnabled()) return next(); - const token = - req.headers.authorization?.replace('Bearer ', '') || - req.headers['x-forwarded-auth'] || ''; + const token = req.headers.authorization?.replace('Bearer ', '') || req.headers['x-forwarded-auth'] || ''; if (!token) { return res.status(401).json({ error: 'Authentication required' }); @@ -121,4 +117,4 @@ export function createAuthMiddleware(config: any) { req.user = user; next(); }; -} \ No newline at end of file +}