tsunami-code 3.11.8 → 3.12.0

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 (3) hide show
  1. package/index.js +12 -6
  2. package/lib/ui.js +54 -1
  3. package/package.json +1 -1
package/index.js CHANGED
@@ -26,7 +26,7 @@ import {
26
26
  } from './lib/memory.js';
27
27
  import { listMemories, readMemory, saveMemory, deleteMemory, getMemdirPath } from './lib/memdir.js';
28
28
 
29
- const VERSION = '3.11.8';
29
+ const VERSION = '3.12.0';
30
30
  const CONFIG_DIR = join(os.homedir(), '.tsunami-code');
31
31
  const CONFIG_FILE = join(CONFIG_DIR, 'config.json');
32
32
  const DEFAULT_SERVER = 'https://radiometric-reita-amuck.ngrok-free.dev';
@@ -830,12 +830,18 @@ async function run() {
830
830
  case 'mode': {
831
831
  const m = rest[0]?.toLowerCase();
832
832
  if (!m) {
833
- console.log(blue('\n Permission modes:\n'));
834
- for (const pm of PERM_MODES) {
835
- const active = pm === permMode ? green(' active') : '';
836
- console.log(` ${cyan(pm.padEnd(16))}${active}`);
833
+ // Interactive arrow-key picker
834
+ const currentIdx = PERM_MODES.indexOf(permMode);
835
+ const selected = await ui.selectFromList(PERM_MODES, currentIdx >= 0 ? currentIdx : 0, blue(' Select permission mode:'));
836
+ if (selected) {
837
+ permMode = selected;
838
+ planMode = selected === 'readonly';
839
+ ui.setPlanMode(planMode);
840
+ updateModeLabel();
841
+ console.log(green(`\n Mode: ${selected}\n`));
842
+ } else {
843
+ console.log(dim(' Cancelled.\n'));
837
844
  }
838
- console.log(dim('\n Usage: /mode <name>\n'));
839
845
  break;
840
846
  }
841
847
  if (!PERM_MODES.includes(m)) {
package/lib/ui.js CHANGED
@@ -281,6 +281,59 @@ export function createUI({ planMode: initPlanMode = false, onLine, onTab, onExit
281
281
  });
282
282
  }
283
283
 
284
+ // ── selectFromList — arrow-key interactive picker ─────────────────────────
285
+ function selectFromList(items, initialIdx = 0, header = null) {
286
+ return new Promise((resolve) => {
287
+ let idx = initialIdx;
288
+ const n = items.length;
289
+ const extra = header ? 2 : 0; // header line + blank line
290
+
291
+ if (header) process.stdout.write(`\n${header}\n\n`);
292
+
293
+ function render(isFirst) {
294
+ if (!isFirst) process.stdout.write(`\x1b[${n}A`);
295
+ for (let i = 0; i < n; i++) {
296
+ const active = i === idx;
297
+ const label = active ? ` ${cyan('❯')} ${items[i]}` : ` ${dim(items[i])}`;
298
+ process.stdout.write(`\x1b[2K${label}\n`);
299
+ }
300
+ }
301
+
302
+ render(true);
303
+
304
+ function clearList() {
305
+ // Erase list + header
306
+ const total = n + extra;
307
+ process.stdout.write(`\x1b[${n}A`); // back to top of list
308
+ for (let i = 0; i < n; i++) process.stdout.write(`\x1b[2K\n`);
309
+ process.stdout.write(`\x1b[${n}A`); // cursor to top of list
310
+ if (extra) {
311
+ process.stdout.write(`\x1b[${extra}A`); // back past header
312
+ for (let i = 0; i < extra; i++) process.stdout.write(`\x1b[2K\n`);
313
+ process.stdout.write(`\x1b[${extra}A`);
314
+ }
315
+ }
316
+
317
+ lineHandler = (key) => {
318
+ if (key === '\x1b[A') {
319
+ idx = (idx - 1 + n) % n;
320
+ render(false);
321
+ } else if (key === '\x1b[B') {
322
+ idx = (idx + 1) % n;
323
+ render(false);
324
+ } else if (key === '\r' || key === '\n') {
325
+ lineHandler = null;
326
+ clearList();
327
+ resolve(items[idx]);
328
+ } else if (key === '\x1b' || key === '\x03') {
329
+ lineHandler = null;
330
+ clearList();
331
+ resolve(null);
332
+ }
333
+ };
334
+ });
335
+ }
336
+
284
337
  function readChar(promptText) {
285
338
  return new Promise((resolve) => {
286
339
  process.stdout.write(promptText);
@@ -374,7 +427,7 @@ export function createUI({ planMode: initPlanMode = false, onLine, onTab, onExit
374
427
  return {
375
428
  start, pause, resume,
376
429
  setPlanMode, setContinuation, setModelLabel, setModeLabel,
377
- readLine, readChar,
430
+ readLine, readChar, selectFromList,
378
431
  wasInterrupted, stop, exitUI,
379
432
  };
380
433
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tsunami-code",
3
- "version": "3.11.8",
3
+ "version": "3.12.0",
4
4
  "description": "Tsunami Code CLI — AI coding agent by Keystone World Management Navy Seal Unit XI3",
5
5
  "type": "module",
6
6
  "bin": {