#!/usr/bin/env node import fs from 'node:fs'; import path from 'node:path'; import mysql from 'mysql2/promise'; function loadEnvFile(filePath) { if (!fs.existsSync(filePath)) return; for (const line of fs.readFileSync(filePath, 'utf8').split('\n')) { const trimmed = line.trim(); if (!trimmed || trimmed.startsWith('#')) continue; const idx = trimmed.indexOf('='); if (idx < 0) continue; const key = trimmed.slice(0, idx).trim(); const value = trimmed.slice(idx + 1).trim(); if (!process.env[key]) process.env[key] = value; } } function parseMysqlConfig() { if (process.env.DATABASE_URL) { const url = new URL(process.env.DATABASE_URL); if (url.protocol !== 'mysql:') { throw new Error(`Unsupported DATABASE_URL scheme for tool runtime check: ${url.protocol}`); } return { host: url.hostname, port: Number(url.port || 3306), user: decodeURIComponent(url.username), password: decodeURIComponent(url.password), database: url.pathname.replace(/^\/+/, ''), charset: 'utf8mb4', }; } return { host: process.env.MYSQL_HOST, port: Number(process.env.MYSQL_PORT || 3306), user: process.env.MYSQL_USER, password: process.env.MYSQL_PASSWORD, database: process.env.MYSQL_DATABASE, charset: 'utf8mb4', }; } loadEnvFile(process.env.MEMIND_ENV_FILE || path.join(process.cwd(), '.env')); const conn = await mysql.createConnection(parseMysqlConfig()); try { const [roleRows] = await conn.query( `SELECT capability_key, allowed FROM h5_capability_grants WHERE subject_type = 'role' AND subject_id = 'user' AND capability_key IN ('aider', 'openhands') ORDER BY capability_key`, ); const roleDefaults = Object.fromEntries( roleRows.map((row) => [row.capability_key, Boolean(row.allowed)]), ); const [userRows] = await conn.query( `SELECT subject_id, capability_key, allowed FROM h5_capability_grants WHERE subject_type = 'user' AND capability_key IN ('aider', 'openhands') AND allowed = 1 ORDER BY subject_id, capability_key`, ); const grantsByUser = new Map(); for (const row of userRows) { if (!grantsByUser.has(row.subject_id)) grantsByUser.set(row.subject_id, []); grantsByUser.get(row.subject_id).push(row.capability_key); } const sampledUsers = [...grantsByUser.entries()].slice(0, 10).map(([userId, grantedTools]) => ({ userId, grantedTools: grantedTools.sort(), chatHasCodeTools: false, codeHasCodeTools: grantedTools.includes('aider') || grantedTools.includes('openhands'), })); const ok = Boolean( roleDefaults.aider === false && roleDefaults.openhands === false && sampledUsers.every((user) => user.chatHasCodeTools === false) && sampledUsers.every((user) => user.codeHasCodeTools === true), ); console.log(JSON.stringify({ ok, checkedAt: new Date().toISOString(), roleDefaults, whitelistUserCount: grantsByUser.size, sampledUsers, runtimePolicy: { defaultMode: 'chat', codeToolMode: 'code', chatInjectsCodeTools: false, aiderTimeoutMs: Number(process.env.MEMIND_AIDER_TIMEOUT_MS ?? 600_000), openhandsTimeoutMs: Number(process.env.MEMIND_OPENHANDS_TIMEOUT_MS ?? 900_000), toolGateway: { enabled: ['1', 'true', 'yes', 'on'].includes( String(process.env.MEMIND_TOOL_GATEWAY_ENABLED ?? '').trim().toLowerCase(), ), dryRun: ['1', 'true', 'yes', 'on'].includes( String(process.env.MEMIND_TOOL_GATEWAY_DRY_RUN ?? '').trim().toLowerCase(), ), protocol: 'agent-run-v1', defaultExecutor: process.env.MEMIND_TOOL_GATEWAY_DEFAULT_EXECUTOR ?? 'aider', openhandsTaskTypes: process.env.MEMIND_TOOL_GATEWAY_OPENHANDS_TASK_TYPES ?? 'repo_refactor,multi_file,complex_repo', }, }, }, null, 2)); process.exit(ok ? 0 : 1); } finally { await conn.end(); }