@vercel/board 0.30.0 → 0.31.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.
Files changed (125) hide show
  1. package/README.md +8 -17
  2. package/dist/{a2-D_28Pdip.mjs → a2-DngWY2Eb.mjs} +573 -361
  3. package/dist/{access-CQ39gqEV.mjs → access-CY5xVMIK.mjs} +19 -3
  4. package/dist/{access-CV96SYgr.d.mts → access-DPcrKIH_.d.mts} +6 -1
  5. package/dist/access.d.mts +4 -4
  6. package/dist/access.mjs +5 -4
  7. package/dist/{actors-xPiamLWk.mjs → actors-Bm-ojWWT.mjs} +2 -2
  8. package/dist/{auth-BPinwfN0.mjs → auth-CyO2aw5n.mjs} +127 -14
  9. package/dist/{auth-CU56Q2Xh.d.mts → auth-DLYSt_-A.d.mts} +32 -4
  10. package/dist/{backend-n3hajfrD.d.mts → backend-Bob-5CM7.d.mts} +86 -41
  11. package/dist/backend-a2/blob-vercel.d.mts +1 -1
  12. package/dist/backend-a2/contract.d.mts +37 -97
  13. package/dist/backend-a2/contract.mjs +1 -1
  14. package/dist/backend-a2/store-memory.d.mts +1 -1
  15. package/dist/backend-a2/store-memory.mjs +2 -2
  16. package/dist/backend-a2/store-postgres.d.mts +1 -1
  17. package/dist/backend-a2/store-postgres.mjs +1 -1
  18. package/dist/backend-a2/store-redis-http.d.mts +1 -1
  19. package/dist/backend-a2/store-redis-http.mjs +1 -1
  20. package/dist/backend-a2/store-redis.d.mts +1 -1
  21. package/dist/backend-a2/store-redis.mjs +1 -1
  22. package/dist/backend-a2/store-sqlite.d.mts +1 -1
  23. package/dist/backend-a2/store-sqlite.mjs +2 -2
  24. package/dist/backend-a2.d.mts +131 -240
  25. package/dist/backend-a2.mjs +2 -2
  26. package/dist/{backend-http-3PoBYQoo.d.mts → backend-http-BRrRbjRx.d.mts} +49 -45
  27. package/dist/{backend-http-BPaNYLWj.mjs → backend-http-CvUKOn-m.mjs} +11 -9
  28. package/dist/{backend-http-16SbvnAK.mjs → backend-http-D8mUzuKQ.mjs} +2 -2
  29. package/dist/backend-http.d.mts +1 -1
  30. package/dist/backend-http.mjs +1 -1
  31. package/dist/backend-http.node.d.mts +1 -1
  32. package/dist/backend-http.node.mjs +1 -1
  33. package/dist/{backend-vercel-Kd8nvW5A.mjs → backend-vercel-Ba8JDtqW.mjs} +3 -3
  34. package/dist/{backend-vercel-qznuDxnN.d.mts → backend-vercel-BaSEUdwg.d.mts} +49 -45
  35. package/dist/{backend-vercel-C4Kee2-K.mjs → backend-vercel-CmY9yZ9m.mjs} +80 -15
  36. package/dist/backend-vercel.d.mts +1 -1
  37. package/dist/backend-vercel.mjs +1 -1
  38. package/dist/backend-vercel.node.d.mts +1 -1
  39. package/dist/backend-vercel.node.mjs +1 -1
  40. package/dist/{blob-CqILvdmr.d.mts → blob-Bx8QPWEd.d.mts} +2 -2
  41. package/dist/{blob-DQK5prXT.mjs → blob-WbB2DJKn.mjs} +30 -11
  42. package/dist/{chat-provider-D6jVT4wS.d.mts → chat-provider-BChYrrA7.d.mts} +32 -28
  43. package/dist/{chat-provider-COtsU5jB.mjs → chat-provider-DkFp2hrh.mjs} +105 -60
  44. package/dist/cli-commands.d.mts +1 -1
  45. package/dist/cli-commands.mjs +1 -1
  46. package/dist/cli-embedded.d.mts +2 -2
  47. package/dist/cli-embedded.mjs +1 -1
  48. package/dist/cli.mjs +4 -4
  49. package/dist/{client-CjAatpV4.d.mts → client-COfYrgfV.d.mts} +184 -221
  50. package/dist/{commands-BtH69yqt.d.mts → commands-DK4W3HCJ.d.mts} +30 -10
  51. package/dist/{commands-B5DHjZKK.mjs → commands-L1JSGz2Z.mjs} +162 -45
  52. package/dist/{conformance-C7fwIvmN.mjs → conformance-DK43HiHf.mjs} +224 -20
  53. package/dist/conformance.d.mts +8 -2
  54. package/dist/conformance.mjs +1 -1
  55. package/dist/{engine-DkdkXQEy.mjs → engine-DurYIRt8.mjs} +29 -210
  56. package/dist/{errors-87TVtXeb.d.mts → errors-2vGs49Db.d.mts} +1 -1
  57. package/dist/{errors-C_5rB8Iz.mjs → errors-CJXhhzvT.mjs} +28 -23
  58. package/dist/{provider-BaVmi4Hi.mjs → events-lujPA3gQ.mjs} +80 -17
  59. package/dist/{exports-CG0P8Qi4.mjs → exports-CWIdN0Ub.mjs} +146 -80
  60. package/dist/exports-FBOQWT7O.d.mts +5 -0
  61. package/dist/{folds-DGYkMW19.mjs → folds-Cu9Q9zyQ.mjs} +2 -22
  62. package/dist/{folds-8wRRXjAt.d.mts → folds-DLUzfibJ.d.mts} +13 -40
  63. package/dist/{index-BygAXP7t.d.mts → index-Ho-ayyQL.d.mts} +2 -2
  64. package/dist/index.d.mts +7 -7
  65. package/dist/{index.default-DhkgpfR2.mjs → index.default-BUcwDaAe.mjs} +1 -1
  66. package/dist/index.mjs +5 -5
  67. package/dist/index.node.d.mts +7 -7
  68. package/dist/index.node.mjs +3 -3
  69. package/dist/inspect/app.js +62 -61
  70. package/dist/inspect/engine.js +3 -3
  71. package/dist/{main-D2s-QBOK.mjs → main-Dz-8komG.mjs} +189 -112
  72. package/dist/{middleware-DlJQYiwV.mjs → middleware-BavYSDUa.mjs} +1 -1
  73. package/dist/{node-D8Rtg1su.mjs → node-BgeGcwX0.mjs} +7 -3
  74. package/dist/{project-C_2xs57L.mjs → project-CQ4VUibA.mjs} +1 -1
  75. package/dist/{provider-Bm46LAdp.d.mts → provider-gynDUoyW.d.mts} +13 -43
  76. package/dist/{redis-tables-BWJZHUGg.mjs → redis-tables-DHf85ixI.mjs} +1 -19
  77. package/dist/{reducers-8jqLewgw.mjs → reducers-DcENwtwZ.mjs} +72 -75
  78. package/dist/{serve-lPoBDEds.mjs → serve-CTmow-YH.mjs} +6 -6
  79. package/dist/{server-D82a4uMe.mjs → server-DG8D6VV0.mjs} +4 -4
  80. package/dist/{serving-DKny38LK.mjs → serving-DBFRo6U_.mjs} +1 -1
  81. package/dist/{sql-Bj0fk6dn.mjs → sql-CT5xVuGr.mjs} +2 -8
  82. package/dist/{sqlite-db-Dc8G78XV.mjs → sqlite-db-0WeSOEZN.mjs} +1 -1
  83. package/dist/{sync-kapso.d.mts → sync-chat-sdk.d.mts} +33 -37
  84. package/dist/sync-chat-sdk.mjs +44 -0
  85. package/dist/sync-github.d.mts +5 -18
  86. package/dist/sync-github.mjs +4 -70
  87. package/dist/sync-linear.d.mts +3 -3
  88. package/dist/sync-linear.mjs +2 -3
  89. package/dist/sync-slack.d.mts +7 -7
  90. package/dist/sync-slack.mjs +33 -54
  91. package/dist/sync.d.mts +3 -3
  92. package/dist/sync.mjs +2 -4
  93. package/dist/{tokens-C5_yGRlL.mjs → tokens-Dnyd_y1n.mjs} +15 -6
  94. package/dist/{types-shZmGZ-q.d.mts → types-CmrFjpml.d.mts} +1 -3
  95. package/dist/{watchers-hash-B_5b0-ZG.mjs → watchers-hash-BjwlnGD9.mjs} +85 -105
  96. package/docs/01-board/01-mounting.md +2 -2
  97. package/docs/01-board/03-identity.md +5 -6
  98. package/docs/01-board/04-messages.md +3 -2
  99. package/docs/01-board/05-reading.md +2 -1
  100. package/docs/01-board/06-watchers.md +8 -6
  101. package/docs/01-board/08-middleware.md +1 -1
  102. package/docs/01-board/09-access.md +145 -14
  103. package/docs/02-sync/01-overview.md +19 -26
  104. package/docs/02-sync/02-functions.md +8 -9
  105. package/docs/02-sync/03-slack.md +5 -5
  106. package/docs/02-sync/04-github.md +2 -2
  107. package/docs/02-sync/06-providers.md +91 -62
  108. package/docs/03-guides/01-coding-agents.md +1 -1
  109. package/docs/03-guides/03-ci.md +7 -4
  110. package/docs/04-reference/01-cli.md +86 -41
  111. package/docs/04-reference/02-board.md +56 -23
  112. package/docs/04-reference/03-http.md +59 -32
  113. package/docs/04-reference/04-log.md +8 -11
  114. package/docs/04-reference/05-conformance.md +35 -6
  115. package/docs/index.md +2 -2
  116. package/package.json +6 -9
  117. package/sql/postgres/board.sql +1 -1
  118. package/sql/postgres/schema.sql +1 -1
  119. package/dist/events-qf5XG5VQ.mjs +0 -75
  120. package/dist/exports-CrGA5_yN.d.mts +0 -5
  121. package/dist/sync-kapso.mjs +0 -89
  122. package/dist/sync.chat.d.mts +0 -2
  123. package/dist/sync.chat.mjs +0 -2
  124. package/dist/users-yryHKFMo.mjs +0 -42
  125. package/docs/02-sync/05-whatsapp.md +0 -152
@@ -1,5 +1,5 @@
1
- import{C as e,D as t,E as n,O as r,S as i,T as a,_ as o,a as s,b as c,c as l,d as u,f as d,g as f,h as p,i as m,k as h,l as g,m as _,n as v,o as y,p as b,r as x,s as ee,t as te,u as S,v as C,w,x as T,y as E}from"./app.js";const D=/^\[vc\]: #\S*\r?\n/,O=/\[([^\]]+)\]\((https?:\/\/[^)\s]+)\)/g,k=/!\[([^\]]+)\]\(https:\/\/vercel\.com\/static\/status\/[^)]+\)\s*(?:\[[^\]]*\]\((https?:\/\/[^)\s]+)\))?/;function A(e){if(!D.test(e))return null;let t=e.search(/<details><summary>\d+ Skipped Deployments?<\/summary>/),n=[],r=0;for(let i of e.split(/\r?\n/)){let a=r;if(r=e.indexOf(`
1
+ import{C as e,D as t,E as n,O as r,S as i,T as a,_ as o,a as s,b as c,c as l,d as u,f as d,g as f,h as p,i as m,l as h,m as g,n as _,o as v,p as y,r as b,s as ee,t as x,u as S,v as C,w,x as T,y as E}from"./app.js";const D=/^\[vc\]: #\S*\r?\n/,O=/\[([^\]]+)\]\((https?:\/\/[^)\s]+)\)/g,k=/!\[([^\]]+)\]\(https:\/\/vercel\.com\/static\/status\/[^)]+\)\s*(?:\[[^\]]*\]\((https?:\/\/[^)\s]+)\))?/;function A(e){if(!D.test(e))return null;let t=e.search(/<details><summary>\d+ Skipped Deployments?<\/summary>/),n=[],r=0;for(let i of e.split(/\r?\n/)){let a=r;if(r=e.indexOf(`
2
2
  `,r)+1||e.length,!i.startsWith(`|`)||/^\|\s*(Project|Name)\s*\|/.test(i)||/^\|\s*:?-/.test(i))continue;let o=i.split(`|`).slice(1,-1).map(e=>e.trim());if(o.length<2)continue;let s=k.exec(o[1]);if(!s)continue;let c=[...o[0].matchAll(O)].at(-1),l=c?.[1]??o[0].replace(/<[^>]*>/g,``).trim();if(!l)continue;let u=o[2]?[...o[2].matchAll(O)].find(e=>/preview/i.test(e[1])):void 0,d=o[3]?.match(/<relative-time[^>]*>([^<]+)<\/relative-time>/)?.[1]?.trim()??(o[3]?.replace(/<[^>]*>/g,``).trim()||null);n.push({project:l,projectUrl:c?.[2]??null,status:s[1].trim(),deploymentUrl:s[2]??null,previewUrl:u?.[2]??null,updated:d,skipped:t!==-1&&a>t})}return n.length>0?n:null}const j={ready:`✅`,building:`⏳`,queued:`⏳`,initializing:`⏳`,error:`❌`,failed:`❌`,canceled:`⚪`,ignored:`⚪`},M=(e,t)=>t?`[${e}](${t})`:e;function N(e){return e.map(e=>`- ${[`${j[e.status.toLowerCase()]??`▲`} **${M(e.project,e.projectUrl)}**`,`${M(e.status,e.deploymentUrl)}${e.skipped?` (skipped)`:``}`,...e.previewUrl?[M(`Preview`,e.previewUrl)]:[],...e.updated?[e.updated]:[]].join(` · `)}`).join(`
