bortexcode 1.2.5 → 1.2.6
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/bin/bortex.js +137 -0
- package/package.json +1 -1
package/bin/bortex.js
CHANGED
|
@@ -1167,6 +1167,85 @@ function classifyLocalPromptIntent(text) {
|
|
|
1167
1167
|
return null;
|
|
1168
1168
|
}
|
|
1169
1169
|
|
|
1170
|
+
function stripMatchingQuotes(value) {
|
|
1171
|
+
const s = String(value || '').trim();
|
|
1172
|
+
if (s.length >= 2 && ((s[0] === '"' && s[s.length - 1] === '"') || (s[0] === '\'' && s[s.length - 1] === '\'') || (s[0] === '`' && s[s.length - 1] === '`'))) {
|
|
1173
|
+
return s.slice(1, -1);
|
|
1174
|
+
}
|
|
1175
|
+
return s;
|
|
1176
|
+
}
|
|
1177
|
+
|
|
1178
|
+
function extractNaturalFilePath(text) {
|
|
1179
|
+
const raw = String(text || '');
|
|
1180
|
+
const quotedAfterFile = raw.match(/(?:file|path|percorso)\s+["'`]([^"'`]+)["'`]/i);
|
|
1181
|
+
if (quotedAfterFile?.[1]) return quotedAfterFile[1].trim();
|
|
1182
|
+
const tokenAfterFile = raw.match(/(?:file|path|percorso)\s+([^\s,;]+)/i);
|
|
1183
|
+
if (tokenAfterFile?.[1]) return stripMatchingQuotes(tokenAfterFile[1]);
|
|
1184
|
+
const absoluteOrRelative = raw.match(/["'`]?((?:[A-Za-z]:[\\/]|\/|~\/|\.{1,2}[\\/])[^"'`\s,;:]+)["'`]?/);
|
|
1185
|
+
if (absoluteOrRelative?.[1]) return absoluteOrRelative[1].trim();
|
|
1186
|
+
return '';
|
|
1187
|
+
}
|
|
1188
|
+
|
|
1189
|
+
function extractNaturalFileContent(text, filePath) {
|
|
1190
|
+
const raw = String(text || '').trim();
|
|
1191
|
+
const marker = raw.match(/(?:con\s+(?:contenuto|testo)|contenente|with\s+(?:content|text)|containing)\s+([\s\S]+)$/i);
|
|
1192
|
+
let content = marker?.[1] || '';
|
|
1193
|
+
if (!content && filePath) {
|
|
1194
|
+
const beforeFile = raw.slice(0, raw.toLowerCase().indexOf(filePath.toLowerCase())).trim();
|
|
1195
|
+
const writePrefix = beforeFile.match(/(?:scrivi|write|salva|metti)\s+([\s\S]+?)(?:\s+(?:nel|nella|in|su|to)\s+(?:file|path|percorso)?\s*)?$/i);
|
|
1196
|
+
if (writePrefix?.[1]) content = writePrefix[1];
|
|
1197
|
+
}
|
|
1198
|
+
content = String(content || '').trim();
|
|
1199
|
+
if (!content) return '';
|
|
1200
|
+
if ((content[0] === '"' || content[0] === '\'' || content[0] === '`')) {
|
|
1201
|
+
const quote = content[0];
|
|
1202
|
+
const end = content.indexOf(quote, 1);
|
|
1203
|
+
if (end > 0) return content.slice(1, end);
|
|
1204
|
+
}
|
|
1205
|
+
content = content
|
|
1206
|
+
.replace(/\s+(?:e|and|poi|then)\s+(?:rispondi|reply|dimmi|dillo|fammi sapere|tell me)\b[\s\S]*$/i, '')
|
|
1207
|
+
.replace(/\s+(?:rispondi|reply)\b[\s\S]*$/i, '')
|
|
1208
|
+
.trim();
|
|
1209
|
+
return stripMatchingQuotes(content);
|
|
1210
|
+
}
|
|
1211
|
+
|
|
1212
|
+
function buildNaturalLocalFileActionCalls(text) {
|
|
1213
|
+
const raw = String(text || '').trim();
|
|
1214
|
+
const lower = raw.toLowerCase();
|
|
1215
|
+
if (!raw) return null;
|
|
1216
|
+
const filePath = extractNaturalFilePath(raw);
|
|
1217
|
+
if (!filePath) return null;
|
|
1218
|
+
|
|
1219
|
+
if (/(?:^|\b)(leggi|mostra|apri|read|show|cat)(?:\b|$)/i.test(raw) && /(?:file|path|percorso|\/|\\|\.\.?[\\/])/.test(raw)) {
|
|
1220
|
+
return [{ tool: 'read', path: filePath }];
|
|
1221
|
+
}
|
|
1222
|
+
|
|
1223
|
+
const wantsAppend = /(?:^|\b)(appendi|accoda|aggiungi|append|add)(?:\b|$)/i.test(raw) && /(?:file|path|percorso|\/|\\|\.\.?[\\/])/.test(raw);
|
|
1224
|
+
const wantsWrite = /(?:^|\b)(crea|creare|scrivi|write|create|salva|metti)(?:\b|$)/i.test(raw) && /(?:file|path|percorso|\/|\\|\.\.?[\\/])/.test(raw);
|
|
1225
|
+
if (!wantsAppend && !wantsWrite) return null;
|
|
1226
|
+
|
|
1227
|
+
const textValue = extractNaturalFileContent(raw, filePath);
|
|
1228
|
+
if (!textValue && !/(?:^|\b)(crea|create)(?:\b|$)/i.test(raw)) return null;
|
|
1229
|
+
return [{
|
|
1230
|
+
tool: wantsAppend ? 'append' : 'write',
|
|
1231
|
+
path: filePath,
|
|
1232
|
+
text: textValue,
|
|
1233
|
+
confirm: true
|
|
1234
|
+
}];
|
|
1235
|
+
}
|
|
1236
|
+
|
|
1237
|
+
async function runNaturalLocalFileAction(opts, prompt) {
|
|
1238
|
+
const calls = buildNaturalLocalFileActionCalls(prompt);
|
|
1239
|
+
if (!calls) return { handled: false };
|
|
1240
|
+
opts.lastToolRunReport = await executeStructuredToolBatch(opts, calls, {
|
|
1241
|
+
continueOnError: true,
|
|
1242
|
+
goal: prompt,
|
|
1243
|
+
source: 'natural-local-file-action',
|
|
1244
|
+
reportLabel: 'natural-local-action'
|
|
1245
|
+
});
|
|
1246
|
+
return { handled: true, data: opts.lastToolRunReport };
|
|
1247
|
+
}
|
|
1248
|
+
|
|
1170
1249
|
async function runSshStatusCommand(opts, { quiet = false } = {}) {
|
|
1171
1250
|
const cwd = opts?.cwd || process.cwd();
|
|
1172
1251
|
const platform = os.platform();
|
|
@@ -2644,6 +2723,50 @@ async function executeStructuredBuiltinTool(opts, call) {
|
|
|
2644
2723
|
error: res.ok ? null : `${label} exit ${res.code == null ? 'null' : res.code}`
|
|
2645
2724
|
};
|
|
2646
2725
|
};
|
|
2726
|
+
if (tool === 'read') {
|
|
2727
|
+
const filePath = resolveCliPath(opts, call.path);
|
|
2728
|
+
try {
|
|
2729
|
+
const text = fs.readFileSync(filePath, 'utf8');
|
|
2730
|
+
process.stdout.write(text.endsWith('\n') ? text : `${text}\n`);
|
|
2731
|
+
return {
|
|
2732
|
+
ok: true,
|
|
2733
|
+
stdout: text,
|
|
2734
|
+
stderr: null,
|
|
2735
|
+
data: { path: filePath, bytes: Buffer.byteLength(text, 'utf8') }
|
|
2736
|
+
};
|
|
2737
|
+
} catch (err) {
|
|
2738
|
+
return { ok: false, stdout: null, stderr: err.message, data: { path: filePath }, error: err.message };
|
|
2739
|
+
}
|
|
2740
|
+
}
|
|
2741
|
+
if (tool === 'mkdir') {
|
|
2742
|
+
const dirPath = resolveCliPath(opts, call.path || '.');
|
|
2743
|
+
try {
|
|
2744
|
+
fs.mkdirSync(dirPath, { recursive: true });
|
|
2745
|
+
console.log(`Mkdir ok -> ${dirPath}`);
|
|
2746
|
+
return { ok: true, stdout: `Mkdir ok -> ${dirPath}\n`, stderr: null, data: { path: dirPath } };
|
|
2747
|
+
} catch (err) {
|
|
2748
|
+
return { ok: false, stdout: null, stderr: err.message, data: { path: dirPath }, error: err.message };
|
|
2749
|
+
}
|
|
2750
|
+
}
|
|
2751
|
+
if (tool === 'write' || tool === 'append') {
|
|
2752
|
+
const filePath = resolveCliPath(opts, call.path);
|
|
2753
|
+
const text = String(call.text ?? '');
|
|
2754
|
+
try {
|
|
2755
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
2756
|
+
if (tool === 'append') fs.appendFileSync(filePath, text, 'utf8');
|
|
2757
|
+
else fs.writeFileSync(filePath, text, 'utf8');
|
|
2758
|
+
const label = tool === 'append' ? 'Append' : 'Write';
|
|
2759
|
+
console.log(`${label} ok -> ${filePath}`);
|
|
2760
|
+
return {
|
|
2761
|
+
ok: true,
|
|
2762
|
+
stdout: `${label} ok -> ${filePath}\n`,
|
|
2763
|
+
stderr: null,
|
|
2764
|
+
data: { path: filePath, bytes: Buffer.byteLength(text, 'utf8'), mode: tool }
|
|
2765
|
+
};
|
|
2766
|
+
} catch (err) {
|
|
2767
|
+
return { ok: false, stdout: null, stderr: err.message, data: { path: filePath, mode: tool }, error: err.message };
|
|
2768
|
+
}
|
|
2769
|
+
}
|
|
2647
2770
|
if (tool === 'gitStatus') {
|
|
2648
2771
|
const res = await runChild('git', ['status', '--short', ...(call.branch ? ['-b'] : [])], { cwd: opts.cwd, shell: false });
|
|
2649
2772
|
const entries = parseGitStatusShortEntries(res.stdout || '');
|
|
@@ -3222,6 +3345,11 @@ function buildStructuredToolPlanFromGoal(opts, goal) {
|
|
|
3222
3345
|
{ tool: 'review', mode: 'all', fixSuggestions: true }
|
|
3223
3346
|
];
|
|
3224
3347
|
|
|
3348
|
+
const naturalFileCalls = buildNaturalLocalFileActionCalls(g);
|
|
3349
|
+
if (naturalFileCalls) {
|
|
3350
|
+
return naturalFileCalls;
|
|
3351
|
+
}
|
|
3352
|
+
|
|
3225
3353
|
const wantsStatus = /(verifica|controlla|check|mostra|stato|status|attivo|active|running|enabled|on)/.test(lower);
|
|
3226
3354
|
if (wantsStatus && /(\bssh\b|sshd|remote login)/.test(lower)) {
|
|
3227
3355
|
return [{ tool: 'project' }, { tool: 'sshStatus' }];
|
|
@@ -6397,6 +6525,8 @@ async function runSinglePrompt(opts) {
|
|
|
6397
6525
|
const localResult = await handleLocalCommand(opts, opts.prompt);
|
|
6398
6526
|
if (localResult.handled) return;
|
|
6399
6527
|
}
|
|
6528
|
+
const naturalFileAction = await runNaturalLocalFileAction(opts, opts.prompt);
|
|
6529
|
+
if (naturalFileAction.handled) return;
|
|
6400
6530
|
const localIntent = classifyLocalPromptIntent(opts.prompt);
|
|
6401
6531
|
if (localIntent?.command) {
|
|
6402
6532
|
const localResult = await handleLocalCommand(opts, localIntent.command);
|
|
@@ -6495,6 +6625,13 @@ async function runRepl(opts) {
|
|
|
6495
6625
|
continue;
|
|
6496
6626
|
}
|
|
6497
6627
|
}
|
|
6628
|
+
try {
|
|
6629
|
+
const naturalFileAction = await runNaturalLocalFileAction(opts, line);
|
|
6630
|
+
if (naturalFileAction.handled) continue;
|
|
6631
|
+
} catch (err) {
|
|
6632
|
+
console.error(`Local tool error: ${err.message}`);
|
|
6633
|
+
continue;
|
|
6634
|
+
}
|
|
6498
6635
|
if (opts.offline) {
|
|
6499
6636
|
console.log('Offline mode: use /help for local tools or restart without --offline to use the server.');
|
|
6500
6637
|
continue;
|