@bike4mind/cli 3.3.0 → 4.0.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 +139 -37
- package/bin/bike4mind-cli.mjs +55 -1
- package/bin/hearth-hook.mjs +53 -7
- package/dist/{AgentHistoryStore-CLidbOHG.mjs → AgentHistoryStore-DgqmcVLN.mjs} +5597 -5568
- package/dist/ApiClient-_JrM_xWw.mjs +133 -0
- package/dist/{ConfigStore-DUATzLxB.mjs → ConfigStore-BwqsQ0U9.mjs} +1409 -189
- package/dist/RemoteSkillSource-qTvRkaNA.mjs +455 -0
- package/dist/{WorkItemsClient-BSoOblGp.mjs → WorkItemsClient-CERTDrhT.mjs} +1 -1
- package/dist/{buildAgent-DyZWK0LW.mjs → buildToolSearch-D9GEK91u.mjs} +406 -47
- package/dist/commands/acpCommand.mjs +13 -13
- package/dist/commands/apiCommand.mjs +1 -1
- package/dist/commands/doctorCommand.mjs +1 -1
- package/dist/commands/envCommand.mjs +1 -1
- package/dist/commands/headlessCommand.mjs +5 -4
- package/dist/commands/mcpCommand.mjs +4 -3
- package/dist/commands/pluginCommand.mjs +1 -1
- package/dist/commands/skillsCommand.mjs +259 -0
- package/dist/commands/updateCommand.mjs +1 -1
- package/dist/{dist-DPP7UmK9.mjs → dist-Ul2qwn5x.mjs} +17 -132
- package/dist/index.mjs +171 -48
- package/dist/{package-DYPVrada.mjs → package-CQaam4Dq.mjs} +1 -1
- package/dist/{serve-w6v3rZiR.mjs → serve-CFxUhjml.mjs} +657 -27
- package/dist/store-BYeE0shc.mjs +2 -0
- package/dist/{store-BxmPEI3v.mjs → store-BbvSi3KK.mjs} +26 -2
- package/package.json +11 -10
- package/dist/store-BkMur6uk.mjs +0 -2
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { nn as splitSkillMdFrontmatter, p as requireApiUrl, t as ConfigStore, tn as readSkillMdSourceUrl } from "../ConfigStore-BwqsQ0U9.mjs";
|
|
3
|
+
import { r as isReservedCommandName, t as RemoteSkillSource } from "../RemoteSkillSource-qTvRkaNA.mjs";
|
|
4
|
+
import { t as ApiClient } from "../ApiClient-_JrM_xWw.mjs";
|
|
5
|
+
import { promises } from "fs";
|
|
6
|
+
import os from "os";
|
|
7
|
+
import path from "path";
|
|
8
|
+
import { createInterface } from "readline";
|
|
9
|
+
import YAML from "yaml";
|
|
10
|
+
const THIRD_PARTY_LINE = new RegExp(`^\\s*#\\s*Third-party skill from the Bike4Mind Skill Market - review before use`, "m");
|
|
11
|
+
const ARGUMENT_HINT_LINE = /^(argument-hint:[ \t]*)([^\r\n]+?)[ \t]*$/gm;
|
|
12
|
+
function quoteRawArgumentHint(frontmatter) {
|
|
13
|
+
return frontmatter.replace(ARGUMENT_HINT_LINE, (line, key, raw) => {
|
|
14
|
+
if (/^["'|>]/.test(raw)) return line;
|
|
15
|
+
const hint = (raw.startsWith("[") || raw.startsWith("<") ? raw.replace(/([\]>])[ \t]+#.*$/, "$1") : raw.replace(/[ \t]+#.*$/, "")).trimEnd();
|
|
16
|
+
return hint.startsWith("[") || hint.startsWith("<") || hint.includes(": ") ? `${key}${JSON.stringify(hint)}` : line;
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
const toText = (value) => typeof value === "string" || typeof value === "number" || typeof value === "boolean" ? String(value) : void 0;
|
|
20
|
+
/** Parses a SKILL.md into skill form fields. Never throws; a malformed file yields `ok: false`. */
|
|
21
|
+
function parseSkillMd(input) {
|
|
22
|
+
const text = input.replace(/\r\n?/g, "\n");
|
|
23
|
+
const split = splitSkillMdFrontmatter(text);
|
|
24
|
+
if (!split) {
|
|
25
|
+
if (/^\uFEFF?---/.test(text)) return {
|
|
26
|
+
ok: false,
|
|
27
|
+
error: "The frontmatter block has no closing --- line."
|
|
28
|
+
};
|
|
29
|
+
return {
|
|
30
|
+
ok: true,
|
|
31
|
+
fields: { body: stripBodyNewlines(text.replace(/^\uFEFF/, "")) },
|
|
32
|
+
dropped: []
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
if (split.lang && split.lang !== "yaml" && split.lang !== "yml") return {
|
|
36
|
+
ok: false,
|
|
37
|
+
error: `Unsupported frontmatter language "${split.lang}"; only YAML is supported.`
|
|
38
|
+
};
|
|
39
|
+
let data;
|
|
40
|
+
try {
|
|
41
|
+
const frontmatter = quoteRawArgumentHint(split.frontmatter);
|
|
42
|
+
data = YAML.parse(frontmatter) ?? {};
|
|
43
|
+
} catch (error) {
|
|
44
|
+
return {
|
|
45
|
+
ok: false,
|
|
46
|
+
error: `Invalid YAML frontmatter: ${error instanceof Error ? error.message : String(error)}`
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
if (typeof data !== "object" || data === null || Array.isArray(data)) return {
|
|
50
|
+
ok: false,
|
|
51
|
+
error: "The frontmatter must be a set of key: value pairs."
|
|
52
|
+
};
|
|
53
|
+
const fields = { body: stripBodyNewlines(split.rest) };
|
|
54
|
+
const dropped = [];
|
|
55
|
+
for (const [key, value] of Object.entries(data)) switch (key) {
|
|
56
|
+
case "name":
|
|
57
|
+
fields.name = toText(value);
|
|
58
|
+
break;
|
|
59
|
+
case "description":
|
|
60
|
+
fields.description = toText(value);
|
|
61
|
+
break;
|
|
62
|
+
case "argument-hint":
|
|
63
|
+
fields.argumentHint = toText(value);
|
|
64
|
+
break;
|
|
65
|
+
case "disable-model-invocation":
|
|
66
|
+
fields.disableModelInvocation = fields.disableModelInvocation || value === true || value === "true";
|
|
67
|
+
break;
|
|
68
|
+
case "autoinvoke":
|
|
69
|
+
fields.disableModelInvocation = fields.disableModelInvocation || value === false || value === "false";
|
|
70
|
+
break;
|
|
71
|
+
default: dropped.push(key);
|
|
72
|
+
}
|
|
73
|
+
const url = readSkillMdSourceUrl(split.frontmatter);
|
|
74
|
+
return {
|
|
75
|
+
ok: true,
|
|
76
|
+
fields: Object.fromEntries(Object.entries(fields).filter(([, v]) => v !== void 0)),
|
|
77
|
+
dropped,
|
|
78
|
+
...url !== void 0 && { source: {
|
|
79
|
+
url,
|
|
80
|
+
thirdParty: THIRD_PARTY_LINE.test(split.frontmatter)
|
|
81
|
+
} }
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
/** Drops the blank line after the closing fence and the single trailing newline toSkillMd adds. */
|
|
85
|
+
function stripBodyNewlines(rest) {
|
|
86
|
+
return rest.replace(/^\r?\n/, "").replace(/\r?\n$/, "");
|
|
87
|
+
}
|
|
88
|
+
//#endregion
|
|
89
|
+
//#region src/commands/skillsCommand.ts
|
|
90
|
+
/**
|
|
91
|
+
* External skills command (b4m skills push <path>).
|
|
92
|
+
* Imports a Claude Code SKILL.md (or commands/*.md) as the user's own
|
|
93
|
+
* Bike4Mind skill. Runs outside the interactive CLI session. Validation is
|
|
94
|
+
* left to the server (apps/client/server/utils/skillValidation.ts).
|
|
95
|
+
*/
|
|
96
|
+
const MAX_LISTED_FILES = 10;
|
|
97
|
+
const DESCRIPTION_MAX = 500;
|
|
98
|
+
/** The server requires a description, but many Claude Code commands/*.md files have none. */
|
|
99
|
+
function descriptionFor(fields) {
|
|
100
|
+
if (fields.description?.trim()) return fields.description;
|
|
101
|
+
return ((fields.body ?? "").split("\n").map((line) => line.replace(/^#+\s*/, "").trim()).find(Boolean) ?? "").slice(0, DESCRIPTION_MAX);
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Built from an explicit field list (never a spread of parsed data) so
|
|
105
|
+
* allowedTools can't reach the server: Claude Code tool names don't map to B4M.
|
|
106
|
+
* disableModelInvocation is always sent so an update mirrors the file.
|
|
107
|
+
*/
|
|
108
|
+
function buildSkillPayload(fields, fallbackName) {
|
|
109
|
+
return {
|
|
110
|
+
name: (fields.name?.trim() || fallbackName).trim().toLowerCase(),
|
|
111
|
+
description: descriptionFor(fields),
|
|
112
|
+
body: fields.body ?? "",
|
|
113
|
+
...fields.argumentHint !== void 0 && { argumentHint: fields.argumentHint },
|
|
114
|
+
disableModelInvocation: fields.disableModelInvocation ?? false
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
/** Resolves <dir>, <dir>/SKILL.md or any .md file and parses it. */
|
|
118
|
+
async function readSkillFile(input) {
|
|
119
|
+
const expanded = input === "~" || input.startsWith("~/") ? path.join(os.homedir(), input.slice(1)) : input;
|
|
120
|
+
const resolved = path.resolve(expanded);
|
|
121
|
+
let stat;
|
|
122
|
+
try {
|
|
123
|
+
stat = await promises.stat(resolved);
|
|
124
|
+
} catch {
|
|
125
|
+
throw new Error(`No such file or folder: ${input}`);
|
|
126
|
+
}
|
|
127
|
+
let filePath;
|
|
128
|
+
let fallbackName;
|
|
129
|
+
if (stat.isDirectory()) {
|
|
130
|
+
filePath = path.join(resolved, "SKILL.md");
|
|
131
|
+
fallbackName = path.basename(resolved);
|
|
132
|
+
} else if (stat.isFile() && resolved.toLowerCase().endsWith(".md")) {
|
|
133
|
+
filePath = resolved;
|
|
134
|
+
fallbackName = path.basename(resolved).toLowerCase() === "skill.md" ? path.basename(path.dirname(resolved)) : path.basename(resolved, path.extname(resolved));
|
|
135
|
+
} else throw new Error(`Not a skill: ${input} - pass a skill folder, its SKILL.md, or a .md file.`);
|
|
136
|
+
let text;
|
|
137
|
+
try {
|
|
138
|
+
text = await promises.readFile(filePath, "utf8");
|
|
139
|
+
} catch {
|
|
140
|
+
throw new Error(stat.isDirectory() ? "No SKILL.md in that folder." : `Cannot read ${input}`);
|
|
141
|
+
}
|
|
142
|
+
const parsed = parseSkillMd(text);
|
|
143
|
+
if (!parsed.ok) throw new Error(parsed.error);
|
|
144
|
+
const name = path.basename(filePath);
|
|
145
|
+
const extraFiles = name.toLowerCase() === "skill.md" ? (await promises.readdir(path.dirname(filePath))).filter((f) => f !== name && !f.startsWith(".")).sort() : [];
|
|
146
|
+
return {
|
|
147
|
+
payload: buildSkillPayload(parsed.fields, fallbackName),
|
|
148
|
+
dropped: parsed.dropped,
|
|
149
|
+
...parsed.source && { source: parsed.source },
|
|
150
|
+
extraFiles
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
function serverMessage(error) {
|
|
154
|
+
const err = error;
|
|
155
|
+
return err?.response?.data?.error ?? err?.response?.data?.message ?? err?.message ?? String(error);
|
|
156
|
+
}
|
|
157
|
+
/** The stamp is plain comment text, so only a same-origin URL is presented as provenance. */
|
|
158
|
+
function isSameOrigin(url, apiOrigin) {
|
|
159
|
+
try {
|
|
160
|
+
return apiOrigin !== void 0 && new URL(url).origin === new URL(apiOrigin).origin;
|
|
161
|
+
} catch {
|
|
162
|
+
return false;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Prints the notices, applies the third-party and name-conflict gates, then
|
|
167
|
+
* creates or updates the skill. Nothing is sent to the server before the
|
|
168
|
+
* third-party gate passes. Throws on any exit-1 condition.
|
|
169
|
+
*/
|
|
170
|
+
async function pushSkill(file, deps) {
|
|
171
|
+
const { payload, dropped, source, extraFiles } = file;
|
|
172
|
+
const { api, log } = deps;
|
|
173
|
+
if (dropped.length > 0) log(`Ignored (not supported in Bike4Mind skills): ${dropped.join(", ")}`);
|
|
174
|
+
if (extraFiles.length > 0) log(`Not uploaded: supporting files (${extraFiles.slice(0, MAX_LISTED_FILES).join(", ")}${extraFiles.length > MAX_LISTED_FILES ? ` and ${extraFiles.length - MAX_LISTED_FILES} more` : ""}) - only SKILL.md is pushed.`);
|
|
175
|
+
if (isReservedCommandName(payload.name)) log(`Note: /${payload.name} is a built-in B4M CLI command, so the CLI won't list this skill (the web still will).`);
|
|
176
|
+
if (source) {
|
|
177
|
+
log(isSameOrigin(source.url, deps.apiOrigin) ? `This skill came from Bike4Mind: ${source.url}` : `This file has a Bike4Mind source stamp from another site: ${source.url}`);
|
|
178
|
+
if (source.thirdParty) {
|
|
179
|
+
log("This is another author's Skill Market skill - import it from the market instead to keep updates and attribution.");
|
|
180
|
+
if (!deps.yes) {
|
|
181
|
+
if (!deps.isTTY) throw new Error("Re-run with --yes to push another author's skill as your own.");
|
|
182
|
+
if (!await deps.confirm("Push it as your own skill anyway? [y/N] ")) return "aborted";
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
try {
|
|
187
|
+
const me = await api.getCurrentUser();
|
|
188
|
+
if (!me) throw new Error("Not authenticated. Run `b4m /login` to authenticate.");
|
|
189
|
+
let existing;
|
|
190
|
+
for (let page = 1; !existing; page++) {
|
|
191
|
+
const { data, hasMore } = await api.get("/api/skills", { params: {
|
|
192
|
+
query: payload.name,
|
|
193
|
+
limit: 100,
|
|
194
|
+
page
|
|
195
|
+
} });
|
|
196
|
+
existing = data.find((s) => s.name === payload.name && s.userId === me.id);
|
|
197
|
+
if (!hasMore) break;
|
|
198
|
+
}
|
|
199
|
+
if (!existing) {
|
|
200
|
+
await api.post("/api/skills", payload);
|
|
201
|
+
return "created";
|
|
202
|
+
}
|
|
203
|
+
if (existing.importedFrom) throw new Error(`"${payload.name}" is the name of a Skill Market skill you imported - rename the local skill or delete the import first.`);
|
|
204
|
+
if (!deps.update) {
|
|
205
|
+
if (!deps.isTTY) throw new Error(`A skill named "${payload.name}" already exists - re-run with --update to overwrite it.`);
|
|
206
|
+
if (!await deps.confirm(`A skill named "${payload.name}" already exists. Update it? [y/N] `)) return "aborted";
|
|
207
|
+
}
|
|
208
|
+
await api.put(`/api/skills/${existing.id}`, {
|
|
209
|
+
...payload,
|
|
210
|
+
argumentHint: payload.argumentHint ?? ""
|
|
211
|
+
});
|
|
212
|
+
return "updated";
|
|
213
|
+
} catch (error) {
|
|
214
|
+
throw new Error(serverMessage(error));
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
async function confirm(question) {
|
|
218
|
+
const rl = createInterface({
|
|
219
|
+
input: process.stdin,
|
|
220
|
+
output: process.stdout
|
|
221
|
+
});
|
|
222
|
+
try {
|
|
223
|
+
const answer = await new Promise((resolve) => rl.question(question, resolve));
|
|
224
|
+
return /^y(es)?$/i.test(answer.trim());
|
|
225
|
+
} finally {
|
|
226
|
+
rl.close();
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
async function handleSkillsCommand(subcommand, argv) {
|
|
230
|
+
if (subcommand !== "push") throw new Error(`Unknown skills subcommand: ${subcommand ?? "(none)"}`);
|
|
231
|
+
if (!argv.path) throw new Error("Usage: b4m skills push <path>");
|
|
232
|
+
const file = await readSkillFile(argv.path);
|
|
233
|
+
const configStore = new ConfigStore();
|
|
234
|
+
const config = await configStore.load();
|
|
235
|
+
if (!await configStore.getAuthTokens()) throw new Error("Not authenticated. Run `b4m /login` to authenticate.");
|
|
236
|
+
const apiUrl = requireApiUrl(config.apiConfig);
|
|
237
|
+
const apiClient = new ApiClient(apiUrl, configStore);
|
|
238
|
+
const result = await pushSkill(file, {
|
|
239
|
+
api: apiClient,
|
|
240
|
+
confirm,
|
|
241
|
+
isTTY: Boolean(process.stdin.isTTY),
|
|
242
|
+
update: Boolean(argv.update),
|
|
243
|
+
yes: Boolean(argv.yes),
|
|
244
|
+
apiOrigin: apiUrl,
|
|
245
|
+
log: (line) => console.log(line)
|
|
246
|
+
});
|
|
247
|
+
if (result === "aborted") {
|
|
248
|
+
console.log("Aborted");
|
|
249
|
+
return;
|
|
250
|
+
}
|
|
251
|
+
try {
|
|
252
|
+
await new RemoteSkillSource(apiClient).clearCache();
|
|
253
|
+
} catch (error) {
|
|
254
|
+
console.warn(`Skill saved, but the local skills cache could not be cleared: ${serverMessage(error)}`);
|
|
255
|
+
}
|
|
256
|
+
console.log(`${result === "created" ? "Created" : "Updated"} skill /${file.payload.name}`);
|
|
257
|
+
}
|
|
258
|
+
//#endregion
|
|
259
|
+
export { buildSkillPayload, handleSkillsCommand, pushSkill, readSkillFile };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { t as version } from "../package-
|
|
2
|
+
import { t as version } from "../package-CQaam4Dq.mjs";
|
|
3
3
|
import { a as forceCheckForUpdate, c as isNpmPrefixWritable, d as shouldAttemptAutoUpdate, l as readCachedUpdate, n as REEXEC_GUARD_ENV, o as getAutoUpdatePreference, t as INSTALL_CMD, u as setAutoUpdatePreference } from "../updateChecker-CCLVVdH0.mjs";
|
|
4
4
|
import { t as checkRipgrep } from "../ripgrepCheck-BmkyTK2i.mjs";
|
|
5
5
|
import { execSync, spawnSync } from "child_process";
|
|
@@ -1,136 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { b as logger, g as DeviceFlowClient, h as AuthenticatedApiClient, t as ConfigStore } from "./ConfigStore-DUATzLxB.mjs";
|
|
3
|
-
import { t as version } from "./package-DYPVrada.mjs";
|
|
4
|
-
import { isAxiosError } from "axios";
|
|
5
|
-
import { Readable } from "node:stream";
|
|
6
2
|
import { createParser } from "eventsource-parser";
|
|
7
|
-
//#region src/auth/OAuthClient.ts
|
|
8
|
-
/**
|
|
9
|
-
* The CLI's RFC 8628 `client_id`; the desktop app registers under its own. Typed against the
|
|
10
|
-
* server allowlist so dropping this id there fails this build rather than this client's logins
|
|
11
|
-
* (see OAUTH_DEVICE_CLIENT_IDS).
|
|
12
|
-
*/
|
|
13
|
-
const CLI_OAUTH_CLIENT_ID = "b4m-cli";
|
|
14
|
-
/**
|
|
15
|
-
* The shared device-flow client, pinned to the CLI's registration.
|
|
16
|
-
*/
|
|
17
|
-
var OAuthClient = class extends DeviceFlowClient {
|
|
18
|
-
constructor(baseURL = "http://localhost:3000") {
|
|
19
|
-
super({
|
|
20
|
-
baseUrl: baseURL,
|
|
21
|
-
clientId: CLI_OAUTH_CLIENT_ID
|
|
22
|
-
});
|
|
23
|
-
}
|
|
24
|
-
};
|
|
25
|
-
//#endregion
|
|
26
|
-
//#region src/auth/providerKeyFailure.ts
|
|
27
|
-
const PROVIDER_KEY_ERROR_CODES = /* @__PURE__ */ new Set(["provider_not_configured", "provider_rejected"]);
|
|
28
|
-
/**
|
|
29
|
-
* A 401 whose body names a missing or rejected AI-provider key (e.g. /api/ai/tts). The
|
|
30
|
-
* Bike4Mind credential was accepted, so refreshing or re-authenticating cannot fix it.
|
|
31
|
-
* Shared by the ApiClient refresh interceptor and mapApiError in mcp/b4mApiClient.ts.
|
|
32
|
-
*/
|
|
33
|
-
function isProviderKeyFailure(data) {
|
|
34
|
-
const body = decodeBinaryBody(data);
|
|
35
|
-
if (!body || typeof body !== "object") return false;
|
|
36
|
-
const { errorCode } = body;
|
|
37
|
-
return typeof errorCode === "string" && PROVIDER_KEY_ERROR_CODES.has(errorCode);
|
|
38
|
-
}
|
|
39
|
-
/**
|
|
40
|
-
* An arraybuffer-typed request (every @bike4mind/sdk call, through ApiClient.fetch) gets its
|
|
41
|
-
* error body as raw bytes, and the refresh interceptor sees it before any caller can decode it.
|
|
42
|
-
*/
|
|
43
|
-
function decodeBinaryBody(data) {
|
|
44
|
-
const bytes = data instanceof ArrayBuffer ? Buffer.from(data) : ArrayBuffer.isView(data) ? Buffer.from(data.buffer, data.byteOffset, data.byteLength) : void 0;
|
|
45
|
-
if (!bytes) return data;
|
|
46
|
-
try {
|
|
47
|
-
return JSON.parse(bytes.toString("utf8"));
|
|
48
|
-
} catch {
|
|
49
|
-
return;
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
//#endregion
|
|
53
|
-
//#region src/auth/ApiClient.ts
|
|
54
|
-
const USER_AGENT = `b4m-cli/${version}`;
|
|
55
|
-
/**
|
|
56
|
-
* Per-request timeout. Generous by design: a waited chat (POST /api/chat with wait:true)
|
|
57
|
-
* runs a full server-side quest, so the bound only exists to stop a hung backend from
|
|
58
|
-
* wedging a caller forever, not to cap a normal long quest. Override with B4M_API_TIMEOUT_MS
|
|
59
|
-
* (0 disables the timeout entirely).
|
|
60
|
-
*/
|
|
61
|
-
const DEFAULT_API_TIMEOUT_MS = 6e5;
|
|
62
|
-
function resolveTimeoutMs() {
|
|
63
|
-
const raw = process.env.B4M_API_TIMEOUT_MS;
|
|
64
|
-
if (raw === void 0 || raw.trim() === "") return DEFAULT_API_TIMEOUT_MS;
|
|
65
|
-
const parsed = Number(raw);
|
|
66
|
-
return Number.isFinite(parsed) && parsed >= 0 ? parsed : DEFAULT_API_TIMEOUT_MS;
|
|
67
|
-
}
|
|
68
|
-
/**
|
|
69
|
-
* The shared authenticated client, wired to the CLI's storage, logger and branding.
|
|
70
|
-
*/
|
|
71
|
-
var ApiClient = class extends AuthenticatedApiClient {
|
|
72
|
-
/**
|
|
73
|
-
* @param apiKey - When set, requests authenticate with this instance API key via
|
|
74
|
-
* the `x-api-key` header and the OAuth-JWT path (Bearer injection + refresh-on-401)
|
|
75
|
-
* is bypassed entirely. Omit to keep the default stored-JWT behavior unchanged.
|
|
76
|
-
*/
|
|
77
|
-
constructor(baseURL = "http://localhost:3000", configStore, apiKey) {
|
|
78
|
-
super({
|
|
79
|
-
baseUrl: baseURL,
|
|
80
|
-
tokenStore: configStore || new ConfigStore(),
|
|
81
|
-
oauthClient: new OAuthClient(baseURL),
|
|
82
|
-
logger,
|
|
83
|
-
apiKey,
|
|
84
|
-
timeoutMs: resolveTimeoutMs(),
|
|
85
|
-
headers: {
|
|
86
|
-
"User-Agent": USER_AGENT,
|
|
87
|
-
"X-B4M-Client": USER_AGENT
|
|
88
|
-
},
|
|
89
|
-
reauthMessages: {
|
|
90
|
-
refreshFailed: "Authentication expired. Please run `b4m login` again.",
|
|
91
|
-
stillUnauthorized: "Authentication failed. Please run /login to authenticate.",
|
|
92
|
-
notLoggedIn: "Authentication failed: not logged in. Please run `b4m login` to authenticate."
|
|
93
|
-
},
|
|
94
|
-
isPassThrough401: isProviderKeyFailure
|
|
95
|
-
});
|
|
96
|
-
this.fetch = async (input, init = {}) => {
|
|
97
|
-
const headers = Object.fromEntries(new Headers(init.headers));
|
|
98
|
-
try {
|
|
99
|
-
return toFetchResponse(await this.getAxiosInstance().request({
|
|
100
|
-
url: input instanceof Request ? input.url : String(input),
|
|
101
|
-
method: init.method ?? "GET",
|
|
102
|
-
headers,
|
|
103
|
-
data: init.body ?? void 0,
|
|
104
|
-
signal: init.signal ?? void 0,
|
|
105
|
-
responseType: /text\/event-stream/i.test(headers.accept ?? "") ? "stream" : "arraybuffer",
|
|
106
|
-
...init.redirect && init.redirect !== "follow" ? { maxRedirects: 0 } : {}
|
|
107
|
-
}));
|
|
108
|
-
} catch (error) {
|
|
109
|
-
if (isAxiosError(error) && error.response) return toFetchResponse(error.response);
|
|
110
|
-
throw error;
|
|
111
|
-
}
|
|
112
|
-
};
|
|
113
|
-
}
|
|
114
|
-
get baseURL() {
|
|
115
|
-
return this.getAxiosInstance().defaults.baseURL ?? "";
|
|
116
|
-
}
|
|
117
|
-
};
|
|
118
|
-
function toFetchResponse(response) {
|
|
119
|
-
const headers = new Headers();
|
|
120
|
-
for (const [name, value] of Object.entries(response.headers)) if (value !== void 0 && value !== null) headers.set(name, Array.isArray(value) ? value.join(", ") : String(value));
|
|
121
|
-
const body = [
|
|
122
|
-
101,
|
|
123
|
-
204,
|
|
124
|
-
205,
|
|
125
|
-
304
|
|
126
|
-
].includes(response.status) ? null : response.data instanceof Readable ? Readable.toWeb(response.data) : response.data;
|
|
127
|
-
return new Response(body, {
|
|
128
|
-
status: response.status,
|
|
129
|
-
statusText: response.statusText,
|
|
130
|
-
headers
|
|
131
|
-
});
|
|
132
|
-
}
|
|
133
|
-
//#endregion
|
|
134
3
|
//#region ../sdk/dist/index.mjs
|
|
135
4
|
/**
|
|
136
5
|
* A non-2xx response. `message` is the envelope's `error` (`ErrorResponse` in the spec); branch on `status` and
|
|
@@ -255,6 +124,10 @@ const operations = {
|
|
|
255
124
|
method: "POST",
|
|
256
125
|
path: "/api/v1/sessions"
|
|
257
126
|
},
|
|
127
|
+
createTextRefinement: {
|
|
128
|
+
method: "POST",
|
|
129
|
+
path: "/api/v1/text-refinements"
|
|
130
|
+
},
|
|
258
131
|
createTranscription: {
|
|
259
132
|
method: "POST",
|
|
260
133
|
path: "/api/v1/transcriptions"
|
|
@@ -331,6 +204,14 @@ const operations = {
|
|
|
331
204
|
method: "GET",
|
|
332
205
|
path: "/api/v1/artifacts/{id}/versions/{version}"
|
|
333
206
|
},
|
|
207
|
+
getClaudeMarketplace: {
|
|
208
|
+
method: "GET",
|
|
209
|
+
path: "/api/v1/skills/market/claude-marketplace.json"
|
|
210
|
+
},
|
|
211
|
+
getClaudePluginZip: {
|
|
212
|
+
method: "GET",
|
|
213
|
+
path: "/api/v1/skills/market/{id}/claude-plugin.zip"
|
|
214
|
+
},
|
|
334
215
|
getCreditBalance: {
|
|
335
216
|
method: "GET",
|
|
336
217
|
path: "/api/v1/credits"
|
|
@@ -371,6 +252,10 @@ const operations = {
|
|
|
371
252
|
method: "GET",
|
|
372
253
|
path: "/api/v1/video-generations/{id}"
|
|
373
254
|
},
|
|
255
|
+
getWhatsNewMedia: {
|
|
256
|
+
method: "GET",
|
|
257
|
+
path: "/api/v1/whats-new/media/{id}/{asset}"
|
|
258
|
+
},
|
|
374
259
|
listAgents: {
|
|
375
260
|
method: "GET",
|
|
376
261
|
path: "/api/v1/agents"
|
|
@@ -736,4 +621,4 @@ function toBase64(bytes) {
|
|
|
736
621
|
return btoa(binary);
|
|
737
622
|
}
|
|
738
623
|
//#endregion
|
|
739
|
-
export {
|
|
624
|
+
export { parseRetryAfterSeconds as i, B4mStreamEndedError as n, createClient as r, B4mApiError as t };
|