@lovek4d/kickflip 0.6.1 → 0.7.1

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 (76) hide show
  1. package/CONTEXT.md +60 -48
  2. package/SKILL.md +20 -14
  3. package/bin/event-lines.mjs +1 -0
  4. package/bin/kickflip.mjs +8 -8
  5. package/bin/wait-events.mjs +2 -2
  6. package/board/agents.mjs +2 -2
  7. package/board/arch-review.mjs +4 -4
  8. package/board/bin/board.mjs +118 -104
  9. package/board/domain.mjs +7 -7
  10. package/board/finish.mjs +4 -4
  11. package/board/headless.mjs +8 -9
  12. package/board/hub-lines.mjs +1 -0
  13. package/board/onboarding.mjs +2 -2
  14. package/board/prototype-page.mjs +1 -1
  15. package/board/public/app-arch.js +3 -3
  16. package/board/public/app-banner.js +1 -1
  17. package/board/public/app-cards.js +17 -9
  18. package/board/public/app-header.js +1 -1
  19. package/board/public/app-lanes.js +39 -61
  20. package/board/public/app-needs.js +116 -30
  21. package/board/public/app-reader.js +54 -44
  22. package/board/public/app-threads.js +2 -2
  23. package/board/public/app.html +1 -1
  24. package/board/public/app.js +23 -13
  25. package/board/public/dialogs.js +1 -1
  26. package/board/public/hub-kit.js +1 -1
  27. package/board/public/hub.css +2 -0
  28. package/board/public/linear-key.js +1 -1
  29. package/board/public/new-task.js +1 -1
  30. package/board/public/progress-review.js +4 -4
  31. package/board/public/prototype-kit.js +27 -16
  32. package/board/public/prototype-switcher.js +47 -32
  33. package/board/public/prototype.html +7 -4
  34. package/board/public/router.js +35 -7
  35. package/board/public/shell.js +19 -13
  36. package/board/public/sidebar.css +4 -4
  37. package/board/public/sidebar.js +2 -2
  38. package/board/public/style.css +74 -36
  39. package/board/public/ui-cards.js +56 -37
  40. package/board/public/ui-enums.js +10 -1
  41. package/board/public/ui-hub.js +1 -5
  42. package/board/public/ui-links.js +6 -6
  43. package/board/public/ui-order.js +8 -6
  44. package/board/public/ui-progress-bar.js +6 -7
  45. package/board/public/ui-questions.js +30 -13
  46. package/board/public/ui-sidebar.js +3 -2
  47. package/board/public/ui-threads.js +20 -19
  48. package/board/public/ui.js +15 -4
  49. package/board/public/view-hub-settings.js +28 -31
  50. package/board/public/view-hub.js +7 -9
  51. package/board/public/view-notes.js +2 -2
  52. package/board/public/view-questions.js +35 -6
  53. package/board/public/view-settings.js +63 -31
  54. package/board/render-check.mjs +10 -10
  55. package/board/review.mjs +1 -1
  56. package/board/server.mjs +18 -20
  57. package/board/stop.mjs +1 -1
  58. package/board/store.mjs +26 -25
  59. package/board/tickets.mjs +1 -1
  60. package/board/transcript.mjs +4 -2
  61. package/hooks-plugin/hooks/board-data-gate.mjs +6 -6
  62. package/hub/awake.mjs +1 -1
  63. package/hub/boards.mjs +4 -4
  64. package/hub/github.mjs +3 -3
  65. package/hub/home.mjs +1 -1
  66. package/hub/hub.mjs +11 -11
  67. package/hub/incoming.mjs +2 -2
  68. package/hub/locks.mjs +1 -1
  69. package/hub/new-board.mjs +2 -2
  70. package/hub/owner-checks.mjs +1 -1
  71. package/hub/try.mjs +6 -7
  72. package/hub/upgrade.mjs +1 -1
  73. package/hub/views.mjs +1 -1
  74. package/package.json +1 -1
  75. package/shared/tailscale.mjs +1 -1
  76. package/shared/usage-gate.mjs +1 -1
