@i4ctime/q-ring 0.16.0 → 0.16.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +22 -2
- package/dist/{chunk-NCM5GHNW.js → chunk-KFILBHOY.js} +58 -7
- package/dist/chunk-KFILBHOY.js.map +1 -0
- package/dist/{chunk-5LFKBZ3Q.js → chunk-SQKTDYSO.js} +80 -7
- package/dist/chunk-SQKTDYSO.js.map +1 -0
- package/dist/dashboard-2MLSJZQR.js +658 -0
- package/dist/dashboard-2MLSJZQR.js.map +1 -0
- package/dist/dashboard-WVPR5BQO.js +658 -0
- package/dist/dashboard-WVPR5BQO.js.map +1 -0
- package/dist/index.js +96 -27
- package/dist/index.js.map +1 -1
- package/dist/mcp.js +16 -6
- package/dist/mcp.js.map +1 -1
- package/package.json +12 -12
- package/dist/chunk-5LFKBZ3Q.js.map +0 -1
- package/dist/chunk-NCM5GHNW.js.map +0 -1
- package/dist/dashboard-A3GJQCJX.js +0 -658
- package/dist/dashboard-A3GJQCJX.js.map +0 -1
- package/dist/dashboard-R3FWTFFW.js +0 -658
- package/dist/dashboard-R3FWTFFW.js.map +0 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/core/dashboard.ts","../src/core/dashboard-client.ts","../src/core/dashboard-html.ts"],"sourcesContent":["/**\n * Quantum Status Dashboard: local HTTP server with SSE live updates.\n *\n * Collects a full snapshot of all quantum state every few seconds and\n * pushes it to connected browsers. Never exposes secret values.\n */\n\nimport { createServer, type IncomingMessage, type ServerResponse, type Server } from \"node:http\";\nimport { randomBytes, timingSafeEqual } from \"node:crypto\";\nimport { listSecrets } from \"./keyring.js\";\nimport { checkDecay, type DecayStatus, type QuantumEnvelope } from \"./envelope.js\";\nimport { listEntanglements, type EntanglementPair } from \"./entanglement.js\";\nimport { tunnelList } from \"./tunnel.js\";\nimport { queryAudit, detectAnomalies, verifyAuditChain, type AuditEvent, type AccessAnomaly, type AuditAction } from \"./observer.js\";\nimport { collapseEnvironment, readProjectConfig, type CollapseResult } from \"./collapse.js\";\nimport { listHooks, type HookEntry, type HookType } from \"./hooks.js\";\nimport { listApprovals } from \"./approval.js\";\nimport { listMemory } from \"./memory.js\";\nimport { getPolicySummary } from \"./policy.js\";\nimport { getDashboardHtml } from \"./dashboard-html.js\";\nimport { PACKAGE_VERSION } from \"../version.js\";\n\nexport interface SecretSnapshot {\n key: string;\n scope: string;\n type: \"collapsed\" | \"superposition\";\n environments?: string[];\n defaultEnv?: string;\n decay: DecayStatus;\n accessCount: number;\n lastAccessedAt?: string;\n createdAt: string;\n updatedAt: string;\n description?: string;\n tags?: string[];\n entangled?: { service: string; key: string }[];\n /** Validation provider name (e.g. \"openai\", \"stripe\") */\n provider?: string;\n /** Whether reads require explicit approval via MCP */\n requiresApproval?: boolean;\n /** Just-In-Time provisioner name, if any */\n jitProvider?: string;\n /** Whether the rotation format is declared on this envelope */\n hasRotationFormat: boolean;\n}\n\nexport interface TunnelSnapshot {\n id: string;\n createdAt: number;\n expiresAt?: number;\n accessCount: number;\n maxReads?: number;\n}\n\nexport interface ApprovalSnapshot {\n id: string;\n key: string;\n scope: string;\n reason: string;\n grantedBy: string;\n grantedAt: string;\n expiresAt: string;\n /** Seconds remaining until expiry; negative if expired */\n secondsRemaining: number;\n /** True if HMAC verifies and not expired */\n valid: boolean;\n /** True if HMAC failed (forged/tampered) */\n tampered: boolean;\n}\n\nexport interface HookSnapshot {\n id: string;\n type: HookType;\n description?: string;\n enabled: boolean;\n match: HookEntry[\"match\"];\n /** A short, human-readable summary of the match criteria */\n matchSummary: string;\n}\n\nexport interface ManifestSnapshot {\n /** Total declared keys in `.q-ring.json` `secrets` */\n declared: number;\n /** Number marked `required: true` */\n required: number;\n /** Required keys that are missing from project scope */\n missing: string[];\n /** Required keys that are present but expired */\n expired: string[];\n /** Required keys that are present but stale */\n stale: string[];\n}\n\nexport interface PolicySnapshot {\n hasMcpPolicy: boolean;\n hasExecPolicy: boolean;\n hasSecretPolicy: boolean;\n /** Counts per restriction so the UI can render at-a-glance dots */\n counts: {\n allowTools: number;\n denyTools: number;\n deniedKeys: number;\n deniedTags: number;\n allowCommands: number;\n denyCommands: number;\n requireApprovalForTags: number;\n requireRotationFormatForTags: number;\n };\n maxTtlSeconds?: number;\n maxRuntimeSeconds?: number;\n}\n\nexport interface AuditMetrics {\n /** Counts grouped by action over the audited window */\n byAction: Record<AuditAction, number>;\n /** Counts by source (cli, mcp, agent, etc.) */\n bySource: Record<string, number>;\n /** Top accessed keys (read action) over the window */\n topRead: Array<{ key: string; reads: number }>;\n /** Total events read for the metrics window */\n total: number;\n /** Window covered by the metrics, in seconds */\n windowSeconds: number;\n}\n\nexport interface ScopeBreakdown {\n global: number;\n project: number;\n team: number;\n org: number;\n}\n\nexport interface DashboardSnapshot {\n /** Snapshot generation timestamp (ISO) */\n timestamp: string;\n /** q-ring version that produced the snapshot */\n version: string;\n /** Project path in use (for manifest/policy resolution) */\n projectPath: string;\n /** All visible secrets (no values) */\n secrets: SecretSnapshot[];\n /** Headline counts of secret health */\n health: { healthy: number; stale: number; expired: number; noDecay: number; total: number };\n /** Counts per scope */\n scopes: ScopeBreakdown;\n /** Secrets that require approval before MCP read */\n protectedCount: number;\n /** Active entanglement links */\n entanglements: EntanglementPair[];\n /** In-memory tunnels */\n tunnels: TunnelSnapshot[];\n /** Recent audit events (most recent first; \"list\" filtered out) */\n audit: AuditEvent[];\n /** Aggregate audit metrics (read counts, top keys, source breakdown) */\n auditMetrics: AuditMetrics;\n /** Detected access anomalies */\n anomalies: AccessAnomaly[];\n /** Auto-detected environment & detection source */\n environment: CollapseResult | null;\n /** `.q-ring.json` manifest analysis (declared vs missing) */\n manifest: ManifestSnapshot | null;\n /** Governance policy summary */\n policy: PolicySnapshot;\n /** Active approval tokens (HMAC-verified) */\n approvals: ApprovalSnapshot[];\n /** Registered hooks (shell/http/signal) */\n hooks: HookSnapshot[];\n /** Agent memory key count */\n memoryKeys: number;\n /** Audit hash-chain integrity (same data as `qring audit:verify`) */\n auditChain: {\n intact: boolean;\n totalEvents: number;\n validEvents: number;\n brokenAt?: number;\n };\n}\n\nconst AUDIT_WINDOW_SECONDS = 24 * 60 * 60; // 24h\n\nfunction toSecretSnapshot(entry: {\n key: string;\n scope: string;\n envelope?: QuantumEnvelope;\n decay?: DecayStatus;\n}): SecretSnapshot {\n const envelope = entry.envelope;\n const decay = envelope ? checkDecay(envelope) : {\n isExpired: false,\n isStale: false,\n lifetimePercent: 0,\n secondsRemaining: null,\n timeRemaining: null,\n };\n\n return {\n key: entry.key,\n scope: entry.scope,\n type: envelope?.states ? \"superposition\" : \"collapsed\",\n environments: envelope?.states ? Object.keys(envelope.states) : undefined,\n defaultEnv: envelope?.defaultEnv,\n decay,\n accessCount: envelope?.meta.accessCount ?? 0,\n lastAccessedAt: envelope?.meta.lastAccessedAt,\n createdAt: envelope?.meta.createdAt ?? \"\",\n updatedAt: envelope?.meta.updatedAt ?? \"\",\n description: envelope?.meta.description,\n tags: envelope?.meta.tags,\n entangled: envelope?.meta.entangled,\n provider: envelope?.meta.provider,\n requiresApproval: envelope?.meta.requiresApproval,\n jitProvider: envelope?.meta.jitProvider,\n hasRotationFormat: !!envelope?.meta.rotationFormat,\n };\n}\n\nfunction summariseHookMatch(match: HookEntry[\"match\"]): string {\n const parts: string[] = [];\n if (match.key) parts.push(`key=${match.key}`);\n if (match.keyPattern) parts.push(`pattern=${match.keyPattern}`);\n if (match.tag) parts.push(`tag=${match.tag}`);\n if (match.scope) parts.push(`scope=${match.scope}`);\n if (match.action?.length) parts.push(`on=${match.action.join(\"|\")}`);\n return parts.length ? parts.join(\" · \") : \"any change\";\n}\n\nfunction buildManifest(\n projectPath: string,\n secrets: SecretSnapshot[],\n): ManifestSnapshot | null {\n const config = readProjectConfig(projectPath);\n if (!config?.secrets) return null;\n const declared = Object.keys(config.secrets);\n const projectSecrets = new Map(\n secrets.filter((s) => s.scope === \"project\").map((s) => [s.key, s]),\n );\n\n const required: string[] = [];\n const missing: string[] = [];\n const expired: string[] = [];\n const stale: string[] = [];\n\n for (const key of declared) {\n const entry = config.secrets[key];\n if (entry.required) required.push(key);\n const present = projectSecrets.get(key);\n if (!present) {\n if (entry.required) missing.push(key);\n continue;\n }\n if (present.decay.isExpired) expired.push(key);\n else if (present.decay.isStale) stale.push(key);\n }\n\n return {\n declared: declared.length,\n required: required.length,\n missing,\n expired,\n stale,\n };\n}\n\nfunction buildPolicySnapshot(projectPath: string): PolicySnapshot {\n const summary = getPolicySummary(projectPath);\n const mcp = summary.details.mcp ?? {};\n const exec = summary.details.exec ?? {};\n const sec = summary.details.secrets ?? {};\n return {\n hasMcpPolicy: summary.hasMcpPolicy,\n hasExecPolicy: summary.hasExecPolicy,\n hasSecretPolicy: summary.hasSecretPolicy,\n counts: {\n allowTools: mcp.allowTools?.length ?? 0,\n denyTools: mcp.denyTools?.length ?? 0,\n deniedKeys: mcp.deniedKeys?.length ?? 0,\n deniedTags: mcp.deniedTags?.length ?? 0,\n allowCommands: exec.allowCommands?.length ?? 0,\n denyCommands: exec.denyCommands?.length ?? 0,\n requireApprovalForTags: sec.requireApprovalForTags?.length ?? 0,\n requireRotationFormatForTags: sec.requireRotationFormatForTags?.length ?? 0,\n },\n maxTtlSeconds: sec.maxTtlSeconds,\n maxRuntimeSeconds: exec.maxRuntimeSeconds,\n };\n}\n\nfunction buildAuditMetrics(events: AuditEvent[]): AuditMetrics {\n const byAction = {} as Record<AuditAction, number>;\n const bySource: Record<string, number> = {};\n const readCounts = new Map<string, number>();\n\n for (const e of events) {\n byAction[e.action] = (byAction[e.action] ?? 0) + 1;\n bySource[e.source] = (bySource[e.source] ?? 0) + 1;\n if (e.action === \"read\" && e.key) {\n readCounts.set(e.key, (readCounts.get(e.key) ?? 0) + 1);\n }\n }\n\n const topRead = [...readCounts.entries()]\n .map(([key, reads]) => ({ key, reads }))\n .sort((a, b) => b.reads - a.reads)\n .slice(0, 10);\n\n return {\n byAction,\n bySource,\n topRead,\n total: events.length,\n windowSeconds: AUDIT_WINDOW_SECONDS,\n };\n}\n\nexport function collectSnapshot(): DashboardSnapshot {\n const projectPath = process.cwd();\n const entries = listSecrets({ source: \"api\", silent: true, projectPath });\n\n const secrets = entries.map(toSecretSnapshot);\n\n let healthy = 0;\n let stale = 0;\n let expired = 0;\n let noDecay = 0;\n let protectedCount = 0;\n const scopes: ScopeBreakdown = { global: 0, project: 0, team: 0, org: 0 };\n\n for (const s of secrets) {\n if (!s.decay.timeRemaining) noDecay++;\n else if (s.decay.isExpired) expired++;\n else if (s.decay.isStale) stale++;\n else healthy++;\n if (s.requiresApproval) protectedCount++;\n if (s.scope === \"global\") scopes.global++;\n else if (s.scope === \"project\") scopes.project++;\n else if (s.scope === \"team\") scopes.team++;\n else if (s.scope === \"org\") scopes.org++;\n }\n\n const auditWindow = queryAudit({\n since: new Date(Date.now() - AUDIT_WINDOW_SECONDS * 1000).toISOString(),\n }).filter((e) => e.action !== \"list\");\n\n const recent = auditWindow.slice(0, 80);\n\n const approvals = listApprovals().map<ApprovalSnapshot>((a) => ({\n id: a.id,\n key: a.key,\n scope: a.scope,\n reason: a.reason,\n grantedBy: a.grantedBy,\n grantedAt: a.grantedAt,\n expiresAt: a.expiresAt,\n secondsRemaining: Math.floor(\n (new Date(a.expiresAt).getTime() - Date.now()) / 1000,\n ),\n valid: a.valid && !a.tampered,\n tampered: a.tampered,\n }));\n\n const hookEntries = listHooks().map<HookSnapshot>((h) => ({\n id: h.id,\n type: h.type,\n description: h.description,\n enabled: h.enabled,\n match: h.match,\n matchSummary: summariseHookMatch(h.match),\n }));\n\n return {\n timestamp: new Date().toISOString(),\n version: PACKAGE_VERSION,\n projectPath,\n secrets,\n health: { healthy, stale, expired, noDecay, total: secrets.length },\n scopes,\n protectedCount,\n entanglements: listEntanglements(),\n tunnels: tunnelList(),\n audit: recent,\n auditMetrics: buildAuditMetrics(auditWindow),\n anomalies: detectAnomalies(),\n environment: collapseEnvironment({ projectPath }),\n manifest: buildManifest(projectPath, secrets),\n policy: buildPolicySnapshot(projectPath),\n approvals,\n hooks: hookEntries,\n memoryKeys: listMemory().length,\n auditChain: (() => {\n const chain = verifyAuditChain();\n return {\n intact: chain.intact,\n totalEvents: chain.totalEvents,\n validEvents: chain.validEvents,\n ...(chain.brokenAt != null ? { brokenAt: chain.brokenAt } : {}),\n };\n })(),\n };\n}\n\nexport interface DashboardServerOptions {\n port?: number;\n}\n\n// Enforced on every response. The page is fully self-contained (inline\n// CSS/JS, no external fetches), so the CSP can deny everything except inline\n// code and same-origin SSE. `no-referrer` keeps the ?token= URL out of any\n// outbound Referer header; `no-store` keeps the token-bearing HTML out of\n// shared caches.\nconst SECURITY_HEADERS: Record<string, string> = {\n \"Content-Security-Policy\":\n \"default-src 'none'; script-src 'unsafe-inline'; style-src 'unsafe-inline'; img-src data:; connect-src 'self'; frame-ancestors 'none'; base-uri 'none'; form-action 'none'\",\n \"X-Frame-Options\": \"DENY\",\n \"X-Content-Type-Options\": \"nosniff\",\n \"Referrer-Policy\": \"no-referrer\",\n \"Cache-Control\": \"no-store\",\n};\n\nfunction timingSafeStringEqual(a: string, b: string): boolean {\n const ab = Buffer.from(a, \"utf8\");\n const bb = Buffer.from(b, \"utf8\");\n if (ab.length !== bb.length) return false;\n return timingSafeEqual(ab, bb);\n}\n\nexport function startDashboardServer(\n options: DashboardServerOptions = {},\n): { port: number; token: string; url: string; close: () => void; server: Server } {\n const port = options.port ?? 9876;\n const clients = new Set<ServerResponse>();\n let intervalHandle: ReturnType<typeof setInterval> | null = null;\n\n // Per-launch bearer token. The dashboard binds to 127.0.0.1, but on a shared\n // host any local user/process could otherwise read key names, the audit log,\n // and approvals. Every route requires this token (passed as ?token=).\n const token = randomBytes(24).toString(\"base64url\");\n const html = getDashboardHtml(token);\n\n function broadcast() {\n const snapshot = collectSnapshot();\n const data = `data: ${JSON.stringify(snapshot)}\\n\\n`;\n for (const res of clients) {\n if (res.writableEnded || res.destroyed) {\n clients.delete(res);\n continue;\n }\n try {\n const ok = res.write(data);\n if (!ok) {\n clients.delete(res);\n try { res.end(); } catch { try { res.destroy(); } catch { /* noop */ } }\n }\n } catch {\n clients.delete(res);\n try { res.end(); } catch { try { res.destroy(); } catch { /* noop */ } }\n }\n }\n }\n\n const server = createServer((req: IncomingMessage, res: ServerResponse) => {\n let pathname: string;\n let query: URLSearchParams;\n try {\n const parsed = new URL(req.url ?? \"/\", \"http://127.0.0.1\");\n pathname = parsed.pathname;\n query = parsed.searchParams;\n } catch {\n res.writeHead(400, { ...SECURITY_HEADERS, \"Content-Type\": \"text/plain; charset=utf-8\" });\n res.end(\"Bad Request: invalid URL\");\n return;\n }\n\n const provided = query.get(\"token\") ?? \"\";\n if (!timingSafeStringEqual(provided, token)) {\n res.writeHead(403, { ...SECURITY_HEADERS, \"Content-Type\": \"text/plain; charset=utf-8\" });\n res.end(\"Forbidden: missing or invalid token\");\n return;\n }\n\n if (pathname === \"/events\") {\n res.writeHead(200, {\n ...SECURITY_HEADERS,\n \"Content-Type\": \"text/event-stream\",\n \"Cache-Control\": \"no-cache\",\n Connection: \"keep-alive\",\n });\n\n const snapshot = collectSnapshot();\n res.write(`data: ${JSON.stringify(snapshot)}\\n\\n`);\n\n clients.add(res);\n req.on(\"close\", () => clients.delete(res));\n return;\n }\n\n if (pathname === \"/api/status\") {\n const snapshot = collectSnapshot();\n res.writeHead(200, {\n ...SECURITY_HEADERS,\n \"Content-Type\": \"application/json\",\n });\n res.end(JSON.stringify(snapshot, null, 2));\n return;\n }\n\n res.writeHead(200, { ...SECURITY_HEADERS, \"Content-Type\": \"text/html; charset=utf-8\" });\n res.end(html);\n });\n\n server.listen(port, \"127.0.0.1\", () => {\n intervalHandle = setInterval(broadcast, 5000);\n if (intervalHandle && typeof intervalHandle === \"object\" && \"unref\" in intervalHandle) {\n intervalHandle.unref();\n }\n });\n\n return {\n port,\n token,\n url: `http://127.0.0.1:${port}/?token=${token}`,\n close: () => {\n if (intervalHandle) clearInterval(intervalHandle);\n for (const res of clients) {\n try { res.end(); } catch { /* noop */ }\n }\n clients.clear();\n server.close();\n },\n server,\n };\n}\n","// AUTO-GENERATED by scripts/build-dashboard-client.mjs — do not edit by hand.\n// Bundled status-dashboard client: Preact 10.29.8 + htm 3.1.1 + app.\n// Source: src/dashboard-client/main.js. Regenerate with: pnpm run build:dashboard\n\nexport const DASHBOARD_CLIENT = \"\\\"use strict\\\";(()=>{var J,b,we,tt,q,ge,Ce,Me,se,W,I,Se,ie,ae,oe,nt,O={},Y=[],st=/acit|ex(?:s|g|n|p|$)|rph|grid|ows|mnc|ntw|ine[ch]|zoo|^ord|itera/i,Q=Array.isArray;function T(t,e){for(var n in e)t[n]=e[n];return t}function le(t){t&&t.parentNode&&t.parentNode.removeChild(t)}function re(t,e,n){var s,o,a,l={};for(a in e)a==\\\"key\\\"?s=e[a]:a==\\\"ref\\\"?o=e[a]:l[a]=e[a];if(arguments.length>2&&(l.children=arguments.length>3?J.call(arguments,2):n),typeof t==\\\"function\\\"&&t.defaultProps!=null)for(a in t.defaultProps)l[a]===void 0&&(l[a]=t.defaultProps[a]);return K(t,l,s,o,null)}function K(t,e,n,s,o){var a={type:t,props:e,key:n,ref:s,__k:null,__:null,__b:0,__e:null,__c:null,constructor:void 0,__v:o==null?++we:o,__i:-1,__u:0};return o==null&&b.vnode!=null&&b.vnode(a),a}function X(t){return t.children}function z(t,e){this.props=t,this.context=e}function D(t,e){if(e==null)return t.__?D(t.__,t.__i+1):null;for(var n;e<t.__k.length;e++)if((n=t.__k[e])!=null&&n.__e!=null)return n.__e;return typeof t.type==\\\"function\\\"?D(t):null}function at(t){if(t.__P&&t.__d){var e=t.__v,n=e.__e,s=[],o=[],a=T({},e);a.__v=e.__v+1,b.vnode&&b.vnode(a),ce(t.__P,a,e,t.__n,t.__P.namespaceURI,32&e.__u?[n]:null,s,n==null?D(e):n,!!(32&e.__u),o),a.__v=e.__v,a.__.__k[a.__i]=a,Te(s,a,o),e.__e=e.__=null,a.__e!=n&&Ae(a)}}function Ae(t){if((t=t.__)!=null&&t.__c!=null)return t.__e=t.__c.base=null,t.__k.some(function(e){if(e!=null&&e.__e!=null)return t.__e=t.__c.base=e.__e}),Ae(t)}function ke(t){(!t.__d&&(t.__d=!0)&&q.push(t)&&!G.__r++||ge!=b.debounceRendering)&&((ge=b.debounceRendering)||Ce)(G)}function G(){try{for(var t,e=1;q.length;)q.length>e&&q.sort(Me),t=q.shift(),e=q.length,at(t)}finally{q.length=G.__r=0}}function Ee(t,e,n,s,o,a,l,u,_,c,r){var p,i,v,f,h,y,m=s&&s.__k||Y,$=e.length;for(_=ot(n,e,m,_,$),p=0;p<$;p++)(v=n.__k[p])!=null&&(i=v.__i!=-1&&m[v.__i]||O,v.__i=p,y=ce(t,v,i,o,a,l,u,_,c,r),f=v.__e,v.ref&&i.ref!=v.ref&&(i.ref&&de(i.ref,null,v),r.push(v.ref,v.__c||f,v)),h==null&&f!=null&&(h=f),4&v.__u?(_=Pe(v,_,t),i.__e&&(i.__e=null)):typeof v.type==\\\"function\\\"&&y!==void 0?_=y:f&&(_=f.nextSibling),v.__u&=-7);return n.__e=h,_}function ot(t,e,n,s,o){var a,l,u,_,c,r=n.length,p=r,i=0;for(t.__k=new Array(o),a=0;a<o;a++)(l=e[a])!=null&&typeof l!=\\\"boolean\\\"&&typeof l!=\\\"function\\\"?(typeof l==\\\"string\\\"||typeof l==\\\"number\\\"||typeof l==\\\"bigint\\\"||l.constructor==String?l=t.__k[a]=K(null,l,null,null,null):Q(l)?l=t.__k[a]=K(X,{children:l},null,null,null):l.constructor===void 0&&l.__b>0?l=t.__k[a]=K(l.type,l.props,l.key,l.ref?l.ref:null,l.__v):t.__k[a]=l,_=a+i,l.__=t,l.__b=t.__b+1,u=null,(c=l.__i=it(l,n,_,p))!=-1&&(p--,(u=n[c])&&(u.__u|=2)),u==null||u.__v==null?(c==-1&&(o>r?i--:o<r&&i++),typeof l.type!=\\\"function\\\"&&(l.__u|=4)):c!=_&&(c==_-1?i--:c==_+1?i++:(c>_?i--:i++,l.__u|=4))):t.__k[a]=null;if(p)for(a=0;a<r;a++)(u=n[a])!=null&&(2&u.__u)==0&&(u.__e==s&&(s=D(u)),Ne(u,u));return s}function Pe(t,e,n){var s,o;if(typeof t.type==\\\"function\\\"){for(s=t.__k,o=0;s&&o<s.length;o++)s[o]&&(s[o].__=t,e=Pe(s[o],e,n));return e}t.__e!=e&&(e&&t.type&&!e.parentNode&&(e=D(t)),e=n.insertBefore(t.__e,e||null));do e=e&&e.nextSibling;while(e!=null&&e.nodeType==8);return e}function it(t,e,n,s){var o,a,l,u=t.key,_=t.type,c=e[n],r=c!=null&&(2&c.__u)==0;if(c===null&&u==null||r&&u==c.key&&_==c.type)return n;if(s>(r?1:0)){for(o=n-1,a=n+1;o>=0||a<e.length;)if((c=e[l=o>=0?o--:a++])!=null&&(2&c.__u)==0&&u==c.key&&_==c.type)return l}return-1}function be(t,e,n){e[0]==\\\"-\\\"?t.setProperty(e,n==null?\\\"\\\":n):t[e]=n==null?\\\"\\\":typeof n!=\\\"number\\\"||st.test(e)?n:n+\\\"px\\\"}function V(t,e,n,s,o){var a,l;e:if(e==\\\"style\\\")if(typeof n==\\\"string\\\")t.style.cssText=n;else{if(typeof s==\\\"string\\\"&&(t.style.cssText=s=\\\"\\\"),s)for(e in s)n&&e in n||be(t.style,e,\\\"\\\");if(n)for(e in n)s&&n[e]==s[e]||be(t.style,e,n[e])}else if(e[0]==\\\"o\\\"&&e[1]==\\\"n\\\")a=e!=(e=e.replace(Se,\\\"$1\\\")),l=e.toLowerCase(),e=l in t||e==\\\"onFocusOut\\\"||e==\\\"onFocusIn\\\"?l.slice(2):e.slice(2),t.l||(t.l={}),t.l[e+a]=n,n?s?n[I]=s[I]:(n[I]=ie,t.addEventListener(e,a?oe:ae,a)):t.removeEventListener(e,a?oe:ae,a);else{if(o==\\\"http://www.w3.org/2000/svg\\\")e=e.replace(/xlink(H|:h)/,\\\"h\\\").replace(/sName$/,\\\"s\\\");else if(e!=\\\"width\\\"&&e!=\\\"height\\\"&&e!=\\\"href\\\"&&e!=\\\"list\\\"&&e!=\\\"form\\\"&&e!=\\\"tabIndex\\\"&&e!=\\\"download\\\"&&e!=\\\"rowSpan\\\"&&e!=\\\"colSpan\\\"&&e!=\\\"role\\\"&&e!=\\\"popover\\\"&&e in t)try{t[e]=n==null?\\\"\\\":n;break e}catch{}typeof n==\\\"function\\\"||(n==null||n===!1&&e[4]!=\\\"-\\\"?t.removeAttribute(e):t.setAttribute(e,e==\\\"popover\\\"&&n==1?\\\"\\\":n))}}function xe(t){return function(e){if(this.l){var n=this.l[e.type+t];if(e[W]==null)e[W]=ie++;else if(e[W]<n[I])return;return n(b.event?b.event(e):e)}}}function ce(t,e,n,s,o,a,l,u,_,c){var r,p,i,v,f,h,y,m,$,g,w,P,H,me,B,ne,R=e.type;if(e.constructor!==void 0)return null;128&n.__u&&(_=!!(32&n.__u),a=[u=e.__e=n.__e]),(r=b.__b)&&r(e);e:if(typeof R==\\\"function\\\"){p=l.length;try{if($=e.props,g=R.prototype&&R.prototype.render,w=(r=R.contextType)&&s[r.__c],P=r?w?w.props.value:r.__:s,n.__c?m=(i=e.__c=n.__c).__=i.__E:(g?e.__c=i=new R($,P):(e.__c=i=new z($,P),i.constructor=R,i.render=rt),w&&w.sub(i),i.state||(i.state={}),i.__n=s,v=i.__d=!0,i.__h=[],i._sb=[]),g&&i.__s==null&&(i.__s=i.state),g&&R.getDerivedStateFromProps!=null&&(i.__s==i.state&&(i.__s=T({},i.__s)),T(i.__s,R.getDerivedStateFromProps($,i.__s))),f=i.props,h=i.state,i.__v=e,v)g&&R.getDerivedStateFromProps==null&&i.componentWillMount!=null&&i.componentWillMount(),g&&i.componentDidMount!=null&&i.__h.push(i.componentDidMount);else{if(g&&R.getDerivedStateFromProps==null&&$!==f&&i.componentWillReceiveProps!=null&&i.componentWillReceiveProps($,P),e.__v==n.__v||!i.__e&&i.shouldComponentUpdate!=null&&i.shouldComponentUpdate($,i.__s,P)===!1){e.__v!=n.__v&&(i.props=$,i.state=i.__s,i.__d=!1),e.__e=n.__e,e.__k=n.__k,e.__k.some(function(j){j&&(j.__=e)}),Y.push.apply(i.__h,i._sb),i._sb=[],i.__h.length&&l.push(i),u=D(n);break e}i.componentWillUpdate!=null&&i.componentWillUpdate($,i.__s,P),g&&i.componentDidUpdate!=null&&i.__h.push(function(){i.componentDidUpdate(f,h,y)})}if(i.context=P,i.props=$,i.__P=t,i.__e=!1,H=b.__r,me=0,g)i.state=i.__s,i.__d=!1,H&&H(e),r=i.render(i.props,i.state,i.context),Y.push.apply(i.__h,i._sb),i._sb=[];else do i.__d=!1,H&&H(e),r=i.render(i.props,i.state,i.context),i.state=i.__s;while(i.__d&&++me<25);i.state=i.__s,i.getChildContext!=null&&(s=T(T({},s),i.getChildContext())),g&&!v&&i.getSnapshotBeforeUpdate!=null&&(y=i.getSnapshotBeforeUpdate(f,h)),B=r!=null&&r.type===X&&r.key==null?qe(r.props.children):r,u=Ee(t,Q(B)?B:[B],e,n,s,o,a,l,u,_,c),i.base=e.__e,e.__u&=-161,i.__h.length&&l.push(i),m&&(i.__E=i.__=null)}catch(j){if(l.length=p,e.__v=null,_||a!=null){if(j.then){for(e.__u|=_?160:128;u&&u.nodeType==8&&u.nextSibling;)u=u.nextSibling;a!=null&&(a[a.indexOf(u)]=null),e.__e=u}else if(a!=null)for(ne=a.length;ne--;)le(a[ne])}else e.__e=n.__e;e.__k==null&&(e.__k=n.__k||[]),j.then||Re(e),b.__e(j,e,n)}}else a==null&&e.__v==n.__v?(e.__k=n.__k,e.__e=n.__e):u=e.__e=lt(n.__e,e,n,s,o,a,l,_,c);return(r=b.diffed)&&r(e),128&e.__u?void 0:u}function Re(t){t&&(t.__c&&(t.__c.__e=!0),t.__k&&t.__k.some(Re))}function Te(t,e,n){for(var s=0;s<n.length;s++)de(n[s],n[++s],n[++s]);b.__c&&b.__c(e,t),t.some(function(o){try{t=o.__h,o.__h=[],t.some(function(a){a.call(o)})}catch(a){b.__e(a,o.__v)}})}function qe(t){return typeof t!=\\\"object\\\"||t==null||t.__b>0?t:Q(t)?t.map(qe):t.constructor!==void 0?null:T({},t)}function lt(t,e,n,s,o,a,l,u,_){var c,r,p,i,v,f,h,y=n.props||O,m=e.props,$=e.type;if($==\\\"svg\\\"?o=\\\"http://www.w3.org/2000/svg\\\":$==\\\"math\\\"?o=\\\"http://www.w3.org/1998/Math/MathML\\\":o||(o=\\\"http://www.w3.org/1999/xhtml\\\"),a!=null){for(c=0;c<a.length;c++)if((v=a[c])&&\\\"setAttribute\\\"in v==!!$&&($?v.localName==$:v.nodeType==3)){t=v,a[c]=null;break}}if(t==null){if($==null)return document.createTextNode(m);t=document.createElementNS(o,$,m.is&&m),u&&(b.__m&&b.__m(e,a),u=!1),a=null}if($==null)y===m||u&&t.data==m||(t.data=m);else{if(a=$==\\\"textarea\\\"&&m.defaultValue!=null?null:a&&J.call(t.childNodes),!u&&a!=null)for(y={},c=0;c<t.attributes.length;c++)y[(v=t.attributes[c]).name]=v.value;for(c in y)v=y[c],c==\\\"dangerouslySetInnerHTML\\\"?p=v:c==\\\"children\\\"||c in m||c==\\\"value\\\"&&\\\"defaultValue\\\"in m||c==\\\"checked\\\"&&\\\"defaultChecked\\\"in m||V(t,c,null,v,o);for(c in m)v=m[c],c==\\\"children\\\"?i=v:c==\\\"dangerouslySetInnerHTML\\\"?r=v:c==\\\"value\\\"?f=v:c==\\\"checked\\\"?h=v:u&&typeof v!=\\\"function\\\"||y[c]===v||V(t,c,v,y[c],o);if(r)u||p&&(r.__html==p.__html||r.__html==t.innerHTML)||(t.innerHTML=r.__html),e.__k=[];else if(p&&(t.innerHTML=\\\"\\\"),Ee(e.type==\\\"template\\\"?t.content:t,Q(i)?i:[i],e,n,s,$==\\\"foreignObject\\\"?\\\"http://www.w3.org/1999/xhtml\\\":o,a,l,a?a[0]:n.__k&&D(n,0),u,_),a!=null)for(c=a.length;c--;)le(a[c]);u&&$!=\\\"textarea\\\"||(c=\\\"value\\\",$==\\\"progress\\\"&&f==null?t.removeAttribute(\\\"value\\\"):f!=null&&(f!==t[c]||$==\\\"progress\\\"&&!f||$==\\\"option\\\"&&f!=y[c])&&V(t,c,f,y[c],o),c=\\\"checked\\\",h!=null&&h!=t[c]&&V(t,c,h,y[c],o))}return t}function de(t,e,n){try{if(typeof t==\\\"function\\\"){var s=typeof t.__u==\\\"function\\\";s&&t.__u(),s&&e==null||(t.__u=t(e))}else t.current=e}catch(o){b.__e(o,n)}}function Ne(t,e,n){var s,o;if(b.unmount&&b.unmount(t),(s=t.ref)&&(s.current&&s.current!=t.__e||de(s,null,e)),(s=t.__c)!=null){if(s.componentWillUnmount)try{s.componentWillUnmount()}catch(a){b.__e(a,e)}s.base=s.__P=s.__n=null}if(s=t.__k)for(o=0;o<s.length;o++)s[o]&&Ne(s[o],e,n||typeof t.type!=\\\"function\\\");n||le(t.__e),t.__c=t.__=t.__e=void 0}function rt(t,e,n){return this.constructor(t,n)}function He(t,e,n){var s,o,a,l;e==document&&(e=document.documentElement),b.__&&b.__(t,e),o=(s=typeof n==\\\"function\\\")?null:n&&n.__k||e.__k,a=[],l=[],ce(e,t=(!s&&n||e).__k=re(X,null,[t]),o||O,O,e.namespaceURI,!s&&n?[n]:o?null:e.firstChild?J.call(e.childNodes):null,a,!s&&n?n:o?o.__e:e.firstChild,s,l),Te(a,t,l),t.props.children=null}J=Y.slice,b={__e:function(t,e,n,s){for(var o,a,l;e=e.__;)if((o=e.__c)&&!o.__)try{if((a=o.constructor)&&a.getDerivedStateFromError!=null&&(o.setState(a.getDerivedStateFromError(t)),l=o.__d),o.componentDidCatch!=null&&(o.componentDidCatch(t,s||{}),l=o.__d),l)return o.__E=o}catch(u){t=u}throw t}},we=0,tt=function(t){return t!=null&&t.constructor===void 0},z.prototype.setState=function(t,e){var n;n=this.__s!=null&&this.__s!=this.state?this.__s:this.__s=T({},this.state),typeof t==\\\"function\\\"&&(t=t(T({},n),this.props)),t&&T(n,t),t!=null&&this.__v&&(e&&this._sb.push(e),ke(this))},z.prototype.forceUpdate=function(t){this.__v&&(this.__e=!0,t&&this.__h.push(t),ke(this))},z.prototype.render=X,q=[],Ce=typeof Promise==\\\"function\\\"?Promise.prototype.then.bind(Promise.resolve()):setTimeout,Me=function(t,e){return t.__v.__b-e.__v.__b},G.__r=0,se=Math.random().toString(8),W=\\\"__d\\\"+se,I=\\\"__a\\\"+se,Se=/(PointerCapture)$|Capture$/i,ie=0,ae=xe(!1),oe=xe(!0),nt=0;var U,x,ue,De,F=0,We=[],C=b,je=C.__b,Le=C.__r,Ie=C.diffed,Ue=C.__c,Fe=C.unmount,Be=C.__;function _e(t,e){C.__h&&C.__h(x,t,F||e),F=0;var n=x.__H||(x.__H={__:[],__h:[]});return t>=n.__.length&&n.__.push({}),n.__[t]}function N(t){return F=1,ct(ze,t)}function ct(t,e,n){var s=_e(U++,2);if(s.t=t,!s.__c&&(s.__=[n?n(e):ze(void 0,e),function(u){var _=s.__N?s.__N[0]:s.__[0],c=s.t(_,u);_!==c&&(s.__N=[c,s.__[1]],s.__c.setState({}))}],s.__c=x,!x.__f)){var o=function(u,_,c){if(!s.__c.__H)return!0;var r=!1,p=s.__c.props!==u;if(s.__c.__H.__.some(function(v){if(v.__N){r=!0;var f=v.__[0];v.__=v.__N,v.__N=void 0,f!==v.__[0]&&(p=!0)}}),a){var i=a.call(this,u,_,c);return r?i||p:i}return!r||p};x.__f=!0;var a=x.shouldComponentUpdate,l=x.componentWillUpdate;x.componentWillUpdate=function(u,_,c){if(this.__e){var r=a;a=void 0,o(u,_,c),a=r}l&&l.call(this,u,_,c)},x.shouldComponentUpdate=o}return s.__N||s.__}function ee(t,e){var n=_e(U++,3);!C.__s&&Ke(n.__H,e)&&(n.__=t,n.u=e,x.__H.__h.push(n))}function ve(t){return F=5,te(function(){return{current:t}},[])}function te(t,e){var n=_e(U++,7);return Ke(n.__H,e)&&(n.__=t(),n.__H=e,n.__h=t),n.__}function he(t,e){return F=8,te(function(){return t},e)}function dt(){for(var t;t=We.shift();){var e=t.__H;if(t.__P&&e)try{e.__h.some(Z),e.__h.some(pe),e.__h=[]}catch(n){e.__h=[],C.__e(n,t.__v)}}}C.__b=function(t){x=null,je&&je(t)},C.__=function(t,e){t&&e.__k&&e.__k.__m&&(t.__m=e.__k.__m),Be&&Be(t,e)},C.__r=function(t){Le&&Le(t),U=0;var e=(x=t.__c).__H;e&&(ue===x?(e.__h=[],x.__h=[],e.__.some(function(n){n.__N&&(n.__=n.__N),n.u=n.__N=void 0})):(e.__h.some(Z),e.__h.some(pe),e.__h=[],U=0)),ue=x},C.diffed=function(t){Ie&&Ie(t);var e=t.__c;e&&e.__H&&(e.__H.__h.length&&(We.push(e)!==1&&De===C.requestAnimationFrame||((De=C.requestAnimationFrame)||ut)(dt)),e.__H.__.some(function(n){n.u&&(n.__H=n.u,n.u=void 0)})),ue=x=null},C.__c=function(t,e){e.some(function(n){try{n.__h.some(Z),n.__h=n.__h.filter(function(s){return!s.__||pe(s)})}catch(s){e.some(function(o){o.__h&&(o.__h=[])}),e=[],C.__e(s,n.__v)}}),Ue&&Ue(t,e)},C.unmount=function(t){Fe&&Fe(t);var e,n=t.__c;n&&n.__H&&(n.__H.__.some(function(s){try{Z(s)}catch(o){e=o}}),n.__H=void 0,e&&C.__e(e,n.__v))};var Ve=typeof requestAnimationFrame==\\\"function\\\";function ut(t){var e,n=function(){clearTimeout(s),Ve&&cancelAnimationFrame(e),setTimeout(t)},s=setTimeout(n,35);Ve&&(e=requestAnimationFrame(n))}function Z(t){var e=x,n=t.__c;typeof n==\\\"function\\\"&&(t.__c=void 0,n()),x=e}function pe(t){var e=x;t.__c=t.__(),x=e}function Ke(t,e){return!t||t.length!==e.length||e.some(function(n,s){return n!==t[s]})}function ze(t,e){return typeof e==\\\"function\\\"?e(t):e}var Ye=function(t,e,n,s){var o;e[0]=0;for(var a=1;a<e.length;a++){var l=e[a++],u=e[a]?(e[0]|=l?1:2,n[e[a++]]):e[++a];l===3?s[0]=u:l===4?s[1]=Object.assign(s[1]||{},u):l===5?(s[1]=s[1]||{})[e[++a]]=u:l===6?s[1][e[++a]]+=u+\\\"\\\":l?(o=t.apply(u,Ye(t,u,n,[\\\"\\\",null])),s.push(o),u[0]?e[0]|=2:(e[a-2]=0,e[a]=o)):s.push(u)}return s},Oe=new Map;function Ge(t){var e=Oe.get(this);return e||(e=new Map,Oe.set(this,e)),(e=Ye(this,e.get(t)||(e.set(t,e=(function(n){for(var s,o,a=1,l=\\\"\\\",u=\\\"\\\",_=[0],c=function(i){a===1&&(i||(l=l.replace(/^\\\\s*\\\\n\\\\s*|\\\\s*\\\\n\\\\s*$/g,\\\"\\\")))?_.push(0,i,l):a===3&&(i||l)?(_.push(3,i,l),a=2):a===2&&l===\\\"...\\\"&&i?_.push(4,i,0):a===2&&l&&!i?_.push(5,0,!0,l):a>=5&&((l||!i&&a===5)&&(_.push(a,0,l,o),a=6),i&&(_.push(a,i,0,o),a=6)),l=\\\"\\\"},r=0;r<n.length;r++){r&&(a===1&&c(),c(r));for(var p=0;p<n[r].length;p++)s=n[r][p],a===1?s===\\\"<\\\"?(c(),_=[_],a=3):l+=s:a===4?l===\\\"--\\\"&&s===\\\">\\\"?(a=1,l=\\\"\\\"):l=s+l[0]:u?s===u?u=\\\"\\\":l+=s:s==='\\\"'||s===\\\"'\\\"?u=s:s===\\\">\\\"?(c(),a=1):a&&(s===\\\"=\\\"?(a=5,o=l,l=\\\"\\\"):s===\\\"/\\\"&&(a<5||n[r][p+1]===\\\">\\\")?(c(),a===3&&(_=_[0]),a=_,(_=_[0]).push(2,0,a),a=0):s===\\\" \\\"||s===\\\"\\t\\\"||s===`\\n`||s===\\\"\\\\r\\\"?(c(),a=2):l+=s),a===3&&l===\\\"!--\\\"&&(a=4,_=_[0])}return c(),_})(t)),e),arguments,[])).length>1?e:e[0]}var d=Ge.bind(re),Je=window.__QRING__&&window.__QRING__.token||\\\"\\\",fe=Je?\\\"?token=\\\"+encodeURIComponent(Je):\\\"\\\",A=t=>'<svg width=\\\"14\\\" height=\\\"14\\\" viewBox=\\\"0 0 24 24\\\" fill=\\\"none\\\" stroke=\\\"url(#neon-grad)\\\" stroke-width=\\\"2\\\" stroke-linecap=\\\"round\\\" stroke-linejoin=\\\"round\\\">'+t+\\\"</svg>\\\",k={health:A('<path d=\\\"M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z\\\"/>'),environment:A('<polyline points=\\\"22 12 18 12 15 21 9 3 6 12 2 12\\\"/>'),decay:A('<path d=\\\"M5 22h14\\\"/><path d=\\\"M5 2h14\\\"/><path d=\\\"M17 22v-4.172a2 2 0 0 0-.586-1.414L12 12l-4.414 4.414A2 2 0 0 0 7 17.828V22\\\"/><path d=\\\"M7 2v4.172a2 2 0 0 0 .586 1.414L12 12l4.414-4.414A2 2 0 0 0 17 6.172V2\\\"/>'),superposition:A('<polygon points=\\\"12 2 2 7 12 12 22 7 12 2\\\"/><polyline points=\\\"2 17 12 22 22 17\\\"/><polyline points=\\\"2 12 12 17 22 12\\\"/>'),entangle:A('<path d=\\\"M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71\\\"/><path d=\\\"M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71\\\"/>'),tunnel:A('<path d=\\\"M9 10h.01\\\"/><path d=\\\"M15 10h.01\\\"/><path d=\\\"M12 2a8 8 0 0 0-8 8v12l3-3 2.5 2.5L12 19l2.5 2.5L17 19l3 3V10a8 8 0 0 0-8-8z\\\"/>'),anomaly:A('<path d=\\\"M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z\\\"/><line x1=\\\"12\\\" y1=\\\"9\\\" x2=\\\"12\\\" y2=\\\"13\\\"/><line x1=\\\"12\\\" y1=\\\"17\\\" x2=\\\"12.01\\\" y2=\\\"17\\\"/>'),audit:A('<circle cx=\\\"12\\\" cy=\\\"12\\\" r=\\\"10\\\"/><circle cx=\\\"12\\\" cy=\\\"12\\\" r=\\\"6\\\"/><circle cx=\\\"12\\\" cy=\\\"12\\\" r=\\\"2\\\"/>'),key:A('<path d=\\\"M21 2l-2 2m-7.61 7.61a5.5 5.5 0 1 1-7.778 7.778 5.5 5.5 0 0 1 7.777-7.777zm0 0L15.5 7.5m0 0l3 3L22 7l-3-3m-3.5 3.5L19 4\\\"/>'),hook:A('<path d=\\\"M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71\\\"/><path d=\\\"M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71\\\"/>'),approve:A('<path d=\\\"M9 11l3 3L22 4\\\"/><path d=\\\"M21 12v7a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11\\\"/>'),manifest:A('<path d=\\\"M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z\\\"/><polyline points=\\\"14 2 14 8 20 8\\\"/><line x1=\\\"16\\\" y1=\\\"13\\\" x2=\\\"8\\\" y2=\\\"13\\\"/><line x1=\\\"16\\\" y1=\\\"17\\\" x2=\\\"8\\\" y2=\\\"17\\\"/>'),policy:A('<path d=\\\"M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z\\\"/><path d=\\\"M9 12l2 2 4-4\\\"/>'),memory:A('<rect x=\\\"4\\\" y=\\\"4\\\" width=\\\"16\\\" height=\\\"16\\\" rx=\\\"2\\\"/><rect x=\\\"9\\\" y=\\\"9\\\" width=\\\"6\\\" height=\\\"6\\\"/><line x1=\\\"9\\\" y1=\\\"2\\\" x2=\\\"9\\\" y2=\\\"4\\\"/><line x1=\\\"15\\\" y1=\\\"2\\\" x2=\\\"15\\\" y2=\\\"4\\\"/><line x1=\\\"9\\\" y1=\\\"20\\\" x2=\\\"9\\\" y2=\\\"22\\\"/><line x1=\\\"15\\\" y1=\\\"20\\\" x2=\\\"15\\\" y2=\\\"22\\\"/><line x1=\\\"20\\\" y1=\\\"9\\\" x2=\\\"22\\\" y2=\\\"9\\\"/><line x1=\\\"20\\\" y1=\\\"14\\\" x2=\\\"22\\\" y2=\\\"14\\\"/><line x1=\\\"2\\\" y1=\\\"9\\\" x2=\\\"4\\\" y2=\\\"9\\\"/><line x1=\\\"2\\\" y1=\\\"14\\\" x2=\\\"4\\\" y2=\\\"14\\\"/>'),folder:'<svg viewBox=\\\"0 0 24 24\\\" fill=\\\"none\\\" stroke=\\\"currentColor\\\" stroke-width=\\\"2\\\" stroke-linecap=\\\"round\\\" stroke-linejoin=\\\"round\\\"><path d=\\\"M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z\\\"/></svg>',search:'<svg viewBox=\\\"0 0 24 24\\\" fill=\\\"none\\\" stroke=\\\"currentColor\\\" stroke-width=\\\"2\\\" stroke-linecap=\\\"round\\\" stroke-linejoin=\\\"round\\\"><circle cx=\\\"11\\\" cy=\\\"11\\\" r=\\\"8\\\"/><line x1=\\\"21\\\" y1=\\\"21\\\" x2=\\\"16.65\\\" y2=\\\"16.65\\\"/></svg>',lock:'<svg viewBox=\\\"0 0 24 24\\\" fill=\\\"none\\\" stroke=\\\"currentColor\\\" stroke-width=\\\"2\\\" stroke-linecap=\\\"round\\\" stroke-linejoin=\\\"round\\\"><rect x=\\\"3\\\" y=\\\"11\\\" width=\\\"18\\\" height=\\\"11\\\" rx=\\\"2\\\" ry=\\\"2\\\"/><path d=\\\"M7 11V7a5 5 0 0 1 10 0v4\\\"/></svg>'};function $e(t){if(!t)return\\\"env-default\\\";let e=t.toLowerCase();return e===\\\"prod\\\"||e===\\\"production\\\"?\\\"env-prod\\\":e===\\\"staging\\\"||e===\\\"stage\\\"?\\\"env-staging\\\":e===\\\"dev\\\"||e===\\\"development\\\"?\\\"env-dev\\\":e===\\\"test\\\"||e===\\\"testing\\\"?\\\"env-test\\\":\\\"env-default\\\"}function Xe(t,e){return e||t>=90?\\\"var(--danger)\\\":t>=75?\\\"var(--warning)\\\":\\\"var(--accent)\\\"}function Ze(t){let e=new Date(t);return String(e.getHours()).padStart(2,\\\"0\\\")+\\\":\\\"+String(e.getMinutes()).padStart(2,\\\"0\\\")+\\\":\\\"+String(e.getSeconds()).padStart(2,\\\"0\\\")}function et(t){if(!t)return\\\"\\\\u2014\\\";let e=Math.floor((Date.now()-new Date(t).getTime())/1e3);return isNaN(e)?\\\"\\\\u2014\\\":e<5?\\\"just now\\\":e<60?e+\\\"s ago\\\":e<3600?Math.floor(e/60)+\\\"m ago\\\":e<86400?Math.floor(e/3600)+\\\"h ago\\\":Math.floor(e/86400)+\\\"d ago\\\"}function ye(t){return t==null?\\\"\\\\u2014\\\":t<0?\\\"expired\\\":t<60?t+\\\"s\\\":t<3600?Math.floor(t/60)+\\\"m\\\":t<86400?Math.floor(t/3600)+\\\"h\\\":Math.floor(t/86400)+\\\"d\\\"}var L=({svg:t})=>d`<span style=\\\"display:contents\\\" dangerouslySetInnerHTML=${{__html:t}} />`;function M({icon:t,title:e,aside:n,asideStyle:s}){return d`<div class=\\\"card-title\\\">\\n <${L} svg=${t} />${e}\\n ${n!=null?d`<span class=\\\"title-aside\\\" style=${s||\\\"\\\"}>${n}</span>`:null}\\n </div>`}function S({delay:t=0,wide:e=!1,style:n=\\\"\\\",children:s}){let o=\\\"card\\\"+(e?\\\" grid-wide\\\":\\\"\\\"),a=\\\"animation-delay:\\\"+t+\\\"ms\\\"+(n?\\\";\\\"+n:\\\"\\\");return d`<div class=${o} style=${a}>${s}</div>`}function E({children:t,cta:e}){return d`<div class=\\\"empty\\\">${t}${e?d`<span class=\\\"empty-cta\\\">${e}</span>`:null}</div>`}function Qe({snap:t,paused:e,connState:n,onPause:s,onRefresh:o,lastUpdate:a}){let l=e?\\\"paused\\\":n===\\\"live\\\"?\\\"live\\\":n===\\\"reconnecting\\\"?\\\"reconnecting\\\\u2026\\\":\\\"connecting\\\\u2026\\\",u=\\\"status-dot\\\"+(e?\\\" paused\\\":n===\\\"reconnecting\\\"?\\\" disconnected\\\":\\\"\\\");return d`\\n <header class=\\\"header\\\">\\n <div class=\\\"header-left\\\">\\n <h1>\\n <span class=\\\"q-icon\\\"><svg width=\\\"26\\\" height=\\\"26\\\" viewBox=\\\"0 0 24 24\\\" fill=\\\"none\\\" stroke=\\\"url(#neon-grad)\\\" stroke-width=\\\"2\\\" stroke-linecap=\\\"round\\\" stroke-linejoin=\\\"round\\\" aria-hidden=\\\"true\\\" focusable=\\\"false\\\"><circle cx=\\\"12\\\" cy=\\\"12\\\" r=\\\"10\\\"/><circle cx=\\\"12\\\" cy=\\\"12\\\" r=\\\"6\\\"/><circle cx=\\\"12\\\" cy=\\\"12\\\" r=\\\"2\\\"/></svg></span>\\n <span class=\\\"brand\\\">q-ring</span>\\n <span class=\\\"sub\\\">quantum status</span>\\n </h1>\\n <span class=\\\"meta-chip\\\" title=\\\"q-ring version\\\">\\n <svg viewBox=\\\"0 0 24 24\\\" fill=\\\"none\\\" stroke=\\\"currentColor\\\" stroke-width=\\\"2\\\" stroke-linecap=\\\"round\\\" stroke-linejoin=\\\"round\\\"><polyline points=\\\"20 6 9 17 4 12\\\"/></svg>\\n ${\\\" v\\\"+(t?t.version:\\\"\\\")}\\n </span>\\n <span class=\\\"meta-chip\\\" title=\\\"Project working directory\\\">\\n <${L} svg=${k.folder} /> ${t?t.projectPath:\\\"\\\"}\\n </span>\\n </div>\\n <div class=\\\"header-right\\\">\\n <span class=\\\"conn-label\\\" title=\\\"Last update from the live stream\\\"><span>${a}</span></span>\\n <button class=\\\"btn\\\" type=\\\"button\\\" title=\\\"Pause / resume the live stream (P)\\\" onClick=${s}>\\n ${e?d`<svg viewBox=\\\"0 0 24 24\\\" fill=\\\"none\\\" stroke=\\\"currentColor\\\" stroke-width=\\\"2\\\" stroke-linecap=\\\"round\\\" stroke-linejoin=\\\"round\\\"><polygon points=\\\"5 3 19 12 5 21 5 3\\\"/></svg>`:d`<svg viewBox=\\\"0 0 24 24\\\" fill=\\\"none\\\" stroke=\\\"currentColor\\\" stroke-width=\\\"2\\\" stroke-linecap=\\\"round\\\" stroke-linejoin=\\\"round\\\"><rect x=\\\"6\\\" y=\\\"4\\\" width=\\\"4\\\" height=\\\"16\\\" rx=\\\"1\\\"/><rect x=\\\"14\\\" y=\\\"4\\\" width=\\\"4\\\" height=\\\"16\\\" rx=\\\"1\\\"/></svg>`}\\n <span>${e?\\\"Resume\\\":\\\"Pause\\\"}</span>\\n </button>\\n <button class=\\\"btn\\\" type=\\\"button\\\" title=\\\"Force-refresh now (R)\\\" onClick=${o}>\\n <svg viewBox=\\\"0 0 24 24\\\" fill=\\\"none\\\" stroke=\\\"currentColor\\\" stroke-width=\\\"2\\\" stroke-linecap=\\\"round\\\" stroke-linejoin=\\\"round\\\"><path d=\\\"M21 12a9 9 0 1 1-3.5-7.1\\\"/><polyline points=\\\"21 3 21 9 15 9\\\"/></svg>\\n Refresh\\n </button>\\n <a class=\\\"btn\\\" href=${\\\"/api/status\\\"+fe} target=\\\"_blank\\\" rel=\\\"noopener\\\" title=\\\"Open the raw JSON snapshot in a new tab\\\">\\n <svg viewBox=\\\"0 0 24 24\\\" fill=\\\"none\\\" stroke=\\\"currentColor\\\" stroke-width=\\\"2\\\" stroke-linecap=\\\"round\\\" stroke-linejoin=\\\"round\\\"><polyline points=\\\"16 18 22 12 16 6\\\"/><polyline points=\\\"8 6 2 12 8 18\\\"/></svg>\\n JSON\\n </a>\\n <span class=\\\"conn-label\\\"><span class=${u}></span><span>${l}</span></span>\\n </div>\\n </header>\\n `}function pt({snap:t}){let e=t,n=e.auditMetrics||{total:0,byAction:{},topRead:[]},s=n.byAction.read||0,o=(n.byAction.write||0)+(n.byAction.delete||0)+(n.byAction.rotate||0),a=n.byAction.policy_deny||0,l=(e.hooks||[]).filter(r=>r.enabled).length,u=(e.approvals||[]).filter(r=>r.valid).length,_=(e.anomalies||[]).length,c=[{label:\\\"Secrets\\\",icon:k.key,value:e.health.total,sub:e.health.healthy+\\\" healthy \\\\xB7 \\\"+e.health.stale+\\\" stale \\\\xB7 \\\"+e.health.expired+\\\" expired\\\",cls:\\\"\\\"},{label:\\\"Environment\\\",icon:k.environment,value:e.environment?e.environment.env:\\\"none\\\",sub:e.environment?\\\"via \\\"+e.environment.source:\\\"no env detected\\\",cls:e.environment?\\\"\\\":\\\"dim\\\"},{label:\\\"Protected\\\",icon:k.lock,value:e.protectedCount,sub:\\\"require approval\\\",cls:e.protectedCount>0?\\\"warning\\\":\\\"dim\\\"},{label:\\\"Approvals\\\",icon:k.approve,value:u,sub:\\\"active grants\\\",cls:u>0?\\\"green\\\":\\\"dim\\\"},{label:\\\"Hooks\\\",icon:k.hook,value:l,sub:\\\"enabled / \\\"+(e.hooks||[]).length+\\\" total\\\",cls:l>0?\\\"\\\":\\\"dim\\\"},{label:\\\"Reads (24h)\\\",icon:k.audit,value:s,sub:o+\\\" writes \\\\xB7 \\\"+a+\\\" denied\\\",cls:\\\"\\\"},{label:\\\"Anomalies\\\",icon:k.anomaly,value:_,sub:_?\\\"investigate now\\\":\\\"all clear\\\",cls:_?\\\"danger\\\":\\\"green\\\"}];return d`<section class=\\\"kpi-strip\\\">\\n ${c.map(r=>d`<div class=\\\"kpi\\\" key=${r.label}>\\n <div class=\\\"kpi-label\\\"><${L} svg=${r.icon} />${r.label}</div>\\n <div class=\\\"kpi-value ${r.cls}\\\">${r.value}</div>\\n <div class=\\\"kpi-sub\\\">${r.sub}</div>\\n </div>`)}\\n </section>`}function _t({snap:t,delay:e}){let n=t.health,s=n.total||1,o=42,a=2*Math.PI*o,l=[{v:n.healthy,c:\\\"var(--accent)\\\"},{v:n.stale,c:\\\"var(--warning)\\\"},{v:n.expired,c:\\\"var(--danger)\\\"},{v:n.noDecay,c:\\\"var(--text-dim)\\\"}],u=0,_=l.map((r,p)=>{let i=r.v/s*a,v=d`<circle key=${p} cx=\\\"50\\\" cy=\\\"50\\\" r=${o} fill=\\\"none\\\" stroke=${r.c} stroke-width=\\\"12\\\" stroke-dasharray=${i+\\\" \\\"+(a-i)} stroke-dashoffset=${-u} stroke-linecap=\\\"round\\\" opacity=\\\"0.85\\\" />`;return u+=i,v}),c=t.scopes||{global:0,project:0,team:0,org:0};return d`<${S} delay=${e}>\\n <${M} icon=${k.health} title=\\\"Health Summary\\\" aside=${n.healthy+\\\"/\\\"+n.total+\\\" healthy\\\"} />\\n <div class=\\\"health-row\\\">\\n <div class=\\\"donut-wrap\\\">\\n <svg viewBox=\\\"0 0 100 100\\\" width=\\\"100\\\" height=\\\"100\\\">${_}</svg>\\n <div class=\\\"donut-label\\\">${n.total}<small>secrets</small></div>\\n </div>\\n <div class=\\\"health-legend\\\">\\n <div class=\\\"legend-item\\\"><span class=\\\"legend-dot\\\" style=\\\"background:var(--accent)\\\"></span>Healthy ${n.healthy}</div>\\n <div class=\\\"legend-item\\\"><span class=\\\"legend-dot\\\" style=\\\"background:var(--warning)\\\"></span>Stale ${n.stale}</div>\\n <div class=\\\"legend-item\\\"><span class=\\\"legend-dot\\\" style=\\\"background:var(--danger)\\\"></span>Expired ${n.expired}</div>\\n <div class=\\\"legend-item\\\"><span class=\\\"legend-dot\\\" style=\\\"background:var(--text-dim)\\\"></span>No decay ${n.noDecay}</div>\\n </div>\\n </div>\\n <div class=\\\"scope-row\\\">\\n ${[\\\"global\\\",\\\"project\\\",\\\"team\\\",\\\"org\\\"].filter(r=>c[r]).map(r=>d`<span class=\\\"scope-pill\\\" key=${r}><strong>${c[r]}</strong>${r}</span>`)}\\n </div>\\n <//>`}function vt({snap:t,delay:e}){let n=t.environment;return d`<${S} delay=${e}>\\n <${M} icon=${k.environment} title=\\\"Environment\\\" />\\n ${n?d`<div class=\\\"env-status\\\"><span class=\\\"env-big env-pill ${$e(n.env)}\\\">${n.env}</span><span class=\\\"env-source\\\">detected via ${n.source}</span></div>`:d`<${E} cta=\\\"$ qring env --project-path .\\\">No environment detected.<//>`}\\n <//>`}function ht({snap:t,delay:e}){let n=t.manifest;if(!n)return d`<${S} delay=${e}>\\n <${M} icon=${k.manifest} title=\\\"Manifest\\\" />\\n <${E} cta=${d`$ qring wizard <name>`}>No <code>.q-ring.json</code> manifest in this project.<//>\\n <//>`;let s=n.required-n.missing.length-n.expired.length-n.stale.length,o=n.required?Math.max(0,Math.round(s/n.required*100)):100,a=n.missing.length?\\\"var(--danger)\\\":n.expired.length||n.stale.length?\\\"var(--warning)\\\":\\\"var(--green)\\\",l=(_,c,r)=>d`<div class=\\\"label\\\">${_} (${c.length})</div><div>${c.map(p=>d`<span class=\\\"key-pill ${r}\\\" key=${p}>${p}</span>`)}</div>`,u=n.missing.length||n.expired.length||n.stale.length;return d`<${S} delay=${e}>\\n <${M} icon=${k.manifest} title=\\\"Manifest\\\" aside=${n.required+\\\" required / \\\"+n.declared+\\\" declared\\\"} />\\n <div class=\\\"manifest-bars\\\">\\n <div class=\\\"manifest-row\\\"><span style=\\\"min-width:54px\\\">healthy</span><div class=\\\"manifest-bar\\\"><div class=\\\"manifest-fill\\\" style=${\\\"width:\\\"+o+\\\"%;background:\\\"+a}></div></div><span>${o}%</span></div>\\n </div>\\n <div class=\\\"manifest-list\\\">\\n ${n.missing.length?l(\\\"Missing required\\\",n.missing,\\\"miss\\\"):null}\\n ${n.expired.length?l(\\\"Expired\\\",n.expired,\\\"exp\\\"):null}\\n ${n.stale.length?l(\\\"Stale\\\",n.stale,\\\"exp\\\"):null}\\n ${u?null:d`<div class=\\\"label\\\">All required keys present and healthy.</div>`}\\n </div>\\n <//>`}function ft({snap:t,delay:e}){let n=t.policy||{counts:{}},s=n.counts||{};if(!(n.hasMcpPolicy||n.hasExecPolicy||n.hasSecretPolicy))return d`<${S} delay=${e}>\\n <${M} icon=${k.policy} title=\\\"Policy\\\" />\\n <${E} cta=${d`add <code>policy</code> to <code>.q-ring.json</code>`}>No governance policy declared.<//>\\n <//>`;let a=({label:u,value:_})=>d`<div class=\\\"policy-row\\\"><span class=\\\"policy-label\\\">${u}</span><span class=\\\"policy-value ${_?\\\"\\\":\\\"zero\\\"}\\\">${_||\\\"\\\\u2014\\\"}</span></div>`,l=[n.hasMcpPolicy?\\\"mcp\\\":\\\"\\\",n.hasExecPolicy?\\\"exec\\\":\\\"\\\",n.hasSecretPolicy?\\\"secrets\\\":\\\"\\\"].filter(Boolean).join(\\\" \\\\xB7 \\\")||\\\"none\\\";return d`<${S} delay=${e}>\\n <${M} icon=${k.policy} title=\\\"Policy\\\" aside=${l} />\\n <div class=\\\"policy-rows\\\">\\n <${a} label=\\\"MCP allow tools\\\" value=${s.allowTools} />\\n <${a} label=\\\"MCP deny tools\\\" value=${s.denyTools} />\\n <${a} label=\\\"Denied keys\\\" value=${s.deniedKeys} />\\n <${a} label=\\\"Denied tags\\\" value=${s.deniedTags} />\\n <${a} label=\\\"Exec allow\\\" value=${s.allowCommands} />\\n <${a} label=\\\"Exec deny\\\" value=${s.denyCommands} />\\n <${a} label=\\\"Tag → approval\\\" value=${s.requireApprovalForTags} />\\n <${a} label=\\\"Tag → rotation\\\" value=${s.requireRotationFormatForTags} />\\n ${n.maxTtlSeconds?d`<${a} label=\\\"Max TTL\\\" value=${ye(n.maxTtlSeconds)} />`:null}\\n ${n.maxRuntimeSeconds?d`<${a} label=\\\"Max exec runtime\\\" value=${ye(n.maxRuntimeSeconds)} />`:null}\\n </div>\\n <//>`}function yt({snap:t,delay:e}){let n=(t.secrets||[]).filter(s=>s.decay&&s.decay.timeRemaining).sort((s,o)=>(s.decay.secondsRemaining||0)-(o.decay.secondsRemaining||0));return d`<${S} delay=${e}>\\n <${M} icon=${k.decay} title=\\\"Decay Timers\\\" aside=${n.length+\\\" tracked\\\"} />\\n ${n.length?d`<div class=\\\"decay-list\\\">${n.slice(0,12).map(s=>{let o=Math.min(s.decay.lifetimePercent,100),a=Xe(o,s.decay.isExpired),l=s.decay.isExpired?\\\"expired\\\":s.decay.timeRemaining||\\\"\\\";return d`<div class=\\\"decay-item\\\" key=${s.scope+\\\":\\\"+s.key}>\\n <span class=\\\"decay-key\\\" title=${s.key}>${s.key}</span>\\n <div class=\\\"decay-bar\\\"><div class=\\\"decay-fill\\\" style=${\\\"width:\\\"+o+\\\"%;background:\\\"+a}></div></div>\\n <span class=\\\"decay-time\\\" style=${\\\"color:\\\"+a}>${l}</span>\\n </div>`})}</div>`:d`<${E} cta=\\\"$ qring set KEY --ttl 86400\\\">No secrets with decay configured.<//>`}\\n <//>`}function $t({snap:t,delay:e}){let n=(t.secrets||[]).filter(s=>s.type===\\\"superposition\\\"&&s.environments&&s.environments.length);return d`<${S} delay=${e}>\\n <${M} icon=${k.superposition} title=\\\"Superposition\\\" aside=${n.length+\\\" multi-env\\\"} />\\n ${n.length?d`<div class=\\\"super-list\\\">${n.slice(0,14).map(s=>d`<div class=\\\"super-item\\\" key=${s.scope+\\\":\\\"+s.key}>\\n <span class=\\\"super-key\\\">${s.key}</span>\\n ${(s.environments||[]).map(o=>d`<span class=\\\"env-pill ${$e(o)}\\\" key=${o}>${o}${o===s.defaultEnv?\\\" \\\\u2713\\\":\\\"\\\"}</span>`)}\\n </div>`)}</div>`:d`<${E} cta=\\\"$ qring set KEY --env prod\\\">No secrets in superposition.<//>`}\\n <//>`}function mt({snap:t,delay:e}){let n=t.entanglements||[],s=new Set,o=n.filter(h=>{let y=[h.source.service,h.source.key,h.target.service,h.target.key].sort().join(\\\"|\\\");return s.has(y)?!1:(s.add(y),!0)}),a=[],l=new Map;for(let h of o)for(let y of[h.source.key,h.target.key])l.has(y)||(l.set(y,a.length),a.push(y));let u=o.length>0&&a.length<=14,_=320,c=210,r=_/2,p=c/2,i=Math.min(_,c)/2-34,v=a.map((h,y)=>{let m=2*Math.PI*y/a.length-Math.PI/2;return{x:r+i*Math.cos(m),y:p+i*Math.sin(m)}}),f=h=>h.length>14?h.slice(0,12)+\\\"\\\\u2026\\\":h;return d`<${S} delay=${e}>\\n <${M} icon=${k.entangle} title=\\\"Entanglement\\\" aside=${o.length+\\\" pair\\\"+(o.length===1?\\\"\\\":\\\"s\\\")+\\\" \\\\xB7 \\\"+a.length+\\\" keys\\\"} />\\n ${o.length?u?d`<svg class=\\\"entangle-graph\\\" viewBox=\\\"0 0 ${_} ${c}\\\" role=\\\"img\\\" aria-label=\\\"Entanglement graph: ${o.length} linked pairs\\\">\\n ${o.map((h,y)=>{let m=v[l.get(h.source.key)],$=v[l.get(h.target.key)];return d`<line key=${\\\"e\\\"+y} x1=${m.x} y1=${m.y} x2=${$.x} y2=${$.y} class=\\\"entangle-edge\\\" />`})}\\n ${a.map((h,y)=>{let m=v[y],$=m.x>=r;return d`<g key=${\\\"n\\\"+y}>\\n <circle cx=${m.x} cy=${m.y} r=\\\"5\\\" class=\\\"entangle-node\\\" />\\n <text x=${m.x+($?9:-9)} y=${m.y+3.5} text-anchor=${$?\\\"start\\\":\\\"end\\\"} class=\\\"entangle-label\\\">${f(h)}<title>${h}</title></text>\\n </g>`})}\\n </svg>`:d`<div class=\\\"entangle-list\\\">${o.slice(0,12).map((h,y)=>d`<div class=\\\"entangle-pair\\\" key=${y}>\\n <span>${h.source.key}</span><span class=\\\"entangle-arrow\\\">↔</span><span>${h.target.key}</span>\\n </div>`)}</div>`:d`<${E} cta=\\\"$ qring entangle KEY_A KEY_B\\\">No entangled secrets.<//>`}\\n <//>`}function gt({snap:t,delay:e}){let n=t.tunnels||[];return d`<${S} delay=${e}>\\n <${M} icon=${k.tunnel} title=\\\"Quantum Tunnels\\\" aside=${n.length+\\\" live\\\"} />\\n ${n.length?d`<div class=\\\"tunnel-list\\\">${n.map(s=>{let o=s.expiresAt?Math.max(0,Math.floor((s.expiresAt-Date.now())/1e3)):null;return d`<div class=\\\"tunnel-card\\\" key=${s.id}>${s.id}<div class=\\\"tunnel-meta\\\">\\n <span>reads: ${s.accessCount}${s.maxReads?\\\"/\\\"+s.maxReads:\\\"\\\"}</span>\\n ${o!==null?d`<span>expires: ${o}s</span>`:d`<span>no expiry</span>`}\\n </div></div>`})}</div>`:d`<${E} cta=${d`$ qring tunnel create <value>`}>No active tunnels.<//>`}\\n <//>`}function kt({snap:t,delay:e}){let n=t.approvals||[];return d`<${S} delay=${e}>\\n <${M} icon=${k.approve} title=\\\"Approvals\\\" aside=${n.filter(s=>s.valid).length+\\\" valid\\\"} />\\n ${n.length?d`<div class=\\\"approval-list\\\">${n.slice(0,8).map(s=>{let o=s.tampered?\\\"tampered\\\":s.secondsRemaining<300?\\\"expiring\\\":\\\"\\\";return d`<div class=\\\"approval-card ${o}\\\" key=${s.id}>\\n <div class=\\\"approval-head\\\"><span>${s.key}${s.tampered?\\\" \\\\u26A0 TAMPERED\\\":\\\"\\\"}</span><span>${ye(s.secondsRemaining)} left</span></div>\\n <div class=\\\"approval-reason\\\">${s.reason||\\\"(no reason)\\\"}</div>\\n <div class=\\\"approval-meta\\\"><span>scope:${s.scope}</span><span>by:${s.grantedBy}</span></div>\\n </div>`})}</div>`:d`<${E} cta=${'$ qring approve KEY --for 1800 --reason \\\"...\\\"'}>No active approvals.<//>`}\\n <//>`}function bt({snap:t,delay:e}){let n=t.hooks||[];return d`<${S} delay=${e}>\\n <${M} icon=${k.hook} title=\\\"Hooks\\\" aside=${n.filter(s=>s.enabled).length+\\\" enabled / \\\"+n.length} />\\n ${n.length?d`<div class=\\\"hooks-list\\\">${n.slice(0,8).map(s=>d`<div class=\\\"hook-row ${s.enabled?\\\"\\\":\\\"disabled\\\"}\\\" key=${s.id}>\\n <span class=\\\"hook-type ${s.type}\\\">${s.type}</span>\\n <span class=\\\"hook-summary\\\" title=${s.matchSummary}>${s.description||s.matchSummary}</span>\\n <span class=\\\"hook-id\\\">${s.id}</span>\\n </div>`)}</div>`:d`<${E} cta=${'$ qring hook add --tag payments --action rotate --exec \\\"...\\\"'}>No hooks registered.<//>`}\\n <//>`}function xt({snap:t,delay:e}){return d`<${S} delay=${e}>\\n <${M} icon=${k.memory} title=\\\"Agent Memory\\\" />\\n <div class=\\\"empty\\\" style=\\\"display:flex;align-items:baseline;gap:8px;font-style:normal\\\">\\n <span style=\\\"font-size:1.6rem;font-weight:700;color:var(--text-primary);font-family:var(--font-display)\\\">${t.memoryKeys||0}</span>\\n <span>memory key${t.memoryKeys===1?\\\"\\\":\\\"s\\\"} encrypted at <code>~/.config/q-ring/agent-memory.enc</code></span>\\n </div>\\n <//>`}function wt({snap:t,delay:e}){let n=t.anomalies||[];if(!n.length)return d`<${S} delay=${e} wide=${!0}>\\n <${M} icon=${k.anomaly} title=\\\"Anomaly Alerts\\\" aside=\\\"all clear\\\" asideStyle=\\\"color:var(--green)\\\" />\\n <${E}>No anomalies detected — quantum field is stable.<//>\\n <//>`;let s=o=>o===\\\"tampered\\\"?\\\"$ qring audit:verify\\\":o===\\\"burst\\\"?\\\"$ qring audit --key \\\\u2026 --action read\\\":o===\\\"unusual-hour\\\"?'$ qring audit --action read --since \\\"1am\\\"':\\\"$ qring audit --limit 100\\\";return d`<${S} delay=${e} wide=${!0}>\\n <${M} icon=${k.anomaly} title=\\\"Anomaly Alerts\\\" aside=${n.length+\\\" active\\\"} asideStyle=\\\"color:var(--danger)\\\" />\\n <div class=\\\"anomaly-list\\\">${n.map((o,a)=>d`<div class=\\\"anomaly-card\\\" key=${a}>\\n <div class=\\\"anomaly-type\\\">${o.type}</div>\\n <div class=\\\"anomaly-desc\\\">${o.description}</div>\\n <div class=\\\"anomaly-hint\\\">${s(o.type)}</div>\\n </div>`)}</div>\\n <//>`}function Ct(t,e){let n=e.trim().toLowerCase();return n?n===\\\"expired\\\"?t.filter(s=>s.decay&&s.decay.isExpired):n===\\\"stale\\\"?t.filter(s=>s.decay&&s.decay.isStale&&!s.decay.isExpired):n===\\\"protected\\\"?t.filter(s=>s.requiresApproval):t.filter(s=>[s.key,s.scope,s.type,s.provider||\\\"\\\",s.defaultEnv||\\\"\\\",(s.environments||[]).join(\\\" \\\"),(s.tags||[]).join(\\\" \\\")].join(\\\" \\\").toLowerCase().indexOf(n)!==-1):t.slice()}function Mt(t,e){let n=e.dir===\\\"asc\\\"?1:-1;return t.sort((s,o)=>{let a,l;switch(e.col){case\\\"key\\\":a=s.key,l=o.key;break;case\\\"scope\\\":a=s.scope,l=o.scope;break;case\\\"env\\\":a=s.defaultEnv||(s.environments||[])[0]||\\\"\\\",l=o.defaultEnv||(o.environments||[])[0]||\\\"\\\";break;case\\\"type\\\":a=s.type,l=o.type;break;case\\\"decay\\\":a=s.decay&&s.decay.secondsRemaining!=null?s.decay.secondsRemaining:1/0,l=o.decay&&o.decay.secondsRemaining!=null?o.decay.secondsRemaining:1/0;break;case\\\"tags\\\":a=(s.tags||[]).join(\\\",\\\"),l=(o.tags||[]).join(\\\",\\\");break;default:a=s.lastAccessedAt?new Date(s.lastAccessedAt).getTime():0,l=o.lastAccessedAt?new Date(o.lastAccessedAt).getTime():0}return a<l?-1*n:a>l?1*n:0})}function St({snap:t,query:e,setQuery:n,sort:s,setSort:o,searchRef:a}){let l=t.secrets||[],u=te(()=>Mt(Ct(l,e),s),[l,e,s]),_=[{id:\\\"key\\\",label:\\\"Key\\\"},{id:\\\"scope\\\",label:\\\"Scope\\\"},{id:\\\"env\\\",label:\\\"Env\\\"},{id:\\\"type\\\",label:\\\"Type\\\"},{id:\\\"decay\\\",label:\\\"Decay\\\"},{id:\\\"tags\\\",label:\\\"Tags\\\"},{id:\\\"lastAccessedAt\\\",label:\\\"Last read\\\"}],c=r=>{s.col===r?o({col:r,dir:s.dir===\\\"asc\\\"?\\\"desc\\\":\\\"asc\\\"}):o({col:r,dir:r===\\\"key\\\"?\\\"asc\\\":\\\"desc\\\"})};return d`<section><${S} wide=${!0} style=\\\"margin-bottom:16px\\\">\\n <${M} icon=${k.key} title=\\\"Secrets\\\" aside=${u.length+\\\" of \\\"+l.length} />\\n <div class=\\\"secrets-toolbar\\\">\\n <label class=\\\"search\\\"><${L} svg=${k.search} /><input ref=${a} id=\\\"secretsSearch\\\" type=\\\"search\\\" placeholder=\\\"Filter by key, scope, env, tag, provider…\\\" autocomplete=\\\"off\\\" value=${e} onInput=${r=>n(r.target.value)} /></label>\\n <button class=\\\"btn\\\" type=\\\"button\\\" onClick=${()=>n(\\\"expired\\\")}>${t.health.expired||0} expired</button>\\n <button class=\\\"btn\\\" type=\\\"button\\\" onClick=${()=>n(\\\"stale\\\")}>${t.health.stale||0} stale</button>\\n <button class=\\\"btn\\\" type=\\\"button\\\" onClick=${()=>n(\\\"protected\\\")}>${t.protectedCount||0} protected</button>\\n <button class=\\\"btn\\\" type=\\\"button\\\" onClick=${()=>n(\\\"\\\")}>clear</button>\\n </div>\\n <div class=\\\"secrets-table-wrap\\\">\\n ${u.length?d`<table class=\\\"secrets-table\\\">\\n <thead><tr>${_.map(r=>d`<th key=${r.id} class=${s.col===r.id?\\\"sorted\\\":\\\"\\\"} onClick=${()=>c(r.id)}>${r.label}<span class=\\\"sort-arrow\\\">${s.dir===\\\"asc\\\"?\\\"\\\\u25B2\\\":\\\"\\\\u25BC\\\"}</span></th>`)}</tr></thead>\\n <tbody>${u.map(r=>{let p=r.type===\\\"superposition\\\"?(r.environments||[]).map(f=>d`<span class=\\\"env-pill ${$e(f)}\\\" style=\\\"margin-right:3px\\\" key=${f}>${f}${f===r.defaultEnv?\\\" \\\\u2713\\\":\\\"\\\"}</span>`):d`<span class=\\\"env-pill env-default\\\">—</span>`,i=r.decay&&r.decay.timeRemaining?(()=>{let f=Math.min(r.decay.lifetimePercent,100),h=Xe(f,r.decay.isExpired);return d`<span class=\\\"mini-decay\\\"><span class=\\\"mini-decay-bar\\\"><span class=\\\"mini-decay-fill\\\" style=${\\\"width:\\\"+f+\\\"%;background:\\\"+h}></span></span><span class=\\\"mini-decay-time\\\" style=${\\\"color:\\\"+h}>${r.decay.isExpired?\\\"expired\\\":r.decay.timeRemaining}</span></span>`})():d`<span class=\\\"mini-decay-time\\\">—</span>`,v=r.tags||[];return d`<tr key=${r.scope+\\\":\\\"+r.key}>\\n <td class=\\\"col-key\\\" title=${r.key}>${r.key}${r.requiresApproval?d`<span class=\\\"protected-icon\\\" title=\\\"requires approval for MCP read\\\"><${L} svg=${k.lock} /></span>`:null}</td>\\n <td><span class=\\\"scope-tag ${r.scope}\\\">${r.scope}</span></td>\\n <td>${p}</td>\\n <td><span class=\\\"type-tag ${r.type}\\\">${r.type}</span></td>\\n <td class=\\\"col-decay\\\">${i}</td>\\n <td class=\\\"col-tags\\\">${v.slice(0,3).map(f=>d`<span class=\\\"tag-pill\\\" key=${f}>${f}</span>`)}${v.length>3?\\\"+\\\"+(v.length-3):\\\"\\\"}</td>\\n <td>${et(r.lastAccessedAt)}</td>\\n </tr>`})}</tbody>\\n </table>`:d`<div class=\\\"empty\\\" style=\\\"padding:18px;text-align:center\\\">No secrets match the filter.</div>`}\\n </div>\\n <//></section>`}function At({snap:t,delay:e,filter:n,setFilter:s}){let o=t.audit||[],a=t.auditMetrics||{byAction:{},bySource:{}},l=o.filter(p=>{if(n.action&&p.action!==n.action||n.source&&p.source!==n.source)return!1;let i=n.text.trim().toLowerCase();return!(i&&[p.key||\\\"\\\",p.action,p.source,p.detail||\\\"\\\",p.scope||\\\"\\\",p.env||\\\"\\\"].join(\\\" \\\").toLowerCase().indexOf(i)===-1)}),u=[\\\"read\\\",\\\"write\\\",\\\"delete\\\",\\\"rotate\\\",\\\"generate\\\",\\\"entangle\\\",\\\"tunnel\\\",\\\"teleport\\\",\\\"approve\\\",\\\"revoke\\\",\\\"policy_deny\\\",\\\"collapse\\\",\\\"export\\\"],_=[\\\"cli\\\",\\\"mcp\\\",\\\"agent\\\",\\\"ci\\\",\\\"hook\\\",\\\"api\\\"],c=(p,i)=>s({...n,[p]:n[p]===i?\\\"\\\":i}),r=t.auditChain;return d`<${S} delay=${e} wide=${!0}>\\n <${M} icon=${k.audit} title=\\\"Audit Log (24h)\\\" aside=${l.length+\\\" of \\\"+o.length+\\\" shown \\\\xB7 window \\\"+(a.windowSeconds||86400)/3600+\\\"h\\\"} />\\n ${r&&r.totalEvents>0?d`<div class=\\\"chain-badge ${r.intact?\\\"ok\\\":\\\"broken\\\"}\\\" title=\\\"Hash-chain integrity — same check as qring audit:verify\\\">\\n ${r.intact?d`⛓ chain intact · ${r.totalEvents} events verified`:d`⛓ chain BROKEN at event #${r.brokenAt} · ${r.validEvents}/${r.totalEvents} valid — run $ qring audit:verify`}\\n </div>`:null}\\n <div class=\\\"audit-actions-strip\\\">\\n ${u.filter(p=>a.byAction[p]).length?u.filter(p=>a.byAction[p]).map(p=>d`<span class=\\\"audit-chip ${n.action===p?\\\"active\\\":\\\"\\\"}\\\" key=${p} onClick=${()=>c(\\\"action\\\",p)}>${p}<strong>${a.byAction[p]}</strong></span>`):d`<span class=\\\"empty\\\" style=\\\"padding:0\\\">no actions</span>`}\\n </div>\\n ${_.filter(p=>a.bySource[p]).length?d`<div class=\\\"audit-actions-strip\\\">${_.filter(p=>a.bySource[p]).map(p=>d`<span class=\\\"audit-chip ${n.source===p?\\\"active\\\":\\\"\\\"}\\\" key=${p} onClick=${()=>c(\\\"source\\\",p)}>${p}<strong>${a.bySource[p]}</strong></span>`)}</div>`:null}\\n <div class=\\\"audit-toolbar\\\">\\n <label class=\\\"search\\\"><${L} svg=${k.search} /><input id=\\\"auditSearch\\\" type=\\\"search\\\" placeholder=\\\"Filter by key, detail, scope…\\\" autocomplete=\\\"off\\\" value=${n.text} onInput=${p=>s({...n,text:p.target.value})} /></label>\\n <button class=\\\"btn\\\" type=\\\"button\\\" onClick=${()=>s({action:\\\"\\\",source:\\\"\\\",text:\\\"\\\"})}>clear</button>\\n </div>\\n ${l.length?d`<div class=\\\"audit-feed\\\">${l.slice(0,80).map((p,i)=>d`<div class=\\\"audit-row\\\" key=${i}>\\n <span class=\\\"audit-ts\\\" title=${p.timestamp}>${Ze(p.timestamp)}</span>\\n <span class=\\\"audit-action ${p.action}\\\">${p.action}</span>\\n <span class=\\\"audit-source\\\">${p.source}</span>\\n <span><span class=\\\"audit-key\\\">${p.key||\\\"\\\\u2014\\\"}</span> <span class=\\\"audit-detail\\\">${p.detail||\\\"\\\"}</span></span>\\n </div>`)}</div>`:d`<${E}>No audit events match the filter.<//>`}\\n <//>`}function Et(){let[t,e]=N(null),[n,s]=N(!1),[o,a]=N(\\\"connecting\\\"),[l,u]=N(\\\"\\\"),[_,c]=N({col:\\\"lastAccessedAt\\\",dir:\\\"desc\\\"}),[r,p]=N({action:\\\"\\\",source:\\\"\\\",text:\\\"\\\"}),[,i]=N(0),v=ve(n);v.current=n;let f=ve(null),h=he(g=>{try{e(typeof g==\\\"string\\\"?JSON.parse(g):g)}catch{}},[]),y=he(()=>{fetch(\\\"/api/status\\\"+fe,{cache:\\\"no-store\\\"}).then(g=>g.json()).then(h).catch(()=>{})},[h]);ee(()=>{let g=new EventSource(\\\"/events\\\"+fe);return g.onopen=()=>a(\\\"live\\\"),g.onmessage=w=>{v.current||h(w.data)},g.onerror=()=>a(\\\"reconnecting\\\"),()=>g.close()},[h]),ee(()=>{let g=setInterval(()=>i(w=>w+1),5e3);return()=>clearInterval(g)},[]),ee(()=>{let g=w=>{let P=w.target&&w.target.tagName;if(P===\\\"INPUT\\\"||P===\\\"TEXTAREA\\\"){w.key===\\\"Escape\\\"&&w.target.blur();return}w.key===\\\"/\\\"?(w.preventDefault(),f.current&&f.current.focus()):w.key===\\\"p\\\"||w.key===\\\"P\\\"?s(H=>!H):(w.key===\\\"r\\\"||w.key===\\\"R\\\")&&y()};return document.addEventListener(\\\"keydown\\\",g),()=>document.removeEventListener(\\\"keydown\\\",g)},[y]);let m=t?\\\"updated \\\"+et(t.timestamp):\\\"\\\\u2014\\\";if(!t)return d`<div class=\\\"container\\\">\\n <${Qe} snap=${null} paused=${n} connState=${o} onPause=${()=>s(g=>!g)} onRefresh=${y} lastUpdate=${m} />\\n <p class=\\\"foot\\\"><span>Connecting to the live quantum stream…</span></p>\\n </div>`;let $=t.auditMetrics||{total:0};return d`<div class=\\\"container\\\">\\n <${Qe} snap=${t} paused=${n} connState=${o} onPause=${()=>s(g=>!g)} onRefresh=${y} lastUpdate=${m} />\\n <${pt} snap=${t} />\\n <section class=\\\"grid\\\">\\n <${_t} snap=${t} delay=${0} />\\n <${vt} snap=${t} delay=${60} />\\n <${ht} snap=${t} delay=${120} />\\n <${ft} snap=${t} delay=${180} />\\n </section>\\n <${St} snap=${t} query=${l} setQuery=${u} sort=${_} setSort=${c} searchRef=${f} />\\n <section class=\\\"grid\\\">\\n <${yt} snap=${t} delay=${0} />\\n <${$t} snap=${t} delay=${60} />\\n <${mt} snap=${t} delay=${120} />\\n <${gt} snap=${t} delay=${180} />\\n </section>\\n <section class=\\\"grid\\\">\\n <${kt} snap=${t} delay=${0} />\\n <${bt} snap=${t} delay=${60} />\\n <${xt} snap=${t} delay=${120} />\\n </section>\\n <section class=\\\"grid\\\">\\n <${wt} snap=${t} delay=${0} />\\n <${At} snap=${t} delay=${60} filter=${r} setFilter=${p} />\\n </section>\\n <p class=\\\"foot\\\">\\n <span>${\\\"q-ring v\\\"+t.version+\\\" \\\\xB7 snapshot \\\"+Ze(t.timestamp)+\\\" \\\\xB7 \\\"+($.total||0)+\\\" audit events in last 24h\\\"}</span>\\n · keyboard: <span class=\\\"kbd\\\">/</span> search · <span class=\\\"kbd\\\">P</span> pause · <span class=\\\"kbd\\\">R</span> refresh\\n </p>\\n </div>`}He(d`<${Et} />`,document.getElementById(\\\"app\\\"));})();\";\n","/**\n * Self-contained HTML dashboard for q-ring quantum status.\n *\n * Matches the gh-pages site design system: deep-navy palette, neon cyan→violet\n * SVG icons, glassmorphism cards. The page is fully self-contained and offline:\n * inline CSS plus an inlined Preact + htm client bundle (see DASHBOARD_CLIENT,\n * generated from src/dashboard-client/main.js). Rendering is VDOM-diffed so the\n * SSE stream updates data in place — entrance animations run once on mount\n * instead of replaying on every tick.\n *\n * Layout:\n * 1. Sticky header with brand, version, project path, live/paused state,\n * pause+refresh controls, last-update timestamp.\n * 2. KPI strip — at-a-glance counters (total, healthy, stale/expired,\n * protected, hooks, approvals, anomalies, audit-24h).\n * 3. Health donut + Environment + Manifest + Policy summary cards.\n * 4. Searchable + sortable secrets table (key, scope, env, type, decay,\n * tags, last accessed).\n * 5. Quantum side panels — Decay, Superposition, Entanglement, Tunnels.\n * 6. Governance — Approvals, Hooks, Memory.\n * 7. Anomalies (full-width).\n * 8. Audit log with action+source+text filters and an action-count strip.\n */\n\nimport { DASHBOARD_CLIENT } from \"./dashboard-client.js\";\n\n/** Escape a value for safe embedding inside an inline <script> string literal. */\nfunction jsString(value: string): string {\n return JSON.stringify(value).replace(/</g, \"\\\\u003c\");\n}\n\nexport function getDashboardHtml(token: string): string {\n return `<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n<meta charset=\"utf-8\"/>\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\"/>\n<title>q-ring — quantum status</title>\n<style>\n*,*::before,*::after{box-sizing:border-box;margin:0;padding:0}\n:root{\n --bg-deep:#04080f;\n --bg-section:#080e18;\n --bg-card:#0f1a2e;\n --bg-card-hover:#142240;\n --border:#1a2d4a;\n --border-glow:#0ea5e9;\n\n --text-primary:#f8f8ff;\n --text-secondary:#c8cfe0;\n --text-dim:#8899b4;\n\n --accent:#0ea5e9;\n --accent-bright:#38bdf8;\n --accent-dim:rgba(14,165,233,0.15);\n --accent-glow:rgba(14,165,233,0.4);\n\n --danger:#ff5e5b;\n --warning:#fbbf24;\n --green:#22c55e;\n --violet:#a855f7;\n --pink:#ff0055;\n\n --font-display:system-ui,-apple-system,'Segoe UI',Roboto,sans-serif;\n --font-mono:'SF Mono','Cascadia Code','Fira Code',Consolas,'Liberation Mono',monospace;\n\n --radius:12px;\n --radius-sm:8px;\n --radius-lg:20px;\n\n --glass-bg:rgba(15,26,46,0.65);\n --glass-border:rgba(26,45,74,0.8);\n --shadow-card:0 4px 24px -1px rgba(0,0,0,0.4),0 0 1px rgba(255,255,255,0.04);\n --shadow-glow:0 0 20px rgba(14,165,233,0.12),0 0 40px rgba(168,85,247,0.08);\n}\nhtml{background:var(--bg-deep);color:var(--text-primary);font-family:var(--font-display);font-size:16px;line-height:1.5}\nbody{min-height:100vh;overflow-x:hidden;position:relative}\n\n/* Hidden SVG defs for gradient icons */\n.svg-defs{position:absolute;width:0;height:0;overflow:hidden}\n\n/* Mesh blobs */\n.blob{position:fixed;border-radius:50%;filter:blur(100px);opacity:.12;pointer-events:none;z-index:0}\n.blob-1{width:600px;height:600px;top:-120px;left:-100px;background:radial-gradient(circle,var(--accent),transparent 70%);animation:drift1 22s ease-in-out infinite}\n.blob-2{width:500px;height:500px;bottom:-80px;right:-60px;background:radial-gradient(circle,var(--violet),transparent 70%);animation:drift2 26s ease-in-out infinite}\n.blob-3{width:350px;height:350px;top:40%;left:50%;background:radial-gradient(circle,var(--green),transparent 70%);animation:drift3 30s ease-in-out infinite;opacity:.06}\n@keyframes drift1{0%,100%{transform:translate(0,0) scale(1)}33%{transform:translate(60px,-40px) scale(1.08)}66%{transform:translate(-30px,50px) scale(.95)}}\n@keyframes drift2{0%,100%{transform:translate(0,0) scale(1)}33%{transform:translate(-50px,30px) scale(1.05)}66%{transform:translate(40px,-60px) scale(.92)}}\n@keyframes drift3{0%,100%{transform:translate(-50%,-50%) scale(1)}50%{transform:translate(-40%,-60%) scale(1.1)}}\n\n.container{position:relative;z-index:1;max-width:1320px;margin:0 auto;padding:20px 20px 48px}\n\n/* Header */\n.header{position:sticky;top:0;z-index:5;display:flex;align-items:center;justify-content:space-between;flex-wrap:wrap;gap:14px;margin-bottom:20px;padding:14px 18px;background:rgba(4,8,15,0.85);backdrop-filter:blur(16px) saturate(1.2);-webkit-backdrop-filter:blur(16px) saturate(1.2);border:1px solid var(--border);border-radius:var(--radius)}\n.header-left{display:flex;align-items:center;gap:14px;flex-wrap:wrap;min-width:0}\n.header h1{font-family:var(--font-display);font-size:1.45rem;font-weight:700;letter-spacing:-.02em;display:flex;align-items:center;gap:10px}\n.header h1 .q-icon{display:flex;filter:drop-shadow(0 0 6px rgba(14,165,233,0.6))}\n.header h1 .brand{background:linear-gradient(135deg,#00D1FF,var(--violet));-webkit-background-clip:text;background-clip:text;-webkit-text-fill-color:transparent}\n.header h1 .sub{color:var(--text-dim);font-weight:400;font-size:1rem;-webkit-text-fill-color:var(--text-dim)}\n.meta-chip{font-family:var(--font-mono);font-size:.72rem;color:var(--text-dim);background:rgba(255,255,255,.04);border:1px solid var(--border);padding:3px 8px;border-radius:99px;display:inline-flex;align-items:center;gap:6px;max-width:340px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\n.meta-chip svg{width:11px;height:11px;flex-shrink:0;opacity:.7}\n.header-right{display:flex;align-items:center;gap:10px;flex-wrap:wrap}\n.status-dot{width:8px;height:8px;border-radius:50%;background:var(--green);box-shadow:0 0 8px var(--green);display:inline-block;animation:pulse 2s ease-in-out infinite}\n.status-dot.disconnected{background:var(--danger);box-shadow:0 0 8px var(--danger);animation:none}\n.status-dot.paused{background:var(--warning);box-shadow:0 0 8px var(--warning);animation:none}\n@keyframes pulse{0%,100%{opacity:1}50%{opacity:.4}}\n.conn-label{font-size:.78rem;color:var(--text-dim);display:flex;align-items:center;gap:6px;font-family:var(--font-mono)}\n.btn{font-family:var(--font-display);font-size:.78rem;font-weight:500;color:var(--text-secondary);background:rgba(255,255,255,.04);border:1px solid var(--border);padding:5px 11px;border-radius:var(--radius-sm);cursor:pointer;display:inline-flex;align-items:center;gap:5px;transition:background .2s,border-color .2s,color .2s}\n.btn:hover{background:rgba(14,165,233,.08);border-color:var(--accent);color:var(--text-primary)}\n.btn svg{width:13px;height:13px}\n.btn.active{background:var(--accent-dim);color:var(--accent-bright);border-color:rgba(14,165,233,.4)}\n.kbd{font-family:var(--font-mono);font-size:.65rem;color:var(--text-dim);background:rgba(255,255,255,.06);border:1px solid var(--border);border-bottom-width:2px;padding:1px 5px;border-radius:4px;margin-left:4px}\n\n/* KPI strip */\n.kpi-strip{display:grid;grid-template-columns:repeat(auto-fit,minmax(140px,1fr));gap:10px;margin-bottom:20px}\n.kpi{background:var(--glass-bg);backdrop-filter:blur(20px);-webkit-backdrop-filter:blur(20px);border:1px solid var(--glass-border);border-radius:var(--radius);padding:12px 14px;display:flex;flex-direction:column;gap:2px;transition:border-color .25s,transform .25s}\n.kpi:hover{border-color:var(--border-glow);transform:translateY(-2px)}\n.kpi-label{font-size:.66rem;text-transform:uppercase;letter-spacing:.1em;color:var(--text-dim);font-weight:600;display:flex;align-items:center;gap:6px}\n.kpi-label svg{width:12px;height:12px;flex-shrink:0}\n.kpi-value{font-family:var(--font-display);font-size:1.5rem;font-weight:700;line-height:1.1;color:var(--text-primary)}\n.kpi-value.warning{color:var(--warning)}\n.kpi-value.danger{color:var(--danger)}\n.kpi-value.green{color:var(--green)}\n.kpi-value.dim{color:var(--text-dim)}\n.kpi-sub{font-size:.7rem;color:var(--text-dim);font-family:var(--font-mono)}\n\n/* Grid */\n.grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(360px,1fr));gap:16px;margin-bottom:16px}\n.grid-wide{grid-column:1/-1}\n\n/* Cards — reveal animation */\n.card{background:var(--glass-bg);backdrop-filter:blur(20px);-webkit-backdrop-filter:blur(20px);border:1px solid var(--glass-border);border-radius:var(--radius);padding:18px 20px;box-shadow:var(--shadow-card);transition:border-color .3s,box-shadow .3s,transform .3s;opacity:0;transform:translateY(16px);animation:cardReveal .5s cubic-bezier(0.16,1,0.3,1) forwards}\n.card:hover{border-color:var(--border-glow);box-shadow:var(--shadow-card),var(--shadow-glow)}\n@keyframes cardReveal{to{opacity:1;transform:translateY(0)}}\n\n.card-title{font-family:var(--font-display);font-size:.78rem;text-transform:uppercase;letter-spacing:.1em;color:var(--text-dim);margin-bottom:14px;display:flex;align-items:center;gap:8px;font-weight:600}\n.card-title svg{width:16px;height:16px;flex-shrink:0;filter:drop-shadow(0 0 4px rgba(14,165,233,0.4))}\n.card-title .title-aside{margin-left:auto;font-size:.7rem;color:var(--text-dim);font-weight:400;letter-spacing:.04em;text-transform:none;display:flex;align-items:center;gap:6px}\n\n/* Health donut */\n.health-row{display:flex;align-items:center;gap:24px}\n.donut-wrap{position:relative;width:100px;height:100px;flex-shrink:0}\n.donut-wrap svg{transform:rotate(-90deg)}\n.donut-wrap .donut-label{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;flex-direction:column;font-size:1.5rem;font-weight:700;line-height:1;font-family:var(--font-display)}\n.donut-wrap .donut-label small{font-size:.7rem;color:var(--text-dim);font-weight:400;margin-top:2px;letter-spacing:.04em}\n.health-legend{display:flex;flex-direction:column;gap:6px;font-family:var(--font-mono)}\n.legend-item{display:flex;align-items:center;gap:8px;font-size:.82rem}\n.legend-dot{width:8px;height:8px;border-radius:50%;flex-shrink:0}\n.scope-row{display:flex;gap:6px;margin-top:14px;flex-wrap:wrap;font-family:var(--font-mono);font-size:.72rem;color:var(--text-dim)}\n.scope-pill{padding:2px 8px;border-radius:99px;background:rgba(255,255,255,.04);border:1px solid var(--border)}\n.scope-pill strong{color:var(--text-primary);margin-right:4px;font-weight:600}\n\n/* Decay bars */\n.decay-list{display:flex;flex-direction:column;gap:8px;max-height:280px;overflow-y:auto}\n.decay-item{display:flex;align-items:center;gap:10px}\n.decay-key{font-family:var(--font-mono);font-size:.85rem;min-width:120px;max-width:180px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\n.decay-bar{flex:1;height:6px;border-radius:3px;background:rgba(255,255,255,0.06);overflow:hidden;position:relative}\n.decay-fill{height:100%;border-radius:3px;transition:width .6s ease}\n.decay-time{font-size:.8rem;color:var(--text-dim);min-width:60px;text-align:right;font-family:var(--font-mono)}\n\n/* Superposition pills */\n.super-list{display:flex;flex-direction:column;gap:8px;max-height:280px;overflow-y:auto}\n.super-item{display:flex;align-items:center;gap:8px;flex-wrap:wrap}\n.super-key{font-family:var(--font-mono);font-size:.85rem;min-width:100px}\n.env-pill{font-size:.72rem;padding:2px 8px;border-radius:99px;font-weight:600;letter-spacing:.03em;font-family:var(--font-mono)}\n.env-prod{background:rgba(255,0,85,0.2);color:var(--pink);border:1px solid rgba(255,0,85,0.3)}\n.env-staging{background:rgba(251,191,36,0.15);color:var(--warning);border:1px solid rgba(251,191,36,0.25)}\n.env-dev{background:rgba(34,197,94,0.15);color:var(--green);border:1px solid rgba(34,197,94,0.25)}\n.env-default{background:rgba(168,85,247,0.15);color:var(--violet);border:1px solid rgba(168,85,247,0.25)}\n.env-test{background:rgba(14,165,233,0.15);color:var(--accent-bright);border:1px solid rgba(14,165,233,0.25)}\n\n/* Entanglement */\n.entangle-list{display:flex;flex-direction:column;gap:6px;max-height:240px;overflow-y:auto}\n.entangle-pair{display:flex;align-items:center;gap:8px;font-family:var(--font-mono);font-size:.83rem}\n.entangle-arrow{color:var(--accent-bright)}\n.entangle-graph{display:block;width:100%;height:auto;max-height:260px}\n.entangle-edge{stroke:var(--accent);stroke-opacity:.45;stroke-width:1.2}\n.entangle-node{fill:var(--accent-bright);stroke:var(--accent-glow);stroke-width:3;paint-order:stroke}\n.entangle-label{fill:var(--text-secondary);font-family:var(--font-mono);font-size:9px}\n\n/* Audit chain badge */\n.chain-badge{display:inline-flex;align-items:center;gap:6px;font-family:var(--font-mono);font-size:.78rem;padding:4px 10px;border-radius:999px;margin-bottom:10px}\n.chain-badge.ok{color:var(--green);background:rgba(34,197,94,.1);border:1px solid rgba(34,197,94,.25)}\n.chain-badge.broken{color:var(--danger);background:rgba(255,94,91,.12);border:1px solid rgba(255,94,91,.3)}\n\n/* Tunnels */\n.tunnel-list{display:flex;flex-direction:column;gap:8px;max-height:240px;overflow-y:auto}\n.tunnel-card{background:rgba(168,85,247,0.06);border:1px solid rgba(168,85,247,0.15);border-radius:var(--radius-sm);padding:10px 12px;font-size:.83rem;font-family:var(--font-mono)}\n.tunnel-meta{display:flex;gap:12px;margin-top:4px;font-size:.78rem;color:var(--text-dim)}\n\n/* Audit feed */\n.audit-toolbar{display:flex;flex-wrap:wrap;gap:6px;margin-bottom:10px;align-items:center}\n.audit-actions-strip{display:flex;flex-wrap:wrap;gap:6px;margin-bottom:10px}\n.audit-chip{font-family:var(--font-mono);font-size:.7rem;padding:2px 9px;border-radius:99px;background:rgba(255,255,255,.04);border:1px solid var(--border);color:var(--text-secondary);cursor:pointer;transition:all .2s}\n.audit-chip:hover{border-color:var(--accent);color:var(--text-primary)}\n.audit-chip.active{background:var(--accent-dim);color:var(--accent-bright);border-color:rgba(14,165,233,.4)}\n.audit-chip strong{color:var(--text-primary);margin-left:5px;font-weight:600}\n.audit-feed{display:flex;flex-direction:column;gap:2px;max-height:340px;overflow-y:auto;font-size:.82rem;font-family:var(--font-mono)}\n.audit-row{display:grid;grid-template-columns:78px 76px 56px 1fr;gap:8px;padding:4px 0;border-bottom:1px solid rgba(255,255,255,0.03);align-items:baseline}\n.audit-ts{color:var(--text-dim)}\n.audit-action{font-weight:600}\n.audit-action.read{color:var(--accent)}.audit-action.write{color:var(--green)}.audit-action.delete{color:var(--danger)}\n.audit-action.entangle{color:var(--violet)}.audit-action.tunnel{color:var(--violet)}.audit-action.teleport{color:var(--warning)}\n.audit-action.generate{color:var(--warning)}.audit-action.list{color:var(--text-dim)}.audit-action.export{color:var(--text-dim)}\n.audit-action.collapse{color:var(--accent)}.audit-action.approve{color:var(--green)}.audit-action.revoke{color:var(--danger)}\n.audit-action.policy_deny{color:var(--danger)}.audit-action.rotate{color:var(--violet)}\n.audit-source{color:var(--text-dim);font-size:.72rem;text-transform:uppercase;letter-spacing:.04em}\n.audit-detail{color:var(--text-dim);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\n.audit-key{color:var(--text-secondary)}\n\n/* Anomalies */\n.anomaly-list{display:flex;flex-direction:column;gap:8px}\n.anomaly-card{background:rgba(255,94,91,0.06);border:1px solid rgba(255,94,91,0.15);border-radius:var(--radius-sm);padding:10px 14px;animation:anomaly-pulse 3s ease-in-out infinite}\n@keyframes anomaly-pulse{0%,100%{border-color:rgba(255,94,91,0.15)}50%{border-color:rgba(255,94,91,0.4)}}\n.anomaly-type{font-size:.7rem;text-transform:uppercase;letter-spacing:.06em;color:var(--danger);font-weight:700;margin-bottom:2px;font-family:var(--font-display)}\n.anomaly-desc{font-size:.86rem;color:var(--text-primary)}\n.anomaly-hint{font-size:.74rem;color:var(--text-dim);margin-top:4px;font-family:var(--font-mono)}\n\n/* Environment badge */\n.env-status{display:flex;align-items:center;gap:12px;flex-wrap:wrap}\n.env-big{font-size:1rem;font-weight:700;padding:4px 14px;border-radius:var(--radius-sm)}\n.env-source{font-size:.78rem;color:var(--text-dim);font-family:var(--font-mono)}\n\n/* Manifest */\n.manifest-bars{display:flex;flex-direction:column;gap:8px}\n.manifest-row{display:flex;align-items:center;gap:8px;font-family:var(--font-mono);font-size:.78rem}\n.manifest-bar{flex:1;height:6px;border-radius:3px;background:rgba(255,255,255,.06);overflow:hidden}\n.manifest-fill{height:100%;border-radius:3px}\n.manifest-list{margin-top:10px;display:flex;flex-direction:column;gap:3px;font-family:var(--font-mono);font-size:.78rem;color:var(--text-secondary)}\n.manifest-list .label{color:var(--text-dim);text-transform:uppercase;font-size:.66rem;letter-spacing:.08em;margin-top:6px}\n.manifest-list .key-pill{display:inline-block;padding:1px 6px;background:rgba(255,255,255,.04);border:1px solid var(--border);border-radius:4px;margin:1px 3px 1px 0;font-size:.74rem}\n.manifest-list .key-pill.miss{color:var(--danger);border-color:rgba(255,94,91,.3)}\n.manifest-list .key-pill.exp{color:var(--warning);border-color:rgba(251,191,36,.3)}\n\n/* Policy */\n.policy-rows{display:flex;flex-direction:column;gap:6px;font-family:var(--font-mono);font-size:.78rem;color:var(--text-secondary)}\n.policy-row{display:flex;justify-content:space-between;padding:2px 0;border-bottom:1px solid rgba(255,255,255,.04)}\n.policy-label{color:var(--text-dim)}\n.policy-value{color:var(--text-primary)}\n.policy-value.zero{color:var(--text-dim)}\n\n/* Approvals */\n.approval-list{display:flex;flex-direction:column;gap:8px;max-height:240px;overflow-y:auto}\n.approval-card{background:rgba(34,197,94,.05);border:1px solid rgba(34,197,94,.2);border-radius:var(--radius-sm);padding:10px 12px;font-size:.82rem}\n.approval-card.tampered{background:rgba(255,94,91,.06);border-color:rgba(255,94,91,.4)}\n.approval-card.expiring{background:rgba(251,191,36,.06);border-color:rgba(251,191,36,.3)}\n.approval-head{display:flex;justify-content:space-between;gap:8px;font-family:var(--font-mono);font-weight:600}\n.approval-reason{color:var(--text-dim);font-size:.76rem;margin-top:3px}\n.approval-meta{display:flex;gap:12px;margin-top:4px;font-size:.72rem;color:var(--text-dim);font-family:var(--font-mono)}\n\n/* Hooks */\n.hooks-list{display:flex;flex-direction:column;gap:6px;max-height:240px;overflow-y:auto}\n.hook-row{display:flex;align-items:center;gap:8px;font-family:var(--font-mono);font-size:.8rem;padding:6px 10px;background:rgba(255,255,255,.03);border:1px solid var(--border);border-radius:var(--radius-sm)}\n.hook-row.disabled{opacity:.5}\n.hook-type{font-size:.7rem;text-transform:uppercase;font-weight:600;padding:1px 6px;border-radius:99px;letter-spacing:.04em}\n.hook-type.shell{background:rgba(168,85,247,.15);color:var(--violet)}\n.hook-type.http{background:rgba(14,165,233,.15);color:var(--accent-bright)}\n.hook-type.signal{background:rgba(251,191,36,.15);color:var(--warning)}\n.hook-summary{flex:1;color:var(--text-secondary);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\n.hook-id{color:var(--text-dim);font-size:.72rem}\n\n/* Secrets table */\n.secrets-toolbar{display:flex;gap:8px;flex-wrap:wrap;margin-bottom:10px;align-items:center}\n.search{flex:1;min-width:180px;display:flex;align-items:center;gap:6px;background:rgba(255,255,255,.04);border:1px solid var(--border);border-radius:var(--radius-sm);padding:5px 10px;transition:border-color .2s}\n.search:focus-within{border-color:var(--accent);box-shadow:0 0 0 3px rgba(14,165,233,.12)}\n.search svg{width:13px;height:13px;color:var(--text-dim);flex-shrink:0}\n.search input{flex:1;background:transparent;border:none;outline:none;color:var(--text-primary);font-family:var(--font-mono);font-size:.82rem}\n.search input::placeholder{color:var(--text-dim)}\n.secrets-table-wrap{max-height:380px;overflow:auto;border:1px solid var(--border);border-radius:var(--radius-sm);background:rgba(4,8,15,.4)}\n.secrets-table{width:100%;border-collapse:collapse;font-family:var(--font-mono);font-size:.78rem}\n.secrets-table thead th{position:sticky;top:0;background:rgba(8,14,24,.95);backdrop-filter:blur(8px);text-align:left;padding:8px 10px;color:var(--text-dim);font-weight:600;font-size:.7rem;text-transform:uppercase;letter-spacing:.05em;border-bottom:1px solid var(--border);cursor:pointer;user-select:none;white-space:nowrap}\n.secrets-table thead th:hover{color:var(--text-primary)}\n.secrets-table thead th .sort-arrow{display:inline-block;margin-left:4px;color:var(--accent-bright);opacity:0;font-size:.7rem}\n.secrets-table thead th.sorted .sort-arrow{opacity:1}\n.secrets-table tbody td{padding:6px 10px;border-bottom:1px solid rgba(255,255,255,.03);color:var(--text-secondary);vertical-align:middle}\n.secrets-table tbody tr:hover td{background:rgba(14,165,233,.05);color:var(--text-primary)}\n.secrets-table .col-key{color:var(--text-primary);font-weight:500;max-width:240px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\n.secrets-table .col-decay{min-width:90px}\n.secrets-table .col-tags{max-width:200px;overflow:hidden}\n.tag-pill{display:inline-block;font-size:.68rem;padding:1px 6px;background:rgba(168,85,247,.1);color:var(--violet);border:1px solid rgba(168,85,247,.2);border-radius:99px;margin-right:3px;font-family:var(--font-mono)}\n.scope-tag{display:inline-block;font-size:.68rem;padding:1px 6px;border-radius:4px;letter-spacing:.04em;font-weight:600;text-transform:uppercase}\n.scope-tag.global{background:rgba(14,165,233,.15);color:var(--accent-bright)}\n.scope-tag.project{background:rgba(34,197,94,.15);color:var(--green)}\n.scope-tag.team{background:rgba(168,85,247,.15);color:var(--violet)}\n.scope-tag.org{background:rgba(251,191,36,.15);color:var(--warning)}\n.type-tag{display:inline-block;font-size:.68rem;padding:1px 6px;border-radius:4px;letter-spacing:.04em;text-transform:uppercase;background:rgba(255,255,255,.05);color:var(--text-dim)}\n.type-tag.superposition{background:rgba(168,85,247,.12);color:var(--violet)}\n.protected-icon{color:var(--warning);margin-left:4px;display:inline-flex;vertical-align:middle}\n.mini-decay{display:inline-flex;align-items:center;gap:6px;width:100%}\n.mini-decay-bar{flex:1;height:4px;border-radius:2px;background:rgba(255,255,255,.06);overflow:hidden;min-width:30px}\n.mini-decay-fill{height:100%;border-radius:2px}\n.mini-decay-time{color:var(--text-dim);font-size:.7rem;min-width:38px;text-align:right}\n\n/* Empty states */\n.empty{color:var(--text-dim);font-size:.86rem;font-style:italic;padding:8px 0}\n.empty-cta{margin-top:6px;display:block;font-style:normal;font-family:var(--font-mono);font-size:.78rem;color:var(--accent-bright)}\n\n/* Footer */\n.foot{margin-top:24px;text-align:center;color:var(--text-dim);font-size:.72rem;font-family:var(--font-mono)}\n.foot a{color:var(--accent-bright);text-decoration:none}\n.foot a:hover{text-decoration:underline}\n\n/* Scrollbar */\n::-webkit-scrollbar{width:6px;height:6px}\n::-webkit-scrollbar-track{background:transparent}\n::-webkit-scrollbar-thumb{background:rgba(255,255,255,0.08);border-radius:3px}\n::-webkit-scrollbar-thumb:hover{background:rgba(255,255,255,0.15)}\n\n/* Print */\n@media print{\n .blob,.header-right{display:none}\n .header{position:static}\n body{background:#fff;color:#000}\n}\n</style>\n</head>\n<body>\n\n<!-- Shared SVG gradient definition (referenced by the neon icons) -->\n<svg class=\"svg-defs\" aria-hidden=\"true\" focusable=\"false\">\n <defs>\n <linearGradient id=\"neon-grad\" x1=\"0%\" y1=\"0%\" x2=\"100%\" y2=\"100%\">\n <stop offset=\"0%\" stop-color=\"#00D1FF\"/>\n <stop offset=\"100%\" stop-color=\"#a855f7\"/>\n </linearGradient>\n </defs>\n</svg>\n\n<div class=\"blob blob-1\"></div>\n<div class=\"blob blob-2\"></div>\n<div class=\"blob blob-3\"></div>\n\n<!-- Preact mounts the entire UI here; see src/dashboard-client/main.js -->\n<div id=\"app\"></div>\n\n<script>window.__QRING__=Object.freeze({token:${jsString(token)}});</script>\n<script>${DASHBOARD_CLIENT}</script>\n</body>\n</html>`;\n}\n"],"mappings":"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","names":[]}
|
package/dist/index.js
CHANGED
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
deleteSecret,
|
|
11
11
|
detectAnomalies,
|
|
12
12
|
disableHook,
|
|
13
|
+
disarmCanary,
|
|
13
14
|
disentangleSecrets,
|
|
14
15
|
enableHook,
|
|
15
16
|
entangleSecrets,
|
|
@@ -45,7 +46,7 @@ import {
|
|
|
45
46
|
tunnelList,
|
|
46
47
|
tunnelRead,
|
|
47
48
|
verifyAuditChain
|
|
48
|
-
} from "./chunk-
|
|
49
|
+
} from "./chunk-SQKTDYSO.js";
|
|
49
50
|
|
|
50
51
|
// src/cli/commands.ts
|
|
51
52
|
import { Command, Help } from "commander";
|
|
@@ -363,8 +364,16 @@ function registerSecretsCommands(program2) {
|
|
|
363
364
|
requiresApproval: cmd.requiresApproval,
|
|
364
365
|
jitProvider: cmd.jitProvider
|
|
365
366
|
};
|
|
367
|
+
const priorEnvelope = getEnvelope(key, opts);
|
|
368
|
+
if (priorEnvelope?.envelope.meta.canary) {
|
|
369
|
+
console.error(
|
|
370
|
+
c.yellow(
|
|
371
|
+
`${SYMBOLS.warning} "${key}" is a canary honeytoken \u2014 the alarm stays armed on the new value. Disarm first with: qring canary disarm ${key}`
|
|
372
|
+
)
|
|
373
|
+
);
|
|
374
|
+
}
|
|
366
375
|
if (cmd.env) {
|
|
367
|
-
const existing =
|
|
376
|
+
const existing = priorEnvelope;
|
|
368
377
|
const states = existing?.envelope?.states ?? {};
|
|
369
378
|
states[cmd.env] = value;
|
|
370
379
|
if (existing?.envelope?.value && !states["default"]) {
|
|
@@ -2588,7 +2597,7 @@ function registerToolingCommands(program2) {
|
|
|
2588
2597
|
}
|
|
2589
2598
|
});
|
|
2590
2599
|
program2.command("status").description("Launch the quantum status dashboard in your browser").option("--port <port>", "Port to serve on", "9876").option("--no-open", "Don't auto-open the browser").action(async (cmd) => {
|
|
2591
|
-
const { startDashboardServer } = await import("./dashboard-
|
|
2600
|
+
const { startDashboardServer } = await import("./dashboard-2MLSJZQR.js");
|
|
2592
2601
|
const { exec } = await import("child_process");
|
|
2593
2602
|
const { platform } = await import("os");
|
|
2594
2603
|
const port = Number(cmd.port);
|
|
@@ -2624,7 +2633,7 @@ function registerAuditCommands(program2) {
|
|
|
2624
2633
|
program2.command("audit").description("View the audit log (observer effect)").option("-k, --key <key>", "Filter by key").option(
|
|
2625
2634
|
"-a, --action <action>",
|
|
2626
2635
|
"Filter by action (read, write, delete, etc.)"
|
|
2627
|
-
).option("-n, --limit <n>", "Number of events to show", parseInt, 20).option("--anomalies", "Detect access anomalies").option("--json", "Output as JSON").action((cmd) => {
|
|
2636
|
+
).option("--agent <label>", "Filter by agent label (clientInfo name@version)").option("-n, --limit <n>", "Number of events to show", parseInt, 20).option("--anomalies", "Detect access anomalies").option("--json", "Output as JSON").action((cmd) => {
|
|
2628
2637
|
if (cmd.anomalies) {
|
|
2629
2638
|
const anomalies = detectAnomalies(cmd.key);
|
|
2630
2639
|
if (emitJson(program2, cmd, { anomalies })) return;
|
|
@@ -2646,6 +2655,7 @@ ${SYMBOLS.warning} ${c.bold(c.yellow(`${anomalies.length} anomaly/anomalies dete
|
|
|
2646
2655
|
const events = queryAudit({
|
|
2647
2656
|
key: cmd.key,
|
|
2648
2657
|
action: cmd.action,
|
|
2658
|
+
agent: cmd.agent,
|
|
2649
2659
|
limit: cmd.limit
|
|
2650
2660
|
});
|
|
2651
2661
|
if (emitJson(program2, cmd, { events })) return;
|
|
@@ -2666,6 +2676,7 @@ ${SYMBOLS.warning} ${c.bold(c.yellow(`${anomalies.length} anomaly/anomalies dete
|
|
|
2666
2676
|
actionColor(event.action.padEnd(8)),
|
|
2667
2677
|
event.key ? c.bold(event.key) : "",
|
|
2668
2678
|
event.scope ? c.dim(`[${event.scope}]`) : "",
|
|
2679
|
+
event.agent ? c.cyan(`\u27E8${event.agent}\u27E9`) : "",
|
|
2669
2680
|
event.detail ? c.dim(event.detail) : ""
|
|
2670
2681
|
];
|
|
2671
2682
|
console.log(` ${parts.filter(Boolean).join(" ")}`);
|
|
@@ -4076,15 +4087,28 @@ function plantCanary(key, opts = {}) {
|
|
|
4076
4087
|
}
|
|
4077
4088
|
const value = opts.value ?? format.generate();
|
|
4078
4089
|
const scope = opts.scope ?? "global";
|
|
4090
|
+
const existing = getEnvelope(key, { ...opts, scope });
|
|
4091
|
+
if (existing && !existing.envelope.meta.canary && !opts.force) {
|
|
4092
|
+
throw new Error(
|
|
4093
|
+
`"${key}" already holds a real secret in ${scope} scope. Use --force to replace it with a canary.`
|
|
4094
|
+
);
|
|
4095
|
+
}
|
|
4079
4096
|
setSecret(key, value, {
|
|
4080
4097
|
...opts,
|
|
4081
4098
|
scope,
|
|
4082
4099
|
canary: true,
|
|
4083
4100
|
canaryFormat: formatName,
|
|
4084
|
-
description: opts.
|
|
4101
|
+
description: opts.description
|
|
4085
4102
|
});
|
|
4086
4103
|
return { key, value, format: formatName, scope };
|
|
4087
4104
|
}
|
|
4105
|
+
function disarmCanary2(key, opts = {}) {
|
|
4106
|
+
const found = getEnvelope(key, opts);
|
|
4107
|
+
if (!found) {
|
|
4108
|
+
throw new Error(`"${key}" not found in any applicable scope`);
|
|
4109
|
+
}
|
|
4110
|
+
return disarmCanary(key, { ...opts, scope: found.scope });
|
|
4111
|
+
}
|
|
4088
4112
|
function listCanaries(opts = {}) {
|
|
4089
4113
|
const out = [];
|
|
4090
4114
|
for (const entry of listSecrets({ ...opts, silent: true })) {
|
|
@@ -4109,18 +4133,15 @@ function registerCanaryCommands(program2) {
|
|
|
4109
4133
|
"-f, --format <format>",
|
|
4110
4134
|
`Token shape to imitate (${Object.keys(CANARY_FORMATS).join(", ")})`,
|
|
4111
4135
|
DEFAULT_CANARY_FORMAT
|
|
4112
|
-
).option("--value <value>", "Plant this exact value instead of generating one").option("--force", "Overwrite an existing non-canary secret at this key").option("-g, --global", "Global scope (default)").option("-p, --project", "Project scope").option("--team <id>", "Team scope").option("--org <id>", "Org scope").option("--project-path <path>", "Project path (defaults to cwd)").action((key, cmd) => {
|
|
4113
|
-
const
|
|
4114
|
-
|
|
4115
|
-
|
|
4116
|
-
|
|
4117
|
-
|
|
4118
|
-
|
|
4119
|
-
|
|
4120
|
-
|
|
4121
|
-
process.exit(1);
|
|
4122
|
-
}
|
|
4123
|
-
const result = plantCanary(key, { ...opts, format: cmd.format, value: cmd.value });
|
|
4136
|
+
).option("--value <value>", "Plant this exact value instead of generating one").option("--description <text>", "Cover description (none by default \u2014 no tell)").option("--force", "Overwrite an existing non-canary secret at this key").option("--json", "Output the plant result as JSON").option("-g, --global", "Global scope (default)").option("-p, --project", "Project scope").option("--team <id>", "Team scope").option("--org <id>", "Org scope").option("--project-path <path>", "Project path (defaults to cwd)").action((key, cmd) => {
|
|
4137
|
+
const result = plantCanary(key, {
|
|
4138
|
+
...buildOpts(cmd),
|
|
4139
|
+
format: cmd.format,
|
|
4140
|
+
value: cmd.value,
|
|
4141
|
+
description: cmd.description,
|
|
4142
|
+
force: cmd.force === true
|
|
4143
|
+
});
|
|
4144
|
+
if (emitJson(program2, cmd, result)) return;
|
|
4124
4145
|
console.log(
|
|
4125
4146
|
`${SYMBOLS.sparkle} ${c.yellow("canary planted")} ${c.bold(result.key)} ${c.dim(`(${result.format}, ${result.scope} scope)`)}`
|
|
4126
4147
|
);
|
|
@@ -4134,6 +4155,16 @@ function registerCanaryCommands(program2) {
|
|
|
4134
4155
|
c.dim(" Watch trips with: qring canary list \xB7 qring audit --action canary")
|
|
4135
4156
|
);
|
|
4136
4157
|
});
|
|
4158
|
+
canary.command("disarm <key>").description("Clear the canary flag so reads stop alarming (value stays fake)").option("-g, --global", "Global scope").option("-p, --project", "Project scope").option("--team <id>", "Team scope").option("--org <id>", "Org scope").option("--project-path <path>", "Project path (defaults to cwd)").action((key, cmd) => {
|
|
4159
|
+
if (disarmCanary2(key, buildOpts(cmd))) {
|
|
4160
|
+
console.log(
|
|
4161
|
+
`${SYMBOLS.check} ${c.green("disarmed")} ${c.bold(key)} ${c.dim("\u2014 now an ordinary secret; the stored value is still fake until you overwrite it")}`
|
|
4162
|
+
);
|
|
4163
|
+
} else {
|
|
4164
|
+
console.error(c.red(`${SYMBOLS.cross} "${key}" is not a canary`));
|
|
4165
|
+
process.exit(1);
|
|
4166
|
+
}
|
|
4167
|
+
});
|
|
4137
4168
|
canary.command("list").alias("ls").description("List planted canaries and their trip counts").option("--json", "Output as JSON").option("-g, --global", "Global scope only").option("-p, --project", "Project scope only").option("--team <id>", "Team scope").option("--org <id>", "Org scope").option("--project-path <path>", "Project path (defaults to cwd)").action((cmd) => {
|
|
4138
4169
|
const canaries = listCanaries(buildOpts(cmd));
|
|
4139
4170
|
if (emitJson(program2, cmd, { canaries })) return;
|
|
@@ -4169,20 +4200,37 @@ import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
|
4169
4200
|
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
|
|
4170
4201
|
import {
|
|
4171
4202
|
ListToolsRequestSchema,
|
|
4172
|
-
CallToolRequestSchema
|
|
4203
|
+
CallToolRequestSchema,
|
|
4204
|
+
ToolListChangedNotificationSchema,
|
|
4205
|
+
McpError
|
|
4173
4206
|
} from "@modelcontextprotocol/sdk/types.js";
|
|
4207
|
+
var DEFAULT_DOWNSTREAM_TIMEOUT_MS = 10 * 60 * 1e3;
|
|
4208
|
+
function downstreamTimeoutMs() {
|
|
4209
|
+
const raw = Number(process.env.QRING_WRAP_TIMEOUT_MS);
|
|
4210
|
+
return Number.isFinite(raw) && raw > 0 ? raw : DEFAULT_DOWNSTREAM_TIMEOUT_MS;
|
|
4211
|
+
}
|
|
4174
4212
|
function createAirlockServer(downstream, session) {
|
|
4175
4213
|
const downstreamInfo = downstream.getServerVersion();
|
|
4176
4214
|
const name = downstreamInfo ? `${downstreamInfo.name} (q-ring airlock)` : "q-ring-airlock";
|
|
4177
4215
|
const proxy = new Server(
|
|
4178
4216
|
{ name, version: PACKAGE_VERSION },
|
|
4179
|
-
{
|
|
4217
|
+
{
|
|
4218
|
+
capabilities: { tools: { listChanged: true } },
|
|
4219
|
+
// Servers ship usage guidance in `instructions`; hosts inject it into
|
|
4220
|
+
// the system prompt. Losing it would degrade the wrapped server.
|
|
4221
|
+
instructions: downstream.getInstructions()
|
|
4222
|
+
}
|
|
4180
4223
|
);
|
|
4181
4224
|
proxy.oninitialized = () => {
|
|
4182
4225
|
const info = proxy.getClientVersion();
|
|
4183
4226
|
if (info) setAuditAgentLabel(`${info.name}@${info.version}`);
|
|
4184
4227
|
};
|
|
4228
|
+
downstream.setNotificationHandler(ToolListChangedNotificationSchema, () => {
|
|
4229
|
+
void proxy.sendToolListChanged().catch(() => {
|
|
4230
|
+
});
|
|
4231
|
+
});
|
|
4185
4232
|
proxy.setRequestHandler(ListToolsRequestSchema, async (request) => {
|
|
4233
|
+
if (!downstream.getServerCapabilities()?.tools) return { tools: [] };
|
|
4186
4234
|
return downstream.listTools(request.params);
|
|
4187
4235
|
});
|
|
4188
4236
|
proxy.setRequestHandler(CallToolRequestSchema, async (request, extra) => {
|
|
@@ -4193,11 +4241,23 @@ function createAirlockServer(downstream, session) {
|
|
|
4193
4241
|
detail: `tool call "${toolName}" \u2192 ${session.label}`,
|
|
4194
4242
|
correlationId: session.correlationId
|
|
4195
4243
|
});
|
|
4244
|
+
const hostToken = request.params._meta?.progressToken;
|
|
4245
|
+
const onprogress = hostToken !== void 0 ? (progress) => {
|
|
4246
|
+
void extra.sendNotification({
|
|
4247
|
+
method: "notifications/progress",
|
|
4248
|
+
params: { ...progress, progressToken: hostToken }
|
|
4249
|
+
}).catch(() => {
|
|
4250
|
+
});
|
|
4251
|
+
} : void 0;
|
|
4196
4252
|
try {
|
|
4197
4253
|
return await downstream.callTool(request.params, void 0, {
|
|
4198
|
-
signal: extra.signal
|
|
4254
|
+
signal: extra.signal,
|
|
4255
|
+
timeout: downstreamTimeoutMs(),
|
|
4256
|
+
resetTimeoutOnProgress: true,
|
|
4257
|
+
onprogress
|
|
4199
4258
|
});
|
|
4200
4259
|
} catch (err) {
|
|
4260
|
+
if (err instanceof McpError) throw err;
|
|
4201
4261
|
const message = err instanceof Error ? err.message : String(err);
|
|
4202
4262
|
logAudit({
|
|
4203
4263
|
action: "wrap",
|
|
@@ -4237,6 +4297,13 @@ async function runWrap(opts) {
|
|
|
4237
4297
|
const session = { label, correlationId: randomUUID() };
|
|
4238
4298
|
const downstream = await connectDownstream(opts);
|
|
4239
4299
|
const downstreamInfo = downstream.getServerVersion();
|
|
4300
|
+
if (!downstream.getServerCapabilities()?.tools) {
|
|
4301
|
+
await downstream.close().catch(() => {
|
|
4302
|
+
});
|
|
4303
|
+
throw new Error(
|
|
4304
|
+
`wrapped server "${downstreamInfo?.name ?? label}" exposes no tools capability \u2014 the airlock is tools-only (resources/prompts are not proxied yet)`
|
|
4305
|
+
);
|
|
4306
|
+
}
|
|
4240
4307
|
logAudit({
|
|
4241
4308
|
action: "wrap",
|
|
4242
4309
|
source: "cli",
|
|
@@ -4259,20 +4326,22 @@ async function runWrap(opts) {
|
|
|
4259
4326
|
detail: `airlock session ended: ${reason}`,
|
|
4260
4327
|
correlationId: session.correlationId
|
|
4261
4328
|
});
|
|
4262
|
-
|
|
4263
|
-
|
|
4264
|
-
void
|
|
4265
|
-
});
|
|
4266
|
-
resolve(code);
|
|
4329
|
+
const closes = Promise.allSettled([proxy.close(), downstream.close()]);
|
|
4330
|
+
const deadline = new Promise((r) => setTimeout(r, 6e3).unref());
|
|
4331
|
+
void Promise.race([closes, deadline]).then(() => resolve(code));
|
|
4267
4332
|
};
|
|
4268
4333
|
downstream.onclose = () => finish(1, "wrapped server exited");
|
|
4269
|
-
|
|
4270
|
-
|
|
4334
|
+
proxy.onclose = () => finish(0, "host disconnected");
|
|
4335
|
+
proxy.onerror = (err) => {
|
|
4336
|
+
console.error(`q-ring airlock: transport error: ${err.message}`);
|
|
4337
|
+
};
|
|
4271
4338
|
process.stdin.on("end", () => finish(0, "host disconnected"));
|
|
4272
4339
|
process.stdout.on("error", (err) => {
|
|
4273
4340
|
if (err.code === "EPIPE") finish(0, "host disconnected");
|
|
4274
4341
|
else finish(1, `stdout error: ${err.message}`);
|
|
4275
4342
|
});
|
|
4343
|
+
process.once("SIGTERM", () => finish(0, "terminated"));
|
|
4344
|
+
process.once("SIGINT", () => finish(0, "interrupted"));
|
|
4276
4345
|
proxy.connect(transport).catch((err) => {
|
|
4277
4346
|
console.error(
|
|
4278
4347
|
`q-ring airlock: failed to start: ${err instanceof Error ? err.message : String(err)}`
|