kaizen-agent 1.22.1 → 1.23.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/cli/settings.ts CHANGED
@@ -117,6 +117,7 @@ const SETTINGS: Setting[] = [
117
117
  { key: "dictation.model", label: "Dictation model", group: "Board", values: ["tiny", "base", "small", "large-v3-turbo"], help: "Whisper model the web board dictates with, in the browser. tiny ~60 MB rough; base ~165 MB English-leaning; small ~410 MB accurate across languages; large-v3-turbo ~1.5 GB best, needs a capable GPU. Downloaded once on the next mic press" },
118
118
  { key: "sources.every", label: "Gather requests", group: "Board", values: ["off", "1h", "6h", "24h"], help: "How often the board checks your request sources for new ideas while it is running. off: only when you press Gather now" },
119
119
  { key: "board.projects_dir", label: "Projects folder", group: "Board", values: [], text: true, folder: true, help: `A folder your projects live in, e.g. ${process.platform === "win32" ? join(homedir(), "Projects") : "~/Projects"}. Every folder inside it appears in the board's project list, ready to choose; choosing one sets it up. Empty: only projects kaizen already knows` },
120
+ { key: "board.open", label: "Open the board in", group: "Board", values: ["browser", "app"], help: "browser: a tab in your default browser. app: a Chrome, Edge or Brave window with no tabs or address bar, like a desktop app; your default browser when none is installed" },
120
121
  { key: "ui.mouse", label: "Enable mouse", group: "Terminal", values: ["false", "true"], help: "Click to select, click again to act. While on, the terminal cannot select text with the mouse" },
121
122
  ];
122
123
 
package/cli/state.test.ts CHANGED
@@ -463,6 +463,26 @@ test("nativePath on Windows reads the spellings Git Bash writes into the project
463
463
  } finally { Object.defineProperty(process, "platform", platform); }
464
464
  });
465
465
 
