Add local proxy server for AI draft button; point button at /api/claude

This commit is contained in:
2026-08-20 14:49:26 -07:00
parent ee8f869905
commit e5c01003f8
7 changed files with 269 additions and 11 deletions

2
.env.example Normal file
View File

@@ -0,0 +1,2 @@
## Copy this file to .env and fill in your key. Do not commit .env.
ANTHROPIC_API_KEY=your-anthropic-api-key-here

1
.gitignore vendored
View File

@@ -2,3 +2,4 @@ node_modules/
.DS_Store
Thumbs.db
*.log
.env

112
QUICKSTART.md Normal file
View File

@@ -0,0 +1,112 @@
# Quick Start: SDE Meeting Toolkit
## Open a tool
1. Go to the `tools` folder.
2. Double-click `field-problem-workshop.html` or `scope-lock-meeting-suite.html`.
3. The file opens in your default browser.
No install step is needed for this method.
## Field Problem Workshop
The tool has three panels: Problem Map, Breadcrumbs, Actions.
### Problem Map panel
1. Type a problem into the entry box and click **Add**.
2. Click **Add cluster** to create a new group.
3. Drag a problem into a cluster, or use the cluster picker on the item.
4. Use dot voting to mark priority problems.
5. Unsorted problems stay under **Unclustered** at the top.
### Breadcrumbs panel
1. Click **Add a blank breadcrumb** to start a new one.
2. Fill in **Baseline**, **Method**, **Metric**, and, once you have a baseline, **Target**.
3. Click **Scan for incomplete items** to find breadcrumbs missing a field.
4. Click **Save** on a breadcrumb to lock in your edits.
### Actions panel
1. Click **Add** to create an action item.
2. Assign an owner and a due date.
3. Click **Save** to store the action.
### Save your session
The tool does not save to disk on its own.
1. Click **Save JSON** before you close the tab.
2. Store the file somewhere you can find it.
3. Next time, click **Load JSON** and pick that file to pick up where you left off.
4. Click **Export summary** to produce the meeting write-up (markdown).
5. Click **Reset** only when you want to return to the July 21 baseline (43 problems, 7 clusters, 6 breadcrumbs, 5 actions). Reset erases current data, so export first.
## Scope Lock Meeting Suite
The tool has four panels: Scope Boundary Board, MVP Priority Ranker, Decision Registry, Micron Pilot Readiness.
### Scope Boundary Board
1. Click **Add item** to enter a new scope item.
2. Move it into one of four columns: **In MVP**, **Later Phase**, **Out**, **Parking Lot**.
3. Items start in **Unsorted** until you sort them.
### MVP Priority Ranker
1. Use **Move line up** / **Move line down** to reorder items by priority.
### Decision Registry
1. Click **Log decision** to record a decision made in the meeting.
### Micron Pilot Readiness
1. Review the readiness checklist shown on this panel.
### Save your session
1. This tool autosaves to the browser's local storage on the computer you use. It does not need a manual save step.
2. Local storage does not follow you to another computer or browser.
3. Click **Export meeting summary** to create the durable record to share or file.
4. **Reset all** returns the tool to its starting state. Export first if you want to keep current data.
## Run it from a local server instead
Use this if you want the tools served over HTTP, or if you want the AI draft button to work outside of Claude.
1. Open a terminal in the `sde-meeting-toolkit` folder.
2. Run `npm run serve`.
3. Open `http://localhost:5173` in a browser.
4. Click a tool link on that page, or go directly to a file under `tools`.
## Use the AI draft button
The Field Problem Workshop has an AI draft feature on the Breadcrumbs panel. It calls Claude to draft breadcrumb fields for you. Use one of these two methods.
### Method A: Local server (needs an API key)
1. Copy `.env.example` to a new file named `.env`, in the `sde-meeting-toolkit` folder.
2. Open `.env` and set `ANTHROPIC_API_KEY` to your key.
3. Run `npm run serve`.
4. Open the tool at `http://localhost:5173/tools/field-problem-workshop.html`.
5. Click the AI draft button.
If the button reports a missing key, check that `.env` exists, holds the correct key, and that you restarted the server after editing it.
### Method B: Claude artifact (no key needed)
1. Upload `field-problem-workshop.html` to a Claude chat, or add it to a Claude project.
2. Ask Claude to render it as an artifact.
3. Use the AI draft button from inside that artifact.
If you open the file directly, with no server running, the AI draft button fails. This is expected. Use Method A or Method B instead.
## Install the breadcrumb skill
1. In Claude, open `skills/construction-breadcrumbs.skill`.
2. Use **Save skill** to install it.
3. Start a new chat to use it.
Next: open a tool and click **Add** to create your first item.