3
- `)}function ne(e,t){let n=t===`github:user:35613825`?A(e):null;return n?`**▲ Vercel**\n${N(n)}`:e}const re=e=>e>10;i({id:e().min(1),platform:e().min(1),key:e().min(1),...g.shape,raw:w()});const ie=i({root:T(e=>typeof e==`function`).optional(),text:e().optional(),labels:p.optional(),unset:E(_).optional(),noSync:c().optional()}).strict();function P(e){if(e&&typeof e==`object`){for(let t of Object.values(e))P(t);Object.freeze(e)}return e}const ae=i({target:e().min(1),channel:e().min(1),kind:C([`channel`,`thread`])}).strict();var F=class extends Error{code;constructor(e,t){super(t),this.code=e}};const oe=e=>e instanceof F&&e.code===`channel_not_found`,se=i({event:l,raw:w(),dedupe:e().min(1)}),I=/* @__PURE__ */ new WeakMap,ce=e=>I.get(e),L=(e,t=!1)=>{let n=o(e),r=n.trim()===``?t?``:`(no text)`:n;return r.length>1e4?`${r.slice(0,f-1).toWellFormed()}…`:r},R=(e,n,r=e.name??`a file`)=>`📎 ${r}${e.size===void 0?``:` (${t(e.size)})`} didn't come over: ${n}`,z=e=>e instanceof m&&e.code===`no_files`,B=e=>{let t=e.name??`a file`;if(!e.url||!/^https?:\/\//.test(e.url))return R(e,`this board keeps no files`);let n=t.replace(/[[\]\\]/g,e=>`\\${e}`),r=e.url.replace(/[ ()]/g,e=>`%${e.charCodeAt(0).toString(16).toUpperCase()}`);return R(e,`this board keeps no files`,`[${n}](${r})`)},V=(e,t)=>t.length?`${e.trim()?`${e}\n\n`:``}${t.join(`
3
+ `)}function te(e,t){let n=t===`github:user:35613825`?A(e):null;return n?`**▲ Vercel**\n${N(n)}`:e}const ne=e=>e>10;T({id:i().min(1),platform:i().min(1),key:i().min(1),...h.shape,raw:e()});const re=T({root:c(e=>typeof e==`function`).optional(),text:i().optional(),labels:g.optional(),unset:C(y).optional(),noSync:E().optional()}).strict();function P(e){if(e&&typeof e==`object`){for(let t of Object.values(e))P(t);Object.freeze(e)}return e}const ie=T({target:i().min(1),channel:i().min(1),kind:o([`channel`,`thread`])}).strict();var F=class extends Error{code;constructor(e,t){super(t),this.code=e}};const ae=e=>e instanceof F&&e.code===`channel_not_found`,oe=T({event:l,raw:e(),dedupe:i().min(1)}),se=/* @__PURE__ */ new WeakMap,ce=e=>se.get(e),I=(e,t=!1)=>{let n=f(e),r=n.trim()===``?t?``:`(no text)`:n;return r.length>1e4?`${r.slice(0,p-1).toWellFormed()}…`:r},L=(e,t,r=e.name??`a file`)=>`📎 ${r}${e.size===void 0?``:` (${n(e.size)})`} didn't come over: ${t}`,R=e=>e instanceof m&&e.code===`no_files`,z=e=>{let t=e.name??`a file`;if(!e.url||!/^https?:\/\//.test(e.url))return L(e,`this board keeps no files`);let n=t.replace(/[[\]\\]/g,e=>`\\${e}`),r=e.url.replace(/[ ()]/g,e=>`%${e.charCodeAt(0).toString(16).toUpperCase()}`);return L(e,`this board keeps no files`,`[${n}](${r})`)},B=(e,t)=>t.length?`${e.trim()?`${e}\n\n`:``}${t.join(`
4
4
  `)}`:e,le=e=>e.split(`
5
- `).filter(e=>/^📎 .* didn't come over: /u.test(e)),H=e=>JSON.stringify([e.url,e.message,e.snapshot,e.unavailable]),ue=(e,t)=>e.length===t.length&&e.every((e,n)=>H(e)===H(t[n])),de=/^\s*`?(vb-[a-z0-9]{4}-[a-z0-9]{4})`?\s*$/i,fe=6e4,pe=new AbortController().signal,me=e=>{try{return JSON.parse(JSON.stringify(e??null))}catch{return null}},U=e=>typeof e==`string`?o(e):Array.isArray(e)?e.map(U):typeof e==`object`&&e?Object.fromEntries(Object.entries(e).map(([e,t])=>[o(e),U(t)])):e,W=e=>{let t=e?.data?.error,n=typeof t==`string`?t:e instanceof Error?e.message:String(e);return o(n.slice(0,500))||`unknown error`},G=e=>e instanceof m&&e.status===404;function he(e){let i=null,o=()=>{if(!i)throw Error(`sync used before board() bound it`);return i},c={self:`sync`,origin:{via:{kind:`engine`}}};function l(e,t,n=pe,r=c){let i=o().backend;return i[e].call(i,r,t,n)}let f=t=>{let n=e[t];if(!n)throw s(`no sync entry named ${t}`);return n},p=e=>e.slice(0,e.indexOf(`:`)),g=t=>e[p(t)]!==void 0,_=e=>e.slice(e.indexOf(`:`)+1);async function C(e){try{return await l(`syncThread`,{thread:e})}catch(e){if(G(e))return null;throw e}}async function w(e){try{let{message:t,replies:n}=await l(`message`,{id:e});return[t,...n]}catch(e){if(G(e))return[];throw e}}async function T(e){return(await w(e)).find(t=>t.id===e)??null}async function E(e){return e.length===0?{}:(await l(`syncCopies`,{message:[...new Set(e)]})).copies}let D=(e,t)=>l(`syncFind`,e,t);async function O(){let{actors:e}=await l(`actors`,{}),t={byId:{},links:{}};for(let{person:n,...r}of e)t.byId[r.id]={id:r.id,kind:r.kind,handle:r.handle,name:r.name,avatar:r.avatar},n&&(t.links[r.id]=n.id);return t}let k=(e,t)=>Object.entries(e.links).filter(([,e])=>e===t).map(([e])=>e),A=/* @__PURE__ */ new Map;function j(e,t){let n=`${e}:${t}`,r=A.get(n);if(r&&(!r.failed||Date.now()-r.at<6e4))return r.id;let i={id:f(e).resolveChannel(t),failed:!1,at:Date.now()};return i.id.catch(r=>{i.failed=!0;let a=oe(r);!a&&A.get(n)===i&&A.delete(n),console.warn(`[board] sync ${e}: ${a?`skipping`:`couldn't resolve`} ${t}: ${r.message}`)}),A.set(n,i),i.id}function M(e,t){if(!d(t))return _(t);try{let n=f(e).inboxChannel(e,t);return n===null?null:_(n)}catch{return null}}async function N(t){for(let n of Object.values(e)){let e=await n.findByEmail(t).catch(()=>null);if(e)return{id:e.id,kind:`external`,handle:e.handle,name:e.name,avatar:e.avatar}}return null}async function I(e){let t=e.indexOf(`:`);if(t<=0)throw s(`sync targets look like <key>:<channel or thread>, got: ${e}`);let n=e.slice(0,t),r=f(n);try{let i=ae.parse(await r.resolveTarget(e.slice(t+1)));if(i.kind===`channel`&&!r.canStartThreads)throw Error(`${r.platform} cannot start threads; use an existing thread`);return{target:`${n}:${i.target}`,kind:i.kind}}catch(t){throw s(`can't sync to ${e}: ${t.message}`)}}async function H(e,t){let{event:n,raw:r,dedupe:i}=se.parse(U({...t,raw:me(t.raw)}));for(let t of[n.channel,n.thread,`subthread`in n?n.subthread:null])if(t!==null&&!t.startsWith(`${e}:`))throw s(`provider locations must belong to sync entry ${e}`);let a=ce(f(e));(!a||await a(_(n.channel),t=>j(e,t))||await he(n.thread))&&await l(`inboxPush`,{key:e,events:[{id:i,event:n,raw:r}]})}async function he(e){try{return(await D({thread:e},AbortSignal.timeout(2e3))).thread!==null}catch(e){let t=e?.name;if(t===`TimeoutError`||t===`AbortError`)return!0;throw e}}async function ge(t,n){let r=e[t];if(!r)throw y(`no sync entry named ${t}`);let i;try{i=await r.receive(t,n,e=>H(t,e))}catch(e){return console.error(`[board] intake failed`,e),new Response(`intake failed`,{status:500})}let a=_e([t]).catch(e=>console.error(`[board] couldn't pull ${t}'s inbox:`,W(e)));return x(a),i}async function _e(t=Object.keys(e)){let n=0,r=Date.now()+24e4;for(let e of t)for(;Date.now()<r;){let{items:t}=await l(`inboxNext`,{key:e,limit:10});if(t.length===0)break;await Promise.all(t.map(t=>ve(e,t))),n+=t.length}return n}async function ve(e,t){let n;try{await xe(e,{event:t.event,raw:t.raw},{id:t.id,attempt:t.attempt+1})}catch(r){console.error(`[board] sync ${e} couldn't import ${t.id}:`,W(r)),n=W(r)}await l(`inboxAck`,{key:e,id:t.id,lease:t.lease,...n===void 0?{}:{error:n}}).catch(e=>console.error(`[board] couldn't ack an inbox event:`,W(e)))}async function ye(e,t,n,r=[]){if(t.length===0)return[];let i=[];for(let a of t){if(i.some(e=>e.url===a.url))continue;let t=(await D({thread:a.thread,message:a.message})).message,o=t===null?null:await T(t);if(o&&!o.deleted&&o.channel===n){i.push({url:a.url,message:o.id,unavailable:null,snapshot:{author:o.author?.name??o.by.actor,text:o.text,createdAt:o.createdAt,files:o.attachments.map(e=>({name:e.name,url:null}))}});continue}if(a.snapshot){i.push({url:a.url,message:null,snapshot:a.snapshot,unavailable:null});continue}let s=r.find(e=>e.url===a.url&&e.snapshot&&e.message===null);if(s){i.push(s);continue}let c=await e.fetchLink(a).catch(e=>({snapshot:null,unavailable:W(e)}));i.push({url:a.url,message:null,snapshot:c.snapshot,unavailable:c.snapshot?null:(c.unavailable??`unknown`).slice(0,300)})}return U(i)}async function be(e,t,i){let o=[],s=[],c=!1;for(let u of t.slice(0,10)){if(c){s.push(B(u));continue}if(u.size!==void 0&&u.size>26214400){s.push(R(u,`over ${n/1024/1024} MB`));continue}let t;try{let n=await e.fetchFile(u);if(n===null){s.push(R(u,`${e.platform} can't hand it over`));continue}t=n}catch(t){if(i<3)throw t;console.warn(`[board] couldn't fetch ${u.name??`a file`} from ${e.platform}:`,W(t)),s.push(R(u,`${e.platform} wouldn't hand it over`));continue}if(t.byteLength>26214400){s.push(R(u,`over ${n/1024/1024} MB`));continue}if(t.byteLength===0){s.push(R(u,`it was empty`));continue}let d=u.name??`${u.type}`;try{o.push((await l(`upload`,{data:t,name:d,type:u.mimeType||r(d)})).attachment)}catch(e){if(e instanceof m&&(e.code===`too_large`||e.code===`no_direct_uploads`)){s.push(R(u,`over ${a/1024/1024} MB`));continue}if(!z(e))throw e;c=!0,s.push(B(u))}}for(let e of t.slice(10))s.push(R(e,`a message carries at most 10`));return{attachments:o,notes:s}}async function xe(e,t,n){let{event:r,raw:i}=t,a=f(e),c=(await D({thread:r.thread})).thread??void 0;if(r.kind===`message`){let t=(await D({thread:r.subthread??r.thread,message:r.message})).message;if(t!==null){let e=(await T(t))?.origin;if(!e||e.message!==r.message||(e.subthread??e.thread)!==(r.subthread??r.thread))return}else if(a.users&&n.attempt<6&&await we(c,r))throw Error(`holding ${r.message} until ${r.author.id}'s board message there has its receipt`);let i=r.author.isBot?void 0:de.exec(r.text)?.[1];if(i&&await Te(e,r,i))return}let l=`author`in r?r.author:{id:`${a.platform}:system`,handle:a.platform,name:a.platform,avatar:null,isBot:!0},u,d=()=>u??=P(a.event({id:n.id,key:e,event:r,raw:i})),p,m,h=!0,g={direction:`in`,key:e,channel:_(r.channel),get event(){return d()},board:o().client({author:l,key:e,...Ce(e,r)},n.id),import(r,i={}){if(!h)throw s(`the sync function has finished`);return p??=Se(e,t,n,c,r,i),p},permalink(){let e=m??a.permalink(d());return m=e,e.catch(()=>{m===e&&(m=void 0)}),e},resolve:t=>j(e,t)};try{await a.sync(g),await p}finally{h=!1}}async function Se(e,t,n,r,i,a){let o=i??(r?(await C(r))?.channel:void 0);if(o===void 0)return{root:null,message:null};try{return await Ae(e,t,n,b.parse(o),ie.parse(a))}catch(e){if(!(e instanceof te))throw e;let n=(await D({thread:t.event.thread})).thread;return{root:n===null?null:(await C(n))?.root??null,message:null}}}function Ce(e,t){let n=f(e),r=(`subthread`in t?t.subthread:null)??t.thread;return{via:{kind:`sync`,key:e,platform:n.platform,channel:t.channel,thread:r},respond:async e=>{await n.post(_(r),`thread`,{markdown:e})}}}async function we(e,t){let n=(await D({account:t.author.id})).person;if(!n||n.method!==`consent`)return!1;let r=Date.now(),i=(e,t,i)=>e.by.principal===n.id&&e.by.profile===null&&e.from===null&&!e.deleted&&!e.noSync&&r-Date.parse(e.createdAt)<fe&&!i[t]&&!e.undelivered[t];if(e!==void 0){let n=await C(e);if(!n)return!1;let r=n.targets.filter(e=>n.external[e]===t.thread),a=await w(n.root),o=await E(a.map(e=>e.id));return a.some(e=>r.some(t=>i(e,t,o[e.id]??{})))}let{roots:a}=await l(`syncRoots`,{by:n.id,since:r-fe}),o=await E(a.map(e=>e.id));for(let e of a){let n=await C(e.thread);if(n?.targets.includes(t.channel)&&!n.external[t.channel]&&i(e,t.channel,o[e.id]??{}))return!0}return!1}async function Te(e,t,n){try{await l(`syncAccount`,{sync:e,account:t.author,code:n,message:u(t.subthread??t.thread,t.message)})}catch(e){if(G(e))return!1;throw e}return await f(e).react(_(t.subthread??t.thread),t.message,`check`,!0).catch(t=>console.warn(`[board] couldn't confirm a linking code on ${e}:`,W(t))),!0}let Ee=e=>f(e).users!==null;async function De(e,t,n){let r=f(e);if(!r.users)throw s(`${e} can't sign people in: link it with a code`);return r.users.authorize(t,n?{callback:n}:{})}async function Oe(e,t){let n=f(e);if(!n.users)throw s(`${e} can't sign people in: link it with a code`);let r=await n.users.token(t);if(!r)return null;let i=await n.whoAmI(r);if(!i.id.startsWith(`${n.platform}:user:`))throw s(`${e} named an account that isn't a ${n.platform} user`);return i}async function ke(e,t,n){let r=await Oe(e,t);return r?(await l(`syncAccount`,{sync:e,account:r,code:n,method:`consent`})).link:null}async function Ae(e,t,n,r,i){let{event:a,raw:o}=t,c=Ce(e,a),u=f(e),d=(await D({thread:a.thread})).thread??void 0,p=d?await C(d):null,m=p?.root,g=e=>ee(`board-inbound`,n.id,String(e)),v={key:e,channel:c.via.channel,thread:c.via.thread},y=async e=>{if(!e.dropped)return e.message;for(let t of e.dropped)await c.respond(t);throw new te(e.dropped)},b=async({attachments:t=[],...n})=>({id:await y(await l(`syncImport`,{op:`post`,from:e,source:v,...n,origin:n.origin??null,...t.length?{attachments:t.map(e=>e.id)}:{}})),re:n.re??null}),x=e=>({root:e.re??e.id,message:e.id}),S=(e={})=>{let t={...e,...i.labels};for(let e of i.unset??[])delete t[e];return t},w=e=>{if(!i.root)return e;let t=i.root(P(structuredClone(e)));if(t&&typeof t==`object`&&`then`in t)throw s(`root overrides must be synchronous`);let n=ie.pick({text:!0,labels:!0}).parse(t);return{...e,text:n.text??e.text,labels:{...e.labels,...n.labels}}},E=(t,n,r)=>l(`syncImport`,{op:`labels`,author:t,from:e,origin:{thread:a.thread},set:n,unset:r}),O=async(e,t)=>{if(!i.labels&&!i.unset)return;let n=await T(t),r=Object.fromEntries(Object.entries(i.labels??{}).filter(([e,t])=>n?.labels[e]!==t)),a=(i.unset??[]).filter(e=>n?.labels[e]!==void 0);(Object.keys(r).length||a.length)&&await E(e,r,a)};switch(a.kind){case`message`:{let e=(await D({thread:a.subthread??a.thread,message:a.message})).message;if(e!==null)return{root:m??e,message:e};let{attachments:t,notes:s}=a.files?.length?await be(u,a.files,n.attempt):{attachments:[],notes:[]},c=await ye(u,a.links??[],p?.channel??r),l={...c.length?{links:c}:{},author:a.author,text:L(V(i.text??a.text,s),t.length>0),...t.length?{attachments:t}:{},origin:{channel:a.channel,thread:a.thread,subthread:a.subthread,message:a.message,context:a.context},id:await g(0)},d={...l,...i.noSync?{noSync:!0}:{}};if(m!==void 0)return await O(a.author,m),x(await b({...d,re:m}));let f=await u.fetchRoot(_(a.thread));if(f===null){let e=w({text:a.text,labels:{},author:a.author,raw:o});return x(await b({...l,channel:r,text:L(V(i.text??e.text,s),t.length>0),labels:S(e.labels)}))}let h=w({...f,raw:f.raw??null}),v=await b({author:f.author,channel:r,text:L(h.text),labels:S(h.labels),origin:{channel:a.channel,thread:a.thread,subthread:null,message:`root:${n.id}`,context:null},id:await g(1)});return x(await b({...d,re:v.id}))}case`edited`:case`deleted`:case`reaction`:{let t=(await D({thread:a.subthread??a.thread,message:a.message})).message;if(t===null)return{root:m??null,message:null};let n=`author`in a?a.author:{id:`${u.platform}:system`,handle:u.platform,name:u.platform,avatar:null,isBot:!0},r={thread:a.subthread??a.thread,message:a.message};if(m!==void 0&&await O(n,m),a.kind===`deleted`)await l(`syncImport`,{op:`delete`,author:n,from:e,origin:r});else if(a.kind===`edited`){let o=await T(t),s=L(V(i.text??a.text,o?le(o.text):[]),(o?.attachments.length??0)>0);o&&!a.linksOnly&&o.text!==s&&await y(await l(`syncImport`,{op:`edit`,author:n,from:e,origin:r,text:s,source:v}));let c=o?await ye(u,a.links??[],o.channel,o.links):[];o&&!ue(c,o.links)&&await l(`syncImport`,{op:`links`,author:n,from:e,origin:r,links:c})}else{if(u.singleReaction){let i=h(a.emoji);for(let[o,s]of Object.entries((await T(t))?.reactions??{}))s.includes(n.id)&&!(a.added&&o===i)&&await l(`syncImport`,{op:`react`,author:n,from:e,origin:r,emoji:o,remove:!0})}(!u.singleReaction||a.added)&&await l(`syncImport`,{op:`react`,author:n,from:e,origin:r,emoji:a.emoji,remove:!a.added})}return{root:m??null,message:t}}case`lifecycle`:{if(m!==void 0){let t=await T(m),r=t?.origin?.message===`lifecycle:${n.id}`,s=S((r?w({text:K(a),labels:a.labels,author:a.author,raw:o}):null)?.labels??a.labels),c=e=>a.owns?.some(t=>e.startsWith(t))??!1,u=Object.fromEntries(Object.entries(s).filter(([e,n])=>t?.labels[e]!==n)),d=Object.keys(t?.labels??{}).filter(e=>!(e in s)&&(a.unset.includes(e)||i.unset?.includes(e)||c(e)));if(t&&!t.deleted&&(Object.keys(u).length||d.length)&&await E(a.author,u,d),a.retitled&&t&&!t.deleted&&t.from===e&&t.origin?.thread===a.thread){let n=L(i.text??w({text:K(a),labels:t.labels,author:a.author,raw:o}).text);t.text!==n&&await y(await l(`syncImport`,{op:`edit`,author:a.author,from:e,origin:{thread:t.origin.thread,message:t.origin.message},text:n}))}return a.reply?x(await b({author:a.author,re:m,text:L(i.text??a.reply),...i.noSync?{noSync:!0}:{},id:await g(1)})):{root:m,message:r?m:null}}if(!a.createRoot)return{root:null,message:null};let t=w({text:K(a),labels:a.labels,author:a.author,raw:o}),s=await b({author:a.author,channel:r,text:L(a.reply?t.text:i.text??t.text),labels:S(t.labels),origin:{channel:a.channel,thread:a.thread,subthread:null,message:`lifecycle:${n.id}`,context:null},id:await g(0)});return a.reply?x(await b({author:a.author,re:s.id,text:L(i.text??a.reply),...i.noSync?{noSync:!0}:{},id:await g(1)})):x(s)}}}function je(e,t,n,r=t.text){let i=t.mentions.map(e=>n.byId[e]).filter(e=>e!==void 0);return v(r,(t,r)=>{let a=t.kind===`actor`?i.find(e=>e.id===t.actor):i.find(e=>e.handle.toLowerCase()===t.handle);return a?Me(e,a.id,n)??a.name:null})}function Me(e,t,n){let r=t=>{let r=n.byId[t];return r?e.mention(r):null},i=n.links[t]??t,a=r(t)??(i===t?null:r(i));if(a)return a;for(let e of k(n,i)){let t=r(e);if(t)return t}return null}async function Ne(e,t,n){if(!e.users||t.from!==null||t.by.profile!==null||!t.by.principal.startsWith(`vercel:user:`))return null;if(n){let{accounts:n}=await D({person:t.by.principal});if(!n.some(t=>t.method===`consent`&&t.id.startsWith(`${e.platform}:user:`)))return null}try{return await e.users.token(t.by.principal)}catch(n){return console.warn(`[board] no ${e.platform} token for ${t.by.principal}; posting #${t.id} as the app:`,W(n)),null}}function Pe(e,t,n){let r=f(p(e)),i=je(r,t,n,ne(t.text,t.by.actor));return{markdown:t.attachments.length>0&&!r.uploadsFiles?`${i}\n${Re(t)}`:i}}async function Fe(e,t,n,r,i){let a=f(p(e));if(!r.text.trim())return null;let o=await Ne(a,r,!0);if(!o)return null;try{return await a.postAs(o,_(t),n,Pe(e,r,i))}catch(t){return console.warn(`[board] couldn't post #${r.id} to ${e} as its author; posting as the app:`,W(t)),null}}function Ie(t,n,r,i,a){let o=i.byId[r.by.actor],c=o?.name??r.by.actor;r.by.profile&&o?.handle.includes(`/`)&&(c=`${o.name} via ${o.handle.split(`/`)[0]}`),r.from&&(c=`${c} (${e[r.from]?.platform??r.by.actor.slice(0,r.by.actor.indexOf(`:`))})`);let l=f(t),u=(a??(e=>l.render(e)))(P({message:{...structuredClone(r),text:ne(r.text,r.by.actor)},author:o?{...o}:null,mentionOf:e=>Me(l,e,i),label:c,target:n}));if(typeof u!=`string`)throw s(`a provider renderer must return markdown`);let d=je(l,r,i,u);return r.by.profile&&!r.from?{markdown:d,as:{name:c,avatar:Le(r,o,i)}}:{markdown:d}}function Le(e,t,n){let r=e=>!e||/\/avatars\/[a-z0-9-]+$/.test(e),i=t?.avatar??null;if(!r(i))return i;let a=n.byId[e.by.principal]?.avatar;if(!r(a))return a;let o=t?.handle.split(`/`)[0]??``;return!e.by.principal.startsWith(`vercel:user:`)||!o||/^user-/.test(o)?i:`https://vercel.com/api/www/avatar?u=${encodeURIComponent(o)}&s=64&format=png`}let Re=e=>e.attachments.map(n=>`\n📎 ${n.name} (${t(n.size)}, on the board: #${e.id})`).join(``),q=(e,t,n,r=!1,i)=>{let a=p(e),{markdown:o,as:s}=Ie(a,e,t,n,i);return{markdown:t.attachments.length>0&&(r||!f(a).uploadsFiles)?`${o}\n${Re(t)}`:o,...s?{as:s}:{}}};async function ze(e,t,n,r,i){let a=e.replyTo===null?void 0:(await E([e.replyTo]))[e.replyTo]?.[t],o=a!==void 0&&a.thread!==n?a.thread:n,c=f(p(t)),l=q(t,e,r,!1,i),u,d;if(!l.markdown.trim()&&e.attachments.length&&c.uploadsFiles)try{let n=await Ve(e,t,o);d=n.message,u=n.files}catch(n){if(!c.refusesFiles(n)&&!z(n))throw n;l=q(t,e,r,!0,i),d=(await c.post(_(o),`thread`,l)).message,u=null}else{let n=await Fe(t,o,`thread`,e,r);if(n){let r={thread:o,message:n.message,asUser:!0};return await J(e,t,r),r}if(!l.markdown.trim())throw s(`a renderer must return text unless the message has media`);d=(await c.post(_(o),`thread`,l)).message}let m={thread:o,message:d,...u===void 0?{}:{files:u}};return await J(e,t,m),m}async function J(e,t,n){await l(`syncReceipt`,{kind:`message`,message:e.id,target:t,external:{thread:n.thread,message:n.message,...n.asUser?{asUser:!0}:{}},...n.files===void 0?{}:{files:n.files}})}async function Be(e){return Promise.all(e.attachments.map(async e=>{let{data:t}=await l(`attachment`,{id:e.id});return{data:t instanceof Uint8Array?t:new Uint8Array(await new Response(t).arrayBuffer()),filename:e.name,mimeType:e.type}}))}async function Ve(e,t,n){return f(p(t)).uploadFiles(_(n),await Be(e))}async function Y(e,t,n,r,i){let a=f(p(t));if(!a.uploadsFiles||e.attachments.length===0||n.files!==void 0)return;let o;try{o=(await Ve(e,t,n.thread)).files}catch(s){if(!a.refusesFiles(s)&&!z(s))throw Object.assign(/* @__PURE__ */ Error(`files: ${W(s)}`),{cause:s,data:{error:`files: ${W(s)}`}});console.warn(`[board] ${t} won't take #${e.id}'s files (${W(s)}); noting them in its text`),o=null,await a.edit(_(n.thread),n.message,q(t,e,r,!0,i)).catch(n=>console.warn(`[board] couldn't note #${e.id}'s files on ${t}:`,W(n)))}await l(`syncReceipt`,{kind:`files`,message:e.id,target:t,files:o})}let X=(e,t,n,r)=>{e.some(e=>e.error===r)||e.push({id:t,target:n,error:r})};async function Z(e,t){if(t.length!==0){if(e<5||re(e))throw t[0].error;for(let{id:e,target:n,error:r}of t)console.error(`[board] gave up ${n===null?`syncing #${e}`:`delivering #${e} to ${n}`}:`,W(r)),n!==null&&await l(`syncReceipt`,{kind:`failed`,message:e,target:n,error:W(r)})}}async function Q(e,t,n,r,i){let a,o=!0,c=()=>s(`the sync function has finished`),l={direction:`out`,key:e,channel:r===null?null:M(e,r),event:P(structuredClone(t)),message:P(structuredClone(n)),target:r,deliver(e={}){if(!o)throw c();return a??=r===null?Promise.resolve():i(e.render),a},start(t){if(!o)throw c();return He(e,n,t)},resolve:t=>j(e,t)};try{await f(e).sync(l),await a}finally{o=!1}}async function He(e,t,n){let{target:r,kind:i}=await I(`${e}:${n}`),a=t.re===null?t:await T(t.re);!a||a.deleted||a.noSync||r!==(a.origin?a.origin.channel??S(a.origin.thread):null)&&await l(`syncReceipt`,{kind:`thread`,message:a.id,target:r,targetKind:i,cause:`config`})}async function Ue(t,n){let r=Object.keys(e).filter(t=>e[t].canStartThreads);if(t.payload.noSync||r.length===0)return;let i=await T(t.message.id);if(!i||i.deleted)return;let a=[];for(let e of r)try{await Q(e,t,i,null,async()=>{})}catch(e){X(a,i.id,null,e)}await Z(n,a)}async function We(e,t){let{thread:n,target:r}=e.payload;if(!g(r)||(e.payload.kind??(d(r)?`thread`:`channel`))===`thread`)return;let i=await C(n),[a,...o]=i?await w(i.root):[];if(!i||!a||a.id!==i.root||a.deleted)return;let s=[];try{await Q(p(r),e,a,r,e=>Ge(i,a,o,r,s,e))}catch(e){X(s,a.id,r,e)}await Z(t,s)}async function Ge(e,t,n,r,i,a){let o=await O(),c=await E([t.id,...n.map(e=>e.id)]),l=e=>(i.push({id:t.id,target:r,error:e}),e),u=e.external[r];if(u){let e=c[t.id]?.[r];if(e)try{await Y(t,r,e,o,a)}catch(e){throw l(e)}}else try{let e=f(p(r)),n=q(r,t,o,!1,a),i=!n.markdown.trim()&&t.attachments.length>0&&e.uploadsFiles;if(!n.markdown.trim()&&!i)throw s(`a renderer must return text unless the message has media`);let c,l=!1,d=await(async()=>{if(!i){let i=await Fe(r,r,`channel`,t,o);if(i)return l=!0,i;if(t.attachments.length===0||!e.startsThreadsWithFiles)return e.post(_(r),`channel`,n)}try{let a=await e.post(_(r),`channel`,{...n,files:await Be(t)});return c=i?a.files??[]:a.files,a}catch(n){if(!e.refusesFiles(n)&&!z(n))throw n;return c=null,e.post(_(r),`channel`,q(r,t,o,!0,a))}})();u=`${p(r)}:${d.thread}`;let m={thread:u,message:d.message,...c===void 0?{}:{files:c},...l?{asUser:!0}:{}};await J(t,r,m),await Y(t,r,m,o,a)}catch(e){throw l(e)}let d=[];for(let e of n)if(!(e.deleted||e.noSync||Ke(e,r)))try{await Y(e,r,c[e.id]?.[r]??await ze(e,r,u,o,a),o,a)}catch(t){d.push({id:e.id,target:r,error:t})}if(i.push(...d),d.length)throw d[0].error}let Ke=(e,t)=>e.origin?.thread===t||e.from!==null&&p(t)===e.from;async function qe(e,t){if(!e.message)return;if(e.message.re===null)return Ue(e,t);if(e.payload.noSync)return;let n=await C(e.payload.thread);if(!n||n.targets.length===0)return;let r=await T(e.message.id);if(!r||r.deleted)return;let i=await O(),a=[];for(let t of n.targets){let o=n.external[t];if(g(t)&&o&&!Ke(r,t))try{await Q(p(t),e,r,t,async e=>{let n=(await E([r.id]))[r.id]?.[t]??await ze(r,t,o,i,e);await Y(r,t,n,i,e)})}catch(e){X(a,r.id,t,e)}}await Z(t,a)}async function Je(e){let t=(await E([e.payload.id]))[e.payload.id]??{};if(Object.keys(t).length===0)return;let n=e.type===`message.edited`?await T(e.payload.id):e.message??await T(e.payload.id);if(!n||e.type===`message.edited`&&n.deleted)return;let r=await O();for(let[i,a]of Object.entries(t))if(!(!g(i)||e.payload.from!==null&&p(i)===e.payload.from))try{await Q(p(i),e,n,i,async t=>{let o=f(p(i)),s=_(a.thread),c=a.asUser&&(e.type===`message.edited`||e.type===`message.deleted`),l=c?await Ne(o,n,!1):null;if(c&&!l){console.warn(`[board] skipped ${e.type} on ${i}: #${n.id} was posted as its author, and their token is gone`);return}e.type===`message.edited`?l?await o.editAs(l,s,a.message,Pe(i,n,r)):await o.edit(s,a.message,q(i,n,r,a.files===null,t)):e.type===`message.deleted`?(l?await o.deleteAs(l,s,a.message):await o.delete(s,a.message),a.files?.length&&await o.deleteFiles(a.files)):e.type===`reaction.added`?await o.react(s,a.message,h(e.payload.emoji),!0):await o.react(s,a.message,h(e.payload.emoji),!1)})}catch(t){console.warn(`[board] skipped ${e.type} on ${i}:`,t.message)}}async function Ye(e){let{id:t}=e.payload,n=(await E([t]))[t]??{},r=e.message;if(!r||Object.keys(n).length===0)return;let i=await T(t);if(!i||i.deleted)return;let a=await O(),o={...i,labels:r.labels},s={...i,labels:e.before?.labels??{}};for(let[t,r]of Object.entries(n))if(g(t))try{await Q(p(t),e,o,t,async e=>{let n=q(t,o,a,r.files===null,e);n.markdown!==q(t,s,a,r.files===null,e).markdown&&await f(p(t)).edit(_(r.thread),r.message,n)})}catch(e){console.warn(`[board] skipped labels.changed on ${t}:`,W(e))}}async function Xe(e,{attempt:t}){switch(e.type){case`thread.synced`:return We(e,t);case`message.posted`:return qe(e,t);case`message.edited`:case`message.deleted`:case`reaction.added`:case`reaction.removed`:return Je(e);case`labels.changed`:return Ye(e)}}async function Ze(e,t){let n=e[0]?await C(e[0].thread):null;if(!n)return[];let r=[...new Set(Object.values(n.external))].filter(g);return(await Promise.all(r.map(async n=>{let r=p(n);try{let i=f(r),a=null;for(let t=e.length-1;t>=0&&a===null;t--){let r=e[t];!r.deleted&&r.origin?.thread===n&&(a=r.origin.message)}return await i.startTyping(_(n),a,t),n}catch(e){return console.warn(`[board] skipped typing on ${n}:`,W(e)),null}}))).filter(e=>e!==null)}async function Qe(e,t){try{return`${e}:${await f(e).resolveChannel(t)}`}catch(e){throw s(`can't resolve inbox channel: ${e.message}`)}}let $=e=>e===`unsupported`||e===`invalid_target`?400:e===`channel_not_found`?404:e===`ratelimited`?429:[`no_client`,`list_failed`,`internal_error`,`fatal_error`,`service_unavailable`,`request_timeout`].includes(e)?502:403;async function $e(e,t,n,r){let i=f(e);try{return await i.read(t,{limit:n,after:r})}catch(n){throw n instanceof F?new m($(n.code),n.code,n.message):s(`can't read ${t} from ${e}: ${W(n)}`,`platform_error`)}}async function et(e,t){let n=f(e);try{return await n.join(t)}catch(n){throw n instanceof F?new m($(n.code),n.code,n.message):s(`can't join ${t} on ${e}: ${W(n)}`,`platform_error`)}}return{entries:e,bind(e){i=e},caller:c,deliver:Xe,receive:xe,pull:_e,normalizeTarget:I,findByEmail:N,handleWebhook:ge,inboxChannel:Qe,signsIn:Ee,signIn:De,signedIn:Oe,redeemSignIn:ke,typing:Ze,readSource:$e,joinSource:et}}function K(e){let t=e.url?`${e.title}\n${e.url}`:e.title;return e.body?`${t}\n\n${e.body}`:t}export{he as createSyncEngine};
5
+ `).filter(e=>/^📎 .* didn't come over: /u.test(e)),V=e=>JSON.stringify([e.url,e.message,e.snapshot,e.unavailable]),ue=(e,t)=>e.length===t.length&&e.every((e,n)=>V(e)===V(t[n])),de=new AbortController().signal,fe=e=>{try{return JSON.parse(JSON.stringify(e??null))}catch{return null}},H=e=>typeof e==`string`?f(e):Array.isArray(e)?e.map(H):typeof e==`object`&&e?Object.fromEntries(Object.entries(e).map(([e,t])=>[f(e),H(t)])):e,U=e=>{let t=e?.data?.error,n=typeof t==`string`?t:e instanceof Error?e.message:String(e);return f(n.slice(0,500))||`unknown error`},W=e=>e instanceof m&&e.status===404;function G(e){let i=null,o=()=>{if(!i)throw Error(`sync used before board() bound it`);return i},c={self:`sync`,origin:{via:{kind:`engine`}}};function l(e,t,n=de,r=c){let i=o().backend;return i[e].call(i,r,t,n)}let f=t=>{let n=e[t];if(!n)throw s(`no sync entry named ${t}`);return n},p=e=>e.slice(0,e.indexOf(`:`)),h=t=>e[p(t)]!==void 0,g=e=>e.slice(e.indexOf(`:`)+1);async function y(e){try{return await l(`syncThread`,{thread:e})}catch(e){if(W(e))return null;throw e}}async function C(e){try{let{message:t,replies:n}=await l(`message`,{id:e});return[t,...n]}catch(e){if(W(e))return[];throw e}}async function T(e){return(await C(e)).find(t=>t.id===e)??null}async function E(e){return e.length===0?{}:(await l(`syncCopies`,{message:[...new Set(e)]})).copies}let D=(e,t)=>l(`syncFind`,e,t);async function O(){let{actors:e}=await l(`actors`,{}),t={byId:{}};for(let n of e)t.byId[n.id]={id:n.id,kind:n.kind,handle:n.handle,name:n.name,avatar:n.avatar};return t}let k=/* @__PURE__ */ new Map;function A(e,t){let n=`${e}:${t}`,r=k.get(n);if(r&&(!r.failed||Date.now()-r.at<6e4))return r.id;let i={id:f(e).resolveChannel(t),failed:!1,at:Date.now()};return i.id.catch(r=>{i.failed=!0;let a=ae(r);!a&&k.get(n)===i&&k.delete(n),console.warn(`[board] sync ${e}: ${a?`skipping`:`couldn't resolve`} ${t}: ${r.message}`)}),k.set(n,i),i.id}function j(e,t){if(!u(t))return g(t);try{let n=f(e).inboxChannel(e,t);return n===null?null:g(n)}catch{return null}}async function M(t){for(let n of Object.values(e)){let e=await n.findByEmail(t).catch(()=>null);if(e)return{id:e.id,kind:`external`,handle:e.handle,name:e.name,avatar:e.avatar}}return null}async function N(e){let t=e.indexOf(`:`);if(t<=0)throw s(`sync targets look like <key>:<channel or thread>, got: ${e}`);let n=e.slice(0,t),r=f(n);try{let i=ie.parse(await r.resolveTarget(e.slice(t+1)));if(i.kind===`channel`&&!r.canStartThreads)throw Error(`${r.platform} cannot start threads; use an existing thread`);return{target:`${n}:${i.target}`,kind:i.kind}}catch(t){throw s(`can't sync to ${e}: ${t.message}`)}}async function se(e,t){let{event:n,raw:r,dedupe:i}=oe.parse(H({...t,raw:fe(t.raw)}));for(let t of[n.channel,n.thread,`subthread`in n?n.subthread:null])if(t!==null&&!t.startsWith(`${e}:`))throw s(`provider locations must belong to sync entry ${e}`);let a=ce(f(e));(!a||await a(g(n.channel),t=>A(e,t))||await V(n.thread))&&await l(`inboxPush`,{key:e,events:[{id:i,event:n,raw:r}]})}async function V(e){try{return(await D({thread:e},AbortSignal.timeout(2e3))).thread!==null}catch(e){let t=e?.name;if(t===`TimeoutError`||t===`AbortError`)return!0;throw e}}async function G(t,n){let r=e[t];if(!r)throw v(`no sync entry named ${t}`);let i;try{i=await r.receive(t,n,e=>se(t,e))}catch(e){return console.error(`[board] intake failed`,e),new Response(`intake failed`,{status:500})}let a=q([t]).catch(e=>console.error(`[board] couldn't pull ${t}'s inbox:`,U(e)));return b(a),i}async function q(t=Object.keys(e)){let n=0,r=Date.now()+24e4;for(let e of t)for(;Date.now()<r;){let{items:t}=await l(`inboxNext`,{key:e,limit:10});if(t.length===0)break;await Promise.all(t.map(t=>pe(e,t))),n+=t.length}return n}async function pe(e,t){let n;try{await he(e,{event:t.event,raw:t.raw},{id:t.id,attempt:t.attempt+1})}catch(r){console.error(`[board] sync ${e} couldn't import ${t.id}:`,U(r)),n=U(r)}await l(`inboxAck`,{key:e,id:t.id,lease:t.lease,...n===void 0?{}:{error:n}}).catch(e=>console.error(`[board] couldn't ack an inbox event:`,U(e)))}async function J(e,t,n,r,i=[]){if(t.length===0)return[];let a=[];for(let o of t){if(a.some(e=>e.url===o.url))continue;let t=(await D({thread:o.thread,message:o.message})).message,s=t===null?null:await T(t);if(s&&!s.deleted&&s.channel===r){a.push({url:o.url,message:s.id,unavailable:null,snapshot:{author:s.author?.name??s.by.actor,text:s.text,createdAt:s.createdAt,files:s.attachments.map(e=>({name:e.name,url:null}))}});continue}if(o.snapshot){a.push({url:o.url,message:null,snapshot:o.snapshot,unavailable:null});continue}let c=i.find(e=>e.url===o.url&&e.snapshot&&e.message===null);if(c){a.push(c);continue}let l=await e.fetchLink(o,n).catch(e=>({snapshot:null,unavailable:U(e)}));a.push({url:o.url,message:null,snapshot:l.snapshot,unavailable:l.snapshot?null:(l.unavailable??`unknown`).slice(0,300)})}return H(a)}async function me(e,n,r){let i=[],o=[],s=!1;for(let c of n.slice(0,10)){if(s){o.push(z(c));continue}if(c.size!==void 0&&c.size>26214400){o.push(L(c,`over ${a/1024/1024} MB`));continue}let n;try{let t=await e.fetchFile(c);if(t===null){o.push(L(c,`${e.platform} can't hand it over`));continue}n=t}catch(t){if(r<3)throw t;console.warn(`[board] couldn't fetch ${c.name??`a file`} from ${e.platform}:`,U(t)),o.push(L(c,`${e.platform} wouldn't hand it over`));continue}if(n.byteLength>26214400){o.push(L(c,`over ${a/1024/1024} MB`));continue}if(n.byteLength===0){o.push(L(c,`it was empty`));continue}let u=c.name??`${c.type}`;try{i.push((await l(`upload`,{data:n,name:u,type:c.mimeType||t(u)})).attachment)}catch(e){if(e instanceof m&&(e.code===`too_large`||e.code===`no_direct_uploads`)){o.push(L(c,`over ${w/1024/1024} MB`));continue}if(!R(e))throw e;s=!0,o.push(z(c))}}for(let e of n.slice(10))o.push(L(e,`a message carries at most 10`));return{attachments:i,notes:o}}async function he(e,t,n){let{event:r,raw:i}=t,a=f(e),c=(await D({thread:r.thread})).thread??void 0;if(r.kind===`message`){let e=(await D({thread:r.subthread??r.thread,message:r.message})).message;if(e!==null){let t=(await T(e))?.origin;if(!t||t.message!==r.message||(t.subthread??t.thread)!==(r.subthread??r.thread))return}}let l=`author`in r?r.author:{id:`${a.platform}:system`,handle:a.platform,name:a.platform,avatar:null,isBot:!0},u,d=()=>u??=P(a.event({id:n.id,key:e,event:r,raw:i})),p,m,h=!0,_={direction:`in`,key:e,channel:g(r.channel),get event(){return d()},board:o().client({author:l,key:e,..._e(e,r)},n.id),import(r,i={}){if(!h)throw s(`the sync function has finished`);return p??=ge(e,t,n,c,r,i),p},permalink(){let e=m??a.permalink(d());return m=e,e.catch(()=>{m===e&&(m=void 0)}),e},resolve:t=>A(e,t)};try{await a.sync(_),await p}finally{h=!1}}async function ge(e,t,n,r,i,a){let o=i??(r?(await y(r))?.channel:void 0);if(o===void 0)return{root:null,message:null};try{return await ve(e,t,n,d.parse(o),re.parse(a))}catch(e){if(!(e instanceof x))throw e;let n=(await D({thread:t.event.thread})).thread;return{root:n===null?null:(await y(n))?.root??null,message:null}}}function _e(e,t){let n=f(e),r=(`subthread`in t?t.subthread:null)??t.thread;return{via:{kind:`sync`,key:e,platform:n.platform,channel:t.channel,thread:r},respond:async e=>{await n.post(g(r),`thread`,{markdown:e})}}}async function ve(e,t,n,i,a){let{event:o,raw:c}=t,u=_e(e,o),d=f(e),p=(await D({thread:o.thread})).thread??void 0,m=p?await y(p):null,h=m?.root,_=e=>ee(`board-inbound`,n.id,String(e)),v={key:e,channel:u.via.channel,thread:u.via.thread},b=async e=>{if(!e.dropped)return e.message;for(let t of e.dropped)await u.respond(t);throw new x(e.dropped)},S=async({attachments:t=[],...n})=>({id:await b(await l(`syncImport`,{op:`post`,from:e,source:v,...n,origin:n.origin??null,...t.length?{attachments:t.map(e=>e.id)}:{}})),re:n.re??null}),C=e=>({root:e.re??e.id,message:e.id}),w=(e={})=>{let t={...e,...a.labels};for(let e of a.unset??[])delete t[e];return t},E=e=>{if(!a.root)return e;let t=a.root(P(structuredClone(e)));if(t&&typeof t==`object`&&`then`in t)throw s(`root overrides must be synchronous`);let n=re.pick({text:!0,labels:!0}).parse(t);return{...e,text:n.text??e.text,labels:{...e.labels,...n.labels}}},O=(t,n,r)=>l(`syncImport`,{op:`labels`,author:t,from:e,origin:{thread:o.thread},set:n,unset:r}),k=async(e,t)=>{if(!a.labels&&!a.unset)return;let n=await T(t),r=Object.fromEntries(Object.entries(a.labels??{}).filter(([e,t])=>n?.labels[e]!==t)),i=(a.unset??[]).filter(e=>n?.labels[e]!==void 0);(Object.keys(r).length||i.length)&&await O(e,r,i)};switch(o.kind){case`message`:{let e=(await D({thread:o.subthread??o.thread,message:o.message})).message;if(e!==null)return{root:h??e,message:e};let{attachments:t,notes:r}=o.files?.length?await me(d,o.files,n.attempt):{attachments:[],notes:[]},s=await J(d,o.links??[],{thread:o.subthread??o.thread,message:o.message},m?.channel??i),l={...s.length?{links:s}:{},author:o.author,text:I(B(a.text??o.text,r),t.length>0),...t.length?{attachments:t}:{},origin:{channel:o.channel,thread:o.thread,subthread:o.subthread,message:o.message,context:o.context},id:await _(0)},u={...l,...a.noSync?{noSync:!0}:{}};if(h!==void 0)return await k(o.author,h),C(await S({...u,re:h}));let f=await d.fetchRoot(g(o.thread));if(f===null){let e=E({text:o.text,labels:{},author:o.author,raw:c});return C(await S({...l,channel:i,text:I(B(a.text??e.text,r),t.length>0),labels:w(e.labels)}))}let p=E({...f,raw:f.raw??null}),v=await S({author:f.author,channel:i,text:I(p.text),labels:w(p.labels),origin:{channel:o.channel,thread:o.thread,subthread:null,message:`root:${n.id}`,context:null},id:await _(1)});return C(await S({...u,re:v.id}))}case`edited`:case`deleted`:case`reaction`:{let t=(await D({thread:o.subthread??o.thread,message:o.message})).message;if(t===null)return{root:h??null,message:null};let n=`author`in o?o.author:{id:`${d.platform}:system`,handle:d.platform,name:d.platform,avatar:null,isBot:!0},i={thread:o.subthread??o.thread,message:o.message};if(h!==void 0&&await k(n,h),o.kind===`deleted`)await l(`syncImport`,{op:`delete`,author:n,from:e,origin:i});else if(o.kind===`edited`){let r=await T(t),s=I(B(a.text??o.text,r?le(r.text):[]),(r?.attachments.length??0)>0);r&&!o.linksOnly&&r.text!==s&&await b(await l(`syncImport`,{op:`edit`,author:n,from:e,origin:i,text:s,source:v}));let c=r?await J(d,o.links??[],i,r.channel,r.links):[];r&&!ue(c,r.links)&&await l(`syncImport`,{op:`links`,author:n,from:e,origin:i,links:c})}else{if(d.singleReaction){let a=r(o.emoji);for(let[r,s]of Object.entries((await T(t))?.reactions??{}))s.includes(n.id)&&!(o.added&&r===a)&&await l(`syncImport`,{op:`react`,author:n,from:e,origin:i,emoji:r,remove:!0})}(!d.singleReaction||o.added)&&await l(`syncImport`,{op:`react`,author:n,from:e,origin:i,emoji:o.emoji,remove:!o.added})}return{root:h??null,message:t}}case`lifecycle`:{if(h!==void 0){let t=await T(h),r=t?.origin?.message===`lifecycle:${n.id}`,i=w((r?E({text:K(o),labels:o.labels,author:o.author,raw:c}):null)?.labels??o.labels),s=e=>o.owns?.some(t=>e.startsWith(t))??!1,u=Object.fromEntries(Object.entries(i).filter(([e,n])=>t?.labels[e]!==n)),d=Object.keys(t?.labels??{}).filter(e=>!(e in i)&&(o.unset.includes(e)||a.unset?.includes(e)||s(e)));if(t&&!t.deleted&&(Object.keys(u).length||d.length)&&await O(o.author,u,d),o.retitled&&t&&!t.deleted&&t.from===e&&t.origin?.thread===o.thread){let n=I(a.text??E({text:K(o),labels:t.labels,author:o.author,raw:c}).text);t.text!==n&&await b(await l(`syncImport`,{op:`edit`,author:o.author,from:e,origin:{thread:t.origin.thread,message:t.origin.message},text:n}))}return o.reply?C(await S({author:o.author,re:h,text:I(a.text??o.reply),...a.noSync?{noSync:!0}:{},id:await _(1)})):{root:h,message:r?h:null}}if(!o.createRoot)return{root:null,message:null};let t=E({text:K(o),labels:o.labels,author:o.author,raw:c}),r=await S({author:o.author,channel:i,text:I(o.reply?t.text:a.text??t.text),labels:w(t.labels),origin:{channel:o.channel,thread:o.thread,subthread:null,message:`lifecycle:${n.id}`,context:null},id:await _(0)});return o.reply?C(await S({author:o.author,re:r.id,text:I(a.text??o.reply),...a.noSync?{noSync:!0}:{},id:await _(1)})):C(r)}}}function ye(e,t,n,r=t.text){let i=t.mentions.map(e=>n.byId[e]).filter(e=>e!==void 0);return _(r,(t,r)=>{let a=t.kind===`actor`?i.find(e=>e.id===t.actor):i.find(e=>e.handle.toLowerCase()===t.handle);return a?be(e,a.id,n)??a.name:null})}function be(e,t,n){let r=n.byId[t];return r?e.mention(r):null}function xe(t,n,r,i,a){let o=i.byId[r.by.actor],c=o?.name??r.by.actor;r.by.profile&&o?.handle.includes(`/`)&&(c=`${o.name} via ${o.handle.split(`/`)[0]}`),r.from&&(c=`${c} (${e[r.from]?.platform??r.by.actor.slice(0,r.by.actor.indexOf(`:`))})`);let l=f(t),u=(a??(e=>l.render(e)))(P({message:{...structuredClone(r),text:te(r.text,r.by.actor)},author:o?{...o}:null,mentionOf:e=>be(l,e,i),label:c,target:n}));if(typeof u!=`string`)throw s(`a provider renderer must return markdown`);let d=ye(l,r,i,u);return r.by.profile&&!r.from?{markdown:d,as:{name:c,avatar:Se(r,o,i)}}:{markdown:d}}function Se(e,t,n){let r=e=>!e||/\/avatars\/[a-z0-9-]+$/.test(e),i=t?.avatar??null;if(!r(i))return i;let a=n.byId[e.by.principal]?.avatar;if(!r(a))return a;let o=t?.handle.split(`/`)[0]??``;return!e.by.principal.startsWith(`vercel:user:`)||!o||/^user-/.test(o)?i:`https://vercel.com/api/www/avatar?u=${encodeURIComponent(o)}&s=64&format=png`}let Ce=e=>e.attachments.map(t=>`\n📎 ${t.name} (${n(t.size)}, on the board: #${e.id})`).join(``),Y=(e,t,n,r=!1,i)=>{let a=p(e),{markdown:o,as:s}=xe(a,e,t,n,i);return{markdown:t.attachments.length>0&&(r||!f(a).uploadsFiles)?`${o}\n${Ce(t)}`:o,...s?{as:s}:{}}};async function we(e,t,n,r,i){let a=e.replyTo===null?void 0:(await E([e.replyTo]))[e.replyTo]?.[t],o=a!==void 0&&a.thread!==n?a.thread:n,c=f(p(t)),l=Y(t,e,r,!1,i),u,d;if(!l.markdown.trim()&&e.attachments.length&&c.uploadsFiles)try{let n=await De(e,t,o);d=n.message,u=n.files}catch(n){if(!c.refusesFiles(n)&&!R(n))throw n;l=Y(t,e,r,!0,i),d=(await c.post(g(o),`thread`,l)).message,u=null}else{if(!l.markdown.trim())throw s(`a renderer must return text unless the message has media`);d=(await c.post(g(o),`thread`,l)).message}let m={thread:o,message:d,...u===void 0?{}:{files:u}};return await Te(e,t,m),m}async function Te(e,t,n){await l(`syncReceipt`,{kind:`message`,message:e.id,target:t,external:{thread:n.thread,message:n.message},...n.files===void 0?{}:{files:n.files}})}async function Ee(e){return Promise.all(e.attachments.map(async e=>{let{data:t}=await l(`attachment`,{id:e.id});return{data:t instanceof Uint8Array?t:new Uint8Array(await new Response(t).arrayBuffer()),filename:e.name,mimeType:e.type}}))}async function De(e,t,n){return f(p(t)).uploadFiles(g(n),await Ee(e))}async function X(e,t,n,r,i){let a=f(p(t));if(!a.uploadsFiles||e.attachments.length===0||n.files!==void 0)return;let o;try{o=(await De(e,t,n.thread)).files}catch(s){if(!a.refusesFiles(s)&&!R(s))throw Object.assign(/* @__PURE__ */ Error(`files: ${U(s)}`),{cause:s,data:{error:`files: ${U(s)}`}});console.warn(`[board] ${t} won't take #${e.id}'s files (${U(s)}); noting them in its text`),o=null,await a.edit(g(n.thread),n.message,Y(t,e,r,!0,i)).catch(n=>console.warn(`[board] couldn't note #${e.id}'s files on ${t}:`,U(n)))}await l(`syncReceipt`,{kind:`files`,message:e.id,target:t,files:o})}let Z=(e,t,n,r)=>{e.some(e=>e.error===r)||e.push({id:t,target:n,error:r})};async function Q(e,t){if(t.length!==0){if(e<5||ne(e))throw t[0].error;for(let{id:e,target:n,error:r}of t)console.error(`[board] gave up ${n===null?`syncing #${e}`:`delivering #${e} to ${n}`}:`,U(r)),n!==null&&await l(`syncReceipt`,{kind:`failed`,message:e,target:n,error:U(r)})}}async function $(e,t,n,r,i){let a,o=!0,c=()=>s(`the sync function has finished`),l={direction:`out`,key:e,channel:r===null?null:j(e,r),event:P(structuredClone(t)),message:P(structuredClone(n)),target:r,deliver(e={}){if(!o)throw c();return a??=r===null?Promise.resolve():i(e.render),a},start(t){if(!o)throw c();return Oe(e,n,t)},resolve:t=>A(e,t)};try{await f(e).sync(l),await a}finally{o=!1}}async function Oe(e,t,n){let{target:r,kind:i}=await N(`${e}:${n}`),a=t.re===null?t:await T(t.re);!a||a.deleted||a.noSync||r!==(a.origin?a.origin.channel??S(a.origin.thread):null)&&await l(`syncReceipt`,{kind:`thread`,message:a.id,target:r,targetKind:i,cause:`config`})}async function ke(t,n){let r=Object.keys(e).filter(t=>e[t].canStartThreads);if(t.payload.noSync||r.length===0)return;let i=await T(t.message.id);if(!i||i.deleted)return;let a=[];for(let e of r)try{await $(e,t,i,null,async()=>{})}catch(e){Z(a,i.id,null,e)}await Q(n,a)}async function Ae(e,t){let{thread:n,target:r}=e.payload;if(!h(r)||(e.payload.kind??(u(r)?`thread`:`channel`))===`thread`)return;let i=await y(n),[a,...o]=i?await C(i.root):[];if(!i||!a||a.id!==i.root||a.deleted)return;let s=[];try{await $(p(r),e,a,r,e=>je(i,a,o,r,s,e))}catch(e){Z(s,a.id,r,e)}await Q(t,s)}async function je(e,t,n,r,i,a){let o=await O(),c=await E([t.id,...n.map(e=>e.id)]),l=e=>(i.push({id:t.id,target:r,error:e}),e),u=e.external[r];if(u){let e=c[t.id]?.[r];if(e)try{await X(t,r,e,o,a)}catch(e){throw l(e)}}else try{let e=f(p(r)),n=Y(r,t,o,!1,a),i=!n.markdown.trim()&&t.attachments.length>0&&e.uploadsFiles;if(!n.markdown.trim()&&!i)throw s(`a renderer must return text unless the message has media`);let c,l=await(async()=>{if(!i&&(t.attachments.length===0||!e.startsThreadsWithFiles))return e.post(g(r),`channel`,n);try{let a=await e.post(g(r),`channel`,{...n,files:await Ee(t)});return c=i?a.files??[]:a.files,a}catch(n){if(!e.refusesFiles(n)&&!R(n))throw n;return c=null,e.post(g(r),`channel`,Y(r,t,o,!0,a))}})();u=`${p(r)}:${l.thread}`;let d={thread:u,message:l.message,...c===void 0?{}:{files:c}};await Te(t,r,d),await X(t,r,d,o,a)}catch(e){throw l(e)}let d=[];for(let e of n)if(!(e.deleted||e.noSync||Me(e,r)))try{await X(e,r,c[e.id]?.[r]??await we(e,r,u,o,a),o,a)}catch(t){d.push({id:e.id,target:r,error:t})}if(i.push(...d),d.length)throw d[0].error}let Me=(e,t)=>e.origin?.thread===t||e.from!==null&&p(t)===e.from;async function Ne(e,t){if(!e.message)return;if(e.message.re===null)return ke(e,t);if(e.payload.noSync)return;let n=await y(e.payload.thread);if(!n||n.targets.length===0)return;let r=await T(e.message.id);if(!r||r.deleted)return;let i=await O(),a=[];for(let t of n.targets){let o=n.external[t];if(h(t)&&o&&!Me(r,t))try{await $(p(t),e,r,t,async e=>{let n=(await E([r.id]))[r.id]?.[t]??await we(r,t,o,i,e);await X(r,t,n,i,e)})}catch(e){Z(a,r.id,t,e)}}await Q(t,a)}async function Pe(e){let t=(await E([e.payload.id]))[e.payload.id]??{};if(Object.keys(t).length===0)return;let n=e.type===`message.edited`?await T(e.payload.id):e.message??await T(e.payload.id);if(!n||e.type===`message.edited`&&n.deleted)return;let i=await O();for(let[a,o]of Object.entries(t))if(!(!h(a)||e.payload.from!==null&&p(a)===e.payload.from))try{await $(p(a),e,n,a,async t=>{let s=f(p(a)),c=g(o.thread);e.type===`message.edited`?await s.edit(c,o.message,Y(a,n,i,o.files===null,t)):e.type===`message.deleted`?(await s.delete(c,o.message),o.files?.length&&await s.deleteFiles(o.files)):e.type===`reaction.added`?await s.react(c,o.message,r(e.payload.emoji),!0):await s.react(c,o.message,r(e.payload.emoji),!1)})}catch(t){console.warn(`[board] skipped ${e.type} on ${a}:`,t.message)}}async function Fe(e){let{id:t}=e.payload,n=(await E([t]))[t]??{},r=e.message;if(!r||Object.keys(n).length===0)return;let i=await T(t);if(!i||i.deleted)return;let a=await O(),o={...i,labels:r.labels},s={...i,labels:e.before?.labels??{}};for(let[t,r]of Object.entries(n))if(h(t))try{await $(p(t),e,o,t,async e=>{let n=Y(t,o,a,r.files===null,e);n.markdown!==Y(t,s,a,r.files===null,e).markdown&&await f(p(t)).edit(g(r.thread),r.message,n)})}catch(e){console.warn(`[board] skipped labels.changed on ${t}:`,U(e))}}async function Ie(e,{attempt:t}){switch(e.type){case`thread.synced`:return Ae(e,t);case`message.posted`:return Ne(e,t);case`message.edited`:case`message.deleted`:case`reaction.added`:case`reaction.removed`:return Pe(e);case`labels.changed`:return Fe(e)}}async function Le(e,t){let n=e[0]?await y(e[0].thread):null;if(!n)return[];let r=[...new Set(Object.values(n.external))].filter(h);return(await Promise.all(r.map(async n=>{let r=p(n);try{let i=f(r),a=null;for(let t=e.length-1;t>=0&&a===null;t--){let r=e[t];!r.deleted&&r.origin?.thread===n&&(a=r.origin.message)}return await i.startTyping(g(n),a,t),n}catch(e){return console.warn(`[board] skipped typing on ${n}:`,U(e)),null}}))).filter(e=>e!==null)}async function Re(e,t){try{return`${e}:${await f(e).resolveChannel(t)}`}catch(e){throw s(`can't resolve inbox channel: ${e.message}`)}}let ze=e=>e===`unsupported`||e===`invalid_target`?400:e===`channel_not_found`?404:e===`ratelimited`?429:[`no_client`,`list_failed`,`internal_error`,`fatal_error`,`service_unavailable`,`request_timeout`].includes(e)?502:403;async function Be(e,t,n,r){let i=f(e);try{return await i.read(t,{limit:n,after:r})}catch(n){throw n instanceof F?new m(ze(n.code),n.code,n.message):s(`can't read ${t} from ${e}: ${U(n)}`,`platform_error`)}}async function Ve(e,t){let n=f(e);try{return await n.join(t)}catch(n){throw n instanceof F?new m(ze(n.code),n.code,n.message):s(`can't join ${t} on ${e}: ${U(n)}`,`platform_error`)}}return{entries:e,bind(e){i=e},caller:c,deliver:Ie,receive:he,pull:q,normalizeTarget:N,findByEmail:M,handleWebhook:G,inboxChannel:Re,typing:Le,readSource:Be,joinSource:Ve}}function K(e){let t=e.url?`${e.title}\n${e.url}`:e.title;return e.body?`${t}\n\n${e.body}`:t}export{G as createSyncEngine};
@@ -1,14 +1,15 @@
1
1
  import { i as fileSize, n as DIRECT_UPLOAD_BYTES_MAX } from "./files-SniSmeEr.mjs";
2
2
  import { a as AttachmentId } from "./schemas-DBmUO6pr.mjs";
3
- import { d as VERSION, i as withMentions } from "./watchers-hash-B_5b0-ZG.mjs";
3
+ import { d as VERSION, i as withMentions } from "./watchers-hash-BjwlnGD9.mjs";
4
4
  import { n as displayEmoji, r as normalizeEmoji } from "./emoji-CTLpvH-A.mjs";
5
- import { u as BoardError } from "./errors-C_5rB8Iz.mjs";
6
- import { i as groups, n as filters$1, o as options, r as globalOptions, s as usage$1, t as commands } from "./commands-B5DHjZKK.mjs";
7
- import { a as handFetch, d as vercelProjectFile, n as boardUrl, o as handLogin, r as connect } from "./node-D8Rtg1su.mjs";
8
- import { t as http } from "./backend-http-16SbvnAK.mjs";
9
- import { t as board } from "./server-D82a4uMe.mjs";
10
- import { t as conformance } from "./conformance-C7fwIvmN.mjs";
11
- import { n as handTeam, r as vercel, t as credentialSource } from "./backend-vercel-Kd8nvW5A.mjs";
5
+ import { u as BoardError } from "./errors-CJXhhzvT.mjs";
6
+ import { i as groups, n as filters$1, o as options, r as globalOptions, s as usage$1, t as commands } from "./commands-L1JSGz2Z.mjs";
7
+ import { i as parseScope } from "./access-CY5xVMIK.mjs";
8
+ import { a as handFetch, d as vercelProjectFile, n as boardUrl, o as handLogin, r as connect } from "./node-BgeGcwX0.mjs";
9
+ import { t as http } from "./backend-http-D8mUzuKQ.mjs";
10
+ import { t as board } from "./server-DG8D6VV0.mjs";
11
+ import { t as conformance } from "./conformance-DK43HiHf.mjs";
12
+ import { n as handTeam, r as vercel, t as credentialSource } from "./backend-vercel-Ba8JDtqW.mjs";
12
13
  import { t as displayText } from "./bots-CGkO76qH.mjs";
13
14
  import { spawn } from "node:child_process";
14
15
  import { readFile, stat, writeFile } from "node:fs/promises";
@@ -34,7 +35,6 @@ function authorName(message) {
34
35
  if (!author) return message.by.actor;
35
36
  const slash = author.handle.indexOf("/");
36
37
  if (message.by.profile !== null && slash !== -1) return `${author.name} via ${author.handle.slice(0, slash)}`;
37
- if (author.person && author.person.handle !== author.handle) return `${author.name} (${author.person.handle})`;
38
38
  return author.name;
39
39
  }
40
40
  function ago(iso, now = Date.now()) {
@@ -161,6 +161,36 @@ function formatAccess(access) {
161
161
  return `${pattern.padEnd(width)} ${permissions.includes("manage") ? "all" : permissions.length === 0 ? "nothing" : permissions.join(" ")}`;
162
162
  });
163
163
  }
164
+ /** What a grant or token may do, as `--can` takes it: `dev/=read,post;ci/=read`, or `all`. */
165
+ function formatCan(can) {
166
+ const patterns = Object.keys(can);
167
+ if (patterns.length === 1 && patterns[0] === "*" && can["*"].includes("manage")) return "all";
168
+ if (patterns.length === 0) return "nothing";
169
+ return patterns.map((pattern) => `${pattern}=${can[pattern].includes("manage") ? "all" : can[pattern].join(",")}`).join(";");
170
+ }
171
+ /** Rows of cells, each column padded to its widest, two spaces apart. */
172
+ const table = (rows) => {
173
+ const widths = rows[0].map((_, column) => Math.max(...rows.map((row) => row[column].length)));
174
+ return rows.map((row) => row.map((cell, column) => column === row.length - 1 ? cell : cell.padEnd(widths[column])).join(" ").trimEnd()).join("\n");
175
+ };
176
+ /** Each grant on a row: its ID, its issuer, the claims it matches, and what it lets do. */
177
+ function formatGrants(grants) {
178
+ return table(grants.map((grant) => [
179
+ grant.id,
180
+ grant.issuer,
181
+ Object.entries(grant.claims).map(([name, value]) => `${name}=${typeof value === "string" ? value : value.join("|")}`).join(" "),
182
+ formatCan(grant.can)
183
+ ]));
184
+ }
185
+ /** Each token on a row: its ID, its name, what it may do, and when it expires. */
186
+ function formatTokens(tokens, now = Date.now()) {
187
+ return table(tokens.map((token) => [
188
+ token.id,
189
+ token.name,
190
+ formatCan(token.can),
191
+ `expires ${until(token.expiresAt, now)}${token.profile !== null ? ` · as ${token.profile}` : ""}${token.parent !== null ? ` · minted with ${token.parent}` : ""}`
192
+ ]));
193
+ }
164
194
  /** `90m` → `1h30m`, `1d`: milliseconds as a duration flag would say them. */
165
195
  function span(ms) {
166
196
  const parts = [];
@@ -448,9 +478,9 @@ function rows(entries, pad = padOf(entries)) {
448
478
  /** An option as help writes it: `--limit <N>`, `-h, --help`, `--label <k[=v|w]>...`. */
449
479
  const flag = (option) => `${option.short ? `-${option.short}, ` : ""}--${option.name}${option.value ? option.optionalValue ? ` [${option.value}]` : ` <${option.value}>` : ""}${option.multiple ? "..." : ""}`;
450
480
  const shown = (list) => list.filter((option) => !option.hidden);
451
- /** A command's rows in the list: itself, when it runs alone, and each of its subcommands. */
481
+ /** A command's rows in the list: itself, when it runs alone and no subcommand is what it runs, and each of its subcommands. */
452
482
  function listed(command) {
453
- return [...!command.subcommands || command.runsWithoutSubcommand ? [[usage$1([command]), command.description]] : [], ...(command.subcommands ?? []).filter((sub) => !sub.hidden).map((sub) => [usage$1([command, sub]), sub.description])];
483
+ return [...!command.subcommands || command.runsWithoutSubcommand && !command.subcommands.some((sub) => sub.default) ? [[usage$1([command]), command.description]] : [], ...(command.subcommands ?? []).filter((sub) => !sub.hidden).map((sub) => [usage$1([command, sub]), sub.description])];
454
484
  }
455
485
  /** Every command, by group, then what the app adds (`serve`, `inspect`), and the options they share. */
456
486
  function rootHelp(app, version) {
@@ -497,7 +527,7 @@ function commandHelp(name, path) {
497
527
  return lines.join("\n");
498
528
  }
499
529
  /**
500
- * The command `words` name, and its subcommand when they name one (`identity link`, `sources slack
530
+ * The command `words` name, and its subcommand when they name one (`schedule cancel`, `sources slack
501
531
  * read`): what `--help` shows the page of. Null when the first word names no command.
502
532
  */
503
533
  function commandPath(words, all) {
@@ -735,7 +765,7 @@ let typed = [];
735
765
  let warnings = [];
736
766
  /** The board this run reached by its URL, for a failure to name. */
737
767
  let reached = null;
738
- /** The help `words` ask for: a command's page when they name one (`read`, `identity link`), or else every command. */
768
+ /** The help `words` ask for: a command's page when they name one (`read`, `sources read`), or else every command. */
739
769
  function help(words = []) {
740
770
  const own = Object.values(app.commands).map((command) => command.spec);
741
771
  const path = commandPath(words, [...commands, ...own]);
@@ -840,7 +870,7 @@ const result = (value) => io.write(`${JSON.stringify(warnings.length ? {
840
870
  ...value,
841
871
  warnings
842
872
  } : value, null, io.tty ? 2 : void 0)}\n`);
843
- /** One line of a JSON stream (`tail`, `identity link`): compact, wherever it goes. */
873
+ /** One line of a JSON stream (`tail`): compact, wherever it goes. */
844
874
  const line = (value) => io.write(`${JSON.stringify(value)}\n`);
845
875
  /** A message's ID as you'd write it: `#812` or `812`. It's passed to the board as it is. */
846
876
  function messageId(value, what = "message ID") {
@@ -908,6 +938,31 @@ function scheduleId(value) {
908
938
  if (!/^[^\s/]+$/.test(value)) throw usage(`${value} isn't a schedule ID like s12.`);
909
939
  return value;
910
940
  }
941
+ /** A grant's or token's ID as the board gave it: `g3`, `t12`, or another board's own. */
942
+ function trustId(what, value) {
943
+ const like = what === "grant" ? "g3" : "t12";
944
+ if (value === void 0) throw usage(`Missing the ${what} ID, like ${like}.`, { reason: "missing_arguments" });
945
+ if (!/^[^\s/]+$/.test(value)) throw usage(`${value} isn't a ${what} ID like ${like}.`);
946
+ return value;
947
+ }
948
+ /** `--can`, as `x-board-scope` writes it: `dev/=read,post;ci/=read`. */
949
+ function canFlag(value) {
950
+ try {
951
+ return parseScope(value);
952
+ } catch (error) {
953
+ throw usage(`${error.message.replace(/x-board-scope/g, "--can")}.`);
954
+ }
955
+ }
956
+ /** `--claim k=v`, each again: the same name twice is any of its values. */
957
+ function claimPairs(values) {
958
+ const claims = {};
959
+ for (const value of values ?? []) {
960
+ const at = value.indexOf("=");
961
+ if (at <= 0 || at === value.length - 1) throw usage(`Claims look like name=value, and ${value} isn't one.`);
962
+ (claims[value.slice(0, at)] ??= []).push(value.slice(at + 1));
963
+ }
964
+ return Object.fromEntries(Object.entries(claims).map(([name, list]) => [name, list.length === 1 ? list[0] : list]));
965
+ }
911
966
  /** A time flag: anything `Date` reads, as ISO. */
912
967
  function instant(flag, value) {
913
968
  const time = new Date(value);
@@ -976,11 +1031,12 @@ function doing() {
976
1031
  case "typing": return `show typing in ${ref(a)}`;
977
1032
  case "sync": return b === void 0 ? `list where ${ref(a)} is synced` : `sync ${ref(a)} with ${b}`;
978
1033
  case "schedule": return a === "cancel" ? `cancel schedule ${b}` : `schedule a message in ${message ? ref(a) : a ?? "a channel"}`;
1034
+ case "channels": return a === "create" ? `create ${b ?? "the channel"}` : "list channels";
979
1035
  case "schedules":
980
- case "channels":
981
1036
  case "actors": return `list ${command}`;
1037
+ case "grants": return a === "create" ? `grant ${b ?? "an issuer"}` : a === "revoke" ? `revoke grant ${b}` : "list grants";
1038
+ case "tokens": return a === "create" ? `mint token ${b ?? ""}`.trimEnd() : a === "revoke" ? `revoke token ${b}` : "list tokens";
982
1039
  case "sources": return b === void 0 ? "list sources" : `${b} ${c ?? "it"} on ${a}`;
983
- case "identity": return a === "link" ? `link your account on ${b}` : a === "unlink" ? `unlink ${b}` : "list your linked accounts";
984
1040
  case "download": return `download ${a ?? "the attachment"}`;
985
1041
  case "inbox": return `read ${a}'s inbox`;
986
1042
  case "tail": return `follow ${a ?? "the board"}`;
@@ -1013,6 +1069,7 @@ function missing() {
1013
1069
  "schedule"
1014
1070
  ].includes(command) && isMessageRef(a)) return `message ${ref(a)}`;
1015
1071
  if (command === "schedule" && a === "cancel" && b !== void 0) return `schedule ${b}`;
1072
+ if ((command === "grants" || command === "tokens") && a === "revoke" && b !== void 0) return `${command === "grants" ? "grant" : "token"} ${b}`;
1016
1073
  if (command === "download") return `attachment ${a}`;
1017
1074
  }
1018
1075
  /**
@@ -1115,7 +1172,7 @@ function failureOf(error) {
1115
1172
  });
1116
1173
  const specific = (fallback) => /^[a-z]+(_[a-z]+)+$/.test(code) && code !== "invalid_request" && code !== "remote_error" ? code : fallback;
1117
1174
  if (status === 400) return theirs("usage", specific("invalid_arguments"), `Can't ${doing()}.`);
1118
- if (status === 401) return theirs("auth", "login_required", "The board didn't accept your credentials.", [{
1175
+ if (status === 401) return theirs("auth", "login_required", "The board didn't accept your credentials.", process.env.BOARD_TOKEN ? [{ instruction: "Pass a token it accepts in BOARD_TOKEN, or unset it" }] : [{
1119
1176
  instruction: "Log in again",
1120
1177
  argv: ["vercel", "login"]
1121
1178
  }]);
@@ -1210,7 +1267,10 @@ function connection(url = flags.url, options = {}) {
1210
1267
  const team = flags.scope ?? flags.team;
1211
1268
  const configured = url ?? (team === void 0 ? configuredUrl() : null);
1212
1269
  if (configured === null) {
1213
- const board$1 = board({ backend: vercel(team !== void 0 ? { team } : {}) });
1270
+ const board$1 = board({ backend: vercel({
1271
+ ...team !== void 0 ? { team } : {},
1272
+ ...sending(options.token)
1273
+ }) });
1214
1274
  return {
1215
1275
  url: null,
1216
1276
  transport: { source: credentialSource },
@@ -1221,7 +1281,10 @@ function connection(url = flags.url, options = {}) {
1221
1281
  reached = at;
1222
1282
  const onVercel = vercelBoard(at);
1223
1283
  if (onVercel) {
1224
- const board$3 = board({ backend: vercel(onVercel) });
1284
+ const board$3 = board({ backend: vercel({
1285
+ ...onVercel,
1286
+ ...sending(options.token)
1287
+ }) });
1225
1288
  return {
1226
1289
  url: at,
1227
1290
  transport: { source: credentialSource },
@@ -1240,6 +1303,15 @@ function connection(url = flags.url, options = {}) {
1240
1303
  board: profile !== void 0 ? board$2.as(profile) : board$2
1241
1304
  };
1242
1305
  }
1306
+ /** For Vercel's API, `token` in place of your own credentials: a board token's secret. */
1307
+ function sending(token) {
1308
+ if (token === void 0) return {};
1309
+ return { fetch: (input, init) => {
1310
+ const request = new Request(input, init);
1311
+ request.headers.set("authorization", `Bearer ${token}`);
1312
+ return globalThis.fetch(request);
1313
+ } };
1314
+ }
1243
1315
  /** A handle on the board this command talks to: `vb()`'s, as `--as` says, or the one `connection()` finds. */
1244
1316
  function client() {
1245
1317
  if (!io.board) return connection().board;
@@ -1323,94 +1395,6 @@ function portFlag() {
1323
1395
  if (port !== void 0 && !(Number.isInteger(port) && port >= 0 && port <= 65535)) throw usage(`${flags.port} isn't a port.`, { reason: "invalid_port" });
1324
1396
  return port;
1325
1397
  }
1326
- /** How long `identity link` waits for the sign-in or the code. */
1327
- const LINK_WAIT_MS = 6e5;
1328
- async function identity(args) {
1329
- const board = client();
1330
- const [sub, target] = args;
1331
- if (sub === void 0) {
1332
- const view = await board.identity();
1333
- if (flags.json) return result(view), 0;
1334
- if (view.links.length === 0) out("no linked accounts");
1335
- for (const link of view.links) out(`${link.handle ? `@${link.handle} ` : ""}${link.actor} by ${link.method === "code" ? "code" : "sign-in"} · ${link.at}`);
1336
- if (view.unlinked?.length) out(`not linked ${view.unlinked.join(", ")} (${named()} identity link <key>)`);
1337
- return 0;
1338
- }
1339
- if (sub === "unlink") {
1340
- if (!target) throw usage(`Missing the account to unlink: a handle or actor ID from \`${named()} identity\`.`, { reason: "missing_arguments" });
1341
- if (args.length !== 2) throw usage("identity unlink takes one account.");
1342
- const unlinked = await board.unlinkAccount(target);
1343
- if (flags.json) result({
1344
- ...unlinked,
1345
- outcome: "unlinked"
1346
- });
1347
- else out(`unlinked ${unlinked.actor}`);
1348
- return 0;
1349
- }
1350
- if (sub !== "link") throw usage(`Unknown command ${sub} for ${named()} identity.`, { reason: "unknown_command" });
1351
- if (!target) throw usage("Missing the sync entry to link your account on, such as slack.", { reason: "missing_arguments" });
1352
- if (args.length !== 2) throw usage("identity link takes one sync entry.");
1353
- const before = new Set((await board.identity()).links.map((link) => `${link.actor}|${link.at}`));
1354
- const platform = (await board.discovery().catch(() => null))?.sync.find((entry) => entry.key === target)?.platform ?? target;
1355
- const { code, expiresAt, signIn } = await board.syncCode(target);
1356
- const consent = signIn !== void 0 && !flags.code;
1357
- if (consent && browsable(signIn) === null) throw new Failed({
1358
- class: "platform",
1359
- reason: "invalid_sign_in",
1360
- message: "The board's sign-in isn't an http or https URL.",
1361
- details: [JSON.stringify(signIn)],
1362
- data: { signIn }
1363
- });
1364
- if (flags.json) line({
1365
- type: "code",
1366
- sync: target,
1367
- platform,
1368
- code,
1369
- expiresAt,
1370
- ...signIn !== void 0 ? { signIn } : {}
1371
- });
1372
- const until = Date.now() + LINK_WAIT_MS;
1373
- const sleep = () => new Promise((resolve) => setTimeout(resolve, 2e3));
1374
- /** An account linked on that platform since this run began: where the code or the sign-in landed. */
1375
- const linkedSince = async () => (await board.identity()).links.find((link) => link.actor.startsWith(`${platform}:user:`) && !before.has(`${link.actor}|${link.at}`));
1376
- if (consent) {
1377
- if (!flags.json) {
1378
- out(`Sign in to ${platform} as yourself to link it (and to post as you there):\n\n ${signIn}\n`);
1379
- out(`Only open it yourself: whoever completes it links their account to you.`);
1380
- }
1381
- if (!flags["no-open"] && !flags.json) openBrowser(signIn);
1382
- } else if (!flags.json) {
1383
- const minutes = Math.round((Date.parse(expiresAt) - Date.now()) / 6e4);
1384
- out(`From your ${platform} account, post this as a message anywhere the board syncs, within ${minutes} minutes:\n\n ${code}\n`);
1385
- }
1386
- if (flags["no-wait"]) return 0;
1387
- while (Date.now() < Math.min(until, consent ? Infinity : Date.parse(expiresAt))) {
1388
- await sleep();
1389
- const linked = await linkedSince();
1390
- if (linked) {
1391
- if (flags.json) line({
1392
- type: "linked",
1393
- account: linked
1394
- });
1395
- else out(`linked ${linked.handle ? `@${linked.handle} ` : ""}(${linked.actor})`);
1396
- return 0;
1397
- }
1398
- }
1399
- throw new Failed({
1400
- class: "timeout",
1401
- reason: "timeout",
1402
- message: consent ? "Nothing linked an account within 10 minutes." : "The code expired before a message carried it.",
1403
- next: [{
1404
- instruction: "Try again",
1405
- argv: [
1406
- ...cli(),
1407
- "identity",
1408
- "link",
1409
- target
1410
- ]
1411
- }]
1412
- });
1413
- }
1414
1398
  async function run(command, args) {
1415
1399
  switch (command) {
1416
1400
  case void 0:
@@ -1446,12 +1430,31 @@ async function run(command, args) {
1446
1430
  }
1447
1431
  case "conformance": {
1448
1432
  if (args.length > 0) throw usage("conformance takes no arguments: pass its channel with --channel.");
1449
- const report = await conformance(client(), flags.channel ? { channel: flags.channel } : {});
1433
+ const withToken = io.board ? {} : { withToken: (secret) => connection(flags.url, { token: secret }).board };
1434
+ const report = await conformance(client(), {
1435
+ ...flags.channel ? { channel: flags.channel } : {},
1436
+ ...withToken
1437
+ });
1450
1438
  if (flags.json) result(report);
1451
1439
  else out(formatConformance(report));
1452
1440
  return report.ok ? 0 : 1;
1453
1441
  }
1454
1442
  case "channels": {
1443
+ const [sub, ...rest] = args;
1444
+ if (sub === "create") {
1445
+ const [path, extra] = rest;
1446
+ if (!path) throw usage("Missing the channel to create, such as dev/alerts.", { reason: "missing_arguments" });
1447
+ if (extra !== void 0) throw usage(`channels create takes one channel: ${extra} is one too many.`);
1448
+ const { channel, created } = await client().createChannel(path);
1449
+ if (flags.json) result({
1450
+ channel,
1451
+ outcome: created ? "created" : "unchanged"
1452
+ });
1453
+ else out(created ? `created ${channel.path}` : `${channel.path} already exists`);
1454
+ return 0;
1455
+ }
1456
+ if (sub !== void 0 && sub !== "list") throw usage(`Unknown command ${sub} for ${named()} channels.`, { reason: "unknown_command" });
1457
+ if (rest.length > 0) throw usage(`channels list takes no arguments: ${rest[0]} is one too many.`);
1455
1458
  const channels = await client().channels();
1456
1459
  if (flags.json) return result({ channels }), 0;
1457
1460
  if (channels.length === 0) return out("no channels yet"), 0;
@@ -1463,17 +1466,18 @@ async function run(command, args) {
1463
1466
  group = prefix;
1464
1467
  }
1465
1468
  const count = (n, one, many) => `${n} ${n === 1 ? one : many}`;
1466
- out(`${prefix ? " " : ""}${channel.path} ${count(channel.threads, "thread", "threads")} · ${count(channel.messages, "message", "messages")} · last #${channel.lastIndex}`);
1469
+ const { threads, messages, lastIndex } = channel;
1470
+ const counted = threads !== void 0 && messages !== void 0 ? ` ${count(threads, "thread", "threads")} · ${count(messages, "message", "messages")}${lastIndex ? ` · last #${lastIndex}` : ""}` : "";
1471
+ out(`${prefix ? " " : ""}${channel.path}${counted}`);
1467
1472
  }
1468
1473
  return 0;
1469
1474
  }
1470
1475
  case "actors": {
1471
1476
  const actors = await client().actors();
1472
1477
  if (flags.json) return result({ actors }), 0;
1473
- for (const actor of actors) out(`@${actor.handle} ${actor.name} ${actor.id}${actor.person ? ` → @${actor.person.handle}` : ""}`);
1478
+ for (const actor of actors) out(`@${actor.handle} ${actor.name} ${actor.id}`);
1474
1479
  return 0;
1475
1480
  }
1476
- case "identity": return identity(args);
1477
1481
  case "read": {
1478
1482
  const target = args[0];
1479
1483
  const board = client();
@@ -1620,6 +1624,80 @@ async function run(command, args) {
1620
1624
  else out(formatSchedule(schedule));
1621
1625
  return 0;
1622
1626
  }
1627
+ case "grants": {
1628
+ const [verb, ...rest] = args;
1629
+ if (verb === void 0 || verb === "list") {
1630
+ if (rest.length > 0) throw usage(`grants list takes no arguments: ${rest[0]} is one too many.`);
1631
+ const list = await client().grants();
1632
+ if (flags.json) return result({ grants: list }), 0;
1633
+ out(list.length === 0 ? "no grants" : formatGrants(list));
1634
+ return 0;
1635
+ }
1636
+ if (verb === "revoke") {
1637
+ const id = trustId("grant", rest[0]);
1638
+ const { revoked } = await client().revokeGrant(id);
1639
+ if (flags.json) result({
1640
+ grant: id,
1641
+ outcome: revoked ? "revoked" : "unchanged"
1642
+ });
1643
+ else out(revoked ? `revoked ${id}` : `${id} is already gone`);
1644
+ return 0;
1645
+ }
1646
+ if (verb !== "create") throw usage(`Unknown command ${verb} for ${named()} grants.`, { reason: "unknown_command" });
1647
+ const [issuer, extra] = rest;
1648
+ if (!issuer) throw usage("Missing the issuer to grant: vercel, github, or an https URL.", { reason: "missing_arguments" });
1649
+ if (extra !== void 0) throw usage(`grants create takes one issuer, and its claims as --claim name=value: ${extra} is one too many.`);
1650
+ const claims = claimPairs(flags.claim);
1651
+ if (Object.keys(claims).length === 0) throw usage(`Missing the claims ${issuer}'s tokens must carry: pass --claim name=value.`, { reason: "missing_arguments" });
1652
+ const { grant, created } = await client().createGrant({
1653
+ issuer,
1654
+ claims,
1655
+ ...flags.can !== void 0 ? { can: canFlag(flags.can) } : {}
1656
+ });
1657
+ if (flags.json) result({
1658
+ grant,
1659
+ outcome: created ? "granted" : "updated"
1660
+ });
1661
+ else out(`${created ? "granted" : "updated"} ${grant.id}`);
1662
+ return 0;
1663
+ }
1664
+ case "tokens": {
1665
+ const [verb, ...rest] = args;
1666
+ if (verb === void 0 || verb === "list") {
1667
+ if (rest.length > 0) throw usage(`tokens list takes no arguments: ${rest[0]} is one too many.`);
1668
+ const list = await client().tokens();
1669
+ if (flags.json) return result({ tokens: list }), 0;
1670
+ out(list.length === 0 ? "no tokens" : formatTokens(list));
1671
+ return 0;
1672
+ }
1673
+ if (verb === "revoke") {
1674
+ const id = trustId("token", rest[0]);
1675
+ const { revoked } = await client().revokeToken(id);
1676
+ if (flags.json) result({
1677
+ token: id,
1678
+ outcome: revoked ? "revoked" : "unchanged"
1679
+ });
1680
+ else out(revoked ? `revoked ${id}` : `${id} isn't a live token of yours`);
1681
+ return 0;
1682
+ }
1683
+ if (verb !== "create") throw usage(`Unknown command ${verb} for ${named()} tokens.`, { reason: "unknown_command" });
1684
+ const [name, extra] = rest;
1685
+ if (!name) throw usage("Missing a name for the token, like feedback-widget.", { reason: "missing_arguments" });
1686
+ if (extra !== void 0) throw usage(`tokens create takes one name: ${extra} is one too many.`);
1687
+ const { token, secret } = await client().createToken({
1688
+ name,
1689
+ ...flags.can !== void 0 ? { can: canFlag(flags.can) } : {},
1690
+ ...flags.profile !== void 0 ? { profile: flags.profile } : {},
1691
+ ...flags.session !== void 0 ? { session: flags.session } : {},
1692
+ ...flags.expires !== void 0 ? { expires: flags.expires } : {}
1693
+ });
1694
+ if (flags.json) result({
1695
+ token,
1696
+ secret
1697
+ });
1698
+ else out(secret);
1699
+ return 0;
1700
+ }
1623
1701
  case "schedules": {
1624
1702
  const list = await client().schedules({
1625
1703
  ...args[0] ? { re: messageId(args[0]) } : {},
@@ -1935,7 +2013,6 @@ async function runOnce(argv, options) {
1935
2013
  /** The commands about the machine the CLI runs on, not the board: none of them run in-process. */
1936
2014
  const MACHINE = /* @__PURE__ */ new Set([
1937
2015
  "whoami",
1938
- "identity",
1939
2016
  "hooks",
1940
2017
  "hook",
1941
2018
  "serve",
@@ -1,5 +1,5 @@
1
1
  import { m as MESSAGE_TEXT_MAX, p as Labels } from "./schemas-DBmUO6pr.mjs";
2
- import { t as HttpError } from "./errors-C_5rB8Iz.mjs";
2
+ import { t as HttpError } from "./errors-CJXhhzvT.mjs";
3
3
  import { z } from "zod";
4
4
  //#region src/server/middleware.ts
5
5
  /** A write the middleware didn't let through: the API answers 422 with what it responded. */
@@ -1,4 +1,4 @@
1
- import { u as BoardError } from "./errors-C_5rB8Iz.mjs";
1
+ import { u as BoardError } from "./errors-CJXhhzvT.mjs";
2
2
  import { execFile } from "node:child_process";
3
3
  import { createHash } from "node:crypto";
4
4
  import { mkdir, readFile, readdir, stat, unlink, writeFile } from "node:fs/promises";
@@ -496,12 +496,16 @@ function guarded(url, doFetch, suppliedToken) {
496
496
  envRejected = true;
497
497
  }
498
498
  if (process.env.VERCEL === "1") {
499
+ let edgeless = false;
499
500
  if (!headers.has("x-vercel-trusted-oidc-idp-token")) {
500
501
  const token = headers.get("authorization")?.match(/^Bearer (.+)$/i)?.[1] ?? (typeof suppliedToken === "function" ? await suppliedToken() : suppliedToken) ?? (process.env.BOARD_TOKEN || await hostedToken(url));
501
- headers.set("x-vercel-trusted-oidc-idp-token", token);
502
+ const edge = token.startsWith("board_") ? await hostedToken(url).catch(() => null) : token;
503
+ if (edge === null) edgeless = true;
504
+ else headers.set("x-vercel-trusted-oidc-idp-token", edge);
502
505
  }
503
506
  return through(doFetch, input, init, null).catch((error) => {
504
- throw error instanceof Wall ? new BoardError("protected", `${url} is behind Vercel Deployment Protection, and it rejected this deployment's Vercel OIDC token.`, error.status, { hint: "Add this project and environment to the board project's Deployment Protection Trusted Sources." }) : error;
507
+ if (!(error instanceof Wall)) throw error;
508
+ throw edgeless ? new BoardError("protected", `${url} is behind Vercel Deployment Protection, and this deployment has no Vercel OIDC token to get through it.`, error.status, { hint: "Enable OIDC on its project, and add it to the board project's Deployment Protection Trusted Sources." }) : new BoardError("protected", `${url} is behind Vercel Deployment Protection, and it rejected this deployment's Vercel OIDC token.`, error.status, { hint: "Add this project and environment to the board project's Deployment Protection Trusted Sources." });
505
509
  });
506
510
  }
507
511
  const used = await (secret ??= readBypass(host));
@@ -1,5 +1,5 @@
1
1
  import { n as setOwn, t as own } from "./own-OwgPWdmh.mjs";
2
- import { n as foldMessages, r as foldSync } from "./folds-DGYkMW19.mjs";
2
+ import { n as foldMessages, r as foldSync } from "./folds-Cu9Q9zyQ.mjs";
3
3
  //#region src/store/project.ts
4
4
  /** The message an event concerns, if any. */
5
5
  function concernedMessageId(event) {