@lesscap/baton-cli 0.2.2 → 0.2.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.
Files changed (2) hide show
  1. package/dist/baton.mjs +56 -23
  2. package/package.json +1 -1
package/dist/baton.mjs CHANGED
@@ -587,9 +587,13 @@ var sessionsClient = (baseUrl) => {
587
587
  const matches = all.filter((s) => s.name === name);
588
588
  return matches.length === 0 ? null : matches[matches.length - 1] ?? null;
589
589
  },
590
- sendMessage: (id, text, attachments) => request(u(`/sessions/${id}/messages`), {
590
+ sendMessage: (id, text, attachments, planMode) => request(u(`/sessions/${id}/messages`), {
591
591
  method: "POST",
592
- body: attachments && attachments.length > 0 ? { text, attachments } : { text }
592
+ body: {
593
+ text,
594
+ ...attachments && attachments.length > 0 ? { attachments } : {},
595
+ ...planMode ? { planMode: true } : {}
596
+ }
593
597
  }),
594
598
  // Raw-body upload: the file streams as the request body (no multipart), so
595
599
  // a Blob backed by a file on disk uploads without buffering. filename rides
@@ -734,6 +738,11 @@ var viewWorker = (cfg) => {
734
738
  apiToken: worker2.apiToken
735
739
  };
736
740
  };
741
+ var worktreeConfig = (cfg) => ({
742
+ server: cfg.server,
743
+ project: cfg.projectId,
744
+ worker: { id: cfg.workerId, name: cfg.name, machineId: cfg.machineId, apiToken: cfg.apiToken }
745
+ });
737
746
 
738
747
  // src/config.ts
739
748
  var resolveBaseUrl = (urlArg, env = process.env) => {
@@ -1795,14 +1804,15 @@ ${text}` : header;
1795
1804
  };
1796
1805
 
1797
1806
  // src/session/runner/query.ts
1798
- var startQuery = (config, text, resuming, queryFn, abortController, log, envOverlay) => {
1807
+ var startQuery = (config, text, resuming, queryFn, abortController, log, envOverlay, planMode = false) => {
1799
1808
  const env = buildSdkEnv(envOverlay);
1800
1809
  const exe = claudeExecutable();
1801
1810
  const addDirs = additionalDirs();
1802
1811
  if (addDirs) log(`[add-dir] ${addDirs.join(", ")}`);
1812
+ if (planMode) log("[plan] read-only planning turn");
1803
1813
  const options = {
1804
1814
  cwd: config.worktreePath,
1805
- permissionMode: "bypassPermissions",
1815
+ permissionMode: planMode ? "plan" : "bypassPermissions",
1806
1816
  includePartialMessages: false,
1807
1817
  abortController,
1808
1818
  stderr: (line) => log(`[claude] ${line.trimEnd()}`),
@@ -1868,6 +1878,7 @@ var runTurn = async (config, worker2, msg, resuming, queryFn, log = (m) => conso
1868
1878
  const payload = msg.payload;
1869
1879
  const rawText = typeof payload?.text === "string" ? payload.text : "";
1870
1880
  const attachments = Array.isArray(payload?.attachments) ? payload.attachments : [];
1881
+ const planMode = payload?.planMode === true;
1871
1882
  if (rawText.length === 0 && attachments.length === 0) {
1872
1883
  await worker2.emitEvent("turn_error", { message: "user_message missing text and attachments" });
1873
1884
  return -1;
@@ -1883,15 +1894,15 @@ var runTurn = async (config, worker2, msg, resuming, queryFn, log = (m) => conso
1883
1894
  const abort = new AbortController();
1884
1895
  const ceiling = turnTimeoutMs();
1885
1896
  let timer;
1886
- const timeout = new Promise((resolve) => {
1887
- timer = setTimeout(() => resolve(TIMEOUT), ceiling);
1897
+ const timeout = new Promise((resolve2) => {
1898
+ timer = setTimeout(() => resolve2(TIMEOUT), ceiling);
1888
1899
  });
1889
- const interrupted = new Promise((resolve) => {
1890
- if (externalSignal?.aborted) resolve(ABORTED);
1891
- else externalSignal?.addEventListener("abort", () => resolve(ABORTED), { once: true });
1900
+ const interrupted = new Promise((resolve2) => {
1901
+ if (externalSignal?.aborted) resolve2(ABORTED);
1902
+ else externalSignal?.addEventListener("abort", () => resolve2(ABORTED), { once: true });
1892
1903
  });
1893
1904
  try {
1894
- const messages = startQuery(config, text, resuming, queryFn, abort, log, envOverlay);
1905
+ const messages = startQuery(config, text, resuming, queryFn, abort, log, envOverlay, planMode);
1895
1906
  const consume = streamSdkEvents(messages, worker2);
1896
1907
  void consume.catch(() => {
1897
1908
  });
@@ -1906,7 +1917,9 @@ var runTurn = async (config, worker2, msg, resuming, queryFn, log = (m) => conso
1906
1917
  if (outcome === TIMEOUT) {
1907
1918
  log(`[watchdog] turn exceeded ${ceiling}ms \u2014 aborting claude`);
1908
1919
  abort.abort();
1909
- await worker2.emitEvent("turn_error", { message: `turn exceeded ${ceiling}ms \u2014 claude aborted` });
1920
+ await worker2.emitEvent("turn_error", {
1921
+ message: `turn exceeded ${ceiling}ms \u2014 claude aborted`
1922
+ });
1910
1923
  await worker2.emitEvent("turn_complete", {});
1911
1924
  return -1;
1912
1925
  }
@@ -1940,9 +1953,9 @@ var subscribeStream = (url, ESCtor, seen, log, onUserMessage, onInterrupt, onRea
1940
1953
  es.onerror = () => log("sse error (eventsource will retry)");
1941
1954
  return es;
1942
1955
  };
1943
- var waitForAbort = (signal) => new Promise((resolve) => {
1944
- if (signal.aborted) return resolve();
1945
- signal.addEventListener("abort", () => resolve(), { once: true });
1956
+ var waitForAbort = (signal) => new Promise((resolve2) => {
1957
+ if (signal.aborted) return resolve2();
1958
+ signal.addEventListener("abort", () => resolve2(), { once: true });
1946
1959
  });
1947
1960
  var shouldReap = (lastActivity, now, busy, queueLen, idleMs) => !busy && queueLen === 0 && now - lastActivity >= idleMs;
1948
1961
  var idleMsFromEnv = () => Number(process.env.BATON_SESSION_IDLE_MS) || 30 * 6e4;
@@ -2025,12 +2038,12 @@ var runDaemon = async (config, deps, signal) => {
2025
2038
  );
2026
2039
  const idleMs = idleMsFromEnv();
2027
2040
  let idleTimer;
2028
- const reaped = new Promise((resolve) => {
2041
+ const reaped = new Promise((resolve2) => {
2029
2042
  idleTimer = setInterval(
2030
2043
  () => {
2031
2044
  if (shouldReap(lastActivity, Date.now(), busy, pendingQueue.length, idleMs)) {
2032
2045
  log("idle \u2014 shutting down (resumes on next message)");
2033
- resolve();
2046
+ resolve2();
2034
2047
  }
2035
2048
  },
2036
2049
  Math.min(idleMs, 3e4)
@@ -2401,8 +2414,8 @@ ${exchange}`;
2401
2414
 
2402
2415
  // src/session/worktree.ts
2403
2416
  import { execFileSync, spawnSync as spawnSync2 } from "node:child_process";
2404
- import { existsSync as existsSync4, mkdirSync } from "node:fs";
2405
- import { dirname } from "node:path";
2417
+ import { appendFileSync, existsSync as existsSync4, mkdirSync, readFileSync as readFileSync3 } from "node:fs";
2418
+ import { dirname, isAbsolute as isAbsolute2, resolve } from "node:path";
2406
2419
  var createWorktree = (input) => {
2407
2420
  if (existsSync4(input.worktreePath))
2408
2421
  throw new Error(`worktree path already exists: ${input.worktreePath}`);
@@ -2425,6 +2438,24 @@ var repoHeadBranch = (repo) => {
2425
2438
  const branch = r.status === 0 ? r.stdout.trim() : "";
2426
2439
  return branch || "main";
2427
2440
  };
2441
+ var ensureExcluded = (repo, pattern) => {
2442
+ try {
2443
+ const r = spawnSync2("git", ["-C", repo, "rev-parse", "--git-path", "info/exclude"], {
2444
+ stdio: "pipe",
2445
+ encoding: "utf8"
2446
+ });
2447
+ if (r.status !== 0) return;
2448
+ const rel = r.stdout.trim();
2449
+ const path = isAbsolute2(rel) ? rel : resolve(repo, rel);
2450
+ const current = existsSync4(path) ? readFileSync3(path, "utf8") : "";
2451
+ if (current.split("\n").includes(pattern)) return;
2452
+ mkdirSync(dirname(path), { recursive: true });
2453
+ const sep = current === "" || current.endsWith("\n") ? "" : "\n";
2454
+ appendFileSync(path, `${sep}${pattern}
2455
+ `, "utf8");
2456
+ } catch {
2457
+ }
2458
+ };
2428
2459
  var removeWorktree = (repo, worktreePath) => {
2429
2460
  if (!existsSync4(worktreePath)) return;
2430
2461
  try {
@@ -2494,6 +2525,8 @@ var runWorkerDaemon = async (cfg, client, signal) => {
2494
2525
  await client.sessions.materialize(sessionId, { agentSessionId, worktreePath }, cfg.apiToken);
2495
2526
  log(`materialized session #${sessionId} \u2192 ${worktreePath}`);
2496
2527
  }
2528
+ ensureExcluded(repo, PROJECT_CONFIG_NAME);
2529
+ saveProjectConfig(join5(worktreePath, PROJECT_CONFIG_NAME), worktreeConfig(cfg));
2497
2530
  spawnChild(sessionId, worktreePath);
2498
2531
  };
2499
2532
  const onStop = (sessionId) => {
@@ -2565,9 +2598,9 @@ var runWorkerDaemon = async (cfg, client, signal) => {
2565
2598
  };
2566
2599
  es.onerror = () => log("command stream error (eventsource will retry)");
2567
2600
  log(`listening for commands (server ${cfg.server}, repo ${repo})`);
2568
- await new Promise((resolve) => {
2569
- if (signal.aborted) return resolve();
2570
- signal.addEventListener("abort", () => resolve(), { once: true });
2601
+ await new Promise((resolve2) => {
2602
+ if (signal.aborted) return resolve2();
2603
+ signal.addEventListener("abort", () => resolve2(), { once: true });
2571
2604
  });
2572
2605
  es.close();
2573
2606
  clearInterval(hb);
@@ -2576,13 +2609,13 @@ var runWorkerDaemon = async (cfg, client, signal) => {
2576
2609
 
2577
2610
  // src/worker/machine-id.ts
2578
2611
  import { randomUUID as randomUUID2 } from "node:crypto";
2579
- import { existsSync as existsSync6, mkdirSync as mkdirSync2, readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from "node:fs";
2612
+ import { existsSync as existsSync6, mkdirSync as mkdirSync2, readFileSync as readFileSync4, writeFileSync as writeFileSync2 } from "node:fs";
2580
2613
  import { homedir as homedir3 } from "node:os";
2581
2614
  import { dirname as dirname3, join as join6 } from "node:path";
2582
2615
  var machineIdPath = (env = process.env) => join6(env.XDG_DATA_HOME ?? join6(env.HOME ?? homedir3(), ".local/share"), "baton", "machine-id");
2583
2616
  var readOrCreateMachineId = (path = machineIdPath()) => {
2584
2617
  if (existsSync6(path)) {
2585
- const s = readFileSync3(path, "utf8").trim();
2618
+ const s = readFileSync4(path, "utf8").trim();
2586
2619
  if (s) return s;
2587
2620
  }
2588
2621
  const id = randomUUID2();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lesscap/baton-cli",
3
- "version": "0.2.2",
3
+ "version": "0.2.3",
4
4
  "type": "module",
5
5
  "description": "baton CLI — register/run a worker, manage projects/requirements/tasks/sessions",
6
6
  "bin": {