bortexcode 1.2.1 → 1.2.3

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 +377 -7
  2. package/package.json +2 -1
package/bin/bortex.js CHANGED
@@ -170,7 +170,7 @@ function usage() {
170
170
  console.log(' -h, --help Show help');
171
171
  console.log('');
172
172
  console.log('REPL commands: /agent on|off, /status, /exit, /help, /llm-config');
173
- console.log('Local tools: /pwd, /cd, /ls, /tree, /read, /write, /append, /mkdir, /git, /sh');
173
+ console.log('Local tools: /pwd, /cd, /ls, /tree, /read, /write, /append, /mkdir, /git, /ssh-status, /sh');
174
174
  console.log('');
175
175
  console.log('Environment:');
176
176
  console.log(' BORTEX_URL Server URL');
@@ -427,16 +427,15 @@ async function maybePromptForCliUpdate(opts = {}) {
427
427
  const installTarget = latest.installTarget || 'bortexcode';
428
428
  const installCommand = latest.installCommand || `npm install -g ${installTarget}`;
429
429
 
430
- if (shouldSkipUpdatePrompt(latest.version) && !opts.forceUpdateCheck) {
431
- return;
432
- }
433
-
434
430
  console.log(`Update available: Bortex Code ${CLI_VERSION} -> ${latest.version}`);
435
431
  console.log(`Install command: ${installCommand}`);
436
432
 
433
+ if (!process.stdin?.isTTY || !process.stdout?.isTTY) {
434
+ return;
435
+ }
436
+
437
437
  const shouldInstall = await promptYesNo('Install update now?', true);
438
438
  if (!shouldInstall) {
439
- rememberDeclinedUpdate(latest.version);
440
439
  return;
441
440
  }
442
441
 
@@ -1147,10 +1146,257 @@ function detectWorkspaceProfile(opts) {
1147
1146
  return profile;
1148
1147
  }
1149
1148
 
1149
+ function classifyLocalPromptIntent(text) {
1150
+ const t = String(text || '').toLowerCase();
1151
+ if (!t) return null;
1152
+ const wantsStatus = /(verifica|controlla|check|show|mostra|stato|status|attivo|active|running|enabled|on)/.test(t);
1153
+ if (wantsStatus && (/(\bssh\b|sshd|remote login)/.test(t))) {
1154
+ return { command: '/ssh-status' };
1155
+ }
1156
+ if (wantsStatus && (/(whoami|username|user name|hostname|host name|machine|computer|system|sistema|platform|os|runtime|shell|environment|env|path)/.test(t) || /(\bnode\b|\bnpm\b)/.test(t))) {
1157
+ return { command: '/sys-status' };
1158
+ }
1159
+ if (wantsStatus && /(\bprocess\b|\bprocessi\b|\bpid\b|\bcpu\b|\bram\b|\bmemory\b|\bmem\b)/.test(t)) {
1160
+ const processMatch = t.match(/(?:process(?:o|i)?|pid)\s+(?:di\s+|of\s+)?([a-z0-9._-]{2,})/i) || t.match(/([a-z0-9._-]{2,})\s+(?:process(?:o|i)?|pid)/i);
1161
+ return { command: processMatch ? `/process-status ${processMatch[1]}` : '/process-status' };
1162
+ }
1163
+ if (wantsStatus && /(\bport\b|\bporte\b|\bsocket\b|\blisten\b|\blistening\b)/.test(t)) {
1164
+ const portMatch = t.match(/\b(\d{2,5})\b/);
1165
+ return { command: portMatch ? `/port-status ${portMatch[1]}` : '/port-status' };
1166
+ }
1167
+ return null;
1168
+ }
1169
+
1170
+ async function runSshStatusCommand(opts, { quiet = false } = {}) {
1171
+ const cwd = opts?.cwd || process.cwd();
1172
+ const platform = os.platform();
1173
+ let command = '';
1174
+ let stdout = '';
1175
+ let stderr = '';
1176
+ let enabled = null;
1177
+ let active = null;
1178
+
1179
+ if (platform === 'darwin') {
1180
+ command = 'launchctl print-disabled system | grep com.openssh.sshd';
1181
+ const res = await runChild('bash', ['-lc', 'launchctl print-disabled system 2>/dev/null | grep -i "com.openssh.sshd" || true'], { cwd, shell: false });
1182
+ stdout = String(res.stdout || '').trim();
1183
+ stderr = String(res.stderr || '').trim();
1184
+ const match = stdout.match(/com\.openssh\.sshd"\s*=>\s*(enabled|disabled)/i);
1185
+ if (match) {
1186
+ enabled = match[1].toLowerCase() === 'enabled';
1187
+ active = enabled;
1188
+ }
1189
+ } else if (platform === 'win32') {
1190
+ command = 'Get-Service sshd';
1191
+ const res = await runChild('powershell.exe', [
1192
+ '-NoProfile',
1193
+ '-Command',
1194
+ 'if (Get-Service sshd -ErrorAction SilentlyContinue) { (Get-Service sshd).Status } else { "Missing" }'
1195
+ ], { cwd, shell: false });
1196
+ stdout = String(res.stdout || '').trim();
1197
+ stderr = String(res.stderr || '').trim();
1198
+ if (/running/i.test(stdout)) active = true;
1199
+ else if (/stopped/i.test(stdout)) active = false;
1200
+ } else {
1201
+ command = 'systemctl is-active sshd || systemctl is-active ssh';
1202
+ const res = await runChild('bash', [
1203
+ '-lc',
1204
+ 'if command -v systemctl >/dev/null 2>&1; then if systemctl is-active sshd >/dev/null 2>&1 || systemctl is-active ssh >/dev/null 2>&1; then echo active; elif systemctl is-enabled sshd >/dev/null 2>&1 || systemctl is-enabled ssh >/dev/null 2>&1; then echo enabled; else echo inactive; fi; elif pgrep -x sshd >/dev/null 2>&1; then echo active; else echo unknown; fi'
1205
+ ], { cwd, shell: false });
1206
+ stdout = String(res.stdout || '').trim();
1207
+ stderr = String(res.stderr || '').trim();
1208
+ if (/active/i.test(stdout)) active = true;
1209
+ else if (/enabled/i.test(stdout)) active = true;
1210
+ else if (/inactive|stopped/i.test(stdout)) active = false;
1211
+ }
1212
+
1213
+ const statusText = platform === 'darwin'
1214
+ ? `macOS Remote Login: ${enabled === true ? 'On' : enabled === false ? 'Off' : 'Unknown'}`
1215
+ : platform === 'win32'
1216
+ ? `Windows SSH service: ${stdout || 'Unknown'}`
1217
+ : `SSH service: ${active === true ? 'active' : active === false ? 'inactive' : stdout || 'unknown'}`;
1218
+
1219
+ if (!quiet) console.log(statusText);
1220
+ return {
1221
+ ok: true,
1222
+ stdout: statusText,
1223
+ stderr: stderr || null,
1224
+ data: {
1225
+ platform,
1226
+ command,
1227
+ enabled,
1228
+ active,
1229
+ raw: stdout || null,
1230
+ stderr: stderr || null
1231
+ }
1232
+ };
1233
+ }
1234
+
1235
+ async function runSystemStatusCommand(opts) {
1236
+ const cwd = opts?.cwd || process.cwd();
1237
+ const platform = os.platform();
1238
+ const arch = os.arch();
1239
+ const release = os.release();
1240
+ const shell = String(process.env.SHELL || process.env.ComSpec || '').trim() || null;
1241
+
1242
+ const runText = async (command, args, fallback = '') => {
1243
+ const res = await runChild(command, args, { cwd, shell: false });
1244
+ const text = String(res.stdout || '').trim();
1245
+ if (text) return text.split(/\r?\n/)[0].trim();
1246
+ return fallback;
1247
+ };
1248
+
1249
+ let user = null;
1250
+ let host = null;
1251
+ if (platform === 'win32') {
1252
+ user = await runText('powershell.exe', ['-NoProfile', '-Command', '$env:USERNAME'], 'unknown');
1253
+ host = await runText('powershell.exe', ['-NoProfile', '-Command', '$env:COMPUTERNAME'], 'unknown');
1254
+ } else {
1255
+ user = await runText('bash', ['-lc', 'whoami'], 'unknown');
1256
+ host = await runText('bash', ['-lc', 'hostname'], 'unknown');
1257
+ }
1258
+
1259
+ const nodeRes = await runChild('node', ['--version'], { cwd, shell: false });
1260
+ const npmRes = await runChild('npm', ['--version'], { cwd, shell: false });
1261
+ const gitRes = await runChild('git', ['--version'], { cwd, shell: false });
1262
+ const sshRes = await runSshStatusCommand(opts, { quiet: true });
1263
+ const lines = [
1264
+ `Platform: ${platform} ${release} ${arch}`,
1265
+ `User: ${user}`,
1266
+ `Host: ${host}`,
1267
+ `Shell: ${shell || 'unknown'}`,
1268
+ `Node: ${nodeRes.ok ? String(nodeRes.stdout || '').trim() : 'not found'}`,
1269
+ `npm: ${npmRes.ok ? String(npmRes.stdout || '').trim() : 'not found'}`,
1270
+ `Git: ${gitRes.ok ? String(gitRes.stdout || '').trim() : 'not found'}`,
1271
+ sshRes?.stdout ? `SSH: ${sshRes.stdout}` : 'SSH: unknown'
1272
+ ];
1273
+ const summary = lines.join('\n');
1274
+ console.log(summary);
1275
+ return {
1276
+ ok: true,
1277
+ stdout: summary,
1278
+ stderr: null,
1279
+ data: {
1280
+ platform,
1281
+ release,
1282
+ arch,
1283
+ user,
1284
+ host,
1285
+ shell,
1286
+ nodeVersion: nodeRes.ok ? String(nodeRes.stdout || '').trim() : null,
1287
+ npmVersion: npmRes.ok ? String(npmRes.stdout || '').trim() : null,
1288
+ gitVersion: gitRes.ok ? String(gitRes.stdout || '').trim() : null,
1289
+ ssh: sshRes?.data || null
1290
+ }
1291
+ };
1292
+ }
1293
+
1294
+ async function runProcessStatusCommand(opts, call = {}) {
1295
+ const cwd = opts?.cwd || process.cwd();
1296
+ const platform = os.platform();
1297
+ const filter = String(call.name || call.filter || '').trim();
1298
+ const filterLabel = filter || null;
1299
+ let command = '';
1300
+ let stdout = '';
1301
+ let stderr = '';
1302
+
1303
+ if (platform === 'win32') {
1304
+ const escaped = filter.replace(/'/g, "''");
1305
+ const ps = filter
1306
+ ? `Get-Process | Where-Object { $_.ProcessName -like '*${escaped}*' } | Select-Object -First 20 Id,ProcessName,CPU,WS | Format-Table -AutoSize`
1307
+ : 'Get-Process | Sort-Object CPU -Descending | Select-Object -First 12 Id,ProcessName,CPU,WS | Format-Table -AutoSize';
1308
+ command = ps;
1309
+ const res = await runChild('powershell.exe', ['-NoProfile', '-Command', ps], { cwd, shell: false });
1310
+ stdout = String(res.stdout || '').trim();
1311
+ stderr = String(res.stderr || '').trim();
1312
+ } else {
1313
+ const quoted = JSON.stringify(filter);
1314
+ const bashCmd = filter
1315
+ ? `pgrep -af ${quoted} || true`
1316
+ : 'ps -ax -o pid=,ppid=,comm=,%cpu=,%mem= | head -n 12';
1317
+ command = bashCmd;
1318
+ const res = await runChild('bash', ['-lc', bashCmd], { cwd, shell: false });
1319
+ stdout = String(res.stdout || '').trim();
1320
+ stderr = String(res.stderr || '').trim();
1321
+ }
1322
+
1323
+ const text = stdout || '(no matching processes)';
1324
+ console.log(text);
1325
+ return {
1326
+ ok: true,
1327
+ stdout: text,
1328
+ stderr: stderr || null,
1329
+ data: {
1330
+ platform,
1331
+ filter: filterLabel,
1332
+ command,
1333
+ raw: stdout || null,
1334
+ stderr: stderr || null
1335
+ }
1336
+ };
1337
+ }
1338
+
1339
+ async function runPortStatusCommand(opts, call = {}) {
1340
+ const cwd = opts?.cwd || process.cwd();
1341
+ const platform = os.platform();
1342
+ const target = String(call.port || call.target || '').trim();
1343
+ const isPort = /^\d+$/.test(target);
1344
+ let command = '';
1345
+ let stdout = '';
1346
+ let stderr = '';
1347
+
1348
+ if (platform === 'win32') {
1349
+ const ps = isPort
1350
+ ? `Get-NetTCPConnection -State Listen -LocalPort ${Number(target)} | Select-Object LocalAddress,LocalPort,OwningProcess | Format-Table -AutoSize`
1351
+ : 'Get-NetTCPConnection -State Listen | Select-Object -First 20 LocalAddress,LocalPort,OwningProcess | Format-Table -AutoSize';
1352
+ command = ps;
1353
+ const res = await runChild('powershell.exe', ['-NoProfile', '-Command', ps], { cwd, shell: false });
1354
+ stdout = String(res.stdout || '').trim();
1355
+ stderr = String(res.stderr || '').trim();
1356
+ } else {
1357
+ const bashCmd = isPort
1358
+ ? `if command -v lsof >/dev/null 2>&1; then lsof -nP -iTCP:${Number(target)} -sTCP:LISTEN || true; elif command -v ss >/dev/null 2>&1; then ss -ltnp | grep ":${Number(target)} " || true; else netstat -an 2>/dev/null | grep "${Number(target)}" || true; fi`
1359
+ : 'if command -v lsof >/dev/null 2>&1; then lsof -nP -iTCP -sTCP:LISTEN | head -n 20; elif command -v ss >/dev/null 2>&1; then ss -ltnp | head -n 20; else netstat -an 2>/dev/null | grep LISTEN | head -n 20; fi';
1360
+ command = bashCmd;
1361
+ const res = await runChild('bash', ['-lc', bashCmd], { cwd, shell: false });
1362
+ stdout = String(res.stdout || '').trim();
1363
+ stderr = String(res.stderr || '').trim();
1364
+ }
1365
+
1366
+ const text = stdout || (isPort ? `(port ${target} not listening)` : '(no listening ports found)');
1367
+ console.log(text);
1368
+ return {
1369
+ ok: true,
1370
+ stdout: text,
1371
+ stderr: stderr || null,
1372
+ data: {
1373
+ platform,
1374
+ target: target || null,
1375
+ command,
1376
+ raw: stdout || null,
1377
+ stderr: stderr || null
1378
+ }
1379
+ };
1380
+ }
1381
+
1150
1382
  function suggestCommandsForStep(stepText, opts) {
1151
1383
  const t = String(stepText || '').toLowerCase();
1152
1384
  const profile = opts ? detectWorkspaceProfile(opts) : { suggested: { test: [] } };
1153
1385
  const testCmds = Array.isArray(profile.suggested?.test) ? profile.suggested.test.slice(0, 2) : [];
1386
+ if (/(\bssh\b|sshd|remote login)/.test(t) && /(verifica|controlla|check|show|mostra|stato|status|attivo|active|running|enabled|on)/.test(t)) {
1387
+ return ['/ssh-status', '/status'];
1388
+ }
1389
+ if ((/(whoami|username|user name|hostname|host name|machine|computer|system|sistema|platform|os|runtime|shell|environment|env|path)/.test(t) || /(\bnode\b|\bnpm\b)/.test(t)) && /(verifica|controlla|check|show|mostra|stato|status|attivo|active|running|enabled|on)/.test(t)) {
1390
+ return ['/sys-status', '/status'];
1391
+ }
1392
+ if (/(\bprocess\b|\bprocessi\b|\bpid\b|\bcpu\b|\bram\b|\bmemory\b|\bmem\b)/.test(t) && /(verifica|controlla|check|show|mostra|stato|status|attivo|active|running|enabled|on)/.test(t)) {
1393
+ const processMatch = t.match(/(?:process(?:o|i)?|pid)\s+(?:di\s+|of\s+)?([a-z0-9._-]{2,})/i) || t.match(/([a-z0-9._-]{2,})\s+(?:process(?:o|i)?|pid)/i);
1394
+ return [processMatch ? `/process-status ${processMatch[1]}` : '/process-status', '/status'];
1395
+ }
1396
+ if (/(\bport\b|\bporte\b|\bsocket\b|\blisten\b|\blistening\b)/.test(t) && /(verifica|controlla|check|show|mostra|stato|status|attivo|active|running|enabled|on)/.test(t)) {
1397
+ const portMatch = t.match(/\b(\d{2,5})\b/);
1398
+ return [portMatch ? `/port-status ${portMatch[1]}` : '/port-status', '/status'];
1399
+ }
1154
1400
  if (/analizza|contesto|file/.test(t)) return ['/ls', '/tree . 2', '/git status -sb', '/diff all'];
1155
1401
  if (/riproduci|problema|causa/.test(t)) return ['/git status -sb', '/read <file>', ...(testCmds[0] ? [`/sh ${testCmds[0]}`] : ['/sh <cmd test/run>'])];
1156
1402
  if (/fix|modifiche|implementa/.test(t)) return ['/read <file>', '/write <file> <text> | /patch-apply-inline', '/diff unstaged'];
@@ -1194,6 +1440,10 @@ function isReadMostlyCommandForAuto(cmd) {
1194
1440
  c.startsWith('/hunks') ||
1195
1441
  c.startsWith('/show-hunk') ||
1196
1442
  c.startsWith('/status') ||
1443
+ c.startsWith('/ssh-status') ||
1444
+ c.startsWith('/sys-status') ||
1445
+ c.startsWith('/process-status') ||
1446
+ c.startsWith('/port-status') ||
1197
1447
  c.startsWith('/history') ||
1198
1448
  c.startsWith('/artifacts') ||
1199
1449
  c.startsWith('/project') ||
@@ -2456,6 +2706,18 @@ async function executeStructuredBuiltinTool(opts, call) {
2456
2706
  };
2457
2707
  return { ok: true, stdout: stdout || null, stderr: null, data };
2458
2708
  }
2709
+ if (tool === 'sshStatus') {
2710
+ return runSshStatusCommand(opts);
2711
+ }
2712
+ if (tool === 'systemStatus') {
2713
+ return runSystemStatusCommand(opts);
2714
+ }
2715
+ if (tool === 'processStatus') {
2716
+ return runProcessStatusCommand(opts, call);
2717
+ }
2718
+ if (tool === 'portStatus') {
2719
+ return runPortStatusCommand(opts, call);
2720
+ }
2459
2721
  if (tool === 'search') {
2460
2722
  const pattern = String(call.pattern || '').trim();
2461
2723
  const searchPath = String(call.path || '.');
@@ -2596,6 +2858,8 @@ function validateStructuredToolCall(raw) {
2596
2858
  case 'pwd':
2597
2859
  case 'project':
2598
2860
  case 'gitStatus':
2861
+ case 'systemStatus':
2862
+ case 'sshStatus':
2599
2863
  break;
2600
2864
  case 'runTest':
2601
2865
  case 'runBuild':
@@ -2666,6 +2930,14 @@ function validateStructuredToolCall(raw) {
2666
2930
  break;
2667
2931
  case 'commitSuggest':
2668
2932
  break;
2933
+ case 'processStatus':
2934
+ if (call.name != null && typeof call.name !== 'string') return { ok: false, error: '`name` deve essere stringa.' };
2935
+ if (call.filter != null && typeof call.filter !== 'string') return { ok: false, error: '`filter` deve essere stringa.' };
2936
+ break;
2937
+ case 'portStatus':
2938
+ if (call.port != null && typeof call.port !== 'string' && !Number.isFinite(Number(call.port))) return { ok: false, error: '`port` deve essere stringa o numero.' };
2939
+ if (call.target != null && typeof call.target !== 'string' && !Number.isFinite(Number(call.target))) return { ok: false, error: '`target` deve essere stringa o numero.' };
2940
+ break;
2669
2941
  default:
2670
2942
  return { ok: false, error: `Tool non supportato: ${tool}` };
2671
2943
  }
@@ -2693,6 +2965,10 @@ function structuredToolCallToLocalCommand(call) {
2693
2965
  case 'gitDiff': return `gitDiff(${call.mode || 'all'}${call.statOnly === false ? ',patch' : ',stat'})`;
2694
2966
  case 'search': return `search(${call.pattern}${call.path ? ` in ${call.path}` : ''}${call.glob ? ` glob=${call.glob}` : ''})`;
2695
2967
  case 'git': return `/git ${call.args.join(' ')}`;
2968
+ case 'sshStatus': return '/ssh-status';
2969
+ case 'systemStatus': return '/sys-status';
2970
+ case 'processStatus': return `/process-status${call.name || call.filter ? ` ${call.name || call.filter}` : ''}`;
2971
+ case 'portStatus': return `/port-status${call.port || call.target ? ` ${call.port || call.target}` : ''}`;
2696
2972
  case 'sh': return `/sh ${call.cmd}`;
2697
2973
  case 'review': {
2698
2974
  const mode = call.mode ? ` --${call.mode}` : '';
@@ -2946,6 +3222,23 @@ function buildStructuredToolPlanFromGoal(opts, goal) {
2946
3222
  { tool: 'review', mode: 'all', fixSuggestions: true }
2947
3223
  ];
2948
3224
 
3225
+ const wantsStatus = /(verifica|controlla|check|mostra|stato|status|attivo|active|running|enabled|on)/.test(lower);
3226
+ if (wantsStatus && /(\bssh\b|sshd|remote login)/.test(lower)) {
3227
+ return [{ tool: 'project' }, { tool: 'sshStatus' }];
3228
+ }
3229
+ if (wantsStatus && (/(whoami|username|user name|hostname|host name|machine|computer|system|sistema|platform|os|runtime|shell|environment|env|path)/.test(lower) || /(\bnode\b|\bnpm\b)/.test(lower))) {
3230
+ return [{ tool: 'project' }, { tool: 'systemStatus' }];
3231
+ }
3232
+ if (wantsStatus && /(\bprocess\b|\bprocessi\b|\bpid\b|\bcpu\b|\bram\b|\bmemory\b|\bmem\b)/.test(lower)) {
3233
+ const processMatch = g.match(/(?:process(?:o|i)?|pid)\s+(?:di\s+|of\s+)?([a-z0-9._-]{2,})/i) || g.match(/([a-z0-9._-]{2,})\s+(?:process(?:o|i)?|pid)/i);
3234
+ const filter = processMatch ? processMatch[1] : '';
3235
+ return [{ tool: 'project' }, { tool: 'processStatus', ...(filter ? { name: filter } : {}) }];
3236
+ }
3237
+ if (wantsStatus && /(\bport\b|\bporte\b|\bsocket\b|\blisten\b|\blistening\b)/.test(lower)) {
3238
+ const portMatch = g.match(/\b(\d{2,5})\b/);
3239
+ const port = portMatch ? portMatch[1] : '';
3240
+ return [{ tool: 'project' }, { tool: 'portStatus', ...(port ? { port } : {}) }];
3241
+ }
2949
3242
  if (/bug|errore|fix/.test(lower)) {
2950
3243
  const extractedKeyword = g.split(/\s+/).filter((w) => w.length >= 4).slice(-1)[0];
2951
3244
  if (extractedKeyword) plan.push({ tool: 'search', pattern: extractedKeyword, path: '.', maxResults: 20 });
@@ -3064,7 +3357,7 @@ async function buildStructuredToolPlanWithLlm(opts, goal, llmOptions = {}) {
3064
3357
  const profile = detectWorkspaceProfile(opts);
3065
3358
  const supportedTools = [
3066
3359
  'project', 'pwd', 'ls', 'tree', 'glob', 'read', 'readMany', 'mkdir', 'write', 'append',
3067
- 'diff', 'git', 'gitStatus', 'gitDiff', 'search', 'runTest', 'runBuild', 'runLint', 'sh', 'review', 'commitSuggest'
3360
+ 'diff', 'git', 'gitStatus', 'gitDiff', 'search', 'runTest', 'runBuild', 'runLint', 'sshStatus', 'systemStatus', 'processStatus', 'portStatus', 'sh', 'review', 'commitSuggest'
3068
3361
  ];
3069
3362
  const testCmd = profile.suggested.test?.[0] || '';
3070
3363
  const buildCmd = profile.suggested.build?.[0] || '';
@@ -3155,6 +3448,7 @@ async function buildStructuredToolPlanWithLlm(opts, goal, llmOptions = {}) {
3155
3448
  'Usa solo campi validi per tool.',
3156
3449
  'Regole:',
3157
3450
  '- Preferisci tool read-only prima dei tool rischiosi',
3451
+ '- Per controlli di stato locale usa sshStatus/systemStatus/processStatus/portStatus invece di sh quando possibile',
3158
3452
  '- Includi review e commitSuggest verso la fine',
3159
3453
  '- Se proponi `runTest`/`runBuild`/`runLint` preferiscili a `sh` quando possibile',
3160
3454
  '- Se proponi `sh`, usa comandi test/build/lint concreti se disponibili',
@@ -3368,6 +3662,10 @@ function printLocalHelp() {
3368
3662
  console.log(' /append <file> <text>');
3369
3663
  console.log(' /mkdir <path>');
3370
3664
  console.log(' /git <args...> es: /git status -sb');
3665
+ console.log(' /ssh-status check SSH / Remote Login status');
3666
+ console.log(' /sys-status check system / runtime status');
3667
+ console.log(' /process-status check running processes');
3668
+ console.log(' /port-status check listening ports');
3371
3669
  console.log(' /diff [unstaged|staged|all]');
3372
3670
  console.log(' /stage <file>|--all');
3373
3671
  console.log(' /unstage <file>|--all');
@@ -3453,6 +3751,34 @@ function printTodoList(opts) {
3453
3751
  function makeSimplePlan(goal) {
3454
3752
  const g = String(goal || '').trim();
3455
3753
  if (!g) return [];
3754
+ if (/(\bssh\b|sshd|remote login)/i.test(g) && /(verifica|controlla|check|mostra|stato|status|attivo|active|running|enabled|on)/i.test(g)) {
3755
+ return [
3756
+ 'Verifica stato SSH / Remote Login',
3757
+ "Riporta l'esito del controllo"
3758
+ ];
3759
+ }
3760
+ if (/(whoami|username|user name|hostname|host name|machine|computer|system|sistema|platform|os|runtime|shell|environment|env|path|\bnode\b|\bnpm\b)/i.test(g) && /(verifica|controlla|check|mostra|stato|status|attivo|active|running|enabled|on)/i.test(g)) {
3761
+ return [
3762
+ 'Verifica stato del sistema locale',
3763
+ "Riporta l'esito del controllo"
3764
+ ];
3765
+ }
3766
+ if (/(\bprocess\b|\bprocessi\b|\bpid\b|\bcpu\b|\bram\b|\bmemory\b|\bmem\b)/i.test(g) && /(verifica|controlla|check|mostra|stato|status|attivo|active|running|enabled|on)/i.test(g)) {
3767
+ const processMatch = g.match(/(?:process(?:o|i)?|pid)\s+(?:di\s+|of\s+)?([a-z0-9._-]{2,})/i) || g.match(/([a-z0-9._-]{2,})\s+(?:process(?:o|i)?|pid)/i);
3768
+ const target = processMatch ? processMatch[1] : '';
3769
+ return [
3770
+ target ? `Verifica il processo ${target}` : 'Verifica i processi attivi o il processo indicato',
3771
+ "Riporta l'esito del controllo"
3772
+ ];
3773
+ }
3774
+ if (/(\bport\b|\bporte\b|\bsocket\b|\blisten\b|\blistening\b)/i.test(g) && /(verifica|controlla|check|mostra|stato|status|attivo|active|running|enabled|on)/i.test(g)) {
3775
+ const portMatch = g.match(/\b(\d{2,5})\b/);
3776
+ const target = portMatch ? portMatch[1] : '';
3777
+ return [
3778
+ target ? `Verifica la porta ${target} in ascolto` : 'Verifica le porte in ascolto',
3779
+ "Riporta l'esito del controllo"
3780
+ ];
3781
+ }
3456
3782
  const steps = ['Analizza contesto e file coinvolti'];
3457
3783
  if (/bug|errore|fix/i.test(g)) {
3458
3784
  steps.push('Riproduci il problema e isola la causa');
@@ -5567,6 +5893,31 @@ async function handleLocalCommand(opts, line) {
5567
5893
  }
5568
5894
  };
5569
5895
  }
5896
+ if (lc === '/sys-status') {
5897
+ recordArtifact(opts, 'sys-status', 'check');
5898
+ const data = await runSystemStatusCommand(opts);
5899
+ try { saveCliWorkspaceState(opts); } catch (_err) { }
5900
+ return { handled: true, data };
5901
+ }
5902
+ if (lc === '/process-status') {
5903
+ recordArtifact(opts, 'process-status', rest.join(' ') || 'check');
5904
+ const data = await runProcessStatusCommand(opts, { name: rest.join(' ') });
5905
+ try { saveCliWorkspaceState(opts); } catch (_err) { }
5906
+ return { handled: true, data };
5907
+ }
5908
+ if (lc === '/port-status') {
5909
+ const target = String(rest[0] || '').trim();
5910
+ recordArtifact(opts, 'port-status', target || 'check');
5911
+ const data = await runPortStatusCommand(opts, { port: target });
5912
+ try { saveCliWorkspaceState(opts); } catch (_err) { }
5913
+ return { handled: true, data };
5914
+ }
5915
+ if (lc === '/ssh-status') {
5916
+ recordArtifact(opts, 'ssh-status', 'check');
5917
+ const data = await runSshStatusCommand(opts);
5918
+ try { saveCliWorkspaceState(opts); } catch (_err) { }
5919
+ return { handled: true, data };
5920
+ }
5570
5921
  if (lc === '/agent-run') {
5571
5922
  const sub = String(rest[0] || '').toLowerCase();
5572
5923
  if (sub === 'policy') {
@@ -5932,6 +6283,10 @@ const SLASH_COMMANDS = [
5932
6283
  ['/write <file> <text>', 'Write a file'],
5933
6284
  ['/append <file> <text>', 'Append to a file'],
5934
6285
  ['/git <args>', 'Run a git command'],
6286
+ ['/ssh-status', 'Check SSH/Remote Login status'],
6287
+ ['/sys-status', 'Check local system/runtime status'],
6288
+ ['/process-status', 'Check running processes'],
6289
+ ['/port-status', 'Check listening ports'],
5935
6290
  ['/diff [unstaged|staged|all]', 'Show git diff'],
5936
6291
  ['/review [--staged|--all]', 'Review local changes'],
5937
6292
  ['/commit-suggest [--staged]', 'Suggest a commit message'],
@@ -6038,6 +6393,11 @@ function printWelcomeCard(opts) {
6038
6393
  }
6039
6394
 
6040
6395
  async function runSinglePrompt(opts) {
6396
+ const localIntent = classifyLocalPromptIntent(opts.prompt);
6397
+ if (localIntent?.command) {
6398
+ const localResult = await handleLocalCommand(opts, localIntent.command);
6399
+ if (localResult.handled) return;
6400
+ }
6041
6401
  if (opts.offline) {
6042
6402
  console.error('Modalita --offline: usa REPL interattiva e comandi locali (/help).');
6043
6403
  process.exit(1);
@@ -6121,6 +6481,16 @@ async function runRepl(opts) {
6121
6481
  console.error(`Local tool error: ${err.message}`);
6122
6482
  continue;
6123
6483
  }
6484
+ const localIntent = classifyLocalPromptIntent(line);
6485
+ if (localIntent?.command) {
6486
+ try {
6487
+ const localResult = await handleLocalCommand(opts, localIntent.command);
6488
+ if (localResult.handled) continue;
6489
+ } catch (err) {
6490
+ console.error(`Local tool error: ${err.message}`);
6491
+ continue;
6492
+ }
6493
+ }
6124
6494
  if (opts.offline) {
6125
6495
  console.log('Offline mode: use /help for local tools or restart without --offline to use the server.');
6126
6496
  continue;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bortexcode",
3
- "version": "1.2.1",
3
+ "version": "1.2.3",
4
4
  "description": "Bortex Code CLI - AI coding assistant powered by bortex.site",
5
5
  "homepage": "https://bortex.site",
6
6
  "license": "UNLICENSED",
@@ -17,6 +17,7 @@
17
17
  "node": ">=18.0.0"
18
18
  },
19
19
  "bin": {
20
+ "bortex": "bin/bortexcode.js",
20
21
  "bortexcode": "bin/bortexcode.js",
21
22
  "bortex-code": "bin/bortexcode.js",
22
23
  "bortex-agent": "bin/bortexcode-agent.js"