@nomad-e/bluma-cli 0.1.17 → 0.1.19

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/README.md +62 -12
  2. package/dist/config/native_tools.json +7 -0
  3. package/dist/config/skills/git-commit/LICENSE.txt +18 -0
  4. package/dist/config/skills/git-commit/SKILL.md +258 -0
  5. package/dist/config/skills/git-commit/references/REFERENCE.md +249 -0
  6. package/dist/config/skills/git-commit/scripts/validate_commit_msg.py +163 -0
  7. package/dist/config/skills/git-pr/LICENSE.txt +18 -0
  8. package/dist/config/skills/git-pr/SKILL.md +293 -0
  9. package/dist/config/skills/git-pr/references/REFERENCE.md +256 -0
  10. package/dist/config/skills/git-pr/scripts/validate_commits.py +112 -0
  11. package/dist/config/skills/pdf/LICENSE.txt +26 -0
  12. package/dist/config/skills/pdf/SKILL.md +327 -0
  13. package/dist/config/skills/pdf/references/FORMS.md +69 -0
  14. package/dist/config/skills/pdf/references/REFERENCE.md +52 -0
  15. package/dist/config/skills/pdf/scripts/create_report.py +59 -0
  16. package/dist/config/skills/pdf/scripts/merge_pdfs.py +39 -0
  17. package/dist/config/skills/skill-creator/LICENSE.txt +26 -0
  18. package/dist/config/skills/skill-creator/SKILL.md +229 -0
  19. package/dist/config/skills/xlsx/LICENSE.txt +18 -0
  20. package/dist/config/skills/xlsx/SKILL.md +298 -0
  21. package/dist/config/skills/xlsx/references/REFERENCE.md +337 -0
  22. package/dist/config/skills/xlsx/scripts/office/__init__.py +2 -0
  23. package/dist/config/skills/xlsx/scripts/office/__pycache__/__init__.cpython-312.pyc +0 -0
  24. package/dist/config/skills/xlsx/scripts/office/__pycache__/pack.cpython-312.pyc +0 -0
  25. package/dist/config/skills/xlsx/scripts/office/__pycache__/soffice.cpython-312.pyc +0 -0
  26. package/dist/config/skills/xlsx/scripts/office/__pycache__/unpack.cpython-312.pyc +0 -0
  27. package/dist/config/skills/xlsx/scripts/office/__pycache__/validate.cpython-312.pyc +0 -0
  28. package/dist/config/skills/xlsx/scripts/office/pack.py +58 -0
  29. package/dist/config/skills/xlsx/scripts/office/soffice.py +180 -0
  30. package/dist/config/skills/xlsx/scripts/office/unpack.py +63 -0
  31. package/dist/config/skills/xlsx/scripts/office/validate.py +122 -0
  32. package/dist/config/skills/xlsx/scripts/recalc.py +143 -0
  33. package/dist/main.js +275 -89
  34. package/package.json +1 -1
  35. package/dist/config/example.bluma-mcp.json.txt +0 -14
  36. package/dist/config/models_config.json +0 -78
  37. package/dist/skills/git-conventional/LICENSE.txt +0 -3
  38. package/dist/skills/git-conventional/SKILL.md +0 -83
  39. package/dist/skills/skill-creator/SKILL.md +0 -495
  40. package/dist/skills/testing/LICENSE.txt +0 -3
  41. package/dist/skills/testing/SKILL.md +0 -114
