@lovek4d/kickflip 0.6.0 → 0.7.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/CONTEXT.md +42 -26
- package/SKILL.md +16 -12
- package/bin/doctor.mjs +5 -4
- package/bin/event-lines.mjs +1 -0
- package/bin/install.mjs +3 -3
- package/bin/kickflip.mjs +7 -7
- package/bin/wait-events.mjs +2 -2
- package/board/agents.mjs +2 -2
- package/board/arch-review.mjs +1 -1
- package/board/bin/board.mjs +70 -42
- package/board/domain.mjs +7 -7
- package/board/finish.mjs +4 -4
- package/board/headless.mjs +8 -9
- package/board/onboarding.mjs +1 -1
- package/board/prototype-page.mjs +1 -1
- package/board/public/app-arch.js +2 -2
- package/board/public/app-banner.js +1 -1
- package/board/public/app-cards.js +36 -48
- package/board/public/app-lanes.js +6 -9
- package/board/public/app-needs.js +107 -26
- package/board/public/app-reader.js +52 -42
- package/board/public/app-threads.js +14 -10
- package/board/public/app.html +1 -1
- package/board/public/app.js +22 -12
- package/board/public/progress-review.js +4 -4
- package/board/public/prototype-kit.js +25 -11
- package/board/public/prototype-switcher.js +53 -33
- package/board/public/prototype.html +7 -4
- package/board/public/router.js +34 -6
- package/board/public/shell.js +23 -16
- package/board/public/sidebar.css +7 -2
- package/board/public/sidebar.js +3 -0
- package/board/public/style.css +69 -30
- package/board/public/ui-cards.js +61 -38
- package/board/public/ui-enums.js +12 -3
- package/board/public/ui-header.js +4 -6
- package/board/public/ui-hub.js +7 -4
- package/board/public/ui-links.js +1 -1
- package/board/public/ui-order.js +9 -6
- package/board/public/ui-page-header.js +1 -1
- package/board/public/ui-progress-bar.js +6 -7
- package/board/public/ui-questions.js +18 -3
- package/board/public/ui-sidebar.js +12 -5
- package/board/public/ui-snapshot.js +1 -1
- package/board/public/ui-threads.js +10 -12
- package/board/public/ui.js +7 -4
- package/board/public/view-hub.js +3 -1
- package/board/public/view-notes.js +1 -1
- package/board/public/view-questions.js +19 -3
- package/board/public/view-settings.js +60 -28
- package/board/server.mjs +19 -19
- package/board/stop.mjs +1 -1
- package/board/store.mjs +27 -25
- package/board/tickets.mjs +1 -1
- package/board/transcript.mjs +4 -2
- package/hooks-plugin/hooks/board-data-gate.mjs +9 -6
- package/hub/front-door.mjs +1 -1
- package/hub/home.mjs +1 -1
- package/hub/hub.mjs +6 -6
- package/hub/sessions.mjs +2 -2
- package/hub/try.mjs +7 -7
- package/hub/upgrade.mjs +1 -1
- package/hub/usage.mjs +3 -3
- package/package.json +1 -1
- package/shared/card-prs.mjs +1 -1
- package/shared/machine-gate.mjs +2 -2
- package/shared/tailscale.mjs +1 -1
- package/shared/usage-gate.mjs +1 -1
package/hub/try.mjs
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
var
|
|
2
|
-
`,{mode:384}),
|
|
3
|
-
`)})}a(Et,"failure");function ot(t){return t.ending||(Kt(t.child,"SIGTERM"),t.ending=new Promise(e=>{const i=a(()=>{clearTimeout(s),clearInterval(r),e()},"finish");let s=setTimeout(()=>{Kt(t.child,"SIGKILL"),s=setTimeout(i,1e3)},4e3);const r=setInterval(()=>{try{process.kill(-t.child.pid,0)}catch{i()}},20)})),t.ending}a(ot,"terminate");async function _t(t,e=!1){t.keepPort=e,t.stopped=!0,clearTimeout(t.deadline),clearInterval(t.poll),await ot(t),await t.done}a(_t,"halt");async function Z(t,e=!1){await _t(t,e),X(t.board,t.key,{state:void 0,port:void 0,https:void 0,...t.ready?{}:{sha:void 0}}),t.ready||M.delete(D(t.board,t.key))}a(Z,"stopRun");async function jt(t,e,i,s={}){if(!i)throw new x("This card has no valid worktree.");const r=Lt(i,t.build_target),n=fe(r);if(n)throw new x(n);if(!T.realpathSync(r).startsWith(T.realpathSync(i)+O.sep))throw new x("Build script must be inside the worktree.");const o=await C(i,"rev-parse","HEAD");if(W)throw new x("The hub is stopping. Try again after it restarts.",503);const{state:m}=N(t),S=kt(m,e),d={...F(e)?S:{},...s,sha:o,script:!0,state:"running",...F(e)?{at:ct(),head:o}:{}};delete d.port,delete d.https;const _=Wt(t,e);T.mkdirSync(O.dirname(_),{recursive:!0});const E=T.openSync(_,"w",384);T.fchmodSync(E,384);const $=ie(r,[e,i],{cwd:i,detached:!0,stdio:["ignore","pipe","pipe"],env:{...process.env,KICKFLIP_BOARD:t.route,HOST:"127.0.0.1"}});zt(m,e,d),A(t);const B=D(t,e),l={child:$,board:t,key:e,logFile:_,visited:c(),stopped:!1,port:null,ready:!1};u.set(B,l),M.set(B,o);let R,I;const q=a(P=>{I||=new Error(P),ot(l)},"fail");$.on("error",P=>{R=P}),$.stdout.on("data",P=>T.writeSync(E,P)),$.stderr.on("data",P=>T.writeSync(E,P));const L=Dt({input:$.stdout,crlfDelay:1/0});return L.on("line",P=>{const it=/^port (\d{1,5})$/.exec(P);if(!it||l.port||l.stopped)return;const K=Number(it[1]);if(K<1||K>65535){q("Build script printed an invalid port.");return}l.port=K,clearTimeout(l.deadline);const tt=c()+6e4;let pt=!1;const At=a(async()=>{if(!(pt||l.stopped||l.ready||$.exitCode!==null||$.signalCode!==null)){pt=!0;try{if(await f(K)){if(l.stopped||I||$.exitCode!==null||$.signalCode!==null||(l.opening=Vt(l),await l.opening,l.stopped||I||$.exitCode!==null||$.signalCode!==null))return;l.ready=!0,l.visited=c(),m.kind="web",X(t,e,{state:"ready",port:K,https:l.https}),clearInterval(l.poll)}else c()>=tt&&q("Nothing answered on the build script\u2019s port within 60 seconds.")}finally{pt=!1}}},"check");l.poll=setInterval(()=>At().catch(ht=>q(ht.message)),250),At().catch(ht=>q(ht.message))}),l.deadline=setTimeout(()=>q("No port line within 60 minutes."),60*6e4),$.once("exit",()=>{ot(l)}),l.done=new Promise(P=>$.once("close",(it,K)=>{clearTimeout(l.deadline),clearInterval(l.poll),L.close(),T.closeSync(E),(async()=>{if(await ot(l),await Qt(l),l.finished=!0,l.stopped)return;const tt=e===J&&!l.port?new Error('Build script exited without printing "port <n>".'):null;I||R||it!==0||l.port&&!l.ready||tt?await Et(l,it??1,K,I||R||(l.port&&!l.ready?new Error("Build script exited before its port answered."):tt)):l.ready?(e==="main"?(delete m.main.state,delete m.main.port,delete m.main.https):St(m,e),A(t)):(m.kind="app",X(t,e,{state:"ready"}))})().catch(tt=>w(`build /${t.route} ${e}: ${tt.message}`)).finally(()=>{u.get(B)===l&&u.delete(B),P()})})),w(`build /${t.route} ${e}: started; log ${_}`),{status:"started"}}a(jt,"launch");async function It(t,e,i){const s=D(t,"main");if(y.has(s))throw new x("A build is already running for main.",409);y.add(s);try{u.has(s)&&await Z(u.get(s),!0);const r=O.join(Nt.homedir(),".claude","kickflip","try",`${t.route}-main`);return T.mkdirSync(O.dirname(r),{recursive:!0}),gt(r)?await C(r,"checkout","--detach","--force",i.sha):(await C(e,"worktree","prune"),await C(e,"worktree","add","--detach",r,i.sha)),await jt(t,"main",r,i)}catch(r){throw r.status!==503&&(N(t).state.main={...i,state:"failed"},$t(t,"main",r),A(t)),r}finally{y.delete(s)}}a(It,"launchMain");async function ut(t,{manifest:e=z()}={}){if(await rt(e),lt(e),W)throw new x("The hub is stopping. Try again after it restarts.",503);if(!t||Array.isArray(t)||typeof t!="object"||Object.keys(t).some(d=>!["route","key"].includes(d))||typeof t.route!="string"||typeof t.key!="string")throw new x("Send only the board route and card key.");const{route:i,key:s}=t;if(F(s)&&(!/^[\w.-]+\/[\w.-]+$/.test(s)||s.split("/").some(d=>d==="."||d==="..")))throw new x("Card key must be <wave>/<id>.");const r=U(e).find(d=>d.route===i);if(!r)throw new x(`/${i} is not a board.`,404);if(F(s)&&!r.waves.some(({slug:d,wave:_})=>_.items.some(E=>`${d}/${E.id}`===s)))throw new x("There is no item with this card key.",404);const n=await h();if(n)throw new x(n,409);const o=D(r,s),m=u.get(o);if(y.has(o)||m&&!m.ready&&!m.finished)throw new x(`A build is already running for ${s==="main"?"main":s===J?"the prototype site":"this card"}.`,409);if(s==="main"){const d=N(r).state.main;if(d?.state==="running")throw new x("A build is already running for main.",409);if(d?.state!=="failed"||!d.sha)throw new x("Main has no failed build to retry.",409);const{sha:_,branch:E,slug:$,at:B,script:l}=d;return It(r,et(i,e).repo,{sha:_,branch:E,slug:$,at:B,script:l,...r.build_target?{target:r.build_target}:{}})}y.add(o);const S=g.delete(o);try{u.has(o)&&await Z(u.get(o),!0);const d=s===J?Ct(r):await Ut(r,s,e);return await jt(r,s,d)}catch(d){throw v.delete(o),S&&d.status!==503&&X(r,s,{state:void 0,sha:void 0}),d}finally{y.delete(o)}}a(ut,"start");const Ct=a(t=>{const e=ye(t.route);return gt(e)&&T.statSync(e).isDirectory()?T.realpathSync(e):""},"baseWorktree");async function Xt(t,e){const{state:i}=N(t),s=D(t,J),r=u.get(s);if(!((await me(t.dir)).length>0&&Ct(t))){r&&await Z(r),i.prototypes&&(i.prototypes=null,A(t));return}if(!(r||y.has(s)||i.prototypes?.state==="failed"||await h()))try{await ut({route:t.route,key:J},{manifest:e})}catch(o){if([409,503].includes(o.status))return;throw i.prototypes={state:"failed"},$t(t,J,o),A(t),o}}a(Xt,"serveBase");function Mt(t,e,{manifest:i=z()}={}){const s=U(i).find(n=>n.route===t);if(!s)return null;const r=[...u.values()].find(n=>n.board.dir===s.dir&&n.key.split("/").at(-1).toLowerCase()===e.toLowerCase()&&n.ready&&!n.stopped&&n.child.exitCode===null&&n.child.signalCode===null);return r?{key:r.key,port:r.port}:null}a(Mt,"lookup");async function ft({manifest:t=z(),routes:e}={}){if(await rt(t),lt(t),W)return H;if(H&&e){if(!Q){const i={routes:new Set,manifest:t};i.done=H.catch(()=>{}).then(()=>(Q=null,ft({manifest:i.manifest,routes:[...i.routes]}))),Q=i}Q.manifest=t;for(const i of e)Q.routes.add(i);return Q.done}return H||(H=(async()=>{const i=U(t),s=st(oe,{}).prs||{};for(const r of[...u.values()])if(!i.some(n=>n.dir===r.board.dir)||F(r.key)&&r.ready&&c()-r.visited>=Ee){await Z(r);const{state:n}=N(r.board);St(n,r.key),A(r.board)}for(const r of i){if(W)return;if(e&&!e.includes(r.route))continue;const{state:n}=N(r),o=et(r.route,t),m=o.branch||"main",S=ae(o);for(const{slug:d,wave:_}of r.waves)for(const E of _.items){const $=`${d}/${E.id}`,B=ge(E,r.github||_.repo).map(l=>s[ve(l)]);if(!(E.lane==="done"||B.length&&B.every(l=>["MERGED","CLOSED"].includes(l?.state))))try{const l=D(r,$),R=await Ut(r,$,t);if(R){const L=await C(R,"rev-parse","HEAD");let P=0;try{P=Number(await C(R,"rev-list","--count",`${S}..HEAD`))}catch{}Object.assign(n.cards[$]||={},{head:L,ahead:P,script:gt(Lt(R,r.build_target))}),A(r)}else if(!g.has(l))continue;const I=n.cards[$];if((g.has(l)||r.auto_build&&I.script&&I.ahead>0&&(I.sha||M.get(l))!==I.head&&I.state!=="running")&&!y.has(l))try{await ut({route:r.route,key:$},{manifest:t})}catch(L){if([409,503].includes(L.status))continue;throw X(r,$,{sha:I.head}),await Et({board:r,key:$,logFile:$t(r,$,L)},1),L}}catch(l){w(`try /${r.route} ${$}: ${l.message}`)}}await Xt(r,t).catch(d=>w(`try /${r.route} prototypes: ${d.message}`));try{if(!o.repo||await h())continue;const d=await C(o.repo,"rev-parse",S);let _=!1;try{await C(o.repo,"cat-file","-e",`${d}:${Gt(r.build_target)}`),_=!0}catch{}if(!_){const I=u.get(D(r,"main"));I&&await Z(I),n.main=null,A(r);continue}const E=n.main;if(E?.state==="running"||E?.sha===d&&(E.target||null)===(r.build_target||null)&&(E.state==="failed"||E.state==="ready"&&(n.kind!=="web"||Mt(r.route,"main",{manifest:t}))))continue;const $=D(r,"main");if(y.has($))continue;const B=await C(o.repo,"log","-1","--format=%cI",d);let l=r.github;if(!l)try{l=Ft(await C(o.repo,"remote","get-url","origin"))}catch{}const R={sha:d,branch:m,slug:l||"",at:B,script:_,...r.build_target?{target:r.build_target}:{}};await It(r,o.repo,R)}catch(d){w(`try /${r.route} main: ${d.message}`)}}})().finally(()=>{H=null}),H)}a(ft,"tick");function Pt(t,e){if(G.has(t.dir))return G.get(t.dir);const i=(async()=>{const s=et(t.route,e),r=s.branch||"main",{state:n}=N(t);n.pull={...n.pull,running:!0},A(t);let o,m;try{o=await Ie(s.repo,r)}catch(S){m=S.message}if(o===null?delete n.pull:n.pull={at:ct(),...m?{error:m}:{}},A(t),o===null)return null;if(m)return w(`pull /${t.route}: ${m}`),{commits:0,error:m};if(o.commits){w(`pull /${t.route}: ${r} fast-forwarded ${o.commits} commit${o.commits===1?"":"s"} to ${o.to.slice(0,7)}`);try{await b(t,"main-pulled",{branch:r,...o})}catch(S){w(`pull /${t.route}: could not send main-pulled (${S.message})`)}}return{commits:o.commits}})().finally(()=>G.delete(t.dir));return G.set(t.dir,i),i}a(Pt,"pullMain");async function Zt({manifest:t=z(),routes:e}={}){await rt(t);const i=[];for(const s of U(t)){if(W)break;if(e&&!e.includes(s.route))continue;const r=et(s.route,t);!r.local||!r.repo||(await Pt(s,t))?.commits&&i.push(s.route)}return i}a(Zt,"pullMains");async function qt(t,{manifest:e=z()}={}){if(await rt(e),W)throw new x("The hub is stopping. Try again after it restarts.",503);if(!t||Array.isArray(t)||typeof t!="object"||Object.keys(t).some(n=>n!=="route")||typeof t.route!="string")throw new x("Send only the board route.");const i=U(e).find(n=>n.route===t.route);if(!i)throw new x(`/${t.route} is not a board.`,404);const s=et(i.route,e);if(!s.local||!s.repo)throw new x("This board fetches its branch on every poll; only a local board pulls.",409);const r=await Pt(i,e);if(!r)throw new x("This board\u2019s folder has no origin to pull from.",409);return r.commits&&ft({manifest:e,routes:[i.route]}).catch(n=>w(`try /${i.route} main: ${n.message}`)),r}a(qt,"pull");async function te(){W=!0;const t=Promise.all([...u.values()].map(e=>e.ready?Z(e):_t(e)));await Promise.all([t,H,...G.values()])}return a(te,"stop"),{start:ut,tick:ft,stop:te,lookup:Mt,initialize:rt,pull:qt,pullMains:Zt,endUnshared:Yt}}a(je,"createTryManager");const vt=1e4,bt=a(async(c,...f)=>(await wt("git",["-C",c,...f],{encoding:"utf8",timeout:vt,env:le()})).stdout,"originGit");async function Ie(c,f,{run:w=bt}={}){const h=a(async(...p)=>(await w(...p)).trim(),"git"),b=a(p=>{throw new Error(`can\u2019t pull: ${p}`)},"refuse");try{await h(c,"config","--get","remote.origin.url")}catch{return null}try{await h(c,"fetch","--quiet","--no-tags","origin",`+refs/heads/${f}:refs/remotes/origin/${f}`)}catch(p){b(p.killed?`git fetch timed out after ${vt/1e3} s`:Ht(p.stderr,{repo:c}).error)}try{const p=await h(c,"rev-parse","--verify",`refs/heads/${f}^{commit}`),k=await h(c,"rev-parse","--verify",`refs/remotes/origin/${f}^{commit}`),u=a((v,j)=>h(c,"merge-base","--is-ancestor",v,j).then(()=>!0,M=>{if(M.code===1)return!1;throw M}),"isAncestor");if(p===k||await u(k,p))return{from:p,to:p,commits:0};await u(p,k)||b(`${f} has split from origin`);const y=Number(await h(c,"rev-list","--count",`${p}..${k}`)),g=(await h(c,"worktree","list","--porcelain")).split(`
|
|
1
|
+
var se=Object.defineProperty;var a=(c,f)=>se(c,"name",{value:f,configurable:!0});import O from"node:fs";import T from"node:path";import Mt from"node:os";import mt from"node:http";import{execFile as ie,spawn as ne}from"node:child_process";import{createInterface as Nt}from"node:readline";import{promisify as oe}from"node:util";import{listBoards as J,readManifest as z,readJSON as nt,saveJSON as Bt,appendEvent as ae,GITHUB_STATE as ce,REPO_FETCH as Rt}from"./boards.mjs";import{boardRepo as et,githubFromOrigin as Ht,mainRefOf as le}from"./board-repo.mjs";import{gitReachFault as Ft,remoteFromInput as de,remoteGitEnv as ue}from"./repos.mjs";import{readiness as fe}from"./readiness.mjs";import{tailscaleBin as pe}from"../shared/tailscale.mjs";import{buildScriptError as he,buildScriptOf as Wt}from"../board/build-script.mjs";import{appendThread as me,recordedWorktree as we,servedRoundsOpen as ye,setItem as ge}from"../board/store.mjs";import{BASE_KEY as K,baseWorktreeOf as $e}from"../board/prototype-base.mjs";import{cardPRs as ve,hasPR as be,prKeyOf as Se}from"../shared/card-prs.mjs";import{hubGate as ke}from"../board/on-host.mjs";import{pipeUpgrade as Oe,refuse as Te,refuseUpgrade as Ee}from"./front-door.mjs";import{shareOf as xe}from"./share.mjs";class E extends Error{static{a(this,"BuildError")}constructor(f,h=400){super(f),this.status=h}}const wt=oe(ie),_=a(async(c,...f)=>(await wt("git",["-C",c,...f],{encoding:"utf8",timeout:3e3})).stdout.trim(),"git"),je=/errSecInternalComponent|keychain[^\n]*\blocked\b|\blocked\b[^\n]*keychain/i,Y={first:8500,last:8599},yt=a(c=>/^\d+$/.test(String(c))&&Number(c)>=Y.first&&Number(c)<=Y.last,"isTryPort"),_e=30*6e4,Gt=["tailscale-user-login","tailscale-user-name","tailscale-user-profile-pic","x-forwarded-for"],lt={main:"main",[K]:"prototypes"},H=a(c=>!Object.hasOwn(lt,c),"isCard"),Lt=a((c,f)=>T.join(c,Wt(f)),"scriptAt"),gt=a(c=>O.existsSync(c),"exists"),Kt=a((c,f)=>T.join(c.dir,"data","try",`${f.split("/").at(-1)}.log`),"logFileOf");function $t(c,f,h){const m=Kt(c,f);return O.mkdirSync(T.dirname(m),{recursive:!0}),O.writeFileSync(m,h.message+`
|
|
2
|
+
`,{mode:384}),O.chmodSync(m,384),m}a($t,"failureLog");const Ie=a(c=>new Promise(f=>{const h=mt.get({host:"127.0.0.1",port:c,path:"/",timeout:1e3},m=>{m.destroy(),f(!0)});h.on("timeout",()=>h.destroy()),h.on("error",()=>f(!1))}),"probe"),Ut=a((c,f)=>{try{process.kill(-c.pid,f)}catch{}},"signalGroup");async function Jt(c,f,h){const m=await we(c.dir,f),[S,p]=f.split("/"),k=c.waves.find(b=>b.slug===S)?.wave,u=m?.path||k?.items.find(b=>b.id===p)?.worktree;if(typeof u!="string"||!u||u.split(T.sep).includes(".."))return"";let y=m?.repo;const g=(c.github||k?.repo||"").toLowerCase();if(!y&&g){const b=[c.owner?.repo,c.dir,...Object.values(h?.sites||{}).map(I=>I.owner?.repo)];for(const I of new Set(b.filter(P=>typeof P=="string"&&T.isAbsolute(P))))try{if(/github\.com[:/]([^/]+\/[^/]+?)(?:\.git)?$/.exec(await _(I,"remote","get-url","origin"))?.[1].toLowerCase()===g){y=await _(I,"rev-parse","--show-toplevel");break}}catch{}}if(!m?.path&&!y||m?.path&&!T.isAbsolute(u))return"";try{const b=[T.join(O.realpathSync(Mt.homedir()),".claude","kickflip","worktrees")];if(typeof y=="string"&&T.isAbsolute(y))try{b.push(T.join(O.realpathSync(y),".claude","worktrees"))}catch{}const I=T.resolve(y||"/",u),P=O.realpathSync(I);return b.some(W=>I.startsWith(W+T.sep)&&P.startsWith(W+T.sep))&&O.statSync(P).isDirectory()?P:""}catch{return""}}a(Jt,"installWorktree");function Ce({now:c=Date.now,answers:f=Ie,log:h=console.log,waiting:m=a(async()=>null,"waiting"),event:S=ae,gate:p=ke}={}){const k=new Map,u=new Map,y=new Set,g=new Set,b=new Map,I=new Map,P=new Map,W=new Map;let G=!1,F=null,Q=null,ot=null;const at=a(()=>new Date(c()).toISOString(),"stamp");function M(t){if(k.has(t.dir))return k.get(t.dir);const e=T.join(t.dir,"data","try.json"),s=nt(e,{kind:null,main:null,cards:{}}),i={...yt(s.main_port)?{main_port:s.main_port}:{},kind:s.kind||null,main:s.main||null,cards:{...s.cards},...s.pull?{pull:{...s.pull,running:void 0}}:{}},r={state:i,file:e,written:JSON.stringify(s)};k.set(t.dir,r),i.main_port&&I.set(t.dir,i.main_port);const n=Object.entries(i.cards).filter(([,o])=>o.state==="running");for(const[o]of n)g.add(N(t,o));return i.kind==="web"?(i.cards=Object.fromEntries(n),i.main&&i.main.state!=="failed"&&(delete i.main.state,delete i.main.port,delete i.main.https)):i.main?.state==="running"&&(delete i.main.state,delete i.main.sha),s.prototypes?.state==="failed"&&(i.prototypes=s.prototypes),D(t),r}a(M,"stateOf");function D(t){const e=k.get(t.dir),s=JSON.stringify(e.state);s!==e.written&&(Bt(e.file,e.state),e.written=s)}a(D,"persist");const N=a((t,e)=>`${t.dir}\0${e}`,"runKey"),St=a((t,e)=>H(e)?t.cards[e]:t[lt[e]],"entryOf"),zt=a((t,e,s)=>{H(e)?t.cards[e]=s:t[lt[e]]=s},"setEntry"),kt=a((t,e)=>{H(e)?delete t.cards[e]:t[lt[e]]=null},"dropEntry");function X(t,e,s){const{state:i}=M(t),r=St(i,e);if(!r)return;const n=Object.hasOwn(s,"state")&&s.state!==r.state;Object.assign(r,s,H(e)&&n?{at:at()}:{}),D(t)}a(X,"update");function dt(t){const e=[...Object.values(t?.sites||{}),...Object.values(t?.closed||{}).map(s=>s.site)];for(const s of e){if(!s?.dir)continue;const i=nt(T.join(s.dir,"data","try.json"),{}).main_port;yt(i)&&I.set(s.dir,Number(i))}}a(dt,"reserveMains");function Yt(t){const{board:e,key:s}=t,i=N(e,s),r=s==="main"?I.get(e.dir):b.get(i);if(r)return r;const n=new Set([...I.values(),...b.values()]);let o=Y.first;for(;n.has(o)&&o<=Y.last;)o++;if(o>Y.last)throw new Error(`No try ports available (${Y.first}\u2013${Y.last}). Stop a served card and retry.`);return s==="main"?(I.set(e.dir,o),M(e).state.main_port=o,D(e)):b.set(i,o),o}a(Yt,"publicPort");const rt=a(async t=>{const e=pe();if(!e)throw new Error("Tailscale isn't installed on this host");return wt(e,t,{encoding:"utf8",timeout:1e4})},"tailscale");async function Ot(t){try{await rt(["serve",`--https=${t}`,"off"])}catch(e){h(`try port ${t}: could not close port: ${(e.stderr||e.message).trim()}`)}}a(Ot,"removePort");const Tt=a(t=>xe(nt(T.join(t.dir,"board.json"),{})),"sharedWith");async function Et(t,e){const s=await p(t),i=t.headers["tailscale-user-login"];return s!=="other"||!i||!Tt(e).includes(i)?s:t.method==="GET"||t.method==="HEAD"?"shared":"view-only"}a(Et,"relayVerdict");function Vt(){for(const t of u.values()){if(!t.shared?.size)continue;const e=Tt(t.board);for(const s of t.shared)e.includes(s.login)||(t.shared.delete(s),s.res.destroy())}}a(Vt,"endUnshared");async function Qt(t){t.https=Yt(t);const e=t.sockets=new Set,s=a(()=>{t.visited=c()},"visit"),i=a(n=>{e.add(n),n.on("data",s),n.on("close",()=>e.delete(n))},"track"),r=t.shared=new Set;if(t.relay=mt.createServer(async(n,o)=>{const w=await Et(n,t.board);if(w!=="owner"&&w!=="shared"){Te(n,o,w);return}if(w==="shared"){const $={login:n.headers["tailscale-user-login"],res:o};r.add($),o.on("close",()=>r.delete($))}const x=Object.fromEntries(Object.entries(n.headers).filter(([$])=>!Gt.includes($))),d=mt.request({host:"127.0.0.1",port:t.port,method:n.method,path:n.url,headers:x},$=>{o.writeHead($.statusCode,$.headers),$.on("data",s),$.pipe(o),$.on("close",()=>{$.complete||o.destroy()})});o.on("close",()=>{o.writableFinished||d.destroy()}),d.on("error",()=>{o.headersSent?o.destroy():o.writeHead(502).end()}),n.pipe(d)}),t.relay.on("connection",n=>{s(),i(n)}),t.relay.on("upgrade",async(n,o,w)=>{const x=await Et(n,t.board);if(x!=="owner"){Ee(o,x==="shared"?"view-only":x);return}i(Oe({req:n,socket:o,head:w,port:t.port,path:n.url,replace:Gt}))}),await new Promise((n,o)=>{t.relay.once("error",o),t.relay.listen(0,"127.0.0.1",n)}),!t.stopped){t.exposed=!0;try{await rt(["serve","--bg",`--https=${t.https}`,`http://127.0.0.1:${t.relay.address().port}`])}catch(n){throw new Error(`Couldn't open try port ${t.https}: ${(n.stderr||n.message).trim()}`)}}}a(Qt,"openPort");async function Xt(t){if(t.closing=!0,await t.opening?.catch(()=>{}),t.exposed&&!G&&await Ot(t.https),t.relay){for(const e of t.sockets)e.destroy();await new Promise(e=>t.relay.close(e))}t.keepPort||b.delete(N(t.board,t.key))}a(Xt,"closePort");async function Zt(){const t=[...u.values()].filter(s=>s.ready&&s.relay&&!s.closing);if(!t.length)return;let e;try{e=JSON.parse((await rt(["serve","status","--json"])).stdout)}catch(s){h(`try ports check: ${(s.stderr||s.message).trim()}`);return}for(const s of t){const i=`http://127.0.0.1:${s.relay.address().port}`,r=Object.entries(e?.Web||{}).find(([n])=>n.endsWith(`:${s.https}`))?.[1];if(r?.Handlers?.["/"]?.Proxy!==i){h(`try port ${s.https}: its serve mapping was ${r?"changed":"gone"}; serving it again`);try{await rt(["serve","--bg",`--https=${s.https}`,i])}catch(n){h(`try port ${s.https}: could not serve it again: ${(n.stderr||n.message).trim()}`)}}}}a(Zt,"reserve");function st(t=z()){if(ot)return ot;dt(t);for(const e of J(t))M(e);return ot=(async()=>{try{const{stdout:e}=await rt(["serve","status","--json"]),s=JSON.parse(e);for(const i of Object.keys(s?.TCP||{}))yt(i)&&![...u.values()].some(r=>r.https===Number(i))&&await Ot(i)}catch(e){h(`try startup cleanup: ${(e.stderr||e.message).trim()}`)}})(),ot}a(st,"initialize");async function xt(t,e,s,i){const{board:r,key:n}=t;if(X(r,n,{state:"failed",port:void 0,https:void 0}),!H(n))return;const o=[];let w=!1;try{for await(const d of Nt({input:O.createReadStream(t.logFile),crlfDelay:1/0}))w||=je.test(d),o.push(d),o.length>5&&o.shift()}catch(d){o.push(`Could not read the build log: ${d.message}`)}const x=t.port||(M(r).state.kind||(r.waves.some(({wave:d})=>d.items.some($=>$.install?.url))?"app":"web"))==="web"?"Serve":"Build";await me(r.dir,n,{text:[`${x} failed (exit ${e??1})`,i?.message,s&&`Stopped by ${s}.`,w&&"The login keychain must be unlocked on the host.",...o].filter(Boolean).join(`
|
|
3
|
+
`)})}a(xt,"failure");function ct(t){return t.ending||(Ut(t.child,"SIGTERM"),t.ending=new Promise(e=>{const s=a(()=>{clearTimeout(i),clearInterval(r),e()},"finish");let i=setTimeout(()=>{Ut(t.child,"SIGKILL"),i=setTimeout(s,1e3)},4e3);const r=setInterval(()=>{try{process.kill(-t.child.pid,0)}catch{s()}},20)})),t.ending}a(ct,"terminate");async function jt(t,e=!1){t.keepPort=e,t.stopped=!0,clearTimeout(t.deadline),clearInterval(t.poll),await ct(t),await t.done}a(jt,"halt");async function Z(t,e=!1){await jt(t,e),X(t.board,t.key,{state:void 0,port:void 0,https:void 0,...t.ready?{}:{sha:void 0}}),t.ready||P.delete(N(t.board,t.key))}a(Z,"stopRun");async function _t(t,e,s,i={}){if(!s)throw new E("This card has no valid worktree.");const r=Lt(s,t.build_target),n=he(r);if(n)throw new E(n);if(!O.realpathSync(r).startsWith(O.realpathSync(s)+T.sep))throw new E("Build script must be inside the worktree.");const o=await _(s,"rev-parse","HEAD");if(G)throw new E("The hub is stopping. Try again after it restarts.",503);const{state:w}=M(t),x=St(w,e),d={...H(e)?x:{},...i,sha:o,script:!0,state:"running",...H(e)?{at:at(),head:o}:{},...e===K?{since:at()}:{}};delete d.port,delete d.https;const $=Kt(t,e);O.mkdirSync(T.dirname($),{recursive:!0});const j=O.openSync($,"w",384);O.fchmodSync(j,384);const v=ne(r,[e,s],{cwd:s,detached:!0,stdio:["ignore","pipe","pipe"],env:{...process.env,KICKFLIP_BOARD:t.route,HOST:"127.0.0.1"}});zt(w,e,d),D(t);const B=N(t,e),l={child:v,board:t,key:e,logFile:$,visited:c(),stopped:!1,port:null,ready:!1};u.set(B,l),P.set(B,o);let R,C;const q=a(A=>{C||=new Error(A),ct(l)},"fail");v.on("error",A=>{R=A}),v.stdout.on("data",A=>O.writeSync(j,A)),v.stderr.on("data",A=>O.writeSync(j,A));const L=Nt({input:v.stdout,crlfDelay:1/0});return L.on("line",A=>{const it=/^port (\d{1,5})$/.exec(A);if(!it||l.port||l.stopped)return;const U=Number(it[1]);if(U<1||U>65535){q("Build script printed an invalid port.");return}l.port=U,clearTimeout(l.deadline);const tt=c()+6e4;let pt=!1;const Dt=a(async()=>{if(!(pt||l.stopped||l.ready||v.exitCode!==null||v.signalCode!==null)){pt=!0;try{if(await f(U)){if(l.stopped||C||v.exitCode!==null||v.signalCode!==null||(l.opening=Qt(l),await l.opening,l.stopped||C||v.exitCode!==null||v.signalCode!==null))return;l.ready=!0,l.visited=c(),w.kind="web",X(t,e,{state:"ready",port:U,https:l.https}),clearInterval(l.poll)}else c()>=tt&&q("Nothing answered on the build script\u2019s port within 60 seconds.")}finally{pt=!1}}},"check");l.poll=setInterval(()=>Dt().catch(ht=>q(ht.message)),250),Dt().catch(ht=>q(ht.message))}),l.deadline=setTimeout(()=>q("No port line within 60 minutes."),60*6e4),v.once("exit",()=>{ct(l)}),l.done=new Promise(A=>v.once("close",(it,U)=>{clearTimeout(l.deadline),clearInterval(l.poll),L.close(),O.closeSync(j),(async()=>{if(await ct(l),await Xt(l),l.finished=!0,l.stopped)return;const tt=e===K&&!l.port?new Error('Build script exited without printing "port <n>".'):null;C||R||it!==0||l.port&&!l.ready||tt?await xt(l,it??1,U,C||R||(l.port&&!l.ready?new Error("Build script exited before its port answered."):tt)):l.ready?(e==="main"?(delete w.main.state,delete w.main.port,delete w.main.https):kt(w,e),D(t)):(w.kind="app",X(t,e,{state:"ready"}))})().catch(tt=>h(`build /${t.route} ${e}: ${tt.message}`)).finally(()=>{u.get(B)===l&&u.delete(B),A()})})),h(`build /${t.route} ${e}: started; log ${$}`),{status:"started"}}a(_t,"launch");async function It(t,e,s){const i=N(t,"main");if(y.has(i))throw new E("A build is already running for main.",409);y.add(i);try{u.has(i)&&await Z(u.get(i),!0);const r=T.join(Mt.homedir(),".claude","kickflip","try",`${t.route}-main`);return O.mkdirSync(T.dirname(r),{recursive:!0}),gt(r)?await _(r,"checkout","--detach","--force",s.sha):(await _(e,"worktree","prune"),await _(e,"worktree","add","--detach",r,s.sha)),await _t(t,"main",r,s)}catch(r){throw r.status!==503&&(M(t).state.main={...s,state:"failed"},$t(t,"main",r),D(t)),r}finally{y.delete(i)}}a(It,"launchMain");async function ut(t,{manifest:e=z()}={}){if(await st(e),dt(e),G)throw new E("The hub is stopping. Try again after it restarts.",503);if(!t||Array.isArray(t)||typeof t!="object"||Object.keys(t).some(d=>!["route","key"].includes(d))||typeof t.route!="string"||typeof t.key!="string")throw new E("Send only the board route and card key.");const{route:s,key:i}=t;if(H(i)&&(!/^[\w.-]+\/[\w.-]+$/.test(i)||i.split("/").some(d=>d==="."||d==="..")))throw new E("Card key must be <wave>/<id>.");const r=J(e).find(d=>d.route===s);if(!r)throw new E(`/${s} is not a board.`,404);if(H(i)&&!r.waves.some(({slug:d,wave:$})=>$.items.some(j=>`${d}/${j.id}`===i)))throw new E("There is no item with this card key.",404);const n=await m();if(n)throw new E(n,409);const o=N(r,i),w=u.get(o);if(y.has(o)||w&&!w.ready&&!w.finished)throw new E(`A build is already running for ${i==="main"?"main":i===K?"the prototype site":"this card"}.`,409);if(i==="main"){const d=M(r).state.main;if(d?.state==="running")throw new E("A build is already running for main.",409);if(d?.state!=="failed"||!d.sha)throw new E("Main has no failed build to retry.",409);const{sha:$,branch:j,slug:v,at:B,script:l}=d;return It(r,et(s,e).repo,{sha:$,branch:j,slug:v,at:B,script:l,...r.build_target?{target:r.build_target}:{}})}y.add(o);const x=g.delete(o);try{u.has(o)&&await Z(u.get(o),!0);const d=i===K?Ct(r):await Jt(r,i,e);return await _t(r,i,d)}catch(d){throw b.delete(o),x&&d.status!==503&&X(r,i,{state:void 0,sha:void 0}),d}finally{y.delete(o)}}a(ut,"start");const Ct=a(t=>{const e=$e(t.route);return gt(e)&&O.statSync(e).isDirectory()?O.realpathSync(e):""},"baseWorktree");async function qt(t,e){const{state:s}=M(t),i=N(t,K),r=u.get(i),n=(await ye(t.dir)).length>0&&Ct(t);if(!n){r&&await Z(r),s.prototypes&&(s.prototypes=null,D(t));return}if(r?.ready&&!r.stopped&&!y.has(i)){const o=await _(n,"rev-parse","HEAD");if(o===s.prototypes?.sha)return;h(`try /${t.route} prototypes: the base moved to ${o.slice(0,7)}; restarting its serve`)}else if(r||y.has(i)||s.prototypes?.state==="failed"||await m())return;try{await ut({route:t.route,key:K},{manifest:e})}catch(o){if([409,503].includes(o.status))return;throw s.prototypes={state:"failed"},$t(t,K,o),D(t),o}}a(qt,"serveBase");function Pt(t,e,{manifest:s=z()}={}){const i=J(s).find(n=>n.route===t);if(!i)return null;const r=[...u.values()].find(n=>n.board.dir===i.dir&&n.key.split("/").at(-1).toLowerCase()===e.toLowerCase()&&n.ready&&!n.stopped&&n.child.exitCode===null&&n.child.signalCode===null);return r?{key:r.key,port:r.port}:null}a(Pt,"lookup");async function ft({manifest:t=z(),routes:e}={}){if(await st(t),dt(t),G)return F;if(F&&e){if(!Q){const s={routes:new Set,manifest:t};s.done=F.catch(()=>{}).then(()=>(Q=null,ft({manifest:s.manifest,routes:[...s.routes]}))),Q=s}Q.manifest=t;for(const s of e)Q.routes.add(s);return Q.done}return F||(F=(async()=>{await Zt();const s=J(t),i=nt(ce,{}).prs||{};for(const r of[...u.values()])if(!s.some(n=>n.dir===r.board.dir)||H(r.key)&&r.ready&&c()-r.visited>=_e){await Z(r);const{state:n}=M(r.board);kt(n,r.key),D(r.board)}for(const r of s){if(G)return;if(e&&!e.includes(r.route))continue;const{state:n}=M(r),o=et(r.route,t),w=o.branch||"main",x=le(o);for(const{slug:d,wave:$}of r.waves)for(const j of $.items){const v=`${d}/${j.id}`,B=ve(j,r.github||$.repo).map(l=>i[Se(l)]);if(!(j.lane==="done"||B.length&&B.every(l=>["MERGED","CLOSED"].includes(l?.state))))try{const l=N(r,v),R=await Jt(r,v,t);if(R){const L=await _(R,"rev-parse","HEAD");let A=0;try{A=Number(await _(R,"rev-list","--count",`${x}..HEAD`))}catch{}Object.assign(n.cards[v]||={},{head:L,ahead:A,script:gt(Lt(R,r.build_target))}),D(r)}else if(!g.has(l))continue;const C=n.cards[v];if((g.has(l)||r.auto_build&&C.script&&C.ahead>0&&(C.sha||P.get(l))!==C.head&&C.state!=="running")&&!y.has(l))try{await ut({route:r.route,key:v},{manifest:t})}catch(L){if([409,503].includes(L.status))continue;throw X(r,v,{sha:C.head}),await xt({board:r,key:v,logFile:$t(r,v,L)},1),L}}catch(l){h(`try /${r.route} ${v}: ${l.message}`)}}await qt(r,t).catch(d=>h(`try /${r.route} prototypes: ${d.message}`));try{if(!o.repo||await m())continue;const d=await _(o.repo,"rev-parse",x);let $=!1;try{await _(o.repo,"cat-file","-e",`${d}:${Wt(r.build_target)}`),$=!0}catch{}if(!$){const C=u.get(N(r,"main"));C&&await Z(C),n.main=null,D(r);continue}const j=n.main;if(j?.state==="running"||j?.sha===d&&(j.target||null)===(r.build_target||null)&&(j.state==="failed"||j.state==="ready"&&(n.kind!=="web"||Pt(r.route,"main",{manifest:t}))))continue;const v=N(r,"main");if(y.has(v))continue;const B=await _(o.repo,"log","-1","--format=%cI",d);let l=r.github;if(!l)try{l=Ht(await _(o.repo,"remote","get-url","origin"))}catch{}const R={sha:d,branch:w,slug:l||"",at:B,script:$,...r.build_target?{target:r.build_target}:{}};await It(r,o.repo,R)}catch(d){h(`try /${r.route} main: ${d.message}`)}}})().finally(()=>{F=null}),F)}a(ft,"tick");function At(t,e){if(W.has(t.dir))return W.get(t.dir);const s=(async()=>{const i=et(t.route,e),r=i.branch||"main",{state:n}=M(t);n.pull={...n.pull,running:!0},D(t);let o,w;try{o=await Pe(i.repo,r)}catch(x){w=x.message}if(o===null?delete n.pull:n.pull={at:at(),...w?{error:w}:{}},D(t),o===null)return null;if(w)return h(`pull /${t.route}: ${w}`),{commits:0,error:w};if(o.commits){h(`pull /${t.route}: ${r} fast-forwarded ${o.commits} commit${o.commits===1?"":"s"} to ${o.to.slice(0,7)}`);try{await S(t,"main-pulled",{branch:r,...o})}catch(x){h(`pull /${t.route}: could not send main-pulled (${x.message})`)}}return{commits:o.commits}})().finally(()=>W.delete(t.dir));return W.set(t.dir,s),s}a(At,"pullMain");async function te({manifest:t=z(),routes:e}={}){await st(t);const s=[];for(const i of J(t)){if(G)break;if(e&&!e.includes(i.route))continue;const r=et(i.route,t);!r.local||!r.repo||(await At(i,t))?.commits&&s.push(i.route)}return s}a(te,"pullMains");async function ee(t,{manifest:e=z()}={}){if(await st(e),G)throw new E("The hub is stopping. Try again after it restarts.",503);if(!t||Array.isArray(t)||typeof t!="object"||Object.keys(t).some(n=>n!=="route")||typeof t.route!="string")throw new E("Send only the board route.");const s=J(e).find(n=>n.route===t.route);if(!s)throw new E(`/${t.route} is not a board.`,404);const i=et(s.route,e);if(!i.local||!i.repo)throw new E("This board fetches its branch on every poll; only a local board pulls.",409);const r=await At(s,e);if(!r)throw new E("This board\u2019s folder has no origin to pull from.",409);return r.commits&&ft({manifest:e,routes:[s.route]}).catch(n=>h(`try /${s.route} main: ${n.message}`)),r}a(ee,"pull");async function re(){G=!0;const t=Promise.all([...u.values()].map(e=>e.ready?Z(e):jt(e)));await Promise.all([t,F,...W.values()])}return a(re,"stop"),{start:ut,tick:ft,stop:re,lookup:Pt,initialize:st,pull:ee,pullMains:te,endUnshared:Vt}}a(Ce,"createTryManager");const vt=1e4,bt=a(async(c,...f)=>(await wt("git",["-C",c,...f],{encoding:"utf8",timeout:vt,env:ue()})).stdout,"originGit");async function Pe(c,f,{run:h=bt}={}){const m=a(async(...p)=>(await h(...p)).trim(),"git"),S=a(p=>{throw new Error(`can\u2019t pull: ${p}`)},"refuse");try{await m(c,"config","--get","remote.origin.url")}catch{return null}try{await m(c,"fetch","--quiet","--no-tags","origin",`+refs/heads/${f}:refs/remotes/origin/${f}`)}catch(p){S(p.killed?`git fetch timed out after ${vt/1e3} s`:Ft(p.stderr,{repo:c}).error)}try{const p=await m(c,"rev-parse","--verify",`refs/heads/${f}^{commit}`),k=await m(c,"rev-parse","--verify",`refs/remotes/origin/${f}^{commit}`),u=a((b,I)=>m(c,"merge-base","--is-ancestor",b,I).then(()=>!0,P=>{if(P.code===1)return!1;throw P}),"isAncestor");if(p===k||await u(k,p))return{from:p,to:p,commits:0};await u(p,k)||S(`${f} has split from origin`);const y=Number(await m(c,"rev-list","--count",`${p}..${k}`)),g=(await m(c,"worktree","list","--porcelain")).split(`
|
|
4
4
|
|
|
5
|
-
`).map(
|
|
6
|
-
`)).find(
|
|
7
|
-
`).at(-1))}}a(
|
|
8
|
-
`).map(u=>/^([\da-f]+)\trefs\/heads\/(.+)$/.exec(u)).filter(Boolean).map(u=>[u[2],u[1]])),k=
|
|
5
|
+
`).map(b=>b.split(`
|
|
6
|
+
`)).find(b=>b.includes(`branch refs/heads/${f}`))?.[0].slice(9);return g?(await m(g,"status","--porcelain","--untracked-files=no")&&S("local changes"),await m(g,"merge","--ff-only","--quiet",k)):await m(c,"update-ref",`refs/heads/${f}`,k,p),{from:p,to:k,commits:y}}catch(p){if(p.message.startsWith("can\u2019t pull: "))throw p;S(String(p.stderr||p.message).trim().split(`
|
|
7
|
+
`).at(-1))}}a(Pe,"pullBranch");async function tr({manifest:c=z(),log:f=console.log,routes:h}={}){const m=h?nt(Rt,{}):{};for(const S of J(c)){if(h&&!h.includes(S.route))continue;const p=et(S.route,c);if(p.local||!p.repo)continue;const k=p.branch||"main",u=new Date().toISOString(),y=Date.now();try{await bt(p.repo,"fetch","--quiet","--no-tags","origin",`+refs/heads/${k}:refs/remotes/origin/${k}`),m[S.route]={at:u,ok:!0}}catch(g){f(`fetch /${S.route} after ${Date.now()-y} ms: ${g.message}`);let b=S.github;if(!b&&!S.remote)try{b=Ht(await _(p.repo,"remote","get-url","origin"))}catch{}m[S.route]={at:u,ok:!1,...Ft(g.killed?`git fetch timed out after ${vt/1e3} s`:g.stderr,{github:b||"",remote:S.remote,repo:p.repo})};continue}if(S.remote)try{await Ae(S,p.repo,k,f)}catch(g){f(`pushed branches /${S.route}: ${g.message}`)}}Bt(Rt,m)}a(tr,"fetchTryBranches");async function Ae(c,f,h,m){const S=c.waves.flatMap(({slug:u,wave:y})=>y.items.filter(g=>g.pushed?.branch&&!be(g)&&g.lane!=="done").map(g=>({key:`${u}/${g.id}`,branch:g.pushed.branch})));if(!S.length)return;const p=new Map((await bt(f,"ls-remote","--heads","origin")).split(`
|
|
8
|
+
`).map(u=>/^([\da-f]+)\trefs\/heads\/(.+)$/.exec(u)).filter(Boolean).map(u=>[u[2],u[1]])),k=de(c.remote)?.host||"the remote";for(const u of S){let y=p.get(u.branch);if(!y)try{y=await _(f,"rev-parse","--verify","--quiet",`refs/heads/${u.branch}^{commit}`)}catch{}let g=!1;if(y)try{await _(f,"merge-base","--is-ancestor",y,`refs/remotes/origin/${h}`),g=!0}catch{}if(!(!g&&p.has(u.branch)))try{await ge(c.dir,u.key,{done:!0,force:!0,landed:g,text:g?`${u.branch} is merged into ${h} on ${k}, so this card is Done.`:`${u.branch} is gone from ${k}, so this card is Done.`})}catch(b){m(`pushed ${u.key}: ${b.message}`)}}}a(Ae,"settlePushed");const V=Ce({waiting:a(()=>fe.waiting("tailscale"),"waiting")}),er=a((...c)=>V.start(...c),"startBuild"),rr=a((...c)=>V.tick(...c),"tryTick"),sr=a((...c)=>V.pull(...c),"pullNow"),ir=a((...c)=>V.pullMains(...c),"pullMains"),nr=a(()=>V.stop(),"stopTries"),or=a(()=>V.endUnshared(),"endUnsharedTries"),ar=a((...c)=>V.initialize(...c),"initializeTries");export{E as BuildError,Y as TRY_PORTS,Ce as createTryManager,or as endUnsharedTries,tr as fetchTryBranches,ar as initializeTries,Jt as installWorktree,yt as isTryPort,Pe as pullBranch,ir as pullMains,sr as pullNow,er as startBuild,nr as stopTries,rr as tryTick};
|
package/hub/upgrade.mjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
var
|
|
1
|
+
var y=Object.defineProperty;var e=(n,s)=>y(n,"name",{value:s,configurable:!0});import p from"node:fs";import u from"node:path";const S="https://registry.npmjs.org/@lovek4d%2Fkickflip/latest",v=60*6e4,m=e(n=>String(n).split("-")[0].split(".").map(Number),"parts");function g(n,s){const[a,i]=[m(n),m(s)];for(let r=0;r<3;r++)if((a[r]||0)!==(i[r]||0))return(a[r]||0)>(i[r]||0);return!1}e(g,"newer");function _({root:n,fetch:s=globalThis.fetch,run:a,log:i=e(()=>{},"log"),pulled:r=e(()=>{},"pulled")}){const k=p.existsSync(u.join(n,".git")),l=e(()=>JSON.parse(p.readFileSync(u.join(n,"package.json"),"utf8")).version,"installed"),d=l();let c=!1;async function h(){if(k)return;let o;try{const t=await s(S,{signal:AbortSignal.timeout(15e3)});if(!t.ok)throw new Error(`npm answered ${t.status}`);o=(await t.json()).version}catch(t){i(`kickflip update check failed: ${t.message}`);return}g(o,l())&&w(o)}e(h,"check");function w(o){c||(c=!0,a().then(({ok:t})=>{if(!t)return;const f=l();g(f,d)?r():i(`update left v${f}; npm has ${o}`)},t=>i(`kickflip update failed: ${t.message}`)).finally(()=>{c=!1}))}return e(w,"pull"),{check:h}}e(_,"upgrader");export{v as CHECK_MS,S as LATEST_URL,g as newer,_ as upgrader};
|
package/hub/usage.mjs
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
var
|
|
2
|
-
`);
|
|
3
|
-
`),
|
|
1
|
+
var k=Object.defineProperty;var u=(e,t)=>k(e,"name",{value:t,configurable:!0});import d from"node:fs";import w from"node:os";import f from"node:path";import{audit as v,hubAudit as D}from"../shared/audit.mjs";import{FOREVER as M}from"./duration.mjs";const b=6,L=12,$=180,I={five_hour:5*36e5,seven_day:7*864e5},S=u(e=>{try{return JSON.parse(d.readFileSync(e,"utf8"))}catch{return null}},"readJson"),_=u(e=>e==null||e===""||!Number.isFinite(Number(e))?null:Number(e),"num"),y=u(e=>{if(e==null||e==="")return null;const n=typeof e=="number"||/^\d+(\.\d+)?$/.test(String(e))?Number(e)>1e12?Number(e):Number(e)*1e3:Date.parse(String(e));return Number.isFinite(new Date(n).getTime())?new Date(n).toISOString():null},"isoOf"),h=u((e,t)=>({used_percentage:_(e),resets_at:y(t)}),"win");function x(e,t,n){let s;try{s=d.openSync(e,"r");const r=d.fstatSync(s).size;for(const o of[256*1024,2*1024*1024]){const a=Math.max(0,r-o),c=Buffer.alloc(r-a);d.readSync(s,c,0,c.length,a);const m=c.toString("utf8").split(`
|
|
2
|
+
`);a>0&&m.shift();for(let i=m.length-1;i>=0;i--)if(m[i].includes(t))try{const l=n(JSON.parse(m[i]));if(l)return l}catch{}if(a===0)break}}catch{}finally{if(s!==void 0)try{d.closeSync(s)}catch{}}return null}u(x,"lastMatch");const z=u(e=>x(e,"rate_limits",t=>{const n=t?.payload?.rate_limits??t?.rate_limits;return n?.primary?{rl:n}:null}),"lastRateLimits"),O=u(e=>x(e,'"rate_limit_event"',t=>t?.type==="rate_limit_event"&&t.rate_limit_info?t.rate_limit_info:null),"lastRateLimitEvent");function A(e){const t=u(n=>{const s=e.unifiedWindows?.[n];if(!s||_(s.utilization)==null)return null;const r=s.status==="rejected"||e.status==="rejected"&&e.rateLimitType===n;return h(r?100:Math.round(_(s.utilization)*1e3)/10,s.resetsAt)},"window");return{five_hour:t("five_hour"),seven_day:t("seven_day")}}u(A,"eventWindows");function q(e,t=Date.now()){const n=O(e);if(n?.status!=="rejected")return null;const s=Object.values(n.unifiedWindows||{}).find(o=>o?.status==="rejected"),r=y(s?.resetsAt??n.resetsAt);return r&&Date.parse(r)>t?r:null}u(q,"runLimit");const R=/-(\d{4}-\d\d-\d\dT\d\d)-(\d\d)-(\d\d)-(\d{3})Z\.log$/;function E(e,t){let n;try{n=d.readdirSync(e)}catch{return[]}const s=[];for(const r of n){const o=r.match(R);if(o)try{const a=d.statSync(f.join(e,r));s.push({file:f.join(e,r),started:`${o[1]}:${o[2]}:${o[3]}.${o[4]}Z`,mtime:a.mtimeMs,size:a.size})}catch{}}return s.sort((r,o)=>o.mtime-r.mtime).slice(0,L).map(r=>{const o=`${r.size}:${r.mtime}`;let a=t.get(r.file);if(a?.key!==o){const c=r.size?O(r.file):null;a={key:o,claude:r.size>0&&F(r.file)==="{",windows:c?A(c):null},t.set(r.file,a)}return{...r,claude:a.claude,windows:a.windows}})}u(E,"runLogs");function F(e){let t;try{t=d.openSync(e,"r");const n=Buffer.alloc(1);return d.readSync(t,n,0,1,0),n.toString()}catch{return null}finally{if(t!==void 0)try{d.closeSync(t)}catch{}}}u(F,"firstByte");function C(e,t){const n=t.filter(o=>o.windows).map(o=>({at:new Date(o.mtime).toISOString(),...o.windows}));if(e&&typeof e=="object"&&!Array.isArray(e)&&n.push(e),!n.length)return null;const s=u(o=>{const a=n.map(i=>i[o]&&h(i[o].used_percentage,i[o].resets_at)).filter(i=>i?.used_percentage!=null),c=Math.max(...a.map(i=>i.resets_at?Date.parse(i.resets_at):-1/0));return a.filter(i=>i.resets_at?Math.abs(Date.parse(i.resets_at)-c)<=$*1e3:c===-1/0).reduce((i,l)=>!i||l.used_percentage>i.used_percentage?l:i,null)},"window");return{at:n.map(o=>y(o.at)).filter(Boolean).sort().at(-1)??null,five_hour:s("five_hour"),seven_day:s("seven_day")}}u(C,"claudeReading");function T(e,t){const n=e.filter(s=>s.claude).sort((s,r)=>r.started.localeCompare(s.started))[0];return n?{at:n.started,reported:!!n.windows||!!(t&&Date.parse(t)>Date.parse(n.started))}:null}u(T,"newestStart");function W(e){const t=[],n=u(s=>{try{return d.readdirSync(s).sort().reverse()}catch{return[]}},"kids");e:for(const s of n(e))for(const r of n(f.join(e,s)))for(const o of n(f.join(e,s,r))){const a=f.join(e,s,r,o);for(const c of n(a))if(/^rollout-.*\.jsonl$/.test(c))try{t.push({file:f.join(a,c),mtime:d.statSync(f.join(a,c)).mtimeMs})}catch{}if(t.length>=b*2)break e}return t.sort((s,r)=>r.mtime-s.mtime).slice(0,b)}u(W,"newestRollouts");function B(e){const t=[];for(const{file:i,mtime:l}of W(e)){const p=z(i);p&&t.push({...p,mtime:l,resets:_(p.rl.primary.resets_at)})}if(!t.length)return null;const n=Math.max(...t.map(i=>i.resets??-1/0)),s=t.filter(i=>i.resets!=null&&Math.abs(i.resets-n)<=$),r=u(i=>s.reduce((l,p)=>(_(p.rl[i]?.used_percent)??-1)>(_(l?.rl[i]?.used_percent)??-1)?p:l,null),"pick");if(!s.length)return null;const o=r("primary"),a=r("secondary"),c=s.reduce((i,l)=>l.mtime>i.mtime?l:i),m=u((i,l)=>i?h(i.rl[l].used_percent,i.rl[l].resets_at):null,"w");return{five_hour:m(o,"primary"),seven_day:m(a,"secondary"),at:new Date(c.mtime).toISOString()}}u(B,"scanCodex");function N(e,t){if(!e||typeof e!="object"||Array.isArray(e))return null;const n=y(e.at),s=u(r=>{const o=e[r];if(!o)return null;const a=h(o.used_percentage,o.resets_at);if(a.used_percentage==null)return null;const c=!n||t-Date.parse(n)>I[r]?"stale":a.resets_at&&t>=Date.parse(a.resets_at)?"reset":"live";return{...a,used_percentage:c==="reset"?0:a.used_percentage,state:c}},"window");return{at:n,five_hour:s("five_hour"),seven_day:s("seven_day")}}u(N,"readingOf");function H({home:e=w.homedir(),now:t=Date.now,cacheMs:n=6e4}={}){const s=f.join(e,".codex","sessions"),r=f.join(e,".claude","kickflip","logs"),o=new Map;let a=null,c=-1/0;return({codexShare:m="off"}={})=>{const i=t(),l=S(f.join(e,".claude","kickflip","usage.json"))?.claude,p=E(r,o),g=N(C(l,p),i),j=T(p,y(l?.at));return m==="off"?{claude:g,codex:null,start:j}:(i-c>=n&&(a=B(s),c=i),{claude:g,codex:N(a||{at:null},i),start:j})}}u(H,"usageReader");const J=95,K=u((e,t)=>e==="deaf"&&t?.percent>=J,"contextFull");function Q(e,{home:t=w.homedir()}={}){if(typeof e!="string"||!/^[\w-]+$/.test(e))return null;const n=S(f.join(t,".claude","kickflip","context",`${e}.json`));return _(n?.percent)==null||!n.at?null:{percent:_(n.percent),at:n.at}}u(Q,"contextReading");async function Y({gate:e,home:t=w.homedir(),resume:n}){const s=f.join(t,".claude","kickflip","usage-gate-state.json"),r=S(s);if(e.state==="paused"&&r?.state!=="paused"){const a=e.manual?`The owner paused the hub ${e.resets_at===M?"until they resume it":`until ${e.resets_at}`}.`:`Claude's ${e.window==="five_hour"?"5-hour":"weekly"} usage reached the ${e.pause_at}% line.`;v(D(t),{call:"usage-pause",result:"paused",reason:a,inputs:{gate:e,previous:r?.state??null}})}if(r?.state==="paused"&&e.state==="normal"){const a=r.manual?"The owner's pause of the hub ended, so every board was told.":`Claude's ${r.window==="five_hour"?"5-hour":"weekly"} usage is under the line again, so every board was told.`;v(D(t),{call:"usage-resume",result:"resumed",reason:a,inputs:{gate:e,previous:r.manual?"manual":r.window}}),await n(r.manual?"manual":r.window)}d.mkdirSync(f.dirname(s),{recursive:!0});const o=`${s}.${process.pid}.tmp`;d.writeFileSync(o,JSON.stringify(e)+`
|
|
3
|
+
`),d.renameSync(o,s)}u(Y,"checkUsagePause");export{J as CONTEXT_FULL,Y as checkUsagePause,K as contextFull,Q as contextReading,q as runLimit,H as usageReader};
|
package/package.json
CHANGED
package/shared/card-prs.mjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
var i=Object.defineProperty;var
|
|
1
|
+
var i=Object.defineProperty;var n=(e,r)=>i(e,"name",{value:r,configurable:!0});const s=/^(?:([\w.-]+\/[\w.-]+)#)?([1-9]\d*)$/,o=n(e=>Array.isArray(e?.prs)?e.prs:Number.isInteger(e?.pr)?[{number:e.pr}]:[],"storedPRs"),l=n((e,r)=>o(e).map(t=>({repo:t.repo||r,number:t.number,...t.merged_at?{merged_at:t.merged_at}:{},...t.kept?{kept:t.kept}:{}})).filter(t=>t.repo),"cardPRs"),m=n(e=>o(e).length>0,"hasPR"),u=n(({repo:e,number:r})=>`${e}#${r}`,"prKeyOf"),d=n(({repo:e,number:r})=>`https://github.com/${e}/pull/${r}`,"prURL"),x=n((e,r)=>e.map(t=>t.repo&&t.repo!==r?u(t):`#${t.number}`).join(", "),"prNames");function g(e){const r=s.exec(String(e).trim());return r?{...r[1]?{repo:r[1]}:{},number:Number(r[2])}:null}n(g,"parsePRRef");const a=["MERGED","CLOSED"],c=n(e=>e.checks?.state==="fail"||!!e.failing?.length,"failing");function p(e){return e?.state?a.includes(e.state)?5:e.isDraft?1:c(e)?2:e.reviewDecision==="APPROVED"?4:3:0}n(p,"readiness");function R(e){if(!e.length)return-1;const r=e.map(p);return r.every(t=>t===5)?Math.max(0,e.findIndex(t=>t.state==="MERGED")):r.indexOf(Math.min(...r))}n(R,"leastReady");const h=n(e=>e.length>0&&e.every(r=>a.includes(r?.state))&&e.some(r=>r.state==="MERGED"),"allLanded");export{h as allLanded,l as cardPRs,m as hasPR,R as leastReady,g as parsePRRef,u as prKeyOf,x as prNames,d as prURL,o as storedPRs};
|
package/shared/machine-gate.mjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
var k=Object.defineProperty;var s=(e,t)=>k(e,"name",{value:t,configurable:!0});import i from"node:fs";import c from"node:os";import _ from"node:path";import{execFileSync as
|
|
2
|
-
`),i.renameSync(x,m),$}s(z,"checkMachine");export{
|
|
1
|
+
var k=Object.defineProperty;var s=(e,t)=>k(e,"name",{value:t,configurable:!0});import i from"node:fs";import c from"node:os";import _ from"node:path";import{execFileSync as v}from"node:child_process";import{audit as d,hubAudit as y}from"./audit.mjs";const h=4,f=3,A=10*6e4,M=Object.freeze({state:"normal",reason:null}),D={1:"normal",2:"warn",4:"critical"},F=s(e=>D[e]??null,"pressureOf");function N(){let e=null;try{e=Number(v("sysctl",["-n","kern.memorystatus_vm_pressure_level"],{encoding:"utf8",timeout:2e3,stdio:["ignore","pipe","ignore"]}).trim())}catch{}return{load:c.loadavg()[0],cores:c.cpus().length,pressure:F(e)}}s(N,"readMachine");function R(e,{load:t,cores:r,pressure:o}){const a=(e==="busy"?f:h)*r;return o==="critical"||t>=a?"busy":"normal"}s(R,"machineState");const b=s((e=c.homedir())=>_.join(e,".claude","kickflip","machine-state.json"),"machineFile"),g=s(e=>{try{return JSON.parse(i.readFileSync(b(e),"utf8"))}catch{return null}},"readFile");function T(e=c.homedir()){const t=g(e);return t?.state==="busy"?{state:"busy",reason:t.reason}:M}s(T,"readMachineState");function z({home:e=c.homedir(),reading:t=N(),now:r=new Date}={}){const o=g(e),a=o?.state??"normal",n=R(a,t),O=n==="busy"?t.pressure==="critical"?"memory pressure critical":"load high":null,l={reading:t,previous:o?.state??null,hold:h,release:f},u=`1-minute load ${t.load.toFixed(1)}`,p=n!==a,S=!o?.logged_at||r.getTime()-Date.parse(o.logged_at)>=A;if(p&&n==="busy"){const E=t.pressure==="critical"?"memory pressure is critical":`${u} is at least ${h}\xD7 its ${t.cores} cores`;d(y(e),{call:"machine-busy",result:n,reason:`The host is busy: ${E}, so no new work starts.`,inputs:l},{now:r})}else p?d(y(e),{call:"machine-eased",result:n,reason:`Memory pressure is ${t.pressure??"unread"} and ${u} is under ${f}\xD7 its ${t.cores} cores, so new work starts again.`,inputs:l},{now:r}):S&&d(y(e),{call:"machine-reading",result:n,reason:`Memory pressure is ${t.pressure??"unread"} and ${u} on ${t.cores} cores.`,inputs:l},{now:r});const $={state:n,reason:O,at:r.toISOString(),reading:t,logged_at:p||S?r.toISOString():o.logged_at},m=b(e);i.mkdirSync(_.dirname(m),{recursive:!0});const x=`${m}.${process.pid}.tmp`;return i.writeFileSync(x,JSON.stringify($)+`
|
|
2
|
+
`),i.renameSync(x,m),$}s(z,"checkMachine");export{h as HOLD_LOAD,M as MACHINE_NORMAL,f as RELEASE_LOAD,z as checkMachine,b as machineFile,R as machineState,F as pressureOf,N as readMachine,T as readMachineState};
|
package/shared/tailscale.mjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
var p=Object.defineProperty;var
|
|
1
|
+
var p=Object.defineProperty;var a=(t,s)=>p(t,"name",{value:s,configurable:!0});import i from"node:fs";import u from"node:os";import o from"node:path";import{spawnSync as f}from"node:child_process";const d=o.join("Tailscale.app","Contents","MacOS","Tailscale"),m=a(t=>{try{return i.statSync(t).isFile()&&(i.accessSync(t,i.constants.X_OK),!0)}catch{return!1}},"isExecutable");function l({env:t=process.env,platform:s=process.platform,apps:n=["/Applications",o.join(u.homedir(),"Applications")]}={}){if(t.KICKFLIP_TAILSCALE)return[t.KICKFLIP_TAILSCALE];const r=(t.PATH||"").split(o.delimiter).filter(Boolean).map(e=>o.join(e,"tailscale"));return s==="darwin"&&r.push(...n.map(e=>o.join(e,d))),[...new Set(r)].filter(m)}a(l,"tailscaleCandidates");function S(t){const s=f(t,["status","--json"],{encoding:"utf8",timeout:3e3,stdio:["ignore","pipe","ignore"]});try{return JSON.parse(s.stdout).BackendState==="Running"}catch{return!1}}a(S,"daemonRunning");function L({running:t=S,...s}={}){const n=l(s);return n.length>1?n.find(t)??n[0]:n[0]??null}a(L,"tailscaleBin");function w(t,s={}){const n=l(s);return n.includes(t)?n.slice(0,n.indexOf(t)):[]}a(w,"tailscaleSkipped");const c=a((t=process.platform)=>t==="darwin"?"brew install --cask tailscale-app":"curl -fsSL https://tailscale.com/install.sh | sh","tailscaleInstallCommand"),g=a((t=process.platform)=>t==="darwin"?`install Tailscale from https://tailscale.com/download, or ${c(t)}`:c(t),"tailscaleInstallHint");export{S as daemonRunning,L as tailscaleBin,l as tailscaleCandidates,c as tailscaleInstallCommand,g as tailscaleInstallHint,w as tailscaleSkipped};
|
package/shared/usage-gate.mjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
var
|
|
1
|
+
var v=Object.defineProperty;var n=(e,s)=>v(e,"name",{value:s,configurable:!0});import B from"node:fs";import x from"node:os";import I from"node:path";import{usageReader as U}from"../hub/usage.mjs";import{readSettingsFile as _,writeSettingsFile as $,readCodexShare as G,readClaudeModel as L}from"../hub/push.mjs";import{FOREVER as N,durationEnd as T,storedEnd as z}from"../hub/duration.mjs";import{codexStatus as J,codexStatusReader as K,effectiveCodexShare as V}from"./codex.mjs";import{MACHINE_NORMAL as q,readMachineState as Q}from"./machine-gate.mjs";const X=n(e=>Number.isInteger(e)&&e>=50&&e<=100&&e%5===0,"validPauseAt"),c=n(e=>Number(e),"timeOf"),R=["five_hour","seven_day"],b=n((e,s)=>e===N||typeof e=="string"&&Date.parse(e)>c(s)?e:null,"pausedUntil");function Y(e,s=new Date){if(e==="off")return null;const o=T(new Date(c(s)),e);if(!o)throw Object.assign(new Error("A pause ends after now and within 30 days: <n>m, <n>h, <n>d, <n>w, forever or a date and time; or off."),{status:400});return z(o)}n(Y,"pauseEnd");const Z=n(e=>e===N?"until the owner resumes it":`until ${e}`,"pauseUntilWords");function ee({claude:e,codex:s,pauseAt:o=90,keepGoing:t,hubPause:a,boardPause:r,machine:u=q,start:w=null,now:i=Date.now()}={}){const p=X(o)?o:90,D=n(l=>p===100?null:R.find(d=>{const f=l?.[d];return f?.state==="live"&&Number.isFinite(f.used_percentage)&&f.used_percentage>=p&&(!f.resets_at||Date.parse(f.resets_at)>c(i))})||null,"past"),k=n((l,d)=>({window:d,used:d?l[d].used_percentage:null,resets_at:d?l[d].resets_at:null}),"details"),m=D(e),A=k(e,m),C=n((l,d,f)=>l?.window===d&&Math.abs(Date.parse(l.until)-Date.parse(f))<=3e5&&c(i)<Date.parse(l.until),"covers"),j=m&&!t?.near&&C(t,m,A.resets_at),h=m?null:te(e,p,i);h&&(h.going=!!t?.near&&C(t,h.window,h.resets_at),h.waiting=!h.going&&!!w&&!w.reported&&c(i)-Date.parse(w.at)<se);const M=D(s),E={past:!!M,...k(s,M)},g=b(a,i)?"hub":b(r,i)?"board":null;return g?{pause_at:p,state:"paused",manual:g,window:null,used:null,resets_at:b(g==="hub"?a:r,i),near:null,codex:E,machine:u}:{pause_at:p,state:m?j?"going":"paused":"normal",manual:null,...A,near:h,codex:E,machine:u}}n(ee,"usageGate");const se=10*6e4,y=n(e=>2*e-100,"nearFrom");function te(e,s,o){if(s===100)return null;const t=R.find(a=>{const r=e?.[a];return r?.state==="live"&&Number.isFinite(r.used_percentage)&&r.used_percentage>=y(s)&&(!r.resets_at||Date.parse(r.resets_at)>c(o))});return t?{window:t,used:e[t].used_percentage,resets_at:e[t].resets_at,from:y(s)}:null}n(te,"nearOf");const ne=n(e=>{try{return JSON.parse(B.readFileSync(I.join(e,"board.json"),"utf8"))||{}}catch{return{}}},"readBoardJson"),ae=n((e,s=Date.now())=>b(ne(e).paused_until,s),"boardPausedUntil"),O=new Map;function W({home:e=x.homedir(),now:s=Date.now(),dir:o=null}={}){let t=O.get(e);t||(t={now:c(s)},t.usage=U({home:e,now:n(()=>t.now,"now")}),t.codex=K({home:e,now:n(()=>t.now,"now")}),O.set(e,t)),t.now=c(s);const a=_(e);return ee({...t.usage({codexShare:G(t.codex(),e)}),pauseAt:a.pauseAt,keepGoing:a.keepGoing,hubPause:a.paused_until,boardPause:o?ae(o,s):null,machine:Q(e),now:s})}n(W,"readUsageGate");function be(e,{home:s=x.homedir(),now:o=new Date}={}){const t=Y(e,o),{paused_until:a,...r}=_(s);return $(t?{...r,paused_until:t}:r,s),t}n(be,"setHubPause");function ge(e,{home:s=x.homedir()}={}){if(!/^(?:50|55|60|65|70|75|80|85|90|95|100)$/.test(String(e)))throw Object.assign(new Error("pause-at takes 50..100 in steps of 5"),{status:400});$({..._(s),pauseAt:Number(e)},s)}n(ge,"setPauseAt");function $e(e,{home:s=x.homedir(),now:o=Date.now()}={}){if(!["on","off"].includes(e))throw Object.assign(new Error("keep-going takes on or off"),{status:400});const t=_(s);if(e==="on"){const a=W({home:s,now:o});if(a.state==="paused")t.keepGoing={window:a.window,until:a.resets_at};else if(a.near)t.keepGoing={window:a.near.window,until:a.near.resets_at,near:!0};else throw Object.assign(new Error("Nothing is paused."),{status:409})}else delete t.keepGoing;$(t,s)}n($e,"setKeepGoing");const Se=n(e=>{try{return["github-change","check-github","card-unblocked","overnight-triage","pr-review","owner-check-broken"].includes(JSON.parse(e).kind)}catch{return!1}},"heldUsageEvent");function De({home:e=x.homedir(),now:s=Date.now(),card:o={}}={}){const t=J({home:e}),a={codex:t.reason??"usable",claudeModel:L(e),size:o.size??null,cardModel:o.model??null},r=n(w=>{const{model:i,reason:p}=oe(a.claudeModel,o);return{kind:"claude",model:i,reason:`${w} ${p}`,inputs:a}},"claude");if(!t.usable)return r(t.words);if(a.codexShare=V(_(e).codexShare,t),a.codexShare==="off")return r("Codex share is off.");const u=W({home:e,now:s});return Object.assign(a,{pauseAt:u.pause_at,codexUsage:u.codex}),u.codex.past?r(`Codex is at ${Math.round(u.codex.used)}%, past the ${u.pause_at}% line until ${u.codex.resets_at}.`):{kind:"codex",reason:`Codex share is ${a.codexShare} and Codex is under the ${u.pause_at}% line.`,inputs:a}}n(De,"builderPick");const F={opus:"Opus",sonnet:"Sonnet"};function oe(e,s){return e!=="auto"?{model:e,reason:`Claude model is ${F[e]}.`}:s.model?{model:s.model,reason:`Claude model is Auto and the card asks for Claude ${F[s.model]}.`}:s.size==="small"?{model:"sonnet",reason:"Claude model is Auto and the card is small."}:{model:"opus",reason:"Claude model is Auto and the card isn't small."}}n(oe,"claudePick");const H={hub:["the hub","Pause hub in Hub settings"],board:["this board","Pause board in Board settings"]},re=n(e=>e.manual?`Work is paused: the owner paused ${H[e.manual][0]} ${Z(e.resets_at)}. ${H[e.manual][1]} resumes it.`:e.state==="paused"?ie(e):e.machine.state==="busy"||!e.near?.waiting?P(e):ue(e),"pauseReason"),ue=n(e=>`Work is held: Claude's ${S(e.near.window)} usage is at ${Math.round(e.near.used)}%, near the ${e.pause_at}% line, so agents start one at a time, each once the last one has reported its usage. The owner can tap Keep going on the Hub page.`,"nearReason"),P=n(e=>`Work is held: the host is busy (${e.machine.reason}). It starts again when the host eases.`,"busyReason"),ke="Held: the host is busy. It starts again when the host eases.",Ae=n(e=>e.state==="paused"||e.machine.state==="busy","holdsWork"),Ce=n(e=>`usage paused ${e.resets_at} ${e.manual?`manual ${e.manual}`:`${e.window} ${e.used}%`}`,"pausedLine"),ie=n(e=>`Work is paused: Claude's ${S(e.window)} usage is at ${Math.round(e.used)}%. Resumes at ${e.resets_at}. The owner can tap Keep going on the Hub page.`,"usagePauseReason"),de=n((e,s)=>!!e.manual||e.machine.state==="busy"||(s==="codex"?e.codex.past:e.state==="paused"),"heldFor"),Me=n((e,s)=>de(e,s)||s!=="codex"&&!!e.near?.waiting,"launchHeld"),S=n(e=>e==="five_hour"?"5-hour":"weekly","windowWord"),Ee=n((e,s)=>s==="codex"&&!e.manual&&e.codex.past?`Codex is paused: its ${S(e.codex.window)} usage is at ${Math.round(e.codex.used)}%, past the ${e.pause_at}% line. Resumes at ${e.codex.resets_at}.`:s==="codex"&&!e.manual?P(e):re(e),"pauseReasonFor");export{ke as MACHINE_HELD_LINE,ae as boardPausedUntil,De as builderPick,de as heldFor,Se as heldUsageEvent,Ae as holdsWork,Me as launchHeld,Y as pauseEnd,re as pauseReason,Ee as pauseReasonFor,Z as pauseUntilWords,Ce as pausedLine,W as readUsageGate,be as setHubPause,$e as setKeepGoing,ge as setPauseAt,ee as usageGate,X as validPauseAt};
|