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.
- package/README.md +78 -0
- package/dist/main.js +707 -0
- 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
|
+
}
|