claude-do-it 0.1.2 → 0.1.3
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/README.md +6 -0
- package/bin/daemon.js +2 -2
- package/bin/do-it.js +1 -1
- package/package.json +1 -1
- package/skill/SKILL.md +50 -11
- package/web/assets/index-D1yk8ubK.css +2 -0
- package/web/assets/index-DVWNl8ep.js +169 -0
- package/web/index.html +2 -2
- package/web/assets/index-CYgjGBYX.js +0 -169
- package/web/assets/index-DwsKU-BX.css +0 -2
package/README.md
CHANGED
|
@@ -11,6 +11,11 @@ on disk all merge instead of overwriting each other: every open file is one
|
|
|
11
11
|
[Loro](https://loro.dev) CRDT held by a small daemon on your machine. The file
|
|
12
12
|
itself stays plain Markdown, so any other editor still works on it.
|
|
13
13
|
|
|
14
|
+
<picture>
|
|
15
|
+
<source media="(prefers-color-scheme: dark)" srcset="docs/images/editor-dark.png">
|
|
16
|
+
<img alt="The do-it editor: a Markdown todo with tasks, tags, and the status lines agents write under each task" src="docs/images/editor-light.png">
|
|
17
|
+
</picture>
|
|
18
|
+
|
|
14
19
|
```mermaid
|
|
15
20
|
flowchart LR
|
|
16
21
|
you["You<br/>browser editor"] <-->|"live sync"| daemon
|
|
@@ -141,6 +146,7 @@ The skill checks where it runs and starts workers there:
|
|
|
141
146
|
| Herdr (`HERDR_ENV=1`) | one Claude session per task in a Herdr worktree and space, started in the background |
|
|
142
147
|
| [Orca](https://www.onorca.dev) | one Orca worktree per task, with Claude in a terminal of its own (`orca worktree create`, then `orca terminal create`) |
|
|
143
148
|
| [T3 Code](https://github.com/pingdotgg/t3code), with its `t3` CLI | one T3 Code thread per task, in its own worktree (`t3 thread start --worktree`) |
|
|
149
|
+
| super.engineering (formerly Superconductor), with its `sc` CLI | one super.engineering worktree per task with a Claude session working it, started in the background (`sc worktree create --background`) |
|
|
144
150
|
| Any other terminal | Claude Code subagents in worktrees, which need nothing beyond Claude Code |
|
|
145
151
|
|
|
146
152
|
Set `DO_IT_BROWSER` to the command that should open links, or to `true` to
|
package/bin/daemon.js
CHANGED
|
@@ -8,8 +8,8 @@ ${l}${s}`})}function It(e,t){let o=e.replace(/[\r\n]+$/,""),n=A(o).at(-1),r=!o?"
|
|
|
8
8
|
`}}function De(e,t,o){if(t=t.replace(/[\r\n]+$/,""),!t.trim())throw new y({code:"invalid",message:"There is no text to add"});if(o===void 0)return be(e,It(e,t));let n=F(e,o),r=e.slice(n.from,n.to),i=r.replace(/[\r\n]+$/,""),s=It(i,t);return be(e,{from:n.from+s.from,to:n.from+s.to,insert:i.length===r.length?s.insert:s.insert.replace(/\n+$/,"")})}function tt(e,t,o,n){if(!t)throw new y({code:"invalid",message:"Text to replace must not be empty"});let r=n===void 0?{from:0,to:e.length}:F(e,n),i=e.slice(r.from,r.to),s=i.indexOf(t);if(s===-1)throw new y({code:"not-found",message:"The text to replace is not in the document"});if(i.indexOf(t,s+t.length)!==-1)throw new y({code:"ambiguous",message:"Several places match the text to replace, include more of it so one does"});return be(e,{from:r.from+s,to:r.from+s+t.length,insert:o})}import{diffArrays as Qo,diffChars as qo}from"diff";function Ae(e,t,o=t){return{kind:e,number:t.number,text:H(t.text),line:t.line,oldLine:o.line}}function en(e,t){if(e===t)return null;if(t==="done")return"checked";if(t==="cancelled")return"cancelled";return e==="done"?"unchecked":"uncancelled"}function tn(e,t){let o=Array.from(e).length+Array.from(t).length;if(!o)return 1;return 2*qo(e,t).reduce((r,i)=>r+(i.added||i.removed?0:i.count),0)/o}function ot(e,t){let o=Q(e),n=Q(t),r=Qo(o.map((d)=>H(d.text)),n.map((d)=>H(d.text))),i=[],s=0,a=0;for(let d=0;d<r.length;d++){let l=r[d];if(!l.added&&!l.removed){for(let g=0;g<l.count;g++){let w=o[s++],b=n[a++],O=en(w.state,b.state);if(O)i.push(Ae(O,b,w))}continue}let u=[],f=[];while(d<r.length&&(r[d].added||r[d].removed)){let g=r[d];if(g.removed)u.push(...o.slice(s,s+g.count)),s+=g.count;else f.push(...n.slice(a,a+g.count)),a+=g.count;d++}d--;let h=new Set;for(let g of f){let w=-1,b=0.5;for(let[O,Ue]of u.entries()){if(h.has(O))continue;let xe=tn(Ue.text,g.text);if(xe>=b)w=O,b=xe}if(w===-1)i.push(Ae("added",g));else h.add(w),i.push(Ae("edited",g,u[w]))}for(let[g,w]of u.entries())if(!h.has(g))i.push(Ae("removed",w))}return i}import{LoroDoc as Lt}from"loro-crdt";var q="markdown",on=8,nn=Symbol("WriteTicket");function rn(e,t){return e.length===t.length&&e.every((o)=>t.some((n)=>n.peer===o.peer&&n.counter===o.counter))}function se(e,t){for(let o=t.length-1;o>=0;o--){let n=t[o];if(n.to!==n.from)e.delete(n.from,n.to-n.from);if(n.insert)e.insert(n.from,n.insert)}}class ce{doc;body;current;currentVersion;history=new we;disk;agents=new Map;writing=[];tickets=new WeakMap;generation=0;constructor(e,t){this.doc=new Lt,this.body=this.doc.getText(q),this.body.insert(0,e),this.doc.commit(),this.current=e,this.currentVersion=t,this.disk=this.snapshot(),this.history.record(t,this.doc.frontiers(),null)}static open(e,t=1){return U(e),Ge(t),new ce(e,t)}get text(){return this.current}get version(){return this.currentVersion}get synced(){return{text:this.disk.text,version:this.disk.version}}get dirty(){return this.current!==this.disk.text}agentEdit(e,t,o){if(t.section!==void 0)F(this.current,t.section);let n=null;if(t.version!==this.version){let l=this.textAt(t.version),u=l===null?null:Te(l,this.current,t.version,this.version),f=l===null||t.section===void 0?null:ie(l,t.section);if(f===null||f!==ie(this.current,t.section))throw new y({code:"conflict",message:`The document is at version ${this.version}, not ${t.version}`,conflict:{expected:t.version,current:this.version,diff:u,forgotten:l===null}});n={since:t.version,diff:u}}let r=Ye(this.current,o(this.current));if(pe(this.current,r)===this.current)return this.applied(!1,n);this.checkNextVersion();let s=this.doc.fork(),a=s.getText(q),d=this.doc.oplogVersion();try{let l=Je(e),u=this.agents.get(l);if(u!==void 0)s.setPeerId(u);return se(a,r),s.commit(),this.doc.import(s.export({mode:"update",from:d})),this.agents.set(l,s.peerIdStr),this.applied(this.refresh(e),n)}finally{d.free(),a.free(),s.free()}}userEdit(e,t){U(t);let o=this.history.find(e);if(!o)throw new y({code:"forgotten",message:`Version ${e} is no longer kept`});if(t===this.current)return this.applied(!1);if(e===this.version)return this.checkNextVersion(),se(this.body,Z(this.current,t)),this.doc.commit(),this.applied(this.refresh({kind:"user"}));let n=this.doc.forkAt(o.frontiers),r=n.getText(q);try{let i=r.toString();if(i===t)return this.applied(!1);return se(r,Z(i,t)),this.importFork(n),this.applied(this.refresh({kind:"user"}))}finally{r.free(),n.free()}}mergeFile(e){if(U(e),e===this.disk.text)return!1;let t=this.writing.find((r)=>r.text===e);if(t)return this.wrote(t),!1;if(e===this.current)return this.disk=this.snapshot(),!1;if(rn(this.disk.frontiers,this.doc.frontiers()))return this.checkNextVersion(),se(this.body,Z(this.disk.text,e)),this.doc.commit(),this.disk={text:e,version:this.disk.version,frontiers:this.doc.frontiers()},this.refresh({kind:"file"});let o=this.doc.forkAt(this.disk.frontiers),n=o.getText(q);try{se(n,Z(this.disk.text,e)),o.commit();let r=o.frontiers();return this.importFork(o),this.disk={text:e,version:this.disk.version,frontiers:r},this.refresh({kind:"file"})}finally{n.free(),o.free()}}importFork(e){e.commit();let t=this.doc.oplogVersion(),o=e.oplogVersion(),n=e.getText(q);try{let r=e.export({mode:"update",from:t});e.import(this.doc.export({mode:"update",from:o}));let i=n.toString();if(U(i),i!==this.current)this.checkNextVersion();this.doc.import(r)}finally{n.free(),o.free(),t.free()}}toWrite(){let e=this.snapshot(),t=Object.freeze({text:e.text,version:e.version,[nn]:!0});if(this.tickets.set(t,{snapshot:e,generation:this.generation}),this.writing.length===on)this.writing.shift();return this.writing.push(t),t}wrote(e){let t=this.ticketSnapshot(e),o=this.writing.indexOf(e);if(o!==-1)this.writing.splice(0,o+1);this.disk=t}notWritten(e){this.ticketSnapshot(e);let t=this.writing.indexOf(e);if(t!==-1)this.writing.splice(t,1)}ticketSnapshot(e){let t=this.tickets.get(e);if(!t||t.generation!==this.generation)throw new y({code:"invalid",message:"The write ticket does not belong to this document's current history"});return t.snapshot}textAt(e){if(e===this.version)return this.current;let t=this.history.find(e);if(!t)return null;let o=this.doc.forkAt(t.frontiers),n=o.getText(q);try{return n.toString()}finally{n.free(),o.free()}}changes(e,t){let o=this.history.originsSince(e);if(o===null)return null;let n=this.textAt(e),r=this.current,i=t===void 0?n:ie(n,t)??"",s=t===void 0?r:ie(r,t)??"",a=ot(n,r);if(t!==void 0){let d=t.trim().replace(/^#+/,""),l=_(n).find((g)=>g.id===d),u=_(r).find((g)=>g.id===d),f=A(n),h=A(r);a=a.filter((g)=>{let w=g.kind==="removed",b=w?l:u,O=w?f[g.oldLine]:h[g.line];return b!==void 0&&O!==void 0&&O.from>=b.from&&O.from<b.to})}return{from:e,to:this.version,by:o,diff:Te(i,s,e,this.version),todos:a}}origin(e){let t=this.history.find(e)?.origin;return t?{...t}:null}compact(e=Math.max(50000,4*this.current.length),t=256,o=Ke){if(this.dirty||this.writing.length)return null;let n=0,r=0,i=this.doc.oplogVersion();try{n=this.doc.opCount(),r=i.length()}finally{i.free()}if(n<=e&&r<=t)return null;let s=n>e?"ops":"peers",a=this.history.list().slice(-Math.max(1,o)).map((f)=>({...f,text:f.version===this.version?this.current:this.textAt(f.version)}));if(a.length===0||a.at(-1).version!==this.version)throw Error("Compaction history does not include the current document");let d=new Lt,l=d.getText(q),u=new we;try{let f=a[0];l.insert(0,f.text),d.commit(),u.record(f.version,d.frontiers(),f.origin);let h=f.text;for(let g of a.slice(1))se(l,Z(h,g.text)),d.commit(),u.record(g.version,d.frontiers(),g.origin),h=g.text;if(h!==this.current)throw Error("Compaction did not preserve the current document")}catch(f){throw l.free(),d.free(),f}return this.body.free(),this.doc.free(),this.doc=d,this.body=l,this.disk={...this.disk,frontiers:d.frontiers()},this.history=u,this.generation++,{reason:s,ops:n,peers:r,kept:a.length,oldest:a[0].version}}snapshot(){return{text:this.current,version:this.version,frontiers:this.doc.frontiers()}}checkNextVersion(){Ge(this.version+1)}refresh(e){let t=this.body.toString(),o=t!==this.current;if(o)this.current=t,this.currentVersion++;return this.history.record(this.version,this.doc.frontiers(),o?e:this.origin(this.version)),o}applied(e,t=null){return{version:this.version,changed:e,others:t}}free(){this.body.free(),this.doc.free()}}import{Effect as S,FileSystem as rt,Option as Mt,Path as sn,PlatformError as cn}from"effect";import{SecureRandom as an}from"effect/crypto";function it(e){return{mtimeMs:Mt.match(e.mtime,{onNone:()=>0,onSome:(t)=>t.getTime()}),size:Number(e.size),ino:Number(Mt.getOrElse(e.ino,()=>0n))}}function K(e){return cn.isPlatformError(e)&&e.reason._tag==="NotFound"}function Re(e,t){if(t instanceof y)return t;return new y({code:K(t)?"not-found":"invalid",message:`Could not access ${e}: ${t instanceof Error?t.message:String(t)}`})}function nt(e){return e>j?S.fail(new y({code:"too-large",message:`The file is ${e} bytes, over the limit of ${j}`})):S.void}var Vt=(e)=>new y({code:"invalid",message:`${e} is not a regular file`}),ee=S.fn("stamp")(function*(e){return yield*(yield*rt.FileSystem).stat(e).pipe(S.map((o)=>it(o)),S.catchIf(K,()=>S.succeed(null)),S.mapError((o)=>Re(e,o)))}),fe=S.fn("readFile")(function*(e){let t=yield*rt.FileSystem;if((yield*t.stat(e)).type!=="File")return yield*S.fail(Vt(e));return yield*S.scoped(S.gen(function*(){let o=yield*t.open(e,{flag:"r"}),n=yield*o.stat;if(n.type!=="File")return yield*S.fail(Vt(e));yield*nt(Number(n.size));let r=new Uint8Array(j+1),i=0;while(i<r.length){let u=yield*o.read(r.subarray(i));if(!u)break;i+=u}yield*nt(i);let s;try{s=new TextDecoder("utf-8",{fatal:!0,ignoreBOM:!0}).decode(r.subarray(0,i))}catch{return yield*S.fail(new y({code:"invalid",message:`${e} is not valid UTF-8 text`}))}let a=s.startsWith("\uFEFF");if(a)s=s.slice(1);let d=s.indexOf(`
|
|
9
9
|
`),l=d>0&&s[d-1]==="\r";return{text:s.replace(/\r\n/g,`
|
|
10
10
|
`),stamp:it(n),format:{bom:a,crlf:l}}}))},(e,t)=>S.mapError(e,(o)=>Re(t,o))),Oe=S.fn("writeAtomic")(function*(e,t,o){let n=yield*rt.FileSystem,r=yield*sn.Path;yield*S.try({try:()=>U(t),catch:(u)=>Re(e,u)});let i=new TextEncoder().encode((o.bom?"\uFEFF":"")+(o.crlf?t.replace(/\n/g,`\r
|
|
11
|
-
`):t));yield*nt(i.length);let s=r.join(r.dirname(e),`.${r.basename(e)}.do-it-${an.uuidV4Unsafe()}.tmp`),a=yield*n.stat(e).pipe(S.map((u)=>u.mode&4095),S.catchIf(K,()=>S.succeed(void 0))),d=!1;return yield*S.scoped(S.gen(function*(){let u=yield*n.open(s,{flag:"wx",mode:a??438});if(d=!0,yield*u.writeAll(i),a!==void 0)yield*n.chmod(s,a);return yield*u.sync,it(yield*u.stat)})).pipe(S.tap(()=>n.rename(s,e)),S.onError(()=>d?S.ignore(n.remove(s)):S.void))},(e,t)=>S.mapError(e,(o)=>Re(t,o)));import{Schema as c}from"effect";var Wt=2097152,dn=4096,E=c.String.pipe(c.check(c.isMaxLength(Wt))),st=c.String.pipe(c.check(c.isMaxLength(dn))),P=c.String.pipe(c.check(c.makeFilter((e)=>e.length>0&&e.startsWith("/")?void 0:"an absolute, non-empty path is required"),c.isMaxLength(Wt))),T=c.Int.pipe(c.check(c.isGreaterThan(0))),te=c.String.pipe(c.check(c.isPattern(/^[a-f0-9]{64}$/))),ln=c.TaggedStruct("User",{}),pn=c.TaggedStruct("Agent",{name:c.String.pipe(c.check(c.isBetweenLength(1,64),c.isPattern(/^[A-Za-z0-9._-]+$/)))}),fn=c.TaggedStruct("File",{}),ct=c.Union([ln,pn,fn]),Ut=c.Literals(["open","done","cancelled"]),un=c.Struct({number:c.Number,line:c.Number,state:Ut,text:st,indent:c.Number,section:c.NullOr(E)}),Bt=c.Struct({kind:c.Literals(["added","removed","checked","unchecked","cancelled","uncancelled","edited"]),number:c.Number,text:st,line:c.Number,oldLine:c.Number}),hn=c.Struct({id:E,title:E,level:c.Literals([1,2,3]),line:c.Number}),mn=c.Struct({since:T,diff:c.NullOr(E)}),gn=c.TaggedStruct("Replace",{old:E,new:E}),yn=c.TaggedStruct("Append",{text:E}),xn=c.TaggedStruct("TodoAdd",{text:st}),vn=c.TaggedStruct("TodoSet",{target:c.Union([c.Number,E]),state:Ut}),Sn=c.TaggedStruct("TodoStart",{target:c.Union([c.Number,E])}),En=c.Union([gn,yn,xn,vn,Sn]),Fe=c.optional(E),Ht=c.Struct({}),wn=c.Struct({path:P,version:T,watchers:c.Number}),zt=c.Struct({pid:c.Number,appVersion:E,port:c.Number,startedAt:c.Number,build:c.optionalKey(E),documents:c.Array(wn)}),jt=c.Struct({}),Kt=c.Struct({}),Xt=c.Struct({path:P,create:c.Boolean}),Gt=c.Struct({path:P,version:T,url:E,created:c.Boolean}),Jt=c.Struct({path:P,section:Fe}),Yt=c.Struct({version:T,text:E,hash:c.optionalKey(te)}),Zt=c.Struct({path:P}),Qt=c.Struct({version:T,sections:c.Array(hn)}),qt=c.Struct({path:P,section:Fe}),eo=c.Struct({version:T,todos:c.Array(un)}),to=c.Struct({path:P,author:ct,version:T,section:Fe,op:En}),oo=c.Struct({version:T,changed:c.Boolean,section:c.NullOr(E),others:c.NullOr(mn)}),no=c.Struct({path:P,base:T,text:E,baseHash:c.optionalKey(te)}),ro=c.Struct({version:T,text:E,hash:c.optionalKey(te)}),io=c.Struct({path:P,since:T,wait:c.Number.pipe(c.check(c.isBetween({minimum:0,maximum:600})))}),so=c.Struct({from:T,to:T,by:c.Array(ct),diff:c.NullOr(E),todos:c.Array(Bt)}),co=c.Struct({path:P,since:c.optional(T),sinceHash:c.optionalKey(te),section:Fe,text:c.Boolean}),ao=c.TaggedStruct("Watching",{version:T,hash:c.optionalKey(te),text:c.optionalKey(E)}),lo=c.TaggedStruct("Change",{from:T,to:T,by:c.Array(ct),diff:c.NullOr(E),todos:c.Array(Bt),hash:c.optionalKey(te),text:c.optionalKey(E)}),po=c.TaggedStruct("Resync",{version:T,hash:c.optionalKey(te),text:c.optionalKey(E)}),fo=c.TaggedStruct("Deleted",{}),uo=c.Union([ao,lo,po,fo]);import{Schema as M}from"effect";class ue extends M.TaggedError()("VersionMismatch",{latest:T,since:T,diff:M.NullOr(E),forgotten:M.Boolean}){}var Tn=M.Literals(["not-found","ambiguous","too-large","invalid"]);class R extends M.TaggedError()("Refused",{code:Tn,message:E}){}class X extends M.TaggedError()("Forgotten",{latest:T}){}class Pe extends M.TaggedError()("NothingYet",{}){}class N extends M.TaggedError()("DocumentGone",{path:P}){}class ho extends M.TaggedError()("Unauthorized",{}){}var I=M.Union([ue,R,X,Pe,N,ho]);import{Rpc as V,RpcGroup as bn}from"effect/rpc";var kn=V.make("status",{payload:Ht,success:zt,error:I}),Dn=V.make("stop",{payload:jt,success:Kt,error:I}),An=V.make("open",{payload:Xt,success:Gt,error:I}),Rn=V.make("read",{payload:Jt,success:Yt,error:I}),On=V.make("sections",{payload:Zt,success:Qt,error:I}),Fn=V.make("todos",{payload:qt,success:eo,error:I}),Pn=V.make("edit",{payload:to,success:oo,error:I}),Cn=V.make("sync",{payload:no,success:ro,error:I}),$n=V.make("diff",{payload:io,success:so,error:I}),_n=V.make("watch",{payload:co,success:uo,error:I,stream:!0}),Ce=bn.make(kn,Dn,An,Rn,On,Fn,Pn,Cn,$n,_n);var ae="0.1.
|
|
12
|
-
`);class lt extends Un.TaggedError("InvalidConfig"){}var zn=new URL("../web",import.meta.url),Ie=(e)=>dt.option(dt.String(e)).pipe(he.map(Bn.getOrUndefined)),xo=he.gen(function*(){let e=yield*Hn.Path,t=Number((yield*Ie("DO_IT_PORT"))??47100),o=Number((yield*Ie("DO_IT_IDLE_MS"))??1800000);if(!Number.isInteger(t)||t<0||t>65535)return yield*he.fail(new lt({message:"DO_IT_PORT must be between 0 and 65535"}));if(!Number.isFinite(o)||o<=0)return yield*he.fail(new lt({message:"DO_IT_IDLE_MS must be positive"}));let n=(yield*Ie("DO_IT_HOME"))??e.join(yield*dt.String("HOME"),".do-it"),r=(yield*Ie("DO_IT_WEB_DIST"))??(yield*e.fromFileUrl(zn));return{home:e.resolve(n),port:t,idleMs:o,webDist:e.resolve(r)}});var ys=new URL("../../../",import.meta.url),vo=he.succeed("1fc7246.mw4rm3m2");import{Cause as Eo,Context as Gn,Deferred as ut,Effect as p,Exit as Jn,Fiber as ht,FileSystem as Yn,Layer as Zn,Path as Qn,Scope as mt,Semaphore as wo}from"effect";import{Deferred as pt,Effect as G,Fiber as jn,FileSystem as Kn,Path as Xn,Stream as ft}from"effect";var So=G.fn("watchFile")(function*(e,t){let o=yield*Kn.FileSystem,n=yield*Xn.Path,r=n.basename(e),i=yield*pt.make(),s=yield*o.watch(n.dirname(e),{onReady:()=>pt.doneUnsafe(i,G.void)}).pipe(ft.filter((a)=>n.basename(a.path)===r),ft.debounce("50 millis"),ft.runForEach(()=>t),G.ignore,G.forkScoped);yield*t.pipe(G.delay("2 seconds"),G.forever,G.forkScoped),yield*G.race(pt.await(i),jn.await(s))});var qn=64,er="25 millis",tr="500 millis";function bo(e,t=1){if(e instanceof y){if(e.code==="conflict"&&e.conflict){let o=e.conflict;return new ue({latest:o.current,since:o.expected,diff:o.diff,forgotten:o.forgotten})}if(e.code==="forgotten")return new X({latest:t});return new R({code:e.code==="conflict"?"invalid":e.code,message:e.message})}if(e instanceof R||e instanceof N||e instanceof X||e instanceof ue)return e;return new R({code:"invalid",message:e instanceof Error?e.message:String(e)})}function ne(e,t){return p.try({try:e,catch:(o)=>bo(o,t?.())})}function To(e,t){return e.mtimeMs===t.mtimeMs&&e.ino===t.ino&&e.size===t.size}class gt{versions;log;context;scope;entries=new Map;opening=wo.makeUnsafe(1);totalWatchers=0;closing=!1;constructor(e,t,o,n){this.versions=e;this.log=t;this.context=o;this.scope=n}static make=p.fn("DocumentRegistry.make")(function*(e,t){return new gt(e,t,yield*p.context(),yield*p.scope)});disk(e){return p.provideContext(e,this.context)}logged(e,t){return t.pipe(p.catchCause((o)=>Eo.hasInterruptsOnly(o)?p.failCause(o):this.log.write(`${e}: ${String(Eo.squash(o))}`)))}validate(e){return this.disk(p.gen({self:this},function*(){let t=yield*Qn.Path,o=yield*Yn.FileSystem;if(!t.isAbsolute(e)||![".md",".markdown"].includes(t.extname(e).toLowerCase()))return yield*p.fail(new R({code:"invalid",message:"The path must be absolute and end in .md or .markdown"}));let n=t.resolve(e);if(!(yield*o.stat(t.dirname(n)).pipe(p.map((i)=>i.type==="Directory"),p.catch(()=>p.succeed(!1))))){if(this.entries.has(n)||this.versions.has(n))return yield*p.fail(new N({path:n}));return yield*p.fail(new R({code:"invalid",message:"The document's parent directory must exist"}))}return n}))}open(e,t=!1){return this.acquire(e,t,!1)}get(e){return p.map(this.open(e),({entry:t})=>t)}withEntry(e,t,o=!1){return this.withOpened(e,({entry:n})=>t(n),o)}withOpened(e,t,o=!1){return p.acquireUseRelease(this.acquire(e,o,!0),t,({entry:n})=>p.sync(()=>{n.waiters--,n.lastUsed=Date.now()}))}acquire(e,t,o){return p.gen({self:this},function*(){let n=yield*this.validate(e);while(!0){if(this.closing)return yield*p.fail(new R({code:"invalid",message:"The daemon is shutting down"}));let r=this.entries.get(n),i=r?yield*this.reuse(r):yield*this.opening.withPermit(p.suspend(()=>{let s=this.entries.get(n);return s?this.reuse(s):this.create(n,t)}));if(i.entry.closed)continue;if(o)i.entry.waiters++;return i}}).pipe(p.mapError((n)=>bo(n)))}status(){return[...this.entries.values()].map((e)=>({path:e.path,version:e.doc.version,watchers:e.watchers}))}reuse(e){return e.lastUsed=Date.now(),this.disk(ee(e.path)).pipe(p.flatMap((t)=>e.deleted||t===null?p.fail(new N({path:e.path})):p.succeed({entry:e,created:!1})))}create(e,t){let o=p.gen({self:this},function*(){let n=(yield*this.disk(ee(e)))===null;if(n&&!t){if(this.versions.has(e))return yield*p.fail(new N({path:e}));return yield*p.fail(new R({code:"invalid",message:"The document does not exist, open it with create enabled"}))}if(yield*this.makeRoom(),n)yield*this.disk(Oe(e,yo,{bom:!1,crlf:!1}));return{missing:n,file:yield*this.disk(fe(e))}});return p.uninterruptibleMask((n)=>p.gen({self:this},function*(){let{missing:r,file:i}=yield*n(o),s=yield*this.versions.open(e,i.text),a={path:e,doc:s,signal:ut.makeUnsafe(),format:i.format,stamp:i.stamp,watchers:0,waiters:0,lastUsed:Date.now(),deleted:!1,closed:!1,lock:wo.makeUnsafe(1),scope:yield*mt.fork(this.scope),writing:void 0,missing:void 0,hash:void 0};return this.entries.set(e,a),yield*this.disk(So(e,this.poll(a))).pipe(mt.provide(a.scope)),{entry:a,created:r}}))}makeRoom(){return p.gen({self:this},function*(){if(this.entries.size<qn)return;let e=[...this.entries.values()].filter((t)=>t.watchers===0&&t.waiters===0).sort((t,o)=>t.lastUsed-o.lastUsed);for(let t of e)if(yield*this.flush(t),yield*this.evict(t))return;return yield*p.fail(new R({code:"too-large",message:"At most 64 documents may be open, all available documents are busy"}))})}notify(e){let t=e.signal;e.signal=ut.makeUnsafe(),ut.doneUnsafe(t,p.void)}changed(e,t){if(e.doc.version===t)return p.void;return e.lastUsed=Date.now(),this.notify(e),p.andThen(this.versions.record(e.path,e.doc.version,e.doc.text),this.scheduleWrite(e))}hashOf(e,t,o){return p.suspend(()=>{if(e.hash?.version===t)return p.succeed(e.hash.value);return p.tap(this.versions.sha256(o),(n)=>p.sync(()=>{if(t>(e.hash?.version??0))e.hash={version:t,value:n}}))})}hashAt(e,t){return p.suspend(()=>{if(e.hash?.version===t)return p.succeed(e.hash.value);let o=e.doc.textAt(t);return o===null?p.succeed(null):this.hashOf(e,t,o)})}subscribe(e){if(e.deleted)throw new N({path:e.path});if(e.watchers>=32||this.totalWatchers>=256)throw new R({code:"too-large",message:"The document or daemon watch stream limit has been reached"});return e.watchers++,this.totalWatchers++,()=>{e.watchers--,this.totalWatchers--,e.lastUsed=Date.now()}}scheduleWrite(e){return p.suspend(()=>{if(e.writing||this.closing||e.deleted||e.closed||!e.doc.dirty)return p.void;let t=p.suspend(()=>(e.writing=void 0,this.flushNow(e)));return p.forkIn(p.delay(t,er),this.scope).pipe(p.map((o)=>{e.writing=o}))})}poll(e){return e.lock.withPermit(this.pollLocked(e))}pollLocked(e){return this.logged(`Could not merge ${e.path}`,p.gen({self:this},function*(){if(e.deleted||e.closed||this.closing)return;let t=yield*this.disk(ee(e.path));if(!t){if(!e.missing)e.missing=yield*p.forkIn(this.recheck(e),this.scope);return}if(e.missing){let o=e.missing;e.missing=void 0,yield*ht.interrupt(o)}if(!To(e.stamp,t)){let o=yield*this.disk(fe(e.path)),n=e.doc.version;e.doc.mergeFile(o.text),e.format=o.format,e.stamp=o.stamp,yield*this.changed(e,n),yield*this.versions.record(e.path,e.doc.version,e.doc.text),yield*this.compact(e)}yield*this.scheduleWrite(e)}).pipe(p.uninterruptible))}recheck(e){let t=p.suspend(()=>(e.missing=void 0,e.lock.withPermit(p.gen({self:this},function*(){if(e.deleted||e.closed||this.closing)return!1;if((yield*this.disk(ee(e.path)))===null)return!0;return yield*this.pollLocked(e),!1})).pipe(p.flatMap((o)=>o?this.deleted(e):p.void))));return this.logged(`Could not check ${e.path}`,p.delay(t,tr))}flush(e){return p.suspend(()=>{let t=e.writing;return e.writing=void 0,p.andThen(t?ht.interrupt(t):p.void,this.flushNow(e))})}flushNow(e){return e.lock.withPermit(this.flushLocked(e))}flushLocked(e){let t,o=p.gen({self:this},function*(){if(e.deleted||e.closed)return;let n=yield*this.disk(ee(e.path));if(!n){if(!this.closing)yield*this.pollLocked(e);return}if(!To(e.stamp,n)){let r=yield*this.disk(fe(e.path)),i=e.doc.version;if(e.doc.mergeFile(r.text),e.format=r.format,e.stamp=r.stamp,e.doc.version!==i)this.notify(e)}if(e.doc.dirty)t=e.doc.toWrite(),e.stamp=yield*this.disk(Oe(e.path,t.text,e.format)),e.doc.wrote(t),t=void 0;yield*this.versions.record(e.path,e.doc.version,e.doc.text),yield*this.compact(e)}).pipe(p.onError(()=>p.sync(()=>{if(t)e.doc.notWritten(t)})));return this.logged(`Could not write ${e.path}`,p.uninterruptible(o))}compact(e){let t=e.doc.compact();return t?this.log.write(`Compacted ${e.path} at version ${e.doc.version} (${t.reason}: ${t.ops} ops, ${t.peers} peers), kept versions ${t.oldest}-${e.doc.version}`):p.void}deleted(e){return p.suspend(()=>(e.deleted=!0,this.notify(e),this.closeEntry(e)))}evict(e){return e.lock.withPermit(p.suspend(()=>{if(e.closed)return p.succeed(!0);if(e.watchers>0||e.waiters>0||!e.deleted&&e.doc.dirty)return p.succeed(!1);return p.as(this.closeLocked(e),!0)}))}closeEntry(e){return e.lock.withPermit(this.closeLocked(e))}closeLocked(e){return p.suspend(()=>{if(e.closed)return p.void;e.closed=!0;let t=[e.writing,e.missing].filter((o)=>o!==void 0);if(e.writing=void 0,e.missing=void 0,this.entries.get(e.path)===e)this.entries.delete(e.path),e.doc.free();return p.andThen(ht.interruptAll(t),mt.close(e.scope,Jn.void))})}close(){return p.gen({self:this},function*(){if(this.closing)return;this.closing=!0;for(let e of[...this.entries.values()])yield*this.flush(e),e.deleted=!0,this.notify(e),yield*this.closeEntry(e);yield*this.versions.flush()})}}class re extends Gn.Service()("@do-it/server/Documents"){}function ko(e,t){return Zn.effect(re,p.acquireRelease(gt.make(e,t),(o)=>o.close()))}import{Cause as or,Deferred as Do,Effect as x,Stream as nr}from"effect";function rr(e){return e.kind==="agent"?{_tag:"Agent",name:e.name}:e.kind==="user"?{_tag:"User"}:{_tag:"File"}}function ir(e){return e._tag==="Agent"?{kind:"agent",name:e.name}:{kind:e._tag==="User"?"user":"file"}}function Ao(e,t,o){let n=e.doc.changes(t,o);return n?{...n,by:n.by.map(rr)}:null}function sr(e,t){return nr.fromPull(x.gen(function*(){let{entry:o,release:n}=yield*e.withEntry(t.path,(d)=>x.gen(function*(){if(t.section!==void 0)yield*ne(()=>F(d.doc.text,t.section));return{entry:d,release:yield*ne(()=>e.subscribe(d))}}));yield*x.addFinalizer(()=>x.sync(n));let r=!0,i=!1,s=t.since??o.doc.version;return x.gen(function*(){if(i)return yield*or.done();while(!0){let d=o.signal;if(r){r=!1;let l=t.since!==void 0&&(s>o.doc.version||s<o.doc.version&&o.doc.textAt(s)===null);if(!l&&t.since!==void 0&&t.sinceHash!==void 0)l=(yield*e.hashAt(o,s))!==t.sinceHash;if(t.since===void 0||l)s=o.doc.version;let{version:u,text:f}=o.doc;return[{_tag:l?"Resync":"Watching",version:u,hash:yield*e.hashOf(o,u,f),...t.text?{text:f}:{}}]}if(o.deleted)return i=!0,[{_tag:"Deleted"}];if(o.doc.version>s){let l=Ao(o,s,t.section);s=o.doc.version;let u=o.doc.text,f=yield*e.hashOf(o,s,u);return[l?{_tag:"Change",...l,hash:f,...t.text?{text:u}:{}}:{_tag:"Resync",version:s,hash:f,...t.text?{text:u}:{}}]}yield*Do.await(d)}})}))}function cr(e,t){return e.withEntry(t.path,(o)=>x.gen(function*(){if(t.baseHash!==void 0&&(yield*e.hashAt(o,t.base))!==t.baseHash)return yield*x.fail(new X({latest:o.doc.version}));if(o.deleted||o.closed)return yield*x.fail(new N({path:o.path}));let n=o.doc.version;yield*ne(()=>o.doc.userEdit(t.base,t.text),()=>o.doc.version);let{version:r,text:i}=o.doc;return yield*e.changed(o,n),{version:r,text:i,hash:yield*e.hashOf(o,r,i)}}))}function Ro(e,t){return Ce.toLayer(x.gen(function*(){let o=yield*re;return{status:()=>x.sync(()=>({pid:process.pid,appVersion:ae,port:t.port,startedAt:t.startedAt,build:t.build,documents:o.status()})).pipe(x.withSpan("rpc.status")),stop:x.fn("rpc.stop")(function*(){return yield*t.stop,{}}),open:(n)=>o.withOpened(n.path,({entry:r,created:i})=>x.gen(function*(){let s=r.doc.version,a=yield*e.mint(r.path);return{path:r.path,version:s,created:i,url:`http://127.0.0.1:${t.port}/auth?code=${a}&path=${encodeURIComponent(r.path)}`}}),n.create).pipe(x.withSpan("rpc.open")),read:(n)=>o.withEntry(n.path,(r)=>x.gen(function*(){let{version:i,text:s}=r.doc,a=n.section===void 0?null:yield*ne(()=>F(s,n.section));return{version:i,text:a?s.slice(a.from,a.to):s,hash:yield*o.hashOf(r,i,s)}})).pipe(x.withSpan("rpc.read")),sections:(n)=>o.withEntry(n.path,(r)=>x.sync(()=>({version:r.doc.version,sections:_(r.doc.text).map(({id:i,title:s,level:a,line:d})=>({id:i,title:s,level:a,line:d}))}))).pipe(x.withSpan("rpc.sections")),todos:(n)=>o.withEntry(n.path,(r)=>ne(()=>{let i=Q(r.doc.text);if(n.section===void 0)return{version:r.doc.version,todos:i};let s=F(r.doc.text,n.section),a=_(r.doc.text).find((d)=>d.from===s.to)?.line??1/0;return{version:r.doc.version,todos:i.filter((d)=>d.line>=s.line&&d.line<a)}})).pipe(x.withSpan("rpc.todos")),edit:(n)=>o.withEntry(n.path,(r)=>x.gen(function*(){let i=r.doc.version,s=yield*ne(()=>r.doc.agentEdit(ir(n.author),{version:n.version,section:n.section},(a)=>{let d=n.op;switch(d._tag){case"Replace":return tt(a,d.old,d.new,n.section);case"Append":return De(a,d.text,n.section);case"TodoAdd":return et(a,d.text,n.section);case"TodoSet":return qe(a,ke(a,d.target,n.section),d.state,new Date);case"TodoStart":return Qe(a,ke(a,d.target,n.section),"started",new Date)}}),()=>r.doc.version);return yield*o.changed(r,i),{...s,section:n.section??null}})).pipe(x.withSpan("rpc.edit")),sync:(n)=>cr(o,n).pipe(x.withSpan("rpc.sync")),diff:(n)=>o.withEntry(n.path,(r)=>x.gen(function*(){let i=Date.now()+n.wait*1000;while(!0){let s=r.signal;if(r.deleted)return yield*x.fail(new N({path:r.path}));let a=Ao(r,n.since);if(!a)return yield*x.fail(new X({latest:r.doc.version}));if(r.doc.version!==n.since)return a;let d=i-Date.now();if(d<=0)return yield*x.fail(new Pe({}));yield*Do.await(s).pipe(x.timeoutOption(d))}})).pipe(x.withSpan("rpc.diff")),watch:(n)=>sr(o,n)}}))}import{Data as ar,Effect as C,FileSystem as Oo}from"effect";import{SecureRandom as dr}from"effect/crypto";class yt extends ar.TaggedError("StateFileError"){}var me=C.fn("readJson")(function*(e,t=2097152){let o=yield*Oo.FileSystem,n=yield*C.scoped(C.gen(function*(){let r=yield*o.open(e,{flag:"r"}),i=new Uint8Array(t+1),s=0;while(s<=t){let a=yield*r.read(i.subarray(s));if(a===0)return i.subarray(0,s);s+=a}return yield*C.fail(new yt({message:`State file exceeds ${t} bytes`}))}));return yield*C.try({try:()=>JSON.parse(new TextDecoder().decode(n)),catch:(r)=>new yt({message:`State file is not JSON: ${String(r)}`})})}),Fo=C.fn("writeJson")(function*(e,t){let o=yield*Oo.FileSystem,n=`${e}.${dr.uuidV4Unsafe()}.tmp`,r=!1;yield*C.scoped(C.gen(function*(){let i=yield*o.open(n,{flag:"wx",mode:384});r=!0,yield*i.writeAll(new TextEncoder().encode(JSON.stringify(t)+`
|
|
11
|
+
`):t));yield*nt(i.length);let s=r.join(r.dirname(e),`.${r.basename(e)}.do-it-${an.uuidV4Unsafe()}.tmp`),a=yield*n.stat(e).pipe(S.map((u)=>u.mode&4095),S.catchIf(K,()=>S.succeed(void 0))),d=!1;return yield*S.scoped(S.gen(function*(){let u=yield*n.open(s,{flag:"wx",mode:a??438});if(d=!0,yield*u.writeAll(i),a!==void 0)yield*n.chmod(s,a);return yield*u.sync,it(yield*u.stat)})).pipe(S.tap(()=>n.rename(s,e)),S.onError(()=>d?S.ignore(n.remove(s)):S.void))},(e,t)=>S.mapError(e,(o)=>Re(t,o)));import{Schema as c}from"effect";var Wt=2097152,dn=4096,E=c.String.pipe(c.check(c.isMaxLength(Wt))),st=c.String.pipe(c.check(c.isMaxLength(dn))),P=c.String.pipe(c.check(c.makeFilter((e)=>e.length>0&&e.startsWith("/")?void 0:"an absolute, non-empty path is required"),c.isMaxLength(Wt))),T=c.Int.pipe(c.check(c.isGreaterThan(0))),te=c.String.pipe(c.check(c.isPattern(/^[a-f0-9]{64}$/))),ln=c.TaggedStruct("User",{}),pn=c.TaggedStruct("Agent",{name:c.String.pipe(c.check(c.isBetweenLength(1,64),c.isPattern(/^[A-Za-z0-9._-]+$/)))}),fn=c.TaggedStruct("File",{}),ct=c.Union([ln,pn,fn]),Ut=c.Literals(["open","done","cancelled"]),un=c.Struct({number:c.Number,line:c.Number,state:Ut,text:st,indent:c.Number,section:c.NullOr(E)}),Bt=c.Struct({kind:c.Literals(["added","removed","checked","unchecked","cancelled","uncancelled","edited"]),number:c.Number,text:st,line:c.Number,oldLine:c.Number}),hn=c.Struct({id:E,title:E,level:c.Literals([1,2,3]),line:c.Number}),mn=c.Struct({since:T,diff:c.NullOr(E)}),gn=c.TaggedStruct("Replace",{old:E,new:E}),yn=c.TaggedStruct("Append",{text:E}),xn=c.TaggedStruct("TodoAdd",{text:st}),vn=c.TaggedStruct("TodoSet",{target:c.Union([c.Number,E]),state:Ut}),Sn=c.TaggedStruct("TodoStart",{target:c.Union([c.Number,E])}),En=c.Union([gn,yn,xn,vn,Sn]),Fe=c.optional(E),Ht=c.Struct({}),wn=c.Struct({path:P,version:T,watchers:c.Number}),zt=c.Struct({pid:c.Number,appVersion:E,port:c.Number,startedAt:c.Number,build:c.optionalKey(E),documents:c.Array(wn)}),jt=c.Struct({}),Kt=c.Struct({}),Xt=c.Struct({path:P,create:c.Boolean}),Gt=c.Struct({path:P,version:T,url:E,created:c.Boolean}),Jt=c.Struct({path:P,section:Fe}),Yt=c.Struct({version:T,text:E,hash:c.optionalKey(te)}),Zt=c.Struct({path:P}),Qt=c.Struct({version:T,sections:c.Array(hn)}),qt=c.Struct({path:P,section:Fe}),eo=c.Struct({version:T,todos:c.Array(un)}),to=c.Struct({path:P,author:ct,version:T,section:Fe,op:En}),oo=c.Struct({version:T,changed:c.Boolean,section:c.NullOr(E),others:c.NullOr(mn)}),no=c.Struct({path:P,base:T,text:E,baseHash:c.optionalKey(te)}),ro=c.Struct({version:T,text:E,hash:c.optionalKey(te)}),io=c.Struct({path:P,since:T,wait:c.Number.pipe(c.check(c.isBetween({minimum:0,maximum:600})))}),so=c.Struct({from:T,to:T,by:c.Array(ct),diff:c.NullOr(E),todos:c.Array(Bt)}),co=c.Struct({path:P,since:c.optional(T),sinceHash:c.optionalKey(te),section:Fe,text:c.Boolean}),ao=c.TaggedStruct("Watching",{version:T,hash:c.optionalKey(te),text:c.optionalKey(E)}),lo=c.TaggedStruct("Change",{from:T,to:T,by:c.Array(ct),diff:c.NullOr(E),todos:c.Array(Bt),hash:c.optionalKey(te),text:c.optionalKey(E)}),po=c.TaggedStruct("Resync",{version:T,hash:c.optionalKey(te),text:c.optionalKey(E)}),fo=c.TaggedStruct("Deleted",{}),uo=c.Union([ao,lo,po,fo]);import{Schema as M}from"effect";class ue extends M.TaggedError()("VersionMismatch",{latest:T,since:T,diff:M.NullOr(E),forgotten:M.Boolean}){}var Tn=M.Literals(["not-found","ambiguous","too-large","invalid"]);class R extends M.TaggedError()("Refused",{code:Tn,message:E}){}class X extends M.TaggedError()("Forgotten",{latest:T}){}class Pe extends M.TaggedError()("NothingYet",{}){}class N extends M.TaggedError()("DocumentGone",{path:P}){}class ho extends M.TaggedError()("Unauthorized",{}){}var I=M.Union([ue,R,X,Pe,N,ho]);import{Rpc as V,RpcGroup as bn}from"effect/rpc";var kn=V.make("status",{payload:Ht,success:zt,error:I}),Dn=V.make("stop",{payload:jt,success:Kt,error:I}),An=V.make("open",{payload:Xt,success:Gt,error:I}),Rn=V.make("read",{payload:Jt,success:Yt,error:I}),On=V.make("sections",{payload:Zt,success:Qt,error:I}),Fn=V.make("todos",{payload:qt,success:eo,error:I}),Pn=V.make("edit",{payload:to,success:oo,error:I}),Cn=V.make("sync",{payload:no,success:ro,error:I}),$n=V.make("diff",{payload:io,success:so,error:I}),_n=V.make("watch",{payload:co,success:uo,error:I,stream:!0}),Ce=bn.make(kn,Dn,An,Rn,On,Fn,Pn,Cn,$n,_n);var ae="0.1.3";import{BunHttpServer as _o,BunHttpServerRequest as br}from"@effect/platform-bun";import{Deferred as xt,Effect as m,FileSystem as kr,Layer as ye,Path as Dr,Schedule as Ar}from"effect";import{HttpEffect as Rr,HttpRouter as Or,HttpServerRequest as No,HttpServerResponse as D}from"effect/http";import{RpcSerialization as Fr,RpcServer as Pr}from"effect/rpc";import{Data as Nn,Effect as oe,FileSystem as In,Path as Ln}from"effect";import{Mac as $e,SecureRandom as Mn}from"effect/crypto";import{Hex as at}from"effect/encoding";var _e=new TextEncoder,Vn=300000;class go extends Nn.TaggedError("InvalidToken"){}function mo(e,t){let o=_e.encode(e),n=_e.encode(t);return o.length===n.length&&$e.timingSafeEqual(o,n)}function Wn(e){return e.reason._tag==="AlreadyExists"}class Ne{token;cookie;random;codes=new Map;constructor(e,t,o){this.token=e;this.cookie=t;this.random=o}static make=oe.fn("Auth.make")(function*(e){let t=yield*In.FileSystem,o=yield*Ln.Path,n=yield*Mn.SecureRandom,r=yield*$e.Mac,i=o.join(e,"token"),s=at.encode(yield*n.randomBytes(32));yield*t.writeFileString(i,s,{flag:"wx",mode:384}).pipe(oe.catchIf(Wn,()=>oe.void));let a=new go({message:"The daemon token file is invalid"});if(Number((yield*t.stat(i)).size)>128)return yield*oe.fail(a);let d=(yield*t.readFileString(i)).trim();if(!/^[a-f0-9]{64}$/.test(d))return yield*oe.fail(a);yield*t.chmod(i,384);let l=yield*$e.key($e.hmacSha256,_e.encode(d)),u=at.encode(yield*r.sign(l,_e.encode("web cookie")));return new Ne(d,u,n)});authorized(e,t){return e!==void 0&&mo(e,`Bearer ${this.token}`)||t!==void 0&&mo(t,this.cookie)}mint(e,t=Date.now()){return oe.gen({self:this},function*(){for(let[n,r]of this.codes)if(r.until<=t)this.codes.delete(n);if(this.codes.size>=64)return yield*oe.fail(new R({code:"too-large",message:"Too many unused browser sign-in codes, use an existing link or wait five minutes"}));let o=at.encode(yield*this.random.randomBytes(32));return this.codes.set(o,{path:e,until:t+Vn}),o})}consume(e,t,o=Date.now()){let n=this.codes.get(e);if(!n)return!1;if(n.until<=o)return this.codes.delete(e),!1;if(n.path!==t)return!1;return this.codes.delete(e),!0}}import{Config as dt,Data as Un,Effect as he,Option as Bn,Path as Hn}from"effect";var Le=4194304,yo=["# TODO","","Write one task per line under Tasks. Agents pick up each new line, plan it, and write their status under it. Keys say Cmd and Alt: on Windows and Linux use Ctrl for Cmd, and Alt is Option on a Mac. Delete the Example section once you know your way around.","","## Example","","- [ ] Fix the login redirect @high @today"," - **t0** M, building with Opus","- [ ] Pick a chart library for the stats page @due(friday)"," - **t0** waiting for you"," - **t0 question:** Recharts or Visx? You answer by typing under a question like this","- [x] Cmd-Enter starts a task, and Cmd-D or a click on its box marks it done"," Shift-Enter adds a note like this one, which moves and archives with its task","- [-] Cmd-Alt-Enter or Alt-click cancels a task","- [x] Cmd-Shift-A moves done and cancelled tasks to Archive","- [x] Type t, h, or d then Tab for the today, high, or due tag; s then Tab twice stamps a start time","- [x] Cmd-/ lists every key","","## Tasks","","- [ ] ",""].join(`
|
|
12
|
+
`);class lt extends Un.TaggedError("InvalidConfig"){}var zn=new URL("../web",import.meta.url),Ie=(e)=>dt.option(dt.String(e)).pipe(he.map(Bn.getOrUndefined)),xo=he.gen(function*(){let e=yield*Hn.Path,t=Number((yield*Ie("DO_IT_PORT"))??47100),o=Number((yield*Ie("DO_IT_IDLE_MS"))??1800000);if(!Number.isInteger(t)||t<0||t>65535)return yield*he.fail(new lt({message:"DO_IT_PORT must be between 0 and 65535"}));if(!Number.isFinite(o)||o<=0)return yield*he.fail(new lt({message:"DO_IT_IDLE_MS must be positive"}));let n=(yield*Ie("DO_IT_HOME"))??e.join(yield*dt.String("HOME"),".do-it"),r=(yield*Ie("DO_IT_WEB_DIST"))??(yield*e.fromFileUrl(zn));return{home:e.resolve(n),port:t,idleMs:o,webDist:e.resolve(r)}});var ys=new URL("../../../",import.meta.url),vo=he.succeed("36d2353.mw8d2srx");import{Cause as Eo,Context as Gn,Deferred as ut,Effect as p,Exit as Jn,Fiber as ht,FileSystem as Yn,Layer as Zn,Path as Qn,Scope as mt,Semaphore as wo}from"effect";import{Deferred as pt,Effect as G,Fiber as jn,FileSystem as Kn,Path as Xn,Stream as ft}from"effect";var So=G.fn("watchFile")(function*(e,t){let o=yield*Kn.FileSystem,n=yield*Xn.Path,r=n.basename(e),i=yield*pt.make(),s=yield*o.watch(n.dirname(e),{onReady:()=>pt.doneUnsafe(i,G.void)}).pipe(ft.filter((a)=>n.basename(a.path)===r),ft.debounce("50 millis"),ft.runForEach(()=>t),G.ignore,G.forkScoped);yield*t.pipe(G.delay("2 seconds"),G.forever,G.forkScoped),yield*G.race(pt.await(i),jn.await(s))});var qn=64,er="25 millis",tr="500 millis";function bo(e,t=1){if(e instanceof y){if(e.code==="conflict"&&e.conflict){let o=e.conflict;return new ue({latest:o.current,since:o.expected,diff:o.diff,forgotten:o.forgotten})}if(e.code==="forgotten")return new X({latest:t});return new R({code:e.code==="conflict"?"invalid":e.code,message:e.message})}if(e instanceof R||e instanceof N||e instanceof X||e instanceof ue)return e;return new R({code:"invalid",message:e instanceof Error?e.message:String(e)})}function ne(e,t){return p.try({try:e,catch:(o)=>bo(o,t?.())})}function To(e,t){return e.mtimeMs===t.mtimeMs&&e.ino===t.ino&&e.size===t.size}class gt{versions;log;context;scope;entries=new Map;opening=wo.makeUnsafe(1);totalWatchers=0;closing=!1;constructor(e,t,o,n){this.versions=e;this.log=t;this.context=o;this.scope=n}static make=p.fn("DocumentRegistry.make")(function*(e,t){return new gt(e,t,yield*p.context(),yield*p.scope)});disk(e){return p.provideContext(e,this.context)}logged(e,t){return t.pipe(p.catchCause((o)=>Eo.hasInterruptsOnly(o)?p.failCause(o):this.log.write(`${e}: ${String(Eo.squash(o))}`)))}validate(e){return this.disk(p.gen({self:this},function*(){let t=yield*Qn.Path,o=yield*Yn.FileSystem;if(!t.isAbsolute(e)||![".md",".markdown"].includes(t.extname(e).toLowerCase()))return yield*p.fail(new R({code:"invalid",message:"The path must be absolute and end in .md or .markdown"}));let n=t.resolve(e);if(!(yield*o.stat(t.dirname(n)).pipe(p.map((i)=>i.type==="Directory"),p.catch(()=>p.succeed(!1))))){if(this.entries.has(n)||this.versions.has(n))return yield*p.fail(new N({path:n}));return yield*p.fail(new R({code:"invalid",message:"The document's parent directory must exist"}))}return n}))}open(e,t=!1){return this.acquire(e,t,!1)}get(e){return p.map(this.open(e),({entry:t})=>t)}withEntry(e,t,o=!1){return this.withOpened(e,({entry:n})=>t(n),o)}withOpened(e,t,o=!1){return p.acquireUseRelease(this.acquire(e,o,!0),t,({entry:n})=>p.sync(()=>{n.waiters--,n.lastUsed=Date.now()}))}acquire(e,t,o){return p.gen({self:this},function*(){let n=yield*this.validate(e);while(!0){if(this.closing)return yield*p.fail(new R({code:"invalid",message:"The daemon is shutting down"}));let r=this.entries.get(n),i=r?yield*this.reuse(r):yield*this.opening.withPermit(p.suspend(()=>{let s=this.entries.get(n);return s?this.reuse(s):this.create(n,t)}));if(i.entry.closed)continue;if(o)i.entry.waiters++;return i}}).pipe(p.mapError((n)=>bo(n)))}status(){return[...this.entries.values()].map((e)=>({path:e.path,version:e.doc.version,watchers:e.watchers}))}reuse(e){return e.lastUsed=Date.now(),this.disk(ee(e.path)).pipe(p.flatMap((t)=>e.deleted||t===null?p.fail(new N({path:e.path})):p.succeed({entry:e,created:!1})))}create(e,t){let o=p.gen({self:this},function*(){let n=(yield*this.disk(ee(e)))===null;if(n&&!t){if(this.versions.has(e))return yield*p.fail(new N({path:e}));return yield*p.fail(new R({code:"invalid",message:"The document does not exist, open it with create enabled"}))}if(yield*this.makeRoom(),n)yield*this.disk(Oe(e,yo,{bom:!1,crlf:!1}));return{missing:n,file:yield*this.disk(fe(e))}});return p.uninterruptibleMask((n)=>p.gen({self:this},function*(){let{missing:r,file:i}=yield*n(o),s=yield*this.versions.open(e,i.text),a={path:e,doc:s,signal:ut.makeUnsafe(),format:i.format,stamp:i.stamp,watchers:0,waiters:0,lastUsed:Date.now(),deleted:!1,closed:!1,lock:wo.makeUnsafe(1),scope:yield*mt.fork(this.scope),writing:void 0,missing:void 0,hash:void 0};return this.entries.set(e,a),yield*this.disk(So(e,this.poll(a))).pipe(mt.provide(a.scope)),{entry:a,created:r}}))}makeRoom(){return p.gen({self:this},function*(){if(this.entries.size<qn)return;let e=[...this.entries.values()].filter((t)=>t.watchers===0&&t.waiters===0).sort((t,o)=>t.lastUsed-o.lastUsed);for(let t of e)if(yield*this.flush(t),yield*this.evict(t))return;return yield*p.fail(new R({code:"too-large",message:"At most 64 documents may be open, all available documents are busy"}))})}notify(e){let t=e.signal;e.signal=ut.makeUnsafe(),ut.doneUnsafe(t,p.void)}changed(e,t){if(e.doc.version===t)return p.void;return e.lastUsed=Date.now(),this.notify(e),p.andThen(this.versions.record(e.path,e.doc.version,e.doc.text),this.scheduleWrite(e))}hashOf(e,t,o){return p.suspend(()=>{if(e.hash?.version===t)return p.succeed(e.hash.value);return p.tap(this.versions.sha256(o),(n)=>p.sync(()=>{if(t>(e.hash?.version??0))e.hash={version:t,value:n}}))})}hashAt(e,t){return p.suspend(()=>{if(e.hash?.version===t)return p.succeed(e.hash.value);let o=e.doc.textAt(t);return o===null?p.succeed(null):this.hashOf(e,t,o)})}subscribe(e){if(e.deleted)throw new N({path:e.path});if(e.watchers>=32||this.totalWatchers>=256)throw new R({code:"too-large",message:"The document or daemon watch stream limit has been reached"});return e.watchers++,this.totalWatchers++,()=>{e.watchers--,this.totalWatchers--,e.lastUsed=Date.now()}}scheduleWrite(e){return p.suspend(()=>{if(e.writing||this.closing||e.deleted||e.closed||!e.doc.dirty)return p.void;let t=p.suspend(()=>(e.writing=void 0,this.flushNow(e)));return p.forkIn(p.delay(t,er),this.scope).pipe(p.map((o)=>{e.writing=o}))})}poll(e){return e.lock.withPermit(this.pollLocked(e))}pollLocked(e){return this.logged(`Could not merge ${e.path}`,p.gen({self:this},function*(){if(e.deleted||e.closed||this.closing)return;let t=yield*this.disk(ee(e.path));if(!t){if(!e.missing)e.missing=yield*p.forkIn(this.recheck(e),this.scope);return}if(e.missing){let o=e.missing;e.missing=void 0,yield*ht.interrupt(o)}if(!To(e.stamp,t)){let o=yield*this.disk(fe(e.path)),n=e.doc.version;e.doc.mergeFile(o.text),e.format=o.format,e.stamp=o.stamp,yield*this.changed(e,n),yield*this.versions.record(e.path,e.doc.version,e.doc.text),yield*this.compact(e)}yield*this.scheduleWrite(e)}).pipe(p.uninterruptible))}recheck(e){let t=p.suspend(()=>(e.missing=void 0,e.lock.withPermit(p.gen({self:this},function*(){if(e.deleted||e.closed||this.closing)return!1;if((yield*this.disk(ee(e.path)))===null)return!0;return yield*this.pollLocked(e),!1})).pipe(p.flatMap((o)=>o?this.deleted(e):p.void))));return this.logged(`Could not check ${e.path}`,p.delay(t,tr))}flush(e){return p.suspend(()=>{let t=e.writing;return e.writing=void 0,p.andThen(t?ht.interrupt(t):p.void,this.flushNow(e))})}flushNow(e){return e.lock.withPermit(this.flushLocked(e))}flushLocked(e){let t,o=p.gen({self:this},function*(){if(e.deleted||e.closed)return;let n=yield*this.disk(ee(e.path));if(!n){if(!this.closing)yield*this.pollLocked(e);return}if(!To(e.stamp,n)){let r=yield*this.disk(fe(e.path)),i=e.doc.version;if(e.doc.mergeFile(r.text),e.format=r.format,e.stamp=r.stamp,e.doc.version!==i)this.notify(e)}if(e.doc.dirty)t=e.doc.toWrite(),e.stamp=yield*this.disk(Oe(e.path,t.text,e.format)),e.doc.wrote(t),t=void 0;yield*this.versions.record(e.path,e.doc.version,e.doc.text),yield*this.compact(e)}).pipe(p.onError(()=>p.sync(()=>{if(t)e.doc.notWritten(t)})));return this.logged(`Could not write ${e.path}`,p.uninterruptible(o))}compact(e){let t=e.doc.compact();return t?this.log.write(`Compacted ${e.path} at version ${e.doc.version} (${t.reason}: ${t.ops} ops, ${t.peers} peers), kept versions ${t.oldest}-${e.doc.version}`):p.void}deleted(e){return p.suspend(()=>(e.deleted=!0,this.notify(e),this.closeEntry(e)))}evict(e){return e.lock.withPermit(p.suspend(()=>{if(e.closed)return p.succeed(!0);if(e.watchers>0||e.waiters>0||!e.deleted&&e.doc.dirty)return p.succeed(!1);return p.as(this.closeLocked(e),!0)}))}closeEntry(e){return e.lock.withPermit(this.closeLocked(e))}closeLocked(e){return p.suspend(()=>{if(e.closed)return p.void;e.closed=!0;let t=[e.writing,e.missing].filter((o)=>o!==void 0);if(e.writing=void 0,e.missing=void 0,this.entries.get(e.path)===e)this.entries.delete(e.path),e.doc.free();return p.andThen(ht.interruptAll(t),mt.close(e.scope,Jn.void))})}close(){return p.gen({self:this},function*(){if(this.closing)return;this.closing=!0;for(let e of[...this.entries.values()])yield*this.flush(e),e.deleted=!0,this.notify(e),yield*this.closeEntry(e);yield*this.versions.flush()})}}class re extends Gn.Service()("@do-it/server/Documents"){}function ko(e,t){return Zn.effect(re,p.acquireRelease(gt.make(e,t),(o)=>o.close()))}import{Cause as or,Deferred as Do,Effect as x,Stream as nr}from"effect";function rr(e){return e.kind==="agent"?{_tag:"Agent",name:e.name}:e.kind==="user"?{_tag:"User"}:{_tag:"File"}}function ir(e){return e._tag==="Agent"?{kind:"agent",name:e.name}:{kind:e._tag==="User"?"user":"file"}}function Ao(e,t,o){let n=e.doc.changes(t,o);return n?{...n,by:n.by.map(rr)}:null}function sr(e,t){return nr.fromPull(x.gen(function*(){let{entry:o,release:n}=yield*e.withEntry(t.path,(d)=>x.gen(function*(){if(t.section!==void 0)yield*ne(()=>F(d.doc.text,t.section));return{entry:d,release:yield*ne(()=>e.subscribe(d))}}));yield*x.addFinalizer(()=>x.sync(n));let r=!0,i=!1,s=t.since??o.doc.version;return x.gen(function*(){if(i)return yield*or.done();while(!0){let d=o.signal;if(r){r=!1;let l=t.since!==void 0&&(s>o.doc.version||s<o.doc.version&&o.doc.textAt(s)===null);if(!l&&t.since!==void 0&&t.sinceHash!==void 0)l=(yield*e.hashAt(o,s))!==t.sinceHash;if(t.since===void 0||l)s=o.doc.version;let{version:u,text:f}=o.doc;return[{_tag:l?"Resync":"Watching",version:u,hash:yield*e.hashOf(o,u,f),...t.text?{text:f}:{}}]}if(o.deleted)return i=!0,[{_tag:"Deleted"}];if(o.doc.version>s){let l=Ao(o,s,t.section);s=o.doc.version;let u=o.doc.text,f=yield*e.hashOf(o,s,u);return[l?{_tag:"Change",...l,hash:f,...t.text?{text:u}:{}}:{_tag:"Resync",version:s,hash:f,...t.text?{text:u}:{}}]}yield*Do.await(d)}})}))}function cr(e,t){return e.withEntry(t.path,(o)=>x.gen(function*(){if(t.baseHash!==void 0&&(yield*e.hashAt(o,t.base))!==t.baseHash)return yield*x.fail(new X({latest:o.doc.version}));if(o.deleted||o.closed)return yield*x.fail(new N({path:o.path}));let n=o.doc.version;yield*ne(()=>o.doc.userEdit(t.base,t.text),()=>o.doc.version);let{version:r,text:i}=o.doc;return yield*e.changed(o,n),{version:r,text:i,hash:yield*e.hashOf(o,r,i)}}))}function Ro(e,t){return Ce.toLayer(x.gen(function*(){let o=yield*re;return{status:()=>x.sync(()=>({pid:process.pid,appVersion:ae,port:t.port,startedAt:t.startedAt,build:t.build,documents:o.status()})).pipe(x.withSpan("rpc.status")),stop:x.fn("rpc.stop")(function*(){return yield*t.stop,{}}),open:(n)=>o.withOpened(n.path,({entry:r,created:i})=>x.gen(function*(){let s=r.doc.version,a=yield*e.mint(r.path);return{path:r.path,version:s,created:i,url:`http://127.0.0.1:${t.port}/auth?code=${a}&path=${encodeURIComponent(r.path)}`}}),n.create).pipe(x.withSpan("rpc.open")),read:(n)=>o.withEntry(n.path,(r)=>x.gen(function*(){let{version:i,text:s}=r.doc,a=n.section===void 0?null:yield*ne(()=>F(s,n.section));return{version:i,text:a?s.slice(a.from,a.to):s,hash:yield*o.hashOf(r,i,s)}})).pipe(x.withSpan("rpc.read")),sections:(n)=>o.withEntry(n.path,(r)=>x.sync(()=>({version:r.doc.version,sections:_(r.doc.text).map(({id:i,title:s,level:a,line:d})=>({id:i,title:s,level:a,line:d}))}))).pipe(x.withSpan("rpc.sections")),todos:(n)=>o.withEntry(n.path,(r)=>ne(()=>{let i=Q(r.doc.text);if(n.section===void 0)return{version:r.doc.version,todos:i};let s=F(r.doc.text,n.section),a=_(r.doc.text).find((d)=>d.from===s.to)?.line??1/0;return{version:r.doc.version,todos:i.filter((d)=>d.line>=s.line&&d.line<a)}})).pipe(x.withSpan("rpc.todos")),edit:(n)=>o.withEntry(n.path,(r)=>x.gen(function*(){let i=r.doc.version,s=yield*ne(()=>r.doc.agentEdit(ir(n.author),{version:n.version,section:n.section},(a)=>{let d=n.op;switch(d._tag){case"Replace":return tt(a,d.old,d.new,n.section);case"Append":return De(a,d.text,n.section);case"TodoAdd":return et(a,d.text,n.section);case"TodoSet":return qe(a,ke(a,d.target,n.section),d.state,new Date);case"TodoStart":return Qe(a,ke(a,d.target,n.section),"started",new Date)}}),()=>r.doc.version);return yield*o.changed(r,i),{...s,section:n.section??null}})).pipe(x.withSpan("rpc.edit")),sync:(n)=>cr(o,n).pipe(x.withSpan("rpc.sync")),diff:(n)=>o.withEntry(n.path,(r)=>x.gen(function*(){let i=Date.now()+n.wait*1000;while(!0){let s=r.signal;if(r.deleted)return yield*x.fail(new N({path:r.path}));let a=Ao(r,n.since);if(!a)return yield*x.fail(new X({latest:r.doc.version}));if(r.doc.version!==n.since)return a;let d=i-Date.now();if(d<=0)return yield*x.fail(new Pe({}));yield*Do.await(s).pipe(x.timeoutOption(d))}})).pipe(x.withSpan("rpc.diff")),watch:(n)=>sr(o,n)}}))}import{Data as ar,Effect as C,FileSystem as Oo}from"effect";import{SecureRandom as dr}from"effect/crypto";class yt extends ar.TaggedError("StateFileError"){}var me=C.fn("readJson")(function*(e,t=2097152){let o=yield*Oo.FileSystem,n=yield*C.scoped(C.gen(function*(){let r=yield*o.open(e,{flag:"r"}),i=new Uint8Array(t+1),s=0;while(s<=t){let a=yield*r.read(i.subarray(s));if(a===0)return i.subarray(0,s);s+=a}return yield*C.fail(new yt({message:`State file exceeds ${t} bytes`}))}));return yield*C.try({try:()=>JSON.parse(new TextDecoder().decode(n)),catch:(r)=>new yt({message:`State file is not JSON: ${String(r)}`})})}),Fo=C.fn("writeJson")(function*(e,t){let o=yield*Oo.FileSystem,n=`${e}.${dr.uuidV4Unsafe()}.tmp`,r=!1;yield*C.scoped(C.gen(function*(){let i=yield*o.open(n,{flag:"wx",mode:384});r=!0,yield*i.writeAll(new TextEncoder().encode(JSON.stringify(t)+`
|
|
13
13
|
`)),yield*i.sync})).pipe(C.andThen(o.rename(n,e)),C.onError(()=>r?C.ignore(o.remove(n)):C.void))});import{Effect as ge,FileSystem as lr,Option as pr,Path as fr,Semaphore as ur}from"effect";var hr=1048576,mr=16384;class Me{fs;path;lock=ur.makeUnsafe(1);constructor(e,t){this.fs=e;this.path=t}static make=ge.fn("Log.make")(function*(e){let t=yield*lr.FileSystem,o=yield*fr.Path;return new Me(t,o.join(e,"daemon.log"))});rotate(){return this.lock.withPermit(this.rotateUnlocked())}write(e){let{fs:t,path:o}=this;return this.lock.withPermit(ge.gen({self:this},function*(){yield*this.rotateUnlocked();let n=new TextEncoder().encode(`${new Date().toISOString()} ${e}
|
|
14
14
|
`);if(n.length<=mr)yield*t.writeFile(o,n,{flag:"a",mode:384})})).pipe(ge.ignore)}rotateUnlocked(){let{fs:e,path:t}=this;return ge.gen(function*(){let o=yield*e.stat(t).pipe(ge.option);if(pr.isNone(o)||Number(o.value.size)<hr)return;yield*e.copyFile(t,`${t}.old`),yield*e.truncate(t,0)})}}function Po(e){try{return process.kill(e,0),!0}catch(t){return t.code==="EPERM"}}import{Effect as Ve,FileSystem as gr,Option as yr,Path as xr}from"effect";import{HttpServerResponse as de,Mime as vr}from"effect/http";var Co=Ve.fn("staticResponse")(function*(e,t){let o=yield*gr.FileSystem,n=yield*xr.Path;if(!(yield*o.exists(n.resolve(e,"index.html"))))return de.html("<!doctype html><p>Run <code>bun run build:web</code> to build the web app.</p>");let r;try{r=decodeURIComponent(t)}catch{return de.text("Invalid path",{status:400})}if(r.includes("\x00")||r.includes("\\"))return de.text("Invalid path",{status:400});let i=yield*o.realPath(e),s=n.resolve(i,`.${r}`);if(s!==i&&!s.startsWith(i+n.sep))return de.text("Forbidden",{status:403});if(!(yield*o.stat(s).pipe(Ve.map((d)=>d.type==="File"),Ve.catch(()=>Ve.succeed(!1)))))s=n.resolve(i,"index.html");if(s=yield*o.realPath(s),!s.startsWith(i+n.sep))return de.text("Forbidden",{status:403});return yield*de.file(s,{contentType:yr.getOrElse(vr.getType(s),()=>"application/octet-stream")})});import{Effect as k,Fiber as Sr,Path as Er}from"effect";import{Digest as $o}from"effect/crypto";var wr=256,Tr=new TextEncoder;class We{path;log;digest;context;scope;entries=new Map;timer;constructor(e,t,o,n,r){this.path=e;this.log=t;this.digest=o;this.context=n;this.scope=r}static make=k.fn("Versions.make")(function*(e,t){let o=yield*Er.Path,n=new We(o.join(e,"versions.json"),t,yield*$o.Digest,yield*k.context(),yield*k.scope),r=yield*me(n.path).pipe(k.catch((i)=>K(i)?k.succeed(void 0):k.as(t.write("Could not read versions.json, starting with fresh version records"),void 0)));if(r&&typeof r==="object"&&!Array.isArray(r))for(let[i,s]of Object.entries(r)){let a=s;if(a&&Number.isSafeInteger(a.version)&&a.version>0&&typeof a.sha256==="string"&&/^[a-f0-9]{64}$/.test(a.sha256))n.entries.set(i,a),n.trim()}return n});sha256(e){return this.digest.digestHex($o.sha256,Tr.encode(e))}has(e){return this.entries.has(e)}open(e,t){return k.gen({self:this},function*(){let o=this.entries.get(e),n;if(o&&o.sha256!==(yield*this.sha256(t)))n=ce.open(t===""?`
|
|
15
15
|
`:"",o.version),n.mergeFile(t),n.compact(0,0,1);else n=ce.open(t,o?.version);return yield*this.record(e,n.version,t),n})}record(e,t,o){return k.gen({self:this},function*(){let n=yield*this.sha256(o);if(this.entries.delete(e),this.entries.set(e,{version:t,sha256:n}),this.trim(),!this.timer){let r=k.suspend(()=>(this.timer=void 0,this.save()));this.timer=yield*k.forkIn(k.delay(r,"1 second"),this.scope)}})}flush(){return k.suspend(()=>{let e=this.timer;return this.timer=void 0,k.andThen(e?Sr.interrupt(e):k.void,this.save())})}trim(){while(this.entries.size>wr)this.entries.delete(this.entries.keys().next().value)}save(){return Fo(this.path,Object.fromEntries(this.entries)).pipe(k.provideContext(this.context),k.catch((e)=>this.log.write(`Could not save versions: ${String(e)}`)))}}var Cr=new TextEncoder,Io=m.fn("makeServer")(function*(e){let t=e??(yield*xo),o=yield*kr.FileSystem,n=yield*Dr.Path;yield*o.makeDirectory(t.home,{recursive:!0,mode:448}),yield*o.chmod(t.home,448);let r=yield*Me.make(t.home),i=yield*Ne.make(t.home),s=yield*We.make(t.home,r),a=yield*xt.make(),d=new AbortController,l=yield*m.scope,u=!1,f=yield*vo,h={port:t.port,startedAt:Date.now(),build:f,stop:m.suspend(()=>{if(u)return m.void;return u=!0,m.asVoid(m.forkIn(m.delay(xt.succeed(a,void 0),"50 millis"),l))})},g={hostname:"127.0.0.1",port:t.port,maxRequestBodySize:Le,disablePreemptiveShutdown:!0},w=n.join(t.home,"daemon.json"),b;yield*m.addFinalizer(()=>m.suspend(()=>{let v=b;if(v===void 0)return m.void;return m.gen(function*(){yield*me(w,4096).pipe(m.flatMap((J)=>{let z=J;return z.pid===v.pid&&z.startedAt===v.startedAt&&z.port===v.port?o.remove(w):m.void}),m.ignore),yield*r.write("Daemon stopped")})}));let O=yield*_o.make(g).pipe(m.catchCause((v)=>t.port===0?m.failCause(v):_o.make({...g,port:0})));if(!("port"in O.address))return yield*m.die(Error("Expected an IP listening address"));h.port=O.address.port;let Ue=ko(s,r),xe=yield*ye.build(Ue),ve=yield*re.pipe(m.provideContext(xe)),Mo=Pr.layerHttp({group:Ce,path:"/rpc",protocol:"http",streamBufferSize:1,concurrency:512}).pipe(ye.provide(Ro(i,h)),ye.provide(ye.succeed(re,ve)),ye.provide(Fr.layerNdjson)),Vo=yield*Or.toHttpEffect(Mo),Se=0,Be=Date.now(),Wo=m.gen(function*(){let v=yield*No.HttpServerRequest,J=v.headers.host,z=v.headers.origin,Et=[`127.0.0.1:${h.port}`,`localhost:${h.port}`];if(!J||!Et.includes(J)||z!==void 0&&!Et.map((W)=>`http://${W}`).includes(z))return D.text("Forbidden",{status:403});if(Be=Date.now(),Se>=512)return D.text("Too many requests",{status:503});Se++,yield*m.addFinalizer(()=>m.sync(()=>{Se--,Be=Date.now()}));let Y=new URL(v.url,`http://127.0.0.1:${h.port}`);if(v.method==="GET"&&Y.pathname==="/health")return D.jsonUnsafe({ok:!0,pid:process.pid,appVersion:ae,build:f});if(v.method==="GET"&&Y.pathname==="/auth"){let W=Y.searchParams.get("path")??"";if(!i.consume(Y.searchParams.get("code")??"",W))return D.text("<!doctype html><p>This link is invalid or expired. Run <code>do-it open</code> again.</p>",{status:401,contentType:"text/html"});return D.redirect(`/?path=${encodeURIComponent(W)}`,{status:303,headers:{"set-cookie":`do_it=${i.cookie}; HttpOnly; SameSite=Strict; Path=/`,"cache-control":"no-store","referrer-policy":"no-referrer"}})}if(Y.pathname==="/rpc"||Y.pathname==="/rpc/"){if(!i.authorized(v.headers.authorization,v.cookies.do_it))return D.text("Unauthorized",{status:401});if(v.method!=="POST")return D.text("Use POST",{status:405});if(Number(v.headers["content-length"]??0)>Le)return D.text("Request body is too large",{status:413});let W=yield*v.text.pipe(m.result);if(W._tag==="Failure")return D.text("Could not read request body",{status:413});if(Cr.encode(W.success).length>Le)return D.text("Request body is too large",{status:413});let Bo=0;for(let je=0;je<W.success.length;je++)if(W.success.charCodeAt(je)===10&&++Bo>64)return D.text("Too many RPC messages",{status:413});return yield*Vo}if(v.method!=="GET"&&v.method!=="HEAD")return D.text("Not found",{status:404});return yield*Co(t.webDist,Y.pathname)});yield*O.serve(Wo.pipe(m.flatMap((v)=>m.gen(function*(){if(v.body._tag!=="Stream")return v;let J=yield*No.HttpServerRequest;if(D.omitsBody(v,J.method==="HEAD"))return v;let z=yield*m.context();return D.raw(D.toWeb(Rr.scopeTransferToStream(v),{context:z,signal:AbortSignal.any([br.toBunServerRequest(J).signal,d.signal])}))}))));let He={pid:process.pid,port:h.port,appVersion:ae,build:f,startedAt:h.startedAt},ze=yield*me(w,4096).pipe(m.map((v)=>v),m.catch(()=>m.succeed(void 0)));if(ze?.pid&&ze.pid!==process.pid&&Po(ze.pid))return yield*m.fail(Error("A daemon is already running for this home"));yield*o.remove(w).pipe(m.catchIf(K,()=>m.void)),yield*o.writeFileString(w,JSON.stringify(He),{mode:384,flag:"wx"}),b=He;let Uo=m.gen(function*(){if(yield*m.ignore(r.rotate()),Se===0&&ve.totalWatchers===0&&Date.now()-Be>=t.idleMs)yield*h.stop});return yield*m.forkScoped(m.repeat(Uo,Ar.spaced(Math.max(10,Math.min(1000,t.idleMs/4))))),yield*m.addFinalizer(()=>m.gen(function*(){d.abort(),yield*ve.close()})),yield*r.write(`Daemon listening on 127.0.0.1:${h.port}`),{...He,home:t.home,auth:i,documents:ve,stopped:xt.await(a),stop:h.stop}});import{BunCrypto as vt,BunHttpPlatform as $r,BunServices as _r}from"@effect/platform-bun";import{Layer as Nr}from"effect";var Lo=Nr.mergeAll(_r.layer,vt.layerDigest,vt.layerMac,vt.layerSecureRandom,$r.layer);Ir.runMain(St.scoped(St.gen(function*(){yield*(yield*Io()).stopped})).pipe(St.provide(Lo)),{teardown:(e,t)=>t(Mr.isFailure(e)&&!Lr.hasInterruptsOnly(e.cause)?1:0)});
|
package/bin/do-it.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env bun
|
|
2
2
|
// @bun
|
|
3
|
-
import{BunRuntime as Mo,BunServices as Wo}from"@effect/platform-bun";import{Config as Ae,Effect as Oe,Option as bn,Path as Qe}from"effect";var Pn=Oe.gen(function*(){let e=yield*Qe.Path,t=yield*Ae.option(Ae.String("DO_IT_HOME"));if(bn.isSome(t))return t.value;return e.join(yield*Ae.String("HOME"),".do-it")}).pipe(Oe.orDie);function te(e){return Oe.gen(function*(){let t=yield*Qe.Path,n=t.resolve(e??(yield*Pn)),r=t.join(n,"daemon.lock");return{home:n,daemon:t.join(n,"daemon.json"),token:t.join(n,"token"),lock:r,lockPid:t.join(r,"pid"),log:t.join(n,"daemon.log"),versions:t.join(n,"versions.json")}})}import{Data as wn,Effect as se,FileSystem as En}from"effect";class J extends wn.TaggedError("DaemonUnavailable"){}function ne(e){return se.gen(function*(){let t=yield*En.FileSystem,n=yield*te(e);if((yield*t.stat(n.daemon)).size>4096n||(yield*t.stat(n.token)).size>128n)return null;let r=yield*t.readFileString(n.daemon),o=(yield*t.readFileString(n.token)).trim(),s=yield*se.try(()=>JSON.parse(r));if(!s||!Number.isSafeInteger(s.pid)||s.pid<=0||!Number.isInteger(s.port)||s.port<1||s.port>65535||typeof s.appVersion!=="string"||!Number.isFinite(s.startedAt)||!/^[a-f0-9]{64}$/.test(o))return null;let u=s.port;return{home:n.home,pid:s.pid,port:u,appVersion:s.appVersion,startedAt:s.startedAt,token:o,baseUrl:`http://127.0.0.1:${u}`,...typeof s.build==="string"?{build:s.build}:{}}}).pipe(se.catch(()=>se.succeed(null)))}function X(e){try{return process.kill(e,0),!0}catch(t){return t.code==="EPERM"}}var ae=(e)=>se.promise(async(t)=>{let n=AbortSignal.timeout(500);try{let r=await fetch(`${e.baseUrl}/health`,{signal:AbortSignal.any([t,n])});if(!r.ok)return null;let o=await r.json();return o.ok===!0&&o.pid===e.pid&&typeof o.appVersion==="string"?{ok:!0,pid:e.pid,appVersion:o.appVersion,...typeof o.build==="string"?{build:o.build}:{}}:null}catch{return n.aborted?"slow":null}}),et=(e)=>ae(e).pipe(se.map((t)=>t==="slow"?null:t));import{Effect as vn}from"effect";var Dr=new URL("../../../",import.meta.url),_e=vn.succeed("1fc7246.mw4rm3m2");import{Schema as i}from"effect";var tt=2097152,Cn=4096,x=i.String.pipe(i.check(i.isMaxLength(tt))),Fe=i.String.pipe(i.check(i.isMaxLength(Cn))),B=i.String.pipe(i.check(i.makeFilter((e)=>e.length>0&&e.startsWith("/")?void 0:"an absolute, non-empty path is required"),i.isMaxLength(tt))),b=i.Int.pipe(i.check(i.isGreaterThan(0))),oe=i.String.pipe(i.check(i.isPattern(/^[a-f0-9]{64}$/))),Tn=i.TaggedStruct("User",{}),$n=i.TaggedStruct("Agent",{name:i.String.pipe(i.check(i.isBetweenLength(1,64),i.isPattern(/^[A-Za-z0-9._-]+$/)))}),Dn=i.TaggedStruct("File",{}),Ne=i.Union([Tn,$n,Dn]),nt=i.Literals(["open","done","cancelled"]),Rn=i.Struct({number:i.Number,line:i.Number,state:nt,text:Fe,indent:i.Number,section:i.NullOr(x)}),ot=i.Struct({kind:i.Literals(["added","removed","checked","unchecked","cancelled","uncancelled","edited"]),number:i.Number,text:Fe,line:i.Number,oldLine:i.Number}),In=i.Struct({id:x,title:x,level:i.Literals([1,2,3]),line:i.Number}),An=i.Struct({since:b,diff:i.NullOr(x)}),On=i.TaggedStruct("Replace",{old:x,new:x}),_n=i.TaggedStruct("Append",{text:x}),Fn=i.TaggedStruct("TodoAdd",{text:Fe}),Nn=i.TaggedStruct("TodoSet",{target:i.Union([i.Number,x]),state:nt}),Ln=i.TaggedStruct("TodoStart",{target:i.Union([i.Number,x])}),Vn=i.Union([On,_n,Fn,Nn,Ln]),be=i.optional(x),rt=i.Struct({}),Bn=i.Struct({path:B,version:b,watchers:i.Number}),it=i.Struct({pid:i.Number,appVersion:x,port:i.Number,startedAt:i.Number,build:i.optionalKey(x),documents:i.Array(Bn)}),st=i.Struct({}),at=i.Struct({}),lt=i.Struct({path:B,create:i.Boolean}),ct=i.Struct({path:B,version:b,url:x,created:i.Boolean}),pt=i.Struct({path:B,section:be}),dt=i.Struct({version:b,text:x,hash:i.optionalKey(oe)}),ut=i.Struct({path:B}),ft=i.Struct({version:b,sections:i.Array(In)}),ht=i.Struct({path:B,section:be}),yt=i.Struct({version:b,todos:i.Array(Rn)}),mt=i.Struct({path:B,author:Ne,version:b,section:be,op:Vn}),gt=i.Struct({version:b,changed:i.Boolean,section:i.NullOr(x),others:i.NullOr(An)}),kt=i.Struct({path:B,base:b,text:x,baseHash:i.optionalKey(oe)}),xt=i.Struct({version:b,text:x,hash:i.optionalKey(oe)}),St=i.Struct({path:B,since:b,wait:i.Number.pipe(i.check(i.isBetween({minimum:0,maximum:600})))}),bt=i.Struct({from:b,to:b,by:i.Array(Ne),diff:i.NullOr(x),todos:i.Array(ot)}),Pt=i.Struct({path:B,since:i.optional(b),sinceHash:i.optionalKey(oe),section:be,text:i.Boolean}),wt=i.TaggedStruct("Watching",{version:b,hash:i.optionalKey(oe),text:i.optionalKey(x)}),Et=i.TaggedStruct("Change",{from:b,to:b,by:i.Array(Ne),diff:i.NullOr(x),todos:i.Array(ot),hash:i.optionalKey(oe),text:i.optionalKey(x)}),vt=i.TaggedStruct("Resync",{version:b,hash:i.optionalKey(oe),text:i.optionalKey(x)}),Ct=i.TaggedStruct("Deleted",{}),Tt=i.Union([wt,Et,vt,Ct]);import{Schema as K}from"effect";class $t extends K.TaggedError()("VersionMismatch",{latest:b,since:b,diff:K.NullOr(x),forgotten:K.Boolean}){}var jn=K.Literals(["not-found","ambiguous","too-large","invalid"]);class Dt extends K.TaggedError()("Refused",{code:jn,message:x}){}class Rt extends K.TaggedError()("Forgotten",{latest:b}){}class It extends K.TaggedError()("NothingYet",{}){}class At extends K.TaggedError()("DocumentGone",{path:B}){}class Ot extends K.TaggedError()("Unauthorized",{}){}var U=K.Union([$t,Dt,Rt,It,At,Ot]);import{Rpc as G,RpcGroup as Un}from"effect/rpc";var Hn=G.make("status",{payload:rt,success:it,error:U}),Mn=G.make("stop",{payload:st,success:at,error:U}),Wn=G.make("open",{payload:lt,success:ct,error:U}),Kn=G.make("read",{payload:pt,success:dt,error:U}),Gn=G.make("sections",{payload:ut,success:ft,error:U}),zn=G.make("todos",{payload:ht,success:yt,error:U}),qn=G.make("edit",{payload:mt,success:gt,error:U}),Yn=G.make("sync",{payload:kt,success:xt,error:U}),Jn=G.make("diff",{payload:St,success:bt,error:U}),Xn=G.make("watch",{payload:Pt,success:Tt,error:U,stream:!0}),_t=Un.make(Hn,Mn,Wn,Kn,Gn,zn,qn,Yn,Jn,Xn);var Ft=(e)=>{switch(e._tag){case"User":return"the user";case"Agent":return e.name;case"File":return"file"}},Le=(e)=>`${e.kind} ${e.number}. ${e.text}`,Pe=(e,t,n)=>{let r=e===t||t===e+1?`Version ${t}`:`Versions ${e+1}-${t}`;return n.length===0?r:`${r} by ${n.map(Ft).join(", ")}`},Nt=(e)=>{switch(e._tag){case"Refused":return e.code==="invalid"?2:1;case"NothingYet":return 4;case"DocumentGone":return 9;case"Unauthorized":return 3;case"VersionMismatch":case"Forgotten":return 1}};var j="0.1.2",Lt="1.3.0";import{Effect as c,FileSystem as to,Option as Be,Path as no,Semaphore as je}from"effect";import{ChildProcess as oo,ChildProcessSpawner as ro}from"effect/process";import{Context as Zn,Effect as Ve,Layer as de}from"effect";import{FetchHttpClient as Qn,HttpClient as we,HttpClientRequest as Vt}from"effect/http";import{RpcClient as Bt,RpcSerialization as eo}from"effect/rpc";class Ee extends Zn.Service()("@do-it/client/RpcClient"){}function jt(e){let t=de.effect(we.HttpClient,Ve.map(we.HttpClient,we.filterStatusOk)).pipe(de.provide(Qn.layer)),n=Bt.layerProtocolHttp({url:`${e.baseUrl}/rpc`,transformClient:(r)=>we.mapRequest(r,(o)=>o.pipe(Vt.setUrl(`${e.baseUrl}/rpc`),Vt.setHeader("Authorization",`Bearer ${e.token}`)))}).pipe(de.provide([t,eo.layerNdjson]));return de.effect(Ee,Bt.make(_t)).pipe(de.provide(n))}function ue(e,t){return Ve.flatMap(Ee,t).pipe(Ve.provide(jt(e)))}var z=(e)=>e instanceof J?e:new J({message:e instanceof Error?e.message:String(e)}),io=(e)=>c.try({try:e,catch:z});function ve(e,t){return(n)=>c.catchIf(n,(r)=>r.reason._tag===e,()=>c.succeed(t))}var Ut=je.makeUnsafe(1),so=new URL("./daemon.js",import.meta.url),ao='exec "$0" "$1" >>"$2" 2>&1',Ce=c.fn("stopDaemon")(function*(e){let t=yield*ne(e);if(!t||!X(t.pid))return!1;if(!(yield*ue(t,(o)=>o.stop({})).pipe(c.timeout("500 millis"),c.map(()=>!0),c.catch(()=>c.succeed(!1))))){if((yield*io(()=>{if(t.pid!==process.pid&&X(t.pid))process.kill(t.pid,"SIGTERM")}).pipe(c.result))._tag==="Failure")return!1}let r=Date.now()+5000;while(Date.now()<r){if(!(yield*et(t)))return!0;yield*c.sleep("50 millis")}return!1}),Ht=["500 millis","1 second","2 seconds"],lo=(e,t)=>t!==null&&e.pid===t.pid&&e.startedAt===t.startedAt,q=c.fn("ensureDaemon")(function*(e={}){let t=yield*to.FileSystem,n=yield*no.Path,r=yield*ro.ChildProcessSpawner,o=yield*te(e.home??e.env?.DO_IT_HOME),s=(S,O=null)=>c.gen(function*(){for(let _=0;;_++){let y=yield*ne(o.home);if(!y||!X(y.pid)||y.appVersion!==j)return{usable:!1,info:y};let E=yield*ae(y);if(E!=="slow")return E?.appVersion===j?{usable:!0,info:y}:{usable:!1,info:y};if(!S||_>=Ht.length||lo(y,O))return{usable:!1,info:y};yield*c.sleep(Ht[_])}}),u=yield*s(!0);if(u.usable)return u.info;if(e.spawn===!1)return yield*c.fail(new J({message:"No compatible daemon is running"}));yield*t.makeDirectory(o.home,{recursive:!0,mode:448}).pipe(c.andThen(t.chmod(o.home,448)),c.mapError(z));let h=c.gen(function*(){if(yield*t.makeDirectory(o.lock,{mode:448}).pipe(c.as(!0),ve("AlreadyExists",!1)))return yield*t.writeFileString(o.lockPid,String(process.pid),{mode:384}),Be.getOrNull((yield*t.stat(o.lock)).ino);let O=yield*t.stat(o.lock).pipe(ve("NotFound",void 0));if(!O)return;let _=yield*t.readFileString(o.lockPid).pipe(ve("NotFound",void 0)),y=_===void 0?void 0:Number(_),E=Be.match(O.mtime,{onNone:()=>0,onSome:(w)=>w.getTime()});if(y!==void 0&&Number.isSafeInteger(y)&&y>0?!X(y):Date.now()-E>1e4)yield*t.remove(o.lock,{recursive:!0,force:!0});return}).pipe(je.withPermit(Ut),c.mapError(z)),m,C=Date.now()+12000;while(m===void 0){if(m=yield*h,m!==void 0)break;if(Date.now()>C)return yield*c.fail(new J({message:"Timed out waiting for the daemon startup lock"}));yield*c.sleep("50 millis")}let F=m,N=t.stat(o.lock).pipe(c.flatMap((S)=>Be.getOrNull(S.ino)===F?t.remove(o.lock,{recursive:!0,force:!0}):c.void),je.withPermit(Ut),c.ignore),A=c.gen(function*(){let S=yield*s(!0,u.info);if(S.usable)return S.info;let O=yield*ne(o.home);if(O&&X(O.pid)&&!(yield*Ce(o.home)))return yield*c.fail(new J({message:"The previous daemon could not be stopped"}));if((yield*t.stat(o.log).pipe(c.map((l)=>l.size),ve("NotFound",0n),c.mapError(z)))>=1024n*1024n)yield*t.rename(o.log,`${o.log}.old`).pipe(c.mapError(z));yield*t.writeFileString(o.log,"",{flag:"a",mode:384}).pipe(c.mapError(z));let y=yield*n.fromFileUrl(so).pipe(c.mapError(z)),E={...e.env,DO_IT_HOME:o.home};if(e.port!==void 0)E.DO_IT_PORT=String(e.port);let T=oo.make("/bin/sh",["-c",ao,process.execPath,y,o.log],{detached:!0,stdin:"ignore",stdout:"ignore",stderr:"ignore",env:E,extendEnv:!0}),w=yield*r.spawn(T).pipe(c.mapError(z));return yield*w.unref.pipe(c.asVoid,c.mapError(z)),yield*c.gen(function*(){let l=Date.now()+5000;while(Date.now()<l){let a=yield*s(!1);if(a.usable)return a.info;if(!(yield*w.isRunning.pipe(c.mapError(z))))break;yield*c.sleep("50 millis")}return yield*c.fail(new J({message:`The daemon did not start, see ${o.log}`}))}).pipe(c.onError(()=>w.kill({forceKillAfter:"1 second"}).pipe(c.ignore)))});return yield*c.scoped(A).pipe(c.ensuring(N))});import{Cause as pe,Duration as Ko,Effect as f,Exit as ke,FiberHandle as xn,Stdio as Go,Stream as zo}from"effect";import{CliConfig as qo,CliOutput as Yo,Command as Jo}from"effect/cli";import{Config as Mt,Effect as fe,Option as co}from"effect";import{ChildProcess as po,ChildProcessSpawner as uo}from"effect/process";var Wt=(e)=>fe.gen(function*(){let t=co.getOrUndefined(yield*Mt.option(Mt.String("DO_IT_BROWSER")));if(t?.toLowerCase()==="true")return;let n=t||(process.platform==="darwin"?"open":"xdg-open"),r=yield*uo.ChildProcessSpawner,o=po.make("/bin/sh",["-c",`${n} "$1"`,"sh",e],{detached:!0,stdin:"ignore",stdout:"ignore",stderr:"ignore"});yield*fe.scoped(fe.flatMap(r.spawn(o),(s)=>fe.asVoid(s.unref)))}).pipe(fe.ignore);import{Config as qt,Effect as H,FileSystem as Zt,Option as ho,Path as Qt}from"effect";import{Config as Ue,Effect as v,FileSystem as Me,Option as he,Path as ye}from"effect";var fo=new URL("../skill",import.meta.url),He=".do-it-version",le=v.gen(function*(){return yield*(yield*ye.Path).fromFileUrl(fo)}).pipe(v.orDie),Kt=v.gen(function*(){let e=yield*Me.FileSystem,n=(yield*ye.Path).join(yield*le,"SKILL.md");return{path:n,content:yield*e.readFileString(n)}}),We=v.gen(function*(){let e=yield*ye.Path,t=yield*Ue.option(Ue.String("CLAUDE_CONFIG_DIR")),n=he.isSome(t)?t.value:e.join(yield*Ue.String("HOME"),".claude");return e.join(n,"skills","do-it")}).pipe(v.orDie),Gt=v.fn("installSkill")(function*(e,t){let n=yield*Me.FileSystem,r=yield*ye.Path,o=yield*le,s=r.resolve(e??(yield*We)),u=yield*n.lstat(s).pipe(v.option);if(he.isSome(u)){if(yield*n.realPath(s).pipe(v.map((C)=>C===o),v.orElseSucceed(()=>!1)))return{kind:"linked",path:s};if(!(u.value.type==="Directory"&&(yield*n.exists(r.join(s,He))))&&!t)return{kind:"occupied",path:s};yield*n.remove(s,{recursive:!0})}return yield*n.makeDirectory(r.dirname(s),{recursive:!0}),yield*n.copy(o,s),yield*n.writeFileString(r.join(s,He),`${j}
|
|
3
|
+
import{BunRuntime as Mo,BunServices as Wo}from"@effect/platform-bun";import{Config as Ae,Effect as Oe,Option as bn,Path as Qe}from"effect";var Pn=Oe.gen(function*(){let e=yield*Qe.Path,t=yield*Ae.option(Ae.String("DO_IT_HOME"));if(bn.isSome(t))return t.value;return e.join(yield*Ae.String("HOME"),".do-it")}).pipe(Oe.orDie);function te(e){return Oe.gen(function*(){let t=yield*Qe.Path,n=t.resolve(e??(yield*Pn)),r=t.join(n,"daemon.lock");return{home:n,daemon:t.join(n,"daemon.json"),token:t.join(n,"token"),lock:r,lockPid:t.join(r,"pid"),log:t.join(n,"daemon.log"),versions:t.join(n,"versions.json")}})}import{Data as wn,Effect as se,FileSystem as En}from"effect";class J extends wn.TaggedError("DaemonUnavailable"){}function ne(e){return se.gen(function*(){let t=yield*En.FileSystem,n=yield*te(e);if((yield*t.stat(n.daemon)).size>4096n||(yield*t.stat(n.token)).size>128n)return null;let r=yield*t.readFileString(n.daemon),o=(yield*t.readFileString(n.token)).trim(),s=yield*se.try(()=>JSON.parse(r));if(!s||!Number.isSafeInteger(s.pid)||s.pid<=0||!Number.isInteger(s.port)||s.port<1||s.port>65535||typeof s.appVersion!=="string"||!Number.isFinite(s.startedAt)||!/^[a-f0-9]{64}$/.test(o))return null;let u=s.port;return{home:n.home,pid:s.pid,port:u,appVersion:s.appVersion,startedAt:s.startedAt,token:o,baseUrl:`http://127.0.0.1:${u}`,...typeof s.build==="string"?{build:s.build}:{}}}).pipe(se.catch(()=>se.succeed(null)))}function X(e){try{return process.kill(e,0),!0}catch(t){return t.code==="EPERM"}}var ae=(e)=>se.promise(async(t)=>{let n=AbortSignal.timeout(500);try{let r=await fetch(`${e.baseUrl}/health`,{signal:AbortSignal.any([t,n])});if(!r.ok)return null;let o=await r.json();return o.ok===!0&&o.pid===e.pid&&typeof o.appVersion==="string"?{ok:!0,pid:e.pid,appVersion:o.appVersion,...typeof o.build==="string"?{build:o.build}:{}}:null}catch{return n.aborted?"slow":null}}),et=(e)=>ae(e).pipe(se.map((t)=>t==="slow"?null:t));import{Effect as vn}from"effect";var Dr=new URL("../../../",import.meta.url),_e=vn.succeed("36d2353.mw8d2srx");import{Schema as i}from"effect";var tt=2097152,Cn=4096,x=i.String.pipe(i.check(i.isMaxLength(tt))),Fe=i.String.pipe(i.check(i.isMaxLength(Cn))),B=i.String.pipe(i.check(i.makeFilter((e)=>e.length>0&&e.startsWith("/")?void 0:"an absolute, non-empty path is required"),i.isMaxLength(tt))),b=i.Int.pipe(i.check(i.isGreaterThan(0))),oe=i.String.pipe(i.check(i.isPattern(/^[a-f0-9]{64}$/))),Tn=i.TaggedStruct("User",{}),$n=i.TaggedStruct("Agent",{name:i.String.pipe(i.check(i.isBetweenLength(1,64),i.isPattern(/^[A-Za-z0-9._-]+$/)))}),Dn=i.TaggedStruct("File",{}),Ne=i.Union([Tn,$n,Dn]),nt=i.Literals(["open","done","cancelled"]),Rn=i.Struct({number:i.Number,line:i.Number,state:nt,text:Fe,indent:i.Number,section:i.NullOr(x)}),ot=i.Struct({kind:i.Literals(["added","removed","checked","unchecked","cancelled","uncancelled","edited"]),number:i.Number,text:Fe,line:i.Number,oldLine:i.Number}),In=i.Struct({id:x,title:x,level:i.Literals([1,2,3]),line:i.Number}),An=i.Struct({since:b,diff:i.NullOr(x)}),On=i.TaggedStruct("Replace",{old:x,new:x}),_n=i.TaggedStruct("Append",{text:x}),Fn=i.TaggedStruct("TodoAdd",{text:Fe}),Nn=i.TaggedStruct("TodoSet",{target:i.Union([i.Number,x]),state:nt}),Ln=i.TaggedStruct("TodoStart",{target:i.Union([i.Number,x])}),Vn=i.Union([On,_n,Fn,Nn,Ln]),be=i.optional(x),rt=i.Struct({}),Bn=i.Struct({path:B,version:b,watchers:i.Number}),it=i.Struct({pid:i.Number,appVersion:x,port:i.Number,startedAt:i.Number,build:i.optionalKey(x),documents:i.Array(Bn)}),st=i.Struct({}),at=i.Struct({}),lt=i.Struct({path:B,create:i.Boolean}),ct=i.Struct({path:B,version:b,url:x,created:i.Boolean}),pt=i.Struct({path:B,section:be}),dt=i.Struct({version:b,text:x,hash:i.optionalKey(oe)}),ut=i.Struct({path:B}),ft=i.Struct({version:b,sections:i.Array(In)}),ht=i.Struct({path:B,section:be}),yt=i.Struct({version:b,todos:i.Array(Rn)}),mt=i.Struct({path:B,author:Ne,version:b,section:be,op:Vn}),gt=i.Struct({version:b,changed:i.Boolean,section:i.NullOr(x),others:i.NullOr(An)}),kt=i.Struct({path:B,base:b,text:x,baseHash:i.optionalKey(oe)}),xt=i.Struct({version:b,text:x,hash:i.optionalKey(oe)}),St=i.Struct({path:B,since:b,wait:i.Number.pipe(i.check(i.isBetween({minimum:0,maximum:600})))}),bt=i.Struct({from:b,to:b,by:i.Array(Ne),diff:i.NullOr(x),todos:i.Array(ot)}),Pt=i.Struct({path:B,since:i.optional(b),sinceHash:i.optionalKey(oe),section:be,text:i.Boolean}),wt=i.TaggedStruct("Watching",{version:b,hash:i.optionalKey(oe),text:i.optionalKey(x)}),Et=i.TaggedStruct("Change",{from:b,to:b,by:i.Array(Ne),diff:i.NullOr(x),todos:i.Array(ot),hash:i.optionalKey(oe),text:i.optionalKey(x)}),vt=i.TaggedStruct("Resync",{version:b,hash:i.optionalKey(oe),text:i.optionalKey(x)}),Ct=i.TaggedStruct("Deleted",{}),Tt=i.Union([wt,Et,vt,Ct]);import{Schema as K}from"effect";class $t extends K.TaggedError()("VersionMismatch",{latest:b,since:b,diff:K.NullOr(x),forgotten:K.Boolean}){}var jn=K.Literals(["not-found","ambiguous","too-large","invalid"]);class Dt extends K.TaggedError()("Refused",{code:jn,message:x}){}class Rt extends K.TaggedError()("Forgotten",{latest:b}){}class It extends K.TaggedError()("NothingYet",{}){}class At extends K.TaggedError()("DocumentGone",{path:B}){}class Ot extends K.TaggedError()("Unauthorized",{}){}var U=K.Union([$t,Dt,Rt,It,At,Ot]);import{Rpc as G,RpcGroup as Un}from"effect/rpc";var Hn=G.make("status",{payload:rt,success:it,error:U}),Mn=G.make("stop",{payload:st,success:at,error:U}),Wn=G.make("open",{payload:lt,success:ct,error:U}),Kn=G.make("read",{payload:pt,success:dt,error:U}),Gn=G.make("sections",{payload:ut,success:ft,error:U}),zn=G.make("todos",{payload:ht,success:yt,error:U}),qn=G.make("edit",{payload:mt,success:gt,error:U}),Yn=G.make("sync",{payload:kt,success:xt,error:U}),Jn=G.make("diff",{payload:St,success:bt,error:U}),Xn=G.make("watch",{payload:Pt,success:Tt,error:U,stream:!0}),_t=Un.make(Hn,Mn,Wn,Kn,Gn,zn,qn,Yn,Jn,Xn);var Ft=(e)=>{switch(e._tag){case"User":return"the user";case"Agent":return e.name;case"File":return"file"}},Le=(e)=>`${e.kind} ${e.number}. ${e.text}`,Pe=(e,t,n)=>{let r=e===t||t===e+1?`Version ${t}`:`Versions ${e+1}-${t}`;return n.length===0?r:`${r} by ${n.map(Ft).join(", ")}`},Nt=(e)=>{switch(e._tag){case"Refused":return e.code==="invalid"?2:1;case"NothingYet":return 4;case"DocumentGone":return 9;case"Unauthorized":return 3;case"VersionMismatch":case"Forgotten":return 1}};var j="0.1.3",Lt="1.3.0";import{Effect as c,FileSystem as to,Option as Be,Path as no,Semaphore as je}from"effect";import{ChildProcess as oo,ChildProcessSpawner as ro}from"effect/process";import{Context as Zn,Effect as Ve,Layer as de}from"effect";import{FetchHttpClient as Qn,HttpClient as we,HttpClientRequest as Vt}from"effect/http";import{RpcClient as Bt,RpcSerialization as eo}from"effect/rpc";class Ee extends Zn.Service()("@do-it/client/RpcClient"){}function jt(e){let t=de.effect(we.HttpClient,Ve.map(we.HttpClient,we.filterStatusOk)).pipe(de.provide(Qn.layer)),n=Bt.layerProtocolHttp({url:`${e.baseUrl}/rpc`,transformClient:(r)=>we.mapRequest(r,(o)=>o.pipe(Vt.setUrl(`${e.baseUrl}/rpc`),Vt.setHeader("Authorization",`Bearer ${e.token}`)))}).pipe(de.provide([t,eo.layerNdjson]));return de.effect(Ee,Bt.make(_t)).pipe(de.provide(n))}function ue(e,t){return Ve.flatMap(Ee,t).pipe(Ve.provide(jt(e)))}var z=(e)=>e instanceof J?e:new J({message:e instanceof Error?e.message:String(e)}),io=(e)=>c.try({try:e,catch:z});function ve(e,t){return(n)=>c.catchIf(n,(r)=>r.reason._tag===e,()=>c.succeed(t))}var Ut=je.makeUnsafe(1),so=new URL("./daemon.js",import.meta.url),ao='exec "$0" "$1" >>"$2" 2>&1',Ce=c.fn("stopDaemon")(function*(e){let t=yield*ne(e);if(!t||!X(t.pid))return!1;if(!(yield*ue(t,(o)=>o.stop({})).pipe(c.timeout("500 millis"),c.map(()=>!0),c.catch(()=>c.succeed(!1))))){if((yield*io(()=>{if(t.pid!==process.pid&&X(t.pid))process.kill(t.pid,"SIGTERM")}).pipe(c.result))._tag==="Failure")return!1}let r=Date.now()+5000;while(Date.now()<r){if(!(yield*et(t)))return!0;yield*c.sleep("50 millis")}return!1}),Ht=["500 millis","1 second","2 seconds"],lo=(e,t)=>t!==null&&e.pid===t.pid&&e.startedAt===t.startedAt,q=c.fn("ensureDaemon")(function*(e={}){let t=yield*to.FileSystem,n=yield*no.Path,r=yield*ro.ChildProcessSpawner,o=yield*te(e.home??e.env?.DO_IT_HOME),s=(S,O=null)=>c.gen(function*(){for(let _=0;;_++){let y=yield*ne(o.home);if(!y||!X(y.pid)||y.appVersion!==j)return{usable:!1,info:y};let E=yield*ae(y);if(E!=="slow")return E?.appVersion===j?{usable:!0,info:y}:{usable:!1,info:y};if(!S||_>=Ht.length||lo(y,O))return{usable:!1,info:y};yield*c.sleep(Ht[_])}}),u=yield*s(!0);if(u.usable)return u.info;if(e.spawn===!1)return yield*c.fail(new J({message:"No compatible daemon is running"}));yield*t.makeDirectory(o.home,{recursive:!0,mode:448}).pipe(c.andThen(t.chmod(o.home,448)),c.mapError(z));let h=c.gen(function*(){if(yield*t.makeDirectory(o.lock,{mode:448}).pipe(c.as(!0),ve("AlreadyExists",!1)))return yield*t.writeFileString(o.lockPid,String(process.pid),{mode:384}),Be.getOrNull((yield*t.stat(o.lock)).ino);let O=yield*t.stat(o.lock).pipe(ve("NotFound",void 0));if(!O)return;let _=yield*t.readFileString(o.lockPid).pipe(ve("NotFound",void 0)),y=_===void 0?void 0:Number(_),E=Be.match(O.mtime,{onNone:()=>0,onSome:(w)=>w.getTime()});if(y!==void 0&&Number.isSafeInteger(y)&&y>0?!X(y):Date.now()-E>1e4)yield*t.remove(o.lock,{recursive:!0,force:!0});return}).pipe(je.withPermit(Ut),c.mapError(z)),m,C=Date.now()+12000;while(m===void 0){if(m=yield*h,m!==void 0)break;if(Date.now()>C)return yield*c.fail(new J({message:"Timed out waiting for the daemon startup lock"}));yield*c.sleep("50 millis")}let F=m,N=t.stat(o.lock).pipe(c.flatMap((S)=>Be.getOrNull(S.ino)===F?t.remove(o.lock,{recursive:!0,force:!0}):c.void),je.withPermit(Ut),c.ignore),A=c.gen(function*(){let S=yield*s(!0,u.info);if(S.usable)return S.info;let O=yield*ne(o.home);if(O&&X(O.pid)&&!(yield*Ce(o.home)))return yield*c.fail(new J({message:"The previous daemon could not be stopped"}));if((yield*t.stat(o.log).pipe(c.map((l)=>l.size),ve("NotFound",0n),c.mapError(z)))>=1024n*1024n)yield*t.rename(o.log,`${o.log}.old`).pipe(c.mapError(z));yield*t.writeFileString(o.log,"",{flag:"a",mode:384}).pipe(c.mapError(z));let y=yield*n.fromFileUrl(so).pipe(c.mapError(z)),E={...e.env,DO_IT_HOME:o.home};if(e.port!==void 0)E.DO_IT_PORT=String(e.port);let T=oo.make("/bin/sh",["-c",ao,process.execPath,y,o.log],{detached:!0,stdin:"ignore",stdout:"ignore",stderr:"ignore",env:E,extendEnv:!0}),w=yield*r.spawn(T).pipe(c.mapError(z));return yield*w.unref.pipe(c.asVoid,c.mapError(z)),yield*c.gen(function*(){let l=Date.now()+5000;while(Date.now()<l){let a=yield*s(!1);if(a.usable)return a.info;if(!(yield*w.isRunning.pipe(c.mapError(z))))break;yield*c.sleep("50 millis")}return yield*c.fail(new J({message:`The daemon did not start, see ${o.log}`}))}).pipe(c.onError(()=>w.kill({forceKillAfter:"1 second"}).pipe(c.ignore)))});return yield*c.scoped(A).pipe(c.ensuring(N))});import{Cause as pe,Duration as Ko,Effect as f,Exit as ke,FiberHandle as xn,Stdio as Go,Stream as zo}from"effect";import{CliConfig as qo,CliOutput as Yo,Command as Jo}from"effect/cli";import{Config as Mt,Effect as fe,Option as co}from"effect";import{ChildProcess as po,ChildProcessSpawner as uo}from"effect/process";var Wt=(e)=>fe.gen(function*(){let t=co.getOrUndefined(yield*Mt.option(Mt.String("DO_IT_BROWSER")));if(t?.toLowerCase()==="true")return;let n=t||(process.platform==="darwin"?"open":"xdg-open"),r=yield*uo.ChildProcessSpawner,o=po.make("/bin/sh",["-c",`${n} "$1"`,"sh",e],{detached:!0,stdin:"ignore",stdout:"ignore",stderr:"ignore"});yield*fe.scoped(fe.flatMap(r.spawn(o),(s)=>fe.asVoid(s.unref)))}).pipe(fe.ignore);import{Config as qt,Effect as H,FileSystem as Zt,Option as ho,Path as Qt}from"effect";import{Config as Ue,Effect as v,FileSystem as Me,Option as he,Path as ye}from"effect";var fo=new URL("../skill",import.meta.url),He=".do-it-version",le=v.gen(function*(){return yield*(yield*ye.Path).fromFileUrl(fo)}).pipe(v.orDie),Kt=v.gen(function*(){let e=yield*Me.FileSystem,n=(yield*ye.Path).join(yield*le,"SKILL.md");return{path:n,content:yield*e.readFileString(n)}}),We=v.gen(function*(){let e=yield*ye.Path,t=yield*Ue.option(Ue.String("CLAUDE_CONFIG_DIR")),n=he.isSome(t)?t.value:e.join(yield*Ue.String("HOME"),".claude");return e.join(n,"skills","do-it")}).pipe(v.orDie),Gt=v.fn("installSkill")(function*(e,t){let n=yield*Me.FileSystem,r=yield*ye.Path,o=yield*le,s=r.resolve(e??(yield*We)),u=yield*n.lstat(s).pipe(v.option);if(he.isSome(u)){if(yield*n.realPath(s).pipe(v.map((C)=>C===o),v.orElseSucceed(()=>!1)))return{kind:"linked",path:s};if(!(u.value.type==="Directory"&&(yield*n.exists(r.join(s,He))))&&!t)return{kind:"occupied",path:s};yield*n.remove(s,{recursive:!0})}return yield*n.makeDirectory(r.dirname(s),{recursive:!0}),yield*n.copy(o,s),yield*n.writeFileString(r.join(s,He),`${j}
|
|
4
4
|
`),{kind:"installed",path:s,replaced:he.isSome(u)}}),zt=v.fn("inspectSkill")(function*(e,t){let n=yield*Me.FileSystem,r=yield*ye.Path,o=yield*n.lstat(e).pipe(v.option);if(he.isNone(o))return{kind:"missing"};let s=yield*n.realPath(e).pipe(v.orElseSucceed(()=>null)),u=yield*n.realPath(t).pipe(v.orElseSucceed(()=>t));if(s===u)return{kind:"linked"};let h=o.value.type==="SymbolicLink"?s??(yield*n.readLink(e).pipe(v.orElseSucceed(()=>e))):null,m=yield*n.readFileString(r.join(e,He)).pipe(v.map((S)=>S.trim()),v.orElseSucceed(()=>null)),C=yield*n.readFile(r.join(t,"SKILL.md")).pipe(v.orDie),F=yield*n.readFile(r.join(e,"SKILL.md")).pipe(v.orElseSucceed(()=>null));if(F!==null&&Buffer.from(F).equals(Buffer.from(C)))return{kind:"same",link:h,marker:m};let N=yield*n.stat(e).pipe(v.option),A=he.isSome(N)&&N.value.type==="Directory";return{kind:"differs",link:h,marker:m,directory:A}});var g=(e,t,n,r=[],o={})=>({name:e,status:t,message:n,fix:r,details:o});function M(e){return/^[\w@%+=:,./-]+$/.test(e)?e:`'${e.replaceAll("'","'\\''")}'`}function Ge(e){return`export PATH="${e.replace(/["\\$`]/g,"\\$&")}:$PATH"`}function yo(e){if(e.endsWith("/packages/cli/src/bin.ts"))return{kind:"checkout",root:e.slice(0,-24)};let n=/^(.*)\/bin\/do-it\.js$/.exec(e);if(!n)return{kind:"other",root:null};let r=n[1];if(r.includes("/install/global/node_modules/"))return{kind:"bun-global",root:r};if(/\/bunx-[^/]+\//.test(r))return{kind:"bunx",root:r};if(r.endsWith("/lib/node_modules/claude-do-it"))return{kind:"npm-global",root:r};return{kind:"package",root:r}}function Te(e){switch(e.kind){case"checkout":return`the repo checkout at ${e.root}`;case"bun-global":return"the global bun install";case"bunx":return"a bunx copy";case"npm-global":return"the global npm install";case"package":return`the claude-do-it package in ${e.root}`;case"other":return e.realEntry}}function mo(e,t){if(t)return"do-it";if(e.kind==="bunx"&&e.version!==null)return`bunx claude-do-it@${e.version}`;return`bun ${M(e.realEntry)}`}var $e=(e)=>e.replace(/[-+].*$/,""),Yt=(e,t)=>Bun.semver.order($e(e),$e(t))>=0;function go(e){let t={version:e.version,execPath:e.execPath,minimum:e.minimum,onPath:e.onPath};if(!Yt(e.version,e.minimum))return g("bun","problem",`Bun ${e.version} is older than ${e.minimum}, which do-it needs.`,[e.onPath?.same?"bun upgrade":`${M(e.execPath)} upgrade`],t);if(e.onPath===null){let n=e.execPath.slice(0,Math.max(1,e.execPath.lastIndexOf("/")));return g("bun","problem",`bun is not on PATH, so the do-it command and bunx cannot start (this CLI runs on ${e.execPath}).`,[Ge(n)],t)}if(!e.onPath.same){if(e.onPath.version===null)return g("bun","warn",`bun on PATH (${e.onPath.path}) did not report its version.`,[],t);if(!Yt(e.onPath.version,e.minimum))return g("bun","problem",`bun on PATH (${e.onPath.path}) is ${e.onPath.version}, older than ${e.minimum}.`,[`${M(e.onPath.path)} upgrade`],t);return g("bun","ok",`Bun ${e.version} at ${e.execPath}, and bun on PATH (${e.onPath.path}) is ${e.onPath.version} (do-it needs ${e.minimum} or newer)`,[],t)}return g("bun","ok",`Bun ${e.version} at ${e.onPath.path} (do-it needs ${e.minimum} or newer)`,[],t)}function ko(e){let t={self:e.self,onPath:e.onPath},n=e.onPath;if(n!==null&&n.realEntry===e.self.realEntry)return g("path","ok",`do-it on PATH is this CLI (${n.path})`,[],t);if(n===null){if(e.self.kind==="checkout"){let m=`${e.home}/.local/bin`;return g("path","problem","do-it is not on PATH.",[`mkdir -p ${M(m)}`,`ln -sf ${M(e.self.realEntry)} ${M(`${m}/do-it`)}`,...e.pathDirs.includes(m)?[]:[Ge(m)]],t)}let h=`${e.bunInstall}/bin`;return g("path","problem","do-it is not on PATH.",["bun add -g claude-do-it",...e.pathDirs.includes(h)?[]:[Ge(h)]],t)}let r=`do-it on PATH (${n.path}) is ${Te(n)}`;if(n.version===null)return g("path","warn",`${r}, not this CLI (${Te(e.self)}), and its version is unknown.`,[],t);if(n.version===e.appVersion)return g("path","warn",`${r}, not this CLI (${Te(e.self)}). Run do-it doctor to check that one too.`,[],t);if(Bun.semver.order($e(n.version),$e(e.appVersion))>0)return g("path","warn",`${r} at version ${n.version}, newer than this CLI (${e.appVersion}). Run do-it doctor to check that one.`,[],t);let o=e.self.kind==="checkout"?"latest":e.appVersion,s=n.kind==="bun-global"?`bun add -g claude-do-it@${o}`:n.kind==="npm-global"?`npm install -g claude-do-it@${o}`:n.kind==="checkout"&&e.self.kind==="checkout"?`ln -sf ${M(e.self.realEntry)} ${M(n.path)}`:null,u=`${r} at version ${n.version}, but this CLI is ${e.appVersion}.`;if(s===null)return g("path","warn",`${u} Update or remove that copy.`,[],t);return g("path","problem",u,[s],t)}function xo(e){let t=e.info;if(t===null)return g("daemon","ok",`No daemon runs for ${e.home}.`,[],{running:!1});if(!e.alive)return g("daemon","ok",`No daemon runs for ${e.home} (daemon.json names pid ${t.pid}, which has exited).`,[],{running:!1,pid:t.pid});let n=`The daemon (pid ${t.pid}, 127.0.0.1:${t.port})`,r=[`${e.self} stop`],o=`Stop it and the next ${e.self} command starts this CLI's build; open editor tabs need a reload after that.`;if(e.health==="slow")return g("daemon","warn",`${n} did not answer /health within 500 ms.`,[],{running:!0,pid:t.pid,port:t.port});if(e.health===null)return g("daemon","problem",`${n} does not answer /health.`,r,{running:!0,pid:t.pid,port:t.port});let s={running:!0,pid:t.pid,port:t.port,appVersion:e.health.appVersion,build:e.health.build??null};if(e.health.appVersion!==e.appVersion)return g("daemon","problem",`${n} runs do-it ${e.health.appVersion}, but this CLI is ${e.appVersion}. ${o}`,r,s);if(e.health.build===void 0)return g("daemon","problem",`${n} runs an older build of do-it ${e.appVersion} that reports no build id. ${o}`,r,s);if(e.health.build!==e.build)return g("daemon","problem",`${n} runs build ${e.health.build}, but this CLI starts build ${e.build}. ${o}`,r,s);return g("daemon","ok",`${n} runs this CLI's version and build.`,[],s)}function So(e){let t=e.dir?` --dir ${M(e.target)}`:"",n=`${e.self} skill install${t}`,r=`${e.self} skill install --force${t}`,o={path:e.target,source:e.source,state:e.state},s=`The skill in ${e.target}`;switch(e.state.kind){case"missing":return g("skill","problem",`The do-it skill is not installed in ${e.target}.`,[n],o);case"linked":return g("skill","ok",`${s} links to this CLI's skill.`,[],o);case"same":return g("skill","ok",e.state.link===null?`${s} matches this CLI's skill.`:`${s} links to ${e.state.link}, which matches this CLI's skill.`,[],o);case"differs":if(e.state.link!==null)return g("skill","problem",`${s} links to ${e.state.link}, which differs from this CLI's skill.`,[e.selfKind==="checkout"?`ln -sfn ${M(e.source)} ${M(e.target)}`:r],o);if(e.state.directory&&e.state.marker!==null)return g("skill","problem",`${s} is a copy do-it ${e.state.marker} installed, and it differs from this CLI's skill.`,[n],o);return g("skill","problem",`${s} differs from this CLI's skill and was not installed by do-it; --force replaces it.`,[r],o)}}var Jt=(e,t)=>`${e} ${t}${e===1?"":"s"}`;function en(e){let t=[`do-it ${e.version}, build ${e.build}, ${Te(e.cli)}`];for(let o of e.checks){t.push(`${o.status.padEnd(8)}${o.message}`);for(let s of o.fix)t.push(` fix: ${s}`)}let n=e.checks.filter((o)=>o.status==="problem").length,r=e.checks.filter((o)=>o.status==="warn").length;if(n>0)t.push(`${Jt(n,"problem")} found. Run the ${n===1?"fix":"fixes"}, then run ${e.self} doctor again.`);else if(r>0)t.push(`No problems found, ${Jt(r,"warning")}.`);else t.push("All checks passed.");return t.join(`
|
|
5
5
|
`)}var bo=new URL("./do-it.js",import.meta.url),Xt=H.fn("inspectInstall")(function*(e){let t=yield*Zt.FileSystem,n=yield*Qt.Path,r=yield*t.realPath(e).pipe(H.orElseSucceed(()=>n.resolve(e))),{kind:o,root:s}=yo(r),u=null;if(s!==null&&o==="checkout"){let h=yield*t.readFileString(n.join(s,"packages","protocol","src","index.ts")).pipe(H.orElseSucceed(()=>""));u=/APP_VERSION = ['"]([^'"]+)['"]/.exec(h)?.[1]??null}else if(s!==null){let h=yield*t.readFileString(n.join(s,"package.json")).pipe(H.flatMap((m)=>H.try(()=>JSON.parse(m))),H.orElseSucceed(()=>null));u=h?.name==="claude-do-it"&&typeof h.version==="string"?h.version:null}return{kind:o,entry:e,realEntry:r,root:s,version:u}}),Ke=(e)=>qt.option(qt.String(e)).pipe(H.map(ho.getOrUndefined),H.orDie);function Po(e){try{let t=Bun.spawnSync([e,"--version"],{stdout:"pipe",stderr:"ignore",timeout:2000}),n=t.stdout.toString().trim();return t.exitCode===0&&/^\d+\.\d+\.\d+/.test(n)?n:null}catch{return null}}var tn=H.fn("runDoctor")(function*(e){let t=yield*Zt.FileSystem,n=yield*Qt.Path,r=(yield*Ke("PATH"))??"",o=r.split(":").filter(Boolean),s=(yield*Ke("HOME"))??"",u=(yield*Ke("BUN_INSTALL"))??n.join(s,".bun"),h=(Se)=>t.realPath(Se).pipe(H.orElseSucceed(()=>Se)),m=yield*Xt(yield*n.fromFileUrl(bo).pipe(H.orDie)),C=Bun.which("do-it",{PATH:r}),F=C===null?null:{...yield*Xt(C),path:C},N=F!==null&&F.realEntry===m.realEntry,A=mo(m,N),S=Bun.which("bun",{PATH:r}),O=S!==null&&(yield*h(S))===(yield*h(process.execPath)),_=go({version:Bun.version,execPath:process.execPath,minimum:Lt,onPath:S===null?null:{path:S,same:O,version:O?Bun.version:Po(S)}}),y=yield*_e,E=(yield*te()).home,T=yield*ne(),w=T!==null&&X(T.pid),R=w?yield*ae(T):null,l=xo({home:E,info:T,alive:w,health:R,appVersion:j,build:y,self:A}),a=yield*le,d=n.resolve(e.skillDir??(yield*We)),ie=So({target:d,source:a,state:yield*zt(d,a),self:A,dir:e.skillDir!==void 0,selfKind:m.kind}),xe=[_,ko({self:m,onPath:F,pathDirs:o,home:s,bunInstall:u,appVersion:j}),l,ie];return{ok:xe.every((Se)=>Se.status!=="problem"),version:j,build:y,cli:m,self:A,checks:xe}});import{Console as wo}from"effect";function Z(e=""){return wo.log(e.endsWith(`
|
|
6
6
|
`)?e.slice(0,-1):e)}function re(e){return Z(JSON.stringify(e))}function me(e,t){return t?`${e}
|
package/package.json
CHANGED
package/skill/SKILL.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: do-it
|
|
3
|
-
description: "Run a Markdown todo as the user's only interface to a team of agents. `/do-it [file]` opens the todo (default TODO.md, created when missing) in the browser; `/do-it <request>` first runs the request read only and writes its result into the todo as one task per item, which wait until the person writes under them. Then this session becomes the conductor: it watches the file, sizes every task line, plans it on a model tier that fits its size, splits large tasks into subtasks the person reviews first, delivers it through worker agents in their own worktrees and one shared tester, and writes status, questions, and results back under each task. Works anywhere Claude Code runs: inside Herdr, Orca,
|
|
3
|
+
description: "Run a Markdown todo as the user's only interface to a team of agents. `/do-it [file]` opens the todo (default TODO.md, created when missing) in the browser; `/do-it <request>` first runs the request read only and writes its result into the todo as one task per item, which wait until the person writes under them. Then this session becomes the conductor: it watches the file, sizes every task line, plans it on a model tier that fits its size, splits large tasks into subtasks the person reviews first, delivers it through worker agents in their own worktrees and one shared tester, and writes status, questions, and results back under each task. Works anywhere Claude Code runs: inside Herdr, Orca, T3 Code, or super.engineering each worker gets its own worktree and session in that app; in any other terminal the workers are Claude Code subagents in worktrees. Also the reference for reading and changing any do-it document with versioned `do-it` commands. Trigger on '/do-it', 'do-it', 'do it from my todo', 'work through TODO.md', 'conduct this todo'."
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# do-it
|
|
@@ -9,6 +9,8 @@ Arguments: **$ARGUMENTS**. When the first line is empty, or names an existing fi
|
|
|
9
9
|
|
|
10
10
|
You are the **conductor**. The person only looks at the todo in their browser. Everything they want done arrives as lines in it, and everything they need to know (status, plans, questions, results) goes back into it, under the task it belongs to. Never ask them to relay anything between agents, and do not expect them to read this chat.
|
|
11
11
|
|
|
12
|
+
**Never take the person's focus.** Open the todo once, in Step 1, and never again during the run. No agent opens, reloads, selects, or focuses the todo's tab or split, a browser tab, a Sheprd or Herdr pane, tab, or workspace that the person did not just ask for: they may be typing somewhere else. To see the todo, `do-it read` it. The worker, tester, and messenger briefs carry this rule as their SCREEN line; add that line to every other brief you write.
|
|
13
|
+
|
|
12
14
|
**You delegate and monitor; you do not do the work.** You size tasks, dispatch agents, watch the channels, check the facts a delivery claims, and set the small `@started` and `@done` labels. Everything else goes to an agent: searching or reading code, docs, and diffs (a read only `Explore` agent, keep only its conclusion), answering questions and research (Q tasks), planning (a planner), building (workers), testing (the tester), reviewing (a reviewer), and, once a messenger runs, writing the person's status lines, questions, and results (Step 4). Reading what your own agents hand back (a verdict, an answer file, an inbox line) is fine; reading the code yourself is not. Keep your context for the whole todo, not for one task.
|
|
13
15
|
|
|
14
16
|
## The do-it commands
|
|
@@ -39,7 +41,7 @@ $D doctor # checks Bun, PATH, the daemon build,
|
|
|
39
41
|
- Name yourself: `--as NAME` or `export DO_IT_AGENT=NAME`. The person sees the name next to your changes, and `watch --as NAME` skips your own changes.
|
|
40
42
|
- The do-it Claude Code plugin puts `do-it` on the Bash tool's PATH, so the next bullet applies only without it. When a Monitor command reports `do-it` not found, spell out the `bunx claude-do-it` call there as the next bullet says.
|
|
41
43
|
- When `do-it` is not on PATH, define it as a shell function at the top of every Bash call (functions do not survive between calls, and `D="bun ..."` breaks in zsh because `$D` is not word split): `do-it() { bunx claude-do-it "$@"; }` (from a repo checkout: `do-it() { bun "<repo>/packages/cli/src/bin.ts" "$@"; }`). Monitor commands spell out the `bunx claude-do-it` (or `bun .../bin.ts`) call in full.
|
|
42
|
-
- When a do-it command fails in a way the replies above do not explain (exit 3, an unknown command), run `do-it doctor` once. Run its `do-it stop` and plain `do-it skill install` fixes yourself (`do-it stop` restarts the daemon for every agent, so tell the person to reload the editor tab). Show the person every other `fix:` line (`--force`, `ln`, `upgrade`, `add -g`, `npm install`, `export PATH`) and let them run it: those change their machine, and an `export` in your Bash call does not outlast it.
|
|
44
|
+
- When a do-it command fails in a way the replies above do not explain (exit 3, an unknown command), run `do-it doctor` once. Run its `do-it stop` and plain `do-it skill install` fixes yourself (`do-it stop` restarts the daemon for every agent, so tell the person to reload the editor tab, and never reopen or reload it yourself). Show the person every other `fix:` line (`--force`, `ln`, `upgrade`, `add -g`, `npm install`, `export PATH`) and let them run it: those change their machine, and an `export` in your Bash call does not outlast it.
|
|
43
45
|
- Exit codes: 0 done; 1 refused, version mismatch, or a doctor problem; 2 invalid arguments; 3 daemon not running and could not start; 4 nothing yet; 9 the document was deleted.
|
|
44
46
|
|
|
45
47
|
## Tags
|
|
@@ -118,6 +120,8 @@ Find out where this session runs, once. It decides how workers start (Step 4) an
|
|
|
118
120
|
detect_harness() {
|
|
119
121
|
if [ "${HERDR_ENV:-}" = 1 ] && command -v herdr >/dev/null; then echo herdr; return; fi
|
|
120
122
|
if [ -n "${ORCA_TERMINAL_HANDLE:-}${ORCA_WORKTREE_ID:-}" ] && command -v orca >/dev/null; then echo orca; return; fi
|
|
123
|
+
if [ -n "${SUPER_ENGINEERING_ENV:-}${SUPER_ENGINEERING_WORKTREE_PATH:-}${SUPERCONDUCTOR_WORKTREE_PATH:-}" ] &&
|
|
124
|
+
command -v sc >/dev/null && sc instance current --json >/dev/null 2>&1; then echo sc; return; fi # super.engineering, formerly Superconductor
|
|
121
125
|
if command -v t3 >/dev/null; then
|
|
122
126
|
if [ -n "${T3CODE_PROJECT_ROOT:-}${T3CODE_WORKTREE_PATH:-}" ]; then echo t3code; return; fi
|
|
123
127
|
pid=$PPID # T3 Code does not mark its agent sessions yet, so look for it among the parent processes
|
|
@@ -132,7 +136,7 @@ detect_harness() {
|
|
|
132
136
|
detect_harness > "$STATE/harness"; cat "$STATE/harness" # later Bash calls: HARNESS=$(cat "$STATE/harness")
|
|
133
137
|
```
|
|
134
138
|
|
|
135
|
-
`herdr`, `orca`, and `t3code` start workers as sessions in that app; `plain` (any other terminal) uses Claude Code subagents, which need nothing beyond Claude Code.
|
|
139
|
+
`herdr`, `orca`, `sc` (super.engineering), and `t3code` start workers as sessions in that app; `plain` (any other terminal) uses Claude Code subagents, which need nothing beyond Claude Code.
|
|
136
140
|
|
|
137
141
|
Find out once which models you can reach beyond Claude Code's own `opus` and `sonnet`. Only L tasks use them (planning, review, and the hard subtasks). Look in two places: the Agent tool's list of agent types and their descriptions, for an agent type whose description names a model stronger than Opus; and PATH, for a CLI that runs another frontier model non interactively. Run `<cli> --help` once for any CLI you find, and store the full non interactive command (read only for planning and review), so later steps and the worker brief's HARD PARTS can paste it. Write what you found to `$STATE/models.json`, for example `{"planner": "agent <type>", "reviewer": null, "hard_parts": "cli <command>"}`, with `null` for anything you did not find. Use only what is there; never assume an agent type or CLI exists. Everything not found falls back to Opus.
|
|
138
142
|
|
|
@@ -144,14 +148,17 @@ for app in /Applications/Sheprd.app/Contents/MacOS/sheprd /Applications/Herdr.ap
|
|
|
144
148
|
[ -x "$app" ] && { SHEPRD=$app; break; }
|
|
145
149
|
done
|
|
146
150
|
if [ "${HERDR_ENV:-}" = 1 ] && [ -n "$SHEPRD" ]; then
|
|
147
|
-
DO_IT_BROWSER="$SHEPRD browser open --beside" do-it open "$TODO" # a split next to this pane
|
|
151
|
+
DO_IT_BROWSER="$SHEPRD browser open --beside --no-focus" do-it open "$TODO" # a split next to this pane, keyboard stays here
|
|
152
|
+
elif [ "$(cat "$STATE/harness")" = sc ]; then
|
|
153
|
+
# the workspace's browser split, which never moves focus; the system browser when browser automation is off or denied
|
|
154
|
+
DO_IT_BROWSER='f() { sc browser open "$1" --split >/dev/null 2>&1 || open "$1"; }; f' do-it open "$TODO"
|
|
148
155
|
else
|
|
149
156
|
do-it open "$TODO"
|
|
150
157
|
fi
|
|
151
158
|
do-it read "$TODO"
|
|
152
159
|
```
|
|
153
160
|
|
|
154
|
-
|
|
161
|
+
This is the only `do-it open` of the run, and on a resume (Step 1a) skip it when the person already has the todo open. Opening it again reloads and shows the split, which pulls the person away from what they are doing: never run `do-it open`, `sheprd browser open`, `sheprd browser reload`, or any `focus` command on the todo later, not after a daemon restart, not to check a change, not to show a result. When the editor needs a reload (after `do-it stop`), write that in chat and let the person do it.
|
|
155
162
|
|
|
156
163
|
Tell the person in one line that the todo is open and that they can add tasks there. Create the two agent channels with your Write tool if missing (nobody has them open yet): `$STATE/inbox.md` (`# Inbox`, `## Messages`) and `$STATE/tests.md` (`# Tests`, `## Queue`). If `$STATE/state.json` exists, this is a resume: do Step 1a, which ends with the Step 2 watches armed, then go on with Step 5 events.
|
|
157
164
|
|
|
@@ -162,13 +169,13 @@ When `DO_IT_HOME` or `DO_IT_PORT` is set in your environment, every agent you st
|
|
|
162
169
|
A conductor that stopped (a crash, a closed tab, a new session) leaves its workers, tester, and messenger running, and events kept arriving while nobody watched. Resuming is catching up, then conducting as usual: it is never done by one pass.
|
|
163
170
|
|
|
164
171
|
1. **Make sure no conductor still runs.** Only one conductor runs per todo. List the watches with `pgrep -fl "do-it watch"` and keep those naming this run's todo or `$STATE` with `--as conductor`. For each, walk up its parents (`ps -o ppid= -p PID`, then `ps -o comm= -p PPID`): when one is a live `claude` process other than your own session, another conductor still runs, so do not resume: tell the person in chat and stop. Otherwise these watches are orphans of the stopped conductor: kill them. Never touch the tester's or messenger's watches, or another run's.
|
|
165
|
-
2. **Find what still runs.** Match each task by the worktree path and branch saved in `state.json`, and use the agent name only as a cross check (another run in the same repo can have its own `do-tN`). Herdr: `herdr agent list` (cwd and name), `herdr worktree list --json`; Orca: `orca worktree list --json`, `orca terminal list --json`; T3 Code: `t3 thread list`; plain: `git worktree list` (subagents do not survive their conductor, so every plain worker is gone). Reuse the tester and messenger saved in `state.json` while they run; start new ones only when they are gone.
|
|
172
|
+
2. **Find what still runs.** Match each task by the worktree path and branch saved in `state.json`, and use the agent name only as a cross check (another run in the same repo can have its own `do-tN`). Herdr: `herdr agent list` (cwd and name), `herdr worktree list --json`; Orca: `orca worktree list --json`, `orca terminal list --json`; super.engineering: `sc agents list --worktree <path> --output json` for each saved worktree; T3 Code: `t3 thread list`; plain: `git worktree list` (subagents do not survive their conductor, so every plain worker is gone). Reuse the tester and messenger saved in `state.json` while they run; start new ones only when they are gone.
|
|
166
173
|
3. **Read what you missed.** `do-it read` the todo, the inbox, and `tests.md` whole (not a watch: a watch only shows what comes next). Compare with `state.json`: inbox lines after `versions.inbox` (all of them when it is missing), answers in `tests.md`, and the person's edits in the todo since `versions.todo` (new tasks, notes under tasks, answers to questions, a checked `Review this task`).
|
|
167
174
|
4. **Act on each task**, by its `state.json` status and what you found:
|
|
168
175
|
- `delivered`: nothing. Never re-dispatch it, re-check it, or rewrite its status line.
|
|
169
176
|
- a worker whose `done` line is in the inbox but who is not `delivered`: verify and deliver it now (Step 5).
|
|
170
177
|
- a live worker without `done`: keep it, send it any note the person wrote while you were away (`From the TODO: ...`), and never start a second worker on that task.
|
|
171
|
-
- a worker that is gone without `done`: its worktree and branch still hold its work, so start a new worker in that same worktree with its brief plus `RESUMED: continue from the commits on your branch`, and never create a second worktree for it (when the saved worktree is missing or was never saved, dispatch it fresh, Step 4). Herdr: `herdr worktree open --no-focus`, then focus
|
|
178
|
+
- a worker that is gone without `done`: its worktree and branch still hold its work, so start a new worker in that same worktree with its brief plus `RESUMED: continue from the commits on your branch`, and never create a second worktree for it (when the saved worktree is missing or was never saved, dispatch it fresh, Step 4). Herdr: `herdr worktree open --no-focus`, then put focus back where it was only if it moved (as in Step 4), then start `do-tN` in its root pane; Orca: a new terminal in the saved worktree id; super.engineering: `sc layout run tabs --worktree <saved path> --open-if-needed --active keep` as in Step 4; T3 Code: `t3 thread send` to the saved thread; plain: `Agent` without `isolation`, with `CWD: <saved worktree>` in the brief and `cd` there in every Bash call.
|
|
172
179
|
- `queued`: dispatch it in tag order as workers free up, with any note the person wrote under it in the brief.
|
|
173
180
|
- a task line with `@started` and a `**tN**` status line but no `state.json` entry: look for its worker before treating it as new: a worktree whose branch or label names the task, or an agent whose brief or cwd points at this `$STATE`; when nothing matches for sure, ask the person in chat before dispatching it; with `@started` and no status line, triage it (Step 3) and keep the stamp.
|
|
174
181
|
- any other status (`review`, `listed`, `waiting for you`, `stopped`): apply the Step 5 row for what changed in the todo since `versions.todo`, or nothing when nothing did.
|
|
@@ -312,6 +319,9 @@ SUBTASKS (L only): <the subtask lines as the person left them, without Review th
|
|
|
312
319
|
in order. When you begin one, append "- tN subtask started: <its text>" to the inbox; when its
|
|
313
320
|
commits are in, "- tN subtask done: <its text>". HARD PARTS: <the hard_parts command from models.json, or none>.
|
|
314
321
|
NAME: export DO_IT_AGENT=tN before any do-it command.
|
|
322
|
+
SCREEN: never open, reload, select, or focus anything on the person's screen: no do-it open, no
|
|
323
|
+
browser, tab, pane, worktree, or workspace command of Sheprd, Herdr, Orca, T3 Code, or sc that
|
|
324
|
+
opens, selects, activates, or focuses something, no new browser windows.
|
|
315
325
|
COMPUTE RULE: never run builds, tests, linters, type checkers, dev servers, or browsers yourself.
|
|
316
326
|
Formatters are fine. To test, queue one request and wait for the answer:
|
|
317
327
|
do-it todo add <STATE>/tests.md --version <v> --section queue "tNrK tN: <command> in <your worktree>"
|
|
@@ -341,12 +351,19 @@ run exactly that command in that path with output to <STATE>/logs/rN.log, then u
|
|
|
341
351
|
or FAIL in 1m05s, log <path>: <the first error, at most 15 lines, and the cause in one sentence>
|
|
342
352
|
and check the request off (do-it todo check). Never edit code, never commit, never fix failures.
|
|
343
353
|
If a command would run longer than 30 minutes or needs a desktop you do not have, answer BLOCKED
|
|
344
|
-
with the reason.
|
|
354
|
+
with the reason. Drive browsers headless only.
|
|
355
|
+
SCREEN: never open, reload, select, or focus anything on the person's screen: no do-it open, no
|
|
356
|
+
browser, tab, pane, worktree, or workspace command of Sheprd, Herdr, Orca, T3 Code, or sc that
|
|
357
|
+
opens, selects, activates, or focuses something, no new browser windows.
|
|
358
|
+
When no open request is left, end your run with "QUEUE EMPTY".
|
|
345
359
|
```
|
|
346
360
|
|
|
347
361
|
Messenger brief:
|
|
348
362
|
|
|
349
363
|
```
|
|
364
|
+
SCREEN: never open, reload, select, or focus anything on the person's screen: no do-it open, no
|
|
365
|
+
browser, tab, pane, worktree, or workspace command of Sheprd, Herdr, Orca, T3 Code, or sc that
|
|
366
|
+
opens, selects, activates, or focuses something, no new browser windows.
|
|
350
367
|
You write the person's todo for the conductor. export DO_IT_AGENT=messenger. TODO=<todo path>,
|
|
351
368
|
INBOX=<STATE>/inbox.md (section messages), CURSOR=<STATE>/messenger.version, and
|
|
352
369
|
STATE=<STATE>/state.json, where each task's entry gives the task text for its tN. Your input is every
|
|
@@ -376,7 +393,7 @@ is written, save that version to CURSOR, then look again. When no line is left,
|
|
|
376
393
|
"INBOX DRAINED".
|
|
377
394
|
```
|
|
378
395
|
|
|
379
|
-
### Session workers (Herdr, Orca, T3 Code)
|
|
396
|
+
### Session workers (Herdr, Orca, T3 Code, super.engineering)
|
|
380
397
|
|
|
381
398
|
In these apps every worker is a full Claude session in its own worktree, not a subagent, so the person sees each task in the app and it can call its own subagents. Every worker, tester, and messenger starts in the background: the person stays on the todo and the conversation, and opens a task's space only when they choose to. Never activate, focus, or reveal what you create. Its brief is the worker brief above with `CWD: <worktree> BRANCH: <branch> BASE: <base branch> REPO RULES: read CLAUDE.md/AGENTS.md` in place of the REPO line (`CWD: the worktree you start in` when the app makes the worktree), plus:
|
|
382
399
|
|
|
@@ -408,14 +425,17 @@ Run `herdr --help` once for the CLI; never split your own pane.
|
|
|
408
425
|
- **Worker per task**: a Herdr worktree and space in the background (a branch named after the task, based on the repo's integration branch, under `.claude/worktrees/`), then Claude in its root pane named `do-tN` (`$MODEL`: `sonnet` for S, `opus` for M and L; `$EFFORT`: `high`, `xhigh` for L) with the session worker brief above. Create, start, and brief it without ever showing it:
|
|
409
426
|
```bash
|
|
410
427
|
SRC_WS=$HERDR_WORKSPACE_ID; BRANCH=<branch>; BASE=<base>; REPO_ROOT=<repo root>
|
|
428
|
+
focused() { herdr workspace list | jq -r '.result.workspaces[] | select(.focused) | .workspace_id'; }
|
|
429
|
+
BEFORE=$(focused) # wherever the person is now, often not your workspace
|
|
411
430
|
WT=$(herdr worktree create --workspace "$SRC_WS" --branch "$BRANCH" --base "$BASE" \
|
|
412
431
|
--path "$REPO_ROOT/.claude/worktrees/$BRANCH" --label "$BRANCH" --no-focus --json)
|
|
413
|
-
|
|
432
|
+
# create can move focus even with --no-focus: put it back where it was, and only then
|
|
433
|
+
[ -n "$BEFORE" ] && [ "$(focused)" != "$BEFORE" ] && herdr workspace focus "$BEFORE"
|
|
414
434
|
ROOT_PANE=$(jq -r .result.root_pane.pane_id <<<"$WT")
|
|
415
435
|
herdr agent start do-tN --kind claude --pane "$ROOT_PANE" -- --model "$MODEL" --effort "$EFFORT" --dangerously-skip-permissions
|
|
416
436
|
herdr agent prompt do-tN "$BRIEF" --wait --until working --timeout 20000
|
|
417
437
|
```
|
|
418
|
-
Never pass `--focus`, never focus, select, or reveal the new space, its tabs, or its panes, and never use `herdr worktree open` for a new worker. When you must reopen an existing worktree with `worktree open` (a resume), pass `--no-focus` and
|
|
438
|
+
Never pass `--focus`, never focus, select, or reveal the new space, its tabs, or its panes, and never use `herdr worktree open` for a new worker. When you must reopen an existing worktree with `worktree open` (a resume), pass `--no-focus` and restore focus the same way (save `BEFORE` first, refocus it only when `open` moved focus), since `open` focuses its target anyway. Never focus your own workspace to "get back": the person may be in another one.
|
|
419
439
|
- **Answers** go to the worker as `herdr agent prompt do-tN "From the TODO: <their text>"`; it keeps working while it waits.
|
|
420
440
|
- **Parked workers**: when no inbox message came from a task for 30 minutes, read its screen (`herdr agent read do-tN --source recent-unwrapped --lines 80`); if it waits on something that already finished, prompt it to continue.
|
|
421
441
|
|
|
@@ -443,6 +463,25 @@ Each task becomes an Orca workspace with Claude in its first terminal. Read Orca
|
|
|
443
463
|
- **Answers**: `orca terminal send --terminal "$H" --text "From the TODO: <their text>" --enter --wait-submit 10 --json`. `accepted: true` is enough; never resend on silence.
|
|
444
464
|
- **Parked workers**: after 30 quiet minutes, `orca terminal read --terminal "$H" --json`, and prompt it to continue if it waits. After an Orca restart or a `terminal_handle_stale` error, take the new handle from `orca terminal list --worktree "id:$WT" --json` and use only that one.
|
|
445
465
|
|
|
466
|
+
### Inside super.engineering (`HARNESS=sc`): worktrees and tabs
|
|
467
|
+
|
|
468
|
+
Each task becomes a super.engineering worktree with a Claude session working its brief. Run `sc instructions worktree` and `sc instructions orchestration` once and follow them; `/do-it` is the person asking for app-managed worker sessions, which those rules require before any `sc` orchestration. Run `sc chat providers --json` once for the Claude provider key and the model and reasoning values it accepts, and use only those (below: `claude`, `$MODEL`, `$EFFORT` as in Herdr). Keep `CONDUCTOR_WT=${SUPER_ENGINEERING_WORKTREE_PATH:-${SUPERCONDUCTOR_WORKTREE_PATH:-$PWD}}`.
|
|
469
|
+
|
|
470
|
+
- **Worker per task**: a new worktree with its task, created in the background:
|
|
471
|
+
```bash
|
|
472
|
+
W=$(printf '%s' "$BRIEF" | sc worktree create --stdin --provider claude --model "$MODEL" --reasoning "$EFFORT" --background --json)
|
|
473
|
+
```
|
|
474
|
+
Take the worktree's item id and path from `$W` (when it names neither, the new row in `sc workspace list --json`), its branch from `git -C "$WT" branch --show-current`, then name its session: `sc agents list --worktree "$WT" --output json` for its target and `sc agents label set --to <target> do-tN --worktree "$WT"`. Keep the item id, path, branch, and label in `state.json`. The app names the branch; never pass `--branch` or `--worktree-name` with a prompt (it refuses), and never run `git worktree add` for a worker. Never pass `--activate`, and never run `sc worktree select`, `sc worktree open`, `sc workspace select`, or `sc browser tab switch`: each one moves the person's view.
|
|
475
|
+
- **Tester**: one tab in your own worktree, started once and watching the queue itself, as in Herdr. Reuse a live `do-tester` from `sc agents list --worktree "$CONDUCTOR_WT" --output json` first:
|
|
476
|
+
```bash
|
|
477
|
+
sc layout run tabs --worktree "$CONDUCTOR_WT" --label do-tester --provider claude --model sonnet --reasoning high \
|
|
478
|
+
--ui terminal --active keep --prompt "$TESTER_BRIEF" --output json
|
|
479
|
+
```
|
|
480
|
+
- **Messenger**: a second tab labelled `do-messenger` made the same way (`--model sonnet --reasoning medium`) with the messenger brief, watching the inbox itself as in Herdr. Reuse a live one first.
|
|
481
|
+
- **Answers**: `sc agent send --to label:do-tN --worktree "$WT" --queue --prompt "From the TODO: <their text>" --output json`. A successful send means it was queued; never resend on silence.
|
|
482
|
+
- **Parked workers**: after 30 quiet minutes, `sc agent read --to label:do-tN --worktree "$WT" --last 80 --output json`, and prompt it to continue (same send) if it waits. A worker whose session is gone gets a new tab in its worktree: `sc layout run tabs --worktree "$WT" --open-if-needed --label do-tN --provider claude --model "$MODEL" --reasoning "$EFFORT" --ui terminal --active keep --prompt "<brief> RESUMED: continue from the commits on your branch"`.
|
|
483
|
+
- **Cleanup** after delivery, only when the person asks: `sc worktree delete <item id>` (never `--force` on unpushed work).
|
|
484
|
+
|
|
446
485
|
### Inside T3 Code (`HARNESS=t3code`): threads in worktrees
|
|
447
486
|
|
|
448
487
|
Each task becomes a T3 Code thread in its own worktree. Flags differ between T3 Code versions: run `t3 thread start --help` once and use only flags it lists.
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
/*! tailwindcss v4.3.3 | MIT License | https://tailwindcss.com */
|
|
2
|
+
@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000;--tw-outline-style:solid;--tw-blur:initial;--tw-brightness:initial;--tw-contrast:initial;--tw-grayscale:initial;--tw-hue-rotate:initial;--tw-invert:initial;--tw-opacity:initial;--tw-saturate:initial;--tw-sepia:initial;--tw-drop-shadow:initial;--tw-drop-shadow-color:initial;--tw-drop-shadow-alpha:100%;--tw-drop-shadow-size:initial}}}@layer theme{:root,:host{--font-sans:-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", "Noto Sans", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";--font-mono:ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono)}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", "Noto Sans", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring:where(:not(iframe)){outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab, red, red)){::placeholder{color:color-mix(in oklab, currentcolor 50%, transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}}@layer components;@layer utilities{.collapse{visibility:collapse}.visible{visibility:visible}.absolute{position:absolute}.fixed{position:fixed}.relative{position:relative}.container{width:100%}@media (width>=40rem){.container{max-width:40rem}}@media (width>=48rem){.container{max-width:48rem}}@media (width>=64rem){.container{max-width:64rem}}@media (width>=80rem){.container{max-width:80rem}}@media (width>=96rem){.container{max-width:96rem}}.block{display:block}.flow-root{display:flow-root}.grid{display:grid}.hidden{display:none}.inline{display:inline}.table{display:table}.grow{flex-grow:1}.uppercase{text-transform:uppercase}.italic{font-style:italic}.shadow{--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a), 0 1px 2px -1px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.ring{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.outline{outline-style:var(--tw-outline-style);outline-width:1px}.filter{filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)}}:root{--lightningcss-light:initial;--lightningcss-dark: ;color-scheme:light;--pt-paper:#efe9b7;--pt-prose:#858266;--pt-ink:#000;--pt-resolved:#00000090;--pt-project:#bc644a;--pt-project-bg:#00000010;--pt-link:#003e7290;--pt-delimiter:#0004;--pt-selection:#00000029;--pt-active-row:#f7dc8688;--pt-marker-open:#0006;--pt-marker-done:#00723e;--pt-marker-cancelled:#b60101;--pt-tag:#a02e2b99;--pt-tag-resolved:#00000050;--pt-tag-today:#eadd4e;--pt-tag-high:#ff7f00;--pt-tag-low:#222;--pt-tag-critical:red;--pt-tag-done:#a85000;--pt-key-paper:#faf6d8;--pt-key-edge:#00000038;--pt-key-shade:#0000001f;--pt-key-ink:#3b392c;--pt-tag-chip-ink:#000;--pt-tag-low-ink:#fff;--pt-remote-flash:#f7dc86;--pt-shadow-tooltip:0 6px 16px #00000024;--pt-shadow-popover:0 12px 28px #00000029;font-family:Menlo,Consolas,Liberation Mono,monospace}.pt-keys{font:inherit;vertical-align:middle;white-space:nowrap;background:0 0;border:0;align-items:center;gap:3px;margin:0;padding:0;display:inline-flex}.pt-key{border:1px solid var(--pt-key-edge);background:var(--pt-key-paper);min-width:20px;height:20px;box-shadow:0 1px 0 var(--pt-key-shade);color:var(--pt-key-ink);letter-spacing:.01em;border-radius:4px;justify-content:center;align-items:center;padding:0 5px;font:600 11px/1 system-ui,-apple-system,Segoe UI,Roboto,sans-serif;display:inline-flex}.pt-key-icon{padding:0 4px}.pt-key-svg{width:12px;height:12px;display:block}.pt-visually-hidden{clip-path:inset(50%);white-space:nowrap;width:1px;height:1px;position:absolute;overflow:hidden}*{box-sizing:border-box}html,body{background:var(--pt-paper);min-width:320px;min-height:100%;color:var(--pt-ink);margin:0}body,button,input,textarea{font-family:Menlo,Consolas,Liberation Mono,monospace}body{font-size:17px;line-height:1.6}button,input,textarea{font-size:inherit}button{cursor:pointer}.pt-shell{--pt-content-inset:24px;background:var(--pt-paper);width:100%;height:100dvh;color:var(--pt-ink);font-variant-ligatures:none;font-feature-settings:"liga" 0, "clig" 0;grid-template-rows:40px 36px minmax(0,1fr) 28px;font-size:17px;line-height:1.6;display:grid;overflow:hidden}.pt-header{border-bottom:1px solid var(--pt-delimiter);background:var(--pt-project-bg);align-items:center;min-width:0;padding:0 16px;font-size:13px;line-height:20px;display:flex}.pt-file-name{border:1px solid var(--pt-delimiter);border-bottom-color:var(--pt-paper);background:var(--pt-paper);min-width:0;max-width:min(52vw,520px);color:var(--pt-ink);text-overflow:ellipsis;white-space:nowrap;align-self:stretch;align-items:center;margin:0;padding:0 14px;font-size:13px;font-weight:400;line-height:38px;display:flex;overflow:hidden}.pt-header-actions{align-items:center;gap:18px;margin-left:auto;padding-left:20px;display:flex}.pt-header-button{min-height:28px;color:var(--pt-prose);background:0 0;border:0;align-items:center;gap:7px;padding:3px 0;font-size:13px;line-height:20px;display:inline-flex}.pt-header-button:hover,.pt-header-button-active{color:var(--pt-ink)}.pt-header-button-active{background:var(--pt-project-bg)}.pt-header-button-active .pt-header-label{text-underline-offset:3px;text-decoration:underline;text-decoration-thickness:1px}.pt-toolbar{min-width:0;padding-inline:calc(var(--pt-content-inset) - 6px);border-bottom:1px solid var(--pt-delimiter);background:var(--pt-paper);align-items:center;gap:2px;display:flex}.pt-toolbar-button{width:28px;height:28px;color:var(--pt-prose);cursor:pointer;background:0 0;border:1px solid #0000;flex:none;justify-content:center;align-items:center;padding:0;display:inline-flex;position:relative}.pt-tooltip{z-index:30;border:1px solid var(--pt-delimiter);background:var(--pt-paper);box-shadow:var(--pt-shadow-tooltip);color:var(--pt-ink);white-space:nowrap;opacity:0;visibility:hidden;pointer-events:none;align-items:center;gap:8px;padding:4px 6px 4px 8px;font-size:12px;line-height:20px;transition:opacity .12s,visibility 0s linear .12s;display:inline-flex;position:absolute;top:calc(100% + 6px);left:0}.pt-tooltip-end{left:auto;right:0}.pt-toolbar-spacer{flex:auto}.pt-toolbar-toggle[aria-pressed=true]{border-color:var(--pt-delimiter);background:var(--pt-active-row);color:var(--pt-ink)}.pt-toolbar-button:hover .pt-tooltip,.pt-toolbar-button:focus-visible .pt-tooltip{opacity:1;visibility:visible;transition:opacity .12s .35s,visibility 0s linear .35s}.pt-toolbar-button:hover{border-color:var(--pt-delimiter);background:var(--pt-project-bg);color:var(--pt-ink)}.pt-toolbar-button:focus-visible{outline:2px solid var(--pt-link);outline-offset:-2px;color:var(--pt-ink)}.pt-toolbar-button:active{background:var(--pt-active-row)}.pt-toolbar-icon{pointer-events:none;display:block}.pt-toolbar-divider{background:var(--pt-delimiter);flex:none;width:1px;height:18px;margin:0 6px}.pt-workspace{background:var(--pt-paper);grid-template-rows:minmax(0,1fr);grid-template-columns:minmax(0,1fr);min-height:0;display:grid;overflow:hidden}.pt-workspace-with-activity{grid-template-rows:minmax(0,1fr) minmax(0,35%)}.pt-editor-host{background:var(--pt-paper);min-width:0;min-height:0;overflow:hidden}.pt-activity{border-top:1px solid var(--pt-delimiter);background:var(--pt-paper);min-width:0;min-height:0;color:var(--pt-prose);font-size:12px;line-height:18px;overflow:auto}.pt-activity-title{color:var(--pt-prose);letter-spacing:.12em;text-transform:uppercase;margin:0;padding:12px 14px 8px;font-size:11px;font-weight:700;line-height:16px}.pt-activity-list{margin:0;padding:0 14px 12px;list-style:none}.pt-activity-item{border-top:1px solid var(--pt-delimiter);padding:9px 0}.pt-activity-header{color:var(--pt-ink);font-size:12px;line-height:18px}.pt-activity-line,.pt-activity-empty{color:var(--pt-prose);font-size:12px;line-height:18px}.pt-activity-empty{margin:0;padding:0 14px 12px}.pt-status-bar{border-top:1px solid var(--pt-delimiter);background:var(--pt-project-bg);min-width:0;height:28px;color:var(--pt-prose);white-space:nowrap;grid-template-columns:minmax(max-content,1fr) auto minmax(max-content,1fr);align-items:center;padding:0 16px;font-size:13px;line-height:28px;display:grid}.pt-status-left,.pt-status-right{flex-shrink:0;align-items:center;gap:16px;min-width:max-content;display:flex;overflow:visible}.pt-status-right{font-variant-numeric:tabular-nums;grid-column:3;justify-content:flex-end}.pt-status-center{flex-shrink:1;grid-column:2;justify-content:center;justify-self:center;align-items:center;gap:14px;min-width:0;padding:0 16px;display:none;overflow:hidden}.pt-status-left{grid-column:1}.pt-hint{min-width:0;color:var(--pt-prose);flex-shrink:1;align-items:center;gap:5px;display:inline-flex}.pt-hint-label{font-size:12px}.pt-status-item{text-overflow:ellipsis;overflow:hidden}.pt-status-saving{color:var(--pt-prose)}.pt-status-reconnecting{color:var(--pt-marker-cancelled)}.pt-notice{color:var(--pt-ink);text-overflow:ellipsis;overflow:hidden}.pt-palette-backdrop{z-index:20;background:0 0;position:fixed;inset:0}.pt-palette{border:1px solid var(--pt-delimiter);background:var(--pt-paper);width:min(600px,92vw);max-width:100%;box-shadow:var(--pt-shadow-popover);color:var(--pt-ink);position:absolute;top:10vh;left:50%;overflow:hidden;transform:translate(-50%)}.pt-palette-input{border:0;border-bottom:1px solid var(--pt-delimiter);width:100%;min-height:44px;color:var(--pt-ink);background:0 0;outline:0;padding:10px 12px;font-size:15px;line-height:22px;display:block}.pt-palette-input::placeholder{color:var(--pt-prose)}.pt-palette-list{max-height:50vh;margin:0;padding:4px 0;list-style:none;overflow:auto}.pt-palette-item{text-align:left;width:100%;min-width:0;color:var(--pt-ink);font:inherit;background:0 0;border:0;grid-template-columns:minmax(0,1fr) auto;gap:12px;padding:6px 12px;display:grid}.pt-palette-item-active{background:var(--pt-active-row)}.pt-palette-item>span:first-child{align-items:baseline;gap:12px;min-width:0;display:flex}.pt-palette-title,.pt-palette-detail{text-overflow:ellipsis;white-space:nowrap;min-width:0;overflow:hidden}.pt-palette-title{color:var(--pt-ink)}.pt-palette-detail{color:var(--pt-prose);font-size:12px}.pt-palette-keys{color:var(--pt-prose);justify-content:flex-end;align-self:center;align-items:center;gap:6px;display:flex}.pt-palette-or{color:var(--pt-prose);font-size:11px}.pt-palette-note{flex-wrap:wrap;align-items:center;gap:4px;line-height:22px;display:flex}.pt-palette-empty{color:var(--pt-prose);margin:0;padding:12px}.pt-palette>.pt-palette-detail{border-top:1px solid var(--pt-delimiter);white-space:normal;margin:0;padding:10px 12px 12px;line-height:18px}.pt-editor-host .cm-editor,.pt-editor-host .cm-scroller,.pt-editor-host .cm-content{font-variant-ligatures:none;font-feature-settings:"liga" 0, "clig" 0;font-family:Menlo,Consolas,Liberation Mono,monospace}.pt-editor-host .cm-editor{background:var(--pt-paper);height:100%;color:var(--pt-ink);font-size:17px;line-height:1.6}.pt-editor-host .cm-scroller{line-height:1.6;overflow:auto}.pt-editor-host .cm-content{min-height:100%;padding-block:28px 40px;padding-inline:var(--pt-content-inset);color:var(--pt-ink);white-space:pre;caret-color:var(--pt-ink);line-height:1.6}.pt-editor-host .cm-content.cm-lineWrapping{white-space:pre-wrap;overflow-wrap:anywhere}.pt-editor-host .cm-line{min-height:1.6em;padding:0;line-height:1.6}.pt-editor-host .pt-hang{margin-left:calc(var(--pt-content-inset) * -1);padding-left:calc(var(--pt-hang) * 1ch + var(--pt-content-inset));text-indent:calc(var(--pt-hang) * -1ch)}.pt-editor-host .cm-cursor,.pt-editor-host .cm-dropCursor{border-left:2px solid var(--pt-ink)}.pt-editor-host .cm-activeLine{background:var(--pt-active-row)}.pt-editor-host .cm-selectionBackground,.pt-editor-host .cm-focused .cm-selectionBackground{background:var(--pt-selection)}.pt-editor-host .cm-content::selection{background:var(--pt-selection)}.pt-editor-host .cm-editor.cm-focused{outline:0}.pt-editor-host .cm-panels,.pt-editor-host .cm-search{border-color:var(--pt-delimiter);background:var(--pt-paper);color:var(--pt-ink);font-family:Menlo,Consolas,Liberation Mono,monospace;font-size:13px}.pt-editor-host .cm-panels-top{border-bottom:1px solid var(--pt-delimiter)}.pt-editor-host .cm-panels-bottom{border-top:1px solid var(--pt-delimiter)}.pt-editor-host .cm-search{align-items:center;gap:6px;padding:5px 8px;display:flex}.pt-editor-host .cm-panels input,.pt-editor-host .cm-search input{border:1px solid var(--pt-delimiter);background:var(--pt-paper);min-width:12em;color:var(--pt-ink);outline:0;padding:2px 4px;font:13px/20px Menlo,Consolas,Liberation Mono,monospace}.pt-editor-host .cm-panels input:focus,.pt-editor-host .cm-search input:focus{border-color:var(--pt-ink)}.pt-editor-host .cm-button{border:1px solid var(--pt-delimiter);background:var(--pt-project-bg);color:var(--pt-ink);padding:2px 6px;font:12px/18px Menlo,Consolas,Liberation Mono,monospace}.pt-editor-host .cm-button:hover{border-color:var(--pt-ink);background:var(--pt-paper)}.pt-editor-host .pt-line-project{color:var(--pt-project);font-weight:700}.pt-editor-host .pt-line-project.pt-level-1{font-size:1.6em;line-height:1.45}.pt-editor-host .pt-line-project.pt-level-2{font-size:1.35em;line-height:1.45}.pt-editor-host .pt-line-project.pt-level-3{font-size:1.18em;line-height:1.5}.pt-editor-host .pt-line-project.pt-level-4,.pt-editor-host .pt-line-project.pt-level-5,.pt-editor-host .pt-line-project.pt-level-6{font-size:1.05em;line-height:1.55}.pt-editor-host .pt-heading-title{background:var(--pt-project-bg);box-shadow:2px 0 0 var(--pt-project-bg), -2px 0 0 var(--pt-project-bg);color:var(--pt-project)}.pt-editor-host .pt-heading-marks,.pt-editor-host .pt-bullet,.pt-editor-host .pt-bullet-hidden,.pt-editor-host .pt-strong-mark,.pt-editor-host .pt-em-mark,.pt-editor-host .pt-strike-mark,.pt-editor-host .pt-code-mark,.pt-editor-host .pt-link-mark,.pt-editor-host .pt-line-rule{color:var(--pt-delimiter)}.pt-editor-host .pt-bullet-hidden{color:#0000}.pt-editor-host .pt-line-task,.pt-editor-host .pt-line-task-open,.pt-editor-host .pt-line-task-open .pt-label{color:var(--pt-ink)}.pt-editor-host .pt-line-task-done .pt-label,.pt-editor-host .pt-line-task-cancelled .pt-label{color:var(--pt-resolved);font-style:italic;text-decoration:none}.pt-editor-host .pt-line-status,.pt-editor-host .pt-line-note,.pt-editor-host .pt-line-prose,.pt-editor-host .pt-line-quote{color:var(--pt-prose)}.pt-editor-host .pt-strong{color:inherit;font-weight:700}.pt-editor-host .pt-em{color:inherit;font-style:italic}.pt-editor-host .pt-strike{color:inherit;text-decoration:line-through;text-decoration-thickness:1px}.pt-editor-host .pt-code{background:var(--pt-project-bg);color:inherit;font-size:.94em}.pt-editor-host .pt-link{color:var(--pt-link);text-underline-offset:2px;text-decoration:underline;text-decoration-thickness:1px}.pt-editor-host .pt-tag{color:var(--pt-tag);font-weight:700}.pt-editor-host .pt-tag-today,.pt-editor-host .pt-tag-high,.pt-editor-host .pt-tag-low,.pt-editor-host .pt-tag-critical{background:var(--pt-tag-today);color:var(--pt-tag-chip-ink);font-weight:700}.pt-editor-host .pt-tag-high{background:var(--pt-tag-high)}.pt-editor-host .pt-tag-low{background:var(--pt-tag-low);color:var(--pt-tag-low-ink)}.pt-editor-host .pt-tag-critical{background:var(--pt-tag-critical)}.pt-editor-host .pt-tag-cancelled{color:var(--pt-tag-resolved);font-weight:400}.pt-editor-host .pt-tag-done,.pt-editor-host .pt-tag-lasted{color:var(--pt-tag-done);font-weight:700}.pt-editor-host .pt-tag-project,.pt-editor-host .pt-tag-started{color:var(--pt-tag);font-weight:700}.pt-editor-host .pt-line-task-done .pt-tag,.pt-editor-host .pt-line-task-cancelled .pt-tag{color:var(--pt-tag-resolved);background:0 0;font-weight:400}.pt-editor-host .pt-bullet-dot,.pt-editor-host .pt-bullet-gap{color:var(--pt-prose);text-indent:0;white-space:nowrap;display:inline-block}.pt-editor-host .pt-bullet-gap:before{content:""}.pt-editor-host .pt-line-code{background:var(--pt-project-bg);color:var(--pt-ink)}.pt-editor-host .pt-line-archived{opacity:.7}.pt-editor-host .pt-marker{cursor:pointer;text-indent:0;white-space:nowrap;display:inline-block}.pt-editor-host .pt-marker-box{text-align:right;width:3ch;padding-right:.15ch;display:inline-block}.pt-editor-host .pt-marker-symbol{font-family:Apple Symbols,Segoe UI Symbol,DejaVu Sans,Menlo,sans-serif;font-size:1.15em;line-height:1}.pt-editor-host .pt-marker-open,.pt-editor-host .pt-marker-empty{color:var(--pt-marker-open);font-weight:400}.pt-editor-host .pt-marker-done{color:var(--pt-marker-done);font-weight:700}.pt-editor-host .pt-marker-cancelled{color:var(--pt-marker-cancelled);font-weight:700}.pt-editor-host .cm-tooltip.pt-link-popover{border:1px solid var(--pt-delimiter);background:var(--pt-paper);width:min(360px,86vw);box-shadow:var(--pt-shadow-popover);color:var(--pt-ink);text-indent:0;gap:8px;padding:10px 12px;font-family:Menlo,Consolas,Liberation Mono,monospace;font-size:13px;line-height:18px;display:grid}.pt-link-popover-field{grid-template-columns:4ch minmax(0,1fr);align-items:center;gap:8px;display:grid}.pt-link-popover-caption{color:var(--pt-prose);font-size:12px}.pt-link-popover-input{border:1px solid var(--pt-delimiter);min-width:0;color:var(--pt-ink);font:inherit;background:0 0;outline:0;padding:4px 6px}.pt-link-popover-input:focus{border-color:var(--pt-link)}.pt-link-popover-input::placeholder{color:var(--pt-prose)}.pt-link-popover-actions{justify-content:flex-end;gap:6px;display:flex}.pt-link-popover-button{border:1px solid var(--pt-delimiter);color:var(--pt-ink);font:inherit;cursor:pointer;background:0 0;padding:2px 8px}.pt-link-popover-button[type=submit]{background:var(--pt-active-row)}.pt-link-popover-button:hover,.pt-link-popover-button:focus-visible{border-color:var(--pt-ink)}.pt-editor-host .pt-remote-highlight{animation:3s ease-out forwards pt-remote-highlight}@keyframes pt-remote-highlight{0%{background:var(--pt-remote-flash)}to{background:0 0}}.empty-shell,.loading-shell,.message-shell{background:var(--pt-paper);min-height:100dvh;color:var(--pt-ink);align-content:center;justify-items:start;gap:12px;margin:0;padding:10vw;display:grid}.empty-mark,.eyebrow{color:var(--pt-delimiter);letter-spacing:.12em;text-transform:uppercase;font-size:12px;font-weight:700;line-height:18px}.empty-title,.loading-title,.message-title{max-width:28ch;color:var(--pt-ink);margin:0;font-family:Menlo,Consolas,Liberation Mono,monospace;font-size:clamp(1.5rem,4vw,2.6rem);font-weight:700;line-height:1.2}.empty-copy,.loading-copy{max-width:48rem;color:var(--pt-prose);margin:0;font-size:14px;line-height:24px}.message-title{max-width:44ch;font-size:clamp(1.25rem,3vw,2rem)}.file-name{color:var(--pt-ink);margin:0;font-family:Menlo,Consolas,Liberation Mono,monospace;font-size:15px;font-weight:700;line-height:24px}.file-path{overflow-wrap:anywhere;color:var(--pt-prose);font-size:12px;line-height:18px}.connection-status{color:var(--pt-prose);font-size:12px;line-height:18px}.connection-deleted{color:var(--pt-resolved)}.connection-reconnecting{color:var(--pt-marker-cancelled)}@media (width>=900px){.pt-workspace-with-activity{grid-template-rows:minmax(0,1fr);grid-template-columns:minmax(0,1fr) 280px}.pt-activity{border-top:0;border-left:1px solid var(--pt-delimiter)}}@media (width>=1100px){.pt-status-center{display:flex}}@media (width<=640px){.pt-shell{--pt-content-inset:14px}.pt-header{padding:0 10px}.pt-header-actions{gap:12px;padding-left:12px}.pt-status-bar{padding:0 10px}}@media (prefers-reduced-motion:reduce){.pt-editor-host .pt-remote-highlight{background:var(--pt-remote-flash);animation:none}}@scope(:root[data-theme=dark]){:scope{--lightningcss-light: ;--lightningcss-dark:initial;color-scheme:dark;--pt-paper:#1a1914;--pt-prose:#b3ae94;--pt-ink:#e8e3c9;--pt-resolved:#e8e3c9b8;--pt-project:#ef9f80;--pt-project-bg:#ffffff0a;--pt-link:#9fc5f0;--pt-delimiter:#ffffff5c;--pt-selection:#e8e3c933;--pt-active-row:#f7dc8612;--pt-marker-open:#e8e3c98c;--pt-marker-done:#5fd08a;--pt-marker-cancelled:#ff8070;--pt-tag:#f2918a;--pt-tag-resolved:#e8e3c9a8;--pt-tag-today:#eadd4e;--pt-tag-high:#ff7f00;--pt-tag-low:#4a473c;--pt-tag-critical:#ff4d4d;--pt-tag-done:#f5ae62;--pt-key-paper:#2c2b23;--pt-key-edge:#ffffff40;--pt-key-shade:#00000080;--pt-key-ink:#dcd7bc;--pt-remote-flash:#463a12;--pt-shadow-tooltip:0 6px 16px #00000080;--pt-shadow-popover:0 12px 28px #0009;--pt-search-match:#eadd4e2e;--pt-search-match-selected:#ff7f0042}.pt-editor-host .cm-editor.cm-focused>.cm-scroller>.cm-selectionLayer .cm-selectionBackground{background:var(--pt-selection)}.pt-editor-host .cm-searchMatch{background-color:var(--pt-search-match)}.pt-editor-host .cm-searchMatch-selected{background-color:var(--pt-search-match-selected);outline:1px solid var(--pt-tag-high)}.pt-editor-host .cm-button:active{background-image:none;background-color:var(--pt-active-row)}.pt-editor-host .cm-panels input::placeholder{color:var(--pt-prose)}.pt-palette-item-active{background:var(--pt-selection);box-shadow:inset 3px 0 0 var(--pt-project)}.pt-editor-host .pt-heading-marks,.pt-editor-host .pt-bullet{color:var(--pt-prose)}.pt-editor-host .pt-line-rule{color:var(--pt-marker-open)}.empty-mark,.eyebrow{color:var(--pt-prose)}.pt-editor-host .pt-line-archived{opacity:.85}}@media (forced-colors:active){.pt-editor-host .pt-bullet-hidden{color:#0000}.pt-editor-host .pt-marker{color:canvastext}.pt-key{box-shadow:none;color:canvastext;background:canvas;border-color:canvastext}}.pt-editor-host .cm-editor{--pt-fold-gutter-width:14px}.pt-editor-host .cm-editor .cm-gutters.cm-gutters-before{background:var(--pt-paper);border:0}.pt-editor-host .pt-fold-gutter{width:var(--pt-fold-gutter-width);min-width:var(--pt-fold-gutter-width);max-width:var(--pt-fold-gutter-width)}.pt-editor-host .pt-fold-gutter .cm-gutterElement{padding:0}.pt-editor-host .cm-content{padding-inline-start:calc(var(--pt-content-inset) - var(--pt-fold-gutter-width))}.pt-editor-host .pt-hang{margin-left:calc((var(--pt-content-inset) - var(--pt-fold-gutter-width)) * -1);padding-left:calc(var(--pt-hang) * 1ch + var(--pt-content-inset) - var(--pt-fold-gutter-width))}.pt-editor-host .pt-fold-button{width:var(--pt-fold-gutter-width);height:1.6em;color:var(--pt-prose);font:inherit;cursor:pointer;opacity:0;background:0 0;border:0;justify-content:center;align-items:center;margin:0;padding:0;display:inline-flex}.pt-editor-host .pt-fold-chevron{border-bottom:1px solid;border-right:1px solid;width:5px;height:5px;display:block;transform:rotate(-45deg)}.pt-editor-host .pt-fold-button[aria-expanded=true] .pt-fold-chevron{transform:rotate(45deg)}.pt-editor-host .pt-fold-button[aria-expanded=false],.pt-editor-host .pt-fold-button.pt-fold-active,.pt-editor-host .pt-fold-gutter .cm-gutterElement:hover .pt-fold-button{opacity:.65}.pt-editor-host .pt-fold-placeholder{color:var(--pt-prose);font:inherit;vertical-align:baseline;white-space:pre;cursor:pointer;-webkit-user-select:none;user-select:none;background:0 0;border:0;margin:0;padding:0 0 0 .75ch;font-size:.75em;display:inline-flex}.pt-editor-host .pt-fold-button:focus-visible,.pt-editor-host .pt-fold-placeholder:focus-visible{outline:1px solid var(--pt-prose);outline-offset:1px;opacity:1}.pt-editor-host .cm-line.pt-details-header{padding-left:calc(var(--pt-details-indent) * 1ch);text-indent:calc(var(--pt-details-indent) * -1ch)}.pt-editor-host .cm-line.pt-details-header-two-line{text-indent:0}.pt-editor-host .pt-details-title{color:var(--pt-ink);font-weight:600}.pt-editor-host .cm-line.pt-details-header-owned{text-indent:0}.pt-editor-host .pt-details-header-owned .pt-details-title{color:var(--pt-prose);font-weight:400}.pt-editor-host .pt-details-end{background:var(--pt-delimiter);vertical-align:middle;width:5ch;height:1px;display:inline-block}@media (hover:none){.pt-editor-host .pt-fold-button{opacity:.4}}@property --tw-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-shadow-color{syntax:"*";inherits:false}@property --tw-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-inset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-shadow-color{syntax:"*";inherits:false}@property --tw-inset-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-ring-color{syntax:"*";inherits:false}@property --tw-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-ring-color{syntax:"*";inherits:false}@property --tw-inset-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-ring-inset{syntax:"*";inherits:false}@property --tw-ring-offset-width{syntax:"<length>";inherits:false;initial-value:0}@property --tw-ring-offset-color{syntax:"*";inherits:false;initial-value:#fff}@property --tw-ring-offset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-outline-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-blur{syntax:"*";inherits:false}@property --tw-brightness{syntax:"*";inherits:false}@property --tw-contrast{syntax:"*";inherits:false}@property --tw-grayscale{syntax:"*";inherits:false}@property --tw-hue-rotate{syntax:"*";inherits:false}@property --tw-invert{syntax:"*";inherits:false}@property --tw-opacity{syntax:"*";inherits:false}@property --tw-saturate{syntax:"*";inherits:false}@property --tw-sepia{syntax:"*";inherits:false}@property --tw-drop-shadow{syntax:"*";inherits:false}@property --tw-drop-shadow-color{syntax:"*";inherits:false}@property --tw-drop-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-drop-shadow-size{syntax:"*";inherits:false}
|