hatchee 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.mjs +416 -58
- package/package.json +1 -1
package/dist/cli.mjs
CHANGED
|
@@ -7925,8 +7925,13 @@ function toolTitle(name, input) {
|
|
|
7925
7925
|
return `WebSearch(${clip(String(input?.query ?? "?"), 64)})`;
|
|
7926
7926
|
case "Task":
|
|
7927
7927
|
return `Task(${clip(String(input?.description ?? "agent"), 64)})`;
|
|
7928
|
-
case "TodoWrite":
|
|
7929
|
-
|
|
7928
|
+
case "TodoWrite": {
|
|
7929
|
+
const todos = Array.isArray(input?.todos) ? input.todos : [];
|
|
7930
|
+
const done = todos.filter((t) => t?.status === "completed").length;
|
|
7931
|
+
return todos.length ? `Update Todos (${done}/${todos.length} done)` : "Update Todos";
|
|
7932
|
+
}
|
|
7933
|
+
case "ExitPlanMode":
|
|
7934
|
+
return "Plan ready for review";
|
|
7930
7935
|
case "NotebookEdit":
|
|
7931
7936
|
return `Edit(${clip(String(input?.notebook_path ?? "?"), 64)})`;
|
|
7932
7937
|
default:
|
|
@@ -7953,6 +7958,14 @@ function toolDetail(name, input) {
|
|
|
7953
7958
|
return String(input?.query ?? "");
|
|
7954
7959
|
case "Task":
|
|
7955
7960
|
return String(input?.prompt ?? input?.description ?? "").slice(0, 600);
|
|
7961
|
+
case "TodoWrite": {
|
|
7962
|
+
const todos = Array.isArray(input?.todos) ? input.todos : [];
|
|
7963
|
+
const box = (s) => s === "completed" ? "☑" : s === "in_progress" ? "◐" : "☐";
|
|
7964
|
+
return todos.map((t) => `${box(String(t?.status))} ${String(t?.content ?? "")}`).join(`
|
|
7965
|
+
`).slice(0, 1200);
|
|
7966
|
+
}
|
|
7967
|
+
case "ExitPlanMode":
|
|
7968
|
+
return String(input?.plan ?? "").slice(0, 2000);
|
|
7956
7969
|
default:
|
|
7957
7970
|
try {
|
|
7958
7971
|
const s = JSON.stringify(input);
|
|
@@ -8005,7 +8018,7 @@ function flattenItem(item) {
|
|
|
8005
8018
|
return { text: item.text, color: item.color };
|
|
8006
8019
|
}
|
|
8007
8020
|
}
|
|
8008
|
-
var PROTOCOL_VERSION =
|
|
8021
|
+
var PROTOCOL_VERSION = 3, PROTOCOL_MIN = 1, AgentState, RuleAction, Rule, DiffStat, LineColor, TermLine, ToolDecision, TranscriptItem, SessionSnapshot, ServerHello, Encrypted, SessionUpsert, SessionRemoved, Line, Item, PermissionRequest, PermissionResolved, ErrorMsg, Pong, RulesUpdate, DirEntry, DirsUpdate, RecentSession, SessionsUpdate, DaemonToPhone, DaemonInner, ClientHello, PermissionReply, RulesSync, UserMessage, SpawnMode, SpawnEffort, Spawn, Stop, SetSession, Interrupt, ListDirs, ListSessions, Ping, PushRegister, PhoneToDaemon, PhoneInner, clip = (s, n) => s.length > n ? s.slice(0, n - 1) + "…" : s;
|
|
8009
8022
|
var init_src = __esm(() => {
|
|
8010
8023
|
init_zod();
|
|
8011
8024
|
AgentState = exports_external.enum([
|
|
@@ -8076,6 +8089,7 @@ var init_src = __esm(() => {
|
|
|
8076
8089
|
plan: exports_external.array(exports_external.string()).default([]),
|
|
8077
8090
|
diff: exports_external.array(DiffStat).default([]),
|
|
8078
8091
|
spawned: exports_external.boolean().default(false),
|
|
8092
|
+
mode: exports_external.string().nullable().default(null),
|
|
8079
8093
|
startedAt: exports_external.number(),
|
|
8080
8094
|
updatedAt: exports_external.number()
|
|
8081
8095
|
});
|
|
@@ -8138,6 +8152,17 @@ var init_src = __esm(() => {
|
|
|
8138
8152
|
t: exports_external.literal("dirs"),
|
|
8139
8153
|
dirs: exports_external.array(DirEntry)
|
|
8140
8154
|
});
|
|
8155
|
+
RecentSession = exports_external.object({
|
|
8156
|
+
id: exports_external.string(),
|
|
8157
|
+
cwd: exports_external.string(),
|
|
8158
|
+
name: exports_external.string(),
|
|
8159
|
+
summary: exports_external.string(),
|
|
8160
|
+
updatedAt: exports_external.number()
|
|
8161
|
+
});
|
|
8162
|
+
SessionsUpdate = exports_external.object({
|
|
8163
|
+
t: exports_external.literal("sessions"),
|
|
8164
|
+
sessions: exports_external.array(RecentSession)
|
|
8165
|
+
});
|
|
8141
8166
|
DaemonToPhone = exports_external.discriminatedUnion("t", [
|
|
8142
8167
|
ServerHello,
|
|
8143
8168
|
SessionUpsert,
|
|
@@ -8150,6 +8175,7 @@ var init_src = __esm(() => {
|
|
|
8150
8175
|
Pong,
|
|
8151
8176
|
RulesUpdate,
|
|
8152
8177
|
DirsUpdate,
|
|
8178
|
+
SessionsUpdate,
|
|
8153
8179
|
Encrypted
|
|
8154
8180
|
]);
|
|
8155
8181
|
DaemonInner = exports_external.discriminatedUnion("t", [
|
|
@@ -8162,7 +8188,8 @@ var init_src = __esm(() => {
|
|
|
8162
8188
|
ErrorMsg,
|
|
8163
8189
|
Pong,
|
|
8164
8190
|
RulesUpdate,
|
|
8165
|
-
DirsUpdate
|
|
8191
|
+
DirsUpdate,
|
|
8192
|
+
SessionsUpdate
|
|
8166
8193
|
]);
|
|
8167
8194
|
ClientHello = exports_external.object({
|
|
8168
8195
|
t: exports_external.literal("hello"),
|
|
@@ -8194,13 +8221,25 @@ var init_src = __esm(() => {
|
|
|
8194
8221
|
cwd: exports_external.string().nullable().default(null),
|
|
8195
8222
|
mode: SpawnMode.default("edits"),
|
|
8196
8223
|
model: exports_external.string().nullable().default(null),
|
|
8197
|
-
effort: SpawnEffort.nullable().default(null)
|
|
8224
|
+
effort: SpawnEffort.nullable().default(null),
|
|
8225
|
+
resume: exports_external.string().nullable().default(null)
|
|
8198
8226
|
});
|
|
8199
8227
|
Stop = exports_external.object({
|
|
8200
8228
|
t: exports_external.literal("stop"),
|
|
8201
8229
|
sessionId: exports_external.string()
|
|
8202
8230
|
});
|
|
8231
|
+
SetSession = exports_external.object({
|
|
8232
|
+
t: exports_external.literal("set_session"),
|
|
8233
|
+
sessionId: exports_external.string(),
|
|
8234
|
+
mode: SpawnMode.nullable().default(null),
|
|
8235
|
+
model: exports_external.string().nullable().default(null)
|
|
8236
|
+
});
|
|
8237
|
+
Interrupt = exports_external.object({
|
|
8238
|
+
t: exports_external.literal("interrupt"),
|
|
8239
|
+
sessionId: exports_external.string()
|
|
8240
|
+
});
|
|
8203
8241
|
ListDirs = exports_external.object({ t: exports_external.literal("list_dirs") });
|
|
8242
|
+
ListSessions = exports_external.object({ t: exports_external.literal("list_sessions") });
|
|
8204
8243
|
Ping = exports_external.object({ t: exports_external.literal("ping") });
|
|
8205
8244
|
PushRegister = exports_external.object({
|
|
8206
8245
|
t: exports_external.literal("push_register"),
|
|
@@ -8215,7 +8254,10 @@ var init_src = __esm(() => {
|
|
|
8215
8254
|
UserMessage,
|
|
8216
8255
|
Spawn,
|
|
8217
8256
|
Stop,
|
|
8257
|
+
SetSession,
|
|
8258
|
+
Interrupt,
|
|
8218
8259
|
ListDirs,
|
|
8260
|
+
ListSessions,
|
|
8219
8261
|
Ping,
|
|
8220
8262
|
PushRegister,
|
|
8221
8263
|
Encrypted
|
|
@@ -8226,7 +8268,10 @@ var init_src = __esm(() => {
|
|
|
8226
8268
|
UserMessage,
|
|
8227
8269
|
Spawn,
|
|
8228
8270
|
Stop,
|
|
8271
|
+
SetSession,
|
|
8272
|
+
Interrupt,
|
|
8229
8273
|
ListDirs,
|
|
8274
|
+
ListSessions,
|
|
8230
8275
|
Ping,
|
|
8231
8276
|
PushRegister
|
|
8232
8277
|
]);
|
|
@@ -8251,6 +8296,7 @@ class SpawnedSession {
|
|
|
8251
8296
|
mode = "edits";
|
|
8252
8297
|
claudeSessionId = null;
|
|
8253
8298
|
stopped = false;
|
|
8299
|
+
busy = false;
|
|
8254
8300
|
constructor(daemon, sessionId, cwd, model = null, effort = null, claudeBin = process.env.DROVER_CLAUDE_BIN || "claude") {
|
|
8255
8301
|
this.daemon = daemon;
|
|
8256
8302
|
this.sessionId = sessionId;
|
|
@@ -8263,6 +8309,29 @@ class SpawnedSession {
|
|
|
8263
8309
|
this.mode = mode;
|
|
8264
8310
|
this.spawnProc(firstTask);
|
|
8265
8311
|
}
|
|
8312
|
+
resumeFrom(claudeSessionId) {
|
|
8313
|
+
this.claudeSessionId = claudeSessionId;
|
|
8314
|
+
}
|
|
8315
|
+
reconfigure(mode, model) {
|
|
8316
|
+
if (mode)
|
|
8317
|
+
this.mode = mode;
|
|
8318
|
+
if (model)
|
|
8319
|
+
this.model = model;
|
|
8320
|
+
if (this.proc && !this.busy) {
|
|
8321
|
+
try {
|
|
8322
|
+
this.proc.stdin.end();
|
|
8323
|
+
} catch {}
|
|
8324
|
+
}
|
|
8325
|
+
}
|
|
8326
|
+
interrupt() {
|
|
8327
|
+
if (!this.proc?.stdin.writable)
|
|
8328
|
+
return;
|
|
8329
|
+
const msg = { type: "control_request", request_id: crypto.randomUUID(), request: { subtype: "interrupt" } };
|
|
8330
|
+
try {
|
|
8331
|
+
this.proc.stdin.write(JSON.stringify(msg) + `
|
|
8332
|
+
`);
|
|
8333
|
+
} catch {}
|
|
8334
|
+
}
|
|
8266
8335
|
spawnProc(firstMessage) {
|
|
8267
8336
|
const args = [
|
|
8268
8337
|
"--print",
|
|
@@ -8297,6 +8366,7 @@ class SpawnedSession {
|
|
|
8297
8366
|
this.send(firstMessage);
|
|
8298
8367
|
}
|
|
8299
8368
|
send(text) {
|
|
8369
|
+
this.busy = true;
|
|
8300
8370
|
if (this.proc?.stdin.writable) {
|
|
8301
8371
|
const msg = { type: "user", message: { role: "user", content: text } };
|
|
8302
8372
|
this.proc.stdin.write(JSON.stringify(msg) + `
|
|
@@ -8307,6 +8377,7 @@ class SpawnedSession {
|
|
|
8307
8377
|
}
|
|
8308
8378
|
onProcExit(_code) {
|
|
8309
8379
|
this.proc = null;
|
|
8380
|
+
this.busy = false;
|
|
8310
8381
|
if (this.stopped)
|
|
8311
8382
|
return;
|
|
8312
8383
|
this.daemon.spawnState(this.sessionId, "waiting", "ready — send your next message");
|
|
@@ -8331,8 +8402,10 @@ class SpawnedSession {
|
|
|
8331
8402
|
switch (m.type) {
|
|
8332
8403
|
case "system":
|
|
8333
8404
|
if (m.subtype === "init") {
|
|
8334
|
-
if (typeof m.session_id === "string")
|
|
8405
|
+
if (typeof m.session_id === "string") {
|
|
8335
8406
|
this.claudeSessionId = m.session_id;
|
|
8407
|
+
this.daemon.adoptClaudeSession(this.sessionId, m.session_id);
|
|
8408
|
+
}
|
|
8336
8409
|
this.daemon.spawnState(this.sessionId, "working", "session ready");
|
|
8337
8410
|
}
|
|
8338
8411
|
break;
|
|
@@ -8377,13 +8450,23 @@ class SpawnedSession {
|
|
|
8377
8450
|
break;
|
|
8378
8451
|
}
|
|
8379
8452
|
case "result": {
|
|
8453
|
+
this.busy = false;
|
|
8380
8454
|
const ok = m.is_error !== true;
|
|
8455
|
+
const u = m.usage;
|
|
8456
|
+
if (u && (u.input_tokens || u.output_tokens)) {
|
|
8457
|
+
const secs = m.duration_ms ? `${(m.duration_ms / 1000).toFixed(1)}s · ` : "";
|
|
8458
|
+
this.daemon.spawnLine(this.sessionId, `⏱ ${secs}${fmtTokens(u.input_tokens)} in / ${fmtTokens(u.output_tokens)} out`, "muted");
|
|
8459
|
+
}
|
|
8381
8460
|
this.daemon.spawnState(this.sessionId, ok ? "waiting" : "error", ok ? "waiting for your next message" : String(m.result ?? "error"));
|
|
8382
8461
|
break;
|
|
8383
8462
|
}
|
|
8384
8463
|
}
|
|
8385
8464
|
}
|
|
8386
8465
|
}
|
|
8466
|
+
function fmtTokens(n) {
|
|
8467
|
+
const v = Number(n) || 0;
|
|
8468
|
+
return v >= 1000 ? `${(v / 1000).toFixed(1)}k` : String(v);
|
|
8469
|
+
}
|
|
8387
8470
|
function contentText(content) {
|
|
8388
8471
|
if (typeof content === "string")
|
|
8389
8472
|
return content;
|
|
@@ -11612,9 +11695,9 @@ function lanIPv4() {
|
|
|
11612
11695
|
|
|
11613
11696
|
// src/server.ts
|
|
11614
11697
|
import { createServer } from "node:http";
|
|
11615
|
-
import { readdirSync, statSync, existsSync as
|
|
11616
|
-
import { homedir as
|
|
11617
|
-
import { join as
|
|
11698
|
+
import { readdirSync as readdirSync2, statSync as statSync2, existsSync as existsSync3 } from "node:fs";
|
|
11699
|
+
import { homedir as homedir3 } from "node:os";
|
|
11700
|
+
import { join as join3, basename as basename2 } from "node:path";
|
|
11618
11701
|
|
|
11619
11702
|
// ../../node_modules/ws/wrapper.mjs
|
|
11620
11703
|
var import_stream = __toESM(require_stream(), 1);
|
|
@@ -11690,6 +11773,201 @@ class Registry {
|
|
|
11690
11773
|
}
|
|
11691
11774
|
}
|
|
11692
11775
|
|
|
11776
|
+
// src/transcript.ts
|
|
11777
|
+
init_src();
|
|
11778
|
+
import { openSync, readSync, closeSync, fstatSync, readdirSync, statSync, existsSync as existsSync2, readFileSync as readFileSync2 } from "node:fs";
|
|
11779
|
+
import { homedir as homedir2 } from "node:os";
|
|
11780
|
+
import { join as join2, basename } from "node:path";
|
|
11781
|
+
var claudeDir = () => process.env.CLAUDE_CONFIG_DIR || join2(homedir2(), ".claude");
|
|
11782
|
+
|
|
11783
|
+
class TranscriptReader {
|
|
11784
|
+
offsets = new Map;
|
|
11785
|
+
read(sessionId, path) {
|
|
11786
|
+
if (typeof path !== "string" || !path)
|
|
11787
|
+
return [];
|
|
11788
|
+
let fd;
|
|
11789
|
+
try {
|
|
11790
|
+
fd = openSync(path, "r");
|
|
11791
|
+
} catch {
|
|
11792
|
+
return [];
|
|
11793
|
+
}
|
|
11794
|
+
try {
|
|
11795
|
+
const size = fstatSync(fd).size;
|
|
11796
|
+
let off = this.offsets.get(sessionId) ?? 0;
|
|
11797
|
+
if (off > size)
|
|
11798
|
+
off = 0;
|
|
11799
|
+
if (size <= off)
|
|
11800
|
+
return [];
|
|
11801
|
+
const buf = Buffer.alloc(size - off);
|
|
11802
|
+
readSync(fd, buf, 0, buf.length, off);
|
|
11803
|
+
const text = buf.toString("utf8");
|
|
11804
|
+
const lastNL = text.lastIndexOf(`
|
|
11805
|
+
`);
|
|
11806
|
+
if (lastNL < 0)
|
|
11807
|
+
return [];
|
|
11808
|
+
this.offsets.set(sessionId, off + Buffer.byteLength(text.slice(0, lastNL + 1)));
|
|
11809
|
+
const items = [];
|
|
11810
|
+
for (const line of text.slice(0, lastNL).split(`
|
|
11811
|
+
`)) {
|
|
11812
|
+
parseEntry(line, items, { proseOnly: true });
|
|
11813
|
+
}
|
|
11814
|
+
return items;
|
|
11815
|
+
} catch {
|
|
11816
|
+
return [];
|
|
11817
|
+
} finally {
|
|
11818
|
+
try {
|
|
11819
|
+
closeSync(fd);
|
|
11820
|
+
} catch {}
|
|
11821
|
+
}
|
|
11822
|
+
}
|
|
11823
|
+
}
|
|
11824
|
+
function parseEntry(line, out, opts) {
|
|
11825
|
+
if (!line.trim())
|
|
11826
|
+
return;
|
|
11827
|
+
let e;
|
|
11828
|
+
try {
|
|
11829
|
+
e = JSON.parse(line);
|
|
11830
|
+
} catch {
|
|
11831
|
+
return;
|
|
11832
|
+
}
|
|
11833
|
+
if (e.isSidechain)
|
|
11834
|
+
return;
|
|
11835
|
+
const uuid = String(e.uuid ?? "");
|
|
11836
|
+
if (!uuid)
|
|
11837
|
+
return;
|
|
11838
|
+
const content = e.message?.content;
|
|
11839
|
+
if (e.type === "user" && !opts.proseOnly) {
|
|
11840
|
+
const text = typeof content === "string" ? content : Array.isArray(content) ? content.filter((b) => b?.type === "text").map((b) => String(b.text ?? "")).join(`
|
|
11841
|
+
`) : "";
|
|
11842
|
+
if (text.trim() && !text.startsWith("<")) {
|
|
11843
|
+
out.push({ kind: "user", id: `${uuid}:u`, text: clipOutput(text.trim(), 10, 500) });
|
|
11844
|
+
}
|
|
11845
|
+
return;
|
|
11846
|
+
}
|
|
11847
|
+
if (e.type !== "assistant" || !Array.isArray(content))
|
|
11848
|
+
return;
|
|
11849
|
+
content.forEach((b, idx) => {
|
|
11850
|
+
if (b?.type === "text" && String(b.text ?? "").trim()) {
|
|
11851
|
+
out.push({ kind: "text", id: `${uuid}:${idx}`, text: clipOutput(String(b.text).trim(), 60, 4000) });
|
|
11852
|
+
} else if (b?.type === "thinking" && String(b.thinking ?? "").trim()) {
|
|
11853
|
+
out.push({ kind: "thinking", id: `${uuid}:${idx}`, text: clipOutput(String(b.thinking).trim(), 40, 2000) });
|
|
11854
|
+
}
|
|
11855
|
+
});
|
|
11856
|
+
}
|
|
11857
|
+
function listRecentSessions(limit = 20) {
|
|
11858
|
+
const root = join2(claudeDir(), "projects");
|
|
11859
|
+
let dirs = [];
|
|
11860
|
+
try {
|
|
11861
|
+
dirs = readdirSync(root);
|
|
11862
|
+
} catch {
|
|
11863
|
+
return [];
|
|
11864
|
+
}
|
|
11865
|
+
const found = [];
|
|
11866
|
+
for (const d of dirs) {
|
|
11867
|
+
const dir = join2(root, d);
|
|
11868
|
+
let files = [];
|
|
11869
|
+
try {
|
|
11870
|
+
files = readdirSync(dir);
|
|
11871
|
+
} catch {
|
|
11872
|
+
continue;
|
|
11873
|
+
}
|
|
11874
|
+
for (const f of files) {
|
|
11875
|
+
if (!f.endsWith(".jsonl"))
|
|
11876
|
+
continue;
|
|
11877
|
+
const p = join2(dir, f);
|
|
11878
|
+
let mtime = 0, size = 0;
|
|
11879
|
+
try {
|
|
11880
|
+
const st = statSync(p);
|
|
11881
|
+
mtime = st.mtimeMs;
|
|
11882
|
+
size = st.size;
|
|
11883
|
+
} catch {
|
|
11884
|
+
continue;
|
|
11885
|
+
}
|
|
11886
|
+
if (size < 200)
|
|
11887
|
+
continue;
|
|
11888
|
+
found.push({ id: f.slice(0, -6), cwd: "", name: "", summary: "", updatedAt: Math.round(mtime), mtime });
|
|
11889
|
+
found[found.length - 1].path = p;
|
|
11890
|
+
}
|
|
11891
|
+
}
|
|
11892
|
+
found.sort((a, b) => b.mtime - a.mtime);
|
|
11893
|
+
const top = found.slice(0, limit);
|
|
11894
|
+
for (const s of top) {
|
|
11895
|
+
const meta = sessionMeta(s.path);
|
|
11896
|
+
s.cwd = meta.cwd;
|
|
11897
|
+
s.name = meta.cwd ? basename(meta.cwd) : "";
|
|
11898
|
+
s.summary = meta.summary;
|
|
11899
|
+
}
|
|
11900
|
+
return top.filter((s) => s.summary).map(({ id, cwd, name, summary, updatedAt }) => ({ id, cwd, name, summary, updatedAt }));
|
|
11901
|
+
}
|
|
11902
|
+
function sessionMeta(path) {
|
|
11903
|
+
let head = "";
|
|
11904
|
+
try {
|
|
11905
|
+
const fd = openSync(path, "r");
|
|
11906
|
+
try {
|
|
11907
|
+
const buf = Buffer.alloc(32 * 1024);
|
|
11908
|
+
const n = readSync(fd, buf, 0, buf.length, 0);
|
|
11909
|
+
head = buf.toString("utf8", 0, n);
|
|
11910
|
+
} finally {
|
|
11911
|
+
closeSync(fd);
|
|
11912
|
+
}
|
|
11913
|
+
} catch {
|
|
11914
|
+
return { cwd: "", summary: "" };
|
|
11915
|
+
}
|
|
11916
|
+
let cwd = "", summary = "";
|
|
11917
|
+
for (const line of head.split(`
|
|
11918
|
+
`)) {
|
|
11919
|
+
if (!line.trim())
|
|
11920
|
+
continue;
|
|
11921
|
+
let e;
|
|
11922
|
+
try {
|
|
11923
|
+
e = JSON.parse(line);
|
|
11924
|
+
} catch {
|
|
11925
|
+
continue;
|
|
11926
|
+
}
|
|
11927
|
+
if (!cwd && typeof e.cwd === "string")
|
|
11928
|
+
cwd = e.cwd;
|
|
11929
|
+
if (!summary && e.type === "summary" && typeof e.summary === "string")
|
|
11930
|
+
summary = e.summary;
|
|
11931
|
+
if (!summary && e.type === "user" && !e.isSidechain) {
|
|
11932
|
+
const c = e.message?.content;
|
|
11933
|
+
const text = typeof c === "string" ? c : Array.isArray(c) ? c.filter((b) => b?.type === "text").map((b) => String(b.text ?? "")).join(" ") : "";
|
|
11934
|
+
if (text.trim() && !text.startsWith("<"))
|
|
11935
|
+
summary = text.trim();
|
|
11936
|
+
}
|
|
11937
|
+
if (cwd && summary)
|
|
11938
|
+
break;
|
|
11939
|
+
}
|
|
11940
|
+
return { cwd, summary: summary.replace(/\s+/g, " ").slice(0, 120) };
|
|
11941
|
+
}
|
|
11942
|
+
function backfillItems(resumeId, maxItems = 20) {
|
|
11943
|
+
const root = join2(claudeDir(), "projects");
|
|
11944
|
+
let path = "";
|
|
11945
|
+
try {
|
|
11946
|
+
for (const d of readdirSync(root)) {
|
|
11947
|
+
const p = join2(root, d, `${resumeId}.jsonl`);
|
|
11948
|
+
if (existsSync2(p)) {
|
|
11949
|
+
path = p;
|
|
11950
|
+
break;
|
|
11951
|
+
}
|
|
11952
|
+
}
|
|
11953
|
+
} catch {
|
|
11954
|
+
return [];
|
|
11955
|
+
}
|
|
11956
|
+
if (!path)
|
|
11957
|
+
return [];
|
|
11958
|
+
let raw = "";
|
|
11959
|
+
try {
|
|
11960
|
+
raw = readFileSync2(path, "utf8");
|
|
11961
|
+
} catch {
|
|
11962
|
+
return [];
|
|
11963
|
+
}
|
|
11964
|
+
const items = [];
|
|
11965
|
+
for (const line of raw.split(`
|
|
11966
|
+
`))
|
|
11967
|
+
parseEntry(line, items, { proseOnly: false });
|
|
11968
|
+
return items.slice(-maxItems);
|
|
11969
|
+
}
|
|
11970
|
+
|
|
11693
11971
|
// src/server.ts
|
|
11694
11972
|
var VERSION = "0.1.0";
|
|
11695
11973
|
var HOOK_WAIT_MS = 110000;
|
|
@@ -11704,6 +11982,8 @@ class Daemon {
|
|
|
11704
11982
|
relayPush = null;
|
|
11705
11983
|
spawned = new Map;
|
|
11706
11984
|
openTools = new Map;
|
|
11985
|
+
aliases = new Map;
|
|
11986
|
+
transcripts = new TranscriptReader;
|
|
11707
11987
|
defaultCwd = process.cwd();
|
|
11708
11988
|
ephemeral;
|
|
11709
11989
|
constructor(cfg, opts = {}) {
|
|
@@ -11797,7 +12077,7 @@ class Daemon {
|
|
|
11797
12077
|
relayLeave(conn) {
|
|
11798
12078
|
this.phones.delete(conn);
|
|
11799
12079
|
}
|
|
11800
|
-
async spawnSession(task, cwd, mode = "edits", model = null, effort = null) {
|
|
12080
|
+
async spawnSession(task, cwd, mode = "edits", model = null, effort = null, resume = null) {
|
|
11801
12081
|
const { SpawnedSession: SpawnedSession2 } = await Promise.resolve().then(() => (init_spawn(), exports_spawn));
|
|
11802
12082
|
const id = `spawn-${crypto.randomUUID()}`;
|
|
11803
12083
|
const dir = cwd || this.defaultCwd;
|
|
@@ -11807,44 +12087,56 @@ class Daemon {
|
|
|
11807
12087
|
agentName: "Claude Code",
|
|
11808
12088
|
state: "working",
|
|
11809
12089
|
spawned: true,
|
|
11810
|
-
|
|
12090
|
+
mode,
|
|
12091
|
+
action: task ? task.slice(0, 60) : resume ? "continuing previous session…" : "starting…",
|
|
11811
12092
|
...this.registry.gitInfo(dir)
|
|
11812
12093
|
}, this.cfg.host);
|
|
11813
12094
|
this.broadcast({ t: "session_upsert", session: s });
|
|
12095
|
+
if (resume) {
|
|
12096
|
+
for (const item of backfillItems(resume))
|
|
12097
|
+
this.emit(id, item);
|
|
12098
|
+
this.aliases.set(resume, id);
|
|
12099
|
+
}
|
|
11814
12100
|
if (task)
|
|
11815
12101
|
this.emit(id, { kind: "user", id: crypto.randomUUID(), text: task });
|
|
11816
12102
|
const session = new SpawnedSession2(this, id, dir, model, effort);
|
|
12103
|
+
if (resume)
|
|
12104
|
+
session.resumeFrom(resume);
|
|
11817
12105
|
this.spawned.set(id, session);
|
|
11818
12106
|
session.start(task, mode);
|
|
11819
12107
|
}
|
|
12108
|
+
adoptClaudeSession(spawnId, claudeId) {
|
|
12109
|
+
if (claudeId && claudeId !== spawnId)
|
|
12110
|
+
this.aliases.set(claudeId, spawnId);
|
|
12111
|
+
}
|
|
11820
12112
|
listProjectDirs() {
|
|
11821
|
-
const home =
|
|
11822
|
-
const roots = [home, ...["Projects", "code", "dev", "src", "repos", "Developer", "work"].map((d) =>
|
|
12113
|
+
const home = homedir3();
|
|
12114
|
+
const roots = [home, ...["Projects", "code", "dev", "src", "repos", "Developer", "work"].map((d) => join3(home, d))];
|
|
11823
12115
|
const out = new Map;
|
|
11824
12116
|
const add2 = (p) => {
|
|
11825
12117
|
if (out.has(p))
|
|
11826
12118
|
return;
|
|
11827
|
-
out.set(p, { path: p, name:
|
|
12119
|
+
out.set(p, { path: p, name: basename2(p) || p, git: existsSync3(join3(p, ".git")) });
|
|
11828
12120
|
};
|
|
11829
12121
|
add2(this.defaultCwd);
|
|
11830
12122
|
for (const root of roots) {
|
|
11831
12123
|
let entries = [];
|
|
11832
12124
|
try {
|
|
11833
|
-
entries =
|
|
12125
|
+
entries = readdirSync2(root);
|
|
11834
12126
|
} catch {
|
|
11835
12127
|
continue;
|
|
11836
12128
|
}
|
|
11837
12129
|
for (const e of entries) {
|
|
11838
12130
|
if (e.startsWith(".") || out.size > 80)
|
|
11839
12131
|
continue;
|
|
11840
|
-
const p =
|
|
12132
|
+
const p = join3(root, e);
|
|
11841
12133
|
try {
|
|
11842
|
-
if (!
|
|
12134
|
+
if (!statSync2(p).isDirectory())
|
|
11843
12135
|
continue;
|
|
11844
12136
|
} catch {
|
|
11845
12137
|
continue;
|
|
11846
12138
|
}
|
|
11847
|
-
if (root !== home ||
|
|
12139
|
+
if (root !== home || existsSync3(join3(p, ".git")))
|
|
11848
12140
|
add2(p);
|
|
11849
12141
|
}
|
|
11850
12142
|
}
|
|
@@ -11986,7 +12278,7 @@ class Daemon {
|
|
|
11986
12278
|
if (s) {
|
|
11987
12279
|
this.broadcast({
|
|
11988
12280
|
t: "session_upsert",
|
|
11989
|
-
session: this.registry.upsert({ id: s.id, state: msg.allow ? "working" : "canceled", action: msg.allow ? "continuing…" : "denied from phone", command: "" }, this.cfg.host)
|
|
12281
|
+
session: this.registry.upsert({ id: s.id, state: msg.allow ? "working" : "canceled", action: msg.allow ? "continuing…" : "denied from phone", command: "", plan: [] }, this.cfg.host)
|
|
11990
12282
|
});
|
|
11991
12283
|
}
|
|
11992
12284
|
break;
|
|
@@ -12025,12 +12317,33 @@ class Daemon {
|
|
|
12025
12317
|
break;
|
|
12026
12318
|
}
|
|
12027
12319
|
case "spawn": {
|
|
12028
|
-
this.spawnSession(msg.task ?? "", msg.cwd ?? null, msg.mode ?? "edits", msg.model ?? null, msg.effort ?? null);
|
|
12320
|
+
this.spawnSession(msg.task ?? "", msg.cwd ?? null, msg.mode ?? "edits", msg.model ?? null, msg.effort ?? null, msg.resume ?? null);
|
|
12321
|
+
break;
|
|
12322
|
+
}
|
|
12323
|
+
case "set_session": {
|
|
12324
|
+
const owned = this.spawned.get(msg.sessionId);
|
|
12325
|
+
if (!owned)
|
|
12326
|
+
break;
|
|
12327
|
+
owned.reconfigure(msg.mode ?? null, msg.model ?? null);
|
|
12328
|
+
if (msg.mode) {
|
|
12329
|
+
const s = this.registry.upsert({ id: msg.sessionId, mode: msg.mode }, this.cfg.host);
|
|
12330
|
+
this.broadcast({ t: "session_upsert", session: s });
|
|
12331
|
+
}
|
|
12332
|
+
const what = [msg.mode && `mode → ${msg.mode}`, msg.model && `model → ${msg.model}`].filter(Boolean).join(" · ");
|
|
12333
|
+
if (what)
|
|
12334
|
+
this.note(msg.sessionId, `⏵ ${what} (applies from the next turn)`, "muted");
|
|
12335
|
+
break;
|
|
12336
|
+
}
|
|
12337
|
+
case "interrupt": {
|
|
12338
|
+
this.spawned.get(msg.sessionId)?.interrupt();
|
|
12029
12339
|
break;
|
|
12030
12340
|
}
|
|
12031
12341
|
case "list_dirs":
|
|
12032
12342
|
this.sendTo(p, { t: "dirs", dirs: this.listProjectDirs() });
|
|
12033
12343
|
break;
|
|
12344
|
+
case "list_sessions":
|
|
12345
|
+
this.sendTo(p, { t: "sessions", sessions: listRecentSessions() });
|
|
12346
|
+
break;
|
|
12034
12347
|
case "stop": {
|
|
12035
12348
|
const owned = this.spawned.get(msg.sessionId);
|
|
12036
12349
|
if (owned) {
|
|
@@ -12048,10 +12361,21 @@ class Daemon {
|
|
|
12048
12361
|
}
|
|
12049
12362
|
async handleHookEvent(body) {
|
|
12050
12363
|
const event = body.hook_event_name ?? "";
|
|
12051
|
-
const
|
|
12364
|
+
const rawId = body.session_id ?? "unknown";
|
|
12365
|
+
const sessionId = this.aliases.get(rawId) ?? rawId;
|
|
12366
|
+
const isSpawned = sessionId !== rawId || this.spawned.has(sessionId);
|
|
12052
12367
|
const cwd = body.cwd ?? "";
|
|
12368
|
+
if (!isSpawned) {
|
|
12369
|
+
for (const item of this.transcripts.read(sessionId, body.transcript_path)) {
|
|
12370
|
+
const seen = this.registry.items.get(sessionId)?.some((i) => i.id === item.id);
|
|
12371
|
+
if (!seen)
|
|
12372
|
+
this.emit(sessionId, item);
|
|
12373
|
+
}
|
|
12374
|
+
}
|
|
12053
12375
|
switch (event) {
|
|
12054
12376
|
case "SessionStart": {
|
|
12377
|
+
if (isSpawned)
|
|
12378
|
+
return { body: { ok: true } };
|
|
12055
12379
|
const git = this.registry.gitInfo(cwd);
|
|
12056
12380
|
const s = this.registry.upsert({
|
|
12057
12381
|
id: sessionId,
|
|
@@ -12066,6 +12390,8 @@ class Daemon {
|
|
|
12066
12390
|
return { body: { ok: true } };
|
|
12067
12391
|
}
|
|
12068
12392
|
case "UserPromptSubmit": {
|
|
12393
|
+
if (isSpawned)
|
|
12394
|
+
return { body: { ok: true } };
|
|
12069
12395
|
const prompt = String(body.prompt ?? "").slice(0, 500);
|
|
12070
12396
|
this.emit(sessionId, { kind: "user", id: crypto.randomUUID(), text: prompt });
|
|
12071
12397
|
const s = this.registry.upsert({ id: sessionId, cwd, state: "working", action: prompt.slice(0, 80) }, this.cfg.host);
|
|
@@ -12073,8 +12399,10 @@ class Daemon {
|
|
|
12073
12399
|
return { body: { ok: true } };
|
|
12074
12400
|
}
|
|
12075
12401
|
case "PreToolUse":
|
|
12076
|
-
return this.handlePreToolUse(body, sessionId, cwd);
|
|
12402
|
+
return this.handlePreToolUse(body, sessionId, cwd, isSpawned);
|
|
12077
12403
|
case "PostToolUse": {
|
|
12404
|
+
if (isSpawned)
|
|
12405
|
+
return { body: { ok: true } };
|
|
12078
12406
|
const toolUseId = this.takeOpenTool(sessionId, body);
|
|
12079
12407
|
const out = extractToolOutput(body.tool_response);
|
|
12080
12408
|
this.emit(sessionId, {
|
|
@@ -12103,6 +12431,8 @@ class Daemon {
|
|
|
12103
12431
|
return { body: { ok: true } };
|
|
12104
12432
|
}
|
|
12105
12433
|
case "Stop": {
|
|
12434
|
+
if (isSpawned)
|
|
12435
|
+
return { body: { ok: true } };
|
|
12106
12436
|
const s = this.registry.upsert({ id: sessionId, cwd, state: "done", action: "finished", command: "" }, this.cfg.host);
|
|
12107
12437
|
this.broadcast({ t: "session_upsert", session: s });
|
|
12108
12438
|
return { body: { ok: true } };
|
|
@@ -12111,20 +12441,30 @@ class Daemon {
|
|
|
12111
12441
|
return { body: { ok: true } };
|
|
12112
12442
|
}
|
|
12113
12443
|
}
|
|
12114
|
-
async handlePreToolUse(body, sessionId, cwd) {
|
|
12444
|
+
async handlePreToolUse(body, sessionId, cwd, isSpawned = false) {
|
|
12115
12445
|
const command = toolSummary(body.tool_name, body.tool_input);
|
|
12116
12446
|
const rawCommand = body.tool_name === "Bash" ? String(body.tool_input?.command ?? "") : command;
|
|
12117
12447
|
const danger = isDangerous(rawCommand) || isDangerousTool(body.tool_name, body.tool_input);
|
|
12118
|
-
const
|
|
12119
|
-
const
|
|
12120
|
-
const
|
|
12448
|
+
const isPlan = body.tool_name === "ExitPlanMode";
|
|
12449
|
+
const toolUseId = isPlan ? null : isSpawned ? this.streamToolTarget(sessionId) : this.registerOpenTool(sessionId, body);
|
|
12450
|
+
const mark = (d) => {
|
|
12451
|
+
if (toolUseId) {
|
|
12452
|
+
this.emit(sessionId, { kind: "tool_decision", id: crypto.randomUUID(), toolUseId, decision: d });
|
|
12453
|
+
} else {
|
|
12454
|
+
const line = flattenItem({ kind: "tool_decision", id: "", toolUseId: "", decision: d });
|
|
12455
|
+
if (line)
|
|
12456
|
+
this.note(sessionId, line.text, line.color);
|
|
12457
|
+
}
|
|
12458
|
+
};
|
|
12459
|
+
const ruled = isPlan ? null : verdict(this.cfg.rules, rawCommand, this.cfg.host);
|
|
12121
12460
|
if (!danger && ruled === "allow") {
|
|
12122
12461
|
mark("auto");
|
|
12123
12462
|
return decision("allow", "drover rule");
|
|
12124
12463
|
}
|
|
12125
12464
|
if (ruled === "deny") {
|
|
12126
12465
|
mark("blocked");
|
|
12127
|
-
|
|
12466
|
+
if (toolUseId)
|
|
12467
|
+
this.closeOpenTool(sessionId, toolUseId);
|
|
12128
12468
|
const s2 = this.registry.upsert({ id: sessionId, cwd, state: "working", action: "blocked by your rules" }, this.cfg.host);
|
|
12129
12469
|
this.broadcast({ t: "session_upsert", session: s2 });
|
|
12130
12470
|
return decision("deny", "blocked by drover rule");
|
|
@@ -12135,13 +12475,17 @@ class Daemon {
|
|
|
12135
12475
|
return decision("ask", "no phone connected");
|
|
12136
12476
|
}
|
|
12137
12477
|
const requestId = crypto.randomUUID();
|
|
12478
|
+
const plan = isPlan ? String(body.tool_input?.plan ?? "").split(`
|
|
12479
|
+
`).map((l) => l.trim()).filter(Boolean).slice(0, 30) : [];
|
|
12480
|
+
const displayCommand = isPlan ? "Review the plan" : command;
|
|
12138
12481
|
const s = this.registry.upsert({
|
|
12139
12482
|
id: sessionId,
|
|
12140
12483
|
cwd,
|
|
12141
12484
|
state: "approval",
|
|
12142
|
-
action: danger ? "wants to run a DANGEROUS command" : `wants to run: ${command.slice(0, 60)}`,
|
|
12143
|
-
command,
|
|
12144
|
-
danger
|
|
12485
|
+
action: isPlan ? "plan ready — review before anything runs" : danger ? "wants to run a DANGEROUS command" : `wants to run: ${command.slice(0, 60)}`,
|
|
12486
|
+
command: displayCommand,
|
|
12487
|
+
danger,
|
|
12488
|
+
plan
|
|
12145
12489
|
}, this.cfg.host);
|
|
12146
12490
|
this.broadcast({ t: "session_upsert", session: s });
|
|
12147
12491
|
this.broadcast({
|
|
@@ -12149,7 +12493,7 @@ class Daemon {
|
|
|
12149
12493
|
requestId,
|
|
12150
12494
|
sessionId,
|
|
12151
12495
|
toolName: String(body.tool_name ?? ""),
|
|
12152
|
-
command,
|
|
12496
|
+
command: displayCommand,
|
|
12153
12497
|
danger,
|
|
12154
12498
|
expiresAt: Date.now() + HOOK_WAIT_MS
|
|
12155
12499
|
});
|
|
@@ -12158,7 +12502,7 @@ class Daemon {
|
|
|
12158
12502
|
const timer = setTimeout(() => {
|
|
12159
12503
|
this.pending.delete(requestId);
|
|
12160
12504
|
this.broadcast({ t: "permission_resolved", requestId, allowed: false, by: "timeout" });
|
|
12161
|
-
const back = this.registry.upsert({ id: sessionId, state: "working", action: "answer in the terminal (phone timed out)", command: "" }, this.cfg.host);
|
|
12505
|
+
const back = this.registry.upsert({ id: sessionId, state: "working", action: "answer in the terminal (phone timed out)", command: "", plan: [] }, this.cfg.host);
|
|
12162
12506
|
this.broadcast({ t: "session_upsert", session: back });
|
|
12163
12507
|
resolve({ allow: false, by: "timeout" });
|
|
12164
12508
|
}, HOOK_WAIT_MS);
|
|
@@ -12193,6 +12537,20 @@ class Daemon {
|
|
|
12193
12537
|
this.openTools.set(sessionId, open2);
|
|
12194
12538
|
return id;
|
|
12195
12539
|
}
|
|
12540
|
+
streamToolTarget(sessionId) {
|
|
12541
|
+
const items = this.registry.items.get(sessionId) ?? [];
|
|
12542
|
+
const settled = new Set;
|
|
12543
|
+
for (const i of items) {
|
|
12544
|
+
if (i.kind === "tool_result" || i.kind === "tool_decision")
|
|
12545
|
+
settled.add(i.toolUseId);
|
|
12546
|
+
}
|
|
12547
|
+
for (let k = items.length - 1;k >= 0; k--) {
|
|
12548
|
+
const i = items[k];
|
|
12549
|
+
if (i.kind === "tool_use" && !settled.has(i.id))
|
|
12550
|
+
return i.id;
|
|
12551
|
+
}
|
|
12552
|
+
return null;
|
|
12553
|
+
}
|
|
12196
12554
|
closeOpenTool(sessionId, toolUseId) {
|
|
12197
12555
|
const open2 = this.openTools.get(sessionId);
|
|
12198
12556
|
if (!open2)
|
|
@@ -12449,18 +12807,18 @@ function b64url2(bytes) {
|
|
|
12449
12807
|
}
|
|
12450
12808
|
|
|
12451
12809
|
// src/hooks.ts
|
|
12452
|
-
import { readFileSync as
|
|
12453
|
-
import { homedir as
|
|
12454
|
-
import { join as
|
|
12455
|
-
var
|
|
12456
|
-
var settingsPath = () =>
|
|
12810
|
+
import { readFileSync as readFileSync3, writeFileSync as writeFileSync2, existsSync as existsSync4, mkdirSync as mkdirSync2 } from "node:fs";
|
|
12811
|
+
import { homedir as homedir4 } from "node:os";
|
|
12812
|
+
import { join as join4 } from "node:path";
|
|
12813
|
+
var claudeDir2 = () => process.env.CLAUDE_CONFIG_DIR || join4(homedir4(), ".claude");
|
|
12814
|
+
var settingsPath = () => join4(claudeDir2(), "settings.json");
|
|
12457
12815
|
var EVENTS = ["SessionStart", "UserPromptSubmit", "PreToolUse", "PostToolUse", "Notification", "Stop"];
|
|
12458
12816
|
function installHooks(droverBin) {
|
|
12459
|
-
mkdirSync2(
|
|
12817
|
+
mkdirSync2(claudeDir2(), { recursive: true });
|
|
12460
12818
|
let settings = {};
|
|
12461
|
-
if (
|
|
12819
|
+
if (existsSync4(settingsPath())) {
|
|
12462
12820
|
try {
|
|
12463
|
-
settings = JSON.parse(
|
|
12821
|
+
settings = JSON.parse(readFileSync3(settingsPath(), "utf8"));
|
|
12464
12822
|
} catch {
|
|
12465
12823
|
settings = {};
|
|
12466
12824
|
}
|
|
@@ -12495,10 +12853,10 @@ function isOurs(x) {
|
|
|
12495
12853
|
return cmd.includes("hatchee") || cmd.includes("drover") || cmd.includes("daemon/src/cli.ts");
|
|
12496
12854
|
}
|
|
12497
12855
|
function uninstallHooks() {
|
|
12498
|
-
if (!
|
|
12856
|
+
if (!existsSync4(settingsPath()))
|
|
12499
12857
|
return;
|
|
12500
12858
|
try {
|
|
12501
|
-
const settings = JSON.parse(
|
|
12859
|
+
const settings = JSON.parse(readFileSync3(settingsPath(), "utf8"));
|
|
12502
12860
|
for (const event of Object.keys(settings.hooks ?? {})) {
|
|
12503
12861
|
settings.hooks[event] = settings.hooks[event].filter((h) => !h?.hooks?.some(isOurs));
|
|
12504
12862
|
if (settings.hooks[event].length === 0)
|
|
@@ -12560,27 +12918,27 @@ async function runHook(hookPort) {
|
|
|
12560
12918
|
}
|
|
12561
12919
|
|
|
12562
12920
|
// src/service.ts
|
|
12563
|
-
import { homedir as
|
|
12564
|
-
import { join as
|
|
12565
|
-
import { mkdirSync as mkdirSync3, writeFileSync as writeFileSync3, copyFileSync, chmodSync, existsSync as
|
|
12921
|
+
import { homedir as homedir5 } from "node:os";
|
|
12922
|
+
import { join as join5, dirname } from "node:path";
|
|
12923
|
+
import { mkdirSync as mkdirSync3, writeFileSync as writeFileSync3, copyFileSync, chmodSync, existsSync as existsSync5, rmSync } from "node:fs";
|
|
12566
12924
|
import { execFileSync } from "node:child_process";
|
|
12567
|
-
var HOME =
|
|
12568
|
-
var HATCHEE_DIR =
|
|
12569
|
-
var BIN_DIR =
|
|
12570
|
-
var STABLE_BIN =
|
|
12571
|
-
var LOG =
|
|
12925
|
+
var HOME = homedir5();
|
|
12926
|
+
var HATCHEE_DIR = join5(HOME, ".hatchee");
|
|
12927
|
+
var BIN_DIR = join5(HATCHEE_DIR, "bin");
|
|
12928
|
+
var STABLE_BIN = join5(BIN_DIR, "hatchee.mjs");
|
|
12929
|
+
var LOG = join5(HATCHEE_DIR, "daemon.log");
|
|
12572
12930
|
var LABEL = "cloud.hatchee.daemon";
|
|
12573
|
-
var PLIST =
|
|
12574
|
-
var UNIT =
|
|
12931
|
+
var PLIST = join5(HOME, "Library", "LaunchAgents", `${LABEL}.plist`);
|
|
12932
|
+
var UNIT = join5(HOME, ".config", "systemd", "user", "hatchee.service");
|
|
12575
12933
|
function servicePath(node) {
|
|
12576
12934
|
const system = ["/usr/bin", "/bin", "/usr/sbin", "/sbin"];
|
|
12577
12935
|
const userTool = [
|
|
12578
12936
|
dirname(node),
|
|
12579
12937
|
"/opt/homebrew/bin",
|
|
12580
12938
|
"/usr/local/bin",
|
|
12581
|
-
|
|
12582
|
-
|
|
12583
|
-
|
|
12939
|
+
join5(HOME, ".bun/bin"),
|
|
12940
|
+
join5(HOME, ".npm-global/bin"),
|
|
12941
|
+
join5(HOME, ".local/bin")
|
|
12584
12942
|
];
|
|
12585
12943
|
const seen = new Set;
|
|
12586
12944
|
return [...system, ...userTool].filter((p) => p && !seen.has(p) && seen.add(p)).join(":");
|
|
@@ -12731,12 +13089,12 @@ function uninstallService() {
|
|
|
12731
13089
|
if (process.platform === "darwin") {
|
|
12732
13090
|
const uid = String(process.getuid?.() ?? "");
|
|
12733
13091
|
run("launchctl", ["bootout", `gui/${uid}/${LABEL}`]);
|
|
12734
|
-
if (
|
|
13092
|
+
if (existsSync5(PLIST))
|
|
12735
13093
|
rmSync(PLIST);
|
|
12736
13094
|
console.log(` ✓ background service removed (launchd: ${LABEL})`);
|
|
12737
13095
|
} else if (process.platform === "linux") {
|
|
12738
13096
|
run("systemctl", ["--user", "disable", "--now", "hatchee.service"]);
|
|
12739
|
-
if (
|
|
13097
|
+
if (existsSync5(UNIT)) {
|
|
12740
13098
|
rmSync(UNIT);
|
|
12741
13099
|
run("systemctl", ["--user", "daemon-reload"]);
|
|
12742
13100
|
}
|
|
@@ -12744,7 +13102,7 @@ function uninstallService() {
|
|
|
12744
13102
|
} else {
|
|
12745
13103
|
console.log(` no background service on ${process.platform}.`);
|
|
12746
13104
|
}
|
|
12747
|
-
if (
|
|
13105
|
+
if (existsSync5(STABLE_BIN))
|
|
12748
13106
|
rmSync(STABLE_BIN);
|
|
12749
13107
|
}
|
|
12750
13108
|
|