@halfwhey/claudraband 0.5.0 → 0.6.1

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 CHANGED
@@ -17,8 +17,8 @@ Claude Code for the power user
17
17
 
18
18
  It provides:
19
19
 
20
- - Resumable non-interactive workflows. Essentially claude -p with session support: cband continue <session-id> 'what was the result of the research?'
21
- - An http daemon for remote or headless session control
20
+ - Resumable non-interactive workflows. Essentially `claude -p` with session support: `cband continue <session-id> 'what was the result of the research?'`
21
+ - An HTTP daemon for remote or headless session control
22
22
  - An ACP server for editor and alternate frontend integration
23
23
  - A TypeScript library for building these workflows into your own tools
24
24
 
@@ -73,7 +73,7 @@ cband continue <session-id> --select 2
73
73
 
74
74
  The daemon defaults to using `tmux` as the terminal runtime, just like the local path. Use `--connect` only when creating a new daemon-backed session; after that, tracked `continue`, `attach`, and `sessions` route through the recorded live owner automatically.
75
75
 
76
- ## Experimental Xterm Backend
76
+ ## Experimental xterm.js Backend
77
77
 
78
78
  `--backend xterm` exists for local or daemon use, but it is experimental and slower than `tmux`. Use it when you need a headless fallback, not as the default path for long-lived interactive work. See [docs/cli.md](docs/cli.md) for current caveats and backend behavior.
79
79
 
