23f7624596
ADR-166 MCP Bridge Security Lock / Static-source security lock (push) Failing after 0s
ADR-166 MCP Bridge Security Lock / Compose default binds loopback + Mongo has auth (push) Failing after 2s
CodeQL Advanced / Analyze (rust) (push) Failing after 0s
ADR-166 MCP Bridge Security Lock / plugin-agent-federation bindHost default (push) Failing after 1s
ADR-166 MCP Bridge Security Lock / Runtime behavior — 401 + terminal gate + fail-closed (push) Failing after 4s
business-pods-smoke / smoke (push) Failing after 1s
all-plugins-smoke / smoke-all (push) Failing after 2s
CI/CD Pipeline / Security & Code Quality (push) Failing after 1s
CI/CD Pipeline / Test Suite (ubuntu-latest) (push) Failing after 1s
CI/CD Pipeline / Build & Package (macos-latest) (push) Has been skipped
CI/CD Pipeline / Build & Package (ubuntu-latest) (push) Has been skipped
CI/CD Pipeline / Build & Package (windows-latest) (push) Has been skipped
CI/CD Pipeline / Documentation & Examples (push) Failing after 1s
Clone Tracker (14-day rolling) / Snapshot clones for ruflo ecosystem (push) Failing after 1s
CodeQL Advanced / Analyze (actions) (push) Failing after 1s
CodeQL Advanced / Analyze (javascript-typescript) (push) Failing after 1s
federation-peer-rust / stable-noop (push) Failing after 1s
metaharness-ci / score (push) Failing after 1s
metaharness-ci / router-compat (push) Failing after 0s
metaharness-ci / similarity-tests (push) Failing after 0s
no-agentbbs-smoke / smoke-without-agentbbs (push) Failing after 1s
V3 CI/CD Pipeline / Build V3 (windows-latest) (push) Has been skipped
codex-integration-audit / Codex integration audit (push) Failing after 1s
helpers-manifest-guard / guard (push) Failing after 1s
🔗 Cross-Agent Integration Tests / 🤝 Agent Coordination Tests (push) Has been skipped
🔗 Cross-Agent Integration Tests / 🧠 Memory Sharing Integration (push) Has been skipped
🔗 Cross-Agent Integration Tests / 🛡️ Fault Tolerance Tests (push) Has been skipped
🔗 Cross-Agent Integration Tests / ⚡ Performance Integration Tests (push) Has been skipped
metaharness-ci / mcp-scan (push) Failing after 1s
metaharness-ci / eject-dryrun (push) Failing after 1s
metaharness-ci / metaharness-real-data (push) Failing after 0s
no-cli-optdep-bloat-2561 / guard (push) Failing after 1s
no-metaharness-smoke / smoke-without-metaharness (push) Failing after 1s
no-phantom-agentic-flow-subpath / guard (push) Failing after 1s
🔄 Automated Rollback Manager / 🚨 Failure Detection (push) Failing after 1s
V3 CI/CD Pipeline / Plugin hooks smoke / ubuntu-latest / Node 22 (push) Failing after 1s
V3 CI/CD Pipeline / ruflo-graph-intelligence build + test smoke (#2044, ADR-123) (push) Failing after 1s
CVE Audit Gate / Audit root (critical-blocking) (push) Failing after 2s
cost-tracker-smoke / smoke (push) Failing after 3s
oia-audit-weekly / audit (push) Failing after 2s
ruflo-agent-smoke / ruflo-agent structural smoke (push) Failing after 1s
📊 Status Badges Update / 📊 Update Status Badges (push) Failing after 1s
V3 CI/CD Pipeline / Static regression guards (#2267 YAML + (push) Failing after 1s
V3 CI/CD Pipeline / Test V3 Packages (push) Failing after 0s
V3 CI/CD Pipeline / agent_execute provider routing smoke (#2042) (push) Failing after 0s
CVE Audit Gate / Audit v3 (critical-blocking) (push) Failing after 1s
federation-peer-rust / stable-native (push) Failing after 2s
🔗 Cross-Agent Integration Tests / 🚀 Integration Test Setup (push) Failing after 2s
neural-trader-smoke / runtime-smoke (push) Failing after 1s
V3 CI/CD Pipeline / Build V3 (macos-latest) (push) Has been skipped
V3 CI/CD Pipeline / Build V3 (ubuntu-latest) (push) Has been skipped
V3 CI/CD Pipeline / Type Check V3 (push) Failing after 1s
V3 CI/CD Pipeline / Smoke (no better-sqlite3) / ubuntu-latest / Node 24 (push) Failing after 1s
V3 CI/CD Pipeline / Smoke (no better-sqlite3) / ubuntu-latest / Node 22 (push) Failing after 2s
V3 CI/CD Pipeline / browser rvf create flag smoke (#2015) (push) Failing after 0s
V3 CI/CD Pipeline / Dependency review (#2046) (push) Has been skipped
V3 CI/CD Pipeline / Supply-chain audit (#2046) (push) Failing after 0s
V3 CI/CD Pipeline / witness marker drift smoke (#2021) (push) Failing after 1s
V3 CI/CD Pipeline / neural-trader portfolio CG smoke (#2068, ADR-126 Phase 3) (push) Failing after 1s
V3 CI/CD Pipeline / neural-trader backtest signing smoke (#2068, ADR-126 Phase 4) (push) Failing after 1s
V3 CI/CD Pipeline / kg-extract type-import classification smoke (#2049) (push) Failing after 0s
V3 CI/CD Pipeline / witness verify precondition smoke (#1880) (push) Failing after 2s
V3 CI/CD Pipeline / neural-trader pipeline risk-gate smoke (#2068, ADR-126 Phase 5) (push) Failing after 0s
V3 CI/CD Pipeline / neural-trader feature attribution smoke (#2068, ADR-126 Phase 6) (push) Failing after 0s
V3 CI/CD Pipeline / plugin-registry signature verification smoke (#1922, CWE-347) (push) Failing after 4s
V3 CI/CD Pipeline / memory stats legacy-DB smoke (#2120) (push) Failing after 4s
V3 CI/CD Pipeline / github deprecated actions smoke (#2089, ADR-127 Phase 3) (push) Failing after 1s
V3 CI/CD Pipeline / graph query + pathfinder smoke (ADR-130 P2+P5) (push) Has been skipped
V3 CI/CD Pipeline / graph trajectory hooks smoke (ADR-130 P3) (push) Has been skipped
V3 CI/CD Pipeline / graph plugin adapter smoke (ADR-130 P4) (push) Has been skipped
V3 CI/CD Pipeline / graph benchmark (ADR-130 P6) (push) Has been skipped
V3 CI/CD Pipeline / statusline generator delegation smoke (#2195) (push) Failing after 1s
V3 CI/CD Pipeline / wizard init regression guard (#2206 (push) Failing after 1s
V3 CI/CD Pipeline / memory no-stray-db smoke (ADR-125 P7) (push) Failing after 1s
V3 CI/CD Pipeline / github-safe injection smoke (#2089, ADR-127 Phase 1) (push) Failing after 1s
V3 CI/CD Pipeline / github actions pin smoke (#2089, ADR-127 Phase 1) (push) Failing after 1s
V3 CI/CD Pipeline / github attribution opt-in smoke (#2089, ADR-127 Phase 4) (push) Failing after 1s
V3 CI/CD Pipeline / pre-bash hook safety smoke (#2017) (push) Failing after 1s
V3 CI/CD Pipeline / Memory import smoke / ubuntu-latest (push) Failing after 0s
V3 CI/CD Pipeline / MCP protocol smoke / ubuntu-latest (push) Failing after 2s
V3 CI/CD Pipeline / ruvllm WASM auto-init smoke (#2086) (push) Failing after 4s
V3 CI/CD Pipeline / MCP paired-tool round-trip smoke (#1889) (push) Failing after 1s
V3 CI/CD Pipeline / Plugin package install-safety (#1902/#1903/#1904) (push) Failing after 1s
V3 CI/CD Pipeline / Tool description discoverability (ADR-112) (push) Failing after 3s
V3 CI/CD Pipeline / CLI npx-install smoke (#1147 / (22) (push) Failing after 1s
V3 CI/CD Pipeline / CLI npx-install smoke (#1147 / (24) (push) Failing after 1s
V3 CI/CD Pipeline / Windows hook shim smoke (#2132) / ubuntu-latest (push) Failing after 2s
V3 CI/CD Pipeline / Windows hook execution smoke (#2132) / ubuntu-latest (push) Failing after 1s
V3 CI/CD Pipeline / Windows init hooks smoke (#2132) / ubuntu-latest (push) Failing after 1s
V3 CI/CD Pipeline / Vector-index dimension audit (#1947) (push) Failing after 0s
V3 CI/CD Pipeline / Hook-command install safety (#1921) (push) Failing after 1s
V3 CI/CD Pipeline / ToolOutputGuardrail smoke (ADR-131, (push) Failing after 1s
V3 CI/CD Pipeline / init-bundle invariants smoke (#2095, ADR-128 Phase 5) (push) Failing after 1s
V3 CI/CD Pipeline / wasm provider bridge smoke (ADR-129 P1) (push) Failing after 2s
V3 CI/CD Pipeline / wasm gallery CRUD smoke (ADR-129 P3) (push) Failing after 1s
V3 CI/CD Pipeline / wasm plugin bridge smoke (ADR-129 P4) (push) Failing after 0s
V3 CI/CD Pipeline / wasm compose smoke (ADR-129 P2) (push) Failing after 4s
V3 CI/CD Pipeline / graph schema smoke (ADR-130 P1) (push) Failing after 0s
Validate Marketplace / validate (push) Failing after 1s
🔍 Verification Pipeline / 🚀 Setup Verification (push) Failing after 1s
🔍 Verification Pipeline / 🛡️ Security Verification (push) Has been skipped
🔍 Verification Pipeline / 📝 Code Quality (push) Has been skipped
🔍 Verification Pipeline / 🧪 Test Verification (${{ matrix.os }}, Node ${{ matrix.node }}) (push) Has been skipped
🔍 Verification Pipeline / 🏗️ Build Verification (push) Has been skipped
🔍 Verification Pipeline / 📚 Documentation Verification (push) Has been skipped
CVE Audit Gate / High-severity report (warn only) (push) Has been cancelled
🔄 Automated Rollback Manager / 🔄 Execute Rollback (push) Has been cancelled
🔄 Automated Rollback Manager / ✅ Post-Rollback Verification (push) Has been cancelled
🔄 Automated Rollback Manager / 📊 Rollback Monitoring (push) Has been cancelled
V3 CI/CD Pipeline / Windows init hooks smoke (#2132) / windows-latest (push) Has been cancelled
V3 CI/CD Pipeline / Windows hook execution smoke (#2132) / macos-latest (push) Has been cancelled
V3 CI/CD Pipeline / Windows hook execution smoke (#2132) / windows-latest (push) Has been cancelled
🔄 Automated Rollback Manager / ⏳ Manual Rollback Approval (push) Has been cancelled
V3 CI/CD Pipeline / MCP protocol smoke / macos-latest (push) Has been cancelled
V3 CI/CD Pipeline / Memory import smoke / macos-latest (push) Has been cancelled
V3 CI/CD Pipeline / Windows hook shim smoke (#2132) / macos-latest (push) Has been cancelled
V3 CI/CD Pipeline / Windows hook shim smoke (#2132) / windows-latest (push) Has been cancelled
V3 CI/CD Pipeline / Windows init hooks smoke (#2132) / macos-latest (push) Has been cancelled
V3 CI/CD Pipeline / Witness verify (signed manifest) / macos-latest (push) Has been cancelled
V3 CI/CD Pipeline / Witness verify (signed manifest) / ubuntu-latest (push) Has been cancelled
V3 CI/CD Pipeline / Witness verify (signed manifest) / windows-latest (push) Has been cancelled
V3 CI/CD Pipeline / Publish to npm (alpha) (push) Has been cancelled
V3 CI/CD Pipeline / Smoke (no better-sqlite3) / macos-latest / Node 22 (push) Has been cancelled
V3 CI/CD Pipeline / Plugin hooks smoke / macos-latest / Node 22 (push) Has been cancelled
CI/CD Pipeline / Deploy & Release (push) Has been cancelled
CI/CD Pipeline / CI Status (push) Has been cancelled
🔗 Cross-Agent Integration Tests / 📊 Integration Test Report (push) Has been cancelled
🔄 Automated Rollback Manager / 🔍 Pre-Rollback Validation (push) Has been cancelled
🔍 Verification Pipeline / ⚡ Performance Verification (push) Has been cancelled
🔍 Verification Pipeline / 📊 Verification Report (push) Has been cancelled
230 lines
10 KiB
JavaScript
Executable File
230 lines
10 KiB
JavaScript
Executable File
#!/usr/bin/env node
|
||
// test-with-openrouter.mjs — runtime test that exercises metaharness
|
||
// scaffolding + lifecycle commands using OPENROUTER_API_KEY fetched
|
||
// from GCP Secret Manager.
|
||
//
|
||
// WHAT IT DOES (end-to-end)
|
||
// 1. Fetch OPENROUTER_API_KEY from GCP Secret Manager via
|
||
// `gcloud secrets versions access latest --secret=OPENROUTER_API_KEY`
|
||
// 2. Verify the secret authenticates against OpenRouter by listing
|
||
// models (1 HTTP call, ~$0)
|
||
// 3. Scaffold a fresh harness into a temp dir via
|
||
// `metaharness new --name test-h --template vertical:coding
|
||
// --host claude-code --yes`
|
||
// 4. Run lifecycle commands against the scaffold:
|
||
// - harness doctor (smoke health check)
|
||
// - harness validate (full validation; --skip-gcp to avoid
|
||
// nested GCP fetches in this test)
|
||
// - harness score (5-dim scorecard of the scaffolded
|
||
// harness itself)
|
||
// - harness genome (7-section report)
|
||
// 5. (Optional) make one real LLM call through OpenRouter to a
|
||
// cheap model (e.g. openrouter/auto with a 1-token prompt) to
|
||
// prove the token works for actual inference
|
||
// 6. Clean up the temp dir
|
||
//
|
||
// COST: ~$0 (1 model-list call + optional <$0.0001 inference call)
|
||
//
|
||
// USAGE
|
||
// node scripts/test-with-openrouter.mjs # full e2e
|
||
// node scripts/test-with-openrouter.mjs --skip-inference # no LLM call
|
||
// node scripts/test-with-openrouter.mjs --keep-fixtures # leave scaffold for inspection
|
||
//
|
||
// EXIT CODES
|
||
// 0 all checks passed
|
||
// 1 at least one assertion failed
|
||
// 2 setup error (gcloud not authed, OPENROUTER_API_KEY missing in GCP, etc.)
|
||
// 3 cost/safety guard tripped
|
||
|
||
import { spawnSync, execSync } from 'node:child_process';
|
||
import { mkdtempSync, rmSync, mkdirSync, existsSync } from 'node:fs';
|
||
import { tmpdir } from 'node:os';
|
||
import { join } from 'node:path';
|
||
|
||
const ARGS = (() => {
|
||
const a = { skipInference: false, keep: false, format: 'table' };
|
||
for (let i = 2; i < process.argv.length; i++) {
|
||
const v = process.argv[i];
|
||
if (v === '--skip-inference') a.skipInference = true;
|
||
else if (v === '--keep-fixtures') a.keep = true;
|
||
else if (v === '--format') a.format = process.argv[++i];
|
||
}
|
||
return a;
|
||
})();
|
||
|
||
let passed = 0, failed = 0;
|
||
const failures = [];
|
||
function assert(cond, label) {
|
||
if (cond) { console.log(` ✓ ${label}`); passed++; }
|
||
else { console.log(` ✗ ${label}`); failures.push(label); failed++; }
|
||
}
|
||
|
||
function fetchSecretFromGcp(secretName) {
|
||
try {
|
||
const out = execSync(
|
||
`gcloud secrets versions access latest --secret=${secretName}`,
|
||
{ encoding: 'utf-8', stdio: ['ignore', 'pipe', 'pipe'], timeout: 15_000 },
|
||
);
|
||
return out.trim();
|
||
} catch (e) {
|
||
console.error(` ⚠ gcloud secret fetch failed: ${(e.message || '').slice(0, 120)}`);
|
||
return null;
|
||
}
|
||
}
|
||
|
||
async function listOpenRouterModels(apiKey) {
|
||
const resp = await fetch('https://openrouter.ai/api/v1/models', {
|
||
headers: { Authorization: `Bearer ${apiKey}` },
|
||
});
|
||
if (!resp.ok) {
|
||
return { ok: false, status: resp.status, body: (await resp.text()).slice(0, 200) };
|
||
}
|
||
const data = await resp.json();
|
||
return { ok: true, modelCount: data.data?.length ?? 0 };
|
||
}
|
||
|
||
async function cheapInferenceCall(apiKey) {
|
||
// openrouter/auto picks the cheapest viable model; 5-token prompt;
|
||
// max_tokens=1 to bound cost at ~$0.00005.
|
||
const resp = await fetch('https://openrouter.ai/api/v1/chat/completions', {
|
||
method: 'POST',
|
||
headers: {
|
||
Authorization: `Bearer ${apiKey}`,
|
||
'Content-Type': 'application/json',
|
||
'HTTP-Referer': 'https://github.com/ruvnet/ruflo',
|
||
'X-Title': 'ruflo-metaharness-test',
|
||
},
|
||
body: JSON.stringify({
|
||
model: 'openrouter/auto',
|
||
messages: [{ role: 'user', content: 'OK' }],
|
||
max_tokens: 1,
|
||
}),
|
||
});
|
||
if (!resp.ok) return { ok: false, status: resp.status, body: (await resp.text()).slice(0, 200) };
|
||
const data = await resp.json();
|
||
return { ok: true, model: data.model, usage: data.usage };
|
||
}
|
||
|
||
function runHarness(args, opts = {}) {
|
||
const r = spawnSync('npx', ['-y', '-p', 'metaharness@latest', 'harness', ...args], {
|
||
stdio: ['ignore', 'pipe', 'pipe'], encoding: 'utf-8',
|
||
timeout: opts.timeoutMs ?? 60_000,
|
||
env: { ...process.env, ...opts.env },
|
||
});
|
||
return { exitCode: r.status ?? 1, stdout: r.stdout || '', stderr: r.stderr || '' };
|
||
}
|
||
|
||
function runMetaharness(args, opts = {}) {
|
||
const r = spawnSync('npx', ['-y', 'metaharness@latest', ...args], {
|
||
stdio: ['ignore', 'pipe', 'pipe'], encoding: 'utf-8',
|
||
timeout: opts.timeoutMs ?? 120_000,
|
||
cwd: opts.cwd, // metaharness new writes to cwd/<name>; --target is ignored
|
||
env: { ...process.env, ...opts.env },
|
||
});
|
||
return { exitCode: r.status ?? 1, stdout: r.stdout || '', stderr: r.stderr || '' };
|
||
}
|
||
|
||
async function main() {
|
||
console.log('# test-with-openrouter — metaharness × GCP × OpenRouter e2e\n');
|
||
|
||
// ── 0. Preflight ──────────────────────────────────────────────────
|
||
console.log('Phase 0 — preflight');
|
||
const gcloudWho = execSync('gcloud config get-value account 2>&1', { encoding: 'utf-8' }).trim();
|
||
assert(!!gcloudWho && !/None/.test(gcloudWho), `gcloud authenticated (${gcloudWho || 'none'})`);
|
||
|
||
// ── 1. Fetch OPENROUTER_API_KEY ───────────────────────────────────
|
||
console.log('\nPhase 1 — fetch OPENROUTER_API_KEY from GCP Secret Manager');
|
||
const apiKey = fetchSecretFromGcp('OPENROUTER_API_KEY');
|
||
assert(typeof apiKey === 'string' && apiKey.length > 20, 'OPENROUTER_API_KEY fetched (length OK)');
|
||
if (!apiKey) {
|
||
console.error('Setup error: cannot proceed without OPENROUTER_API_KEY. Skipping further phases.');
|
||
process.exit(2);
|
||
}
|
||
// Echo only length+prefix, never the raw key
|
||
console.log(` key length: ${apiKey.length}, prefix: ${apiKey.slice(0, 7)}…`);
|
||
|
||
// ── 2. Verify the key authenticates against OpenRouter ────────────
|
||
console.log('\nPhase 2 — verify OpenRouter authentication');
|
||
const modelsResp = await listOpenRouterModels(apiKey);
|
||
assert(modelsResp.ok, `OpenRouter /api/v1/models returns 2xx (got ${modelsResp.ok ? 'ok' : modelsResp.status})`);
|
||
if (modelsResp.ok) {
|
||
assert(modelsResp.modelCount > 10, `model list non-empty (${modelsResp.modelCount} models)`);
|
||
}
|
||
|
||
// ── 3. Scaffold a fresh harness ───────────────────────────────────
|
||
// CRITICAL: `metaharness new <name>` writes to $CWD/<name> — the
|
||
// --target flag is ignored by the CLI (verified 2026-06-16 against
|
||
// metaharness@0.1.11). Run from inside a fresh temp dir so the
|
||
// scaffold lands there, not in the ruflo project root.
|
||
console.log('\nPhase 3 — scaffold a fresh harness');
|
||
const fixture = mkdtempSync(join(tmpdir(), 'ruflo-mh-openrouter-'));
|
||
const target = join(fixture, 'test-harness');
|
||
console.log(` fixture cwd: ${fixture}`);
|
||
console.log(` expected target: ${target}`);
|
||
const scaffold = runMetaharness(
|
||
['test-harness', '--template', 'vertical:coding', '--host', 'claude-code'],
|
||
{ timeoutMs: 180_000, cwd: fixture },
|
||
);
|
||
assert(scaffold.exitCode === 0, `metaharness new exit 0 (got ${scaffold.exitCode})`);
|
||
assert(existsSync(target), `target dir created at ${target}`);
|
||
|
||
// ── 4. Run lifecycle commands against the scaffold ────────────────
|
||
console.log('\nPhase 4 — lifecycle commands on the scaffold');
|
||
// harness doctor — quick smoke
|
||
const doc = runHarness(['doctor', target]);
|
||
assert(doc.exitCode === 0, `harness doctor exit 0 (got ${doc.exitCode})`);
|
||
|
||
// harness score on the scaffold itself
|
||
const score = runHarness(['score', target, '--json']);
|
||
assert(score.exitCode === 0, `harness score exit 0 (got ${score.exitCode})`);
|
||
const scoreJson = (() => {
|
||
const m = /\{[\s\S]*\}/.exec(score.stdout);
|
||
try { return m ? JSON.parse(m[0]) : null; } catch { return null; }
|
||
})();
|
||
assert(scoreJson && typeof scoreJson.score === 'number', 'score.json has numeric score');
|
||
|
||
// harness genome — exit 0 (ready) or 1 (needs-work) both acceptable.
|
||
// Only exit 2 (blocked / scan-error) is a real failure.
|
||
const gen = runHarness(['genome', target, '--json']);
|
||
assert(gen.exitCode === 0 || gen.exitCode === 1, `harness genome exit 0 or 1 (got ${gen.exitCode})`);
|
||
|
||
// harness mcp-scan
|
||
const scan = runHarness(['mcp-scan', target]);
|
||
// mcp-scan can exit 1 on findings; either 0 or 1 is acceptable here.
|
||
assert(scan.exitCode === 0 || scan.exitCode === 1, `harness mcp-scan exit 0 or 1 (got ${scan.exitCode})`);
|
||
|
||
// ── 5. (Optional) one real OpenRouter inference call ──────────────
|
||
if (!ARGS.skipInference) {
|
||
console.log('\nPhase 5 — single OpenRouter inference call (cheapest auto-route, max_tokens=1)');
|
||
const inf = await cheapInferenceCall(apiKey);
|
||
assert(inf.ok, `OpenRouter inference 2xx (got ${inf.ok ? 'ok' : inf.status})`);
|
||
if (inf.ok) {
|
||
console.log(` model used: ${inf.model}`);
|
||
console.log(` usage: prompt=${inf.usage?.prompt_tokens} completion=${inf.usage?.completion_tokens}`);
|
||
}
|
||
} else {
|
||
console.log('\nPhase 5 — SKIPPED (--skip-inference)');
|
||
}
|
||
|
||
// ── 6. Cleanup ────────────────────────────────────────────────────
|
||
if (!ARGS.keep) {
|
||
rmSync(fixture, { recursive: true, force: true });
|
||
console.log(`\nFixture cleaned: ${fixture}`);
|
||
} else {
|
||
console.log(`\nFixture kept at: ${fixture}`);
|
||
}
|
||
|
||
console.log(`\n${passed} passed, ${failed} failed`);
|
||
if (failed > 0) {
|
||
console.log('\nFailures:');
|
||
for (const f of failures) console.log(` - ${f}`);
|
||
process.exit(1);
|
||
}
|
||
console.log('\n✓ All harness × OpenRouter integration checks passed.');
|
||
}
|
||
|
||
main().catch((e) => {
|
||
console.error('test-with-openrouter crashed:', e.message || e);
|
||
process.exit(2);
|
||
});
|