@bankr/cli 0.2.13 → 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 +27 -0
- package/dist/cli.js +145 -0
- 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 +32 -1
- package/dist/commands/login.js +4 -2
- package/dist/commands/webhooks.d.ts +29 -0
- package/dist/commands/webhooks.js +657 -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
|
@@ -848,6 +848,10 @@ export async function setupClaudeCodeCommand() {
|
|
|
848
848
|
console.log();
|
|
849
849
|
output.dim("All Claude Code requests will route through the Bankr gateway.");
|
|
850
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.");
|
|
851
855
|
}
|
|
852
856
|
/* ─────────────────────── Shared launcher helpers ────────────────────────── */
|
|
853
857
|
function requireAuth() {
|
|
@@ -887,11 +891,38 @@ function fileContains(filePath, search) {
|
|
|
887
891
|
}
|
|
888
892
|
}
|
|
889
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
|
+
}
|
|
890
920
|
export async function claudeCommand(args) {
|
|
891
921
|
const llmKey = requireAuth();
|
|
892
922
|
const llmUrl = getLlmUrl();
|
|
923
|
+
const translated = translateClaudeModelArgs(args);
|
|
893
924
|
output.dim(`Launching Claude Code via ${llmUrl}`);
|
|
894
|
-
return launchTool("claude",
|
|
925
|
+
return launchTool("claude", translated, { ANTHROPIC_BASE_URL: llmUrl, ANTHROPIC_AUTH_TOKEN: llmKey }, "https://docs.anthropic.com/en/docs/claude-code");
|
|
895
926
|
}
|
|
896
927
|
/* ────────────────────────── bankr llm opencode ───────────────────────────── */
|
|
897
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
|
|
@@ -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
|