@bike4mind/cli 0.18.4 → 0.20.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.
Files changed (41) hide show
  1. package/LICENSE +17 -3
  2. package/README.md +204 -35
  3. package/bin/bike4mind-cli.mjs +137 -24
  4. package/bin/hearth-hook.mjs +292 -0
  5. package/dist/AgentHistoryStore-C8uUKjjC.mjs +35512 -0
  6. package/dist/ApiClient-B_CQrUiF.mjs +277 -0
  7. package/dist/{ConfigStore-Cq20962p.mjs → ConfigStore-DD3DcC3-.mjs} +6256 -3911
  8. package/dist/{ImageStore-BVmEG1xc.mjs → ImageStore-kVo-oHoS.mjs} +2 -2
  9. package/dist/PluginStore-DwvOJ-G3.mjs +206 -0
  10. package/dist/ProxyManager-Bqr7Lmsd.mjs +3 -0
  11. package/dist/{ProxyManager-CV94yZUW.mjs → ProxyManager-C5H0pUyK.mjs} +2 -2
  12. package/dist/{SandboxOrchestrator-BS6gALNq.mjs → SandboxOrchestrator-BFPVpmB5.mjs} +1 -1
  13. package/dist/{SandboxOrchestrator-BoINxbX4.mjs → SandboxOrchestrator-C8uleDn2.mjs} +7 -7
  14. package/dist/ShellSessionManager-6o8KZzl1-vrbPAUTq.mjs +252 -0
  15. package/dist/{ViolationLogStore-B-plqJfn.mjs → ViolationLogStore-byEhxa2A.mjs} +1 -1
  16. package/dist/WorkItemsClient-Cow6nXx7.mjs +382 -0
  17. package/dist/{bashExecute-B1N1lMOS-TZVDbcQ4.mjs → bashExecute-CrdPpBqk-DCATrE-D.mjs} +116 -16
  18. package/dist/buildAgent-mVuXU_H4.mjs +824 -0
  19. package/dist/commands/acpCommand.mjs +798 -0
  20. package/dist/commands/apiCommand.mjs +14 -16
  21. package/dist/commands/doctorCommand.mjs +5 -5
  22. package/dist/commands/envCommand.mjs +1 -1
  23. package/dist/commands/headlessCommand.mjs +272 -76
  24. package/dist/commands/mcpCommand.mjs +14 -1
  25. package/dist/commands/pluginCommand.mjs +232 -0
  26. package/dist/commands/updateCommand.mjs +10 -9
  27. package/dist/{grepSearch-DJs-cubo-Bm0Y8oS3.mjs → grepSearch-BaYUfIYs-C-fxWc9G.mjs} +3 -3
  28. package/dist/index.mjs +3281 -2322
  29. package/dist/{package-CBaK53NX.mjs → package-BqKSCbso.mjs} +1 -1
  30. package/dist/serve-CuF0I5en.mjs +772 -0
  31. package/dist/store-BG3e54c8.mjs +3 -0
  32. package/dist/{store-DV5s-qni.mjs → store-CvjTpQPs.mjs} +70 -3
  33. package/dist/{terminalSetup-BbJt04ZG.mjs → terminalSetup-DjXAwpDy.mjs} +2 -3
  34. package/dist/{treeSitterEngine-BRbQ9b7I.mjs → treeSitterEngine-QBE3YkmG.mjs} +51 -1
  35. package/dist/{updateChecker-C8xsNY2L.mjs → updateChecker-CQW8bxo6.mjs} +10 -10
  36. package/package.json +48 -43
  37. package/dist/BackgroundAgentManager-DOesheMD.mjs +0 -27171
  38. package/dist/ProxyManager-ByuAHFMq.mjs +0 -3
  39. package/dist/store-DgzCTRkN.mjs +0 -3
  40. package/dist/utils-Cdktpk_k.mjs +0 -158
  41. package/dist/utils-DEizxshI.mjs +0 -3