package/dist/main.js CHANGED
@@ -1835,7 +1835,7 @@ ${finalDiff}`,
1835
1835
  // src/app/agent/tools/natives/message.ts
1836
1836
  import { v4 as uuidv4 } from "uuid";
1837
1837
  function message(args) {
1838
- const { content, message_type } = args;
1838
+ const { content, message_type, attachments } = args;
1839
1839
  const result = {
1840
1840
  type: "message",
1841
1841
  message_type,
@@ -1844,6 +1844,7 @@ function message(args) {
1844
1844
  content: {
1845
1845
  body: content
1846
1846
  },
1847
+ attachments: Array.isArray(attachments) ? attachments : void 0,
1847
1848
  success: true,
1848
1849
  delivered: true
1849
1850
  };
@@ -3375,19 +3376,51 @@ async function loadSkill(args) {
3375
3376
  message: `Skill "${skill_name}" not found. Available skills: ${availableNames}`
3376
3377
  };
3377
3378
  }
3379
+ const warnings = [];
3380
+ const conflicts = globalContext.skillLoader.getConflicts();
3381
+ const thisConflict = conflicts.find((c) => c.name === skill_name);
3382
+ if (thisConflict) {
3383
+ warnings.push(
3384
+ `Skill "${skill_name}" exists as a native BluMa skill AND as a user skill at "${thisConflict.userPath}" (${thisConflict.userSource}). The native (bundled) version was loaded. Please rename your custom skill to avoid this conflict.`
3385
+ );
3386
+ }
3378
3387
  if (globalContext.history) {
3388
+ let injected = `[SKILL:${skill.name}]
3389
+
3390
+ ${skill.content}`;
3391
+ if (skill.references.length > 0 || skill.scripts.length > 0) {
3392
+ injected += "\n\n---\n";
3393
+ }
3394
+ if (skill.references.length > 0) {
3395
+ injected += "\n## Available References\n";
3396
+ for (const ref of skill.references) {
3397
+ injected += `- ${ref.name}: ${ref.path}
3398
+ `;
3399
+ }
3400
+ injected += "\nTo read a reference: use read_file_lines with the path above.\n";
3401
+ }
3402
+ if (skill.scripts.length > 0) {
3403
+ injected += "\n## Available Scripts\n";
3404
+ for (const s of skill.scripts) {
3405
+ injected += `- ${s.name}: ${s.path}
3406
+ `;
3407
+ }
3408
+ injected += '\nTo run a script: use shell_command with "python <path> [args]".\n';
3409
+ }
3379
3410
  globalContext.history.push({
3380
3411
  role: "user",
3381
- content: `[SKILL:${skill.name}]
3382
-
3383
- ${skill.content}`
3412
+ content: injected
3384
3413
  });
3385
3414
  }
3386
3415
  return {
3387
3416
  success: true,
3388
- message: `Skill "${skill_name}" loaded successfully. Follow the instructions in the skill content above.`,
3417
+ message: warnings.length > 0 ? `Skill "${skill_name}" loaded (native). WARNING: ${warnings[0]}` : `Skill "${skill_name}" loaded successfully (${skill.source}). Follow the instructions in the skill content above.`,
3389
3418
  skill_name: skill.name,
3390
- description: skill.description
3419
+ description: skill.description,
3420
+ source: skill.source,
3421
+ warnings: warnings.length > 0 ? warnings : void 0,
3422
+ references: skill.references.length > 0 ? skill.references : void 0,
3423
+ scripts: skill.scripts.length > 0 ? skill.scripts : void 0
3391
3424
  };
3392
3425
  }
3393
3426
 
@@ -3408,8 +3441,8 @@ var ToolInvoker = class {
3408
3441
  async initialize() {
3409
3442
  try {
3410
3443
  const __filename = fileURLToPath(import.meta.url);
3411
- const __dirname = path10.dirname(__filename);
3412
- const configPath = path10.resolve(__dirname, "config", "native_tools.json");
3444
+ const __dirname2 = path10.dirname(__filename);
3445
+ const configPath = path10.resolve(__dirname2, "config", "native_tools.json");
3413
3446
  const fileContent = await fs8.readFile(configPath, "utf-8");
3414
3447
  const config2 = JSON.parse(fileContent);
3415
3448
  this.toolDefinitions = config2.nativeTools;
@@ -3499,8 +3532,8 @@ var MCPClient = class {
3499
3532
  });
3500
3533
  }
3501
3534
  const __filename = fileURLToPath2(import.meta.url);
3502
- const __dirname = path11.dirname(__filename);
3503
- const defaultConfigPath = path11.resolve(__dirname, "config", "bluma-mcp.json");
3535
+ const __dirname2 = path11.dirname(__filename);
3536
+ const defaultConfigPath = path11.resolve(__dirname2, "config", "bluma-mcp.json");
3504
3537
  const userConfigPath = path11.join(os4.homedir(), ".bluma", "bluma-mcp.json");
3505
3538
  const defaultConfig = await this.loadMcpConfig(defaultConfigPath, "Default");
3506
3539
  const userConfig = await this.loadMcpConfig(userConfigPath, "User");
@@ -3851,33 +3884,71 @@ import { execSync } from "child_process";
3851
3884
  import fs11 from "fs";
3852
3885
  import path13 from "path";
3853
3886
  import os6 from "os";
3854
- var SkillLoader = class {
3887
+ import { fileURLToPath as fileURLToPath3 } from "url";
3888
+ var SkillLoader = class _SkillLoader {
3889
+ bundledSkillsDir;
3855
3890
  projectSkillsDir;
3856
3891
  globalSkillsDir;
3857
3892
  cache = /* @__PURE__ */ new Map();
3858
- constructor(projectRoot) {
3893
+ conflicts = [];
3894
+ constructor(projectRoot, bundledDir) {
3859
3895
  this.projectSkillsDir = path13.join(projectRoot, ".bluma", "skills");
3860
3896
  this.globalSkillsDir = path13.join(os6.homedir(), ".bluma", "skills");
3897
+ this.bundledSkillsDir = bundledDir || _SkillLoader.resolveBundledDir();
3898
+ }
3899
+ /**
3900
+ * Resolve o diretório de skills nativas relativo ao binário (dist/config/skills).
3901
+ * Funciona tanto em ESM como quando executado a partir de dist/.
3902
+ */
3903
+ static resolveBundledDir() {
3904
+ if (process.env.NODE_ENV === "test" || process.env.JEST_WORKER_ID !== void 0) {
3905
+ if (typeof __dirname !== "undefined") {
3906
+ return path13.join(__dirname, "config", "skills");
3907
+ }
3908
+ return path13.join(process.cwd(), "dist", "config", "skills");
3909
+ }
3910
+ try {
3911
+ const currentFile = fileURLToPath3(import.meta.url);
3912
+ const distDir = path13.dirname(currentFile);
3913
+ return path13.join(distDir, "config", "skills");
3914
+ } catch {
3915
+ return path13.join(process.cwd(), "dist", "config", "skills");
3916
+ }
3861
3917
  }
3862
3918
  /**
3863
- * Lista skills disponíveis de ambas as fontes
3864
- * Skills de projeto têm prioridade sobre globais com mesmo nome
3919
+ * Lista skills disponíveis de todas as fontes.
3920
+ * Skills nativas sempre presentes; skills do utilizador com nomes conflitantes são excluídas.
3865
3921
  */
3866
3922
  listAvailable() {
3923
+ this.conflicts = [];
3867
3924
  const skills = /* @__PURE__ */ new Map();
3868
- const globalSkills = this.listFromDir(this.globalSkillsDir, "global");
3869
- for (const skill of globalSkills) {
3870
- skills.set(skill.name, skill);
3871
- }
3872
- const projectSkills = this.listFromDir(this.projectSkillsDir, "project");
3873
- for (const skill of projectSkills) {
3925
+ const bundledSkills = this.listFromDir(this.bundledSkillsDir, "bundled");
3926
+ const bundledNames = new Set(bundledSkills.map((s) => s.name));
3927
+ for (const skill of bundledSkills) {
3874
3928
  skills.set(skill.name, skill);
3875
3929
  }
3930
+ this.mergeUserSkills(skills, bundledNames, this.globalSkillsDir, "global");
3931
+ this.mergeUserSkills(skills, bundledNames, this.projectSkillsDir, "project");
3876
3932
  return Array.from(skills.values());
3877
3933
  }
3878
3934
  /**
3879
- * Lista skills de um diretório específico
3935
+ * Adiciona skills do utilizador ao mapa, detetando conflitos com nativas.
3880
3936
  */
3937
+ mergeUserSkills(skills, bundledNames, dir, source) {
3938
+ const userSkills = this.listFromDir(dir, source);
3939
+ for (const skill of userSkills) {
3940
+ if (bundledNames.has(skill.name)) {
3941
+ this.conflicts.push({
3942
+ name: skill.name,
3943
+ userSource: source,
3944
+ userPath: path13.join(dir, skill.name, "SKILL.md"),
3945
+ bundledPath: path13.join(this.bundledSkillsDir, skill.name, "SKILL.md")
3946
+ });
3947
+ continue;
3948
+ }
3949
+ skills.set(skill.name, skill);
3950
+ }
3951
+ }
3881
3952
  listFromDir(dir, source) {
3882
3953
  if (!fs11.existsSync(dir)) return [];
3883
3954
  try {
@@ -3889,9 +3960,6 @@ var SkillLoader = class {
3889
3960
  return [];
3890
3961
  }
3891
3962
  }
3892
- /**
3893
- * Carrega metadata de um path específico
3894
- */
3895
3963
  loadMetadataFromPath(skillPath, skillName, source) {
3896
3964
  if (!fs11.existsSync(skillPath)) return null;
3897
3965
  try {
@@ -3910,20 +3978,47 @@ var SkillLoader = class {
3910
3978
  }
3911
3979
  }
3912
3980
  /**
3913
- * Carrega skill completa - procura primeiro em projeto, depois global
3981
+ * Carrega skill completa.
3982
+ * Ordem: bundled > project > global.
3983
+ * Retorna null se não encontrada.
3984
+ * Lança erro se o nome existir como nativa E como skill do utilizador (conflito).
3914
3985
  */
3915
3986
  load(name) {
3916
3987
  if (this.cache.has(name)) return this.cache.get(name);
3988
+ const bundledPath = path13.join(this.bundledSkillsDir, name, "SKILL.md");
3917
3989
  const projectPath = path13.join(this.projectSkillsDir, name, "SKILL.md");
3918
- if (fs11.existsSync(projectPath)) {
3990
+ const globalPath = path13.join(this.globalSkillsDir, name, "SKILL.md");
3991
+ const existsBundled = fs11.existsSync(bundledPath);
3992
+ const existsProject = fs11.existsSync(projectPath);
3993
+ const existsGlobal = fs11.existsSync(globalPath);
3994
+ if (existsBundled && (existsProject || existsGlobal)) {
3995
+ const conflictSource = existsProject ? "project" : "global";
3996
+ const conflictPath = existsProject ? projectPath : globalPath;
3997
+ const conflict = {
3998
+ name,
3999
+ userSource: conflictSource,
4000
+ userPath: conflictPath,
4001
+ bundledPath
4002
+ };
4003
+ if (!this.conflicts.find((c) => c.name === name)) {
4004
+ this.conflicts.push(conflict);
4005
+ }
4006
+ }
4007
+ if (existsBundled) {
4008
+ const skill = this.loadFromPath(bundledPath, name, "bundled");
4009
+ if (skill) {
4010
+ this.cache.set(name, skill);
4011
+ return skill;
4012
+ }
4013
+ }
4014
+ if (existsProject) {
3919
4015
  const skill = this.loadFromPath(projectPath, name, "project");
3920
4016
  if (skill) {
3921
4017
  this.cache.set(name, skill);
3922
4018
  return skill;
3923
4019
  }
3924
4020
  }
3925
- const globalPath = path13.join(this.globalSkillsDir, name, "SKILL.md");
3926
- if (fs11.existsSync(globalPath)) {
4021
+ if (existsGlobal) {
3927
4022
  const skill = this.loadFromPath(globalPath, name, "global");
3928
4023
  if (skill) {
3929
4024
  this.cache.set(name, skill);
@@ -3932,13 +4027,11 @@ var SkillLoader = class {
3932
4027
  }
3933
4028
  return null;
3934
4029
  }
3935
- /**
3936
- * Carrega skill de um path específico
3937
- */
3938
4030
  loadFromPath(skillPath, name, source) {
3939
4031
  try {
3940
4032
  const raw = fs11.readFileSync(skillPath, "utf-8");
3941
4033
  const parsed = this.parseFrontmatter(raw);
4034
+ const skillDir = path13.dirname(skillPath);
3942
4035
  return {
3943
4036
  name: parsed.name || name,
3944
4037
  description: parsed.description || "",
@@ -3946,15 +4039,28 @@ var SkillLoader = class {
3946
4039
  source,
3947
4040
  version: parsed.version,
3948
4041
  author: parsed.author,
3949
- license: parsed.license
4042
+ license: parsed.license,
4043
+ references: this.scanAssets(path13.join(skillDir, "references")),
4044
+ scripts: this.scanAssets(path13.join(skillDir, "scripts"))
3950
4045
  };
3951
4046
  } catch {
3952
4047
  return null;
3953
4048
  }
3954
4049
  }
3955
- /**
3956
- * Parse simples de YAML frontmatter
3957
- */
4050
+ scanAssets(dir) {
4051
+ if (!fs11.existsSync(dir)) return [];
4052
+ try {
4053
+ return fs11.readdirSync(dir).filter((f) => {
4054
+ const fp = path13.join(dir, f);
4055
+ return fs11.statSync(fp).isFile();
4056
+ }).map((f) => ({
4057
+ name: f,
4058
+ path: path13.resolve(dir, f)
4059
+ }));
4060
+ } catch {
4061
+ return [];
4062
+ }
4063
+ }
3958
4064
  parseFrontmatter(raw) {
3959
4065
  const lines = raw.split("\n");
3960
4066
  if (lines[0]?.trim() !== "---") {
@@ -3990,25 +4096,38 @@ var SkillLoader = class {
3990
4096
  content
3991
4097
  };
3992
4098
  }
3993
- /**
3994
- * Limpa o cache
3995
- */
3996
4099
  clearCache() {
3997
4100
  this.cache.clear();
3998
4101
  }
3999
- /**
4000
- * Verifica se uma skill existe (em projeto ou global)
4001
- */
4002
4102
  exists(name) {
4103
+ const bundledPath = path13.join(this.bundledSkillsDir, name, "SKILL.md");
4003
4104
  const projectPath = path13.join(this.projectSkillsDir, name, "SKILL.md");
4004
4105
  const globalPath = path13.join(this.globalSkillsDir, name, "SKILL.md");
4005
- return fs11.existsSync(projectPath) || fs11.existsSync(globalPath);
4106
+ return fs11.existsSync(bundledPath) || fs11.existsSync(projectPath) || fs11.existsSync(globalPath);
4107
+ }
4108
+ /**
4109
+ * Retorna conflitos detetados (skills do utilizador com mesmo nome de nativas).
4110
+ */
4111
+ getConflicts() {
4112
+ return [...this.conflicts];
4006
4113
  }
4007
4114
  /**
4008
- * Retorna os diretórios de skills
4115
+ * Verifica se existem conflitos (útil para logs/warnings rápidos).
4009
4116
  */
4117
+ hasConflicts() {
4118
+ return this.conflicts.length > 0;
4119
+ }
4120
+ /**
4121
+ * Formata mensagens de conflito legíveis para o utilizador.
4122
+ */
4123
+ formatConflictWarnings() {
4124
+ return this.conflicts.map(
4125
+ (c) => `Skill "${c.name}" already exists as a native BluMa skill. Your skill at "${c.userPath}" (${c.userSource}) was ignored. Please rename your skill to avoid this conflict.`
4126
+ );
4127
+ }
4010
4128
  getSkillsDirs() {
4011
4129
  return {
4130
+ bundled: this.bundledSkillsDir,
4012
4131
  project: this.projectSkillsDir,
4013
4132
  global: this.globalSkillsDir
4014
4133
  };
@@ -4155,6 +4274,21 @@ var SYSTEM_PROMPT = `
4155
4274
  - NEVER invent skill names that aren't in \`<available_skills>\`
