can2cup 0.10.0 → 0.10.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/INSTALL.zh-tw.md +126 -123
- package/LICENSE +202 -202
- package/NOTICE +5 -5
- package/README.md +502 -483
- package/SKILL.md +239 -239
- package/dist/cli/index.js +20 -4
- package/dist/mcp/state.js +20 -20
- package/dist/viewer/page.js +95 -95
- package/package.json +75 -55
package/dist/viewer/page.js
CHANGED
|
@@ -1,97 +1,97 @@
|
|
|
1
1
|
/** The viewer's single page. Kept as a string so the viewer has zero build/asset steps. */
|
|
2
|
-
export const PAGE = /* html */ `<!doctype html>
|
|
3
|
-
<html lang="zh-Hant"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
|
|
4
|
-
<title>can2cup viewer</title>
|
|
5
|
-
<style>
|
|
6
|
-
:root{--bg:#0f1115;--panel:#161a21;--line:#262c36;--fg:#e6e6e6;--mute:#8a93a3;--me:#2f4f8f;--them:#232a35;--sys:#1b1f27;--ok:#3ecf8e;--bad:#ff5c5c;--warn:#f0b429;--accent:#f4a26b}
|
|
7
|
-
*{box-sizing:border-box}html,body{height:100%;margin:0}
|
|
8
|
-
body{background:var(--bg);color:var(--fg);font:14px/1.45 ui-sans-serif,system-ui,-apple-system,"Segoe UI",Roboto,"Noto Sans TC",sans-serif;display:grid;grid-template-columns:260px 1fr;grid-template-rows:48px 1fr}
|
|
9
|
-
header{grid-column:1/3;display:flex;align-items:center;gap:14px;padding:0 16px;border-bottom:1px solid var(--line);background:var(--panel)}
|
|
10
|
-
header b{color:var(--accent);letter-spacing:.04em}
|
|
11
|
-
header .id{color:var(--mute);font-family:ui-monospace,Menlo,Consolas,monospace;font-size:12px}
|
|
12
|
-
header .sp{flex:1}
|
|
13
|
-
button{background:transparent;color:var(--fg);border:1px solid var(--line);border-radius:6px;padding:5px 10px;cursor:pointer;font:inherit}
|
|
14
|
-
button.pause{border-color:var(--warn);color:var(--warn)}button.pause.on{background:var(--bad);border-color:var(--bad);color:#fff}
|
|
15
|
-
aside{border-right:1px solid var(--line);background:var(--panel);overflow:auto}
|
|
16
|
-
aside .room{padding:10px 14px;border-bottom:1px solid var(--line);cursor:pointer}
|
|
17
|
-
aside .room:hover,aside .room.sel{background:#1c2129}
|
|
18
|
-
aside .room .n{font-weight:600}aside .room .m{color:var(--mute);font-size:12px;font-family:ui-monospace,Menlo,Consolas,monospace}
|
|
19
|
-
main{display:flex;flex-direction:column;min-height:0}
|
|
20
|
-
.bar{display:flex;gap:12px;align-items:center;padding:8px 16px;border-bottom:1px solid var(--line);color:var(--mute);font-size:12px;flex-wrap:wrap}
|
|
21
|
-
.bar .ok{color:var(--ok)}.bar .bad{color:var(--bad)}.bar .st{padding:1px 8px;border:1px solid var(--line);border-radius:10px}
|
|
22
|
-
#log{flex:1;overflow:auto;padding:16px;display:flex;flex-direction:column;gap:8px}
|
|
23
|
-
.msg{max-width:72%;padding:8px 12px;border-radius:12px;background:var(--them);align-self:flex-start;position:relative}
|
|
24
|
-
.msg.me{background:var(--me);align-self:flex-end}
|
|
25
|
-
.msg.sys{align-self:center;background:var(--sys);color:var(--mute);font-size:12px;max-width:90%;padding:4px 10px}
|
|
26
|
-
.msg.blocked{align-self:flex-end;background:transparent;border:1px dashed var(--bad);color:var(--bad)}
|
|
27
|
-
.meta{font-size:11px;color:var(--mute);display:flex;gap:8px;align-items:center;margin-bottom:3px}
|
|
28
|
-
.msg.me .meta{color:#c9d4ea}
|
|
29
|
-
.type{font-family:ui-monospace,Menlo,Consolas,monospace;padding:0 6px;border-radius:8px;background:rgba(255,255,255,.08)}
|
|
30
|
-
.type.accept,.type.grant{background:var(--ok);color:#052}.type.close,.type.reject,.type.withdraw,.type.revoke{background:#444}.type.proposal,.type.counter{background:var(--accent);color:#3a1d05}.type.escalate{background:var(--warn);color:#3a2a05}.type.attachment,.type.question{background:#3b4a6b;color:#dfe7ff}
|
|
31
|
-
.att a{color:var(--accent)}.grantline{font-size:12px;color:#bfe9d3;margin-top:3px;font-family:ui-monospace,Menlo,Consolas,monospace}
|
|
32
|
-
#invite{display:none;position:fixed;right:16px;top:56px;width:340px;background:var(--panel);border:1px solid var(--line);border-radius:12px;padding:14px;z-index:5;box-shadow:0 8px 30px rgba(0,0,0,.5)}
|
|
33
|
-
#invite.on{display:block}#invite h3{margin:0 0 8px;font-size:13px;color:var(--accent);letter-spacing:.06em}
|
|
34
|
-
#invite img{display:block;margin:0 auto 8px;background:#fff;padding:6px;border-radius:8px}
|
|
35
|
-
#invite textarea{width:100%;height:64px;background:#0b0d11;color:var(--fg);border:1px solid var(--line);border-radius:6px;font:12px ui-monospace,Menlo,Consolas,monospace;padding:6px;resize:none}
|
|
36
|
-
#invite .warn{font-size:11px;color:var(--mute);margin:6px 0 0}
|
|
37
|
-
.amt{font-weight:700;font-variant-numeric:tabular-nums}
|
|
38
|
-
.v{margin-left:auto}.v.ok{color:var(--ok)}.v.bad{color:var(--bad)}
|
|
39
|
-
.rat{margin-top:6px;padding:6px 8px;border-left:2px solid var(--warn);background:rgba(0,0,0,.25);font-size:12px;color:#e8d9a8;font-style:italic}
|
|
40
|
-
.rat b{font-style:normal;color:var(--warn);font-size:10px;letter-spacing:.06em}
|
|
41
|
-
.empty{margin:auto;color:var(--mute)}
|
|
42
|
-
</style></head><body>
|
|
43
|
-
<header><b>can2cup</b><span id="who" class="id"></span><span class="sp"></span>
|
|
44
|
-
<span id="pausedTxt" style="color:var(--mute);font-size:12px"></span>
|
|
45
|
-
<button id="inviteBtn" title="Invite link + QR for the selected room">INVITE</button>
|
|
46
|
-
<button id="pauseBtn" class="pause" title="Create/remove the PAUSED file: while paused your agent cannot send anything.">PAUSE</button></header>
|
|
47
|
-
<div id="invite"><h3>INVITE · <span id="invRoom"></span></h3><img id="invQr" width="240" height="240" alt="QR of the invite link"><textarea id="invLink" readonly></textarea><button id="invCopy">copy link</button> <span id="invCopied" style="color:var(--ok);font-size:12px"></span><div class="warn">This link is the room key — whoever holds it can read and post. Hand it to the other principal out-of-band (LINE, mail, in person via the QR); never post it publicly.</div></div>
|
|
48
|
-
<aside id="rooms"></aside>
|
|
49
|
-
<main><div class="bar" id="bar">選一間房</div><div id="log"><div class="empty">no room selected</div></div></main>
|
|
50
|
-
<script>
|
|
51
|
-
const $=s=>document.querySelector(s);
|
|
52
|
-
let state=null, cur=null, names={}, me="", seen=new Set(), blockedSeen=new Set(), poller=0;
|
|
53
|
-
const esc=s=>String(s).replace(/[&<>"]/g,c=>({"&":"&","<":"<",">":">",'"':"""}[c]));
|
|
54
|
-
const t=(iso)=>{const d=new Date(iso);return d.toLocaleTimeString("zh-TW",{hour12:false})};
|
|
55
|
-
async function loadState(){state=await (await fetch("/api/state")).json();me=state.me.pub;
|
|
56
|
-
$("#who").textContent=state.me.name+" · "+me.slice(0,8)+" · "+state.home+(state.notify?" · 🔔":"");
|
|
57
|
-
setPaused(state.paused);
|
|
58
|
-
const el=$("#rooms");el.innerHTML="";
|
|
59
|
-
for(const r of state.rooms){const d=document.createElement("div");d.className="room"+(cur&&cur.id===r.id?" sel":"");
|
|
60
|
-
d.innerHTML='<div class="n">'+esc(r.name||"(unnamed)")+'</div><div class="m">'+esc(r.id)+" · "+esc(r.state)+" · seq "+esc(r.lastSeq)+"</div>";
|
|
61
|
-
d.onclick=()=>openRoom(r);el.appendChild(d);}
|
|
62
|
-
if(!cur&&state.rooms.length)openRoom(state.rooms[0]);}
|
|
63
|
-
function setPaused(on){$("#pauseBtn").classList.toggle("on",on);$("#pauseBtn").textContent=on?"PAUSED — click to resume":"PAUSE";$("#pausedTxt").textContent=on?"⛔ your agent cannot send":"";}
|
|
64
|
-
$("#inviteBtn").onclick=async()=>{const p=$("#invite");if(p.classList.contains("on")){p.classList.remove("on");return;}if(!cur)return;
|
|
65
|
-
const r=await (await fetch("/api/rooms/"+cur.id+"/invite")).json();$("#invRoom").textContent=(cur.name||cur.id);$("#invQr").src=r.qr;$("#invLink").value=r.link;$("#invCopied").textContent="";p.classList.add("on");};
|
|
66
|
-
$("#invCopy").onclick=async()=>{try{await navigator.clipboard.writeText($("#invLink").value);$("#invCopied").textContent="copied ✓";}catch(e){$("#invLink").select();}};
|
|
67
|
-
$("#pauseBtn").onclick=async()=>{const on=!$("#pauseBtn").classList.contains("on");const r=await (await fetch("/api/pause",{method:"POST",body:JSON.stringify({on})})).json();setPaused(r.paused);};
|
|
68
|
-
function extra(m){const b=m.body||{};let h="";
|
|
69
|
-
if(m.type==="grant")h+='<div class="grantline">scope '+esc(b.scope||"")+' · until '+esc(b.expires||"")+(b.revocable===false?" · irrevocable":"")+'</div>';
|
|
70
|
-
if(m.type==="revoke")h+='<div class="grantline">revokes #'+esc(b.ref)+'</div>';
|
|
71
|
-
if(m.type==="attachment")h+='<div class="att">📎 '+esc(b.name||"")+' <a href="'+esc(b.url||"#")+'" target="_blank" rel="noopener">'+esc(b.url||"")+'</a>'+(b.sha256?' <span class="mute" style="font-size:11px">sha256 '+esc(String(b.sha256).slice(0,12))+'…</span>':"")+'</div>';
|
|
72
|
-
return h;}
|
|
73
|
-
function bubble(m){const mine=m.from===me, sys=m.from==="relay";
|
|
74
|
-
const d=document.createElement("div");d.className="msg"+(mine?" me":"")+(sys?" sys":"");
|
|
75
|
-
if(sys){const b=m.body||{};d.textContent=t(m.ts)+" · "+(b.event||"system")+" · "+(b.name||"")+" ("+String(b.by||"").slice(0,8)+")";return d;}
|
|
76
|
-
const who=mine?"you":(names[m.from]||m.from.slice(0,8));const body=m.body||{};
|
|
77
|
-
d.innerHTML='<div class="meta"><span>'+esc(who)+'</span><span class="type '+esc(m.type)+'">'+esc(m.type)+'</span><span>'+t(m.ts)+'</span><span>#'+esc(m.seq)+'</span><span class="v '+(m.ok?"ok":"bad")+'" title="'+esc((m.errors||[]).join("; "))+'">'+(m.ok?"✓ verified":"✗ "+esc((m.errors||[]).join("; ")))+'</span></div>'
|
|
78
|
-
+'<div>'+esc(typeof body==="string"?body:(body.text||JSON.stringify(body)))+(body.amount!=null?' <span class="amt">'+esc(body.amount)+'</span>':"")+'</div>'+extra(m);
|
|
79
|
-
return d;}
|
|
80
|
-
function blockedBubble(b){const d=document.createElement("div");d.className="msg blocked";
|
|
81
|
-
d.innerHTML='<div class="meta"><span>NOT SENT</span><span class="type '+esc(b.type)+'">'+esc(b.type)+'</span><span>'+t(b.at)+'</span></div><div>'+esc((b.body&&b.body.text)||JSON.stringify(b.body))+(b.body&&b.body.amount!=null?' <span class="amt">'+esc(b.body.amount)+'</span>':"")+'</div><div class="rat"><b>BLOCKED</b> '+esc(b.reason||"")+(b.rationale?'<br><b>RATIONALE</b> '+esc(b.rationale):"")+'</div>';return d;}
|
|
82
|
-
function render(data){const log=$("#log");if(log.querySelector(".empty"))log.innerHTML="";names=data.names||names;
|
|
83
|
-
const items=[];
|
|
84
|
-
for(const m of data.msgs){if(seen.has(m.seq))continue;seen.add(m.seq);items.push({at:m.ts,el:bubble(m),seq:m.seq,m});}
|
|
85
|
-
for(const b of data.blocked||[]){const k=b.at+"|"+JSON.stringify(b.body);if(blockedSeen.has(k))continue;blockedSeen.add(k);items.push({at:b.at,el:blockedBubble(b)});}
|
|
86
|
-
items.sort((a,b)=>a.at.localeCompare(b.at));
|
|
87
|
-
for(const it of items){if(it.m&&it.m.from===me&&data.rationale&&data.rationale[it.seq]){const r=document.createElement("div");r.className="rat";r.innerHTML="<b>PRIVATE RATIONALE</b> "+esc(data.rationale[it.seq]);it.el.appendChild(r);}log.appendChild(it.el);}
|
|
88
|
-
if(items.length)log.scrollTop=log.scrollHeight;
|
|
89
|
-
const parts=Object.entries(names).map(([pk,n])=>esc(n)+(pk===me?" (you)":"")+" "+pk.slice(0,8)).join(" · ");
|
|
90
|
-
$("#bar").innerHTML='<b>'+esc(data.room.name||"(unnamed)")+'</b><span class="st">'+esc(data.room.state)+'</span><span>'+esc(data.room.id)+'</span><span>'+parts+'</span><span class="sp"></span><span class="'+(data.chainOk?"ok":"bad")+'">'+(data.chainOk?"chain ✓ ("+esc(data.lastSeq)+")":"CHAIN BROKEN")+'</span>';}
|
|
91
|
-
async function openRoom(r){cur=r;seen=new Set();blockedSeen=new Set();$("#log").innerHTML="";clearTimeout(poller);$("#invite").classList.remove("on");
|
|
92
|
-
document.querySelectorAll(".room").forEach(e=>e.classList.toggle("sel",e.querySelector(".m").textContent.startsWith(r.id)));
|
|
93
|
-
const first=await (await fetch("/api/rooms/"+r.id+"?full=1")).json();render(first);poll();}
|
|
94
|
-
async function poll(){if(!cur)return;const id=cur.id;try{const d=await (await fetch("/api/rooms/"+id+"?wait=25")).json();if(cur&&cur.id===id)render(d);}catch(e){}
|
|
95
|
-
if(cur&&cur.id===id)poller=setTimeout(poll,300);}
|
|
96
|
-
loadState();setInterval(async()=>{const s=await (await fetch("/api/state")).json();setPaused(s.paused);if(s.rooms.length!==(state?state.rooms.length:0))loadState();},5000);
|
|
2
|
+
export const PAGE = /* html */ `<!doctype html>
|
|
3
|
+
<html lang="zh-Hant"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
|
|
4
|
+
<title>can2cup viewer</title>
|
|
5
|
+
<style>
|
|
6
|
+
:root{--bg:#0f1115;--panel:#161a21;--line:#262c36;--fg:#e6e6e6;--mute:#8a93a3;--me:#2f4f8f;--them:#232a35;--sys:#1b1f27;--ok:#3ecf8e;--bad:#ff5c5c;--warn:#f0b429;--accent:#f4a26b}
|
|
7
|
+
*{box-sizing:border-box}html,body{height:100%;margin:0}
|
|
8
|
+
body{background:var(--bg);color:var(--fg);font:14px/1.45 ui-sans-serif,system-ui,-apple-system,"Segoe UI",Roboto,"Noto Sans TC",sans-serif;display:grid;grid-template-columns:260px 1fr;grid-template-rows:48px 1fr}
|
|
9
|
+
header{grid-column:1/3;display:flex;align-items:center;gap:14px;padding:0 16px;border-bottom:1px solid var(--line);background:var(--panel)}
|
|
10
|
+
header b{color:var(--accent);letter-spacing:.04em}
|
|
11
|
+
header .id{color:var(--mute);font-family:ui-monospace,Menlo,Consolas,monospace;font-size:12px}
|
|
12
|
+
header .sp{flex:1}
|
|
13
|
+
button{background:transparent;color:var(--fg);border:1px solid var(--line);border-radius:6px;padding:5px 10px;cursor:pointer;font:inherit}
|
|
14
|
+
button.pause{border-color:var(--warn);color:var(--warn)}button.pause.on{background:var(--bad);border-color:var(--bad);color:#fff}
|
|
15
|
+
aside{border-right:1px solid var(--line);background:var(--panel);overflow:auto}
|
|
16
|
+
aside .room{padding:10px 14px;border-bottom:1px solid var(--line);cursor:pointer}
|
|
17
|
+
aside .room:hover,aside .room.sel{background:#1c2129}
|
|
18
|
+
aside .room .n{font-weight:600}aside .room .m{color:var(--mute);font-size:12px;font-family:ui-monospace,Menlo,Consolas,monospace}
|
|
19
|
+
main{display:flex;flex-direction:column;min-height:0}
|
|
20
|
+
.bar{display:flex;gap:12px;align-items:center;padding:8px 16px;border-bottom:1px solid var(--line);color:var(--mute);font-size:12px;flex-wrap:wrap}
|
|
21
|
+
.bar .ok{color:var(--ok)}.bar .bad{color:var(--bad)}.bar .st{padding:1px 8px;border:1px solid var(--line);border-radius:10px}
|
|
22
|
+
#log{flex:1;overflow:auto;padding:16px;display:flex;flex-direction:column;gap:8px}
|
|
23
|
+
.msg{max-width:72%;padding:8px 12px;border-radius:12px;background:var(--them);align-self:flex-start;position:relative}
|
|
24
|
+
.msg.me{background:var(--me);align-self:flex-end}
|
|
25
|
+
.msg.sys{align-self:center;background:var(--sys);color:var(--mute);font-size:12px;max-width:90%;padding:4px 10px}
|
|
26
|
+
.msg.blocked{align-self:flex-end;background:transparent;border:1px dashed var(--bad);color:var(--bad)}
|
|
27
|
+
.meta{font-size:11px;color:var(--mute);display:flex;gap:8px;align-items:center;margin-bottom:3px}
|
|
28
|
+
.msg.me .meta{color:#c9d4ea}
|
|
29
|
+
.type{font-family:ui-monospace,Menlo,Consolas,monospace;padding:0 6px;border-radius:8px;background:rgba(255,255,255,.08)}
|
|
30
|
+
.type.accept,.type.grant{background:var(--ok);color:#052}.type.close,.type.reject,.type.withdraw,.type.revoke{background:#444}.type.proposal,.type.counter{background:var(--accent);color:#3a1d05}.type.escalate{background:var(--warn);color:#3a2a05}.type.attachment,.type.question{background:#3b4a6b;color:#dfe7ff}
|
|
31
|
+
.att a{color:var(--accent)}.grantline{font-size:12px;color:#bfe9d3;margin-top:3px;font-family:ui-monospace,Menlo,Consolas,monospace}
|
|
32
|
+
#invite{display:none;position:fixed;right:16px;top:56px;width:340px;background:var(--panel);border:1px solid var(--line);border-radius:12px;padding:14px;z-index:5;box-shadow:0 8px 30px rgba(0,0,0,.5)}
|
|
33
|
+
#invite.on{display:block}#invite h3{margin:0 0 8px;font-size:13px;color:var(--accent);letter-spacing:.06em}
|
|
34
|
+
#invite img{display:block;margin:0 auto 8px;background:#fff;padding:6px;border-radius:8px}
|
|
35
|
+
#invite textarea{width:100%;height:64px;background:#0b0d11;color:var(--fg);border:1px solid var(--line);border-radius:6px;font:12px ui-monospace,Menlo,Consolas,monospace;padding:6px;resize:none}
|
|
36
|
+
#invite .warn{font-size:11px;color:var(--mute);margin:6px 0 0}
|
|
37
|
+
.amt{font-weight:700;font-variant-numeric:tabular-nums}
|
|
38
|
+
.v{margin-left:auto}.v.ok{color:var(--ok)}.v.bad{color:var(--bad)}
|
|
39
|
+
.rat{margin-top:6px;padding:6px 8px;border-left:2px solid var(--warn);background:rgba(0,0,0,.25);font-size:12px;color:#e8d9a8;font-style:italic}
|
|
40
|
+
.rat b{font-style:normal;color:var(--warn);font-size:10px;letter-spacing:.06em}
|
|
41
|
+
.empty{margin:auto;color:var(--mute)}
|
|
42
|
+
</style></head><body>
|
|
43
|
+
<header><b>can2cup</b><span id="who" class="id"></span><span class="sp"></span>
|
|
44
|
+
<span id="pausedTxt" style="color:var(--mute);font-size:12px"></span>
|
|
45
|
+
<button id="inviteBtn" title="Invite link + QR for the selected room">INVITE</button>
|
|
46
|
+
<button id="pauseBtn" class="pause" title="Create/remove the PAUSED file: while paused your agent cannot send anything.">PAUSE</button></header>
|
|
47
|
+
<div id="invite"><h3>INVITE · <span id="invRoom"></span></h3><img id="invQr" width="240" height="240" alt="QR of the invite link"><textarea id="invLink" readonly></textarea><button id="invCopy">copy link</button> <span id="invCopied" style="color:var(--ok);font-size:12px"></span><div class="warn">This link is the room key — whoever holds it can read and post. Hand it to the other principal out-of-band (LINE, mail, in person via the QR); never post it publicly.</div></div>
|
|
48
|
+
<aside id="rooms"></aside>
|
|
49
|
+
<main><div class="bar" id="bar">選一間房</div><div id="log"><div class="empty">no room selected</div></div></main>
|
|
50
|
+
<script>
|
|
51
|
+
const $=s=>document.querySelector(s);
|
|
52
|
+
let state=null, cur=null, names={}, me="", seen=new Set(), blockedSeen=new Set(), poller=0;
|
|
53
|
+
const esc=s=>String(s).replace(/[&<>"]/g,c=>({"&":"&","<":"<",">":">",'"':"""}[c]));
|
|
54
|
+
const t=(iso)=>{const d=new Date(iso);return d.toLocaleTimeString("zh-TW",{hour12:false})};
|
|
55
|
+
async function loadState(){state=await (await fetch("/api/state")).json();me=state.me.pub;
|
|
56
|
+
$("#who").textContent=state.me.name+" · "+me.slice(0,8)+" · "+state.home+(state.notify?" · 🔔":"");
|
|
57
|
+
setPaused(state.paused);
|
|
58
|
+
const el=$("#rooms");el.innerHTML="";
|
|
59
|
+
for(const r of state.rooms){const d=document.createElement("div");d.className="room"+(cur&&cur.id===r.id?" sel":"");
|
|
60
|
+
d.innerHTML='<div class="n">'+esc(r.name||"(unnamed)")+'</div><div class="m">'+esc(r.id)+" · "+esc(r.state)+" · seq "+esc(r.lastSeq)+"</div>";
|
|
61
|
+
d.onclick=()=>openRoom(r);el.appendChild(d);}
|
|
62
|
+
if(!cur&&state.rooms.length)openRoom(state.rooms[0]);}
|
|
63
|
+
function setPaused(on){$("#pauseBtn").classList.toggle("on",on);$("#pauseBtn").textContent=on?"PAUSED — click to resume":"PAUSE";$("#pausedTxt").textContent=on?"⛔ your agent cannot send":"";}
|
|
64
|
+
$("#inviteBtn").onclick=async()=>{const p=$("#invite");if(p.classList.contains("on")){p.classList.remove("on");return;}if(!cur)return;
|
|
65
|
+
const r=await (await fetch("/api/rooms/"+cur.id+"/invite")).json();$("#invRoom").textContent=(cur.name||cur.id);$("#invQr").src=r.qr;$("#invLink").value=r.link;$("#invCopied").textContent="";p.classList.add("on");};
|
|
66
|
+
$("#invCopy").onclick=async()=>{try{await navigator.clipboard.writeText($("#invLink").value);$("#invCopied").textContent="copied ✓";}catch(e){$("#invLink").select();}};
|
|
67
|
+
$("#pauseBtn").onclick=async()=>{const on=!$("#pauseBtn").classList.contains("on");const r=await (await fetch("/api/pause",{method:"POST",body:JSON.stringify({on})})).json();setPaused(r.paused);};
|
|
68
|
+
function extra(m){const b=m.body||{};let h="";
|
|
69
|
+
if(m.type==="grant")h+='<div class="grantline">scope '+esc(b.scope||"")+' · until '+esc(b.expires||"")+(b.revocable===false?" · irrevocable":"")+'</div>';
|
|
70
|
+
if(m.type==="revoke")h+='<div class="grantline">revokes #'+esc(b.ref)+'</div>';
|
|
71
|
+
if(m.type==="attachment")h+='<div class="att">📎 '+esc(b.name||"")+' <a href="'+esc(b.url||"#")+'" target="_blank" rel="noopener">'+esc(b.url||"")+'</a>'+(b.sha256?' <span class="mute" style="font-size:11px">sha256 '+esc(String(b.sha256).slice(0,12))+'…</span>':"")+'</div>';
|
|
72
|
+
return h;}
|
|
73
|
+
function bubble(m){const mine=m.from===me, sys=m.from==="relay";
|
|
74
|
+
const d=document.createElement("div");d.className="msg"+(mine?" me":"")+(sys?" sys":"");
|
|
75
|
+
if(sys){const b=m.body||{};d.textContent=t(m.ts)+" · "+(b.event||"system")+" · "+(b.name||"")+" ("+String(b.by||"").slice(0,8)+")";return d;}
|
|
76
|
+
const who=mine?"you":(names[m.from]||m.from.slice(0,8));const body=m.body||{};
|
|
77
|
+
d.innerHTML='<div class="meta"><span>'+esc(who)+'</span><span class="type '+esc(m.type)+'">'+esc(m.type)+'</span><span>'+t(m.ts)+'</span><span>#'+esc(m.seq)+'</span><span class="v '+(m.ok?"ok":"bad")+'" title="'+esc((m.errors||[]).join("; "))+'">'+(m.ok?"✓ verified":"✗ "+esc((m.errors||[]).join("; ")))+'</span></div>'
|
|
78
|
+
+'<div>'+esc(typeof body==="string"?body:(body.text||JSON.stringify(body)))+(body.amount!=null?' <span class="amt">'+esc(body.amount)+'</span>':"")+'</div>'+extra(m);
|
|
79
|
+
return d;}
|
|
80
|
+
function blockedBubble(b){const d=document.createElement("div");d.className="msg blocked";
|
|
81
|
+
d.innerHTML='<div class="meta"><span>NOT SENT</span><span class="type '+esc(b.type)+'">'+esc(b.type)+'</span><span>'+t(b.at)+'</span></div><div>'+esc((b.body&&b.body.text)||JSON.stringify(b.body))+(b.body&&b.body.amount!=null?' <span class="amt">'+esc(b.body.amount)+'</span>':"")+'</div><div class="rat"><b>BLOCKED</b> '+esc(b.reason||"")+(b.rationale?'<br><b>RATIONALE</b> '+esc(b.rationale):"")+'</div>';return d;}
|
|
82
|
+
function render(data){const log=$("#log");if(log.querySelector(".empty"))log.innerHTML="";names=data.names||names;
|
|
83
|
+
const items=[];
|
|
84
|
+
for(const m of data.msgs){if(seen.has(m.seq))continue;seen.add(m.seq);items.push({at:m.ts,el:bubble(m),seq:m.seq,m});}
|
|
85
|
+
for(const b of data.blocked||[]){const k=b.at+"|"+JSON.stringify(b.body);if(blockedSeen.has(k))continue;blockedSeen.add(k);items.push({at:b.at,el:blockedBubble(b)});}
|
|
86
|
+
items.sort((a,b)=>a.at.localeCompare(b.at));
|
|
87
|
+
for(const it of items){if(it.m&&it.m.from===me&&data.rationale&&data.rationale[it.seq]){const r=document.createElement("div");r.className="rat";r.innerHTML="<b>PRIVATE RATIONALE</b> "+esc(data.rationale[it.seq]);it.el.appendChild(r);}log.appendChild(it.el);}
|
|
88
|
+
if(items.length)log.scrollTop=log.scrollHeight;
|
|
89
|
+
const parts=Object.entries(names).map(([pk,n])=>esc(n)+(pk===me?" (you)":"")+" "+pk.slice(0,8)).join(" · ");
|
|
90
|
+
$("#bar").innerHTML='<b>'+esc(data.room.name||"(unnamed)")+'</b><span class="st">'+esc(data.room.state)+'</span><span>'+esc(data.room.id)+'</span><span>'+parts+'</span><span class="sp"></span><span class="'+(data.chainOk?"ok":"bad")+'">'+(data.chainOk?"chain ✓ ("+esc(data.lastSeq)+")":"CHAIN BROKEN")+'</span>';}
|
|
91
|
+
async function openRoom(r){cur=r;seen=new Set();blockedSeen=new Set();$("#log").innerHTML="";clearTimeout(poller);$("#invite").classList.remove("on");
|
|
92
|
+
document.querySelectorAll(".room").forEach(e=>e.classList.toggle("sel",e.querySelector(".m").textContent.startsWith(r.id)));
|
|
93
|
+
const first=await (await fetch("/api/rooms/"+r.id+"?full=1")).json();render(first);poll();}
|
|
94
|
+
async function poll(){if(!cur)return;const id=cur.id;try{const d=await (await fetch("/api/rooms/"+id+"?wait=25")).json();if(cur&&cur.id===id)render(d);}catch(e){}
|
|
95
|
+
if(cur&&cur.id===id)poller=setTimeout(poll,300);}
|
|
96
|
+
loadState();setInterval(async()=>{const s=await (await fetch("/api/state")).json();setPaused(s.paused);if(s.rooms.length!==(state?state.rooms.length:0))loadState();},5000);
|
|
97
97
|
</script></body></html>`;
|
package/package.json
CHANGED
|
@@ -1,55 +1,75 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "can2cup",
|
|
3
|
-
"version": "0.10.
|
|
4
|
-
"description": "can2cup 傳聲罐罐 — a tin can, a paper cup, one string: signed rooms where two people's AI agents talk under their principals' mandates.",
|
|
5
|
-
"type": "module",
|
|
6
|
-
"homepage": "https://can2cup.com",
|
|
7
|
-
"repository": {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
"
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
"
|
|
16
|
-
"
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
"
|
|
20
|
-
"
|
|
21
|
-
"
|
|
22
|
-
"
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
"
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
"
|
|
29
|
-
},
|
|
30
|
-
"
|
|
31
|
-
|
|
32
|
-
"
|
|
33
|
-
"
|
|
34
|
-
"
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
"
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
"
|
|
41
|
-
"
|
|
42
|
-
"
|
|
43
|
-
"
|
|
44
|
-
"
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
"
|
|
48
|
-
"
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
"
|
|
53
|
-
"
|
|
54
|
-
|
|
55
|
-
|
|
1
|
+
{
|
|
2
|
+
"name": "can2cup",
|
|
3
|
+
"version": "0.10.2",
|
|
4
|
+
"description": "can2cup 傳聲罐罐 — a tin can, a paper cup, one string: signed rooms where two people's AI agents talk under their principals' mandates.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"homepage": "https://can2cup.com",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "git+https://github.com/ccqqder/can2cup.git"
|
|
10
|
+
},
|
|
11
|
+
"bugs": {
|
|
12
|
+
"url": "https://can2cup.com/guide/#faq"
|
|
13
|
+
},
|
|
14
|
+
"keywords": [
|
|
15
|
+
"mcp",
|
|
16
|
+
"agent",
|
|
17
|
+
"a2a",
|
|
18
|
+
"line",
|
|
19
|
+
"ed25519",
|
|
20
|
+
"claude-code",
|
|
21
|
+
"can2cup",
|
|
22
|
+
"傳聲罐罐"
|
|
23
|
+
],
|
|
24
|
+
"engines": {
|
|
25
|
+
"node": ">=18"
|
|
26
|
+
},
|
|
27
|
+
"publishConfig": {
|
|
28
|
+
"access": "public"
|
|
29
|
+
},
|
|
30
|
+
"bin": {
|
|
31
|
+
"can2cup": "dist/cli/index.js",
|
|
32
|
+
"can2cup-mcp": "dist/mcp/index.js",
|
|
33
|
+
"can2can": "dist/cli/index.js",
|
|
34
|
+
"parley": "dist/cli/index.js"
|
|
35
|
+
},
|
|
36
|
+
"scripts": {
|
|
37
|
+
"build": "tsc -p tsconfig.json",
|
|
38
|
+
"check:relay": "tsc -p tsconfig.relay.json --noEmit",
|
|
39
|
+
"dev:relay": "wrangler dev --local-upstream 127.0.0.1 --upstream-protocol http",
|
|
40
|
+
"deploy:relay": "wrangler deploy",
|
|
41
|
+
"release:relay": "node scripts/routes-check.mjs && node scripts/stage-tarball.mjs --from-npm && node scripts/release-sign.mjs && wrangler deploy",
|
|
42
|
+
"release:key": "node scripts/release-key.mjs",
|
|
43
|
+
"release:npm": "node scripts/release-npm.mjs",
|
|
44
|
+
"check:routes": "node scripts/routes-check.mjs",
|
|
45
|
+
"smoke": "node dist/scripts/smoke.js",
|
|
46
|
+
"view": "node dist/viewer/index.js",
|
|
47
|
+
"pack": "npm run build && npm pack",
|
|
48
|
+
"release:relay:local": "node scripts/routes-check.mjs && node scripts/stage-tarball.mjs && node scripts/release-sign.mjs && wrangler deploy"
|
|
49
|
+
},
|
|
50
|
+
"license": "Apache-2.0",
|
|
51
|
+
"dependencies": {
|
|
52
|
+
"@modelcontextprotocol/sdk": "^1.30.0",
|
|
53
|
+
"@noble/ed25519": "^3.1.0",
|
|
54
|
+
"@noble/hashes": "^2.3.0",
|
|
55
|
+
"hono": "^4.13.2",
|
|
56
|
+
"qrcode": "^1.5.4",
|
|
57
|
+
"zod": "^4.4.3"
|
|
58
|
+
},
|
|
59
|
+
"devDependencies": {
|
|
60
|
+
"@cloudflare/workers-types": "^5.20260818.1",
|
|
61
|
+
"@types/node": "^26.2.0",
|
|
62
|
+
"@types/qrcode": "^1.5.6",
|
|
63
|
+
"typescript": "^5.9.3",
|
|
64
|
+
"wrangler": "^4.123.0"
|
|
65
|
+
},
|
|
66
|
+
"files": [
|
|
67
|
+
"dist",
|
|
68
|
+
"!dist/scripts",
|
|
69
|
+
"README.md",
|
|
70
|
+
"SKILL.md",
|
|
71
|
+
"INSTALL.zh-tw.md",
|
|
72
|
+
"LICENSE",
|
|
73
|
+
"NOTICE"
|
|
74
|
+
]
|
|
75
|
+
}
|