View File

@@ -14,7 +14,7 @@ This folder holds two workshop tools for Project SDE. Each tool is one HTML file
## Requirements
Install Node.js version 18 or later to run the local server. Node.js includes `npx`.
Install Node.js version 18 or later to run the local server.
You do not need Node.js to open a tool file directly in a browser.
@@ -28,8 +28,11 @@ Follow these steps to set up the project on your computer.
```
git status
```
4. To use the AI draft button through the local server, copy `.env.example` to `.env`.
5. Open `.env` and set `ANTHROPIC_API_KEY` to your key.
6. Do not commit `.env`. Git already ignores it.
The setup step ends here. The project has no other dependencies to install.
Skip steps 4 through 6 if you plan to run the AI draft button only from inside a Claude artifact (see Method 3 below). The project has no other dependencies to install.
## Run a tool
@@ -45,7 +48,7 @@ This method works offline. This method does not run the AI draft button in the F
### Method 2: Run a local server
Use this method to test the tools the way a browser would load them from a web server.
Use this method to test the tools the way a browser would load them from a web server. This method also makes the AI draft button work, if you completed steps 4 through 6 in Setup.
1. Open a terminal.
2. Go to the `sde-meeting-toolkit` folder.
@@ -53,20 +56,32 @@ Use this method to test the tools the way a browser would load them from a web s
```
npm run serve
```
4. Open a browser.
5. Go to this address: `http://localhost:5173`
6. Click a tool file under the `tools` folder.
7. Press `Ctrl+C` in the terminal to stop the server.
4. Check the terminal output. It reports whether it found an API key.
5. Open a browser.
6. Go to this address: `http://localhost:5173`
7. Click a tool link, or go directly to a file under the `tools` folder.
8. Press `Ctrl+C` in the terminal to stop the server.
### Method 3: Use the tool as a Claude artifact
Use this method to run the AI draft button in the Field Problem Workshop.
Use this method to run the AI draft button without setting up a local API key.
1. Open a chat in Claude.
2. Upload the tool file, or add it to a Claude project.
3. Ask Claude to render the file as an artifact.
The AI draft button needs a network connection and the Claude environment. Outside of Claude, the button fails without an error.
This method needs a network connection. It does not need the local server or a `.env` file.
## AI draft button
The Field Problem Workshop calls Claude to draft breadcrumb fields. It reaches Claude through one of two paths, never directly from the browser to the Anthropic API.
- **Local server path**: The browser calls `/api/claude` on your own machine. `server.js` reads `ANTHROPIC_API_KEY` from `.env` and forwards the request. Your key stays on your machine and is never sent to the browser.
- **Claude artifact path**: The Claude environment handles the request. No key setup is needed on your side.
If you run a tool by double-clicking the file, with no server, the AI draft button fails. This is expected: a plain file has no server to call.
If you run `npm run serve` without a `.env` file, or with `ANTHROPIC_API_KEY` unset, the button reports that the key is missing. Add the key to `.env` and restart the server to fix it.
## Save and load your work

View File

@@ -4,7 +4,7 @@
"private": true,
"description": "Local environment for the Project SDE meeting and workshop tools",
"scripts": {
"serve": "npx --yes serve . -l 5173",
"serve": "node server.js",
"start": "npm run serve"
}
}

128
server.js Normal file
View File

