kaizen-agent 1.3.10 → 1.4.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/README.md +11 -2
- package/cli/dashboard.ts +26 -3
- package/cli/install.ts +82 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -574,11 +574,20 @@ with a comment on each key, if you would rather edit the file.
|
|
|
574
574
|
| `--yes` | Take every default, ask nothing |
|
|
575
575
|
| `--verbose` | List every link instead of a one-line summary |
|
|
576
576
|
| `upgrade` | Pull, relink, and clear the installer cache. No prompts. |
|
|
577
|
+
| `uninstall` | Remove the links, the clone, and the launcher. Asks whether to keep your runs and settings. |
|
|
578
|
+
| `uninstall --purge` | The same, and delete `~/.kaizen` and this project's `.kaizen/` too. |
|
|
577
579
|
| `settings` | Open the settings browser for the nearest `.kaizen/`. Also `config`. |
|
|
578
580
|
|
|
579
|
-
|
|
581
|
+
Uninstalling works the same on Linux, macOS and Windows — `kaizen uninstall`, or
|
|
582
|
+
`bunx kaizen-agent uninstall` when the command itself is already gone. It offers three
|
|
583
|
+
answers — keep your runs and settings, remove everything, or cancel — and takes the
|
|
584
|
+
first without asking when the output is not a terminal. It lists what it removed; the `## Kaizen workflow` line in each project's `CLAUDE.md` is left for
|
|
585
|
+
you to delete, since you may have edited around it.
|
|
586
|
+
|
|
587
|
+
A global install also writes `~/.local/bin/kaizen` (`kaizen.cmd` in bun's bin
|
|
588
|
+
directory on Windows), a two-line launcher pointing at
|
|
580
589
|
the clone, so `kaizen upgrade` works from anywhere. It tells you if that directory is
|
|
581
|
-
not on your PATH.
|
|
590
|
+
not on your PATH. `uninstall` removes it; `--project` installs skip it.
|
|
582
591
|
|
|
583
592
|
Re-running is safe: correct links are left alone.
|
|
584
593
|
|
package/cli/dashboard.ts
CHANGED
|
@@ -440,8 +440,7 @@ export async function dashboard(
|
|
|
440
440
|
// Only searching under $HOME misses every project on another drive or mount,
|
|
441
441
|
// which on Windows is most of them. The folder the dashboard was opened in is
|
|
442
442
|
// searched too, and it is where the projects being worked on actually are.
|
|
443
|
-
const roots = [
|
|
444
|
-
.filter((d, i, all) => all.indexOf(d) === i);
|
|
443
|
+
const roots = searchRoots([dirname(state ?? process.cwd()), process.cwd()]);
|
|
445
444
|
const found = [...new Set(roots.flatMap((r) => findProjects(r)))];
|
|
446
445
|
for (const dir of found) remember(dir);
|
|
447
446
|
const added = knownProjects().length - before;
|
|
@@ -1289,6 +1288,21 @@ export async function dashboard(
|
|
|
1289
1288
|
// read and corrected in an editor; blanks and # lines ignored.
|
|
1290
1289
|
const REGISTRY = join(home, ".kaizen", "projects");
|
|
1291
1290
|
|
|
1291
|
+
// Windows spreads work across drives -- the profile on C:, the projects on D: or E: --
|
|
1292
|
+
// and a search that only knows $HOME finds none of them. Every letter that answers is
|
|
1293
|
+
// a root worth walking; elsewhere there is one filesystem and $HOME is where work
|
|
1294
|
+
// lives, so walking / would cost minutes to find nothing.
|
|
1295
|
+
export function searchRoots(extra: string[] = []): string[] {
|
|
1296
|
+
const roots = [home, ...extra];
|
|
1297
|
+
if (process.platform === "win32") {
|
|
1298
|
+
for (const letter of "ABCDEFGHIJKLMNOPQRSTUVWXYZ") {
|
|
1299
|
+
const drive = `${letter}:\\`;
|
|
1300
|
+
try { readdirSync(drive); roots.push(drive); } catch { /* no such drive */ }
|
|
1301
|
+
}
|
|
1302
|
+
}
|
|
1303
|
+
return roots.filter((d, i, all) => d && all.indexOf(d) === i);
|
|
1304
|
+
}
|
|
1305
|
+
|
|
1292
1306
|
export function knownProjects(): string[] {
|
|
1293
1307
|
if (!existsSync(REGISTRY)) return [];
|
|
1294
1308
|
return readFileSync(REGISTRY, "utf8").split("\n")
|
|
@@ -1306,10 +1320,16 @@ export function remember(projectDir: string) {
|
|
|
1306
1320
|
} catch { /* nothing here is worth an error */ }
|
|
1307
1321
|
}
|
|
1308
1322
|
|
|
1323
|
+
const SKIP = new Set([
|
|
1324
|
+
"windows", "program files", "program files (x86)", "programdata", "$recycle.bin",
|
|
1325
|
+
"system volume information", "appdata", "node_modules", "recovery", "perflogs",
|
|
1326
|
+
"library", "applications", "onedrive", "onedrivetemp",
|
|
1327
|
+
]);
|
|
1328
|
+
|
|
1309
1329
|
// Bounded on purpose. Depth is what keeps this from becoming a filesystem walk, and
|
|
1310
1330
|
// the skips are the directories that make one slow: node_modules, and anything dotted
|
|
1311
1331
|
// (which includes .git, and every state directory that is not a project of its own).
|
|
1312
|
-
function findProjects(root: string, depth = 4): string[] {
|
|
1332
|
+
export function findProjects(root: string, depth = 4): string[] {
|
|
1313
1333
|
const found: string[] = [];
|
|
1314
1334
|
const walk = (dir: string, left: number) => {
|
|
1315
1335
|
let entries: string[];
|
|
@@ -1322,6 +1342,9 @@ function findProjects(root: string, depth = 4): string[] {
|
|
|
1322
1342
|
if (left === 0) return;
|
|
1323
1343
|
for (const name of entries) {
|
|
1324
1344
|
if (name.startsWith(".") || name === "node_modules") continue;
|
|
1345
|
+
// Walking a whole drive means walking Windows itself otherwise: tens of
|
|
1346
|
+
// thousands of directories that have never held a project.
|
|
1347
|
+
if (SKIP.has(name.toLowerCase())) continue;
|
|
1325
1348
|
try { if (statSync(join(dir, name)).isDirectory()) walk(join(dir, name), left - 1); }
|
|
1326
1349
|
catch { /* unreadable or vanished mid-walk */ }
|
|
1327
1350
|
}
|
package/cli/install.ts
CHANGED
|
@@ -14,6 +14,7 @@ const args = new Set(Bun.argv.slice(2));
|
|
|
14
14
|
const upgrade = args.has("upgrade") || args.has("--upgrade");
|
|
15
15
|
const wantSettings = args.has("settings") || args.has("config");
|
|
16
16
|
const check = args.has("--check");
|
|
17
|
+
const uninstall = args.has("uninstall") || args.has("--uninstall");
|
|
17
18
|
const force = args.has("--force");
|
|
18
19
|
const verbose = args.has("--verbose");
|
|
19
20
|
const interactive = process.stdin.isTTY && !check && !upgrade && !args.has("--yes");
|
|
@@ -68,6 +69,87 @@ const onPath = new Set(
|
|
|
68
69
|
const found = (r: string) =>
|
|
69
70
|
AGENTS.filter((a) => existsSync(join(r, a.dir)) || onPath.has(a.dir));
|
|
70
71
|
|
|
72
|
+
if (uninstall) { await removeEverything(); process.exit(0); }
|
|
73
|
+
|
|
74
|
+
// Everything kaizen put on this machine, in one place, so uninstalling needs no
|
|
75
|
+
// clone to read: the links are known by name, the rest by path. State (~/.kaizen
|
|
76
|
+
// and any project's .kaizen) is left alone unless --purge says otherwise, because
|
|
77
|
+
// it holds runs and decisions this program did not write.
|
|
78
|
+
async function removeEverything() {
|
|
79
|
+
let purge = args.has("--purge") || args.has("--all");
|
|
80
|
+
// Deleting someone's runs is not a thing to do on a typo, so ask unless the
|
|
81
|
+
// answer already arrived as a flag.
|
|
82
|
+
if (interactive && !purge) {
|
|
83
|
+
const choice = await select("Uninstall kaizen", [
|
|
84
|
+
{ label: "Keep my runs and settings", hint: "removes the app, keeps ~/.kaizen and each project's .kaizen/", value: "keep" },
|
|
85
|
+
{ label: "Remove everything", hint: "also deletes runs, backlog, memory and settings", value: "all" },
|
|
86
|
+
{ label: "Cancel", hint: "change nothing", value: "cancel" },
|
|
87
|
+
]);
|
|
88
|
+
if (choice === "cancel") { console.log(`\n ${c.dim("Nothing removed.")}\n`); return; }
|
|
89
|
+
purge = choice === "all";
|
|
90
|
+
}
|
|
91
|
+
const gone: string[] = [];
|
|
92
|
+
const stuck: string[] = [];
|
|
93
|
+
const drop = (p: string) => {
|
|
94
|
+
if (!existsSync(p) && !isLink(p)) return;
|
|
95
|
+
// Windows refuses to delete a file another process holds open, and a silent
|
|
96
|
+
// failure here would read as a clean uninstall that removed nothing.
|
|
97
|
+
try { rmSync(p, { recursive: true, force: true }); gone.push(tilde(p)); }
|
|
98
|
+
catch { stuck.push(tilde(p)); }
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
// A --project install puts links in the project folder, and on Windows those
|
|
102
|
+
// projects live on other drives entirely. Every project kaizen knows about is
|
|
103
|
+
// checked, plus whatever a drive-wide scan turns up.
|
|
104
|
+
const { knownProjects, searchRoots, findProjects } = await import("./dashboard.ts");
|
|
105
|
+
step("looking for project installs");
|
|
106
|
+
const roots = [home, process.cwd(), ...knownProjects(),
|
|
107
|
+
...searchRoots([process.cwd()]).flatMap((r) => findProjects(r))]
|
|
108
|
+
.filter((d, i, all) => all.indexOf(d) === i);
|
|
109
|
+
|
|
110
|
+
for (const root of roots) {
|
|
111
|
+
for (const a of AGENTS) {
|
|
112
|
+
for (const n of ["kaizen", "kaizen-help"]) drop(join(root, a.dir, "skills", n));
|
|
113
|
+
for (const sub of ["agents", "commands"]) {
|
|
114
|
+
const dir = join(root, a.dir, sub);
|
|
115
|
+
try {
|
|
116
|
+
for (const f of readdirSync(dir)) {
|
|
117
|
+
if (f.startsWith("kaizen-") && f.endsWith(".md")) drop(join(dir, f));
|
|
118
|
+
}
|
|
119
|
+
} catch { /* no such directory */ }
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
drop(process.env.KAIZEN_HOME ?? join(home, "kaizen"));
|
|
125
|
+
drop(join(process.env.BUN_INSTALL ?? join(home, ".bun"), "bin", "kaizen.cmd"));
|
|
126
|
+
drop(join(home, ".local", "bin", "kaizen"));
|
|
127
|
+
await clearBunxCache();
|
|
128
|
+
|
|
129
|
+
if (purge) {
|
|
130
|
+
drop(join(home, ".kaizen"));
|
|
131
|
+
drop(join(process.cwd(), ".kaizen"));
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
console.log(`\n ${c.bold("Uninstalled")}`);
|
|
135
|
+
for (const p of gone) console.log(` ${c.dim("removed " + p)}`);
|
|
136
|
+
if (!gone.length && !stuck.length) console.log(` ${c.dim("nothing left to remove")}`);
|
|
137
|
+
if (stuck.length) {
|
|
138
|
+
console.log(`\n ${c.bold("could not remove")} ${c.dim("— close any dashboard or agent still using these, then run it again")}`);
|
|
139
|
+
for (const p of stuck) console.log(` ${c.dim(p)}`);
|
|
140
|
+
}
|
|
141
|
+
console.log(purge
|
|
142
|
+
? `\n ${c.dim("Runs and settings deleted too.")}\n`
|
|
143
|
+
: `\n ${c.dim("Runs and settings kept in")} ${c.cyan(tilde(join(home, ".kaizen")))}${c.dim(" — add --purge to delete them.")}\n`);
|
|
144
|
+
console.log(` ${c.dim("The CLAUDE.md / AGENTS.md line in each project is left for you to remove.")}\n`);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// A dangling symlink fails existsSync, and a stale link is exactly what wants
|
|
148
|
+
// removing here.
|
|
149
|
+
function isLink(p: string) {
|
|
150
|
+
try { return lstatSync(p).isSymbolicLink(); } catch { return false; }
|
|
151
|
+
}
|
|
152
|
+
|
|
71
153
|
// Nothing to install into. kaizen is a workflow an agent runs; on its own it does
|
|
72
154
|
// nothing, so send them to the agent first rather than laying out files no reader
|
|
73
155
|
// will ever load.
|
package/package.json
CHANGED