[object Object]

← back to Api Token Dashboard

API & token connection dashboard: live status + fix links (read-only, never displays secret values)

b39d577dabf09b083276c65bf22af633ddfe5200 · 2026-07-01 13:44:13 -0700 · Steve Abrams

Files touched

Diff

commit b39d577dabf09b083276c65bf22af633ddfe5200
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Wed Jul 1 13:44:13 2026 -0700

    API & token connection dashboard: live status + fix links (read-only, never displays secret values)
---
 .deploy.conf      |   3 +
 .gitignore        |   5 ++
 public/index.html | 115 +++++++++++++++++++++++++++++++++++++
 server.mjs        | 168 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
 4 files changed, 291 insertions(+)

diff --git a/.deploy.conf b/.deploy.conf
new file mode 100644
index 0000000..b197715
--- /dev/null
+++ b/.deploy.conf
@@ -0,0 +1,3 @@
+PROJECT_NAME=api-token-dashboard
+DEPLOY_PATH=/root/public-projects/api-token-dashboard
+HEALTH_URL=http://127.0.0.1:9791/
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..ff2422c
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,5 @@
+node_modules/
+.env*
+tmp/
+*.log
+.DS_Store
diff --git a/public/index.html b/public/index.html
new file mode 100644
index 0000000..aa77ad3
--- /dev/null
+++ b/public/index.html
@@ -0,0 +1,115 @@
+<!doctype html>
+<html lang="en">
+<head>
+<meta charset="utf-8">
+<meta name="viewport" content="width=device-width, initial-scale=1">
+<title>API &amp; Token Connections</title>
+<style>
+  :root{--bg:#0f1216;--card:#171b22;--line:#252b34;--fg:#e6e9ee;--dim:#93a0b0;
+        --ok:#2ecc71;--dead:#ff5252;--missing:#ffb020;--unknown:#7f8ea3;--accent:#34f4ab}
+  *{box-sizing:border-box}
+  body{margin:0;background:var(--bg);color:var(--fg);font:15px/1.5 -apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,sans-serif}
+  header{position:sticky;top:0;background:linear-gradient(#0f1216,#0f1216ee);backdrop-filter:blur(6px);
+         padding:18px 24px;border-bottom:1px solid var(--line);display:flex;align-items:center;gap:16px;flex-wrap:wrap;z-index:5}
+  h1{font-size:18px;margin:0;font-weight:650;letter-spacing:.2px}
+  .counts{display:flex;gap:10px;flex-wrap:wrap;font-size:12.5px}
+  .pill{padding:3px 10px;border-radius:999px;border:1px solid var(--line);white-space:nowrap}
+  .pill b{font-variant-numeric:tabular-nums}
+  .c-ok{color:var(--ok)} .c-dead{color:var(--dead)} .c-missing{color:var(--missing)} .c-unknown{color:var(--unknown)}
+  button{background:var(--accent);color:#04140d;border:0;padding:8px 16px;border-radius:8px;font-weight:650;cursor:pointer}
+  button.ghost{background:transparent;color:var(--fg);border:1px solid var(--line)}
+  main{padding:20px 24px 60px;max-width:1150px;margin:0 auto}
+  .filters{display:flex;gap:8px;margin:0 0 18px;flex-wrap:wrap}
+  .filters .ghost.active{border-color:var(--accent);color:var(--accent)}
+  .cat{margin:26px 0 10px;font-size:12px;text-transform:uppercase;letter-spacing:1.4px;color:var(--dim)}
+  .grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(340px,1fr));gap:14px}
+  .card{background:var(--card);border:1px solid var(--line);border-radius:12px;padding:15px 16px;display:flex;flex-direction:column;gap:9px}
+  .card.dead{border-color:#5a2222}.card.missing{border-color:#5a4718}
+  .top{display:flex;align-items:center;justify-content:space-between;gap:10px}
+  .name{font-weight:620}
+  .tag{font-size:12px;color:var(--dim);font-variant-numeric:tabular-nums}
+  .status{display:inline-flex;align-items:center;gap:6px;font-size:12.5px;font-weight:650;padding:3px 9px;border-radius:999px;border:1px solid var(--line)}
+  .dot{width:8px;height:8px;border-radius:50%}
+  .s-connected .dot{background:var(--ok)} .s-connected{color:var(--ok)}
+  .s-dead .dot{background:var(--dead)} .s-dead{color:var(--dead)}
+  .s-missing .dot{background:var(--missing)} .s-missing{color:var(--missing)}
+  .s-unknown .dot{background:var(--unknown)} .s-unknown{color:var(--unknown)}
+  .detail{font-size:12px;color:var(--dim)}
+  .instructions{font-size:12.5px;color:#c4ccd6;border-top:1px dashed var(--line);padding-top:9px}
+  .actions{display:flex;gap:8px;align-items:center;flex-wrap:wrap}
+  a.fix{font-size:12.5px;color:var(--accent);text-decoration:none;border:1px solid var(--line);padding:5px 10px;border-radius:7px}
+  a.fix:hover{border-color:var(--accent)}
+  code{background:#0c0f13;padding:1px 6px;border-radius:5px;font-size:12px;color:#c8d2dd}
+  .muted{color:var(--dim);font-size:12.5px}
+  .ts{font-size:11.5px;color:var(--dim)}
+</style>
+</head>
+<body>
+<header>
+  <h1>🔌 API &amp; Token Connections</h1>
+  <div class="counts" id="counts"></div>
+  <div style="flex:1"></div>
+  <span class="ts" id="ts"></span>
+  <button id="recheck" onclick="load()">Re-check all</button>
+</header>
+<main>
+  <div class="filters" id="filters">
+    <button class="ghost active" data-f="all">All</button>
+    <button class="ghost" data-f="action">⚠︎ Needs action</button>
+    <button class="ghost" data-f="connected">Connected</button>
+  </div>
+  <div id="body"><p class="muted">Probing connections…</p></div>
+</main>
+<script>
+let DATA=[], FILTER='all';
+const SEV={dead:0,missing:1,unknown:2,connected:3};
+function esc(s){return (s||'').replace(/[&<>]/g,c=>({'&':'&amp;','<':'&lt;','>':'&gt;'}[c]))}
+async function load(){
+  document.getElementById('recheck').textContent='Checking…';
+  try{
+    const r=await fetch('/api/status'); const j=await r.json();
+    DATA=j.results.sort((a,b)=>SEV[a.status]-SEV[b.status]||a.name.localeCompare(b.name));
+    document.getElementById('ts').textContent='updated '+new Date(j.generatedAt).toLocaleString();
+    render();
+  }catch(e){ document.getElementById('body').innerHTML='<p class="muted">Failed to load status: '+esc(e.message)+'</p>'; }
+  document.getElementById('recheck').textContent='Re-check all';
+}
+function render(){
+  const by={connected:0,dead:0,missing:0,unknown:0};
+  DATA.forEach(d=>by[d.status]++);
+  document.getElementById('counts').innerHTML=
+    `<span class="pill c-ok">✅ Connected <b>${by.connected}</b></span>`+
+    `<span class="pill c-dead">❌ Dead <b>${by.dead}</b></span>`+
+    `<span class="pill c-missing">⚠︎ Missing <b>${by.missing}</b></span>`+
+    `<span class="pill c-unknown">• Unknown <b>${by.unknown}</b></span>`;
+  let rows=DATA;
+  if(FILTER==='action') rows=DATA.filter(d=>d.status==='dead'||d.status==='missing');
+  if(FILTER==='connected') rows=DATA.filter(d=>d.status==='connected');
+  const cats=[...new Set(rows.map(d=>d.category))];
+  const html=cats.map(cat=>{
+    const items=rows.filter(d=>d.category===cat);
+    return `<div class="cat">${esc(cat)}</div><div class="grid">`+items.map(card).join('')+`</div>`;
+  }).join('');
+  document.getElementById('body').innerHTML=html||'<p class="muted">Nothing in this filter.</p>';
+}
+function card(d){
+  const cls=d.status==='connected'?'':d.status;
+  const label={connected:'Connected',dead:'Disconnected',missing:'Not set',unknown:'Unknown'}[d.status];
+  const showFix=d.status!=='connected';
+  return `<div class="card ${cls}">
+    <div class="top"><span class="name">${esc(d.name)}</span>
+      <span class="status s-${d.status}"><span class="dot"></span>${label}</span></div>
+    <div class="detail">${d.tag?`<code>${esc(d.envName)}</code> ${esc(d.tag)}`:`<code>${esc(d.envName||'—')}</code>`}${d.detail?` · ${esc(d.detail)}`:''}</div>
+    ${showFix?`<div class="instructions">${esc(d.instructions)}</div>
+    <div class="actions"><a class="fix" href="${esc(d.fixUrl)}" target="_blank" rel="noopener noreferrer">Open fix ↗</a></div>`:''}
+  </div>`;
+}
+document.getElementById('filters').addEventListener('click',e=>{
+  const b=e.target.closest('button'); if(!b)return;
+  [...document.querySelectorAll('#filters button')].forEach(x=>x.classList.remove('active'));
+  b.classList.add('active'); FILTER=b.dataset.f; render();
+});
+load();
+</script>
+</body>
+</html>
diff --git a/server.mjs b/server.mjs
new file mode 100644
index 0000000..bbe74ea
--- /dev/null
+++ b/server.mjs
@@ -0,0 +1,168 @@
+// API & Token Connection Dashboard — read-only status + fix links.
+// NEVER returns secret values to the browser; only last4 + live status.
+import http from 'node:http';
+import fs from 'node:fs';
+import path from 'node:path';
+import { fileURLToPath } from 'node:url';
+
+const __dirname = path.dirname(fileURLToPath(import.meta.url));
+const PORT = Number(process.env.PORT) || 9791;
+const AUTH = process.env.BASIC_AUTH || 'admin:DW2024!';
+const HOME = process.env.HOME;
+
+// ---- read-only env loaders ---------------------------------------------------
+function loadEnv(p) {
+  const c = {};
+  try {
+    for (const l of fs.readFileSync(p, 'utf8').split('\n')) {
+      const m = l.match(/^([A-Z0-9_]+)=(.*)$/);
+      if (m) c[m[1]] = m[2].trim().replace(/^["']|["']$/g, '');
+    }
+  } catch {}
+  return c;
+}
+const S = () => loadEnv(`${HOME}/Projects/secrets-manager/.env`);
+const G = () => loadEnv(`${HOME}/Projects/Designer-Wallcoverings/DW-MCP/.env`);
+const last4 = (v) => (v && v.length >= 4 ? '…' + v.slice(-4) : v ? '…' : '');
+
+// ---- probe helpers -----------------------------------------------------------
+const TIMEOUT = 9000;
+async function fetchT(url, opts = {}) {
+  const ac = new AbortController();
+  const t = setTimeout(() => ac.abort(), TIMEOUT);
+  try { return await fetch(url, { ...opts, signal: ac.signal }); }
+  finally { clearTimeout(t); }
+}
+const OK = (detail = '') => ({ status: 'connected', detail });
+const DEAD = (detail) => ({ status: 'dead', detail });
+const MISSING = (detail = 'not configured') => ({ status: 'missing', detail });
+const UNKNOWN = (detail) => ({ status: 'unknown', detail });
+
+async function googleRefresh(id, sec, rt) {
+  if (!id || !sec || !rt) return MISSING('client id / secret / refresh token absent');
+  try {
+    const r = await fetchT('https://oauth2.googleapis.com/token', {
+      method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
+      body: new URLSearchParams({ client_id: id, client_secret: sec, refresh_token: rt, grant_type: 'refresh_token' }),
+    });
+    const j = await r.json().catch(() => ({}));
+    return j.access_token ? OK() : DEAD(j.error || ('http ' + r.status));
+  } catch (e) { return UNKNOWN(e.name === 'AbortError' ? 'timeout' : e.message); }
+}
+
+// ---- the connection catalog --------------------------------------------------
+// Each item: { key, name, category, fixUrl, instructions, probe() }
+function catalog() {
+  const s = S(), g = G();
+  const gid = g.GMAIL_CLIENT_ID, gs = g.GMAIL_CLIENT_SECRET;
+  const items = [];
+  const add = (o) => items.push(o);
+
+  // ---- Google / George OAuth ----
+  const mailbox = (label, rt, envName) => add({
+    key: 'george-' + label, name: 'George: ' + label, category: 'Google · George Mail', tag: last4(rt),
+    fixUrl: 'http://127.0.0.1:9850/auth' + (label === 'steve-office' ? '' : '/' + label),
+    instructions: 'If DEAD: open the auth URL in a browser, sign in to the mailbox, grant consent. George mints a fresh refresh token (~7-day expiry in Testing mode).',
+    probe: () => googleRefresh(gid, gs, rt), envName,
+  });
+  mailbox('steve-office', g.GMAIL_REFRESH_TOKEN, 'GMAIL_REFRESH_TOKEN');
+  mailbox('info', g.INFO_REFRESH_TOKEN || g.GMAIL_INFO_REFRESH_TOKEN, 'INFO_REFRESH_TOKEN');
+  mailbox('steve-personal', g.STEVE_PERSONAL_REFRESH_TOKEN || g.PERSONAL_REFRESH_TOKEN, 'STEVE_PERSONAL_REFRESH_TOKEN');
+  mailbox('agentabrams', g.AGENTABRAMS_REFRESH_TOKEN, 'AGENTABRAMS_REFRESH_TOKEN');
+
+  add({
+    key: 'google-calendar', name: 'George Calendar (event create)', category: 'Google · George Mail', tag: last4(g.GOOGLE_CALENDAR_REFRESH_TOKEN),
+    fixUrl: 'https://console.developers.google.com/apis/api/calendar-json.googleapis.com/overview?project=493480746821',
+    instructions: 'ROOT CAUSE: Calendar API is DISABLED in project 493480746821 (403 accessNotConfigured), and the standalone token is dead. Fix: click the link → Enable the API. Then the code can use the live GMAIL client (which already has the calendar scope) — no re-consent needed.',
+    probe: () => googleRefresh(gid, gs, g.GOOGLE_CALENDAR_REFRESH_TOKEN), envName: 'GOOGLE_CALENDAR_REFRESH_TOKEN',
+  });
+
+  add({
+    key: 'google-drive', name: 'Google Drive', category: 'Google · Other', tag: last4(s.GOOGLE_DRIVE_REFRESH_TOKEN),
+    fixUrl: 'https://console.cloud.google.com/apis/credentials',
+    instructions: 'If MISSING/DEAD: re-run the Drive OAuth consent for the app that owns GOOGLE_DRIVE_CLIENT_ID, capture the new refresh token, route via the secrets skill.',
+    probe: () => googleRefresh(s.GOOGLE_DRIVE_CLIENT_ID, s.GOOGLE_DRIVE_CLIENT_SECRET, s.GOOGLE_DRIVE_REFRESH_TOKEN), envName: 'GOOGLE_DRIVE_REFRESH_TOKEN',
+  });
+
+  add({
+    key: 'ventura-scheduling', name: 'Ventura Smart-Scheduling', category: 'Google · Other', tag: last4(s.GOOGLE_OAUTH_CLIENT_ID),
+    fixUrl: 'https://venturacorridor.com/api/appointments/oauth/start?owner_email=info@designerwallcoverings.com',
+    instructions: 'NOT CONNECTED (0 owners consented) AND the venturacorridor.com domain is parked + the app server is down. Decide REVIVE (deploy + DNS + consent) vs SHELVE first. If reviving: bring the server up, then open the consent URL.',
+    probe: async () => {
+      try { const r = await fetchT('https://venturacorridor.com/api/appointments/oauth/status?owner_email=info@designerwallcoverings.com'); const j = await r.json().catch(() => ({})); return j.connected ? OK() : DEAD('parked/undeployed — 0 owners'); }
+      catch { return DEAD('server unreachable (parked/down)'); }
+    }, envName: 'GOOGLE_OAUTH_CLIENT_ID',
+  });
+
+  // ---- Meta ----
+  add({
+    key: 'meta', name: 'Meta — Instagram + Facebook', category: 'Social', tag: last4(s.META_ACCESS_TOKEN),
+    fixUrl: 'https://developers.facebook.com/tools/explorer/',
+    instructions: 'error 190 = expired. In Graph API Explorer select the DW app + Page, grant pages_manage_posts, instagram_basic, instagram_content_publish. Exchange for a 60-day token at /tools/debug/accesstoken, then route to META_ACCESS_TOKEN via secrets.',
+    probe: async () => {
+      if (!s.META_ACCESS_TOKEN) return MISSING();
+      try { const r = await fetchT('https://graph.facebook.com/v19.0/me?access_token=' + encodeURIComponent(s.META_ACCESS_TOKEN)); const j = await r.json().catch(() => ({})); return j.id ? OK() : DEAD('error ' + (j.error?.code || r.status)); }
+      catch (e) { return UNKNOWN(e.message); }
+    }, envName: 'META_ACCESS_TOKEN',
+  });
+
+  // ---- Programmatic API keys (read-only verify endpoints) ----
+  const key = (o) => add({ ...o, category: o.category || 'API keys' });
+  key({ key: 'anthropic', name: 'Anthropic', tag: last4(s.ANTHROPIC_API_KEY), fixUrl: 'https://console.anthropic.com/settings/keys', instructions: 'Rotate at the console → paste new key → secrets skill routes it.', envName: 'ANTHROPIC_API_KEY',
+    probe: async () => { if (!s.ANTHROPIC_API_KEY) return MISSING(); try { const r = await fetchT('https://api.anthropic.com/v1/models', { headers: { 'x-api-key': s.ANTHROPIC_API_KEY, 'anthropic-version': '2023-06-01' } }); return r.ok ? OK() : DEAD('http ' + r.status); } catch (e) { return UNKNOWN(e.message); } } });
+  key({ key: 'openai', name: 'OpenAI', tag: last4(s.OPENAI_API_KEY), fixUrl: 'https://platform.openai.com/api-keys', instructions: 'Rotate at platform.openai.com/api-keys.', envName: 'OPENAI_API_KEY',
+    probe: async () => { if (!s.OPENAI_API_KEY) return MISSING(); try { const r = await fetchT('https://api.openai.com/v1/models', { headers: { Authorization: 'Bearer ' + s.OPENAI_API_KEY } }); return r.ok ? OK() : DEAD('http ' + r.status); } catch (e) { return UNKNOWN(e.message); } } });
+  key({ key: 'gemini', name: 'Google Gemini', tag: last4(s.GEMINI_API_KEY), fixUrl: 'https://aistudio.google.com/app/apikey', instructions: 'Create/rotate at AI Studio → API keys.', envName: 'GEMINI_API_KEY',
+    probe: async () => { if (!s.GEMINI_API_KEY) return MISSING(); try { const r = await fetchT('https://generativelanguage.googleapis.com/v1beta/models?key=' + s.GEMINI_API_KEY); return r.ok ? OK() : DEAD('http ' + r.status); } catch (e) { return UNKNOWN(e.message); } } });
+  key({ key: 'replicate', name: 'Replicate', tag: last4(s.REPLICATE_API_TOKEN), fixUrl: 'https://replicate.com/account/api-tokens', instructions: 'Rotate at replicate.com/account/api-tokens.', envName: 'REPLICATE_API_TOKEN',
+    probe: async () => { if (!s.REPLICATE_API_TOKEN) return MISSING(); try { const r = await fetchT('https://api.replicate.com/v1/account', { headers: { Authorization: 'Token ' + s.REPLICATE_API_TOKEN } }); return r.ok ? OK() : DEAD('http ' + r.status); } catch (e) { return UNKNOWN(e.message); } } });
+  key({ key: 'elevenlabs', name: 'ElevenLabs', tag: last4(s.ELEVENLABS_API_KEY), fixUrl: 'https://elevenlabs.io/app/settings/api-keys', instructions: 'Rotate in ElevenLabs settings → API keys.', envName: 'ELEVENLABS_API_KEY',
+    probe: async () => { if (!s.ELEVENLABS_API_KEY) return MISSING(); try { const r = await fetchT('https://api.elevenlabs.io/v1/user', { headers: { 'xi-api-key': s.ELEVENLABS_API_KEY } }); return r.ok ? OK() : DEAD('http ' + r.status); } catch (e) { return UNKNOWN(e.message); } } });
+  key({ key: 'cloudflare', name: 'Cloudflare', tag: last4(s.CLOUDFLARE_API_TOKEN), fixUrl: 'https://dash.cloudflare.com/profile/api-tokens', instructions: 'Roll the token in CF dashboard → API tokens.', envName: 'CLOUDFLARE_API_TOKEN',
+    probe: async () => { if (!s.CLOUDFLARE_API_TOKEN) return MISSING(); try { const r = await fetchT('https://api.cloudflare.com/client/v4/user/tokens/verify', { headers: { Authorization: 'Bearer ' + s.CLOUDFLARE_API_TOKEN } }); const j = await r.json().catch(() => ({})); return j.success ? OK() : DEAD('http ' + r.status); } catch (e) { return UNKNOWN(e.message); } } });
+  key({ key: 'shopify', name: 'Shopify Admin', tag: last4(s.SHOPIFY_ADMIN_TOKEN), fixUrl: 'https://admin.shopify.com/settings/apps', instructions: 'Reinstall/rotate the custom app token in Shopify admin → Apps.', envName: 'SHOPIFY_ADMIN_TOKEN',
+    probe: async () => { const dom = s.SHOPIFY_STORE_DOMAIN, tok = s.SHOPIFY_ADMIN_TOKEN; if (!dom || !tok) return MISSING(); try { const r = await fetchT('https://' + dom + '/admin/api/2024-01/shop.json', { headers: { 'X-Shopify-Access-Token': tok } }); return r.ok ? OK() : DEAD('http ' + r.status); } catch (e) { return UNKNOWN(e.message); } } });
+  key({ key: 'stripe', name: 'Stripe', tag: last4(s.STRIPE_SECRET_KEY), fixUrl: 'https://dashboard.stripe.com/apikeys', instructions: 'Roll the secret key in the Stripe dashboard → Developers → API keys.', envName: 'STRIPE_SECRET_KEY',
+    probe: async () => { if (!s.STRIPE_SECRET_KEY) return MISSING(); try { const r = await fetchT('https://api.stripe.com/v1/balance', { headers: { Authorization: 'Bearer ' + s.STRIPE_SECRET_KEY } }); return r.ok ? OK() : DEAD('http ' + r.status); } catch (e) { return UNKNOWN(e.message); } } });
+  key({ key: 'twilio', name: 'Twilio', tag: last4(s.TWILIO_AUTH_TOKEN), fixUrl: 'https://console.twilio.com/', instructions: 'Rotate the auth token in the Twilio console.', envName: 'TWILIO_AUTH_TOKEN',
+    probe: async () => { const sid = s.TWILIO_ACCOUNT_SID, tok = s.TWILIO_AUTH_TOKEN; if (!sid || !tok) return MISSING(); try { const r = await fetchT('https://api.twilio.com/2010-04-01/Accounts/' + sid + '.json', { headers: { Authorization: 'Basic ' + Buffer.from(sid + ':' + tok).toString('base64') } }); return r.ok ? OK() : DEAD('http ' + r.status); } catch (e) { return UNKNOWN(e.message); } } });
+  key({ key: 'purelymail', name: 'Purelymail', tag: last4(s.PURELYMAIL_API_TOKEN), fixUrl: 'https://purelymail.com/manage/api', instructions: 'Regenerate the API token in Purelymail → Manage → API.', envName: 'PURELYMAIL_API_TOKEN',
+    probe: async () => { if (!s.PURELYMAIL_API_TOKEN) return MISSING(); try { const r = await fetchT('https://purelymail.com/api/v0/checkAccountCredit', { method: 'POST', headers: { 'Purelymail-Api-Token': s.PURELYMAIL_API_TOKEN, 'Content-Type': 'application/json' }, body: '{}' }); const j = await r.json().catch(() => ({})); return (j.type === 'success' || j.result) ? OK() : DEAD('http ' + r.status); } catch (e) { return UNKNOWN(e.message); } } });
+  key({ key: 'browserbase', name: 'Browserbase', tag: last4(s.BROWSERBASE_API_KEY), fixUrl: 'https://www.browserbase.com/settings', instructions: 'Rotate the API key in Browserbase settings.', envName: 'BROWSERBASE_API_KEY',
+    probe: async () => { if (!s.BROWSERBASE_API_KEY) return MISSING(); try { const r = await fetchT('https://api.browserbase.com/v1/projects', { headers: { 'X-BB-API-Key': s.BROWSERBASE_API_KEY } }); return r.ok ? OK() : DEAD('http ' + r.status); } catch (e) { return UNKNOWN(e.message); } } });
+  key({ key: 'moonshot', name: 'Moonshot (Kimi)', tag: last4(s.MOONSHOT_API_KEY), fixUrl: 'https://platform.moonshot.ai/console/api-keys', instructions: 'Rotate the key in the Moonshot console.', envName: 'MOONSHOT_API_KEY',
+    probe: async () => { if (!s.MOONSHOT_API_KEY) return MISSING(); try { const r = await fetchT('https://api.moonshot.ai/v1/models', { headers: { Authorization: 'Bearer ' + s.MOONSHOT_API_KEY } }); return r.ok ? OK() : DEAD('http ' + r.status); } catch (e) { return UNKNOWN(e.message); } } });
+  key({ key: 'godaddy', name: 'GoDaddy', tag: last4(s.GODADDY_API_KEY), fixUrl: 'https://developer.godaddy.com/keys', instructions: 'Create a production key at developer.godaddy.com/keys (OTE keys 403 on prod).', envName: 'GODADDY_API_KEY',
+    probe: async () => { const k = s.GODADDY_API_KEY, sec = s.GODADDY_API_SECRET; if (!k || !sec) return MISSING(); try { const r = await fetchT('https://api.godaddy.com/v1/domains?limit=1', { headers: { Authorization: 'sso-key ' + k + ':' + sec } }); return r.ok ? OK() : DEAD('http ' + r.status); } catch (e) { return UNKNOWN(e.message); } } });
+
+  return items;
+}
+
+// ---- server ------------------------------------------------------------------
+function unauthorized(res) { res.writeHead(401, { 'WWW-Authenticate': 'Basic realm="tokens"' }); res.end('auth required'); }
+function checkAuth(req) { const h = req.headers.authorization || ''; const m = h.match(/^Basic (.+)$/); if (!m) return false; return Buffer.from(m[1], 'base64').toString() === AUTH; }
+
+const server = http.createServer(async (req, res) => {
+  if (!checkAuth(req)) return unauthorized(res);
+  const url = new URL(req.url, 'http://x');
+  if (url.pathname === '/api/status') {
+    const items = catalog();
+    const results = await Promise.all(items.map(async (it) => {
+      let r; try { r = await it.probe(); } catch (e) { r = UNKNOWN(e.message); }
+      return { key: it.key, name: it.name, category: it.category, tag: it.tag || '', envName: it.envName || '',
+        fixUrl: it.fixUrl, instructions: it.instructions, status: r.status, detail: r.detail || '' };
+    }));
+    res.writeHead(200, { 'Content-Type': 'application/json' });
+    return res.end(JSON.stringify({ generatedAt: new Date().toISOString(), results }));
+  }
+  // static
+  let f = url.pathname === '/' ? '/index.html' : url.pathname;
+  const fp = path.join(__dirname, 'public', path.normalize(f).replace(/^(\.\.[/\\])+/, ''));
+  fs.readFile(fp, (err, data) => {
+    if (err) { res.writeHead(404); return res.end('not found'); }
+    const ext = path.extname(fp);
+    const ct = ext === '.html' ? 'text/html' : ext === '.js' ? 'text/javascript' : ext === '.css' ? 'text/css' : 'application/octet-stream';
+    res.writeHead(200, { 'Content-Type': ct }); res.end(data);
+  });
+});
+server.listen(PORT, () => console.log(`[api-token-dashboard] http://127.0.0.1:${PORT}  (auth ${AUTH.split(':')[0]}/****)`));

(oldest)  ·  back to Api Token Dashboard  ·  dashboard: read Drive token from its real .env (thesetdecora 4c55a10 →