tsunami-code 3.9.0 → 3.10.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 (2) hide show
  1. package/index.js +51 -6
  2. 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.9.0';
29
+ const VERSION = '3.10.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';
@@ -68,13 +68,38 @@ function printBanner(serverUrl) {
68
68
  console.log(dim(' International AI Wars\n'));
69
69
  }
70
70
 
71
- function printToolCall(name, args) {
71
+ function printToolCall(name, args, elapsedMs) {
72
72
  let parsed = {};
73
73
  try { parsed = JSON.parse(args); } catch {}
74
74
  const preview = Object.entries(parsed)
75
75
  .map(([k, v]) => `${k}=${JSON.stringify(String(v).slice(0, 60))}`)
76
76
  .join(', ');
77
- process.stdout.write('\n' + dim(` ${name}(${preview})\n`));
77
+ const timing = elapsedMs != null ? chalk.dim(` (${(elapsedMs / 1000).toFixed(1)}s)`) : '';
78
+ process.stdout.write('\n' + dim(` ⚙ ${name}`) + timing + dim(`(${preview})\n`));
79
+ }
80
+
81
+ // ── Spinner — shows while waiting for first token ────────────────────────────
82
+ function createSpinner() {
83
+ const frames = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
84
+ const labels = ['thinking', 'pondering', 'reasoning', 'analyzing'];
85
+ let fi = 0, li = 0, ticks = 0, interval = null;
86
+
87
+ const start = () => {
88
+ interval = setInterval(() => {
89
+ ticks++;
90
+ if (ticks % 25 === 0) li = (li + 1) % labels.length; // rotate label every ~2s
91
+ process.stdout.write(`\r ${dim(frames[fi++ % frames.length] + ' ' + labels[li] + '...')} `);
92
+ }, 80);
93
+ };
94
+
95
+ const stop = () => {
96
+ if (!interval) return;
97
+ clearInterval(interval);
98
+ interval = null;
99
+ process.stdout.write('\r' + ' '.repeat(35) + '\r');
100
+ };
101
+
102
+ return { start, stop };
78
103
  }
79
104
 
80
105
  function formatBytes(bytes) {
@@ -1188,26 +1213,45 @@ Output ONLY the TSUNAMI.md content, starting with "# Project: <name>"`;
1188
1213
  // Open a new undo turn — all Write/Edit calls during this turn grouped together
1189
1214
  beginUndoTurn();
1190
1215
 
1216
+ const spinner = createSpinner();
1217
+ const turnStart = Date.now();
1191
1218
  let firstToken = true;
1219
+ let toolTimers = {}; // track per-tool duration
1220
+
1221
+ spinner.start();
1192
1222
  const highlight = createHighlighter((s) => process.stdout.write(s));
1223
+
1193
1224
  try {
1194
1225
  await agentLoop(
1195
1226
  currentServerUrl,
1196
1227
  fullMessages,
1197
1228
  (token) => {
1198
- if (firstToken) { process.stdout.write(' '); firstToken = false; }
1229
+ if (firstToken) {
1230
+ spinner.stop();
1231
+ process.stdout.write(' ');
1232
+ firstToken = false;
1233
+ }
1199
1234
  highlight(token);
1200
1235
  },
1201
1236
  (toolName, toolArgs) => {
1202
1237
  flushHighlighter(highlight);
1203
- printToolCall(toolName, toolArgs);
1238
+ spinner.stop();
1239
+ const elapsed = toolTimers[toolName] != null ? Date.now() - toolTimers[toolName] : null;
1240
+ toolTimers[toolName] = Date.now();
1241
+ printToolCall(toolName, toolArgs, elapsed);
1242
+ spinner.start();
1204
1243
  firstToken = true;
1205
1244
  },
1206
1245
  { sessionDir, cwd, planMode },
1207
1246
  makeConfirmCallback(rl)
1208
1247
  );
1248
+ spinner.stop();
1209
1249
  flushHighlighter(highlight);
1210
1250
 
1251
+ const elapsed = ((Date.now() - turnStart) / 1000).toFixed(1);
1252
+ const tok = tokenStats.output > 0 ? ` · ${tokenStats.output - (_outputTokens || 0)} tok` : '';
1253
+ process.stdout.write(dim(`\n ↳ ${elapsed}s${tok}\n`));
1254
+
1211
1255
  // Token estimation
1212
1256
  const inputChars = fullMessages.reduce((s, m) => s + (typeof m.content === 'string' ? m.content.length : 0), 0);
1213
1257
  _inputTokens += Math.round(inputChars / 4);
@@ -1230,8 +1274,9 @@ Output ONLY the TSUNAMI.md content, starting with "# Project: <name>"`;
1230
1274
  const newSkills = loadSkills(cwd);
1231
1275
  if (newSkills.length !== skills.length) skills = newSkills;
1232
1276
 
1233
- process.stdout.write('\n\n');
1277
+ process.stdout.write('\n');
1234
1278
  } catch (e) {
1279
+ spinner.stop();
1235
1280
  process.stdout.write('\n');
1236
1281
  console.error(red(` Error: ${e.message}\n`));
1237
1282
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tsunami-code",
3
- "version": "3.9.0",
3
+ "version": "3.10.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": {