@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.
- package/README.md +62 -12
- package/dist/config/native_tools.json +7 -0
- package/dist/config/skills/git-commit/LICENSE.txt +18 -0
- package/dist/config/skills/git-commit/SKILL.md +258 -0
- package/dist/config/skills/git-commit/references/REFERENCE.md +249 -0
- package/dist/config/skills/git-commit/scripts/validate_commit_msg.py +163 -0
- package/dist/config/skills/git-pr/LICENSE.txt +18 -0
- package/dist/config/skills/git-pr/SKILL.md +293 -0
- package/dist/config/skills/git-pr/references/REFERENCE.md +256 -0
- package/dist/config/skills/git-pr/scripts/validate_commits.py +112 -0
- package/dist/config/skills/pdf/LICENSE.txt +26 -0
- package/dist/config/skills/pdf/SKILL.md +327 -0
- package/dist/config/skills/pdf/references/FORMS.md +69 -0
- package/dist/config/skills/pdf/references/REFERENCE.md +52 -0
- package/dist/config/skills/pdf/scripts/create_report.py +59 -0
- package/dist/config/skills/pdf/scripts/merge_pdfs.py +39 -0
- package/dist/config/skills/skill-creator/LICENSE.txt +26 -0
- package/dist/config/skills/skill-creator/SKILL.md +229 -0
- package/dist/config/skills/xlsx/LICENSE.txt +18 -0
- package/dist/config/skills/xlsx/SKILL.md +298 -0
- package/dist/config/skills/xlsx/references/REFERENCE.md +337 -0
- package/dist/config/skills/xlsx/scripts/office/__init__.py +2 -0
- package/dist/config/skills/xlsx/scripts/office/__pycache__/__init__.cpython-312.pyc +0 -0
- package/dist/config/skills/xlsx/scripts/office/__pycache__/pack.cpython-312.pyc +0 -0
- package/dist/config/skills/xlsx/scripts/office/__pycache__/soffice.cpython-312.pyc +0 -0
- package/dist/config/skills/xlsx/scripts/office/__pycache__/unpack.cpython-312.pyc +0 -0
- package/dist/config/skills/xlsx/scripts/office/__pycache__/validate.cpython-312.pyc +0 -0
- package/dist/config/skills/xlsx/scripts/office/pack.py +58 -0
- package/dist/config/skills/xlsx/scripts/office/soffice.py +180 -0
- package/dist/config/skills/xlsx/scripts/office/unpack.py +63 -0
- package/dist/config/skills/xlsx/scripts/office/validate.py +122 -0
- package/dist/config/skills/xlsx/scripts/recalc.py +143 -0
- package/dist/main.js +275 -89
- package/package.json +1 -1
- package/dist/config/example.bluma-mcp.json.txt +0 -14
- package/dist/config/models_config.json +0 -78
- package/dist/skills/git-conventional/LICENSE.txt +0 -3
- package/dist/skills/git-conventional/SKILL.md +0 -83
- package/dist/skills/skill-creator/SKILL.md +0 -495
- package/dist/skills/testing/LICENSE.txt +0 -3
- 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:
|
|
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
|
|
3412
|
-
const configPath = path10.resolve(
|
|
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
|
|
3503
|
-
const defaultConfigPath = path11.resolve(
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
3864
|
-
* Skills
|
|
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
|
|
3869
|
-
|
|
3870
|
-
|
|
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
|
-
*
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
*
|
|
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
|
-
|
|
4430
|
-
|
|
4431
|
-
-
|
|
4432
|
-
- You
|
|
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
|
-
|
|
4438
|
-
|
|
4439
|
-
|
|
4440
|
-
|
|
4441
|
-
|
|
4442
|
-
|
|
4443
|
-
|
|
4444
|
-
-
|
|
4445
|
-
|
|
4446
|
-
|
|
4447
|
-
|
|
4448
|
-
|
|
4449
|
-
|
|
4450
|
-
|
|
4451
|
-
|
|
4452
|
-
|
|
4453
|
-
|
|
4454
|
-
|
|
4455
|
-
|
|
4456
|
-
|
|
4457
|
-
|
|
4458
|
-
|
|
4459
|
-
|
|
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
|
-
-
|
|
4464
|
-
- Prefer **structured, step-wise logs**
|
|
4465
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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 =
|
|
6874
|
-
const
|
|
6875
|
-
pkg = findBlumaPackageJson(
|
|
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,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
|
-
|