maistro 1.0.390

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 (111) hide show
  1. package/LICENSE +15 -0
  2. package/README.md +107 -0
  3. package/dist/app.d.ts +247 -0
  4. package/dist/app.d.ts.map +1 -0
  5. package/dist/app.js +4971 -0
  6. package/dist/app.js.map +1 -0
  7. package/dist/buildInfo.d.ts +5 -0
  8. package/dist/buildInfo.d.ts.map +1 -0
  9. package/dist/buildInfo.js +2 -0
  10. package/dist/buildInfo.js.map +1 -0
  11. package/dist/caffeinate.d.ts +72 -0
  12. package/dist/caffeinate.d.ts.map +1 -0
  13. package/dist/caffeinate.js +258 -0
  14. package/dist/caffeinate.js.map +1 -0
  15. package/dist/claudePath.d.ts +10 -0
  16. package/dist/claudePath.d.ts.map +1 -0
  17. package/dist/claudePath.js +34 -0
  18. package/dist/claudePath.js.map +1 -0
  19. package/dist/clipboard.d.ts +44 -0
  20. package/dist/clipboard.d.ts.map +1 -0
  21. package/dist/clipboard.js +442 -0
  22. package/dist/clipboard.js.map +1 -0
  23. package/dist/config.d.ts +211 -0
  24. package/dist/config.d.ts.map +1 -0
  25. package/dist/config.js +933 -0
  26. package/dist/config.js.map +1 -0
  27. package/dist/constants.d.ts +50 -0
  28. package/dist/constants.d.ts.map +1 -0
  29. package/dist/constants.js +81 -0
  30. package/dist/constants.js.map +1 -0
  31. package/dist/contextBuilder.d.ts +38 -0
  32. package/dist/contextBuilder.d.ts.map +1 -0
  33. package/dist/contextBuilder.js +113 -0
  34. package/dist/contextBuilder.js.map +1 -0
  35. package/dist/dependencyDetector.d.ts +57 -0
  36. package/dist/dependencyDetector.d.ts.map +1 -0
  37. package/dist/dependencyDetector.js +505 -0
  38. package/dist/dependencyDetector.js.map +1 -0
  39. package/dist/executor.d.ts +83 -0
  40. package/dist/executor.d.ts.map +1 -0
  41. package/dist/executor.js +583 -0
  42. package/dist/executor.js.map +1 -0
  43. package/dist/git.d.ts +85 -0
  44. package/dist/git.d.ts.map +1 -0
  45. package/dist/git.js +283 -0
  46. package/dist/git.js.map +1 -0
  47. package/dist/imageManager.d.ts +161 -0
  48. package/dist/imageManager.d.ts.map +1 -0
  49. package/dist/imageManager.js +674 -0
  50. package/dist/imageManager.js.map +1 -0
  51. package/dist/index.d.ts +3 -0
  52. package/dist/index.d.ts.map +1 -0
  53. package/dist/index.js +437 -0
  54. package/dist/index.js.map +1 -0
  55. package/dist/input-visual-test.d.ts +9 -0
  56. package/dist/input-visual-test.d.ts.map +1 -0
  57. package/dist/input-visual-test.js +108 -0
  58. package/dist/input-visual-test.js.map +1 -0
  59. package/dist/inputBox.d.ts +228 -0
  60. package/dist/inputBox.d.ts.map +1 -0
  61. package/dist/inputBox.js +966 -0
  62. package/dist/inputBox.js.map +1 -0
  63. package/dist/logger.d.ts +136 -0
  64. package/dist/logger.d.ts.map +1 -0
  65. package/dist/logger.js +347 -0
  66. package/dist/logger.js.map +1 -0
  67. package/dist/orchestrator.d.ts +149 -0
  68. package/dist/orchestrator.d.ts.map +1 -0
  69. package/dist/orchestrator.js +821 -0
  70. package/dist/orchestrator.js.map +1 -0
  71. package/dist/planner.d.ts +86 -0
  72. package/dist/planner.d.ts.map +1 -0
  73. package/dist/planner.js +830 -0
  74. package/dist/planner.js.map +1 -0
  75. package/dist/pty-test-runner.d.ts +87 -0
  76. package/dist/pty-test-runner.d.ts.map +1 -0
  77. package/dist/pty-test-runner.js +721 -0
  78. package/dist/pty-test-runner.js.map +1 -0
  79. package/dist/screen.d.ts +44 -0
  80. package/dist/screen.d.ts.map +1 -0
  81. package/dist/screen.js +152 -0
  82. package/dist/screen.js.map +1 -0
  83. package/dist/taskQueue.d.ts +70 -0
  84. package/dist/taskQueue.d.ts.map +1 -0
  85. package/dist/taskQueue.js +282 -0
  86. package/dist/taskQueue.js.map +1 -0
  87. package/dist/tui-test-harness.d.ts +216 -0
  88. package/dist/tui-test-harness.d.ts.map +1 -0
  89. package/dist/tui-test-harness.js +527 -0
  90. package/dist/tui-test-harness.js.map +1 -0
  91. package/dist/types.d.ts +257 -0
  92. package/dist/types.d.ts.map +1 -0
  93. package/dist/types.js +46 -0
  94. package/dist/types.js.map +1 -0
  95. package/dist/ui-visual-test.d.ts +15 -0
  96. package/dist/ui-visual-test.d.ts.map +1 -0
  97. package/dist/ui-visual-test.js +141 -0
  98. package/dist/ui-visual-test.js.map +1 -0
  99. package/dist/ui.d.ts +272 -0
  100. package/dist/ui.d.ts.map +1 -0
  101. package/dist/ui.js +1531 -0
  102. package/dist/ui.js.map +1 -0
  103. package/dist/validator.d.ts +53 -0
  104. package/dist/validator.d.ts.map +1 -0
  105. package/dist/validator.js +491 -0
  106. package/dist/validator.js.map +1 -0
  107. package/dist/versionCheck.d.ts +63 -0
  108. package/dist/versionCheck.d.ts.map +1 -0
  109. package/dist/versionCheck.js +261 -0
  110. package/dist/versionCheck.js.map +1 -0
  111. package/package.json +62 -0