@@ -0,0 +1,128 @@
/*
* Local server for the SDE meeting toolkit.
* Serves the static tool files.
* Proxies AI draft requests to the Anthropic API, so the API key
* stays on the server and never appears in the browser.
*/
const http = require('http');
const fs = require('fs');
const path = require('path');
const ROOT = __dirname;
const PORT = process.env.PORT || 5173;
const ANTHROPIC_VERSION = '2023-06-01';
const MIME = {
'.html': 'text/html; charset=utf-8',
'.js': 'text/javascript; charset=utf-8',
'.css': 'text/css; charset=utf-8',
'.json': 'application/json; charset=utf-8',
'.svg': 'image/svg+xml',
'.png': 'image/png',
'.skill': 'application/zip'
};
function loadEnvFile() {
const envPath = path.join(ROOT, '.env');
let content;
try {
content = fs.readFileSync(envPath, 'utf8');
} catch (err) {
return; // no .env file; that is allowed, the AI draft route will report it
}
content.split('\n').forEach((line) => {
const trimmed = line.trim();
if (!trimmed || trimmed.startsWith('#')) return;
const idx = trimmed.indexOf('=');
if (idx === -1) return;
const key = trimmed.slice(0, idx).trim();
const value = trimmed.slice(idx + 1).trim().replace(/^["']|["']$/g, '');
if (key && !(key in process.env)) process.env[key] = value;
});
}
loadEnvFile();
function sendJson(res, status, obj) {
const body = JSON.stringify(obj);
res.writeHead(status, { 'Content-Type': 'application/json; charset=utf-8' });
res.end(body);
}
function proxyToClaude(req, res) {
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.'
});
return;
}
let body = '';
req.on('data', (chunk) => { body += chunk; });
req.on('end', async () => {
try {
const upstream = await fetch('https://api.anthropic.com/v1/messages', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'x-api-key': process.env.ANTHROPIC_API_KEY,
'anthropic-version': ANTHROPIC_VERSION
},
body
});
const text = await upstream.text();
res.writeHead(upstream.status, { 'Content-Type': 'application/json; charset=utf-8' });
res.end(text);
} catch (err) {
sendJson(res, 502, { error: 'Could not reach the Anthropic API: ' + err.message });
}
});
}
function serveStatic(req, res) {
const urlPath = decodeURIComponent(req.url.split('?')[0]);
if (urlPath === '/') {
const index = `<!doctype html><html><body style="font:14px system-ui;padding:2rem">
<h1>SDE Meeting Toolkit</h1>
<ul>
<li><a href="/tools/field-problem-workshop.html">Field Problem Workshop</a></li>
<li><a href="/tools/scope-lock-meeting-suite.html">Scope Lock Meeting Suite</a></li>
</ul>
</body></html>`;
res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
res.end(index);
return;
}
const filePath = path.normalize(path.join(ROOT, urlPath));
if (!filePath.startsWith(ROOT)) {
res.writeHead(403);
res.end('Forbidden');
return;
}
fs.readFile(filePath, (err, data) => {
if (err) {
res.writeHead(404, { 'Content-Type': 'text/plain' });
res.end('Not found: ' + urlPath);
return;
}
const ext = path.extname(filePath);
res.writeHead(200, { 'Content-Type': MIME[ext] || 'application/octet-stream' });
res.end(data);
});
}
const server = http.createServer((req, res) => {
if (req.method === 'POST' && req.url === '/api/claude') {
proxyToClaude(req, res);
return;
}
if (req.method === 'GET') {
serveStatic(req, res);
return;
}
res.writeHead(405, { 'Content-Type': 'text/plain' });
res.end('Method not allowed');
});
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.');
});

View File

@@ -653,7 +653,7 @@ document.getElementById('btnAddBc').addEventListener('click', ()=>{
/* ===== AI: draft breadcrumb fields from methodology ===== */
async function callClaude(prompt){
const response = await fetch("https://api.anthropic.com/v1/messages", {
const response = await fetch("/api/claude", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ model: "claude-sonnet-4-6", max_tokens: 1000, messages: [{ role: "user", content: prompt }] })