kaizen-agent 1.3.3 → 1.3.5

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/dashboard.ts CHANGED
@@ -79,6 +79,20 @@ function findTerminal(cwd: string, fullCmd: string[]): { cmd: string[]; detached
79
79
  const hasDisplay = Boolean(process.env.WAYLAND_DISPLAY || process.env.DISPLAY);
80
80
  const inTmux = Boolean(process.env.TMUX);
81
81
 
82
+ // Windows has no xdg anything, and none of the terminals below exist there.
83
+ // PowerShell is what a Windows user has open anyway: one -Command string,
84
+ // where a single quote is doubled rather than escaped. Windows Terminal hosts
85
+ // it when installed; otherwise `start` gives it a window of its own.
86
+ if (process.platform === "win32") {
87
+ const q = (a: string) => `'${a.replace(/'/g, "''")}'`;
88
+ const shell = Bun.which("pwsh.exe") ? "pwsh.exe" : "powershell.exe";
89
+ const ps = `Set-Location -LiteralPath ${q(cwd)}; & ${fullCmd.map(q).join(" ")}`;
90
+ if (Bun.which("wt.exe")) {
91
+ return { cmd: ["wt.exe", "-d", cwd, shell, "-NoExit", "-Command", ps], detached: true };
92
+ }
93
+ return { cmd: ["cmd.exe", "/c", "start", "kaizen", shell, "-NoExit", "-Command", ps], detached: true };
94
+ }
95
+
82
96
  if (process.env.TERMINAL && Bun.which(process.env.TERMINAL)) {
83
97
  const term = process.env.TERMINAL;
84
98
  if (term.includes("kitty")) return { cmd: [term, "--directory", cwd, ...fullCmd], detached: true };
@@ -209,15 +223,15 @@ export async function dashboard(
209
223
  ...(state ? [
210
224
  { key: "board", label: "Board", hint: "ideas and runs, by stage" },
211
225
  { key: "runs", label: "Runs", hint: "every run, and what each is waiting on" },
212
- { key: "backlog", label: "Backlog", hint: "what runs noticed and did not do" },
226
+ { key: "backlog", label: "Backlog", hint: "things to do later" },
213
227
  ] : []),
214
- { key: "settings", label: "Settings", hint: "modes, approvals, who builds" },
228
+ { key: "settings", label: "Settings", hint: "change how kaizen works" },
215
229
  // ~/.kaizen is the global state, not a project: there is nothing to set up
216
230
  // there, and it never holds the spec files a project copy does.
217
231
  ...(state && state !== join(home, ".kaizen") && !existsSync(join(state, "spec.md"))
218
- ? [{ key: "init", label: "Set up this project", hint: "write .kaizen/ here" }] : []),
219
- ...(!state ? [{ key: "init", label: "Set up this project", hint: "write .kaizen/ here" }] : []),
220
- { key: "upgrade", label: "Upgrade", hint: "pull, relink, clear the installer cache" },
232
+ ? [{ key: "init", label: "Set up this project", hint: "add kaizen to this folder" }] : []),
233
+ ...(!state ? [{ key: "init", label: "Set up this project", hint: "add kaizen to this folder" }] : []),
234
+ { key: "upgrade", label: "Upgrade", hint: "get the latest version" },
221
235
  { key: "quit", label: "Quit", hint: "" },
222
236
  ];
223
237
 
@@ -430,14 +444,18 @@ export async function dashboard(
430
444
  for (;;) {
431
445
  const here = from ?? state!;
432
446
  const runs = readRuns(here);
433
- const width = Math.max(...runs.map((r) => r.id.length));
447
+ const now = Date.now();
448
+ // The dot the board already uses, a four-letter word for anyone not reading
449
+ // colour, and the date dropped from the id: every run made the same day
450
+ // repeated it, which is ten columns saying nothing.
451
+ const width = Math.max(0, ...runs.map((r) => short(r.id).length));
434
452
  const rows = runs.map((r) => {
435
- const mark = r.stage === "done" ? c.dim("done ")
436
- : r.stage === "abandoned" ? c.dim("dropped")
437
- : r.awaiting ? c.amber("waiting") : c.cyan("running");
453
+ const { dot, status } = dotFor(r, now);
454
+ const word = { waiting: "wait", running: "run", stalled: "idle", done: "done", abandoned: "drop" }[status] ?? status;
455
+ const mark = status === "waiting" ? c.amber(word.padEnd(4)) : c.dim(word.padEnd(4));
438
456
  // The mark already says done or dropped; repeating the stage beside it is noise.
439
457
  const tail = r.stage === "done" || r.stage === "abandoned" ? "" : (r.awaiting ?? r.stage);
440
- return `${mark} ${r.id.padEnd(width)} ${c.dim(tail)}`;
458
+ return `${dot} ${mark} ${short(r.id).padEnd(width)} ${c.dim(tail)}`;
441
459
  });
442
460
  const { index } = await pick(from ? `Runs — ${label(from)}` : "Runs", rows);
443
461
  if (index === null) return;
@@ -637,7 +655,10 @@ export async function dashboard(
637
655
  stdout.write(`\n ${c.bold("Board")} ${c.dim(scope)}\n\n`);
638
656
  stdout.write(" " + cols.map(({ meta }, n) =>
639
657
  pad(n === col ? c.cyan(meta.title) : c.dim(meta.title), inner)).join(" ") + "\n");
640
- stdout.write(" " + cols.map(() => c.dim("─".repeat(inner))).join(" ") + "\n");
658
+ // Only the column you are in gets a solid rule; the rest fade to a dotted one,
659
+ // which says where the cursor is without painting a block over the card.
660
+ stdout.write(" " + cols.map((_, n) =>
661
+ n === col ? c.cyan("─".repeat(inner)) : c.dim("┈".repeat(inner))).join(" ") + "\n");
641
662
  const height = Math.max(4, (stdout.rows ?? 24) - 11);
642
663
  const deep = Math.max(0, ...cols.map((x) => x.lines.length));
643
664
  // The selected card drags the viewport with it; a cursor that can leave the
@@ -1011,10 +1032,14 @@ export async function dashboard(
1011
1032
  await report([
1012
1033
  c.bold("Could not open terminal"),
1013
1034
  "",
1014
- c.dim("No supported terminal emulator found (xdg-terminal-exec, kitty, ghostty, alacritty, tmux)."),
1035
+ c.dim(process.platform === "darwin"
1036
+ ? "Terminal.app could not be driven from here."
1037
+ : "No supported terminal emulator found (xdg-terminal-exec, kitty, ghostty, alacritty, tmux)."),
1015
1038
  "",
1016
1039
  "Run manually:",
1017
- ` ${c.cyan(`cd "${projectDir}" && ${agent.cmd} "${prompt}"`)}`,
1040
+ ` ${c.cyan(process.platform === "win32"
1041
+ ? `Set-Location '${projectDir}'; & ${agent.cmd} '${prompt.replace(/'/g, "''")}'`
1042
+ : `cd "${projectDir}" && ${agent.cmd} "${prompt}"`)}`,
1018
1043
  ]);
1019
1044
  return;
1020
1045
  }
package/cli/install.ts CHANGED
@@ -147,6 +147,20 @@ if (interactive && !upgrade && Bun.argv.length === 2) {
147
147
  }
148
148
 
149
149
  if (upgrade) await clearBunxCache();
150
+
151
+ // An install made without git is a copy, not a clone, so there is nothing to pull:
152
+ // upgrading itself would copy ~/kaizen over ~/kaizen and report "already up to
153
+ // date" forever. The new version comes from npm, and the installer inside it does
154
+ // the copying and relinking, so hand the job over and stop.
155
+ if (upgrade && !existsSync(join(dirname(import.meta.dir), ".git"))) {
156
+ const latest = await npmLatest();
157
+ const now = versionOf(dirname(import.meta.dir));
158
+ if (latest && latest !== now) {
159
+ step(`fetching kaizen ${latest} from npm`);
160
+ await Bun.$`${process.execPath} x kaizen-agent@${latest} --yes`.catch(() => {});
161
+ process.exit(0);
162
+ }
163
+ }
150
164
  if (!check && !upgrade) await welcome();
151
165
 
152
166
  async function welcome() {
@@ -448,17 +462,27 @@ if (!args.has("--project")) installLauncher();
448
462
  // type afterwards. A launcher pointed at the clone gives one, and keeps working
449
463
  // after the clone updates.
450
464
  function installLauncher() {
451
- const binDir = join(home, ".local", "bin");
452
- const bin = join(binDir, "kaizen");
453
- const script = `#!/bin/sh\n# kaizen launcher -- installed by kaizen-agent\nexec bun run ${join(repo, "cli/install.ts")} "$@"\n`;
465
+ const entry = join(repo, "cli/install.ts");
466
+ // Windows cannot run a shell script and has no ~/.local/bin on PATH. Bun's own
467
+ // bin directory is already there — bun put it there when it installed itself —
468
+ // so a .cmd beside bun is the launcher that works without touching PATH.
469
+ const win = process.platform === "win32";
470
+ const binDir = win
471
+ ? join(process.env.BUN_INSTALL ?? join(home, ".bun"), "bin")
472
+ : join(home, ".local", "bin");
473
+ const bin = join(binDir, win ? "kaizen.cmd" : "kaizen");
474
+ const script = win
475
+ ? `@echo off\r\nrem kaizen launcher -- installed by kaizen-agent\r\nbun run "${entry}" %*\r\n`
476
+ : `#!/bin/sh\n# kaizen launcher -- installed by kaizen-agent\nexec bun run ${entry} "$@"\n`;
454
477
  try {
455
478
  if (existsSync(bin) && readFileSync(bin, "utf8") === script) return;
456
479
  mkdirSync(binDir, { recursive: true });
457
480
  writeFileSync(bin, script, { mode: 0o755 });
458
- const onPath = (process.env.PATH ?? "").split(":").includes(binDir);
481
+ const onPath = (process.env.PATH ?? "").split(win ? ";" : ":")
482
+ .some((p) => p.replace(/[\\/]+$/, "").toLowerCase() === binDir.replace(/[\\/]+$/, "").toLowerCase());
459
483
  step(onPath
460
484
  ? `${c.cyan("kaizen upgrade")} is now on your PATH`
461
- : `wrote ${tilde(bin)} ${c.dim("— add ~/.local/bin to PATH to use it")}`);
485
+ : `wrote ${tilde(bin)} ${c.dim(`— add ${tilde(binDir)} to PATH to use it`)}`);
462
486
  } catch {
463
487
  /* a read-only home is not worth failing the install over */
464
488
  }
@@ -608,7 +632,9 @@ async function runUpgrade(repoDir: string) {
608
632
  .map((l) => c.dim(l.trim())),
609
633
  ];
610
634
  if (latest && latest !== after) {
611
- lines.push("", c.dim(`npm publishes ${latest}; this install follows the git clone.`));
635
+ lines.push("", c.dim(existsSync(join(repoDir, ".git"))
636
+ ? `npm publishes ${latest}; this install follows the git clone.`
637
+ : `npm publishes ${latest} — run: bunx kaizen-agent@${latest}`));
612
638
  }
613
639
  lines.push("", c.dim("Restart your agent to pick up new slash commands."));
614
640
  return lines;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kaizen-agent",
3
- "version": "1.3.3",
3
+ "version": "1.3.5",
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": {