@lardexx/arenacode 2.9.0 → 2.11.1

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/src/tui/app.js CHANGED
@@ -14,8 +14,9 @@ const { getTheme, themeNames } = require('./theme');
14
14
 
15
15
  const { Bridge } = require('../bridge');
16
16
  const { buildMeta, readProjectContext } = require('../meta');
17
- const { runShell, runFsOp, resolveInRoot } = require('../local');
17
+ const { runShell, runFsOp, resolveInRoot, detectShell, availableShells, shellCommand } = require('../local');
18
18
  const { readConfig, patchConfig, deviceId, resolveUrl, resolveKey, openFolder, VERSION } = require('../config');
19
+ const { ensureContextFile, touchSession } = require('../context');
19
20
 
20
21
  const { MODE_LINES, modeLabel, nextMode, normaliseMode } = require('../modes');
21
22
  const { attachRefs, attachImage, decorate, strings } = require('../attach');
@@ -49,6 +50,7 @@ class App {
49
50
  this.autoApprove = Boolean(opts.yes);
50
51
  this.confirmWrites = Boolean(opts.confirmWrites);
51
52
  this.approval = normaliseApproval(opts.approval);
53
+ this.shell = String(opts.shell || cfg.shell || '').trim() || detectShell();
52
54
 
53
55
  this.state = {
54
56
  mode: normaliseMode(opts.mode || cfg.mode || 'agent'),
@@ -89,6 +91,14 @@ class App {
89
91
 
90
92
  // ---------------------------------------------------------------- lifecycle
91
93
  async start() {
94
+ // the sheet belongs to the folder, not to the task: it is there as soon as the
95
+ // folder is open, and it records this session next to the previous ones
96
+ try {
97
+ const made = ensureContextFile(this.folder, { lang: this.lang, bridgeUrl: this.url });
98
+ if (!made.created) touchSession(this.folder, { lang: this.lang, bridgeUrl: this.url });
99
+ } catch {
100
+ /* a read-only folder must not stop the client */
101
+ }
92
102
  this.term.enter();
93
103
  this.term.on('key', (chunk) => {
94
104
  this.parser.feed(chunk);
@@ -493,6 +503,7 @@ class App {
493
503
  { slash: '/ask', title: 'Ask and Proceed: ask before commands, files and plans', hint: '', group: 'Session', run: () => this.setApproval('ask') },
494
504
  { slash: '/proceed', title: 'Proceed: approve the plan and run everything without asking', hint: '', group: 'Session', run: () => this.setApproval('proceed') },
495
505
  { slash: '/theme', title: 'Cycle the colour theme', hint: 'ctrl+x t', group: 'Session', run: by('Theme') },
506
+ { slash: '/shell', title: 'Show or change the shell commands run in', hint: '', group: 'Session', run: () => this.pickShell() },
496
507
  { slash: '/status', title: 'Ping the bridge', hint: '', group: 'Session', run: by('Bridge status') },
497
508
  { slash: '/help', title: 'Every keybinding', hint: 'f1', group: 'Session', run: () => this.openHelp() },
498
509
  { slash: '/quit', title: 'Leave arenacode', hint: 'ctrl+x q', group: 'Session', run: () => this.quit() },
@@ -538,6 +549,42 @@ class App {
538
549
  this.toast(`approval: ${next === 'proceed' ? 'Proceed' : 'Ask and Proceed'}`, next === 'proceed' ? 'warn' : 'ok');
539
550
  }
540
551
 
552
+ /** `/shell` (and the double-click on a card's shell line): pick from the shells here. */
553
+ async pickShell(name = '') {
554
+ const shells = availableShells();
555
+ const wanted = String(name || '').trim().toLowerCase();
556
+ if (wanted) {
557
+ if (!shells.includes(wanted)) {
558
+ this.toast(`shell: ${wanted} not found (${shells.join(', ')})`, 'warn');
559
+ return;
560
+ }
561
+ this.setShell(wanted);
562
+ return;
563
+ }
564
+ // a free-text prompt instead of a dialog: esc keeps the current shell
565
+ this.toast(`shell: ${this.shell} (${shells.join(', ')})`);
566
+ const current = shellCommand(this.shell).file;
567
+ const answer = String(await this.prompt(`shell [${current}]`)).trim().toLowerCase();
568
+ if (!answer) return;
569
+ if (!shells.includes(answer)) {
570
+ this.toast(`shell: ${answer} not found (${shells.join(', ')})`, 'warn');
571
+ return;
572
+ }
573
+ this.setShell(answer);
574
+ }
575
+
576
+ setShell(name) {
577
+ this.shell = name;
578
+ try {
579
+ patchConfig({ shell: name });
580
+ } catch {
581
+ /* a read-only HOME keeps the choice in memory only */
582
+ }
583
+ const { file } = shellCommand(name);
584
+ this.push({ kind: 'system', text: `shell: ${name} · ${file}` });
585
+ this.toast(`shell: ${name}`, 'ok');
586
+ }
587
+
541
588
  onOverlayKey(key) {
542
589
  const ov = this.state.overlay;
543
590
  if (key.name === 'escape') {
@@ -672,7 +719,7 @@ class App {
672
719
  const cmd = text.slice(1).trim();
673
720
  this.push({ kind: 'user', text: cmd, shell: true });
674
721
  const item = this.push({ kind: 'tool', icon: 'terminal', label: cmd, state: 'running', detail: '' });
675
- const { output, code } = await runShell(cmd, { cwd: this.folder, shell: undefined, quiet: true });
722
+ const { output, code } = await runShell(cmd, { cwd: this.folder, shell: this.shell, quiet: true });
676
723
  item.state = code === 0 ? 'done' : 'failed';
677
724
  item.detail = output.trim();
678
725
  item.secs = undefined;