package/dist/bin.js CHANGED
@@ -397,6 +397,9 @@ function hasSession(name) {
397
397
  function hasPane(id) {
398
398
  return spawnSync2("bash", ["-lc", shellCommand("tmux", "display-message", "-p", "-t", id, "#{pane_id}")], { stdio: "pipe" }).status === 0;
399
399
  }
400
+ function windowTarget(sessionName, windowName) {
401
+ return `${sessionName}:${windowName}`;
402
+ }
400
403
  async function killSession(name) {
401
404
  if (!hasSession(name))
402
405
  return;
@@ -443,15 +446,12 @@ class Session {
443
446
  throw new Error("tmuxctl: command is required");
444
447
  }
445
448
  const format = "#{window_id}\t#{pane_id}";
446
- const result = hasSession(name) ? await tmux("new-window", "-P", "-F", format, "-t", name, "-n", windowName, ...workingDir ? ["-c", workingDir] : [], ...command) : await tmux("new-session", "-d", "-P", "-F", format, "-s", name, "-n", windowName, "-x", String(width), "-y", String(height), ...workingDir ? ["-c", workingDir] : [], ...command);
449
+ const target = windowTarget(name, windowName);
450
+ const result = hasSession(name) ? await tmux("new-window", "-P", "-F", format, "-t", name, "-n", windowName, ...workingDir ? ["-c", workingDir] : [], ...command, ";", "set-option", "-q", "-t", name, "destroy-unattached", "off", ";", "set-option", "-q", "-t", name, "status", "off", ";", "resize-window", "-t", target, "-x", String(width), "-y", String(height)) : await tmux("new-session", "-d", "-P", "-F", format, "-s", name, "-n", windowName, "-x", String(width), "-y", String(height), ...workingDir ? ["-c", workingDir] : [], ...command, ";", "set-option", "-q", "-t", name, "destroy-unattached", "off", ";", "set-option", "-q", "-t", name, "status", "off", ";", "resize-window", "-t", target, "-x", String(width), "-y", String(height));
447
451
  const [windowId, paneId] = result.stdout.trim().split(/\s+/, 2);
448
452
  if (!windowId || !paneId) {
449
453
  throw new Error(`tmuxctl: failed to parse tmux target ids: ${result.stdout}`);
450
454
  }
451
- try {
452
- await tmux("set-option", "-t", name, "status", "off");
453
- } catch {}
454
- await tmux("resize-window", "-t", windowId, "-x", String(width), "-y", String(height));
455
455
  return new Session(name, command, windowId, paneId);
456
456
  }
457
457
  static async find(sessionName, windowName) {
@@ -5285,6 +5285,13 @@ function resolveClaudeExecutable(explicitPath) {
5285
5285
  throw new Error(`claudraband could not resolve bundled Claude Code @anthropic-ai/claude-code@2.1.96 (${reason}). ` + `Install dependencies or set ${ENV_OVERRIDE} or ClaudrabandOptions.claudeExecutable.`);
5286
5286
  }
5287
5287
  }
5288
+ function resolveClaudeLaunchCommand(explicitPath) {
5289
+ const executable = resolveClaudeExecutable(explicitPath);
5290
+ if (executable.endsWith(".js")) {
5291
+ return [process.execPath, executable];
5292
+ }
5293
+ return [executable];
5294
+ }
5288
5295
  var require2, ENV_OVERRIDE = "CLAUDRABAND_CLAUDE_PATH";
5289
5296
  var init_resolve = __esm(() => {
5290
5297
  require2 = createRequire2(import.meta.url);
@@ -5392,7 +5399,7 @@ class ClaudeWrapper {
5392
5399
  }
5393
5400
  buildCmd(...extra) {
5394
5401
  const cmd = [
5395
- resolveClaudeExecutable(this.cfg.claudeExecutable),
5402
+ ...resolveClaudeLaunchCommand(this.cfg.claudeExecutable),
5396
5403
  "--model",
5397
5404
  this.cfg.model,
5398
5405
  ...this.cfg.claudeArgs
@@ -2,7 +2,7 @@ export { ClaudeWrapper } from "./claude";
2
2
  export { sessionPath } from "./claude";
3
3
  export { parseClaudeArgs } from "./claude";
4
4
  export type { ClaudeConfig } from "./claude";
5
- export { resolveClaudeExecutable } from "./resolve";
5
+ export { resolveClaudeExecutable, resolveClaudeLaunchCommand } from "./resolve";
6
6
  export { Tailer, parseLineEvents } from "./parser";
7
7
  export { hasPendingNativePrompt, hasPendingQuestion, parseNativePermissionPrompt, } from "./inspect";
8
8
  export { createTerminalHost, hasTmuxBinary, resolveTerminalBackend } from "../terminal";
@@ -1,4 +1,5 @@
1
1
  export declare function resolveClaudeExecutable(explicitPath?: string): string;
2
+ export declare function resolveClaudeLaunchCommand(explicitPath?: string): string[];
2
3
  export declare const __test: {
3
4
  ENV_OVERRIDE: string;
4
5
  };
package/dist/index.js CHANGED
@@ -128,6 +128,9 @@ function hasSession(name) {
128
128
  function hasPane(id) {
129
129
  return spawnSync("bash", ["-lc", shellCommand("tmux", "display-message", "-p", "-t", id, "#{pane_id}")], { stdio: "pipe" }).status === 0;
130
130
  }
131
+ function windowTarget(sessionName, windowName) {
132
+ return `${sessionName}:${windowName}`;
133
+ }
131
134
  async function killSession(name) {
132
135
  if (!hasSession(name))
133
136
  return;
@@ -174,15 +177,12 @@ class Session {
174
177
  throw new Error("tmuxctl: command is required");
175
178
  }
176
179
  const format = "#{window_id}\t#{pane_id}";
177
- const result = hasSession(name) ? await tmux("new-window", "-P", "-F", format, "-t", name, "-n", windowName, ...workingDir ? ["-c", workingDir] : [], ...command) : await tmux("new-session", "-d", "-P", "-F", format, "-s", name, "-n", windowName, "-x", String(width), "-y", String(height), ...workingDir ? ["-c", workingDir] : [], ...command);
180
+ const target = windowTarget(name, windowName);
181
+ const result = hasSession(name) ? await tmux("new-window", "-P", "-F", format, "-t", name, "-n", windowName, ...workingDir ? ["-c", workingDir] : [], ...command, ";", "set-option", "-q", "-t", name, "destroy-unattached", "off", ";", "set-option", "-q", "-t", name, "status", "off", ";", "resize-window", "-t", target, "-x", String(width), "-y", String(height)) : await tmux("new-session", "-d", "-P", "-F", format, "-s", name, "-n", windowName, "-x", String(width), "-y", String(height), ...workingDir ? ["-c", workingDir] : [], ...command, ";", "set-option", "-q", "-t", name, "destroy-unattached", "off", ";", "set-option", "-q", "-t", name, "status", "off", ";", "resize-window", "-t", target, "-x", String(width), "-y", String(height));
178
182
  const [windowId, paneId] = result.stdout.trim().split(/\s+/, 2);
179
183
  if (!windowId || !paneId) {
180
184
  throw new Error(`tmuxctl: failed to parse tmux target ids: ${result.stdout}`);
181
185
  }
182
- try {
183
- await tmux("set-option", "-t", name, "status", "off");
184
- } catch {}
185
- await tmux("resize-window", "-t", windowId, "-x", String(width), "-y", String(height));
186
186
  return new Session(name, command, windowId, paneId);
187
187
  }
188
188
  static async find(sessionName, windowName) {
@@ -5305,6 +5305,13 @@ function resolveClaudeExecutable(explicitPath) {
5305
5305
  throw new Error(`claudraband could not resolve bundled Claude Code @anthropic-ai/claude-code@2.1.96 (${reason}). ` + `Install dependencies or set ${ENV_OVERRIDE} or ClaudrabandOptions.claudeExecutable.`);
5306
5306
  }
5307
5307
  }
5308
+ function resolveClaudeLaunchCommand(explicitPath) {
5309
+ const executable = resolveClaudeExecutable(explicitPath);
5310
+ if (executable.endsWith(".js")) {
5311
+ return [process.execPath, executable];
5312
+ }
5313
+ return [executable];
5314
+ }
5308
5315
 
5309
5316
  // ../claudraband-core/src/claude/claude.ts
5310
5317
  function sessionPath(cwd, sessionID) {
@@ -5404,7 +5411,7 @@ class ClaudeWrapper {
5404
5411
  }
5405
5412
  buildCmd(...extra) {
5406
5413
  const cmd = [
5407
- resolveClaudeExecutable(this.cfg.claudeExecutable),
5414
+ ...resolveClaudeLaunchCommand(this.cfg.claudeExecutable),
5408
5415
  "--model",
5409
5416
  this.cfg.model,
5410
5417
  ...this.cfg.claudeArgs
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@halfwhey/claudraband",
3
- "version": "0.5.0",
3
+ "version": "0.6.1",
4
4
  "description": "Control the official Claude Code CLI programmatically. Use as a library, a direct CLI, an ACP server, or a persistent session daemon.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",