Files
hack/tools/scripts/llm-key-hunter/results/_cache/content/autho369-code_stellar-redesigned.json
T
OpenCode 68a9071cf1 feat: reconcile local master with origin (exa/hot-platform hunters, leaks ledger, latest results)
Local branch had diverged from origin/master (sibling commits on the same base). Rewrote local history linearly on top of origin/master, folding in all local content: exa/hot-platform discovery hunters, leaks ledger, nightly sweep orchestrator, updated .gitignore and skill docs, plus the latest hunt output and vault state. Remote-only files (vault keys, channel scripts) were restored rather than dropped, so the resulting tree is a full union of both sides.
2026-08-07 12:53:47 +08:00

1 line
18 KiB
JSON
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{"repo": "autho369-code/stellar-redesigned", "files": {"api/chat.js": {"fdd5c210725a9e5cd6aa0ce34e5621f61dbc89ec": {"t": 1786068493.4161994, "n": 17573, "h": "e8079b8f52a3138d", "c": "// Vercel serverless function: Claude-powered site concierge (\"Arthur\").\n// Requires ANTHROPIC_API_KEY in the Vercel project environment variables.\n// The site widget falls back to its built-in knowledge base when this\n// endpoint is unavailable, so the bot works either way.\n\n// Allows res.write() to reach the client incrementally when the widget\n// requests a streamed reply ({ stream: true } in the body).\nexport const config = { supportsResponseStreaming: true };\n\n// Identity (name + personality) is loaded LIVE from the shared ops database\n// (agent_public_profile view) so the website concierge is the same Arthur\n// configured at /ops/agent. The facts and safety rules below stay in code.\nconst CORE_PROMPT = `Stellar Property Management is a Chicago community association management firm (condominiums, HOAs, townhomes only; NO apartment rentals).\n\nFACTS (the only facts you may state):\n- Serving Chicago & the North Shore since 2007. 42 associations, 2,450+ residences, 96% client retention.\n- Office: 5107 N Western Ave, Suite 1S, Chicago, IL 60625. Phone: 773.728.0652 (24/7 live emergency line). Email: mirsad@stellarpropertygroup.com.\n- Services: condominium management, HOA management, townhome management, financial management (flat-fee, never percentage-based), maintenance coordination with 24/7 emergency response, board support, violation management.\n- Owner/resident portal (AppFolio): https://stellarpropertygrp.appfolio.com/connect/ — payments (eCheck or card), autopay, maintenance requests, documents.\n- Free payment option — bank Bill Pay: Payee \"Your Association Name\"; mail to 5107 N. Western Avenue, Suite 1S, Chicago, IL 60625; memo = association 4-number address (e.g. \"1740\") and unit number. Details: stellarpropertygroup.com/resources#bill-pay\n- Report a violation: stellarpropertygroup.com/resources?form=violation. Board nomination: /resources?form=nomination.\n- Coverage: 70+ Chicago neighborhoods (Gold Coast, Streeterville, River North, Lincoln Park, Lakeview, Loop, West Loop, South Loop, Edgewater, Hyde Park, and more) plus North Shore: Evanston, Skokie, Glenview, Wilmette, Winnetka, Highland Park, Northbrook, Glencoe, Kenilworth, Lake Forest, Deerfield.\n- Switching firms: managed 30–60 day transition (records, banking, vendors, owner communication) — the board only signs.\n- Pricing: customized flat monthly fee; NEVER quote a number — invite a proposal request at stellarpropertygroup.com/contact.\n\nRULES:\n- Answer ONLY about Stellar, its services, payments, portals, forms, and service areas. Politely decline anything else.\n- Never give legal, tax, or investment advice; suggest the association consult its attorney.\n- Never invent facts, prices, staff names, or client names.\n- Building emergencies (flood, fire, elevator entrapment): tell them to call 773.728.0652 immediately — and 911 first if life-threatening.\n- Be warm, concise (2-4 short sentences), and concrete. When relevant, end by offering the proposal form (/contact) or the portal link.\n- Plain text only — no markdown. Write links as bare URLs (e.g. stellarpropertygroup.com/contact), never [text](url).\n- If the visitor seems to be a board member interested in management services, encourage them to leave name/email/building via the \"Request a Proposal\" option.`;\n\n// Shared stellar-ops database (browser-safe publishable key; all reads below\n// run under the VISITOR'S OWN session token, so row-level security limits\n// them to the signed-in owner's records and their association's knowledge).\nconst SUPABASE_URL = 'https://qfjhmzvuaifxnvmwblux.supabase.co';\nconst SUPABASE_PUBLISHABLE_KEY = 'sb_publishable_zLJMg0YOC9jHmg05IfE7-g_VIWA-v1G';\n\n// Staff authorized for Arthur's staff session (full, all-association access).\n// Must match AUTHORIZED_STAFF in src/lib/ownerAuth.ts.\nconst AUTHORIZED_STAFF = [\n 'mirsad@stellarpropertygroup.com',\n 'mustafa@stellarpropertygroup.com',\n 'meho@stellarpropertygroup.com',\n];\n\n// USD per million tokens — used only for the estimated_cost_usd ledger column.\nconst CHAT_PRICES = {\n 'claude-haiku-4-5-20251001': { in: 1.0, out: 5.0, cacheRead: 0.1, cacheWrite: 1.25 },\n 'deepseek-chat': { in: 0.27, out: 1.1, cacheRead: 0.07, cacheWrite: 0.27 },\n};\n\n/** Best-effort usage row into ai_usage_ledger (service role; never blocks the reply). */\nasync function logChatUsage(model, u) {\n const key = process.env.SUPABASE_SERVICE_ROLE_KEY;\n if (!key || !u || (!u.input && !u.output)) return;\n const p = CHAT_PRICES[model] || { in: 0, out: 0, cacheRead: 0, cacheWrite: 0 };\n const cost =\n ((u.input || 0) * p.in +\n (u.output || 0) * p.out +\n (u.cacheRead || 0) * p.cacheRead +\n (u.cacheCreate || 0) * p.cacheWrite) /\n 1e6;\n try {\n await fetch(`${SUPABASE_URL}/rest/v1/ai_usage_ledger`, {\n method: 'POST',\n headers: {\n apikey: key,\n authorization: `Bearer ${key}`,\n 'content-type': 'application/json',\n prefer: 'return=minimal',\n },\n body: JSON.stringify({\n kind: 'chat',\n model,\n input_tokens: u.input || 0,\n output_tokens: u.output || 0,\n cache_read_input_tokens: u.cacheRead || 0,\n cache_creation_input_tokens: u.cacheCreate || 0,\n estimated_cost_usd: Number(cost.toFixed(6)),\n status: 'succeeded',\n }),\n });\n } catch (e) {\n console.error('chat usage log failed', e?.message);\n }\n}\n\nasync function restGet(path, userToken) {\n const r = await fetch(`${SUPABASE_URL}/rest/v1/${path}`, {\n headers: {\n apikey: SUPABASE_PUBLISHABLE_KEY,\n authorization: `Bearer ${userToken || SUPABASE_PUBLISHABLE_KEY}`,\n },\n });\n if (!r.ok) return null;\n return r.json();\n}\n\n/** Live identity from the ops database — same Arthur as /ops/agent. */\nasync function buildIdentity() {\n let name = 'Arthur';\n let persona = '';\n try {\n const rows = await restGet('agent_public_profile?select=agent_name,persona&limit=1', null);\n if (Array.isArray(rows) && rows[0]) {\n name = String(rows[0].agent_name || 'Arthur').slice(0, 60);\n persona = String(rows[0].persona || '').slice(0, 1500);\n }\n } catch { /* fall back to defaults */ }\n return (\n `You are ${name}, the online concierge for Stellar Property Management.` +\n (persona ? `\\nPERSONALITY (set by the office — embody it): ${persona}` : '')\n );\n}\n\n/** Email claim from a Supabase JWT (display/filtering only — RLS enforces access). */\nfunction jwtEmail(token) {\n try {\n const payload = JSON.parse(Buffer.from(token.split('.')[1], 'base64url').toString('utf8'));\n return String(payload.email || '').toLowerCase();\n } catch {\n return '';\n }\n}\n\n/** Owner profile + association-knowledge retrieval for signed-in visitors. */\nasync function buildOwnerContext(userToken, lastUserMessage) {\n let ownerNote = '';\n let knowledgeNote = '';\n let isStaff = false;\n\n // Match the owner row to the login email explicitly — staff sessions can\n // read every owner row and must not be presented as a random owner.\n const email = jwtEmail(userToken);\n const owners = email\n ? await restGet(\n `owners?select=name,unit:units!owners_unit_id_fkey(number,association:associations(name))&email=ilike.${encodeURIComponent(email)}&limit=1`,\n userToken\n )\n : null;\n const o = Array.isArray(owners) ? owners[0] : null;\n if (o?.name) {\n const unit = o.unit?.number ? `, unit ${o.unit.number}` : '';\n const assoc = o.unit?.association?.name ? `, ${o.unit.association.name}` : '';\n ownerNote = `\\n\\nSIGNED-IN OWNER (verified session): ${o.name}${unit}${assoc}. Greet them by first name and tailor answers to their community. Never reveal information about other owners or units.`;\n } else if (email && AUTHORIZED_STAFF.includes(email)) {\n // No owner row — Stellar staff (team_members) sign in with company-wide\n // RLS; recognize them from their own team_members record.\n const staff = await restGet(\n `team_members?select=name&email=ilike.${encodeURIComponent(email)}&active=eq.true&limit=1`,\n userToken\n );\n const s = Array.isArray(staff) ? staff[0] : null;\n if (s?.name) {\n isStaff = true;\n ownerNote = `\\n\\nSIGNED-IN STAFF (verified session): ${s.name}, Stellar Property Management staff. Greet them by first name. They may ask about any association — answer from the knowledge entries and always say which association each fact belongs to.`;\n }\n }\n\n // Full-text search the curated owner knowledge base with the visitor's\n // question; RLS returns only global entries + their association's entries.\n const words = String(lastUserMessage || '')\n .replace(/[^\\p{L}\\p{N}\\s]/gu, ' ')\n .split(/\\s+/)\n .filter((w) => w.length > 2)\n .slice(0, 10)\n .join(' ');\n if (words) {\n const rows = await restGet(\n `owner_knowledge?select=title,body,association:associations(name)&fts=wfts.${encodeURIComponent(words)}&limit=3`,\n userToken\n );\n if (Array.isArray(rows) && rows.length) {\n const entries = rows\n .map((k) => `• [${k.association?.name || 'ALL COMMUNITIES'}] ${k.title}: ${String(k.body).slice(0, 1200)}`)\n .join('\\n');\n knowledgeNote = isStaff\n ? `\\n\\nASSOCIATION KNOWLEDGE BASE (staff session — entries from any association may be used, but each applies ONLY to the association named in brackets; always name the association when citing a rule):\\n${entries}`\n : `\\n\\nASSOCIATION KNOWLEDGE BASE (each entry applies ONLY to the association named in brackets — NEVER answer using another association's rules; if no entry matches the owner's own community, say the office will confirm and point to the AppFolio document library or the board):\\n${entries}`;\n }\n }\n\n return ownerNote + knowledgeNote;\n}\n\n/**\n * Relay an upstream SSE body to the client as a plain-text token stream.\n * `extract` maps one parsed SSE JSON payload to its text delta (or '').\n * Returns the accumulated text so callers can detect an empty reply.\n */\nasync function relaySse(upstreamBody, res, extract) {\n const decoder = new TextDecoder();\n let buffer = '';\n let total = '';\n const reader = upstreamBody.getReader();\n for (;;) {\n const { done, value } = await reader.read();\n if (done) break;\n buffer += decoder.decode(value, { stream: true });\n const lines = buffer.split('\\n');\n buffer = lines.pop() || '';\n for (const line of lines) {\n if (!line.startsWith('data:')) continue;\n const payload = line.slice(5).trim();\n if (!payload || payload === '[DONE]') continue;\n try {\n const delta = extract(JSON.parse(payload));\n if (delta) {\n total += delta;\n res.write(delta);\n }\n } catch { /* skip malformed frames */ }\n }\n }\n return total;\n}\n\nexport default async function handler(req, res) {\n if (req.method !== 'POST') {\n res.status(405).json({ error: 'Method not allowed' });\n return;\n }\n\n // Provider auto-detect (same pattern as the stellar-ops agent):\n // DeepSeek (OpenAI-compatible, low cost) if configured, else Anthropic.\n const deepseekKey = process.env.DEEPSEEK_API_KEY;\n const anthropicKey = process.env.ANTHROPIC_API_KEY;\n if (!deepseekKey && !anthropicKey) {\n res.status(503).json({ error: 'AI not configured' });\n return;\n }\n\n try {\n const { messages, ownerContext, stream } = req.body || {};\n const wantStream = stream === true;\n if (!Array.isArray(messages) || messages.length === 0 || messages.length > 30) {\n res.status(400).json({ error: 'Bad request' });\n return;\n }\n\n // Owner personalization. Preferred path: the visitor's own session token\n // (Authorization header) — the server reads their profile + association\n // knowledge under THEIR RLS scope. Fallback: client-passed context.\n let ownerNote = '';\n const authHeader = req.headers.authorization || '';\n if (authHeader.startsWith('Bearer ') && authHeader.length > 20) {\n const lastUser = [...messages].reverse().find((m) => m && m.role === 'user');\n try {\n ownerNote = await buildOwnerContext(authHeader.slice(7), lastUser?.content);\n } catch (e) {\n console.error('owner context fetch failed', e);\n ownerNote = '';\n }\n }\n if (!ownerNote && ownerContext && typeof ownerContext === 'object') {\n const clean = (v) => (typeof v === 'string' ? v.replace(/[\\r\\n]+/g, ' ').slice(0, 120) : '');\n const name = clean(ownerContext.name);\n const unit = clean(ownerContext.unit);\n const assoc = clean(ownerContext.association);\n // Staff sessions are only honored via the verified token path above —\n // a client-claimed isStaff flag is ignored.\n if (name && ownerContext.isStaff !== true) {\n ownerNote = `\\n\\nSIGNED-IN OWNER (verified session): ${name}${unit ? `, unit ${unit}` : ''}${assoc ? `, ${assoc}` : ''}. Greet them by first name and tailor answers to their community. Still never reveal information about other owners or units.`;\n }\n }\n\n // Only pass role/content through; cap message sizes.\n const safeMessages = messages.slice(-12).map((m) => ({\n role: m.role === 'assistant' ? 'assistant' : 'user',\n content: String(m.content || '').slice(0, 2000),\n }));\n\n const identity = await buildIdentity();\n const system = identity + '\\n\\n' + CORE_PROMPT + ownerNote;\n\n let text = '';\n\n if (deepseekKey) {\n // DeepSeek — OpenAI-compatible chat completions.\n const r = await fetch('https://api.deepseek.com/chat/completions', {\n method: 'POST',\n headers: {\n 'content-type': 'application/json',\n authorization: `Bearer ${deepseekKey}`,\n },\n body: JSON.stringify({\n model: 'deepseek-chat',\n max_tokens: 400,\n stream: wantStream,\n ...(wantStream ? { stream_options: { include_usage: true } } : {}),\n messages: [{ role: 'system', content: system }, ...safeMessages],\n }),\n });\n if (!r.ok) {\n const detail = await r.text();\n console.error('DeepSeek API error', r.status, detail.slice(0, 300));\n res.status(502).json({ error: 'Upstream error' });\n return;\n }\n if (wantStream && r.body) {\n res.writeHead(200, {\n 'content-type': 'text/plain; charset=utf-8',\n 'cache-control': 'no-cache',\n });\n const usage = {};\n const total = await relaySse(r.body, res, (d) => {\n if (d.usage) {\n usage.input = d.usage.prompt_tokens || 0;\n usage.output = d.usage.completion_tokens || 0;\n usage.cacheRead = d.usage.prompt_cache_hit_tokens || 0;\n }\n return d.choices?.[0]?.delta?.content || '';\n });\n if (!total.trim()) res.write('I’m sorry — could you rephrase that?');\n await logChatUsage('deepseek-chat', usage);\n res.end();\n return;\n }\n const data = await r.json();\n text = (data.choices?.[0]?.message?.content || '').trim();\n await logChatUsage('deepseek-chat', {\n input: data.usage?.prompt_tokens,\n output: data.usage?.completion_tokens,\n cacheRead: data.usage?.prompt_cache_hit_tokens,\n });\n } else {\n const r = await fetch('https://api.anthropic.com/v1/messages', {\n method: 'POST',\n headers: {\n 'content-type': 'application/json',\n 'x-api-key': anthropicKey,\n 'anthropic-version': '2023-06-01',\n },\n body: JSON.stringify({\n model: 'claude-haiku-4-5-20251001',\n max_tokens: 400,\n stream: wantStream,\n system,\n messages: safeMessages,\n }),\n });\n if (!r.ok) {\n const detail = await r.text();\n console.error('Anthropic API error', r.status, detail.slice(0, 300));\n res.status(502).json({ error: 'Upstream error' });\n return;\n }\n if (wantStream && r.body) {\n res.writeHead(200, {\n 'content-type': 'text/plain; charset=utf-8',\n 'cache-control': 'no-cache',\n });\n const usage = {};\n const total = await relaySse(r.body, res, (d) => {\n if (d.type === 'message_start' && d.message?.usage) {\n usage.input = d.message.usage.input_tokens || 0;\n usage.cacheRead = d.message.usage.cache_read_input_tokens || 0;\n usage.cacheCreate = d.message.usage.cache_creation_input_tokens || 0;\n }\n if (d.type === 'message_delta' && d.usage) {\n usage.output = d.usage.output_tokens || 0;\n }\n return d.type === 'content_block_delta' && d.delta?.type === 'text_delta'\n ? d.delta.text\n : '';\n });\n if (!total.trim()) res.write('I’m sorry — could you rephrase that?');\n await logChatUsage('claude-haiku-4-5-20251001', usage);\n res.end();\n return;\n }\n const data = await r.json();\n text = (data.content || [])\n .filter((b) => b.type === 'text')\n .map((b) => b.text)\n .join('\\n')\n .trim();\n await logChatUsage('claude-haiku-4-5-20251001', {\n input: data.usage?.input_tokens,\n output: data.usage?.output_tokens,\n cacheRead: data.usage?.cache_read_input_tokens,\n cacheCreate: data.usage?.cache_creation_input_tokens,\n });\n }\n\n res.status(200).json({ reply: text || 'I’m sorry — could you rephrase that?' });\n } catch (err) {\n console.error('chat handler error', err);\n if (res.headersSent) {\n res.end();\n } else {\n res.status(500).json({ error: 'Server error' });\n }\n }\n}\n"}}}}