beecork 2.5.0 → 2.5.2

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 (3) hide show
  1. package/README.md +6 -5
  2. package/dist/index.js +70 -17
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -77,11 +77,12 @@ The agent can delegate an open-ended investigation to a **read-only sub-agent**
77
77
  (reading, searching, browsing the web) in a separate context and returns just a summary — keeping the
78
78
  main conversation clean. It cannot modify anything, run commands, or recurse.
79
79
 
80
- ### Status line
80
+ ### Pinned UI + status line
81
81
 
82
- An optional bar on the terminal's bottom row shows `model · effort · git branch · ~tokens · background
83
- tasks`. It's **off by default** while a proper pinned-input version is built; opt into the interim one
84
- with `STATUSLINE=1`.
82
+ On an interactive terminal, beecork pins a persistent input box and a rich status line
83
+ (`mode · model · effort · git branch · ~tokens · background tasks`) to the bottom, with the
84
+ conversation scrolling above (Claude-Code style). Shift+Tab rotates the mode. This is **on by default**;
85
+ opt out with `STATUSLINE=0` to use the classic inline editor. Piped/non-TTY input is unaffected either way.
85
86
 
86
87
  ### Skipping permissions (danger)
87
88
 
@@ -125,7 +126,7 @@ All variables are read from the real shell environment only (never a project fil
125
126
  | `VERIFY_COMMAND` | Command auto-run after edits (e.g. `npm run typecheck`) | — |
126
127
  | `AUTO_APPROVE` | Headless: skip approval prompts (out-of-root/risky shell are still hard-denied) | off |
127
128
  | `BEECORK_DANGEROUSLY_SKIP_PERMISSIONS` | Sandbox-only: skip the whole gate (also `--dangerously-skip-permissions`) | off |
128
- | `STATUSLINE` / `STATUSLINE_REFRESH_MS` | Bottom status bar on/off · refresh interval | on · `2000` |
129
+ | `STATUSLINE` / `STATUSLINE_REFRESH_MS` | Pinned UI + status bar (set `0` to opt out) · refresh interval | on · `2000` |
129
130
  | `NO_UPDATE_NOTIFIER` / `CI` | Disable the "update available" check | off |
130
131
  | `MAX_STEPS` | Max tool steps per turn | `50` |
131
132
  | `EXEC_TIMEOUT_MS` | `run_bash` timeout | `30000` |
package/dist/index.js CHANGED
@@ -114,10 +114,11 @@ var config = {
114
114
  // Sub-agent (explore tool)
115
115
  subAgentMaxSteps: num("SUBAGENT_MAX_STEPS", 15),
116
116
  // child explorer's step budget (bounds cost/latency)
117
- // Live status line (bottom row: model · effort · git branch · ~tokens · bg tasks). DEFAULT OFF —
118
- // the interim scroll-region version clashes with the inline input (banner loss / flicker); it's
119
- // being replaced by a proper pinned-input UI. Opt in with STATUSLINE=1.
120
- statuslineEnabled: ["1", "true", "on", "yes"].includes((process.env.STATUSLINE ?? "").trim().toLowerCase()),
117
+ // Pinned bottom UI: a persistent input box + a rich statusline (mode · model · effort · branch ·
118
+ // ~tokens · bg tasks), the conversation scrolling above. DEFAULT ON for interactive TTYs; opt out
119
+ // with STATUSLINE=0 (falls back to the classic inline editor). Non-TTY (piped/CI) is unaffected
120
+ // either way chromeEnabled() also requires a TTY.
121
+ statuslineEnabled: !["0", "false", "off", "no"].includes((process.env.STATUSLINE ?? "").trim().toLowerCase()),
121
122
  statuslineRefreshMs: num("STATUSLINE_REFRESH_MS", 2e3),
122
123
  // bar refresh interval
123
124
  // Integrations / modes
@@ -135,9 +136,10 @@ var config = {
135
136
  };
136
137
 
137
138
  // src/update.ts
138
- import { readFile, writeFile, mkdir } from "node:fs/promises";
139
+ import { readFile, writeFile, mkdir, realpath } from "node:fs/promises";
139
140
  import { homedir as homedir2 } from "node:os";
140
- import { join, dirname } from "node:path";
141
+ import { join, dirname, basename, delimiter } from "node:path";
142
+ import { fileURLToPath } from "node:url";
141
143
  import { spawn } from "node:child_process";
142
144
  var CHECK_INTERVAL_MS = 24 * 60 * 60 * 1e3;
143
145
  var cacheFile = () => join(homedir2(), ".beecork", "update-check.json");
@@ -149,6 +151,48 @@ async function currentVersion() {
149
151
  return "0.0.0";
150
152
  }
151
153
  }
154
+ function runningPkgRoot() {
155
+ return fileURLToPath(new URL("..", import.meta.url));
156
+ }
157
+ function prefixFromPkgRoot(pkgRoot) {
158
+ const nm = dirname(pkgRoot);
159
+ if (basename(nm) !== "node_modules") return null;
160
+ const up = dirname(nm);
161
+ return basename(up) === "lib" ? dirname(up) : up;
162
+ }
163
+ function installPrefix() {
164
+ return prefixFromPkgRoot(runningPkgRoot());
165
+ }
166
+ async function findShadowingInstalls() {
167
+ let running;
168
+ try {
169
+ running = await realpath(runningPkgRoot());
170
+ } catch {
171
+ running = runningPkgRoot();
172
+ }
173
+ const names = process.platform === "win32" ? ["beecork.cmd", "beecork"] : ["beecork"];
174
+ const seenRoots = /* @__PURE__ */ new Set([running]);
175
+ const others = [];
176
+ for (const dir of (process.env.PATH ?? "").split(delimiter).filter(Boolean)) {
177
+ for (const name of names) {
178
+ try {
179
+ const bin = join(dir, name);
180
+ const root = dirname(dirname(await realpath(bin)));
181
+ if (seenRoots.has(root)) continue;
182
+ seenRoots.add(root);
183
+ const pkg = JSON.parse(await readFile(join(root, "package.json"), "utf8"));
184
+ if (String(pkg.name) === "beecork") others.push({ bin, version: String(pkg.version ?? "?").replace(/[^\w.+-]/g, "") });
185
+ } catch {
186
+ }
187
+ }
188
+ }
189
+ return others;
190
+ }
191
+ async function shadowWarning() {
192
+ const others = await findShadowingInstalls();
193
+ if (!others.length) return null;
194
+ return "\u26A0 Another beecork is on your PATH and may shadow the version you just installed:\n" + others.map((o) => ` ${o.bin} (v${o.version})`).join("\n") + "\n Remove the stale one(s) so `beecork` always runs the newest install (may need sudo):\n" + others.map((o) => ` rm ${o.bin}`).join("\n");
195
+ }
152
196
  function isNewer(a, b) {
153
197
  const pa = a.split(".").map((n) => parseInt(n, 10) || 0);
154
198
  const pb = b.split(".").map((n) => parseInt(n, 10) || 0);
@@ -195,7 +239,9 @@ async function checkForUpdate(current) {
195
239
  function selfUpdate(timeoutMs = 12e4) {
196
240
  return new Promise((resolve2) => {
197
241
  const npm = process.platform === "win32" ? "npm.cmd" : "npm";
198
- const child = spawn(npm, ["install", "-g", "beecork@latest"], { stdio: ["ignore", "pipe", "pipe"] });
242
+ const prefix = installPrefix();
243
+ const args = ["install", "-g", "beecork@latest", ...prefix ? ["--prefix", prefix] : []];
244
+ const child = spawn(npm, args, { stdio: ["ignore", "pipe", "pipe"] });
199
245
  let out3 = "", done = false;
200
246
  const finish = (r) => {
201
247
  if (done) return;
@@ -494,8 +540,9 @@ function markLines(width) {
494
540
  }
495
541
  return lines.filter((l) => l.length > 0);
496
542
  }
497
- function printBanner(model, sources) {
543
+ function printBanner(model, version, sources) {
498
544
  const safeModel = stripControl(model);
545
+ const safeVersion = stripControl(version);
499
546
  const word = [
500
547
  " _ _ ",
501
548
  " | |__ ___ ___ ___ ___ _ __| | __",
@@ -527,7 +574,7 @@ function printBanner(model, sources) {
527
574
  const mem = sources.filter((s) => s.endsWith("memory.md"));
528
575
  const cwd = tildify(process.cwd());
529
576
  const rows2 = [
530
- ["", "\u{1F41D} a tiny CLI coding agent"],
577
+ ["", `\u{1F41D} a tiny CLI coding agent \xB7 v${safeVersion}`],
531
578
  ["dir", cwd],
532
579
  ["model", safeModel],
533
580
  ["cork.md", cork.length ? cork.join(", ") : "none"],
@@ -1307,7 +1354,7 @@ async function runExplorer(task, focus, signal) {
1307
1354
 
1308
1355
  // src/safety.ts
1309
1356
  import { homedir as homedir3 } from "node:os";
1310
- import { basename } from "node:path";
1357
+ import { basename as basename2 } from "node:path";
1311
1358
  function pathGuard(args) {
1312
1359
  const { abs, inRoot } = resolveInRoot(String(args.path ?? "."));
1313
1360
  return inRoot ? {} : { needsApproval: true, reason: `path is outside the project root: ${abs}`, cacheKey: `path:${abs}` };
@@ -1315,7 +1362,7 @@ function pathGuard(args) {
1315
1362
  var SECRET_FILE = /(^|\/)(\.env(\.[\w.-]+)?|[\w.-]*\.(env|pem|key|secret|pfx|p12|jks|keystore)|id_(rsa|ed25519|ecdsa|dsa)|credentials|\.git-credentials|\.pgpass|\.npmrc|\.netrc)$/i;
1316
1363
  function isSecretPath(userPath) {
1317
1364
  const { abs } = resolveInRoot(userPath);
1318
- return SECRET_FILE.test(abs) || SECRET_FILE.test(basename(abs));
1365
+ return SECRET_FILE.test(abs) || SECRET_FILE.test(basename2(abs));
1319
1366
  }
1320
1367
  function secretGuard(args) {
1321
1368
  const p = pathGuard(args);
@@ -3664,8 +3711,11 @@ async function handleCommand(input, messages) {
3664
3711
  } else if (cmd === "/update") {
3665
3712
  console.log(color.dim("updating beecork\u2026 (npm install -g beecork@latest)"));
3666
3713
  const { ok, output } = await selfUpdate();
3667
- if (ok) console.log(color.green("\u2713 updated \u2014 restart beecork to use the new version.") + "\n");
3668
- else {
3714
+ if (ok) {
3715
+ console.log(color.green("\u2713 updated \u2014 restart beecork to use the new version.") + "\n");
3716
+ const w = await shadowWarning();
3717
+ if (w) console.log(color.yellow(w) + "\n");
3718
+ } else {
3669
3719
  console.log(color.red("update failed: ") + output.split("\n").filter(Boolean).slice(-1)[0]);
3670
3720
  console.log(color.dim(" run manually: npm install -g beecork (may need sudo / your version manager)") + "\n");
3671
3721
  }
@@ -3835,8 +3885,11 @@ async function main() {
3835
3885
  if (process.argv[2] === "update") {
3836
3886
  console.log("Updating beecork\u2026");
3837
3887
  const { ok, output } = await selfUpdate();
3838
- if (ok) console.log(color.green("\u2713 beecork updated. Run `beecork` to use the new version."));
3839
- else {
3888
+ if (ok) {
3889
+ console.log(color.green("\u2713 beecork updated. Run `beecork` to use the new version."));
3890
+ const w = await shadowWarning();
3891
+ if (w) console.log("\n" + color.yellow(w));
3892
+ } else {
3840
3893
  console.error(color.red("Update failed:") + "\n" + output);
3841
3894
  console.error(color.dim("\nTry manually: npm install -g beecork (you may need sudo, or your Node version manager)."));
3842
3895
  process.exitCode = 1;
@@ -3917,12 +3970,12 @@ ${instr.trusted}`;
3917
3970
  }