@@ -0,0 +1,232 @@
1
+ #!/usr/bin/env node
2
+ import { n as getDefaultPluginsDir, r as isFeatureEnabled, t as PluginStore } from "../PluginStore-DwvOJ-G3.mjs";
3
+ import { t as ConfigStore } from "../ConfigStore-DD3DcC3-.mjs";
4
+ import { execFileSync } from "child_process";
5
+ import { promises } from "fs";
6
+ import path from "path";
7
+ //#region src/commands/pluginCommand.ts
8
+ /**
9
+ * External plugin commands (b4m plugin list, b4m plugin add, b4m plugin remove).
10
+ * These run outside the interactive CLI session. Plugins install to
11
+ * ~/.bike4mind/plugins via npm --prefix; discovery/validation lives in
12
+ * src/plugins/PluginStore.ts and loading in src/features/loadPlugin.ts.
13
+ */
14
+ const NPM_TIMEOUT_MS = 12e4;
15
+ /**
16
+ * Conservative allowlist over npm's spec grammar: bare and scoped package
17
+ * names (with optional @version/@tag), github:user/repo (optionally #ref),
18
+ * user/repo shorthand, and file:<path>. Defense-in-depth on top of the
19
+ * arg-vector exec - never the primary injection control.
20
+ */
21
+ function validatePluginSpec(spec) {
22
+ const dangerous = /^file:/i.test(spec) ? /[;&|<>`$(){}!*?[\]'"%]/ : /[;&|<>`$(){}\\!*?[\]'"%]/;
23
+ if (!spec || /\s/.test(spec) || dangerous.test(spec)) return false;
24
+ return [
25
+ /^[a-z0-9][a-z0-9~._-]*(@[a-z0-9~._^>=<-]+)?$/i,
26
+ /^@[a-z0-9][a-z0-9~._-]*\/[a-z0-9][a-z0-9~._-]*(@[a-z0-9~._^>=<-]+)?$/i,
27
+ /^github:[a-z0-9][a-z0-9._-]*\/[a-z0-9][a-z0-9._-]*(#[a-z0-9._/-]+)?$/i,
28
+ /^[a-z0-9][a-z0-9._-]*\/[a-z0-9][a-z0-9._-]*(#[a-z0-9._/-]+)?$/i,
29
+ /^file:.+$/i
30
+ ].some((pattern) => pattern.test(spec));
31
+ }
32
+ /** Map a user-supplied name to an installed plugin: configKey, package name, then short name. */
33
+ function resolvePluginByName(descriptors, name) {
34
+ const exact = descriptors.find((d) => d.valid && d.configKey === name || d.name === name);
35
+ if (exact) return { plugin: exact };
36
+ const shortMatches = descriptors.filter((d) => {
37
+ const bare = d.name.replace(/^@[^/]+\//, "");
38
+ return bare === name || bare === `b4m-plugin-${name}`;
39
+ });
40
+ if (shortMatches.length === 1) return { plugin: shortMatches[0] };
41
+ if (shortMatches.length > 1) return { candidates: shortMatches };
42
+ return {};
43
+ }
44
+ function formatPluginList(descriptors, config) {
45
+ if (descriptors.length === 0) return [
46
+ "No plugins installed.",
47
+ "",
48
+ "Install one with:",
49
+ " b4m plugin add <npm-package>",
50
+ " b4m plugin add github:user/repo",
51
+ " b4m plugin add file:/path/to/local/plugin"
52
+ ].join("\n");
53
+ const lines = [];
54
+ for (const descriptor of descriptors) if (descriptor.valid) {
55
+ const enabled = isFeatureEnabled(config.features, descriptor.configKey);
56
+ lines.push(`• ${descriptor.name}@${descriptor.version} - ${enabled ? "✅ Enabled" : "⏸️ Disabled"} (key: ${descriptor.configKey})`);
57
+ if (descriptor.description) lines.push(` ${descriptor.description}`);
58
+ } else lines.push(`• ${descriptor.name} - ⚠️ ${descriptor.reason}`);
59
+ return lines.join("\n");
60
+ }
61
+ /**
62
+ * Run npm with the given args, or print a friendly message and exit. `action`
63
+ * ('install'/'remove') only shapes the error text.
64
+ */
65
+ /**
66
+ * Quote npm args for the Windows shell path. npm.cmd needs `shell: true`, under
67
+ * which Node joins args with spaces and hands them to cmd.exe - so a homedir
68
+ * path with a metachar (`&`, space, ...) would be split or reinterpreted.
69
+ * Double-quote every arg (escaping embedded `"` as `""`) to keep it literal. A
70
+ * literal `%` in the homedir still env-expands (a pathological username, out of
71
+ * scope); the user-supplied spec - the real untrusted input - already rejects
72
+ * `%`. No-op on posix, where execFileSync runs shell-free.
73
+ */
74
+ function quoteNpmArgs(args, platform = process.platform) {
75
+ if (platform !== "win32") return args;
76
+ return args.map((a) => `"${a.replace(/"/g, "\"\"")}"`);
77
+ }
78
+ function runNpmOrExit(args, cwd, action, target) {
79
+ try {
80
+ const isWindows = process.platform === "win32";
81
+ execFileSync(isWindows ? "npm.cmd" : "npm", quoteNpmArgs(args), {
82
+ cwd,
83
+ stdio: "inherit",
84
+ timeout: NPM_TIMEOUT_MS,
85
+ shell: isWindows
86
+ });
87
+ } catch (error) {
88
+ if (error.code === "ENOENT") console.error(`❌ npm is required to ${action} plugins. Install Node.js/npm and retry.`);
89
+ else console.error(`❌ ${action === "install" ? "Install" : "Uninstall"} failed for ${target} - see npm output above.`);
90
+ process.exit(1);
91
+ }
92
+ }
93
+ /** Top-level dependency names recorded in the plugins-dir package.json. */
94
+ async function readTopLevelDeps(pluginsDir) {
95
+ try {
96
+ const raw = JSON.parse(await promises.readFile(path.join(pluginsDir, "package.json"), "utf-8"));
97
+ return new Set(Object.keys(raw.dependencies ?? {}));
98
+ } catch {
99
+ return /* @__PURE__ */ new Set();
100
+ }
101
+ }
102
+ async function ensurePluginsDir(pluginsDir) {
103
+ await promises.mkdir(pluginsDir, { recursive: true });
104
+ const manifestPath = path.join(pluginsDir, "package.json");
105
+ try {
106
+ await promises.access(manifestPath);
107
+ } catch {
108
+ await promises.writeFile(manifestPath, JSON.stringify({
109
+ name: "b4m-plugins",
110
+ private: true,
111
+ version: "0.0.0"
112
+ }, null, 2));
113
+ }
114
+ }
115
+ async function handleAdd(spec, pluginsDir, configStore) {
116
+ if (!validatePluginSpec(spec)) {
117
+ console.error(`❌ Unsupported plugin spec: ${spec}`);
118
+ console.error("Supported forms: <npm-package>, @scope/<package>, github:user/repo, file:<path>");
119
+ process.exit(1);
120
+ }
121
+ await ensurePluginsDir(pluginsDir);
122
+ const store = new PluginStore({ pluginsDir });
123
+ const before = new Set((await store.discover()).map((d) => d.name));
124
+ const depsBefore = await readTopLevelDeps(pluginsDir);
125
+ runNpmOrExit([
126
+ "install",
127
+ "--prefix",
128
+ pluginsDir,
129
+ "--no-fund",
130
+ "--no-audit",
131
+ spec
132
+ ], pluginsDir, "install", spec);
133
+ const added = (await store.discover()).filter((d) => !before.has(d.name));
134
+ const newTopLevel = new Set([...await readTopLevelDeps(pluginsDir)].filter((d) => !depsBefore.has(d)));
135
+ if (added.length === 0) {
136
+ if (newTopLevel.size > 0) {
137
+ console.log(`✅ Installed ${spec}.`);
138
+ console.warn("⚠️ It has no \"b4m-plugin\" manifest, so it will not load as a plugin.");
139
+ } else console.log(`✅ Installed ${spec}. No new plugin was enabled (it may already be installed).`);
140
+ console.log("Run `b4m plugin list` to see installed plugins and their enabled state.");
141
+ return;
142
+ }
143
+ for (const descriptor of added) {
144
+ if (!descriptor.valid) {
145
+ console.warn(`⚠️ ${descriptor.name} installed, but its manifest is invalid: ${descriptor.reason}`);
146
+ console.warn("It will not load until the manifest is fixed.");
147
+ continue;
148
+ }
149
+ if (!newTopLevel.has(descriptor.name)) {
150
+ console.warn(`ℹ️ ${descriptor.name} is a dependency and carries a plugin manifest; not enabling it.`);
151
+ console.warn(` Enable it explicitly with: b4m plugin add ${descriptor.name}`);
152
+ continue;
153
+ }
154
+ const config = await configStore.load();
155
+ await configStore.save({
156
+ ...config,
157
+ features: {
158
+ ...config.features,
159
+ [descriptor.configKey]: true
160
+ }
161
+ });
162
+ console.log(`✅ Installed ${descriptor.name}@${descriptor.version} (plugin key: ${descriptor.configKey})`);
163
+ console.log(`Enabled feature "${descriptor.configKey}". It will load next time you start b4m.`);
164
+ }
165
+ }
166
+ async function handleRemove(name, pluginsDir, configStore) {
167
+ const descriptors = await new PluginStore({ pluginsDir }).discover();
168
+ const { plugin, candidates } = resolvePluginByName(descriptors, name);
169
+ if (candidates) {
170
+ console.error(`❌ "${name}" is ambiguous. Matches: ${candidates.map((c) => c.name).join(", ")}`);
171
+ process.exit(1);
172
+ }
173
+ if (!plugin) {
174
+ console.error(`❌ No installed plugin matches "${name}".`);
175
+ if (descriptors.length > 0) console.error(`Installed: ${descriptors.map((d) => d.name).join(", ")}`);
176
+ process.exit(1);
177
+ return;
178
+ }
179
+ runNpmOrExit([
180
+ "uninstall",
181
+ "--prefix",
182
+ pluginsDir,
183
+ plugin.name
184
+ ], pluginsDir, "remove", plugin.name);
185
+ if (plugin.valid) {
186
+ const config = await configStore.load();
187
+ await configStore.save({
188
+ ...config,
189
+ features: {
190
+ ...config.features,
191
+ [plugin.configKey]: false
192
+ }
193
+ });
194
+ }
195
+ console.log(`✅ Removed plugin ${plugin.name}${plugin.valid ? ` and disabled "${plugin.configKey}"` : ""}.`);
196
+ }
197
+ async function handleList(pluginsDir, configStore) {
198
+ const descriptors = await new PluginStore({ pluginsDir }).discover();
199
+ const config = await configStore.load();
200
+ console.log("🔌 Plugins");
201
+ console.log("");
202
+ console.log(formatPluginList(descriptors, config));
203
+ }
204
+ async function handlePluginCommand(subcommand, argv) {
205
+ const configStore = new ConfigStore();
206
+ const pluginsDir = getDefaultPluginsDir();
207
+ switch (subcommand) {
208
+ case "list":
209
+ await handleList(pluginsDir, configStore);
210
+ break;
211
+ case "add":
212
+ if (!argv.spec) {
213
+ console.error("❌ Usage: b4m plugin add <spec>");
214
+ process.exit(1);
215
+ }
216
+ await handleAdd(argv.spec, pluginsDir, configStore);
217
+ break;
218
+ case "remove":
219
+ if (!argv.name) {
220
+ console.error("❌ Usage: b4m plugin remove <name>");
221
+ process.exit(1);
222
+ }
223
+ await handleRemove(argv.name, pluginsDir, configStore);
224
+ break;
225
+ default:
226
+ console.error(`❌ Unknown plugin subcommand: ${subcommand}`);
227
+ console.error("Available: list, add <spec>, remove <name>");
228
+ process.exit(1);
229
+ }
230
+ }
231
+ //#endregion
232
+ export { formatPluginList, handleAdd, handlePluginCommand, handleRemove, quoteNpmArgs, resolvePluginByName, validatePluginSpec };
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
- import { t as version } from "../package-CBaK53NX.mjs";
3
- import { c as isNpmPrefixWritable, l as setAutoUpdatePreference, n as REEXEC_GUARD_ENV, o as forceCheckForUpdate, r as checkForUpdate, s as getAutoUpdatePreference, t as INSTALL_CMD, u as shouldAttemptAutoUpdate } from "../updateChecker-C8xsNY2L.mjs";
2
+ import { t as version } from "../package-BqKSCbso.mjs";
3
+ import { c as isNpmPrefixWritable, l as setAutoUpdatePreference, n as REEXEC_GUARD_ENV, o as forceCheckForUpdate, r as checkForUpdate, s as getAutoUpdatePreference, t as INSTALL_CMD, u as shouldAttemptAutoUpdate } from "../updateChecker-CQW8bxo6.mjs";
4
4
  import { t as checkRipgrep } from "../ripgrepCheck-BmkyTK2i.mjs";
5
5
  import { execSync, spawnSync } from "child_process";
6
6
  import { createInterface } from "readline";
@@ -28,7 +28,7 @@ function runGlobalInstall() {
28
28
  /**
29
29
  * Ask the user how to handle an available update (the `'ask'` preference).
30
30
  * Plain readline (not Ink) because this runs in the bin bootstrap before the
31
- * code-split app loads the only window in which it's safe to install.
31
+ * code-split app loads - the only window in which it's safe to install.
32
32
  *
33
33
  * Maps the [U/a/s/n] keys to a choice; an empty line defaults to `'update'`
34
34
  * (capital `U` in the prompt is the default). The interface is fully torn down
@@ -65,7 +65,7 @@ async function promptUpdateChoice(currentVersion, latestVersion) {
65
65
  }
66
66
  /**
67
67
  * Install the latest version and re-exec into it so the session the user just
68
- * opened runs the new code zero version skew, no mid-session file-swap risk.
68
+ * opened runs the new code - zero version skew, no mid-session file-swap risk.
69
69
  *
70
70
  * spawnSync inherits the TTY so Ink renders normally in the child; the guard
71
71
  * env prevents an update loop. npm install overwrites the global package in
@@ -95,14 +95,14 @@ function installAndReexec(currentVersion, latestVersion) {
95
95
  * Auto-update on launch (Claude-Code-style), consent-first.
96
96
  *
97
97
  * Called from the bin bootstrap on the interactive path *before* the
98
- * code-split app (`dist/index.mjs`) is imported the only safe install window
98
+ * code-split app (`dist/index.mjs`) is imported - the only safe install window
99
99
  * (running an install while dist chunks are loaded would crash them).
100
100
  *
101
101
  * Behaviour by `autoUpdate` preference once an update is available on a
102
102
  * writable prefix:
103
- * - `'auto'` install silently and re-exec into the new version.
104
- * - `'never'` do nothing (the startup notify banner still informs).
105
- * - `'ask'` prompt the user: Update once / Always (persist `auto`) /
103
+ * - `'auto'` -> install silently and re-exec into the new version.
104
+ * - `'never'` -> do nothing (the startup notify banner still informs).
105
+ * - `'ask'` -> prompt the user: Update once / Always (persist `auto`) /
106
106
  * Skip (ask again next launch) / Never (persist `never`).
107
107
  *
108
108
  * It is a safe no-op (returns without installing) when: already re-exec'd this
@@ -112,7 +112,8 @@ function installAndReexec(currentVersion, latestVersion) {
112
112
  * user is never blocked from launching the current version.
113
113
  */
114
114
  async function maybeAutoUpdateOnLaunch() {
115
- if (!shouldAttemptAutoUpdate({ isTTY: Boolean(process.stdin.isTTY && process.stdout.isTTY) })) return;
115
+ const isInteractive = Boolean(process.stdin.isTTY && process.stdout.isTTY);
116
+ if (!shouldAttemptAutoUpdate({ isTTY: isInteractive })) return;
116
117
  const preference = await getAutoUpdatePreference();
117
118
  if (preference === "never") return;
118
119
  const currentVersion = version;
@@ -5,7 +5,7 @@ import { existsSync } from "fs";
5
5
  import path from "path";
6
6
  import { stat } from "fs/promises";
7
7
  import { promisify } from "util";
8
- //#region ../../b4m-core/services/dist/grepSearch-DJs-cubo.mjs
8
+ //#region ../../b4m-core/services/dist/grepSearch-BaYUfIYs.mjs
9
9
  const execFileAsync = promisify(execFile);
10
10
  /** Cached ripgrep binary path after first resolution */
11
11
  let cachedRgPath = null;
@@ -13,7 +13,7 @@ let cachedRgPath = null;
13
13
  * Resolve ripgrep binary path via the package's exported `rgPath`.
14
14
  * `@vscode/ripgrep` is an optional dependency, so we load it lazily; in 1.18+
15
15
  * the binary lives in a platform-specific sibling package and is resolved by
16
- * the package itself we just have to ask for it.
16
+ * the package itself - we just have to ask for it.
17
17
  */
18
18
  async function getRipgrepPath() {
19
19
  if (cachedRgPath) return cachedRgPath;
@@ -67,7 +67,7 @@ async function searchFiles(params, allowedDirectories) {
67
67
  rgArgs.push(pattern, targetDir);
68
68
  let stdout;
69
69
  try {
70
- stdout = (await execFileAsync(rgPath, rgArgs, { maxBuffer: 50 * 1024 * 1024 })).stdout;
70
+ stdout = (await execFileAsync(rgPath, rgArgs, { maxBuffer: 52428800 })).stdout;
71
71
  } catch (error) {
72
72
  const execError = error;
73
73
  if (execError.code === 1 && execError.stdout) stdout = execError.stdout;