[object Object]

← back to Cred Launcher

cred-launcher: localhost credential launcher served on :51858, launchd keep-alive

ae8f127c1964b81995f72ce77464a6144cd5dbd0 · 2026-09-22 16:49:12 -0700 · Steve Abrams

Serves the 132-credential launcher page at http://127.0.0.1:51858/ so its
provider-console Launch links open in a real browser. Kept alive by
com.steve.credlauncher (KeepAlive=true, RunAtLoad=true).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

Files touched

Diff

commit ae8f127c1964b81995f72ce77464a6144cd5dbd0
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Tue Sep 22 16:49:12 2026 -0700

    cred-launcher: localhost credential launcher served on :51858, launchd keep-alive
    
    Serves the 132-credential launcher page at http://127.0.0.1:51858/ so its
    provider-console Launch links open in a real browser. Kept alive by
    com.steve.credlauncher (KeepAlive=true, RunAtLoad=true).
    
    Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---
 .gitignore |   8 +
 page.html  | 596 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 server.js  |  44 +++++
 3 files changed, 648 insertions(+)

diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..1924158
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,8 @@
+node_modules/
+.env*
+tmp/
+*.log
+.DS_Store
+dist/
+build/
+.next/
diff --git a/page.html b/page.html
new file mode 100644
index 0000000..293a112
--- /dev/null
+++ b/page.html
@@ -0,0 +1,596 @@
+<title>Credential Launcher</title>
+<link rel="preconnect" href="https://fonts.googleapis.com">
+<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
+<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=IBM+Plex+Sans:wght@400;500;600;700&family=IBM+Plex+Mono:wght@400;500;600&display=swap">
+<style>
+  :root{
+    --bg:#f4f6f8; --surface:#ffffff; --surface-2:#eef1f5; --text:#171c23; --muted:#5c6773;
+    --border:#dde2e9; --border-strong:#c6cdd6;
+    --accent:#0e7490; --accent-ink:#ffffff; --accent-soft:#d7eef3;
+    --warn:#b45309; --warn-soft:#fbebd5; --warn-bd:#e6b877;
+    --good:#15803d; --good-soft:#dcf1e2; --good-bd:#8fd0a4;
+    --danger:#be123c; --danger-soft:#fbe0e6; --danger-bd:#eaa3b3;
+    --shadow:0 1px 2px rgba(20,28,38,.06), 0 4px 16px rgba(20,28,38,.05);
+    --radius:12px;
+    color-scheme:light;
+  }
+  :root:not([data-theme="light"]){ @media (prefers-color-scheme: dark){
+    --bg:#0d1117; --surface:#161b22; --surface-2:#1c232d; --text:#e6ebf2; --muted:#9aa5b3;
+    --border:#2a313c; --border-strong:#3a434f;
+    --accent:#22d3ee; --accent-ink:#04222a; --accent-soft:#0f3a44;
+    --warn:#f59e0b; --warn-soft:#3a2b0f; --warn-bd:#7a5a1c;
+    --good:#4ade80; --good-soft:#123222; --good-bd:#1f5a3a;
+    --danger:#fb7185; --danger-soft:#3a1620; --danger-bd:#7a2a3a;
+    --shadow:0 1px 2px rgba(0,0,0,.4), 0 6px 20px rgba(0,0,0,.35);
+    color-scheme:dark;
+  }}
+  :root[data-theme="dark"]{
+    --bg:#0d1117; --surface:#161b22; --surface-2:#1c232d; --text:#e6ebf2; --muted:#9aa5b3;
+    --border:#2a313c; --border-strong:#3a434f;
+    --accent:#22d3ee; --accent-ink:#04222a; --accent-soft:#0f3a44;
+    --warn:#f59e0b; --warn-soft:#3a2b0f; --warn-bd:#7a5a1c;
+    --good:#4ade80; --good-soft:#123222; --good-bd:#1f5a3a;
+    --danger:#fb7185; --danger-soft:#3a1620; --danger-bd:#7a2a3a;
+    --shadow:0 1px 2px rgba(0,0,0,.4), 0 6px 20px rgba(0,0,0,.35);
+    color-scheme:dark;
+  }
+
+  *{box-sizing:border-box}
+  body{background:var(--bg);color:var(--text);font-family:"IBM Plex Sans",system-ui,-apple-system,sans-serif;line-height:1.45;-webkit-font-smoothing:antialiased}
+  .wrap{max-width:1080px;margin:0 auto;padding-inline:16px;padding-block:0 120px}
+  .mono{font-family:"IBM Plex Mono",ui-monospace,SFMono-Regular,Menlo,monospace}
+
+  /* header */
+  header.top{position:sticky;top:env(safe-area-inset-top,0px);z-index:20;background:var(--bg);
+    border-bottom:1px solid var(--border);padding-block:14px 10px}
+  .title-row{display:flex;align-items:baseline;gap:12px;flex-wrap:wrap}
+  h1{font-size:1.35rem;font-weight:700;margin:0;letter-spacing:-.01em}
+  .sub{color:var(--muted);font-size:.82rem}
+  .stats{display:flex;gap:8px;flex-wrap:wrap;margin-top:10px}
+  .stat{display:flex;align-items:center;gap:6px;background:var(--surface);border:1px solid var(--border);
+    border-radius:999px;padding:4px 11px;font-size:.76rem;font-weight:500}
+  .stat b{font-weight:700;font-variant-numeric:tabular-nums}
+  .dot{width:8px;height:8px;border-radius:50%;flex:none}
+  .dot.warn{background:var(--warn)} .dot.good{background:var(--good)}
+  .dot.ext{background:var(--accent)} .dot.int{background:var(--border-strong)}
+
+  .controls{display:flex;gap:8px;flex-wrap:wrap;margin-top:12px;align-items:center}
+  .search{flex:1;min-width:190px;display:flex;align-items:center;gap:8px;background:var(--surface);
+    border:1px solid var(--border-strong);border-radius:10px;padding:8px 12px}
+  .search input{border:0;background:transparent;color:var(--text);font:inherit;width:100%;outline:none}
+  .search svg{flex:none;color:var(--muted)}
+  select,.seg button{font:inherit;color:var(--text)}
+  select{background:var(--surface);border:1px solid var(--border-strong);border-radius:10px;padding:8px 10px;font-size:.82rem}
+  .seg{display:flex;background:var(--surface);border:1px solid var(--border-strong);border-radius:10px;overflow:hidden}
+  .seg button{background:transparent;border:0;padding:8px 11px;font-size:.78rem;cursor:pointer;color:var(--muted)}
+  .seg button[aria-pressed="true"]{background:var(--accent);color:var(--accent-ink);font-weight:600}
+
+  .chips{display:flex;gap:7px;flex-wrap:wrap;margin-top:10px}
+  .chip{background:var(--surface);border:1px solid var(--border-strong);border-radius:999px;
+    padding:5px 12px;font-size:.78rem;cursor:pointer;color:var(--muted);white-space:nowrap}
+  .chip[aria-pressed="true"]{background:var(--text);color:var(--bg);border-color:var(--text);font-weight:600}
+  .chip .n{opacity:.6;font-variant-numeric:tabular-nums;margin-left:2px}
+
+  /* sections */
+  .sec-label{display:flex;align-items:center;gap:8px;margin:22px 2px 10px;font-size:.72rem;
+    font-weight:700;letter-spacing:.09em;text-transform:uppercase;color:var(--muted)}
+  .sec-label .rule{flex:1;height:1px;background:var(--border)}
+  .sec-label.attn{color:var(--warn)}
+
+  /* rows */
+  .list{display:flex;flex-direction:column;gap:8px}
+  .row{display:grid;grid-template-columns:auto 1fr auto;gap:12px;align-items:center;
+    background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);
+    padding:13px 15px;box-shadow:var(--shadow);cursor:pointer;transition:border-color .12s,transform .06s}
+  .row:hover{border-color:var(--border-strong)}
+  .row.sel{border-color:var(--accent);box-shadow:0 0 0 2px var(--accent-soft),var(--shadow)}
+  .row.attn{border-left:4px solid var(--warn)}
+  .row.done{opacity:.5}
+  .row.done .kname{text-decoration:line-through}
+
+  .rmark{width:20px;height:20px;border-radius:50%;border:2px solid var(--border-strong);flex:none;
+    display:grid;place-items:center;color:transparent}
+  .row.sel .rmark{border-color:var(--accent);background:var(--accent);color:var(--accent-ink)}
+
+  .rmid{min-width:0}
+  .kname{font-family:"IBM Plex Mono",monospace;font-weight:600;font-size:.9rem;word-break:break-all}
+  .meta{display:flex;align-items:center;gap:8px;flex-wrap:wrap;margin-top:3px}
+  .prov{font-size:.8rem;font-weight:600;color:var(--accent)}
+  .badge{font-size:.66rem;font-weight:700;letter-spacing:.04em;text-transform:uppercase;
+    padding:2px 7px;border-radius:6px;border:1px solid transparent;white-space:nowrap}
+  .badge.rot{background:var(--warn-soft);color:var(--warn);border-color:var(--warn-bd)}
+  .badge.live{background:var(--good-soft);color:var(--good);border-color:var(--good-bd)}
+  .badge.alias{background:var(--danger-soft);color:var(--danger);border-color:var(--danger-bd)}
+  .badge.int{background:var(--surface-2);color:var(--muted);border-color:var(--border)}
+  .label{font-size:.8rem;color:var(--muted);margin-top:4px;overflow:hidden;text-overflow:ellipsis;
+    display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical}
+  .dest{font-size:.72rem;color:var(--muted);margin-top:4px;opacity:.85}
+
+  .rright{display:flex;flex-direction:column;align-items:flex-end;gap:8px}
+  a.launch{display:inline-flex;align-items:center;gap:6px;text-decoration:none;font-size:.8rem;font-weight:600;
+    background:var(--accent);color:var(--accent-ink);border:1px solid var(--accent);
+    padding:7px 13px;border-radius:9px;white-space:nowrap}
+  a.launch.ghost{background:transparent;color:var(--muted);border-color:var(--border-strong)}
+  a.launch:hover{filter:brightness(1.06)}
+  .nocon{font-size:.72rem;color:var(--muted);font-style:italic;padding:7px 4px}
+  .donebtn{display:inline-flex;align-items:center;gap:5px;font-size:.72rem;color:var(--muted);
+    background:transparent;border:0;cursor:pointer;padding:2px 4px}
+  .donebtn:hover{color:var(--good)}
+  .doneck{width:14px;height:14px;border:1.5px solid var(--border-strong);border-radius:4px;display:grid;place-items:center;color:transparent}
+  .row.done .doneck{background:var(--good);border-color:var(--good);color:#fff}
+
+  .empty{text-align:center;color:var(--muted);padding:40px 0;font-size:.9rem}
+
+  /* launch bar */
+  .launchbar{position:fixed;left:0;right:0;bottom:0;z-index:30;
+    background:var(--surface);border-top:1px solid var(--border-strong);
+    padding:12px 16px calc(12px + env(safe-area-inset-bottom,0px));
+    box-shadow:0 -4px 20px rgba(0,0,0,.08);transform:translateY(120%);transition:transform .18s ease}
+  .launchbar.show{transform:translateY(0)}
+  .lb-inner{max-width:1080px;margin:0 auto;display:flex;align-items:center;gap:12px}
+  .lb-txt{min-width:0;flex:1}
+  .lb-k{font-family:"IBM Plex Mono",monospace;font-weight:600;font-size:.85rem;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
+  .lb-p{font-size:.75rem;color:var(--muted)}
+  a.lb-go{display:inline-flex;align-items:center;gap:8px;text-decoration:none;font-weight:700;font-size:.9rem;
+    background:var(--accent);color:var(--accent-ink);padding:11px 20px;border-radius:11px;white-space:nowrap}
+  a.lb-go[aria-disabled="true"]{opacity:.4;pointer-events:none}
+  .lb-close{background:transparent;border:0;color:var(--muted);font-size:1.3rem;cursor:pointer;line-height:1;padding:4px 6px}
+
+  @media (max-width:560px){
+    .row{grid-template-columns:auto 1fr;grid-template-areas:"mark mid" "right right"}
+    .rmark{grid-area:mark}.rmid{grid-area:mid}.rright{grid-area:right;flex-direction:row;justify-content:flex-end;align-items:center}
+  }
+  @media (prefers-reduced-motion:reduce){*{transition:none!important}}
+
+  /* compact density */
+  body.compact .row{padding:8px 13px}
+  body.compact .label,body.compact .dest{display:none}
+  body.compact .meta{margin-top:1px}
+</style>
+
+<div class="wrap">
+  <header class="top">
+    <div class="title-row">
+      <h1>Credential Launcher</h1>
+      <span class="sub">click a credential → open its provider console to view / rotate</span>
+    </div>
+    <div class="stats" id="stats"></div>
+    <div class="controls">
+      <label class="search">
+        <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="11" cy="11" r="7"/><path d="m21 21-4.3-4.3"/></svg>
+        <input id="q" type="text" placeholder="Search key, provider, or note…" autocomplete="off" spellcheck="false">
+      </label>
+      <select id="sort" aria-label="Sort">
+        <option value="attn">Sort: Attention first</option>
+        <option value="prov">Sort: Provider A→Z</option>
+        <option value="key">Sort: Key A→Z</option>
+        <option value="type">Sort: Type</option>
+      </select>
+      <div class="seg" role="group" aria-label="Density">
+        <button id="d-comf" aria-pressed="true">Comfortable</button>
+        <button id="d-comp" aria-pressed="false">Compact</button>
+      </div>
+    </div>
+    <div class="chips" id="chips"></div>
+  </header>
+
+  <main id="main"></main>
+</div>
+
+<div class="launchbar" id="lbar">
+  <div class="lb-inner">
+    <div class="lb-txt">
+      <div class="lb-k" id="lb-k">—</div>
+      <div class="lb-p" id="lb-p"></div>
+    </div>
+    <a class="lb-go" id="lb-go" target="_blank" rel="noopener" aria-disabled="true">Launch ↗</a>
+    <button class="lb-close" id="lb-close" aria-label="Clear selection">✕</button>
+  </div>
+</div>
+
+<script type="application/json" id="creds">
+[
+  {"key":"IG_AGENT_AUTH","label":"Norma instagram-agent HTTP Authorization header for DW reels publisher","provider":"Instagram","type":"internal","status":"registered","dest":"1 project/env file"},
+  {"key":"SI_API_KEY","label":"api.data.gov key (Smithsonian Open Access + other data.gov APIs) — CelebritySignatures artists harvest; account steve@designerwallcoverings.com, created 2025-08-25","provider":"data.gov / Smithsonian","type":"external_api","status":"registered","dest":"1 project/env file"},
+  {"key":"PRINTIFY_API_TOKEN","label":"Printify personal access token — CelebritySignatures standalone wear fulfillment","provider":"Printify","type":"external_api","status":"registered","dest":"1 project/env file"},
+  {"key":"PG_DW_ADMIN_PASSWORD","label":"Postgres dw_admin role password (local Mac2 + prod via prestage 3b). DSN_REWRITE rewrites DATABASE_URL pw in each env_file dest.","provider":"PostgreSQL (local/Kamatera)","type":"internal","status":"registered","dest":"4 project/env files"},
+  {"key":"REPLICATE_API_TOKEN","label":"Replicate API Token — wallco.ai SDXL gen fallback + animate-museum-posts + interiordesignershowroom scene/hero regen","provider":"Replicate","type":"external_api","status":"registered","dest":"3 project/env files, ssh to Kamatera (1)"},
+  {"key":"GOOGLE_PLACES_API_KEY_GOVARB","label":"Google Places API Key - govarbitrage ONLY (Places API New)","provider":"Google Cloud (Places)","type":"external_api","status":"registered","dest":"1 project/env file [clean replacement for a prior leaked shared key]"},
+  {"key":"GOOGLE_PLACES_API_KEY_RESTAURANT","label":"Google Places API Key - restaurant-directory ONLY (Places API Legacy)","provider":"Google Cloud (Places)","type":"external_api","status":"registered","dest":"1 project/env file"},
+  {"key":"BRAVE_SEARCH_API_KEY","label":"Brave Search API (Web)","provider":"Brave Search","type":"external_api","status":"registered","dest":"2 project/env files"},
+  {"key":"MOONSHOT_API_KEY","label":"Moonshot AI / Kimi K2 API Key","provider":"Moonshot AI (Kimi)","type":"external_api","status":"registered","dest":"1 project/env file, 1 skill env, ssh to Kamatera (2)"},
+  {"key":"CLOUDFLARE_API_TOKEN","label":"Cloudflare API Token","provider":"Cloudflare","type":"external_api","status":"registered","dest":"7 project/env files, 3 skill envs"},
+  {"key":"STRIPE_TEST_SECRET_KEY","label":"Stripe test secret key — Charge & Explore test-mode billing only","provider":"Stripe","type":"external_api","status":"registered","dest":"ssh to Kamatera (1)"},
+  {"key":"STRIPE_TEST_WEBHOOK_SECRET","label":"Stripe test webhook signing secret — Charge & Explore","provider":"Stripe","type":"external_api","status":"registered","dest":"ssh to Kamatera (1)"},
+  {"key":"STRIPE_SECRET_KEY","label":"Stripe Secret Key","provider":"Stripe","type":"external_api","status":"registered","dest":"10 project/env files, 3 site-local domains, ssh to Kamatera (2)"},
+  {"key":"STRIPE_PUBLISHABLE_KEY","label":"Stripe Publishable Key","provider":"Stripe","type":"external_api","status":"registered","dest":"6 project/env files, 3 site-local domains, ssh to Kamatera (2)"},
+  {"key":"STRIPE_WEBHOOK_SECRET","label":"Stripe Webhook Signing Secret","provider":"Stripe","type":"external_api","status":"registered","dest":"2 project/env files, 3 site-local domains"},
+  {"key":"STRIPE_TIER_WEBHOOK_SECRET","label":"Stripe Tier-Subscription Webhook Signing Secret (Counsel & Bar Lawyer Pro $29/mo)","provider":"Stripe","type":"external_api","status":"registered","dest":"1 project/env file"},
+  {"key":"STRIPE_PRICE_LAWYER_PRO_MONTHLY","label":"Stripe Price ID (public): Counsel & Bar Lawyer Pro $29/mo recurring","provider":"Stripe","type":"internal","status":"registered","dest":"1 project/env file"},
+  {"key":"STRIPE_VCL_WEBHOOK_SECRET","label":"Stripe webhook signing secret — VCL endpoint we_1TUF1Y63uNmiRsMbNr1kLu52","provider":"Stripe","type":"external_api","status":"registered","dest":"1 project/env file, ssh to Kamatera (1)"},
+  {"key":"STRIPE_PRICE_VCL_STARTER_MONTH","label":"Stripe Price ID — VCL Starter $49/mo","provider":"Stripe","type":"internal","status":"registered","dest":"1 project/env file, ssh to Kamatera (1)"},
+  {"key":"STRIPE_PRICE_VCL_STANDARD_MONTH","label":"Stripe Price ID — VCL Standard $99/mo","provider":"Stripe","type":"internal","status":"registered","dest":"1 project/env file, ssh to Kamatera (1)"},
+  {"key":"STRIPE_PRICE_VCL_PREMIER_MONTH","label":"Stripe Price ID — VCL Premier $199/mo","provider":"Stripe","type":"internal","status":"registered","dest":"1 project/env file, ssh to Kamatera (1)"},
+  {"key":"HEYGEN_API_KEY","label":"HeyGen API key — talking-head avatar video render (session-debrief skill)","provider":"HeyGen","type":"external_api","status":"registered","dest":"4 project/env files, 4 skill envs"},
+  {"key":"INFO_VENTURACLAW_COM_PASSWORD","label":"Purelymail mailbox password — info@venturaclaw.com","provider":"PurelyMail","type":"internal","status":"registered","dest":"2 project/env files, ssh to Kamatera (2)"},
+  {"key":"INFO_NATIONALPAPERHANGERS_COM_PASSWORD","label":"Purelymail mailbox password — info@nationalpaperhangers.com","provider":"PurelyMail","type":"internal","status":"registered","dest":"1 project/env file, ssh to Kamatera (1)"},
+  {"key":"STRIPE_NPH_WEBHOOK_SECRET","label":"Stripe Webhook Signing Secret — NPH endpoint we_1TUDHV63uNmiRsMbRJea7EqU","provider":"Stripe","type":"external_api","status":"registered","dest":"1 project/env file, ssh to Kamatera (1)"},
+  {"key":"STRIPE_PRICE_PRO_MONTH","label":"Stripe Price ID — NPH Pro $39/mo","provider":"Stripe","type":"internal","status":"registered","dest":"1 project/env file, ssh to Kamatera (1)"},
+  {"key":"STRIPE_PRICE_PRO_YEAR","label":"Stripe Price ID — NPH Pro $399/yr","provider":"Stripe","type":"internal","status":"registered","dest":"1 project/env file, ssh to Kamatera (1)"},
+  {"key":"STRIPE_PRICE_SIGNATURE_MONTH","label":"Stripe Price ID — NPH Signature $149/mo","provider":"Stripe","type":"internal","status":"registered","dest":"1 project/env file, ssh to Kamatera (1)"},
+  {"key":"STRIPE_PRICE_SIGNATURE_YEAR","label":"Stripe Price ID — NPH Signature $1500/yr","provider":"Stripe","type":"internal","status":"registered","dest":"1 project/env file, ssh to Kamatera (1)"},
+  {"key":"STRIPE_PRICE_ENTERPRISE_MONTH","label":"Stripe Price ID — NPH Enterprise $399/mo","provider":"Stripe","type":"internal","status":"registered","dest":"1 project/env file, ssh to Kamatera (1)"},
+  {"key":"VERCEL_TOKEN","label":"Vercel API Token","provider":"Vercel","type":"external_api","status":"registered","dest":"1 project/env file"},
+  {"key":"PURELYMAIL_API_TOKEN","label":"PurelyMail API Token","provider":"PurelyMail","type":"external_api","status":"registered","dest":"3 project/env files, 1 MCP server"},
+  {"key":"GODADDY_API_KEY","label":"GoDaddy API Key","provider":"GoDaddy","type":"external_api","status":"registered","dest":"1 MCP server"},
+  {"key":"GODADDY_API_SECRET","label":"GoDaddy API Secret","provider":"GoDaddy","type":"external_api","status":"registered","dest":"1 MCP server"},
+  {"key":"BROWSERBASE_API_KEY","label":"Browserbase API Key","provider":"Browserbase","type":"external_api","status":"registered","dest":"5 project/env files, 1 skill env, ssh to Kamatera (1)"},
+  {"key":"BROWSERBASE_PROJECT_ID","label":"Browserbase Project ID","provider":"Browserbase","type":"internal","status":"registered","dest":"5 project/env files, 1 skill env, ssh to Kamatera (1)"},
+  {"key":"ANTHROPIC_API_KEY","label":"Anthropic API Key — COMPROMISED via git history in DW-Agents/ecosystem.config.cjs. Rotate at console.anthropic.com, then route via secrets skill + set on Kamatera /root/DW-Agents/.env.","provider":"Anthropic","type":"external_api","status":"needs_rotation","dest":"2 project/env files"},
+  {"key":"OPENAI_API_KEY","label":"OpenAI API Key","provider":"OpenAI","type":"external_api","status":"registered","dest":"8 project/env files, ssh to Kamatera (3)"},
+  {"key":"GEMINI_API_KEY","label":"Google Gemini API Key","provider":"Google AI Studio (Gemini)","type":"external_api","status":"registered","dest":"2 project/env files, 2 skill envs"},
+  {"key":"GEMINI_API_KEY_DW_ENRICH","label":"Gemini key DEDICATED to DW image-enrichment / vendor-command-center. Pre-staged empty; rotation target for prior leaked orphan keys.","provider":"Google AI Studio (Gemini)","type":"external_api","status":"registered","dest":"1 project/env file, ssh to Kamatera (1) [pre-staged rotation alias]"},
+  {"key":"GEMINI_API_KEY_WALLCO","label":"Gemini key DEDICATED to wallco.ai gen pipeline (nano-banana / gemini-2.5-flash-image fallback).","provider":"Google AI Studio (Gemini)","type":"external_api","status":"registered","dest":"2 project/env files, ssh to Kamatera (1)"},
+  {"key":"GOOGLE_GENERATIVE_AI_API_KEY","label":"Google Generative AI API Key (alias of GEMINI_API_KEY)","provider":"Google AI Studio (Gemini)","type":"external_api","status":"registered","dest":"1 project/env file"},
+  {"key":"SHOPIFY_ADMIN_TOKEN","label":"Shopify Admin API Access Token — COMPROMISED via git history in DW-Agents/ecosystem.config.cjs. Regenerate in the DW store admin (Apps → Develop apps).","provider":"Shopify","type":"external_api","status":"needs_rotation","dest":"5 project/env files"},
+  {"key":"SHOPIFY_ADMIN_ACCESS_TOKEN","label":"Shopify Admin token — ACCESS-name alias of SHOPIFY_ADMIN_TOKEN (same DW live store). Rotate TOGETHER with SHOPIFY_ADMIN_TOKEN.","provider":"Shopify","type":"external_api","status":"alias_rotate","dest":"2 project/env files"},
+  {"key":"SHOPIFY_API_KEY","label":"Shopify API Key","provider":"Shopify","type":"external_api","status":"registered","dest":"no destinations registered"},
+  {"key":"RESEND_API_KEY","label":"Resend API Key","provider":"Resend","type":"external_api","status":"registered","dest":"no destinations registered"},
+  {"key":"STRIPE_RESTRICTED_KEY","label":"Stripe Restricted Key (site-factory)","provider":"Stripe","type":"external_api","status":"registered","dest":"1 project/env file"},
+  {"key":"DPLA_API_KEY","label":"Digital Public Library of America API Key","provider":"DPLA","type":"external_api","status":"registered","dest":"3 project/env files, 1 skill env"},
+  {"key":"GEMINI_API_KEY_RALPH","label":"Gemini key (dedicated to Letsbegin Ralph runner)","provider":"Google AI Studio (Gemini)","type":"external_api","status":"registered","dest":"1 project/env file, ssh to Kamatera (1)"},
+  {"key":"ELEVENLABS_API_KEY","label":"ElevenLabs Voice Synthesis API Key","provider":"ElevenLabs","type":"external_api","status":"registered","dest":"2 project/env files, ssh to Kamatera (1)"},
+  {"key":"NEXT_PUBLIC_GOOGLE_MAPS_API_KEY","label":"Google Maps JavaScript API Key (browser-exposed; needs HTTP-referrer restrictions)","provider":"Google Cloud (Maps)","type":"external_api","status":"registered","dest":"6 project/env files, ssh to Kamatera (2)"},
+  {"key":"LINKEDIN_CLIENT_ID","label":"LinkedIn OAuth Client ID — 'For Claude - 5-6-26' app, bound to Designer Wallcoverings Page","provider":"LinkedIn","type":"external_api","status":"registered","dest":"1 project/env file, ssh to Kamatera (1)"},
+  {"key":"LINKEDIN_CLIENT_SECRET","label":"LinkedIn OAuth Client Secret — 'For Claude - 5-6-26' app","provider":"LinkedIn","type":"external_api","status":"registered","dest":"1 project/env file, ssh to Kamatera (1)"},
+  {"key":"GOOGLE_OAUTH_CLIENT_ID","label":"Google OAuth Client ID — DW Activation Calendar (local :9765)","provider":"Google Cloud (OAuth)","type":"external_api","status":"registered","dest":"1 project/env file"},
+  {"key":"GOOGLE_OAUTH_CLIENT_SECRET","label":"Google OAuth Client Secret — DW Activation Calendar (local :9765)","provider":"Google Cloud (OAuth)","type":"external_api","status":"registered","dest":"1 project/env file"},
+  {"key":"NPH_GOOGLE_OAUTH_CLIENT_ID","label":"Google OAuth Client ID — buyer sign-in on National Paper Hangers","provider":"Google Cloud (OAuth)","type":"external_api","status":"registered","dest":"1 project/env file, ssh to Kamatera (1)"},
+  {"key":"NPH_GOOGLE_OAUTH_CLIENT_SECRET","label":"Google OAuth Client Secret — buyer sign-in on National Paper Hangers","provider":"Google Cloud (OAuth)","type":"external_api","status":"registered","dest":"1 project/env file, ssh to Kamatera (1)"},
+  {"key":"GEORGE_AUTH","label":"George Gmail Basic Auth (DW microsite leads)","provider":"George Gmail relay (own service)","type":"internal","status":"registered","dest":"52 project/env files"},
+  {"key":"CLOUDFLARE_PAGES_TOKEN","label":"Cloudflare Pages API Token","provider":"Cloudflare","type":"external_api","status":"registered","dest":"1 project/env file"},
+  {"key":"SHOPIFY_THEME_TOKEN","label":"Shopify full-access theme/files token (read_themes+files)","provider":"Shopify","type":"external_api","status":"registered","dest":"3 project/env files"},
+  {"key":"SLACK_APP_ID","label":"Slack app 'Claude Access to Slack' — App ID (A0BDER23QRK). Not secret; registered for completeness.","provider":"Slack","type":"internal","status":"registered","dest":"1 project/env file"},
+  {"key":"SLACK_CLIENT_ID","label":"Slack app Client ID — used with Client Secret in oauth.v2.access requests.","provider":"Slack","type":"external_api","status":"registered","dest":"1 project/env file"},
+  {"key":"SLACK_CLIENT_SECRET","label":"Slack app Client Secret — OAuth token exchange. NOT the bot token.","provider":"Slack","type":"external_api","status":"registered","dest":"1 project/env file"},
+  {"key":"SLACK_SIGNING_SECRET","label":"Slack app Signing Secret — verifies inbound request signatures.","provider":"Slack","type":"external_api","status":"registered","dest":"1 project/env file"},
+  {"key":"SLACK_VERIFICATION_TOKEN","label":"Slack DEPRECATED verification token — prefer SLACK_SIGNING_SECRET.","provider":"Slack","type":"external_api","status":"registered","dest":"1 project/env file"},
+  {"key":"SLACK_APP_TOKEN","label":"Slack app-level token (xapp-1-…) for Socket Mode — connections:write. NOT the bot token.","provider":"Slack","type":"external_api","status":"registered","dest":"1 project/env file"},
+  {"key":"CENSUS_API_KEY","label":"US Census Bureau Data API key — commercialrealestate ACS5 by ZCTA. Verifier can't probe (302); verify via ACS5 curl.","provider":"US Census Bureau","type":"external_api","status":"registered","dest":"1 project/env file"},
+  {"key":"POSTIZ_API_KEY","label":"Postiz public API key — DW marketing slideshow engine + Postiz MCP","provider":"Postiz","type":"external_api","status":"registered","dest":"1 project/env file"},
+  {"key":"X_API_KEY","label":"X (Twitter) API v2 — Consumer/API key. Pairs with X_API_SECRET.","provider":"X (Twitter)","type":"external_api","status":"registered","dest":"1 project/env file"},
+  {"key":"X_API_SECRET","label":"X (Twitter) API v2 — Consumer/API secret.","provider":"X (Twitter)","type":"external_api","status":"registered","dest":"1 project/env file"},
+  {"key":"X_BEARER_TOKEN","label":"X (Twitter) API v2 — app-only Bearer token (use RAW/as-issued).","provider":"X (Twitter)","type":"external_api","status":"registered","dest":"1 project/env file"},
+  {"key":"MAGIC_API_KEY","label":"21st.dev Magic (Four Horsemen) API Key","provider":"21st.dev Magic","type":"external_api","status":"registered","dest":"1 MCP server"},
+  {"key":"ADOBE_CLIENT_ID","label":"Adobe Firefly Services / Photoshop API client id (model-arena)","provider":"Adobe","type":"external_api","status":"registered","dest":"1 project/env file"},
+  {"key":"ADOBE_CLIENT_SECRET","label":"Adobe Firefly Services / Photoshop API client secret (model-arena)","provider":"Adobe","type":"external_api","status":"registered","dest":"1 project/env file"},
+  {"key":"FIGMA_TOKEN","label":"Figma personal access token, read-only (model-arena + hyperframes imports)","provider":"Figma","type":"external_api","status":"registered","dest":"1 project/env file"},
+  {"key":"BUTLR_OWNER_TOKEN","label":"BUTLR_OWNER_TOKEN","provider":"Butlr","type":"external_api","status":"registered","dest":"1 project/env file"},
+  {"key":"DEEPSEEK_API_KEY","label":"DeepSeek platform API key — DeepSeek Harness CLI (dsh)","provider":"DeepSeek","type":"external_api","status":"registered","dest":"no destinations registered"},
+  {"key":"TYPESAFE_API_KEY","label":"TypeSafe (typesafe.ai) Jev /v1/systemone API key — jev-ultrafast; created 2026-09-22","provider":"TypeSafe","type":"external_api","status":"registered","dest":"1 project/env file"},
+  {"key":"TEXT_MODEL_API_KEY","label":"OpenRouter API key (sk-or-v1) — jev-ultrafast TYPE_TEXT helper; created 2026-09-22","provider":"OpenRouter","type":"external_api","status":"registered","dest":"1 project/env file"},
+  {"key":"CLOUDFLARE_ACCOUNT_ID","label":"Cloudflare Account ID (identifier, not secret)","provider":"Cloudflare","type":"internal","status":"registered","dest":"7 project/env files"},
+  {"key":"SMB_BUILDER_ADMIN_TOKEN","label":"Small Business Builder admin token (own app)","provider":"Small Business Builder (own app)","type":"internal","status":"registered","dest":"1 project/env file"},
+  {"key":"SHOPIFY_ORDERS_TOKEN","label":"Shopify Admin token (orders+products scope, weekend-report1-1 Partner app)","provider":"Shopify","type":"external_api","status":"registered","dest":"1 skill env"},
+  {"key":"SPOONFLOWER_EMAIL","label":"Spoonflower login email","provider":"Spoonflower (login)","type":"internal","status":"registered","dest":"2 project/env files"},
+  {"key":"SPOONFLOWER_PASSWORD","label":"Spoonflower login password","provider":"Spoonflower (login)","type":"internal","status":"registered","dest":"2 project/env files"},
+  {"key":"GOOGLE_DRIVE_CLIENT_ID","label":"Google Drive OAuth Client ID — TSD photo-import-cli desktop app","provider":"Google Cloud (Drive OAuth)","type":"external_api","status":"registered","dest":"1 project/env file"},
+  {"key":"GOOGLE_DRIVE_CLIENT_SECRET","label":"Google Drive OAuth Client Secret — TSD photo-import-cli","provider":"Google Cloud (Drive OAuth)","type":"external_api","status":"registered","dest":"1 project/env file"},
+  {"key":"GOOGLE_DRIVE_REFRESH_TOKEN","label":"Google Drive OAuth refresh token — read-only, steveabramsdesigns@gmail.com","provider":"Google Cloud (Drive OAuth)","type":"external_api","status":"registered","dest":"1 project/env file"},
+  {"key":"TWILIO_ACCOUNT_SID","label":"Twilio Account SID","provider":"Twilio","type":"external_api","status":"registered","dest":"1 project/env file"},
+  {"key":"TWILIO_AUTH_TOKEN","label":"Twilio Auth Token","provider":"Twilio","type":"external_api","status":"registered","dest":"1 project/env file"},
+  {"key":"TWILIO_PHONE_NUMBER","label":"Twilio phone number (identifier)","provider":"Twilio","type":"internal","status":"registered","dest":"1 project/env file"},
+  {"key":"TMDB_API_KEY","label":"TMDB v3 API key — asseeninmovies Phase B drain","provider":"TMDB","type":"external_api","status":"registered","dest":"1 project/env file"},
+  {"key":"TMDB_READ_ACCESS_TOKEN","label":"TMDB v4 read-access token (bearer) — alt to v3 api_key","provider":"TMDB","type":"external_api","status":"registered","dest":"1 project/env file"},
+  {"key":"VAPI_API_KEY","label":"Vapi API key","provider":"Vapi","type":"external_api","status":"registered","dest":"1 project/env file, ssh to Kamatera (1)"},
+  {"key":"VAPI_PUBLIC_KEY","label":"Vapi public key","provider":"Vapi","type":"external_api","status":"registered","dest":"1 project/env file, ssh to Kamatera (1)"},
+  {"key":"AYRSHARE_API_KEY","label":"Ayrshare API key (social posting)","provider":"Ayrshare","type":"external_api","status":"registered","dest":"1 project/env file"},
+  {"key":"META_ACCESS_TOKEN","label":"Meta Graph API access token","provider":"Meta","type":"external_api","status":"registered","dest":"1 project/env file"},
+  {"key":"META_IG_USER_ID","label":"Meta / Instagram user id (identifier)","provider":"Meta","type":"internal","status":"registered","dest":"1 project/env file"},
+  {"key":"META_FB_PAGE_ID","label":"Meta / Facebook page id (identifier)","provider":"Meta","type":"internal","status":"registered","dest":"1 project/env file"},
+  {"key":"SHOPIFY_DRAFT_TOKEN","label":"Shopify Admin token (write_draft_orders) — DW mural PDP custom-size checkout","provider":"Shopify","type":"external_api","status":"registered","dest":"1 project/env file"},
+  {"key":"SHOPIFY_FULL_ACCESS_TOKEN","label":"Shopify Admin token — DW full Online Store pages, content, navigation, theme","provider":"Shopify","type":"external_api","status":"registered","dest":"2 project/env files"},
+  {"key":"SHOPIFY_STORE_DOMAIN","label":"Shopify store domain for DW vendor microsites Memo-Sample checkout (identifier)","provider":"Shopify","type":"internal","status":"registered","dest":"1 project/env file, ssh to Kamatera (1)"},
+  {"key":"SHOPIFY_SAMPLE_VARIANT_ID","label":"Shopify Memo Sample variant id (identifier)","provider":"Shopify","type":"internal","status":"registered","dest":"1 project/env file, ssh to Kamatera (1)"},
+  {"key":"SHOPIFY_STOREFRONT_TOKEN","label":"Shopify Storefront API token for DW vendor microsites (unauthenticated checkout)","provider":"Shopify","type":"external_api","status":"registered","dest":"1 project/env file, ssh to Kamatera (1)"},
+  {"key":"TWOCAPTCHA_API_KEY","label":"2Captcha API key (captcha solving for trade-portal scrapers)","provider":"2Captcha","type":"external_api","status":"registered","dest":"2 skill envs"},
+  {"key":"DEDAR_LOGIN_URL","label":"Dedar trade-portal login URL (supervised outbound-tier)","provider":"Dedar (trade portal)","type":"internal","status":"registered","dest":"2 project/env files"},
+  {"key":"DEDAR_USERNAME","label":"Dedar trade-portal username","provider":"Dedar (trade portal)","type":"internal","status":"registered","dest":"2 project/env files"},
+  {"key":"DEDAR_PASSWORD","label":"Dedar trade-portal password","provider":"Dedar (trade portal)","type":"internal","status":"registered","dest":"2 project/env files"},
+  {"key":"YORKWALL_USER","label":"York Wallcoverings trade-portal username","provider":"York Wallcoverings (trade portal)","type":"internal","status":"registered","dest":"2 project/env files"},
+  {"key":"YORKWALL_PASS","label":"York Wallcoverings trade-portal password","provider":"York Wallcoverings (trade portal)","type":"internal","status":"registered","dest":"2 project/env files"},
+  {"key":"GOVAUCTIONS_API_KEY","label":"GovAuctions API key","provider":"GovAuctions","type":"external_api","status":"registered","dest":"1 project/env file"},
+  {"key":"APIFY_TOKEN","label":"Apify API token","provider":"Apify","type":"external_api","status":"registered","dest":"1 project/env file"},
+  {"key":"IG_USER_ID","label":"Instagram user id (identifier)","provider":"Instagram","type":"internal","status":"registered","dest":"1 project/env file"},
+  {"key":"TIKTOK_CLIENT_KEY","label":"TikTok client key","provider":"TikTok","type":"external_api","status":"registered","dest":"1 project/env file, ssh to Kamatera (1)"},
+  {"key":"TIKTOK_CLIENT_SECRET","label":"TikTok client secret","provider":"TikTok","type":"external_api","status":"registered","dest":"1 project/env file, ssh to Kamatera (1)"},
+  {"key":"THREADS_APP_ID","label":"Meta Threads app id (identifier)","provider":"Meta (Threads)","type":"internal","status":"registered","dest":"1 project/env file"},
+  {"key":"THREADS_APP_SECRET","label":"Meta Threads app secret","provider":"Meta (Threads)","type":"external_api","status":"registered","dest":"1 project/env file"},
+  {"key":"THREADS_ACCESS_TOKEN","label":"Meta Threads access token","provider":"Meta (Threads)","type":"external_api","status":"registered","dest":"1 project/env file"},
+  {"key":"THREADS_USER_ID","label":"Meta Threads user id (identifier)","provider":"Meta (Threads)","type":"internal","status":"registered","dest":"1 project/env file"},
+  {"key":"DATAFORSEO_API_KEY","label":"DataForSEO API key (base64 login:api-password) — OpenSEO backend","provider":"DataForSEO","type":"external_api","status":"registered","dest":"1 project/env file"},
+  {"key":"OSBORNE_USERNAME","label":"Osborne & Little trade portal (tradenew.osborneandlittle.com) login","provider":"Osborne & Little (trade portal)","type":"internal","status":"registered","dest":"1 skill env"},
+  {"key":"OSBORNE_PASSWORD","label":"Osborne & Little trade portal password","provider":"Osborne & Little (trade portal)","type":"internal","status":"registered","dest":"1 skill env"},
+  {"key":"OPENROUTER_API_KEY","label":"OpenRouter API key","provider":"OpenRouter","type":"external_api","status":"registered","dest":"1 project/env file"},
+  {"key":"SHOPIFY_DW_APP_CLIENT_SECRET","label":"Shopify DW app API secret (OAuth + webhook HMAC). Rotated 2026-08-10.","provider":"Shopify","type":"external_api","status":"registered","dest":"master .env only"},
+  {"key":"SHOPIFY_FULFILLMENT_TOKEN","label":"Shopify dw-signup-fulfillment offline Admin API token.","provider":"Shopify","type":"external_api","status":"registered","dest":"1 project/env file, ssh to Kamatera (1)"},
+  {"key":"SHOPIFY_SIGNUP_APP_CLIENT_SECRET","label":"Shopify dw-signup-fulfillment installed app secret — OAuth + webhook HMAC.","provider":"Shopify","type":"external_api","status":"registered","dest":"1 project/env file, ssh to Kamatera (1)"},
+  {"key":"SHOPIFY_FREE_SAMPLES_APP_CLIENT_SECRET","label":"Shopify DW Free Samples app secret — orders/paid webhook HMAC.","provider":"Shopify","type":"external_api","status":"registered","dest":"1 project/env file, ssh to Kamatera (1)"},
+  {"key":"XAI_API_KEY","label":"xAI / Grok API key","provider":"xAI (Grok)","type":"external_api","status":"registered","dest":"ssh to Kamatera (2)"},
+  {"key":"SLACK_BOT_TOKEN","label":"Slack bot token (dw_reports_bot @ Designerwallcoverings, bot_id B0BC0EB3GPP) — slack MCP server. Rotated + verified live 2026-09-22.","provider":"Slack","type":"external_api","status":"rotated_live_today","dest":"1 MCP server"},
+  {"key":"INNOVATIONS_USERNAME","label":"Innovations USA trade-portal username (innovationsusa.com, DW acct 58315 'Net Less 20%').","provider":"Innovations USA (trade portal)","type":"internal","status":"registered","dest":"2 project/env files"},
+  {"key":"INNOVATIONS_PASSWORD","label":"Innovations USA trade-portal password (DW acct 58315).","provider":"Innovations USA (trade portal)","type":"internal","status":"registered","dest":"2 project/env files"},
+  {"key":"INNOVATIONS_LOGIN_URL","label":"Innovations USA trade-portal login URL (https://www.innovationsusa.com/login; reCAPTCHA, use browserbase+2captcha).","provider":"Innovations USA (trade portal)","type":"internal","status":"registered","dest":"2 project/env files"},
+  {"key":"DW_SESSION_SECRET","label":"DW room-setting-app session secret (own app)","provider":"DW room-setting-app (own app)","type":"internal","status":"registered","dest":"1 project/env file"}
+]
+</script>
+
+<script>
+(function(){
+  "use strict";
+  var LS_DONE="credlauncher.done.v1", LS_PREF="credlauncher.pref.v1";
+
+  // provider -> its actual console / key-management page. "" = no external console.
+  var CONSOLE = {
+    "Anthropic":"https://console.anthropic.com/settings/keys",
+    "OpenAI":"https://platform.openai.com/api-keys",
+    "Slack":"https://api.slack.com/apps/A0BDER23QRK",
+    "Shopify":"https://admin.shopify.com/store/designer-laboratory-sandbox/settings/apps/development",
+    "Google AI Studio (Gemini)":"https://aistudio.google.com/app/apikey",
+    "Google Cloud (Places)":"https://console.cloud.google.com/apis/credentials",
+    "Google Cloud (Maps)":"https://console.cloud.google.com/apis/credentials",
+    "Google Cloud (OAuth)":"https://console.cloud.google.com/apis/credentials",
+    "Google Cloud (Drive OAuth)":"https://console.cloud.google.com/apis/credentials",
+    "Cloudflare":"https://dash.cloudflare.com/profile/api-tokens",
+    "Stripe":"https://dashboard.stripe.com/apikeys",
+    "Twilio":"https://console.twilio.com/us1/account/keys-credentials/api-keys",
+    "Replicate":"https://replicate.com/account/api-tokens",
+    "ElevenLabs":"https://elevenlabs.io/app/settings/api-keys",
+    "HeyGen":"https://app.heygen.com/settings?nav=API",
+    "Vercel":"https://vercel.com/account/tokens",
+    "PurelyMail":"https://purelymail.com/manage/account",
+    "GoDaddy":"https://developer.godaddy.com/keys",
+    "Browserbase":"https://www.browserbase.com/settings",
+    "LinkedIn":"https://www.linkedin.com/developers/apps",
+    "Brave Search":"https://api-dashboard.search.brave.com/app/keys",
+    "Moonshot AI (Kimi)":"https://platform.moonshot.ai/console/api-keys",
+    "xAI (Grok)":"https://console.x.ai/",
+    "OpenRouter":"https://openrouter.ai/keys",
+    "DeepSeek":"https://platform.deepseek.com/api_keys",
+    "Figma":"https://www.figma.com/settings",
+    "Adobe":"https://developer.adobe.com/console/projects",
+    "Printify":"https://printify.com/app/account/api",
+    "Vapi":"https://dashboard.vapi.ai/keys",
+    "TMDB":"https://www.themoviedb.org/settings/api",
+    "X (Twitter)":"https://developer.x.com/en/portal/dashboard",
+    "Meta":"https://developers.facebook.com/apps/",
+    "Meta (Threads)":"https://developers.facebook.com/apps/",
+    "Instagram":"https://developers.facebook.com/apps/",
+    "TikTok":"https://developers.tiktok.com/apps/",
+    "Postiz":"https://app.postiz.com/settings",
+    "Ayrshare":"https://app.ayrshare.com/api",
+    "Apify":"https://console.apify.com/account/integrations/api",
+    "2Captcha":"https://2captcha.com/enterpage",
+    "DataForSEO":"https://app.dataforseo.com/api-access",
+    "US Census Bureau":"https://api.census.gov/data/key_signup.html",
+    "data.gov / Smithsonian":"https://api.data.gov/signup/",
+    "DPLA":"https://pro.dp.la/developers/policies",
+    "21st.dev Magic":"https://21st.dev/settings/billing",
+    "TypeSafe":"https://typesafe.ai/",
+    "Butlr":"https://portal.butlr.io/",
+    "Resend":"https://resend.com/api-keys",
+    "GovAuctions":"",
+    "Spoonflower (login)":"https://www.spoonflower.com/login",
+    "Osborne & Little (trade portal)":"https://tradenew.osborneandlittle.com/",
+    "Innovations USA (trade portal)":"https://www.innovationsusa.com/login",
+    "Dedar (trade portal)":"",
+    "York Wallcoverings (trade portal)":"",
+    "PostgreSQL (local/Kamatera)":"",
+    "George Gmail relay (own service)":"",
+    "Small Business Builder (own app)":"",
+    "DW room-setting-app (own app)":""
+  };
+  var SELF_MANAGED = {
+    "PostgreSQL (local/Kamatera)":1,"George Gmail relay (own service)":1,
+    "Small Business Builder (own app)":1,"DW room-setting-app (own app)":1
+  };
+  function launchFor(rec){
+    var u = CONSOLE[rec.provider];
+    if(u) return {url:u, kind:(rec.type==="internal" && /trade portal|login/.test(rec.provider))?"login":"console"};
+    if(SELF_MANAGED[rec.provider]) return {url:"", kind:"none"};
+    return {url:"https://www.google.com/search?q="+encodeURIComponent(rec.provider+" API key console"), kind:"search"};
+  }
+
+  var STATUS_RANK = {needs_rotation:0, alias_rotate:1, rotated_live_today:2, registered:3};
+  function statusBadge(s){
+    if(s==="needs_rotation") return {cls:"rot", txt:"Needs rotation"};
+    if(s==="alias_rotate")  return {cls:"alias", txt:"Rotate w/ Shopify token"};
+    if(s==="rotated_live_today") return {cls:"live", txt:"Rotated today"};
+    return null;
+  }
+
+  var DATA;
+  try { DATA = JSON.parse(document.getElementById("creds").textContent); }
+  catch(e){ DATA = []; }
+
+  // ---- persisted prefs / done set ----
+  var pref = {filter:"all", sort:"attn", q:"", compact:false};
+  try{ var p=JSON.parse(localStorage.getItem(LS_PREF)||"{}"); Object.assign(pref,p);}catch(e){}
+  var done = {};
+  try{ done = JSON.parse(localStorage.getItem(LS_DONE)||"{}")||{}; }catch(e){}
+  function savePref(){ try{ localStorage.setItem(LS_PREF, JSON.stringify(pref)); }catch(e){} }
+  function saveDone(){ try{ localStorage.setItem(LS_DONE, JSON.stringify(done)); }catch(e){} }
+
+  var selKey = null;
+
+  // ---- header stats ----
+  function renderStats(){
+    var ext=0,int=0,rot=0,live=0;
+    DATA.forEach(function(r){
+      if(r.type==="external_api") ext++; else int++;
+      if(r.status==="needs_rotation"||r.status==="alias_rotate") rot++;
+      if(r.status==="rotated_live_today") live++;
+    });
+    var el=document.getElementById("stats");
+    el.innerHTML =
+      stat("", DATA.length+" total")+
+      stat("ext", "<b>"+ext+"</b> external API")+
+      stat("int", "<b>"+int+"</b> internal")+
+      (rot?stat("warn","<b>"+rot+"</b> need attention"):"")+
+      (live?stat("good","<b>"+live+"</b> rotated today"):"");
+  }
+  function stat(dot,html){ return '<span class="stat">'+(dot?'<span class="dot '+dot+'"></span>':'')+html+'</span>'; }
+
+  // ---- filter chips (dynamic providers with counts) ----
+  var FILTERS;
+  function buildFilters(){
+    var provCounts={};
+    DATA.forEach(function(r){ provCounts[r.provider]=(provCounts[r.provider]||0)+1; });
+    FILTERS=[
+      {id:"all", label:"All", n:DATA.length},
+      {id:"attn", label:"Needs attention", n:DATA.filter(function(r){return r.status==="needs_rotation"||r.status==="alias_rotate";}).length},
+      {id:"external_api", label:"External APIs", n:DATA.filter(function(r){return r.type==="external_api";}).length},
+      {id:"internal", label:"Internal", n:DATA.filter(function(r){return r.type==="internal";}).length}
+    ];
+    var el=document.getElementById("chips");
+    el.innerHTML = FILTERS.map(function(f){
+      return '<button class="chip" data-f="'+f.id+'" aria-pressed="'+(pref.filter===f.id)+'">'+f.label+'<span class="n">'+f.n+'</span></button>';
+    }).join("");
+    el.querySelectorAll(".chip").forEach(function(b){
+      b.addEventListener("click",function(){ pref.filter=b.getAttribute("data-f"); savePref(); syncChips(); render(); });
+    });
+  }
+  function syncChips(){
+    document.querySelectorAll("#chips .chip").forEach(function(b){
+      b.setAttribute("aria-pressed", String(pref.filter===b.getAttribute("data-f")));
+    });
+  }
+
+  function esc(s){ return String(s==null?"":s).replace(/[&<>"]/g,function(c){return {"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;"}[c];}); }
+
+  function passFilter(r){
+    if(pref.filter==="all") return true;
+    if(pref.filter==="attn") return r.status==="needs_rotation"||r.status==="alias_rotate";
+    return r.type===pref.filter;
+  }
+  function passSearch(r){
+    var q=pref.q.trim().toLowerCase(); if(!q) return true;
+    return (r.key+" "+r.provider+" "+(r.label||"")).toLowerCase().indexOf(q)>=0;
+  }
+  function sortRows(a,b){
+    if(pref.sort==="attn"){
+      var d=STATUS_RANK[a.status]-STATUS_RANK[b.status]; if(d) return d;
+      return a.provider.localeCompare(b.provider)||a.key.localeCompare(b.key);
+    }
+    if(pref.sort==="prov") return a.provider.localeCompare(b.provider)||a.key.localeCompare(b.key);
+    if(pref.sort==="key") return a.key.localeCompare(b.key);
+    if(pref.sort==="type") return a.type.localeCompare(b.type)||a.provider.localeCompare(b.provider);
+    return 0;
+  }
+
+  function rowHTML(r){
+    var lf=launchFor(r), b=statusBadge(r.status);
+    var isDone=!!done[r.key];
+    var right;
+    if(lf.kind==="none"){
+      right='<span class="nocon">self-managed<br>no console</span>';
+    } else {
+      var lbl = lf.kind==="login"?"Log in ↗" : lf.kind==="search"?"Find console ↗" : "Launch ↗";
+      var cls = lf.kind==="console"?"launch":"launch ghost";
+      right='<a class="'+cls+'" href="'+esc(lf.url)+'" target="_blank" rel="noopener" data-launch="1">'+lbl+'</a>';
+    }
+    right += '<button class="donebtn" data-done="'+esc(r.key)+'"><span class="doneck">✓</span>'+(isDone?"done":"mark done")+'</button>';
+    var badges='<span class="badge '+(r.type==="external_api"?"":"int")+'" style="'+(r.type==="external_api"?"display:none":"")+'">internal</span>';
+    if(b) badges='<span class="badge '+b.cls+'">'+b.txt+'</span>'+(r.type==="internal"?'<span class="badge int">internal</span>':'');
+    else if(r.type==="internal") badges='<span class="badge int">internal</span>';
+    else badges='';
+    return '<div class="row'+(selKey===r.key?" sel":"")+((r.status==="needs_rotation"||r.status==="alias_rotate")?" attn":"")+(isDone?" done":"")+'" data-key="'+esc(r.key)+'" tabindex="0">'+
+      '<span class="rmark">✓</span>'+
+      '<div class="rmid">'+
+        '<div class="kname">'+esc(r.key)+'</div>'+
+        '<div class="meta"><span class="prov">'+esc(r.provider)+'</span>'+badges+'</div>'+
+        (r.label?'<div class="label">'+esc(r.label)+'</div>':'')+
+        '<div class="dest">→ '+esc(r.dest)+'</div>'+
+      '</div>'+
+      '<div class="rright">'+right+'</div>'+
+    '</div>';
+  }
+
+  function render(){
+    var rows=DATA.filter(function(r){return passFilter(r)&&passSearch(r);}).slice().sort(sortRows);
+    var main=document.getElementById("main");
+    if(!rows.length){ main.innerHTML='<div class="empty">No credentials match.</div>'; return; }
+
+    var attn=rows.filter(function(r){return r.status==="needs_rotation"||r.status==="alias_rotate";});
+    var rest=rows.filter(function(r){return !(r.status==="needs_rotation"||r.status==="alias_rotate");});
+    var html="";
+    if(attn.length && pref.sort==="attn" && pref.filter!=="internal"){
+      html+='<div class="sec-label attn"><span>⚠ Action needed — '+attn.length+'</span><span class="rule"></span></div>';
+      html+='<div class="list">'+attn.map(rowHTML).join("")+'</div>';
+      if(rest.length){
+        html+='<div class="sec-label"><span>All credentials — '+rest.length+'</span><span class="rule"></span></div>';
+        html+='<div class="list">'+rest.map(rowHTML).join("")+'</div>';
+      }
+    } else {
+      html+='<div class="list">'+rows.map(rowHTML).join("")+'</div>';
+    }
+    main.innerHTML=html;
+    wireRows();
+  }
+
+  function wireRows(){
+    document.querySelectorAll(".row").forEach(function(row){
+      row.addEventListener("click",function(ev){
+        if(ev.target.closest("[data-launch]")) return;            // let launch anchor work
+        if(ev.target.closest("[data-done]")){ toggleDone(ev.target.closest("[data-done]").getAttribute("data-done")); return; }
+        select(row.getAttribute("data-key"));
+      });
+      row.addEventListener("keydown",function(ev){
+        if(ev.key==="Enter"||ev.key===" "){ ev.preventDefault(); select(row.getAttribute("data-key")); }
+      });
+    });
+  }
+
+  function byKey(k){ for(var i=0;i<DATA.length;i++) if(DATA[i].key===k) return DATA[i]; return null; }
+
+  function select(k){
+    selKey = (selKey===k)?null:k;
+    document.querySelectorAll(".row").forEach(function(r){ r.classList.toggle("sel", r.getAttribute("data-key")===selKey); });
+    updateBar();
+  }
+  function updateBar(){
+    var bar=document.getElementById("lbar"), go=document.getElementById("lb-go");
+    if(!selKey){ bar.classList.remove("show"); return; }
+    var r=byKey(selKey), lf=launchFor(r);
+    document.getElementById("lb-k").textContent=r.key;
+    document.getElementById("lb-p").textContent=r.provider+(lf.kind==="none"?" · self-managed (no console)":lf.kind==="search"?" · console unknown — web search":"");
+    if(lf.url){ go.setAttribute("href",lf.url); go.setAttribute("aria-disabled","false");
+      go.textContent=(lf.kind==="login"?"Log in":lf.kind==="search"?"Search":"Launch")+" ↗"; }
+    else { go.removeAttribute("href"); go.setAttribute("aria-disabled","true"); go.textContent="No console"; }
+    bar.classList.add("show");
+  }
+  function toggleDone(k){
+    if(done[k]) delete done[k]; else done[k]=Date.now();
+    saveDone(); render();
+    if(selKey) updateBar();
+  }
+
+  // ---- controls ----
+  var qEl=document.getElementById("q"); qEl.value=pref.q||"";
+  qEl.addEventListener("input",function(){ pref.q=qEl.value; savePref(); render(); });
+  var sortEl=document.getElementById("sort"); sortEl.value=pref.sort;
+  sortEl.addEventListener("change",function(){ pref.sort=sortEl.value; savePref(); render(); });
+  function setCompact(on){
+    pref.compact=on; savePref();
+    document.body.classList.toggle("compact",on);
+    document.getElementById("d-comf").setAttribute("aria-pressed",String(!on));
+    document.getElementById("d-comp").setAttribute("aria-pressed",String(on));
+  }
+  document.getElementById("d-comf").addEventListener("click",function(){setCompact(false);});
+  document.getElementById("d-comp").addEventListener("click",function(){setCompact(true);});
+  document.getElementById("lb-close").addEventListener("click",function(){ selKey=null; document.querySelectorAll(".row.sel").forEach(function(r){r.classList.remove("sel");}); updateBar(); });
+
+  // ---- boot ----
+  renderStats(); buildFilters(); setCompact(!!pref.compact); render();
+})();
+</script>
diff --git a/server.js b/server.js
new file mode 100644
index 0000000..94f5e8e
--- /dev/null
+++ b/server.js
@@ -0,0 +1,44 @@
+#!/usr/bin/env node
+// Credential Launcher — tiny static server, localhost only, kept alive by launchd.
+// Serves page.html (authored for the Artifact skeleton) wrapped in a standalone
+// HTML document so it renders in a normal browser at http://127.0.0.1:51858/.
+const http = require('http');
+const fs = require('fs');
+const path = require('path');
+
+const PORT = Number(process.env.PORT) || 51858;
+const HOST = '127.0.0.1';
+const PAGE = path.join(__dirname, 'page.html');
+
+const HEAD = '<!doctype html><html lang="en"><head>' +
+  '<meta charset="utf-8">' +
+  '<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">' +
+  '<style>html,body{margin:0}img{max-width:100%}[hidden]{display:none!important}</style>';
+
+function render() {
+  // Read fresh each request so editing page.html shows up on reload.
+  const body = fs.readFileSync(PAGE, 'utf8');
+  return HEAD + body + '</html>';
+}
+
+const server = http.createServer((req, res) => {
+  if (req.url === '/health') {
+    res.writeHead(200, { 'content-type': 'text/plain' });
+    return res.end('ok');
+  }
+  try {
+    const html = render();
+    res.writeHead(200, { 'content-type': 'text/html; charset=utf-8', 'cache-control': 'no-store' });
+    res.end(html);
+  } catch (e) {
+    res.writeHead(500, { 'content-type': 'text/plain' });
+    res.end('cred-launcher error: ' + e.message);
+  }
+});
+
+server.listen(PORT, HOST, () => {
+  console.log(`[${new Date().toISOString()}] cred-launcher listening on http://${HOST}:${PORT}/`);
+});
+
+// Fail loud but let launchd KeepAlive restart us.
+process.on('uncaughtException', (e) => { console.error('uncaught', e); process.exit(1); });

(oldest)  ·  back to Cred Launcher  ·  (newest)