@bankr/cli 0.2.11 → 0.2.15
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 +34 -7
- package/dist/cli.js +178 -11
- package/dist/commands/club.d.ts +9 -0
- package/dist/commands/club.js +151 -0
- package/dist/commands/files.d.ts +34 -0
- package/dist/commands/files.js +501 -0
- package/dist/commands/llm.js +35 -2
- package/dist/commands/login.js +4 -2
- package/dist/commands/prompt.js +25 -0
- package/dist/commands/webhooks.d.ts +29 -0
- package/dist/commands/webhooks.js +657 -0
- package/dist/commands/x402.js +13 -0
- package/package.json +1 -1
|
@@ -0,0 +1,501 @@
|
|
|
1
|
+
import { readFileSync, statSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { basename } from "node:path";
|
|
3
|
+
import { CLI_USER_AGENT, getApiUrl, requireApiKey } from "../lib/config.js";
|
|
4
|
+
import * as output from "../lib/output.js";
|
|
5
|
+
async function readStdin() {
|
|
6
|
+
if (process.stdin.isTTY) {
|
|
7
|
+
throw new Error("No content provided. Pipe content via stdin or pass --from <file>.");
|
|
8
|
+
}
|
|
9
|
+
const chunks = [];
|
|
10
|
+
for await (const chunk of process.stdin)
|
|
11
|
+
chunks.push(chunk);
|
|
12
|
+
return Buffer.concat(chunks).toString("utf-8");
|
|
13
|
+
}
|
|
14
|
+
function authHeaders() {
|
|
15
|
+
return {
|
|
16
|
+
"X-API-Key": requireApiKey(),
|
|
17
|
+
"Content-Type": "application/json",
|
|
18
|
+
"User-Agent": CLI_USER_AGENT,
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
// ── List files ──────────────────────────────────────────────────────────
|
|
22
|
+
export async function filesListCommand(opts) {
|
|
23
|
+
const spinner = output.spinner("Fetching files...");
|
|
24
|
+
try {
|
|
25
|
+
const params = new URLSearchParams();
|
|
26
|
+
if (opts.folder)
|
|
27
|
+
params.set("folder", opts.folder);
|
|
28
|
+
const res = await fetch(`${getApiUrl()}/user/files?${params.toString()}`, {
|
|
29
|
+
headers: authHeaders(),
|
|
30
|
+
});
|
|
31
|
+
if (!res.ok) {
|
|
32
|
+
const body = await res.json().catch(() => ({ error: res.statusText }));
|
|
33
|
+
throw new Error(body.error || res.statusText);
|
|
34
|
+
}
|
|
35
|
+
const { files } = (await res.json());
|
|
36
|
+
spinner.stop();
|
|
37
|
+
if (files.length === 0) {
|
|
38
|
+
output.dim("No files found.");
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
// Show folders first, then files
|
|
42
|
+
const folders = files.filter((f) => f.isFolder);
|
|
43
|
+
const regularFiles = files.filter((f) => !f.isFolder);
|
|
44
|
+
for (const f of folders) {
|
|
45
|
+
console.log(` ${output.fmt.brand("\u{1F4C2}")} ${output.fmt.brandBold(f.name + "/")} ${output.fmt.dim(f.folder)}`);
|
|
46
|
+
}
|
|
47
|
+
for (const f of regularFiles) {
|
|
48
|
+
const size = formatSize(f.sizeBytes);
|
|
49
|
+
const date = new Date(f.createdAt).toLocaleDateString();
|
|
50
|
+
console.log(` ${output.fmt.dim(f._id.slice(0, 8))} ${f.name} ${output.fmt.dim(size)} ${output.fmt.dim(date)} ${output.fmt.dim(f.folder)}`);
|
|
51
|
+
}
|
|
52
|
+
output.blank();
|
|
53
|
+
output.dim(`${regularFiles.length} file(s), ${folders.length} folder(s)`);
|
|
54
|
+
}
|
|
55
|
+
catch (err) {
|
|
56
|
+
spinner.stop();
|
|
57
|
+
output.error(err instanceof Error ? err.message : String(err));
|
|
58
|
+
process.exit(1);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
// ── Upload file ─────────────────────────────────────────────────────────
|
|
62
|
+
export async function filesUploadCommand(filePath, opts) {
|
|
63
|
+
const spinner = output.spinner(`Uploading ${filePath}...`);
|
|
64
|
+
try {
|
|
65
|
+
// Read the file
|
|
66
|
+
const stat = statSync(filePath);
|
|
67
|
+
if (stat.size > 10 * 1024 * 1024) {
|
|
68
|
+
throw new Error("File exceeds 10MB limit");
|
|
69
|
+
}
|
|
70
|
+
const buffer = readFileSync(filePath);
|
|
71
|
+
const name = basename(filePath);
|
|
72
|
+
// Build multipart form data
|
|
73
|
+
const boundary = `----BankrCLI${Date.now()}`;
|
|
74
|
+
const folder = opts.folder || "/";
|
|
75
|
+
const parts = [];
|
|
76
|
+
// Folder field
|
|
77
|
+
parts.push(Buffer.from(`--${boundary}\r\nContent-Disposition: form-data; name="folder"\r\n\r\n${folder}\r\n`));
|
|
78
|
+
// File field
|
|
79
|
+
const mimeType = inferMimeType(name);
|
|
80
|
+
parts.push(Buffer.from(`--${boundary}\r\nContent-Disposition: form-data; name="file"; filename="${name}"\r\nContent-Type: ${mimeType}\r\n\r\n`));
|
|
81
|
+
parts.push(buffer);
|
|
82
|
+
parts.push(Buffer.from(`\r\n--${boundary}--\r\n`));
|
|
83
|
+
const body = Buffer.concat(parts);
|
|
84
|
+
const res = await fetch(`${getApiUrl()}/user/files/upload`, {
|
|
85
|
+
method: "POST",
|
|
86
|
+
headers: {
|
|
87
|
+
"X-API-Key": requireApiKey(),
|
|
88
|
+
"Content-Type": `multipart/form-data; boundary=${boundary}`,
|
|
89
|
+
"User-Agent": CLI_USER_AGENT,
|
|
90
|
+
},
|
|
91
|
+
body,
|
|
92
|
+
});
|
|
93
|
+
if (!res.ok) {
|
|
94
|
+
const errBody = await res.json().catch(() => ({ error: res.statusText }));
|
|
95
|
+
throw new Error(errBody.error || res.statusText);
|
|
96
|
+
}
|
|
97
|
+
const result = (await res.json());
|
|
98
|
+
spinner.stop();
|
|
99
|
+
output.success(`Uploaded ${result.file.name} (${formatSize(result.file.sizeBytes)})`);
|
|
100
|
+
output.dim(`File ID: ${result.file._id}`);
|
|
101
|
+
output.dim(`Download: ${result.downloadUrl}`);
|
|
102
|
+
}
|
|
103
|
+
catch (err) {
|
|
104
|
+
spinner.stop();
|
|
105
|
+
output.error(err instanceof Error ? err.message : String(err));
|
|
106
|
+
process.exit(1);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
// ── Download file ───────────────────────────────────────────────────────
|
|
110
|
+
export async function filesDownloadCommand(fileId) {
|
|
111
|
+
const spinner = output.spinner("Getting download URL...");
|
|
112
|
+
try {
|
|
113
|
+
const res = await fetch(`${getApiUrl()}/user/files/${fileId}/download`, {
|
|
114
|
+
headers: authHeaders(),
|
|
115
|
+
});
|
|
116
|
+
if (!res.ok) {
|
|
117
|
+
const body = await res.json().catch(() => ({ error: res.statusText }));
|
|
118
|
+
throw new Error(body.error || res.statusText);
|
|
119
|
+
}
|
|
120
|
+
const { downloadUrl } = (await res.json());
|
|
121
|
+
spinner.stop();
|
|
122
|
+
output.success("Download URL (valid for 15 minutes):");
|
|
123
|
+
console.log(downloadUrl);
|
|
124
|
+
}
|
|
125
|
+
catch (err) {
|
|
126
|
+
spinner.stop();
|
|
127
|
+
output.error(err instanceof Error ? err.message : String(err));
|
|
128
|
+
process.exit(1);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
// ── Create folder ───────────────────────────────────────────────────────
|
|
132
|
+
export async function filesMkdirCommand(name, opts) {
|
|
133
|
+
const spinner = output.spinner(`Creating folder ${name}...`);
|
|
134
|
+
try {
|
|
135
|
+
const res = await fetch(`${getApiUrl()}/user/files/folder`, {
|
|
136
|
+
method: "POST",
|
|
137
|
+
headers: authHeaders(),
|
|
138
|
+
body: JSON.stringify({
|
|
139
|
+
name,
|
|
140
|
+
parentFolder: opts.parent || "/",
|
|
141
|
+
}),
|
|
142
|
+
});
|
|
143
|
+
if (!res.ok) {
|
|
144
|
+
const body = await res.json().catch(() => ({ error: res.statusText }));
|
|
145
|
+
throw new Error(body.error || res.statusText);
|
|
146
|
+
}
|
|
147
|
+
spinner.stop();
|
|
148
|
+
output.success(`Created folder: ${name}`);
|
|
149
|
+
}
|
|
150
|
+
catch (err) {
|
|
151
|
+
spinner.stop();
|
|
152
|
+
output.error(err instanceof Error ? err.message : String(err));
|
|
153
|
+
process.exit(1);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
// ── Delete file ─────────────────────────────────────────────────────────
|
|
157
|
+
export async function filesRmCommand(fileId) {
|
|
158
|
+
const spinner = output.spinner("Deleting file...");
|
|
159
|
+
try {
|
|
160
|
+
const res = await fetch(`${getApiUrl()}/user/files/${fileId}`, {
|
|
161
|
+
method: "DELETE",
|
|
162
|
+
headers: authHeaders(),
|
|
163
|
+
});
|
|
164
|
+
if (!res.ok) {
|
|
165
|
+
const body = await res.json().catch(() => ({ error: res.statusText }));
|
|
166
|
+
throw new Error(body.error || res.statusText);
|
|
167
|
+
}
|
|
168
|
+
spinner.stop();
|
|
169
|
+
output.success("File deleted.");
|
|
170
|
+
}
|
|
171
|
+
catch (err) {
|
|
172
|
+
spinner.stop();
|
|
173
|
+
output.error(err instanceof Error ? err.message : String(err));
|
|
174
|
+
process.exit(1);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
// ── Storage usage ───────────────────────────────────────────────────────
|
|
178
|
+
export async function filesStorageCommand() {
|
|
179
|
+
const spinner = output.spinner("Fetching storage usage...");
|
|
180
|
+
try {
|
|
181
|
+
const res = await fetch(`${getApiUrl()}/user/files/storage`, {
|
|
182
|
+
headers: authHeaders(),
|
|
183
|
+
});
|
|
184
|
+
if (!res.ok) {
|
|
185
|
+
const body = await res.json().catch(() => ({ error: res.statusText }));
|
|
186
|
+
throw new Error(body.error || res.statusText);
|
|
187
|
+
}
|
|
188
|
+
const usage = (await res.json());
|
|
189
|
+
spinner.stop();
|
|
190
|
+
const filePct = usage.files.quotaBytes > 0
|
|
191
|
+
? ((usage.files.usedBytes / usage.files.quotaBytes) * 100).toFixed(1)
|
|
192
|
+
: "0";
|
|
193
|
+
output.label("Storage", `${formatSize(usage.files.usedBytes)} / ${formatSize(usage.files.quotaBytes)} (${filePct}%)`);
|
|
194
|
+
output.label("Files", `${usage.files.fileCount}`);
|
|
195
|
+
if (usage.cliCache) {
|
|
196
|
+
const cachePct = usage.cliCache.quotaBytes > 0
|
|
197
|
+
? ((usage.cliCache.usedBytes / usage.cliCache.quotaBytes) *
|
|
198
|
+
100).toFixed(1)
|
|
199
|
+
: "0";
|
|
200
|
+
output.label("CLI cache", `${formatSize(usage.cliCache.usedBytes)} / ${formatSize(usage.cliCache.quotaBytes)} (${cachePct}%)`);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
catch (err) {
|
|
204
|
+
spinner.stop();
|
|
205
|
+
output.error(err instanceof Error ? err.message : String(err));
|
|
206
|
+
process.exit(1);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
// ── Read file contents ──────────────────────────────────────────────────
|
|
210
|
+
export async function filesCatCommand(fileId, opts) {
|
|
211
|
+
const spinner = output.spinner("Fetching file...");
|
|
212
|
+
try {
|
|
213
|
+
const urlRes = await fetch(`${getApiUrl()}/user/files/${fileId}/download`, {
|
|
214
|
+
headers: authHeaders(),
|
|
215
|
+
});
|
|
216
|
+
if (!urlRes.ok) {
|
|
217
|
+
const body = await urlRes
|
|
218
|
+
.json()
|
|
219
|
+
.catch(() => ({ error: urlRes.statusText }));
|
|
220
|
+
throw new Error(body.error || urlRes.statusText);
|
|
221
|
+
}
|
|
222
|
+
const { downloadUrl } = (await urlRes.json());
|
|
223
|
+
const contentRes = await fetch(downloadUrl);
|
|
224
|
+
if (!contentRes.ok) {
|
|
225
|
+
throw new Error(`Failed to download file: ${contentRes.statusText}`);
|
|
226
|
+
}
|
|
227
|
+
spinner.stop();
|
|
228
|
+
if (opts.output) {
|
|
229
|
+
const buffer = Buffer.from(await contentRes.arrayBuffer());
|
|
230
|
+
writeFileSync(opts.output, buffer);
|
|
231
|
+
output.success(`Saved ${buffer.length} bytes to ${opts.output}`);
|
|
232
|
+
}
|
|
233
|
+
else {
|
|
234
|
+
const text = await contentRes.text();
|
|
235
|
+
process.stdout.write(text);
|
|
236
|
+
if (!text.endsWith("\n"))
|
|
237
|
+
process.stdout.write("\n");
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
catch (err) {
|
|
241
|
+
spinner.stop();
|
|
242
|
+
output.error(err instanceof Error ? err.message : String(err));
|
|
243
|
+
process.exit(1);
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
// ── Edit file (find/replace or overwrite) ───────────────────────────────
|
|
247
|
+
export async function filesEditCommand(fileId, opts) {
|
|
248
|
+
const hasReplace = opts.find !== undefined || opts.replace !== undefined;
|
|
249
|
+
const hasOverwrite = opts.content !== undefined || opts.from !== undefined;
|
|
250
|
+
if (!hasReplace && !hasOverwrite) {
|
|
251
|
+
output.error("Specify either --find/--replace for find-and-replace, or --content/--from for full overwrite.");
|
|
252
|
+
process.exit(1);
|
|
253
|
+
}
|
|
254
|
+
if (hasReplace && hasOverwrite) {
|
|
255
|
+
output.error("Cannot combine --find/--replace with --content/--from.");
|
|
256
|
+
process.exit(1);
|
|
257
|
+
}
|
|
258
|
+
if (hasReplace && (opts.find === undefined || opts.replace === undefined)) {
|
|
259
|
+
output.error("Find-and-replace requires both --find and --replace.");
|
|
260
|
+
process.exit(1);
|
|
261
|
+
}
|
|
262
|
+
const spinner = output.spinner("Editing file...");
|
|
263
|
+
try {
|
|
264
|
+
let nextContent;
|
|
265
|
+
if (hasOverwrite) {
|
|
266
|
+
nextContent = opts.content ?? readFileSync(opts.from, "utf-8");
|
|
267
|
+
}
|
|
268
|
+
else {
|
|
269
|
+
// Fetch current content, apply find/replace locally
|
|
270
|
+
const urlRes = await fetch(`${getApiUrl()}/user/files/${fileId}/download`, { headers: authHeaders() });
|
|
271
|
+
if (!urlRes.ok) {
|
|
272
|
+
const body = await urlRes
|
|
273
|
+
.json()
|
|
274
|
+
.catch(() => ({ error: urlRes.statusText }));
|
|
275
|
+
throw new Error(body.error || urlRes.statusText);
|
|
276
|
+
}
|
|
277
|
+
const { downloadUrl } = (await urlRes.json());
|
|
278
|
+
const contentRes = await fetch(downloadUrl);
|
|
279
|
+
if (!contentRes.ok) {
|
|
280
|
+
throw new Error(`Failed to fetch file: ${contentRes.statusText}`);
|
|
281
|
+
}
|
|
282
|
+
const current = await contentRes.text();
|
|
283
|
+
const occurrences = current.split(opts.find).length - 1;
|
|
284
|
+
if (occurrences === 0) {
|
|
285
|
+
throw new Error(`--find text not found in file (must match exactly, including whitespace).`);
|
|
286
|
+
}
|
|
287
|
+
if (occurrences > 1 && !opts.all) {
|
|
288
|
+
throw new Error(`--find text appears ${occurrences} times. Pass --all to replace every occurrence, or provide a more specific --find string.`);
|
|
289
|
+
}
|
|
290
|
+
if (opts.all) {
|
|
291
|
+
nextContent = current.split(opts.find).join(opts.replace);
|
|
292
|
+
}
|
|
293
|
+
else {
|
|
294
|
+
const idx = current.indexOf(opts.find);
|
|
295
|
+
nextContent =
|
|
296
|
+
current.slice(0, idx) +
|
|
297
|
+
opts.replace +
|
|
298
|
+
current.slice(idx + opts.find.length);
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
const putRes = await fetch(`${getApiUrl()}/user/files/${fileId}/content`, {
|
|
302
|
+
method: "PUT",
|
|
303
|
+
headers: authHeaders(),
|
|
304
|
+
body: JSON.stringify({ content: nextContent }),
|
|
305
|
+
});
|
|
306
|
+
if (!putRes.ok) {
|
|
307
|
+
const body = await putRes
|
|
308
|
+
.json()
|
|
309
|
+
.catch(() => ({ error: putRes.statusText }));
|
|
310
|
+
throw new Error(body.error || putRes.statusText);
|
|
311
|
+
}
|
|
312
|
+
const { file } = (await putRes.json());
|
|
313
|
+
spinner.stop();
|
|
314
|
+
output.success(`Updated ${file.name} (${formatSize(file.sizeBytes)})`);
|
|
315
|
+
}
|
|
316
|
+
catch (err) {
|
|
317
|
+
spinner.stop();
|
|
318
|
+
output.error(err instanceof Error ? err.message : String(err));
|
|
319
|
+
process.exit(1);
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
// ── Write/replace file content from stdin or local file ─────────────────
|
|
323
|
+
export async function filesWriteCommand(fileId, opts) {
|
|
324
|
+
const spinner = output.spinner("Writing file content...");
|
|
325
|
+
try {
|
|
326
|
+
const content = opts.from
|
|
327
|
+
? readFileSync(opts.from, "utf-8")
|
|
328
|
+
: await readStdin();
|
|
329
|
+
const res = await fetch(`${getApiUrl()}/user/files/${fileId}/content`, {
|
|
330
|
+
method: "PUT",
|
|
331
|
+
headers: authHeaders(),
|
|
332
|
+
body: JSON.stringify({ content }),
|
|
333
|
+
});
|
|
334
|
+
if (!res.ok) {
|
|
335
|
+
const body = await res.json().catch(() => ({ error: res.statusText }));
|
|
336
|
+
throw new Error(body.error || res.statusText);
|
|
337
|
+
}
|
|
338
|
+
const { file } = (await res.json());
|
|
339
|
+
spinner.stop();
|
|
340
|
+
output.success(`Wrote ${formatSize(file.sizeBytes)} to ${file.name}`);
|
|
341
|
+
}
|
|
342
|
+
catch (err) {
|
|
343
|
+
spinner.stop();
|
|
344
|
+
output.error(err instanceof Error ? err.message : String(err));
|
|
345
|
+
process.exit(1);
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
// ── Search files ────────────────────────────────────────────────────────
|
|
349
|
+
export async function filesSearchCommand(query, opts) {
|
|
350
|
+
const spinner = output.spinner(`Searching for "${query}"...`);
|
|
351
|
+
try {
|
|
352
|
+
const params = new URLSearchParams({ query });
|
|
353
|
+
if (opts.folder)
|
|
354
|
+
params.set("folder", opts.folder);
|
|
355
|
+
if (opts.mimeType)
|
|
356
|
+
params.set("mimeType", opts.mimeType);
|
|
357
|
+
if (opts.limit)
|
|
358
|
+
params.set("limit", opts.limit);
|
|
359
|
+
const res = await fetch(`${getApiUrl()}/user/files/search?${params.toString()}`, { headers: authHeaders() });
|
|
360
|
+
if (!res.ok) {
|
|
361
|
+
const body = await res.json().catch(() => ({ error: res.statusText }));
|
|
362
|
+
throw new Error(body.error || res.statusText);
|
|
363
|
+
}
|
|
364
|
+
const { files } = (await res.json());
|
|
365
|
+
spinner.stop();
|
|
366
|
+
if (files.length === 0) {
|
|
367
|
+
output.dim(`No files found matching "${query}".`);
|
|
368
|
+
return;
|
|
369
|
+
}
|
|
370
|
+
for (const f of files) {
|
|
371
|
+
const size = formatSize(f.sizeBytes);
|
|
372
|
+
const date = new Date(f.createdAt).toLocaleDateString();
|
|
373
|
+
const desc = f.metadata?.description
|
|
374
|
+
? ` — ${f.metadata.description}`
|
|
375
|
+
: "";
|
|
376
|
+
console.log(` ${output.fmt.dim(f._id.slice(0, 8))} ${f.name} ${output.fmt.dim(size)} ${output.fmt.dim(date)} ${output.fmt.dim(f.folder)}${output.fmt.dim(desc)}`);
|
|
377
|
+
}
|
|
378
|
+
output.blank();
|
|
379
|
+
output.dim(`${files.length} result(s)`);
|
|
380
|
+
}
|
|
381
|
+
catch (err) {
|
|
382
|
+
spinner.stop();
|
|
383
|
+
output.error(err instanceof Error ? err.message : String(err));
|
|
384
|
+
process.exit(1);
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
// ── Move file to a different folder ─────────────────────────────────────
|
|
388
|
+
export async function filesMvCommand(fileId, folder) {
|
|
389
|
+
const spinner = output.spinner("Moving file...");
|
|
390
|
+
try {
|
|
391
|
+
const res = await fetch(`${getApiUrl()}/user/files/${fileId}/move`, {
|
|
392
|
+
method: "PATCH",
|
|
393
|
+
headers: authHeaders(),
|
|
394
|
+
body: JSON.stringify({ folder }),
|
|
395
|
+
});
|
|
396
|
+
if (!res.ok) {
|
|
397
|
+
const body = await res.json().catch(() => ({ error: res.statusText }));
|
|
398
|
+
throw new Error(body.error || res.statusText);
|
|
399
|
+
}
|
|
400
|
+
const { file } = (await res.json());
|
|
401
|
+
spinner.stop();
|
|
402
|
+
output.success(`Moved ${file.name} to ${file.folder}`);
|
|
403
|
+
}
|
|
404
|
+
catch (err) {
|
|
405
|
+
spinner.stop();
|
|
406
|
+
output.error(err instanceof Error ? err.message : String(err));
|
|
407
|
+
process.exit(1);
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
// ── Rename file ─────────────────────────────────────────────────────────
|
|
411
|
+
export async function filesRenameCommand(fileId, name) {
|
|
412
|
+
const spinner = output.spinner("Renaming file...");
|
|
413
|
+
try {
|
|
414
|
+
const res = await fetch(`${getApiUrl()}/user/files/${fileId}/rename`, {
|
|
415
|
+
method: "PATCH",
|
|
416
|
+
headers: authHeaders(),
|
|
417
|
+
body: JSON.stringify({ name }),
|
|
418
|
+
});
|
|
419
|
+
if (!res.ok) {
|
|
420
|
+
const body = await res.json().catch(() => ({ error: res.statusText }));
|
|
421
|
+
throw new Error(body.error || res.statusText);
|
|
422
|
+
}
|
|
423
|
+
const { file } = (await res.json());
|
|
424
|
+
spinner.stop();
|
|
425
|
+
output.success(`Renamed to ${file.name}`);
|
|
426
|
+
}
|
|
427
|
+
catch (err) {
|
|
428
|
+
spinner.stop();
|
|
429
|
+
output.error(err instanceof Error ? err.message : String(err));
|
|
430
|
+
process.exit(1);
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
// ── File info ───────────────────────────────────────────────────────────
|
|
434
|
+
export async function filesInfoCommand(fileId) {
|
|
435
|
+
const spinner = output.spinner("Fetching file metadata...");
|
|
436
|
+
try {
|
|
437
|
+
const res = await fetch(`${getApiUrl()}/user/files/${fileId}`, {
|
|
438
|
+
headers: authHeaders(),
|
|
439
|
+
});
|
|
440
|
+
if (!res.ok) {
|
|
441
|
+
const body = await res.json().catch(() => ({ error: res.statusText }));
|
|
442
|
+
throw new Error(body.error || res.statusText);
|
|
443
|
+
}
|
|
444
|
+
const { file } = (await res.json());
|
|
445
|
+
spinner.stop();
|
|
446
|
+
output.label("Name", file.name);
|
|
447
|
+
output.label("ID", file._id);
|
|
448
|
+
output.label("Folder", file.folder);
|
|
449
|
+
output.label("Type", file.isFolder ? "folder" : file.mimeType);
|
|
450
|
+
output.label("Size", formatSize(file.sizeBytes));
|
|
451
|
+
output.label("Created", new Date(file.createdAt).toLocaleString());
|
|
452
|
+
if (file.metadata?.description) {
|
|
453
|
+
output.label("Description", file.metadata.description);
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
catch (err) {
|
|
457
|
+
spinner.stop();
|
|
458
|
+
output.error(err instanceof Error ? err.message : String(err));
|
|
459
|
+
process.exit(1);
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
// ── Helpers ─────────────────────────────────────────────────────────────
|
|
463
|
+
function formatSize(bytes) {
|
|
464
|
+
if (bytes < 1024)
|
|
465
|
+
return `${bytes} B`;
|
|
466
|
+
if (bytes < 1024 * 1024)
|
|
467
|
+
return `${(bytes / 1024).toFixed(1)} KB`;
|
|
468
|
+
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
|
469
|
+
}
|
|
470
|
+
const EXTENSION_MIME_MAP = {
|
|
471
|
+
csv: "text/csv",
|
|
472
|
+
json: "application/json",
|
|
473
|
+
txt: "text/plain",
|
|
474
|
+
md: "text/markdown",
|
|
475
|
+
html: "text/html",
|
|
476
|
+
pdf: "application/pdf",
|
|
477
|
+
js: "text/javascript",
|
|
478
|
+
ts: "text/typescript",
|
|
479
|
+
py: "text/x-python",
|
|
480
|
+
sql: "text/x-sql",
|
|
481
|
+
yaml: "text/yaml",
|
|
482
|
+
yml: "text/yaml",
|
|
483
|
+
css: "text/css",
|
|
484
|
+
sol: "text/x-solidity",
|
|
485
|
+
rs: "text/x-rust",
|
|
486
|
+
go: "text/x-go",
|
|
487
|
+
rb: "text/x-ruby",
|
|
488
|
+
sh: "text/x-shellscript",
|
|
489
|
+
toml: "text/toml",
|
|
490
|
+
svg: "image/svg+xml",
|
|
491
|
+
png: "image/png",
|
|
492
|
+
jpg: "image/jpeg",
|
|
493
|
+
jpeg: "image/jpeg",
|
|
494
|
+
gif: "image/gif",
|
|
495
|
+
webp: "image/webp",
|
|
496
|
+
};
|
|
497
|
+
function inferMimeType(filename) {
|
|
498
|
+
const ext = filename.split(".").pop()?.toLowerCase();
|
|
499
|
+
return (ext && EXTENSION_MIME_MAP[ext]) || "application/octet-stream";
|
|
500
|
+
}
|
|
501
|
+
//# sourceMappingURL=files.js.map
|
package/dist/commands/llm.js
CHANGED
|
@@ -484,7 +484,9 @@ export async function creditsAddCommand(amount, opts) {
|
|
|
484
484
|
});
|
|
485
485
|
spin.stop();
|
|
486
486
|
if (res.status === 402) {
|
|
487
|
-
|
|
487
|
+
const errBody = (await res.json().catch(() => ({})));
|
|
488
|
+
output.error(errBody.error ??
|
|
489
|
+
"Insufficient wallet balance. Add funds to your wallet first.");
|
|
488
490
|
process.exit(1);
|
|
489
491
|
}
|
|
490
492
|
if (res.status === 403) {
|
|
@@ -846,6 +848,10 @@ export async function setupClaudeCodeCommand() {
|
|
|
846
848
|
console.log();
|
|
847
849
|
output.dim("All Claude Code requests will route through the Bankr gateway.");
|
|
848
850
|
output.dim("Or launch directly: bankr llm claude");
|
|
851
|
+
console.log();
|
|
852
|
+
output.info("Model format:");
|
|
853
|
+
output.dim(" Claude Code accepts Anthropic-style IDs (dashed): claude-opus-4-7, claude-sonnet-4-6.");
|
|
854
|
+
output.dim(" `bankr llm claude` auto-translates the dotted form (claude-opus-4.7) for you.");
|
|
849
855
|
}
|
|
850
856
|
/* ─────────────────────── Shared launcher helpers ────────────────────────── */
|
|
851
857
|
function requireAuth() {
|
|
@@ -885,11 +891,38 @@ function fileContains(filePath, search) {
|
|
|
885
891
|
}
|
|
886
892
|
}
|
|
887
893
|
/* ─────────────────────────── bankr llm claude ─────────────────────────────── */
|
|
894
|
+
/**
|
|
895
|
+
* Rewrite gateway-canonical Claude IDs (dotted, e.g. `claude-opus-4.7`) to
|
|
896
|
+
* the wire format Claude Code expects (dashed, e.g. `claude-opus-4-7`).
|
|
897
|
+
* Otherwise Claude Code silently falls back to its default model — the
|
|
898
|
+
* `--model` flag appears honored but the real request is a different
|
|
899
|
+
* model. Any suffix (e.g. `[1m]` context tier) is preserved.
|
|
900
|
+
*/
|
|
901
|
+
function toAnthropicModelId(model) {
|
|
902
|
+
return model.replace(/^(claude-(?:opus|sonnet|haiku)-)(\d+)\.(\d+)(.*)$/, "$1$2-$3$4");
|
|
903
|
+
}
|
|
904
|
+
function translateClaudeModelArgs(args) {
|
|
905
|
+
const out = [...args];
|
|
906
|
+
for (let i = 0; i < out.length; i++) {
|
|
907
|
+
const arg = out[i];
|
|
908
|
+
const inlineMatch = /^(--model|-m)=(.+)$/.exec(arg);
|
|
909
|
+
if (inlineMatch) {
|
|
910
|
+
out[i] = `${inlineMatch[1]}=${toAnthropicModelId(inlineMatch[2])}`;
|
|
911
|
+
continue;
|
|
912
|
+
}
|
|
913
|
+
if ((arg === "--model" || arg === "-m") && i + 1 < out.length) {
|
|
914
|
+
out[i + 1] = toAnthropicModelId(out[i + 1]);
|
|
915
|
+
i++;
|
|
916
|
+
}
|
|
917
|
+
}
|
|
918
|
+
return out;
|
|
919
|
+
}
|
|
888
920
|
export async function claudeCommand(args) {
|
|
889
921
|
const llmKey = requireAuth();
|
|
890
922
|
const llmUrl = getLlmUrl();
|
|
923
|
+
const translated = translateClaudeModelArgs(args);
|
|
891
924
|
output.dim(`Launching Claude Code via ${llmUrl}`);
|
|
892
|
-
return launchTool("claude",
|
|
925
|
+
return launchTool("claude", translated, { ANTHROPIC_BASE_URL: llmUrl, ANTHROPIC_AUTH_TOKEN: llmKey }, "https://docs.anthropic.com/en/docs/claude-code");
|
|
893
926
|
}
|
|
894
927
|
/* ────────────────────────── bankr llm opencode ───────────────────────────── */
|
|
895
928
|
export async function opencodeCommand(args) {
|
package/dist/commands/login.js
CHANGED
|
@@ -444,8 +444,10 @@ function isValidIpOrCidr(value) {
|
|
|
444
444
|
// ::ffff: mapped IPv4 addresses normalize to IPv4 at runtime,
|
|
445
445
|
// so validate prefix against IPv4 max (32) not IPv6 max (128)
|
|
446
446
|
const isV4Mapped = version === 6 && ip.toLowerCase().startsWith("::ffff:");
|
|
447
|
-
const
|
|
448
|
-
|
|
447
|
+
const isV4 = version === 4 || isV4Mapped;
|
|
448
|
+
const maxPrefix = isV4 ? 32 : 128;
|
|
449
|
+
const minPrefix = isV4 ? 8 : 16;
|
|
450
|
+
return prefix >= minPrefix && prefix <= maxPrefix;
|
|
449
451
|
}
|
|
450
452
|
function parseAndValidateIps(raw) {
|
|
451
453
|
const ips = raw
|
package/dist/commands/prompt.js
CHANGED
|
@@ -5,9 +5,34 @@ import * as output from "../lib/output.js";
|
|
|
5
5
|
/** Valid Max Mode model IDs — hardcoded because CLI is standalone (no monorepo imports).
|
|
6
6
|
* Must be manually kept in sync with models in the LLM gateway DB. */
|
|
7
7
|
const VALID_MAX_MODE_MODELS = new Set([
|
|
8
|
+
// Claude (Vertex AI)
|
|
8
9
|
"claude-opus-4.6",
|
|
10
|
+
"claude-opus-4.5",
|
|
9
11
|
"claude-sonnet-4.6",
|
|
12
|
+
"claude-sonnet-4.5",
|
|
13
|
+
"claude-haiku-4.5",
|
|
14
|
+
// Gemini (Vertex AI)
|
|
10
15
|
"gemini-3.1-pro",
|
|
16
|
+
"gemini-3-pro",
|
|
17
|
+
"gemini-2.5-pro",
|
|
18
|
+
// OpenAI (OpenRouter)
|
|
19
|
+
"gpt-5.4",
|
|
20
|
+
"gpt-5.4-mini",
|
|
21
|
+
"gpt-5.2",
|
|
22
|
+
"gpt-5-mini",
|
|
23
|
+
// Grok (OpenRouter)
|
|
24
|
+
"grok-4.1-fast",
|
|
25
|
+
// DeepSeek (OpenRouter)
|
|
26
|
+
"deepseek-v3.2",
|
|
27
|
+
// Qwen (OpenRouter)
|
|
28
|
+
"qwen3-coder",
|
|
29
|
+
"qwen3.5-plus",
|
|
30
|
+
// MiniMax (direct)
|
|
31
|
+
"minimax-m2.5",
|
|
32
|
+
"minimax-m2.7",
|
|
33
|
+
// Other (OpenRouter)
|
|
34
|
+
"kimi-k2.5",
|
|
35
|
+
"glm-5",
|
|
11
36
|
]);
|
|
12
37
|
export async function promptCommand(text, options = {}) {
|
|
13
38
|
requireApiKey();
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CLI commands for user webhook hosting.
|
|
3
|
+
*
|
|
4
|
+
* bankr webhooks init — Scaffold webhooks/ + bankr.webhooks.json
|
|
5
|
+
* bankr webhooks add <name> — Add a new webhook handler
|
|
6
|
+
* bankr webhooks deploy [name] — Deploy all or a single webhook
|
|
7
|
+
* bankr webhooks list — List deployed webhooks
|
|
8
|
+
* bankr webhooks pause <name> — Pause a webhook
|
|
9
|
+
* bankr webhooks resume <name> — Resume a webhook
|
|
10
|
+
* bankr webhooks delete <name> — Delete a webhook
|
|
11
|
+
* bankr webhooks logs <name> — View recent invocations
|
|
12
|
+
* bankr webhooks env set KEY=VALUE — Set encrypted env var
|
|
13
|
+
* bankr webhooks env list — List env var names
|
|
14
|
+
* bankr webhooks env unset KEY — Remove env var
|
|
15
|
+
*/
|
|
16
|
+
export declare function webhooksInitCommand(): Promise<void>;
|
|
17
|
+
export type WebhookProvider = "slack" | "github" | "stripe" | "generic";
|
|
18
|
+
export declare function webhooksAddCommand(name: string, options?: {
|
|
19
|
+
provider?: string;
|
|
20
|
+
}): Promise<void>;
|
|
21
|
+
export declare function webhooksDeployCommand(name?: string): Promise<void>;
|
|
22
|
+
export declare function webhooksListCommand(): Promise<void>;
|
|
23
|
+
export declare function webhooksPauseResumeCommand(name: string, action: "pause" | "resume"): Promise<void>;
|
|
24
|
+
export declare function webhooksDeleteCommand(name: string): Promise<void>;
|
|
25
|
+
export declare function webhooksLogsCommand(name: string): Promise<void>;
|
|
26
|
+
export declare function webhooksEnvSetCommand(keyValue: string): Promise<void>;
|
|
27
|
+
export declare function webhooksEnvListCommand(): Promise<void>;
|
|
28
|
+
export declare function webhooksEnvUnsetCommand(key: string): Promise<void>;
|
|
29
|
+
//# sourceMappingURL=webhooks.d.ts.map
|