claude-do-it 0.0.0-stage → 0.1.2

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Dominique Rau
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/NOTICE ADDED
@@ -0,0 +1,25 @@
1
+ The browser editor's notebook colours, PlainTasks markers, tag completions,
2
+ stamps, and tag colours are adapted from
3
+ PlainTasks (https://github.com/aziz/PlainTasks), an MIT licensed project,
4
+ Copyright (c) 2012 Allen Bargi and contributors. See the PlainTasks LICENSE file for the source attribution.
5
+
6
+ Copyright 2012-2013 Allen Bargi.
7
+
8
+ Permission is hereby granted, free of charge, to any person obtaining
9
+ a copy of this software and associated documentation files (the
10
+ "Software"), to deal in the Software without restriction, including
11
+ without limitation the rights to use, copy, modify, merge, publish,
12
+ distribute, sublicense, and/or sell copies of the Software, and to
13
+ permit persons to whom the Software is furnished to do so, subject to
14
+ the following conditions:
15
+
16
+ The above copyright notice and this permission notice shall be
17
+ included in all copies or substantial portions of the Software.
18
+
19
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
20
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
21
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
22
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
23
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
24
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
25
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,314 @@
1
- # Temporary Holding Version
1
+ # claude-do-it
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ [![CI](https://github.com/DomiR/claude-do-it/actions/workflows/ci.yml/badge.svg)](https://github.com/DomiR/claude-do-it/actions/workflows/ci.yml)
4
+
5
+ **A Markdown todo that you and Claude edit at the same time.**
6
+
7
+ You type in a browser editor. Claude reads the list, plans each task, hands it
8
+ to worker agents, and writes status, questions, and results back under the line
9
+ it belongs to. Your keystrokes, the agents' changes, and edits made to the file
10
+ on disk all merge instead of overwriting each other: every open file is one
11
+ [Loro](https://loro.dev) CRDT held by a small daemon on your machine. The file
12
+ itself stays plain Markdown, so any other editor still works on it.
13
+
14
+ ```mermaid
15
+ flowchart LR
16
+ you["You<br/>browser editor"] <-->|"live sync"| daemon
17
+ agents["Claude Code<br/>do-it CLI"] <-->|"versioned commands"| daemon
18
+ daemon["do-it daemon<br/>127.0.0.1, one CRDT per file"] <-->|"atomic writes, file watching"| file[("TODO.md<br/>plain Markdown")]
19
+ other["Any other editor"] <--> file
20
+ ```
21
+
22
+ ## What's inside
23
+
24
+ - **The `do-it` CLI.** Versioned commands agents use to read and change a
25
+ todo. Every change names the version it was made against, so a stale edit is
26
+ refused with a diff instead of clobbering someone else's work.
27
+ - **The daemon.** One per user on `127.0.0.1`. It holds the open documents,
28
+ watches their files, merges outside edits, and serves the editor. It starts
29
+ on demand and stops after 30 idle minutes.
30
+ - **The browser editor.** Built with Foldkit and CodeMirror 6. It keeps the
31
+ todo as plain Markdown while showing a live PlainTasks preview with markers,
32
+ tags, actions, stamps, and a command palette. Every line stays rendered
33
+ while you edit it: Markdown you type renders as soon as it is complete, a
34
+ formatting bar on top applies the same formats, task markers show as ☐ ✔ ✘
35
+ and delete like one character, and links show as their text with a popover
36
+ to edit them.
37
+ - **The `/do-it` skill for Claude Code.** It turns a session into a conductor
38
+ that sizes, plans, and delivers every task through worker agents and one
39
+ shared tester.
40
+
41
+ ## Install
42
+
43
+ Needs [Bun](https://bun.sh) 1.3 or newer on macOS or Linux.
44
+
45
+ ### As a Claude Code plugin
46
+
47
+ In Claude Code:
48
+
49
+ ```
50
+ /plugin marketplace add DomiR/claude-do-it
51
+ /plugin install do-it@do-it
52
+ ```
53
+
54
+ Or from a terminal:
55
+
56
+ ```bash
57
+ claude plugin marketplace add DomiR/claude-do-it
58
+ claude plugin install do-it@do-it
59
+ ```
60
+
61
+ The plugin brings the skill and the `do-it` command, with nothing else to
62
+ install. Claude Code puts the plugin's `bin` folder on its Bash tool's PATH,
63
+ and the `do-it` there runs the published CLI of the same version through
64
+ `bunx`, which downloads it on first use. A `do-it` you installed yourself comes
65
+ first, so keep it as current as the plugin. Plugin skills carry the
66
+ plugin's name, so the command is `/do-it:do-it`. The plugin's `do-it` exists
67
+ only inside Claude Code: to run `do-it open TODO.md` in your own terminal, also
68
+ install the CLI as below.
69
+
70
+ ### With the CLI
71
+
72
+ ```bash
73
+ bun add -g claude-do-it # or: npm install -g claude-do-it (Bun still runs it)
74
+ do-it skill install # the /do-it skill, copied into ~/.claude/skills/do-it
75
+ do-it doctor # checks Bun, PATH, the daemon, and the skill
76
+ do-it --help
77
+ ```
78
+
79
+ Run `do-it skill install` again after upgrading to refresh the skill. It never
80
+ replaces a skill directory it did not create unless you pass `--force`.
81
+ `do-it --skill` prints the skill, and `do-it skill path` shows where the
82
+ package keeps it. To try it without installing, use `bunx claude-do-it open TODO.md`.
83
+ Skip `do-it skill install` when you use the plugin: you would get the skill
84
+ twice, as `/do-it` and `/do-it:do-it`.
85
+
86
+ `do-it doctor` checks that Bun is 1.3 or newer, which `do-it` your PATH finds (a
87
+ repo checkout, the global bun install, a bunx copy, or a global npm install),
88
+ that a running daemon has this CLI's version and build, and that the installed
89
+ skill matches the one this package ships. It prints one line per check and the
90
+ exact command that fixes each problem, never starts the daemon, and exits 1
91
+ when it finds a problem. `--skill-dir DIR` checks a skill installed elsewhere.
92
+
93
+ The command is `do-it`, not `do`: `do` is a reserved word in bash and zsh.
94
+
95
+ ### From source
96
+
97
+ ```bash
98
+ git clone https://github.com/DomiR/claude-do-it ~/Developer/claude-do-it
99
+ cd ~/Developer/claude-do-it
100
+ bun install
101
+ bun run build:web # the editor the daemon serves
102
+ mkdir -p ~/.local/bin
103
+ ln -sf "$PWD/packages/cli/src/bin.ts" ~/.local/bin/do-it # any directory on PATH works
104
+ ln -s "$PWD/skill" ~/.claude/skills/do-it # the skill, always current with the checkout
105
+ ```
106
+
107
+ ## Use it from Claude Code
108
+
109
+ ```
110
+ /do-it # TODO.md in the current directory, created when missing
111
+ /do-it PLAN.md
112
+ ```
113
+
114
+ With the plugin, the command is `/do-it:do-it` (for example `/do-it:do-it PLAN.md`).
115
+
116
+ The browser opens on the todo (inside Sheprd, in a split beside the
117
+ conversation). Add one task per line. The session is a conductor: it marks each
118
+ task `@started` as soon as it picks it up, sizes it, and delegates the rest
119
+ (planning, building, the single test queue, review, and answers to questions)
120
+ to agents, watching them instead of doing the work itself. Each task gets one
121
+ status line under it, plus any question it needs you to answer (answer by
122
+ writing under the question). Delivered tasks are checked off with `@done`,
123
+ their branch, and their test result. An optional messenger agent can write
124
+ those lines for the conductor.
125
+
126
+ Small tasks are planned by Opus and built by a Sonnet worker; medium ones use
127
+ Opus for both. Large tasks use a stronger planner, reviewer, and model when the
128
+ session finds one (an agent type or a CLI on PATH), otherwise Opus, and are always split
129
+ into subtasks written under the task. The first subtask is `Review this task`:
130
+ nothing is built until you check it off, and you can edit the other subtasks
131
+ before you do.
132
+
133
+ PlainTasks tags steer the work: `@critical`, `@high`, and `@today` go first,
134
+ `@low` last, and `@due(date)` reaches the worker as a deadline. Agents never
135
+ change your tags; `@started`, `@done`, and `@cancelled` come only from the
136
+ `do-it todo` commands.
137
+ The skill checks where it runs and starts workers there:
138
+
139
+ | Where | Workers |
140
+ | --- | --- |
141
+ | Herdr (`HERDR_ENV=1`) | one Claude session per task in a Herdr worktree and space, started in the background |
142
+ | [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
+ | [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`) |
144
+ | Any other terminal | Claude Code subagents in worktrees, which need nothing beyond Claude Code |
145
+
146
+ Set `DO_IT_BROWSER` to the command that should open links, or to `true` to
147
+ open nothing.
148
+
149
+ ## Use the CLI
150
+
151
+ ```bash
152
+ do-it open TODO.md # start or reuse the daemon, open the browser, return
153
+ do-it read TODO.md # "Version: 7" then the text
154
+ do-it todo list TODO.md
155
+ do-it todo add TODO.md --version 7 --section tasks "Write tests"
156
+ do-it todo start TODO.md --version 8 --section tasks 3
157
+ do-it todo check TODO.md --version 9 3
158
+ do-it edit TODO.md --version 10 --old "ship friday" --new "ship monday"
159
+ do-it append TODO.md --version 11 --section notes -- "- found: flaky CI"
160
+ do-it diff TODO.md --since 9 --wait 300
161
+ do-it watch TODO.md --todos # or --settle 25 for one line per pause
162
+ do-it status
163
+ do-it stop
164
+ do-it doctor # what is wrong with this install, and how to fix it
165
+ ```
166
+
167
+ ### How versions work
168
+
169
+ Every reply starts with the document's version, and every change, by anyone,
170
+ takes the next one. A change names the version it was made against. When the
171
+ document has moved on, nothing is applied and the reply is the latest version
172
+ plus a diff (exit 1). With `--section ID` a stale version still applies while
173
+ that section is unchanged.
174
+
175
+ `do-it watch TODO.md --settle 25` prints one `Settled at version N by WHO`
176
+ line once nobody changed the document for 25 seconds. When the person's latest
177
+ change adds a line (Enter at the end of a task or inside one), it settles after
178
+ `--settle-line` seconds instead (2 by default), so a new todo reaches the agents
179
+ at once. Every other change (typing, editing or deleting text, toggling a box)
180
+ waits the full `--settle`. Set `--settle-line` to the `--settle` value to
181
+ always wait the full period.
182
+
183
+ A resumed watch that missed history prints
184
+ `Missed changes, now at version N: run do-it read`. Use `--quiet` to skip the
185
+ initial `Watching PATH at version N` line.
186
+
187
+ `--json` works on every command, and `--as NAME` (or `DO_IT_AGENT`) names the
188
+ agent.
189
+
190
+ | Exit code | Meaning |
191
+ | --- | --- |
192
+ | 0 | Done |
193
+ | 1 | Refused, version mismatch, or a problem `do-it doctor` found |
194
+ | 2 | Invalid arguments |
195
+ | 3 | The daemon is not running and could not start |
196
+ | 4 | Nothing changed yet |
197
+ | 9 | The document was deleted |
198
+
199
+ ## Editor keys
200
+
201
+ | Action | macOS | Linux and Windows |
202
+ | --- | --- | --- |
203
+ | New task | `Cmd-Enter` or `Cmd-I` | `Ctrl-Enter` or `Ctrl-I` |
204
+ | Toggle done | `Cmd-D` or `Cmd-Shift-Enter` | `Ctrl-D` or `Ctrl-Shift-Enter` |
205
+ | Toggle cancelled | `Ctrl-C` or `Cmd-Alt-Enter` | `Alt-C` or `Ctrl-Alt-Enter` |
206
+ | Archive resolved tasks | `Cmd-Shift-A` or `Cmd-Alt-A` | `Ctrl-Shift-A` or `Ctrl-Alt-A` |
207
+ | Move up | `Cmd-Ctrl-Up` or `Alt-Up` | `Ctrl-Shift-Up` or `Alt-Up` |
208
+ | Move down | `Cmd-Ctrl-Down` or `Alt-Down` | `Ctrl-Shift-Down` or `Alt-Down` |
209
+ | Continue task | `Enter` | `Enter` |
210
+ | Note under the task (folds with it) | `Shift-Enter` | `Shift-Enter` |
211
+ | Expand a PlainTasks tag, or indent | `Tab` | `Tab` |
212
+ | Outdent | `Shift-Tab` or `Cmd-[` | `Shift-Tab` or `Ctrl-[` |
213
+ | Open link | `Cmd-Shift-U` or `Alt-Enter` | `Alt-Enter` |
214
+ | Edit link (or click a link) | `Cmd-K` | `Ctrl-K` |
215
+ | Bold | `Cmd-B` | `Ctrl-B` |
216
+ | Italic | `Cmd-Shift-I` | `Alt-Shift-I` |
217
+ | Strikethrough | `Cmd-Shift-X` | `Ctrl-Shift-X` |
218
+ | Inline code | `Cmd-E` | `Ctrl-E` |
219
+ | Task line on or off | `Cmd-Alt-4` | `Ctrl-Shift-4` |
220
+ | Bulleted line on or off | `Cmd-Alt-5` | `Ctrl-Shift-5` |
221
+ | Project heading on or off | `Cmd-Alt-2` | `Ctrl-Shift-2` |
222
+ | Go to project | `Cmd-P` | `Ctrl-P` |
223
+ | Command palette | `Cmd-Shift-P` or `F1` | `Ctrl-Shift-P` or `F1` |
224
+ | Find | `Cmd-F` | `Ctrl-F` |
225
+ | Keyboard shortcuts | `Cmd-/` | `Ctrl-/` |
226
+
227
+ Escape then Tab leaves the editor and returns focus to the page.
228
+
229
+ ## The daemon
230
+
231
+ There is one daemon per user. Any command starts it on demand; `do-it stop` or
232
+ 30 idle minutes stop it. State lives in `~/.do-it` (`DO_IT_HOME`): the runtime
233
+ record, a per user token (mode 0600), the log, and the last version of each
234
+ file, so version numbers survive restarts. It listens on `127.0.0.1:47100`
235
+ (`DO_IT_PORT`) and only answers requests that carry the token or the browser
236
+ cookie the `open` link sets. The runtime record and `/health` also carry a
237
+ build id (the npm build's id, or `source:` and the checkout path), which
238
+ `do-it doctor` compares with its own. In a checkout that id names the
239
+ checkout, not the revision, so after a `git pull` run `do-it stop` yourself.
240
+
241
+ ## How it's built
242
+
243
+ | Package | What it does |
244
+ | --- | --- |
245
+ | [`protocol`](packages/protocol) | RPC schema, errors, and formatting shared by every side |
246
+ | [`doc`](packages/doc) | The CRDT document, Markdown sections and tasks, diffs, and atomic file I/O |
247
+ | [`server`](packages/server) | The daemon: HTTP and RPC, auth, the document registry, file watching |
248
+ | [`client`](packages/client) | Finds, starts, and stops the daemon, plus a typed RPC client |
249
+ | [`cli`](packages/cli) | The `do-it` command |
250
+ | [`web`](packages/web) | The browser editor |
251
+ | [`tui`](packages/tui) | A terminal editor (not built yet) |
252
+
253
+ Everything runs on [Effect](https://effect.website) 4. File system, paths,
254
+ child processes, standard I/O, crypto, and configuration all go through Effect
255
+ platform services, and Bun provides the implementations. The design notes are
256
+ in [docs/plan.md](docs/plan.md) and [docs/design.md](docs/design.md).
257
+
258
+ ## Develop
259
+
260
+ ```bash
261
+ bun run test # unit, daemon, and end to end tests
262
+ bun run check # typecheck (effect-tsgo tsc)
263
+ bun run lint # Oxlint with the Effect rules
264
+ bun run format # oxfmt (bun run fmt:check only checks)
265
+ bun run build:web
266
+ bun run build:npm # the npm package, in dist/npm
267
+ bun --cwd packages/web run dev # editor with a proxy to the daemon on 47100
268
+ ```
269
+
270
+ CI runs the typecheck, lint, format check, tests, and both builds on every push
271
+ and pull request.
272
+ `claude plugin validate . --strict` checks the plugin and marketplace manifests.
273
+
274
+ ### Release
275
+
276
+ Set `APP_VERSION` in `packages/protocol/src/index.ts` (the npm package takes its
277
+ version from it), and the same version in `.claude-plugin/plugin.json` and in
278
+ the pinned `claude-do-it@` version in `bin/do-it` (`tests/plugin.test.ts` checks
279
+ that the three match). Add a section for the new version at the top of
280
+ `CHANGELOG.md`. Commit to `main`, then tag and push:
281
+
282
+ ```bash
283
+ git tag vX.Y.Z && git push origin vX.Y.Z
284
+ ```
285
+
286
+ The Release workflow checks that the tag equals `v` plus `APP_VERSION` and the
287
+ `version` in `.claude-plugin/plugin.json`, runs the CI checks, builds
288
+ `dist/npm`, and publishes it to npm with trusted publishing (OIDC), which adds
289
+ provenance itself. No `NPM_TOKEN` secret is needed. The job has
290
+ `id-token: write`, runs on a GitHub-hosted runner, and installs the latest npm
291
+ first, because trusted publishing needs npm 11.5.1 or newer. The plugin's
292
+ `do-it` runs that exact npm version, so a plugin installed from `main` fails
293
+ until the workflow has published it: push the tag right after the commit.
294
+
295
+ One time setup on npmjs.com, as the `domir` account: open the package
296
+ `claude-do-it`, then Settings, Trusted Publisher, and choose GitHub Actions.
297
+ Enter the organization or user `DomiR`, the repository `claude-do-it`, and the
298
+ workflow filename `release.yml` (the filename only, with the extension), and
299
+ leave the environment empty. Under allowed actions, tick `npm publish`: the
300
+ workflow publishes directly. The fields are case sensitive, npm does not check
301
+ them when you save, and a new configuration must finish its first successful
302
+ publish within two days. A wrong value shows up only as a failed publish.
303
+ Afterwards, set Publishing access to "Require two-factor authentication and
304
+ disallow tokens".
305
+
306
+ ## Acknowledgements
307
+
308
+ The editor's notebook colours, PlainTasks markers, tag completions, stamps, and
309
+ tag colours are adapted from [PlainTasks](https://github.com/aziz/PlainTasks)
310
+ (MIT). See [NOTICE](NOTICE).
311
+
312
+ ## License
313
+
314
+ [MIT](LICENSE)
package/bin/daemon.js ADDED
@@ -0,0 +1,15 @@
1
+ // @bun
2
+ import{BunRuntime as Ir}from"@effect/platform-bun";import{Cause as Lr,Effect as St,Exit as Mr}from"effect";var j=2097152,L=512,Ke=128,Xe=65536,Ee=4096;import*as wt from"effect/Data";class y extends wt.TaggedError("DocError"){}function Ge(e){if(!Number.isSafeInteger(e)||e<1)throw new y({code:"invalid",message:"A version must be a positive safe integer"})}class we{entries=Array(L);start=0;count=0;get size(){return this.count}record(e,t,o){let n={version:e,frontiers:t,origin:o===null?null:{...o}},r=(this.start+this.count-1)%L;if(this.count&&this.entries[r]?.version===e){this.entries[r]=n;return}if(this.count===L)this.entries[this.start]=n,this.start=(this.start+1)%L;else this.entries[(this.start+this.count)%L]=n,this.count++}find(e){for(let t=this.count-1;t>=0;t--){let o=this.entries[(this.start+t)%L];if(o.version===e)return o}return null}list(){return Array.from({length:this.count},(e,t)=>this.entries[(this.start+t)%L])}originsSince(e){if(!this.find(e))return null;let t=new Set,o=[];for(let n=0;n<this.count;n++){let r=this.entries[(this.start+n)%L];if(r.version<=e||!r.origin)continue;let i=Je(r.origin);if(!t.has(i))t.add(i),o.push({...r.origin})}return o}}function Je(e){return e.kind==="agent"?`agent:${e.name}`:e.kind}var Ho=new TextEncoder;function le(e){return Ho.encode(e).length}function zo(e){let t=le(e);if(t>j)throw new y({code:"too-large",message:`The document would be ${t} bytes, over the limit of ${j}`})}function U(e){if(zo(e),!e.isWellFormed())throw new y({code:"invalid",message:"Text contains an unpaired surrogate"})}function Tt(e,t){let o=e.charCodeAt(t-1),n=e.charCodeAt(t);return!(o>=55296&&o<=56319&&n>=56320&&n<=57343)}function Ye(e,t){let o=t.map((r)=>({...r})).sort((r,i)=>r.from-i.from||r.to-i.to),n=0;for(let r of o){if(!Number.isSafeInteger(r.from)||!Number.isSafeInteger(r.to)||r.from<n||r.from>r.to||r.to>e.length||!Tt(e,r.from)||!Tt(e,r.to))throw new y({code:"invalid",message:`The edit ${r.from}..${r.to} overlaps another edit, is outside the text, or splits a character`});U(r.insert),n=r.to}return o.filter((r)=>e.slice(r.from,r.to)!==r.insert)}function pe(e,t){let o=Ye(e,t),n=[],r=0;for(let s of o)n.push(e.slice(r,s.from),s.insert),r=s.to;n.push(e.slice(r));let i=n.join("");return U(i),i}import{createTwoFilesPatch as jo,diffArrays as Ko,diffChars as Xo}from"diff";var bt=16384,kt=250;function Dt(e,t=0){let o=[],n=t,r=n,i=[];function s(){if(r!==n||i.length)o.push({from:r,to:n,insert:i.join("")});i=[]}for(let a of e)if(a.added)i.push(a.value);else if(a.removed)n+=a.value.length;else s(),n+=a.value.length,r=n;return s(),o}function Z(e,t){if(e===t)return[];let o=performance.now()+kt,n=e.match(/[^\n]*\n|[^\n]+$/g)??[],r=t.match(/[^\n]*\n|[^\n]+$/g)??[],i=o-performance.now(),s=i>0?Ko(n,r,{timeout:i}):void 0;if(!s)return[{from:0,to:e.length,insert:t}];let a=Dt(s.map((l)=>({...l,value:l.value.join("")}))),d=[];for(let l of a){let u=e.slice(l.from,l.to),f=o-performance.now();if(!u||!l.insert||f<=0||le(u)>bt||le(l.insert)>bt){d.push(l);continue}let h=Xo(u,l.insert,{timeout:Math.min(kt/4,f)});if(h)d.push(...Dt(h,l.from));else d.push(l)}return d}function Te(e,t,o,n){if(e===t)return"";let r=jo(String(o),String(n),e,t,void 0,void 0,{context:2,headerOptions:{includeIndex:!1,includeUnderline:!1,includeFileHeaders:!0}});return le(r)>Xe?null:r}function A(e){let t=[],o=null,n=0;while(!0){let r=e.indexOf(`
3
+ `,n),i=r===-1?e.length:r,s=i>n&&e[i-1]==="\r"?i-1:i,a=e.slice(n,s),d=/^[ \t]*/.exec(a)[0],l=a.slice(d.length),u=n+d.length,f={from:n,to:s,indent:d.length,depth:Array.from(d).reduce((h,g)=>h+(g==="\t"?4:1),0),content:u,kind:"paragraph"};if(o){let h=0;while(l[h]===o.marker)h++;if(h>=o.count&&!l.slice(h).trim())f.kind="fence",o=null;else f.kind="code"}else{let h=/^(`{3,}|~{3,})(.*)$/.exec(l),g=/^(#{1,6})(?: |$)/.exec(l),w=/^(?:[-*+]|[0-9]{1,9}[.)])(?:[ \t]|$)/.exec(l);if(h&&(h[1][0]==="~"||!h[2].includes("`")))o={marker:h[1][0],count:h[1].length},f.kind="fence";else if(!l)f.kind="blank";else if(g)f.kind="heading",f.level=g[1].length,f.content=u+g[0].length;else if(/^(?:-(?: *-){2,}|\*(?: *\*){2,}|_(?: *_){2,})\s*$/.test(l))f.kind="rule";else if(l.startsWith(">"))f.kind="quote";else if(w){f.kind="item",f.content=u+w[0].length;let b=/^\[([ xX-])\](?:[ \t]|$)/.exec(l.slice(w[0].length));if(b)f.checkbox=f.content,f.content+=b[0].length,f.state=b[1]===" "?"open":b[1]==="-"?"cancelled":"done"}}if(t.push(f),r===-1)return t;n=i+1}}function At(e,t){let o=e[t].depth,n=t+1;for(let r=n;r<e.length;r++){let i=e[r];if(i.kind==="blank"||i.kind==="code"&&i.from===i.to)continue;if(i.depth<=o||i.kind==="heading")break;n=r+1}return n}function Rt(e){return e.toLowerCase().replace(/[^\p{L}\p{N} _-]/gu,"").replace(/ /g,"-")}function _(e){let t=[],o=new Map;for(let[n,r]of A(e).entries()){if(r.kind!=="heading"||r.level>3)continue;let i=e.slice(r.content,r.to).trim().replace(/#+$/,"").trim(),s=Rt(i),a=o.get(s)??0;o.set(s,a+1);let d=t.at(-1);if(d)d.to=r.from;t.push({id:a?`${s}-${a}`:s,title:i,level:r.level,line:n,from:r.from,to:e.length})}return t}function F(e,t){let o=t.trim().replace(/^#+/,""),n=_(e).find((r)=>r.id===o);if(!n)throw new y({code:"not-found",message:`No section has the ID ${JSON.stringify(o)}`});return n}function ie(e,t){let o=t.trim().replace(/^#+/,""),n=_(e).find((r)=>r.id===o);return n?e.slice(n.from,n.to):null}var Go="started|done|cancelled|lasted";function Ze(e){return new RegExp(`(?:^|[ \\t]+)@(?:${e??Go})(?![A-Za-z0-9_])[ \\t]*\\([^()\\r\\n]*\\)`,"g")}function B(e,t){return e.replace(Ze(t),"")}function Jo(e,t){return Ze(t).test(e)}function Yo(e,t){let o=A(e)[t.line];if(!o||o.state===void 0)return null;return{from:o.content,to:o.to,body:e.slice(o.content,o.to)}}function Ft(e,t){let o=/[ \t]*$/.exec(e),n=e.slice(0,o.index),r=/[ \t]+@project(?:[ \t]*\([^()\r\n]*\))?$/.exec(n),s=(r?n.slice(0,r.index):n).trimEnd();return`${s?`${s} ${t}`:t}${r?.[0]??""}${o[0]}`}function Pt(e,t,o){let n=Yo(e,t);if(!n)return[];let r=o(n.body);return r===n.body?[]:[{from:n.from,to:n.to,insert:r}]}function Ct(e,t){let o=Nt(t);return e==="started"?`@${e}(${o})`:`@${e} (${o})`}function $t(e){let t=/(?:^|[ \t])@started[ \t]*\((\d{2})-(\d{2})-(\d{2}) (\d{2}):(\d{2})\)/.exec(e);if(!t)return null;let[,o,n,r,i,s]=t.map(Number);return new Date(2000+o,n-1,r,i,s)}var Ot=(e)=>Date.UTC(e.getFullYear(),e.getMonth(),e.getDate(),e.getHours(),e.getMinutes());function _t(e){let t=Math.max(0,Math.floor(e/60000)),o=Math.floor(t/1440),n=Math.floor(t%1440/60),r=t%60,i=`${n}:${String(r).padStart(2,"0")}`;if(!o)return i;let s=`${o} day${o===1?"":"s"}`;return n||r?`${s}, ${i}`:s}function Zo(e,t,o){let n=A(e)[t.line];if(!n||n.checkbox===void 0||n.state===o)return[];let r=o==="done"?"[x]":o==="cancelled"?"[-]":"[ ]";return[{from:n.checkbox,to:n.checkbox+3,insert:r}]}function Nt(e){let t=(o)=>String(o).padStart(2,"0");return`${t(e.getFullYear()%100)}-${t(e.getMonth()+1)}-${t(e.getDate())} ${t(e.getHours())}:${t(e.getMinutes())}`}function Qe(e,t,o,n){let r=Ct(o,n);return Pt(e,t,(i)=>Ft(B(i,o),r))}function qe(e,t,o,n){let r=A(e)[t.line]?.state,i=Zo(e,t,o),s=o==="done"?"done":o==="cancelled"?"cancelled":null,a=Pt(e,t,(d)=>{let l=d;if(o==="done")l=B(l,"cancelled");else if(o==="cancelled")l=B(B(l,"done"),"lasted");else l=B(B(B(l,"done"),"cancelled"),"lasted");if(s&&(r!==o||!Jo(l,s))){let u=Ct(s,n),f=s==="done"?$t(l):null;if(f)u+=` @lasted(${_t(Ot(n)-Ot(f))})`;l=Ft(B(B(l,s),"lasted"),u)}return l});return[...i,...a].sort((d,l)=>d.from-l.from||d.to-l.to)}function H(e){return e.replace(Ze(),"").trim()}function Q(e){let t=[],o=null,n=new Map(_(e).map((r)=>[r.line,r.id]));for(let[r,i]of A(e).entries()){let s=n.get(r);if(s!==void 0)o=s;if(i.state===void 0)continue;t.push({number:t.length+1,line:r,state:i.state,text:e.slice(i.content,i.to).trim(),indent:i.depth,section:o})}return t}function ke(e,t,o){let n=o===void 0?null:F(e,o),r=n?A(e):null,i=Q(e).filter((d)=>!n||r[d.line].from>=n.from&&r[d.line].from<n.to),s=H(String(t).trim()).toLowerCase(),a;if(typeof t==="number"||/^\+?\d+$/.test(s))a=i.filter((d)=>d.number===Number(s));else if(a=i.filter((d)=>H(d.text).toLowerCase()===s),!a.length&&s)a=i.filter((d)=>H(d.text).toLowerCase().includes(s));if(!a.length)throw new y({code:"not-found",message:`No todo matches ${JSON.stringify(t)}`});if(a.length>1)throw new y({code:"ambiguous",message:`${a.length} todos match ${JSON.stringify(t)}, name one by its number`});return a[0]}function be(e,t){return pe(e,[t]),[t]}function et(e,t,o){if(t=t.trim(),Array.from(t).length>Ee)throw new y({code:"too-large",message:`A todo may have at most ${Ee} characters`});if(!t||/[\r\n]/.test(t))throw new y({code:"invalid",message:"A todo must be one nonempty line of text"});let n=o===void 0?null:F(e,o),r=A(e),i=-1;for(let[u,f]of r.entries())if(f.state!==void 0&&(!n||f.from>=n.from&&f.from<n.to))i=u;let s=`- [ ] ${t}`;if(i===-1)return De(e,s,o);let a=r[i],d=r[At(r,i)-1].to,l=e.slice(a.from,a.from+a.indent);return be(e,{from:d,to:d,insert:`
4
+ ${l}${s}`})}function It(e,t){let o=e.replace(/[\r\n]+$/,""),n=A(o).at(-1),r=!o?"":n?.kind==="item"&&t.startsWith("- ")?`
5
+ `:`
6
+
7
+ `;return{from:o.length,to:e.length,insert:`${r}${t}
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
+ `),l=d>0&&s[d-1]==="\r";return{text:s.replace(/\r\n/g,`
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.2";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("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)+`
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
+ `);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
+ `:"",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 ADDED
@@ -0,0 +1,15 @@
1
+ #!/usr/bin/env bun
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}
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
+ `)}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
+ `)?e.slice(0,-1):e)}function re(e){return Z(JSON.stringify(e))}function me(e,t){return t?`${e}
7
+ ${t}`:e}function nn(e){if(!e.changed)return{text:`Unchanged. Version: ${e.version}`,json:{kind:"unchanged",version:e.version,section:e.section,others:e.others,diff:e.others?.diff??null}};if(e.section&&e.others){let t=`Updated section "${e.section}". Version: ${e.version}. Other sections changed since ${e.others.since}:`;return{text:me(t,e.others.diff),json:{kind:"updated-section",version:e.version,section:e.section,others:e.others,diff:e.others.diff}}}return{text:`Updated. Version: ${e.version}`,json:{kind:"updated",version:e.version,section:e.section,others:e.others,diff:e.others?.diff??null}}}function L(e,t){return typeof e==="object"&&e!==null&&t in e?e[t]:void 0}function on(e,t){let n=typeof L(e,"latest")==="number"?L(e,"latest"):t;if(L(e,"forgotten")===!0||L(e,"_tag")==="Forgotten")return{text:`Version mismatch, nothing changed. Latest version: ${n}. Version ${t} is no longer kept: run do-it read`,json:{kind:"version-mismatch",latest:n,since:t,diff:null,forgotten:!0}};let o=typeof L(e,"since")==="number"?L(e,"since"):t,s=typeof L(e,"diff")==="string"?L(e,"diff"):null,u=`Version mismatch, nothing changed. Latest version: ${n}. Changes since ${o}:`;return{text:me(u,s),json:{kind:"version-mismatch",latest:n,since:o,diff:s,forgotten:!1}}}function rn(e,t){return t?e.todos.map(Le).join(`
8
+ `):me(Pe(e.from,e.to,e.by),e.diff)}function ze(e){return{event:"change",from:e.from,to:e.to,by:e.by,diff:e.diff,todos:e.todos}}function sn(e){return e.map(Le).join(`
9
+ `)}function an(e){let t=L(e,"_tag");return typeof t==="string"?t:"RpcClientError"}function Eo(e){let t=L(e,"_tag");return typeof t==="string"&&["VersionMismatch","Refused","Forgotten","NothingYet","DocumentGone","Unauthorized"].includes(t)}function ln(e){return Eo(e)?Nt(e):3}function ce(e){let t=L(e,"errors");if(Array.isArray(t)&&t.length>0)return t.map(ce).join(`
10
+ `);let n=L(e,"message");return typeof n==="string"?n:String(e)}function De(e){let t=L(e,"_tag");return typeof t==="string"?t:void 0}function cn(e){if(typeof e!=="object"||e===null)return{};return Object.fromEntries(Object.entries(e).filter(([t])=>t!=="_tag"&&t!=="message"&&t!=="code"))}import{Config as pn,Effect as Re,Option as dn,Path as vo,Schema as Y}from"effect";import{Argument as I,CliConfig as Co,CliError as Ri,CliOutput as Ye,Command as k,Flag as p,GlobalFlag as qe}from"effect/cli";var un={...Ye.defaultFormatter({colors:!1}),formatVersion:(e,t)=>`${e} ${t}`},fn=Ye.layerPresenter({present:(e)=>e._tag==="Help"&&e.reason==="Requested"||e._tag==="Version"?Ye.defaultPresenter.present(e):Re.void}),To=new Set(["open","read","sections","edit","append","todo","diff","watch","status","stop","doctor"]),$o=new Set(["--old","--new","--section","--version","--since","--wait","--settle","--settle-line","--as","--skill-dir"]);function Je(e){if(e[0]==="--skill")return Je(["skill","show",...e.slice(1)]);let t=[],n=!0;for(let r=0;r<e.length;r+=1){let o=e[r];if(n&&o==="--"){n=!1,t.push(o);continue}if(n&&$o.has(o)&&r+1<e.length&&e[r+1]!=="--"){t.push(`${o}=${e[r+1]}`),r+=1;continue}t.push(o)}return t}function Do(e,t){let n=!1;for(let[r,o]of e.entries()){if(r>=t)break;if(n){n=!1;continue}if(o==="--as"){n=!0;continue}if(o==="--json"||o.startsWith("--as=")||o==="--help"||o==="-h")continue;if(o.startsWith("-"))continue;return!0}return!1}function hn(e){let t=e.findIndex((r)=>r==="--version"||r==="-v"),n=t>=0&&!Do(e,t);return Co.make({builtIns:n||!e.some((r)=>To.has(r))?qe.BuiltIns:qe.BuiltIns.filter((r)=>r!==qe.Version)})}var Xe=Y.Int.pipe(Y.check(Y.isGreaterThan(0))),Ze=Y.Finite.pipe(Y.check(Y.isBetween({minimum:0,maximum:600}))),Ro=Y.String.pipe(Y.check(Y.isBetweenLength(1,64),Y.isPattern(/^[A-Za-z0-9._-]+$/))),Q=p.String("section").pipe(p.optional),ge=p.Int("version").pipe(p.withSchema(Xe)),Io=p.Int("since").pipe(p.withSchema(Xe)),Ao=p.Int("since").pipe(p.withSchema(Xe),p.optional),Oo=p.Finite("wait").pipe(p.withDefault(0),p.withSchema(Ze)),_o=p.Finite("settle").pipe(p.withSchema(Ze),p.withDescription('Print one "Settled at version N by WHO" line once nobody changed the document for this many seconds'),p.optional),Fo=p.Finite("settle-line").pipe(p.withSchema(Ze),p.withDefault(2),p.withDescription("With --settle, wait only this many seconds (default 2) when the person's latest change adds a line, as Enter does at the end of a task or inside one. Every other change (typing, editing or deleting text, toggling a box) keeps the full --settle. Set it to the --settle value to always wait the full period")),V=(e)=>dn.isSome(e)?e.value:void 0,W=(e,t=!1)=>{let n=t?e??"TODO.md":e;return{displayPath:n,path:n}},No=pn.option(pn.String("DO_IT_AGENT")).pipe(Re.map((e)=>dn.getOrElse(e,()=>"").trim()||"agent"),Re.orDie),Lo={json:p.Boolean("json").pipe(p.withDefault(!1)),as:p.String("as").pipe(p.withDefault(No),p.withSchema(Ro))};function D(e,t,n,r){return Re.gen(function*(){let o=yield*e,s=yield*vo.Path,u=r(t,o);yield*n("path"in u?{...u,path:s.resolve(u.path)}:u)})}function yn(e){let t=k.make("do-it").pipe(k.withDescription("Commands: open, read, sections, edit, append, todo add, todo start, todo check, todo uncheck, todo cancel, todo uncancel, todo list, diff, watch, status, stop, doctor, skill install, skill path, skill show (or --skill)"),k.withSharedFlags(Lo)),n=k.make("open",{path:I.String("path").pipe(I.optional),noBrowser:p.Boolean("no-browser").pipe(p.withDefault(!1))},(l)=>D(t,l,e,(a,d)=>({kind:"open",options:d,...W(V(a.path),!0),noBrowser:a.noBrowser}))),r=k.make("read",{path:I.String("path"),section:Q},(l)=>D(t,l,e,(a,d)=>({kind:"read",options:d,...W(a.path),section:V(a.section)}))),o=k.make("sections",{path:I.String("path")},(l)=>D(t,l,e,(a,d)=>({kind:"sections",options:d,...W(a.path)}))),s=k.make("edit",{path:I.String("path"),version:ge,section:Q,old:p.String("old"),next:p.String("new")},(l)=>D(t,l,e,(a,d)=>({kind:"edit",options:d,...W(a.path),version:a.version,section:V(a.section),old:a.old,next:a.next}))),u=k.make("append",{path:I.String("path"),version:ge,section:Q,text:I.String("text")},(l)=>D(t,l,e,(a,d)=>({kind:"append",options:d,...W(a.path),version:a.version,section:V(a.section),text:a.text}))),h=k.make("add",{path:I.String("path"),version:ge,section:Q,text:I.String("text")},(l)=>D(t,l,e,(a,d)=>({kind:"todo-add",options:d,...W(a.path),version:a.version,section:V(a.section),text:a.text}))),m=(l,a)=>k.make(l,{path:I.String("path"),version:ge,section:Q,target:I.String("target")},(d)=>D(t,d,e,(ie,xe)=>({kind:"todo-set",options:xe,...W(ie.path),version:ie.version,section:V(ie.section),target:ie.target,state:a}))),C=k.make("start",{path:I.String("path"),version:ge,section:Q,target:I.String("target")},(l)=>D(t,l,e,(a,d)=>({kind:"todo-start",options:d,...W(a.path),version:a.version,section:V(a.section),target:a.target}))),F=k.make("list",{path:I.String("path"),section:Q},(l)=>D(t,l,e,(a,d)=>({kind:"todo-list",options:d,...W(a.path),section:V(a.section)}))),N=k.make("todo").pipe(k.withSubcommands([h,C,m("check","done"),m("uncheck","open"),m("cancel","cancelled"),m("uncancel","open"),F])),A=k.make("diff",{path:I.String("path"),since:Io,wait:Oo},(l)=>D(t,l,e,(a,d)=>({kind:"diff",options:d,...W(a.path),since:a.since,wait:a.wait}))),S=k.make("watch",{path:I.String("path"),optionalSince:Ao,section:Q,todos:p.Boolean("todos").pipe(p.withDefault(!1)),settle:_o,settleLine:Fo,quiet:p.Boolean("quiet").pipe(p.withDefault(!1))},(l)=>D(t,l,e,(a,d)=>({kind:"watch",options:d,...W(a.path),since:V(a.optionalSince),section:V(a.section),todos:a.todos,settle:V(a.settle),settleLine:a.settleLine,quiet:a.quiet}))).pipe(k.withDescription("Print every change to PATH as it lands, or with --settle one line per pause")),O=k.make("status",{},(l)=>D(t,l,e,(a,d)=>({kind:"status",options:d}))),_=k.make("stop",{},(l)=>D(t,l,e,(a,d)=>({kind:"stop",options:d}))),y=k.make("doctor",{skillDir:p.String("skill-dir").pipe(p.optional)},(l)=>D(t,l,e,(a,d)=>({kind:"doctor",options:d,skillDir:V(a.skillDir)}))).pipe(k.withDescription("Check Bun, the do-it on PATH, the daemon's version and build, and the installed skill, and print the fix for each problem")),E=k.make("install",{dir:p.String("dir").pipe(p.optional),force:p.Boolean("force").pipe(p.withDefault(!1))},(l)=>D(t,l,e,(a,d)=>({kind:"skill-install",options:d,dir:V(a.dir),force:a.force}))),T=k.make("path",{},(l)=>D(t,l,e,(a,d)=>({kind:"skill-path",options:d}))),w=k.make("show",{},(l)=>D(t,l,e,(a,d)=>({kind:"skill-show",options:d}))),R=k.make("skill").pipe(k.withDescription("Install the /do-it skill for Claude Code, print it, or print where this CLI keeps it"),k.withSubcommands([E,T,w]));return t.pipe(k.withSubcommands([n,r,o,s,u,N,A,S,O,_,y,R]))}function mn(e){let t=0;for(let n=0;n<e.length;n++)if(e.charCodeAt(n)===10)t++;return t}function Vo(e,t){return mn(t)>mn(e)}function gn(e,t,n){return e!==void 0&&t!==void 0&&n.every((r)=>r._tag!=="Agent")&&Vo(e,t)}import{Effect as Ie,Stdio as Bo,Stream as jo}from"effect";var Uo=2097152,Ho=Ie.gen(function*(){let e=yield*Bo.Stdio,t=[],n=0;yield*jo.runForEach(e.stdin,(s)=>{if(n+=s.byteLength,n>Uo)return Ie.fail(Error("Standard input exceeds 2 MiB"));return t.push(s),Ie.void});let r=new Uint8Array(n),o=0;for(let s of t)r.set(s,o),o+=s.byteLength;return new TextDecoder("utf-8",{ignoreBOM:!0}).decode(r)});function kn(e){return e==="-"?Ho:Ie.succeed(e)}function P(e,t,n){return e.json?re(n):Z(t)}function Xo(e,t){return e.length>0&&e.every((n)=>n._tag==="Agent"&&n.name===t)}function Zo(e){return e.map((t)=>t._tag==="User"?"the user":t._tag==="File"?"file":t.name).join(", ")}function ee(e,t){return ue(e,t)}function Qo(e){return f.gen(function*(){let t=yield*q(),n=yield*ee(t,(o)=>o.open({path:e.path,create:!0}));if(!e.noBrowser)yield*Wt(n.url);let r=e.options.json?n.path:e.displayPath;return yield*P(e.options,`Opened ${r} at version ${n.version}: ${n.url}`,{path:r,version:n.version,url:n.url,created:n.created}),0})}function er(e){return f.gen(function*(){let t=yield*q(),n=yield*ee(t,(r)=>r.read({path:e.path,...e.section===void 0?{}:{section:e.section}}));if(e.options.json)yield*re({version:n.version,text:n.text});else yield*Z(`Version: ${n.version}
11
+ ${n.text}`.replace(/\n$/,""));return 0})}function tr(e){return f.gen(function*(){let t=yield*q(),n=yield*ee(t,(o)=>o.sections({path:e.path})),r=[`Version: ${n.version}`,...n.sections.map((o)=>`${o.id} (${o.level}) ${o.title}`)].join(`
12
+ `);return yield*P(e.options,r,{version:n.version,sections:n.sections}),0})}function nr(e,t){if(e.kind==="edit")return{_tag:"Replace",old:e.old,new:e.next};if(e.kind==="append")return{_tag:"Append",text:t??e.text};if(e.kind==="todo-add")return{_tag:"TodoAdd",text:t??e.text};if(e.kind==="todo-start")return{_tag:"TodoStart",target:/^[+]?\d+$/.test(e.target.trim())?Number(e.target.trim()):e.target};return{_tag:"TodoSet",target:/^[+]?\d+$/.test(e.target.trim())?Number(e.target.trim()):e.target,state:e.state}}function or(e){return f.gen(function*(){let t=yield*q(),n=e.kind==="append"||e.kind==="todo-add"?yield*kn(e.text):void 0,r=yield*ee(t,(s)=>s.edit({path:e.path,author:{_tag:"Agent",name:e.options.as},version:e.version,...e.section===void 0?{}:{section:e.section},op:nr(e,n)})),o=nn(r);return yield*P(e.options,o.text,o.json),0})}function rr(e){return f.gen(function*(){let t=yield*q(),n=yield*ee(t,(o)=>o.todos({path:e.path,...e.section===void 0?{}:{section:e.section}})),r=n.todos.map((o)=>`${o.number}. [${o.state==="done"?"x":o.state==="cancelled"?"-":" "}] ${o.text}`);return yield*P(e.options,[`Version: ${n.version}`,...r].join(`
13
+ `),{version:n.version,todos:n.todos}),0})}function ir(e){return f.gen(function*(){let t=yield*q(),n=yield*ee(t,(o)=>o.diff({path:e.path,since:e.since,wait:e.wait})),r=me(Pe(n.from,n.to,n.by),n.diff);return yield*P(e.options,r,{from:n.from,to:n.to,by:n.by,diff:n.diff,todos:n.todos}),0})}function sr(e){return f.gen(function*(){let t=yield*q({spawn:!1}),n=yield*ee(t,(s)=>s.status({})),r=Math.floor(Math.max(0,Date.now()-n.startedAt)/1000),o=Math.floor(r/60);if(e.options.json)yield*re({pid:n.pid,port:n.port,startedAt:n.startedAt,appVersion:n.appVersion,...n.build===void 0?{}:{build:n.build},documents:n.documents,upSeconds:r});else yield*Z(`Daemon pid ${n.pid} on 127.0.0.1:${n.port}, up ${o}m
14
+ ${n.documents.map((s)=>` ${s.path} version ${s.version} ${s.watchers} watchers`).join(`
15
+ `)}`);return 0})}function ar(e){return f.gen(function*(){let t=yield*Ce();return yield*P(e.options,t?"Stopped":"Not running",{stopped:t}),0})}function lr(e){return f.gen(function*(){let t=yield*tn(e.skillDir===void 0?{}:{skillDir:e.skillDir});return yield*P(e.options,en(t),t),t.ok?0:1})}function cr(e){return f.gen(function*(){let t=yield*Gt(e.dir,e.force);if(t.kind==="occupied")return yield*P(e.options,`${t.path} already exists and was not installed by do-it. Pass --force to replace it.`,{error:{code:"Refused",message:"The skill directory already exists",path:t.path}}),1;let n=t.kind==="linked"?`${t.path} already links to this skill.`:`${t.replaced?"Updated":"Installed"} the do-it skill in ${t.path}. In Claude Code, run /do-it to start.`;return yield*P(e.options,n,{kind:t.kind,path:t.path}),0})}function pr(e){return f.gen(function*(){let t=yield*le;return yield*P(e.options,t,{path:t}),0})}function dr(e){return f.gen(function*(){let t=yield*Kt;return yield*P(e.options,t.content,t),0})}function ur(e,t){if(e.quiet)return f.void;return P(e.options,`Watching ${e.displayPath} at version ${t}`,{event:"watching",version:t})}function fr(e,t){return P(e.options,`Daemon restarted, now version ${t}`,{event:"restarted",version:t})}function hr(e,t){return P(e.options,`Missed changes, now at version ${t}: run do-it read`,{event:"resync",version:t})}function yr(e){return P(e.options,`Deleted ${e.displayPath}`,{event:"deleted"})}function mr(e){return f.scoped(f.gen(function*(){let t=e.since,n,r=!0,o=!1,s=250,u=!1,h,m=yield*xn.make(),C=!1,F=0,N=[],A=!1,S,O=(y,E)=>f.sleep(y).pipe(f.andThen(f.uninterruptible(f.suspend(()=>{let T=F,w=N;C=!1,N=[],A=!1;let R=w.join(", ");return P(e.options,`Settled at version ${T}${R?` by ${R}`:""}`,{event:"settled",version:T,by:w,reason:E})})))),_=(y,E)=>f.suspend(()=>{if(e.settle===void 0)return f.void;let T=Zo(E).split(", ").filter(Boolean);F=y,N=[...new Set(C?[...N,...T]:T)],C=!0;let w=A&&e.settleLine<e.settle;return xn.run(m,O(Ko.millis((w?e.settleLine:e.settle)*1000),w?"line":"quiet")).pipe(f.asVoid)});while(!u){let y=yield*f.exit(q());if(ke.isFailure(y)){if(pe.hasInterruptsOnly(y.cause))break;let R=pe.squash(y.cause);if(De(R)!=="DaemonUnavailable"){let a=ce(R);if(a!==h)yield*P(e.options,`Watch failed: ${a}, retrying`,{event:"error",message:a});h=a}else h=void 0;yield*f.sleep(`${s} millis`),s=Math.min(5000,s*2);continue}let E=y.value;if(n!==void 0&&n!==E.pid)o=!0;n=E.pid;let T={path:e.path,...t===void 0?{}:{since:t},...e.section===void 0?{}:{section:e.section},text:e.settle!==void 0},w=yield*f.exit(ee(E,(R)=>R.watch(T).pipe(zo.runForEach((l)=>f.gen(function*(){if(h=void 0,l._tag==="Watching"){if(s=250,r)yield*ur(e,l.version),r=!1;else if(o)yield*fr(e,l.version),o=!1;if(S===void 0)S=l.text;t=l.version;return}if(l._tag==="Resync"){if(S=l.text,A=!1,yield*hr(e,l.version),e.settle!==void 0)yield*_(l.version,[]);t=l.version,o=!1;return}if(l._tag==="Deleted"){yield*yr(e),u=!0;return}t=l.to;let a=S;if(S=l.text,Xo(l.by,e.options.as))return;if(l.by.some((d)=>d._tag!=="Agent"))A=gn(a,l.text,l.by);if(e.settle!==void 0){yield*_(l.to,l.by);return}if(e.todos){if(!l.todos.length)return;if(e.options.json)yield*re(ze(l));else yield*Z(sn(l.todos))}else if(e.options.json)yield*re(ze(l));else yield*Z(rn(l,!1))})))));if(u)break;if(ke.isFailure(w)){if(pe.hasInterruptsOnly(w.cause))break;let R=pe.squash(w.cause),l=De(R);if(l==="DocumentGone"||l==="Refused")return yield*Sn(e,R);if(l!=="DaemonUnavailable"&&l!=="RpcClientError"){let a=ce(R);if(a!==h)yield*P(e.options,`Watch failed: ${a}, retrying`,{event:"error",message:a});h=a}else h=void 0}else h=void 0;yield*f.sleep(`${s} millis`),s=Math.min(5000,s*2)}return u?9:0}))}function gr(e){switch(e.kind){case"open":return Qo(e);case"read":return er(e);case"sections":return tr(e);case"edit":case"append":case"todo-add":case"todo-set":case"todo-start":return or(e);case"todo-list":return rr(e);case"diff":return ir(e);case"watch":return mr(e);case"status":return sr(e);case"stop":return ar(e);case"doctor":return lr(e);case"skill-install":return cr(e);case"skill-path":return pr(e);case"skill-show":return dr(e)}}function Sn(e,t){return f.gen(function*(){let n=("version"in e?e.version:("since"in e)?e.since:0)??0,r=De(t);if(e.kind==="status"&&r==="DaemonUnavailable")return yield*P(e.options,"Not running",{error:{code:"DaemonUnavailable",message:"Not running"}}),3;if(r==="VersionMismatch"||r==="Forgotten"){let h=on(t,n),{kind:m,...C}=h.json;return yield*P(e.options,h.text,{error:{code:r,message:h.text,...C}}),1}let o=ln(t),s=r==="NothingYet"&&e.kind==="diff"?`No change since version ${e.since} in ${e.wait} ${e.wait===1?"second":"seconds"}`:ce(t),u=cn(t);return yield*P(e.options,s,{error:{code:an(t),message:s,...u}}),o})}function kr(){let e=0,t=yn((n)=>f.gen(function*(){let r=yield*f.exit(gr(n));e=ke.isSuccess(r)?r.value:yield*Sn(n,pe.squash(r.cause))}));return f.gen(function*(){let n=yield*(yield*Go.Stdio).args,r=Je(n),o=yield*f.exit(Jo.runWith(t,{version:j,renderErrors:!0})(r).pipe(f.provide(qo.layer(hn(r))),f.provide(Yo.layer(un)),f.provide(fn)));if(ke.isFailure(o)){let s=ce(pe.squash(o.cause));if(n.includes("--json"))yield*re({error:{code:"invalid",message:s}});else yield*Z(s);return 2}return e}).pipe(f.provide(Wo.layer))}var xr=kr();Mo.runMain(xr,{disableErrorReporting:!0,teardown:(e,t)=>t(ke.isSuccess(e)?Number(e.value):1)});