package/hub/views.mjs CHANGED
@@ -1 +1 @@
1
- var F=Object.defineProperty;var s=(e,t)=>F(e,"name",{value:t,configurable:!0});import{clock as d,holdLine as S,sendWords as V,snoozeEnd as y}from"./schedule.mjs";import{CHOICES as H,FOREVER as $,durationEnd as W,endOf as M}from"./duration.mjs";import{siteIcon as B}from"./icons.mjs";import{codexStatus as G}from"../shared/codex.mjs";const Z=s(e=>e.charAt(0).toUpperCase()+e.slice(1),"capital"),O=s((e,t)=>`${e} ${t}${e===1?"":"s"}`,"plural"),p=s(e=>{const[t,n]=e.split(":").map(Number),o=new Date;return o.setHours(t,n,0,0),d(o,o)},"timeOfDay"),j=s(e=>`checks every ${e.every===1?"minute":`${e.every} min`} while you\u2019re ${e.present?"here":"away"}${e.low?", slower while GitHub\u2019s points are low":""}`,"cadenceLine"),J=s((e,t,n)=>`${j(t)}${e.hold?`; ${S(e.hold,n)}`:""}`,"nowLine"),C=s((e,t)=>e.hold?S(e.hold,t):"pushes go out after each check","deviceLine"),Q=s(e=>`${p(e.quiet.start)} \u2013 ${p(e.quiet.end)}${e.quiet.hold?" \xB7 pushes held":""}`,"quietSummary"),X="checks every minute while you're here, every 5 when away",Y=s((e,t)=>e.count?`${e.count} push${e.count===1?"":"es"} queued; next send ${V(e.nextFlushAt,t)}.`:"No pushes queued.","queuedLine");function ee({running:e=!1,nextAt:t,last:n=null},o){if(e)return"Checking GitHub now";const a=`Next check ${d(t,o)}`;if(!n)return a;const u=n.failed.length?"could not reach GitHub for every PR":n.watched?`${O(n.watched,"PR")}, ${n.changes.length?`${n.changes.length} changed`:"no change"}`:"no PRs watched";return`${a} \xB7 last ${d(new Date(n.at),o)}, ${u}`}s(ee,"pollLine");function te(e,t,n){return{title:Z(J(e,t,n)),lines:[ee(t,n),Y(e,n)],count:e.count}}s(te,"pollingNow");function ne(e,t){const n={port:"down \xB7 not answering",files:"down \xB7 folder missing"}[e.serves]||"down \xB7 restarting";if(e.state!=="up")return{tone:e.state==="down"?"down":"wait",word:e.state==="down"?n:e.state,lines:[]};if(!t)return{tone:"ok",word:"up",lines:[]};const o=t.filter(r=>!r.ok),a=o.filter(r=>r.state==="broken"),u=t.find(r=>r.link==="events"),l=u?.listen==="asleep"||u?.listen==="waking"?u.listen:u?.ok?"listening":"not listening",c=a.length?`degraded \xB7 ${l}`:o.length?`up \xB7 ${l} \xB7 ${o.length} settling`:`up \xB7 ${l} \xB7 every link passes`;return{tone:o.length?"wait":"ok",word:c,lines:o.map(r=>({link:r.link,detail:`${r.state==="broken"?"":"settling: "}${r.detail}`}))}}s(ne,"siteHealth");const R=[{value:"always",label:"Always on",description:s(()=>"Codex implements and reviews. Work pauses when it runs out.","description")},{value:"auto",label:"Auto",description:s(e=>`Codex implements and reviews, and ${e} takes over while it's out.`,"description")},{value:"off",label:"Off",description:s(e=>`${e} implements and reviews.`,"description")}],oe=s((e,t,n)=>({usable:t.usable,tag:t.usable?null:t.reason,share:e,choices:R.map(({value:o,label:a})=>({value:o,label:a,on:o===e})),description:t.usable?R.find(o=>o.value===e).description(_[n]):t.words,fix:t.fix}),"codexView"),_={opus:"Opus",sonnet:"Sonnet",auto:"Claude"},P=[{value:"opus",label:"Opus",description:"Always Opus. Auto would give small cards to Sonnet."},{value:"auto",label:"Auto",description:"Auto: small cards get Sonnet, the rest get Opus."},{value:"sonnet",label:"Sonnet",description:"Always Sonnet. Auto would give large cards to Opus."}],se=s(e=>({model:e,builder:_[e],choices:P.map(({value:t,label:n})=>({value:t,label:n,on:t===e})),description:P.find(t=>t.value===e).description}),"claudeView");function me({now:e,st:t,poll:n,sidebar:o,rows:a,links:u=new Map,code:l={},host:c="",version:r,rules:h=[],remembered:w=new Map,integrations:m=[],codexShare:g="off",claudeModel:A="auto",codex:L=G({installed:!1}),awake:q=null}){const b=new Set(o.sites.filter(i=>i.board).map(i=>i.route)),N=!!(l.running&&l.running===l.onDisk),z=N?` \xB7 runs the code in its files (${l.onDisk})`:"",D=s(i=>({route:i.route,title:i.title,icon:B(i.route,i),href:`/${encodeURIComponent(i.route)}/`,...ne(i,u.get(i.route)),remembered:w.get(i.route)||[],restart:b.has(i.route)&&i.restart===!0}),"site"),U=[["Boards",a.filter(i=>b.has(i.route))],["Pages",a.filter(i=>!b.has(i.route))]].filter(([,i])=>i.length).map(([i,K])=>({label:i,sites:K.map(D)}));return{hub:{tone:o.hub.tone,word:o.hub.word,detail:`${o.hub.detail}${z}`,restart:!N},awake:q&&f(q,e,E),groups:U,integrations:m,usage:o.usage,pause:o.pause,codex:oe(g,L,A),claude:se(A),empty:a.length===0,polling:te(t,n,e),notifications:{on:t.settings.notifications.on,text:o.notifications.text,device:C(t,e)},hold:t.hold&&t.hold.why!=="off"?{text:`Notifications ${S(t.hold,e)}`,end:t.hold.why==="snoozed"}:null,rules:h.length?O(h.length,"rule"):"None yet",sites:O(a.length,"site"),host:c,version:r,total:o.total}}s(me,"hubView");const ge="That snooze ends in the past or more than 30 days on. Pick another time.",ye="That keep awake ends in the past or more than 30 days on. Pick another time.",T="until you end it",E="until you turn it off",I="until you resume",f=s((e,t,n)=>e===$?n:`until ${d(e,t)}`,"untilWords"),v=s((e,t)=>H.map(([n,o])=>({value:n,label:o,until:f(W(e,n),e,t)})),"durationChoices"),ie=s((e,t)=>e.on?M(e.snoozed_until,t):null,"snoozedUntil");function $e({now:e,st:t,rules:n=[],linear:o={set:!1},slack:a={set:!1},awake:u={until:null,unsupported:null},pausedUntil:l=null}){const c=t.settings,r=c.notifications,h=ie(r,e),w=t.count?` The ${t.count} queued push${t.count===1?" is":"es are"} dropped now.`:"",m=h&&`Snoozed ${f(h,e,T)}`,g=l&&`Paused ${f(l,e,I)}`;return{pause:{on:l?{title:g}:null,summary:l===$?"Paused \xB7 forever":g||"Off",choices:l?null:v(e,I)},snoozed:h?{title:m,summary:h===$?"Snoozed \xB7 forever":m}:null,device:C(t,e),on:{on:r.on,sub:r.on?"On. Turn off to drop every push, urgent ones too.":"Off. Every push is dropped, urgent ones too. Needs you and the badge still update.",confirm:`Turn off notifications? Every push is dropped until you turn them back on, urgent ones too.${w} Needs you and the badge still update.`},snooze:r.on&&!h?{choices:v(e,T)}:null,awake:{unsupported:u.unsupported,on:u.until?{title:`Keeping awake ${f(u.until,e,E)}`}:null,choices:u.unsupported||u.until?null:v(e,E),summary:u.unsupported||(u.until?u.until===$?"On \xB7 forever":`On until ${d(u.until,e)}`:"Off")},weekends:{on:r.weekends_off,sub:`Pushes wait from Friday ${p(c.quiet.start)} until Monday ${p(c.quiet.end)}. Urgent ones still go.`},quiet:le(c,e),rules:n,linear:ae(o),slack:re(a)}}s($e,"settingsView");const ue={display_information:{name:"Kickflip"},oauth_config:{scopes:{user:["chat:write","im:write","reactions:write","users:read"]}}},x={url:`https://api.slack.com/apps?new_app=1&manifest_json=${encodeURIComponent(JSON.stringify(ue))}`,label:"a new Slack app"};function re(e){const t=e.team?`${e.team} workspace`:"",n=`Make ${x.label}, install it to your workspace, then paste its User OAuth Token (xoxp-\u2026) here.`;return e.set?e.refused?{set:!0,refused:!0,title:t||"Slack token",down:"doesn\u2019t work",sub:`Slack refused the token. ${n}`,keys:x,summary:t?`Token doesn\u2019t work \xB7 ${t}`:"Token doesn\u2019t work"}:{set:!0,title:t,sub:`Token \u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022 \xB7 sends as ${e.user}`,summary:`Token set \xB7 ${t}`}:{set:!1,title:"No token",sub:n,keys:x,summary:"No token"}}s(re,"slackWords");const k={url:"https://linear.app/settings/account/security",label:"Linear \u203A Settings \u203A Security & access \u203A Personal API keys"};function ae(e){const t=e.workspace?`${e.workspace} workspace`:"";return e.set?e.refused?{set:!0,refused:!0,title:t||"Linear key",down:"doesn\u2019t work",sub:`Linear refused the key. Make a new one in ${k.label} and paste it here.`,keys:k,summary:t?`Key doesn\u2019t work \xB7 ${t}`:"Key doesn\u2019t work"}:{set:!0,title:t,sub:"Key \u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022",summary:`Key set \xB7 ${t}`}:{set:!1,title:"No key",sub:`Make a personal API key in ${k.label}, then paste it here.`,keys:k,summary:"No key"}}s(ae,"linearWords");function le(e,t){const n=e.quiet,[o,a]=[p(n.start),p(n.end)],u=y(t,"tonight",e),l=[["Snooze tonight",`Until ${d(u,t)}${n.hold?`, then quiet hours hold pushes until ${d(y(u,"morning",e),t)}`:""}.`],["Snooze morning",`Until ${d(y(t,"morning",e),t)}.`],["Snooze monday",`Until ${d(y(t,"monday",e),t)}.`],["Off on weekends",e.notifications.weekends_off?`Pushes wait from Friday ${o} until Monday ${a}.`:"Off."],["Daily wake",`Every board wakes once at ${a}, when quiet hours end.`]];return{start:n.start,end:n.end,summary:Q(e),hint:X,hold:{on:n.hold,sub:n.hold?`Pushes queued after ${o} wait until ${a}. Urgent ones still go.`:"Off. Pushes go out all night."},reads:l.map(([c,r])=>({title:c,sub:r}))}}s(le,"quietView");function ke(e){return e?e.ok?{sent:e.sent,count:e.count,ok:!0,message:`Sent ${e.count} queued push${e.count===1?"":"es"}.`}:{sent:e.sent,count:e.count,ok:!1,message:"Could not send the queue. It stays queued for the next check."}:{sent:0,count:0,ok:!0,message:"Nothing was queued."}}s(ke,"flushView");export{ye as AWAKE_REFUSED,X as QUIET_HINT,ge as SNOOZE_REFUSED,j as cadenceLine,Z as capital,v as durationChoices,ke as flushView,me as hubView,J as nowLine,O as plural,ee as pollLine,Q as quietSummary,$e as settingsView,ne as siteHealth};
1
+ var L=Object.defineProperty;var i=(e,t)=>L(e,"name",{value:t,configurable:!0});import{clock as p,holdLine as v,snoozeEnd as k}from"./schedule.mjs";import{CHOICES as V,FOREVER as $,durationEnd as K,endOf as F}from"./duration.mjs";import{siteIcon as M}from"./icons.mjs";import{codexStatus as H}from"../shared/codex.mjs";const ue=i(e=>e.charAt(0).toUpperCase()+e.slice(1),"capital"),E=i((e,t)=>`${e} ${t}${e===1?"":"s"}`,"plural"),f=i(e=>{const[t,n]=e.split(":").map(Number),o=new Date;return o.setHours(t,n,0,0),p(o,o)},"timeOfDay"),x=i((e,t)=>e.hold?v(e.hold,t):"pushes go out after each check","deviceLine"),W=i(e=>`${f(e.quiet.start)} \u2013 ${f(e.quiet.end)}${e.quiet.hold?" \xB7 pushes held":""}`,"quietSummary"),B="checks every minute while you're here, every 5 when away";function Z(e,t){const n={port:"down \xB7 not answering",files:"down \xB7 folder missing"}[e.serves]||"down \xB7 restarting";if(e.state!=="up")return{tone:e.state==="down"?"down":"wait",word:e.state==="down"?n:e.state,lines:[]};if(!t)return{tone:"ok",word:"up",lines:[]};const o=t.filter(u=>!u.ok),l=o.filter(u=>u.state==="broken"),r=t.find(u=>u.link==="events"),a=r?.listen==="asleep"||r?.listen==="waking"?r.listen:r?.ok?"listening":"not listening",d=l.length?`degraded \xB7 ${a}`:o.length?`up \xB7 ${a} \xB7 ${o.length} settling`:`up \xB7 ${a} \xB7 every link passes`;return{tone:o.length?"wait":"ok",word:d,lines:o.map(u=>({link:u.link,detail:`${u.state==="broken"?"":"settling: "}${u.detail}`}))}}i(Z,"siteHealth");const A=[{value:"always",label:"Always on",description:i(()=>"Codex implements and reviews. Work pauses when it runs out.","description")},{value:"auto",label:"Auto",description:i(e=>`Codex implements and reviews, and ${e} takes over while it's out.`,"description")},{value:"off",label:"Off",description:i(e=>`${e} implements and reviews.`,"description")}],j=i((e,t,n)=>({usable:t.usable,tag:t.usable?null:t.reason,share:e,choices:A.map(({value:o,label:l})=>({value:o,label:l,on:o===e})),description:t.usable?A.find(o=>o.value===e).description(q[n]):t.words,fix:t.fix}),"codexView"),q={opus:"Opus",sonnet:"Sonnet",auto:"Claude"},C=[{value:"opus",label:"Opus",description:"Always Opus. Auto would give small cards to Sonnet."},{value:"auto",label:"Auto",description:"Auto: small cards get Sonnet, the rest get Opus."},{value:"sonnet",label:"Sonnet",description:"Always Sonnet. Auto would give large cards to Opus."}],J=i(e=>({model:e,builder:q[e],choices:C.map(({value:t,label:n})=>({value:t,label:n,on:t===e})),description:C.find(t=>t.value===e).description}),"claudeView");function re({now:e,st:t,sidebar:n,rows:o,links:l=new Map,code:r={},host:a="",version:d,rules:u=[],remembered:c=new Map,integrations:g=[],codexShare:m="off",claudeModel:h="auto",codex:T=H({installed:!1}),awake:P=null}){const w=new Set(n.sites.filter(s=>s.board).map(s=>s.route)),O=!!(r.running&&r.running===r.onDisk),z=O?` \xB7 runs the code in its files (${r.onDisk})`:"",R=i(s=>({route:s.route,title:s.title,icon:M(s.route,s),href:`/${encodeURIComponent(s.route)}/`,...Z(s,l.get(s.route)),remembered:c.get(s.route)||[],restart:w.has(s.route)&&s.restart===!0}),"site"),U=[["Boards",o.filter(s=>w.has(s.route))],["Pages",o.filter(s=>!w.has(s.route))]].filter(([,s])=>s.length).map(([s,D])=>({label:s,sites:D.map(R)}));return{hub:{tone:n.hub.tone,word:n.hub.word,detail:`${n.hub.detail}${z}`,restart:!O},awake:!!P,groups:U,integrations:g,usage:n.usage,pause:n.pause,codex:j(m,T,h),claude:J(h),empty:o.length===0,notifications:{on:t.settings.notifications.on,text:n.notifications.text,device:x(t,e)},hold:t.hold&&t.hold.why!=="off"?{text:`Notifications ${v(t.hold,e)}`,end:t.hold.why==="snoozed"}:null,rules:u.length?E(u.length,"rule"):"None yet",sites:E(o.length,"site"),host:a,version:d,total:n.total}}i(re,"hubView");const ae="That snooze ends in the past or more than 30 days on. Pick another time.",N="until you end it",_="until you resume",b=i((e,t,n)=>e===$?n:`until ${p(e,t)}`,"untilWords"),I=i((e,t)=>V.map(([n,o])=>({value:n,label:o,until:b(K(e,n),e,t)})),"durationChoices"),Q=i((e,t)=>e.on?F(e.snoozed_until,t):null,"snoozedUntil");function le({now:e,st:t,rules:n=[],linear:o={set:!1},slack:l={set:!1},awake:r={on:!1,unsupported:null},pausedUntil:a=null}){const d=t.settings,u=d.notifications,c=Q(u,e),g=t.count?` The ${t.count} queued push${t.count===1?" is":"es are"} dropped now.`:"",m=c&&`Snoozed ${b(c,e,N)}`,h=a&&`Paused ${b(a,e,_)}`;return{pause:{on:a?{title:h}:null,summary:a===$?"Paused \xB7 forever":h||"Off",choices:a?null:I(e,_)},snoozed:c?{title:m,summary:c===$?"Snoozed \xB7 forever":m}:null,device:x(t,e),on:{on:u.on,sub:u.on?"On. Turn off to drop every push, urgent ones too.":"Off. Every push is dropped, urgent ones too. Needs you and the badge still update.",confirm:`Turn off notifications? Every push is dropped until you turn them back on, urgent ones too.${g} Needs you and the badge still update.`},snooze:u.on&&!c?{choices:I(e,N)}:null,awake:{unsupported:r.unsupported,on:r.on,summary:r.unsupported||(r.on?"On":"Off")},weekends:{on:u.weekends_off,sub:`Pushes wait from Friday ${f(d.quiet.start)} until Monday ${f(d.quiet.end)}. Urgent ones still go.`},quiet:ee(d,e),rules:n,linear:G(o),slack:Y(l)}}i(le,"settingsView");const X={display_information:{name:"Kickflip"},oauth_config:{scopes:{user:["chat:write","im:write","reactions:write","users:read"]}}},S={url:`https://api.slack.com/apps?new_app=1&manifest_json=${encodeURIComponent(JSON.stringify(X))}`,label:"a new Slack app"};function Y(e){const t=e.team?`${e.team} workspace`:"",n=`Make ${S.label}, install it to your workspace, then paste its User OAuth Token (xoxp-\u2026) here.`;return e.set?e.refused?{set:!0,refused:!0,title:t||"Slack token",down:"doesn\u2019t work",sub:`Slack refused the token. ${n}`,keys:S,summary:t?`Token doesn\u2019t work \xB7 ${t}`:"Token doesn\u2019t work"}:{set:!0,title:t,sub:`Token \u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022 \xB7 sends as ${e.user}`,summary:`Token set \xB7 ${t}`}:{set:!1,title:"No token",sub:n,keys:S,summary:"No token"}}i(Y,"slackWords");const y={url:"https://linear.app/settings/account/security",label:"Linear \u203A Settings \u203A Security & access \u203A Personal API keys"};function G(e){const t=e.workspace?`${e.workspace} workspace`:"";return e.set?e.refused?{set:!0,refused:!0,title:t||"Linear key",down:"doesn\u2019t work",sub:`Linear refused the key. Make a new one in ${y.label} and paste it here.`,keys:y,summary:t?`Key doesn\u2019t work \xB7 ${t}`:"Key doesn\u2019t work"}:{set:!0,title:t,sub:"Key \u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022",summary:`Key set \xB7 ${t}`}:{set:!1,title:"No key",sub:`Make a personal API key in ${y.label}, then paste it here.`,keys:y,summary:"No key"}}i(G,"linearWords");function ee(e,t){const n=e.quiet,[o,l]=[f(n.start),f(n.end)],r=k(t,"tonight",e),a=[["Snooze tonight",`Until ${p(r,t)}${n.hold?`, then quiet hours hold pushes until ${p(k(r,"morning",e),t)}`:""}.`],["Snooze morning",`Until ${p(k(t,"morning",e),t)}.`],["Snooze monday",`Until ${p(k(t,"monday",e),t)}.`],["Off on weekends",e.notifications.weekends_off?`Pushes wait from Friday ${o} until Monday ${l}.`:"Off."],["Daily wake",`Every board wakes once at ${l}, when quiet hours end.`]];return{start:n.start,end:n.end,summary:W(e),hint:B,hold:{on:n.hold,sub:n.hold?`Pushes queued after ${o} wait until ${l}. Urgent ones still go.`:"Off. Pushes go out all night."},reads:a.map(([d,u])=>({title:d,sub:u}))}}i(ee,"quietView");function de(e){return e?e.ok?{sent:e.sent,count:e.count,ok:!0,message:`Sent ${e.count} queued push${e.count===1?"":"es"}.`}:{sent:e.sent,count:e.count,ok:!1,message:"Could not send the queue. It stays queued for the next check."}:{sent:0,count:0,ok:!0,message:"Nothing was queued."}}i(de,"flushView");export{B as QUIET_HINT,ae as SNOOZE_REFUSED,ue as capital,I as durationChoices,de as flushView,re as hubView,E as plural,W as quietSummary,le as settingsView,Z as siteHealth};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lovek4d/kickflip",
3
- "version": "0.6.1",
3
+ "version": "0.7.1",
4
4
  "description": "Follow and steer Claude Code sessions from your phone.",
