bankai-cli 0.2.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.
Files changed (3) hide show
  1. package/README.md +78 -0
  2. package/dist/main.js +707 -0
  3. package/package.json +31 -0
package/README.md ADDED
@@ -0,0 +1,78 @@
1
+ # bankai
2
+
3
+ CLI tool that prints approval-bypass startup commands for coding agent CLIs.
4
+
5
+ bankai does **not** execute the agents — it only outputs the commands for copy-paste.
6
+
7
+ ## Install
8
+
9
+ ```bash
10
+ bun install -g bankai-cli
11
+ ```
12
+
13
+ ## Usage
14
+
15
+ ```bash
16
+ # Print bypass command for a specific agent
17
+ bankai claude
18
+
19
+ # Interactive agent picker
20
+ bankai
21
+
22
+ # List all supported agents
23
+ bankai agents
24
+
25
+ # List only agents installed on your system
26
+ bankai agents --installed
27
+ ```
28
+
29
+ ## Supported Agents
30
+
31
+ ### CLI Agents (flag output)
32
+
33
+ | Agent | Command | Docs |
34
+ |-------|---------|------|
35
+ | Claude Code | `claude --dangerously-skip-permissions` | [Settings - Claude Code Docs](https://code.claude.com/docs/en/settings) |
36
+ | Codex CLI | `codex --dangerously-bypass-approvals-and-sandbox` | [CLI Reference - OpenAI Codex](https://developers.openai.com/codex/cli/reference/) |
37
+ | GitHub Copilot CLI | `copilot --allow-all-tools` | [Copilot CLI Docs](https://docs.github.com/en/copilot) |
38
+ | Gemini CLI | `gemini --yolo` | [Configuration - Gemini CLI](https://github.com/google-gemini/gemini-cli/blob/main/docs/get-started/configuration.md) |
39
+ | OpenHands | `openhands --always-approve` | [CLI Mode - OpenHands Docs](https://docs.openhands.dev/openhands/usage/run-openhands/cli-mode) |
40
+ | Aider | `aider --yes-always` | [Options Reference - aider](https://aider.chat/docs/config/options.html) |
41
+ | Qwen Code | `qwen-code --yolo` | [Approval Mode - Qwen Code Docs](https://qwenlm.github.io/qwen-code-docs/en/users/features/approval-mode/) |
42
+ | Kimi Code | `kimi --yolo` | [Interaction Guide - Kimi Code Docs](https://www.kimi.com/code/docs/en/kimi-cli/guides/interaction.html) |
43
+
44
+ ### Settings Agents (config file / DB modification)
45
+
46
+ | Agent | Target | Description |
47
+ |-------|--------|-------------|
48
+ | Cursor Agent CLI | `.cursor/cli.json` / `~/.cursor/cli-config.json` | Writes permission allow-list for Cursor Agent CLI |
49
+ | Cursor IDE | SQLite DB (`state.vscdb`) | Enables Auto-Run Mode, disables protections (requires restart) |
50
+
51
+ ## Custom Agents
52
+
53
+ Register agents not in the built-in list:
54
+
55
+ ```bash
56
+ # Non-interactive
57
+ bankai add --cmd opencode --line "opencode --yolo"
58
+
59
+ # Interactive
60
+ bankai add
61
+
62
+ # Edit an existing custom agent
63
+ bankai edit opencode
64
+
65
+ # Remove a custom agent
66
+ bankai remove opencode
67
+ ```
68
+
69
+ Custom agents are stored in `~/.config/bankai/agents.json` (XDG-compliant, varies by OS).
70
+
71
+ ## Development
72
+
73
+ ```bash
74
+ bun install
75
+ bun run dev -- claude # Run from source
76
+ bun run build # Build to dist/
77
+ bun run test # Run tests
78
+ ```
package/dist/main.js ADDED
@@ -0,0 +1,707 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/main.ts
4
+ import { Command } from "commander";
5
+
6
+ // src/commands/print.ts
7
+ import chalk3 from "chalk";
8
+
9
+ // src/registry/builtin.ts
10
+ var builtinAgents = [
11
+ {
12
+ type: "cli",
13
+ cmd: "claude",
14
+ displayName: "Claude Code",
15
+ lines: ["claude --dangerously-skip-permissions"]
16
+ },
17
+ {
18
+ type: "cli",
19
+ cmd: "codex",
20
+ displayName: "Codex CLI",
21
+ lines: ["codex --dangerously-bypass-approvals-and-sandbox"]
22
+ },
23
+ {
24
+ type: "cli",
25
+ cmd: "copilot",
26
+ displayName: "GitHub Copilot CLI",
27
+ lines: ["copilot --allow-all-tools"]
28
+ },
29
+ {
30
+ type: "cli",
31
+ cmd: "gemini",
32
+ displayName: "Gemini CLI",
33
+ lines: ["gemini --yolo"],
34
+ cmdAliases: ["gemini-cli"]
35
+ },
36
+ {
37
+ type: "cli",
38
+ cmd: "openhands",
39
+ displayName: "OpenHands",
40
+ lines: ["openhands --always-approve"]
41
+ },
42
+ {
43
+ type: "cli",
44
+ cmd: "aider",
45
+ displayName: "Aider",
46
+ lines: ["aider --yes-always"]
47
+ },
48
+ {
49
+ type: "cli",
50
+ cmd: "qwen",
51
+ displayName: "Qwen Code",
52
+ lines: ["qwen-code --yolo"],
53
+ cmdAliases: ["qwen-code"]
54
+ },
55
+ {
56
+ type: "cli",
57
+ cmd: "kimi",
58
+ displayName: "Kimi Code",
59
+ lines: ["kimi --yolo"]
60
+ },
61
+ {
62
+ type: "settings",
63
+ cmd: "cursor-agent",
64
+ displayName: "Cursor Agent CLI",
65
+ targets: [
66
+ {
67
+ kind: "json",
68
+ scope: "project",
69
+ filePath: ".cursor/cli.json",
70
+ merge: {
71
+ permissions: {
72
+ allow: [
73
+ "Shell(**)",
74
+ "Read(**)",
75
+ "Write(**)",
76
+ "Delete(**)",
77
+ "Grep(**)",
78
+ "LS(**)"
79
+ ]
80
+ }
81
+ },
82
+ description: "Project (.cursor/cli.json)"
83
+ },
84
+ {
85
+ kind: "json",
86
+ scope: "global",
87
+ filePath: "~/.cursor/cli-config.json",
88
+ merge: {
89
+ permissions: {
90
+ allow: [
91
+ "Shell(**)",
92
+ "Read(**)",
93
+ "Write(**)",
94
+ "Delete(**)",
95
+ "Grep(**)",
96
+ "LS(**)"
97
+ ]
98
+ }
99
+ },
100
+ description: "Global (~/.cursor/cli-config.json)"
101
+ }
102
+ ]
103
+ },
104
+ {
105
+ type: "settings",
106
+ cmd: "cursor",
107
+ displayName: "Cursor IDE",
108
+ targets: [
109
+ {
110
+ kind: "sqlite",
111
+ scope: "global",
112
+ dbPath: "~/Library/Application Support/Cursor/User/globalStorage/state.vscdb",
113
+ table: "ItemTable",
114
+ key: "src.vs.platform.reactivestorage.browser.reactiveStorageServiceImpl.persistentStorage.applicationUser",
115
+ mergePath: "composerState",
116
+ merge: {
117
+ playwrightProtection: false,
118
+ yoloDotFilesDisabled: false,
119
+ yoloOutsideWorkspaceDisabled: false
120
+ },
121
+ modes4Patch: {
122
+ id: "agent",
123
+ set: { autoRun: true, fullAutoRun: true }
124
+ },
125
+ description: "IDE Auto-Run (SQLite DB)"
126
+ }
127
+ ]
128
+ }
129
+ ];
130
+
131
+ // src/registry/custom.ts
132
+ import fs from "fs";
133
+ import path from "path";
134
+ import envPaths from "env-paths";
135
+
136
+ // src/registry/types.ts
137
+ import { z } from "zod";
138
+ var CliAgentDefSchema = z.object({
139
+ type: z.literal("cli").default("cli"),
140
+ cmd: z.string().min(1),
141
+ displayName: z.string().optional(),
142
+ lines: z.array(z.string().min(1)).min(1),
143
+ cmdAliases: z.array(z.string().min(1)).optional()
144
+ });
145
+ var JsonTargetSchema = z.object({
146
+ kind: z.literal("json"),
147
+ scope: z.enum(["project", "global"]),
148
+ filePath: z.string(),
149
+ merge: z.record(z.unknown()),
150
+ description: z.string().optional()
151
+ });
152
+ var SqliteTargetSchema = z.object({
153
+ kind: z.literal("sqlite"),
154
+ scope: z.literal("global"),
155
+ dbPath: z.string(),
156
+ table: z.string(),
157
+ key: z.string(),
158
+ mergePath: z.string(),
159
+ merge: z.record(z.unknown()),
160
+ modes4Patch: z.object({
161
+ id: z.string(),
162
+ set: z.record(z.unknown())
163
+ }).optional(),
164
+ description: z.string().optional()
165
+ });
166
+ var SettingsTargetSchema = z.union([
167
+ JsonTargetSchema,
168
+ SqliteTargetSchema
169
+ ]);
170
+ var SettingsAgentDefSchema = z.object({
171
+ type: z.literal("settings"),
172
+ cmd: z.string().min(1),
173
+ displayName: z.string().optional(),
174
+ cmdAliases: z.array(z.string().min(1)).optional(),
175
+ targets: z.array(SettingsTargetSchema).min(1)
176
+ });
177
+ var AgentDefSchema = z.preprocess(
178
+ (val) => typeof val === "object" && val !== null && !("type" in val) ? { ...val, type: "cli" } : val,
179
+ z.discriminatedUnion("type", [CliAgentDefSchema, SettingsAgentDefSchema])
180
+ );
181
+ var CustomAgentDefSchema = CliAgentDefSchema;
182
+ var CustomAgentsFileSchema = z.array(CustomAgentDefSchema);
183
+
184
+ // src/registry/custom.ts
185
+ var paths = envPaths("bankai");
186
+ var agentsFile = path.join(paths.config, "agents.json");
187
+ function ensureConfigDir() {
188
+ fs.mkdirSync(paths.config, { recursive: true });
189
+ }
190
+ function loadCustomAgents(filePath = agentsFile) {
191
+ if (!fs.existsSync(filePath)) return [];
192
+ const raw = JSON.parse(fs.readFileSync(filePath, "utf-8"));
193
+ return CustomAgentsFileSchema.parse(raw);
194
+ }
195
+ function saveCustomAgents(agents, filePath = agentsFile) {
196
+ if (filePath === agentsFile) ensureConfigDir();
197
+ fs.writeFileSync(filePath, JSON.stringify(agents, null, 2) + "\n");
198
+ }
199
+ function addAgent(agent, filePath = agentsFile) {
200
+ const agents = loadCustomAgents(filePath);
201
+ const existing = agents.findIndex((a) => a.cmd === agent.cmd);
202
+ if (existing !== -1) {
203
+ throw new Error(`Agent "${agent.cmd}" already exists. Use edit to update.`);
204
+ }
205
+ agents.push(agent);
206
+ saveCustomAgents(agents, filePath);
207
+ }
208
+ function updateAgent(cmd, updates, filePath = agentsFile) {
209
+ const agents = loadCustomAgents(filePath);
210
+ const idx = agents.findIndex((a) => a.cmd === cmd);
211
+ if (idx === -1) {
212
+ throw new Error(`Agent "${cmd}" not found.`);
213
+ }
214
+ agents[idx] = { ...agents[idx], ...updates };
215
+ saveCustomAgents(agents, filePath);
216
+ }
217
+ function removeAgent(cmd, filePath = agentsFile) {
218
+ const agents = loadCustomAgents(filePath);
219
+ const filtered = agents.filter((a) => a.cmd !== cmd);
220
+ if (filtered.length === agents.length) {
221
+ throw new Error(`Agent "${cmd}" not found.`);
222
+ }
223
+ saveCustomAgents(filtered, filePath);
224
+ }
225
+
226
+ // src/registry/resolve.ts
227
+ function resolveAgent(cmd, customFilePath) {
228
+ const custom = loadCustomAgents(customFilePath);
229
+ for (const agent of custom) {
230
+ if (agent.cmd === cmd) return agent;
231
+ if (agent.cmdAliases?.includes(cmd)) return agent;
232
+ }
233
+ for (const agent of builtinAgents) {
234
+ if (agent.cmd === cmd) return agent;
235
+ if (agent.cmdAliases?.includes(cmd)) return agent;
236
+ }
237
+ return void 0;
238
+ }
239
+ function resolveAll(customFilePath) {
240
+ const custom = loadCustomAgents(customFilePath);
241
+ const merged = /* @__PURE__ */ new Map();
242
+ for (const agent of builtinAgents) {
243
+ merged.set(agent.cmd, agent);
244
+ }
245
+ for (const agent of custom) {
246
+ merged.set(agent.cmd, agent);
247
+ }
248
+ return [...merged.values()];
249
+ }
250
+
251
+ // src/format.ts
252
+ import chalk from "chalk";
253
+ function formatOutput(agent) {
254
+ const name = agent.displayName ?? agent.cmd;
255
+ const header = chalk.bold.cyan(`# ${name}`);
256
+ const lines = agent.lines.map((l) => chalk.green(l)).join("\n");
257
+ return `${header}
258
+ ${lines}`;
259
+ }
260
+
261
+ // src/commands/apply.ts
262
+ import chalk2 from "chalk";
263
+ import select from "@inquirer/select";
264
+ import confirm from "@inquirer/confirm";
265
+
266
+ // src/settings.ts
267
+ import fs2 from "fs";
268
+ import path2 from "path";
269
+ import os from "os";
270
+ import Database from "better-sqlite3";
271
+ function resolveTargetPath(filePath) {
272
+ if (filePath.startsWith("~/")) {
273
+ return path2.join(os.homedir(), filePath.slice(2));
274
+ }
275
+ return path2.resolve(filePath);
276
+ }
277
+ function deepMerge(base, overlay) {
278
+ const result = { ...base };
279
+ for (const key of Object.keys(overlay)) {
280
+ const baseVal = base[key];
281
+ const overVal = overlay[key];
282
+ if (typeof baseVal === "object" && baseVal !== null && !Array.isArray(baseVal) && typeof overVal === "object" && overVal !== null && !Array.isArray(overVal)) {
283
+ result[key] = deepMerge(
284
+ baseVal,
285
+ overVal
286
+ );
287
+ } else {
288
+ result[key] = overVal;
289
+ }
290
+ }
291
+ return result;
292
+ }
293
+ function isDeepSubset(haystack, needle) {
294
+ if (needle === haystack) return true;
295
+ if (typeof needle === "object" && needle !== null && !Array.isArray(needle) && typeof haystack === "object" && haystack !== null && !Array.isArray(haystack)) {
296
+ const h = haystack;
297
+ const n = needle;
298
+ return Object.keys(n).every((key) => isDeepSubset(h[key], n[key]));
299
+ }
300
+ if (Array.isArray(needle) && Array.isArray(haystack)) {
301
+ if (needle.length !== haystack.length) return false;
302
+ return needle.every((item, i) => isDeepSubset(haystack[i], item));
303
+ }
304
+ return JSON.stringify(haystack) === JSON.stringify(needle);
305
+ }
306
+ function patchModes4(modes4, patch) {
307
+ return modes4.map((entry) => {
308
+ if (entry.id === patch.id) {
309
+ return { ...entry, ...patch.set };
310
+ }
311
+ return entry;
312
+ });
313
+ }
314
+ function isModes4PatchApplied(modes4, patch) {
315
+ const entry = modes4.find((e) => e.id === patch.id);
316
+ if (!entry) return false;
317
+ return Object.entries(patch.set).every(
318
+ ([key, val]) => entry[key] === val
319
+ );
320
+ }
321
+ function isJsonAlreadyApplied(target) {
322
+ const filePath = resolveTargetPath(target.filePath);
323
+ if (!fs2.existsSync(filePath)) return false;
324
+ try {
325
+ const data = JSON.parse(fs2.readFileSync(filePath, "utf-8"));
326
+ return isDeepSubset(data, target.merge);
327
+ } catch {
328
+ return false;
329
+ }
330
+ }
331
+ function applyJsonSettings(target) {
332
+ const filePath = resolveTargetPath(target.filePath);
333
+ let data = {};
334
+ if (fs2.existsSync(filePath)) {
335
+ try {
336
+ data = JSON.parse(fs2.readFileSync(filePath, "utf-8"));
337
+ } catch {
338
+ }
339
+ } else {
340
+ fs2.mkdirSync(path2.dirname(filePath), { recursive: true });
341
+ }
342
+ const merged = deepMerge(data, target.merge);
343
+ fs2.writeFileSync(filePath, JSON.stringify(merged, null, 2) + "\n");
344
+ }
345
+ function isSqliteAlreadyApplied(target) {
346
+ const dbPath = resolveTargetPath(target.dbPath);
347
+ if (!fs2.existsSync(dbPath)) return false;
348
+ try {
349
+ const db = new Database(dbPath, { readonly: true });
350
+ const row = db.prepare(`SELECT value FROM ${target.table} WHERE key = ?`).get(target.key);
351
+ db.close();
352
+ if (!row) return false;
353
+ const data = JSON.parse(row.value);
354
+ const sub = getNestedValue(data, target.mergePath);
355
+ if (sub === void 0) return false;
356
+ const mergeApplied = isDeepSubset(sub, target.merge);
357
+ if (target.modes4Patch) {
358
+ const modes4 = sub.modes4;
359
+ if (!modes4) return false;
360
+ return mergeApplied && isModes4PatchApplied(modes4, target.modes4Patch);
361
+ }
362
+ return mergeApplied;
363
+ } catch {
364
+ return false;
365
+ }
366
+ }
367
+ function applySqliteSettings(target) {
368
+ const dbPath = resolveTargetPath(target.dbPath);
369
+ if (!fs2.existsSync(dbPath)) {
370
+ throw new Error(`Database not found: ${dbPath}`);
371
+ }
372
+ const db = new Database(dbPath);
373
+ const row = db.prepare(`SELECT value FROM ${target.table} WHERE key = ?`).get(target.key);
374
+ let data = {};
375
+ if (row) {
376
+ data = JSON.parse(row.value);
377
+ }
378
+ let sub = getNestedValue(data, target.mergePath);
379
+ if (!sub || typeof sub !== "object") {
380
+ sub = {};
381
+ }
382
+ const merged = deepMerge(sub, target.merge);
383
+ if (target.modes4Patch && Array.isArray(merged.modes4)) {
384
+ merged.modes4 = patchModes4(
385
+ merged.modes4,
386
+ target.modes4Patch
387
+ );
388
+ }
389
+ setNestedValue(data, target.mergePath, merged);
390
+ const newValue = JSON.stringify(data);
391
+ if (row) {
392
+ db.prepare(`UPDATE ${target.table} SET value = ? WHERE key = ?`).run(
393
+ newValue,
394
+ target.key
395
+ );
396
+ } else {
397
+ db.prepare(`INSERT INTO ${target.table} (key, value) VALUES (?, ?)`).run(
398
+ target.key,
399
+ newValue
400
+ );
401
+ }
402
+ db.close();
403
+ }
404
+ function isAlreadyApplied(target) {
405
+ if (target.kind === "json") return isJsonAlreadyApplied(target);
406
+ return isSqliteAlreadyApplied(target);
407
+ }
408
+ function applySettings(target) {
409
+ if (target.kind === "json") {
410
+ applyJsonSettings(target);
411
+ } else {
412
+ applySqliteSettings(target);
413
+ }
414
+ }
415
+ function getNestedValue(obj, path3) {
416
+ const keys = path3.split(".");
417
+ let current = obj;
418
+ for (const key of keys) {
419
+ if (typeof current !== "object" || current === null) return void 0;
420
+ current = current[key];
421
+ }
422
+ return current;
423
+ }
424
+ function setNestedValue(obj, path3, value) {
425
+ const keys = path3.split(".");
426
+ let current = obj;
427
+ for (let i = 0; i < keys.length - 1; i++) {
428
+ if (typeof current[keys[i]] !== "object" || current[keys[i]] === null) {
429
+ current[keys[i]] = {};
430
+ }
431
+ current = current[keys[i]];
432
+ }
433
+ current[keys[keys.length - 1]] = value;
434
+ }
435
+
436
+ // src/commands/apply.ts
437
+ async function applySettingsAgent(agent) {
438
+ const name = agent.displayName ?? agent.cmd;
439
+ console.log(chalk2.bold.cyan(`# ${name}`));
440
+ console.log(chalk2.dim("This agent uses settings files instead of CLI flags.\n"));
441
+ const statuses = agent.targets.map((t) => ({
442
+ target: t,
443
+ applied: isAlreadyApplied(t)
444
+ }));
445
+ const allApplied = statuses.every((s) => s.applied);
446
+ if (allApplied) {
447
+ console.log(
448
+ chalk2.green("All settings are already applied:")
449
+ );
450
+ for (const s of statuses) {
451
+ console.log(chalk2.green(` \u2713 ${s.target.description ?? s.target.kind}`));
452
+ }
453
+ return;
454
+ }
455
+ for (const s of statuses) {
456
+ const label2 = s.target.description ?? s.target.kind;
457
+ if (s.applied) {
458
+ console.log(chalk2.green(` \u2713 ${label2} (already applied)`));
459
+ } else {
460
+ console.log(chalk2.yellow(` \u25CB ${label2} (not applied)`));
461
+ }
462
+ }
463
+ console.log();
464
+ const unapplied = statuses.filter((s) => !s.applied);
465
+ let target;
466
+ if (unapplied.length === 1) {
467
+ target = unapplied[0].target;
468
+ } else {
469
+ const chosen = await select({
470
+ message: "Select a target to apply:",
471
+ choices: unapplied.map((s) => ({
472
+ name: s.target.description ?? s.target.kind,
473
+ value: s.target
474
+ }))
475
+ });
476
+ target = chosen;
477
+ }
478
+ const label = target.description ?? target.kind;
479
+ const ok = await confirm({
480
+ message: `Apply settings to ${label}?`,
481
+ default: true
482
+ });
483
+ if (!ok) {
484
+ console.log(chalk2.dim("Cancelled."));
485
+ return;
486
+ }
487
+ try {
488
+ applySettings(target);
489
+ console.log(chalk2.green(`
490
+ \u2713 Applied settings to ${label}`));
491
+ if (target.kind === "sqlite") {
492
+ console.log(chalk2.yellow("\nRestart the application for changes to take effect."));
493
+ }
494
+ } catch (err) {
495
+ const msg = err instanceof Error ? err.message : String(err);
496
+ console.error(chalk2.red(`
497
+ Failed to apply settings: ${msg}`));
498
+ process.exitCode = 1;
499
+ }
500
+ }
501
+
502
+ // src/commands/print.ts
503
+ async function printAgent(cmd) {
504
+ const agent = resolveAgent(cmd);
505
+ if (!agent) {
506
+ console.error(
507
+ chalk3.red(`Unsupported agent: "${cmd}".`) + `
508
+ Run ${chalk3.yellow("bankai agents")} to see available agents, or ${chalk3.yellow("bankai add")} to register a custom one.`
509
+ );
510
+ process.exitCode = 1;
511
+ return;
512
+ }
513
+ if (agent.type === "settings") {
514
+ await applySettingsAgent(agent);
515
+ } else {
516
+ console.log(formatOutput(agent));
517
+ }
518
+ }
519
+
520
+ // src/commands/agents.ts
521
+ import chalk4 from "chalk";
522
+
523
+ // src/detect.ts
524
+ import { spawnSync } from "child_process";
525
+ function isInstalled(cmd) {
526
+ const result = spawnSync("command", ["-v", cmd], {
527
+ shell: true,
528
+ stdio: "ignore"
529
+ });
530
+ return result.status === 0;
531
+ }
532
+ function filterInstalled(agents) {
533
+ return agents.filter((a) => isInstalled(a.cmd));
534
+ }
535
+
536
+ // src/commands/agents.ts
537
+ function listAgents(opts) {
538
+ let agents = resolveAll();
539
+ if (opts.installed) {
540
+ agents = filterInstalled(agents);
541
+ if (agents.length === 0) {
542
+ console.log(chalk4.yellow("No supported agents detected on this system."));
543
+ return;
544
+ }
545
+ }
546
+ for (const agent of agents) {
547
+ const name = agent.displayName ? `${chalk4.bold(agent.cmd)} ${chalk4.dim(`(${agent.displayName})`)}` : chalk4.bold(agent.cmd);
548
+ if (agent.type === "settings") {
549
+ const targets = agent.targets.map((t) => t.description ?? t.kind).join(", ");
550
+ console.log(` ${name} \u2192 ${chalk4.magenta(`[settings: ${targets}]`)}`);
551
+ } else {
552
+ const lines = agent.lines.map((l) => chalk4.green(l)).join(", ");
553
+ console.log(` ${name} \u2192 ${lines}`);
554
+ }
555
+ }
556
+ }
557
+
558
+ // src/commands/add.ts
559
+ import chalk5 from "chalk";
560
+ import input from "@inquirer/input";
561
+ async function addAgentCommand(opts) {
562
+ let cmd = opts.cmd;
563
+ let lines = opts.line;
564
+ let displayName = opts.displayName;
565
+ let aliases = opts.alias;
566
+ if (!cmd) {
567
+ cmd = await input({ message: "Command name (e.g. myagent):" });
568
+ displayName = await input({
569
+ message: "Display name (optional, press Enter to skip):"
570
+ });
571
+ const linesRaw = await input({
572
+ message: "Bypass command(s), comma-separated:"
573
+ });
574
+ lines = linesRaw.split(",").map((l) => l.trim()).filter(Boolean);
575
+ const aliasRaw = await input({
576
+ message: "Aliases, comma-separated (optional, press Enter to skip):"
577
+ });
578
+ aliases = aliasRaw ? aliasRaw.split(",").map((a) => a.trim()).filter(Boolean) : void 0;
579
+ }
580
+ if (!lines || lines.length === 0) {
581
+ console.error(chalk5.red("At least one --line is required."));
582
+ process.exitCode = 1;
583
+ return;
584
+ }
585
+ const agent = CustomAgentDefSchema.parse({
586
+ cmd,
587
+ displayName: displayName || void 0,
588
+ lines,
589
+ cmdAliases: aliases && aliases.length > 0 ? aliases : void 0
590
+ });
591
+ try {
592
+ addAgent(agent);
593
+ console.log(chalk5.green(`Added custom agent "${cmd}".`));
594
+ } catch (err) {
595
+ console.error(chalk5.red(err.message));
596
+ process.exitCode = 1;
597
+ }
598
+ }
599
+
600
+ // src/commands/edit.ts
601
+ import chalk6 from "chalk";
602
+ import input2 from "@inquirer/input";
603
+ async function editAgentCommand(cmd) {
604
+ const custom = loadCustomAgents();
605
+ const existing = custom.find((a) => a.cmd === cmd);
606
+ if (!existing) {
607
+ const builtin = resolveAgent(cmd);
608
+ if (builtin) {
609
+ console.error(
610
+ chalk6.red(`"${cmd}" is a built-in agent. Use ${chalk6.yellow("bankai add")} to create a custom override.`)
611
+ );
612
+ } else {
613
+ console.error(chalk6.red(`Custom agent "${cmd}" not found.`));
614
+ }
615
+ process.exitCode = 1;
616
+ return;
617
+ }
618
+ const displayName = await input2({
619
+ message: "Display name:",
620
+ default: existing.displayName ?? ""
621
+ });
622
+ const linesRaw = await input2({
623
+ message: "Bypass command(s), comma-separated:",
624
+ default: existing.lines.join(", ")
625
+ });
626
+ const lines = linesRaw.split(",").map((l) => l.trim()).filter(Boolean);
627
+ const aliasRaw = await input2({
628
+ message: "Aliases, comma-separated:",
629
+ default: existing.cmdAliases?.join(", ") ?? ""
630
+ });
631
+ const aliases = aliasRaw ? aliasRaw.split(",").map((a) => a.trim()).filter(Boolean) : void 0;
632
+ try {
633
+ updateAgent(cmd, {
634
+ displayName: displayName || void 0,
635
+ lines,
636
+ cmdAliases: aliases && aliases.length > 0 ? aliases : void 0
637
+ });
638
+ console.log(chalk6.green(`Updated agent "${cmd}".`));
639
+ } catch (err) {
640
+ console.error(chalk6.red(err.message));
641
+ process.exitCode = 1;
642
+ }
643
+ }
644
+
645
+ // src/commands/remove.ts
646
+ import chalk7 from "chalk";
647
+ function removeAgentCommand(cmd) {
648
+ try {
649
+ removeAgent(cmd);
650
+ console.log(chalk7.green(`Removed agent "${cmd}".`));
651
+ } catch (err) {
652
+ console.error(chalk7.red(err.message));
653
+ process.exitCode = 1;
654
+ }
655
+ }
656
+
657
+ // src/commands/select.ts
658
+ import chalk8 from "chalk";
659
+ import select2 from "@inquirer/select";
660
+ async function selectAgent() {
661
+ const all = resolveAll();
662
+ const installed = filterInstalled(all);
663
+ const agents = installed.length > 0 ? installed : all;
664
+ const label = installed.length > 0 ? "Detected agents on this system" : "No agents detected \u2014 showing all supported agents";
665
+ console.log(chalk8.dim(label));
666
+ const chosen = await select2({
667
+ message: "Select an agent:",
668
+ choices: agents.map((a) => ({
669
+ name: a.displayName ?? a.cmd,
670
+ value: a.cmd
671
+ }))
672
+ });
673
+ const agent = agents.find((a) => a.cmd === chosen);
674
+ if (agent) {
675
+ console.log();
676
+ if (agent.type === "settings") {
677
+ await applySettingsAgent(agent);
678
+ } else {
679
+ console.log(formatOutput(agent));
680
+ }
681
+ }
682
+ }
683
+
684
+ // src/main.ts
685
+ var program = new Command();
686
+ program.name("bankai").description("Print approval-bypass startup commands for coding agent CLIs").version("0.2.0");
687
+ program.argument("[cmd]", "agent command to look up").option("-a, --agent <cmd>", "agent command to look up (alternative)").action(async (cmd, opts) => {
688
+ const target = cmd || opts.agent;
689
+ if (target) {
690
+ await printAgent(target);
691
+ } else {
692
+ await selectAgent();
693
+ }
694
+ });
695
+ program.command("agents").description("List all supported agents").option("--installed", "Only show agents detected on this system").action((opts) => {
696
+ listAgents(opts);
697
+ });
698
+ program.command("add").description("Register a custom agent").option("--cmd <name>", "Command name").option("--line <line...>", "Bypass command line(s)").option("--display-name <name>", "Display name").option("--alias <alias...>", "Command aliases").action(async (opts) => {
699
+ await addAgentCommand(opts);
700
+ });
701
+ program.command("edit <cmd>").description("Edit an existing custom agent").action(async (cmd) => {
702
+ await editAgentCommand(cmd);
703
+ });
704
+ program.command("remove <cmd>").description("Remove a custom agent").action((cmd) => {
705
+ removeAgentCommand(cmd);
706
+ });
707
+ program.parse();
package/package.json ADDED
@@ -0,0 +1,31 @@
1
+ {
2
+ "name": "bankai-cli",
3
+ "version": "0.2.0",
4
+ "type": "module",
5
+ "files": ["dist", "README.md"],
6
+ "bin": {
7
+ "bankai": "./dist/main.js"
8
+ },
9
+ "scripts": {
10
+ "build": "tsup",
11
+ "dev": "bun run src/main.ts",
12
+ "test": "vitest run"
13
+ },
14
+ "dependencies": {
15
+ "@inquirer/confirm": "^6.0.4",
16
+ "@inquirer/input": "^4.1.0",
17
+ "@inquirer/select": "^4.1.0",
18
+ "better-sqlite3": "^12.6.2",
19
+ "chalk": "^5.4.1",
20
+ "commander": "^13.1.0",
21
+ "env-paths": "^3.0.0",
22
+ "zod": "^3.24.2"
23
+ },
24
+ "devDependencies": {
25
+ "@types/better-sqlite3": "^7.6.13",
26
+ "@types/node": "^22.13.1",
27
+ "tsup": "^8.3.6",
28
+ "typescript": "^5.7.3",
29
+ "vitest": "^3.0.5"
30
+ }
31
+ }