tsunami-code 3.11.9 → 3.12.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/index.js +43 -7
- package/lib/ui.js +25 -9
- package/package.json +1 -1
package/index.js
CHANGED
|
@@ -25,8 +25,9 @@ import {
|
|
|
25
25
|
getSessionContext
|
|
26
26
|
} from './lib/memory.js';
|
|
27
27
|
import { listMemories, readMemory, saveMemory, deleteMemory, getMemdirPath } from './lib/memdir.js';
|
|
28
|
+
import { execSync, spawn } from 'child_process';
|
|
28
29
|
|
|
29
|
-
const VERSION = '3.
|
|
30
|
+
const VERSION = '3.12.1';
|
|
30
31
|
const CONFIG_DIR = join(os.homedir(), '.tsunami-code');
|
|
31
32
|
const CONFIG_FILE = join(CONFIG_DIR, 'config.json');
|
|
32
33
|
const DEFAULT_SERVER = 'https://radiometric-reita-amuck.ngrok-free.dev';
|
|
@@ -831,9 +832,8 @@ async function run() {
|
|
|
831
832
|
const m = rest[0]?.toLowerCase();
|
|
832
833
|
if (!m) {
|
|
833
834
|
// Interactive arrow-key picker
|
|
834
|
-
process.stdout.write(blue('\n Select permission mode:\n\n'));
|
|
835
835
|
const currentIdx = PERM_MODES.indexOf(permMode);
|
|
836
|
-
const selected = await ui.selectFromList(PERM_MODES, currentIdx >= 0 ? currentIdx : 0);
|
|
836
|
+
const selected = await ui.selectFromList(PERM_MODES, currentIdx >= 0 ? currentIdx : 0, blue(' Select permission mode:'));
|
|
837
837
|
if (selected) {
|
|
838
838
|
permMode = selected;
|
|
839
839
|
planMode = selected === 'readonly';
|
|
@@ -1399,7 +1399,43 @@ Output ONLY the TSUNAMI.md content, starting with "# Project: <name>"`;
|
|
|
1399
1399
|
});
|
|
1400
1400
|
}
|
|
1401
1401
|
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
process.
|
|
1405
|
-
|
|
1402
|
+
// ── Whale Watcher — auto-update check on every startup ───────────────────────
|
|
1403
|
+
async function whaleWatcher() {
|
|
1404
|
+
if (!process.stdin.isTTY) return; // skip in --print / pipe mode
|
|
1405
|
+
const frames = ['⠋','⠙','⠹','⠸','⠼','⠴','⠦','⠧','⠇','⠏'];
|
|
1406
|
+
let fi = 0;
|
|
1407
|
+
const interval = setInterval(() => {
|
|
1408
|
+
process.stdout.write(`\r 🐋 ${chalk.bold('Whale Watcher')} ${chalk.dim(frames[fi++ % frames.length]+' checking for updates...')} `);
|
|
1409
|
+
}, 80);
|
|
1410
|
+
const clear = () => process.stdout.write('\r' + ' '.repeat(60) + '\r');
|
|
1411
|
+
try {
|
|
1412
|
+
const res = await fetch('https://registry.npmjs.org/tsunami-code/latest',
|
|
1413
|
+
{ signal: AbortSignal.timeout(5000) });
|
|
1414
|
+
const { version: latest } = await res.json();
|
|
1415
|
+
clearInterval(interval); clear();
|
|
1416
|
+
if (latest && latest !== VERSION) {
|
|
1417
|
+
process.stdout.write(` 🐋 ${chalk.bold('Whale Watcher')} ${chalk.cyan(`update available`)} ${chalk.dim(`v${VERSION} → v${latest}`)}\n`);
|
|
1418
|
+
process.stdout.write(chalk.dim(' Installing...\n'));
|
|
1419
|
+
try {
|
|
1420
|
+
execSync(`npm install -g tsunami-code@${latest}`, { stdio: 'pipe' });
|
|
1421
|
+
process.stdout.write(chalk.green(` ✓ Updated to v${latest} — restarting...\n\n`));
|
|
1422
|
+
const child = spawn(process.execPath, process.argv.slice(1), { stdio: 'inherit' });
|
|
1423
|
+
child.on('exit', code => process.exit(code || 0));
|
|
1424
|
+
await new Promise(() => {}); // wait for child
|
|
1425
|
+
} catch {
|
|
1426
|
+
process.stdout.write(chalk.yellow(` ⚠ Auto-update failed — run: npm install -g tsunami-code\n\n`));
|
|
1427
|
+
}
|
|
1428
|
+
}
|
|
1429
|
+
// up to date — silent, proceed immediately
|
|
1430
|
+
} catch {
|
|
1431
|
+
clearInterval(interval); clear(); // network error — silent
|
|
1432
|
+
}
|
|
1433
|
+
}
|
|
1434
|
+
|
|
1435
|
+
(async () => {
|
|
1436
|
+
await whaleWatcher();
|
|
1437
|
+
run().catch(e => {
|
|
1438
|
+
console.error(chalk.red(`Fatal: ${e.message}`));
|
|
1439
|
+
process.exit(1);
|
|
1440
|
+
});
|
|
1441
|
+
})();
|
package/lib/ui.js
CHANGED
|
@@ -282,13 +282,16 @@ export function createUI({ planMode: initPlanMode = false, onLine, onTab, onExit
|
|
|
282
282
|
}
|
|
283
283
|
|
|
284
284
|
// ── selectFromList — arrow-key interactive picker ─────────────────────────
|
|
285
|
-
function selectFromList(items, initialIdx = 0) {
|
|
285
|
+
function selectFromList(items, initialIdx = 0, header = null) {
|
|
286
286
|
return new Promise((resolve) => {
|
|
287
287
|
let idx = initialIdx;
|
|
288
|
-
const n
|
|
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`);
|
|
289
292
|
|
|
290
293
|
function render(isFirst) {
|
|
291
|
-
if (!isFirst) process.stdout.write(`\x1b[${n}A`);
|
|
294
|
+
if (!isFirst) process.stdout.write(`\x1b[${n}A`);
|
|
292
295
|
for (let i = 0; i < n; i++) {
|
|
293
296
|
const active = i === idx;
|
|
294
297
|
const label = active ? ` ${cyan('❯')} ${items[i]}` : ` ${dim(items[i])}`;
|
|
@@ -298,20 +301,33 @@ export function createUI({ planMode: initPlanMode = false, onLine, onTab, onExit
|
|
|
298
301
|
|
|
299
302
|
render(true);
|
|
300
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
|
+
|
|
301
317
|
lineHandler = (key) => {
|
|
302
|
-
if (key === '\x1b[A') {
|
|
318
|
+
if (key === '\x1b[A') {
|
|
303
319
|
idx = (idx - 1 + n) % n;
|
|
304
320
|
render(false);
|
|
305
|
-
} else if (key === '\x1b[B') {
|
|
321
|
+
} else if (key === '\x1b[B') {
|
|
306
322
|
idx = (idx + 1) % n;
|
|
307
323
|
render(false);
|
|
308
|
-
} else if (key === '\r' || key === '\n') {
|
|
324
|
+
} else if (key === '\r' || key === '\n') {
|
|
309
325
|
lineHandler = null;
|
|
310
|
-
|
|
326
|
+
clearList();
|
|
311
327
|
resolve(items[idx]);
|
|
312
|
-
} else if (key === '\x1b' || key === '\x03') {
|
|
328
|
+
} else if (key === '\x1b' || key === '\x03') {
|
|
313
329
|
lineHandler = null;
|
|
314
|
-
|
|
330
|
+
clearList();
|
|
315
331
|
resolve(null);
|
|
316
332
|
}
|
|
317
333
|
};
|