5
5
  "license": "SEE LICENSE IN LICENSE",
6
6
  "repository": {
@@ -1 +1 @@
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 e from"node:path";import{spawnSync as d}from"node:child_process";const f=e.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 r({env:t=process.env,platform:s=process.platform,apps:n=["/Applications",e.join(u.homedir(),"Applications")]}={}){const l=(t.PATH||"").split(e.delimiter).filter(Boolean).map(o=>e.join(o,"tailscale"));return s==="darwin"&&l.push(...n.map(o=>e.join(o,f))),[...new Set(l)].filter(m)}a(r,"tailscaleCandidates");function h(t){const s=d(t,["status","--json"],{encoding:"utf8",timeout:3e3,stdio:["ignore","pipe","ignore"]});try{return JSON.parse(s.stdout).BackendState==="Running"}catch{return!1}}a(h,"daemonRunning");function y({running:t=h,...s}={}){const n=r(s);return n.length>1?n.find(t)??n[0]:n[0]??null}a(y,"tailscaleBin");function A(t,s={}){const n=r(s);return n.includes(t)?n.slice(0,n.indexOf(t)):[]}a(A,"tailscaleSkipped");const c=a((t=process.platform)=>t==="darwin"?"brew install --cask tailscale-app":"curl -fsSL https://tailscale.com/install.sh | sh","tailscaleInstallCommand"),C=a((t=process.platform)=>t==="darwin"?`install Tailscale from https://tailscale.com/download, or ${c(t)}`:c(t),"tailscaleInstallHint");export{h as daemonRunning,y as tailscaleBin,r as tailscaleCandidates,c as tailscaleInstallCommand,C as tailscaleInstallHint,A as tailscaleSkipped};
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};
@@ -1 +1 @@
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};
1
+ var v=Object.defineProperty;var t=(e,s)=>v(e,"name",{value:s,configurable:!0});import U from"node:fs";import w from"node:os";import H from"node:path";import{usageReader as G}from"../hub/usage.mjs";import{readSettingsFile as _,writeSettingsFile as $,readCodexShare as T,readClaudeModel as I}from"../hub/push.mjs";import{FOREVER as O,durationEnd as z,storedEnd as J}from"../hub/duration.mjs";import{codexStatus as K,codexStatusReader as L,effectiveCodexShare as V}from"./codex.mjs";import{MACHINE_NORMAL as q,readMachineState as Q}from"./machine-gate.mjs";const X=t(e=>Number.isInteger(e)&&e>=50&&e<=100&&e%5===0,"validPauseAt"),c=t(e=>Number(e),"timeOf"),y=["five_hour","seven_day"],b=t((e,s)=>e===O||typeof e=="string"&&Date.parse(e)>c(s)?e:null,"pausedUntil");function Y(e,s=new Date){if(e==="off")return null;const o=z(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 J(o)}t(Y,"pauseEnd");const Z=t(e=>e===O?"until the owner resumes it":`until ${e}`,"pauseUntilWords");function ee({claude:e,codex:s,pauseAt:o=90,keepGoing:n,hubPause:a,boardPause:r,machine:u=q,start:m=null,now:d=Date.now()}={}){const p=X(o)?o:90,k=t(l=>p===100?null:y.find(i=>{const f=l?.[i];return f?.state==="live"&&Number.isFinite(f.used_percentage)&&f.used_percentage>=p&&(!f.resets_at||Date.parse(f.resets_at)>c(d))})||null,"past"),D=t((l,i)=>({window:i,used:i?l[i].used_percentage:null,resets_at:i?l[i].resets_at:null}),"details"),x=k(e),A=D(e,x),C=t((l,i,f)=>l?.window===i&&Math.abs(Date.parse(l.until)-Date.parse(f))<=3e5&&c(d)<Date.parse(l.until),"covers"),j=x&&!n?.near&&C(n,x,A.resets_at),h=x?null:te(e,p,d);h&&(h.going=!!n?.near&&C(n,h.window,h.resets_at),h.waiting=!h.going&&!!m&&!m.reported&&c(d)-Date.parse(m.at)<se);const M=k(s),R={past:!!M,...D(s,M)},g=b(a,d)?"hub":b(r,d)?"board":null;return g?{pause_at:p,state:"paused",manual:g,window:null,used:null,resets_at:b(g==="hub"?a:r,d),near:null,codex:R,machine:u}:{pause_at:p,state:x?j?"going":"paused":"normal",manual:null,...A,near:h,codex:R,machine:u}}t(ee,"usageGate");const se=10*6e4,E=t(e=>2*e-100,"nearFrom");function te(e,s,o){if(s===100)return null;const n=y.find(a=>{const r=e?.[a];return r?.state==="live"&&Number.isFinite(r.used_percentage)&&r.used_percentage>=E(s)&&(!r.resets_at||Date.parse(r.resets_at)>c(o))});return n?{window:n,used:e[n].used_percentage,resets_at:e[n].resets_at,from:E(s)}:null}t(te,"nearOf");const ne=t(e=>{try{return JSON.parse(U.readFileSync(H.join(e,"board.json"),"utf8"))||{}}catch{return{}}},"readBoardJson"),ae=t((e,s=Date.now())=>b(ne(e).paused_until,s),"boardPausedUntil"),N=new Map;function W({home:e=w.homedir(),now:s=Date.now(),dir:o=null}={}){let n=N.get(e);n||(n={now:c(s)},n.usage=G({home:e,now:t(()=>n.now,"now")}),n.codex=L({home:e,now:t(()=>n.now,"now")}),N.set(e,n)),n.now=c(s);const a=_(e);return ee({...n.usage({codexShare:T(n.codex(),e)}),pauseAt:a.pauseAt,keepGoing:a.keepGoing,hubPause:a.paused_until,boardPause:o?ae(o,s):null,machine:Q(e),now:s})}t(W,"readUsageGate");function be(e,{home:s=w.homedir(),now:o=new Date}={}){const n=Y(e,o),{paused_until:a,...r}=_(s);return $(n?{...r,paused_until:n}:r,s),n}t(be,"setHubPause");function ge(e,{home:s=w.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)}t(ge,"setPauseAt");function $e(e,{home:s=w.homedir(),now:o=Date.now()}={}){if(!["on","off"].includes(e))throw Object.assign(new Error("keep-going takes on or off"),{status:400});const n=_(s);if(e==="on"){const a=W({home:s,now:o});if(a.state==="paused")n.keepGoing={window:a.window,until:a.resets_at};else if(a.near)n.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 n.keepGoing;$(n,s)}t($e,"setKeepGoing");const Se=t(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 ke({home:e=w.homedir(),now:s=Date.now(),card:o={}}={}){const n=K({home:e}),a={codex:n.reason??"usable",claudeModel:I(e),size:o.size??null,cardModel:o.model??null},r=t(m=>{const{model:d,reason:p}=oe(a.claudeModel,o);return{kind:"claude",model:d,reason:`${m} ${p}`,inputs:a}},"claude");if(!n.usable)return r(n.words);if(a.codexShare=V(_(e).codexShare,n),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}}t(ke,"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."}}t(oe,"claudePick");const P={hub:["the hub","Pause hub in Hub settings"],board:["this board","Pause board in Board settings"]},re=t(e=>e.manual?`Work is paused: the owner paused ${P[e.manual][0]} ${Z(e.resets_at)}. ${P[e.manual][1]} resumes it.`:e.state==="paused"?de(e):e.machine.state==="busy"||!e.near?.waiting?B(e):ue(e),"pauseReason"),ue=t(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"),B=t(e=>`Work is held: the host is busy (${e.machine.reason}). It starts again when the host eases.`,"busyReason"),De=t(e=>e.state==="paused"||e.machine.state==="busy","holdsWork"),Ae=t(e=>`usage paused ${e.resets_at} ${e.manual?`manual ${e.manual}`:`${e.window} ${e.used}%`}`,"pausedLine"),de=t(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"),ie=t((e,s)=>!!e.manual||e.machine.state==="busy"||(s==="codex"?e.codex.past:e.state==="paused"),"heldFor"),Ce=t((e,s)=>ie(e,s)||s!=="codex"&&!!e.near?.waiting,"launchHeld"),Me=t((e,s)=>!!e.manual||(s==="codex"?e.codex.past:e.state==="paused"),"pausesRunning"),Re=t(e=>e.manual?`${e.manual} paused`:"usage limit","pausedWords"),S=t(e=>e==="five_hour"?"5-hour":"weekly","windowWord"),Oe=t((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?B(e):re(e),"pauseReasonFor");export{ae as boardPausedUntil,ke as builderPick,ie as heldFor,Se as heldUsageEvent,De as holdsWork,Ce as launchHeld,Y as pauseEnd,re as pauseReason,Oe as pauseReasonFor,Z as pauseUntilWords,Ae as pausedLine,Re as pausedWords,Me as pausesRunning,W as readUsageGate,be as setHubPause,$e as setKeepGoing,ge as setPauseAt,ee as usageGate,X as validPauseAt};