4156
4275
  - NEVER try to \`load_skill\` with a name that isn't in \`<available_skills>\`
4157
4276
  - Your base knowledge (testing, git, docker...) is NOT a skill - it's just knowledge you have
4277
+
4278
+ **Progressive Disclosure (Skills with references and scripts):**
4279
+
4280
+ Skills may include additional assets beyond the SKILL.md body:
4281
+ - \`references/\` \u2014 extra documentation files (e.g. REFERENCE.md, FORMS.md) loaded on-demand.
4282
+ - \`scripts/\` \u2014 ready-made executable scripts (e.g. Python) you can run directly.
4283
+
4284
+ When you load a skill via \`load_skill\`, the result includes a manifest listing available references and scripts with their absolute paths.
4285
+
4286
+ Rules:
4287
+ - **Do NOT read references or run scripts unless the task specifically requires them.** The SKILL.md body is often sufficient.
4288
+ - To read a reference: use \`read_file_lines\` with the absolute path from the manifest.
4289
+ - To run a script: use \`shell_command\` with \`python <absolute_path> [args]\`.
4290
+ - The SKILL.md body tells you WHEN to consult each reference or script (e.g. "for forms, read references/FORMS.md").
4291
+ - This keeps your context lean \u2014 only load what you need.
4158
4292
  </skills_knowledge>
4159
4293
 
4160
4294
  ---
@@ -4425,56 +4559,93 @@ var SANDBOX_PROMPT_SUFFIX = `
4425
4559
  Sandbox Name: {sandbox_name}
