@compilr-dev/cli 0.5.4 → 0.5.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.
@@ -29,6 +29,7 @@ const DEFAULT_SETTINGS = {
29
29
  showTips: true, // TODO: Not yet implemented
30
30
  reviseCode: true, // TODO: Not yet implemented
31
31
  verbose: false,
32
+ verbosity: 'normal',
32
33
  progressBar: true,
33
34
  // Update defaults
34
35
  checkUpdates: true, // Check for updates on startup
@@ -112,7 +113,16 @@ export function getSettings() {
112
113
  return settingsCache;
113
114
  }
114
115
  const stored = loadFromDisk();
116
+ let needsSave = false;
117
+ // Migration: convert verbose boolean to verbosity string (before merge with defaults)
118
+ if (!('verbosity' in stored) && stored.verbose === true) {
119
+ stored.verbosity = 'verbose';
120
+ needsSave = true;
121
+ }
115
122
  settingsCache = { ...DEFAULT_SETTINGS, ...stored };
123
+ if (needsSave) {
124
+ saveToDisk(settingsCache);
125
+ }
116
126
  // Migration: convert deprecated 'cwd' to 'off'
117
127
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
118
128
  if (settingsCache.projectStartup === 'cwd') {
@@ -179,7 +189,8 @@ export const getDefaultModel = () => getSetting('defaultModel');
179
189
  export const isAutoCompactEnabled = () => getSetting('autoCompact');
180
190
  export const isShowTipsEnabled = () => getSetting('showTips'); // TODO: Not yet implemented
181
191
  export const isReviseCodeEnabled = () => getSetting('reviseCode'); // TODO: Not yet implemented
182
- export const isVerboseEnabled = () => getSetting('verbose');
192
+ export const getVerbosity = () => getSetting('verbosity');
193
+ export const isVerboseEnabled = () => getVerbosity() === 'verbose';
183
194
  export const isProgressBarEnabled = () => getSetting('progressBar');
184
195
  export const isTelemetryEnabled = () => getSetting('telemetryEnabled');
185
196
  // Update getters
@@ -378,6 +389,18 @@ export function displayToSessionRetention(display) {
378
389
  return parseInt(match[1], 10);
379
390
  return 30; // Default fallback
380
391
  }
392
+ /**
393
+ * Map internal verbosity to display value
394
+ */
395
+ export function verbosityToDisplay(v) {
396
+ return v === 'normal' ? 'Normal' : v === 'focused' ? 'Focused' : 'Verbose';
397
+ }
398
+ /**
399
+ * Map display value to internal verbosity
400
+ */
401
+ export function displayToVerbosity(d) {
402
+ return d === 'Focused' ? 'focused' : d === 'Verbose' ? 'verbose' : 'normal';
403
+ }
381
404
  /**
382
405
  * Map internal compact mode to display value
383
406
  */
@@ -97,6 +97,7 @@ export declare const TOOL_NAMES: {
97
97
  readonly APP_MODEL_VALIDATE: "app_model_validate";
98
98
  readonly FACTORY_SCAFFOLD: "factory_scaffold";
99
99
  readonly FACTORY_LIST_TOOLKITS: "factory_list_toolkits";
100
+ readonly LOAD_CAPABILITY: "load_capability";
100
101
  };
101
102
  /** Type for all valid tool names */
102
103
  export type ToolName = (typeof TOOL_NAMES)[keyof typeof TOOL_NAMES];
@@ -118,6 +118,8 @@ export const TOOL_NAMES = {
118
118
  APP_MODEL_VALIDATE: 'app_model_validate',
119
119
  FACTORY_SCAFFOLD: 'factory_scaffold',
120
120
  FACTORY_LIST_TOOLKITS: 'factory_list_toolkits',
121
+ // Capability loading
122
+ LOAD_CAPABILITY: 'load_capability',
121
123
  };
122
124
  /** Array of all direct tool names (always available) */
123
125
  export const DIRECT_TOOL_NAMES = [
@@ -23,4 +23,9 @@ export declare const getToolInfoTool: Tool<object>;
23
23
  export declare const useToolTool: Tool<object>;
24
24
  export declare const metaTools: Tool<object>[];
25
25
  export declare function createMetaToolFallback(): (name: string, input: Record<string, unknown>) => Promise<ToolExecutionResult | null>;
26
+ /**
27
+ * Generate a filtered tool index showing only tools in the given set.
28
+ * Sets the registry filter, generates the index, then restores the previous filter.
29
+ */
30
+ export declare function generateFilteredToolIndex(activeToolNames: string[]): string;
26
31
  export declare const META_TOOLS_SYSTEM_PROMPT_PREFIX = "\n## Tool Index (Specialized Tools)\n\nThese tools are called **exactly like direct tools** \u2014 just use the tool name with parameters. No special syntax or wrapper needed. The system routes them automatically.\n\n**IMPORTANT \u2014 These are CLI system tools, NOT your project's backend. Never use localhost/curl to access them.**\n\n**Parameter Rules:**\n- For **zero-parameter calls**: call the tool directly (e.g., `workitem_query()`, `git_status()`).\n- For **calls WITH parameters**: you MUST call `get_tool_info(\"tool_name\")` first to get parameter details. The signatures below are summaries only \u2014 do NOT guess parameter structure from them.\n- After a failed tool call, always call `get_tool_info()` before retrying.\n\n";
@@ -44,4 +44,15 @@ export const metaTools = [listToolsTool, getToolInfoTool, useToolTool];
44
44
  export function createMetaToolFallback() {
45
45
  return metaToolSet.createFallback();
46
46
  }
47
+ /**
48
+ * Generate a filtered tool index showing only tools in the given set.
49
+ * Sets the registry filter, generates the index, then restores the previous filter.
50
+ */
51
+ export function generateFilteredToolIndex(activeToolNames) {
52
+ const previousFilter = registry.getFilter();
53
+ registry.setFilter(activeToolNames);
54
+ const index = registry.generateToolIndex();
55
+ registry.setFilter(previousFilter ? [...previousFilter] : null);
56
+ return index;
57
+ }
47
58
  export const META_TOOLS_SYSTEM_PROMPT_PREFIX = SDK_META_TOOLS_SYSTEM_PROMPT_PREFIX;
package/dist/tools.d.ts CHANGED
@@ -70,6 +70,12 @@ export declare function initializeMetaTools(includeWarmTools?: boolean): void;
70
70
  * This lists all meta-registry tools with their signatures.
71
71
  */
72
72
  export declare function getToolIndexForSystemPrompt(): string;
73
+ /**
74
+ * Get a filtered tool index for the system prompt.
75
+ * Only includes tools whose names are in the given set.
76
+ * Used by the capability-aware BeforeLLM hook.
77
+ */
78
+ export declare function getFilteredToolIndexForSystemPrompt(activeToolNames: string[]): string;
73
79
  /**
74
80
  * Get count of meta-registry tools.
75
81
  */
package/dist/tools.js CHANGED
@@ -129,7 +129,7 @@ import { guideTool } from './tools/guide-tool.js';
129
129
  // DB tools for project, work item, document, plan, backlog, anchor, artifact, and episode management
130
130
  import { allDbTools, allFactoryTools } from './tools/db-tools.js';
131
131
  // Meta-tools for dynamic tool loading
132
- import { initializeMetaToolRegistry, generateToolIndex, getMetaToolCount, META_TOOLS_SYSTEM_PROMPT_PREFIX, setMetaToolFilter, getRegisteredMetaTools, createMetaToolFallback, getToolInfoTool, } from './tools/meta-tools.js';
132
+ import { initializeMetaToolRegistry, generateToolIndex, generateFilteredToolIndex, getMetaToolCount, META_TOOLS_SYSTEM_PROMPT_PREFIX, setMetaToolFilter, getRegisteredMetaTools, createMetaToolFallback, getToolInfoTool, } from './tools/meta-tools.js';
133
133
  // Re-export for use in agent.ts
134
134
  export { setMetaToolFilter, getRegisteredMetaTools };
135
135
  /**
@@ -324,6 +324,14 @@ export function initializeMetaTools(includeWarmTools) {
324
324
  export function getToolIndexForSystemPrompt() {
325
325
  return META_TOOLS_SYSTEM_PROMPT_PREFIX + generateToolIndex();
326
326
  }
327
+ /**
328
+ * Get a filtered tool index for the system prompt.
329
+ * Only includes tools whose names are in the given set.
330
+ * Used by the capability-aware BeforeLLM hook.
331
+ */
332
+ export function getFilteredToolIndexForSystemPrompt(activeToolNames) {
333
+ return META_TOOLS_SYSTEM_PROMPT_PREFIX + generateFilteredToolIndex(activeToolNames);
334
+ }
327
335
  /**
328
336
  * Get count of meta-registry tools.
329
337
  */
@@ -11,11 +11,13 @@ import type { AutocompleteController } from './autocomplete-controller.js';
11
11
  import type { OverlayManager } from './overlay-manager.js';
12
12
  import type { StatusBarController } from './status-bar-controller.js';
13
13
  import type { Overlay } from './overlay/index.js';
14
+ import type { Verbosity } from '../settings/index.js';
14
15
  export interface KeyboardHandlerHost {
15
16
  isGuardActive(): boolean;
16
17
  isAgentRunning(): boolean;
17
- getViewMode(): 'normal' | 'verbose-temp';
18
- setViewMode(mode: 'normal' | 'verbose-temp'): void;
18
+ getViewMode(): 'normal' | 'focused-temp' | 'verbose-temp';
19
+ setViewMode(mode: 'normal' | 'focused-temp' | 'verbose-temp'): void;
20
+ getPersistedVerbosity(): Verbosity;
19
21
  requestRender(): void;
20
22
  emit(event: string, ...args: unknown[]): void;
21
23
  readonly input: InputController;
@@ -28,10 +30,10 @@ export interface KeyboardHandlerHost {
28
30
  backgroundBashCommand(): void;
29
31
  hasRestoredHistory(): boolean;
30
32
  showRestoredHistory(): void;
31
- toggleVerboseView(): void;
33
+ cycleVerbosityView(): void;
32
34
  toggleLiveRegionExpanded(): void;
33
35
  toggleTodos(): void;
34
- reRenderConversationVerbose(verbose: boolean): void;
36
+ reRenderConversationWithVerbosity(verbosity: Verbosity): void;
35
37
  }
36
38
  export declare class KeyboardHandler {
37
39
  private keyHandler;
@@ -126,10 +126,10 @@ export class KeyboardHandler {
126
126
  handleEscape() {
127
127
  const host = this.host;
128
128
  const { input, ac } = host;
129
- // In verbose-temp mode, escape returns to normal
130
- if (host.getViewMode() === 'verbose-temp') {
129
+ // In temp mode, escape returns to persisted verbosity
130
+ if (host.getViewMode() !== 'normal') {
131
131
  host.setViewMode('normal');
132
- host.reRenderConversationVerbose(false);
132
+ host.reRenderConversationWithVerbosity(host.getPersistedVerbosity());
133
133
  return;
134
134
  }
135
135
  // Route escape to active overlay if present
@@ -261,17 +261,17 @@ export class KeyboardHandler {
261
261
  host.statusBar.handleKey(str, key);
262
262
  return;
263
263
  }
264
- // In verbose-temp mode, any key (except Ctrl+O) returns to normal
265
- if (host.getViewMode() === 'verbose-temp') {
266
- // Ctrl+O toggles back
264
+ // In temp mode (focused-temp or verbose-temp), any key (except Ctrl+O) returns to persisted
265
+ if (host.getViewMode() !== 'normal') {
266
+ // Ctrl+O cycles to next temp mode
267
267
  if (key.ctrl && key.name === 'o') {
268
- host.toggleVerboseView();
268
+ host.cycleVerbosityView();
269
269
  host.toggleLiveRegionExpanded();
270
270
  return;
271
271
  }
272
- // Any other key returns to normal
272
+ // Any other key returns to persisted verbosity
273
273
  host.setViewMode('normal');
274
- host.reRenderConversationVerbose(false);
274
+ host.reRenderConversationWithVerbosity(host.getPersistedVerbosity());
275
275
  // Don't consume the key - let it be processed normally
276
276
  }
277
277
  // Ctrl+C - interrupt/exit
@@ -284,9 +284,9 @@ export class KeyboardHandler {
284
284
  host.toggleTodos();
285
285
  return;
286
286
  }
287
- // Ctrl+O - toggle verbose view mode AND LiveRegion expansion
287
+ // Ctrl+O - cycle verbosity view mode AND LiveRegion expansion
288
288
  if (key.ctrl && key.name === 'o') {
289
- host.toggleVerboseView();
289
+ host.cycleVerbosityView();
290
290
  host.toggleLiveRegionExpanded();
291
291
  return;
292
292
  }
@@ -144,6 +144,7 @@ export class LiveRegionFacade {
144
144
  summary: exitCode === 0 ? 'Completed' : `Exit code ${String(exitCode)}`,
145
145
  content: output,
146
146
  success: exitCode === 0,
147
+ readOnly: false,
147
148
  });
148
149
  }
149
150
  this.host.requestRender();
@@ -15,7 +15,7 @@ import * as path from 'path';
15
15
  import { execSync } from 'child_process';
16
16
  import { BaseOverlayV2, isEscape, isTab, isShiftTab, isEnter, isSpace, isCtrlC, isNavigateUp, isNavigateDown, isBackspace, getNumberKey, isPrintable, extractPrintable, renderProgressBar, formatTokens, truncate, } from '../../base/index.js';
17
17
  import { getCurrentTheme, } from '../../../themes/index.js';
18
- import { getSettings, setSetting, permissionModeToDisplay, displayToPermissionMode, notificationModeToDisplay, displayToNotificationMode, projectStartupModeToDisplay, displayToProjectStartupMode, projectSessionModeToDisplay, displayToProjectSessionMode, sessionRetentionToDisplay, displayToSessionRetention, compactModeToDisplay, displayToCompactMode, } from '../../../settings/index.js';
18
+ import { getSettings, setSetting, permissionModeToDisplay, displayToPermissionMode, notificationModeToDisplay, displayToNotificationMode, projectStartupModeToDisplay, displayToProjectStartupMode, projectSessionModeToDisplay, displayToProjectSessionMode, sessionRetentionToDisplay, displayToSessionRetention, compactModeToDisplay, displayToCompactMode, verbosityToDisplay, displayToVerbosity, } from '../../../settings/index.js';
19
19
  import { getResolvedPathConfig, setDeleteProtection, setProjectMatchRequired, setWorkspacePath, setProjectsPath, setDataPath, } from '../../../settings/paths.js';
20
20
  import { MASCOT_LABELS, } from '../../mascot/index.js';
21
21
  import { ThemeOverlayV2 } from './theme-overlay-v2.js';
@@ -128,7 +128,7 @@ function getConfigItems(currentModel) {
128
128
  // { id: 'showTips', label: 'Show tips', type: 'boolean', value: settings.showTips },
129
129
  // TODO: Implement checkpoints/rewind feature (Claude Code v2.0)
130
130
  // { id: 'reviseCode', label: 'Revise code (checkpoints)', type: 'boolean', value: settings.reviseCode },
131
- { id: 'verbose', label: 'Verbose output', type: 'boolean', value: settings.verbose },
131
+ { id: 'verbosity', label: 'Verbosity', type: 'cycle', value: verbosityToDisplay(settings.verbosity), options: ['Normal', 'Focused', 'Verbose'] },
132
132
  { id: 'progressBar', label: 'Terminal progress bar', type: 'boolean', value: settings.progressBar },
133
133
  {
134
134
  id: 'permissionMode',
@@ -623,9 +623,7 @@ export class ConfigOverlayV2 extends BaseOverlayV2 {
623
623
  case 'autoCompact':
624
624
  setSetting('autoCompact', newValue);
625
625
  break;
626
- case 'verbose':
627
- setSetting('verbose', newValue);
628
- break;
626
+ // verbose: removed — now a 'cycle' item as 'verbosity'
629
627
  case 'progressBar':
630
628
  setSetting('progressBar', newValue);
631
629
  break;
@@ -680,6 +678,9 @@ export class ConfigOverlayV2 extends BaseOverlayV2 {
680
678
  case 'compactMode':
681
679
  setSetting('compactMode', displayToCompactMode(newValue));
682
680
  break;
681
+ case 'verbosity':
682
+ setSetting('verbosity', displayToVerbosity(newValue));
683
+ break;
683
684
  }
684
685
  return null;
685
686
  }
@@ -63,7 +63,7 @@ export function renderItem(item, config) {
63
63
  }
64
64
  case 'thinking':
65
65
  // Only show thinking in verbose mode
66
- if (config.verbose) {
66
+ if (config.verbosity === 'verbose') {
67
67
  console.log(s.muted(`∴ Thinking…`));
68
68
  console.log('');
69
69
  // Indent thinking text
@@ -76,6 +76,9 @@ export function renderItem(item, config) {
76
76
  // If not verbose, skip entirely (but still stored in history)
77
77
  break;
78
78
  case 'tool-start': {
79
+ // In focused mode, spinner already shows tool name — skip scroll output
80
+ if (config.verbosity === 'focused')
81
+ break;
79
82
  // Truncate long params (e.g., long bash commands)
80
83
  const maxLen = Math.min(60, terminal.getTerminalWidth() - 15);
81
84
  let params = item.params;
@@ -94,6 +97,25 @@ export function renderItem(item, config) {
94
97
  break;
95
98
  }
96
99
  case 'tool-result': {
100
+ // === FOCUSED MODE ===
101
+ if (config.verbosity === 'focused') {
102
+ // Errors always visible
103
+ if (item.success === false) {
104
+ console.log(s.info(`● ${item.name}`) + s.error(` — ${item.summary}`));
105
+ console.log('');
106
+ }
107
+ else if (item.readOnly) {
108
+ // Read-only tools: spinner-only, no scroll output
109
+ break;
110
+ }
111
+ else {
112
+ // Write/mutation tools: single summary line
113
+ console.log(s.info(`● ${item.name}`) + s.muted(` — ${item.summary}`));
114
+ console.log('');
115
+ }
116
+ break;
117
+ }
118
+ // === NORMAL & VERBOSE MODES ===
97
119
  // Truncate long params (e.g., long bash commands)
98
120
  const maxParamsLen = Math.min(60, terminal.getTerminalWidth() - 15);
99
121
  let paramsDisplay = item.params;
@@ -114,7 +136,7 @@ export function renderItem(item, config) {
114
136
  // Diff lines: render directly (already ANSI-formatted by diff.ts)
115
137
  if (item.diffLines && item.diffLines.length > 0) {
116
138
  const maxDiffPreview = 10;
117
- if (config.verbose || item.diffLines.length <= maxDiffPreview) {
139
+ if (config.verbosity === 'verbose' || item.diffLines.length <= maxDiffPreview) {
118
140
  for (const line of item.diffLines) {
119
141
  console.log(` ${line}`);
120
142
  }
@@ -130,7 +152,7 @@ export function renderItem(item, config) {
130
152
  else if (item.content) {
131
153
  const contentLines = item.content.split('\n').filter((l) => l.length > 0);
132
154
  const maxPreviewLines = 3;
133
- if (config.verbose) {
155
+ if (config.verbosity === 'verbose') {
134
156
  // Verbose: show all lines
135
157
  for (const line of contentLines) {
136
158
  console.log(s.muted(` ⎿ ${line}`));
@@ -6,6 +6,7 @@
6
6
  */
7
7
  import type { CommandOption } from './types.js';
8
8
  import type { FileMatch } from './file-autocomplete.js';
9
+ import type { Verbosity } from '../settings/index.js';
9
10
  /**
10
11
  * Message to be sent to the agent from a command.
11
12
  * Allows commands to invoke the agent with a detailed prompt while
@@ -39,6 +40,7 @@ export interface TerminalUIEvents {
39
40
  */
40
41
  export interface TerminalUIConfig {
41
42
  verbose: boolean;
43
+ verbosity: Verbosity;
42
44
  theme: string;
43
45
  showMascot: boolean;
44
46
  }
@@ -121,6 +123,7 @@ export type PrintableItem = {
121
123
  content?: string;
122
124
  diffLines?: string[];
123
125
  success?: boolean;
126
+ readOnly?: boolean;
124
127
  agentId?: string;
125
128
  } | {
126
129
  type: 'tool-error';
@@ -6,6 +6,7 @@
6
6
  */
7
7
  export const DEFAULT_TERMINAL_UI_CONFIG = {
8
8
  verbose: false,
9
+ verbosity: 'normal',
9
10
  theme: 'default',
10
11
  showMascot: true,
11
12
  };
@@ -224,15 +224,15 @@ export declare class TerminalUI extends EventEmitter {
224
224
  */
225
225
  getShowTodos(): boolean;
226
226
  /**
227
- * Toggle verbose view mode (Ctrl+O)
228
- * Shows conversation in verbose mode temporarily.
229
- * Any key returns to normal view.
227
+ * Cycle verbosity view mode (Ctrl+O)
228
+ * Cycles: normal focused-temp → verbose-temp normal
229
+ * Any non-Ctrl+O key returns to persisted verbosity.
230
230
  */
231
- toggleVerboseView(): void;
231
+ cycleVerbosityView(): void;
232
232
  /**
233
- * Re-render conversation with temporary verbose setting.
233
+ * Re-render conversation with temporary verbosity setting.
234
234
  */
235
- private reRenderConversationVerbose;
235
+ private reRenderConversationWithVerbosity;
236
236
  /**
237
237
  * Store restored conversation items for Ctrl+R history view.
238
238
  * Called when a session is resumed/restored.
@@ -83,9 +83,10 @@ export class TerminalUI extends EventEmitter {
83
83
  activeTeamAgent = null;
84
84
  // Todo visibility (Ctrl+T to toggle)
85
85
  showTodos = true;
86
- // View mode (Ctrl+O to toggle verbose view)
87
- // normal: compact output
88
- // verbose-temp: temporarily show last N items verbose (any key returns to normal)
86
+ // View mode (Ctrl+O to cycle: normal → focused-temp → verbose-temp → normal)
87
+ // normal: use persisted verbosity setting
88
+ // focused-temp: temporarily show focused mode (any key returns to persisted)
89
+ // verbose-temp: temporarily show verbose mode (any key returns to persisted)
89
90
  viewMode = 'normal';
90
91
  // Status bar navigation (down arrow into footer indicators)
91
92
  statusBar = new StatusBarController({
@@ -104,7 +105,7 @@ export class TerminalUI extends EventEmitter {
104
105
  isFooterRenderAllowed: () => this.isRunning && !this.isPaused && !this.overlay.hasActiveOverlay(),
105
106
  getRenderData: () => ({
106
107
  liveRegion: this.live.region,
107
- liveVerbose: this.config.verbose,
108
+ liveVerbose: this.config.verbosity === 'verbose',
108
109
  agentRunning: this.agentRunning,
109
110
  currentTool: this.currentTool,
110
111
  spinnerText: this.spinnerText,
@@ -136,6 +137,7 @@ export class TerminalUI extends EventEmitter {
136
137
  isAgentRunning: () => this.agentRunning,
137
138
  getViewMode: () => this.viewMode,
138
139
  setViewMode: (mode) => { this.viewMode = mode; },
140
+ getPersistedVerbosity: () => this.config.verbosity,
139
141
  requestRender: () => { this.needsRender = true; },
140
142
  emit: (event, ...args) => { this.emit(event, ...args); },
141
143
  input: this.input,
@@ -148,10 +150,10 @@ export class TerminalUI extends EventEmitter {
148
150
  backgroundBashCommand: () => { this.backgroundBashCommand(); },
149
151
  hasRestoredHistory: () => this.conversation.hasRestoredHistory(),
150
152
  showRestoredHistory: () => { this.showRestoredHistory(); },
151
- toggleVerboseView: () => { this.toggleVerboseView(); },
153
+ cycleVerbosityView: () => { this.cycleVerbosityView(); },
152
154
  toggleLiveRegionExpanded: () => { this.toggleLiveRegionExpanded(); },
153
155
  toggleTodos: () => { this.toggleTodos(); },
154
- reRenderConversationVerbose: (verbose) => { this.reRenderConversationVerbose(verbose); },
156
+ reRenderConversationWithVerbosity: (verbosity) => { this.reRenderConversationWithVerbosity(verbosity); },
155
157
  });
156
158
  constructor(options = {}) {
157
159
  super();
@@ -517,42 +519,50 @@ export class TerminalUI extends EventEmitter {
517
519
  return this.showTodos;
518
520
  }
519
521
  /**
520
- * Toggle verbose view mode (Ctrl+O)
521
- * Shows conversation in verbose mode temporarily.
522
- * Any key returns to normal view.
522
+ * Cycle verbosity view mode (Ctrl+O)
523
+ * Cycles: normal focused-temp → verbose-temp normal
524
+ * Any non-Ctrl+O key returns to persisted verbosity.
523
525
  */
524
- toggleVerboseView() {
526
+ cycleVerbosityView() {
525
527
  if (this.viewMode === 'normal') {
528
+ this.viewMode = 'focused-temp';
529
+ this.reRenderConversationWithVerbosity('focused');
530
+ }
531
+ else if (this.viewMode === 'focused-temp') {
526
532
  this.viewMode = 'verbose-temp';
527
- this.reRenderConversationVerbose(true);
533
+ this.reRenderConversationWithVerbosity('verbose');
528
534
  }
529
535
  else {
530
536
  this.viewMode = 'normal';
531
- this.reRenderConversationVerbose(false);
537
+ this.reRenderConversationWithVerbosity(this.config.verbosity);
532
538
  }
533
539
  }
534
540
  /**
535
- * Re-render conversation with temporary verbose setting.
541
+ * Re-render conversation with temporary verbosity setting.
536
542
  */
537
- reRenderConversationVerbose(verbose) {
543
+ reRenderConversationWithVerbosity(verbosity) {
538
544
  const s = getStyles();
539
545
  // Clear screen and scrollback buffer
540
546
  process.stdout.write('\x1b[2J\x1b[3J\x1b[H');
541
547
  // Reset footer state
542
548
  this.footer.resetRenderState();
543
- // Show mode indicator at top
544
- if (verbose) {
545
- console.log(s.info(`[Verbose View Mode] Press any key to return to normal view`));
549
+ // Show mode indicator at top for temp modes
550
+ if (this.viewMode === 'focused-temp') {
551
+ console.log(s.info(`[Focused View] Press any key to return`));
552
+ console.log('');
553
+ }
554
+ else if (this.viewMode === 'verbose-temp') {
555
+ console.log(s.info(`[Verbose View] Press any key to return`));
546
556
  console.log('');
547
557
  }
548
- // Re-render all items with temporary verbose override
549
- const originalVerbose = this.config.verbose;
550
- this.config.verbose = verbose;
558
+ // Re-render all items with temporary verbosity override
559
+ const originalVerbosity = this.config.verbosity;
560
+ this.config.verbosity = verbosity;
551
561
  for (const item of this.conversation.history) {
552
562
  this.renderItemToConsole(item);
553
563
  }
554
564
  // Restore original config
555
- this.config.verbose = originalVerbose;
565
+ this.config.verbosity = originalVerbosity;
556
566
  // Footer will be re-rendered by the render loop
557
567
  this.needsRender = true;
558
568
  }
@@ -604,10 +614,14 @@ export class TerminalUI extends EventEmitter {
604
614
  * If verbose changes, re-renders the entire conversation.
605
615
  */
606
616
  setConfig(newConfig) {
607
- const oldVerbose = this.config.verbose;
617
+ const oldVerbosity = this.config.verbosity;
608
618
  this.config = { ...this.config, ...newConfig };
609
- // If verbose changed, re-render entire conversation
610
- if (newConfig.verbose !== undefined && newConfig.verbose !== oldVerbose) {
619
+ // Keep legacy verbose in sync with verbosity
620
+ if (newConfig.verbosity !== undefined) {
621
+ this.config.verbose = newConfig.verbosity === 'verbose';
622
+ }
623
+ // If verbosity changed, re-render entire conversation
624
+ if (newConfig.verbosity !== undefined && newConfig.verbosity !== oldVerbosity) {
611
625
  this.reRenderConversation();
612
626
  }
613
627
  // Theme change requires prompt refresh