@shareai-lab/kode 1.0.71 → 1.0.75

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 (108) hide show
  1. package/README.md +160 -1
  2. package/README.zh-CN.md +65 -1
  3. package/cli.js +5 -10
  4. package/package.json +6 -2
  5. package/src/ProjectOnboarding.tsx +47 -29
  6. package/src/Tool.ts +33 -4
  7. package/src/commands/agents.tsx +3401 -0
  8. package/src/commands/help.tsx +2 -2
  9. package/src/commands/resume.tsx +2 -1
  10. package/src/commands/terminalSetup.ts +4 -4
  11. package/src/commands.ts +3 -0
  12. package/src/components/ApproveApiKey.tsx +1 -1
  13. package/src/components/Config.tsx +10 -6
  14. package/src/components/ConsoleOAuthFlow.tsx +5 -4
  15. package/src/components/CustomSelect/select-option.tsx +28 -2
  16. package/src/components/CustomSelect/select.tsx +14 -5
  17. package/src/components/CustomSelect/theme.ts +45 -0
  18. package/src/components/Help.tsx +4 -4
  19. package/src/components/InvalidConfigDialog.tsx +1 -1
  20. package/src/components/LogSelector.tsx +1 -1
  21. package/src/components/MCPServerApprovalDialog.tsx +1 -1
  22. package/src/components/Message.tsx +2 -0
  23. package/src/components/ModelListManager.tsx +10 -6
  24. package/src/components/ModelSelector.tsx +201 -23
  25. package/src/components/ModelStatusDisplay.tsx +7 -5
  26. package/src/components/PromptInput.tsx +146 -96
  27. package/src/components/SentryErrorBoundary.ts +9 -3
  28. package/src/components/StickerRequestForm.tsx +16 -0
  29. package/src/components/StructuredDiff.tsx +36 -29
  30. package/src/components/TextInput.tsx +13 -0
  31. package/src/components/TodoItem.tsx +47 -0
  32. package/src/components/TrustDialog.tsx +1 -1
  33. package/src/components/messages/AssistantLocalCommandOutputMessage.tsx +5 -1
  34. package/src/components/messages/AssistantToolUseMessage.tsx +14 -4
  35. package/src/components/messages/TaskProgressMessage.tsx +32 -0
  36. package/src/components/messages/TaskToolMessage.tsx +58 -0
  37. package/src/components/permissions/FallbackPermissionRequest.tsx +2 -4
  38. package/src/components/permissions/FileEditPermissionRequest/FileEditPermissionRequest.tsx +1 -1
  39. package/src/components/permissions/FileEditPermissionRequest/FileEditToolDiff.tsx +5 -3
  40. package/src/components/permissions/FileWritePermissionRequest/FileWritePermissionRequest.tsx +1 -1
  41. package/src/components/permissions/FileWritePermissionRequest/FileWriteToolDiff.tsx +5 -3
  42. package/src/components/permissions/FilesystemPermissionRequest/FilesystemPermissionRequest.tsx +2 -4
  43. package/src/components/permissions/PermissionRequest.tsx +3 -5
  44. package/src/constants/macros.ts +2 -0
  45. package/src/constants/modelCapabilities.ts +179 -0
  46. package/src/constants/models.ts +90 -0
  47. package/src/constants/product.ts +1 -1
  48. package/src/context.ts +7 -7
  49. package/src/entrypoints/cli.tsx +23 -3
  50. package/src/entrypoints/mcp.ts +10 -10
  51. package/src/hooks/useCanUseTool.ts +1 -1
  52. package/src/hooks/useTextInput.ts +5 -2
  53. package/src/hooks/useUnifiedCompletion.ts +1405 -0
  54. package/src/messages.ts +1 -0
  55. package/src/query.ts +3 -0
  56. package/src/screens/ConfigureNpmPrefix.tsx +1 -1
  57. package/src/screens/Doctor.tsx +1 -1
  58. package/src/screens/REPL.tsx +11 -12
  59. package/src/services/adapters/base.ts +38 -0
  60. package/src/services/adapters/chatCompletions.ts +90 -0
  61. package/src/services/adapters/responsesAPI.ts +170 -0
  62. package/src/services/claude.ts +198 -62
  63. package/src/services/customCommands.ts +43 -22
  64. package/src/services/gpt5ConnectionTest.ts +340 -0
  65. package/src/services/mcpClient.ts +1 -1
  66. package/src/services/mentionProcessor.ts +273 -0
  67. package/src/services/modelAdapterFactory.ts +69 -0
  68. package/src/services/openai.ts +534 -14
  69. package/src/services/responseStateManager.ts +90 -0
  70. package/src/services/systemReminder.ts +113 -12
  71. package/src/test/testAdapters.ts +96 -0
  72. package/src/tools/AskExpertModelTool/AskExpertModelTool.tsx +120 -56
  73. package/src/tools/BashTool/BashTool.tsx +4 -31
  74. package/src/tools/BashTool/BashToolResultMessage.tsx +1 -1
  75. package/src/tools/BashTool/OutputLine.tsx +1 -0
  76. package/src/tools/FileEditTool/FileEditTool.tsx +4 -5
  77. package/src/tools/FileReadTool/FileReadTool.tsx +43 -10
  78. package/src/tools/MCPTool/MCPTool.tsx +2 -1
  79. package/src/tools/MultiEditTool/MultiEditTool.tsx +2 -2
  80. package/src/tools/NotebookReadTool/NotebookReadTool.tsx +15 -23
  81. package/src/tools/StickerRequestTool/StickerRequestTool.tsx +1 -1
  82. package/src/tools/TaskTool/TaskTool.tsx +170 -86
  83. package/src/tools/TaskTool/prompt.ts +61 -25
  84. package/src/tools/ThinkTool/ThinkTool.tsx +1 -3
  85. package/src/tools/TodoWriteTool/TodoWriteTool.tsx +65 -41
  86. package/src/tools/lsTool/lsTool.tsx +5 -2
  87. package/src/tools.ts +16 -16
  88. package/src/types/conversation.ts +51 -0
  89. package/src/types/logs.ts +58 -0
  90. package/src/types/modelCapabilities.ts +64 -0
  91. package/src/types/notebook.ts +87 -0
  92. package/src/utils/advancedFuzzyMatcher.ts +290 -0
  93. package/src/utils/agentLoader.ts +284 -0
  94. package/src/utils/ask.tsx +1 -0
  95. package/src/utils/commands.ts +1 -1
  96. package/src/utils/commonUnixCommands.ts +161 -0
  97. package/src/utils/config.ts +173 -2
  98. package/src/utils/conversationRecovery.ts +1 -0
  99. package/src/utils/debugLogger.ts +13 -13
  100. package/src/utils/exampleCommands.ts +1 -0
  101. package/src/utils/fuzzyMatcher.ts +328 -0
  102. package/src/utils/messages.tsx +6 -5
  103. package/src/utils/model.ts +120 -42
  104. package/src/utils/responseState.ts +23 -0
  105. package/src/utils/secureFile.ts +559 -0
  106. package/src/utils/terminal.ts +1 -0
  107. package/src/utils/theme.ts +11 -0
  108. package/src/hooks/useSlashCommandTypeahead.ts +0 -137
