claude-do-it 0.1.1 → 0.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +143 -13
- package/bin/daemon.js +14 -2211
- package/bin/do-it.js +13 -1206
- package/package.json +2 -2
- package/skill/SKILL.md +367 -61
- package/web/assets/index-D1yk8ubK.css +2 -0
- package/web/assets/index-DVWNl8ep.js +169 -0
- package/web/index.html +23 -10
- package/web/assets/index-6QftMBqY.css +0 -2
- package/web/assets/index-Bey8I5te.js +0 -130
package/bin/do-it.js
CHANGED
|
@@ -1,1208 +1,15 @@
|
|
|
1
1
|
#!/usr/bin/env bun
|
|
2
2
|
// @bun
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
import {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
}).pipe(
|
|
16
|
-
function paths(home) {
|
|
17
|
-
return Effect.gen(function* () {
|
|
18
|
-
const path = yield* Path.Path;
|
|
19
|
-
const root = path.resolve(home ?? (yield* defaultHome));
|
|
20
|
-
const lock = path.join(root, "daemon.lock");
|
|
21
|
-
return {
|
|
22
|
-
home: root,
|
|
23
|
-
daemon: path.join(root, "daemon.json"),
|
|
24
|
-
token: path.join(root, "token"),
|
|
25
|
-
lock,
|
|
26
|
-
lockPid: path.join(lock, "pid"),
|
|
27
|
-
log: path.join(root, "daemon.log"),
|
|
28
|
-
versions: path.join(root, "versions.json")
|
|
29
|
-
};
|
|
30
|
-
});
|
|
31
|
-
}
|
|
32
|
-
// packages/client/src/info.ts
|
|
33
|
-
import { Data, Effect as Effect2, FileSystem } from "effect";
|
|
34
|
-
class DaemonUnavailable extends Data.TaggedError("DaemonUnavailable") {
|
|
35
|
-
}
|
|
36
|
-
function readDaemonInfo(home) {
|
|
37
|
-
return Effect2.gen(function* () {
|
|
38
|
-
const fs = yield* FileSystem.FileSystem;
|
|
39
|
-
const p = yield* paths(home);
|
|
40
|
-
if ((yield* fs.stat(p.daemon)).size > 4096n || (yield* fs.stat(p.token)).size > 128n)
|
|
41
|
-
return null;
|
|
42
|
-
const text = yield* fs.readFileString(p.daemon);
|
|
43
|
-
const token = (yield* fs.readFileString(p.token)).trim();
|
|
44
|
-
const record = yield* Effect2.try(() => JSON.parse(text));
|
|
45
|
-
if (!record || !Number.isSafeInteger(record.pid) || record.pid <= 0 || !Number.isInteger(record.port) || record.port < 1 || record.port > 65535 || typeof record.appVersion !== "string" || !Number.isFinite(record.startedAt) || !/^[a-f0-9]{64}$/.test(token))
|
|
46
|
-
return null;
|
|
47
|
-
const port = record.port;
|
|
48
|
-
return {
|
|
49
|
-
home: p.home,
|
|
50
|
-
pid: record.pid,
|
|
51
|
-
port,
|
|
52
|
-
appVersion: record.appVersion,
|
|
53
|
-
startedAt: record.startedAt,
|
|
54
|
-
token,
|
|
55
|
-
baseUrl: `http://127.0.0.1:${port}`
|
|
56
|
-
};
|
|
57
|
-
}).pipe(Effect2.catch(() => Effect2.succeed(null)));
|
|
58
|
-
}
|
|
59
|
-
function alive(pid) {
|
|
60
|
-
try {
|
|
61
|
-
process.kill(pid, 0);
|
|
62
|
-
return true;
|
|
63
|
-
} catch (error) {
|
|
64
|
-
return error.code === "EPERM";
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
var probeHealth = (info) => Effect2.tryPromise({
|
|
68
|
-
try: async (signal) => {
|
|
69
|
-
const response = await fetch(`${info.baseUrl}/health`, { signal: AbortSignal.any([signal, AbortSignal.timeout(500)]) });
|
|
70
|
-
if (!response.ok)
|
|
71
|
-
return null;
|
|
72
|
-
const body = await response.json();
|
|
73
|
-
return body.ok === true && body.pid === info.pid && typeof body.appVersion === "string" ? body : null;
|
|
74
|
-
},
|
|
75
|
-
catch: () => new DaemonUnavailable({ message: "The daemon did not answer its health check" })
|
|
76
|
-
}).pipe(Effect2.catch(() => Effect2.succeed(null)));
|
|
77
|
-
// packages/protocol/src/schema.ts
|
|
78
|
-
import { Schema } from "effect";
|
|
79
|
-
var MAX_TEXT_LENGTH = 2 * 1024 * 1024;
|
|
80
|
-
var MAX_TASK_TEXT_LENGTH = 4096;
|
|
81
|
-
var Text = Schema.String.pipe(Schema.check(Schema.isMaxLength(MAX_TEXT_LENGTH)));
|
|
82
|
-
var TaskText = Schema.String.pipe(Schema.check(Schema.isMaxLength(MAX_TASK_TEXT_LENGTH)));
|
|
83
|
-
var Path3 = Schema.String.pipe(Schema.check(Schema.makeFilter((value) => value.length > 0 && value.startsWith("/") ? undefined : "an absolute, non-empty path is required"), Schema.isMaxLength(MAX_TEXT_LENGTH)));
|
|
84
|
-
var Version = Schema.Int.pipe(Schema.check(Schema.isGreaterThan(0)));
|
|
85
|
-
var User = Schema.TaggedStruct("User", {});
|
|
86
|
-
var Agent = Schema.TaggedStruct("Agent", {
|
|
87
|
-
name: Schema.String.pipe(Schema.check(Schema.isBetweenLength(1, 64), Schema.isPattern(/^[A-Za-z0-9._-]+$/)))
|
|
88
|
-
});
|
|
89
|
-
var File = Schema.TaggedStruct("File", {});
|
|
90
|
-
var Author = Schema.Union([User, Agent, File]);
|
|
91
|
-
var TaskState = Schema.Literals(["open", "done", "cancelled"]);
|
|
92
|
-
var Todo = Schema.Struct({
|
|
93
|
-
number: Schema.Number,
|
|
94
|
-
line: Schema.Number,
|
|
95
|
-
state: TaskState,
|
|
96
|
-
text: TaskText,
|
|
97
|
-
indent: Schema.Number,
|
|
98
|
-
section: Schema.NullOr(Text)
|
|
99
|
-
});
|
|
100
|
-
var TodoChange = Schema.Struct({
|
|
101
|
-
kind: Schema.Literals([
|
|
102
|
-
"added",
|
|
103
|
-
"removed",
|
|
104
|
-
"checked",
|
|
105
|
-
"unchecked",
|
|
106
|
-
"cancelled",
|
|
107
|
-
"uncancelled",
|
|
108
|
-
"edited"
|
|
109
|
-
]),
|
|
110
|
-
number: Schema.Number,
|
|
111
|
-
text: TaskText,
|
|
112
|
-
line: Schema.Number,
|
|
113
|
-
oldLine: Schema.Number
|
|
114
|
-
});
|
|
115
|
-
var Section = Schema.Struct({
|
|
116
|
-
id: Text,
|
|
117
|
-
title: Text,
|
|
118
|
-
level: Schema.Literals([1, 2, 3]),
|
|
119
|
-
line: Schema.Number
|
|
120
|
-
});
|
|
121
|
-
var Others = Schema.Struct({
|
|
122
|
-
since: Version,
|
|
123
|
-
diff: Schema.NullOr(Text)
|
|
124
|
-
});
|
|
125
|
-
var Replace = Schema.TaggedStruct("Replace", {
|
|
126
|
-
old: Text,
|
|
127
|
-
new: Text
|
|
128
|
-
});
|
|
129
|
-
var Append = Schema.TaggedStruct("Append", {
|
|
130
|
-
text: Text
|
|
131
|
-
});
|
|
132
|
-
var TodoAdd = Schema.TaggedStruct("TodoAdd", {
|
|
133
|
-
text: TaskText
|
|
134
|
-
});
|
|
135
|
-
var TodoSet = Schema.TaggedStruct("TodoSet", {
|
|
136
|
-
target: Schema.Union([Schema.Number, Text]),
|
|
137
|
-
state: TaskState
|
|
138
|
-
});
|
|
139
|
-
var TodoStart = Schema.TaggedStruct("TodoStart", {
|
|
140
|
-
target: Schema.Union([Schema.Number, Text])
|
|
141
|
-
});
|
|
142
|
-
var EditOp = Schema.Union([Replace, Append, TodoAdd, TodoSet, TodoStart]);
|
|
143
|
-
var SectionId = Schema.optional(Text);
|
|
144
|
-
var StatusPayload = Schema.Struct({});
|
|
145
|
-
var StatusDocument = Schema.Struct({
|
|
146
|
-
path: Path3,
|
|
147
|
-
version: Version,
|
|
148
|
-
watchers: Schema.Number
|
|
149
|
-
});
|
|
150
|
-
var StatusSuccess = Schema.Struct({
|
|
151
|
-
pid: Schema.Number,
|
|
152
|
-
appVersion: Text,
|
|
153
|
-
port: Schema.Number,
|
|
154
|
-
startedAt: Schema.Number,
|
|
155
|
-
documents: Schema.Array(StatusDocument)
|
|
156
|
-
});
|
|
157
|
-
var StopPayload = Schema.Struct({});
|
|
158
|
-
var StopSuccess = Schema.Struct({});
|
|
159
|
-
var OpenPayload = Schema.Struct({
|
|
160
|
-
path: Path3,
|
|
161
|
-
create: Schema.Boolean
|
|
162
|
-
});
|
|
163
|
-
var OpenSuccess = Schema.Struct({
|
|
164
|
-
path: Path3,
|
|
165
|
-
version: Version,
|
|
166
|
-
url: Text,
|
|
167
|
-
created: Schema.Boolean
|
|
168
|
-
});
|
|
169
|
-
var ReadPayload = Schema.Struct({
|
|
170
|
-
path: Path3,
|
|
171
|
-
section: SectionId
|
|
172
|
-
});
|
|
173
|
-
var ReadSuccess = Schema.Struct({
|
|
174
|
-
version: Version,
|
|
175
|
-
text: Text
|
|
176
|
-
});
|
|
177
|
-
var SectionsPayload = Schema.Struct({ path: Path3 });
|
|
178
|
-
var SectionsSuccess = Schema.Struct({
|
|
179
|
-
version: Version,
|
|
180
|
-
sections: Schema.Array(Section)
|
|
181
|
-
});
|
|
182
|
-
var TodosPayload = Schema.Struct({
|
|
183
|
-
path: Path3,
|
|
184
|
-
section: SectionId
|
|
185
|
-
});
|
|
186
|
-
var TodosSuccess = Schema.Struct({
|
|
187
|
-
version: Version,
|
|
188
|
-
todos: Schema.Array(Todo)
|
|
189
|
-
});
|
|
190
|
-
var EditPayload = Schema.Struct({
|
|
191
|
-
path: Path3,
|
|
192
|
-
author: Author,
|
|
193
|
-
version: Version,
|
|
194
|
-
section: SectionId,
|
|
195
|
-
op: EditOp
|
|
196
|
-
});
|
|
197
|
-
var EditSuccess = Schema.Struct({
|
|
198
|
-
version: Version,
|
|
199
|
-
changed: Schema.Boolean,
|
|
200
|
-
section: Schema.NullOr(Text),
|
|
201
|
-
others: Schema.NullOr(Others)
|
|
202
|
-
});
|
|
203
|
-
var SyncPayload = Schema.Struct({
|
|
204
|
-
path: Path3,
|
|
205
|
-
base: Version,
|
|
206
|
-
text: Text
|
|
207
|
-
});
|
|
208
|
-
var SyncSuccess = Schema.Struct({
|
|
209
|
-
version: Version,
|
|
210
|
-
text: Text
|
|
211
|
-
});
|
|
212
|
-
var DiffPayload = Schema.Struct({
|
|
213
|
-
path: Path3,
|
|
214
|
-
since: Version,
|
|
215
|
-
wait: Schema.Number.pipe(Schema.check(Schema.isBetween({ minimum: 0, maximum: 600 })))
|
|
216
|
-
});
|
|
217
|
-
var DiffSuccess = Schema.Struct({
|
|
218
|
-
from: Version,
|
|
219
|
-
to: Version,
|
|
220
|
-
by: Schema.Array(Author),
|
|
221
|
-
diff: Schema.NullOr(Text),
|
|
222
|
-
todos: Schema.Array(TodoChange)
|
|
223
|
-
});
|
|
224
|
-
var WatchPayload = Schema.Struct({
|
|
225
|
-
path: Path3,
|
|
226
|
-
since: Schema.optional(Version),
|
|
227
|
-
section: SectionId,
|
|
228
|
-
text: Schema.Boolean
|
|
229
|
-
});
|
|
230
|
-
var Watching = Schema.TaggedStruct("Watching", {
|
|
231
|
-
version: Version,
|
|
232
|
-
text: Schema.optionalKey(Text)
|
|
233
|
-
});
|
|
234
|
-
var Change = Schema.TaggedStruct("Change", {
|
|
235
|
-
from: Version,
|
|
236
|
-
to: Version,
|
|
237
|
-
by: Schema.Array(Author),
|
|
238
|
-
diff: Schema.NullOr(Text),
|
|
239
|
-
todos: Schema.Array(TodoChange),
|
|
240
|
-
text: Schema.optionalKey(Text)
|
|
241
|
-
});
|
|
242
|
-
var Resync = Schema.TaggedStruct("Resync", {
|
|
243
|
-
version: Version,
|
|
244
|
-
text: Schema.optionalKey(Text)
|
|
245
|
-
});
|
|
246
|
-
var Deleted = Schema.TaggedStruct("Deleted", {});
|
|
247
|
-
var WatchEvent = Schema.Union([Watching, Change, Resync, Deleted]);
|
|
248
|
-
// packages/protocol/src/errors.ts
|
|
249
|
-
import { Schema as Schema2 } from "effect";
|
|
250
|
-
class VersionMismatch extends Schema2.TaggedError()("VersionMismatch", {
|
|
251
|
-
latest: Version,
|
|
252
|
-
since: Version,
|
|
253
|
-
diff: Schema2.NullOr(Text),
|
|
254
|
-
forgotten: Schema2.Boolean
|
|
255
|
-
}) {
|
|
256
|
-
}
|
|
257
|
-
var RefusedCode = Schema2.Literals(["not-found", "ambiguous", "too-large", "invalid"]);
|
|
258
|
-
|
|
259
|
-
class Refused extends Schema2.TaggedError()("Refused", {
|
|
260
|
-
code: RefusedCode,
|
|
261
|
-
message: Text
|
|
262
|
-
}) {
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
class Forgotten extends Schema2.TaggedError()("Forgotten", {
|
|
266
|
-
latest: Version
|
|
267
|
-
}) {
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
class NothingYet extends Schema2.TaggedError()("NothingYet", {}) {
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
class DocumentGone extends Schema2.TaggedError()("DocumentGone", {
|
|
274
|
-
path: Path3
|
|
275
|
-
}) {
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
class Unauthorized extends Schema2.TaggedError()("Unauthorized", {}) {
|
|
279
|
-
}
|
|
280
|
-
var RpcFailure = Schema2.Union([
|
|
281
|
-
VersionMismatch,
|
|
282
|
-
Refused,
|
|
283
|
-
Forgotten,
|
|
284
|
-
NothingYet,
|
|
285
|
-
DocumentGone,
|
|
286
|
-
Unauthorized
|
|
287
|
-
]);
|
|
288
|
-
// packages/protocol/src/rpcs.ts
|
|
289
|
-
import { Rpc, RpcGroup } from "effect/rpc";
|
|
290
|
-
var statusRpc = Rpc.make("status", {
|
|
291
|
-
payload: StatusPayload,
|
|
292
|
-
success: StatusSuccess,
|
|
293
|
-
error: RpcFailure
|
|
294
|
-
});
|
|
295
|
-
var stopRpc = Rpc.make("stop", {
|
|
296
|
-
payload: StopPayload,
|
|
297
|
-
success: StopSuccess,
|
|
298
|
-
error: RpcFailure
|
|
299
|
-
});
|
|
300
|
-
var openRpc = Rpc.make("open", {
|
|
301
|
-
payload: OpenPayload,
|
|
302
|
-
success: OpenSuccess,
|
|
303
|
-
error: RpcFailure
|
|
304
|
-
});
|
|
305
|
-
var readRpc = Rpc.make("read", {
|
|
306
|
-
payload: ReadPayload,
|
|
307
|
-
success: ReadSuccess,
|
|
308
|
-
error: RpcFailure
|
|
309
|
-
});
|
|
310
|
-
var sectionsRpc = Rpc.make("sections", {
|
|
311
|
-
payload: SectionsPayload,
|
|
312
|
-
success: SectionsSuccess,
|
|
313
|
-
error: RpcFailure
|
|
314
|
-
});
|
|
315
|
-
var todosRpc = Rpc.make("todos", {
|
|
316
|
-
payload: TodosPayload,
|
|
317
|
-
success: TodosSuccess,
|
|
318
|
-
error: RpcFailure
|
|
319
|
-
});
|
|
320
|
-
var editRpc = Rpc.make("edit", {
|
|
321
|
-
payload: EditPayload,
|
|
322
|
-
success: EditSuccess,
|
|
323
|
-
error: RpcFailure
|
|
324
|
-
});
|
|
325
|
-
var syncRpc = Rpc.make("sync", {
|
|
326
|
-
payload: SyncPayload,
|
|
327
|
-
success: SyncSuccess,
|
|
328
|
-
error: RpcFailure
|
|
329
|
-
});
|
|
330
|
-
var diffRpc = Rpc.make("diff", {
|
|
331
|
-
payload: DiffPayload,
|
|
332
|
-
success: DiffSuccess,
|
|
333
|
-
error: RpcFailure
|
|
334
|
-
});
|
|
335
|
-
var watchRpc = Rpc.make("watch", {
|
|
336
|
-
payload: WatchPayload,
|
|
337
|
-
success: WatchEvent,
|
|
338
|
-
error: RpcFailure,
|
|
339
|
-
stream: true
|
|
340
|
-
});
|
|
341
|
-
var DoItRpcs = RpcGroup.make(statusRpc, stopRpc, openRpc, readRpc, sectionsRpc, todosRpc, editRpc, syncRpc, diffRpc, watchRpc);
|
|
342
|
-
// packages/protocol/src/format.ts
|
|
343
|
-
var describeAuthor = (author) => {
|
|
344
|
-
switch (author._tag) {
|
|
345
|
-
case "User":
|
|
346
|
-
return "the user";
|
|
347
|
-
case "Agent":
|
|
348
|
-
return author.name;
|
|
349
|
-
case "File":
|
|
350
|
-
return "file";
|
|
351
|
-
}
|
|
352
|
-
};
|
|
353
|
-
var formatTodoChange = (change) => `${change.kind} ${change.number}. ${change.text}`;
|
|
354
|
-
var formatVersionHeader = (from, to, by) => {
|
|
355
|
-
const version = from === to || to === from + 1 ? `Version ${to}` : `Versions ${from + 1}-${to}`;
|
|
356
|
-
return by.length === 0 ? version : `${version} by ${by.map(describeAuthor).join(", ")}`;
|
|
357
|
-
};
|
|
358
|
-
var exitCodeFor = (error) => {
|
|
359
|
-
switch (error._tag) {
|
|
360
|
-
case "Refused":
|
|
361
|
-
return error.code === "invalid" ? 2 : 1;
|
|
362
|
-
case "NothingYet":
|
|
363
|
-
return 4;
|
|
364
|
-
case "DocumentGone":
|
|
365
|
-
return 9;
|
|
366
|
-
case "Unauthorized":
|
|
367
|
-
return 3;
|
|
368
|
-
case "VersionMismatch":
|
|
369
|
-
case "Forgotten":
|
|
370
|
-
return 1;
|
|
371
|
-
}
|
|
372
|
-
};
|
|
373
|
-
// packages/protocol/src/index.ts
|
|
374
|
-
var APP_VERSION = "0.1.1";
|
|
375
|
-
|
|
376
|
-
// packages/client/src/lifecycle.ts
|
|
377
|
-
import { Effect as Effect4, FileSystem as FileSystem2, Option as Option2, Path as Path4, Semaphore } from "effect";
|
|
378
|
-
import { ChildProcess, ChildProcessSpawner } from "effect/process";
|
|
379
|
-
|
|
380
|
-
// packages/client/src/rpc.ts
|
|
381
|
-
import { Context, Effect as Effect3, Layer } from "effect";
|
|
382
|
-
import { FetchHttpClient, HttpClient, HttpClientRequest } from "effect/http";
|
|
383
|
-
import { RpcClient as Rpc2, RpcSerialization } from "effect/rpc";
|
|
384
|
-
|
|
385
|
-
class RpcClient extends Context.Service()("@do-it/client/RpcClient") {
|
|
386
|
-
}
|
|
387
|
-
function RpcClientLive(info) {
|
|
388
|
-
const http = Layer.effect(HttpClient.HttpClient, Effect3.map(HttpClient.HttpClient, HttpClient.filterStatusOk)).pipe(Layer.provide(FetchHttpClient.layer));
|
|
389
|
-
const protocol = Rpc2.layerProtocolHttp({
|
|
390
|
-
url: `${info.baseUrl}/rpc`,
|
|
391
|
-
transformClient: (client) => HttpClient.mapRequest(client, (request) => request.pipe(HttpClientRequest.setUrl(`${info.baseUrl}/rpc`), HttpClientRequest.setHeader("Authorization", `Bearer ${info.token}`)))
|
|
392
|
-
}).pipe(Layer.provide([http, RpcSerialization.layerNdjson]));
|
|
393
|
-
return Layer.effect(RpcClient, Rpc2.make(DoItRpcs)).pipe(Layer.provide(protocol));
|
|
394
|
-
}
|
|
395
|
-
function withClient(info, f) {
|
|
396
|
-
return Effect3.flatMap(RpcClient, f).pipe(Effect3.provide(RpcClientLive(info)));
|
|
397
|
-
}
|
|
398
|
-
|
|
399
|
-
// packages/client/src/lifecycle.ts
|
|
400
|
-
var unavailable = (cause) => cause instanceof DaemonUnavailable ? cause : new DaemonUnavailable({ message: cause instanceof Error ? cause.message : String(cause) });
|
|
401
|
-
var attempt = (f) => Effect4.try({ try: f, catch: unavailable });
|
|
402
|
-
function unless(reason, fallback) {
|
|
403
|
-
return (effect) => Effect4.catch(effect, (error) => error.reason._tag === reason ? Effect4.succeed(fallback) : Effect4.fail(error));
|
|
404
|
-
}
|
|
405
|
-
var lockTurn = Semaphore.makeUnsafe(1);
|
|
406
|
-
var daemonEntry = new URL("./daemon.js", import.meta.url);
|
|
407
|
-
var daemonScript = 'exec "$0" "$1" >>"$2" 2>&1';
|
|
408
|
-
var stopDaemon = Effect4.fn("stopDaemon")(function* (home) {
|
|
409
|
-
const info = yield* readDaemonInfo(home);
|
|
410
|
-
if (!info || !alive(info.pid))
|
|
411
|
-
return false;
|
|
412
|
-
const stopped = yield* withClient(info, (client) => client.stop({})).pipe(Effect4.timeout("500 millis"), Effect4.map(() => true), Effect4.catch(() => Effect4.succeed(false)));
|
|
413
|
-
if (!stopped) {
|
|
414
|
-
const signalled = yield* attempt(() => {
|
|
415
|
-
if (info.pid !== process.pid && alive(info.pid))
|
|
416
|
-
process.kill(info.pid, "SIGTERM");
|
|
417
|
-
}).pipe(Effect4.result);
|
|
418
|
-
if (signalled._tag === "Failure")
|
|
419
|
-
return false;
|
|
420
|
-
}
|
|
421
|
-
const deadline = Date.now() + 5000;
|
|
422
|
-
while (Date.now() < deadline) {
|
|
423
|
-
if (!(yield* probeHealth(info)))
|
|
424
|
-
return true;
|
|
425
|
-
yield* Effect4.sleep("50 millis");
|
|
426
|
-
}
|
|
427
|
-
return false;
|
|
428
|
-
});
|
|
429
|
-
var ensureDaemon = Effect4.fn("ensureDaemon")(function* (options = {}) {
|
|
430
|
-
const fs = yield* FileSystem2.FileSystem;
|
|
431
|
-
const path = yield* Path4.Path;
|
|
432
|
-
const spawner = yield* ChildProcessSpawner.ChildProcessSpawner;
|
|
433
|
-
const p = yield* paths(options.home ?? options.env?.DO_IT_HOME);
|
|
434
|
-
const compatible = Effect4.gen(function* () {
|
|
435
|
-
const info = yield* readDaemonInfo(p.home);
|
|
436
|
-
if (!info || !alive(info.pid))
|
|
437
|
-
return null;
|
|
438
|
-
const health = yield* probeHealth(info);
|
|
439
|
-
return health && health.appVersion === APP_VERSION && info.appVersion === APP_VERSION ? info : null;
|
|
440
|
-
});
|
|
441
|
-
const existing = yield* compatible;
|
|
442
|
-
if (existing)
|
|
443
|
-
return existing;
|
|
444
|
-
if (options.spawn === false)
|
|
445
|
-
return yield* Effect4.fail(new DaemonUnavailable({ message: "No compatible daemon is running" }));
|
|
446
|
-
yield* fs.makeDirectory(p.home, { recursive: true, mode: 448 }).pipe(Effect4.andThen(fs.chmod(p.home, 448)), Effect4.mapError(unavailable));
|
|
447
|
-
const lockOnce = Effect4.gen(function* () {
|
|
448
|
-
const created = yield* fs.makeDirectory(p.lock, { mode: 448 }).pipe(Effect4.as(true), unless("AlreadyExists", false));
|
|
449
|
-
if (created) {
|
|
450
|
-
yield* fs.writeFileString(p.lockPid, String(process.pid), { mode: 384 });
|
|
451
|
-
return Option2.getOrNull((yield* fs.stat(p.lock)).ino);
|
|
452
|
-
}
|
|
453
|
-
const stat = yield* fs.stat(p.lock).pipe(unless("NotFound", undefined));
|
|
454
|
-
if (!stat)
|
|
455
|
-
return;
|
|
456
|
-
const text = yield* fs.readFileString(p.lockPid).pipe(unless("NotFound", undefined));
|
|
457
|
-
const pid = text === undefined ? undefined : Number(text);
|
|
458
|
-
const modified = Option2.match(stat.mtime, { onNone: () => 0, onSome: (date) => date.getTime() });
|
|
459
|
-
const abandoned = pid !== undefined && Number.isSafeInteger(pid) && pid > 0 ? !alive(pid) : Date.now() - modified > 1e4;
|
|
460
|
-
if (abandoned)
|
|
461
|
-
yield* fs.remove(p.lock, { recursive: true, force: true });
|
|
462
|
-
return;
|
|
463
|
-
}).pipe(lockTurn.withPermit, Effect4.mapError(unavailable));
|
|
464
|
-
let inode;
|
|
465
|
-
const lockDeadline = Date.now() + 12000;
|
|
466
|
-
while (inode === undefined) {
|
|
467
|
-
inode = yield* lockOnce;
|
|
468
|
-
if (inode !== undefined)
|
|
469
|
-
break;
|
|
470
|
-
if (Date.now() > lockDeadline)
|
|
471
|
-
return yield* Effect4.fail(new DaemonUnavailable({ message: "Timed out waiting for the daemon startup lock" }));
|
|
472
|
-
yield* Effect4.sleep("50 millis");
|
|
473
|
-
}
|
|
474
|
-
const held = inode;
|
|
475
|
-
const release = fs.stat(p.lock).pipe(Effect4.flatMap((stat) => Option2.getOrNull(stat.ino) === held ? fs.remove(p.lock, { recursive: true, force: true }) : Effect4.void), lockTurn.withPermit, Effect4.ignore);
|
|
476
|
-
const start = Effect4.gen(function* () {
|
|
477
|
-
const found = yield* compatible;
|
|
478
|
-
if (found)
|
|
479
|
-
return found;
|
|
480
|
-
const old = yield* readDaemonInfo(p.home);
|
|
481
|
-
if (old && alive(old.pid) && !(yield* stopDaemon(p.home))) {
|
|
482
|
-
return yield* Effect4.fail(new DaemonUnavailable({ message: "The previous daemon could not be stopped" }));
|
|
483
|
-
}
|
|
484
|
-
const size = yield* fs.stat(p.log).pipe(Effect4.map((stat) => stat.size), unless("NotFound", 0n), Effect4.mapError(unavailable));
|
|
485
|
-
if (size >= 1024n * 1024n)
|
|
486
|
-
yield* fs.rename(p.log, `${p.log}.old`).pipe(Effect4.mapError(unavailable));
|
|
487
|
-
yield* fs.writeFileString(p.log, "", { flag: "a", mode: 384 }).pipe(Effect4.mapError(unavailable));
|
|
488
|
-
const main = yield* path.fromFileUrl(daemonEntry).pipe(Effect4.mapError(unavailable));
|
|
489
|
-
const env = { ...options.env, DO_IT_HOME: p.home };
|
|
490
|
-
if (options.port !== undefined)
|
|
491
|
-
env.DO_IT_PORT = String(options.port);
|
|
492
|
-
const command = ChildProcess.make("/bin/sh", ["-c", daemonScript, process.execPath, main, p.log], {
|
|
493
|
-
detached: true,
|
|
494
|
-
stdin: "ignore",
|
|
495
|
-
stdout: "ignore",
|
|
496
|
-
stderr: "ignore",
|
|
497
|
-
env,
|
|
498
|
-
extendEnv: true
|
|
499
|
-
});
|
|
500
|
-
const child = yield* spawner.spawn(command).pipe(Effect4.mapError(unavailable));
|
|
501
|
-
yield* child.unref.pipe(Effect4.mapError(unavailable));
|
|
502
|
-
const wait = Effect4.gen(function* () {
|
|
503
|
-
const deadline = Date.now() + 5000;
|
|
504
|
-
while (Date.now() < deadline) {
|
|
505
|
-
const info = yield* compatible;
|
|
506
|
-
if (info)
|
|
507
|
-
return info;
|
|
508
|
-
if (!(yield* child.isRunning.pipe(Effect4.mapError(unavailable))))
|
|
509
|
-
break;
|
|
510
|
-
yield* Effect4.sleep("50 millis");
|
|
511
|
-
}
|
|
512
|
-
return yield* Effect4.fail(new DaemonUnavailable({ message: `The daemon did not start, see ${p.log}` }));
|
|
513
|
-
});
|
|
514
|
-
return yield* wait.pipe(Effect4.onError(() => child.kill({ forceKillAfter: "1 second" }).pipe(Effect4.ignore)));
|
|
515
|
-
});
|
|
516
|
-
return yield* Effect4.scoped(start).pipe(Effect4.ensuring(release));
|
|
517
|
-
});
|
|
518
|
-
// packages/cli/src/main.ts
|
|
519
|
-
import { Cause, Duration, Effect as Effect10, Exit, FiberHandle, Stdio as Stdio2, Stream as Stream2 } from "effect";
|
|
520
|
-
import { CliConfig as CliConfig2, CliOutput as CliOutput2, Command as Command2 } from "effect/cli";
|
|
521
|
-
|
|
522
|
-
// packages/cli/src/browser.ts
|
|
523
|
-
import { Config as Config2, Effect as Effect5, Option as Option3 } from "effect";
|
|
524
|
-
import { ChildProcess as ChildProcess2, ChildProcessSpawner as ChildProcessSpawner2 } from "effect/process";
|
|
525
|
-
var openBrowser = (url) => Effect5.gen(function* () {
|
|
526
|
-
const configured = Option3.getOrUndefined(yield* Config2.option(Config2.String("DO_IT_BROWSER")));
|
|
527
|
-
if (configured?.toLowerCase() === "true")
|
|
528
|
-
return;
|
|
529
|
-
const command = configured || (process.platform === "darwin" ? "open" : "xdg-open");
|
|
530
|
-
const spawner = yield* ChildProcessSpawner2.ChildProcessSpawner;
|
|
531
|
-
const opener = ChildProcess2.make("/bin/sh", ["-c", `${command} "$1"`, "sh", url], {
|
|
532
|
-
detached: true,
|
|
533
|
-
stdin: "ignore",
|
|
534
|
-
stdout: "ignore",
|
|
535
|
-
stderr: "ignore"
|
|
536
|
-
});
|
|
537
|
-
yield* Effect5.scoped(Effect5.flatMap(spawner.spawn(opener), (child) => child.unref));
|
|
538
|
-
}).pipe(Effect5.ignore);
|
|
539
|
-
|
|
540
|
-
// packages/cli/src/output.ts
|
|
541
|
-
import { Console } from "effect";
|
|
542
|
-
function writeLine(text = "") {
|
|
543
|
-
return Console.log(text.endsWith(`
|
|
544
|
-
`) ? text.slice(0, -1) : text);
|
|
545
|
-
}
|
|
546
|
-
function writeJson(value) {
|
|
547
|
-
return writeLine(JSON.stringify(value));
|
|
548
|
-
}
|
|
549
|
-
function diffText(header, diff) {
|
|
550
|
-
return diff ? `${header}
|
|
551
|
-
${diff}` : header;
|
|
552
|
-
}
|
|
553
|
-
function editReply(result) {
|
|
554
|
-
if (!result.changed)
|
|
555
|
-
return { text: `Unchanged. Version: ${result.version}`, json: { kind: "unchanged", version: result.version, section: result.section, others: result.others, diff: result.others?.diff ?? null } };
|
|
556
|
-
if (result.section && result.others) {
|
|
557
|
-
const header = `Updated section "${result.section}". Version: ${result.version}. Other sections changed since ${result.others.since}:`;
|
|
558
|
-
return { text: diffText(header, result.others.diff), json: { kind: "updated-section", version: result.version, section: result.section, others: result.others, diff: result.others.diff } };
|
|
559
|
-
}
|
|
560
|
-
return { text: `Updated. Version: ${result.version}`, json: { kind: "updated", version: result.version, section: result.section, others: result.others, diff: result.others?.diff ?? null } };
|
|
561
|
-
}
|
|
562
|
-
function field(error, key) {
|
|
563
|
-
return typeof error === "object" && error !== null && key in error ? error[key] : undefined;
|
|
564
|
-
}
|
|
565
|
-
function mismatchReply(error, requested) {
|
|
566
|
-
const latest = typeof field(error, "latest") === "number" ? field(error, "latest") : requested;
|
|
567
|
-
const forgotten = field(error, "forgotten") === true || field(error, "_tag") === "Forgotten";
|
|
568
|
-
if (forgotten) {
|
|
569
|
-
const text = `Version mismatch, nothing changed. Latest version: ${latest}. Version ${requested} is no longer kept: run do-it read`;
|
|
570
|
-
return { text, json: { kind: "version-mismatch", latest, since: requested, diff: null, forgotten: true } };
|
|
571
|
-
}
|
|
572
|
-
const since = typeof field(error, "since") === "number" ? field(error, "since") : requested;
|
|
573
|
-
const diff = typeof field(error, "diff") === "string" ? field(error, "diff") : null;
|
|
574
|
-
const header = `Version mismatch, nothing changed. Latest version: ${latest}. Changes since ${since}:`;
|
|
575
|
-
return { text: diffText(header, diff), json: { kind: "version-mismatch", latest, since, diff, forgotten: false } };
|
|
576
|
-
}
|
|
577
|
-
function changeReply(change, todosOnly) {
|
|
578
|
-
return todosOnly ? change.todos.map(formatTodoChange).join(`
|
|
579
|
-
`) : diffText(formatVersionHeader(change.from, change.to, change.by), change.diff);
|
|
580
|
-
}
|
|
581
|
-
function changeJson(change) {
|
|
582
|
-
return { event: "change", from: change.from, to: change.to, by: change.by, diff: change.diff, todos: change.todos };
|
|
583
|
-
}
|
|
584
|
-
function todosText(changes) {
|
|
585
|
-
return changes.map(formatTodoChange).join(`
|
|
586
|
-
`);
|
|
587
|
-
}
|
|
588
|
-
function rpcErrorCode(error) {
|
|
589
|
-
const tag = field(error, "_tag");
|
|
590
|
-
return typeof tag === "string" ? tag : "RpcClientError";
|
|
591
|
-
}
|
|
592
|
-
function isRpcFailure(error) {
|
|
593
|
-
const tag = field(error, "_tag");
|
|
594
|
-
return typeof tag === "string" && ["VersionMismatch", "Refused", "Forgotten", "NothingYet", "DocumentGone", "Unauthorized"].includes(tag);
|
|
595
|
-
}
|
|
596
|
-
function rpcExitCode(error) {
|
|
597
|
-
return isRpcFailure(error) ? exitCodeFor(error) : 3;
|
|
598
|
-
}
|
|
599
|
-
function errorMessage(error) {
|
|
600
|
-
const errors = field(error, "errors");
|
|
601
|
-
if (Array.isArray(errors) && errors.length > 0)
|
|
602
|
-
return errors.map(errorMessage).join(`
|
|
603
|
-
`);
|
|
604
|
-
const message = field(error, "message");
|
|
605
|
-
return typeof message === "string" ? message : String(error);
|
|
606
|
-
}
|
|
607
|
-
function errorTag(error) {
|
|
608
|
-
const tag = field(error, "_tag");
|
|
609
|
-
return typeof tag === "string" ? tag : undefined;
|
|
610
|
-
}
|
|
611
|
-
function errorFields(error) {
|
|
612
|
-
if (typeof error !== "object" || error === null)
|
|
613
|
-
return {};
|
|
614
|
-
return Object.fromEntries(Object.entries(error).filter(([key]) => key !== "_tag" && key !== "message" && key !== "code"));
|
|
615
|
-
}
|
|
616
|
-
|
|
617
|
-
// packages/cli/src/parser.ts
|
|
618
|
-
import { Config as Config3, Effect as Effect7, Option as Option4, Path as Path5, Schema as Schema3 } from "effect";
|
|
619
|
-
import { Argument, CliConfig, CliError, CliOutput, Command, Flag, GlobalFlag } from "effect/cli";
|
|
620
|
-
var cliFormatter = {
|
|
621
|
-
...CliOutput.defaultFormatter({ colors: false }),
|
|
622
|
-
formatVersion: (name, version) => `${name} ${version}`
|
|
623
|
-
};
|
|
624
|
-
var cliPresenter = CliOutput.layerPresenter({
|
|
625
|
-
present: (event) => event._tag === "Help" && event.reason === "Requested" || event._tag === "Version" ? CliOutput.defaultPresenter.present(event) : Effect7.void
|
|
626
|
-
});
|
|
627
|
-
var subcommands = new Set(["open", "read", "sections", "edit", "append", "todo", "diff", "watch", "status", "stop"]);
|
|
628
|
-
var valueFlags = new Set(["--old", "--new", "--section", "--version", "--since", "--wait", "--settle", "--as"]);
|
|
629
|
-
function normalizeArgv(input) {
|
|
630
|
-
if (input[0] === "--skill")
|
|
631
|
-
return normalizeArgv(["skill", "show", ...input.slice(1)]);
|
|
632
|
-
const result = [];
|
|
633
|
-
let options = true;
|
|
634
|
-
for (let index = 0;index < input.length; index += 1) {
|
|
635
|
-
const value = input[index];
|
|
636
|
-
if (options && value === "--") {
|
|
637
|
-
options = false;
|
|
638
|
-
result.push(value);
|
|
639
|
-
continue;
|
|
640
|
-
}
|
|
641
|
-
if (options && valueFlags.has(value) && index + 1 < input.length && input[index + 1] !== "--") {
|
|
642
|
-
result.push(`${value}=${input[index + 1]}`);
|
|
643
|
-
index += 1;
|
|
644
|
-
continue;
|
|
645
|
-
}
|
|
646
|
-
result.push(value);
|
|
647
|
-
}
|
|
648
|
-
return result;
|
|
649
|
-
}
|
|
650
|
-
function hasNonGlobalArgument(input, end) {
|
|
651
|
-
let skipAsValue = false;
|
|
652
|
-
for (const [index, value] of input.entries()) {
|
|
653
|
-
if (index >= end)
|
|
654
|
-
break;
|
|
655
|
-
if (skipAsValue) {
|
|
656
|
-
skipAsValue = false;
|
|
657
|
-
continue;
|
|
658
|
-
}
|
|
659
|
-
if (value === "--as") {
|
|
660
|
-
skipAsValue = true;
|
|
661
|
-
continue;
|
|
662
|
-
}
|
|
663
|
-
if (value === "--json" || value.startsWith("--as=") || value === "--help" || value === "-h")
|
|
664
|
-
continue;
|
|
665
|
-
if (value.startsWith("-"))
|
|
666
|
-
continue;
|
|
667
|
-
return true;
|
|
668
|
-
}
|
|
669
|
-
return false;
|
|
670
|
-
}
|
|
671
|
-
function cliConfigFor(input) {
|
|
672
|
-
const versionIndex = input.findIndex((value) => value === "--version" || value === "-v");
|
|
673
|
-
const rootVersion = versionIndex >= 0 && !hasNonGlobalArgument(input, versionIndex);
|
|
674
|
-
return CliConfig.make({ builtIns: rootVersion || !input.some((value) => subcommands.has(value)) ? GlobalFlag.BuiltIns : GlobalFlag.BuiltIns.filter((builtIn) => builtIn !== GlobalFlag.Version) });
|
|
675
|
-
}
|
|
676
|
-
var PositiveInt = Schema3.Int.pipe(Schema3.check(Schema3.isGreaterThan(0)));
|
|
677
|
-
var Seconds = Schema3.Finite.pipe(Schema3.check(Schema3.isBetween({ minimum: 0, maximum: 600 })));
|
|
678
|
-
var AgentName = Schema3.String.pipe(Schema3.check(Schema3.isBetweenLength(1, 64), Schema3.isPattern(/^[A-Za-z0-9._-]+$/)));
|
|
679
|
-
var section = Flag.String("section").pipe(Flag.optional);
|
|
680
|
-
var version = Flag.Int("version").pipe(Flag.withSchema(PositiveInt));
|
|
681
|
-
var since = Flag.Int("since").pipe(Flag.withSchema(PositiveInt));
|
|
682
|
-
var optionalSince = Flag.Int("since").pipe(Flag.withSchema(PositiveInt), Flag.optional);
|
|
683
|
-
var wait = Flag.Finite("wait").pipe(Flag.withDefault(0), Flag.withSchema(Seconds));
|
|
684
|
-
var settle = Flag.Finite("settle").pipe(Flag.withSchema(Seconds), Flag.optional);
|
|
685
|
-
var optionValue = (value) => Option4.isSome(value) ? value.value : undefined;
|
|
686
|
-
var pathValue = (value, optional = false) => {
|
|
687
|
-
const displayPath = optional ? value ?? "TODO.md" : value;
|
|
688
|
-
return { displayPath, path: displayPath };
|
|
689
|
-
};
|
|
690
|
-
var defaultAgent = Config3.option(Config3.String("DO_IT_AGENT")).pipe(Effect7.map((name) => Option4.getOrElse(name, () => "").trim() || "agent"), Effect7.orDie);
|
|
691
|
-
var commonFlags = {
|
|
692
|
-
json: Flag.Boolean("json").pipe(Flag.withDefault(false)),
|
|
693
|
-
as: Flag.String("as").pipe(Flag.withDefault(defaultAgent), Flag.withSchema(AgentName))
|
|
694
|
-
};
|
|
695
|
-
function withCommon(root, input, run, make) {
|
|
696
|
-
return Effect7.gen(function* () {
|
|
697
|
-
const globals = yield* root;
|
|
698
|
-
const path = yield* Path5.Path;
|
|
699
|
-
const command = make(input, globals);
|
|
700
|
-
yield* run("path" in command ? { ...command, path: path.resolve(command.path) } : command);
|
|
701
|
-
});
|
|
702
|
-
}
|
|
703
|
-
function makeCli(run) {
|
|
704
|
-
const root = Command.make("do-it").pipe(Command.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, skill install, skill path, skill show (or --skill)"), Command.withSharedFlags(commonFlags));
|
|
705
|
-
const open = Command.make("open", {
|
|
706
|
-
path: Argument.String("path").pipe(Argument.optional),
|
|
707
|
-
noBrowser: Flag.Boolean("no-browser").pipe(Flag.withDefault(false))
|
|
708
|
-
}, (input) => withCommon(root, input, run, (config, common) => ({ kind: "open", options: common, ...pathValue(optionValue(config.path), true), noBrowser: config.noBrowser })));
|
|
709
|
-
const read = Command.make("read", {
|
|
710
|
-
path: Argument.String("path"),
|
|
711
|
-
section
|
|
712
|
-
}, (input) => withCommon(root, input, run, (config, common) => ({ kind: "read", options: common, ...pathValue(config.path), section: optionValue(config.section) })));
|
|
713
|
-
const sections = Command.make("sections", {
|
|
714
|
-
path: Argument.String("path")
|
|
715
|
-
}, (input) => withCommon(root, input, run, (config, common) => ({ kind: "sections", options: common, ...pathValue(config.path) })));
|
|
716
|
-
const edit = Command.make("edit", {
|
|
717
|
-
path: Argument.String("path"),
|
|
718
|
-
version,
|
|
719
|
-
section,
|
|
720
|
-
old: Flag.String("old"),
|
|
721
|
-
next: Flag.String("new")
|
|
722
|
-
}, (input) => withCommon(root, input, run, (config, common) => ({ kind: "edit", options: common, ...pathValue(config.path), version: config.version, section: optionValue(config.section), old: config.old, next: config.next })));
|
|
723
|
-
const append = Command.make("append", {
|
|
724
|
-
path: Argument.String("path"),
|
|
725
|
-
version,
|
|
726
|
-
section,
|
|
727
|
-
text: Argument.String("text")
|
|
728
|
-
}, (input) => withCommon(root, input, run, (config, common) => ({ kind: "append", options: common, ...pathValue(config.path), version: config.version, section: optionValue(config.section), text: config.text })));
|
|
729
|
-
const todoAdd = Command.make("add", {
|
|
730
|
-
path: Argument.String("path"),
|
|
731
|
-
version,
|
|
732
|
-
section,
|
|
733
|
-
text: Argument.String("text")
|
|
734
|
-
}, (input) => withCommon(root, input, run, (config, common) => ({ kind: "todo-add", options: common, ...pathValue(config.path), version: config.version, section: optionValue(config.section), text: config.text })));
|
|
735
|
-
const todoSet = (name, state) => Command.make(name, {
|
|
736
|
-
path: Argument.String("path"),
|
|
737
|
-
version,
|
|
738
|
-
section,
|
|
739
|
-
target: Argument.String("target")
|
|
740
|
-
}, (input) => withCommon(root, input, run, (config, common) => ({ kind: "todo-set", options: common, ...pathValue(config.path), version: config.version, section: optionValue(config.section), target: config.target, state })));
|
|
741
|
-
const todoStart = Command.make("start", {
|
|
742
|
-
path: Argument.String("path"),
|
|
743
|
-
version,
|
|
744
|
-
section,
|
|
745
|
-
target: Argument.String("target")
|
|
746
|
-
}, (input) => withCommon(root, input, run, (config, common) => ({ kind: "todo-start", options: common, ...pathValue(config.path), version: config.version, section: optionValue(config.section), target: config.target })));
|
|
747
|
-
const todoList = Command.make("list", {
|
|
748
|
-
path: Argument.String("path"),
|
|
749
|
-
section
|
|
750
|
-
}, (input) => withCommon(root, input, run, (config, common) => ({ kind: "todo-list", options: common, ...pathValue(config.path), section: optionValue(config.section) })));
|
|
751
|
-
const todo = Command.make("todo").pipe(Command.withSubcommands([
|
|
752
|
-
todoAdd,
|
|
753
|
-
todoStart,
|
|
754
|
-
todoSet("check", "done"),
|
|
755
|
-
todoSet("uncheck", "open"),
|
|
756
|
-
todoSet("cancel", "cancelled"),
|
|
757
|
-
todoSet("uncancel", "open"),
|
|
758
|
-
todoList
|
|
759
|
-
]));
|
|
760
|
-
const diff = Command.make("diff", {
|
|
761
|
-
path: Argument.String("path"),
|
|
762
|
-
since,
|
|
763
|
-
wait
|
|
764
|
-
}, (input) => withCommon(root, input, run, (config, common) => ({ kind: "diff", options: common, ...pathValue(config.path), since: config.since, wait: config.wait })));
|
|
765
|
-
const watch = Command.make("watch", {
|
|
766
|
-
path: Argument.String("path"),
|
|
767
|
-
optionalSince,
|
|
768
|
-
section,
|
|
769
|
-
todos: Flag.Boolean("todos").pipe(Flag.withDefault(false)),
|
|
770
|
-
settle,
|
|
771
|
-
quiet: Flag.Boolean("quiet").pipe(Flag.withDefault(false))
|
|
772
|
-
}, (input) => withCommon(root, input, run, (config, common) => ({ kind: "watch", options: common, ...pathValue(config.path), since: optionValue(config.optionalSince), section: optionValue(config.section), todos: config.todos, settle: optionValue(config.settle), quiet: config.quiet })));
|
|
773
|
-
const status = Command.make("status", {}, (input) => withCommon(root, input, run, (_config, common) => ({ kind: "status", options: common })));
|
|
774
|
-
const stop = Command.make("stop", {}, (input) => withCommon(root, input, run, (_config, common) => ({ kind: "stop", options: common })));
|
|
775
|
-
const skillInstall = Command.make("install", {
|
|
776
|
-
dir: Flag.String("dir").pipe(Flag.optional),
|
|
777
|
-
force: Flag.Boolean("force").pipe(Flag.withDefault(false))
|
|
778
|
-
}, (input) => withCommon(root, input, run, (config, common) => ({ kind: "skill-install", options: common, dir: optionValue(config.dir), force: config.force })));
|
|
779
|
-
const skillPath = Command.make("path", {}, (input) => withCommon(root, input, run, (_config, common) => ({ kind: "skill-path", options: common })));
|
|
780
|
-
const skillShow = Command.make("show", {}, (input) => withCommon(root, input, run, (_config, common) => ({ kind: "skill-show", options: common })));
|
|
781
|
-
const skill = Command.make("skill").pipe(Command.withDescription("Install the /do-it skill for Claude Code, print it, or print where this CLI keeps it"), Command.withSubcommands([skillInstall, skillPath, skillShow]));
|
|
782
|
-
return root.pipe(Command.withSubcommands([open, read, sections, edit, append, todo, diff, watch, status, stop, skill]));
|
|
783
|
-
}
|
|
784
|
-
|
|
785
|
-
// packages/cli/src/skill.ts
|
|
786
|
-
import { Config as Config4, Effect as Effect8, FileSystem as FileSystem3, Option as Option5, Path as Path6 } from "effect";
|
|
787
|
-
var skillRoot = new URL("../skill", import.meta.url);
|
|
788
|
-
var MARKER = ".do-it-version";
|
|
789
|
-
var skillSource = Effect8.gen(function* () {
|
|
790
|
-
const path = yield* Path6.Path;
|
|
791
|
-
return yield* path.fromFileUrl(skillRoot);
|
|
792
|
-
}).pipe(Effect8.orDie);
|
|
793
|
-
var readSkill = Effect8.gen(function* () {
|
|
794
|
-
const fs = yield* FileSystem3.FileSystem;
|
|
795
|
-
const path = yield* Path6.Path;
|
|
796
|
-
const file = path.join(yield* skillSource, "SKILL.md");
|
|
797
|
-
return { path: file, content: yield* fs.readFileString(file) };
|
|
798
|
-
});
|
|
799
|
-
var defaultSkillDir = Effect8.gen(function* () {
|
|
800
|
-
const path = yield* Path6.Path;
|
|
801
|
-
const configured = yield* Config4.option(Config4.String("CLAUDE_CONFIG_DIR"));
|
|
802
|
-
const base = Option5.isSome(configured) ? configured.value : path.join(yield* Config4.String("HOME"), ".claude");
|
|
803
|
-
return path.join(base, "skills", "do-it");
|
|
804
|
-
}).pipe(Effect8.orDie);
|
|
805
|
-
var installSkill = Effect8.fn("installSkill")(function* (dir, force) {
|
|
806
|
-
const fs = yield* FileSystem3.FileSystem;
|
|
807
|
-
const path = yield* Path6.Path;
|
|
808
|
-
const source = yield* skillSource;
|
|
809
|
-
const target = path.resolve(dir ?? (yield* defaultSkillDir));
|
|
810
|
-
const existing = yield* fs.lstat(target).pipe(Effect8.option);
|
|
811
|
-
if (Option5.isSome(existing)) {
|
|
812
|
-
const same = yield* fs.realPath(target).pipe(Effect8.map((real) => real === source), Effect8.orElseSucceed(() => false));
|
|
813
|
-
if (same)
|
|
814
|
-
return { kind: "linked", path: target };
|
|
815
|
-
const ours = existing.value.type === "Directory" && (yield* fs.exists(path.join(target, MARKER)));
|
|
816
|
-
if (!ours && !force)
|
|
817
|
-
return { kind: "occupied", path: target };
|
|
818
|
-
yield* fs.remove(target, { recursive: true });
|
|
819
|
-
}
|
|
820
|
-
yield* fs.makeDirectory(path.dirname(target), { recursive: true });
|
|
821
|
-
yield* fs.copy(source, target);
|
|
822
|
-
yield* fs.writeFileString(path.join(target, MARKER), `${APP_VERSION}
|
|
823
|
-
`);
|
|
824
|
-
return { kind: "installed", path: target, replaced: Option5.isSome(existing) };
|
|
825
|
-
});
|
|
826
|
-
|
|
827
|
-
// packages/cli/src/stdin.ts
|
|
828
|
-
import { Effect as Effect9, Stdio, Stream } from "effect";
|
|
829
|
-
var MAX_STDIN = 2 * 1024 * 1024;
|
|
830
|
-
var readStdin = Effect9.gen(function* () {
|
|
831
|
-
const stdio = yield* Stdio.Stdio;
|
|
832
|
-
const chunks = [];
|
|
833
|
-
let total = 0;
|
|
834
|
-
yield* Stream.runForEach(stdio.stdin, (chunk) => {
|
|
835
|
-
total += chunk.byteLength;
|
|
836
|
-
if (total > MAX_STDIN)
|
|
837
|
-
return Effect9.fail(new Error("Standard input exceeds 2 MiB"));
|
|
838
|
-
chunks.push(chunk);
|
|
839
|
-
return Effect9.void;
|
|
840
|
-
});
|
|
841
|
-
const bytes = new Uint8Array(total);
|
|
842
|
-
let offset = 0;
|
|
843
|
-
for (const chunk of chunks) {
|
|
844
|
-
bytes.set(chunk, offset);
|
|
845
|
-
offset += chunk.byteLength;
|
|
846
|
-
}
|
|
847
|
-
return new TextDecoder("utf-8", { ignoreBOM: true }).decode(bytes);
|
|
848
|
-
});
|
|
849
|
-
function resolveText(text) {
|
|
850
|
-
return text === "-" ? readStdin : Effect9.succeed(text);
|
|
851
|
-
}
|
|
852
|
-
|
|
853
|
-
// packages/cli/src/main.ts
|
|
854
|
-
function outputText(options, text, json) {
|
|
855
|
-
return options.json ? writeJson(json) : writeLine(text);
|
|
856
|
-
}
|
|
857
|
-
function sameAgent(by, name) {
|
|
858
|
-
return by.length > 0 && by.every((author) => author._tag === "Agent" && author.name === name);
|
|
859
|
-
}
|
|
860
|
-
function authorText(by) {
|
|
861
|
-
return by.map((author) => author._tag === "User" ? "the user" : author._tag === "File" ? "file" : author.name).join(", ");
|
|
862
|
-
}
|
|
863
|
-
function rpc(info, f) {
|
|
864
|
-
return withClient(info, f);
|
|
865
|
-
}
|
|
866
|
-
function openCommand(command) {
|
|
867
|
-
return Effect10.gen(function* () {
|
|
868
|
-
const info = yield* ensureDaemon();
|
|
869
|
-
const result = yield* rpc(info, (client) => client.open({ path: command.path, create: true }));
|
|
870
|
-
if (!command.noBrowser)
|
|
871
|
-
yield* openBrowser(result.url);
|
|
872
|
-
const path = command.options.json ? result.path : command.displayPath;
|
|
873
|
-
yield* outputText(command.options, `Opened ${path} at version ${result.version}: ${result.url}`, { path, version: result.version, url: result.url, created: result.created });
|
|
874
|
-
return 0;
|
|
875
|
-
});
|
|
876
|
-
}
|
|
877
|
-
function readCommand(command) {
|
|
878
|
-
return Effect10.gen(function* () {
|
|
879
|
-
const info = yield* ensureDaemon();
|
|
880
|
-
const result = yield* rpc(info, (client) => client.read({ path: command.path, ...command.section === undefined ? {} : { section: command.section } }));
|
|
881
|
-
if (command.options.json)
|
|
882
|
-
yield* writeJson({ version: result.version, text: result.text });
|
|
883
|
-
else
|
|
884
|
-
yield* writeLine(`Version: ${result.version}
|
|
885
|
-
${result.text}`.replace(/\n$/, ""));
|
|
886
|
-
return 0;
|
|
887
|
-
});
|
|
888
|
-
}
|
|
889
|
-
function sectionsCommand(command) {
|
|
890
|
-
return Effect10.gen(function* () {
|
|
891
|
-
const info = yield* ensureDaemon();
|
|
892
|
-
const result = yield* rpc(info, (client) => client.sections({ path: command.path }));
|
|
893
|
-
const text = [`Version: ${result.version}`, ...result.sections.map((section) => `${section.id} (${section.level}) ${section.title}`)].join(`
|
|
894
|
-
`);
|
|
895
|
-
yield* outputText(command.options, text, { version: result.version, sections: result.sections });
|
|
896
|
-
return 0;
|
|
897
|
-
});
|
|
898
|
-
}
|
|
899
|
-
function operation(command, text) {
|
|
900
|
-
if (command.kind === "edit")
|
|
901
|
-
return { _tag: "Replace", old: command.old, new: command.next };
|
|
902
|
-
if (command.kind === "append")
|
|
903
|
-
return { _tag: "Append", text: text ?? command.text };
|
|
904
|
-
if (command.kind === "todo-add")
|
|
905
|
-
return { _tag: "TodoAdd", text: text ?? command.text };
|
|
906
|
-
if (command.kind === "todo-start") {
|
|
907
|
-
const target = /^[+]?\d+$/.test(command.target.trim()) ? Number(command.target.trim()) : command.target;
|
|
908
|
-
return { _tag: "TodoStart", target };
|
|
909
|
-
}
|
|
910
|
-
const target = /^[+]?\d+$/.test(command.target.trim()) ? Number(command.target.trim()) : command.target;
|
|
911
|
-
return { _tag: "TodoSet", target, state: command.state };
|
|
912
|
-
}
|
|
913
|
-
function editCommand(command) {
|
|
914
|
-
return Effect10.gen(function* () {
|
|
915
|
-
const info = yield* ensureDaemon();
|
|
916
|
-
const text = command.kind === "append" || command.kind === "todo-add" ? yield* resolveText(command.text) : undefined;
|
|
917
|
-
const result = yield* rpc(info, (client) => client.edit({
|
|
918
|
-
path: command.path,
|
|
919
|
-
author: { _tag: "Agent", name: command.options.as },
|
|
920
|
-
version: command.version,
|
|
921
|
-
...command.section === undefined ? {} : { section: command.section },
|
|
922
|
-
op: operation(command, text)
|
|
923
|
-
}));
|
|
924
|
-
const reply = editReply(result);
|
|
925
|
-
yield* outputText(command.options, reply.text, reply.json);
|
|
926
|
-
return 0;
|
|
927
|
-
});
|
|
928
|
-
}
|
|
929
|
-
function todoListCommand(command) {
|
|
930
|
-
return Effect10.gen(function* () {
|
|
931
|
-
const info = yield* ensureDaemon();
|
|
932
|
-
const result = yield* rpc(info, (client) => client.todos({ path: command.path, ...command.section === undefined ? {} : { section: command.section } }));
|
|
933
|
-
const lines = result.todos.map((todo) => `${todo.number}. [${todo.state === "done" ? "x" : todo.state === "cancelled" ? "-" : " "}] ${todo.text}`);
|
|
934
|
-
yield* outputText(command.options, [`Version: ${result.version}`, ...lines].join(`
|
|
935
|
-
`), { version: result.version, todos: result.todos });
|
|
936
|
-
return 0;
|
|
937
|
-
});
|
|
938
|
-
}
|
|
939
|
-
function diffCommand(command) {
|
|
940
|
-
return Effect10.gen(function* () {
|
|
941
|
-
const info = yield* ensureDaemon();
|
|
942
|
-
const result = yield* rpc(info, (client) => client.diff({ path: command.path, since: command.since, wait: command.wait }));
|
|
943
|
-
const text = diffText(formatVersionHeader(result.from, result.to, result.by), result.diff);
|
|
944
|
-
yield* outputText(command.options, text, { from: result.from, to: result.to, by: result.by, diff: result.diff, todos: result.todos });
|
|
945
|
-
return 0;
|
|
946
|
-
});
|
|
947
|
-
}
|
|
948
|
-
function statusCommand(command) {
|
|
949
|
-
return Effect10.gen(function* () {
|
|
950
|
-
const info = yield* ensureDaemon({ spawn: false });
|
|
951
|
-
const result = yield* rpc(info, (client) => client.status({}));
|
|
952
|
-
const upSeconds = Math.floor(Math.max(0, Date.now() - result.startedAt) / 1000);
|
|
953
|
-
const minutes = Math.floor(upSeconds / 60);
|
|
954
|
-
if (command.options.json)
|
|
955
|
-
yield* writeJson({ pid: result.pid, port: result.port, startedAt: result.startedAt, appVersion: result.appVersion, documents: result.documents, upSeconds });
|
|
956
|
-
else
|
|
957
|
-
yield* writeLine(`Daemon pid ${result.pid} on 127.0.0.1:${result.port}, up ${minutes}m
|
|
958
|
-
${result.documents.map((document) => ` ${document.path} version ${document.version} ${document.watchers} watchers`).join(`
|
|
959
|
-
`)}`);
|
|
960
|
-
return 0;
|
|
961
|
-
});
|
|
962
|
-
}
|
|
963
|
-
function stopCommand(command) {
|
|
964
|
-
return Effect10.gen(function* () {
|
|
965
|
-
const stopped = yield* stopDaemon();
|
|
966
|
-
yield* outputText(command.options, stopped ? "Stopped" : "Not running", { stopped });
|
|
967
|
-
return 0;
|
|
968
|
-
});
|
|
969
|
-
}
|
|
970
|
-
function skillInstallCommand(command) {
|
|
971
|
-
return Effect10.gen(function* () {
|
|
972
|
-
const result = yield* installSkill(command.dir, command.force);
|
|
973
|
-
if (result.kind === "occupied") {
|
|
974
|
-
yield* outputText(command.options, `${result.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: result.path } });
|
|
975
|
-
return 1;
|
|
976
|
-
}
|
|
977
|
-
const text = result.kind === "linked" ? `${result.path} already links to this skill.` : `${result.replaced ? "Updated" : "Installed"} the do-it skill in ${result.path}. In Claude Code, run /do-it to start.`;
|
|
978
|
-
yield* outputText(command.options, text, { kind: result.kind, path: result.path });
|
|
979
|
-
return 0;
|
|
980
|
-
});
|
|
981
|
-
}
|
|
982
|
-
function skillPathCommand(command) {
|
|
983
|
-
return Effect10.gen(function* () {
|
|
984
|
-
const path = yield* skillSource;
|
|
985
|
-
yield* outputText(command.options, path, { path });
|
|
986
|
-
return 0;
|
|
987
|
-
});
|
|
988
|
-
}
|
|
989
|
-
function skillShowCommand(command) {
|
|
990
|
-
return Effect10.gen(function* () {
|
|
991
|
-
const skill = yield* readSkill;
|
|
992
|
-
yield* outputText(command.options, skill.content, skill);
|
|
993
|
-
return 0;
|
|
994
|
-
});
|
|
995
|
-
}
|
|
996
|
-
function initialWatchOutput(command, version) {
|
|
997
|
-
if (command.quiet)
|
|
998
|
-
return Effect10.void;
|
|
999
|
-
return outputText(command.options, `Watching ${command.displayPath} at version ${version}`, { event: "watching", version });
|
|
1000
|
-
}
|
|
1001
|
-
function restartOutput(command, version) {
|
|
1002
|
-
return outputText(command.options, `Daemon restarted, now version ${version}`, { event: "restarted", version });
|
|
1003
|
-
}
|
|
1004
|
-
function resyncOutput(command, version) {
|
|
1005
|
-
return outputText(command.options, `Missed changes, now at version ${version}: run do-it read`, { event: "resync", version });
|
|
1006
|
-
}
|
|
1007
|
-
function deletedOutput(command) {
|
|
1008
|
-
return outputText(command.options, `Deleted ${command.displayPath}`, { event: "deleted" });
|
|
1009
|
-
}
|
|
1010
|
-
function watchCommand(command) {
|
|
1011
|
-
return Effect10.scoped(Effect10.gen(function* () {
|
|
1012
|
-
let since = command.since;
|
|
1013
|
-
let pid;
|
|
1014
|
-
let first = true;
|
|
1015
|
-
let announce = false;
|
|
1016
|
-
let backoff = 250;
|
|
1017
|
-
let deleted = false;
|
|
1018
|
-
let lastFailure;
|
|
1019
|
-
const timer = yield* FiberHandle.make();
|
|
1020
|
-
let pending = false;
|
|
1021
|
-
let settleVersion = 0;
|
|
1022
|
-
let settleBy = [];
|
|
1023
|
-
const settled = (delay) => Effect10.sleep(delay).pipe(Effect10.andThen(Effect10.uninterruptible(Effect10.suspend(() => {
|
|
1024
|
-
const version = settleVersion;
|
|
1025
|
-
const by = settleBy;
|
|
1026
|
-
pending = false;
|
|
1027
|
-
settleBy = [];
|
|
1028
|
-
const byText = by.join(", ");
|
|
1029
|
-
return outputText(command.options, `Settled at version ${version}${byText ? ` by ${byText}` : ""}`, { event: "settled", version, by });
|
|
1030
|
-
}))));
|
|
1031
|
-
const schedule = (version, by) => Effect10.suspend(() => {
|
|
1032
|
-
if (command.settle === undefined)
|
|
1033
|
-
return Effect10.void;
|
|
1034
|
-
const authors = authorText(by).split(", ").filter(Boolean);
|
|
1035
|
-
settleVersion = version;
|
|
1036
|
-
settleBy = [...new Set(pending ? [...settleBy, ...authors] : authors)];
|
|
1037
|
-
pending = true;
|
|
1038
|
-
return FiberHandle.run(timer, settled(Duration.millis(command.settle * 1000))).pipe(Effect10.asVoid);
|
|
1039
|
-
});
|
|
1040
|
-
while (!deleted) {
|
|
1041
|
-
const daemon = yield* Effect10.exit(ensureDaemon());
|
|
1042
|
-
if (Exit.isFailure(daemon)) {
|
|
1043
|
-
if (Cause.hasInterruptsOnly(daemon.cause))
|
|
1044
|
-
break;
|
|
1045
|
-
const failure = Cause.squash(daemon.cause);
|
|
1046
|
-
const tag = errorTag(failure);
|
|
1047
|
-
if (tag !== "DaemonUnavailable") {
|
|
1048
|
-
const message = errorMessage(failure);
|
|
1049
|
-
if (message !== lastFailure)
|
|
1050
|
-
yield* outputText(command.options, `Watch failed: ${message}, retrying`, { event: "error", message });
|
|
1051
|
-
lastFailure = message;
|
|
1052
|
-
} else
|
|
1053
|
-
lastFailure = undefined;
|
|
1054
|
-
yield* Effect10.sleep(`${backoff} millis`);
|
|
1055
|
-
backoff = Math.min(5000, backoff * 2);
|
|
1056
|
-
continue;
|
|
1057
|
-
}
|
|
1058
|
-
const info = daemon.value;
|
|
1059
|
-
if (pid !== undefined && pid !== info.pid)
|
|
1060
|
-
announce = true;
|
|
1061
|
-
pid = info.pid;
|
|
1062
|
-
const payload = { path: command.path, ...since === undefined ? {} : { since }, ...command.section === undefined ? {} : { section: command.section }, text: false };
|
|
1063
|
-
const exit = yield* Effect10.exit(rpc(info, (client) => client.watch(payload).pipe(Stream2.runForEach((event) => Effect10.gen(function* () {
|
|
1064
|
-
lastFailure = undefined;
|
|
1065
|
-
if (event._tag === "Watching") {
|
|
1066
|
-
backoff = 250;
|
|
1067
|
-
if (first) {
|
|
1068
|
-
yield* initialWatchOutput(command, event.version);
|
|
1069
|
-
first = false;
|
|
1070
|
-
} else if (announce) {
|
|
1071
|
-
yield* restartOutput(command, event.version);
|
|
1072
|
-
announce = false;
|
|
1073
|
-
}
|
|
1074
|
-
since = event.version;
|
|
1075
|
-
return;
|
|
1076
|
-
}
|
|
1077
|
-
if (event._tag === "Resync") {
|
|
1078
|
-
yield* resyncOutput(command, event.version);
|
|
1079
|
-
if (command.settle !== undefined)
|
|
1080
|
-
yield* schedule(event.version, []);
|
|
1081
|
-
since = event.version;
|
|
1082
|
-
announce = false;
|
|
1083
|
-
return;
|
|
1084
|
-
}
|
|
1085
|
-
if (event._tag === "Deleted") {
|
|
1086
|
-
yield* deletedOutput(command);
|
|
1087
|
-
deleted = true;
|
|
1088
|
-
return;
|
|
1089
|
-
}
|
|
1090
|
-
since = event.to;
|
|
1091
|
-
if (sameAgent(event.by, command.options.as))
|
|
1092
|
-
return;
|
|
1093
|
-
if (command.settle !== undefined) {
|
|
1094
|
-
yield* schedule(event.to, event.by);
|
|
1095
|
-
return;
|
|
1096
|
-
}
|
|
1097
|
-
if (command.todos) {
|
|
1098
|
-
if (!event.todos.length)
|
|
1099
|
-
return;
|
|
1100
|
-
if (command.options.json)
|
|
1101
|
-
yield* writeJson(changeJson(event));
|
|
1102
|
-
else
|
|
1103
|
-
yield* writeLine(todosText(event.todos));
|
|
1104
|
-
} else if (command.options.json)
|
|
1105
|
-
yield* writeJson(changeJson(event));
|
|
1106
|
-
else
|
|
1107
|
-
yield* writeLine(changeReply(event, false));
|
|
1108
|
-
})))));
|
|
1109
|
-
if (deleted)
|
|
1110
|
-
break;
|
|
1111
|
-
if (Exit.isFailure(exit)) {
|
|
1112
|
-
if (Cause.hasInterruptsOnly(exit.cause))
|
|
1113
|
-
break;
|
|
1114
|
-
const failure = Cause.squash(exit.cause);
|
|
1115
|
-
const tag = errorTag(failure);
|
|
1116
|
-
if (tag === "DocumentGone" || tag === "Refused")
|
|
1117
|
-
return yield* errorResult(command, failure);
|
|
1118
|
-
if (tag !== "DaemonUnavailable" && tag !== "RpcClientError") {
|
|
1119
|
-
const message = errorMessage(failure);
|
|
1120
|
-
if (message !== lastFailure)
|
|
1121
|
-
yield* outputText(command.options, `Watch failed: ${message}, retrying`, { event: "error", message });
|
|
1122
|
-
lastFailure = message;
|
|
1123
|
-
} else
|
|
1124
|
-
lastFailure = undefined;
|
|
1125
|
-
} else
|
|
1126
|
-
lastFailure = undefined;
|
|
1127
|
-
yield* Effect10.sleep(`${backoff} millis`);
|
|
1128
|
-
backoff = Math.min(5000, backoff * 2);
|
|
1129
|
-
}
|
|
1130
|
-
return deleted ? 9 : 0;
|
|
1131
|
-
}));
|
|
1132
|
-
}
|
|
1133
|
-
function commandEffect(command) {
|
|
1134
|
-
switch (command.kind) {
|
|
1135
|
-
case "open":
|
|
1136
|
-
return openCommand(command);
|
|
1137
|
-
case "read":
|
|
1138
|
-
return readCommand(command);
|
|
1139
|
-
case "sections":
|
|
1140
|
-
return sectionsCommand(command);
|
|
1141
|
-
case "edit":
|
|
1142
|
-
case "append":
|
|
1143
|
-
case "todo-add":
|
|
1144
|
-
case "todo-set":
|
|
1145
|
-
case "todo-start":
|
|
1146
|
-
return editCommand(command);
|
|
1147
|
-
case "todo-list":
|
|
1148
|
-
return todoListCommand(command);
|
|
1149
|
-
case "diff":
|
|
1150
|
-
return diffCommand(command);
|
|
1151
|
-
case "watch":
|
|
1152
|
-
return watchCommand(command);
|
|
1153
|
-
case "status":
|
|
1154
|
-
return statusCommand(command);
|
|
1155
|
-
case "stop":
|
|
1156
|
-
return stopCommand(command);
|
|
1157
|
-
case "skill-install":
|
|
1158
|
-
return skillInstallCommand(command);
|
|
1159
|
-
case "skill-path":
|
|
1160
|
-
return skillPathCommand(command);
|
|
1161
|
-
case "skill-show":
|
|
1162
|
-
return skillShowCommand(command);
|
|
1163
|
-
}
|
|
1164
|
-
}
|
|
1165
|
-
function errorResult(command, error) {
|
|
1166
|
-
return Effect10.gen(function* () {
|
|
1167
|
-
const requested = ("version" in command ? command.version : ("since" in command) ? command.since : 0) ?? 0;
|
|
1168
|
-
const tag = errorTag(error);
|
|
1169
|
-
if (command.kind === "status" && tag === "DaemonUnavailable") {
|
|
1170
|
-
yield* outputText(command.options, "Not running", { error: { code: "DaemonUnavailable", message: "Not running" } });
|
|
1171
|
-
return 3;
|
|
1172
|
-
}
|
|
1173
|
-
if (tag === "VersionMismatch" || tag === "Forgotten") {
|
|
1174
|
-
const reply = mismatchReply(error, requested);
|
|
1175
|
-
const { kind: _kind, ...fields } = reply.json;
|
|
1176
|
-
yield* outputText(command.options, reply.text, { error: { code: tag, message: reply.text, ...fields } });
|
|
1177
|
-
return 1;
|
|
1178
|
-
}
|
|
1179
|
-
const code = rpcExitCode(error);
|
|
1180
|
-
const message = tag === "NothingYet" && command.kind === "diff" ? `No change since version ${command.since} in ${command.wait} ${command.wait === 1 ? "second" : "seconds"}` : errorMessage(error);
|
|
1181
|
-
const fields = errorFields(error);
|
|
1182
|
-
yield* outputText(command.options, message, { error: { code: rpcErrorCode(error), message, ...fields } });
|
|
1183
|
-
return code;
|
|
1184
|
-
});
|
|
1185
|
-
}
|
|
1186
|
-
function program() {
|
|
1187
|
-
let commandExit = 0;
|
|
1188
|
-
const cli = makeCli((command) => Effect10.gen(function* () {
|
|
1189
|
-
const result = yield* Effect10.exit(commandEffect(command));
|
|
1190
|
-
commandExit = Exit.isSuccess(result) ? result.value : yield* errorResult(command, Cause.squash(result.cause));
|
|
1191
|
-
}));
|
|
1192
|
-
return Effect10.gen(function* () {
|
|
1193
|
-
const args = yield* (yield* Stdio2.Stdio).args;
|
|
1194
|
-
const input = normalizeArgv(args);
|
|
1195
|
-
const result = yield* Effect10.exit(Command2.runWith(cli, { version: APP_VERSION, renderErrors: true })(input).pipe(Effect10.provide(CliConfig2.layer(cliConfigFor(input))), Effect10.provide(CliOutput2.layer(cliFormatter)), Effect10.provide(cliPresenter)));
|
|
1196
|
-
if (Exit.isFailure(result)) {
|
|
1197
|
-
const message = errorMessage(Cause.squash(result.cause));
|
|
1198
|
-
if (args.includes("--json"))
|
|
1199
|
-
yield* writeJson({ error: { code: "invalid", message } });
|
|
1200
|
-
else
|
|
1201
|
-
yield* writeLine(message);
|
|
1202
|
-
return 2;
|
|
1203
|
-
}
|
|
1204
|
-
return commandExit;
|
|
1205
|
-
}).pipe(Effect10.provide(BunServices.layer));
|
|
1206
|
-
}
|
|
1207
|
-
var main = program();
|
|
1208
|
-
BunRuntime.runMain(main, { disableErrorReporting: true, teardown: (exit, done) => done(Exit.isSuccess(exit) ? Number(exit.value) : 1) });
|
|
3
|
+
import{BunRuntime as Mo,BunServices as Wo}from"@effect/platform-bun";import{Config as Ae,Effect as Oe,Option as bn,Path as Qe}from"effect";var Pn=Oe.gen(function*(){let e=yield*Qe.Path,t=yield*Ae.option(Ae.String("DO_IT_HOME"));if(bn.isSome(t))return t.value;return e.join(yield*Ae.String("HOME"),".do-it")}).pipe(Oe.orDie);function te(e){return Oe.gen(function*(){let t=yield*Qe.Path,n=t.resolve(e??(yield*Pn)),r=t.join(n,"daemon.lock");return{home:n,daemon:t.join(n,"daemon.json"),token:t.join(n,"token"),lock:r,lockPid:t.join(r,"pid"),log:t.join(n,"daemon.log"),versions:t.join(n,"versions.json")}})}import{Data as wn,Effect as se,FileSystem as En}from"effect";class J extends wn.TaggedError("DaemonUnavailable"){}function ne(e){return se.gen(function*(){let t=yield*En.FileSystem,n=yield*te(e);if((yield*t.stat(n.daemon)).size>4096n||(yield*t.stat(n.token)).size>128n)return null;let r=yield*t.readFileString(n.daemon),o=(yield*t.readFileString(n.token)).trim(),s=yield*se.try(()=>JSON.parse(r));if(!s||!Number.isSafeInteger(s.pid)||s.pid<=0||!Number.isInteger(s.port)||s.port<1||s.port>65535||typeof s.appVersion!=="string"||!Number.isFinite(s.startedAt)||!/^[a-f0-9]{64}$/.test(o))return null;let u=s.port;return{home:n.home,pid:s.pid,port:u,appVersion:s.appVersion,startedAt:s.startedAt,token:o,baseUrl:`http://127.0.0.1:${u}`,...typeof s.build==="string"?{build:s.build}:{}}}).pipe(se.catch(()=>se.succeed(null)))}function X(e){try{return process.kill(e,0),!0}catch(t){return t.code==="EPERM"}}var ae=(e)=>se.promise(async(t)=>{let n=AbortSignal.timeout(500);try{let r=await fetch(`${e.baseUrl}/health`,{signal:AbortSignal.any([t,n])});if(!r.ok)return null;let o=await r.json();return o.ok===!0&&o.pid===e.pid&&typeof o.appVersion==="string"?{ok:!0,pid:e.pid,appVersion:o.appVersion,...typeof o.build==="string"?{build:o.build}:{}}:null}catch{return n.aborted?"slow":null}}),et=(e)=>ae(e).pipe(se.map((t)=>t==="slow"?null:t));import{Effect as vn}from"effect";var Dr=new URL("../../../",import.meta.url),_e=vn.succeed("36d2353.mw8d2srx");import{Schema as i}from"effect";var tt=2097152,Cn=4096,x=i.String.pipe(i.check(i.isMaxLength(tt))),Fe=i.String.pipe(i.check(i.isMaxLength(Cn))),B=i.String.pipe(i.check(i.makeFilter((e)=>e.length>0&&e.startsWith("/")?void 0:"an absolute, non-empty path is required"),i.isMaxLength(tt))),b=i.Int.pipe(i.check(i.isGreaterThan(0))),oe=i.String.pipe(i.check(i.isPattern(/^[a-f0-9]{64}$/))),Tn=i.TaggedStruct("User",{}),$n=i.TaggedStruct("Agent",{name:i.String.pipe(i.check(i.isBetweenLength(1,64),i.isPattern(/^[A-Za-z0-9._-]+$/)))}),Dn=i.TaggedStruct("File",{}),Ne=i.Union([Tn,$n,Dn]),nt=i.Literals(["open","done","cancelled"]),Rn=i.Struct({number:i.Number,line:i.Number,state:nt,text:Fe,indent:i.Number,section:i.NullOr(x)}),ot=i.Struct({kind:i.Literals(["added","removed","checked","unchecked","cancelled","uncancelled","edited"]),number:i.Number,text:Fe,line:i.Number,oldLine:i.Number}),In=i.Struct({id:x,title:x,level:i.Literals([1,2,3]),line:i.Number}),An=i.Struct({since:b,diff:i.NullOr(x)}),On=i.TaggedStruct("Replace",{old:x,new:x}),_n=i.TaggedStruct("Append",{text:x}),Fn=i.TaggedStruct("TodoAdd",{text:Fe}),Nn=i.TaggedStruct("TodoSet",{target:i.Union([i.Number,x]),state:nt}),Ln=i.TaggedStruct("TodoStart",{target:i.Union([i.Number,x])}),Vn=i.Union([On,_n,Fn,Nn,Ln]),be=i.optional(x),rt=i.Struct({}),Bn=i.Struct({path:B,version:b,watchers:i.Number}),it=i.Struct({pid:i.Number,appVersion:x,port:i.Number,startedAt:i.Number,build:i.optionalKey(x),documents:i.Array(Bn)}),st=i.Struct({}),at=i.Struct({}),lt=i.Struct({path:B,create:i.Boolean}),ct=i.Struct({path:B,version:b,url:x,created:i.Boolean}),pt=i.Struct({path:B,section:be}),dt=i.Struct({version:b,text:x,hash:i.optionalKey(oe)}),ut=i.Struct({path:B}),ft=i.Struct({version:b,sections:i.Array(In)}),ht=i.Struct({path:B,section:be}),yt=i.Struct({version:b,todos:i.Array(Rn)}),mt=i.Struct({path:B,author:Ne,version:b,section:be,op:Vn}),gt=i.Struct({version:b,changed:i.Boolean,section:i.NullOr(x),others:i.NullOr(An)}),kt=i.Struct({path:B,base:b,text:x,baseHash:i.optionalKey(oe)}),xt=i.Struct({version:b,text:x,hash:i.optionalKey(oe)}),St=i.Struct({path:B,since:b,wait:i.Number.pipe(i.check(i.isBetween({minimum:0,maximum:600})))}),bt=i.Struct({from:b,to:b,by:i.Array(Ne),diff:i.NullOr(x),todos:i.Array(ot)}),Pt=i.Struct({path:B,since:i.optional(b),sinceHash:i.optionalKey(oe),section:be,text:i.Boolean}),wt=i.TaggedStruct("Watching",{version:b,hash:i.optionalKey(oe),text:i.optionalKey(x)}),Et=i.TaggedStruct("Change",{from:b,to:b,by:i.Array(Ne),diff:i.NullOr(x),todos:i.Array(ot),hash:i.optionalKey(oe),text:i.optionalKey(x)}),vt=i.TaggedStruct("Resync",{version:b,hash:i.optionalKey(oe),text:i.optionalKey(x)}),Ct=i.TaggedStruct("Deleted",{}),Tt=i.Union([wt,Et,vt,Ct]);import{Schema as K}from"effect";class $t extends K.TaggedError()("VersionMismatch",{latest:b,since:b,diff:K.NullOr(x),forgotten:K.Boolean}){}var jn=K.Literals(["not-found","ambiguous","too-large","invalid"]);class Dt extends K.TaggedError()("Refused",{code:jn,message:x}){}class Rt extends K.TaggedError()("Forgotten",{latest:b}){}class It extends K.TaggedError()("NothingYet",{}){}class At extends K.TaggedError()("DocumentGone",{path:B}){}class Ot extends K.TaggedError()("Unauthorized",{}){}var U=K.Union([$t,Dt,Rt,It,At,Ot]);import{Rpc as G,RpcGroup as Un}from"effect/rpc";var Hn=G.make("status",{payload:rt,success:it,error:U}),Mn=G.make("stop",{payload:st,success:at,error:U}),Wn=G.make("open",{payload:lt,success:ct,error:U}),Kn=G.make("read",{payload:pt,success:dt,error:U}),Gn=G.make("sections",{payload:ut,success:ft,error:U}),zn=G.make("todos",{payload:ht,success:yt,error:U}),qn=G.make("edit",{payload:mt,success:gt,error:U}),Yn=G.make("sync",{payload:kt,success:xt,error:U}),Jn=G.make("diff",{payload:St,success:bt,error:U}),Xn=G.make("watch",{payload:Pt,success:Tt,error:U,stream:!0}),_t=Un.make(Hn,Mn,Wn,Kn,Gn,zn,qn,Yn,Jn,Xn);var Ft=(e)=>{switch(e._tag){case"User":return"the user";case"Agent":return e.name;case"File":return"file"}},Le=(e)=>`${e.kind} ${e.number}. ${e.text}`,Pe=(e,t,n)=>{let r=e===t||t===e+1?`Version ${t}`:`Versions ${e+1}-${t}`;return n.length===0?r:`${r} by ${n.map(Ft).join(", ")}`},Nt=(e)=>{switch(e._tag){case"Refused":return e.code==="invalid"?2:1;case"NothingYet":return 4;case"DocumentGone":return 9;case"Unauthorized":return 3;case"VersionMismatch":case"Forgotten":return 1}};var j="0.1.3",Lt="1.3.0";import{Effect as c,FileSystem as to,Option as Be,Path as no,Semaphore as je}from"effect";import{ChildProcess as oo,ChildProcessSpawner as ro}from"effect/process";import{Context as Zn,Effect as Ve,Layer as de}from"effect";import{FetchHttpClient as Qn,HttpClient as we,HttpClientRequest as Vt}from"effect/http";import{RpcClient as Bt,RpcSerialization as eo}from"effect/rpc";class Ee extends Zn.Service()("@do-it/client/RpcClient"){}function jt(e){let t=de.effect(we.HttpClient,Ve.map(we.HttpClient,we.filterStatusOk)).pipe(de.provide(Qn.layer)),n=Bt.layerProtocolHttp({url:`${e.baseUrl}/rpc`,transformClient:(r)=>we.mapRequest(r,(o)=>o.pipe(Vt.setUrl(`${e.baseUrl}/rpc`),Vt.setHeader("Authorization",`Bearer ${e.token}`)))}).pipe(de.provide([t,eo.layerNdjson]));return de.effect(Ee,Bt.make(_t)).pipe(de.provide(n))}function ue(e,t){return Ve.flatMap(Ee,t).pipe(Ve.provide(jt(e)))}var z=(e)=>e instanceof J?e:new J({message:e instanceof Error?e.message:String(e)}),io=(e)=>c.try({try:e,catch:z});function ve(e,t){return(n)=>c.catchIf(n,(r)=>r.reason._tag===e,()=>c.succeed(t))}var Ut=je.makeUnsafe(1),so=new URL("./daemon.js",import.meta.url),ao='exec "$0" "$1" >>"$2" 2>&1',Ce=c.fn("stopDaemon")(function*(e){let t=yield*ne(e);if(!t||!X(t.pid))return!1;if(!(yield*ue(t,(o)=>o.stop({})).pipe(c.timeout("500 millis"),c.map(()=>!0),c.catch(()=>c.succeed(!1))))){if((yield*io(()=>{if(t.pid!==process.pid&&X(t.pid))process.kill(t.pid,"SIGTERM")}).pipe(c.result))._tag==="Failure")return!1}let r=Date.now()+5000;while(Date.now()<r){if(!(yield*et(t)))return!0;yield*c.sleep("50 millis")}return!1}),Ht=["500 millis","1 second","2 seconds"],lo=(e,t)=>t!==null&&e.pid===t.pid&&e.startedAt===t.startedAt,q=c.fn("ensureDaemon")(function*(e={}){let t=yield*to.FileSystem,n=yield*no.Path,r=yield*ro.ChildProcessSpawner,o=yield*te(e.home??e.env?.DO_IT_HOME),s=(S,O=null)=>c.gen(function*(){for(let _=0;;_++){let y=yield*ne(o.home);if(!y||!X(y.pid)||y.appVersion!==j)return{usable:!1,info:y};let E=yield*ae(y);if(E!=="slow")return E?.appVersion===j?{usable:!0,info:y}:{usable:!1,info:y};if(!S||_>=Ht.length||lo(y,O))return{usable:!1,info:y};yield*c.sleep(Ht[_])}}),u=yield*s(!0);if(u.usable)return u.info;if(e.spawn===!1)return yield*c.fail(new J({message:"No compatible daemon is running"}));yield*t.makeDirectory(o.home,{recursive:!0,mode:448}).pipe(c.andThen(t.chmod(o.home,448)),c.mapError(z));let h=c.gen(function*(){if(yield*t.makeDirectory(o.lock,{mode:448}).pipe(c.as(!0),ve("AlreadyExists",!1)))return yield*t.writeFileString(o.lockPid,String(process.pid),{mode:384}),Be.getOrNull((yield*t.stat(o.lock)).ino);let O=yield*t.stat(o.lock).pipe(ve("NotFound",void 0));if(!O)return;let _=yield*t.readFileString(o.lockPid).pipe(ve("NotFound",void 0)),y=_===void 0?void 0:Number(_),E=Be.match(O.mtime,{onNone:()=>0,onSome:(w)=>w.getTime()});if(y!==void 0&&Number.isSafeInteger(y)&&y>0?!X(y):Date.now()-E>1e4)yield*t.remove(o.lock,{recursive:!0,force:!0});return}).pipe(je.withPermit(Ut),c.mapError(z)),m,C=Date.now()+12000;while(m===void 0){if(m=yield*h,m!==void 0)break;if(Date.now()>C)return yield*c.fail(new J({message:"Timed out waiting for the daemon startup lock"}));yield*c.sleep("50 millis")}let F=m,N=t.stat(o.lock).pipe(c.flatMap((S)=>Be.getOrNull(S.ino)===F?t.remove(o.lock,{recursive:!0,force:!0}):c.void),je.withPermit(Ut),c.ignore),A=c.gen(function*(){let S=yield*s(!0,u.info);if(S.usable)return S.info;let O=yield*ne(o.home);if(O&&X(O.pid)&&!(yield*Ce(o.home)))return yield*c.fail(new J({message:"The previous daemon could not be stopped"}));if((yield*t.stat(o.log).pipe(c.map((l)=>l.size),ve("NotFound",0n),c.mapError(z)))>=1024n*1024n)yield*t.rename(o.log,`${o.log}.old`).pipe(c.mapError(z));yield*t.writeFileString(o.log,"",{flag:"a",mode:384}).pipe(c.mapError(z));let y=yield*n.fromFileUrl(so).pipe(c.mapError(z)),E={...e.env,DO_IT_HOME:o.home};if(e.port!==void 0)E.DO_IT_PORT=String(e.port);let T=oo.make("/bin/sh",["-c",ao,process.execPath,y,o.log],{detached:!0,stdin:"ignore",stdout:"ignore",stderr:"ignore",env:E,extendEnv:!0}),w=yield*r.spawn(T).pipe(c.mapError(z));return yield*w.unref.pipe(c.asVoid,c.mapError(z)),yield*c.gen(function*(){let l=Date.now()+5000;while(Date.now()<l){let a=yield*s(!1);if(a.usable)return a.info;if(!(yield*w.isRunning.pipe(c.mapError(z))))break;yield*c.sleep("50 millis")}return yield*c.fail(new J({message:`The daemon did not start, see ${o.log}`}))}).pipe(c.onError(()=>w.kill({forceKillAfter:"1 second"}).pipe(c.ignore)))});return yield*c.scoped(A).pipe(c.ensuring(N))});import{Cause as pe,Duration as Ko,Effect as f,Exit as ke,FiberHandle as xn,Stdio as Go,Stream as zo}from"effect";import{CliConfig as qo,CliOutput as Yo,Command as Jo}from"effect/cli";import{Config as Mt,Effect as fe,Option as co}from"effect";import{ChildProcess as po,ChildProcessSpawner as uo}from"effect/process";var Wt=(e)=>fe.gen(function*(){let t=co.getOrUndefined(yield*Mt.option(Mt.String("DO_IT_BROWSER")));if(t?.toLowerCase()==="true")return;let n=t||(process.platform==="darwin"?"open":"xdg-open"),r=yield*uo.ChildProcessSpawner,o=po.make("/bin/sh",["-c",`${n} "$1"`,"sh",e],{detached:!0,stdin:"ignore",stdout:"ignore",stderr:"ignore"});yield*fe.scoped(fe.flatMap(r.spawn(o),(s)=>fe.asVoid(s.unref)))}).pipe(fe.ignore);import{Config as qt,Effect as H,FileSystem as Zt,Option as ho,Path as Qt}from"effect";import{Config as Ue,Effect as v,FileSystem as Me,Option as he,Path as ye}from"effect";var fo=new URL("../skill",import.meta.url),He=".do-it-version",le=v.gen(function*(){return yield*(yield*ye.Path).fromFileUrl(fo)}).pipe(v.orDie),Kt=v.gen(function*(){let e=yield*Me.FileSystem,n=(yield*ye.Path).join(yield*le,"SKILL.md");return{path:n,content:yield*e.readFileString(n)}}),We=v.gen(function*(){let e=yield*ye.Path,t=yield*Ue.option(Ue.String("CLAUDE_CONFIG_DIR")),n=he.isSome(t)?t.value:e.join(yield*Ue.String("HOME"),".claude");return e.join(n,"skills","do-it")}).pipe(v.orDie),Gt=v.fn("installSkill")(function*(e,t){let n=yield*Me.FileSystem,r=yield*ye.Path,o=yield*le,s=r.resolve(e??(yield*We)),u=yield*n.lstat(s).pipe(v.option);if(he.isSome(u)){if(yield*n.realPath(s).pipe(v.map((C)=>C===o),v.orElseSucceed(()=>!1)))return{kind:"linked",path:s};if(!(u.value.type==="Directory"&&(yield*n.exists(r.join(s,He))))&&!t)return{kind:"occupied",path:s};yield*n.remove(s,{recursive:!0})}return yield*n.makeDirectory(r.dirname(s),{recursive:!0}),yield*n.copy(o,s),yield*n.writeFileString(r.join(s,He),`${j}
|
|
4
|
+
`),{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)});
|