@scp-dev/dashboard 0.1.0 → 0.2.0

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.
@@ -43,6 +43,7 @@
43
43
  .graph-svg .node.online .disc{stroke:color-mix(in srgb,var(--fg) 34%,transparent)}
44
44
  .graph-svg .node.human .disc{stroke-dasharray:2.5 2}
45
45
  .graph-svg .node.offline{opacity:.5}
46
+ .graph-svg .node.historic{opacity:.3}
46
47
  .graph-svg .node.offline .disc{stroke-dasharray:2 2}
47
48
  .graph-svg .node.enter{opacity:0}
48
49
  .graph-svg .node:hover .disc,.graph-svg .node:focus-visible .disc,.graph-svg .node.on .disc{stroke:var(--fg);stroke-width:1.5}
@@ -145,6 +145,7 @@ function bands(items, o) {
145
145
  return { places, bands: rects, hub, width, height: Math.max(y - o.groupGap, 1) };
146
146
  }
147
147
  function drawn(n, o, talks) {
148
+ if (n.historic) return !!o.historic;
148
149
  if (n.presence === "offline") return o.offline || (n.kind === "human" && talks.has(n.address));
149
150
  if (n.presence === "idle" && n.kind === "agent" && !n.wake) return o.nextTurn;
150
151
  return true;
@@ -154,6 +155,7 @@ const canvas = root.querySelector(".graph-canvas");
154
155
  const tip = root.querySelector(".graph-tip");
155
156
  const offline = document.querySelector("[data-graph-offline]");
156
157
  const nextTurn = document.querySelector("[data-graph-nextturn]");
158
+ const historic = document.querySelector("[data-graph-historic]");
157
159
  const reduce = matchMedia("(prefers-reduced-motion: reduce)").matches;
158
160
  const load = (k) => { try { return localStorage.getItem(k); } catch { return null; } };
159
161
  const save = (k, v) => { try { localStorage.setItem(k, v); } catch {} };
@@ -217,7 +219,7 @@ function setView(v) {
217
219
  }
218
220
 
219
221
  function visible(g) {
220
- const o = { offline: !!(offline && offline.checked), nextTurn: !!(nextTurn && nextTurn.checked) };
222
+ const o = { offline: !!(offline && offline.checked), nextTurn: !!(nextTurn && nextTurn.checked), historic: !!(historic && historic.checked) };
221
223
  const talks = new Set();
222
224
  for (const e of g.edges) if (e.kind === "message") { talks.add(e.from); talks.add(e.to); }
223
225
  const nodes = g.nodes.filter((n) => drawn(n, o, talks));
@@ -435,7 +437,8 @@ function paintNode(e) {
435
437
  const n = e.n, c = e.g.classList;
436
438
  for (const p of ["online", "idle", "offline"]) c.toggle(p, n.presence === p);
437
439
  c.toggle("human", n.kind === "human");
438
- e.g.setAttribute("aria-label", n.name + ", " + n.presence + (n.how ? ", " + n.how : ""));
440
+ c.toggle("historic", !!n.historic);
441
+ e.g.setAttribute("aria-label", n.name + ", " + n.presence + (n.historic ? ", historic" : "") + (n.how ? ", " + n.how : ""));
439
442
  if (e.icon !== n.icon) {
440
443
  e.icon = n.icon;
441
444
  e.glyph.innerHTML = ICONS[n.icon] || ICONS.generic;
@@ -724,6 +727,10 @@ for (const [input, key] of [[offline, "scp.overview.offline"], [nextTurn, "scp.g
724
727
  input.checked = load(key) === "1";
725
728
  input.addEventListener("change", () => { save(key, input.checked ? "1" : "0"); apply(); });
726
729
  }
730
+ if (historic) {
731
+ historic.checked = load("scp.graph.historic") === "1";
732
+ historic.addEventListener("change", () => { save("scp.graph.historic", historic.checked ? "1" : "0"); apply(); refresh(); });
733
+ }
727
734
  let size = "";
728
735
  new ResizeObserver(() => { const s = canvas.clientWidth + "x" + (full ? canvas.clientHeight : 0); if (canvas.clientWidth && s !== size) { size = s; apply(); } }).observe(canvas);
729
736
  new MutationObserver(update).observe(live, { childList: true });
@@ -1 +1 @@
1
- <!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1"><title>scp relay</title><link rel="stylesheet" href="/app.css?v=1oq5q70"><script src="/selfhost.js?v=bn0gye" defer></script></head><body></body></html>
1
+ <!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1"><title>scp relay</title><link rel="stylesheet" href="/app.css?v=1oq5q70"><script src="/selfhost.js?v=m8d5j" defer></script></head><body></body></html>
@@ -1,4 +1,4 @@
1
- (()=>{var f=((e)=>typeof require<"u"?require:typeof Proxy<"u"?new Proxy(e,{get:(t,n)=>(typeof require<"u"?require:t)[n]}):e)(function(e){if(typeof require<"u")return require.apply(this,arguments);throw Error('Dynamic require of "'+e+'" is not supported')});var U=86400000;var rt=(e)=>{let t=2166136261;for(let n=0;n<e.length;n++)t=Math.imul(t^e.charCodeAt(n),16777619);return t>>>0};function ot(e){let t=e.split(/[^\p{L}\p{N}]+/u).filter(Boolean);return(t.length>1?t[0][0]+t[1][0]:(t[0]??e).slice(0,1)).toUpperCase()||"?"}function ee(e){let t=(l)=>l.split("@")[0]||l,n=new Map;for(let l of e)n.set(t(l.email),(n.get(t(l.email))??0)+1);let o=e.map((l)=>({id:l.user_id,name:n.get(t(l.email))>1?l.email:t(l.email)}));o.sort((l,d)=>l.name.localeCompare(d.name));let i=new Set,s=new Map;for(let{id:l,name:d}of o){let a=rt(d)%8;for(let c=0;c<8&&i.has(a);c++)a=(a+1)%8;i.add(a),s.set(l,{name:d,initials:ot(d),tone:a})}return s}function F(e,t){return{...e,machines:e.machines.map(({actor:n,...o})=>({...o,identity:n!==null&&t.get(n)||null}))}}var at=["inbox.wait","ask","scopes","groups"],cn=[...at,"peek","judge","holds"],te={bodyBytes:65536,recipients:16,attachments:16,ttlDefaultS:604800,ttlMaxS:2592000,purgeAfterS:604800,inboxMax:1000,inboxPage:100,waitMaxS:50,hopMax:64,threadMax:200,causalWindowS:120,pairBurst:20,pairBurstWindowS:10,pairPerMinute:30,pairPerHour:300,userPerDay:2000,pingPongAlternations:100,pingPongWindowS:600,pingPongBlockS:600,onlineWithinS:60,hookOnlineS:300,hookIdleS:1800,hookEndedGraceS:30,restartGraceS:60,injectMessages:5,injectBytes:7168,pushBytes:20480,pushes:20,pushGapS:30,pushQuietS:3,pushGraceS:6,stopBlocks:3,directiveHolds:5,injectHoldS:10,deferredShowS:600,deferredSettleMs:2000,listOfflineWithinS:86400,handleBoundS:2592000,idempotencyS:86400,auditKeepS:2592000,noteChars:280,handbackBytes:16384,stderrBytes:1024,activityKeep:5000,activitySummaryChars:200,textField:512,lineageMax:16,depthMax:32,fanOutMax:32,fanOutTotal:64,treePerMinute:120,treePerHour:1200,treePauseS:600,groupPerMinute:60,grantTtlS:604800,groupIdleS:86400,policyLoadsKept:8,titleRefreshTurns:5,wakeDebounceS:60,wakeBackoffMaxS:900,wakeRefChars:512,wakeEtaMaxS:3600};var Re=new Set(["cli","hook","mcp","shell"]);var Tn=`harness IN (${[...Re].map((e)=>`'${e}'`).join(", ")})`;var ne="_gone_",Nn=[`CREATE TABLE IF NOT EXISTS endpoints (
1
+ (()=>{var f=((e)=>typeof require<"u"?require:typeof Proxy<"u"?new Proxy(e,{get:(t,n)=>(typeof require<"u"?require:t)[n]}):e)(function(e){if(typeof require<"u")return require.apply(this,arguments);throw Error('Dynamic require of "'+e+'" is not supported')});var U=86400000;var ot=(e)=>{let t=2166136261;for(let n=0;n<e.length;n++)t=Math.imul(t^e.charCodeAt(n),16777619);return t>>>0};function at(e){let t=e.split(/[^\p{L}\p{N}]+/u).filter(Boolean);return(t.length>1?t[0][0]+t[1][0]:(t[0]??e).slice(0,1)).toUpperCase()||"?"}function ee(e){let t=(l)=>l.split("@")[0]||l,n=new Map;for(let l of e)n.set(t(l.email),(n.get(t(l.email))??0)+1);let o=e.map((l)=>({id:l.user_id,name:n.get(t(l.email))>1?l.email:t(l.email)}));o.sort((l,d)=>l.name.localeCompare(d.name));let i=new Set,s=new Map;for(let{id:l,name:d}of o){let a=ot(d)%8;for(let c=0;c<8&&i.has(a);c++)a=(a+1)%8;i.add(a),s.set(l,{name:d,initials:at(d),tone:a})}return s}function B(e,t){return{...e,machines:e.machines.map(({actor:n,...o})=>({...o,identity:n!==null&&t.get(n)||null}))}}var lt=["inbox.wait","ask","scopes","groups"],un=[...lt,"peek","judge","holds"],te={bodyBytes:65536,recipients:16,attachments:16,ttlDefaultS:604800,ttlMaxS:2592000,purgeAfterS:604800,inboxMax:1000,inboxPage:100,waitMaxS:50,hopMax:64,threadMax:200,causalWindowS:120,pairBurst:20,pairBurstWindowS:10,pairPerMinute:30,pairPerHour:300,userPerDay:2000,pingPongAlternations:100,pingPongWindowS:600,pingPongBlockS:600,onlineWithinS:60,hookOnlineS:300,hookIdleS:1800,hookEndedGraceS:30,restartGraceS:60,injectMessages:5,injectBytes:7168,pushBytes:20480,pushes:20,pushGapS:30,pushQuietS:3,pushGraceS:6,stopBlocks:3,directiveHolds:5,injectHoldS:10,deferredShowS:600,deferredSettleMs:2000,listOfflineWithinS:86400,historicAfterS:3600,handleBoundS:2592000,endpointRetireS:86400,endpointRetireMax:1000,idempotencyS:86400,auditKeepS:2592000,noteChars:280,handbackBytes:16384,stderrBytes:1024,activityKeep:5000,activitySummaryChars:200,textField:512,lineageMax:16,depthMax:32,fanOutMax:32,fanOutTotal:64,treePerMinute:120,treePerHour:1200,treePauseS:600,groupPerMinute:60,grantTtlS:604800,groupIdleS:86400,policyLoadsKept:8,titleRefreshTurns:5,wakeDebounceS:60,wakeBackoffMaxS:900,wakeRefChars:512,wakeEtaMaxS:3600};var Re=["codex-queue"];var Se=new Set(["cli","hook","mcp","shell"]);var An=`harness IN (${[...Se].map((e)=>`'${e}'`).join(", ")})`;var ne="_gone_",Ln=[`CREATE TABLE IF NOT EXISTS endpoints (
2
2
  id TEXT PRIMARY KEY,
3
3
  handle TEXT NOT NULL UNIQUE,
4
4
  kind TEXT NOT NULL,
@@ -87,7 +87,7 @@
87
87
  agent TEXT
88
88
  )`,"CREATE TABLE IF NOT EXISTS peek_turns (id INTEGER PRIMARY KEY AUTOINCREMENT, endpoint TEXT NOT NULL, at INTEGER NOT NULL, role TEXT NOT NULL, text TEXT NOT NULL)","CREATE INDEX IF NOT EXISTS peek_turns_endpoint ON peek_turns (endpoint, id)","CREATE TRIGGER IF NOT EXISTS audit_no_update BEFORE UPDATE ON audit BEGIN SELECT RAISE(ABORT, 'audit is append-only'); END",`CREATE TRIGGER IF NOT EXISTS audit_no_delete BEFORE DELETE ON audit
89
89
  WHEN OLD.at > CAST(strftime('%s', 'now') AS INTEGER) * 1000 - ${te.auditKeepS*1000}
90
- BEGIN SELECT RAISE(ABORT, 'audit is append-only'); END`];var An=`stderr (last ${te.stderrBytes/1024} KB):`,lt=4,Ln=new RegExp(`^[0-9a-z]{${lt},}$`);class k{value;constructor(e){this.value=e}toString(){return this.value}}var ut={"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;","'":"&#39;"};function Te(e){if(e instanceof k)return e.value;if(Array.isArray(e))return e.map(Te).join("");if(e===null||e===void 0||e===!1)return"";return String(e).replace(/[&<>"']/g,(t)=>ut[t])}function r(e,...t){let n=e[0];for(let o=0;o<t.length;o++)n+=Te(t[o])+e[o+1];return new k(n)}function R(e){let t=2166136261;for(let n=0;n<e.length;n++)t=Math.imul(t^e.charCodeAt(n),16777619);return(t>>>0).toString(36)}function re(e,t){let n=e.email?r`<nav class="nav" aria-label="Main">
90
+ BEGIN SELECT RAISE(ABORT, 'audit is append-only'); END`];var On=`stderr (last ${te.stderrBytes/1024} KB):`,dt=4,xn=new RegExp(`^[0-9a-z]{${dt},}$`);var Dn=`COALESCE(wake IN (${Re.map((e)=>`'${e}'`).join(", ")}), 0)`;class k{value;constructor(e){this.value=e}toString(){return this.value}}var ht={"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;","'":"&#39;"};function Ne(e){if(e instanceof k)return e.value;if(Array.isArray(e))return e.map(Ne).join("");if(e===null||e===void 0||e===!1)return"";return String(e).replace(/[&<>"']/g,(t)=>ht[t])}function r(e,...t){let n=e[0];for(let o=0;o<t.length;o++)n+=Ne(t[o])+e[o+1];return new k(n)}function R(e){let t=2166136261;for(let n=0;n<e.length;n++)t=Math.imul(t^e.charCodeAt(n),16777619);return(t>>>0).toString(36)}function re(e,t){let n=e.email?r`<nav class="nav" aria-label="Main">
91
91
  <a href="/orgs" ${e.nav==="orgs"?r`aria-current="page"`:""}>Orgs</a>
92
92
  <a href="/device" ${e.nav==="device"?r`aria-current="page"`:""}>Device</a>
93
93
  <form method="post" action="/sign-out"><input type="hidden" name="csrf" value="${e.csrf}"><button class="linkish" type="submit">Sign out</button></form>
@@ -100,7 +100,7 @@
100
100
  <link rel="preconnect" href="https://fonts.googleapis.com">
101
101
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
102
102
  <link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Geist:wght@400;500;600&family=Geist+Mono:wght@400;500&display=swap">
103
- <link rel="stylesheet" href="/app.css?v=${R(ht)}">
103
+ <link rel="stylesheet" href="/app.css?v=${R(gt)}">
104
104
  ${e.head??""}
105
105
  </head>
106
106
  ${e.bare?r`<body class="bare">${t}</body>
@@ -115,7 +115,7 @@ ${n}
115
115
  <a class="mini-link" href="https://scp.dev/spec">Spec</a>
116
116
  </div></footer>
117
117
  </body>
118
- </html>`}`}`}var ht=`
118
+ </html>`}`}`}var gt=`
119
119
  *,*::before,*::after{margin:0;padding:0;box-sizing:border-box}
120
120
  :root{--bg:#fff;--surface:#f7f7f6;--fg:#0b0b0b;--ink:#555;--dim:#6b6b6b;--faint:#a8a8a8;--line:rgb(0 0 0/.12);--line-soft:rgb(0 0 0/.065);--acc:#4746d4;--acc-soft:rgb(71 70 212/.09);--warn:#b4321f;--head:rgb(255 255 255/.82);
121
121
  --sans:"Geist",-apple-system,BlinkMacSystemFont,"Segoe UI","Helvetica Neue",Arial,sans-serif;--mono:"Geist Mono",ui-monospace,"SF Mono",SFMono-Regular,Menlo,Consolas,monospace;--gutter:clamp(1rem,4vw,3rem);--headh:4rem}
@@ -302,26 +302,26 @@ table.orgs td.end,table.orgs th.end{text-align:right}
302
302
  .harnesses .more{line-height:1}
303
303
  .foot-in{display:flex;flex-wrap:wrap;justify-content:space-between;gap:1rem;padding-block:2rem}
304
304
  @media (prefers-reduced-motion:reduce){*,*::before,*::after{transition:none!important;animation:none!important}}
305
- `;var Es=30*U;var ws=["default-src 'none'","style-src 'self' https://fonts.googleapis.com","font-src https://fonts.gstatic.com","script-src 'self' https://challenges.cloudflare.com","frame-src https://challenges.cloudflare.com","connect-src 'self' https://challenges.cloudflare.com","img-src 'self' data:","form-action 'self'","frame-ancestors 'none'","base-uri 'none'"].join("; ");var L=8000;function x(e,t){if(!e)return"never";let n=Math.max(0,Math.round((t-Date.parse(e))/1000));if(n<5)return"just now";if(n<60)return`${n}s ago`;if(n<3600)return`${Math.floor(n/60)}m ago`;if(n<86400)return`${Math.floor(n/3600)}h ago`;return`${Math.floor(n/86400)}d ago`}function E(e){let[,t="",...n]=e.split("/");return{machine:t,handle:n.join("/")||t}}function X(e,t=!1){let n=new Map(e.machines.map((s)=>[s.name,{name:s.name,machine:s,endpoints:[],away:0}])),o=(s)=>{let l=n.get(s);if(!l)l={name:s,machine:null,endpoints:[],away:0},n.set(s,l);return l},i=new Map(e.endpoints.filter((s)=>s.kind==="agent").map((s)=>[s.address,s]));for(let s of i.values()){let l=s,d=new Set([s.address]);for(let p=t?i.get(l.parent??""):void 0;p&&!d.has(p.address);p=i.get(p.parent??""))d.add(p.address),l=p;let a=E(s.address).machine,c=t?E(l.address).machine:a;if(o(c).endpoints.push(s),c!==a)o(a).away++}return[...n.values()]}var O=(e)=>e.wake&&e.wake!=="none"?e.wake:null,Et=r`<svg class="bolt" viewBox="0 0 16 16" aria-hidden="true"><path d="M9.5 1 3 9h4.5L6.5 15 13 7H8.5z" fill="currentColor"/></svg>`;function G(e){if(e.startsWith("hook:"))return`a message runs its ${e.slice(5)} wake hook`;if(e==="spawn-resume")return"a message resumes it through its spawn";return`a message wakes it via ${e}`}function wt(e){let t=O(e),n=e.waking?"hibernated, its machine is waking":t?`${e.presence}, ${G(t)}`:e.presence==="idle"?"idle, reads messages at its next turn":e.presence;return{cell:r`<span class="dot ${e.presence}${t?" wake":""}" aria-hidden="true"></span><span class="mini">${e.waking?"waking":e.presence}</span>${t?r` <span class="wake-badge">${Et}<span class="sr">${G(t)}</span></span>`:""}`,tip:n}}var de={claude:'<path d="m4.7144 15.9555 4.7174-2.6471.079-.2307-.079-.1275h-.2307l-.7893-.0486-2.6956-.0729-2.3375-.0971-2.2646-.1214-.5707-.1215-.5343-.7042.0546-.3522.4797-.3218.686.0608 1.5179.1032 2.2767.1578 1.6514.0972 2.4468.255h.3886l.0546-.1579-.1336-.0971-.1032-.0972L6.973 9.8356l-2.55-1.6879-1.3356-.9714-.7225-.4918-.3643-.4614-.1578-1.0078.6557-.7225.8803.0607.2246.0607.8925.686 1.9064 1.4754 2.4893 1.8336.3643.3035.1457-.1032.0182-.0728-.164-.2733-1.3539-2.4467-1.445-2.4893-.6435-1.032-.17-.6194c-.0607-.255-.1032-.4674-.1032-.7285L6.287.1335 6.6997 0l.9957.1336.419.3642.6192 1.4147 1.0018 2.2282 1.5543 3.0296.4553.8985.2429.8318.091.255h.1579v-.1457l.1275-1.706.2368-2.0947.2307-2.6957.0789-.7589.3764-.9107.7468-.4918.5828.2793.4797.686-.0668.4433-.2853 1.8517-.5586 2.9021-.3643 1.9429h.2125l.2429-.2429.9835-1.3053 1.6514-2.0643.7286-.8196.85-.9046.5464-.4311h1.0321l.759 1.1293-.34 1.1657-1.0625 1.3478-.8804 1.1414-1.2628 1.7-.7893 1.36.0729.1093.1882-.0183 2.8535-.607 1.5421-.2794 1.8396-.3157.8318.3886.091.3946-.3278.8075-1.967.4857-2.3072.4614-3.4364.8136-.0425.0304.0486.0607 1.5482.1457.6618.0364h1.621l3.0175.2247.7892.522.4736.6376-.079.4857-1.2142.6193-1.6393-.3886-3.825-.9107-1.3113-.3279h-.1822v.1093l1.0929 1.0686 2.0035 1.8092 2.5075 2.3314.1275.5768-.3218.4554-.34-.0486-2.2039-1.6575-.85-.7468-1.9246-1.621h-.1275v.17l.4432.6496 2.3436 3.5214.1214 1.0807-.17.3521-.6071.2125-.6679-.1214-1.3721-1.9246L14.38 17.959l-1.1414-1.9428-.1397.079-.674 7.2552-.3156.3703-.7286.2793-.6071-.4614-.3218-.7468.3218-1.4753.3886-1.9246.3157-1.53.2853-1.9004.17-.6314-.0121-.0425-.1397.0182-1.4328 1.9672-2.1796 2.9446-1.7243 1.8456-.4128.164-.7164-.3704.0667-.6618.4008-.5889 2.386-3.0357 1.4389-1.882.929-1.0868-.0062-.1579h-.0546l-6.3385 4.1164-1.1293.1457-.4857-.4554.0608-.7467.2307-.2429 1.9064-1.3114Z" fill="currentColor"/>',codex:'<path d="M22.2819 9.8211a5.9847 5.9847 0 0 0-.5157-4.9108 6.0462 6.0462 0 0 0-6.5098-2.9A6.0651 6.0651 0 0 0 4.9807 4.1818a5.9847 5.9847 0 0 0-3.9977 2.9 6.0462 6.0462 0 0 0 .7427 7.0966 5.98 5.98 0 0 0 .511 4.9107 6.051 6.051 0 0 0 6.5146 2.9001A5.9847 5.9847 0 0 0 13.2599 24a6.0557 6.0557 0 0 0 5.7718-4.2058 5.9894 5.9894 0 0 0 3.9977-2.9001 6.0557 6.0557 0 0 0-.7475-7.0729zm-9.022 12.6081a4.4755 4.4755 0 0 1-2.8764-1.0408l.1419-.0804 4.7783-2.7582a.7948.7948 0 0 0 .3927-.6813v-6.7369l2.02 1.1686a.071.071 0 0 1 .038.052v5.5826a4.504 4.504 0 0 1-4.4945 4.4944zm-9.6607-4.1254a4.4708 4.4708 0 0 1-.5346-3.0137l.142.0852 4.783 2.7582a.7712.7712 0 0 0 .7806 0l5.8428-3.3685v2.3324a.0804.0804 0 0 1-.0332.0615L9.74 19.9502a4.4992 4.4992 0 0 1-6.1408-1.6464zM2.3408 7.8956a4.485 4.485 0 0 1 2.3655-1.9728V11.6a.7664.7664 0 0 0 .3879.6765l5.8144 3.3543-2.0201 1.1685a.0757.0757 0 0 1-.071 0l-4.8303-2.7865A4.504 4.504 0 0 1 2.3408 7.872zm16.5963 3.8558L13.1038 8.364 15.1192 7.2a.0757.0757 0 0 1 .071 0l4.8303 2.7913a4.4944 4.4944 0 0 1-.6765 8.1042v-5.6772a.79.79 0 0 0-.407-.667zm2.0107-3.0231l-.142-.0852-4.7735-2.7818a.7759.7759 0 0 0-.7854 0L9.409 9.2297V6.8974a.0662.0662 0 0 1 .0284-.0615l4.8303-2.7866a4.4992 4.4992 0 0 1 6.6802 4.66zM8.3065 12.863l-2.02-1.1638a.0804.0804 0 0 1-.038-.0567V6.0742a4.4992 4.4992 0 0 1 7.3757-3.4537l-.142.0805L8.704 5.459a.7948.7948 0 0 0-.3927.6813zm1.0976-2.3654l2.602-1.4998 2.6069 1.4998v2.9994l-2.5974 1.4997-2.6067-1.4997Z" fill="currentColor"/>',opencode:'<path d="M21.6 24H2.4V0h19.2zM16.8 4.8H7.2v14.4h9.6z" fill="currentColor"/><path d="M16.8 9.6H7.2v9.6h9.6z" fill="currentColor" opacity=".45"/>',gemini:'<path d="M11.04 19.32Q12 21.51 12 24q0-2.49.93-4.68.96-2.19 2.58-3.81t3.81-2.55Q21.51 12 24 12q-2.49 0-4.68-.93a12.3 12.3 0 0 1-3.81-2.58 12.3 12.3 0 0 1-2.58-3.81Q12 2.49 12 0q0 2.49-.96 4.68-.93 2.19-2.55 3.81a12.3 12.3 0 0 1-3.81 2.58Q2.49 12 0 12q2.49 0 4.68.96 2.19.93 3.81 2.55t2.55 3.81" fill="currentColor"/>',agy:'<path d="M12.48 10.92v3.28h7.84c-.24 1.84-.853 3.187-1.787 4.133-1.147 1.147-2.933 2.4-6.053 2.4-4.827 0-8.6-3.893-8.6-8.72s3.773-8.72 8.6-8.72c2.6 0 4.507 1.027 5.907 2.347l2.307-2.307C18.747 1.44 16.133 0 12.48 0 5.867 0 .307 5.387.307 12s5.56 12 12.173 12c3.573 0 6.267-1.173 8.373-3.36 2.16-2.16 2.84-5.213 2.84-7.667 0-.76-.053-1.467-.173-2.053H12.48z" fill="currentColor"/>',grok:'<g transform="translate(1.2 1.2) scale(.9)"><path d="M6.469 8.776 16.512 23h-4.464L2.005 8.776H6.47zm-.004 7.9 2.233 3.164L6.467 23H2l4.465-6.324zM22 2.582V23h-3.659V7.764L22 2.582zM22 1l-9.952 14.095-2.233-3.163L17.533 1H22z" fill="currentColor"/></g>',muse:'<g transform="translate(-1.2 -1.2) scale(1.1)"><path d="M6.915 4.03c-1.968 0-3.683 1.28-4.871 3.113C.704 9.208 0 11.883 0 14.449c0 .706.07 1.369.21 1.973a6.624 6.624 0 0 0 .265.86 5.297 5.297 0 0 0 .371.761c.696 1.159 1.818 1.927 3.593 1.927 1.497 0 2.633-.671 3.965-2.444.76-1.012 1.144-1.626 2.663-4.32l.756-1.339.186-.325c.061.1.121.196.183.3l2.152 3.595c.724 1.21 1.665 2.556 2.47 3.314 1.046.987 1.992 1.22 3.06 1.22 1.075 0 1.876-.355 2.455-.843a3.743 3.743 0 0 0 .81-.973c.542-.939.861-2.127.861-3.745 0-2.72-.681-5.357-2.084-7.45-1.282-1.912-2.957-2.93-4.716-2.93-1.047 0-2.088.467-3.053 1.308-.652.57-1.257 1.29-1.82 2.05-.69-.875-1.335-1.547-1.958-2.056-1.182-.966-2.315-1.303-3.454-1.303zm10.16 2.053c1.147 0 2.188.758 2.992 1.999 1.132 1.748 1.647 4.195 1.647 6.4 0 1.548-.368 2.9-1.839 2.9-.58 0-1.027-.23-1.664-1.004-.496-.601-1.343-1.878-2.832-4.358l-.617-1.028a44.908 44.908 0 0 0-1.255-1.98c.07-.109.141-.224.211-.327 1.12-1.667 2.118-2.602 3.358-2.602zm-10.201.553c1.265 0 2.058.791 2.675 1.446.307.327.737.871 1.234 1.579l-1.02 1.566c-.757 1.163-1.882 3.017-2.837 4.338-1.191 1.649-1.81 1.817-2.486 1.817-.524 0-1.038-.237-1.383-.794-.263-.426-.464-1.13-.464-2.046 0-2.221.63-4.535 1.66-6.088.454-.687.964-1.226 1.533-1.533a2.264 2.264 0 0 1 1.088-.285z" fill="currentColor"/></g>',pi:'<path d="M0 0h18v6H0zM0 6h6v18H0zM12 6h6v6h-6zM6 12h6v6H6zM18 12h6v12h-6z" fill="currentColor"/>',t3code:'<path d="M5.177,18.414V7.823H1V5.362H12.374V7.823H8.197V18.414H5.177ZM17.593,18.638C16.686,18.638 15.784,18.52 14.889,18.283C13.994,18.035 13.236,17.687 12.615,17.239L13.789,14.927C14.287,15.288 14.865,15.574 15.523,15.785C16.182,15.996 16.847,16.102 17.518,16.102C18.277,16.102 18.873,15.953 19.308,15.654C19.743,15.356 19.961,14.946 19.961,14.424C19.961,13.927 19.768,13.535 19.383,13.249C18.998,12.963 18.376,12.82 17.518,12.82H16.139V10.825L19.774,6.705L20.11,7.786H13.267V5.362H22.403V7.32L18.786,11.441L17.257,10.564H18.134C19.737,10.564 20.949,10.925 21.769,11.646C22.59,12.367 23,13.293 23,14.424C23,15.157 22.807,15.847 22.422,16.494C22.037,17.127 21.446,17.643 20.651,18.041C19.855,18.439 18.836,18.638 17.593,18.638Z" fill="currentColor"/>',generic:'<g transform="scale(1.5)"><rect x="1.75" y="2.75" width="12.5" height="10.5" rx="1.5" fill="none" stroke="currentColor" stroke-width="1.4"/><path d="M4.5 6.25 6.5 8l-2 1.75M8 10h3" fill="none" stroke="currentColor" stroke-width="1.3" stroke-linecap="round" stroke-linejoin="round"/></g>'},bt={"claude-code":"claude",claude:"claude",codex:"codex",opencode:"opencode",gemini:"gemini",agy:"agy",antigravity:"agy",grok:"grok",muse:"muse",xai:"grok",pi:"pi",t3code:"t3code",t3:"t3code"},J=(e)=>bt[(e??"").toLowerCase()]??"generic",q=(e)=>{let t=J(e);return r`<svg class="hicon h-${t}" width="12" height="12" viewBox="0 0 24 24" role="img" aria-label="${e??"unknown harness"}">${new k(de[t])}</svg>`},yt=r`<svg width="8" height="8" viewBox="0 0 8 8" aria-hidden="true"><path d="M2 .75 6.5 4 2 7.25z" fill="currentColor"/></svg>`,V=(e)=>e.startsWith(ne)?"(ended)":e,S=(e)=>V(E(e).handle),vt=(e)=>Object.entries(e).filter(([t])=>t!=="machine"),Le={online:0,idle:1,hibernated:1,offline:2},oe=(e,t)=>(Le[e.presence]??3)-(Le[t.presence]??3)||E(e.address).handle.localeCompare(E(t.address).handle);function xe(e){let t=new Set(e.map((a)=>a.address)),n=new Map,o=[];for(let a of e)if(a.parent&&a.parent!==a.address&&t.has(a.parent))n.set(a.parent,[...n.get(a.parent)??[],a]);else o.push(a);let i=[],s=new Set,l=(a,c)=>{for(let p of a.sort(oe)){if(s.has(p.address))continue;s.add(p.address),i.push({e:p,depth:c.length,anc:c,kids:0}),l(n.get(p.address)??[],[...c,p.address])}};l(o,[]),l(e.filter((a)=>!s.has(a.address)),[]);let d=new Map(i.map((a)=>[a.e.address,a]));for(let a of i)if(a.anc.length)d.get(a.anc.at(-1)).kids++;return i}var ae=(e)=>e.presence!=="offline";function _t(e){let t=e.filter((a)=>a.kind==="agent"),n=new Set(t.map((a)=>a.address)),o=new Map,i=[];for(let a of t)if(a.parent&&a.parent!==a.address&&n.has(a.parent))o.set(a.parent,[...o.get(a.parent)??[],a]);else i.push(a);let s=new Set,l=(a)=>{s.add(a.address);let c=[],p=[...o.get(a.address)??[]];while(p.length){let w=p.pop();if(s.has(w.address))continue;s.add(w.address),c.push(w),p.push(...o.get(w.address)??[])}return{root:a,kids:c.filter(ae).sort(oe)}},d=i.map(l);for(let a of t)if(!s.has(a.address))d.push(l(a));return d.filter((a)=>ae(a.root)||a.kids.length).sort((a,c)=>c.kids.length-a.kids.length||oe(a.root,c.root))}var kt=3,le=(e)=>r`<span class="cell ${e.presence}${O(e)?" wake":""}" title="${S(e.address)}, ${e.presence}"></span>`;function Oe({root:e,kids:t}){let{machine:n}=E(e.address);return r`<div class="strip-row"><span class="strip-who" title="${e.address}">${q(e.harness)}<span class="mono strip-name">${S(e.address)}</span><span class="mini">${n}</span></span>
306
- <span class="cells">${ae(e)?le(e):""}${t.map(le)}</span>${t.length?r`<span class="mini push">${t.length}</span>`:""}</div>`}function Rt(e){let t=e.filter((n)=>!n.kids.length);if(t.length<=kt)return e.map(Oe);return[...e.filter((n)=>n.kids.length).map(Oe),r`<div class="strip-row"><span class="strip-who"><span class="mono strip-name">${t.length} solo agents</span></span>
307
- <span class="cells">${t.map((n)=>le(n.root))}</span></div>`]}function Ie(e,t){return r`<div class="strip" aria-label="Agents per orchestrator">${Rt(_t(e.endpoints))}</div><p class="mini strip-key"><span class="cell online"></span>online <span class="cell idle wake"></span>wakeable <span class="cell idle"></span>next turn <a class="mini-link push" href="/orgs/${t}/graph">Graph</a></p>`}function St({e,depth:t,anc:n,kids:o},i,s,l){let d=S(e.address),{machine:a}=E(e.address),c=e.parent?S(e.parent):null,p=vt(e.labels),w=e.status?e.status.note?`${e.status.state}: ${e.status.note}`:e.status.state:"none",nt=e.presence==="online"?"now":x(e.last_seen,s),ve=wt(e),st=o?r`<button class="caret" type="button" data-tree="${e.address}" aria-expanded="${String(!i.has(e.address))}" aria-label="${o} under ${d}">${yt}</button>`:r`<span class="caret" aria-hidden="true"></span>`,it=[d,e.harness??"unknown harness",c?`sub of ${c}`:""].filter(Boolean).join(", ");return r`<tr${t?r` class="sub d${Math.min(t,4)}" data-anc="${n.join(" ")}"${n.some((C)=>i.has(C))?" hidden":""}`:""}>
308
- <td class="agent" title="${it}">${st}${q(e.harness)}<span class="mono">${d}</span>${a!==l?r` <span class="tag" title="runs on ${a}">${a}</span>`:""}${c&&!t?r` <span class="mini">sub of ${c}</span>`:""}</td>
305
+ `;var vs=30*U;var _s=["default-src 'none'","style-src 'self' https://fonts.googleapis.com","font-src https://fonts.gstatic.com","script-src 'self' https://challenges.cloudflare.com","frame-src https://challenges.cloudflare.com","connect-src 'self' https://challenges.cloudflare.com","img-src 'self' data:","form-action 'self'","frame-ancestors 'none'","base-uri 'none'"].join("; ");var L=8000;function O(e,t){if(!e)return"never";let n=Math.max(0,Math.round((t-Date.parse(e))/1000));if(n<5)return"just now";if(n<60)return`${n}s ago`;if(n<3600)return`${Math.floor(n/60)}m ago`;if(n<86400)return`${Math.floor(n/3600)}h ago`;return`${Math.floor(n/86400)}d ago`}function E(e){let[,t="",...n]=e.split("/");return{machine:t,handle:n.join("/")||t}}function X(e,t=!1){let n=new Map(e.machines.map((s)=>[s.name,{name:s.name,machine:s,endpoints:[],away:0}])),o=(s)=>{let l=n.get(s);if(!l)l={name:s,machine:null,endpoints:[],away:0},n.set(s,l);return l},i=new Map(e.endpoints.filter((s)=>s.kind==="agent").map((s)=>[s.address,s]));for(let s of i.values()){let l=s,d=new Set([s.address]);for(let p=t?i.get(l.parent??""):void 0;p&&!d.has(p.address);p=i.get(p.parent??""))d.add(p.address),l=p;let a=E(s.address).machine,c=t?E(l.address).machine:a;if(o(c).endpoints.push(s),c!==a)o(a).away++}return[...n.values()]}var x=(e)=>e.wake&&e.wake!=="none"?e.wake:null,bt=r`<svg class="bolt" viewBox="0 0 16 16" aria-hidden="true"><path d="M9.5 1 3 9h4.5L6.5 15 13 7H8.5z" fill="currentColor"/></svg>`;function G(e){if(e.startsWith("hook:"))return`a message runs its ${e.slice(5)} wake hook`;if(e==="spawn-resume")return"a message resumes it through its spawn";return`a message wakes it via ${e}`}function wt(e){let t=x(e),n=e.waking?"hibernated, its machine is waking":t?`${e.presence}, ${G(t)}`:e.presence==="idle"?"idle, reads messages at its next turn":e.presence;return{cell:r`<span class="dot ${e.presence}${t?" wake":""}" aria-hidden="true"></span><span class="mini">${e.waking?"waking":e.presence}</span>${t?r` <span class="wake-badge">${bt}<span class="sr">${G(t)}</span></span>`:""}`,tip:n}}var de={claude:'<path d="m4.7144 15.9555 4.7174-2.6471.079-.2307-.079-.1275h-.2307l-.7893-.0486-2.6956-.0729-2.3375-.0971-2.2646-.1214-.5707-.1215-.5343-.7042.0546-.3522.4797-.3218.686.0608 1.5179.1032 2.2767.1578 1.6514.0972 2.4468.255h.3886l.0546-.1579-.1336-.0971-.1032-.0972L6.973 9.8356l-2.55-1.6879-1.3356-.9714-.7225-.4918-.3643-.4614-.1578-1.0078.6557-.7225.8803.0607.2246.0607.8925.686 1.9064 1.4754 2.4893 1.8336.3643.3035.1457-.1032.0182-.0728-.164-.2733-1.3539-2.4467-1.445-2.4893-.6435-1.032-.17-.6194c-.0607-.255-.1032-.4674-.1032-.7285L6.287.1335 6.6997 0l.9957.1336.419.3642.6192 1.4147 1.0018 2.2282 1.5543 3.0296.4553.8985.2429.8318.091.255h.1579v-.1457l.1275-1.706.2368-2.0947.2307-2.6957.0789-.7589.3764-.9107.7468-.4918.5828.2793.4797.686-.0668.4433-.2853 1.8517-.5586 2.9021-.3643 1.9429h.2125l.2429-.2429.9835-1.3053 1.6514-2.0643.7286-.8196.85-.9046.5464-.4311h1.0321l.759 1.1293-.34 1.1657-1.0625 1.3478-.8804 1.1414-1.2628 1.7-.7893 1.36.0729.1093.1882-.0183 2.8535-.607 1.5421-.2794 1.8396-.3157.8318.3886.091.3946-.3278.8075-1.967.4857-2.3072.4614-3.4364.8136-.0425.0304.0486.0607 1.5482.1457.6618.0364h1.621l3.0175.2247.7892.522.4736.6376-.079.4857-1.2142.6193-1.6393-.3886-3.825-.9107-1.3113-.3279h-.1822v.1093l1.0929 1.0686 2.0035 1.8092 2.5075 2.3314.1275.5768-.3218.4554-.34-.0486-2.2039-1.6575-.85-.7468-1.9246-1.621h-.1275v.17l.4432.6496 2.3436 3.5214.1214 1.0807-.17.3521-.6071.2125-.6679-.1214-1.3721-1.9246L14.38 17.959l-1.1414-1.9428-.1397.079-.674 7.2552-.3156.3703-.7286.2793-.6071-.4614-.3218-.7468.3218-1.4753.3886-1.9246.3157-1.53.2853-1.9004.17-.6314-.0121-.0425-.1397.0182-1.4328 1.9672-2.1796 2.9446-1.7243 1.8456-.4128.164-.7164-.3704.0667-.6618.4008-.5889 2.386-3.0357 1.4389-1.882.929-1.0868-.0062-.1579h-.0546l-6.3385 4.1164-1.1293.1457-.4857-.4554.0608-.7467.2307-.2429 1.9064-1.3114Z" fill="currentColor"/>',codex:'<path d="M22.2819 9.8211a5.9847 5.9847 0 0 0-.5157-4.9108 6.0462 6.0462 0 0 0-6.5098-2.9A6.0651 6.0651 0 0 0 4.9807 4.1818a5.9847 5.9847 0 0 0-3.9977 2.9 6.0462 6.0462 0 0 0 .7427 7.0966 5.98 5.98 0 0 0 .511 4.9107 6.051 6.051 0 0 0 6.5146 2.9001A5.9847 5.9847 0 0 0 13.2599 24a6.0557 6.0557 0 0 0 5.7718-4.2058 5.9894 5.9894 0 0 0 3.9977-2.9001 6.0557 6.0557 0 0 0-.7475-7.0729zm-9.022 12.6081a4.4755 4.4755 0 0 1-2.8764-1.0408l.1419-.0804 4.7783-2.7582a.7948.7948 0 0 0 .3927-.6813v-6.7369l2.02 1.1686a.071.071 0 0 1 .038.052v5.5826a4.504 4.504 0 0 1-4.4945 4.4944zm-9.6607-4.1254a4.4708 4.4708 0 0 1-.5346-3.0137l.142.0852 4.783 2.7582a.7712.7712 0 0 0 .7806 0l5.8428-3.3685v2.3324a.0804.0804 0 0 1-.0332.0615L9.74 19.9502a4.4992 4.4992 0 0 1-6.1408-1.6464zM2.3408 7.8956a4.485 4.485 0 0 1 2.3655-1.9728V11.6a.7664.7664 0 0 0 .3879.6765l5.8144 3.3543-2.0201 1.1685a.0757.0757 0 0 1-.071 0l-4.8303-2.7865A4.504 4.504 0 0 1 2.3408 7.872zm16.5963 3.8558L13.1038 8.364 15.1192 7.2a.0757.0757 0 0 1 .071 0l4.8303 2.7913a4.4944 4.4944 0 0 1-.6765 8.1042v-5.6772a.79.79 0 0 0-.407-.667zm2.0107-3.0231l-.142-.0852-4.7735-2.7818a.7759.7759 0 0 0-.7854 0L9.409 9.2297V6.8974a.0662.0662 0 0 1 .0284-.0615l4.8303-2.7866a4.4992 4.4992 0 0 1 6.6802 4.66zM8.3065 12.863l-2.02-1.1638a.0804.0804 0 0 1-.038-.0567V6.0742a4.4992 4.4992 0 0 1 7.3757-3.4537l-.142.0805L8.704 5.459a.7948.7948 0 0 0-.3927.6813zm1.0976-2.3654l2.602-1.4998 2.6069 1.4998v2.9994l-2.5974 1.4997-2.6067-1.4997Z" fill="currentColor"/>',opencode:'<path d="M21.6 24H2.4V0h19.2zM16.8 4.8H7.2v14.4h9.6z" fill="currentColor"/><path d="M16.8 9.6H7.2v9.6h9.6z" fill="currentColor" opacity=".45"/>',gemini:'<path d="M11.04 19.32Q12 21.51 12 24q0-2.49.93-4.68.96-2.19 2.58-3.81t3.81-2.55Q21.51 12 24 12q-2.49 0-4.68-.93a12.3 12.3 0 0 1-3.81-2.58 12.3 12.3 0 0 1-2.58-3.81Q12 2.49 12 0q0 2.49-.96 4.68-.93 2.19-2.55 3.81a12.3 12.3 0 0 1-3.81 2.58Q2.49 12 0 12q2.49 0 4.68.96 2.19.93 3.81 2.55t2.55 3.81" fill="currentColor"/>',agy:'<path d="M12.48 10.92v3.28h7.84c-.24 1.84-.853 3.187-1.787 4.133-1.147 1.147-2.933 2.4-6.053 2.4-4.827 0-8.6-3.893-8.6-8.72s3.773-8.72 8.6-8.72c2.6 0 4.507 1.027 5.907 2.347l2.307-2.307C18.747 1.44 16.133 0 12.48 0 5.867 0 .307 5.387.307 12s5.56 12 12.173 12c3.573 0 6.267-1.173 8.373-3.36 2.16-2.16 2.84-5.213 2.84-7.667 0-.76-.053-1.467-.173-2.053H12.48z" fill="currentColor"/>',grok:'<g transform="translate(1.2 1.2) scale(.9)"><path d="M6.469 8.776 16.512 23h-4.464L2.005 8.776H6.47zm-.004 7.9 2.233 3.164L6.467 23H2l4.465-6.324zM22 2.582V23h-3.659V7.764L22 2.582zM22 1l-9.952 14.095-2.233-3.163L17.533 1H22z" fill="currentColor"/></g>',muse:'<g transform="translate(-1.2 -1.2) scale(1.1)"><path d="M6.915 4.03c-1.968 0-3.683 1.28-4.871 3.113C.704 9.208 0 11.883 0 14.449c0 .706.07 1.369.21 1.973a6.624 6.624 0 0 0 .265.86 5.297 5.297 0 0 0 .371.761c.696 1.159 1.818 1.927 3.593 1.927 1.497 0 2.633-.671 3.965-2.444.76-1.012 1.144-1.626 2.663-4.32l.756-1.339.186-.325c.061.1.121.196.183.3l2.152 3.595c.724 1.21 1.665 2.556 2.47 3.314 1.046.987 1.992 1.22 3.06 1.22 1.075 0 1.876-.355 2.455-.843a3.743 3.743 0 0 0 .81-.973c.542-.939.861-2.127.861-3.745 0-2.72-.681-5.357-2.084-7.45-1.282-1.912-2.957-2.93-4.716-2.93-1.047 0-2.088.467-3.053 1.308-.652.57-1.257 1.29-1.82 2.05-.69-.875-1.335-1.547-1.958-2.056-1.182-.966-2.315-1.303-3.454-1.303zm10.16 2.053c1.147 0 2.188.758 2.992 1.999 1.132 1.748 1.647 4.195 1.647 6.4 0 1.548-.368 2.9-1.839 2.9-.58 0-1.027-.23-1.664-1.004-.496-.601-1.343-1.878-2.832-4.358l-.617-1.028a44.908 44.908 0 0 0-1.255-1.98c.07-.109.141-.224.211-.327 1.12-1.667 2.118-2.602 3.358-2.602zm-10.201.553c1.265 0 2.058.791 2.675 1.446.307.327.737.871 1.234 1.579l-1.02 1.566c-.757 1.163-1.882 3.017-2.837 4.338-1.191 1.649-1.81 1.817-2.486 1.817-.524 0-1.038-.237-1.383-.794-.263-.426-.464-1.13-.464-2.046 0-2.221.63-4.535 1.66-6.088.454-.687.964-1.226 1.533-1.533a2.264 2.264 0 0 1 1.088-.285z" fill="currentColor"/></g>',pi:'<path d="M0 0h18v6H0zM0 6h6v18H0zM12 6h6v6h-6zM6 12h6v6H6zM18 12h6v12h-6z" fill="currentColor"/>',t3code:'<path d="M5.177,18.414V7.823H1V5.362H12.374V7.823H8.197V18.414H5.177ZM17.593,18.638C16.686,18.638 15.784,18.52 14.889,18.283C13.994,18.035 13.236,17.687 12.615,17.239L13.789,14.927C14.287,15.288 14.865,15.574 15.523,15.785C16.182,15.996 16.847,16.102 17.518,16.102C18.277,16.102 18.873,15.953 19.308,15.654C19.743,15.356 19.961,14.946 19.961,14.424C19.961,13.927 19.768,13.535 19.383,13.249C18.998,12.963 18.376,12.82 17.518,12.82H16.139V10.825L19.774,6.705L20.11,7.786H13.267V5.362H22.403V7.32L18.786,11.441L17.257,10.564H18.134C19.737,10.564 20.949,10.925 21.769,11.646C22.59,12.367 23,13.293 23,14.424C23,15.157 22.807,15.847 22.422,16.494C22.037,17.127 21.446,17.643 20.651,18.041C19.855,18.439 18.836,18.638 17.593,18.638Z" fill="currentColor"/>',generic:'<g transform="scale(1.5)"><rect x="1.75" y="2.75" width="12.5" height="10.5" rx="1.5" fill="none" stroke="currentColor" stroke-width="1.4"/><path d="M4.5 6.25 6.5 8l-2 1.75M8 10h3" fill="none" stroke="currentColor" stroke-width="1.3" stroke-linecap="round" stroke-linejoin="round"/></g>'},yt={"claude-code":"claude","claude-desktop":"claude","claude-cowork":"claude","claude-desktop-chat":"claude","codex-desktop":"codex",claude:"claude",codex:"codex",opencode:"opencode",gemini:"gemini",agy:"agy",antigravity:"agy",grok:"grok",muse:"muse",xai:"grok",pi:"pi",t3code:"t3code",t3:"t3code"},J=(e)=>yt[(e??"").toLowerCase()]??"generic",q=(e)=>{let t=J(e);return r`<svg class="hicon h-${t}" width="12" height="12" viewBox="0 0 24 24" role="img" aria-label="${e??"unknown harness"}">${new k(de[t])}</svg>`},vt=r`<svg width="8" height="8" viewBox="0 0 8 8" aria-hidden="true"><path d="M2 .75 6.5 4 2 7.25z" fill="currentColor"/></svg>`,V=(e)=>e.startsWith(ne)?"(ended)":e,S=(e)=>V(E(e).handle),_t=(e)=>Object.entries(e).filter(([t])=>t!=="machine"),Oe={online:0,idle:1,hibernated:1,offline:2},oe=(e,t)=>(Oe[e.presence]??3)-(Oe[t.presence]??3)||E(e.address).handle.localeCompare(E(t.address).handle);function xe(e){let t=new Set(e.map((a)=>a.address)),n=new Map,o=[];for(let a of e)if(a.parent&&a.parent!==a.address&&t.has(a.parent))n.set(a.parent,[...n.get(a.parent)??[],a]);else o.push(a);let i=[],s=new Set,l=(a,c)=>{for(let p of a.sort(oe)){if(s.has(p.address))continue;s.add(p.address),i.push({e:p,depth:c.length,anc:c,kids:0}),l(n.get(p.address)??[],[...c,p.address])}};l(o,[]),l(e.filter((a)=>!s.has(a.address)),[]);let d=new Map(i.map((a)=>[a.e.address,a]));for(let a of i)if(a.anc.length)d.get(a.anc.at(-1)).kids++;return i}var ae=(e)=>e.presence!=="offline";function kt(e){let t=e.filter((a)=>a.kind==="agent"),n=new Set(t.map((a)=>a.address)),o=new Map,i=[];for(let a of t)if(a.parent&&a.parent!==a.address&&n.has(a.parent))o.set(a.parent,[...o.get(a.parent)??[],a]);else i.push(a);let s=new Set,l=(a)=>{s.add(a.address);let c=[],p=[...o.get(a.address)??[]];while(p.length){let b=p.pop();if(s.has(b.address))continue;s.add(b.address),c.push(b),p.push(...o.get(b.address)??[])}return{root:a,kids:c.filter(ae).sort(oe)}},d=i.map(l);for(let a of t)if(!s.has(a.address))d.push(l(a));return d.filter((a)=>ae(a.root)||a.kids.length).sort((a,c)=>c.kids.length-a.kids.length||oe(a.root,c.root))}var Rt=3,le=(e)=>r`<span class="cell ${e.presence}${x(e)?" wake":""}" title="${S(e.address)}, ${e.presence}"></span>`;function De({root:e,kids:t}){let{machine:n}=E(e.address);return r`<div class="strip-row"><span class="strip-who" title="${e.address}">${q(e.harness)}<span class="mono strip-name">${S(e.address)}</span><span class="mini">${n}</span></span>
306
+ <span class="cells">${ae(e)?le(e):""}${t.map(le)}</span>${t.length?r`<span class="mini push">${t.length}</span>`:""}</div>`}function St(e){let t=e.filter((n)=>!n.kids.length);if(t.length<=Rt)return e.map(De);return[...e.filter((n)=>n.kids.length).map(De),r`<div class="strip-row"><span class="strip-who"><span class="mono strip-name">${t.length} solo agents</span></span>
307
+ <span class="cells">${t.map((n)=>le(n.root))}</span></div>`]}function Me(e,t){return r`<div class="strip" aria-label="Agents per orchestrator">${St(kt(e.endpoints))}</div><p class="mini strip-key"><span class="cell online"></span>online <span class="cell idle wake"></span>wakeable <span class="cell idle"></span>next turn <a class="mini-link push" href="/orgs/${t}/graph">Graph</a></p>`}function Tt({e,depth:t,anc:n,kids:o},i,s,l){let d=S(e.address),{machine:a}=E(e.address),c=e.parent?S(e.parent):null,p=_t(e.labels),b=e.status?e.status.note?`${e.status.state}: ${e.status.note}`:e.status.state:"none",st=e.presence==="online"?"now":O(e.last_seen,s),ve=wt(e),it=o?r`<button class="caret" type="button" data-tree="${e.address}" aria-expanded="${String(!i.has(e.address))}" aria-label="${o} under ${d}">${vt}</button>`:r`<span class="caret" aria-hidden="true"></span>`,rt=[d,e.harness??"unknown harness",c?`sub of ${c}`:""].filter(Boolean).join(", ");return r`<tr${t?r` class="sub d${Math.min(t,4)}" data-anc="${n.join(" ")}"${n.some((C)=>i.has(C))?" hidden":""}`:""}>
308
+ <td class="agent" title="${rt}">${it}${q(e.harness)}<span class="mono">${d}</span>${a!==l?r` <span class="tag" title="runs on ${a}">${a}</span>`:""}${c&&!t?r` <span class="mini">sub of ${c}</span>`:""}</td>
309
309
  <td class="small" title="${e.title??""}">${e.title??""}</td>
310
310
  <td title="${ve.tip}">${ve.cell}</td>
311
- <td title="${w}">${e.status?r`<span class="role">${e.status.state}</span>${e.status.note?r` <span class="small">${e.status.note}</span>`:""}`:r`<span class="mini">none</span>`}</td>
311
+ <td title="${b}">${e.status?r`<span class="role">${e.status.state}</span>${e.status.note?r` <span class="small">${e.status.note}</span>`:""}`:r`<span class="mini">none</span>`}</td>
312
312
  <td title="${p.map(([C,Q])=>`${C}=${Q}`).join(", ")}">${p.map(([C,Q])=>r`<span class="tag">${C}=${Q}</span>`)}</td>
313
- <td class="mono end" title="${e.last_seen??"never"}">${nt}</td>
314
- </tr>`}var Tt=r`<colgroup><col class="c-agent"><col class="c-title"><col class="c-presence"><col class="c-status"><col class="c-labels"><col class="c-seen"></colgroup>`,De=(e,t,n,o)=>{let i=new Set(e.filter((s)=>s.kids&&s.e.presence==="offline").map((s)=>s.e.address));return r`<div class="table"><table class="agents">${Tt}${n?r`<thead><tr><th>Agent</th><th>Title</th><th>Presence</th><th>Status</th><th>Labels</th><th class="end">Seen</th></tr></thead>`:""}<tbody>${e.map((s)=>St(s,i,t,o))}</tbody></table></div>`};function Nt(e,t){let n=e.machine,o=n?n.online?"online":"offline":e.endpoints.some((l)=>l.presence==="online")?"online":"offline",i=xe(e.endpoints.filter((l)=>l.presence!=="offline")),s=xe(e.endpoints.filter((l)=>l.presence==="offline"));return r`<div class="machine">
313
+ <td class="mono end" title="${e.last_seen??"never"}">${st}</td>
314
+ </tr>`}var Nt=r`<colgroup><col class="c-agent"><col class="c-title"><col class="c-presence"><col class="c-status"><col class="c-labels"><col class="c-seen"></colgroup>`,Ie=(e,t,n,o)=>{let i=new Set(e.filter((s)=>s.kids&&s.e.presence==="offline").map((s)=>s.e.address));return r`<div class="table"><table class="agents">${Nt}${n?r`<thead><tr><th>Agent</th><th>Title</th><th>Presence</th><th>Status</th><th>Labels</th><th class="end">Seen</th></tr></thead>`:""}<tbody>${e.map((s)=>Tt(s,i,t,o))}</tbody></table></div>`};function At(e,t){let n=e.machine,o=n?n.online?"online":"offline":e.endpoints.some((l)=>l.presence==="online")?"online":"offline",i=xe(e.endpoints.filter((l)=>l.presence!=="offline")),s=xe(e.endpoints.filter((l)=>l.presence==="offline"));return r`<div class="machine">
315
315
  <div class="machine-head">
316
316
  <span class="dot ${o}" aria-hidden="true"></span><h3 class="mono">${e.name}</h3>
317
317
  ${n?r`<span class="role">${n.kind}</span>`:""}
318
- <span class="mini">${o==="online"?"online":`seen ${x(n?.last_seen??null,t)}`}</span>
318
+ <span class="mini">${o==="online"?"online":`seen ${O(n?.last_seen??null,t)}`}</span>
319
319
  <span class="mini push">${e.endpoints.length} ${e.endpoints.length===1?"agent":"agents"}</span>
320
320
  </div>
321
- ${e.endpoints.length?r`${i.length?De(i,t,!0,e.name):""}${s.length?r`<details class="offline" data-key="${e.name}"><summary class="mini"><span class="when-shut">show</span><span class="when-open">hide</span> ${s.length} offline</summary>${De(s,t,!i.length,e.name)}</details>`:""}`:r`<p class="empty">${e.away?`Its ${e.away===1?"agent shows":"agents show"} under a parent on another machine.`:"No agents on this machine right now."}</p>`}
321
+ ${e.endpoints.length?r`${i.length?Ie(i,t,!0,e.name):""}${s.length?r`<details class="offline" data-key="${e.name}"><summary class="mini"><span class="when-shut">show</span><span class="when-open">hide</span> ${s.length} offline</summary>${Ie(s,t,!i.length,e.name)}</details>`:""}`:r`<p class="empty">${e.away?`Its ${e.away===1?"agent shows":"agents show"} under a parent on another machine.`:"No agents on this machine right now."}</p>`}
322
322
  </div>`}function z(e){switch(e.kind){case"session_start":return"started a session";case"session_end":return"ended its session";case"stop":return e.summary?`finished a turn: ${e.summary}`:"finished a turn";case"tool":return[e.tool,e.summary].filter(Boolean).join(": ")||"used a tool";default:return e.summary??e.kind}}var ce=(e)=>[...e].sort((t,n)=>n.at.localeCompare(t.at));function pe(e,t){return r`<section class="block"><div class="block-head"><h2>Recent activity</h2>${Y(t)}</div>
323
323
  ${e.length?r`<div class="table"><table class="activity"><colgroup><col class="c-when"><col class="c-who"><col></colgroup><thead><tr><th>When</th><th>Agent</th><th>What</th></tr></thead>
324
- <tbody>${ce(e).map((n)=>{let{machine:o,handle:i}=E(n.endpoint),s=`${o}/${V(i)}`,l=z(n);return r`<tr><td class="mono" title="${n.at}">${x(n.at,t)}</td><td class="mono" title="${n.agent?`${s} ${n.agent}`:s}">${s}${n.agent?r` <span class="mini">${n.agent}</span>`:""}</td><td class="small" title="${l}">${l}</td></tr>`})}</tbody></table></div>`:r`<p class="empty">No activity yet. Hooks report tool use and turns as agents work.</p>`}</section>`}var j=(e)=>r`<div class="cmd"><code><span class="prompt" aria-hidden="true">$ </span>${e}</code><button class="btn btn-ghost btn-small" type="button" data-copy="${e}">Copy</button></div>`,Y=(e)=>r`<span class="mini push live-stamp"><span class="dot online pulse" aria-hidden="true"></span><span data-feed>polling</span>, updated ${new Date(e).toISOString().slice(11,19)} UTC</span>`;function ue(e){return r`<section class="block onboard"><h2>Connect your first machine</h2>
324
+ <tbody>${ce(e).map((n)=>{let{machine:o,handle:i}=E(n.endpoint),s=`${o}/${V(i)}`,l=z(n);return r`<tr><td class="mono" title="${n.at}">${O(n.at,t)}</td><td class="mono" title="${n.agent?`${s} ${n.agent}`:s}">${s}${n.agent?r` <span class="mini">${n.agent}</span>`:""}</td><td class="small" title="${l}">${l}</td></tr>`})}</tbody></table></div>`:r`<p class="empty">No activity yet. Hooks report tool use and turns as agents work.</p>`}</section>`}var j=(e)=>r`<div class="cmd"><code><span class="prompt" aria-hidden="true">$ </span>${e}</code><button class="btn btn-ghost btn-small" type="button" data-copy="${e}">Copy</button></div>`,Y=(e)=>r`<span class="mini push live-stamp"><span class="dot online pulse" aria-hidden="true"></span><span data-feed>polling</span>, updated ${new Date(e).toISOString().slice(11,19)} UTC</span>`;function ue(e){return r`<section class="block onboard"><h2>Connect your first machine</h2>
325
325
  <p class="small">No machines have joined this hub yet. Run these on the machine your agents work on. The page updates on its own once it connects.</p>
326
326
  <ol class="steps">
327
327
  <li><p class="label">Install the CLI</p>${j("npm i -g @scp-dev/cli")}</li>
@@ -337,7 +337,7 @@ table.orgs td.end,table.orgs th.end{text-align:right}
337
337
  </section>`}var P=()=>v("The hub did not answer. This page retries on its own.",!0)||r``;function he(e,t,n){if(!e)return P();let o=X(e,!0);if(!o.length)return ue(t);let i=o.reduce((l,d)=>l+d.endpoints.length,0),s=o.filter((l)=>l.machine?.online||l.endpoints.some((d)=>d.presence==="online")).length;return r`<section class="block"><div class="block-head"><h2>Machines</h2>
338
338
  <span class="mini">${o.length} ${o.length===1?"machine":"machines"}, ${s} online, ${i} ${i===1?"agent":"agents"}</span>
339
339
  ${Y(n)}</div>
340
- ${o.map((l)=>Nt(l,n))}
340
+ ${o.map((l)=>At(l,n))}
341
341
  </section>`}var K=`
342
342
  const chosen = new Map();
343
343
  const fold = (root) => {
@@ -418,30 +418,30 @@ if (live) {
418
418
  }
419
419
  const tab = document.querySelector('.tabs [aria-current="page"]');
420
420
  if (tab) tab.scrollIntoView({ block: "nearest", inline: "nearest" });
421
- `;var Xs=7*U;var $e=[["overview","Overview",""],["activity","Activity","/activity"],["sessions","Sessions","/sessions"],["graph","Graph","/graph"],["tokens","Tokens and devices","/tokens"],["members","Members","/members"]],It=(e,t,n)=>r`<nav class="tabs" aria-label="${e}">${t.map(([o,i])=>r`<a href="${o}"${o===n?r` aria-current="page"`:""}>${i}</a>`)}</nav>`;function ge(e,t){let n=`/orgs/${e.org.handle}`;return r`<div class="org-head">
421
+ `;var Ys=7*U;var $e=[["overview","Overview",""],["activity","Activity","/activity"],["sessions","Sessions","/sessions"],["graph","Graph","/graph"],["tokens","Tokens and devices","/tokens"],["members","Members","/members"]],Mt=(e,t,n)=>r`<nav class="tabs" aria-label="${e}">${t.map(([o,i])=>r`<a href="${o}"${o===n?r` aria-current="page"`:""}>${i}</a>`)}</nav>`;function ge(e,t){let n=`/orgs/${e.org.handle}`;return r`<div class="org-head">
422
422
  <p class="label"><a class="mini-link" href="/orgs">Orgs</a> / ${e.org.handle}</p>
423
423
  <div class="org-title"><h1>${e.org.name}</h1><span class="mini">you are <span class="role">${e.role}</span></span></div>
424
424
  </div>
425
- ${It("Organization",$e.map(([,o,i])=>[n+i,o]),n+$e.find(([o])=>o===t)[2])}`}function Ce(e,t,n,o={}){let i=r`<script src="/app.js?v=${R(K)}" defer></script>`,s=o.live?r`${i}<noscript><meta http-equiv="refresh" content="${Math.round(L/1000)}"></noscript>`:i,l=o.live?r`<div class="tab-body" data-live="${L}" data-events="/orgs/${e.org.handle}/events">${n}</div>`:r`<div class="tab-body">${n}</div>`;return{head:s,nav:"orgs",body:r`${ge(e,t)}${l}`}}var v=(e,t=!1)=>e?r`<p class="notice${t?" err":""}" role="${t?"alert":"status"}">${e}</p>`:"";function Ct(e,t){let n=e.endpoints.filter((s)=>s.kind==="agent"),o=n.filter((s)=>s.presence==="idle"),i=X(e);return{online:n.filter((s)=>s.presence==="online").length,wakeable:o.filter((s)=>O(s)).length,nextTurn:o.filter((s)=>!O(s)).length,offline:n.filter((s)=>s.presence==="offline").length,machines:i.length,machinesOnline:i.filter((s)=>s.machine?.online||s.endpoints.some((l)=>l.presence==="online")).length,messages:t?t.edges.filter((s)=>s.kind==="message").reduce((s,l)=>s+(l.count??0),0):null,minutes:t?.minutes??15}}var W=(e,t,n,o="")=>r`<div class="tile${o?` ${o}`:""}"><p class="tile-label">${e}</p><p class="tile-value">${t}</p><p class="mini">${n}</p></div>`;function Ut(e){return r`<div class="tiles">
425
+ ${Mt("Organization",$e.map(([,o,i])=>[n+i,o]),n+$e.find(([o])=>o===t)[2])}`}function Ue(e,t,n,o={}){let i=r`<script src="/app.js?v=${R(K)}" defer></script>`,s=o.live?r`${i}<noscript><meta http-equiv="refresh" content="${Math.round(L/1000)}"></noscript>`:i,l=o.live?r`<div class="tab-body" data-live="${L}" data-events="/orgs/${e.org.handle}/events">${n}</div>`:r`<div class="tab-body">${n}</div>`;return{head:s,nav:"orgs",body:r`${ge(e,t)}${l}`}}var v=(e,t=!1)=>e?r`<p class="notice${t?" err":""}" role="${t?"alert":"status"}">${e}</p>`:"";function Ut(e,t){let n=e.endpoints.filter((s)=>s.kind==="agent"),o=n.filter((s)=>s.presence==="idle"),i=X(e);return{online:n.filter((s)=>s.presence==="online").length,wakeable:o.filter((s)=>x(s)).length,nextTurn:o.filter((s)=>!x(s)).length,offline:n.filter((s)=>s.presence==="offline").length,machines:i.length,machinesOnline:i.filter((s)=>s.machine?.online||s.endpoints.some((l)=>l.presence==="online")).length,messages:t?t.edges.filter((s)=>s.kind==="message").reduce((s,l)=>s+(l.count??0),0):null,minutes:t?.minutes??15}}var W=(e,t,n,o="")=>r`<div class="tile${o?` ${o}`:""}"><p class="tile-label">${e}</p><p class="tile-value">${t}</p><p class="mini">${n}</p></div>`;function Ht(e){return r`<div class="tiles">
426
426
  ${W("Online",e.online,e.online===1?"agent working":"agents working","on")}
427
427
  ${W("Idle, wakeable",e.wakeable,"a message wakes them")}
428
428
  ${W("Idle, next turn",e.nextTurn,"read at their next turn")}
429
429
  ${W("Machines",e.machines,`${e.machinesOnline} online`)}
430
430
  ${W("Messages",e.messages??"-",`last ${e.minutes} min`)}
431
- </div>`}function Ht(e,t){if(e.status)return{lead:r`<span class="role">${e.status.state}</span>`,text:e.status.note??""};let n=t.get(e.address);if(n?.summary)return{lead:r`<span class="mini">tl;dr</span>`,text:n.summary};if(e.title)return{lead:"",text:e.title};return null}var me=6,He={online:0,idle:1,hibernated:1,offline:2};function qt(e,t,n){let o=new Map,i=ce(e.activity);for(let a of i)if(a.kind==="stop"&&a.summary&&!o.has(a.endpoint))o.set(a.endpoint,a);let s=e.endpoints.filter((a)=>a.kind==="agent"&&a.presence!=="offline").map((a)=>({e:a,g:Ht(a,o)})).sort((a,c)=>(He[a.e.presence]??3)-(He[c.e.presence]??3)||Number(!a.g)-Number(!c.g)||(c.e.last_seen??"").localeCompare(a.e.last_seen??"")),l=s.slice(0,me),d=i[0];return r`<section class="panel now"><div class="panel-head"><h2>Happening now</h2><a class="mini-link push" href="/orgs/${n}/sessions">All sessions</a></div>
432
- ${l.length?r`<ul class="now-list">${l.map(({e:a,g:c})=>{let p=O(a);return r`<li><div class="now-who">${q(a.harness)}<span class="mono">${S(a.address)}</span><span class="mini">${E(a.address).machine}</span><span class="push mini"><span class="dot ${a.presence}${p?" wake":""}" aria-hidden="true"></span>${a.presence}</span></div>
431
+ </div>`}function qt(e,t){if(e.status)return{lead:r`<span class="role">${e.status.state}</span>`,text:e.status.note??""};let n=t.get(e.address);if(n?.summary)return{lead:r`<span class="mini">tl;dr</span>`,text:n.summary};if(e.title)return{lead:"",text:e.title};return null}var me=6,qe={online:0,idle:1,hibernated:1,offline:2};function Pt(e,t,n){let o=new Map,i=ce(e.activity);for(let a of i)if(a.kind==="stop"&&a.summary&&!o.has(a.endpoint))o.set(a.endpoint,a);let s=e.endpoints.filter((a)=>a.kind==="agent"&&a.presence!=="offline").map((a)=>({e:a,g:qt(a,o)})).sort((a,c)=>(qe[a.e.presence]??3)-(qe[c.e.presence]??3)||Number(!a.g)-Number(!c.g)||(c.e.last_seen??"").localeCompare(a.e.last_seen??"")),l=s.slice(0,me),d=i[0];return r`<section class="panel now"><div class="panel-head"><h2>Happening now</h2><a class="mini-link push" href="/orgs/${n}/sessions">All sessions</a></div>
432
+ ${l.length?r`<ul class="now-list">${l.map(({e:a,g:c})=>{let p=x(a);return r`<li><div class="now-who">${q(a.harness)}<span class="mono">${S(a.address)}</span><span class="mini">${E(a.address).machine}</span><span class="push mini"><span class="dot ${a.presence}${p?" wake":""}" aria-hidden="true"></span>${a.presence}</span></div>
433
433
  <p class="now-what small" title="${c?.text??""}">${c?r`${c.lead}${c.lead&&c.text?" ":""}${c.text}`:r`<span class="mini">no status yet</span>`}</p></li>`})}</ul>${s.length>me?r`<p class="mini more-line">and ${s.length-me} more on the <a class="mini-link" href="/orgs/${n}/sessions">Sessions</a> tab</p>`:""}`:r`<p class="empty">No agent is online or idle right now.</p>`}
434
- ${d?r`<p class="mini last-line"><span class="what" title="${z(d)}">Last activity ${x(d.at,t)}: ${S(d.endpoint)}, ${z(d)}</span><a class="mini-link" href="/orgs/${n}/activity">All activity</a></p>`:""}
435
- </section>`}var B=(e,t,n=`${t}s`)=>`${e} ${e===1?t:n}`;function qe(e,t,n,o,i){if(!e)return P();if(!e.machines.length&&!e.endpoints.length)return ue(o);let s=Ct(e,t),l=n.holds?r`<p class="notice held" role="status">${B(n.holds,"message")} held by the relay judge, waiting for review. <a href="/orgs/${o}/members#judge">Review</a></p>`:"";return r`<div class="block-head summary-head"><p class="mini">${B(s.online+s.wakeable+s.nextTurn,"agent")} live, ${s.offline} offline</p>${Y(i)}</div>
434
+ ${d?r`<p class="mini last-line"><span class="what" title="${z(d)}">Last activity ${O(d.at,t)}: ${S(d.endpoint)}, ${z(d)}</span><a class="mini-link" href="/orgs/${n}/activity">All activity</a></p>`:""}
435
+ </section>`}var F=(e,t,n=`${t}s`)=>`${e} ${e===1?t:n}`;function Pe(e,t,n,o,i){if(!e)return P();if(!e.machines.length&&!e.endpoints.length)return ue(o);let s=Ut(e,t),l=n.holds?r`<p class="notice held" role="status">${F(n.holds,"message")} held by the relay judge, waiting for review. <a href="/orgs/${o}/members#judge">Review</a></p>`:"";return r`<div class="block-head summary-head"><p class="mini">${F(s.online+s.wakeable+s.nextTurn,"agent")} live, ${s.offline} offline</p>${Y(i)}</div>
436
436
  ${l}
437
- ${Ut(s)}
437
+ ${Ht(s)}
438
438
  <div class="panels">
439
- ${qt(e,i,o)}
439
+ ${Pt(e,i,o)}
440
440
  <section class="panel"><div class="panel-head"><h2>Fleet</h2></div>
441
- ${Ie(e,o)}
442
- <p class="small fleet-line">${B(s.machines,"machine")}, ${s.machinesOnline} online. ${B(n.devices,"device")} and ${B(n.tokens,"API token")} signed in. <a class="mini-link" href="/orgs/${o}/tokens">Manage</a></p>
441
+ ${Me(e,o)}
442
+ <p class="small fleet-line">${F(s.machines,"machine")}, ${s.machinesOnline} online. ${F(n.devices,"device")} and ${F(n.tokens,"API token")} signed in. <a class="mini-link" href="/orgs/${o}/tokens">Manage</a></p>
443
443
  </section>
444
- </div>`}var Pt=["claude","codex","opencode","gemini","agy","grok","muse","pi","t3code"],Wt='<g transform="scale(1.5)"><circle cx="8" cy="5.25" r="2.75" fill="none" stroke="currentColor" stroke-width="1.4"/><path d="M2.75 14c.5-3 2.6-4.75 5.25-4.75S12.75 11 13.25 14" fill="none" stroke="currentColor" stroke-width="1.4" stroke-linecap="round"/></g>',Bt='<path d="M3 3h10v4H7v6H3zM29 29H19v-4h6v-6h4z"/><rect x="11" y="11" width="10" height="10"/>';function Pe(e){let t=e.split("/");return t.length>=3?{machine:t[1],handle:t.slice(2).join("/")}:{machine:"",handle:""}}function Ft(e,t){if(!t||e.length<=t.length+1||!e.startsWith(t))return e;return"-_./:".includes(e[t.length])?e.slice(t.length+1):e}function We(e){if(!e)return r``;let t=e.nodes.map((i)=>{let{machine:s,handle:l}=Pe(i.address),d=i.kind==="human"?"human":V(l),a=i.parent?Pe(i.parent):null;return{address:i.address,kind:i.kind,machine:s,name:d,label:d===l&&a&&a.machine===s?Ft(l,a.handle):d,icon:i.kind==="human"?"human":J(i.harness),harness:i.harness,title:i.title,presence:i.presence,status:i.status,wake:i.wake&&i.wake!=="none"?i.wake:null,how:i.waking?"its machine is waking":i.wake&&i.wake!=="none"?G(i.wake):null,parent:i.parent}}),n=e.machines.filter((i)=>i.kind!=="revoked").map((i)=>({name:i.name,online:i.online,identity:i.identity??null})),o=JSON.stringify({minutes:e.minutes,machines:n,nodes:t,edges:e.edges}).replace(/</g,"\\u003c");return r`<script type="application/json" data-graph-data>${new k(o)}</script>`}var Be=r`<div class="graph-canvas"><svg class="graph-svg" role="group" aria-label="Agents by machine"></svg><div class="graph-tip" role="tooltip" hidden></div></div>`,Fe=r`<span class="trails" role="group" aria-label="Message trail speed">trails <span class="seg">${["1","0.5","0.25"].map((e)=>r`<button type="button" data-trail="${e}" aria-pressed="${e==="1"?"true":"false"}">${e}x</button>`)}</span></span>`,je=r`<label class="mini"><input type="checkbox" data-graph-nextturn> show next-turn idle</label><label class="mini"><input type="checkbox" data-graph-offline> show offline</label>`,Ge=r`<p class="mini graph-legend"><span class="key spawn" aria-hidden="true"></span>spawned <span class="key msg" aria-hidden="true"></span>messages, last <span data-graph-minutes>15</span> min</p>`,fe=()=>r`<script src="/app.js?v=${R(K)}" defer></script><script src="/graph.js?v=${R(Yt())}" defer></script><link rel="stylesheet" href="/graph.css?v=${R(jt)}">`;function Xe(e,t){return r`<div class="graph gfull" data-graph data-full data-events="/orgs/${e.handle}/events" aria-label="Live graph of agents and their links">
444
+ </div>`}var Wt=["claude","codex","opencode","gemini","agy","grok","muse","pi","t3code"],Ft='<g transform="scale(1.5)"><circle cx="8" cy="5.25" r="2.75" fill="none" stroke="currentColor" stroke-width="1.4"/><path d="M2.75 14c.5-3 2.6-4.75 5.25-4.75S12.75 11 13.25 14" fill="none" stroke="currentColor" stroke-width="1.4" stroke-linecap="round"/></g>',Bt='<path d="M3 3h10v4H7v6H3zM29 29H19v-4h6v-6h4z"/><rect x="11" y="11" width="10" height="10"/>';function We(e){let t=e.split("/");return t.length>=3?{machine:t[1],handle:t.slice(2).join("/")}:{machine:"",handle:""}}function jt(e,t){if(!t||e.length<=t.length+1||!e.startsWith(t))return e;return"-_./:".includes(e[t.length])?e.slice(t.length+1):e}function Fe(e){if(!e)return r``;let t=e.nodes.map((i)=>{let{machine:s,handle:l}=We(i.address),d=i.kind==="human"?"human":V(l),a=i.parent?We(i.parent):null;return{address:i.address,kind:i.kind,machine:s,name:d,label:d===l&&a&&a.machine===s?jt(l,a.handle):d,icon:i.kind==="human"?"human":J(i.harness),harness:i.harness,title:i.title,presence:i.presence,status:i.status,wake:i.wake&&i.wake!=="none"?i.wake:null,how:i.waking?"its machine is waking":i.wake&&i.wake!=="none"?G(i.wake):null,parent:i.parent,historic:i.historic===!0}}),n=e.machines.filter((i)=>i.kind!=="revoked").map((i)=>({name:i.name,online:i.online,identity:i.identity??null})),o=JSON.stringify({minutes:e.minutes,machines:n,nodes:t,edges:e.edges}).replace(/</g,"\\u003c");return r`<script type="application/json" data-graph-data>${new k(o)}</script>`}var Be=r`<div class="graph-canvas"><svg class="graph-svg" role="group" aria-label="Agents by machine"></svg><div class="graph-tip" role="tooltip" hidden></div></div>`,je=r`<span class="trails" role="group" aria-label="Message trail speed">trails <span class="seg">${["1","0.5","0.25"].map((e)=>r`<button type="button" data-trail="${e}" aria-pressed="${e==="1"?"true":"false"}">${e}x</button>`)}</span></span>`,Ge=(e)=>r`<label class="mini"><input type="checkbox" data-graph-nextturn> show next-turn idle</label><label class="mini"><input type="checkbox" data-graph-offline> show offline</label>${e?r`<label class="mini"><input type="checkbox" data-graph-historic> show historic</label>`:""}`,Xe=r`<p class="mini graph-legend"><span class="key spawn" aria-hidden="true"></span>spawned <span class="key msg" aria-hidden="true"></span>messages, last <span data-graph-minutes>15</span> min</p>`,fe=()=>r`<script src="/app.js?v=${R(K)}" defer></script><script src="/graph.js?v=${R(Kt())}" defer></script><link rel="stylesheet" href="/graph.css?v=${R(Gt)}">`;function Je(e,t,n={}){return r`<div class="graph gfull" data-graph data-full data-events="/orgs/${e.handle}/events" aria-label="Live graph of agents and their links">
445
445
  ${Be}
446
446
  <header class="g-over g-left">
447
447
  <a class="mini-link" href="/orgs/${e.handle}/graph">Back to the dashboard</a>
@@ -450,13 +450,13 @@ if (tab) tab.scrollIntoView({ block: "nearest", inline: "nearest" });
450
450
  <p class="mini" data-graph-stream></p>
451
451
  </header>
452
452
  <div class="g-over g-right">
453
- ${Ge}
454
- ${je}
455
- <span class="mini">${Fe}</span>
453
+ ${Xe}
454
+ ${Ge(n.historic===!0)}
455
+ <span class="mini">${je}</span>
456
456
  </div>
457
- <div hidden data-live="${L}">${We(t)}</div>
457
+ <div hidden data-live="${L}">${Fe(t)}</div>
458
458
  ${t?"":r`<p class="g-empty mini">The hub did not answer. This page retries on its own.</p>`}
459
- </div>`}function Je(e,t){return r`${ge(e,"graph")}
459
+ </div>`}function Ve(e,t,n={}){return r`${ge(e,"graph")}
460
460
  <div class="graph gfull gtab" data-graph data-full data-events="/orgs/${e.org.handle}/events" aria-label="Live graph of agents and their links">
461
461
  ${Be}
462
462
  <div class="g-over g-left">
@@ -464,13 +464,13 @@ if (tab) tab.scrollIntoView({ block: "nearest", inline: "nearest" });
464
464
  <p class="mini" data-graph-stream></p>
465
465
  </div>
466
466
  <div class="g-over g-right">
467
- ${Ge}
468
- <span class="g-acts">${je}<a class="mini-link" href="/orgs/${e.org.handle}/graph/full">Full screen</a></span>
469
- <span class="mini">${Fe}</span>
467
+ ${Xe}
468
+ <span class="g-acts">${Ge(n.historic===!0)}<a class="mini-link" href="/orgs/${e.org.handle}/graph/full">Full screen</a></span>
469
+ <span class="mini">${je}</span>
470
470
  </div>
471
- <div hidden data-live="${L}">${We(t)}</div>
471
+ <div hidden data-live="${L}">${Fe(t)}</div>
472
472
  ${t?"":r`<p class="g-empty mini">The hub did not answer. This page retries on its own.</p>`}
473
- </div>`}var jt=`
473
+ </div>`}var Gt=`
474
474
  :root{--id-0:#2f6fdb;--id-1:#c2410c;--id-2:#0f8a6c;--id-3:#a3369e;--id-4:#a16207;--id-5:#6d4fd6;--id-6:#be123c;--id-7:#3f7d20}
475
475
  @media (prefers-color-scheme:dark){:root{--id-0:#7aa9ff;--id-1:#f59a63;--id-2:#3fc6a0;--id-3:#e27ad9;--id-4:#e3b341;--id-5:#a991ff;--id-6:#fb7185;--id-7:#8fd16a}}
476
476
  .g-over label{display:inline-flex;align-items:center;gap:.375rem;cursor:pointer}
@@ -515,6 +515,7 @@ if (tab) tab.scrollIntoView({ block: "nearest", inline: "nearest" });
515
515
  .graph-svg .node.online .disc{stroke:color-mix(in srgb,var(--fg) 34%,transparent)}
516
516
  .graph-svg .node.human .disc{stroke-dasharray:2.5 2}
517
517
  .graph-svg .node.offline{opacity:.5}
518
+ .graph-svg .node.historic{opacity:.3}
518
519
  .graph-svg .node.offline .disc{stroke-dasharray:2 2}
519
520
  .graph-svg .node.enter{opacity:0}
520
521
  .graph-svg .node:hover .disc,.graph-svg .node:focus-visible .disc,.graph-svg .node.on .disc{stroke:var(--fg);stroke-width:1.5}
@@ -554,13 +555,14 @@ body.bare{overflow:hidden;height:100dvh;min-height:0}
554
555
  .g-right{top:1rem;right:1rem;justify-items:end}
555
556
  .g-empty{position:absolute;left:50%;top:50%;transform:translate(-50%,-50%)}
556
557
  @media (max-width:40rem){.g-right{top:auto;bottom:1rem}}
557
- `,Gt=`function drawn(n, o, talks) {
558
+ `,Xt=`function drawn(n, o, talks) {
559
+ if (n.historic) return !!o.historic;
558
560
  if (n.presence === "offline") return o.offline || (n.kind === "human" && talks.has(n.address));
559
561
  if (n.presence === "idle" && n.kind === "agent" && !n.wake) return o.nextTurn;
560
562
  return true;
561
- }`,Xt=`function slot(e, w, h, min) {
563
+ }`,Jt=`function slot(e, w, h, min) {
562
564
  return { x0: e.x0, x1: Math.max(e.x1, e.x0 + w, e.x0 + min), y0: e.y0 - h, y1: e.y1 };
563
- }`,Jt=`function bands(items, o) {
565
+ }`,Vt=`function bands(items, o) {
564
566
  const byKey = new Map();
565
567
  for (const it of items) { if (!byKey.has(it.key)) byKey.set(it.key, { key: it.key, items: [] }); byKey.get(it.key).items.push(it); }
566
568
  const groups = [...byKey.values()];
@@ -597,7 +599,7 @@ body.bare{overflow:hidden;height:100dvh;min-height:0}
597
599
  y += Math.max(...row.groups.map((g) => g.h)) + o.groupGap;
598
600
  });
599
601
  return { places, bands: rects, hub, width, height: Math.max(y - o.groupGap, 1) };
600
- }`,Vt={gap:14,ring:84,reach:160,span:2.4,wide:1.4,sub:1.6,apart:64,aspect:1.8},zt=`function cluster(ns, B, o) {
602
+ }`,zt={gap:14,ring:84,reach:160,span:2.4,wide:1.4,sub:1.6,apart:64,aspect:1.8},Yt=`function cluster(ns, B, o) {
601
603
  const at = new Map(ns.map((n, i) => [n.address, i]));
602
604
  const by = (a, b) => (ns[a].address < ns[b].address ? -1 : ns[a].address > ns[b].address ? 1 : 0);
603
605
  const kids = ns.map(() => []);
@@ -687,29 +689,30 @@ function collide(P, B, mob) {
687
689
  else { const s = (dy >= 0 ? 1 : -1) * (oy + 0.5); P[i].y -= s * mi / sum; P[j].y += s * mj / sum; }
688
690
  }
689
691
  return hit;
690
- }`,Yt=()=>`(() => {
692
+ }`,Kt=()=>`(() => {
691
693
  const root = document.querySelector("[data-graph]");
692
694
  const live = document.querySelector("[data-live]");
693
695
  if (!root || !live) return;
694
696
  const bar = document.querySelector("[data-view-bar]");
695
697
  const full = root.hasAttribute("data-full");
696
698
  const NS = "http://www.w3.org/2000/svg";
697
- const ICONS = ${JSON.stringify({...de,human:Wt})};
699
+ const ICONS = ${JSON.stringify({...de,human:Ft})};
698
700
  const MARK = ${JSON.stringify(Bt)};
699
- const COLORED = new Set(${JSON.stringify(Pt)});
701
+ const COLORED = new Set(${JSON.stringify(Wt)});
700
702
  const BOLT = "M9.5 1 3 9h4.5L6.5 15 13 7H8.5z";
701
703
  const R = 16, PAD = 20, HEAD = 26, ID_LINE = 14, GAP = 26, BAND_GAP = 44, LABEL_MAX = 176, NAME_MAX = 220, ID_MAX = 200, MIN_W = 96, CAP = 1.6;
702
704
  const TRAILS = [1, 0.5, 0.25];
703
- const LAYOUT = ${JSON.stringify(Vt)};
704
- ${Xt}
705
- ${zt}
705
+ const LAYOUT = ${JSON.stringify(zt)};
706
706
  ${Jt}
707
- ${Gt}
707
+ ${Yt}
708
+ ${Vt}
709
+ ${Xt}
708
710
  const svg = root.querySelector(".graph-svg");
709
711
  const canvas = root.querySelector(".graph-canvas");
710
712
  const tip = root.querySelector(".graph-tip");
711
713
  const offline = document.querySelector("[data-graph-offline]");
712
714
  const nextTurn = document.querySelector("[data-graph-nextturn]");
715
+ const historic = document.querySelector("[data-graph-historic]");
713
716
  const reduce = matchMedia("(prefers-reduced-motion: reduce)").matches;
714
717
  const load = (k) => { try { return localStorage.getItem(k); } catch { return null; } };
715
718
  const save = (k, v) => { try { localStorage.setItem(k, v); } catch {} };
@@ -773,7 +776,7 @@ function setView(v) {
773
776
  }
774
777
 
775
778
  function visible(g) {
776
- const o = { offline: !!(offline && offline.checked), nextTurn: !!(nextTurn && nextTurn.checked) };
779
+ const o = { offline: !!(offline && offline.checked), nextTurn: !!(nextTurn && nextTurn.checked), historic: !!(historic && historic.checked) };
777
780
  const talks = new Set();
778
781
  for (const e of g.edges) if (e.kind === "message") { talks.add(e.from); talks.add(e.to); }
779
782
  const nodes = g.nodes.filter((n) => drawn(n, o, talks));
@@ -991,7 +994,8 @@ function paintNode(e) {
991
994
  const n = e.n, c = e.g.classList;
992
995
  for (const p of ["online", "idle", "offline"]) c.toggle(p, n.presence === p);
993
996
  c.toggle("human", n.kind === "human");
994
- e.g.setAttribute("aria-label", n.name + ", " + n.presence + (n.how ? ", " + n.how : ""));
997
+ c.toggle("historic", !!n.historic);
998
+ e.g.setAttribute("aria-label", n.name + ", " + n.presence + (n.historic ? ", historic" : "") + (n.how ? ", " + n.how : ""));
995
999
  if (e.icon !== n.icon) {
996
1000
  e.icon = n.icon;
997
1001
  e.glyph.innerHTML = ICONS[n.icon] || ICONS.generic;
@@ -1280,6 +1284,10 @@ for (const [input, key] of [[offline, "scp.overview.offline"], [nextTurn, "scp.g
1280
1284
  input.checked = load(key) === "1";
1281
1285
  input.addEventListener("change", () => { save(key, input.checked ? "1" : "0"); apply(); });
1282
1286
  }
1287
+ if (historic) {
1288
+ historic.checked = load("scp.graph.historic") === "1";
1289
+ historic.addEventListener("change", () => { save("scp.graph.historic", historic.checked ? "1" : "0"); apply(); refresh(); });
1290
+ }
1283
1291
  let size = "";
1284
1292
  new ResizeObserver(() => { const s = canvas.clientWidth + "x" + (full ? canvas.clientHeight : 0); if (canvas.clientWidth && s !== size) { size = s; apply(); } }).observe(canvas);
1285
1293
  new MutationObserver(update).observe(live, { childList: true });
@@ -1289,7 +1297,7 @@ setStream(false);
1289
1297
  update();
1290
1298
  connect();
1291
1299
  })();
1292
- `;var Ve={overview:"",agents:"/sessions",devices:"/tokens",machines:"/tokens",settings:"/members",judge:"/members"};function ze(e){if(!e?.startsWith("/")||/[\u0000-\u001f\u007f\\]/.test(e))return"/";let t="https://relay.invalid";try{let n=new URL(e,t);return n.origin===t&&n.pathname.replace(/\/+$/,"")!=="/sign-in"?n.pathname+n.search+n.hash:"/"}catch{return"/"}}function Ye(e,t){let n=e.replace(/\/+$/,"")||"/",o=`/orgs/${t.org.handle}`;if(n==="/"||n==="/orgs"||n==="/sign-in")return{redirect:o};if(n==="/device")return{page:"device"};if(n===o)return{tab:"overview"};if(!n.startsWith(`${o}/`))return{page:"not-found"};let i=n.slice(o.length+1);if(Object.hasOwn(Ve,i))return{redirect:o+Ve[i]};if(["activity","sessions","graph","graph/full","tokens","members"].includes(i))return{tab:i};return{page:"not-found"}}function T(e,t,n){return re({...t,email:e.signedIn?e.member.name:null,csrf:e.csrf},n)}function Kt(e,t,n,o){let i=n.graph?F(n.graph,ee(e.members.map((s)=>({user_id:s.id,email:s.name})))):null;switch(t){case"overview":{let s={devices:e.devices,tokens:e.tokens,holds:null};return qe(n.overview,i,s,e.org.handle,o)}case"activity":return n.overview?pe(n.overview.activity,o):P();case"sessions":return he(n.overview,e.org.handle,o);case"tokens":return r`${v("Tokens and devices are managed with the scp-relay token CLI.")}`;case"members":return r`${v("Members are managed with the scp-relay account CLI.")}`;case"graph":return Je({org:e.org,role:e.member.role},i)}}function Ke(e,t,n,o){let i={org:e.org,role:e.member.role},s=t==="overview"?e.org.name:`${e.org.name} ${t==="graph/full"?"graph":t}`;if(t==="graph/full"){let p=n.graph?F(n.graph,ee(e.members.map((w)=>({user_id:w.id,email:w.name})))):null;return T(e,{title:s,bare:!0,head:fe()},Xe(e.org,p))}let l=Kt(e,t,n,o);if(t==="graph")return T(e,{title:s,nav:"orgs",head:fe()},l);let d=t==="overview"||t==="activity"||t==="sessions",{body:a,...c}=Ce(i,t,l,{live:d});return T(e,{...c,title:s},a)}function N(e,t="",n){return T(e,{title:"Sign in",narrow:!0},r`<p class="label">Self-hosted relay</p><h1>Sign in</h1>
1300
+ `;var ze={overview:"",agents:"/sessions",devices:"/tokens",machines:"/tokens",settings:"/members",judge:"/members"};function Ye(e){if(!e?.startsWith("/")||/[\u0000-\u001f\u007f\\]/.test(e))return"/";let t="https://relay.invalid";try{let n=new URL(e,t);return n.origin===t&&n.pathname.replace(/\/+$/,"")!=="/sign-in"?n.pathname+n.search+n.hash:"/"}catch{return"/"}}function Ke(e,t){let n=e.replace(/\/+$/,"")||"/",o=`/orgs/${t.org.handle}`;if(n==="/"||n==="/orgs"||n==="/sign-in")return{redirect:o};if(n==="/device")return{page:"device"};if(n===o)return{tab:"overview"};if(!n.startsWith(`${o}/`))return{page:"not-found"};let i=n.slice(o.length+1);if(Object.hasOwn(ze,i))return{redirect:o+ze[i]};if(["activity","sessions","graph","graph/full","tokens","members"].includes(i))return{tab:i};return{page:"not-found"}}function T(e,t,n){return re({...t,email:e.signedIn?e.member.name:null,csrf:e.csrf},n)}function Zt(e,t,n,o){let i=n.graph?B(n.graph,ee(e.members.map((s)=>({user_id:s.id,email:s.name})))):null;switch(t){case"overview":{let s={devices:e.devices,tokens:e.tokens,holds:null};return Pe(n.overview,i,s,e.org.handle,o)}case"activity":return n.overview?pe(n.overview.activity,o):P();case"sessions":return he(n.overview,e.org.handle,o);case"tokens":return r`${v("Tokens and devices are managed with the scp-relay token CLI.")}`;case"members":return r`${v("Members are managed with the scp-relay account CLI.")}`;case"graph":return Ve({org:e.org,role:e.member.role},i,{historic:!0})}}function Ze(e,t,n,o){let i={org:e.org,role:e.member.role},s=t==="overview"?e.org.name:`${e.org.name} ${t==="graph/full"?"graph":t}`;if(t==="graph/full"){let p=n.graph?B(n.graph,ee(e.members.map((b)=>({user_id:b.id,email:b.name})))):null;return T(e,{title:s,bare:!0,head:fe()},Je(e.org,p,{historic:!0}))}let l=Zt(e,t,n,o);if(t==="graph")return T(e,{title:s,nav:"orgs",head:fe()},l);let d=t==="overview"||t==="activity"||t==="sessions",{body:a,...c}=Ue(i,t,l,{live:d});return T(e,{...c,title:s},a)}function N(e,t="",n){return T(e,{title:"Sign in",narrow:!0},r`<p class="label">Self-hosted relay</p><h1>Sign in</h1>
1293
1301
  <p class="lead">Use a relay token or sign in with a device code.</p>
1294
1302
  ${v(t,!0)}
1295
1303
  <form class="form" data-sign-in method="post" action="/v1/dashboard/sign-in">
@@ -1301,7 +1309,7 @@ connect();
1301
1309
  <input type="hidden" name="csrf" value="${e.csrf}">
1302
1310
  <div class="acts"><button class="btn btn-ghost" type="submit"${n?r` disabled`:""}>Sign in with a device code</button></div>
1303
1311
  </form>
1304
- ${n?r`<section class="block"><h2>Approve this device</h2><p class="small">Run this command on the relay, replacing the account and member placeholders:</p><pre><code>scp-relay device approve ${n.user_code} --account &lt;handle&gt; --member &lt;name&gt;</code></pre><p class="mini" role="status">Waiting for approval.</p></section>`:""}`)}var Ee=(e)=>`'${e.replace(/'/g,"'\\''")}'`;function Ze(e,t="<user_code>"){return T(e,{title:"Device",nav:"device",narrow:!0},r`<p class="label">Device sign-in</p><h1>Approve a device</h1>
1312
+ ${n?r`<section class="block"><h2>Approve this device</h2><p class="small">Run this command on the relay, replacing the account and member placeholders:</p><pre><code>scp-relay device approve ${n.user_code} --account &lt;handle&gt; --member &lt;name&gt;</code></pre><p class="mini" role="status">Waiting for approval.</p></section>`:""}`)}var Ee=(e)=>`'${e.replace(/'/g,"'\\''")}'`;function Qe(e,t="<user_code>"){return T(e,{title:"Device",nav:"device",narrow:!0},r`<p class="label">Device sign-in</p><h1>Approve a device</h1>
1305
1313
  <p class="lead">Approve the code shown on the device by running this command on the relay:</p>
1306
1314
  <pre><code>scp-relay device approve ${Ee(t)} --account ${Ee(e.org.handle)} --member ${Ee(e.member.name)}</code></pre>
1307
- <p class="small">Only approve a code for a device you recognize.</p>`)}function Qe(e){return T(e,{title:"Not found",narrow:!0},r`<h1>Not found</h1><p class="lead">This page does not exist.</p><a href="/">Return home</a>`)}function we(e,t){return T(e,{title:"Dashboard unavailable",narrow:!0},r`<h1>Dashboard unavailable</h1>${v(t,!0)}<a href="">Reload</a>`)}var m={signedIn:!1,csrf:""},D=0,be=!1;class M extends Error{}async function I(e,t){let n=await fetch(`/v1/dashboard/${e}`,{method:t?"POST":"GET",credentials:"same-origin",cache:"no-store",headers:t?{accept:"application/json","content-type":"application/json"}:{accept:"application/json"},...t?{body:JSON.stringify({...t,csrf:m.csrf})}:{}}),o=await n.json();if(!n.ok){let i=o&&typeof o==="object"?o:{};if(n.status===401&&i.error==="signed_out")throw new M;throw Error(typeof i.message==="string"?i.message:i.error==="invalid_token"?"The token is not valid.":"The relay could not complete the request.")}return o}function b(e){let t=new DOMParser().parseFromString(e,"text/html"),n=[...t.querySelectorAll("script[src]")];document.head.replaceWith(t.head),document.body.replaceWith(t.body);for(let o of n){let i=document.createElement("script");for(let s of o.attributes)i.setAttribute(s.name,s.value);i.async=!1,o.replaceWith(i)}}function ye(){location.replace(`/sign-in?next=${encodeURIComponent(location.pathname+location.search)}`)}async function et(e){try{return await I(e)}catch(t){if(t instanceof M)throw t;return null}}async function Zt(e){let t=["overview","activity","sessions","graph","graph/full"].includes(e),n=["overview","graph","graph/full"].includes(e),[o,i]=await Promise.all([t?et("overview"):null,n?et("graph"):null]);return{overview:o,graph:i}}var Z=(e)=>e instanceof Error?e.message:"The relay could not complete the request.",tt=()=>location.replace(ze(new URLSearchParams(location.search).get("next")));async function Qt(e,t){let n=Date.now()+e.expires_in*1000,o=Math.max(1,e.interval)*1000;while(t===D){if(await new Promise((s)=>setTimeout(s,o)),t!==D)return;if(Date.now()>=n)break;let i=await I("device/poll",{device_code:e.device_code});if(t!==D)return;if(i.status==="ok"){tt();return}if(i.status!=="pending"){b(N(m,i.status==="denied"?"Device sign-in was denied.":"The device code expired. Try again."));return}}if(t===D)b(N(m,"The device code expired. Try again."))}document.addEventListener("submit",(e)=>{let t=e.target;if(!(t instanceof HTMLFormElement))return;let n=t.getAttribute("action")==="/sign-out",o=t.hasAttribute("data-sign-in"),i=t.hasAttribute("data-device");if(!n&&!o&&!i)return;if(e.preventDefault(),be)return;be=!0;let s=++D;for(let l of t.querySelectorAll("button"))l.disabled=!0;(async()=>{try{if(n)await I("sign-out",{}),location.replace("/sign-in");else if(o){let l=new FormData(t).get("token");await I("sign-in",{token:typeof l==="string"?l:""}),tt()}else{let l=await I("device",{});b(N(m,"",l)),Qt(l,s).catch((d)=>{if(s===D)b(N(m,Z(d)))})}}catch(l){if(l instanceof M)ye();else b(m.signedIn?we(m,Z(l)):N(m,Z(l)))}finally{be=!1}})()});async function en(){if(m=await I("session"),!m.signedIn){b(N(m));return}let e=m,t=Ye(location.pathname,e);if("redirect"in t){location.replace(t.redirect);return}if("page"in t){b(t.page==="device"?Ze(e,new URLSearchParams(location.search).get("user_code")??void 0):Qe(e));return}let n=async()=>Ke(e,t.tab,await Zt(t.tab),Date.now());window.scpRegion=async()=>{try{return new DOMParser().parseFromString(await n(),"text/html").querySelector("[data-live]")}catch(o){if(o instanceof M)ye();return null}},b(await n())}en().catch((e)=>{if(e instanceof M)ye();else b(we(m,Z(e)))});})();
1315
+ <p class="small">Only approve a code for a device you recognize.</p>`)}function et(e){return T(e,{title:"Not found",narrow:!0},r`<h1>Not found</h1><p class="lead">This page does not exist.</p><a href="/">Return home</a>`)}function be(e,t){return T(e,{title:"Dashboard unavailable",narrow:!0},r`<h1>Dashboard unavailable</h1>${v(t,!0)}<a href="">Reload</a>`)}var m={signedIn:!1,csrf:""},D=0,we=!1;class M extends Error{}async function I(e,t){let n=await fetch(`/v1/dashboard/${e}`,{method:t?"POST":"GET",credentials:"same-origin",cache:"no-store",headers:t?{accept:"application/json","content-type":"application/json"}:{accept:"application/json"},...t?{body:JSON.stringify({...t,csrf:m.csrf})}:{}}),o=await n.json();if(!n.ok){let i=o&&typeof o==="object"?o:{};if(n.status===401&&i.error==="signed_out")throw new M;throw Error(typeof i.message==="string"?i.message:i.error==="invalid_token"?"The token is not valid.":"The relay could not complete the request.")}return o}function w(e){let t=new DOMParser().parseFromString(e,"text/html"),n=[...t.querySelectorAll("script[src]")];document.head.replaceWith(t.head),document.body.replaceWith(t.body);for(let o of n){let i=document.createElement("script");for(let s of o.attributes)i.setAttribute(s.name,s.value);i.async=!1,o.replaceWith(i)}}function ye(){location.replace(`/sign-in?next=${encodeURIComponent(location.pathname+location.search)}`)}async function tt(e){try{return await I(e)}catch(t){if(t instanceof M)throw t;return null}}function Qt(){try{return localStorage.getItem("scp.graph.historic")==="1"}catch{return!1}}async function en(e){let t=["overview","activity","sessions","graph","graph/full"].includes(e),n=["overview","graph","graph/full"].includes(e),[o,i]=await Promise.all([t?tt("overview"):null,n?tt(Qt()?"graph?historic=1":"graph"):null]);return{overview:o,graph:i}}var Z=(e)=>e instanceof Error?e.message:"The relay could not complete the request.",nt=()=>location.replace(Ye(new URLSearchParams(location.search).get("next")));async function tn(e,t){let n=Date.now()+e.expires_in*1000,o=Math.max(1,e.interval)*1000;while(t===D){if(await new Promise((s)=>setTimeout(s,o)),t!==D)return;if(Date.now()>=n)break;let i=await I("device/poll",{device_code:e.device_code});if(t!==D)return;if(i.status==="ok"){nt();return}if(i.status!=="pending"){w(N(m,i.status==="denied"?"Device sign-in was denied.":"The device code expired. Try again."));return}}if(t===D)w(N(m,"The device code expired. Try again."))}document.addEventListener("submit",(e)=>{let t=e.target;if(!(t instanceof HTMLFormElement))return;let n=t.getAttribute("action")==="/sign-out",o=t.hasAttribute("data-sign-in"),i=t.hasAttribute("data-device");if(!n&&!o&&!i)return;if(e.preventDefault(),we)return;we=!0;let s=++D;for(let l of t.querySelectorAll("button"))l.disabled=!0;(async()=>{try{if(n)await I("sign-out",{}),location.replace("/sign-in");else if(o){let l=new FormData(t).get("token");await I("sign-in",{token:typeof l==="string"?l:""}),nt()}else{let l=await I("device",{});w(N(m,"",l)),tn(l,s).catch((d)=>{if(s===D)w(N(m,Z(d)))})}}catch(l){if(l instanceof M)ye();else w(m.signedIn?be(m,Z(l)):N(m,Z(l)))}finally{we=!1}})()});async function nn(){if(m=await I("session"),!m.signedIn){w(N(m));return}let e=m,t=Ke(location.pathname,e);if("redirect"in t){location.replace(t.redirect);return}if("page"in t){w(t.page==="device"?Qe(e,new URLSearchParams(location.search).get("user_code")??void 0):et(e));return}let n=async()=>Ze(e,t.tab,await en(t.tab),Date.now());window.scpRegion=async()=>{try{return new DOMParser().parseFromString(await n(),"text/html").querySelector("[data-live]")}catch(o){if(o instanceof M)ye();return null}},w(await n())}nn().catch((e)=>{if(e instanceof M)ye();else w(be(m,Z(e)))});})();
package/dist/server.js CHANGED
@@ -248,7 +248,9 @@ function createDashboard(options) {
248
248
  if (get && ["/v1/dashboard/overview", "/v1/dashboard/graph"].includes(path)) {
249
249
  if (!ctx.identity)
250
250
  return json(res, 401, { error: "signed_out" });
251
- const response = await call(req, path + (path.endsWith("/graph") ? url.search : ""), ctx.session);
251
+ const historic = url.searchParams.get("historic");
252
+ const query = path.endsWith("/graph") ? url.search : historic === null ? "" : `?${new URLSearchParams({ historic })}`;
253
+ const response = await call(req, path + query, ctx.session);
252
254
  if (response.status === 401) {
253
255
  endSession(res, ctx.key);
254
256
  return json(res, 401, { error: "signed_out" });
@@ -393,7 +395,7 @@ Flags: --help, --version, --health`);
393
395
  return;
394
396
  }
395
397
  if (args.includes("--version")) {
396
- console.log("0.1.0");
398
+ console.log("0.2.0");
397
399
  return;
398
400
  }
399
401
  const options = {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@scp-dev/dashboard",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "Optional dashboard for the SCP relay",
5
5
  "type": "module",
6
6
  "license": "Apache-2.0",