k0ntext 3.2.0 → 3.3.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 (44) hide show
  1. package/dist/cli/commands/batch-index.d.ts +9 -5
  2. package/dist/cli/commands/batch-index.d.ts.map +1 -1
  3. package/dist/cli/commands/batch-index.js +2 -2
  4. package/dist/cli/commands/batch-index.js.map +1 -1
  5. package/dist/cli/index.js +28 -2
  6. package/dist/cli/index.js.map +1 -1
  7. package/dist/cli/repl/core/parser.d.ts +84 -0
  8. package/dist/cli/repl/core/parser.d.ts.map +1 -0
  9. package/dist/cli/repl/core/parser.js +309 -0
  10. package/dist/cli/repl/core/parser.js.map +1 -0
  11. package/dist/cli/repl/core/session.d.ts +124 -0
  12. package/dist/cli/repl/core/session.d.ts.map +1 -0
  13. package/dist/cli/repl/core/session.js +196 -0
  14. package/dist/cli/repl/core/session.js.map +1 -0
  15. package/dist/cli/repl/index.d.ts +56 -0
  16. package/dist/cli/repl/index.d.ts.map +1 -0
  17. package/dist/cli/repl/index.js +468 -0
  18. package/dist/cli/repl/index.js.map +1 -0
  19. package/dist/cli/repl/init/wizard.d.ts +61 -0
  20. package/dist/cli/repl/init/wizard.d.ts.map +1 -0
  21. package/dist/cli/repl/init/wizard.js +245 -0
  22. package/dist/cli/repl/init/wizard.js.map +1 -0
  23. package/dist/cli/repl/tui/theme.d.ts +109 -0
  24. package/dist/cli/repl/tui/theme.d.ts.map +1 -0
  25. package/dist/cli/repl/tui/theme.js +225 -0
  26. package/dist/cli/repl/tui/theme.js.map +1 -0
  27. package/dist/cli/repl/update/checker.d.ts +64 -0
  28. package/dist/cli/repl/update/checker.d.ts.map +1 -0
  29. package/dist/cli/repl/update/checker.js +166 -0
  30. package/dist/cli/repl/update/checker.js.map +1 -0
  31. package/dist/db/client.d.ts +1 -0
  32. package/dist/db/client.d.ts.map +1 -1
  33. package/dist/db/client.js +2 -1
  34. package/dist/db/client.js.map +1 -1
  35. package/package.json +4 -1
  36. package/src/cli/commands/batch-index.ts +13 -8
  37. package/src/cli/index.ts +28 -2
  38. package/src/cli/repl/core/parser.ts +373 -0
  39. package/src/cli/repl/core/session.ts +269 -0
  40. package/src/cli/repl/index.ts +554 -0
  41. package/src/cli/repl/init/wizard.ts +300 -0
  42. package/src/cli/repl/tui/theme.ts +276 -0
  43. package/src/cli/repl/update/checker.ts +209 -0
  44. package/src/db/client.ts +9 -7
