@yoavsh/treekeeper 0.0.0-stage → 0.1.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.
Files changed (166) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/LICENSE +21 -0
  3. package/README.md +237 -2
  4. package/dist/cli.d.ts +3 -0
  5. package/dist/cli.d.ts.map +1 -0
  6. package/dist/cli.js +165 -0
  7. package/dist/cli.js.map +1 -0
  8. package/dist/commands/archive.d.ts +3 -0
  9. package/dist/commands/archive.d.ts.map +1 -0
  10. package/dist/commands/archive.js +55 -0
  11. package/dist/commands/archive.js.map +1 -0
  12. package/dist/commands/config.d.ts +2 -0
  13. package/dist/commands/config.d.ts.map +1 -0
  14. package/dist/commands/config.js +54 -0
  15. package/dist/commands/config.js.map +1 -0
  16. package/dist/commands/context.d.ts +34 -0
  17. package/dist/commands/context.d.ts.map +1 -0
  18. package/dist/commands/context.js +53 -0
  19. package/dist/commands/context.js.map +1 -0
  20. package/dist/commands/daemon.d.ts +3 -0
  21. package/dist/commands/daemon.d.ts.map +1 -0
  22. package/dist/commands/daemon.js +81 -0
  23. package/dist/commands/daemon.js.map +1 -0
  24. package/dist/commands/doctor.d.ts +3 -0
  25. package/dist/commands/doctor.d.ts.map +1 -0
  26. package/dist/commands/doctor.js +119 -0
  27. package/dist/commands/doctor.js.map +1 -0
  28. package/dist/commands/gc.d.ts +3 -0
  29. package/dist/commands/gc.d.ts.map +1 -0
  30. package/dist/commands/gc.js +98 -0
  31. package/dist/commands/gc.js.map +1 -0
  32. package/dist/commands/status.d.ts +4 -0
  33. package/dist/commands/status.d.ts.map +1 -0
  34. package/dist/commands/status.js +52 -0
  35. package/dist/commands/status.js.map +1 -0
  36. package/dist/commands/watch.d.ts +10 -0
  37. package/dist/commands/watch.d.ts.map +1 -0
  38. package/dist/commands/watch.js +41 -0
  39. package/dist/commands/watch.js.map +1 -0
  40. package/dist/core/apply.d.ts +47 -0
  41. package/dist/core/apply.d.ts.map +1 -0
  42. package/dist/core/apply.js +223 -0
  43. package/dist/core/apply.js.map +1 -0
  44. package/dist/core/archive.d.ts +69 -0
  45. package/dist/core/archive.d.ts.map +1 -0
  46. package/dist/core/archive.js +202 -0
  47. package/dist/core/archive.js.map +1 -0
  48. package/dist/core/config.d.ts +69 -0
  49. package/dist/core/config.d.ts.map +1 -0
  50. package/dist/core/config.js +261 -0
  51. package/dist/core/config.js.map +1 -0
  52. package/dist/core/decide.d.ts +102 -0
  53. package/dist/core/decide.d.ts.map +1 -0
  54. package/dist/core/decide.js +148 -0
  55. package/dist/core/decide.js.map +1 -0
  56. package/dist/core/forge.d.ts +39 -0
  57. package/dist/core/forge.d.ts.map +1 -0
  58. package/dist/core/forge.js +190 -0
  59. package/dist/core/forge.js.map +1 -0
  60. package/dist/core/git.d.ts +63 -0
  61. package/dist/core/git.d.ts.map +1 -0
  62. package/dist/core/git.js +194 -0
  63. package/dist/core/git.js.map +1 -0
  64. package/dist/core/plan.d.ts +75 -0
  65. package/dist/core/plan.d.ts.map +1 -0
  66. package/dist/core/plan.js +288 -0
  67. package/dist/core/plan.js.map +1 -0
  68. package/dist/core/render.d.ts +14 -0
  69. package/dist/core/render.d.ts.map +1 -0
  70. package/dist/core/render.js +128 -0
  71. package/dist/core/render.js.map +1 -0
  72. package/dist/core/scan.d.ts +22 -0
  73. package/dist/core/scan.d.ts.map +1 -0
  74. package/dist/core/scan.js +99 -0
  75. package/dist/core/scan.js.map +1 -0
  76. package/dist/core/sessions.d.ts +37 -0
  77. package/dist/core/sessions.d.ts.map +1 -0
  78. package/dist/core/sessions.js +300 -0
  79. package/dist/core/sessions.js.map +1 -0
  80. package/dist/core/size.d.ts +3 -0
  81. package/dist/core/size.d.ts.map +1 -0
  82. package/dist/core/size.js +11 -0
  83. package/dist/core/size.js.map +1 -0
  84. package/dist/daemon/lock.d.ts +10 -0
  85. package/dist/daemon/lock.d.ts.map +1 -0
  86. package/dist/daemon/lock.js +64 -0
  87. package/dist/daemon/lock.js.map +1 -0
  88. package/dist/daemon/service.d.ts +34 -0
  89. package/dist/daemon/service.d.ts.map +1 -0
  90. package/dist/daemon/service.js +257 -0
  91. package/dist/daemon/service.js.map +1 -0
  92. package/dist/daemon/state.d.ts +19 -0
  93. package/dist/daemon/state.d.ts.map +1 -0
  94. package/dist/daemon/state.js +32 -0
  95. package/dist/daemon/state.js.map +1 -0
  96. package/dist/index.d.ts +26 -0
  97. package/dist/index.d.ts.map +1 -0
  98. package/dist/index.js +19 -0
  99. package/dist/index.js.map +1 -0
  100. package/dist/util/bytes.d.ts +4 -0
  101. package/dist/util/bytes.d.ts.map +1 -0
  102. package/dist/util/bytes.js +14 -0
  103. package/dist/util/bytes.js.map +1 -0
  104. package/dist/util/color.d.ts +16 -0
  105. package/dist/util/color.d.ts.map +1 -0
  106. package/dist/util/color.js +32 -0
  107. package/dist/util/color.js.map +1 -0
  108. package/dist/util/exec.d.ts +27 -0
  109. package/dist/util/exec.d.ts.map +1 -0
  110. package/dist/util/exec.js +57 -0
  111. package/dist/util/exec.js.map +1 -0
  112. package/dist/util/log.d.ts +29 -0
  113. package/dist/util/log.d.ts.map +1 -0
  114. package/dist/util/log.js +72 -0
  115. package/dist/util/log.js.map +1 -0
  116. package/dist/util/paths.d.ts +18 -0
  117. package/dist/util/paths.d.ts.map +1 -0
  118. package/dist/util/paths.js +87 -0
  119. package/dist/util/paths.js.map +1 -0
  120. package/dist/util/prompt.d.ts +3 -0
  121. package/dist/util/prompt.d.ts.map +1 -0
  122. package/dist/util/prompt.js +15 -0
  123. package/dist/util/prompt.js.map +1 -0
  124. package/dist/util/redact.d.ts +15 -0
  125. package/dist/util/redact.d.ts.map +1 -0
  126. package/dist/util/redact.js +44 -0
  127. package/dist/util/redact.js.map +1 -0
  128. package/dist/util/time.d.ts +13 -0
  129. package/dist/util/time.d.ts.map +1 -0
  130. package/dist/util/time.js +77 -0
  131. package/dist/util/time.js.map +1 -0
  132. package/docs/integrations.md +128 -0
  133. package/docs/safety.md +62 -0
  134. package/package.json +69 -4
  135. package/src/cli.ts +175 -0
  136. package/src/commands/archive.ts +72 -0
  137. package/src/commands/config.ts +57 -0
  138. package/src/commands/context.ts +86 -0
  139. package/src/commands/daemon.ts +86 -0
  140. package/src/commands/doctor.ts +131 -0
  141. package/src/commands/gc.ts +108 -0
  142. package/src/commands/status.ts +68 -0
  143. package/src/commands/watch.ts +48 -0
  144. package/src/core/apply.ts +276 -0
  145. package/src/core/archive.ts +269 -0
  146. package/src/core/config.ts +339 -0
  147. package/src/core/decide.ts +230 -0
  148. package/src/core/forge.ts +243 -0
  149. package/src/core/git.ts +254 -0
  150. package/src/core/plan.ts +431 -0
  151. package/src/core/render.ts +168 -0
  152. package/src/core/scan.ts +105 -0
  153. package/src/core/sessions.ts +339 -0
  154. package/src/core/size.ts +10 -0
  155. package/src/daemon/lock.ts +65 -0
  156. package/src/daemon/service.ts +321 -0
  157. package/src/daemon/state.ts +49 -0
  158. package/src/index.ts +36 -0
  159. package/src/util/bytes.ts +11 -0
  160. package/src/util/color.ts +33 -0
  161. package/src/util/exec.ts +80 -0
  162. package/src/util/log.ts +88 -0
  163. package/src/util/paths.ts +84 -0
  164. package/src/util/prompt.ts +13 -0
  165. package/src/util/redact.ts +43 -0
  166. package/src/util/time.ts +75 -0