3918
3971
  const rl = tty ? null : createInterface({ input: process.stdin, output: process.stdout, completer });
3919
3972
  if (chromeEnabled()) process.stdout.write(ansi.clearScreen + ansi.clearScrollback + ansi.home);
3920
- printBanner(state.model, instr.sources.map(tildify));
3973
+ const version = await currentVersion();
3974
+ printBanner(state.model, version, instr.sources.map(tildify));
3921
3975
  if (config.dangerouslySkipPermissions) {
3922
3976
  console.log(color.red("\u26A0 --dangerously-skip-permissions is ON: the approval gate is OFF. Out-of-root paths and risky") + "\n" + color.red(" shell commands will RUN unprompted. Use only in a disposable sandbox. (read-only mode + catastrophic-") + "\n" + color.red(" command refusal still apply.)") + "\n");
3923
3977
  }
3924
3978
  if (tty) {
3925
- const version = await currentVersion();
3926
3979
  const newer = await checkForUpdate(version);
3927
3980
  if (newer) console.log(color.dim(`\u25B8 beecork ${stripControl(newer)} is available (you have ${version}) \u2014 update: npm install -g beecork`) + "\n");
3928
3981
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "beecork",
3
- "version": "2.5.0",
3
+ "version": "2.5.2",
4
4
  "description": "beecork — a from-scratch CLI coding agent: multi-model (OpenRouter), BYOK, path-confined tools, built part by part.",
5
5
  "type": "module",
6
6
  "bin": {