@@ -0,0 +1,300 @@
1
+ /**
2
+ * Init Wizard
3
+ *
4
+ * Interactive wizard for initializing k0ntext configuration
5
+ */
6
+
7
+ import { input, confirm, select, checkbox } from '@inquirer/prompts';
8
+ import chalk from 'chalk';
9
+ import { ProjectType } from '../core/session.js';
10
+ import { K0NTEXT_THEME } from '../tui/theme.js';
11
+
12
+ /**
13
+ * Wizard configuration result
14
+ */
15
+ export interface WizardConfig {
16
+ apiKey: string;
17
+ projectType: ProjectType;
18
+ aiTools: string[];
19
+ features: string[];
20
+ generateEmbeddings: boolean;
21
+ }
22
+
23
+ /**
24
+ * Project type options
25
+ */
26
+ const PROJECT_TYPES: Array<{ value: ProjectType; name: string; description: string }> = [
27
+ { value: 'monorepo', name: 'Monorepo', description: 'Multiple packages/services in one repository' },
28
+ { value: 'webapp', name: 'Web Application', description: 'Frontend web application (React, Vue, etc.)' },
29
+ { value: 'api', name: 'API/Backend', description: 'Backend API service' },
30
+ { value: 'library', name: 'Library/Package', description: 'Reusable library or npm package' },
31
+ { value: 'cli', name: 'CLI Tool', description: 'Command-line interface tool' },
32
+ { value: 'unknown', name: 'Other', description: 'Other type of project' }
33
+ ];
34
+
35
+ /**
36
+ * AI tool options
37
+ */
38
+ const AI_TOOLS = [
39
+ { name: 'Claude Code', value: 'claude', description: 'Anthropic Claude with advanced reasoning' },
40
+ { name: 'GitHub Copilot', value: 'copilot', description: 'GitHub AI-powered code completion' },
41
+ { name: 'Cline', value: 'cline', description: 'Autonomous coding agent' },
42
+ { name: 'Cursor', value: 'cursor', description: 'AI code editor with integrated AI' },
43
+ { name: 'Windsurf', value: 'windsurf', description: 'AI-powered IDE with code understanding' },
44
+ { name: 'Aider', value: 'aider', description: 'AI pair programming in terminal' },
45
+ { name: 'Continue', value: 'continue', description: 'Open source AI autopilot' }
46
+ ];
47
+
48
+ /**
49
+ * Feature options
50
+ */
51
+ const FEATURES = [
52
+ { name: '📊 Statistics & Analytics', value: 'stats', description: 'Track code metrics and context usage' },
53
+ { name: '🔍 Semantic Search', value: 'search', description: 'AI-powered code search with embeddings' },
54
+ { name: '📝 Auto Documentation', value: 'docs', description: 'Generate documentation from code' },
55
+ { name: '🔄 Drift Detection', value: 'drift', description: 'Detect when docs are out of sync with code' },
56
+ { name: '🎯 Workflow Tracking', value: 'workflows', description: 'Track RPI workflows and tasks' },
57
+ { name: '🤖 MCP Integration', value: 'mcp', description: 'Model Context Protocol server' }
58
+ ];
59
+
60
+ /**
61
+ * Init Wizard
62
+ */
63
+ export class InitWizard {
64
+ private projectRoot: string;
65
+ private hasExistingKey: boolean;
66
+
67
+ constructor(projectRoot: string) {
68
+ this.projectRoot = projectRoot;
69
+ this.hasExistingKey = !!process.env.OPENROUTER_API_KEY;
70
+ }
71
+
72
+ /**
73
+ * Run the complete wizard
74
+ */
75
+ async run(): Promise<WizardConfig | null> {
76
+ this.showWelcome();
77
+
78
+ // Step 1: API Key
79
+ const apiKey = await this.stepApiKey();
80
+ if (!apiKey) {
81
+ console.log(K0NTEXT_THEME.warning('\n⚠ Initialization cancelled.'));
82
+ return null;
83
+ }
84
+
85
+ // Step 2: Project Type
86
+ const projectType = await this.stepProjectType();
87
+
88
+ // Step 3: AI Tools
89
+ const aiTools = await this.stepAITools();
90
+
91
+ // Step 4: Features
92
+ const features = await this.stepFeatures();
93
+
94
+ // Step 5: Embeddings
95
+ const generateEmbeddings = await this.stepEmbeddings();
96
+
97
+ // Show summary
98
+ const config: WizardConfig = {
99
+ apiKey,
100
+ projectType,
101
+ aiTools,
102
+ features,
103
+ generateEmbeddings
104
+ };
105
+
106
+ const confirmed = await this.showSummary(config);
107
+ if (!confirmed) {
108
+ console.log(K0NTEXT_THEME.warning('\n⚠ Initialization cancelled.'));
109
+ return null;
110
+ }
111
+
112
+ return config;
113
+ }
114
+
115
+ /**
116
+ * Show welcome message
117
+ */
118
+ private showWelcome(): void {
119
+ console.log('');
120
+ console.log(K0NTEXT_THEME.box(
121
+ 'K0NTEXT INITIALIZATION',
122
+ `
123
+ Welcome to K0ntext! This wizard will help you set up
124
+ AI context engineering for your project.
125
+
126
+ You'll be asked a few questions to configure K0ntext
127
+ for your specific needs.
128
+ `.trim(),
129
+ 'primary'
130
+ ));
131
+ console.log('');
132
+ }
133
+
134
+ /**
135
+ * Step 1: API Key
136
+ */
137
+ private async stepApiKey(): Promise<string | null> {
138
+ console.log('');
139
+ console.log(K0NTEXT_THEME.header('\n━━━ Step 1/5: OpenRouter API Key ━━━'));
140
+
141
+ if (this.hasExistingKey) {
142
+ console.log(K0NTEXT_THEME.success('\n✓ API key detected in OPENROUTER_API_KEY environment variable'));
143
+ const useExisting = await confirm({
144
+ message: 'Use existing API key?',
145
+ default: true
146
+ });
147
+
148
+ if (useExisting) {
149
+ return process.env.OPENROUTER_API_KEY!;
150
+ }
151
+ }
152
+
153
+ console.log('');
154
+ console.log(K0NTEXT_THEME.info('OpenRouter API key enables intelligent analysis and embeddings.'));
155
+ console.log(K0NTEXT_THEME.dim('Get your key at: https://openrouter.ai/keys'));
156
+
157
+ const apiKey = await input({
158
+ message: 'Enter your OpenRouter API key (or press Enter to skip):',
159
+ validate: (value: string) => {
160
+ if (!value) return true; // Allow skipping
161
+ if (value.startsWith('sk-or-v1-')) return true;
162
+ return 'Invalid API key format. Should start with "sk-or-v1-"';
163
+ }
164
+ });
165
+
166
+ if (!apiKey) {
167
+ const skipAnyway = await confirm({
168
+ message: 'Skip API key? You can add it later with "k0ntext config set apiKey"',
169
+ default: false
170
+ });
171
+
172
+ if (!skipAnyway) {
173
+ return null; // User wants to cancel
174
+ }
175
+ }
176
+
177
+ return apiKey || '';
178
+ }
179
+
180
+ /**
181
+ * Step 2: Project Type
182
+ */
183
+ private async stepProjectType(): Promise<ProjectType> {
184
+ console.log('');
185
+ console.log(K0NTEXT_THEME.header('\n━━━ Step 2/5: Project Type ━━━'));
186
+ console.log('');
187
+
188
+ const projectType = await select({
189
+ message: 'What type of project is this?',
190
+ choices: PROJECT_TYPES.map((t) => ({
191
+ name: `${t.name} - ${K0NTEXT_THEME.dim(t.description)}`,
192
+ value: t.value,
193
+ description: t.description
194
+ }))
195
+ });
196
+
197
+ return projectType as ProjectType;
198
+ }
199
+
200
+ /**
201
+ * Step 3: AI Tools
202
+ */
203
+ private async stepAITools(): Promise<string[]> {
204
+ console.log('');
205
+ console.log(K0NTEXT_THEME.header('\n━━━ Step 3/5: AI Tools ━━━'));
206
+ console.log('');
207
+ console.log(K0NTEXT_THEME.info('Select the AI coding assistants you want to configure:'));
208
+
209
+ const tools = await checkbox({
210
+ message: 'Select AI tools (use space to select, enter to continue):',
211
+ choices: AI_TOOLS.map((t) => ({
212
+ name: `${t.name} - ${K0NTEXT_THEME.dim(t.description)}`,
213
+ value: t.value,
214
+ checked: t.value === 'claude' // Default to Claude
215
+ }))
216
+ });
217
+
218
+ return tools.length > 0 ? tools : ['claude']; // Default to claude if none selected
219
+ }
220
+
221
+ /**
222
+ * Step 4: Features
223
+ */
224
+ private async stepFeatures(): Promise<string[]> {
225
+ console.log('');
226
+ console.log(K0NTEXT_THEME.header('\n━━━ Step 4/5: Features ━━━'));
227
+ console.log('');
228
+ console.log(K0NTEXT_THEME.info('Select the features you want to enable:'));
229
+
230
+ const features = await checkbox({
231
+ message: 'Select features (use space to select, enter to continue):',
232
+ choices: FEATURES.map((f) => ({
233
+ name: `${f.name} - ${K0NTEXT_THEME.dim(f.description)}`,
234
+ value: f.value,
235
+ checked: ['stats', 'search', 'mcp'].includes(f.value) // Default to core features
236
+ }))
237
+ });
238
+
239
+ return features.length > 0 ? features : ['stats', 'mcp'];
240
+ }
241
+
242
+ /**
243
+ * Step 5: Embeddings
244
+ */
245
+ private async stepEmbeddings(): Promise<boolean> {
246
+ console.log('');
247
+ console.log(K0NTEXT_THEME.header('\n━━━ Step 5/5: Embeddings ━━━'));
248
+ console.log('');
249
+ console.log(K0NTEXT_THEME.info('Generate semantic embeddings for AI-powered search?'));
250
+ console.log(K0NTEXT_THEME.dim('This will take a few minutes but greatly improves search quality.'));
251
+
252
+ const generate = await confirm({
253
+ message: 'Generate embeddings now?',
254
+ default: false
255
+ });
256
+
257
+ return generate;
258
+ }
259
+
260
+ /**
261
+ * Show summary and confirm
262
+ */
263
+ private async showSummary(config: WizardConfig): Promise<boolean> {
264
+ console.log('');
265
+ console.log(K0NTEXT_THEME.header('\n━━━ Configuration Summary ━━━'));
266
+ console.log('');
267
+
268
+ const typeInfo = PROJECT_TYPES.find(t => t.value === config.projectType);
269
+ console.log(` ${K0NTEXT_THEME.primary('Project Type:')} ${typeInfo?.name || config.projectType}`);
270
+ console.log(` ${K0NTEXT_THEME.primary('API Key:')} ${config.apiKey ? '✓ Configured' : '○ Skipped'}`);
271
+ console.log(` ${K0NTEXT_THEME.primary('AI Tools:')} ${config.aiTools.map(t => K0NTEXT_THEME.highlight(t)).join(', ')}`);
272
+ console.log(` ${K0NTEXT_THEME.primary('Features:')} ${config.features.map(f => {
273
+ const feat = FEATURES.find(ff => ff.value === f);
274
+ return feat?.name.split(' ')[0] || f;
275
+ }).join(', ')}`);
276
+ console.log(` ${K0NTEXT_THEME.primary('Embeddings:')} ${config.generateEmbeddings ? '✓ Yes' : '○ Later'}`);
277
+ console.log('');
278
+
279
+ return await confirm({
280
+ message: 'Initialize K0ntext with these settings?',
281
+ default: true
282
+ });
283
+ }
284
+
285
+ /**
286
+ * Show success message
287
+ */
288
+ showSuccess(config: WizardConfig): void {
289
+ console.log('');
290
+ console.log(K0NTEXT_THEME.success('\n✓ K0ntext initialized successfully!'));
291
+ console.log('');
292
+
293
+ console.log(K0NTEXT_THEME.header('Next Steps:'));
294
+ console.log(` ${K0NTEXT_THEME.cyan('•')} Type ${K0NTEXT_THEME.highlight('stats')} to view database statistics`);
295
+ console.log(` ${K0NTEXT_THEME.cyan('•')} Type ${K0NTEXT_THEME.highlight('index')} to index your codebase`);
296
+ console.log(` ${K0NTEXT_THEME.cyan('•')} Type ${K0NTEXT_THEME.highlight('search <query>')} to search your code`);
297
+ console.log(` ${K0NTEXT_THEME.cyan('•')} Type ${K0NTEXT_THEME.highlight('help')} for all available commands`);
298
+ console.log('');
299
+ }
300
+ }
@@ -0,0 +1,276 @@
1
+ /**
2
+ * K0ntext Theme System
3
+ *
4
+ * Orange gradient with purple/pink/cyan accents for modern CLI experience
5
+ */
6
+
7
+ import chalk from 'chalk';
8
+
9
+ /**
10
+ * Terminal capabilities
11
+ */
12
+ interface TerminalCapabilities {
13
+ isTTY: boolean;
14
+ supportsColor: boolean;
15
+ supports256: boolean;
16
+ supportsUnicode: boolean;
17
+ }
18
+
19
+ /**
20
+ * Chalk type aliases
21
+ */
22
+ type ChalkColor = typeof chalk;
23
+
24
+ /**
25
+ * K0ntext color theme
26
+ */
27
+ export const K0NTEXT_THEME = {
28
+ // Orange gradient for primary elements
29
+ primary: chalk.hex('#F97316'), // Orange-500
30
+ primaryLight: chalk.hex('#FB923C'), // Orange-400
31
+ primaryDark: chalk.hex('#EA580C'), // Orange-600
32
+
33
+ // Purple accents for headers
34
+ header: chalk.hex('#8B5CF6'), // Purple-500
35
+ headerLight: chalk.hex('#A78BFA'), // Purple-400
36
+
37
+ // Pink for highlights
38
+ highlight: chalk.hex('#EC4899'), // Pink-500
39
+ highlightLight: chalk.hex('#F472B6'), // Pink-400
40
+
41
+ // Cyan for status
42
+ status: chalk.hex('#06B6D4'), // Cyan-500
43
+ statusLight: chalk.hex('#22D3EE'), // Cyan-400
44
+ cyan: chalk.hex('#06B6D4'), // Alias for cyan
45
+
46
+ // Semantic colors
47
+ success: chalk.green,
48
+ warning: chalk.yellow,
49
+ error: chalk.red,
50
+ info: chalk.blue,
51
+
52
+ // Neutrals
53
+ muted: chalk.gray,
54
+ dim: chalk.dim,
55
+
56
+ // Background colors
57
+ bgPrimary: chalk.bgHex('#F97316'),
58
+ bgHeader: chalk.bgHex('#8B5CF6'),
59
+ bgHighlight: chalk.bgHex('#EC4899'),
60
+ bgStatus: chalk.bgHex('#06B6D4'),
61
+
62
+ /**
63
+ * Detect terminal capabilities
64
+ */
65
+ detectCapabilities(): TerminalCapabilities {
66
+ const isTTY = process.stdout.isTTY;
67
+ const supportsColor = chalk.level > 0 && !process.env.NO_COLOR;
68
+ const supports256 = chalk.level >= 2;
69
+ const supportsUnicode = process.platform !== 'win32' ||
70
+ (process.env.TERM?.includes('xterm') || process.env.WT_SESSION);
71
+
72
+ return { isTTY: !!isTTY, supportsColor: !!supportsColor, supports256: !!supports256, supportsUnicode: !!supportsUnicode };
73
+ },
74
+
75
+ /**
76
+ * Get gradient string for logo/banner
77
+ */
78
+ gradientText(text: string): string {
79
+ const { supports256 } = this.detectCapabilities();
80
+
81
+ if (!supports256 || process.env.NO_COLOR) {
82
+ return this.primary(text);
83
+ }
84
+
85
+ // Create gradient effect using ANSI colors
86
+ const colors = ['#F97316', '#FBBF24', '#F472B6', '#8B5CF6', '#06B6D4'];
87
+ let result = '';
88
+ const chunkSize = Math.ceil(text.length / colors.length);
89
+
90
+ for (let i = 0; i < text.length; i++) {
91
+ const colorIndex = Math.floor(i / chunkSize) % colors.length;
92
+ const color = colors[colorIndex];
93
+ result += chalk.hex(color)(text[i]);
94
+ }
95
+
96
+ return result;
97
+ },
98
+
99
+ /**
100
+ * Get border characters based on terminal capabilities
101
+ */
102
+ getBorders() {
103
+ const { supportsUnicode } = this.detectCapabilities();
104
+
105
+ if (supportsUnicode) {
106
+ return {
107
+ topLeft: '╔',
108
+ topRight: '╗',
109
+ bottomLeft: '╚',
110
+ bottomRight: '╝',
111
+ horizontal: '═',
112
+ vertical: '║',
113
+ leftT: '╠',
114
+ rightT: '╣',
115
+ topT: '╦',
116
+ bottomT: '╩',
117
+ cross: '╬'
118
+ };
119
+ }
120
+
121
+ // ASCII fallback
122
+ return {
123
+ topLeft: '+',
124
+ topRight: '+',
125
+ bottomLeft: '+',
126
+ bottomRight: '+',
127
+ horizontal: '-',
128
+ vertical: '|',
129
+ leftT: '+',
130
+ rightT: '+',
131
+ topT: '+',
132
+ bottomT: '+',
133
+ cross: '+'
134
+ };
135
+ },
136
+
137
+ /**
138
+ * Create a styled box with text
139
+ */
140
+ box(title: string, content: string, borderColor = 'primary'): string {
141
+ const borders = this.getBorders();
142
+
143
+ // Get color function based on borderColor
144
+ let colorFn: ChalkColor;
145
+ switch (borderColor) {
146
+ case 'primary':
147
+ colorFn = this.primary;
148
+ break;
149
+ case 'header':
150
+ colorFn = this.header;
151
+ break;
152
+ case 'highlight':
153
+ colorFn = this.highlight;
154
+ break;
155
+ case 'status':
156
+ colorFn = this.status;
157
+ break;
158
+ case 'success':
159
+ colorFn = this.success;
160
+ break;
161
+ default:
162
+ colorFn = this.primary;
163
+ }
164
+
165
+ const lines = content.split('\n');
166
+ const maxLength = Math.max(title.length, ...lines.map(l => l.length));
167
+
168
+ let result = '';
169
+ result += colorFn(borders.topLeft + borders.horizontal.repeat(maxLength + 2) + borders.topRight) + '\n';
170
+ result += colorFn(borders.vertical) + ' ' + this.primary(title.padEnd(maxLength)) + ' ' + colorFn(borders.vertical) + '\n';
171
+ result += colorFn(borders.leftT + borders.horizontal.repeat(maxLength + 2) + borders.rightT) + '\n';
172
+
173
+ for (const line of lines) {
174
+ result += colorFn(borders.vertical) + ' ' + line.padEnd(maxLength) + ' ' + colorFn(borders.vertical) + '\n';
175
+ }
176
+
177
+ result += colorFn(borders.bottomLeft + borders.horizontal.repeat(maxLength + 2) + borders.bottomRight);
178
+
179
+ return result;
180
+ },
181
+
182
+ /**
183
+ * Create progress bar
184
+ */
185
+ progressBar(current: number, total: number, width = 20): string {
186
+ const percentage = Math.min(100, Math.max(0, (current / total) * 100));
187
+ const filled = Math.floor((percentage / 100) * width);
188
+ const empty = width - filled;
189
+
190
+ const filledBar = this.primary('█'.repeat(filled));
191
+ const emptyBar = this.dim('░'.repeat(empty));
192
+ const percentageText = ` ${Math.round(percentage)}%`;
193
+
194
+ return `${filledBar}${emptyBar}${percentageText}`;
195
+ },
196
+
197
+ /**
198
+ * Format file size with color
199
+ */
200
+ formatFileSize(bytes: number): string {
201
+ const units = ['B', 'KB', 'MB', 'GB'];
202
+ let size = bytes;
203
+ let unitIndex = 0;
204
+
205
+ while (size >= 1024 && unitIndex < units.length - 1) {
206
+ size /= 1024;
207
+ unitIndex++;
208
+ }
209
+
210
+ const rounded = Math.round(size * 10) / 10;
211
+ const colorFn = unitIndex < 2 ? this.success : this.warning;
212
+
213
+ return colorFn(`${rounded}${units[unitIndex]}`);
214
+ },
215
+
216
+ /**
217
+ * Format timestamp with color
218
+ */
219
+ formatTimestamp(date: Date): string {
220
+ const now = new Date();
221
+ const diff = now.getTime() - date.getTime();
222
+
223
+ const seconds = Math.floor(diff / 1000);
224
+ const minutes = Math.floor(seconds / 60);
225
+ const hours = Math.floor(minutes / 60);
226
+ const days = Math.floor(hours / 24);
227
+
228
+ if (seconds < 60) return this.success(`${seconds}s ago`);
229
+ if (minutes < 60) return this.status(`${minutes}m ago`);
230
+ if (hours < 24) return this.header(`${hours}h ago`);
231
+ return this.headerLight(`${days}d ago`);
232
+ }
233
+ } as const;
234
+
235
+ /**
236
+ * Terminal utility functions
237
+ */
238
+ export const terminal = {
239
+ /**
240
+ * Clear screen
241
+ */
242
+ clear(): void {
243
+ console.clear();
244
+ },
245
+
246
+ /**
247
+ * Move cursor to position
248
+ */
249
+ moveTo(x: number, y: number): void {
250
+ process.stdout.write(`\x1b[${y};${x}H`);
251
+ },
252
+
253
+ /**
254
+ * Hide cursor
255
+ */
256
+ hideCursor(): void {
257
+ process.stdout.write('\x1b[?25l');
258
+ },
259
+
260
+ /**
261
+ * Show cursor
262
+ */
263
+ showCursor(): void {
264
+ process.stdout.write('\x1b[?25h');
265
+ },
266
+
267
+ /**
268
+ * Get terminal dimensions
269
+ */
270
+ getSize(): { width: number; height: number } {
271
+ return {
272
+ width: process.stdout.columns || 80,
273
+ height: process.stdout.rows || 24
274
+ };
275
+ }
276
+ };