@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,455 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import os from "os";
|
|
3
|
+
import path from "path";
|
|
4
|
+
import fs from "fs/promises";
|
|
5
|
+
//#region src/config/commands.ts
|
|
6
|
+
const COMMANDS = [
|
|
7
|
+
{
|
|
8
|
+
name: "help",
|
|
9
|
+
description: "Show this help message"
|
|
10
|
+
},
|
|
11
|
+
{
|
|
12
|
+
name: "exit",
|
|
13
|
+
description: "Exit the CLI",
|
|
14
|
+
aliases: ["quit"]
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
name: "login",
|
|
18
|
+
description: "Authenticate with your B4M account"
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
name: "logout",
|
|
22
|
+
description: "Clear authentication and sign out"
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
name: "whoami",
|
|
26
|
+
description: "Show current authenticated user"
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
name: "save",
|
|
30
|
+
description: "Save current session",
|
|
31
|
+
args: "<name>"
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
name: "resume",
|
|
35
|
+
description: "List and resume saved sessions",
|
|
36
|
+
aliases: ["sessions"]
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
name: "config",
|
|
40
|
+
description: "Open interactive configuration editor"
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
name: "model",
|
|
44
|
+
description: "Switch the active model (opens a picker, or matches an id/name argument)",
|
|
45
|
+
args: "[id-or-name]"
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
name: "set-api",
|
|
49
|
+
description: "Connect to self-hosted Bike4Mind instance",
|
|
50
|
+
args: "<url>"
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
name: "reset-api",
|
|
54
|
+
description: "Reset to Bike4Mind main service"
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
name: "api-info",
|
|
58
|
+
description: "Show current API configuration"
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
name: "trust",
|
|
62
|
+
description: "Trust a tool, or `folder` to trust this project's repo config/agents/skills/MCP",
|
|
63
|
+
args: "<tool-name|folder>"
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
name: "untrust",
|
|
67
|
+
description: "Remove a tool from the trusted list, or `folder` to revoke project trust",
|
|
68
|
+
args: "<tool-name|folder>"
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
name: "trusted",
|
|
72
|
+
description: "Show folder-trust status and list all trusted tools"
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
name: "usage",
|
|
76
|
+
description: "Show credit usage and balance"
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
name: "clear",
|
|
80
|
+
description: "Start a new session",
|
|
81
|
+
aliases: ["new"]
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
name: "rewind",
|
|
85
|
+
description: "Rewind conversation to a previous point"
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
name: "undo",
|
|
89
|
+
description: "Undo the last file change"
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
name: "checkpoints",
|
|
93
|
+
description: "List available file restore points"
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
name: "restore",
|
|
97
|
+
description: "Restore files to a specific checkpoint",
|
|
98
|
+
args: "<number>"
|
|
99
|
+
},
|
|
100
|
+
{
|
|
101
|
+
name: "diff",
|
|
102
|
+
description: "Show diff between current state and a checkpoint",
|
|
103
|
+
args: "[number]"
|
|
104
|
+
},
|
|
105
|
+
{
|
|
106
|
+
name: "project-config",
|
|
107
|
+
description: "Show merged project configuration"
|
|
108
|
+
},
|
|
109
|
+
{
|
|
110
|
+
name: "commands",
|
|
111
|
+
description: "List all custom commands"
|
|
112
|
+
},
|
|
113
|
+
{
|
|
114
|
+
name: "commands:new",
|
|
115
|
+
description: "Create a new custom command",
|
|
116
|
+
args: "<name>"
|
|
117
|
+
},
|
|
118
|
+
{
|
|
119
|
+
name: "commands:reload",
|
|
120
|
+
description: "Reload custom commands from disk"
|
|
121
|
+
},
|
|
122
|
+
{
|
|
123
|
+
name: "mcp",
|
|
124
|
+
description: "Show MCP server status and connected tools",
|
|
125
|
+
aliases: ["mcp:list"]
|
|
126
|
+
},
|
|
127
|
+
{
|
|
128
|
+
name: "agents",
|
|
129
|
+
description: "List all available agents",
|
|
130
|
+
aliases: ["agents:list"]
|
|
131
|
+
},
|
|
132
|
+
{
|
|
133
|
+
name: "agents:new",
|
|
134
|
+
description: "Create a new agent definition",
|
|
135
|
+
args: "<name>"
|
|
136
|
+
},
|
|
137
|
+
{
|
|
138
|
+
name: "agents:reload",
|
|
139
|
+
description: "Reload agent definitions from disk"
|
|
140
|
+
},
|
|
141
|
+
{
|
|
142
|
+
name: "context",
|
|
143
|
+
description: "Show context window usage"
|
|
144
|
+
},
|
|
145
|
+
{
|
|
146
|
+
name: "compact",
|
|
147
|
+
description: "Compact conversation into new session",
|
|
148
|
+
args: "[instructions]"
|
|
149
|
+
},
|
|
150
|
+
{
|
|
151
|
+
name: "sandbox",
|
|
152
|
+
description: "Show sandbox status and configuration"
|
|
153
|
+
},
|
|
154
|
+
{
|
|
155
|
+
name: "sandbox:enable",
|
|
156
|
+
description: "Enable OS-level sandbox for bash commands"
|
|
157
|
+
},
|
|
158
|
+
{
|
|
159
|
+
name: "sandbox:disable",
|
|
160
|
+
description: "Disable sandbox mode"
|
|
161
|
+
},
|
|
162
|
+
{
|
|
163
|
+
name: "sandbox:mode",
|
|
164
|
+
description: "Set sandbox mode (auto-allow or permissions)",
|
|
165
|
+
args: "<auto-allow|permissions>"
|
|
166
|
+
},
|
|
167
|
+
{
|
|
168
|
+
name: "sandbox:network",
|
|
169
|
+
description: "Toggle sandbox network egress (off is fail-closed default)",
|
|
170
|
+
args: "<on|off>"
|
|
171
|
+
},
|
|
172
|
+
{
|
|
173
|
+
name: "sandbox:trust-domain",
|
|
174
|
+
description: "Add domain(s) to the network proxy allowlist",
|
|
175
|
+
args: "<domain> [...]"
|
|
176
|
+
},
|
|
177
|
+
{
|
|
178
|
+
name: "sandbox:domains",
|
|
179
|
+
description: "Show network proxy allowed domains"
|
|
180
|
+
},
|
|
181
|
+
{
|
|
182
|
+
name: "sandbox:violations",
|
|
183
|
+
description: "Show recent sandbox violations",
|
|
184
|
+
args: "[count]"
|
|
185
|
+
},
|
|
186
|
+
{
|
|
187
|
+
name: "sandbox:violations:clear",
|
|
188
|
+
description: "Clear all recorded sandbox violations"
|
|
189
|
+
},
|
|
190
|
+
{
|
|
191
|
+
name: "terminal-setup",
|
|
192
|
+
description: "Configure Shift+Enter for multi-line input"
|
|
193
|
+
},
|
|
194
|
+
{
|
|
195
|
+
name: "add-dir",
|
|
196
|
+
description: "Add a directory for file access",
|
|
197
|
+
args: "<path>"
|
|
198
|
+
},
|
|
199
|
+
{
|
|
200
|
+
name: "remove-dir",
|
|
201
|
+
description: "Remove a directory from file access",
|
|
202
|
+
args: "<path>"
|
|
203
|
+
},
|
|
204
|
+
{
|
|
205
|
+
name: "dirs",
|
|
206
|
+
description: "List all accessible directories"
|
|
207
|
+
},
|
|
208
|
+
{
|
|
209
|
+
name: "workflow",
|
|
210
|
+
description: "Show workflow overview or a specific section (decisions, blockers, handoff, review-gates; `gates` alias accepted)",
|
|
211
|
+
args: "[decisions|blockers|handoff|review-gates]"
|
|
212
|
+
},
|
|
213
|
+
{
|
|
214
|
+
name: "decisions",
|
|
215
|
+
description: "Show decision log for current session (alias for /workflow decisions)"
|
|
216
|
+
},
|
|
217
|
+
{
|
|
218
|
+
name: "blockers",
|
|
219
|
+
description: "Show tracked blockers for current session (alias for /workflow blockers)"
|
|
220
|
+
},
|
|
221
|
+
{
|
|
222
|
+
name: "review-gates",
|
|
223
|
+
description: "Show review gates for current session (alias for /workflow review-gates)"
|
|
224
|
+
},
|
|
225
|
+
{
|
|
226
|
+
name: "handoff",
|
|
227
|
+
description: "Show or generate the session handoff for cross-session continuity. Use --local for an LLM-free snapshot (works when rate-limited or offline). Alias for /workflow handoff.",
|
|
228
|
+
args: "[generate|--local]"
|
|
229
|
+
}
|
|
230
|
+
];
|
|
231
|
+
/**
|
|
232
|
+
* Get all command names including aliases
|
|
233
|
+
*/
|
|
234
|
+
function getAllCommandNames() {
|
|
235
|
+
const names = [];
|
|
236
|
+
for (const cmd of COMMANDS) {
|
|
237
|
+
names.push(cmd.name);
|
|
238
|
+
if (cmd.aliases) names.push(...cmd.aliases);
|
|
239
|
+
}
|
|
240
|
+
return names;
|
|
241
|
+
}
|
|
242
|
+
/**
|
|
243
|
+
* Checks if a command name is a built-in command
|
|
244
|
+
* @param name - Command name to check
|
|
245
|
+
* @returns true if the command is built-in
|
|
246
|
+
*/
|
|
247
|
+
function isBuiltInCommand(name) {
|
|
248
|
+
return getAllCommandNames().includes(name);
|
|
249
|
+
}
|
|
250
|
+
/**
|
|
251
|
+
* First-party feature-module slash commands (FeatureModuleRegistry). These are
|
|
252
|
+
* registered at runtime, not part of COMMANDS, so isBuiltInCommand misses them -
|
|
253
|
+
* yet a remote/custom skill that shadows one would hijack the feature. Kept as a
|
|
254
|
+
* static list because RemoteSkillSource filters at fetch time, before any registry
|
|
255
|
+
* is built. MUST STAY IN SYNC with the feature modules' getCommands() (tavern,
|
|
256
|
+
* hearth); a drift guard in commands.test.ts derives the names from those modules
|
|
257
|
+
* and fails if this list drifts.
|
|
258
|
+
*/
|
|
259
|
+
const RESERVED_FEATURE_COMMANDS = [
|
|
260
|
+
"tavern",
|
|
261
|
+
"quest",
|
|
262
|
+
"hearth"
|
|
263
|
+
];
|
|
264
|
+
/**
|
|
265
|
+
* Checks if a command name is reserved (a built-in OR a first-party feature command).
|
|
266
|
+
* Use this, not isBuiltInCommand, when deciding whether an external skill may register.
|
|
267
|
+
*
|
|
268
|
+
* `featureCommandNames` carries the LIVE feature/plugin command names from the
|
|
269
|
+
* runtime FeatureModuleRegistry - a dynamic superset of the static
|
|
270
|
+
* RESERVED_FEATURE_COMMANDS. Pass it so load, display, and dispatch share one
|
|
271
|
+
* reserved-name source; a repo-planted command whose name matches a runtime
|
|
272
|
+
* plugin command is otherwise hidden from display yet still loads and executes.
|
|
273
|
+
*/
|
|
274
|
+
function isReservedCommandName(name, featureCommandNames) {
|
|
275
|
+
return isBuiltInCommand(name) || RESERVED_FEATURE_COMMANDS.includes(name) || (featureCommandNames?.has(name) ?? false);
|
|
276
|
+
}
|
|
277
|
+
/**
|
|
278
|
+
* Point a command store's reserved-name gate at a feature registry's live
|
|
279
|
+
* command names, then prune any project command that loaded (pre-registry)
|
|
280
|
+
* under a name the registry now owns. Both index.tsx wiring sites (bootstrap and
|
|
281
|
+
* plugin hot-reload) call this, so load, display, and dispatch share one
|
|
282
|
+
* reserved-name source and the wiring is unit-testable in isolation.
|
|
283
|
+
*/
|
|
284
|
+
function wireReservedCommandNames(store, registry) {
|
|
285
|
+
store.setReservedNameSource(() => new Set(registry.getAllCommands().map((c) => c.name)));
|
|
286
|
+
store.pruneReservedProjectCommands();
|
|
287
|
+
}
|
|
288
|
+
/**
|
|
289
|
+
* Re-wire a CLI state's command store to a feature registry. Both index.tsx
|
|
290
|
+
* wiring sites (bootstrap and plugin hot-reload) call this with the live `state`,
|
|
291
|
+
* so the store-extraction step is pinned by a unit test instead of living
|
|
292
|
+
* untested inline at each call site (no test imports index.tsx).
|
|
293
|
+
*/
|
|
294
|
+
function rewireReservedNames(state, registry) {
|
|
295
|
+
wireReservedCommandNames(state.customCommandStore, registry);
|
|
296
|
+
}
|
|
297
|
+
/**
|
|
298
|
+
* Converts a CustomCommand to a CommandDefinition for unified handling
|
|
299
|
+
* @param customCommand - Custom command to convert
|
|
300
|
+
* @returns CommandDefinition compatible with autocomplete and help
|
|
301
|
+
*/
|
|
302
|
+
function customCommandToDefinition(customCommand) {
|
|
303
|
+
return {
|
|
304
|
+
name: customCommand.name,
|
|
305
|
+
description: customCommand.description,
|
|
306
|
+
args: customCommand.argumentHint,
|
|
307
|
+
source: customCommand.source
|
|
308
|
+
};
|
|
309
|
+
}
|
|
310
|
+
/**
|
|
311
|
+
* Merges custom commands and feature module commands with built-in commands.
|
|
312
|
+
* Built-in commands always take precedence (custom commands cannot override them).
|
|
313
|
+
*
|
|
314
|
+
* @param customCommands - Array of custom commands to merge
|
|
315
|
+
* @param featureCommands - Optional array of feature module command definitions
|
|
316
|
+
* @returns Combined array of all command definitions
|
|
317
|
+
*/
|
|
318
|
+
function mergeCommands(customCommands, featureCommands) {
|
|
319
|
+
const builtInCommands = COMMANDS.map((cmd) => ({
|
|
320
|
+
...cmd,
|
|
321
|
+
source: "built-in"
|
|
322
|
+
}));
|
|
323
|
+
const featureNames = new Set((featureCommands ?? []).map((cmd) => cmd.name));
|
|
324
|
+
const isReserved = (name) => isReservedCommandName(name, featureNames);
|
|
325
|
+
const customDefinitions = customCommands.filter((cmd) => !isReserved(cmd.name)).map(customCommandToDefinition);
|
|
326
|
+
const conflicts = customCommands.filter((cmd) => isReserved(cmd.name));
|
|
327
|
+
if (conflicts.length > 0) console.warn("Warning: The following custom commands have reserved names (a built-in or feature/plugin command) and will be ignored:", conflicts.map((cmd) => cmd.name).join(", "));
|
|
328
|
+
return [
|
|
329
|
+
...builtInCommands,
|
|
330
|
+
...featureCommands ?? [],
|
|
331
|
+
...customDefinitions
|
|
332
|
+
];
|
|
333
|
+
}
|
|
334
|
+
//#endregion
|
|
335
|
+
//#region src/storage/RemoteSkillSource.ts
|
|
336
|
+
const DEFAULT_FRESH_TTL_MS = 3e5;
|
|
337
|
+
const DEFAULT_PAGE_LIMIT = 100;
|
|
338
|
+
var RemoteSkillSource = class {
|
|
339
|
+
constructor(apiClient, options = {}) {
|
|
340
|
+
this.apiClient = apiClient;
|
|
341
|
+
this.forceNext = false;
|
|
342
|
+
this.cacheFilePath = options.cacheFilePath ?? path.join(os.homedir(), ".bike4mind", "cache", "remote-skills.json");
|
|
343
|
+
this.freshTtlMs = options.freshTtlMs ?? DEFAULT_FRESH_TTL_MS;
|
|
344
|
+
this.pageLimit = options.pageLimit ?? DEFAULT_PAGE_LIMIT;
|
|
345
|
+
this.includeImports = options.includeImports ?? false;
|
|
346
|
+
}
|
|
347
|
+
/** Make the next `fetchSkills` go to the network even inside the TTL. The cache stays as the
|
|
348
|
+
* offline fallback. For a host that just changed the account's skills itself. */
|
|
349
|
+
invalidate() {
|
|
350
|
+
this.forceNext = true;
|
|
351
|
+
}
|
|
352
|
+
/**
|
|
353
|
+
* Returns the user's remote skills mapped to `CustomCommand`. Uses an
|
|
354
|
+
* in-memory + on-disk cache to avoid a network round-trip on every
|
|
355
|
+
* `/commands` invocation and to keep working offline.
|
|
356
|
+
*/
|
|
357
|
+
async fetchSkills() {
|
|
358
|
+
const cached = await this.readCache();
|
|
359
|
+
const forced = this.forceNext;
|
|
360
|
+
this.forceNext = false;
|
|
361
|
+
if (cached && !forced && this.isFresh(cached)) return this.mapAndFilter(cached.skills);
|
|
362
|
+
try {
|
|
363
|
+
const skills = (await this.apiClient.get(`/api/skills?limit=${this.pageLimit}`))?.data ?? [];
|
|
364
|
+
if (skills.length > 0 || !cached || cached.skills.length === 0) await this.writeCache({
|
|
365
|
+
fetchedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
366
|
+
skills
|
|
367
|
+
});
|
|
368
|
+
return this.mapAndFilter(skills);
|
|
369
|
+
} catch (error) {
|
|
370
|
+
if (process.env.BIKE4MIND_CLI_DEBUG) console.warn("[RemoteSkillSource] fetch failed, falling back to cache:", error instanceof Error ? error.message : String(error));
|
|
371
|
+
if (cached) return this.mapAndFilter(cached.skills);
|
|
372
|
+
return [];
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
/**
|
|
376
|
+
* Map server skills to `CustomCommand`, dropping any that must not register:
|
|
377
|
+
* - a name that shadows a built-in OR a first-party feature slash command
|
|
378
|
+
* (tavern/quest/hearth), which would otherwise hijack the command, and
|
|
379
|
+
* - a foreign-owned skill (a `userId` other than the current user's), which is
|
|
380
|
+
* only visible cross-account via `isGlobalRead`, and
|
|
381
|
+
* - a Skill Market import: the user owns the copy but someone else wrote it, and local commands run
|
|
382
|
+
* as trusted instructions next to shell/file tools (the web app wraps it as untrusted instead).
|
|
383
|
+
* With `includeImports` it is kept as `untrusted: true`, for a host that wraps it the same way.
|
|
384
|
+
* Own, system (no `userId`), and org-scoped (server-gated, no `userId`) skills pass.
|
|
385
|
+
*
|
|
386
|
+
* Unauthenticated contract (fail-closed, intentional): when getCurrentUser()
|
|
387
|
+
* returns null, currentUserId is undefined and every skill carrying a userId -
|
|
388
|
+
* including the user's OWN - is dropped. This over-restricts rather than risk
|
|
389
|
+
* registering a foreign skill against an unverifiable identity; own skills
|
|
390
|
+
* reappear once authenticated.
|
|
391
|
+
*/
|
|
392
|
+
async mapAndFilter(skills) {
|
|
393
|
+
const currentUserId = (await this.apiClient.getCurrentUser?.())?.id;
|
|
394
|
+
return skills.filter((skill) => {
|
|
395
|
+
if (isReservedCommandName(skill.name)) return false;
|
|
396
|
+
if (skill.userId && skill.userId !== currentUserId) return false;
|
|
397
|
+
if (skill.importedFrom && !this.includeImports) return false;
|
|
398
|
+
return true;
|
|
399
|
+
}).map((skill) => this.toCustomCommand(skill));
|
|
400
|
+
}
|
|
401
|
+
/** For tests and the `/commands:reload` handler - clears the on-disk cache. */
|
|
402
|
+
async clearCache() {
|
|
403
|
+
try {
|
|
404
|
+
await fs.unlink(this.cacheFilePath);
|
|
405
|
+
} catch (error) {
|
|
406
|
+
if (error.code !== "ENOENT") throw error;
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
isFresh(cache) {
|
|
410
|
+
const fetchedAt = Date.parse(cache.fetchedAt);
|
|
411
|
+
if (Number.isNaN(fetchedAt)) return false;
|
|
412
|
+
return Date.now() - fetchedAt < this.freshTtlMs;
|
|
413
|
+
}
|
|
414
|
+
async readCache() {
|
|
415
|
+
try {
|
|
416
|
+
const raw = await fs.readFile(this.cacheFilePath, "utf-8");
|
|
417
|
+
const parsed = JSON.parse(raw);
|
|
418
|
+
if (parsed && typeof parsed === "object" && "fetchedAt" in parsed && "skills" in parsed && Array.isArray(parsed.skills)) return parsed;
|
|
419
|
+
return null;
|
|
420
|
+
} catch (error) {
|
|
421
|
+
if (error.code === "ENOENT") return null;
|
|
422
|
+
if (process.env.BIKE4MIND_CLI_DEBUG) console.warn("[RemoteSkillSource] cache read failed:", error instanceof Error ? error.message : String(error));
|
|
423
|
+
return null;
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
async writeCache(snapshot) {
|
|
427
|
+
try {
|
|
428
|
+
await fs.mkdir(path.dirname(this.cacheFilePath), {
|
|
429
|
+
recursive: true,
|
|
430
|
+
mode: 448
|
|
431
|
+
});
|
|
432
|
+
await fs.writeFile(this.cacheFilePath, JSON.stringify(snapshot, null, 2), {
|
|
433
|
+
encoding: "utf-8",
|
|
434
|
+
mode: 384
|
|
435
|
+
});
|
|
436
|
+
} catch (error) {
|
|
437
|
+
if (process.env.BIKE4MIND_CLI_DEBUG) console.warn("[RemoteSkillSource] cache write failed:", error instanceof Error ? error.message : String(error));
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
toCustomCommand(skill) {
|
|
441
|
+
return {
|
|
442
|
+
name: skill.name,
|
|
443
|
+
description: skill.description,
|
|
444
|
+
body: skill.body,
|
|
445
|
+
argumentHint: skill.argumentHint,
|
|
446
|
+
allowedTools: skill.allowedTools,
|
|
447
|
+
disableModelInvocation: skill.disableModelInvocation,
|
|
448
|
+
...skill.importedFrom ? { untrusted: true } : {},
|
|
449
|
+
source: "remote",
|
|
450
|
+
filePath: `b4m:/api/skills/${skill.id}`
|
|
451
|
+
};
|
|
452
|
+
}
|
|
453
|
+
};
|
|
454
|
+
//#endregion
|
|
455
|
+
export { rewireReservedNames as a, mergeCommands as i, COMMANDS as n, isReservedCommandName as r, RemoteSkillSource as t };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
2
|
+
import { Vt as tokenEstimateMultiplier } from "./ConfigStore-BwqsQ0U9.mjs";
|
|
3
3
|
import { v4 } from "uuid";
|
|
4
4
|
import { get_encoding } from "tiktoken";
|
|
5
5
|
//#region ../../b4m-core/utils/dist/calibratedTokenizer.mjs
|