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.
Files changed (2) hide show
  1. package/bin/bortex.js +137 -0
  2. 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;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bortexcode",
3
- "version": "1.2.5",
3
+ "version": "1.2.6",
4
4
  "description": "Bortex Code CLI - AI coding assistant powered by bortex.site",
5
5
  "homepage": "https://bortex.site",
6
6
  "license": "UNLICENSED",