From 2c3dc66878a7337bcb35c1254ed3e4b4a88b1229 Mon Sep 17 00:00:00 2001 From: Matt Mabrey Date: Fri, 21 Aug 2026 11:40:28 -0700 Subject: [PATCH] Add MOCK_AI mode to test the AI draft button with no API key --- .env.example | 7 ++++++- server.js | 32 +++++++++++++++++++++++++++++++- 2 files changed, 37 insertions(+), 2 deletions(-) diff --git a/.env.example b/.env.example index 625360e..31feb9a 100644 --- a/.env.example +++ b/.env.example @@ -1,6 +1,11 @@ ## Copy this file to .env and fill in real values. Do not commit .env. -# Required for the AI draft button. +# Set to true to test the AI draft button with no API key and no cost. +# Returns a canned draft instead of calling Anthropic. Leave unset or false +# for real drafts. +MOCK_AI=false + +# Required for the AI draft button, unless MOCK_AI=true. ANTHROPIC_API_KEY=your-anthropic-api-key-here # Required for company deployment. Leave both blank for solo local use with diff --git a/server.js b/server.js index 554675e..2f38751 100644 --- a/server.js +++ b/server.js @@ -84,7 +84,33 @@ function isRateLimited(ip) { return hits.length > RATE_LIMIT_MAX; } +/* ===== Mock mode ===== + * Set MOCK_AI=true to test the whole app, including the AI draft button, + * with no API key and no real API call. Returns a canned breadcrumb draft + * shaped like a real Anthropic response, so the frontend parsing and gate + * checks run exactly as they would against the live API. Use this for + * testing the login gate, rate limit, and Docker setup without cost. */ +function mockClaudeResponse(res) { + const draft = { + domain: 'MOCK: Progress Visibility', + driver: 'MOCK driver: schedule forecasts drift from field reality.', + cap: 'MOCK capability: a rules-of-credit progress check against the field.', + metric: 'MOCK metric: claiming accuracy, reported % vs field-verified %, by phase.', + method: 'MOCK method: monthly spot audit of 30+ sampled assets.', + baseline: 'MOCK baseline: first audit result.' + }; + const body = { + content: [{ type: 'text', text: JSON.stringify(draft) }] + }; + res.writeHead(200, { 'Content-Type': 'application/json; charset=utf-8' }); + res.end(JSON.stringify(body)); +} + function proxyToClaude(req, res) { + if (String(process.env.MOCK_AI).toLowerCase() === 'true') { + mockClaudeResponse(res); + return; + } if (!process.env.ANTHROPIC_API_KEY) { sendJson(res, 500, { error: 'ANTHROPIC_API_KEY is not set. Add it to the .env file in this folder, then restart the server.' @@ -169,6 +195,10 @@ const server = http.createServer((req, res) => { server.listen(PORT, () => { console.log(`SDE meeting toolkit running at http://localhost:${PORT}`); - console.log(process.env.ANTHROPIC_API_KEY ? 'ANTHROPIC_API_KEY loaded: AI draft button is live.' : 'No ANTHROPIC_API_KEY found: AI draft button will return an error until you add one to .env.'); + if (String(process.env.MOCK_AI).toLowerCase() === 'true') { + console.log('MOCK_AI=true: AI draft button returns a canned response. No API key used or needed.'); + } else { + console.log(process.env.ANTHROPIC_API_KEY ? 'ANTHROPIC_API_KEY loaded: AI draft button is live.' : 'No ANTHROPIC_API_KEY found: AI draft button will return an error until you add one to .env.'); + } console.log((process.env.APP_USERNAME && process.env.APP_PASSWORD) ? 'Login required: APP_USERNAME/APP_PASSWORD are set.' : 'No login required: APP_USERNAME/APP_PASSWORD are not set.'); });