466
+ test("findTerminal on macOS opens $TERMINAL as an app; iTerm through a script", () => {
467
+ const keys = ["PATH", "TERMINAL", "TMUX", "HERDR_ENV"];
468
+ const saved = keys.map((k) => process.env[k]);
469
+ const platform = Object.getOwnPropertyDescriptor(process, "platform")!;
470
+ try {
471
+ Object.defineProperty(process, "platform", { value: "darwin" });
472
+ process.env.PATH = mkdtempSync(join(tmpdir(), "kz-empty-"));
473
+ delete process.env.TMUX; delete process.env.HERDR_ENV;
474
+ process.env.TERMINAL = "Ghostty";
475
+ expect(findTerminal("/p", ["claude", "x"])!.cmd).toEqual(["open", "-na", "Ghostty", "--args", "--working-directory=/p", "-e", "claude", "x"]);
476
+ process.env.TERMINAL = "iTerm";
477
+ const i = findTerminal("/p", ["claude", "x"])!;
478
+ expect(i.cmd[2]).toContain('create window with default profile command');
479
+ expect(i.cmd.slice(4)).toEqual(["'/p'", "'claude' 'x'"]);
480
+ } finally {
481
+ Object.defineProperty(process, "platform", platform);
482
+ keys.forEach((k, i) => (saved[i] === undefined ? delete process.env[k] : (process.env[k] = saved[i])));
483
+ }
484
+ });
485
+
466
486
  test("findTerminal on Windows names cmd and PowerShell by full path, so a PATH without System32 still launches", () => {
467
487
  const keys = ["PATH", "ComSpec", "SystemRoot", "windir"];
468
488
  const saved = keys.map((k) => process.env[k]);
package/cli/state.ts CHANGED
@@ -210,13 +210,26 @@ if ($o -match '"pane_id":"([^"]*)"') { & ${q(herdr)} pane run $Matches[1] ${q(ty
210
210
  return { cmd: ["tmux", "new-window", "-c", cwd, ...fullCmd], detached: false };
211
211
  }
212
212
 
213
- if (process.env.TERMINAL && Bun.which(process.env.TERMINAL)) {
214
- const term = process.env.TERMINAL;
215
- if (term.includes("kitty")) return { cmd: [term, "--directory", cwd, ...fullCmd], detached: true };
216
- if (term.includes("alacritty")) return { cmd: [term, "--working-directory", cwd, "-e", ...fullCmd], detached: true };
217
- if (term.includes("ghostty")) return { cmd: [term, `--working-directory=${cwd}`, "-e", ...fullCmd], detached: true };
218
- if (term.includes("foot")) return { cmd: [term, "-D", cwd, ...fullCmd], detached: true };
219
- return { cmd: [term, "-e", ...fullCmd], detached: true };
213
+ // $TERMINAL: a command on PATH, or on macOS an app's name (TERMINAL=Ghostty, iTerm).
214
+ const term = process.env.TERMINAL;
215
+ if (term) {
216
+ const t = term.toLowerCase();
217
+ const args = t.includes("kitty") ? ["--directory", cwd, ...fullCmd]
218
+ : t.includes("alacritty") ? ["--working-directory", cwd, "-e", ...fullCmd]
219
+ : t.includes("ghostty") ? [`--working-directory=${cwd}`, "-e", ...fullCmd]
220
+ : t.includes("wezterm") ? ["start", "--cwd", cwd, "--", ...fullCmd]
221
+ : t.includes("foot") ? ["-D", cwd, ...fullCmd]
222
+ : ["-e", ...fullCmd];
223
+ if (process.platform !== "darwin") { if (Bun.which(term)) return { cmd: [term, ...args], detached: true }; }
224
+ else if (t.includes("iterm")) {
225
+ // iTerm takes a command line, not argv: the line goes into a one-shot script, as
226
+ // with Terminal below, and iTerm runs `sh <path>`.
227
+ const script = `f=$(mktemp) && printf 'rm -f "$0"\\n{ cd %s || (exit 127); } && %s\\n${hold}\\n' "$1" "$2" > "$f" && osascript -e "tell application \\"iTerm\\" to create window with default profile command \\"sh $f\\"" -e 'tell application "iTerm" to activate'`;
228
+ return { cmd: ["/bin/sh", "-c", script, "sh", shq(cwd), line], detached: true };
229
+ }
230
+ // On macOS a terminal is an app, started through open (Ghostty's binary cannot open
231
+ // a window itself). -n: a new instance, so the args reach it when it is already open.
232
+ else return { cmd: ["open", "-na", term, "--args", ...args], detached: true };
220
233
  }
221
234
 
222
235
  if (hasDisplay && Bun.which("xdg-terminal-exec")) {
package/cli/web.ts CHANGED
@@ -676,6 +676,11 @@ export async function web(repoDir: string, opts: Opts = {}) {
676
676
  // A profile folder gives the window a browser instance of its own (Kaizen Bot's app);
677
677
  // on macOS that takes -n, as `open` otherwise hands the args to a running browser.
678
678
  export async function openApp(url: string, profile?: string) {
679
+ // Kaizen Bot's window (a profile) is always an app; the board asks the setting.
680
+ if (!profile) {
681
+ const { listSettings } = await import("./settings.ts");
682
+ if (listSettings(join(import.meta.dir, "..")).rows.find((r) => r.key === "board.open")?.value !== "app") return openDefault(url);
683
+ }
679
684
  const args = [`--app=${url}`, ...(profile ? [`--user-data-dir=${profile}`] : [])];
680
685
  const chromes = process.platform === "darwin"
681
686
  ? ["Google Chrome", "Chromium", "Microsoft Edge", "Brave Browser"]
@@ -697,6 +702,10 @@ export async function openApp(url: string, profile?: string) {
697
702
  }
698
703
  }
699
704
  } catch { /* fall through to a plain tab */ }
705
+ return openDefault(url);
706
+ }
707
+
708
+ async function openDefault(url: string) {
700
709
  // `start` is a cmd builtin, not a program, so it has to go through cmd.
701
710
  if (process.platform === "win32") {
702
711
  try { await Bun.$`${cmdExe()} /c start "" ${url}`.quiet(); return true; } catch { return false; }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kaizen-agent",
3
- "version": "1.22.1",
3
+ "version": "1.23.0",
4
4
  "description": "Staged workflow for AI agents — code, writing, research, operations: plan, human approval, build, independent review, bounded fix loop.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -178,6 +178,9 @@ board:
178
178
  # not itself a project (no .git, no package.json) is read as a group, and the
179
179
  # folders inside it are offered too.
180
180
  projects_dir: ""
181
+ # Where `kaizen web` opens the board: browser (a tab in your default browser) or
182
+ # app (a Chrome, Edge or Brave window with no tabs or address bar).
183
+ open: browser
181
184
 
182
185
  sources:
183
186
  # How often the web board checks your request sources (links added in the board's