4426
4560
 
4427
4561
  You are running INSIDE an orchestrated sandbox / API container.
4562
+ You are NOT talking directly to a human; all inputs come from JSON payloads provided by an orchestrator (the Sandbox API).
4428
4563
 
4429
- In this mode:
4430
-
4431
- - You are NOT talking directly to a human; all inputs come from JSON payloads provided by an orchestrator (the Sandbox API).
4432
- - You MUST avoid interactive flows (no REPL, no prompts that wait for human input, no TUI/CLI menus).
4433
- - You MUST keep all outputs deterministic, concise and structured so that external systems can log and replay your reasoning.
4564
+ **Core principles in this mode:**
4565
+ - ZERO interactive flows (no REPL, no prompts, no TUI/CLI menus, no \`input()\`).
4566
+ - ALL outputs must be deterministic, concise and structured for machine parsing.
4567
+ - You own this workspace like a senior developer owns their machine: produce, deliver, clean up.
4434
4568
 
4435
4569
  ### Execution Capabilities (Python-only)
4436
4570
 
4437
- - You are allowed to:
4438
- - Generate and modify **Python code** (modules, scripts, notebooks, tests).
4439
- - Propose and run **Python commands only**, e.g.:
4440
- - \`python main.py\`
4441
- - \`python -m pytest\`
4442
- - Use the existing Python environment and preinstalled libraries inside the sandbox (e.g. pandas and other whitelisted packages).
4443
-
4444
- - You are NOT allowed to:
4445
- - Execute arbitrary shell commands (\`bash\`, \`sh\`, \`zsh\`, \`fish\`, \`cmd\`, \`powershell\`).
4446
- - Run system-level tools (\`docker\`, \`npm\`, \`node\`, \`git\`, \`curl\`, \`wget\`, package managers, etc.).
4447
- - Change global system configuration, users, permissions, or network settings.
4448
- - Depend on interactive stdin/stdout behavior (no \`input()\`, no click/typer prompts).
4449
-
4450
- ### Filesystem & IO
4451
-
4452
- - Assume you are working in a **project directory managed by the orchestrator**.
4453
- - You MAY:
4454
- - Read and write project files (source, tests, configs) as requested by the job.
4455
- - Create temporary Python files or modules needed to execute the job.
4456
- - You MUST NOT:
4457
- - Access files outside the project directory tree.
4458
- - Store secrets or credentials in code or logs.
4459
- - Rely on long-lived state: each job is independent and may run in a fresh environment.
4571
+ You are allowed to:
4572
+ - Generate and modify **Python code** (modules, scripts, notebooks, tests).
4573
+ - Run **Python commands only**: \`python main.py\`, \`python -m pytest\`, \`python script.py\`, etc.
4574
+ - Use the preinstalled Python environment and libraries (pandas, openpyxl, reportlab, etc.).
4575
+
4576
+ You are NOT allowed to:
4577
+ - Execute arbitrary shell commands (\`bash\`, \`sh\`, \`zsh\`, \`fish\`, \`cmd\`, \`powershell\`).
4578
+ - Run system-level tools (\`docker\`, \`npm\`, \`node\`, \`git\`, \`curl\`, \`wget\`, package managers).
4579
+ - Change system configuration, users, permissions, or network settings.
4580
+ - Use interactive stdin/stdout (\`input()\`, click/typer prompts, curses).
4581
+
4582
+ ### File Lifecycle in Sandbox (CRITICAL)
4583
+
4584
+ You are working in an **isolated job workspace**. Treat it as your personal development machine for this job.
4585
+ Follow this workflow for every task that produces deliverables:
4586
+
4587
+ **Step 1 \u2014 Analyse** the request and plan what files you need to generate.
4588
+ **Step 2 \u2014 Write a script** (e.g. \`_task_runner.py\`) to produce the deliverables programmatically.
4589
+ **Step 3 \u2014 Execute the script** via \`shell_command\` (\`python _task_runner.py\`).
4590
+ **Step 4 \u2014 Move or create final documents** inside \`./artifacts/\` directory.
4591
+ **Step 5 \u2014 Attach deliverables** \u2014 In your final \`message\` tool call (\`message_type: "result"\`), include the **absolute paths** of every deliverable file in the \`attachments\` array.
4592
+ **Step 6 \u2014 Clean up** \u2014 Delete all temporary scripts, intermediate files and working data that are NOT final artifacts.
4593
+
4594
+ **What MUST go in \`attachments\`:**
4595
+ - Documents the user should consume: reports, CSVs, PDFs, spreadsheets, ZIPs, JSON exports, images, etc.
4596
+ - Only files that exist inside \`./artifacts/\`.
4597
+ - Always use **absolute paths** (e.g. \`/app/artifacts/sales_report.pdf\`).
4598
+
4599
+ **What MUST NOT go in \`attachments\`:**
4600
+ - Scripts you wrote to generate the deliverables (\`.py\`, \`.sh\`, \`.ipynb\`).
4601
+ - Temporary or intermediate files (\`*.tmp\`, \`*.log\`, working data).
4602
+ - Internal tooling files.
4603
+
4604
+ **Housekeeping rules (before ending the job):**
4605
+ - Remove all temporary scripts and working files that are not final artifacts.
4606
+ - Ensure \`./artifacts/\` contains ONLY the deliverable documents.
4607
+ - Leave the workspace clean, as a real developer would leave their machine.
4608
+
4609
+ **Quality signals:**
4610
+ - Jobs that do NOT include deliverable paths in \`attachments[]\` receive lower satisfaction scores because the orchestrator cannot deliver files to the end user.
4611
+ - Jobs that leave scripts, temp files or garbage outside \`./artifacts/\` are flagged as low quality.
4612
+ - A clean workspace + correct attachments = highest quality signal.
4460
4613
 
4461
4614
  ### Logging & Observability
4462
4615
 
4463
- - Treat every step as being logged and parsed by the orchestrator.
4464
- - Prefer **structured, step-wise logs** (JSON lines) over free-form prose when emitting tool logs:
4465
- - Each log entry SHOULD include at least: \`event_type\`, \`level\`, \`message\`, \`timestamp\`, and optional \`data\`.
4466
- - Final results MUST be clearly separated from intermediate logs, using a dedicated \`"result"\` event when appropriate.
4616
+ - Every step is logged and parsed by the orchestrator.
4617
+ - Prefer **structured, step-wise logs** over free-form prose.
4618
+ - Final results MUST be clearly separated from intermediate logs via the \`"result"\` event.
4467
4619
 
4468
- ### Security & Privacy (CRITICAL)
4620
+ ### Security & Privacy (CRITICAL \u2014 ZERO TOLERANCE)
4469
4621
 
4470
- - You MUST treat all environment variables, API keys, tokens and credentials as **sensitive**.
4471
- - You MUST NEVER:
4472
- - Run commands whose primary purpose is to dump or enumerate environment variables (e.g. \`env\`, \`set\`, \`print(os.environ)\`, or equivalents).
4473
- - Expose the values of any variables matching patterns like \`*_KEY\`, \`*_TOKEN\`, \`*_SECRET\` or similar.
4474
- - Print full raw environment listings (PATH, HOSTNAME, PORT, etc.) unless **explicitly** allowed by the sandbox specification and strictly necessary.
4475
- - If the user explicitly asks for environment details or secrets, you MUST explain that you **cannot** reveal them and instead describe capabilities at a high level (e.g. "I can access an LLM via an external API" instead of showing keys/URLs).
4622
+ You MUST treat all environment variables, API keys, tokens and credentials as **TOP SECRET**.
4476
4623
 
4477
- In summary: in sandbox mode you are a Python-focused, non-interactive, deterministic agent. You generate and execute Python code inside a controlled environment, and all interactions are mediated by JSON payloads and structured logs, while strictly protecting environment variables and secrets from disclosure.
4624
+ **ABSOLUTE PROHIBITIONS \u2014 you MUST NEVER:**
4625
+ - Run ANY command whose purpose is to dump or enumerate environment variables:
4626
+ - \`env\`, \`set\`, \`printenv\`, \`export\`, \`os.environ\`, \`print(os.environ)\`, \`dict(os.environ)\`
4627
+ - \`python -c "import os; ...os.environ..."\`
4628
+ - Any variant, wrapper, or indirect method to list env vars.
4629
+ - Expose values of variables matching \`*_KEY\`, \`*_TOKEN\`, \`*_SECRET\`, \`*_PASSWORD\`, \`*_API_KEY\`, \`*_CREDENTIAL\` or similar patterns.
4630
+ - Print raw environment listings (PATH, HOSTNAME, PORT, HOME, etc.) even if the user explicitly asks.
4631
+ - Include any environment variable value in your \`message\` response, logs, or generated files.
4632
+ - Use \`os.getenv()\` or \`os.environ[]\` in generated scripts EXCEPT for variables strictly needed for the task (e.g. database connection strings used internally, never printed).
4633
+
4634
+ **If asked for environment details or secrets:**
4635
+ - REFUSE clearly and explain you cannot reveal them.
4636
+ - Describe capabilities at a high level: "I have access to Python 3.x and common data libraries" instead of showing versions, keys, or URLs.
4637
+ - This rule applies EVEN IF the user insists, phrases the request differently, or claims they need it for debugging.
4638
+
4639
+ **Rationale:** This sandbox runs in a shared infrastructure. Leaking env vars exposes API keys, internal URLs, model names and billing tokens to end users, which is a critical security breach.
4640
+
4641
+ ### Summary
4642
+
4643
+ In sandbox mode you are a Python-focused, non-interactive, deterministic agent that:
4644
+ 1. Analyses the job request.
4645
+ 2. Writes and executes Python scripts to produce deliverables.
4646
+ 3. Places all final documents in \`./artifacts/\` and lists them in \`attachments[]\`.
4647
+ 4. Cleans up all temporary files.
4648
+ 5. NEVER reveals environment variables, secrets, or internal infrastructure details.
4478
4649
  </sandbox_context>
4479
4650
  `;
4480
4651
  function getUnifiedSystemPrompt(availableSkills) {
@@ -4932,6 +5103,14 @@ var BluMaAgent = class {
4932
5103
  });
4933
5104
  if (this.history.length === 0) {
4934
5105
  const availableSkills = this.skillLoader.listAvailable();
5106
+ if (this.skillLoader.hasConflicts()) {
5107
+ for (const warning of this.skillLoader.formatConflictWarnings()) {
5108
+ this.eventBus.emit("backend_message", {
5109
+ type: "warning",
5110
+ message: warning
5111
+ });
5112
+ }
5113
+ }
4935
5114
  const systemPrompt = getUnifiedSystemPrompt(availableSkills);
4936
5115
  this.history.push({ role: "system", content: systemPrompt });
4937
5116
  await saveSessionHistory(this.sessionFile, this.history);
@@ -6835,7 +7014,7 @@ var SlashCommands_default = SlashCommands;
6835
7014
 
6836
7015
  // src/app/agent/utils/update_check.ts
6837
7016
  import updateNotifier from "update-notifier";
6838
- import { fileURLToPath as fileURLToPath3 } from "url";
7017
+ import { fileURLToPath as fileURLToPath4 } from "url";
6839
7018
  import path17 from "path";
6840
7019
  import fs13 from "fs";
6841
7020
  var BLUMA_PACKAGE_NAME = "@nomad-e/bluma-cli";
@@ -6870,9 +7049,9 @@ async function checkForUpdates() {
6870
7049
  pkg = findBlumaPackageJson(path17.dirname(binPath));
6871
7050
  }
6872
7051
  if (!pkg) {
6873
- const __filename = fileURLToPath3(import.meta.url);
6874
- const __dirname = path17.dirname(__filename);
6875
- pkg = findBlumaPackageJson(__dirname);
7052
+ const __filename = fileURLToPath4(import.meta.url);
7053
+ const __dirname2 = path17.dirname(__filename);
7054
+ pkg = findBlumaPackageJson(__dirname2);
6876
7055
  }
6877
7056
  if (!pkg) {
6878
7057
  return null;
@@ -7561,6 +7740,7 @@ async function runAgentMode() {
7561
7740
  const sessionId = envelope.message_id || uuidv43();
7562
7741
  let lastAssistantMessage = null;
7563
7742
  let reasoningBuffer = null;
7743
+ let lastAttachments = null;
7564
7744
  let resultEmitted = false;
7565
7745
  eventBus.on("backend_message", (payload) => {
7566
7746
  const timestamp = (/* @__PURE__ */ new Date()).toISOString();
@@ -7584,6 +7764,10 @@ async function runAgentMode() {
7584
7764
  if (typeof body === "string") {
7585
7765
  lastAssistantMessage = body;
7586
7766
  }
7767
+ const attachments = parsed?.attachments;
7768
+ if (Array.isArray(attachments)) {
7769
+ lastAttachments = attachments.filter((p) => typeof p === "string");
7770
+ }
7587
7771
  } catch {
7588
7772
  }
7589
7773
  }
@@ -7597,7 +7781,8 @@ async function runAgentMode() {
7597
7781
  message_id: envelope.message_id || sessionId,
7598
7782
  action: envelope.action || "unknown",
7599
7783
  last_assistant_message: lastAssistantMessage,
7600
- reasoning: reasoningBuffer
7784
+ reasoning: reasoningBuffer,
7785
+ attachments: lastAttachments
7601
7786
  }
7602
7787
  });
7603
7788
  process.exit(0);
@@ -7644,7 +7829,8 @@ async function runAgentMode() {
7644
7829
  message_id: envelope.message_id || sessionId,
7645
7830
  action: envelope.action || "unknown",
7646
7831
  last_assistant_message: lastAssistantMessage,
7647
- reasoning: reasoningBuffer
7832
+ reasoning: reasoningBuffer,
7833
+ attachments: lastAttachments
7648
7834
  }
7649
7835
  });
7650
7836
  process.exit(0);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nomad-e/bluma-cli",
3
- "version": "0.1.17",
3
+ "version": "0.1.19",
4
4
  "description": "BluMa independent agent for automation and advanced software engineering.",
5
5
  "author": "Alex Fonseca",
6
6
  "license": "Apache-2.0",
@@ -1,14 +0,0 @@
1
- {
2
- "mcpServers": {
3
- "reasoning": {
4
- "type": "stdio",
5
- "command": "cmd",
6
- "args": [
7
- "/c",
8
- "npx",
9
- "-y",
10
- "@sousaalex1605/bluma-nootebook"
11
- ]
12
- }
13
- }
14
- }
@@ -1,78 +0,0 @@
1
- {
2
- "models": [
3
- {
4
- "id": "google/gemini-3.1-pro-preview-customtools",
5
- "tier": "tools",
6
- "task_type": "multi_tool",
7
- "context_window": 1048576,
8
- "pricing": {
9
- "input_per_1m_tokens": "$2.00",
10
- "output_per_1m_tokens": "$12.00"
11
- },
12
- "description": "Enhanced version of Gemini 3.1 Pro tuned specifically for reliable tool selection and function calling. Designed to avoid overusing generic shell tools when more targeted tools are available, making it ideal for complex coding agents and multi-tool workflows in the CLI.",
13
- "best_for": [
14
- "Workflows that involve many different tools (file operations, search, tests, shell) and require the model to pick the right one.",
15
- "Coordinating multi-step coding tasks where safe and accurate tool use is more important than raw speed or cost.",
16
- "Complex debugging or refactors that need mixing code edits, search, tests, and shell commands.",
17
- "Agent-style tasks where tool misuse is risky and you want maximum reliability in tool choice."
18
- ],
19
- "not_for": "Very simple edits, quick questions, or cheap one-off operations that qwen3.5-flash can handle more economically."
20
- },
21
- {
22
- "id": "qwen/qwen3-coder-next",
23
- "tier": "coder",
24
- "task_type": "multi_file",
25
- "context_window": 262144,
26
- "pricing": {
27
- "input_per_1m_tokens": "$0.12",
28
- "output_per_1m_tokens": "$0.75"
29
- },
30
- "description": "Coder-focused MoE model optimized for long-horizon coding agents and local development workflows. Handles large codebases, multi-file refactors, and recovery from execution failures while remaining cost-effective for always-on agents.",
31
- "best_for": [
32
- "Large refactors across many files where the agent must keep track of a global design.",
33
- "Implementing new subsystems (features, services, modules) with tests and documentation.",
34
- "Agentic coding loops that involve planning, executing edits, running commands/tests, and iterating.",
35
- "Complex bug-hunting sessions where the model must reason over many files and past attempts."
36
- ],
37
- "not_for": "Tiny edits, trivial Q&A, or ultra-simple tasks where qwen3.5-flash is cheaper and fast enough."
38
- },
39
- {
40
- "id": "deepseek/deepseek-chat-v3.1",
41
- "tier": "reasoning",
42
- "task_type": "reasoning",
43
- "context_window": 32768,
44
- "pricing": {
45
- "input_per_1m_tokens": "$0.15",
46
- "output_per_1m_tokens": "$0.75"
47
- },
48
- "description": "Hybrid reasoning model supporting both thinking and non-thinking modes with strong performance on coding, tool use, and long-context reasoning. Good choice when deep structured reasoning or complex analysis is needed in addition to code generation.",
49
- "best_for": [
50
- "Deep reasoning on tricky bugs, architecture decisions, or algorithm design.",
51
- "Analyzing logs, traces, or large textual outputs to identify root causes.",
52
- "Complex code reviews or design reviews where clear, structured justification is needed.",
53
- "Cases where you explicitly want more deliberate reasoning rather than only fast responses."
54
- ],
55
- "not_for": "Purely mechanical edits or very simple tasks where a cheaper, faster model is sufficient."
56
- },
57
- {
58
- "id": "anthropic/claude-haiku-4.5",
59
- "tier": "heavy",
60
- "task_type": "complex",
61
- "context_window": 200000,
62
- "pricing": {
63
- "input_per_1m_tokens": "$1.00",
64
- "output_per_1m_tokens": "$5.00"
65
- },
66
- "description": "High-end, high-speed frontier model with extended thinking and strong performance on coding, reasoning, and computer-use tasks. Best reserved for the most complex, high-stakes workflows where you need frontier-level capability and robustness.",
67
- "best_for": [
68
- "Very large, high-risk changes to critical production code where maximum reliability matters.",
69
- "Long, multi-phase agentic tasks that must not fail (e.g. migrations, deep refactors, safety-sensitive changes).",
70
- "Scenarios where you want frontier-level coding performance and can afford higher cost.",
71
- "Coordinating many sub-agents or parallel tool workflows for large projects."
72
- ],
73
- "not_for": "Routine day-to-day coding tasks, quick fixes, or low-impact work where cheaper models are perfectly adequate."
74
- }
75
- ],
76
- "default_model": "qwen/qwen3-coder-next"
77
- }
78
-