@os-eco/overstory-cli 0.6.6 → 0.6.7

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@os-eco/overstory-cli",
3
- "version": "0.6.6",
3
+ "version": "0.6.7",
4
4
  "description": "Multi-agent orchestration for Claude Code — spawn worker agents in git worktrees via tmux, coordinate through SQLite mail, merge with tiered conflict resolution",
5
5
  "author": "Jaymin West",
6
6
  "license": "MIT",
@@ -282,7 +282,7 @@ async function startCoordinator(
282
282
 
283
283
  if (isRunningAsRoot()) {
284
284
  throw new AgentError(
285
- "Cannot spawn agents as root (UID 0). The claude CLI rejects --dangerously-skip-permissions when run as root, causing the tmux session to die immediately. Run overstory as a non-root user.",
285
+ "Cannot spawn agents as root (UID 0). The claude CLI rejects --permission-mode bypassPermissions when run as root, causing the tmux session to die immediately. Run overstory as a non-root user.",
286
286
  );
287
287
  }
288
288
 
@@ -357,7 +357,7 @@ async function startCoordinator(
357
357
  // (overstory-gaio, overstory-0kwf).
358
358
  const agentDefPath = join(projectRoot, ".overstory", "agent-defs", "coordinator.md");
359
359
  const agentDefFile = Bun.file(agentDefPath);
360
- let claudeCmd = `claude --model ${model} --dangerously-skip-permissions`;
360
+ let claudeCmd = `claude --model ${model} --permission-mode bypassPermissions`;
361
361
  if (await agentDefFile.exists()) {
362
362
  const agentDef = await agentDefFile.text();
363
363
  // Single-quote the content for safe shell expansion (only escape single quotes)
@@ -417,7 +417,7 @@ async function startCoordinator(
417
417
  await tmux.sendKeys(tmuxSession, beacon);
418
418
 
419
419
  // Follow-up Enters with increasing delays to ensure submission
420
- for (const delay of [1_000, 2_000]) {
420
+ for (const delay of [1_000, 2_000, 3_000, 5_000]) {
421
421
  await Bun.sleep(delay);
422
422
  await tmux.sendKeys(tmuxSession, "");
423
423
  }
@@ -67,7 +67,7 @@ async function startMonitor(opts: { json: boolean; attach: boolean }): Promise<v
67
67
 
68
68
  if (isRunningAsRoot()) {
69
69
  throw new AgentError(
70
- "Cannot spawn agents as root (UID 0). The claude CLI rejects --dangerously-skip-permissions when run as root, causing the tmux session to die immediately. Run overstory as a non-root user.",
70
+ "Cannot spawn agents as root (UID 0). The claude CLI rejects --permission-mode bypassPermissions when run as root, causing the tmux session to die immediately. Run overstory as a non-root user.",
71
71
  );
72
72
  }
73
73
 
@@ -137,7 +137,7 @@ async function startMonitor(opts: { json: boolean; attach: boolean }): Promise<v
137
137
  // Spawn tmux session at project root with Claude Code (interactive mode).
138
138
  const agentDefPath = join(projectRoot, ".overstory", "agent-defs", "monitor.md");
139
139
  const agentDefFile = Bun.file(agentDefPath);
140
- let claudeCmd = `claude --model ${model} --dangerously-skip-permissions`;
140
+ let claudeCmd = `claude --model ${model} --permission-mode bypassPermissions`;
141
141
  if (await agentDefFile.exists()) {
142
142
  const agentDef = await agentDefFile.text();
143
143
  const escaped = agentDef.replace(/'/g, "'\\''");
@@ -308,7 +308,7 @@ export async function slingCommand(taskId: string, opts: SlingOptions): Promise<
308
308
 
309
309
  if (isRunningAsRoot()) {
310
310
  throw new AgentError(
311
- "Cannot spawn agents as root (UID 0). The claude CLI rejects --dangerously-skip-permissions when run as root, causing the tmux session to die immediately. Run overstory as a non-root user.",
311
+ "Cannot spawn agents as root (UID 0). The claude CLI rejects --permission-mode bypassPermissions when run as root, causing the tmux session to die immediately. Run overstory as a non-root user.",
312
312
  { agentName: name },
313
313
  );
314
314
  }
@@ -614,7 +614,7 @@ export async function slingCommand(taskId: string, opts: SlingOptions): Promise<
614
614
  // 12. Create tmux session running claude in interactive mode
615
615
  const tmuxSessionName = `overstory-${config.project.name}-${name}`;
616
616
  const { model, env } = resolveModel(config, manifest, capability, agentDef.model);
617
- const claudeCmd = `claude --model ${model} --dangerously-skip-permissions`;
617
+ const claudeCmd = `claude --model ${model} --permission-mode bypassPermissions`;
618
618
  const pid = await createSession(tmuxSessionName, worktreePath, claudeCmd, {
619
619
  ...env,
620
620
  OVERSTORY_AGENT_NAME: name,
@@ -673,7 +673,7 @@ export async function slingCommand(taskId: string, opts: SlingOptions): Promise<
673
673
  // 13c. Follow-up Enters with increasing delays to ensure submission.
674
674
  // Claude Code's TUI may consume early Enters during late initialization
675
675
  // (overstory-yhv6). An Enter on an empty input line is harmless.
676
- for (const delay of [1_000, 2_000]) {
676
+ for (const delay of [1_000, 2_000, 3_000, 5_000]) {
677
677
  await Bun.sleep(delay);
678
678
  await sendKeys(tmuxSessionName, "");
679
679
  }
@@ -9,6 +9,7 @@ import { join } from "node:path";
9
9
  import { Command } from "commander";
10
10
  import { loadConfig } from "../config.ts";
11
11
  import { ValidationError } from "../errors.ts";
12
+ import { color } from "../logging/color.ts";
12
13
  import { createMailStore } from "../mail/store.ts";
13
14
  import { createMergeQueue } from "../merge/queue.ts";
14
15
  import { createMetricsStore } from "../metrics/store.ts";
@@ -254,15 +255,15 @@ export function printStatus(data: StatusData): void {
254
255
  const now = Date.now();
255
256
  const w = process.stdout.write.bind(process.stdout);
256
257
 
257
- w("📊 Overstory Status\n");
258
+ w("Overstory Status\n");
258
259
  w(`${"═".repeat(60)}\n\n`);
259
260
  if (data.currentRunId) {
260
- w(`🏃 Run: ${data.currentRunId}\n`);
261
+ w(`Run: ${data.currentRunId}\n`);
261
262
  }
262
263
 
263
264
  // Active agents
264
265
  const active = data.agents.filter((a) => a.state !== "zombie" && a.state !== "completed");
265
- w(`🤖 Agents: ${active.length} active\n`);
266
+ w(`Agents: ${active.length} active\n`);
266
267
  if (active.length > 0) {
267
268
  const tmuxSessionNames = new Set(data.tmuxSessions.map((s) => s.name));
268
269
  for (const agent of active) {
@@ -272,7 +273,7 @@ export function printStatus(data: StatusData): void {
272
273
  : now;
273
274
  const duration = formatDuration(endTime - new Date(agent.startedAt).getTime());
274
275
  const tmuxAlive = tmuxSessionNames.has(agent.tmuxSession);
275
- const aliveMarker = tmuxAlive ? "" : "";
276
+ const aliveMarker = tmuxAlive ? color.green(">") : color.red("x");
276
277
  w(` ${aliveMarker} ${agent.agentName} [${agent.capability}] `);
277
278
  w(`${agent.state} | ${agent.taskId} | ${duration}\n`);
278
279
 
@@ -291,7 +292,7 @@ export function printStatus(data: StatusData): void {
291
292
 
292
293
  // Worktrees
293
294
  const overstoryWts = data.worktrees.filter((wt) => wt.branch.startsWith("overstory/"));
294
- w(`🌳 Worktrees: ${overstoryWts.length}\n`);
295
+ w(`Worktrees: ${overstoryWts.length}\n`);
295
296
  for (const wt of overstoryWts) {
296
297
  w(` ${wt.branch}\n`);
297
298
  }
@@ -301,13 +302,13 @@ export function printStatus(data: StatusData): void {
301
302
  w("\n");
302
303
 
303
304
  // Mail
304
- w(`📬 Mail: ${data.unreadMailCount} unread\n`);
305
+ w(`Mail: ${data.unreadMailCount} unread\n`);
305
306
 
306
307
  // Merge queue
307
- w(`🔀 Merge queue: ${data.mergeQueueCount} pending\n`);
308
+ w(`Merge queue: ${data.mergeQueueCount} pending\n`);
308
309
 
309
310
  // Metrics
310
- w(`📈 Sessions recorded: ${data.recentMetricsCount}\n`);
311
+ w(`Sessions recorded: ${data.recentMetricsCount}\n`);
311
312
  }
312
313
 
313
314
  interface StatusOpts {
@@ -347,7 +348,9 @@ async function executeStatus(opts: StatusOpts): Promise<void> {
347
348
  }
348
349
 
349
350
  if (watch) {
350
- process.stderr.write("⚠️ --watch is deprecated. Use 'ov dashboard' for live monitoring.\n\n");
351
+ process.stderr.write(
352
+ "Warning: --watch is deprecated. Use 'ov dashboard' for live monitoring.\n\n",
353
+ );
351
354
  // Polling loop (kept for one release cycle)
352
355
  while (true) {
353
356
  // Clear screen
@@ -92,7 +92,7 @@ async function startSupervisor(opts: {
92
92
 
93
93
  if (isRunningAsRoot()) {
94
94
  throw new AgentError(
95
- "Cannot spawn agents as root (UID 0). The claude CLI rejects --dangerously-skip-permissions when run as root, causing the tmux session to die immediately. Run overstory as a non-root user.",
95
+ "Cannot spawn agents as root (UID 0). The claude CLI rejects --permission-mode bypassPermissions when run as root, causing the tmux session to die immediately. Run overstory as a non-root user.",
96
96
  );
97
97
  }
98
98
 
@@ -165,7 +165,7 @@ async function startSupervisor(opts: {
165
165
  const tmuxSession = `overstory-${config.project.name}-supervisor-${opts.name}`;
166
166
  const agentDefPath = join(projectRoot, ".overstory", "agent-defs", "supervisor.md");
167
167
  const agentDefFile = Bun.file(agentDefPath);
168
- let claudeCmd = `claude --model ${model} --dangerously-skip-permissions`;
168
+ let claudeCmd = `claude --model ${model} --permission-mode bypassPermissions`;
169
169
  if (await agentDefFile.exists()) {
170
170
  const agentDef = await agentDefFile.text();
171
171
  const escaped = agentDef.replace(/'/g, "'\\''");
@@ -190,7 +190,7 @@ async function startSupervisor(opts: {
190
190
  await sendKeys(tmuxSession, beacon);
191
191
 
192
192
  // Follow-up Enters with increasing delays to ensure submission
193
- for (const delay of [1_000, 2_000]) {
193
+ for (const delay of [1_000, 2_000, 3_000, 5_000]) {
194
194
  await Bun.sleep(delay);
195
195
  await sendKeys(tmuxSession, "");
196
196
  }
package/src/index.ts CHANGED
@@ -42,7 +42,7 @@ import { createWorktreeCommand } from "./commands/worktree.ts";
42
42
  import { OverstoryError, WorktreeError } from "./errors.ts";
43
43
  import { setQuiet } from "./logging/color.ts";
44
44
 
45
- const VERSION = "0.6.6";
45
+ const VERSION = "0.6.7";
46
46
 
47
47
  const COMMANDS = [
48
48
  "agents",
@@ -957,7 +957,7 @@ describe("waitForTuiReady", () => {
957
957
  });
958
958
 
959
959
  test("returns true immediately when pane has content on first poll", async () => {
960
- spawnSpy.mockImplementation(() => mockSpawnResult("Claude Code ready", "", 0));
960
+ spawnSpy.mockImplementation(() => mockSpawnResult('Try "help" to get started', "", 0));
961
961
 
962
962
  const ready = await waitForTuiReady("overstory-agent", 5_000, 500);
963
963
 
@@ -977,7 +977,7 @@ describe("waitForTuiReady", () => {
977
977
  return mockSpawnResult("", "", 0);
978
978
  }
979
979
  // 4th poll: content appears
980
- return mockSpawnResult("Welcome to Claude Code!", "", 0);
980
+ return mockSpawnResult("Welcome to Claude Code!\n\n\u276f", "", 0);
981
981
  }
982
982
  // has-session: session is alive throughout
983
983
  return mockSpawnResult("", "", 0);
@@ -1011,7 +1011,7 @@ describe("waitForTuiReady", () => {
1011
1011
 
1012
1012
  test("uses default timeout and poll interval", async () => {
1013
1013
  // Return content immediately
1014
- spawnSpy.mockImplementation(() => mockSpawnResult("ready", "", 0));
1014
+ spawnSpy.mockImplementation(() => mockSpawnResult('Try "help"', "", 0));
1015
1015
 
1016
1016
  const ready = await waitForTuiReady("overstory-agent");
1017
1017
 
@@ -447,13 +447,13 @@ export async function capturePaneContent(name: string, lines = 50): Promise<stri
447
447
  */
448
448
  export async function waitForTuiReady(
449
449
  name: string,
450
- timeoutMs = 15_000,
450
+ timeoutMs = 30_000,
451
451
  pollIntervalMs = 500,
452
452
  ): Promise<boolean> {
453
453
  const maxAttempts = Math.ceil(timeoutMs / pollIntervalMs);
454
454
  for (let i = 0; i < maxAttempts; i++) {
455
455
  const content = await capturePaneContent(name);
456
- if (content !== null) {
456
+ if (content !== null && (content.includes("\u276f") || content.includes('Try "'))) {
457
457
  return true;
458
458
  }
459
459
  // Check if session died — no point waiting if it's gone