@@ -0,0 +1,300 @@
1
+ import { readdir, readFile, stat } from "node:fs/promises";
2
+ import { homedir } from "node:os";
3
+ import { join } from "node:path";
4
+ import { run } from "../util/exec.js";
5
+ import { expandPath, isInside } from "../util/paths.js";
6
+ import { parseDuration } from "../util/time.js";
7
+ /** Directories whose contents say nothing about whether an agent is working. */
8
+ const MTIME_SKIP = new Set([
9
+ "node_modules", ".git", "dist", "build", "target", ".next", ".nuxt", ".venv",
10
+ "venv", "__pycache__", ".cache", "coverage", ".turbo", ".pytest_cache",
11
+ ".gradle", ".mypy_cache", ".ruff_cache", "vendor", ".terraform",
12
+ ]);
13
+ const MTIME_MAX_ENTRIES = 4_000;
14
+ const MTIME_MAX_DEPTH = 3;
15
+ export async function createActivityContext(cfg, logger, now = Date.now()) {
16
+ const detectors = new Set(cfg.sessions.detectors);
17
+ const ctx = {
18
+ now,
19
+ graceMs: parseDuration(cfg.sessions.grace),
20
+ detectors,
21
+ extraPaths: cfg.sessions.extraPaths,
22
+ commands: cfg.sessions.commands,
23
+ logger,
24
+ processCwds: null,
25
+ };
26
+ if (detectors.has("process")) {
27
+ ctx.processCwds = await loadProcessCwds(logger);
28
+ }
29
+ return ctx;
30
+ }
31
+ /**
32
+ * Is an agent (or a human) still working in this worktree?
33
+ *
34
+ * Detectors are deliberately layered: the harness-specific ones give a precise
35
+ * answer with a timestamp, while `process` and `mtime` are the catch-all that
36
+ * makes this work with harnesses treekeeper has never heard of.
37
+ */
38
+ export async function detectActivity(ctx, worktreePath) {
39
+ const cutoff = ctx.now - ctx.graceMs;
40
+ const hits = [];
41
+ if (ctx.detectors.has("process") && ctx.processCwds) {
42
+ const match = ctx.processCwds.find((p) => isInside(worktreePath, p));
43
+ if (match) {
44
+ hits.push({
45
+ detector: "process",
46
+ lastActivity: ctx.now,
47
+ detail: "a running process is sitting in this directory",
48
+ });
49
+ }
50
+ }
51
+ if (ctx.detectors.has("claude-code")) {
52
+ const hit = await claudeCodeActivity(worktreePath, cutoff);
53
+ if (hit)
54
+ hits.push(hit);
55
+ }
56
+ if (ctx.detectors.has("codex")) {
57
+ const hit = await codexActivity(worktreePath, cutoff, ctx);
58
+ if (hit)
59
+ hits.push(hit);
60
+ }
61
+ if (ctx.detectors.has("aider")) {
62
+ const hit = await newestFileIn(worktreePath, [".aider.chat.history.md", ".aider.input.history"], "aider", cutoff);
63
+ if (hit)
64
+ hits.push(hit);
65
+ }
66
+ if (ctx.extraPaths.length > 0) {
67
+ const paths = ctx.extraPaths.map((p) => expandPath(p.replaceAll("{worktree}", worktreePath)));
68
+ const hit = await newestPath(paths, "extra-path", cutoff);
69
+ if (hit)
70
+ hits.push(hit);
71
+ }
72
+ if (ctx.detectors.has("mtime")) {
73
+ const hit = await mtimeActivity(worktreePath, cutoff);
74
+ if (hit)
75
+ hits.push(hit);
76
+ }
77
+ for (const cmd of ctx.commands) {
78
+ const r = await run("sh", ["-c", cmd.command], {
79
+ timeoutMs: 15_000,
80
+ env: { ...process.env, TREEKEEPER_WORKTREE: worktreePath },
81
+ cwd: undefined,
82
+ });
83
+ if (r.code === 0) {
84
+ hits.push({
85
+ detector: cmd.name,
86
+ lastActivity: ctx.now,
87
+ detail: `custom check "${cmd.name}" reported activity`,
88
+ });
89
+ }
90
+ }
91
+ if (hits.length === 0)
92
+ return null;
93
+ hits.sort((a, b) => b.lastActivity - a.lastActivity);
94
+ return hits[0];
95
+ }
96
+ /** `true` when the hit falls inside the session grace window. */
97
+ export function isActive(ctx, hit) {
98
+ if (!hit)
99
+ return false;
100
+ return hit.lastActivity >= ctx.now - ctx.graceMs;
101
+ }
102
+ // --- harness-specific detectors --------------------------------------------
103
+ /**
104
+ * Claude Code stores one JSONL transcript per session under
105
+ * `~/.claude/projects/<slugified-cwd>/`, so the newest transcript mtime is the
106
+ * last time an agent did anything in that worktree.
107
+ */
108
+ export function claudeProjectSlugs(worktreePath) {
109
+ const all = worktreePath.replace(/[^a-zA-Z0-9]/g, "-");
110
+ const slashesOnly = worktreePath.replace(/\//g, "-");
111
+ return all === slashesOnly ? [all] : [all, slashesOnly];
112
+ }
113
+ async function claudeCodeActivity(worktreePath, cutoff) {
114
+ const base = process.env.CLAUDE_CONFIG_DIR
115
+ ? expandPath(process.env.CLAUDE_CONFIG_DIR)
116
+ : join(homedir(), ".claude");
117
+ for (const slug of claudeProjectSlugs(worktreePath)) {
118
+ const dir = join(base, "projects", slug);
119
+ const hit = await newestInDir(dir, ".jsonl", "claude-code", cutoff);
120
+ if (hit)
121
+ return hit;
122
+ }
123
+ return null;
124
+ }
125
+ /**
126
+ * Codex CLI writes rollout transcripts under `~/.codex/sessions/<y>/<m>/<d>/`.
127
+ * Only recently-modified files are opened, and only their head is read, so the
128
+ * cwd match stays cheap even with thousands of archived sessions.
129
+ */
130
+ async function codexActivity(worktreePath, cutoff, ctx) {
131
+ const root = join(homedir(), ".codex", "sessions");
132
+ const recent = [];
133
+ const walk = async (dir, depth) => {
134
+ if (depth > 4 || recent.length >= 40)
135
+ return;
136
+ let entries;
137
+ try {
138
+ entries = await readdir(dir, { withFileTypes: true });
139
+ }
140
+ catch {
141
+ return;
142
+ }
143
+ for (const e of entries) {
144
+ const p = join(dir, e.name);
145
+ if (e.isDirectory()) {
146
+ await walk(p, depth + 1);
147
+ }
148
+ else if (e.isFile() && e.name.endsWith(".jsonl")) {
149
+ try {
150
+ const st = await stat(p);
151
+ if (st.mtimeMs >= cutoff)
152
+ recent.push({ path: p, mtime: st.mtimeMs });
153
+ }
154
+ catch {
155
+ /* vanished mid-walk */
156
+ }
157
+ }
158
+ }
159
+ };
160
+ await walk(root, 0);
161
+ recent.sort((a, b) => b.mtime - a.mtime);
162
+ for (const file of recent) {
163
+ try {
164
+ const handle = await readFile(file.path, "utf8");
165
+ const head = handle.slice(0, 8_000);
166
+ if (head.includes(`"cwd":"${worktreePath}"`) || head.includes(`"cwd": "${worktreePath}"`)) {
167
+ return {
168
+ detector: "codex",
169
+ lastActivity: file.mtime,
170
+ detail: "a Codex session transcript was updated",
171
+ };
172
+ }
173
+ }
174
+ catch (err) {
175
+ ctx.logger?.debug(`codex detector: ${err.message}`);
176
+ }
177
+ }
178
+ return null;
179
+ }
180
+ // --- generic detectors ------------------------------------------------------
181
+ /**
182
+ * Last-resort signal that covers every harness: did anything inside the
183
+ * worktree change recently? Bounded in depth and entry count so a sweep over
184
+ * dozens of worktrees stays fast.
185
+ */
186
+ async function mtimeActivity(worktreePath, cutoff) {
187
+ let visited = 0;
188
+ let newest = 0;
189
+ let newestPathSeen = "";
190
+ const walk = async (dir, depth) => {
191
+ if (visited >= MTIME_MAX_ENTRIES)
192
+ return false;
193
+ let entries;
194
+ try {
195
+ entries = await readdir(dir, { withFileTypes: true });
196
+ }
197
+ catch {
198
+ return false;
199
+ }
200
+ for (const e of entries) {
201
+ if (visited >= MTIME_MAX_ENTRIES)
202
+ return false;
203
+ visited += 1;
204
+ if (MTIME_SKIP.has(e.name))
205
+ continue;
206
+ const p = join(dir, e.name);
207
+ if (e.isDirectory()) {
208
+ if (depth < MTIME_MAX_DEPTH && (await walk(p, depth + 1)))
209
+ return true;
210
+ continue;
211
+ }
212
+ if (!e.isFile())
213
+ continue;
214
+ try {
215
+ const st = await stat(p);
216
+ if (st.mtimeMs > newest) {
217
+ newest = st.mtimeMs;
218
+ newestPathSeen = e.name;
219
+ }
220
+ if (st.mtimeMs >= cutoff)
221
+ return true; // early exit: already "active"
222
+ }
223
+ catch {
224
+ /* ignore */
225
+ }
226
+ }
227
+ return false;
228
+ };
229
+ const active = await walk(worktreePath, 0);
230
+ if (!active && newest < cutoff)
231
+ return null;
232
+ return {
233
+ detector: "mtime",
234
+ lastActivity: newest,
235
+ detail: `files changed recently (${newestPathSeen})`,
236
+ };
237
+ }
238
+ async function loadProcessCwds(logger) {
239
+ if (process.platform === "win32")
240
+ return null;
241
+ const r = await run("lsof", ["-d", "cwd", "-Fn"], { timeoutMs: 30_000 });
242
+ if (r.code !== 0 && r.stdout.trim() === "") {
243
+ logger?.debug("process detector: lsof unavailable, skipping");
244
+ return null;
245
+ }
246
+ const out = new Set();
247
+ for (const line of r.stdout.split("\n")) {
248
+ if (line.startsWith("n/"))
249
+ out.add(line.slice(1));
250
+ }
251
+ return [...out];
252
+ }
253
+ async function newestInDir(dir, suffix, detector, cutoff) {
254
+ let entries;
255
+ try {
256
+ entries = await readdir(dir, { withFileTypes: true });
257
+ }
258
+ catch {
259
+ return null;
260
+ }
261
+ let newest = 0;
262
+ for (const e of entries) {
263
+ if (!e.isFile() || !e.name.endsWith(suffix))
264
+ continue;
265
+ try {
266
+ const st = await stat(join(dir, e.name));
267
+ if (st.mtimeMs > newest)
268
+ newest = st.mtimeMs;
269
+ }
270
+ catch {
271
+ /* ignore */
272
+ }
273
+ }
274
+ if (newest === 0 || newest < cutoff)
275
+ return null;
276
+ return { detector, lastActivity: newest, detail: "a session transcript was updated" };
277
+ }
278
+ async function newestFileIn(base, names, detector, cutoff) {
279
+ return newestPath(names.map((n) => join(base, n)), detector, cutoff);
280
+ }
281
+ async function newestPath(paths, detector, cutoff) {
282
+ let newest = 0;
283
+ let which = "";
284
+ for (const p of paths) {
285
+ try {
286
+ const st = await stat(p);
287
+ if (st.mtimeMs > newest) {
288
+ newest = st.mtimeMs;
289
+ which = p;
290
+ }
291
+ }
292
+ catch {
293
+ /* missing is fine */
294
+ }
295
+ }
296
+ if (newest === 0 || newest < cutoff)
297
+ return null;
298
+ return { detector, lastActivity: newest, detail: `${which} was updated` };
299
+ }
300
+ //# sourceMappingURL=sessions.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sessions.js","sourceRoot":"","sources":["../../src/core/sessions.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ /** Disk usage in KB. Uses `du` (fast, counts real blocks); null when unavailable. */
2
+ export declare function dirSizeKb(path: string): Promise<number | null>;
3
+ //# sourceMappingURL=size.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"size.d.ts","sourceRoot":"","sources":["../../src/core/size.ts"],"names":[],"mappings":"AAEA,qFAAqF;AACrF,wBAAsB,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAMpE"}
@@ -0,0 +1,11 @@
1
+ import { run } from "../util/exec.js";
2
+ /** Disk usage in KB. Uses `du` (fast, counts real blocks); null when unavailable. */
3
+ export async function dirSizeKb(path) {
4
+ const r = await run("du", ["-sk", path], { timeoutMs: 120_000 });
5
+ if (r.code !== 0 && r.stdout.trim() === "")
6
+ return null;
7
+ const first = r.stdout.trim().split("\n")[0] ?? "";
8
+ const kb = Number(first.split(/\s+/)[0]);
9
+ return Number.isFinite(kb) ? kb : null;
10
+ }
11
+ //# sourceMappingURL=size.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"size.js","sourceRoot":"","sources":["../../src/core/size.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAE,MAAM,iBAAiB,CAAC;AAEtC,qFAAqF;AACrF,MAAM,CAAC,KAAK,UAAU,SAAS,CAAC,IAAY;IAC1C,MAAM,CAAC,GAAG,MAAM,GAAG,CAAC,IAAI,EAAE,CAAC,KAAK,EAAE,IAAI,CAAC,EAAE,EAAE,SAAS,EAAE,OAAO,EAAE,CAAC,CAAC;IACjE,IAAI,CAAC,CAAC,IAAI,KAAK,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,IAAI,EAAE,KAAK,EAAE;QAAE,OAAO,IAAI,CAAC;IACxD,MAAM,KAAK,GAAG,CAAC,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;IACnD,MAAM,EAAE,GAAG,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IACzC,OAAO,MAAM,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC;AACzC,CAAC"}
@@ -0,0 +1,10 @@
1
+ export interface Lock {
2
+ release(): Promise<void>;
3
+ }
4
+ /**
5
+ * Take the machine-wide sweep lock, so a scheduled sweep and a manual
6
+ * `treekeeper gc --apply` can never remove the same worktree twice.
7
+ * Returns null when another sweep holds it.
8
+ */
9
+ export declare function acquireLock(): Promise<Lock | null>;
10
+ //# sourceMappingURL=lock.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lock.d.ts","sourceRoot":"","sources":["../../src/daemon/lock.ts"],"names":[],"mappings":"AAMA,MAAM,WAAW,IAAI;IACnB,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CAC1B;AAeD;;;;GAIG;AACH,wBAAsB,WAAW,IAAI,OAAO,CAAC,IAAI,GAAG,IAAI,CAAC,CAoCxD"}
@@ -0,0 +1,64 @@
1
+ import { mkdir, readFile, unlink, writeFile } from "node:fs/promises";
2
+ import { join } from "node:path";
3
+ import { treekeeperHome } from "../util/paths.js";
4
+ const LOCK = "sweep.lock";
5
+ function lockPath() {
6
+ return join(treekeeperHome(), LOCK);
7
+ }
8
+ function alive(pid) {
9
+ try {
10
+ process.kill(pid, 0);
11
+ return true;
12
+ }
13
+ catch (err) {
14
+ return err.code === "EPERM";
15
+ }
16
+ }
17
+ /**
18
+ * Take the machine-wide sweep lock, so a scheduled sweep and a manual
19
+ * `treekeeper gc --apply` can never remove the same worktree twice.
20
+ * Returns null when another sweep holds it.
21
+ */
22
+ export async function acquireLock() {
23
+ const path = lockPath();
24
+ await mkdir(treekeeperHome(), { recursive: true });
25
+ for (let attempt = 0; attempt < 2; attempt += 1) {
26
+ try {
27
+ await writeFile(path, JSON.stringify({ pid: process.pid, at: Date.now() }), { flag: "wx" });
28
+ return {
29
+ release: async () => {
30
+ try {
31
+ await unlink(path);
32
+ }
33
+ catch {
34
+ /* already gone */
35
+ }
36
+ },
37
+ };
38
+ }
39
+ catch (err) {
40
+ if (err.code !== "EEXIST")
41
+ throw err;
42
+ // Stale lock from a killed process? Clear it once and retry.
43
+ let holder = null;
44
+ try {
45
+ const parsed = JSON.parse(await readFile(path, "utf8"));
46
+ const pid = parsed.pid;
47
+ holder = typeof pid === "number" ? pid : null;
48
+ }
49
+ catch {
50
+ holder = null;
51
+ }
52
+ if (holder !== null && alive(holder))
53
+ return null;
54
+ try {
55
+ await unlink(path);
56
+ }
57
+ catch {
58
+ return null;
59
+ }
60
+ }
61
+ }
62
+ return null;
63
+ }
64
+ //# sourceMappingURL=lock.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lock.js","sourceRoot":"","sources":["../../src/daemon/lock.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AACtE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAElD,MAAM,IAAI,GAAG,YAAY,CAAC;AAM1B,SAAS,QAAQ;IACf,OAAO,IAAI,CAAC,cAAc,EAAE,EAAE,IAAI,CAAC,CAAC;AACtC,CAAC;AAED,SAAS,KAAK,CAAC,GAAW;IACxB,IAAI,CAAC;QACH,OAAO,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC;QACrB,OAAO,IAAI,CAAC;IACd,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,OAAQ,GAA6B,CAAC,IAAI,KAAK,OAAO,CAAC;IACzD,CAAC;AACH,CAAC;AAED;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,WAAW;IAC/B,MAAM,IAAI,GAAG,QAAQ,EAAE,CAAC;IACxB,MAAM,KAAK,CAAC,cAAc,EAAE,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAEnD,KAAK,IAAI,OAAO,GAAG,CAAC,EAAE,OAAO,GAAG,CAAC,EAAE,OAAO,IAAI,CAAC,EAAE,CAAC;QAChD,IAAI,CAAC;YACH,MAAM,SAAS,CAAC,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,EAAE,GAAG,EAAE,OAAO,CAAC,GAAG,EAAE,EAAE,EAAE,IAAI,CAAC,GAAG,EAAE,EAAE,CAAC,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC;YAC5F,OAAO;gBACL,OAAO,EAAE,KAAK,IAAI,EAAE;oBAClB,IAAI,CAAC;wBACH,MAAM,MAAM,CAAC,IAAI,CAAC,CAAC;oBACrB,CAAC;oBAAC,MAAM,CAAC;wBACP,kBAAkB;oBACpB,CAAC;gBACH,CAAC;aACF,CAAC;QACJ,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,IAAK,GAA6B,CAAC,IAAI,KAAK,QAAQ;gBAAE,MAAM,GAAG,CAAC;YAChE,6DAA6D;YAC7D,IAAI,MAAM,GAAkB,IAAI,CAAC;YACjC,IAAI,CAAC;gBACH,MAAM,MAAM,GAAY,IAAI,CAAC,KAAK,CAAC,MAAM,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,CAAC;gBACjE,MAAM,GAAG,GAAI,MAA4B,CAAC,GAAG,CAAC;gBAC9C,MAAM,GAAG,OAAO,GAAG,KAAK,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC;YAChD,CAAC;YAAC,MAAM,CAAC;gBACP,MAAM,GAAG,IAAI,CAAC;YAChB,CAAC;YACD,IAAI,MAAM,KAAK,IAAI,IAAI,KAAK,CAAC,MAAM,CAAC;gBAAE,OAAO,IAAI,CAAC;YAClD,IAAI,CAAC;gBACH,MAAM,MAAM,CAAC,IAAI,CAAC,CAAC;YACrB,CAAC;YAAC,MAAM,CAAC;gBACP,OAAO,IAAI,CAAC;YACd,CAAC;QACH,CAAC;IACH,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC"}
@@ -0,0 +1,34 @@
1
+ import type { SweepRecord } from "./state.js";
2
+ export declare const LABEL = "com.treekeeper.agent";
3
+ export declare const SYSTEMD_UNIT = "treekeeper";
4
+ export type Manager = "launchd" | "systemd" | "unsupported";
5
+ export interface DaemonStatus {
6
+ manager: Manager;
7
+ installed: boolean;
8
+ /** Loaded with the service manager and scheduled to run. */
9
+ active: boolean;
10
+ unitPath: string | null;
11
+ intervalSeconds: number | null;
12
+ lastSweep: SweepRecord | null;
13
+ logFile: string;
14
+ }
15
+ export declare function currentManager(): Manager;
16
+ /** Absolute path to this installation's CLI entry point. */
17
+ export declare function cliPath(): string;
18
+ /** The command a scheduled sweep runs. */
19
+ export declare function sweepArgv(extra?: string[]): string[];
20
+ export declare function daemonStatus(): Promise<DaemonStatus>;
21
+ export interface InstallResult {
22
+ ok: boolean;
23
+ manager: Manager;
24
+ unitPath: string | null;
25
+ messages: string[];
26
+ }
27
+ /** Write and load the service definition for the current platform. */
28
+ export declare function installDaemon(interval: string): Promise<InstallResult>;
29
+ export declare function uninstallDaemon(): Promise<InstallResult>;
30
+ export declare function setDaemonRunning(running: boolean): Promise<InstallResult>;
31
+ export declare function launchdPlist(seconds: number): string;
32
+ export declare function systemdService(): string;
33
+ export declare function systemdTimer(seconds: number): string;
34
+ //# sourceMappingURL=service.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../../src/daemon/service.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAG9C,eAAO,MAAM,KAAK,yBAAyB,CAAC;AAC5C,eAAO,MAAM,YAAY,eAAe,CAAC;AAEzC,MAAM,MAAM,OAAO,GAAG,SAAS,GAAG,SAAS,GAAG,aAAa,CAAC;AAE5D,MAAM,WAAW,YAAY;IAC3B,OAAO,EAAE,OAAO,CAAC;IACjB,SAAS,EAAE,OAAO,CAAC;IACnB,4DAA4D;IAC5D,MAAM,EAAE,OAAO,CAAC;IAChB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,eAAe,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,SAAS,EAAE,WAAW,GAAG,IAAI,CAAC;IAC9B,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,wBAAgB,cAAc,IAAI,OAAO,CAIxC;AAiBD,4DAA4D;AAC5D,wBAAgB,OAAO,IAAI,MAAM,CAEhC;AAED,0CAA0C;AAC1C,wBAAgB,SAAS,CAAC,KAAK,GAAE,MAAM,EAAO,GAAG,MAAM,EAAE,CAExD;AAED,wBAAsB,YAAY,IAAI,OAAO,CAAC,YAAY,CAAC,CA4C1D;AAED,MAAM,WAAW,aAAa;IAC5B,EAAE,EAAE,OAAO,CAAC;IACZ,OAAO,EAAE,OAAO,CAAC;IACjB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,QAAQ,EAAE,MAAM,EAAE,CAAC;CACpB;AAED,sEAAsE;AACtE,wBAAsB,aAAa,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,CAAC,CA6D5E;AAED,wBAAsB,eAAe,IAAI,OAAO,CAAC,aAAa,CAAC,CAyB9D;AAED,wBAAsB,gBAAgB,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,aAAa,CAAC,CAuB/E;AAED,wBAAgB,YAAY,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAqCpD;AAED,wBAAgB,cAAc,IAAI,MAAM,CAavC;AAED,wBAAgB,YAAY,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAapD"}