@@ -185,7 +185,7 @@ export function ModelStatusDisplay({ onClose }: Props): React.ReactNode {
185
185
  </Text>
186
186
  <Text color={theme.secondaryText}>
187
187
  {' '}
188
- DefaultModelId: {config.defaultModelId || 'not set'}
188
+ DefaultModelId: {(config as any).defaultModelId || 'not set'}
189
189
  </Text>
190
190
  {config.modelPointers && (
191
191
  <>
@@ -195,10 +195,12 @@ export function ModelStatusDisplay({ onClose }: Props): React.ReactNode {
195
195
  {Object.keys(config.modelPointers).length > 0 ? 'Yes' : 'No'}
196
196
  </Text>
197
197
  {Object.entries(config.modelPointers).map(([pointer, modelId]) => (
198
- <Text key={pointer} color={theme.secondaryText}>
199
- {' '}
200
- {pointer}: {modelId || 'not set'}
201
- </Text>
198
+ <React.Fragment key={pointer}>
199
+ <Text color={theme.secondaryText}>
200
+ {' '}
201
+ {pointer}: {modelId || 'not set'}
202
+ </Text>
203
+ </React.Fragment>
202
204
  ))}
203
205
  </>
204
206
  )}
@@ -5,7 +5,7 @@ import * as React from 'react'
5
5
  import { type Message } from '../query'
6
6
  import { processUserInput } from '../utils/messages'
7
7
  import { useArrowKeyHistory } from '../hooks/useArrowKeyHistory'
8
- import { useSlashCommandTypeahead } from '../hooks/useSlashCommandTypeahead'
8
+ import { useUnifiedCompletion } from '../hooks/useUnifiedCompletion'
9
9
  import { addToHistory } from '../history'
10
10
  import TextInput from './TextInput'
11
11
  import { memo, useCallback, useEffect, useMemo, useState } from 'react'
@@ -96,7 +96,6 @@ type Props = {
96
96
  ) => void
97
97
  readFileTimestamps: { [filename: string]: number }
98
98
  abortController: AbortController | null
99
- setAbortController: (abortController: AbortController | null) => void
100
99
  onModelChange?: () => void
101
100
  }
102
101
 
@@ -166,19 +165,53 @@ function PromptInput({
166
165
  [commands],
167
166
  )
168
167
 
168
+ // Unified completion system - one hook to rule them all (now with terminal behavior)
169
169
  const {
170
170
  suggestions,
171
- selectedSuggestion,
172
- updateSuggestions,
173
- clearSuggestions,
174
- } = useSlashCommandTypeahead({
175
- commands,
171
+ selectedIndex,
172
+ isActive: completionActive,
173
+ emptyDirMessage,
174
+ } = useUnifiedCompletion({
175
+ input,
176
+ cursorOffset,
176
177
  onInputChange,
177
- onSubmit,
178
178
  setCursorOffset,
179
- currentInput: input,
179
+ commands,
180
+ onSubmit,
180
181
  })
181
182
 
183
+ // Get theme early for memoized rendering
184
+ const theme = getTheme()
185
+
186
+ // Memoized completion suggestions rendering - after useUnifiedCompletion
187
+ const renderedSuggestions = useMemo(() => {
188
+ if (suggestions.length === 0) return null
189
+
190
+ return suggestions.map((suggestion, index) => {
191
+ const isSelected = index === selectedIndex
192
+ const isAgent = suggestion.type === 'agent'
193
+
194
+ // Simple color logic without complex lookups
195
+ const displayColor = isSelected
196
+ ? theme.suggestion
197
+ : (isAgent && suggestion.metadata?.color)
198
+ ? suggestion.metadata.color
199
+ : undefined
200
+
201
+ return (
202
+ <Box key={`${suggestion.type}-${suggestion.value}-${index}`} flexDirection="row">
203
+ <Text
204
+ color={displayColor}
205
+ dimColor={!isSelected && !displayColor}
206
+ >
207
+ {isSelected ? '◆ ' : ' '}
208
+ {suggestion.displayValue}
209
+ </Text>
210
+ </Box>
211
+ )
212
+ })
213
+ }, [suggestions, selectedIndex, theme.suggestion])
214
+
182
215
  const onChange = useCallback(
183
216
  (value: string) => {
184
217
  if (value.startsWith('!')) {
@@ -189,26 +222,27 @@ function PromptInput({
189
222
  onModeChange('koding')
190
223
  return
191
224
  }
192
- updateSuggestions(value)
193
225
  onInputChange(value)
194
226
  },
195
- [onModeChange, onInputChange, updateSuggestions],
227
+ [onModeChange, onInputChange],
196
228
  )
197
229
 
198
- // Handle Tab key model switching with simple context check
230
+ // Handle Shift+M model switching with enhanced debugging
199
231
  const handleQuickModelSwitch = useCallback(async () => {
200
232
  const modelManager = getModelManager()
201
233
  const currentTokens = countTokens(messages)
202
234
 
235
+ // Get debug info for better error reporting
236
+ const debugInfo = modelManager.getModelSwitchingDebugInfo()
237
+
203
238
  const switchResult = modelManager.switchToNextModel(currentTokens)
204
239
 
205
240
  if (switchResult.success && switchResult.modelName) {
206
- // Successful switch
241
+ // Successful switch - use enhanced message from model manager
207
242
  onSubmitCountChange(prev => prev + 1)
208
- const newModel = modelManager.getModel('main')
209
243
  setModelSwitchMessage({
210
244
  show: true,
211
- text: `✅ Switched to ${switchResult.modelName} (${newModel?.provider || 'Unknown'} | Model: ${newModel?.modelName || 'N/A'})`,
245
+ text: switchResult.message || `✅ Switched to ${switchResult.modelName}`,
212
246
  })
213
247
  setTimeout(() => setModelSwitchMessage({ show: false }), 3000)
214
248
  } else if (switchResult.blocked && switchResult.message) {
@@ -219,14 +253,28 @@ function PromptInput({
219
253
  })
220
254
  setTimeout(() => setModelSwitchMessage({ show: false }), 5000)
221
255
  } else {
222
- // No other models available or other error
256
+ // Enhanced error reporting with debug info
257
+ let errorMessage = switchResult.message
258
+
259
+ if (!errorMessage) {
260
+ if (debugInfo.totalModels === 0) {
261
+ errorMessage = '❌ No models configured. Use /model to add models.'
262
+ } else if (debugInfo.activeModels === 0) {
263
+ errorMessage = `❌ No active models (${debugInfo.totalModels} total, all inactive). Use /model to activate models.`
264
+ } else if (debugInfo.activeModels === 1) {
265
+ // Show ALL models including inactive ones for debugging
266
+ const allModelNames = debugInfo.availableModels.map(m => `${m.name}${m.isActive ? '' : ' (inactive)'}`).join(', ')
267
+ errorMessage = `⚠️ Only 1 active model out of ${debugInfo.totalModels} total models: ${allModelNames}. ALL configured models will be activated for switching.`
268
+ } else {
269
+ errorMessage = `❌ Model switching failed (${debugInfo.activeModels} active, ${debugInfo.totalModels} total models available)`
270
+ }
271
+ }
272
+
223
273
  setModelSwitchMessage({
224
274
  show: true,
225
- text:
226
- switchResult.message ||
227
- '⚠️ No other models configured. Use /model to add more models',
275
+ text: errorMessage,
228
276
  })
229
- setTimeout(() => setModelSwitchMessage({ show: false }), 3000)
277
+ setTimeout(() => setModelSwitchMessage({ show: false }), 6000)
230
278
  }
231
279
  }, [onSubmitCountChange, messages])
232
280
 
@@ -238,15 +286,15 @@ function PromptInput({
238
286
  input,
239
287
  )
240
288
 
241
- // Only use history navigation when there are 0 or 1 slash command suggestions
289
+ // Only use history navigation when there are no suggestions
242
290
  const handleHistoryUp = () => {
243
- if (suggestions.length <= 1) {
291
+ if (!completionActive) {
244
292
  onHistoryUp()
245
293
  }
246
294
  }
247
295
 
248
296
  const handleHistoryDown = () => {
249
- if (suggestions.length <= 1) {
297
+ if (!completionActive) {
250
298
  onHistoryDown()
251
299
  }
252
300
  }
@@ -270,7 +318,7 @@ function PromptInput({
270
318
 
271
319
  // Create additional context to inform Claude this is for KODING.md
272
320
  const kodingContext =
273
- 'The user is using Koding mode. Format your response as a comprehensive, well-structured document suitable for adding to KODE.md. Use proper markdown formatting with headings, lists, code blocks, etc. The response should be complete and ready to add to KODE.md documentation.'
321
+ 'The user is using Koding mode. Format your response as a comprehensive, well-structured document suitable for adding to AGENTS.md. Use proper markdown formatting with headings, lists, code blocks, etc. The response should be complete and ready to add to AGENTS.md documentation.'
274
322
 
275
323
  // Switch to prompt mode but tag the submission for later capture
276
324
  onModeChange('prompt')
@@ -326,7 +374,7 @@ function PromptInput({
326
374
  }
327
375
  }
328
376
 
329
- // If in koding mode or input starts with '#', interpret it using AI before appending to KODE.md
377
+ // If in koding mode or input starts with '#', interpret it using AI before appending to AGENTS.md
330
378
  else if (mode === 'koding' || input.startsWith('#')) {
331
379
  try {
332
380
  // Strip the # if we're in koding mode and the user didn't type it (since it's implied)
@@ -354,7 +402,12 @@ function PromptInput({
354
402
  if (isLoading) {
355
403
  return
356
404
  }
357
- if (suggestions.length > 0 && !isSubmittingSlashCommand) {
405
+
406
+ // Handle Enter key when completions are active
407
+ // If there are suggestions showing, Enter should complete the selection, not send the message
408
+ if (suggestions.length > 0 && completionActive) {
409
+ // The completion is handled by useUnifiedCompletion hook
410
+ // Just return to prevent message sending
358
411
  return
359
412
  }
360
413
 
@@ -373,7 +426,7 @@ function PromptInput({
373
426
  }
374
427
  onInputChange('')
375
428
  onModeChange('prompt')
376
- clearSuggestions()
429
+ // Suggestions are now handled by unified completion
377
430
  setPastedImage(null)
378
431
  setPastedText(null)
379
432
  onSubmitCountChange(_ => _ + 1)
@@ -446,8 +499,29 @@ function PromptInput({
446
499
  setPastedText(text)
447
500
  }
448
501
 
449
- useInput((input, key) => {
450
- if (input === '' && (key.escape || key.backspace || key.delete)) {
502
+ useInput((inputChar, key) => {
503
+ // For bash mode, only exit when deleting the last character (which would be the '!' character)
504
+ if (mode === 'bash' && (key.backspace || key.delete)) {
505
+ // Check the current input state, not the inputChar parameter
506
+ // If current input is empty, we're about to delete the '!' character, so exit bash mode
507
+ if (input === '') {
508
+ onModeChange('prompt')
509
+ }
510
+ return
511
+ }
512
+
513
+ // For koding mode, only exit when deleting the last character (which would be the '#' character)
514
+ if (mode === 'koding' && (key.backspace || key.delete)) {
515
+ // Check the current input state, not the inputChar parameter
516
+ // If current input is empty, we're about to delete the '#' character, so exit koding mode
517
+ if (input === '') {
518
+ onModeChange('prompt')
519
+ }
520
+ return
521
+ }
522
+
523
+ // For other modes, keep the original behavior
524
+ if (inputChar === '' && (key.escape || key.backspace || key.delete)) {
451
525
  onModeChange('prompt')
452
526
  }
453
527
  // esc is a little overloaded:
@@ -464,22 +538,26 @@ function PromptInput({
464
538
  return true // Explicitly handled
465
539
  }
466
540
 
467
- // Tab key for model switching (simple and non-conflicting)
468
- if (key.tab && !key.shift) {
469
- handleQuickModelSwitch()
470
- return true // Explicitly handled
471
- }
472
-
473
541
  return false // Not handled, allow other hooks
474
542
  })
475
543
 
544
+ // Handle special key combinations before character input
545
+ const handleSpecialKey = useCallback((inputChar: string, key: any): boolean => {
546
+ // Shift+M for model switching - intercept before character input
547
+ if (key.shift && (inputChar === 'M' || inputChar === 'm')) {
548
+ handleQuickModelSwitch()
549
+ return true // Prevent character from being input
550
+ }
551
+
552
+ return false // Not handled, allow normal processing
553
+ }, [handleQuickModelSwitch])
554
+
476
555
  const textInputColumns = useTerminalSize().columns - 6
477
556
  const tokenUsage = useMemo(() => countTokens(messages), [messages])
478
- const theme = getTheme()
479
557
 
480
558
  // 🔧 Fix: Track model ID changes to detect external config updates
481
559
  const modelManager = getModelManager()
482
- const currentModelId = modelManager.getModel('main')?.id || null
560
+ const currentModelId = (modelManager.getModel('main') as any)?.id || null
483
561
 
484
562
  const modelInfo = useMemo(() => {
485
563
  // Force fresh ModelManager instance to detect config changes
@@ -491,7 +569,7 @@ function PromptInput({
491
569
 
492
570
  return {
493
571
  name: currentModel.modelName, // 🔧 Fix: Use actual model name, not display name
494
- id: currentModel.id, // 添加模型ID用于调试
572
+ id: (currentModel as any).id, // 添加模型ID用于调试
495
573
  provider: currentModel.provider, // 添加提供商信息
496
574
  contextLength: currentModel.contextLength,
497
575
  currentTokens: tokenUsage,
@@ -559,14 +637,15 @@ function PromptInput({
559
637
  onImagePaste={onImagePaste}
560
638
  columns={textInputColumns}
561
639
  isDimmed={isDisabled || isLoading}
562
- disableCursorMovementForUpDownKeys={suggestions.length > 0}
640
+ disableCursorMovementForUpDownKeys={completionActive}
563
641
  cursorOffset={cursorOffset}
564
642
  onChangeCursorOffset={setCursorOffset}
565
643
  onPaste={onTextPaste}
644
+ onSpecialKey={handleSpecialKey}
566
645
  />
567
646
  </Box>
568
647
  </Box>
569
- {suggestions.length === 0 && (
648
+ {!completionActive && suggestions.length === 0 && (
570
649
  <Box
571
650
  flexDirection="row"
572
651
  justifyContent="space-between"
@@ -592,15 +671,15 @@ function PromptInput({
592
671
  color={mode === 'koding' ? theme.koding : undefined}
593
672
  dimColor={mode !== 'koding'}
594
673
  >
595
- · # for KODE.md
674
+ · # for AGENTS.md
596
675
  </Text>
597
676
  <Text dimColor>
598
- · / for commands · tab to switch model · esc to undo
677
+ · / for commands · shift+m to switch model · esc to undo
599
678
  </Text>
600
679
  </>
601
680
  )}
602
681
  </Box>
603
- <SentryErrorBoundary>
682
+ <SentryErrorBoundary children={
604
683
  <Box justifyContent="flex-end" gap={1}>
605
684
  {!autoUpdaterResult &&
606
685
  !isAutoUpdating &&
@@ -622,9 +701,10 @@ function PromptInput({
622
701
  onChangeIsUpdating={setIsAutoUpdating}
623
702
  /> */}
624
703
  </Box>
625
- </SentryErrorBoundary>
704
+ } />
626
705
  </Box>
627
706
  )}
707
+ {/* Unified completion suggestions - optimized rendering */}
628
708
  {suggestions.length > 0 && (
629
709
  <Box
630
710
  flexDirection="row"
@@ -633,58 +713,28 @@ function PromptInput({
633
713
  paddingY={0}
634
714
  >
635
715
  <Box flexDirection="column">
636
- {suggestions.map((suggestion, index) => {
637
- const command = commands.find(
638
- cmd => cmd.userFacingName() === suggestion.replace('/', ''),
639
- )
640
- return (
641
- <Box
642
- key={suggestion}
643
- flexDirection={columns < 80 ? 'column' : 'row'}
644
- >
645
- <Box width={columns < 80 ? undefined : commandWidth}>
646
- <Text
647
- color={
648
- index === selectedSuggestion
649
- ? theme.suggestion
650
- : undefined
651
- }
652
- dimColor={index !== selectedSuggestion}
653
- >
654
- /{suggestion}
655
- {command?.aliases && command.aliases.length > 0 && (
656
- <Text dimColor> ({command.aliases.join(', ')})</Text>
657
- )}
658
- </Text>
659
- </Box>
660
- {command && (
661
- <Box
662
- width={columns - (columns < 80 ? 4 : commandWidth + 4)}
663
- paddingLeft={columns < 80 ? 4 : 0}
664
- >
665
- <Text
666
- color={
667
- index === selectedSuggestion
668
- ? theme.suggestion
669
- : undefined
670
- }
671
- dimColor={index !== selectedSuggestion}
672
- wrap="wrap"
673
- >
674
- <Text dimColor={index !== selectedSuggestion}>
675
- {command.description}
676
- {command.type === 'prompt' && command.argNames?.length
677
- ? ` (arguments: ${command.argNames.join(', ')})`
678
- : null}
679
- </Text>
680
- </Text>
681
- </Box>
682
- )}
683
- </Box>
684
- )
685
- })}
716
+ {renderedSuggestions}
717
+
718
+ {/* 简洁操作提示框 */}
719
+ <Box marginTop={1} paddingX={3} borderStyle="round" borderColor="gray">
720
+ <Text dimColor={!emptyDirMessage} color={emptyDirMessage ? "yellow" : undefined}>
721
+ {emptyDirMessage || (() => {
722
+ const selected = suggestions[selectedIndex]
723
+ if (!selected) {
724
+ return '↑↓ navigate • → accept • Tab cycle • Esc close'
725
+ }
726
+ if (selected?.value.endsWith('/')) {
727
+ return '→ enter directory • ↑↓ navigate • Tab cycle • Esc close'
728
+ } else if (selected?.type === 'agent') {
729
+ return '→ select agent • ↑↓ navigate • Tab cycle • Esc close'
730
+ } else {
731
+ return '→ insert reference • ↑↓ navigate • Tab cycle • Esc close'
732
+ }
733
+ })()}
734
+ </Text>
735
+ </Box>
686
736
  </Box>
687
- <SentryErrorBoundary>
737
+ <SentryErrorBoundary children={
688
738
  <Box justifyContent="flex-end" gap={1}>
689
739
  <TokenWarning tokenUsage={countTokens(messages)} />
690
740
  <AutoUpdater
@@ -695,7 +745,7 @@ function PromptInput({
695
745
  onChangeIsUpdating={setIsAutoUpdating}
696
746
  />
697
747
  </Box>
698
- </SentryErrorBoundary>
748
+ } />
699
749
  </Box>
700
750
  )}
701
751
  </Box>
@@ -12,7 +12,7 @@ interface State {
12
12
  export class SentryErrorBoundary extends React.Component<Props, State> {
13
13
  constructor(props: Props) {
14
14
  super(props)
15
- this.state = { hasError: false }
15
+ ;(this as any).state = { hasError: false }
16
16
  }
17
17
 
18
18
  static getDerivedStateFromError(): State {
@@ -20,14 +20,20 @@ export class SentryErrorBoundary extends React.Component<Props, State> {
20
20
  }
21
21
 
22
22
  componentDidCatch(error: Error): void {
23
+ // Don't report user-initiated cancellations to Sentry
24
+ if (error.name === 'AbortError' ||
25
+ error.message?.includes('abort') ||
26
+ error.message?.includes('The operation was aborted')) {
27
+ return
28
+ }
23
29
  captureException(error)
24
30
  }
25
31
 
26
32
  render(): React.ReactNode {
27
- if (this.state.hasError) {
33
+ if ((this as any).state.hasError) {
28
34
  return null
29
35
  }
30
36
 
31
- return this.props.children
37
+ return (this as any).props.children
32
38
  }
33
39
  }
@@ -0,0 +1,16 @@
1
+ import React from 'react'
2
+
3
+ export interface FormData {
4
+ // Define form data structure as needed
5
+ [key: string]: any
6
+ }
7
+
8
+ export interface StickerRequestFormProps {
9
+ // Define props as needed
10
+ onSubmit?: (data: FormData) => void
11
+ }
12
+
13
+ export const StickerRequestForm: React.FC<StickerRequestFormProps> = () => {
14
+ // Minimal component implementation
15
+ return null
16
+ }
@@ -10,6 +10,7 @@ type Props = {
10
10
  dim: boolean
11
11
  width: number
12
12
  overrideTheme?: ThemeNames // custom theme for previews
13
+ key?: React.Key
13
14
  }
14
15
 
15
16
  export function StructuredDiff({
@@ -66,43 +67,48 @@ function formatDiff(
66
67
  switch (type) {
67
68
  case 'add':
68
69
  return (
69
- <Text key={key}>
70
- <LineNumber
71
- i={lineIndex === 0 ? i : undefined}
72
- width={maxWidth}
73
- />
74
- <Text
75
- color={overrideTheme ? theme.text : undefined}
76
- backgroundColor={
77
- dim ? theme.diff.addedDimmed : theme.diff.added
78
- }
79
- dimColor={dim}
80
- >
81
- {line}
70
+ <React.Fragment key={key}>
71
+ <Text>
72
+ <LineNumber
73
+ i={lineIndex === 0 ? i : undefined}
74
+ width={maxWidth}
75
+ />
76
+ <Text
77
+ color={overrideTheme ? theme.text : undefined}
78
+ backgroundColor={
79
+ dim ? theme.diff.addedDimmed : theme.diff.added
80
+ }
81
+ dimColor={dim}
82
+ >
83
+ {line}
84
+ </Text>
82
85
  </Text>
83
- </Text>
86
+ </React.Fragment>
84
87
  )
85
88
  case 'remove':
86
89
  return (
87
- <Text key={key}>
88
- <LineNumber
89
- i={lineIndex === 0 ? i : undefined}
90
- width={maxWidth}
91
- />
92
- <Text
93
- color={overrideTheme ? theme.text : undefined}
94
- backgroundColor={
95
- dim ? theme.diff.removedDimmed : theme.diff.removed
96
- }
97
- dimColor={dim}
98
- >
99
- {line}
90
+ <React.Fragment key={key}>
91
+ <Text>
92
+ <LineNumber
93
+ i={lineIndex === 0 ? i : undefined}
94
+ width={maxWidth}
95
+ />
96
+ <Text
97
+ color={overrideTheme ? theme.text : undefined}
98
+ backgroundColor={
99
+ dim ? theme.diff.removedDimmed : theme.diff.removed
100
+ }
101
+ dimColor={dim}
102
+ >
103
+ {line}
104
+ </Text>
100
105
  </Text>
101
- </Text>
106
+ </React.Fragment>
102
107
  )
103
108
  case 'nochange':
104
109
  return (
105
- <Text key={key}>
110
+ <React.Fragment key={key}>
111
+ <Text>
106
112
  <LineNumber
107
113
  i={lineIndex === 0 ? i : undefined}
108
114
  width={maxWidth}
@@ -114,6 +120,7 @@ function formatDiff(
114
120
  {line}
115
121
  </Text>
116
122
  </Text>
123
+ </React.Fragment>
117
124
  )
118
125
  }
119
126
  })
@@ -106,6 +106,12 @@ export type Props = {
106
106
  * Whether to disable cursor movement for up/down arrow keys
107
107
  */
108
108
  readonly disableCursorMovementForUpDownKeys?: boolean
109
+
110
+ /**
111
+ * Optional callback to handle special key combinations before input processing
112
+ * Return true to prevent default handling
113
+ */
114
+ readonly onSpecialKey?: (input: string, key: Key) => boolean
109
115
 
110
116
  readonly cursorOffset: number
111
117
 
@@ -136,6 +142,7 @@ export default function TextInput({
136
142
  onPaste,
137
143
  isDimmed = false,
138
144
  disableCursorMovementForUpDownKeys = false,
145
+ onSpecialKey,
139
146
  cursorOffset,
140
147
  onChangeCursorOffset,
141
148
  }: Props) {
@@ -186,6 +193,12 @@ export default function TextInput({
186
193
  }
187
194
 
188
195
  const wrappedOnInput = (input: string, key: Key): void => {
196
+ // Check for special key combinations first
197
+ if (onSpecialKey && onSpecialKey(input, key)) {
198
+ // Special key was handled, don't process further
199
+ return
200
+ }
201
+
189
202
  // Special handling for backspace or delete
190
203
  if (
191
204
  key.backspace ||
@@ -0,0 +1,47 @@
1
+ import React from 'react'
2
+ import { Box, Text } from 'ink'
3
+ import type { TodoItem as TodoItemType } from '../utils/todoStorage'
4
+
5
+ export interface TodoItemProps {
6
+ todo: TodoItemType
7
+ children?: React.ReactNode
8
+ }
9
+
10
+ export const TodoItem: React.FC<TodoItemProps> = ({ todo, children }) => {
11
+ const statusIconMap = {
12
+ completed: '✅',
13
+ in_progress: '🔄',
14
+ pending: '⏸️',
15
+ }
16
+
17
+ const statusColorMap = {
18
+ completed: '#008000',
19
+ in_progress: '#FFA500',
20
+ pending: '#FFD700',
21
+ }
22
+
23
+ const priorityIconMap = {
24
+ high: '🔴',
25
+ medium: '🟡',
26
+ low: '🟢',
27
+ }
28
+
29
+ const icon = statusIconMap[todo.status]
30
+ const color = statusColorMap[todo.status]
31
+ const priorityIcon = todo.priority ? priorityIconMap[todo.priority] : ''
32
+
33
+ return (
34
+ <Box flexDirection="row" gap={1}>
35
+ <Text color={color}>{icon}</Text>
36
+ {priorityIcon && <Text>{priorityIcon}</Text>}
37
+ <Text
38
+ color={color}
39
+ strikethrough={todo.status === 'completed'}
40
+ bold={todo.status === 'in_progress'}
41
+ >
42
+ {todo.content}
43
+ </Text>
44
+ {children}
45
+ </Box>
46
+ )
47
+ }
@@ -1,7 +1,7 @@
1
1
  import React from 'react'
2
2
  import { Box, Text, useInput } from 'ink'
3
3
  import { getTheme } from '../utils/theme'
4
- import { Select } from '@inkjs/ui'
4
+ import { Select } from './CustomSelect/select'
5
5
  import {
6
6
  saveCurrentProjectConfig,
7
7
  getCurrentProjectConfig,