@mrquake/quakecode-cli 0.64.3 → 0.64.6

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.
@@ -42,6 +42,7 @@ import { ModelSelectorComponent } from "./components/model-selector.js";
42
42
  import { OAuthSelectorComponent } from "./components/oauth-selector.js";
43
43
  import { ScopedModelsSelectorComponent } from "./components/scoped-models-selector.js";
44
44
  import { SessionSelectorComponent } from "./components/session-selector.js";
45
+ import { SessionTabsComponent } from "./components/session-tabs.js";
45
46
  import { SettingsSelectorComponent } from "./components/settings-selector.js";
46
47
  import { SkillInvocationMessageComponent } from "./components/skill-invocation-message.js";
47
48
  import { ToolExecutionComponent } from "./components/tool-execution.js";
@@ -161,6 +162,8 @@ export class InteractiveMode {
161
162
  this.defaultEditor = new CustomEditor(this.ui, getEditorTheme(), this.keybindings, {
162
163
  paddingX: editorPaddingX,
163
164
  autocompleteMaxVisible,
165
+ noBorders: true,
166
+ prompt: theme.bold(theme.fg("accent", "› ")),
164
167
  });
165
168
  this.editor = this.defaultEditor;
166
169
  this.editorContainer = new Container();
@@ -291,17 +294,20 @@ export class InteractiveMode {
291
294
  this.fdPath = fdPath;
292
295
  // Add header container as first child
293
296
  this.ui.addChild(this.headerContainer);
297
+ this.headerContainer.addChild(new SessionTabsComponent(() => this.getSessionTabLabel(), () => DISPLAY_NAME));
298
+ this.headerContainer.addChild(new Spacer(1));
294
299
  // Add header with keybindings from config (unless silenced)
295
300
  if (this.options.verbose || !this.settingsManager.getQuietStartup()) {
296
301
  const currentModel = this.session.model?.id ?? "no model";
297
302
  const cwd = this.sessionManager.getCwd();
298
303
  const owner = os.userInfo().username;
299
304
  const title = `${DISPLAY_NAME} v${this.version}`;
300
- const leftInner = 36;
301
- const maxTotalWidth = Math.max(120, Math.min(this.ui.terminal.columns - 6, 170));
305
+ const leftInner = 44;
306
+ const maxTotalWidth = Math.max(128, Math.min(this.ui.terminal.columns - 6, 172));
302
307
  const rightInner = maxTotalWidth - leftInner - 7;
303
308
  const totalWidth = leftInner + rightInner + 7;
304
309
  const border = (s) => theme.fg("borderAccent", s);
310
+ const accent = (s) => theme.bold(theme.fg("accent", s));
305
311
  const fit = (text, width) => {
306
312
  let out = text;
307
313
  while (visibleWidth(out) > width && out.length > 0) {
@@ -316,19 +322,28 @@ export class InteractiveMode {
316
322
  return out + " ".repeat(remaining);
317
323
  };
318
324
  const row = (left, right) => `${border("│")} ${fit(left, leftInner)} ${border("│")} ${fit(right, rightInner)} ${border("│")}`;
319
- const top = `╭─── ${title} ${"─".repeat(Math.max(0, totalWidth - title.length - 6))}╮`;
325
+ const top = `╭── ${title} ${"─".repeat(Math.max(0, totalWidth - title.length - 5))}╮`;
320
326
  const bottom = `╰${"─".repeat(totalWidth - 2)}╯`;
321
- const ruleRight = border("".repeat(rightInner));
322
- const mascotTop = theme.fg("borderAccent", " ▐▛◉▜▌");
323
- const mascotBottom = theme.fg("borderAccent", " ▝▜▛▘");
327
+ const logo1 = accent(" ██████╗ ██╗ ██╗ █████╗ ██╗ ██╗███████╗ ");
328
+ const logo2 = accent("██╔═══██╗██║ ██║██╔══██╗██║ ██╔╝██╔════╝ ");
329
+ const logo3 = accent("██║ ██║██║ ██║███████║█████╔╝ █████╗ ");
330
+ const logo4 = accent("██║▄▄ ██║██║ ██║██╔══██║██╔═██╗ ██╔══╝ ");
331
+ const logo5 = accent("╚██████╔╝╚██████╔╝██║ ██║██║ ██╗███████╗ ");
332
+ const logo6 = accent(" ╚══▀▀═╝ ╚═════╝ ╚═╝ ╚═╝╚═╝ ╚═╝╚══════╝ ");
333
+ const quick = theme.fg("muted", "/login") + " · " + theme.fg("muted", "/model") + " · " + theme.fg("muted", "/resume") + " · " + theme.fg("muted", "/agents");
334
+ const tips = `${theme.bold("Quick start")} ${quick}`;
335
+ const status = `${theme.bold("Model")} ${theme.fg("borderAccent", currentModel)} · ${theme.bold("User")} ${owner}`;
336
+ const workspace = `${theme.bold("Workspace")} ${cwd}`;
337
+ const note = theme.fg("dim", "Built for terminal-native coding, sessions, tools, and fast iteration.");
324
338
  const dashboard = [
325
339
  border(top),
326
- row(` Welcome back ${owner}!`, theme.bold(theme.fg("borderAccent", "Tips for getting started"))),
327
- row(mascotTop, "Run /init to create AGENTS.md for Quake Code"),
328
- row(mascotBottom, "Use /login, /model, and /settings to configure your session."),
329
- row(` ${currentModel} · API Usage Billing · ${owner}'s`, ruleRight),
330
- row(` ${cwd}`, theme.bold(theme.fg("borderAccent", "Recent activity"))),
331
- row(" Individual Org", theme.fg("dim", "No recent activity")),
340
+ row(logo1, `${theme.bold("Welcome back")} ${owner}`),
341
+ row(logo2, tips),
342
+ row(logo3, "Run /init to generate AGENTS.md for this repo."),
343
+ row(logo4, "Use Tab for completion, Ctrl+R to resume, and /settings to tune Quake."),
344
+ row(logo5, status),
345
+ row(logo6, workspace),
346
+ row(theme.fg("dim", " "), note),
332
347
  border(bottom),
333
348
  ].join("\n");
334
349
  this.builtInHeader = new Text(dashboard, 1, 0);
@@ -402,17 +417,21 @@ export class InteractiveMode {
402
417
  // Initialize available provider count for footer display
403
418
  await this.updateAvailableProviderCount();
404
419
  }
405
- /**
406
- * Update terminal title with session name and cwd.
407
- */
420
+ getSessionTabLabel() {
421
+ return this.sessionManager.getSessionName() || path.basename(this.sessionManager.getCwd()) || "new session";
422
+ }
408
423
  updateTerminalTitle() {
409
- const cwdBasename = path.basename(this.sessionManager.getCwd());
410
424
  const sessionName = this.sessionManager.getSessionName();
411
- if (sessionName) {
412
- this.ui.terminal.setTitle(`${DISPLAY_NAME} - ${sessionName} - ${cwdBasename}`);
425
+ const title = sessionName ? `${DISPLAY_NAME} - ${sessionName}` : DISPLAY_NAME;
426
+ // Add spinner if agent is thinking or working
427
+ const isActive = this.session.agent.state.isStreaming || this.session.agent.state.pendingToolCalls.size > 0;
428
+ if (isActive) {
429
+ const frames = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
430
+ const frame = frames[Math.floor(Date.now() / 100) % frames.length];
431
+ this.ui.terminal.setTitle(`${frame} ${title}`);
413
432
  }
414
433
  else {
415
- this.ui.terminal.setTitle(`${DISPLAY_NAME} - ${cwdBasename}`);
434
+ this.ui.terminal.setTitle(title);
416
435
  }
417
436
  }
418
437
  /**
@@ -439,6 +458,13 @@ export class InteractiveMode {
439
458
  this.showWarning(warning);
440
459
  }
441
460
  });
461
+ // Start thinking/working animation loop for terminal title
462
+ setInterval(() => {
463
+ const state = this.session.agent.state;
464
+ if (state.isStreaming || state.pendingToolCalls.size > 0) {
465
+ this.updateTerminalTitle();
466
+ }
467
+ }, 100);
442
468
  // Show startup warnings
443
469
  const { migratedProviders, modelFallbackMessage, initialMessage, initialImages, initialMessages } = this.options;
444
470
  if (migratedProviders && migratedProviders.length > 0) {
@@ -1885,7 +1911,27 @@ export class InteractiveMode {
1885
1911
  this.loadingAnimation.stop();
1886
1912
  }
1887
1913
  this.statusContainer.clear();
1888
- this.loadingAnimation = new Loader(this.ui, (spinner) => theme.fg("accent", spinner), (text) => theme.fg("muted", text), this.defaultWorkingMessage);
1914
+ // Create loading animation with "shimmer" effect support
1915
+ this.loadingAnimation = new Loader(this.ui, (spinner) => theme.fg("accent", spinner), (text) => {
1916
+ // Custom "shimmer" effect for the working message text
1917
+ const chars = text.split("");
1918
+ if (chars.length === 0)
1919
+ return "";
1920
+ const sweepSeconds = 2.0;
1921
+ const padding = 10;
1922
+ const period = chars.length + padding * 2;
1923
+ const posF = ((Date.now() / 1000) % sweepSeconds) / sweepSeconds * period;
1924
+ const pos = Math.floor(posF);
1925
+ const bandHalfWidth = 5;
1926
+ return chars.map((ch, i) => {
1927
+ const dist = Math.abs(i + padding - pos);
1928
+ if (dist <= bandHalfWidth) {
1929
+ // Glow effect: Bold + Accent color
1930
+ return theme.bold(theme.fg("accent", ch));
1931
+ }
1932
+ return theme.fg("muted", ch);
1933
+ }).join("");
1934
+ }, this.defaultWorkingMessage);
1889
1935
  this.statusContainer.addChild(this.loadingAnimation);
1890
1936
  // Apply any pending working message queued before loader existed
1891
1937
  if (this.pendingWorkingMessage !== undefined) {