@xauyxau/hnch 1.2.1 → 1.3.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.
- package/dist/index.js +4 -4
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import Or from'yargs';import {hideBin}from'yargs/helpers';import at,{useState,useEffect,useCallback,useMemo,useRef}from'react';import {render,useApp,useInput,Box,Text}from'ink';import*as we from'yjs';import {Awareness}from'y-protocols/awareness';import {mkdirSync,chmodSync,writeFileSync,unlinkSync,existsSync,readFileSync}from'fs';import {homedir}from'os';import {join}from'path';import {randomUUID}from'crypto';import {createInterface}from'readline';import {WebsocketProvider}from'y-websocket';import
|
|
3
|
-
`,"utf-8");}async function gn(e){let t=createInterface({input:process.stdin,output:process.stderr});return new Promise(o=>{t.question(e,n=>{t.close(),o(n.trim());});})}function hn(e){return e.startsWith("wss://")?"https://"+e.slice(6):e.startsWith("ws://")?"http://"+e.slice(5):e}async function Te(){let e=de(),t=false;if(e.participantId||(e.participantId=randomUUID(),t=true),!e.displayName){let n=await gn("Enter your display name: ");e.displayName=n||`user-${e.participantId.slice(0,6)}`,t=true;}e.relayUrl||(e.relayUrl=fn,t=true),t
|
|
4
|
-
`,"utf-8");}var Sn={"unknown-room":4001,"wrong-password":4002,"invalid-invite":4003,"expired-invite":4004,"invite-already-redeemed":4005,"room-full":4006,"malformed-credentials":4010},Jt=new Map(Object.entries(Sn).map(([e,t])=>[t,e]));function ue(e){return e.role??"voter"}var Le={id:"fibonacci",cards:["0","1","2","3","5","8","13","21"]},Wt={id:"t-shirt",cards:["XS","S","M","L","XL"]},qt={id:"powers-of-2",cards:["1","2","4","8","16","32","64"]},Gt={id:"linear",cards:["1","2","3","4","5","6","7","8","9","10"]},Qt={id:"risk",cards:["Low","Medium","High","Critical"]},X={[Le.id]:Le,[Wt.id]:Wt,[qt.id]:qt,[Gt.id]:Gt,[Qt.id]:Qt},zt={fibonacci:"Fibonacci","t-shirt":"T-shirt","powers-of-2":"Powers of 2",linear:"Linear (1\u201310)",risk:"Risk"};function pe(e){return e in zt?zt[e]:e.length===0?e:(e.startsWith("custom-")?e.slice(7):e).split("-").map(o=>o.length===0?o:o.charAt(0).toUpperCase()+o.slice(1)).join(" ")}var xn="meta",Ae="settings",yn="participants",wn="rounds",Cn=["roomUuid","createdAt","createdBy","sessionEndedAt"],vn=["name","defaultScale","hostParticipantId"],bn=["displayName","joinedAt","role","leftAt","removedBy"],Rn=["id","title","description","ticketId","parentRoundId","scale","timerStartedAt","revealedAt","createdBy","createdAt"];function ke(e){return e.getMap(xn)}function _e(e){return e.getMap(Ae)}function $e(e){return e.getMap(yn)}function St(e){return e.getArray(wn)}function Fe(e,t){let o={};for(let n of t){let r=e.get(n);r!==void 0&&(o[n]=r);}return o}function In(e){return Fe(ke(e),Cn)}function Tn(e){return Fe(_e(e),vn)}function En(e){let t=$e(e),o=[];return t.forEach((n,r)=>{let s=Fe(n,bn);o.push({id:r,...s});}),o.sort((n,r)=>n.joinedAt-r.joinedAt||n.id.localeCompare(r.id)),o}function An(e){let t={};return e.forEach((o,n)=>{t[n]=o;}),t}function Pn(e){let t=Fe(e,Rn),o=e.get("votes"),n=o?An(o):{};return {...t,votes:n}}function Nn(e){let t=St(e),o=[];return t.forEach(n=>{o.push(Pn(n));}),o}function z(e){return {meta:In(e),settings:Tn(e),participants:En(e),rounds:Nn(e)}}function Kt(e,t){if(ke(e).has("roomUuid"))throw new Error("initDoc: doc already seeded");e.transact(()=>{let o=ke(e);o.set("roomUuid",t.roomUuid),o.set("createdAt",t.createdAt),o.set("createdBy",t.participantId);let n=_e(e);t.sessionName!==void 0&&n.set("name",t.sessionName),n.set("defaultScale",t.defaultScale),n.set("hostParticipantId",t.participantId);let r=new we.Map;r.set("displayName",t.displayName),r.set("joinedAt",t.createdAt),$e(e).set(t.participantId,r);});}function Ye(e,t){e.transact(()=>{let o=$e(e),n=o.get(t.participantId),r=n??new we.Map;r.set("displayName",t.displayName),n===void 0&&r.set("joinedAt",Date.now()),r.delete("leftAt"),r.delete("removedBy"),n===void 0&&o.set(t.participantId,r);});}function Ce(e,t){e.transact(()=>{let o=_e(e);for(let[n,r]of Object.entries(t))r===void 0?o.delete(n):o.set(n,r);});}function xt(e,t,o){e.transact(()=>{let n=$e(e),r=n.get(t),s=r??new we.Map;s.set("displayName",o.displayName),s.set("joinedAt",o.joinedAt),o.role!==void 0&&s.set("role",o.role),r===void 0&&n.set(t,s);});}function yt(e,t){let o=t.scale??_e(e).get("defaultScale");if(!o)throw new Error("appendRound: no scale (settings.defaultScale missing)");return e.transact(()=>{let n=new we.Map;n.set("id",t.id),n.set("createdAt",t.createdAt),n.set("createdBy",t.createdBy),n.set("scale",o),t.title!==void 0&&n.set("title",t.title),t.description!==void 0&&n.set("description",t.description),t.ticketId!==void 0&&n.set("ticketId",t.ticketId),t.parentRoundId!==void 0&&n.set("parentRoundId",t.parentRoundId),n.set("timerStartedAt",t.createdAt),n.set("votes",new we.Map),St(e).push([n]);}),t.id}function wt(e,t){let o=St(e);for(let n=0;n<o.length;n+=1){let r=o.get(n);if(r.get("id")===t)return r}}function Xt(e,t,o,n){let r=wt(e,t);if(!r)throw new Error(`castVote: round ${t} not found`);r.get("revealedAt")===void 0&&e.transact(()=>{r.get("votes").set(o,n);});}function Zt(e,t,o){let n=wt(e,t);if(!n)throw new Error(`clearVote: round ${t} not found`);n.get("revealedAt")===void 0&&e.transact(()=>{n.get("votes").delete(o);});}function Ct(e,t,o){let n=wt(e,t);if(!n)throw new Error(`revealRound: round ${t} not found`);e.transact(()=>{n.set("revealedAt",o);});}function eo(e){e.transact(()=>{ke(e).set("sessionEndedAt",Date.now());});}function Z(e){let{rounds:t}=e;if(t.length===0)return null;for(let o=t.length-1;o>=0;o-=1){let n=t[o];if(n.revealedAt===void 0)return n}return t[t.length-1]}function to(e){let t=Object.values(e.votes);return t.length<2?false:t.every(o=>o===t[0])}var oo="PBKDF2",On="SHA-256",no=new TextEncoder;function Dn(e){return Array.from(new Uint8Array(e)).map(t=>t.toString(16).padStart(2,"0")).join("")}async function me(e,t){let o=await crypto.subtle.importKey("raw",no.encode(e),oo,false,["deriveBits"]),n=await crypto.subtle.deriveBits({name:oo,salt:no.encode(t),iterations:1e5,hash:On},o,256);return Dn(n)}var ro="ABCDEFGHJKMNPQRSTUVWXYZ23456789";if(ro.length!==31)throw new Error(`Room code alphabet must be 31 characters, got ${ro.length}`);function vt(e){return e.trim().replace(/-/g,"").toUpperCase()}function so(e,t=true){let o=vt(e);return !t||o.length!==6?o:`${o.slice(0,3)}-${o.slice(3)}`}function Ve(e,t,o){if(e.startsWith("custom-")){let r=(o??[]).find(s=>s.id===e);return r?{id:r.id,cards:r.cards}:void 0}let n=X[e];if(n){let r=(t??{})[e]??[];if(r.length===0)return n;let s=n.cards.filter((u,i)=>!r.includes(i));return s.length===0?void 0:{id:e,cards:s}}}function Je(e,t){let o=Object.keys(X).map(r=>Ve(r,e,t)).filter(r=>r!==void 0),n=(t??[]).map(r=>({id:r.id,cards:r.cards}));return [...o,...n]}async function io(e,t){let o=await fetch(`${e}/rooms`,{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({})});if(!o.ok)throw new Error(`POST /rooms failed: ${o.status}`);return await o.json()}async function Pe(e,t){let o=await fetch(`${e}/rooms/${encodeURIComponent(t)}`);if(o.status===404){let n=await o.json().catch(()=>null);throw new Error(`Room not found: ${n?.reason??"unknown-room"}`)}if(!o.ok)throw new Error(`GET /rooms/:code failed: ${o.status}`);return await o.json()}async function bt(e,t,o){let n=await fetch(`${e}/rooms/${t}/access`,{method:"PATCH",headers:{"content-type":"application/json"},body:JSON.stringify({passwordHash:o})});if(!n.ok)throw new Error(`PATCH /rooms/:uuid/access failed: ${n.status}`)}function ao(e){let o=`${e.wsBaseUrl.replace(/^http/,"ws")}/ws`,n={did:e.deviceId};e.passwordHash!==void 0&&(n.pw=e.passwordHash);let r=new WebsocketProvider(o,e.roomUuid,e.doc,{params:n,connect:true,...e.awareness!==void 0&&{awareness:e.awareness},WebSocketPolyfill:Hn}),s=c=>{e.onStatusChange?.(c.status);},u=c=>{if(c===null)return;let a=Jt.get(c.code);a!==void 0&&(e.onRejection?.(a),r.shouldConnect=false,r.disconnect());};r.on("status",s),r.on("connection-close",u);let i=false,p=e.passwordHash;return {doc:e.doc,awareness:r.awareness,updateCredentials(c){if(c.passwordHash===p)return;p=c.passwordHash??void 0,r.shouldConnect=false,r.disconnect();let l={did:r.params.did??""};p!==void 0&&(l.pw=p),r.params=l,r.connect();},disconnect(){i||(i=true,r.off("status",s),r.off("connection-close",u),r.shouldConnect=false,r.disconnect(),r.destroy());}}}var Bn=5e3;function co(e,t,o){let n=null,r=null;function s(){let a=new Set;return t.getStates().forEach(l=>{let S=l.participantId;typeof S=="string"&&a.add(S);}),a}function u(){return e.getMap(Ae).get("hostParticipantId")}function i(){r!==null&&(clearTimeout(r),r=null),n=null;}function p(){let a=u(),l=n;if(n=null,!l||a!==l)return;let S=s();if(S.has(a))return;let E=[...S].sort();if(E.length===0)return;let N=E[0];N===o&&Ce(e,{hostParticipantId:N});}function c(){let a=u();if(!a)return;if(s().has(a)){i();return}n!==a&&(i(),n=a,r=setTimeout(()=>{r=null,p();},Bn));}return t.on("change",c),e.getMap(Ae).observe(c),c(),{stop(){i(),t.off("change",c),e.getMap(Ae).unobserve(c);}}}async function po(e){let t,o=await io(e.relayUrl);e.password&&(t=await me(e.password,o.uuid),await bt(e.relayUrl,o.uuid,t));let n=new we.Doc,r=new Awareness(n),s=Date.now();return Kt(n,{roomUuid:o.uuid,createdAt:s,participantId:e.participantId,displayName:e.displayName,defaultScale:e.defaultScale??Le,sessionName:e.sessionName}),r.setLocalStateField("participantId",e.participantId),We({doc:n,awareness:r,roomCode:o.code,roomUuid:o.uuid,participantId:e.participantId,relayUrl:e.relayUrl,passwordHash:t})}async function mo(e){let t=await Pe(e.relayUrl,e.roomCode),o;if(t.accessMode==="password"){if(!e.password)throw new Error("Room requires a password");o=await me(e.password,t.uuid);}let n=new we.Doc,r=new Awareness(n);return Ye(n,{participantId:e.participantId,displayName:e.displayName}),r.setLocalStateField("participantId",e.participantId),We({doc:n,awareness:r,roomCode:e.roomCode,roomUuid:t.uuid,participantId:e.participantId,relayUrl:e.relayUrl,passwordHash:o,onRejection:e.onRejection})}function We(e){let{doc:t,awareness:o,participantId:n,relayUrl:r,roomUuid:s}=e,u=new Set,i=new Set,p=new Set,c=ao({wsBaseUrl:e.relayUrl,roomUuid:e.roomUuid,doc:t,deviceId:n,passwordHash:e.passwordHash,awareness:o,onStatusChange(d){u.forEach(g=>g(d));},onRejection(d){console.error(`Connection rejected: ${d}`),e.onRejection?.(d);}}),a=co(t,o,n),l=()=>{i.forEach(d=>d());};t.getMap("meta").observeDeep(l),t.getMap("settings").observeDeep(l),t.getMap("participants").observeDeep(l),t.getArray("rounds").observeDeep(l);function S(){let d=new Set;return o.getStates().forEach(g=>{let w=g.participantId;typeof w=="string"&&d.add(w);}),d}let E=new Set,N=setInterval(()=>{let d=z(t),g=Z(d);if(!g||g.revealedAt!==void 0||E.has(g.id))return;let w=S();if(w.size===0)return;let h=d.participants.filter(f=>w.has(f.id)&&ue(f)==="voter");if(h.length===0)return;h.filter(f=>f.id in g.votes).length>=h.length&&(E.add(g.id),Ct(t,g.id,Date.now()));},250),R=t.getMap("settings").get("hostParticipantId"),I=()=>{let d=t.getMap("settings").get("hostParticipantId");d!==R&&d!==void 0&&p.forEach(g=>g(d)),R=d;};t.getMap("settings").observe(I);let H=false;return {doc:t,awareness:o,roomCode:e.roomCode,roomUuid:e.roomUuid,projection(){return z(t)},current(){return Z(z(t))},isHost(){return z(t).settings.hostParticipantId===n},onlineIds:S,vote(d){let g=Z(z(t));g&&Xt(t,g.id,n,d);},unvote(){let d=Z(z(t));d&&Zt(t,d.id,n);},reveal(){let d=Z(z(t));d&&Ct(t,d.id,Date.now());},newRound(d){let g=randomUUID();return yt(t,{id:g,createdAt:Date.now(),createdBy:n,title:d}),g},revote(){let d=Z(z(t)),g=randomUUID();return yt(t,{id:g,createdAt:Date.now(),createdBy:n,parentRoundId:d?.id,title:d?.title}),g},updateName(d){Ce(t,{name:d});},updateScale(d){Ce(t,{defaultScale:d});},updateDisplayName(d){let h=z(t).participants.find(M=>M.id===n)?.joinedAt??Date.now();xt(t,n,{displayName:d,joinedAt:h});},async updateAccess(d,g){let w=null;if(d==="password"){if(!g)throw new Error("Password required for password mode");w=await me(g,s);}await bt(r,s,w),c.updateCredentials({passwordHash:w});},handoffHost(d){if(!this.isHost())throw new Error("Only the current host can hand off");if(!S().has(d))throw new Error("Target participant is not online");if(!z(t).participants.some(M=>M.id===d))throw new Error("Target participant does not exist");Ce(t,{hostParticipantId:d});},toggleRole(){let g=z(t).participants.find(f=>f.id===n),h=ue(g??{})==="viewer"?"voter":"viewer",M=g?.joinedAt??Date.now();xt(t,n,{displayName:g?.displayName??"",joinedAt:M,role:h});},endSessionForEveryone(){if(!this.isHost())throw new Error("Only the current host can end the session");eo(t);},onChange(d){return i.add(d),()=>i.delete(d)},onStatusChange(d){return u.add(d),()=>u.delete(d)},onHostChange(d){return p.add(d),()=>p.delete(d)},disconnect(){H||(H=true,clearInterval(N),a.stop(),t.getMap("meta").unobserveDeep(l),t.getMap("settings").unobserveDeep(l),t.getMap("settings").unobserve(I),t.getMap("participants").unobserveDeep(l),t.getArray("rounds").unobserveDeep(l),c.disconnect());}}}function fo(e){let t=vt(e);return t===""?null:t}var Fn=1440*60*1e3;function It(){return join(Ie(),"last-session.json")}function Yn(e){if(typeof e!="object"||e===null)return false;let t=e;return typeof t.roomCode=="string"&&typeof t.roomUuid=="string"&&typeof t.lastSeenAt=="number"&&(t.passwordHash===void 0||typeof t.passwordHash=="string")}function ho(){let e=It();if(!existsSync(e))return null;try{let t=JSON.parse(readFileSync(e,"utf-8"));return !Yn(t)||Date.now()-t.lastSeenAt>Fn?(k(),null):t}catch{return k(),null}}function qe(e){let t=Ie();mkdirSync(t,{recursive:true,mode:448}),chmodSync(t,448);let o=It();writeFileSync(o,JSON.stringify(e,null,2)+`
|
|
5
|
-
`,{encoding:"utf-8",mode:384}),chmodSync(o,384);}function k(){let e=It();try{unlinkSync(e);}catch(t){if(t.code!=="ENOENT")throw t}}async function Ge(e){let t=true;try{await e.clearLastSession();}catch(o){t=false,e.logger.error(`Failed to clear cached session: ${o instanceof Error?o.message:String(o)}`);}return t?e.logger.error("Session expired (room no longer exists). Cached session cleared."):e.logger.error("Session expired. Could not delete ~/.config/hnch/last-session.json \u2014 please remove it manually."),e.exit(1)}function Qe(e){let t=e.doc.getMap("meta"),o=null,n=false;function r(){if(n)return;let s=t.get("sessionEndedAt");if(s==null||s===o)return;o=s;let u=t.get("createdBy"),i=u===e.localParticipantId,p=e.doc.getMap("participants"),c=e.doc.getMap("settings").get("hostParticipantId")??u,a="host";if(c){let S=p.get(c)?.get("displayName");typeof S=="string"&&S.length>0&&(a=S);}setImmediate(()=>{n||e.onEnded({isLocal:i,hostName:a,timestamp:s});});}return t.observe(r),r(),()=>{n=true,t.unobserve(r);}}var Vn="@xauyxau/hnch";function ze(e){return so(e)}function Ke(e){return `${process.env.HNCH_SHARE_ORIGIN??"https://hnch.dev"}/join?code=${e}`}function Xe(e){return `npx ${Vn} join ${e}`}var Jn={connecting:"yellow",connected:"green",disconnected:"red"};function yo({roomCode:e,sessionName:t,status:o,isHost:n}){let r=t?`${e} \xB7 ${t}`:e;return jsxs(Box,{borderStyle:"single",borderColor:"gray",paddingX:1,justifyContent:"space-between",children:[jsxs(Text,{bold:true,children:["hnch \xB7 ",r]}),jsxs(Box,{gap:2,children:[n&&jsx(Text,{color:"yellow",children:"[host]"}),jsx(Text,{color:Jn[o],children:o})]})]})}function ne({items:e,onSelect:t,onClose:o,title:n}){if(e.length===0)return jsxs(Box,{flexDirection:"column",gap:1,children:[n&&jsx(Text,{bold:true,children:n}),jsx(Text,{dimColor:true,children:"(no items)"})]});let r=e.findIndex(i=>!i.disabled),[s,u]=useState(r>=0?r:0);return useInput((i,p)=>{if(p.upArrow)u(c=>{let a=(c-1+e.length)%e.length,l=0;for(;e[a]?.disabled&&l<e.length;)a=(a-1+e.length)%e.length,l++;return a});else if(p.downArrow)u(c=>{let a=(c+1)%e.length,l=0;for(;e[a]?.disabled&&l<e.length;)a=(a+1)%e.length,l++;return a});else if(p.return){let c=e[s];c&&!c.disabled&&t(c.value);}else p.escape&&o?.();}),jsxs(Box,{flexDirection:"column",gap:1,children:[n&&jsx(Text,{bold:true,children:n}),jsx(Box,{flexDirection:"column",children:e.map((i,p)=>{let c=p===s,a=i.disabled??false;return jsxs(Box,{gap:1,children:[jsx(Text,{color:c?"cyan":void 0,dimColor:a,children:c?">":" "}),jsx(Text,{bold:c,dimColor:a,children:i.label}),i.detail&&jsx(Text,{dimColor:true,children:i.detail})]},i.value)})}),jsx(Text,{dimColor:true,children:"\u2191\u2193 select \u23CE confirm esc close"})]})}function W({value:e,onChange:t,onSubmit:o,onCancel:n,placeholder:r,isActive:s=true,mask:u=false}){useInput((a,l)=>{l.escape?n?.():l.return?o(e):l.backspace||l.delete?t(e.slice(0,-1)):a&&!l.ctrl&&!l.meta&&t(e+a);},{isActive:s});let i=u&&e.length>0?"*".repeat(e.length):e,p=i.length>0?i:r??"",c=e.length===0&&r!==void 0;return jsxs(Text,{children:[jsx(Text,{dimColor:c,children:p}),jsx(Text,{inverse:true,children:" "})]})}function bo({mode:e,password:t,onModeChange:o,onPasswordChange:n,onClose:r}){let[s,u]=useState(e==="open"?0:1),[i,p]=useState(false);return useEffect(()=>{u(e==="open"?0:1);},[e]),useInput((c,a)=>{if(a.leftArrow)u(l=>Math.max(0,l-1));else if(a.rightArrow)u(l=>Math.min(1,l+1));else if(a.return){let l=s===0?"open":"password";l!==e&&o(l),l==="password"&&p(true);}else a.escape&&r?.();},{isActive:!i}),jsxs(Box,{flexDirection:"column",gap:1,children:[jsx(Box,{gap:1,children:["open","password"].map((c,a)=>{let l=a===s,S=c===e,E=c==="open"?"Open":"Password";return jsx(Text,{inverse:l,bold:S,color:S?"cyan":void 0,children:S?`[${E}]`:` ${E} `},c)})}),e==="password"&&jsxs(Box,{flexDirection:"column",gap:1,children:[jsx(Text,{children:"Password: "}),jsx(W,{value:t,onChange:n,onSubmit:()=>p(false),onCancel:()=>p(false),placeholder:"(required to join)",isActive:i,mask:true}),!i&&jsx(Text,{dimColor:true,children:"\u23CE edit \u2190 \u2192 switch mode esc close"}),i&&jsx(Text,{dimColor:true,children:"\u23CE done esc cancel"})]}),e==="open"&&jsx(Text,{dimColor:true,children:"\u2190\u2192 toggle \u23CE confirm esc close"})]})}function To({scaleId:e,cards:t,disabledIndices:o,onOverrideChange:n,onClose:r}){let s=useMemo(()=>new Set(o),[o]),[u,i]=useState(0),p=c=>{let a=new Set(s);if(a.has(c))a.delete(c);else {if(a.size>=t.length-1)return;a.add(c);}let l=Array.from(a).sort((S,E)=>S-E);n(e,l);};return useInput((c,a)=>{a.leftArrow?i(l=>Math.max(0,l-1)):a.rightArrow?i(l=>Math.min(t.length-1,l+1)):c===" "?p(u):(a.return||a.escape)&&r();}),jsxs(Box,{flexDirection:"column",gap:1,children:[jsx(Box,{gap:1,children:t.map((c,a)=>{let l=a===u,S=s.has(a);return jsx(Text,{inverse:l,bold:!S,color:S?void 0:"cyan",strikethrough:S,dimColor:S,children:c},c)})}),jsx(Text,{dimColor:true,children:" \u2190\u2192 navigate space toggle \u23CE/esc done"})]})}function Mt({initialName:e="",initialCards:t=[],onSave:o,onCancel:n}){let r=e.length>0,[s,u]=useState(e),[i,p]=useState(t.length>0?t:[""]),[c,a]=useState(0),[l,S]=useState("editing-name"),[E,N]=useState(""),R=()=>{let d=s.trim();if(d.length<1||d.length>40){N("Name must be 1-40 characters");return}u(d),N(""),S("editing-cards");},I=()=>{let d=s.trim();if(d.length<1||d.length>40){N("Name must be 1-40 characters");return}let g=i.map(h=>h.trim()).filter(h=>h.length>0),w=Array.from(new Set(g));if(w.length<2){N("Need at least 2 unique non-empty cards");return}N(""),o(d,w);};return useInput((d,g)=>{if(l==="editing-cards"){if(g.leftArrow)a(w=>Math.max(0,w-1));else if(g.rightArrow)a(w=>{let h=w+1;return h>=i.length&&i.length<25?(p(M=>[...M,""]),h):Math.min(h,i.length-1)});else if(g.return)I();else if(g.escape)n();else if(g.tab)S("editing-name");else if(g.backspace){let w=i[c]||"";if(w.length===0){if(i.length>2){let h=i.filter((M,f)=>f!==c);p(h),a(Math.max(0,c-1));}}else {let h=[...i];h[c]=w.slice(0,-1),p(h);}}else if(d&&!g.ctrl&&!g.meta){let w=i[c]||"";if(w.length<10){let h=[...i];h[c]=w+d,p(h);}}}},{isActive:l==="editing-cards"}),jsxs(Box,{flexDirection:"column",gap:1,children:[jsx(Text,{bold:true,children:r?"\u2500\u2500 Edit Custom Scale \u2500\u2500":"\u2500\u2500 Create Custom Scale \u2500\u2500"}),jsxs(Box,{flexDirection:"column",gap:0,children:[jsx(Text,{children:"Name:"}),jsx(W,{value:s,onChange:u,onSubmit:R,onCancel:n,placeholder:"Scale name",isActive:l==="editing-name"})]}),l==="editing-cards"&&jsxs(Box,{flexDirection:"column",gap:0,children:[jsx(Text,{children:"Cards:"}),jsx(Box,{gap:1,flexWrap:"wrap",children:i.map((d,g)=>{let w=g===c;return jsx(Text,{inverse:w,color:w?void 0:"cyan",children:d.length>0?d:"\xB7"},g)})})]}),E&&jsx(Text,{color:"red",children:E}),jsx(Text,{dimColor:true,children:l==="editing-name"?"\u23CE next esc cancel":"\u2190\u2192 navigate typing adds card bsp delete \u23CE save tab name esc cancel"})]})}function tt({scales:e,selectedScaleId:t,presetOverrides:o,customScales:n=[],onSelect:r,onOverrideChange:s,onCustomScaleCreate:u,onCustomScaleEdit:i,onCustomScaleDelete:p,onClose:c,showNoneOption:a=false}){let[l,S]=useState(0),[E,N]=useState(null),[R,I]=useState("browsing"),[H,d]=useState(null),[g,w]=useState(null),h=u?[...e,{id:"__create-custom__",cards:[]}]:e,M=h.length;if(useInput((f,P)=>{if(R==="browsing"){if(P.escape){c();return}if(M===0)return;if(P.upArrow)S(y=>(y-1+M)%M);else if(P.downArrow)S(y=>(y+1)%M);else if(P.return){let y=h[l];if(!y)return;if(y.id==="__create-custom__"){I("creating");return}y.id in X?N(E===y.id?null:y.id):r(y.id);}else if(f===" "){let y=h[l];y&&y.id!=="__create-custom__"&&r(y.id);}else if(f==="e"||f==="E"){let y=h[l];i&&y?.id.startsWith("custom-")&&(d(y.id),I("editing"));}else if(f==="d"||f==="D"){let y=h[l];p&&y?.id.startsWith("custom-")&&(w(y.id),I("confirming-delete"));}}else R==="confirming-delete"&&(f==="y"||f==="Y"?(g&&p&&p(g),w(null),I("browsing")):(f==="n"||f==="N"||P.escape)&&(w(null),I("browsing")));},{isActive:R==="browsing"&&E===null||R==="confirming-delete"}),R==="creating")return jsx(Mt,{onSave:(f,P)=>{u&&u(f,P),I("browsing");},onCancel:()=>I("browsing")});if(R==="editing"&&H){let f=n?.find(y=>y.id===H);if(e.find(y=>y.id===H)&&f)return jsx(Mt,{initialName:f.label,initialCards:f.cards,onSave:(y,B)=>{i&&i(H,y,B),d(null),I("browsing");},onCancel:()=>{d(null),I("browsing");}})}if(R==="confirming-delete"&&g){let f=h.find(y=>y.id===g),P=f?pe(f.id):"Unknown";return jsxs(Box,{flexDirection:"column",gap:1,children:[jsxs(Text,{children:["Delete ",P,"? "]}),jsx(Text,{children:"[y] yes [n] no esc cancel"})]})}return h.length===0?jsxs(Box,{flexDirection:"column",gap:1,children:[jsx(Text,{dimColor:true,children:"(no scales available)"}),jsx(Text,{dimColor:true,children:"esc back"})]}):jsxs(Box,{flexDirection:"column",gap:1,children:[jsx(Box,{flexDirection:"column",children:h.map((f,P)=>{let y=P===l,B=f.id===t,J=E===f.id,V=f.id in X;f.id.startsWith("custom-");let U=f.id==="__create-custom__",x=f.cards.join(", "),C=a&&f.id==="",T=C?"(none)":U?"[+ Create custom scale]":pe(f.id);return jsxs(Box,{flexDirection:"column",children:[jsxs(Box,{gap:1,children:[jsx(Text,{color:y?"cyan":void 0,children:y?">":" "}),jsx(Text,{bold:y,children:T}),!C&&!U&&jsx(Text,{dimColor:true,children:x}),B&&jsx(Text,{color:"cyan",children:"[current]"})]}),J&&!C&&!U&&jsx(Box,{paddingLeft:2,children:V?jsx(To,{scaleId:f.id,cards:X[f.id]?.cards??[],disabledIndices:o?.[f.id]??[],onOverrideChange:s,onClose:()=>N(null)}):null})]},f.id||"none")})}),jsx(Text,{dimColor:true,children:(()=>{let f=l<h.length?h[l]:null;return f?.id.startsWith("custom-")?`\u2191\u2193 navigate space select${i?" [e] edit":""}${p?" [d] delete":""} esc back`:f&&f.id in X?"\u2191\u2193 navigate \u23CE customize space select esc back":"\u2191\u2193 navigate \u23CE/space select esc back"})()})]})}function sr(e,t){return t?[...e]:e.filter(o=>o.value==="display-name")}function Eo({sessionName:e,displayName:t,currentScale:o,accessMode:n,presetOverrides:r,customScales:s,isHost:u=true,onChangeName:i,onChangeScale:p,onChangeDisplayName:c,onChangeAccess:a,onClose:l}){let[S,E]=useState(r??{}),N=useMemo(()=>Je(S,s),[S,s]),[R,I]=useState("main-menu"),[H,d]=useState(e??""),[g,w]=useState(t),[h,M]=useState(n==="password"?"password":"open"),[f,P]=useState(""),[y,B]=useState(false),J=[{label:"Room name",value:"room-name",detail:`[${e??"(none)"}]`},{label:"Scale",value:"scale",detail:`[${pe(o.id)}]`},{label:"Access",value:"access",detail:`[${n==="open"?"Open":"Password"}]`},{label:"Display name",value:"display-name",detail:`[${t}]`}],V=sr(J,u);function G(m){!u&&m!=="display-name"||(m==="room-name"?(d(e??""),I("editing-room-name")):m==="scale"?I("picking-scale"):m==="access"?I("picking-access"):m==="display-name"&&(w(t),I("editing-display-name")));}function U(m){let O=m.trim()===""?void 0:m.trim();i(O),I("main-menu");}function x(m){let O=m.trim();O.length>0&&(c(O),process.nextTick(()=>$({displayName:O}))),I("main-menu");}function C(m){let O=N.find(K=>K.id===m);O&&p(O),I("main-menu");}function T(m,O){let K={...S};O.length===0?delete K[m]:K[m]=O,E(K),$({presetOverrides:Object.keys(K).length>0?K:void 0});}async function _(m,O){B(true);try{await a(m,O),M(m),P(m==="password"?O??"":"");}catch(K){console.error(`Failed to update access: ${K}`);}finally{B(false),I("main-menu");}}return R==="main-menu"?jsx(ne,{title:"\u2500\u2500 Settings \u2500\u2500",items:V,onSelect:G,onClose:l}):R==="editing-room-name"?jsxs(Box,{flexDirection:"column",gap:1,children:[jsx(Text,{bold:true,children:"\u2500\u2500 Settings \u2500\u2500"}),jsx(Text,{children:"Room name: "}),jsx(W,{value:H,onChange:d,onSubmit:U,onCancel:()=>I("main-menu"),placeholder:"(leave empty to remove)",isActive:true}),jsx(Text,{dimColor:true,children:"\u23CE confirm esc back"})]}):R==="editing-display-name"?jsxs(Box,{flexDirection:"column",gap:1,children:[jsx(Text,{bold:true,children:"\u2500\u2500 Settings \u2500\u2500"}),jsx(Text,{children:"Display name: "}),jsx(W,{value:g,onChange:w,onSubmit:x,onCancel:()=>I("main-menu"),placeholder:"(required)",isActive:true}),jsx(Text,{dimColor:true,children:"\u23CE confirm esc back"})]}):R==="picking-scale"?jsxs(Box,{flexDirection:"column",gap:1,children:[jsx(Text,{bold:true,children:"\u2500\u2500 Settings \u2500\u2500"}),jsx(tt,{scales:N,selectedScaleId:o.id,presetOverrides:S,onSelect:C,onOverrideChange:T,onClose:()=>I("main-menu")})]}):R==="picking-access"?jsxs(Box,{flexDirection:"column",gap:1,children:[jsx(Text,{bold:true,children:"\u2500\u2500 Settings \u2500\u2500"}),y&&jsx(Text,{dimColor:true,children:"Updating access..."}),!y&&jsx(bo,{mode:h,password:f,onModeChange:m=>{m==="open"&&_("open");},onPasswordChange:P,onClose:()=>I("main-menu")})]}):jsx(Box,{})}function st({participants:e,onlineIds:t,hostId:o,votes:n,revealed:r}){return jsxs(Box,{flexDirection:"column",children:[jsxs(Text,{bold:true,children:["Participants (",e.length,")"]}),e.map(s=>{let u=t.has(s.id),i=u?"\u25CF":"\u25CB",p=u?"green":"gray",c=s.id===o,a=ue(s)==="viewer",l=s.id in n,S=a?" (viewer)":r?n[s.id]?` \u2192 ${n[s.id]}`:"":l?" \u2713":" \xB7";return jsxs(Box,{gap:1,children:[jsx(Text,{color:p,children:i}),jsxs(Text,{children:[s.displayName,c?" (host)":""]}),jsx(Text,{color:a?"gray":l?"green":"gray",children:S})]},s.id)})]})}var ir=[{label:"End session for everyone",value:"end-session",detail:"Disconnects all participants. Cannot be undone."}];function No({onPick:e,onClose:t}){return jsx(ne,{title:"Host Actions",items:ir,onSelect:e,onClose:t})}function it({roomCode:e,onPick:t,onClose:o}){let n=[{label:"Copy code",value:"code",detail:ze(e)},{label:"Copy share link",value:"url",detail:Ke(e)},{label:"Copy npx command",value:"npx",detail:Xe(e)}];return jsx(ne,{title:"Share",items:n,onSelect:r=>t(r),onClose:o})}function dr(e){let t=Object.values(e).map(a=>parseFloat(a)).filter(a=>!isNaN(a)).sort((a,l)=>a-l);if(t.length===0)return {average:"-",median:"-",agreement:"-"};let n=t.reduce((a,l)=>a+l,0)/t.length,r=Math.floor(t.length/2),s=t.length%2===0?(t[r-1]+t[r])/2:t[r],u=Object.values(e),i=new Map;for(let a of u)i.set(a,(i.get(a)??0)+1);let p=Math.max(...i.values()),c=u.length>0?Math.round(p/u.length*100):0;return {average:n.toFixed(1),median:s.toFixed(1),agreement:`${c}%`}}function Oo(e,t){let o=Math.floor((t-e)/1e3),n=Math.floor(o/60),r=o%60;return `${n}:${r.toString().padStart(2,"0")}`}function ur(e,t){let o=new Map;for(let u of Object.values(e.votes))o.set(u,(o.get(u)??0)+1);let n=[...o.entries()].sort((u,i)=>i[1]-u[1]||u[0].localeCompare(i[0])).map(([u,i])=>`${u}\xD7${i}`).join(", "),r=e.title?` ${e.title}`:"",s=e.timerStartedAt!==void 0&&e.revealedAt!==void 0?` \u2014 ${Oo(e.timerStartedAt,e.revealedAt)}`:"";return `Round ${t}:${r} \u2014 ${n||"(no votes)"}${s}`}function Do({projection:e,onlineIds:t,isHost:o,participantId:n,rounds:r,currentRound:s,settingsOpen:u,onStartRound:i,onNewRound:p,onRevote:c,onOpenSettings:a,onHandoffHost:l,onEndSession:S,onShare:E,shareOpen:N,onSetShareOpen:R,roomCode:I,confirmingQuit:H,roundTitlePromptActive:d,roundTitleInput:g,onRoundTitleChange:w,onRoundTitleSubmit:h,onRoundTitleCancel:M}){let[f,P]=at.useState("idle"),[y,B]=at.useState(null),[J,V]=at.useState(false),[G,U]=at.useState(false);useEffect(()=>{o||(P("idle"),B(null),V(false),U(false));},[o]),useInput(m=>{if(m==="c"||m==="C"){R(true);return}o&&((m==="e"||m==="E")&&a(),(m==="s"||m==="S")&&i(),(m==="h"||m==="H")&&P("picking"),(m==="m"||m==="M")&&V(true),s?.revealedAt!==void 0&&((m==="n"||m==="N")&&p(),(m==="v"||m==="V")&&c()));},{isActive:!u&&!H&&f==="idle"&&!d&&!J&&!G&&!N}),useInput((m,O)=>{m==="y"||m==="Y"?(S(),U(false)):(m==="n"||m==="N"||O.escape)&&U(false);},{isActive:G});let x=e.participants.filter(m=>m.id!==n&&t.has(m.id));useInput(m=>{if(f==="picking"){let O=parseInt(m,10);!isNaN(O)&&O>=1&&O<=x.length?(B(x[O-1].id),P("confirming")):(m==="Escape"||m==="q"||m==="Q")&&P("idle");}},{isActive:f==="picking"}),useInput(m=>{f==="confirming"&&y&&(m==="y"||m==="Y"?(l(y),P("idle"),B(null)):(m==="n"||m==="N"||m==="Escape")&&(P("idle"),B(null)));},{isActive:f==="confirming"});let C=r.length>0,T=r.filter(m=>m.revealedAt!==void 0),_=s!==null?T.slice(0,-1):T;return J?jsx(No,{onPick:m=>{V(false),m==="end-session"&&U(true);},onClose:()=>V(false)}):N?jsx(it,{roomCode:I,onPick:m=>{R(false),E(m);},onClose:()=>R(false)}):jsxs(Box,{flexDirection:"column",gap:1,children:[jsx(Text,{bold:true,children:C?"Session":"Waiting for participants..."}),!C&&jsx(Text,{dimColor:true,children:"No rounds started yet."}),jsx(st,{participants:e.participants,onlineIds:t,hostId:e.settings.hostParticipantId,votes:{},revealed:false}),s?.revealedAt!==void 0&&jsx(pr,{projection:e,round:s,rounds:r}),_.length>0&&jsxs(Box,{flexDirection:"column",children:[jsx(Text,{dimColor:true,children:"\u2500\u2500 History \u2500\u2500"}),_.map(m=>{let O=r.indexOf(m)+1;return jsx(Text,{dimColor:true,children:ur(m,O)},m.id)})]}),f==="picking"&&x.length>0&&jsxs(Box,{flexDirection:"column",gap:1,children:[jsx(Text,{children:"Hand off to:"}),x.map((m,O)=>jsxs(Text,{children:["[",O+1,"] ",m.displayName]},m.id)),jsx(Text,{dimColor:true,children:"[q] Cancel"})]}),f==="picking"&&x.length===0&&jsxs(Box,{flexDirection:"column",gap:1,children:[jsx(Text,{dimColor:true,children:"No other online participants"}),jsx(Text,{dimColor:true,children:"[q] Cancel"})]}),f==="confirming"&&y&&jsx(Box,{flexDirection:"column",children:jsxs(Text,{children:["Hand off to ",e.participants.find(m=>m.id===y)?.displayName,"? [y/n]"]})}),d&&jsxs(Box,{flexDirection:"column",children:[jsxs(Box,{gap:1,children:[jsx(Text,{children:"Round title (optional, Enter to skip):"}),jsx(W,{value:g,onChange:w,onSubmit:h,onCancel:M,placeholder:"Enter to skip",isActive:d})]}),jsx(Text,{dimColor:true,children:"[Esc] Cancel"})]}),G&&jsxs(Box,{children:[jsx(Text,{color:"red",children:"End session for everyone? This cannot be undone. "}),jsx(Text,{dimColor:true,children:"y/n [Esc] Cancel"})]}),f==="idle"&&!d&&!G&&jsxs(Box,{gap:2,children:[o?jsx(Fragment,{children:s?.revealedAt!==void 0?jsxs(Fragment,{children:[jsx(Text,{color:"yellow",children:"[v] Revote"}),jsx(Text,{color:"yellow",children:"[n] Next"}),jsx(Text,{color:"yellow",children:"[h] Handoff"}),jsx(Text,{color:"yellow",children:"[e] Settings"}),jsx(Text,{color:"yellow",children:"[c] Share"}),jsx(Text,{color:"yellow",children:"[m] Menu"})]}):jsxs(Fragment,{children:[jsx(Text,{color:"yellow",children:"[s] Start round"}),jsx(Text,{color:"yellow",children:"[h] Handoff"}),jsx(Text,{color:"yellow",children:"[e] Settings"}),jsx(Text,{color:"yellow",children:"[c] Share"}),jsx(Text,{color:"yellow",children:"[m] Menu"})]})}):jsxs(Fragment,{children:[jsx(Text,{dimColor:true,children:"Waiting for host to start a round..."}),jsx(Text,{children:"[c] Share"})]}),jsx(Text,{children:"[q] Quit"})]})]})}function pr({projection:e,round:t,rounds:o}){let n=o.indexOf(t)+1,r=t.title?` \u2014 ${t.title}`:"",s=dr(t.votes),u=new Map(e.participants.map(c=>[c.id,c.displayName])),i=t.timerStartedAt!==void 0&&t.revealedAt!==void 0?Oo(t.timerStartedAt,t.revealedAt):null,p=Object.values(t.votes)[0];return jsxs(Box,{flexDirection:"column",gap:1,children:[jsxs(Text,{bold:true,children:["Round ",n,r," \u2014 Results"]}),to(t)&&jsxs(Text,{bold:true,color:"green",children:["Consensus! Everyone voted ",p]}),jsxs(Box,{flexDirection:"column",children:[jsxs(Box,{gap:2,children:[jsx(Text,{bold:true,underline:true,children:"Name".padEnd(20)}),jsx(Text,{bold:true,underline:true,children:"Estimate".padEnd(10)})]}),Object.entries(t.votes).map(([c,a])=>jsxs(Box,{gap:2,children:[jsx(Text,{children:(u.get(c)??c).padEnd(20)}),jsx(Text,{color:"cyan",bold:true,children:a.padEnd(10)})]},c)),e.participants.filter(c=>!(c.id in t.votes)).map(c=>jsxs(Box,{gap:2,children:[jsx(Text,{dimColor:true,children:c.displayName.padEnd(20)}),jsx(Text,{dimColor:true,children:"(no vote)".padEnd(10)})]},c.id))]}),jsxs(Box,{gap:2,children:[jsxs(Text,{children:["Avg: ",jsx(Text,{bold:true,children:s.average})]}),jsxs(Text,{children:["Med: ",jsx(Text,{bold:true,children:s.median})]}),jsxs(Text,{children:["Agreement: ",jsx(Text,{bold:true,children:s.agreement})]}),i!==null&&jsxs(Text,{dimColor:true,children:["\u23F1 ",i]})]})]})}function Bo({scale:e,selected:t,onSelect:o,onClear:n,disabled:r}){let[s,u]=useState(0);return useInput((i,p)=>{if(!r)if(p.leftArrow)u(c=>Math.max(0,c-1));else if(p.rightArrow)u(c=>Math.min(e.cards.length-1,c+1));else if(p.return){let c=e.cards[s];c===t?n():o(c);}else (p.backspace||p.delete)&&n();}),jsxs(Box,{flexDirection:"column",gap:0,children:[jsx(Box,{gap:1,children:e.cards.map((i,p)=>{let c=p===s,a=i===t;return jsx(Text,{inverse:c,bold:a,color:a?"cyan":void 0,children:a?`[${i}]`:` ${i} `},i)})}),jsx(Text,{dimColor:true,children:" \u2190\u2192 navigate \u23CE select \u232B clear"})]})}function Sr(e){let t=Math.floor(e/1e3),o=Math.floor(t/60),n=t%60;return `${o}:${n.toString().padStart(2,"0")}`}function Uo({projection:e,round:t,onlineIds:o,isHost:n,myId:r,myRole:s,elapsedMs:u,roomCode:i,shareOpen:p,settingsOpen:c,onVote:a,onClearVote:l,onReveal:S,onToggleRole:E,onShare:N,onSetShareOpen:R,onOpenSettings:I,confirmingQuit:H}){if(useInput(h=>{if(h==="e"||h==="E"){I();return}if(h==="c"||h==="C"){R(true);return}n&&(h==="r"||h==="R")&&S(),(h==="w"||h==="W")&&E();},{isActive:!H&&!c&&!p}),p)return jsx(it,{roomCode:i,onPick:h=>{R(false),N(h);},onClose:()=>R(false)});let d=t.votes[r],g=e.rounds.indexOf(t)+1,w=t.title?` \u2014 ${t.title}`:"";return jsxs(Box,{flexDirection:"column",gap:1,children:[jsxs(Box,{gap:2,children:[jsxs(Text,{bold:true,children:["Round ",g,w]}),u!==void 0&&jsxs(Text,{dimColor:true,children:["\u23F1 ",Sr(u)]})]}),jsxs(Box,{gap:4,children:[jsx(st,{participants:e.participants,onlineIds:o,hostId:e.settings.hostParticipantId,votes:t.votes,revealed:false}),s==="viewer"?jsx(Box,{flexDirection:"column",children:jsx(Text,{dimColor:true,children:"You're observing"})}):jsx(Bo,{scale:t.scale,selected:d,onSelect:a,onClear:l,disabled:H})]}),jsxs(Box,{gap:2,children:[n&&jsx(Text,{color:"yellow",children:"[r] Reveal"}),jsxs(Text,{color:"yellow",children:["[w] ",s==="viewer"?"Vote":"Observe"]}),jsx(Text,{children:"[e] Settings"}),jsx(Text,{children:"[c] Share"}),jsx(Text,{children:"[q] Quit"})]})]})}var Cr=3e3;function Ut(e=Cr){let[t,o]=useState(null),n=useRef(null),r=useCallback(()=>{n.current!==null&&(clearTimeout(n.current),n.current=null);},[]),s=useCallback(i=>{r(),o(i),n.current=setTimeout(()=>{o(null),n.current=null;},e);},[r,e]),u=useCallback(()=>{r(),o(null);},[r]);return useEffect(()=>()=>{r();},[r]),{notice:t,show:s,dismiss:u}}function Ir(e){let t=Z(e);return !t||t.revealedAt!==void 0?"lobby":"voting"}function ut({session:e,participantId:t,displayName:o,accessMode:n,presetOverrides:r,customScales:s}){let{exit:u}=useApp(),[i,p]=useState(e.projection()),[c,a]=useState("connecting"),[l,S]=useState(e.onlineIds()),[E,N]=useState(void 0),[R,I]=useState(false),[H,d]=useState(n==="password"?"password":"open"),[g,w]=useState(o),[h,M]=useState(false),[f,P]=useState(false),[y,B]=useState(""),J=Ut(3e3),[V,G]=useState(false),U=Ut(3e3);useEffect(()=>{let A=e.onChange(()=>{p(e.projection()),S(e.onlineIds());}),Q=e.onStatusChange(a),nn=e.onHostChange(Ue=>{let sn=e.projection().participants.find(cn=>cn.id===Ue),an=Ue===t?"You are now the host":`${sn?.displayName||"Unknown"} is now the host`;J.show(an);}),rn=setInterval(()=>{S(e.onlineIds());let Ue=e.projection(),je=Z(Ue);je?.timerStartedAt!==void 0&&je.revealedAt===void 0?N(Date.now()-je.timerStartedAt):N(void 0);},1e3);return ()=>{A(),Q(),nn(),clearInterval(rn);}},[e,t,J.show]),useInput(A=>{(A==="q"||A==="Q")&&M(true);},{isActive:!R&&!h&&!f&&!V}),useInput((A,Q)=>{A==="y"||A==="Y"?(e.disconnect(),u()):(A==="n"||A==="N"||Q.escape)&&M(false);},{isActive:h});let x=Ir(i),C=Z(i),T=i.settings.hostParticipantId===t,_=i.participants.find(A=>A.id===t),m=ue(_??{}),O=useCallback(()=>{B(""),P(true);},[]),K=useCallback(A=>{P(false),B(""),e.newRound(A.trim()||void 0);},[e]),Fo=useCallback(()=>{P(false),B("");},[]),Yo=useCallback(A=>{e.vote(A);},[e]),Vo=useCallback(()=>{e.unvote();},[e]),Jo=useCallback(()=>{e.reveal();},[e]),Wo=useCallback(()=>{B(""),P(true);},[]),qo=useCallback(()=>{e.revote();},[e]),Go=useCallback(()=>{e.toggleRole();},[e]),Qo=useCallback(A=>{e.updateName(A);},[e]),zo=useCallback(A=>{e.updateScale(A);},[e]),Ko=useCallback(A=>{e.updateDisplayName(A),w(A);},[e]),Xo=useCallback(async(A,Q)=>{await e.updateAccess(A,Q),d(A);},[e]),Zo=useCallback(A=>{try{e.handoffHost(A);}catch(Q){console.error(`Handoff failed: ${Q instanceof Error?Q.message:String(Q)}`);}},[e]),_t=useCallback(()=>{I(true);},[]),en=useCallback(()=>{I(false);},[]),tn=useCallback(()=>{e.endSessionForEveryone();},[e]),$t=useCallback(async A=>{let Q=A==="code"?ze(e.roomCode):A==="url"?Ke(e.roomCode):Xe(e.roomCode);try{await Rr.write(Q),U.show("Copied!");}catch{U.show(`Copy failed \u2014 ${Q}`);}},[e.roomCode,U]),ft=i.rounds.filter(A=>A.revealedAt!==void 0),on=ft.length>0?ft[ft.length-1]:null;return jsxs(Box,{flexDirection:"column",children:[jsx(yo,{roomCode:e.roomCode,sessionName:i.settings.name,status:c,isHost:T}),J.notice&&jsx(Box,{paddingX:1,children:jsx(Text,{color:"yellow",children:J.notice})}),U.notice&&jsx(Box,{paddingX:1,children:jsx(Text,{color:U.notice.startsWith("Copy failed")?"red":"green",children:U.notice})}),R?jsx(Eo,{sessionName:i.settings.name,displayName:g,currentScale:i.settings.defaultScale,accessMode:H,presetOverrides:r,customScales:s,isHost:T,onChangeName:Qo,onChangeScale:zo,onChangeDisplayName:Ko,onChangeAccess:Xo,onClose:en}):x==="lobby"?jsx(Do,{projection:i,onlineIds:l,isHost:T,participantId:t,rounds:i.rounds,currentRound:on,settingsOpen:R,onStartRound:O,onNewRound:Wo,roundTitlePromptActive:f,roundTitleInput:y,onRoundTitleChange:B,onRoundTitleSubmit:K,onRoundTitleCancel:Fo,onRevote:qo,onOpenSettings:_t,onHandoffHost:Zo,onEndSession:tn,onShare:$t,shareOpen:V,onSetShareOpen:G,roomCode:e.roomCode,confirmingQuit:h}):x==="voting"&&C?jsx(Uo,{projection:i,round:C,onlineIds:l,isHost:T,myId:t,myRole:m,elapsedMs:E,onVote:Yo,onClearVote:Vo,onReveal:Jo,onToggleRole:Go,onShare:$t,onSetShareOpen:G,shareOpen:V,settingsOpen:R,onOpenSettings:_t,roomCode:e.roomCode,confirmingQuit:h}):null,h&&jsx(Box,{children:jsx(Text,{dimColor:true,children:"Leave session? y/n"})})]})}function Lo({presetOverrides:e,customScales:t}){let{exit:o}=useApp(),[n,r]=useState("main-menu"),[s,u]=useState(de()),[i,p]=useState(s.displayName??""),[c,a]=useState(s.relayUrl??""),[l,S]=useState(s.defaultSessionName??""),[E,N]=useState(t??[]),[R,I]=useState(e??{});useInput(x=>{(x==="q"||x==="Q")&&o();},{isActive:n==="main-menu"});let H=useMemo(()=>Je(R,E),[R,E]),d=s.defaultScale,g=d?H.find(x=>x.id===d):void 0,w=[{label:"Display name",value:"display-name",detail:`[${s.displayName??"(none)"}]`},{label:"Relay URL",value:"relay-url",detail:`[${s.relayUrl??"(none)"}]`},{label:"Default scale",value:"default-scale",detail:`[${g?pe(g.id):"(none)"}]`},{label:"Default session name",value:"default-session-name",detail:`[${s.defaultSessionName??"(none)"}]`},{label:"Default password",value:"default-password",detail:`[${s.defaultPassword?"on":"off"}]`}];function h(x){x==="display-name"?(p(s.displayName??""),r("editing-display-name")):x==="relay-url"?(a(s.relayUrl??""),r("editing-relay-url")):x==="default-scale"?r("picking-scale"):x==="default-session-name"?(S(s.defaultSessionName??""),r("editing-session-name")):x==="default-password"&&r("picking-default-password");}function M(x){let C=x.trim();if(C.length>0){let T={...s,displayName:C};u(T),$({displayName:C});}r("main-menu");}function f(x){let C=x.trim();if(C.length>0){let T={...s,relayUrl:C};u(T),$({relayUrl:C});}r("main-menu");}function P(x){let C=x.trim(),T=C.length>0?{...s,defaultSessionName:C}:{...s};C.length===0?(delete T.defaultSessionName,u(T),Ee({defaultSessionName:""})):(u(T),$({defaultSessionName:C})),r("main-menu");}function y(x){let C=x.length>0?{...s,defaultScale:x}:{...s};x.length===0?(delete C.defaultScale,u(C),Ee({defaultScale:""})):(u(C),$({defaultScale:x})),r("main-menu");}function B(x,C){let T={...R};C.length===0?delete T[x]:T[x]=C,I(T),$({presetOverrides:Object.keys(T).length>0?T:void 0});}function J(x){let C=x==="on",T={...s,defaultPassword:C};u(T),$({defaultPassword:C}),r("main-menu");}function V(x,C){let T=`custom-${randomUUID()}`,_=[...E,{id:T,label:x,cards:C}];N(_),u({...s,customScales:_}),$({customScales:_});}function G(x,C,T){let _=E.map(m=>m.id===x?{id:x,label:C,cards:T}:m);N(_),u({...s,customScales:_}),$({customScales:_});}function U(x){let C=E.filter(_=>_.id!==x);N(C);let T={...s,customScales:C};s.defaultScale===x?(T.defaultScale="fibonacci",$({customScales:C,defaultScale:"fibonacci"})):$({customScales:C}),u(T);}if(n==="main-menu")return jsx(ne,{title:"\u2500\u2500 Config Settings \u2500\u2500",items:w,onSelect:h,onClose:()=>o()});if(n==="editing-display-name")return jsxs(Box,{flexDirection:"column",gap:1,children:[jsx(Text,{bold:true,children:"\u2500\u2500 Config Settings \u2500\u2500"}),jsx(Text,{children:"Display name: "}),jsx(W,{value:i,onChange:p,onSubmit:M,onCancel:()=>r("main-menu"),placeholder:"(required)",isActive:true}),jsx(Text,{dimColor:true,children:"\u23CE confirm esc back"})]});if(n==="editing-relay-url")return jsxs(Box,{flexDirection:"column",gap:1,children:[jsx(Text,{bold:true,children:"\u2500\u2500 Config Settings \u2500\u2500"}),jsx(Text,{children:"Relay URL: "}),jsx(W,{value:c,onChange:a,onSubmit:f,onCancel:()=>r("main-menu"),placeholder:"(required)",isActive:true}),jsx(Text,{dimColor:true,children:"\u23CE confirm esc back"})]});if(n==="editing-session-name")return jsxs(Box,{flexDirection:"column",gap:1,children:[jsx(Text,{bold:true,children:"\u2500\u2500 Config Settings \u2500\u2500"}),jsx(Text,{children:"Default session name: "}),jsx(W,{value:l,onChange:S,onSubmit:P,onCancel:()=>r("main-menu"),placeholder:"(leave empty to clear)",isActive:true}),jsx(Text,{dimColor:true,children:"\u23CE confirm esc back"})]});if(n==="picking-scale"){let x=[{id:"",cards:[]},...H];return jsxs(Box,{flexDirection:"column",gap:1,children:[jsx(Text,{bold:true,children:"\u2500\u2500 Config Settings \u2500\u2500"}),jsx(tt,{scales:x,selectedScaleId:d??"",presetOverrides:R,customScales:E,onSelect:y,onOverrideChange:B,onCustomScaleCreate:V,onCustomScaleEdit:G,onCustomScaleDelete:U,onClose:()=>r("main-menu"),showNoneOption:true})]})}if(n==="picking-default-password"){let x=[{label:"On",value:"on",detail:s.defaultPassword?"[current]":void 0},{label:"Off",value:"off",detail:s.defaultPassword?void 0:"[current]"}];return jsxs(Box,{flexDirection:"column",gap:1,children:[jsx(Text,{bold:true,children:"\u2500\u2500 Config Settings \u2500\u2500"}),jsx(ne,{title:"Default password:",items:x,onSelect:J,onClose:()=>r("main-menu")})]})}return jsx(Box,{})}async function kt(){return new Promise(e=>{let t=createInterface({input:process.stdin,output:process.stderr});process.stderr.write("Password: "),t.question("",o=>{t.close(),e(o);});})}async function Hr(e){let t=await Te(),o=e.scale??t.defaultScale??"fibonacci",n=e.name??t.defaultSessionName,r=e.password??t.defaultPassword??false,s;r&&(s=await kt());let u=Ve(o,t.presetOverrides,t.customScales);if(!u){let l=Object.keys(X).join(", ");console.error(`Unknown or fully-disabled scale: "${o}". Builtin IDs: ${l}`),process.exit(1);}let i=await po({participantId:t.participantId,displayName:t.displayName,relayUrl:t.relayUrl,sessionName:n,password:s,defaultScale:u}),p;s&&(p=await me(s,i.roomUuid)),qe({roomCode:i.roomCode,roomUuid:i.roomUuid,passwordHash:p,lastSeenAt:Date.now()}),console.error(`Room created: ${i.roomCode}`),console.error(`Share: hnch join ${i.roomCode}`),console.error("");let{waitUntilExit:c,unmount:a}=render(at.createElement(ut,{session:i,participantId:t.participantId,displayName:t.displayName,accessMode:s?"password":"open",presetOverrides:t.presetOverrides,customScales:t.customScales}));Qe({doc:i.doc,localParticipantId:t.participantId,onEnded:async l=>{console.error(l.isLocal?"Session ended.":`Session ended by ${l.hostName}.`),a();try{await k();}catch(S){console.error(`Failed to clear cached session: ${S instanceof Error?S.message:String(S)}`);}i.disconnect(),process.exit(0);}}),await c(),i.disconnect(),k(),process.exit(0);}async function Br(e){let t=await Te(),o=fo(e.code);o===null&&(console.error("invalid room code"),process.exit(1));let n;e.password&&(n=await kt());let r=await Pe(t.relayUrl,o),s;r.accessMode==="password"&&!e.password?s=await kt():e.password&&(s=n);let u,i=false,p=await mo({participantId:t.participantId,displayName:t.displayName,relayUrl:t.relayUrl,roomCode:o,password:s,onRejection(l){if(l==="unknown-room"){Ge({clearLastSession:k,logger:console,exit:process.exit});return}l==="room-full"&&(i=true,u?.());}}),c;s&&(c=await me(s,p.roomUuid)),qe({roomCode:p.roomCode,roomUuid:p.roomUuid,passwordHash:c,lastSeenAt:Date.now()}),console.error(`Joined room: ${p.roomCode}`),console.error("");let a=render(at.createElement(ut,{session:p,participantId:t.participantId,displayName:t.displayName,accessMode:r.accessMode,presetOverrides:t.presetOverrides,customScales:t.customScales}));u=a.unmount,Qe({doc:p.doc,localParticipantId:t.participantId,onEnded:async l=>{console.error(l.isLocal?"Session ended.":`Session ended by ${l.hostName}.`),u?.();try{await k();}catch(S){console.error(`Failed to clear cached session: ${S instanceof Error?S.message:String(S)}`);}p.disconnect(),process.exit(0);}}),await a.waitUntilExit(),i&&(console.error("This room is full (max 20 participants)."),p.disconnect(),k(),process.exit(1)),p.disconnect(),k(),process.exit(0);}async function Ur(){let e=await Te(),t=ho();t||(console.error("No recent session to resume."),process.exit(0));let o;try{o=await Pe(e.relayUrl,t.roomCode);}catch(a){if((a instanceof Error?a.message:String(a)).includes("Room not found"))return await Ge({clearLastSession:k,logger:console,exit:process.exit});console.error("Could not reach relay \u2014 try again later."),process.exit(0);}o.accessMode==="password"&&!t.passwordHash&&(console.error(`Password changed \u2014 rejoin with: hnch join ${t.roomCode} --password`),k(),process.exit(0)),console.error(`Rejoining room ${t.roomCode}\u2026`),console.error("");let n=new we.Doc,r=new Awareness(n);Ye(n,{participantId:e.participantId,displayName:e.displayName}),r.setLocalStateField("participantId",e.participantId);let s=false,u=false,i,p=We({doc:n,awareness:r,roomCode:t.roomCode,roomUuid:t.roomUuid,participantId:e.participantId,relayUrl:e.relayUrl,passwordHash:t.passwordHash,onRejection(a){if(a==="unknown-room"){Ge({clearLastSession:k,logger:console,exit:process.exit});return}a==="wrong-password"&&(s=true,i?.()),a==="room-full"&&(u=true,i?.());}});qe({roomCode:p.roomCode,roomUuid:p.roomUuid,passwordHash:t.passwordHash,lastSeenAt:Date.now()});let c=render(at.createElement(ut,{session:p,participantId:e.participantId,displayName:e.displayName,accessMode:o.accessMode,presetOverrides:e.presetOverrides,customScales:e.customScales}));i=c.unmount,Qe({doc:p.doc,localParticipantId:e.participantId,onEnded:async a=>{console.error(a.isLocal?"Session ended.":`Session ended by ${a.hostName}.`),i?.();try{await k();}catch(l){console.error(`Failed to clear cached session: ${l instanceof Error?l.message:String(l)}`);}p.disconnect(),process.exit(0);}}),await c.waitUntilExit(),u&&(console.error("This room is full (max 20 participants)."),k(),process.exit(1)),s&&(console.error(`Password changed \u2014 rejoin with: hnch join ${t.roomCode} --password`),k(),process.exit(0)),p.disconnect(),k(),process.exit(0);}async function jr(e){if(!(e.name!==void 0||e.relayUrl!==void 0||e.scale!==void 0||e.sessionName!==void 0||e.password!==void 0)){let n=await Te(),{waitUntilExit:r}=render(at.createElement(Lo,{presetOverrides:n.presetOverrides,customScales:n.customScales}));await r(),process.exit(0);}let o={};if(e.name!==void 0&&(o.displayName=e.name),e.relayUrl!==void 0&&(o.relayUrl=e.relayUrl),e.scale!==void 0){if(e.scale!==""){let n=de();if(!Ve(e.scale,n.presetOverrides,n.customScales)){console.error(`Unknown scale: "${e.scale}". Builtin IDs: ${Object.keys(X).join(", ")}`);return}}o.defaultScale=e.scale;}e.sessionName!==void 0&&(o.defaultSessionName=e.sessionName),e.password!==void 0&&(o.defaultPassword=e.password),Ee(o),console.log("Config updated.");}var $o=Or(hideBin(process.argv)).scriptName("hnch").usage("$0 <command> \u2014 terminal pointing poker").command("create","Create a new room",e=>e.option("name",{type:"string",describe:"Session name"}).option("password",{type:"boolean",describe:"Protect room with a password"}).option("scale",{type:"string",describe:"Estimation scale ID (fibonacci, t-shirt, powers-of-2, linear, risk, or custom-<id>)"}),e=>{Hr(e).catch(t=>{console.error(t instanceof Error?t.message:t),process.exit(1);});}).command("join <code>","Join an existing room",e=>e.positional("code",{type:"string",demandOption:true,describe:"6-character room code"}).option("password",{type:"boolean",describe:"Room requires a password",default:false}),e=>{Br(e).catch(t=>{console.error(t instanceof Error?t.message:t),process.exit(1);});}).command("resume","Rejoin your last session",e=>e,()=>{Ur().catch(e=>{console.error(e instanceof Error?e.message:e),process.exit(1);});}).command("config","View or update CLI config",e=>e.option("name",{type:"string",describe:"Set display name"}).option("relay-url",{type:"string",describe:"Set relay URL"}).option("scale",{type:"string",describe:"Set default estimation scale ID (empty string clears)"}).option("session-name",{type:"string",describe:"Set default session name (empty string clears)"}).option("password",{type:"boolean",describe:"Set default password prompt on (--password) or off (--no-password)"}),e=>{jr(e).catch(t=>{console.error(t instanceof Error?t.message:t),process.exit(1);});}).demandCommand(1,"Specify a command: create, join, resume, or config").strict().help(),Lt=hideBin(process.argv);(Lt.length===0||Lt.length===1&&Lt[0]==="--")&&($o.showHelp(),process.exit(0));$o.parse();
|
|
2
|
+
import Or from'yargs';import {hideBin}from'yargs/helpers';import at,{useState,useEffect,useCallback,useMemo,useRef}from'react';import {render,useApp,useInput,Box,Text}from'ink';import*as we from'yjs';import {Awareness}from'y-protocols/awareness';import {mkdirSync,chmodSync,writeFileSync,unlinkSync,existsSync,readFileSync}from'fs';import {homedir}from'os';import {join}from'path';import {randomUUID}from'crypto';import {createInterface}from'readline';import {WebsocketProvider}from'y-websocket';import kn from'ws';import Rr from'clipboardy';import {jsxs,jsx,Fragment}from'react/jsx-runtime';var fn="https://hnch.dev";function Ie(){let t=process.env.XDG_CONFIG_HOME||join(homedir(),".config");return join(t,"hnch")}function ht(){return join(Ie(),"config.json")}function Yt(){let e=Ie();existsSync(e)||mkdirSync(e,{recursive:true});}function de(){let e=ht();if(!existsSync(e))return {};try{return JSON.parse(readFileSync(e,"utf-8"))}catch{return {}}}function _(e){Yt();let o={...de(),...e};writeFileSync(ht(),JSON.stringify(o,null,2)+`
|
|
3
|
+
`,"utf-8");}async function gn(e){let t=createInterface({input:process.stdin,output:process.stderr});return new Promise(o=>{t.question(e,n=>{t.close(),o(n.trim());});})}function hn(e){return e.startsWith("wss://")?"https://"+e.slice(6):e.startsWith("ws://")?"http://"+e.slice(5):e}async function Te(){let e=de(),t=false;if(e.participantId||(e.participantId=randomUUID(),t=true),!e.displayName){let n=await gn("Enter your display name: ");e.displayName=n||`user-${e.participantId.slice(0,6)}`,t=true;}e.relayUrl||(e.relayUrl=fn,t=true),t&&_(e);let o=process.env.HNCH_RELAY_URL||e.relayUrl;return o=hn(o),{participantId:e.participantId,displayName:e.displayName,relayUrl:o,defaultScale:e.defaultScale,defaultSessionName:e.defaultSessionName,defaultPassword:e.defaultPassword,presetOverrides:e.presetOverrides,customScales:e.customScales}}function Ee(e){Yt();let o={...de()};for(let[n,r]of Object.entries(e))r===""?delete o[n]:r!==void 0&&(o[n]=r);writeFileSync(ht(),JSON.stringify(o,null,2)+`
|
|
4
|
+
`,"utf-8");}var Sn={"unknown-room":4001,"wrong-password":4002,"invalid-invite":4003,"expired-invite":4004,"invite-already-redeemed":4005,"room-full":4006,"malformed-credentials":4010},Jt=new Map(Object.entries(Sn).map(([e,t])=>[t,e]));function ue(e){return e.role??"voter"}var $e={id:"fibonacci",cards:["0","1","2","3","5","8","13","21"]},Wt={id:"t-shirt",cards:["XS","S","M","L","XL"]},qt={id:"powers-of-2",cards:["1","2","4","8","16","32","64"]},Gt={id:"linear",cards:["1","2","3","4","5","6","7","8","9","10"]},zt={id:"risk",cards:["Low","Medium","High","Critical"]},X={[$e.id]:$e,[Wt.id]:Wt,[qt.id]:qt,[Gt.id]:Gt,[zt.id]:zt},Qt={fibonacci:"Fibonacci","t-shirt":"T-shirt","powers-of-2":"Powers of 2",linear:"Linear (1\u201310)",risk:"Risk"};function pe(e){return e in Qt?Qt[e]:e.length===0?e:(e.startsWith("custom-")?e.slice(7):e).split("-").map(o=>o.length===0?o:o.charAt(0).toUpperCase()+o.slice(1)).join(" ")}var xn="meta",Ae="settings",yn="participants",wn="rounds",vn=["roomUuid","createdAt","createdBy","sessionEndedAt"],Cn=["name","defaultScale","hostParticipantId"],bn=["displayName","joinedAt","role","leftAt","removedBy"],Rn=["id","title","description","ticketId","parentRoundId","scale","timerStartedAt","revealedAt","createdBy","createdAt"];function je(e){return e.getMap(xn)}function Le(e){return e.getMap(Ae)}function _e(e){return e.getMap(yn)}function St(e){return e.getArray(wn)}function Fe(e,t){let o={};for(let n of t){let r=e.get(n);r!==void 0&&(o[n]=r);}return o}function In(e){return Fe(je(e),vn)}function Tn(e){return Fe(Le(e),Cn)}function En(e){let t=_e(e),o=[];return t.forEach((n,r)=>{let s=Fe(n,bn);o.push({id:r,...s});}),o.sort((n,r)=>n.joinedAt-r.joinedAt||n.id.localeCompare(r.id)),o}function An(e){let t={};return e.forEach((o,n)=>{t[n]=o;}),t}function Nn(e){let t=Fe(e,Rn),o=e.get("votes"),n=o?An(o):{};return {...t,votes:n}}function Pn(e){let t=St(e),o=[];return t.forEach(n=>{o.push(Nn(n));}),o}function Q(e){return {meta:In(e),settings:Tn(e),participants:En(e),rounds:Pn(e)}}function Kt(e,t){if(je(e).has("roomUuid"))throw new Error("initDoc: doc already seeded");e.transact(()=>{let o=je(e);o.set("roomUuid",t.roomUuid),o.set("createdAt",t.createdAt),o.set("createdBy",t.participantId);let n=Le(e);t.sessionName!==void 0&&n.set("name",t.sessionName),n.set("defaultScale",t.defaultScale),n.set("hostParticipantId",t.participantId);let r=new we.Map;r.set("displayName",t.displayName),r.set("joinedAt",t.createdAt),_e(e).set(t.participantId,r);});}function Ve(e,t){e.transact(()=>{let o=_e(e),n=o.get(t.participantId),r=n??new we.Map;r.set("displayName",t.displayName),n===void 0&&r.set("joinedAt",Date.now()),r.delete("leftAt"),r.delete("removedBy"),n===void 0&&o.set(t.participantId,r);});}function ve(e,t){e.transact(()=>{let o=Le(e);for(let[n,r]of Object.entries(t))r===void 0?o.delete(n):o.set(n,r);});}function xt(e,t,o){e.transact(()=>{let n=_e(e),r=n.get(t),s=r??new we.Map;s.set("displayName",o.displayName),s.set("joinedAt",o.joinedAt),o.role!==void 0&&s.set("role",o.role),r===void 0&&n.set(t,s);});}function yt(e,t){let o=t.scale??Le(e).get("defaultScale");if(!o)throw new Error("appendRound: no scale (settings.defaultScale missing)");return e.transact(()=>{let n=new we.Map;n.set("id",t.id),n.set("createdAt",t.createdAt),n.set("createdBy",t.createdBy),n.set("scale",o),t.title!==void 0&&n.set("title",t.title),t.description!==void 0&&n.set("description",t.description),t.ticketId!==void 0&&n.set("ticketId",t.ticketId),t.parentRoundId!==void 0&&n.set("parentRoundId",t.parentRoundId),n.set("timerStartedAt",t.createdAt),n.set("votes",new we.Map),St(e).push([n]);}),t.id}function wt(e,t){let o=St(e);for(let n=0;n<o.length;n+=1){let r=o.get(n);if(r.get("id")===t)return r}}function Xt(e,t,o,n){let r=wt(e,t);if(!r)throw new Error(`castVote: round ${t} not found`);r.get("revealedAt")===void 0&&e.transact(()=>{r.get("votes").set(o,n);});}function Zt(e,t,o){let n=wt(e,t);if(!n)throw new Error(`clearVote: round ${t} not found`);n.get("revealedAt")===void 0&&e.transact(()=>{n.get("votes").delete(o);});}function vt(e,t,o){let n=wt(e,t);if(!n)throw new Error(`revealRound: round ${t} not found`);e.transact(()=>{n.set("revealedAt",o);});}function eo(e){e.transact(()=>{je(e).set("sessionEndedAt",Date.now());});}function Z(e){let{rounds:t}=e;if(t.length===0)return null;for(let o=t.length-1;o>=0;o-=1){let n=t[o];if(n.revealedAt===void 0)return n}return t[t.length-1]}function to(e){let t=Object.values(e.votes);return t.length<2?false:t.every(o=>o===t[0])}var oo="PBKDF2",On="SHA-256",no=new TextEncoder;function Mn(e){return Array.from(new Uint8Array(e)).map(t=>t.toString(16).padStart(2,"0")).join("")}async function me(e,t){let o=await crypto.subtle.importKey("raw",no.encode(e),oo,false,["deriveBits"]),n=await crypto.subtle.deriveBits({name:oo,salt:no.encode(t),iterations:1e5,hash:On},o,256);return Mn(n)}var ro="ABCDEFGHJKMNPQRSTUVWXYZ23456789";if(ro.length!==31)throw new Error(`Room code alphabet must be 31 characters, got ${ro.length}`);function Ct(e){return e.trim().replace(/-/g,"").toUpperCase()}function so(e,t=true){let o=Ct(e);return !t||o.length!==6?o:`${o.slice(0,3)}-${o.slice(3)}`}function Ye(e,t,o){if(e.startsWith("custom-")){let r=(o??[]).find(s=>s.id===e);return r?{id:r.id,cards:r.cards}:void 0}let n=X[e];if(n){let r=(t??{})[e]??[];if(r.length===0)return n;let s=n.cards.filter((p,a)=>!r.includes(a));return s.length===0?void 0:{id:e,cards:s}}}function Je(e,t){let o=Object.keys(X).map(r=>Ye(r,e,t)).filter(r=>r!==void 0),n=(t??[]).map(r=>({id:r.id,cards:r.cards}));return [...o,...n]}async function io(e,t){let o=await fetch(`${e}/rooms`,{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({})});if(!o.ok)throw new Error(`POST /rooms failed: ${o.status}`);return await o.json()}async function Ne(e,t){let o=await fetch(`${e}/rooms/${encodeURIComponent(t)}`);if(o.status===404){let n=await o.json().catch(()=>null);throw new Error(`Room not found: ${n?.reason??"unknown-room"}`)}if(!o.ok)throw new Error(`GET /rooms/:code failed: ${o.status}`);return await o.json()}async function bt(e,t,o){let n=await fetch(`${e}/rooms/${t}/access`,{method:"PATCH",headers:{"content-type":"application/json"},body:JSON.stringify({passwordHash:o})});if(!n.ok)throw new Error(`PATCH /rooms/:uuid/access failed: ${n.status}`)}function ao(e){let o=`${e.wsBaseUrl.replace(/^http/,"ws")}/ws`,n={did:e.deviceId};e.passwordHash!==void 0&&(n.pw=e.passwordHash);let r=new WebsocketProvider(o,e.roomUuid,e.doc,{params:n,connect:true,...e.awareness!==void 0&&{awareness:e.awareness},WebSocketPolyfill:kn}),s=l=>{e.onStatusChange?.(l.status);},p=l=>{if(l===null)return;let i=Jt.get(l.code);i!==void 0&&(e.onRejection?.(i),r.shouldConnect=false,r.disconnect());};r.on("status",s),r.on("connection-close",p);let a=false,d=e.passwordHash;return {doc:e.doc,awareness:r.awareness,updateCredentials(l){if(l.passwordHash===d)return;d=l.passwordHash??void 0,r.shouldConnect=false,r.disconnect();let c={did:r.params.did??""};d!==void 0&&(c.pw=d),r.params=c,r.connect();},disconnect(){a||(a=true,r.off("status",s),r.off("connection-close",p),r.shouldConnect=false,r.disconnect(),r.destroy());}}}var Hn=5e3;function co(e,t,o){let n=null,r=null;function s(){let i=new Set;return t.getStates().forEach(c=>{let h=c.participantId;typeof h=="string"&&i.add(h);}),i}function p(){return e.getMap(Ae).get("hostParticipantId")}function a(){r!==null&&(clearTimeout(r),r=null),n=null;}function d(){let i=p(),c=n;if(n=null,!c||i!==c)return;let h=s();if(h.has(i))return;let A=[...h].sort();if(A.length===0)return;let O=A[0];O===o&&ve(e,{hostParticipantId:O});}function l(){let i=p();if(!i)return;if(s().has(i)){a();return}n!==i&&(a(),n=i,r=setTimeout(()=>{r=null,d();},Hn));}return t.on("change",l),e.getMap(Ae).observe(l),l(),{stop(){a(),t.off("change",l),e.getMap(Ae).unobserve(l);}}}async function po(e){let t,o=await io(e.relayUrl);e.password&&(t=await me(e.password,o.uuid),await bt(e.relayUrl,o.uuid,t));let n=new we.Doc,r=new Awareness(n),s=Date.now();return Kt(n,{roomUuid:o.uuid,createdAt:s,participantId:e.participantId,displayName:e.displayName,defaultScale:e.defaultScale??$e,sessionName:e.sessionName}),r.setLocalStateField("participantId",e.participantId),We({doc:n,awareness:r,roomCode:o.code,roomUuid:o.uuid,participantId:e.participantId,relayUrl:e.relayUrl,passwordHash:t})}async function mo(e){let t=await Ne(e.relayUrl,e.roomCode),o;if(t.accessMode==="password"){if(!e.password)throw new Error("Room requires a password");o=await me(e.password,t.uuid);}let n=new we.Doc,r=new Awareness(n);return Ve(n,{participantId:e.participantId,displayName:e.displayName}),r.setLocalStateField("participantId",e.participantId),We({doc:n,awareness:r,roomCode:e.roomCode,roomUuid:t.uuid,participantId:e.participantId,relayUrl:e.relayUrl,passwordHash:o,onRejection:e.onRejection})}function We(e){let{doc:t,awareness:o,participantId:n,relayUrl:r,roomUuid:s}=e,p=new Set,a=new Set,d=new Set,l=ao({wsBaseUrl:e.relayUrl,roomUuid:e.roomUuid,doc:t,deviceId:n,passwordHash:e.passwordHash,awareness:o,onStatusChange(u){p.forEach(g=>g(u));},onRejection(u){console.error(`Connection rejected: ${u}`),e.onRejection?.(u);}}),i=co(t,o,n),c=()=>{a.forEach(u=>u());};t.getMap("meta").observeDeep(c),t.getMap("settings").observeDeep(c),t.getMap("participants").observeDeep(c),t.getArray("rounds").observeDeep(c);function h(){let u=new Set;return o.getStates().forEach(g=>{let w=g.participantId;typeof w=="string"&&u.add(w);}),u}let A=new Set,O=setInterval(()=>{let u=Q(t),g=Z(u);if(!g||g.revealedAt!==void 0||A.has(g.id))return;let w=h();if(w.size===0)return;let x=u.participants.filter(f=>w.has(f.id)&&ue(f)==="voter");if(x.length===0)return;x.filter(f=>f.id in g.votes).length>=x.length&&(A.add(g.id),vt(t,g.id,Date.now()));},250),R=t.getMap("settings").get("hostParticipantId"),I=()=>{let u=t.getMap("settings").get("hostParticipantId");u!==R&&u!==void 0&&d.forEach(g=>g(u)),R=u;};t.getMap("settings").observe(I);let k=false;return {doc:t,awareness:o,roomCode:e.roomCode,roomUuid:e.roomUuid,projection(){return Q(t)},current(){return Z(Q(t))},isHost(){return Q(t).settings.hostParticipantId===n},onlineIds:h,vote(u){let g=Z(Q(t));g&&Xt(t,g.id,n,u);},unvote(){let u=Z(Q(t));u&&Zt(t,u.id,n);},reveal(){let u=Z(Q(t));u&&vt(t,u.id,Date.now());},newRound(u){let g=randomUUID();return yt(t,{id:g,createdAt:Date.now(),createdBy:n,title:u}),g},revote(){let u=Z(Q(t)),g=randomUUID();return yt(t,{id:g,createdAt:Date.now(),createdBy:n,parentRoundId:u?.id,title:u?.title,description:u?.description,ticketId:u?.ticketId}),g},updateName(u){ve(t,{name:u});},updateScale(u){ve(t,{defaultScale:u});},updateDisplayName(u){let x=Q(t).participants.find(T=>T.id===n)?.joinedAt??Date.now();xt(t,n,{displayName:u,joinedAt:x});},async updateAccess(u,g){let w=null;if(u==="password"){if(!g)throw new Error("Password required for password mode");w=await me(g,s);}await bt(r,s,w),l.updateCredentials({passwordHash:w});},handoffHost(u){if(!this.isHost())throw new Error("Only the current host can hand off");if(!h().has(u))throw new Error("Target participant is not online");if(!Q(t).participants.some(T=>T.id===u))throw new Error("Target participant does not exist");ve(t,{hostParticipantId:u});},toggleRole(){let g=Q(t).participants.find(f=>f.id===n),x=ue(g??{})==="viewer"?"voter":"viewer",T=g?.joinedAt??Date.now();xt(t,n,{displayName:g?.displayName??"",joinedAt:T,role:x});},endSessionForEveryone(){if(!this.isHost())throw new Error("Only the current host can end the session");eo(t);},onChange(u){return a.add(u),()=>a.delete(u)},onStatusChange(u){return p.add(u),()=>p.delete(u)},onHostChange(u){return d.add(u),()=>d.delete(u)},disconnect(){k||(k=true,clearInterval(O),i.stop(),t.getMap("meta").unobserveDeep(c),t.getMap("settings").unobserveDeep(c),t.getMap("settings").unobserve(I),t.getMap("participants").unobserveDeep(c),t.getArray("rounds").unobserveDeep(c),l.disconnect());}}}function fo(e){let t=Ct(e);return t===""?null:t}var Fn=1440*60*1e3;function It(){return join(Ie(),"last-session.json")}function Vn(e){if(typeof e!="object"||e===null)return false;let t=e;return typeof t.roomCode=="string"&&typeof t.roomUuid=="string"&&typeof t.lastSeenAt=="number"&&(t.passwordHash===void 0||typeof t.passwordHash=="string")}function ho(){let e=It();if(!existsSync(e))return null;try{let t=JSON.parse(readFileSync(e,"utf-8"));return !Vn(t)||Date.now()-t.lastSeenAt>Fn?(j(),null):t}catch{return j(),null}}function qe(e){let t=Ie();mkdirSync(t,{recursive:true,mode:448}),chmodSync(t,448);let o=It();writeFileSync(o,JSON.stringify(e,null,2)+`
|
|
5
|
+
`,{encoding:"utf-8",mode:384}),chmodSync(o,384);}function j(){let e=It();try{unlinkSync(e);}catch(t){if(t.code!=="ENOENT")throw t}}async function Ge(e){let t=true;try{await e.clearLastSession();}catch(o){t=false,e.logger.error(`Failed to clear cached session: ${o instanceof Error?o.message:String(o)}`);}return t?e.logger.error("Session expired (room no longer exists). Cached session cleared."):e.logger.error("Session expired. Could not delete ~/.config/hnch/last-session.json \u2014 please remove it manually."),e.exit(1)}function ze(e){let t=e.doc.getMap("meta"),o=null,n=false;function r(){if(n)return;let s=t.get("sessionEndedAt");if(s==null||s===o)return;o=s;let p=t.get("createdBy"),a=p===e.localParticipantId,d=e.doc.getMap("participants"),l=e.doc.getMap("settings").get("hostParticipantId")??p,i="host";if(l){let h=d.get(l)?.get("displayName");typeof h=="string"&&h.length>0&&(i=h);}setImmediate(()=>{n||e.onEnded({isLocal:a,hostName:i,timestamp:s});});}return t.observe(r),r(),()=>{n=true,t.unobserve(r);}}var Yn="@xauyxau/hnch";function Qe(e){return so(e)}function Ke(e){return `${process.env.HNCH_SHARE_ORIGIN??"https://hnch.dev"}/join?code=${e}`}function Xe(e){return `npx ${Yn} join ${e}`}var Jn={connecting:"yellow",connected:"green",disconnected:"red"};function yo({roomCode:e,sessionName:t,status:o,isHost:n}){let r=t?`${e} \xB7 ${t}`:e;return jsxs(Box,{borderStyle:"single",borderColor:"gray",paddingX:1,justifyContent:"space-between",children:[jsxs(Text,{bold:true,children:["hnch \xB7 ",r]}),jsxs(Box,{gap:2,children:[n&&jsx(Text,{color:"yellow",children:"[host]"}),jsx(Text,{color:Jn[o],children:o})]})]})}function ne({items:e,onSelect:t,onClose:o,title:n}){if(e.length===0)return jsxs(Box,{flexDirection:"column",gap:1,children:[n&&jsx(Text,{bold:true,children:n}),jsx(Text,{dimColor:true,children:"(no items)"})]});let r=e.findIndex(a=>!a.disabled),[s,p]=useState(r>=0?r:0);return useInput((a,d)=>{if(d.upArrow)p(l=>{let i=(l-1+e.length)%e.length,c=0;for(;e[i]?.disabled&&c<e.length;)i=(i-1+e.length)%e.length,c++;return i});else if(d.downArrow)p(l=>{let i=(l+1)%e.length,c=0;for(;e[i]?.disabled&&c<e.length;)i=(i+1)%e.length,c++;return i});else if(d.return){let l=e[s];l&&!l.disabled&&t(l.value);}else d.escape&&o?.();}),jsxs(Box,{flexDirection:"column",gap:1,children:[n&&jsx(Text,{bold:true,children:n}),jsx(Box,{flexDirection:"column",children:e.map((a,d)=>{let l=d===s,i=a.disabled??false;return jsxs(Box,{gap:1,children:[jsx(Text,{color:l?"cyan":void 0,dimColor:i,children:l?">":" "}),jsx(Text,{bold:l,dimColor:i,children:a.label}),a.detail&&jsx(Text,{dimColor:true,children:a.detail})]},a.value)})}),jsx(Text,{dimColor:true,children:"\u2191\u2193 select \u23CE confirm esc close"})]})}function W({value:e,onChange:t,onSubmit:o,onCancel:n,placeholder:r,isActive:s=true,mask:p=false}){useInput((i,c)=>{c.escape?n?.():c.return?o(e):c.backspace||c.delete?t(e.slice(0,-1)):i&&!c.ctrl&&!c.meta&&t(e+i);},{isActive:s});let a=p&&e.length>0?"*".repeat(e.length):e,d=a.length>0?a:r??"",l=e.length===0&&r!==void 0;return jsxs(Text,{children:[jsx(Text,{dimColor:l,children:d}),jsx(Text,{inverse:true,children:" "})]})}function bo({mode:e,password:t,onModeChange:o,onPasswordChange:n,onClose:r}){let[s,p]=useState(e==="open"?0:1),[a,d]=useState(false);return useEffect(()=>{p(e==="open"?0:1);},[e]),useInput((l,i)=>{if(i.leftArrow)p(c=>Math.max(0,c-1));else if(i.rightArrow)p(c=>Math.min(1,c+1));else if(i.return){let c=s===0?"open":"password";c!==e&&o(c),c==="password"&&d(true);}else i.escape&&r?.();},{isActive:!a}),jsxs(Box,{flexDirection:"column",gap:1,children:[jsx(Box,{gap:1,children:["open","password"].map((l,i)=>{let c=i===s,h=l===e,A=l==="open"?"Open":"Password";return jsx(Text,{inverse:c,bold:h,color:h?"cyan":void 0,children:h?`[${A}]`:` ${A} `},l)})}),e==="password"&&jsxs(Box,{flexDirection:"column",gap:1,children:[jsx(Text,{children:"Password: "}),jsx(W,{value:t,onChange:n,onSubmit:()=>d(false),onCancel:()=>d(false),placeholder:"(required to join)",isActive:a,mask:true}),!a&&jsx(Text,{dimColor:true,children:"\u23CE edit \u2190 \u2192 switch mode esc close"}),a&&jsx(Text,{dimColor:true,children:"\u23CE done esc cancel"})]}),e==="open"&&jsx(Text,{dimColor:true,children:"\u2190\u2192 toggle \u23CE confirm esc close"})]})}function To({scaleId:e,cards:t,disabledIndices:o,onOverrideChange:n,onClose:r}){let s=useMemo(()=>new Set(o),[o]),[p,a]=useState(0),d=l=>{let i=new Set(s);if(i.has(l))i.delete(l);else {if(i.size>=t.length-1)return;i.add(l);}let c=Array.from(i).sort((h,A)=>h-A);n(e,c);};return useInput((l,i)=>{i.leftArrow?a(c=>Math.max(0,c-1)):i.rightArrow?a(c=>Math.min(t.length-1,c+1)):l===" "?d(p):(i.return||i.escape)&&r();}),jsxs(Box,{flexDirection:"column",gap:1,children:[jsx(Box,{gap:1,children:t.map((l,i)=>{let c=i===p,h=s.has(i);return jsx(Text,{inverse:c,bold:!h,color:h?void 0:"cyan",strikethrough:h,dimColor:h,children:l},l)})}),jsx(Text,{dimColor:true,children:" \u2190\u2192 navigate space toggle \u23CE/esc done"})]})}function Dt({initialName:e="",initialCards:t=[],onSave:o,onCancel:n}){let r=e.length>0,[s,p]=useState(e),[a,d]=useState(t.length>0?t:[""]),[l,i]=useState(0),[c,h]=useState("editing-name"),[A,O]=useState(""),R=()=>{let u=s.trim();if(u.length<1||u.length>40){O("Name must be 1-40 characters");return}p(u),O(""),h("editing-cards");},I=()=>{let u=s.trim();if(u.length<1||u.length>40){O("Name must be 1-40 characters");return}let g=a.map(x=>x.trim()).filter(x=>x.length>0),w=Array.from(new Set(g));if(w.length<2){O("Need at least 2 unique non-empty cards");return}O(""),o(u,w);};return useInput((u,g)=>{if(c==="editing-cards"){if(g.leftArrow)i(w=>Math.max(0,w-1));else if(g.rightArrow)i(w=>{let x=w+1;return x>=a.length&&a.length<25?(d(T=>[...T,""]),x):Math.min(x,a.length-1)});else if(g.return)I();else if(g.escape)n();else if(g.tab)h("editing-name");else if(g.backspace){let w=a[l]||"";if(w.length===0){if(a.length>2){let x=a.filter((T,f)=>f!==l);d(x),i(Math.max(0,l-1));}}else {let x=[...a];x[l]=w.slice(0,-1),d(x);}}else if(u&&!g.ctrl&&!g.meta){let w=a[l]||"";if(w.length<10){let x=[...a];x[l]=w+u,d(x);}}}},{isActive:c==="editing-cards"}),jsxs(Box,{flexDirection:"column",gap:1,children:[jsx(Text,{bold:true,children:r?"\u2500\u2500 Edit Custom Scale \u2500\u2500":"\u2500\u2500 Create Custom Scale \u2500\u2500"}),jsxs(Box,{flexDirection:"column",gap:0,children:[jsx(Text,{children:"Name:"}),jsx(W,{value:s,onChange:p,onSubmit:R,onCancel:n,placeholder:"Scale name",isActive:c==="editing-name"})]}),c==="editing-cards"&&jsxs(Box,{flexDirection:"column",gap:0,children:[jsx(Text,{children:"Cards:"}),jsx(Box,{gap:1,flexWrap:"wrap",children:a.map((u,g)=>{let w=g===l;return jsx(Text,{inverse:w,color:w?void 0:"cyan",children:u.length>0?u:"\xB7"},g)})})]}),A&&jsx(Text,{color:"red",children:A}),jsx(Text,{dimColor:true,children:c==="editing-name"?"\u23CE next esc cancel":"\u2190\u2192 navigate typing adds card bsp delete \u23CE save tab name esc cancel"})]})}function tt({scales:e,selectedScaleId:t,presetOverrides:o,customScales:n=[],onSelect:r,onOverrideChange:s,onCustomScaleCreate:p,onCustomScaleEdit:a,onCustomScaleDelete:d,onClose:l,showNoneOption:i=false}){let[c,h]=useState(0),[A,O]=useState(null),[R,I]=useState("browsing"),[k,u]=useState(null),[g,w]=useState(null),x=p?[...e,{id:"__create-custom__",cards:[]}]:e,T=x.length;if(useInput((f,P)=>{if(R==="browsing"){if(P.escape){l();return}if(T===0)return;if(P.upArrow)h(y=>(y-1+T)%T);else if(P.downArrow)h(y=>(y+1)%T);else if(P.return){let y=x[c];if(!y)return;if(y.id==="__create-custom__"){I("creating");return}y.id in X?O(A===y.id?null:y.id):r(y.id);}else if(f===" "){let y=x[c];y&&y.id!=="__create-custom__"&&r(y.id);}else if(f==="e"||f==="E"){let y=x[c];a&&y?.id.startsWith("custom-")&&(u(y.id),I("editing"));}else if(f==="d"||f==="D"){let y=x[c];d&&y?.id.startsWith("custom-")&&(w(y.id),I("confirming-delete"));}}else R==="confirming-delete"&&(f==="y"||f==="Y"?(g&&d&&d(g),w(null),I("browsing")):(f==="n"||f==="N"||P.escape)&&(w(null),I("browsing")));},{isActive:R==="browsing"&&A===null||R==="confirming-delete"}),R==="creating")return jsx(Dt,{onSave:(f,P)=>{p&&p(f,P),I("browsing");},onCancel:()=>I("browsing")});if(R==="editing"&&k){let f=n?.find(y=>y.id===k);if(e.find(y=>y.id===k)&&f)return jsx(Dt,{initialName:f.label,initialCards:f.cards,onSave:(y,H)=>{a&&a(k,y,H),u(null),I("browsing");},onCancel:()=>{u(null),I("browsing");}})}if(R==="confirming-delete"&&g){let f=x.find(y=>y.id===g),P=f?pe(f.id):"Unknown";return jsxs(Box,{flexDirection:"column",gap:1,children:[jsxs(Text,{children:["Delete ",P,"? "]}),jsx(Text,{children:"[y] yes [n] no esc cancel"})]})}return x.length===0?jsxs(Box,{flexDirection:"column",gap:1,children:[jsx(Text,{dimColor:true,children:"(no scales available)"}),jsx(Text,{dimColor:true,children:"esc back"})]}):jsxs(Box,{flexDirection:"column",gap:1,children:[jsx(Box,{flexDirection:"column",children:x.map((f,P)=>{let y=P===c,H=f.id===t,J=A===f.id,Y=f.id in X;f.id.startsWith("custom-");let B=f.id==="__create-custom__",S=f.cards.join(", "),v=i&&f.id==="",E=v?"(none)":B?"[+ Create custom scale]":pe(f.id);return jsxs(Box,{flexDirection:"column",children:[jsxs(Box,{gap:1,children:[jsx(Text,{color:y?"cyan":void 0,children:y?">":" "}),jsx(Text,{bold:y,children:E}),!v&&!B&&jsx(Text,{dimColor:true,children:S}),H&&jsx(Text,{color:"cyan",children:"[current]"})]}),J&&!v&&!B&&jsx(Box,{paddingLeft:2,children:Y?jsx(To,{scaleId:f.id,cards:X[f.id]?.cards??[],disabledIndices:o?.[f.id]??[],onOverrideChange:s,onClose:()=>O(null)}):null})]},f.id||"none")})}),jsx(Text,{dimColor:true,children:(()=>{let f=c<x.length?x[c]:null;return f?.id.startsWith("custom-")?`\u2191\u2193 navigate space select${a?" [e] edit":""}${d?" [d] delete":""} esc back`:f&&f.id in X?"\u2191\u2193 navigate \u23CE customize space select esc back":"\u2191\u2193 navigate \u23CE/space select esc back"})()})]})}function sr(e,t){return t?[...e]:e.filter(o=>o.value==="display-name")}function Eo({sessionName:e,displayName:t,currentScale:o,accessMode:n,presetOverrides:r,customScales:s,isHost:p=true,onChangeName:a,onChangeScale:d,onChangeDisplayName:l,onChangeAccess:i,onClose:c}){let[h,A]=useState(r??{}),O=useMemo(()=>Je(h,s),[h,s]),[R,I]=useState("main-menu"),[k,u]=useState(e??""),[g,w]=useState(t),[x,T]=useState(n==="password"?"password":"open"),[f,P]=useState(""),[y,H]=useState(false),J=[{label:"Room name",value:"room-name",detail:`[${e??"(none)"}]`},{label:"Scale",value:"scale",detail:`[${pe(o.id)}]`},{label:"Access",value:"access",detail:`[${n==="open"?"Open":"Password"}]`},{label:"Display name",value:"display-name",detail:`[${t}]`}],Y=sr(J,p);function G(m){!p&&m!=="display-name"||(m==="room-name"?(u(e??""),I("editing-room-name")):m==="scale"?I("picking-scale"):m==="access"?I("picking-access"):m==="display-name"&&(w(t),I("editing-display-name")));}function B(m){let M=m.trim()===""?void 0:m.trim();a(M),I("main-menu");}function S(m){let M=m.trim();M.length>0&&(l(M),process.nextTick(()=>_({displayName:M}))),I("main-menu");}function v(m){let M=O.find(K=>K.id===m);M&&d(M),I("main-menu");}function E(m,M){let K={...h};M.length===0?delete K[m]:K[m]=M,A(K),_({presetOverrides:Object.keys(K).length>0?K:void 0});}async function L(m,M){H(true);try{await i(m,M),T(m),P(m==="password"?M??"":"");}catch(K){console.error(`Failed to update access: ${K}`);}finally{H(false),I("main-menu");}}return R==="main-menu"?jsx(ne,{title:"\u2500\u2500 Settings \u2500\u2500",items:Y,onSelect:G,onClose:c}):R==="editing-room-name"?jsxs(Box,{flexDirection:"column",gap:1,children:[jsx(Text,{bold:true,children:"\u2500\u2500 Settings \u2500\u2500"}),jsx(Text,{children:"Room name: "}),jsx(W,{value:k,onChange:u,onSubmit:B,onCancel:()=>I("main-menu"),placeholder:"(leave empty to remove)",isActive:true}),jsx(Text,{dimColor:true,children:"\u23CE confirm esc back"})]}):R==="editing-display-name"?jsxs(Box,{flexDirection:"column",gap:1,children:[jsx(Text,{bold:true,children:"\u2500\u2500 Settings \u2500\u2500"}),jsx(Text,{children:"Display name: "}),jsx(W,{value:g,onChange:w,onSubmit:S,onCancel:()=>I("main-menu"),placeholder:"(required)",isActive:true}),jsx(Text,{dimColor:true,children:"\u23CE confirm esc back"})]}):R==="picking-scale"?jsxs(Box,{flexDirection:"column",gap:1,children:[jsx(Text,{bold:true,children:"\u2500\u2500 Settings \u2500\u2500"}),jsx(tt,{scales:O,selectedScaleId:o.id,presetOverrides:h,onSelect:v,onOverrideChange:E,onClose:()=>I("main-menu")})]}):R==="picking-access"?jsxs(Box,{flexDirection:"column",gap:1,children:[jsx(Text,{bold:true,children:"\u2500\u2500 Settings \u2500\u2500"}),y&&jsx(Text,{dimColor:true,children:"Updating access..."}),!y&&jsx(bo,{mode:x,password:f,onModeChange:m=>{m==="open"&&L("open");},onPasswordChange:P,onClose:()=>I("main-menu")})]}):jsx(Box,{})}function st({participants:e,onlineIds:t,hostId:o,votes:n,revealed:r}){return jsxs(Box,{flexDirection:"column",children:[jsxs(Text,{bold:true,children:["Participants (",e.length,")"]}),e.map(s=>{let p=t.has(s.id),a=p?"\u25CF":"\u25CB",d=p?"green":"gray",l=s.id===o,i=ue(s)==="viewer",c=s.id in n,h=i?" (viewer)":r?n[s.id]?` \u2192 ${n[s.id]}`:"":c?" \u2713":" \xB7";return jsxs(Box,{gap:1,children:[jsx(Text,{color:d,children:a}),jsxs(Text,{children:[s.displayName,l?" (host)":""]}),jsx(Text,{color:i?"gray":c?"green":"gray",children:h})]},s.id)})]})}var ir=[{label:"End session for everyone",value:"end-session",detail:"Disconnects all participants. Cannot be undone."}];function Po({onPick:e,onClose:t}){return jsx(ne,{title:"Host Actions",items:ir,onSelect:e,onClose:t})}function it({roomCode:e,onPick:t,onClose:o}){let n=[{label:"Copy code",value:"code",detail:Qe(e)},{label:"Copy share link",value:"url",detail:Ke(e)},{label:"Copy npx command",value:"npx",detail:Xe(e)}];return jsx(ne,{title:"Share",items:n,onSelect:r=>t(r),onClose:o})}function dr(e){let t=Object.values(e).map(i=>parseFloat(i)).filter(i=>!isNaN(i)).sort((i,c)=>i-c);if(t.length===0)return {average:"-",median:"-",agreement:"-"};let n=t.reduce((i,c)=>i+c,0)/t.length,r=Math.floor(t.length/2),s=t.length%2===0?(t[r-1]+t[r])/2:t[r],p=Object.values(e),a=new Map;for(let i of p)a.set(i,(a.get(i)??0)+1);let d=Math.max(...a.values()),l=p.length>0?Math.round(d/p.length*100):0;return {average:n.toFixed(1),median:s.toFixed(1),agreement:`${l}%`}}function Oo(e,t){let o=Math.floor((t-e)/1e3),n=Math.floor(o/60),r=o%60;return `${n}:${r.toString().padStart(2,"0")}`}function ur(e,t){let o=new Map;for(let a of Object.values(e.votes))o.set(a,(o.get(a)??0)+1);let n=[...o.entries()].sort((a,d)=>d[1]-a[1]||a[0].localeCompare(d[0])).map(([a,d])=>`${a}\xD7${d}`).join(", "),r=e.title?` ${e.title}`:"",s=e.ticketId?` [${e.ticketId}]`:"",p=e.timerStartedAt!==void 0&&e.revealedAt!==void 0?` \u2014 ${Oo(e.timerStartedAt,e.revealedAt)}`:"";return `Round ${t}:${r}${s} \u2014 ${n||"(no votes)"}${p}`}function Mo({projection:e,onlineIds:t,isHost:o,participantId:n,rounds:r,currentRound:s,settingsOpen:p,onStartRound:a,onNewRound:d,onRevote:l,onOpenSettings:i,onHandoffHost:c,onEndSession:h,onShare:A,shareOpen:O,onSetShareOpen:R,roomCode:I,confirmingQuit:k,roundTitlePromptActive:u,roundTitleInput:g,onRoundTitleChange:w,onRoundTitleSubmit:x,onRoundTitleCancel:T}){let[f,P]=at.useState("idle"),[y,H]=at.useState(null),[J,Y]=at.useState(false),[G,B]=at.useState(false);useEffect(()=>{o||(P("idle"),H(null),Y(false),B(false));},[o]),useInput(m=>{if(m==="c"||m==="C"){R(true);return}o&&((m==="e"||m==="E")&&i(),(m==="s"||m==="S")&&a(),(m==="h"||m==="H")&&P("picking"),(m==="m"||m==="M")&&Y(true),s?.revealedAt!==void 0&&((m==="n"||m==="N")&&d(),(m==="v"||m==="V")&&l()));},{isActive:!p&&!k&&f==="idle"&&!u&&!J&&!G&&!O}),useInput((m,M)=>{m==="y"||m==="Y"?(h(),B(false)):(m==="n"||m==="N"||M.escape)&&B(false);},{isActive:G});let S=e.participants.filter(m=>m.id!==n&&t.has(m.id));useInput(m=>{if(f==="picking"){let M=parseInt(m,10);!isNaN(M)&&M>=1&&M<=S.length?(H(S[M-1].id),P("confirming")):(m==="Escape"||m==="q"||m==="Q")&&P("idle");}},{isActive:f==="picking"}),useInput(m=>{f==="confirming"&&y&&(m==="y"||m==="Y"?(c(y),P("idle"),H(null)):(m==="n"||m==="N"||m==="Escape")&&(P("idle"),H(null)));},{isActive:f==="confirming"});let v=r.length>0,E=r.filter(m=>m.revealedAt!==void 0),L=s!==null?E.slice(0,-1):E;return J?jsx(Po,{onPick:m=>{Y(false),m==="end-session"&&B(true);},onClose:()=>Y(false)}):O?jsx(it,{roomCode:I,onPick:m=>{R(false),A(m);},onClose:()=>R(false)}):jsxs(Box,{flexDirection:"column",gap:1,children:[jsx(Text,{bold:true,children:v?"Session":"Waiting for participants..."}),!v&&jsx(Text,{dimColor:true,children:"No rounds started yet."}),jsx(st,{participants:e.participants,onlineIds:t,hostId:e.settings.hostParticipantId,votes:{},revealed:false}),s?.revealedAt!==void 0&&jsx(pr,{projection:e,round:s,rounds:r}),L.length>0&&jsxs(Box,{flexDirection:"column",children:[jsx(Text,{dimColor:true,children:"\u2500\u2500 History \u2500\u2500"}),L.map(m=>{let M=r.indexOf(m)+1;return jsx(Text,{dimColor:true,children:ur(m,M)},m.id)})]}),f==="picking"&&S.length>0&&jsxs(Box,{flexDirection:"column",gap:1,children:[jsx(Text,{children:"Hand off to:"}),S.map((m,M)=>jsxs(Text,{children:["[",M+1,"] ",m.displayName]},m.id)),jsx(Text,{dimColor:true,children:"[q] Cancel"})]}),f==="picking"&&S.length===0&&jsxs(Box,{flexDirection:"column",gap:1,children:[jsx(Text,{dimColor:true,children:"No other online participants"}),jsx(Text,{dimColor:true,children:"[q] Cancel"})]}),f==="confirming"&&y&&jsx(Box,{flexDirection:"column",children:jsxs(Text,{children:["Hand off to ",e.participants.find(m=>m.id===y)?.displayName,"? [y/n]"]})}),u&&jsxs(Box,{flexDirection:"column",children:[jsxs(Box,{gap:1,children:[jsx(Text,{children:"Round title (optional, Enter to skip):"}),jsx(W,{value:g,onChange:w,onSubmit:x,onCancel:T,placeholder:"Enter to skip",isActive:u})]}),jsx(Text,{dimColor:true,children:"[Esc] Cancel"})]}),G&&jsxs(Box,{children:[jsx(Text,{color:"red",children:"End session for everyone? This cannot be undone. "}),jsx(Text,{dimColor:true,children:"y/n [Esc] Cancel"})]}),f==="idle"&&!u&&!G&&jsxs(Box,{gap:2,children:[o?jsx(Fragment,{children:s?.revealedAt!==void 0?jsxs(Fragment,{children:[jsx(Text,{color:"yellow",children:"[v] Revote"}),jsx(Text,{color:"yellow",children:"[n] Next"}),jsx(Text,{color:"yellow",children:"[h] Handoff"}),jsx(Text,{color:"yellow",children:"[e] Settings"}),jsx(Text,{color:"yellow",children:"[c] Share"}),jsx(Text,{color:"yellow",children:"[m] Menu"})]}):jsxs(Fragment,{children:[jsx(Text,{color:"yellow",children:"[s] Start round"}),jsx(Text,{color:"yellow",children:"[h] Handoff"}),jsx(Text,{color:"yellow",children:"[e] Settings"}),jsx(Text,{color:"yellow",children:"[c] Share"}),jsx(Text,{color:"yellow",children:"[m] Menu"})]})}):jsxs(Fragment,{children:[jsx(Text,{dimColor:true,children:"Waiting for host to start a round..."}),jsx(Text,{children:"[c] Share"})]}),jsx(Text,{children:"[q] Quit"})]})]})}function pr({projection:e,round:t,rounds:o}){let n=o.indexOf(t)+1,r=t.title?` \u2014 ${t.title}`:"",s=t.ticketId?` [${t.ticketId}]`:"",p=dr(t.votes),a=new Map(e.participants.map(i=>[i.id,i.displayName])),d=t.timerStartedAt!==void 0&&t.revealedAt!==void 0?Oo(t.timerStartedAt,t.revealedAt):null,l=Object.values(t.votes)[0];return jsxs(Box,{flexDirection:"column",gap:1,children:[jsxs(Text,{bold:true,children:["Round ",n,r,s," \u2014 Results"]}),to(t)&&jsxs(Text,{bold:true,color:"green",children:["Consensus! Everyone voted ",l]}),jsxs(Box,{flexDirection:"column",children:[jsxs(Box,{gap:2,children:[jsx(Text,{bold:true,underline:true,children:"Name".padEnd(20)}),jsx(Text,{bold:true,underline:true,children:"Estimate".padEnd(10)})]}),Object.entries(t.votes).map(([i,c])=>jsxs(Box,{gap:2,children:[jsx(Text,{children:(a.get(i)??i).padEnd(20)}),jsx(Text,{color:"cyan",bold:true,children:c.padEnd(10)})]},i)),e.participants.filter(i=>!(i.id in t.votes)).map(i=>jsxs(Box,{gap:2,children:[jsx(Text,{dimColor:true,children:i.displayName.padEnd(20)}),jsx(Text,{dimColor:true,children:"(no vote)".padEnd(10)})]},i.id))]}),jsxs(Box,{gap:2,children:[jsxs(Text,{children:["Avg: ",jsx(Text,{bold:true,children:p.average})]}),jsxs(Text,{children:["Med: ",jsx(Text,{bold:true,children:p.median})]}),jsxs(Text,{children:["Agreement: ",jsx(Text,{bold:true,children:p.agreement})]}),d!==null&&jsxs(Text,{dimColor:true,children:["\u23F1 ",d]})]})]})}function Ho({scale:e,selected:t,onSelect:o,onClear:n,disabled:r}){let[s,p]=useState(0);return useInput((a,d)=>{if(!r)if(d.leftArrow)p(l=>Math.max(0,l-1));else if(d.rightArrow)p(l=>Math.min(e.cards.length-1,l+1));else if(d.return){let l=e.cards[s];l===t?n():o(l);}else (d.backspace||d.delete)&&n();}),jsxs(Box,{flexDirection:"column",gap:0,children:[jsx(Box,{gap:1,children:e.cards.map((a,d)=>{let l=d===s,i=a===t;return jsx(Text,{inverse:l,bold:i,color:i?"cyan":void 0,children:i?`[${a}]`:` ${a} `},a)})}),jsx(Text,{dimColor:true,children:" \u2190\u2192 navigate \u23CE select \u232B clear"})]})}function Sr(e){let t=Math.floor(e/1e3),o=Math.floor(t/60),n=t%60;return `${o}:${n.toString().padStart(2,"0")}`}function Bo({projection:e,round:t,onlineIds:o,isHost:n,myId:r,myRole:s,elapsedMs:p,roomCode:a,shareOpen:d,settingsOpen:l,onVote:i,onClearVote:c,onReveal:h,onToggleRole:A,onShare:O,onSetShareOpen:R,onOpenSettings:I,confirmingQuit:k}){if(useInput(T=>{if(T==="e"||T==="E"){I();return}if(T==="c"||T==="C"){R(true);return}n&&(T==="r"||T==="R")&&h(),(T==="w"||T==="W")&&A();},{isActive:!k&&!l&&!d}),d)return jsx(it,{roomCode:a,onPick:T=>{R(false),O(T);},onClose:()=>R(false)});let u=t.votes[r],g=e.rounds.indexOf(t)+1,w=t.title?` \u2014 ${t.title}`:"",x=t.ticketId?` [${t.ticketId}]`:"";return jsxs(Box,{flexDirection:"column",gap:1,children:[jsxs(Box,{gap:2,children:[jsxs(Text,{bold:true,children:["Round ",g,w,x]}),p!==void 0&&jsxs(Text,{dimColor:true,children:["\u23F1 ",Sr(p)]})]}),jsxs(Box,{gap:4,children:[jsx(st,{participants:e.participants,onlineIds:o,hostId:e.settings.hostParticipantId,votes:t.votes,revealed:false}),s==="viewer"?jsx(Box,{flexDirection:"column",children:jsx(Text,{dimColor:true,children:"You're observing"})}):jsx(Ho,{scale:t.scale,selected:u,onSelect:i,onClear:c,disabled:k})]}),jsxs(Box,{gap:2,children:[n&&jsx(Text,{color:"yellow",children:"[r] Reveal"}),jsxs(Text,{color:"yellow",children:["[w] ",s==="viewer"?"Vote":"Observe"]}),jsx(Text,{children:"[e] Settings"}),jsx(Text,{children:"[c] Share"}),jsx(Text,{children:"[q] Quit"})]})]})}var vr=3e3;function Bt(e=vr){let[t,o]=useState(null),n=useRef(null),r=useCallback(()=>{n.current!==null&&(clearTimeout(n.current),n.current=null);},[]),s=useCallback(a=>{r(),o(a),n.current=setTimeout(()=>{o(null),n.current=null;},e);},[r,e]),p=useCallback(()=>{r(),o(null);},[r]);return useEffect(()=>()=>{r();},[r]),{notice:t,show:s,dismiss:p}}function Ir(e){let t=Z(e);return !t||t.revealedAt!==void 0?"lobby":"voting"}function ut({session:e,participantId:t,displayName:o,accessMode:n,presetOverrides:r,customScales:s}){let{exit:p}=useApp(),[a,d]=useState(e.projection()),[l,i]=useState("connecting"),[c,h]=useState(e.onlineIds()),[A,O]=useState(void 0),[R,I]=useState(false),[k,u]=useState(n==="password"?"password":"open"),[g,w]=useState(o),[x,T]=useState(false),[f,P]=useState(false),[y,H]=useState(""),J=Bt(3e3),[Y,G]=useState(false),B=Bt(3e3);useEffect(()=>{let N=e.onChange(()=>{d(e.projection()),h(e.onlineIds());}),z=e.onStatusChange(i),nn=e.onHostChange(Be=>{let sn=e.projection().participants.find(cn=>cn.id===Be),an=Be===t?"You are now the host":`${sn?.displayName||"Unknown"} is now the host`;J.show(an);}),rn=setInterval(()=>{h(e.onlineIds());let Be=e.projection(),Ue=Z(Be);Ue?.timerStartedAt!==void 0&&Ue.revealedAt===void 0?O(Date.now()-Ue.timerStartedAt):O(void 0);},1e3);return ()=>{N(),z(),nn(),clearInterval(rn);}},[e,t,J.show]),useInput(N=>{(N==="q"||N==="Q")&&T(true);},{isActive:!R&&!x&&!f&&!Y}),useInput((N,z)=>{N==="y"||N==="Y"?(e.disconnect(),p()):(N==="n"||N==="N"||z.escape)&&T(false);},{isActive:x});let S=Ir(a),v=Z(a),E=a.settings.hostParticipantId===t,L=a.participants.find(N=>N.id===t),m=ue(L??{}),M=useCallback(()=>{H(""),P(true);},[]),K=useCallback(N=>{P(false),H(""),e.newRound(N.trim()||void 0);},[e]),Fo=useCallback(()=>{P(false),H("");},[]),Vo=useCallback(N=>{e.vote(N);},[e]),Yo=useCallback(()=>{e.unvote();},[e]),Jo=useCallback(()=>{e.reveal();},[e]),Wo=useCallback(()=>{H(""),P(true);},[]),qo=useCallback(()=>{e.revote();},[e]),Go=useCallback(()=>{e.toggleRole();},[e]),zo=useCallback(N=>{e.updateName(N);},[e]),Qo=useCallback(N=>{e.updateScale(N);},[e]),Ko=useCallback(N=>{e.updateDisplayName(N),w(N);},[e]),Xo=useCallback(async(N,z)=>{await e.updateAccess(N,z),u(N);},[e]),Zo=useCallback(N=>{try{e.handoffHost(N);}catch(z){console.error(`Handoff failed: ${z instanceof Error?z.message:String(z)}`);}},[e]),Lt=useCallback(()=>{I(true);},[]),en=useCallback(()=>{I(false);},[]),tn=useCallback(()=>{e.endSessionForEveryone();},[e]),_t=useCallback(async N=>{let z=N==="code"?Qe(e.roomCode):N==="url"?Ke(e.roomCode):Xe(e.roomCode);try{await Rr.write(z),B.show("Copied!");}catch{B.show(`Copy failed \u2014 ${z}`);}},[e.roomCode,B]),ft=a.rounds.filter(N=>N.revealedAt!==void 0),on=ft.length>0?ft[ft.length-1]:null;return jsxs(Box,{flexDirection:"column",children:[jsx(yo,{roomCode:e.roomCode,sessionName:a.settings.name,status:l,isHost:E}),J.notice&&jsx(Box,{paddingX:1,children:jsx(Text,{color:"yellow",children:J.notice})}),B.notice&&jsx(Box,{paddingX:1,children:jsx(Text,{color:B.notice.startsWith("Copy failed")?"red":"green",children:B.notice})}),R?jsx(Eo,{sessionName:a.settings.name,displayName:g,currentScale:a.settings.defaultScale,accessMode:k,presetOverrides:r,customScales:s,isHost:E,onChangeName:zo,onChangeScale:Qo,onChangeDisplayName:Ko,onChangeAccess:Xo,onClose:en}):S==="lobby"?jsx(Mo,{projection:a,onlineIds:c,isHost:E,participantId:t,rounds:a.rounds,currentRound:on,settingsOpen:R,onStartRound:M,onNewRound:Wo,roundTitlePromptActive:f,roundTitleInput:y,onRoundTitleChange:H,onRoundTitleSubmit:K,onRoundTitleCancel:Fo,onRevote:qo,onOpenSettings:Lt,onHandoffHost:Zo,onEndSession:tn,onShare:_t,shareOpen:Y,onSetShareOpen:G,roomCode:e.roomCode,confirmingQuit:x}):S==="voting"&&v?jsx(Bo,{projection:a,round:v,onlineIds:c,isHost:E,myId:t,myRole:m,elapsedMs:A,onVote:Vo,onClearVote:Yo,onReveal:Jo,onToggleRole:Go,onShare:_t,onSetShareOpen:G,shareOpen:Y,settingsOpen:R,onOpenSettings:Lt,roomCode:e.roomCode,confirmingQuit:x}):null,x&&jsx(Box,{children:jsx(Text,{dimColor:true,children:"Leave session? y/n"})})]})}function $o({presetOverrides:e,customScales:t}){let{exit:o}=useApp(),[n,r]=useState("main-menu"),[s,p]=useState(de()),[a,d]=useState(s.displayName??""),[l,i]=useState(s.relayUrl??""),[c,h]=useState(s.defaultSessionName??""),[A,O]=useState(t??[]),[R,I]=useState(e??{});useInput(S=>{(S==="q"||S==="Q")&&o();},{isActive:n==="main-menu"});let k=useMemo(()=>Je(R,A),[R,A]),u=s.defaultScale,g=u?k.find(S=>S.id===u):void 0,w=[{label:"Display name",value:"display-name",detail:`[${s.displayName??"(none)"}]`},{label:"Relay URL",value:"relay-url",detail:`[${s.relayUrl??"(none)"}]`},{label:"Default scale",value:"default-scale",detail:`[${g?pe(g.id):"(none)"}]`},{label:"Default session name",value:"default-session-name",detail:`[${s.defaultSessionName??"(none)"}]`},{label:"Default password",value:"default-password",detail:`[${s.defaultPassword?"on":"off"}]`}];function x(S){S==="display-name"?(d(s.displayName??""),r("editing-display-name")):S==="relay-url"?(i(s.relayUrl??""),r("editing-relay-url")):S==="default-scale"?r("picking-scale"):S==="default-session-name"?(h(s.defaultSessionName??""),r("editing-session-name")):S==="default-password"&&r("picking-default-password");}function T(S){let v=S.trim();if(v.length>0){let E={...s,displayName:v};p(E),_({displayName:v});}r("main-menu");}function f(S){let v=S.trim();if(v.length>0){let E={...s,relayUrl:v};p(E),_({relayUrl:v});}r("main-menu");}function P(S){let v=S.trim(),E=v.length>0?{...s,defaultSessionName:v}:{...s};v.length===0?(delete E.defaultSessionName,p(E),Ee({defaultSessionName:""})):(p(E),_({defaultSessionName:v})),r("main-menu");}function y(S){let v=S.length>0?{...s,defaultScale:S}:{...s};S.length===0?(delete v.defaultScale,p(v),Ee({defaultScale:""})):(p(v),_({defaultScale:S})),r("main-menu");}function H(S,v){let E={...R};v.length===0?delete E[S]:E[S]=v,I(E),_({presetOverrides:Object.keys(E).length>0?E:void 0});}function J(S){let v=S==="on",E={...s,defaultPassword:v};p(E),_({defaultPassword:v}),r("main-menu");}function Y(S,v){let E=`custom-${randomUUID()}`,L=[...A,{id:E,label:S,cards:v}];O(L),p({...s,customScales:L}),_({customScales:L});}function G(S,v,E){let L=A.map(m=>m.id===S?{id:S,label:v,cards:E}:m);O(L),p({...s,customScales:L}),_({customScales:L});}function B(S){let v=A.filter(L=>L.id!==S);O(v);let E={...s,customScales:v};s.defaultScale===S?(E.defaultScale="fibonacci",_({customScales:v,defaultScale:"fibonacci"})):_({customScales:v}),p(E);}if(n==="main-menu")return jsx(ne,{title:"\u2500\u2500 Config Settings \u2500\u2500",items:w,onSelect:x,onClose:()=>o()});if(n==="editing-display-name")return jsxs(Box,{flexDirection:"column",gap:1,children:[jsx(Text,{bold:true,children:"\u2500\u2500 Config Settings \u2500\u2500"}),jsx(Text,{children:"Display name: "}),jsx(W,{value:a,onChange:d,onSubmit:T,onCancel:()=>r("main-menu"),placeholder:"(required)",isActive:true}),jsx(Text,{dimColor:true,children:"\u23CE confirm esc back"})]});if(n==="editing-relay-url")return jsxs(Box,{flexDirection:"column",gap:1,children:[jsx(Text,{bold:true,children:"\u2500\u2500 Config Settings \u2500\u2500"}),jsx(Text,{children:"Relay URL: "}),jsx(W,{value:l,onChange:i,onSubmit:f,onCancel:()=>r("main-menu"),placeholder:"(required)",isActive:true}),jsx(Text,{dimColor:true,children:"\u23CE confirm esc back"})]});if(n==="editing-session-name")return jsxs(Box,{flexDirection:"column",gap:1,children:[jsx(Text,{bold:true,children:"\u2500\u2500 Config Settings \u2500\u2500"}),jsx(Text,{children:"Default session name: "}),jsx(W,{value:c,onChange:h,onSubmit:P,onCancel:()=>r("main-menu"),placeholder:"(leave empty to clear)",isActive:true}),jsx(Text,{dimColor:true,children:"\u23CE confirm esc back"})]});if(n==="picking-scale"){let S=[{id:"",cards:[]},...k];return jsxs(Box,{flexDirection:"column",gap:1,children:[jsx(Text,{bold:true,children:"\u2500\u2500 Config Settings \u2500\u2500"}),jsx(tt,{scales:S,selectedScaleId:u??"",presetOverrides:R,customScales:A,onSelect:y,onOverrideChange:H,onCustomScaleCreate:Y,onCustomScaleEdit:G,onCustomScaleDelete:B,onClose:()=>r("main-menu"),showNoneOption:true})]})}if(n==="picking-default-password"){let S=[{label:"On",value:"on",detail:s.defaultPassword?"[current]":void 0},{label:"Off",value:"off",detail:s.defaultPassword?void 0:"[current]"}];return jsxs(Box,{flexDirection:"column",gap:1,children:[jsx(Text,{bold:true,children:"\u2500\u2500 Config Settings \u2500\u2500"}),jsx(ne,{title:"Default password:",items:S,onSelect:J,onClose:()=>r("main-menu")})]})}return jsx(Box,{})}async function jt(){return new Promise(e=>{let t=createInterface({input:process.stdin,output:process.stderr});process.stderr.write("Password: "),t.question("",o=>{t.close(),e(o);});})}async function kr(e){let t=await Te(),o=e.scale??t.defaultScale??"fibonacci",n=e.name??t.defaultSessionName,r=e.password??t.defaultPassword??false,s;r&&(s=await jt());let p=Ye(o,t.presetOverrides,t.customScales);if(!p){let c=Object.keys(X).join(", ");console.error(`Unknown or fully-disabled scale: "${o}". Builtin IDs: ${c}`),process.exit(1);}let a=await po({participantId:t.participantId,displayName:t.displayName,relayUrl:t.relayUrl,sessionName:n,password:s,defaultScale:p}),d;s&&(d=await me(s,a.roomUuid)),qe({roomCode:a.roomCode,roomUuid:a.roomUuid,passwordHash:d,lastSeenAt:Date.now()}),console.error(`Room created: ${a.roomCode}`),console.error(`Share: hnch join ${a.roomCode}`),console.error("");let{waitUntilExit:l,unmount:i}=render(at.createElement(ut,{session:a,participantId:t.participantId,displayName:t.displayName,accessMode:s?"password":"open",presetOverrides:t.presetOverrides,customScales:t.customScales}));ze({doc:a.doc,localParticipantId:t.participantId,onEnded:async c=>{console.error(c.isLocal?"Session ended.":`Session ended by ${c.hostName}.`),i();try{await j();}catch(h){console.error(`Failed to clear cached session: ${h instanceof Error?h.message:String(h)}`);}a.disconnect(),process.exit(0);}}),await l(),a.disconnect(),j(),process.exit(0);}async function Hr(e){let t=await Te(),o=fo(e.code);o===null&&(console.error("invalid room code"),process.exit(1));let n;e.password&&(n=await jt());let r=await Ne(t.relayUrl,o),s;r.accessMode==="password"&&!e.password?s=await jt():e.password&&(s=n);let p,a=false,d=await mo({participantId:t.participantId,displayName:t.displayName,relayUrl:t.relayUrl,roomCode:o,password:s,onRejection(c){if(c==="unknown-room"){Ge({clearLastSession:j,logger:console,exit:process.exit});return}c==="room-full"&&(a=true,p?.());}}),l;s&&(l=await me(s,d.roomUuid)),qe({roomCode:d.roomCode,roomUuid:d.roomUuid,passwordHash:l,lastSeenAt:Date.now()}),console.error(`Joined room: ${d.roomCode}`),console.error("");let i=render(at.createElement(ut,{session:d,participantId:t.participantId,displayName:t.displayName,accessMode:r.accessMode,presetOverrides:t.presetOverrides,customScales:t.customScales}));p=i.unmount,ze({doc:d.doc,localParticipantId:t.participantId,onEnded:async c=>{console.error(c.isLocal?"Session ended.":`Session ended by ${c.hostName}.`),p?.();try{await j();}catch(h){console.error(`Failed to clear cached session: ${h instanceof Error?h.message:String(h)}`);}d.disconnect(),process.exit(0);}}),await i.waitUntilExit(),a&&(console.error("This room is full (max 20 participants)."),d.disconnect(),j(),process.exit(1)),d.disconnect(),j(),process.exit(0);}async function Br(){let e=await Te(),t=ho();t||(console.error("No recent session to resume."),process.exit(0));let o;try{o=await Ne(e.relayUrl,t.roomCode);}catch(i){if((i instanceof Error?i.message:String(i)).includes("Room not found"))return await Ge({clearLastSession:j,logger:console,exit:process.exit});console.error("Could not reach relay \u2014 try again later."),process.exit(0);}o.accessMode==="password"&&!t.passwordHash&&(console.error(`Password changed \u2014 rejoin with: hnch join ${t.roomCode} --password`),j(),process.exit(0)),console.error(`Rejoining room ${t.roomCode}\u2026`),console.error("");let n=new we.Doc,r=new Awareness(n);Ve(n,{participantId:e.participantId,displayName:e.displayName}),r.setLocalStateField("participantId",e.participantId);let s=false,p=false,a,d=We({doc:n,awareness:r,roomCode:t.roomCode,roomUuid:t.roomUuid,participantId:e.participantId,relayUrl:e.relayUrl,passwordHash:t.passwordHash,onRejection(i){if(i==="unknown-room"){Ge({clearLastSession:j,logger:console,exit:process.exit});return}i==="wrong-password"&&(s=true,a?.()),i==="room-full"&&(p=true,a?.());}});qe({roomCode:d.roomCode,roomUuid:d.roomUuid,passwordHash:t.passwordHash,lastSeenAt:Date.now()});let l=render(at.createElement(ut,{session:d,participantId:e.participantId,displayName:e.displayName,accessMode:o.accessMode,presetOverrides:e.presetOverrides,customScales:e.customScales}));a=l.unmount,ze({doc:d.doc,localParticipantId:e.participantId,onEnded:async i=>{console.error(i.isLocal?"Session ended.":`Session ended by ${i.hostName}.`),a?.();try{await j();}catch(c){console.error(`Failed to clear cached session: ${c instanceof Error?c.message:String(c)}`);}d.disconnect(),process.exit(0);}}),await l.waitUntilExit(),p&&(console.error("This room is full (max 20 participants)."),j(),process.exit(1)),s&&(console.error(`Password changed \u2014 rejoin with: hnch join ${t.roomCode} --password`),j(),process.exit(0)),d.disconnect(),j(),process.exit(0);}async function Ur(e){if(!(e.name!==void 0||e.relayUrl!==void 0||e.scale!==void 0||e.sessionName!==void 0||e.password!==void 0)){let n=await Te(),{waitUntilExit:r}=render(at.createElement($o,{presetOverrides:n.presetOverrides,customScales:n.customScales}));await r(),process.exit(0);}let o={};if(e.name!==void 0&&(o.displayName=e.name),e.relayUrl!==void 0&&(o.relayUrl=e.relayUrl),e.scale!==void 0){if(e.scale!==""){let n=de();if(!Ye(e.scale,n.presetOverrides,n.customScales)){console.error(`Unknown scale: "${e.scale}". Builtin IDs: ${Object.keys(X).join(", ")}`);return}}o.defaultScale=e.scale;}e.sessionName!==void 0&&(o.defaultSessionName=e.sessionName),e.password!==void 0&&(o.defaultPassword=e.password),Ee(o),console.log("Config updated.");}var _o=Or(hideBin(process.argv)).scriptName("hnch").usage("$0 <command> \u2014 terminal pointing poker").command("create","Create a new room",e=>e.option("name",{type:"string",describe:"Session name"}).option("password",{type:"boolean",describe:"Protect room with a password"}).option("scale",{type:"string",describe:"Estimation scale ID (fibonacci, t-shirt, powers-of-2, linear, risk, or custom-<id>)"}),e=>{kr(e).catch(t=>{console.error(t instanceof Error?t.message:t),process.exit(1);});}).command("join <code>","Join an existing room",e=>e.positional("code",{type:"string",demandOption:true,describe:"6-character room code"}).option("password",{type:"boolean",describe:"Room requires a password",default:false}),e=>{Hr(e).catch(t=>{console.error(t instanceof Error?t.message:t),process.exit(1);});}).command("resume","Rejoin your last session",e=>e,()=>{Br().catch(e=>{console.error(e instanceof Error?e.message:e),process.exit(1);});}).command("config","View or update CLI config",e=>e.option("name",{type:"string",describe:"Set display name"}).option("relay-url",{type:"string",describe:"Set relay URL"}).option("scale",{type:"string",describe:"Set default estimation scale ID (empty string clears)"}).option("session-name",{type:"string",describe:"Set default session name (empty string clears)"}).option("password",{type:"boolean",describe:"Set default password prompt on (--password) or off (--no-password)"}),e=>{Ur(e).catch(t=>{console.error(t instanceof Error?t.message:t),process.exit(1);});}).demandCommand(1,"Specify a command: create, join, resume, or config").strict().help(),$t=hideBin(process.argv);($t.length===0||$t.length===1&&$t[0]==="--")&&(_o.showHelp(),process.exit(0));_o.parse();
|