package/dist/config.js ADDED
@@ -0,0 +1,933 @@
1
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
2
+ import { homedir } from 'node:os';
3
+ import { join } from 'node:path';
4
+ import { execa } from 'execa';
5
+ import { getClaudePath } from './claudePath.js';
6
+ /**
7
+ * Default settings for new installations
8
+ */
9
+ export const DEFAULT_SETTINGS = {
10
+ preventSleep: true,
11
+ };
12
+ const CONFIG_DIR = join(homedir(), '.maistro');
13
+ const CONFIG_FILE = join(CONFIG_DIR, 'config.json');
14
+ /**
15
+ * Ensure config directory exists
16
+ */
17
+ function ensureConfigDir() {
18
+ if (!existsSync(CONFIG_DIR)) {
19
+ mkdirSync(CONFIG_DIR, { recursive: true });
20
+ }
21
+ }
22
+ /**
23
+ * Load global configuration
24
+ */
25
+ export function loadConfig() {
26
+ try {
27
+ if (existsSync(CONFIG_FILE)) {
28
+ const content = readFileSync(CONFIG_FILE, 'utf-8');
29
+ return JSON.parse(content);
30
+ }
31
+ }
32
+ catch {
33
+ // Invalid config, return empty
34
+ }
35
+ return {};
36
+ }
37
+ /**
38
+ * Save global configuration
39
+ */
40
+ export function saveConfig(config) {
41
+ ensureConfigDir();
42
+ writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2), 'utf-8');
43
+ }
44
+ /**
45
+ * Check if Claude Code CLI is installed
46
+ */
47
+ export async function isClaudeCodeInstalled() {
48
+ try {
49
+ const claudePath = getClaudePath();
50
+ const result = await execa(claudePath, ['--version'], { reject: false });
51
+ if (result.exitCode === 0) {
52
+ const version = typeof result.stdout === 'string' ? result.stdout.trim() : '';
53
+ return { installed: true, version };
54
+ }
55
+ return { installed: false, error: 'Claude Code CLI returned non-zero exit code' };
56
+ }
57
+ catch (error) {
58
+ return {
59
+ installed: false,
60
+ error: error instanceof Error ? error.message : 'Claude Code CLI not found',
61
+ };
62
+ }
63
+ }
64
+ /**
65
+ * Check if Claude Code is authenticated (can make API calls)
66
+ * Uses cached validation to avoid expensive API calls on every startup
67
+ */
68
+ export async function isClaudeCodeAuthenticated(forceCheck = false) {
69
+ // Check cache first (valid for 1 hour)
70
+ const config = loadConfig();
71
+ const cacheValidMs = 60 * 60 * 1000; // 1 hour
72
+ if (!forceCheck && config.claudeCodeValidated && config.lastValidated) {
73
+ const lastCheck = new Date(config.lastValidated).getTime();
74
+ const now = Date.now();
75
+ if (now - lastCheck < cacheValidMs) {
76
+ return { authenticated: true };
77
+ }
78
+ }
79
+ try {
80
+ const claudePath = getClaudePath();
81
+ // Try a simple command that requires auth
82
+ const result = await execa(claudePath, ['--print', '-p', 'Say "ok"'], {
83
+ reject: false,
84
+ timeout: 30000,
85
+ stdin: 'ignore',
86
+ });
87
+ if (result.exitCode === 0) {
88
+ // Cache successful validation
89
+ saveConfig({
90
+ ...config,
91
+ claudeCodeValidated: true,
92
+ lastValidated: new Date().toISOString(),
93
+ });
94
+ return { authenticated: true };
95
+ }
96
+ const stderr = typeof result.stderr === 'string' ? result.stderr : '';
97
+ if (stderr.includes('auth') || stderr.includes('login') || stderr.includes('API')) {
98
+ // Clear cache on auth failure
99
+ saveConfig({
100
+ ...config,
101
+ claudeCodeValidated: false,
102
+ lastValidated: undefined,
103
+ });
104
+ return { authenticated: false, error: 'Claude Code is not authenticated. Run: claude login' };
105
+ }
106
+ return { authenticated: true }; // Assume authenticated if no auth error
107
+ }
108
+ catch (error) {
109
+ return {
110
+ authenticated: false,
111
+ error: error instanceof Error ? error.message : 'Failed to check authentication',
112
+ };
113
+ }
114
+ }
115
+ /**
116
+ * Test Claude Code interaction with tool use
117
+ * This catches API errors like rate limiting or concurrency issues
118
+ */
119
+ export async function testClaudeInteraction() {
120
+ try {
121
+ const claudePath = getClaudePath();
122
+ // Run a simple task that uses tools - this tests full interaction
123
+ // Note: --output-format stream-json requires --verbose when used with --print
124
+ const result = await execa(claudePath, ['--print', '--verbose', '--output-format', 'stream-json', '-p', 'List one file in the current directory using the Bash tool, then say "test complete"'], {
125
+ reject: false,
126
+ timeout: 60000, // 60 second timeout
127
+ stdin: 'ignore',
128
+ cwd: process.cwd(),
129
+ });
130
+ const stdout = typeof result.stdout === 'string' ? result.stdout : '';
131
+ const stderr = typeof result.stderr === 'string' ? result.stderr : '';
132
+ const combined = stdout + stderr;
133
+ // Check for OAuth token expiration (401 + authentication_error)
134
+ if (combined.includes('OAuth token has expired') ||
135
+ combined.includes('authentication_error') ||
136
+ (combined.includes('API Error: 401') || combined.includes('API Error:401'))) {
137
+ return {
138
+ success: false,
139
+ error: 'OAuth token has expired. Please run /login to re-authenticate.',
140
+ output: stdout,
141
+ tokenExpired: true,
142
+ };
143
+ }
144
+ // Check for API errors in output
145
+ if (stdout.includes('API Error:') || stderr.includes('API Error:')) {
146
+ const apiErrorMatch = (stdout + stderr).match(/API Error:\s*(\d+)\s*([^\n]+)?/i);
147
+ if (apiErrorMatch) {
148
+ return {
149
+ success: false,
150
+ error: `API Error ${apiErrorMatch[1]}: ${apiErrorMatch[2] || 'Unknown error'}`,
151
+ output: stdout,
152
+ };
153
+ }
154
+ return { success: false, error: 'API Error occurred', output: stdout };
155
+ }
156
+ // Check for rate limiting
157
+ if (stdout.includes('rate limit') || stderr.includes('rate limit') ||
158
+ stdout.includes('concurrency') || stderr.includes('concurrency')) {
159
+ return { success: false, error: 'Rate limited or concurrency issues', output: stdout };
160
+ }
161
+ if (result.exitCode === 0) {
162
+ return { success: true, output: stdout };
163
+ }
164
+ // Non-zero exit - check stderr for specific errors
165
+ if (stderr.includes('auth') || stderr.includes('login')) {
166
+ return { success: false, error: 'Authentication error', output: stdout };
167
+ }
168
+ return { success: false, error: stderr || `Exit code ${result.exitCode}`, output: stdout };
169
+ }
170
+ catch (error) {
171
+ return {
172
+ success: false,
173
+ error: error instanceof Error ? error.message : 'Failed to test interaction',
174
+ };
175
+ }
176
+ }
177
+ /**
178
+ * Launch Claude Code /login command interactively to re-authenticate
179
+ * Returns true if login was successful
180
+ */
181
+ export async function launchClaudeLogin() {
182
+ try {
183
+ const claudePath = getClaudePath();
184
+ // Spawn Claude Code interactively with /login - user can interact directly
185
+ const result = await execa(claudePath, ['/login'], {
186
+ stdio: 'inherit', // Connect to user's terminal for interactive login
187
+ reject: false,
188
+ });
189
+ return result.exitCode === 0;
190
+ }
191
+ catch {
192
+ return false;
193
+ }
194
+ }
195
+ /**
196
+ * Check if maistro is configured (Claude Code CLI is available and authenticated)
197
+ */
198
+ export async function isConfigured() {
199
+ const installed = await isClaudeCodeInstalled();
200
+ if (!installed.installed)
201
+ return false;
202
+ const authenticated = await isClaudeCodeAuthenticated();
203
+ return authenticated.authenticated;
204
+ }
205
+ /**
206
+ * Get config file path (for display)
207
+ */
208
+ export function getConfigPath() {
209
+ return CONFIG_FILE;
210
+ }
211
+ /**
212
+ * Get the preventSleep setting (defaults to true)
213
+ */
214
+ export function getPreventSleep() {
215
+ const config = loadConfig();
216
+ return config.preventSleep ?? DEFAULT_SETTINGS.preventSleep;
217
+ }
218
+ /**
219
+ * Set the preventSleep setting
220
+ */
221
+ export function setPreventSleep(value) {
222
+ const config = loadConfig();
223
+ saveConfig({
224
+ ...config,
225
+ preventSleep: value,
226
+ });
227
+ }
228
+ /**
229
+ * Detect the current terminal emulator
230
+ */
231
+ export function detectTerminal() {
232
+ const termProgram = process.env.TERM_PROGRAM || '';
233
+ const termProgramVersion = process.env.TERM_PROGRAM_VERSION || '';
234
+ const term = process.env.TERM || '';
235
+ const lcTerminal = process.env.LC_TERMINAL || '';
236
+ const wtSession = process.env.WT_SESSION || '';
237
+ const alacrittySocket = process.env.ALACRITTY_SOCKET || '';
238
+ const kitty = process.env.KITTY_WINDOW_ID || '';
239
+ const wezterm = process.env.WEZTERM_EXECUTABLE || '';
240
+ const ghostty = process.env.GHOSTTY_RESOURCES_DIR || '';
241
+ // Ghostty - native support
242
+ if (ghostty || termProgram === 'ghostty') {
243
+ return {
244
+ name: 'Ghostty',
245
+ program: 'ghostty',
246
+ supportsShiftEnter: true,
247
+ configurable: false,
248
+ };
249
+ }
250
+ // WezTerm - native support
251
+ if (wezterm || termProgram === 'WezTerm') {
252
+ return {
253
+ name: 'WezTerm',
254
+ program: 'wezterm',
255
+ supportsShiftEnter: true,
256
+ configurable: false,
257
+ };
258
+ }
259
+ // Kitty - native support with CSI u protocol
260
+ if (kitty || term === 'xterm-kitty') {
261
+ return {
262
+ name: 'Kitty',
263
+ program: 'kitty',
264
+ supportsShiftEnter: true,
265
+ configurable: false,
266
+ };
267
+ }
268
+ // VS Code integrated terminal
269
+ if (termProgram === 'vscode') {
270
+ return {
271
+ name: 'VS Code Terminal',
272
+ program: 'vscode',
273
+ supportsShiftEnter: false,
274
+ configurable: true,
275
+ instructions: [
276
+ 'Open VS Code Settings (Cmd+, or Ctrl+,)',
277
+ 'Search for "terminal.integrated.sendKeybindingsToShell"',
278
+ 'Enable this setting',
279
+ '',
280
+ 'Or add to keybindings.json (Cmd+K Cmd+S → Open Keyboard Shortcuts JSON):',
281
+ '{',
282
+ ' "key": "shift+enter",',
283
+ ' "command": "workbench.action.terminal.sendSequence",',
284
+ ' "args": { "text": "\\n" },',
285
+ ' "when": "terminalFocus"',
286
+ '}',
287
+ ],
288
+ };
289
+ }
290
+ // Windows Terminal
291
+ if (wtSession) {
292
+ return {
293
+ name: 'Windows Terminal',
294
+ program: 'windows-terminal',
295
+ supportsShiftEnter: false,
296
+ configurable: true,
297
+ instructions: [
298
+ 'Open Windows Terminal settings (Ctrl+,)',
299
+ 'Go to Actions tab',
300
+ 'Add a new action:',
301
+ ' - Keys: shift+enter',
302
+ ' - Action: Send Input',
303
+ ' - Input: \\x0a',
304
+ ],
305
+ };
306
+ }
307
+ // Alacritty
308
+ if (alacrittySocket || termProgram === 'Alacritty') {
309
+ return {
310
+ name: 'Alacritty',
311
+ program: 'alacritty',
312
+ supportsShiftEnter: false,
313
+ configurable: true,
314
+ instructions: [
315
+ 'Add to ~/.config/alacritty/alacritty.toml:',
316
+ '',
317
+ '[keyboard]',
318
+ 'bindings = [',
319
+ ' { key = "Return", mods = "Shift", chars = "\\u000a" }',
320
+ ']',
321
+ ],
322
+ };
323
+ }
324
+ // iTerm2
325
+ if (termProgram === 'iTerm.app' || lcTerminal === 'iTerm2') {
326
+ return {
327
+ name: 'iTerm2',
328
+ program: 'iterm2',
329
+ supportsShiftEnter: false,
330
+ configurable: true,
331
+ instructions: [
332
+ 'Open iTerm2 → Preferences → Keys → Key Bindings',
333
+ 'Click the + button to add a new binding:',
334
+ ' - Keyboard Shortcut: Shift + Enter',
335
+ ' - Action: Send Escape Sequence',
336
+ ' - Esc+: OM',
337
+ '',
338
+ 'Or use: Action → Send Text with "vi Special Chars"',
339
+ ' - Enter: \\n',
340
+ ],
341
+ };
342
+ }
343
+ // Terminal.app (macOS)
344
+ if (termProgram === 'Apple_Terminal') {
345
+ return {
346
+ name: 'Terminal.app',
347
+ program: 'apple-terminal',
348
+ supportsShiftEnter: false,
349
+ configurable: false, // Terminal.app doesn't support custom key bindings easily
350
+ instructions: [
351
+ 'Terminal.app has limited key binding customization.',
352
+ 'Use Ctrl+J for newlines instead of Shift+Enter.',
353
+ '',
354
+ 'Or consider switching to iTerm2, Kitty, WezTerm, or Ghostty',
355
+ 'which have better keyboard customization support.',
356
+ ],
357
+ };
358
+ }
359
+ // Zed
360
+ if (termProgram === 'Zed') {
361
+ return {
362
+ name: 'Zed Terminal',
363
+ program: 'zed',
364
+ supportsShiftEnter: false,
365
+ configurable: true,
366
+ instructions: [
367
+ 'Add to ~/.config/zed/keymap.json:',
368
+ '',
369
+ '[',
370
+ ' {',
371
+ ' "context": "Terminal",',
372
+ ' "bindings": {',
373
+ ' "shift-enter": ["terminal::SendKeystroke", "\\n"]',
374
+ ' }',
375
+ ' }',
376
+ ']',
377
+ ],
378
+ };
379
+ }
380
+ // Warp
381
+ if (termProgram === 'WarpTerminal') {
382
+ return {
383
+ name: 'Warp',
384
+ program: 'warp',
385
+ supportsShiftEnter: false,
386
+ configurable: true,
387
+ instructions: [
388
+ 'Open Warp → Settings → Keyboard Shortcuts',
389
+ 'Add a custom shortcut:',
390
+ ' - Shortcut: Shift + Enter',
391
+ ' - Action: Send text',
392
+ ' - Text: \\n',
393
+ ],
394
+ };
395
+ }
396
+ // Unknown terminal
397
+ return {
398
+ name: termProgram || term || 'Unknown Terminal',
399
+ program: 'unknown',
400
+ supportsShiftEnter: false,
401
+ configurable: false,
402
+ instructions: [
403
+ 'Your terminal may not support custom Shift+Enter bindings.',
404
+ 'Use Ctrl+J for newlines (this works in all terminals).',
405
+ ],
406
+ };
407
+ }
408
+ /**
409
+ * Check if terminal setup has been shown
410
+ */
411
+ export function isTerminalSetupShown() {
412
+ const config = loadConfig();
413
+ return config.terminalSetupShown === true;
414
+ }
415
+ /**
416
+ * Mark terminal setup as shown
417
+ */
418
+ export function setTerminalSetupShown(value) {
419
+ const config = loadConfig();
420
+ saveConfig({
421
+ ...config,
422
+ terminalSetupShown: value,
423
+ });
424
+ }
425
+ /**
426
+ * Get the shiftEnterEnabled setting
427
+ */
428
+ export function getShiftEnterEnabled() {
429
+ const config = loadConfig();
430
+ return config.shiftEnterEnabled === true;
431
+ }
432
+ /**
433
+ * Set the shiftEnterEnabled setting
434
+ */
435
+ export function setShiftEnterEnabled(value) {
436
+ const config = loadConfig();
437
+ saveConfig({
438
+ ...config,
439
+ shiftEnterEnabled: value,
440
+ });
441
+ }
442
+ /**
443
+ * Get the cmdVPasteEnabled setting
444
+ */
445
+ export function getCmdVPasteEnabled() {
446
+ const config = loadConfig();
447
+ return config.cmdVPasteEnabled === true;
448
+ }
449
+ /**
450
+ * Set the cmdVPasteEnabled setting
451
+ */
452
+ export function setCmdVPasteEnabled(value) {
453
+ const config = loadConfig();
454
+ saveConfig({
455
+ ...config,
456
+ cmdVPasteEnabled: value,
457
+ });
458
+ }
459
+ /**
460
+ * VS Code keybinding for Shift+Enter
461
+ */
462
+ // VS Code keybinding sends the Kitty keyboard protocol sequence for Shift+Enter
463
+ // This is explicitly handled by maistro as a newline insertion
464
+ const VSCODE_SHIFT_ENTER_KEYBINDING = {
465
+ key: 'shift+enter',
466
+ command: 'workbench.action.terminal.sendSequence',
467
+ args: { text: '\x1b[13;2u' },
468
+ when: 'terminalFocus',
469
+ };
470
+ /**
471
+ * VS Code keybinding for Cmd+V (clipboard paste)
472
+ */
473
+ // VS Code keybinding sends the Kitty keyboard protocol sequence for Cmd+V
474
+ // 118 = 'v', 9 = Super/Cmd modifier
475
+ const VSCODE_CMD_V_KEYBINDING = {
476
+ key: 'cmd+v',
477
+ command: 'workbench.action.terminal.sendSequence',
478
+ args: { text: '\x1b[118;9u' },
479
+ when: 'terminalFocus',
480
+ };
481
+ /**
482
+ * Get VS Code keybindings.json path
483
+ */
484
+ function getVSCodeKeybindingsPath() {
485
+ const platform = process.platform;
486
+ if (platform === 'darwin') {
487
+ return join(homedir(), 'Library', 'Application Support', 'Code', 'User', 'keybindings.json');
488
+ }
489
+ else if (platform === 'win32') {
490
+ return join(homedir(), 'AppData', 'Roaming', 'Code', 'User', 'keybindings.json');
491
+ }
492
+ else {
493
+ return join(homedir(), '.config', 'Code', 'User', 'keybindings.json');
494
+ }
495
+ }
496
+ /**
497
+ * Check if VS Code Shift+Enter keybinding is already configured
498
+ */
499
+ export function isVSCodeShiftEnterConfigured() {
500
+ const keybindingsPath = getVSCodeKeybindingsPath();
501
+ if (!existsSync(keybindingsPath)) {
502
+ return false;
503
+ }
504
+ try {
505
+ let content = readFileSync(keybindingsPath, 'utf-8');
506
+ // Remove comments (VS Code keybindings.json supports comments)
507
+ content = content.replace(/\/\/.*$/gm, '').replace(/\/\*[\s\S]*?\*\//g, '');
508
+ const keybindings = JSON.parse(content);
509
+ if (!Array.isArray(keybindings)) {
510
+ return false;
511
+ }
512
+ // Check if a shift+enter binding for terminal exists
513
+ return keybindings.some((kb) => kb.key === 'shift+enter' &&
514
+ kb.command === 'workbench.action.terminal.sendSequence' &&
515
+ kb.when?.includes('terminalFocus'));
516
+ }
517
+ catch {
518
+ return false;
519
+ }
520
+ }
521
+ /**
522
+ * Configure VS Code Shift+Enter keybinding
523
+ * Returns { success, error?, backupPath? }
524
+ */
525
+ export function configureVSCodeShiftEnter() {
526
+ const keybindingsPath = getVSCodeKeybindingsPath();
527
+ const keybindingsDir = join(keybindingsPath, '..');
528
+ try {
529
+ // Ensure directory exists
530
+ if (!existsSync(keybindingsDir)) {
531
+ mkdirSync(keybindingsDir, { recursive: true });
532
+ }
533
+ let keybindings = [];
534
+ let backupPath;
535
+ // Read existing keybindings if file exists
536
+ if (existsSync(keybindingsPath)) {
537
+ const content = readFileSync(keybindingsPath, 'utf-8');
538
+ // Backup existing file
539
+ backupPath = keybindingsPath + '.maistro-backup';
540
+ writeFileSync(backupPath, content, 'utf-8');
541
+ // Parse (remove comments first)
542
+ const cleanContent = content.replace(/\/\/.*$/gm, '').replace(/\/\*[\s\S]*?\*\//g, '');
543
+ try {
544
+ keybindings = JSON.parse(cleanContent);
545
+ if (!Array.isArray(keybindings)) {
546
+ keybindings = [];
547
+ }
548
+ }
549
+ catch {
550
+ // If parse fails, start fresh but keep backup
551
+ keybindings = [];
552
+ }
553
+ }
554
+ // Check if already configured
555
+ const existingIndex = keybindings.findIndex((kb) => kb.key === 'shift+enter' &&
556
+ kb.command === 'workbench.action.terminal.sendSequence' &&
557
+ kb.when?.includes('terminalFocus'));
558
+ if (existingIndex >= 0) {
559
+ // Already configured
560
+ return { success: true };
561
+ }
562
+ // Add the keybinding
563
+ keybindings.push(VSCODE_SHIFT_ENTER_KEYBINDING);
564
+ // Write back
565
+ writeFileSync(keybindingsPath, JSON.stringify(keybindings, null, 2), 'utf-8');
566
+ return { success: true, backupPath };
567
+ }
568
+ catch (error) {
569
+ return {
570
+ success: false,
571
+ error: error instanceof Error ? error.message : 'Failed to configure VS Code',
572
+ };
573
+ }
574
+ }
575
+ /**
576
+ * Remove VS Code Shift+Enter keybinding
577
+ */
578
+ export function removeVSCodeShiftEnter() {
579
+ const keybindingsPath = getVSCodeKeybindingsPath();
580
+ if (!existsSync(keybindingsPath)) {
581
+ return { success: true }; // Nothing to remove
582
+ }
583
+ try {
584
+ const content = readFileSync(keybindingsPath, 'utf-8');
585
+ const cleanContent = content.replace(/\/\/.*$/gm, '').replace(/\/\*[\s\S]*?\*\//g, '');
586
+ let keybindings;
587
+ try {
588
+ keybindings = JSON.parse(cleanContent);
589
+ if (!Array.isArray(keybindings)) {
590
+ return { success: true };
591
+ }
592
+ }
593
+ catch {
594
+ return { success: true };
595
+ }
596
+ // Remove the keybinding
597
+ const filtered = keybindings.filter((kb) => !(kb.key === 'shift+enter' &&
598
+ kb.command === 'workbench.action.terminal.sendSequence' &&
599
+ kb.when?.includes('terminalFocus')));
600
+ if (filtered.length === keybindings.length) {
601
+ // Nothing was removed
602
+ return { success: true };
603
+ }
604
+ // Write back
605
+ writeFileSync(keybindingsPath, JSON.stringify(filtered, null, 2), 'utf-8');
606
+ return { success: true };
607
+ }
608
+ catch (error) {
609
+ return {
610
+ success: false,
611
+ error: error instanceof Error ? error.message : 'Failed to remove VS Code keybinding',
612
+ };
613
+ }
614
+ }
615
+ /**
616
+ * Check if VS Code Cmd+V keybinding is already configured
617
+ */
618
+ export function isVSCodeCmdVConfigured() {
619
+ const keybindingsPath = getVSCodeKeybindingsPath();
620
+ if (!existsSync(keybindingsPath)) {
621
+ return false;
622
+ }
623
+ try {
624
+ let content = readFileSync(keybindingsPath, 'utf-8');
625
+ content = content.replace(/\/\/.*$/gm, '').replace(/\/\*[\s\S]*?\*\//g, '');
626
+ const keybindings = JSON.parse(content);
627
+ if (!Array.isArray(keybindings)) {
628
+ return false;
629
+ }
630
+ return keybindings.some((kb) => kb.key === 'cmd+v' &&
631
+ kb.command === 'workbench.action.terminal.sendSequence' &&
632
+ kb.when?.includes('terminalFocus'));
633
+ }
634
+ catch {
635
+ return false;
636
+ }
637
+ }
638
+ /**
639
+ * Configure VS Code Cmd+V keybinding for clipboard paste
640
+ */
641
+ export function configureVSCodeCmdV() {
642
+ const keybindingsPath = getVSCodeKeybindingsPath();
643
+ const keybindingsDir = join(keybindingsPath, '..');
644
+ try {
645
+ if (!existsSync(keybindingsDir)) {
646
+ mkdirSync(keybindingsDir, { recursive: true });
647
+ }
648
+ let keybindings = [];
649
+ let backupPath;
650
+ if (existsSync(keybindingsPath)) {
651
+ const content = readFileSync(keybindingsPath, 'utf-8');
652
+ backupPath = keybindingsPath + '.maistro-backup';
653
+ writeFileSync(backupPath, content, 'utf-8');
654
+ const cleanContent = content.replace(/\/\/.*$/gm, '').replace(/\/\*[\s\S]*?\*\//g, '');
655
+ try {
656
+ keybindings = JSON.parse(cleanContent);
657
+ if (!Array.isArray(keybindings)) {
658
+ keybindings = [];
659
+ }
660
+ }
661
+ catch {
662
+ keybindings = [];
663
+ }
664
+ }
665
+ const existingIndex = keybindings.findIndex((kb) => kb.key === 'cmd+v' &&
666
+ kb.command === 'workbench.action.terminal.sendSequence' &&
667
+ kb.when?.includes('terminalFocus'));
668
+ if (existingIndex >= 0) {
669
+ return { success: true };
670
+ }
671
+ keybindings.push(VSCODE_CMD_V_KEYBINDING);
672
+ writeFileSync(keybindingsPath, JSON.stringify(keybindings, null, 2), 'utf-8');
673
+ return { success: true, backupPath };
674
+ }
675
+ catch (error) {
676
+ return {
677
+ success: false,
678
+ error: error instanceof Error ? error.message : 'Failed to configure VS Code',
679
+ };
680
+ }
681
+ }
682
+ /**
683
+ * Remove VS Code Cmd+V keybinding
684
+ */
685
+ export function removeVSCodeCmdV() {
686
+ const keybindingsPath = getVSCodeKeybindingsPath();
687
+ if (!existsSync(keybindingsPath)) {
688
+ return { success: true };
689
+ }
690
+ try {
691
+ const content = readFileSync(keybindingsPath, 'utf-8');
692
+ const cleanContent = content.replace(/\/\/.*$/gm, '').replace(/\/\*[\s\S]*?\*\//g, '');
693
+ let keybindings;
694
+ try {
695
+ keybindings = JSON.parse(cleanContent);
696
+ if (!Array.isArray(keybindings)) {
697
+ return { success: true };
698
+ }
699
+ }
700
+ catch {
701
+ return { success: true };
702
+ }
703
+ const filtered = keybindings.filter((kb) => !(kb.key === 'cmd+v' &&
704
+ kb.command === 'workbench.action.terminal.sendSequence' &&
705
+ kb.when?.includes('terminalFocus')));
706
+ if (filtered.length === keybindings.length) {
707
+ return { success: true };
708
+ }
709
+ writeFileSync(keybindingsPath, JSON.stringify(filtered, null, 2), 'utf-8');
710
+ return { success: true };
711
+ }
712
+ catch (error) {
713
+ return {
714
+ success: false,
715
+ error: error instanceof Error ? error.message : 'Failed to remove VS Code keybinding',
716
+ };
717
+ }
718
+ }
719
+ /**
720
+ * Configure terminal for Cmd+V paste support
721
+ * Warning: VS Code keybindings with "terminalFocus" affect ALL terminals,
722
+ * which may interfere with other terminal apps like Claude Code.
723
+ */
724
+ export function configureTerminalCmdVPaste(terminalInfo) {
725
+ // VS Code: Can be configured but affects all terminals (may break Claude Code)
726
+ if (terminalInfo.program === 'vscode') {
727
+ const result = configureVSCodeCmdV();
728
+ if (result.success) {
729
+ return {
730
+ success: true,
731
+ message: 'VS Code Cmd+V keybinding configured',
732
+ backupPath: result.backupPath,
733
+ warning: 'This affects ALL VS Code terminals, including other apps like Claude Code.',
734
+ };
735
+ }
736
+ return result;
737
+ }
738
+ // Other terminals require manual configuration
739
+ return {
740
+ success: false,
741
+ error: `Automatic configuration not supported for ${terminalInfo.name}. See instructions for manual setup.`,
742
+ };
743
+ }
744
+ /**
745
+ * Remove terminal Cmd+V paste configuration
746
+ */
747
+ export function removeTerminalCmdVPaste(terminalInfo) {
748
+ switch (terminalInfo.program) {
749
+ case 'vscode': {
750
+ const result = removeVSCodeCmdV();
751
+ if (result.success) {
752
+ return { success: true, message: 'VS Code Cmd+V keybinding removed' };
753
+ }
754
+ return result;
755
+ }
756
+ default:
757
+ return { success: true };
758
+ }
759
+ }
760
+ /**
761
+ * Configure terminal for Shift+Enter support
762
+ * Returns result with success status and any messages
763
+ */
764
+ export function configureTerminalShiftEnter(terminalInfo) {
765
+ // Terminals with native support don't need configuration
766
+ if (terminalInfo.supportsShiftEnter) {
767
+ return { success: true, message: 'Terminal already supports Shift+Enter natively' };
768
+ }
769
+ // Handle each configurable terminal
770
+ switch (terminalInfo.program) {
771
+ case 'vscode': {
772
+ const result = configureVSCodeShiftEnter();
773
+ if (result.success) {
774
+ return {
775
+ success: true,
776
+ message: 'VS Code keybinding configured. Restart terminal for changes to take effect.',
777
+ backupPath: result.backupPath,
778
+ };
779
+ }
780
+ return result;
781
+ }
782
+ // TODO: Add support for other terminals
783
+ // case 'iterm2':
784
+ // case 'alacritty':
785
+ // case 'zed':
786
+ default:
787
+ return {
788
+ success: false,
789
+ error: `Automatic configuration not supported for ${terminalInfo.name}. Use Ctrl+J instead.`,
790
+ };
791
+ }
792
+ }
793
+ /**
794
+ * Remove terminal Shift+Enter configuration
795
+ */
796
+ export function removeTerminalShiftEnter(terminalInfo) {
797
+ if (terminalInfo.supportsShiftEnter) {
798
+ return { success: true, message: 'Terminal has native support, nothing to remove' };
799
+ }
800
+ switch (terminalInfo.program) {
801
+ case 'vscode': {
802
+ const result = removeVSCodeShiftEnter();
803
+ if (result.success) {
804
+ return { success: true, message: 'VS Code keybinding removed' };
805
+ }
806
+ return result;
807
+ }
808
+ default:
809
+ return { success: true };
810
+ }
811
+ }
812
+ /**
813
+ * Get the appropriate newline hint based on terminal configuration
814
+ * Returns "Ctrl+J newline" or "Ctrl+J | Shift+Enter newline"
815
+ */
816
+ export function getNewlineHint() {
817
+ const terminalInfo = detectTerminal();
818
+ const shiftEnterEnabled = getShiftEnterEnabled();
819
+ // Show Shift+Enter hint if terminal has native support or is configured
820
+ if (terminalInfo.supportsShiftEnter || shiftEnterEnabled) {
821
+ return 'Ctrl+J | Shift+Enter newline';
822
+ }
823
+ return 'Ctrl+J newline';
824
+ }
825
+ /**
826
+ * Get terminal-specific instructions for configuring Cmd+V paste
827
+ * macOS terminals intercept Cmd+V for their own paste handling
828
+ * We need them to send an escape sequence we can detect
829
+ */
830
+ export function getCmdVPasteInstructions() {
831
+ const terminalInfo = detectTerminal();
832
+ switch (terminalInfo.program) {
833
+ case 'iterm2':
834
+ return {
835
+ terminal: 'iTerm2',
836
+ instructions: [
837
+ 'Open iTerm2 → Preferences → Keys → Key Bindings',
838
+ 'Click the + button to add a new binding:',
839
+ ' - Keyboard Shortcut: ⌘V (Cmd+V)',
840
+ ' - Action: Send Escape Sequence',
841
+ ' - Esc+: OV',
842
+ '',
843
+ 'This makes Cmd+V send \\x1bOV which maistro detects for image paste.',
844
+ ],
845
+ };
846
+ case 'kitty':
847
+ return {
848
+ terminal: 'Kitty',
849
+ instructions: [
850
+ 'Kitty uses the CSI u keyboard protocol.',
851
+ 'Add to ~/.config/kitty/kitty.conf:',
852
+ '',
853
+ 'map cmd+v send_text all \\x1b[118;9u',
854
+ '',
855
+ 'Or Kitty may already send this sequence natively.',
856
+ ],
857
+ };
858
+ case 'wezterm':
859
+ return {
860
+ terminal: 'WezTerm',
861
+ instructions: [
862
+ 'Add to ~/.wezterm.lua:',
863
+ '',
864
+ 'return {',
865
+ ' keys = {',
866
+ ' { key = "v", mods = "CMD", action = wezterm.action.SendString("\\x1b[118;9u") },',
867
+ ' },',
868
+ '}',
869
+ ],
870
+ };
871
+ case 'ghostty':
872
+ return {
873
+ terminal: 'Ghostty',
874
+ instructions: [
875
+ 'Add to ~/.config/ghostty/config:',
876
+ '',
877
+ 'keybind = cmd+v=text:\\x1b[118;9u',
878
+ ],
879
+ };
880
+ case 'vscode':
881
+ return {
882
+ terminal: 'VS Code Terminal',
883
+ instructions: [
884
+ '\x1b[33m⚠ WARNING:\x1b[0m VS Code keybindings with "terminalFocus" affect ALL terminals.',
885
+ 'This will change Cmd+V behavior in every terminal, including Claude Code.',
886
+ '',
887
+ '\x1b[1mOption 1: Enable Cmd+V override\x1b[0m (may affect other terminal apps)',
888
+ ' Use the Settings menu to enable "Cmd+V for image paste"',
889
+ ' To remove later: toggle OFF in Settings or delete the keybinding from:',
890
+ ' ~/Library/Application Support/Code/User/keybindings.json',
891
+ '',
892
+ '\x1b[1mOption 2: Use Ctrl+V instead\x1b[0m (recommended, no conflicts)',
893
+ ' Ctrl+V sends the standard paste sequence (\\x16) that maistro detects.',
894
+ ' This works without any configuration and won\'t affect other apps.',
895
+ '',
896
+ '\x1b[1mOption 3: Use /paste command\x1b[0m',
897
+ ' Type /paste in maistro to paste images from clipboard.',
898
+ ],
899
+ };
900
+ case 'alacritty':
901
+ return {
902
+ terminal: 'Alacritty',
903
+ instructions: [
904
+ 'Add to ~/.config/alacritty/alacritty.toml:',
905
+ '',
906
+ '[keyboard]',
907
+ 'bindings = [',
908
+ ' { key = "V", mods = "Command", chars = "\\u001b[118;9u" }',
909
+ ']',
910
+ ],
911
+ };
912
+ case 'apple-terminal':
913
+ return {
914
+ terminal: 'Terminal.app',
915
+ instructions: [
916
+ 'Terminal.app has limited key binding customization.',
917
+ 'Use /paste command to paste images from clipboard.',
918
+ '',
919
+ 'Or consider switching to iTerm2, Kitty, WezTerm, or Ghostty',
920
+ 'which have better keyboard customization support.',
921
+ ],
922
+ };
923
+ default:
924
+ return {
925
+ terminal: terminalInfo.name,
926
+ instructions: [
927
+ 'Configure your terminal to send \\x1b[118;9u for Cmd+V.',
928
+ 'Or use the /paste command to paste images from clipboard.',
929
+ ],
930
+ };
931
+ }
932
+ }
933
+ //# sourceMappingURL=config.js.map