106 lines
3.3 KiB
JavaScript
Executable File
106 lines
3.3 KiB
JavaScript
Executable File
#!/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),
|
|
},
|
|
}, null, 2));
|
|
process.exit(ok ? 0 : 1);
|
|
} finally {
|
|
await conn.end();
|
|
}
|