@nomad-e/bluma-cli 0.1.37 → 0.1.39

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 (2) hide show
  1. package/dist/main.js +395 -92
  2. package/package.json +3 -3
package/dist/main.js CHANGED
@@ -331,8 +331,8 @@ var init_async_command = __esm({
331
331
  import React12 from "react";
332
332
  import { render } from "ink";
333
333
  import { EventEmitter as EventEmitter3 } from "events";
334
- import fs17 from "fs";
335
- import path21 from "path";
334
+ import fs18 from "fs";
335
+ import path22 from "path";
336
336
  import { fileURLToPath as fileURLToPath5 } from "url";
337
337
  import { v4 as uuidv46 } from "uuid";
338
338
 
@@ -2317,7 +2317,7 @@ var ConfirmationPrompt = memo4(ConfirmationPromptComponent);
2317
2317
 
2318
2318
  // src/app/agent/agent.ts
2319
2319
  import * as dotenv from "dotenv";
2320
- import path19 from "path";
2320
+ import path20 from "path";
2321
2321
  import os14 from "os";
2322
2322
 
2323
2323
  // src/app/agent/tool_invoker.ts
@@ -4821,7 +4821,7 @@ var AdvancedFeedbackSystem = class {
4821
4821
  };
4822
4822
 
4823
4823
  // src/app/agent/bluma/core/bluma.ts
4824
- import path18 from "path";
4824
+ import path19 from "path";
4825
4825
  import { v4 as uuidv43 } from "uuid";
4826
4826
 
4827
4827
  // src/app/agent/session_manager/session_manager.ts
@@ -5012,9 +5012,9 @@ async function saveSessionHistory(sessionFile, history, memory) {
5012
5012
 
5013
5013
  // src/app/agent/core/prompt/prompt_builder.ts
5014
5014
  import os11 from "os";
5015
- import fs15 from "fs";
5016
- import path17 from "path";
5017
- import { execSync } from "child_process";
5015
+ import fs16 from "fs";
5016
+ import path18 from "path";
5017
+ import { execSync as execSync2 } from "child_process";
5018
5018
 
5019
5019
  // src/app/agent/skills/skill_loader.ts
5020
5020
  import fs14 from "fs";
@@ -5313,6 +5313,215 @@ var SkillLoader = class _SkillLoader {
5313
5313
  }
5314
5314
  };
5315
5315
 
5316
+ // src/app/agent/core/prompt/workspace_snapshot.ts
5317
+ import fs15 from "fs";
5318
+ import path17 from "path";
5319
+ import { execSync } from "child_process";
5320
+ var LIMITS = {
5321
+ readme: 1e4,
5322
+ blumaMd: 12e3,
5323
+ contributing: 4e3,
5324
+ changelog: 4e3,
5325
+ pyproject: 3500,
5326
+ gitStatusLines: 48,
5327
+ gitLogLines: 14,
5328
+ diffStatLines: 28,
5329
+ topDirEntries: 96
5330
+ };
5331
+ function safeReadFile(filePath, maxChars) {
5332
+ try {
5333
+ if (!fs15.existsSync(filePath)) return null;
5334
+ const st = fs15.statSync(filePath);
5335
+ if (!st.isFile()) return null;
5336
+ const raw = fs15.readFileSync(filePath, "utf8");
5337
+ if (raw.length <= maxChars) return raw;
5338
+ return `${raw.slice(0, maxChars)}
5339
+
5340
+ [\u2026 truncated for prompt size \u2026]`;
5341
+ } catch {
5342
+ return null;
5343
+ }
5344
+ }
5345
+ function tryReadReadme(cwd) {
5346
+ for (const name of ["README.md", "README.MD", "readme.md", "Readme.md"]) {
5347
+ const c = safeReadFile(path17.join(cwd, name), LIMITS.readme);
5348
+ if (c) return `(${name})
5349
+ ${c}`;
5350
+ }
5351
+ return null;
5352
+ }
5353
+ function tryReadBluMaMd(cwd) {
5354
+ const paths = [
5355
+ path17.join(cwd, "BluMa.md"),
5356
+ path17.join(cwd, "BLUMA.md"),
5357
+ path17.join(cwd, ".bluma", "BluMa.md")
5358
+ ];
5359
+ for (const p of paths) {
5360
+ const c = safeReadFile(p, LIMITS.blumaMd);
5361
+ if (c) {
5362
+ const rel = path17.relative(cwd, p) || p;
5363
+ return `(${rel})
5364
+ ${c}`;
5365
+ }
5366
+ }
5367
+ return null;
5368
+ }
5369
+ function summarizePackageJson(cwd) {
5370
+ const p = path17.join(cwd, "package.json");
5371
+ try {
5372
+ if (!fs15.existsSync(p)) return null;
5373
+ const pkg = JSON.parse(fs15.readFileSync(p, "utf8"));
5374
+ const scripts = pkg.scripts;
5375
+ let scriptKeys = "";
5376
+ if (scripts && typeof scripts === "object" && !Array.isArray(scripts)) {
5377
+ scriptKeys = Object.keys(scripts).slice(0, 48).join(", ");
5378
+ }
5379
+ const deps = pkg.dependencies;
5380
+ const devDeps = pkg.devDependencies;
5381
+ const nd = deps && typeof deps === "object" && !Array.isArray(deps) ? Object.keys(deps).length : 0;
5382
+ const nDev = devDeps && typeof devDeps === "object" && !Array.isArray(devDeps) ? Object.keys(devDeps).length : 0;
5383
+ let s = `name: ${String(pkg.name ?? "?")} version: ${String(pkg.version ?? "?")}
5384
+ `;
5385
+ if (typeof pkg.description === "string" && pkg.description.trim()) {
5386
+ s += `description: ${pkg.description.trim().slice(0, 280)}
5387
+ `;
5388
+ }
5389
+ if (scriptKeys) s += `scripts (keys): ${scriptKeys}
5390
+ `;
5391
+ s += `dependencies: ${nd} devDependencies: ${nDev}
5392
+ `;
5393
+ if (pkg.engines) s += `engines: ${JSON.stringify(pkg.engines)}
5394
+ `;
5395
+ if (pkg.type) s += `type: ${String(pkg.type)}
5396
+ `;
5397
+ if (pkg.main) s += `main: ${String(pkg.main)}
5398
+ `;
5399
+ return s.trim();
5400
+ } catch {
5401
+ return null;
5402
+ }
5403
+ }
5404
+ function topLevelListing(cwd) {
5405
+ try {
5406
+ const names = fs15.readdirSync(cwd, { withFileTypes: true });
5407
+ const sorted = [...names].sort((a, b) => a.name.localeCompare(b.name));
5408
+ const limited = sorted.slice(0, LIMITS.topDirEntries);
5409
+ const lines = limited.map((d) => `${d.name}${d.isDirectory() ? "/" : ""}`);
5410
+ if (sorted.length > limited.length) {
5411
+ lines.push(`[\u2026 +${sorted.length - limited.length} more entries at repo root \u2026]`);
5412
+ }
5413
+ return lines.join("\n");
5414
+ } catch {
5415
+ return "(could not list directory)";
5416
+ }
5417
+ }
5418
+ function gitLines(cwd, args, maxLines) {
5419
+ try {
5420
+ const out = execSync(`git ${args}`, {
5421
+ cwd,
5422
+ encoding: "utf8",
5423
+ timeout: 1e4,
5424
+ maxBuffer: 512 * 1024,
5425
+ stdio: ["ignore", "pipe", "ignore"]
5426
+ });
5427
+ const lines = out.trimEnd().split(/\r?\n/).filter((l) => l.length > 0);
5428
+ if (lines.length > maxLines) {
5429
+ return `${lines.slice(0, maxLines).join("\n")}
5430
+ [\u2026 +${lines.length - maxLines} lines \u2026]`;
5431
+ }
5432
+ return lines.length ? lines.join("\n") : null;
5433
+ } catch {
5434
+ return null;
5435
+ }
5436
+ }
5437
+ function insideGitWorkTree(cwd) {
5438
+ try {
5439
+ const v = execSync("git rev-parse --is-inside-work-tree", {
5440
+ cwd,
5441
+ encoding: "utf8",
5442
+ timeout: 4e3,
5443
+ stdio: ["ignore", "pipe", "ignore"]
5444
+ }).trim();
5445
+ return v === "true";
5446
+ } catch {
5447
+ return false;
5448
+ }
5449
+ }
5450
+ function buildWorkspaceSnapshot(cwd) {
5451
+ const parts = [];
5452
+ parts.push(`### Repository root
5453
+ \`${cwd}\`
5454
+ `);
5455
+ parts.push("### Top-level entries\n```");
5456
+ parts.push(topLevelListing(cwd));
5457
+ parts.push("```\n");
5458
+ const pkg = summarizePackageJson(cwd);
5459
+ if (pkg) {
5460
+ parts.push("### package.json (summary)\n```");
5461
+ parts.push(pkg);
5462
+ parts.push("```\n");
5463
+ }
5464
+ const py = safeReadFile(path17.join(cwd, "pyproject.toml"), LIMITS.pyproject);
5465
+ if (py) {
5466
+ parts.push("### pyproject.toml (excerpt)\n```toml");
5467
+ parts.push(py);
5468
+ parts.push("```\n");
5469
+ }
5470
+ const readme = tryReadReadme(cwd);
5471
+ if (readme) {
5472
+ parts.push("### README (excerpt)\n```markdown");
5473
+ parts.push(readme);
5474
+ parts.push("```\n");
5475
+ }
5476
+ const bluma = tryReadBluMaMd(cwd);
5477
+ if (bluma) {
5478
+ parts.push("### BluMa / project context file (excerpt)\n```markdown");
5479
+ parts.push(bluma);
5480
+ parts.push("```\n");
5481
+ }
5482
+ const contrib = safeReadFile(path17.join(cwd, "CONTRIBUTING.md"), LIMITS.contributing);
5483
+ if (contrib) {
5484
+ parts.push("### CONTRIBUTING.md (excerpt)\n```markdown");
5485
+ parts.push(contrib);
5486
+ parts.push("```\n");
5487
+ }
5488
+ const chlog = safeReadFile(path17.join(cwd, "CHANGELOG.md"), LIMITS.changelog);
5489
+ if (!chlog) {
5490
+ const alt = safeReadFile(path17.join(cwd, "CHANGES.md"), LIMITS.changelog);
5491
+ if (alt) {
5492
+ parts.push("### CHANGES.md (excerpt)\n```markdown");
5493
+ parts.push(alt);
5494
+ parts.push("```\n");
5495
+ }
5496
+ } else {
5497
+ parts.push("### CHANGELOG.md (excerpt)\n```markdown");
5498
+ parts.push(chlog);
5499
+ parts.push("```\n");
5500
+ }
5501
+ if (insideGitWorkTree(cwd)) {
5502
+ parts.push("### Git\n");
5503
+ const st = gitLines(cwd, "status --short", LIMITS.gitStatusLines);
5504
+ parts.push("`git status --short`:\n```");
5505
+ parts.push(st ?? "(empty or unavailable)");
5506
+ parts.push("```\n");
5507
+ const log = gitLines(cwd, "log -n 14 --oneline --decorate", LIMITS.gitLogLines);
5508
+ if (log) {
5509
+ parts.push("Recent commits:\n```");
5510
+ parts.push(log);
5511
+ parts.push("```\n");
5512
+ }
5513
+ const stat = gitLines(cwd, "diff --stat HEAD", LIMITS.diffStatLines);
5514
+ if (stat) {
5515
+ parts.push("Uncommitted vs `HEAD` (`git diff --stat HEAD`):\n```");
5516
+ parts.push(stat);
5517
+ parts.push("```\n");
5518
+ }
5519
+ } else {
5520
+ parts.push("### Git\n(not a git work tree, or `git` unavailable)\n");
5521
+ }
5522
+ return parts.join("\n");
5523
+ }
5524
+
5316
5525
  // src/app/agent/core/prompt/prompt_builder.ts
5317
5526
  function getNodeVersion() {
5318
5527
  try {
@@ -5323,14 +5532,14 @@ function getNodeVersion() {
5323
5532
  }
5324
5533
  function getNpmVersion() {
5325
5534
  try {
5326
- return execSync("npm --version", { encoding: "utf-8", timeout: 5e3 }).trim();
5535
+ return execSync2("npm --version", { encoding: "utf-8", timeout: 5e3 }).trim();
5327
5536
  } catch {
5328
5537
  return "unknown";
5329
5538
  }
5330
5539
  }
5331
5540
  function getGitBranch(dir) {
5332
5541
  try {
5333
- return execSync("git rev-parse --abbrev-ref HEAD", {
5542
+ return execSync2("git rev-parse --abbrev-ref HEAD", {
5334
5543
  cwd: dir,
5335
5544
  encoding: "utf-8",
5336
5545
  timeout: 5e3
@@ -5341,10 +5550,10 @@ function getGitBranch(dir) {
5341
5550
  }
5342
5551
  function getPackageManager(dir) {
5343
5552
  try {
5344
- if (fs15.existsSync(path17.join(dir, "pnpm-lock.yaml"))) return "pnpm";
5345
- if (fs15.existsSync(path17.join(dir, "yarn.lock"))) return "yarn";
5346
- if (fs15.existsSync(path17.join(dir, "bun.lockb"))) return "bun";
5347
- if (fs15.existsSync(path17.join(dir, "package-lock.json"))) return "npm";
5553
+ if (fs16.existsSync(path18.join(dir, "pnpm-lock.yaml"))) return "pnpm";
5554
+ if (fs16.existsSync(path18.join(dir, "yarn.lock"))) return "yarn";
5555
+ if (fs16.existsSync(path18.join(dir, "bun.lockb"))) return "bun";
5556
+ if (fs16.existsSync(path18.join(dir, "package-lock.json"))) return "npm";
5348
5557
  return "unknown";
5349
5558
  } catch {
5350
5559
  return "unknown";
@@ -5352,9 +5561,9 @@ function getPackageManager(dir) {
5352
5561
  }
5353
5562
  function getProjectType(dir) {
5354
5563
  try {
5355
- const files = fs15.readdirSync(dir);
5564
+ const files = fs16.readdirSync(dir);
5356
5565
  if (files.includes("package.json")) {
5357
- const pkg = JSON.parse(fs15.readFileSync(path17.join(dir, "package.json"), "utf-8"));
5566
+ const pkg = JSON.parse(fs16.readFileSync(path18.join(dir, "package.json"), "utf-8"));
5358
5567
  if (pkg.dependencies?.next || pkg.devDependencies?.next) return "Next.js";
5359
5568
  if (pkg.dependencies?.react || pkg.devDependencies?.react) return "React";
5360
5569
  if (pkg.dependencies?.express || pkg.devDependencies?.express) return "Express";
@@ -5373,9 +5582,9 @@ function getProjectType(dir) {
5373
5582
  }
5374
5583
  function getTestFramework(dir) {
5375
5584
  try {
5376
- const pkgPath = path17.join(dir, "package.json");
5377
- if (fs15.existsSync(pkgPath)) {
5378
- const pkg = JSON.parse(fs15.readFileSync(pkgPath, "utf-8"));
5585
+ const pkgPath = path18.join(dir, "package.json");
5586
+ if (fs16.existsSync(pkgPath)) {
5587
+ const pkg = JSON.parse(fs16.readFileSync(pkgPath, "utf-8"));
5379
5588
  const deps = { ...pkg.dependencies, ...pkg.devDependencies };
5380
5589
  if (deps.jest) return "jest";
5381
5590
  if (deps.vitest) return "vitest";
@@ -5384,7 +5593,7 @@ function getTestFramework(dir) {
5384
5593
  if (deps["@playwright/test"]) return "playwright";
5385
5594
  if (deps.cypress) return "cypress";
5386
5595
  }
5387
- if (fs15.existsSync(path17.join(dir, "pytest.ini")) || fs15.existsSync(path17.join(dir, "conftest.py"))) return "pytest";
5596
+ if (fs16.existsSync(path18.join(dir, "pytest.ini")) || fs16.existsSync(path18.join(dir, "conftest.py"))) return "pytest";
5388
5597
  return "unknown";
5389
5598
  } catch {
5390
5599
  return "unknown";
@@ -5392,9 +5601,9 @@ function getTestFramework(dir) {
5392
5601
  }
5393
5602
  function getTestCommand(dir) {
5394
5603
  try {
5395
- const pkgPath = path17.join(dir, "package.json");
5396
- if (fs15.existsSync(pkgPath)) {
5397
- const pkg = JSON.parse(fs15.readFileSync(pkgPath, "utf-8"));
5604
+ const pkgPath = path18.join(dir, "package.json");
5605
+ if (fs16.existsSync(pkgPath)) {
5606
+ const pkg = JSON.parse(fs16.readFileSync(pkgPath, "utf-8"));
5398
5607
  if (pkg.scripts?.test) return `npm test`;
5399
5608
  if (pkg.scripts?.["test:unit"]) return `npm run test:unit`;
5400
5609
  }
@@ -5418,7 +5627,7 @@ var SYSTEM_PROMPT = `
5418
5627
  You are not just an assistant - you are a **teammate** who takes ownership of tasks.
5419
5628
 
5420
5629
  Key traits:
5421
- - F like a senior developer who has been on this project for years
5630
+ - Act like a senior developer who has been on this project for years
5422
5631
  - Suggest improvements proactively, don't just follow orders blindly
5423
5632
  - Spot bugs and issues before they become problems
5424
5633
  - Write tests as naturally as you write code
@@ -5426,6 +5635,42 @@ var SYSTEM_PROMPT = `
5426
5635
 
5427
5636
  ---
5428
5637
 
5638
+ <engineering_mindset>
5639
+ ## How you think and act (agentic engineer \u2014 not a chatbot)
5640
+
5641
+ You behave like a **senior engineer in this repo**: you **ship**, you **prove with evidence**, and you **debug methodically**.
5642
+
5643
+ ### Operating mode
5644
+ - **Autonomous by default:** move the task forward until truly blocked (missing secrets, policy, or a decision only the human can make). Do not ask permission to read files, search the tree, run tests, or inspect git when that reduces risk.
5645
+ - **Outcome over monologue:** deliver working changes; keep narration **short and technical** (what changed, which paths, which command proved it).
5646
+ - **No assistant theatre:** drop filler (\u201CAs an AI\u2026\u201D, \u201CI\u2019d be happy to\u2026\u201D, long preambles). Be direct like a teammate on Slack.
5647
+
5648
+ ### Analysis (before you code)
5649
+ - Ground decisions in **observable facts** from tools: file contents, configs, compiler/test output, stack traces \u2014 not guesses.
5650
+ - Form a **hypothesis** you can falsify in **one** step (one command or one targeted read).
5651
+ - Before large refactors, map **blast radius** (callers, tests) with \`grep_search\` / \`read_file_lines\`.
5652
+
5653
+ ### Debugging
5654
+ - **Reproduce first** \u2014 same command, same failure message. If you cannot reproduce, you do not understand the bug yet.
5655
+ - **Localise:** narrow to file + function + line using logs and traces; avoid random edits in unrelated modules.
5656
+ - **Fix minimally:** smallest change for the root cause; do not mix unrelated refactors with bugfixes unless the user asked.
5657
+ - **Verify:** re-run the **same** failing command or test target after the fix; treat **exit_code** and **stderr** as first-class \u2014 read them literally.
5658
+ - **Never claim \u201Cfixed\u201D or \u201Cpassing\u201D** without tool output that shows success.
5659
+
5660
+ ### Implementation
5661
+ - **Read \u2192 edit \u2192 verify:** never edit from memory of a skim; use \`read_file_lines\` on the exact region you change.
5662
+ - Prefer **one logical slice** per burst when possible; for large work, use \`todo\` and complete vertical slices end-to-end (repro \u2192 fix \u2192 test).
5663
+ - Follow conventions from \`<workspace_snapshot>\`, \`BluMa.md\`, \`README\`, and **existing code** over generic templates.
5664
+
5665
+ ### When stuck
5666
+ - Say what you tried, paste or summarise **concrete tool output** (path, exit code, error line), and propose the **next experiment** \u2014 not \u201Cit failed\u201D.
5667
+ - Use \`coding_memory\` for durable project facts; do not rely on chat scroll alone after context compression.
5668
+
5669
+ This mindset overrides generic assistant habits: you exist to **solve problems in the workspace** with tools and proof.
5670
+ </engineering_mindset>
5671
+
5672
+ ---
5673
+
5429
5674
  <skills_knowledge>
5430
5675
  ## Skills vs Base Knowledge (CRITICAL)
5431
5676
 
@@ -5507,6 +5752,22 @@ You MUST adapt all commands to this environment. Use the correct package manager
5507
5752
 
5508
5753
  ---
5509
5754
 
5755
+ <workspace_snapshot>
5756
+ ## Project workspace (loaded from disk when the system prompt is built)
5757
+
5758
+ You are anchored to **the current working directory** (see **Working Directory** above). The block below is an automatic snapshot: tree root, package summary, README / BluMa.md excerpts, git status and recent commits, etc.
5759
+
5760
+ **How to use it (IDE-grade discipline):**
5761
+ - Treat this as a **map**, not ground truth \u2014 re-read files and run tests/build/linter before high-risk edits.
5762
+ - It becomes **stale** after \`git pull\`, installs, refactors, or long chats; verify again when it matters.
5763
+ - **Close the loop:** hypothesis \u2192 smallest change \u2192 run the same command a human would (\`test\`, \`build\`, typecheck) \u2192 read stdout/stderr literally \u2192 repeat until green.
5764
+ - Do not guess file contents; use your tools to confirm.
5765
+
5766
+ <<<BLUMA_WORKSPACE_SNAPSHOT_BODY>>>
5767
+ </workspace_snapshot>
5768
+
5769
+ ---
5770
+
5510
5771
  <coding_memory>
5511
5772
  ## Persistent coding memory (tool: \`coding_memory\`)
5512
5773
 
@@ -5554,6 +5815,12 @@ This is your **long-term scratchpad** (usually \`~/.bluma/coding_memory.json\`).
5554
5815
  5. **Verify** - Check build, lint, and git status
5555
5816
  6. **Summarize** - Report results and any follow-up actions
5556
5817
 
5818
+ ### For debugging / incidents (follow \`<engineering_mindset>\`):
5819
+ 1. **Reproduce** \u2014 run the same command or test the user (or the repo) uses; capture full output via \`command_status\`.
5820
+ 2. **Localise** \u2014 trace to a specific file/line; read that code with tools before changing anything.
5821
+ 3. **Fix** \u2014 minimal diff addressing the root cause.
5822
+ 4. **Prove** \u2014 re-run the same check; only then report success, citing exit code / test summary.
5823
+
5557
5824
  ### For Simple Tasks:
5558
5825
  - Quick acknowledgment + immediate action
5559
5826
  - No TODO needed for single operations
@@ -5572,6 +5839,7 @@ Run tests when modifying code. If a testing skill is listed in available_skills,
5572
5839
 
5573
5840
  ### File Operations:
5574
5841
  - **ALWAYS read a file before editing** - Use read_file_lines or ls_tool first
5842
+ - After \`shell_command\` + \`command_status\`, treat **exit_code**, **stdout**, and **stderr** as authoritative \u2014 failures often appear only in stderr
5575
5843
  - **Use absolute paths** when possible to avoid ambiguity
5576
5844
  - **For edit_tool**: Provide exact content with correct whitespace (read first!)
5577
5845
  - **Truncated CLI preview** (user may press Ctrl+O for more lines): still not a substitute for \`read_file_lines\` \u2014 use the tool for authoritative content before editing
@@ -5707,8 +5975,8 @@ Examples:
5707
5975
 
5708
5976
  **Style:**
5709
5977
  - Direct and technical, like Slack/Discord
5710
- - Get straight to the point
5711
- - Celebrate wins, acknowledge mistakes
5978
+ - Get straight to the point; **agentic engineer**, not customer support tone
5979
+ - Celebrate wins, acknowledge mistakes with concrete next steps
5712
5980
 
5713
5981
  **Message via tool only:**
5714
5982
  - message with \`result\` = END your turn (after questions/completions)
@@ -5895,6 +6163,10 @@ function getUnifiedSystemPrompt(availableSkills) {
5895
6163
  (p, [key, value]) => p.replaceAll(`{${key}}`, value),
5896
6164
  basePrompt
5897
6165
  );
6166
+ prompt = prompt.replace(
6167
+ "<<<BLUMA_WORKSPACE_SNAPSHOT_BODY>>>",
6168
+ buildWorkspaceSnapshot(cwd)
6169
+ );
5898
6170
  if (availableSkills && availableSkills.length > 0) {
5899
6171
  const skillsList = availableSkills.map((s) => `- ${s.name}: ${s.description}`).join("\n");
5900
6172
  prompt += `
@@ -6061,8 +6333,8 @@ ${memorySnapshot.trim().length > 0 ? memorySnapshot : "(No entries yet. Use codi
6061
6333
  }
6062
6334
  function isGitRepo(dir) {
6063
6335
  try {
6064
- const gitPath = path17.join(dir, ".git");
6065
- return fs15.existsSync(gitPath) && fs15.lstatSync(gitPath).isDirectory();
6336
+ const gitPath = path18.join(dir, ".git");
6337
+ return fs16.existsSync(gitPath) && fs16.lstatSync(gitPath).isDirectory();
6066
6338
  } catch {
6067
6339
  return false;
6068
6340
  }
@@ -6980,7 +7252,7 @@ var BluMaAgent = class {
6980
7252
 
6981
7253
  ${editData.error.display}`;
6982
7254
  }
6983
- const filename = path18.basename(toolArgs.file_path);
7255
+ const filename = path19.basename(toolArgs.file_path);
6984
7256
  return createDiff(filename, editData.currentContent || "", editData.newContent);
6985
7257
  } catch (e) {
6986
7258
  return `An unexpected error occurred while generating the edit preview: ${e.message}`;
@@ -7687,14 +7959,14 @@ var RouteManager = class {
7687
7959
  this.subAgents = subAgents;
7688
7960
  this.core = core;
7689
7961
  }
7690
- registerRoute(path22, handler) {
7691
- this.routeHandlers.set(path22, handler);
7962
+ registerRoute(path23, handler) {
7963
+ this.routeHandlers.set(path23, handler);
7692
7964
  }
7693
7965
  async handleRoute(payload) {
7694
7966
  const inputText = String(payload.content || "").trim();
7695
7967
  const { userContext } = payload;
7696
- for (const [path22, handler] of this.routeHandlers) {
7697
- if (inputText === path22 || inputText.startsWith(`${path22} `)) {
7968
+ for (const [path23, handler] of this.routeHandlers) {
7969
+ if (inputText === path23 || inputText.startsWith(`${path23} `)) {
7698
7970
  return handler({ content: inputText, userContext });
7699
7971
  }
7700
7972
  }
@@ -7703,7 +7975,7 @@ var RouteManager = class {
7703
7975
  };
7704
7976
 
7705
7977
  // src/app/agent/agent.ts
7706
- var globalEnvPath = path19.join(os14.homedir(), ".bluma", ".env");
7978
+ var globalEnvPath = path20.join(os14.homedir(), ".bluma", ".env");
7707
7979
  dotenv.config({ path: globalEnvPath });
7708
7980
  var Agent = class {
7709
7981
  sessionId;
@@ -7927,12 +8199,12 @@ var renderShellCommand2 = ({ args }) => {
7927
8199
  };
7928
8200
  var renderLsTool2 = ({ args }) => {
7929
8201
  const parsed = parseArgs(args);
7930
- const path22 = parsed.directory_path || ".";
8202
+ const path23 = parsed.directory_path || ".";
7931
8203
  return /* @__PURE__ */ jsxs9(Box9, { children: [
7932
8204
  /* @__PURE__ */ jsx9(Text9, { color: BLUMA_TERMINAL.brandBlue, bold: true, children: "ls" }),
7933
8205
  /* @__PURE__ */ jsxs9(Text9, { dimColor: true, children: [
7934
8206
  " ",
7935
- path22
8207
+ path23
7936
8208
  ] })
7937
8209
  ] });
7938
8210
  };
@@ -8068,7 +8340,7 @@ var renderFindByName = ({ args }) => {
8068
8340
  var renderGrepSearch = ({ args }) => {
8069
8341
  const parsed = parseArgs(args);
8070
8342
  const query = parsed.query || "";
8071
- const path22 = parsed.path || ".";
8343
+ const path23 = parsed.path || ".";
8072
8344
  return /* @__PURE__ */ jsxs9(Box9, { children: [
8073
8345
  /* @__PURE__ */ jsx9(Text9, { color: BLUMA_TERMINAL.brandBlue, bold: true, children: "grep" }),
8074
8346
  /* @__PURE__ */ jsxs9(Text9, { color: BLUMA_TERMINAL.brandMagenta, children: [
@@ -8078,7 +8350,7 @@ var renderGrepSearch = ({ args }) => {
8078
8350
  ] }),
8079
8351
  /* @__PURE__ */ jsxs9(Text9, { dimColor: true, children: [
8080
8352
  " ",
8081
- path22
8353
+ path23
8082
8354
  ] })
8083
8355
  ] });
8084
8356
  };
@@ -8647,10 +8919,10 @@ var ToolResultDisplayComponent = ({ toolName, result }) => {
8647
8919
  ] }),
8648
8920
  matches.slice(0, 5).map((m, i) => {
8649
8921
  const row = m;
8650
- const path22 = row.file || row.path || row.name || m;
8922
+ const path23 = row.file || row.path || row.name || m;
8651
8923
  const line = row.line;
8652
8924
  return /* @__PURE__ */ jsxs11(Text11, { dimColor: true, children: [
8653
- String(path22),
8925
+ String(path23),
8654
8926
  line != null ? `:${line}` : ""
8655
8927
  ] }, i);
8656
8928
  }),
@@ -8989,66 +9261,94 @@ var SlashCommands = ({
8989
9261
  var SlashCommands_default = SlashCommands;
8990
9262
 
8991
9263
  // src/app/agent/utils/update_check.ts
8992
- import updateNotifier from "update-notifier";
9264
+ import latestVersion from "latest-version";
9265
+ import semverGt from "semver/functions/gt.js";
9266
+ import semverValid from "semver/functions/valid.js";
8993
9267
  import { fileURLToPath as fileURLToPath4 } from "url";
8994
- import path20 from "path";
8995
- import fs16 from "fs";
9268
+ import path21 from "path";
9269
+ import fs17 from "fs";
8996
9270
  var BLUMA_PACKAGE_NAME = "@nomad-e/bluma-cli";
8997
9271
  function findBlumaPackageJson(startDir) {
8998
9272
  let dir = startDir;
8999
- for (let i = 0; i < 10; i++) {
9000
- const candidate = path20.join(dir, "package.json");
9001
- if (fs16.existsSync(candidate)) {
9273
+ for (let i = 0; i < 12; i++) {
9274
+ const candidate = path21.join(dir, "package.json");
9275
+ if (fs17.existsSync(candidate)) {
9002
9276
  try {
9003
- const raw = fs16.readFileSync(candidate, "utf8");
9277
+ const raw = fs17.readFileSync(candidate, "utf8");
9004
9278
  const parsed = JSON.parse(raw);
9005
9279
  if (parsed?.name === BLUMA_PACKAGE_NAME && parsed?.version) {
9006
- return { name: parsed.name, version: parsed.version };
9280
+ return { name: parsed.name, version: String(parsed.version) };
9007
9281
  }
9008
9282
  } catch {
9009
9283
  }
9010
9284
  }
9011
- const parent = path20.dirname(dir);
9285
+ const parent = path21.dirname(dir);
9012
9286
  if (parent === dir) break;
9013
9287
  dir = parent;
9014
9288
  }
9015
9289
  return null;
9016
9290
  }
9291
+ function resolveInstalledBlumaPackage() {
9292
+ const tried = /* @__PURE__ */ new Set();
9293
+ const tryFrom = (dir) => {
9294
+ const abs = path21.resolve(dir);
9295
+ if (tried.has(abs)) return null;
9296
+ tried.add(abs);
9297
+ return findBlumaPackageJson(abs);
9298
+ };
9299
+ try {
9300
+ const fromBundle = tryFrom(path21.dirname(fileURLToPath4(import.meta.url)));
9301
+ if (fromBundle) return fromBundle;
9302
+ } catch {
9303
+ }
9304
+ const argv1 = process.argv[1];
9305
+ if (argv1 && !argv1.startsWith("-")) {
9306
+ try {
9307
+ let resolved = argv1;
9308
+ if (path21.isAbsolute(argv1) && fs17.existsSync(argv1)) {
9309
+ resolved = fs17.realpathSync(argv1);
9310
+ } else {
9311
+ resolved = path21.resolve(process.cwd(), argv1);
9312
+ }
9313
+ const fromArgv = tryFrom(path21.dirname(resolved));
9314
+ if (fromArgv) return fromArgv;
9315
+ } catch {
9316
+ }
9317
+ }
9318
+ return null;
9319
+ }
9017
9320
  async function checkForUpdates() {
9018
9321
  try {
9019
9322
  if (process.env.BLUMA_FORCE_UPDATE_MSG) {
9020
9323
  return String(process.env.BLUMA_FORCE_UPDATE_MSG);
9021
9324
  }
9022
- const binPath = process.argv?.[1];
9023
- let pkg = null;
9024
- if (binPath && fs16.existsSync(binPath)) {
9025
- pkg = findBlumaPackageJson(path20.dirname(binPath));
9325
+ if (process.env.CI) {
9326
+ return null;
9026
9327
  }
9328
+ const pkg = resolveInstalledBlumaPackage();
9027
9329
  if (!pkg) {
9028
- const __filename = fileURLToPath4(import.meta.url);
9029
- const __dirname = path20.dirname(__filename);
9030
- pkg = findBlumaPackageJson(__dirname);
9330
+ return null;
9031
9331
  }
9032
- if (!pkg) {
9332
+ const cur = pkg.version.trim();
9333
+ if (!semverValid(cur)) {
9033
9334
  return null;
9034
9335
  }
9035
- const isCI = Boolean(process.env.CI);
9036
- const updateCheckInterval = 0;
9037
- const notifier = updateNotifier({
9038
- pkg: { name: pkg.name, version: pkg.version },
9039
- updateCheckInterval,
9040
- shouldNotifyInNpmScript: true
9041
- });
9042
- if (notifier.update && !isCI) {
9043
- const cur = notifier.update.current;
9044
- const lat = notifier.update.latest;
9045
- if (cur && lat && cur !== lat) {
9046
- return `Update available for BluMa CLI! ${cur} \u2192 ${lat}
9047
- Run: npm i -g ${BLUMA_PACKAGE_NAME} to update.`;
9048
- }
9336
+ let latRaw;
9337
+ try {
9338
+ latRaw = await latestVersion(BLUMA_PACKAGE_NAME);
9339
+ } catch {
9340
+ return null;
9049
9341
  }
9050
- return null;
9051
- } catch (e) {
9342
+ const lat = String(latRaw).trim();
9343
+ if (!semverValid(lat)) {
9344
+ return null;
9345
+ }
9346
+ if (!semverGt(lat, cur)) {
9347
+ return null;
9348
+ }
9349
+ return `Update available for BluMa CLI! ${cur} \u2192 ${lat}
9350
+ Run: npm i -g ${BLUMA_PACKAGE_NAME} to update.`;
9351
+ } catch {
9052
9352
  return null;
9053
9353
  }
9054
9354
  }
@@ -9241,6 +9541,7 @@ var ExpandedPreviewBlock = memo11(ExpandedPreviewBlockComponent);
9241
9541
 
9242
9542
  // src/app/ui/App.tsx
9243
9543
  import { jsx as jsx20, jsxs as jsxs19 } from "react/jsx-runtime";
9544
+ var blumaUpdateRegistryCheckStarted = false;
9244
9545
  var SAFE_AUTO_APPROVE_TOOLS = [
9245
9546
  // Comunicação/UI
9246
9547
  "message",
@@ -9312,7 +9613,6 @@ var AppComponent = ({ eventBus, sessionId, cliVersion }) => {
9312
9613
  const [recentActivityLine, setRecentActivityLine] = useState6(null);
9313
9614
  const alwaysAcceptList = useRef5([]);
9314
9615
  const workdir = process.cwd();
9315
- const updateCheckRan = useRef5(false);
9316
9616
  const turnStartedAtRef = useRef5(null);
9317
9617
  const appendExpandPreviewToHistory = useCallback3(() => {
9318
9618
  const p = peekLatestExpandable();
@@ -9342,6 +9642,23 @@ var AppComponent = ({ eventBus, sessionId, cliVersion }) => {
9342
9642
  expandPreviewHotkeyBus.off("expand", appendExpandPreviewToHistory);
9343
9643
  };
9344
9644
  }, [appendExpandPreviewToHistory]);
9645
+ useEffect7(() => {
9646
+ if (process.env.CI || blumaUpdateRegistryCheckStarted) return;
9647
+ blumaUpdateRegistryCheckStarted = true;
9648
+ void checkForUpdates().then((msg) => {
9649
+ if (!msg) return;
9650
+ setHistory((prev) => {
9651
+ const nextId3 = prev.length === 0 ? 1 : Math.max(...prev.map((h) => h.id), HEADER_PANEL_HISTORY_ID) + 1;
9652
+ return [
9653
+ ...prev,
9654
+ {
9655
+ id: nextId3,
9656
+ component: /* @__PURE__ */ jsx20(UpdateNotice_default, { message: msg })
9657
+ }
9658
+ ];
9659
+ });
9660
+ });
9661
+ }, []);
9345
9662
  useEffect7(() => {
9346
9663
  setHistory((prev) => {
9347
9664
  const tail = prev.filter((h) => h.id !== HEADER_PANEL_HISTORY_ID);
@@ -9602,20 +9919,6 @@ Please use command_status to check the result and report back to the user.`;
9602
9919
  setToolsCount(parsed.tools);
9603
9920
  setMcpStatus("connected");
9604
9921
  setIsProcessing(false);
9605
- if (!updateCheckRan.current) {
9606
- updateCheckRan.current = true;
9607
- Promise.resolve().then(() => checkForUpdates()).then((msg) => {
9608
- if (msg) {
9609
- setHistory((prev) => [
9610
- ...prev,
9611
- {
9612
- id: prev.length,
9613
- component: /* @__PURE__ */ jsx20(UpdateNotice_default, { message: msg })
9614
- }
9615
- ]);
9616
- }
9617
- }).catch(() => void 0);
9618
- }
9619
9922
  return;
9620
9923
  }
9621
9924
  if (parsed.type === "error") {
@@ -9853,9 +10156,9 @@ async function runAgentMode() {
9853
10156
  try {
9854
10157
  if (inputFileIndex !== -1 && args[inputFileIndex + 1]) {
9855
10158
  const filePath = args[inputFileIndex + 1];
9856
- rawPayload = fs17.readFileSync(filePath, "utf-8");
10159
+ rawPayload = fs18.readFileSync(filePath, "utf-8");
9857
10160
  } else {
9858
- rawPayload = fs17.readFileSync(0, "utf-8");
10161
+ rawPayload = fs18.readFileSync(0, "utf-8");
9859
10162
  }
9860
10163
  } catch (err) {
9861
10164
  writeJsonl({
@@ -10023,9 +10326,9 @@ async function runAgentMode() {
10023
10326
  }
10024
10327
  function readCliPackageVersion() {
10025
10328
  try {
10026
- const base = path21.dirname(fileURLToPath5(import.meta.url));
10027
- const pkgPath = path21.join(base, "..", "package.json");
10028
- const j = JSON.parse(fs17.readFileSync(pkgPath, "utf8"));
10329
+ const base = path22.dirname(fileURLToPath5(import.meta.url));
10330
+ const pkgPath = path22.join(base, "..", "package.json");
10331
+ const j = JSON.parse(fs18.readFileSync(pkgPath, "utf8"));
10029
10332
  return String(j.version || "0.0.0");
10030
10333
  } catch {
10031
10334
  return "0.0.0";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nomad-e/bluma-cli",
3
- "version": "0.1.37",
3
+ "version": "0.1.39",
4
4
  "description": "BluMa independent agent for automation and advanced software engineering.",
5
5
  "author": "Alex Fonseca",
6
6
  "license": "Apache-2.0",
@@ -15,7 +15,6 @@
15
15
  "@types/jest": "^30.0.0",
16
16
  "@types/node": "^20.14.2",
17
17
  "@types/react": "^18.3.3",
18
- "@types/update-notifier": "^6.0.8",
19
18
  "@types/uuid": "^9.0.8",
20
19
  "babel-jest": "^30.0.5",
21
20
  "babel-plugin-transform-import-meta": "^2.3.3",
@@ -52,11 +51,12 @@
52
51
  "ink-spinner": "^5.0.0",
53
52
  "ink-text-input": "^6.0.0",
54
53
  "js-tiktoken": "^1.0.21",
54
+ "latest-version": "^9.0.0",
55
55
  "marked": "^16.1.2",
56
56
  "openai": "^4.47.3",
57
57
  "react-devtools-core": "^4.28.5",
58
58
  "read-package-up": "^11.0.0",
59
- "update-notifier": "^7.0.0",
59
+ "semver": "^7.7.4",
60
60
  "uuid": "^9.0.1"
61
61
  },
62
62
  "files": [