@shareai-lab/kode 1.0.69 → 1.0.71
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.
- package/README.md +205 -72
- package/README.zh-CN.md +246 -0
- package/cli.js +62 -0
- package/package.json +45 -25
- package/scripts/postinstall.js +56 -0
- package/src/ProjectOnboarding.tsx +180 -0
- package/src/Tool.ts +53 -0
- package/src/commands/approvedTools.ts +53 -0
- package/src/commands/bug.tsx +20 -0
- package/src/commands/clear.ts +43 -0
- package/src/commands/compact.ts +120 -0
- package/src/commands/config.tsx +19 -0
- package/src/commands/cost.ts +18 -0
- package/src/commands/ctx_viz.ts +209 -0
- package/src/commands/doctor.ts +24 -0
- package/src/commands/help.tsx +19 -0
- package/src/commands/init.ts +37 -0
- package/src/commands/listen.ts +42 -0
- package/src/commands/login.tsx +51 -0
- package/src/commands/logout.tsx +40 -0
- package/src/commands/mcp.ts +41 -0
- package/src/commands/model.tsx +40 -0
- package/src/commands/modelstatus.tsx +20 -0
- package/src/commands/onboarding.tsx +34 -0
- package/src/commands/pr_comments.ts +59 -0
- package/src/commands/refreshCommands.ts +54 -0
- package/src/commands/release-notes.ts +34 -0
- package/src/commands/resume.tsx +30 -0
- package/src/commands/review.ts +49 -0
- package/src/commands/terminalSetup.ts +221 -0
- package/src/commands.ts +136 -0
- package/src/components/ApproveApiKey.tsx +93 -0
- package/src/components/AsciiLogo.tsx +13 -0
- package/src/components/AutoUpdater.tsx +148 -0
- package/src/components/Bug.tsx +367 -0
- package/src/components/Config.tsx +289 -0
- package/src/components/ConsoleOAuthFlow.tsx +326 -0
- package/src/components/Cost.tsx +23 -0
- package/src/components/CostThresholdDialog.tsx +46 -0
- package/src/components/CustomSelect/option-map.ts +42 -0
- package/src/components/CustomSelect/select-option.tsx +52 -0
- package/src/components/CustomSelect/select.tsx +143 -0
- package/src/components/CustomSelect/use-select-state.ts +414 -0
- package/src/components/CustomSelect/use-select.ts +35 -0
- package/src/components/FallbackToolUseRejectedMessage.tsx +15 -0
- package/src/components/FileEditToolUpdatedMessage.tsx +66 -0
- package/src/components/Help.tsx +215 -0
- package/src/components/HighlightedCode.tsx +33 -0
- package/src/components/InvalidConfigDialog.tsx +113 -0
- package/src/components/Link.tsx +32 -0
- package/src/components/LogSelector.tsx +86 -0
- package/src/components/Logo.tsx +145 -0
- package/src/components/MCPServerApprovalDialog.tsx +100 -0
- package/src/components/MCPServerDialogCopy.tsx +25 -0
- package/src/components/MCPServerMultiselectDialog.tsx +109 -0
- package/src/components/Message.tsx +219 -0
- package/src/components/MessageResponse.tsx +15 -0
- package/src/components/MessageSelector.tsx +211 -0
- package/src/components/ModeIndicator.tsx +88 -0
- package/src/components/ModelConfig.tsx +301 -0
- package/src/components/ModelListManager.tsx +223 -0
- package/src/components/ModelSelector.tsx +3208 -0
- package/src/components/ModelStatusDisplay.tsx +228 -0
- package/src/components/Onboarding.tsx +274 -0
- package/src/components/PressEnterToContinue.tsx +11 -0
- package/src/components/PromptInput.tsx +710 -0
- package/src/components/SentryErrorBoundary.ts +33 -0
- package/src/components/Spinner.tsx +129 -0
- package/src/components/StructuredDiff.tsx +184 -0
- package/src/components/TextInput.tsx +246 -0
- package/src/components/TokenWarning.tsx +31 -0
- package/src/components/ToolUseLoader.tsx +40 -0
- package/src/components/TrustDialog.tsx +106 -0
- package/src/components/binary-feedback/BinaryFeedback.tsx +63 -0
- package/src/components/binary-feedback/BinaryFeedbackOption.tsx +111 -0
- package/src/components/binary-feedback/BinaryFeedbackView.tsx +172 -0
- package/src/components/binary-feedback/utils.ts +220 -0
- package/src/components/messages/AssistantBashOutputMessage.tsx +22 -0
- package/src/components/messages/AssistantLocalCommandOutputMessage.tsx +45 -0
- package/src/components/messages/AssistantRedactedThinkingMessage.tsx +19 -0
- package/src/components/messages/AssistantTextMessage.tsx +144 -0
- package/src/components/messages/AssistantThinkingMessage.tsx +40 -0
- package/src/components/messages/AssistantToolUseMessage.tsx +123 -0
- package/src/components/messages/UserBashInputMessage.tsx +28 -0
- package/src/components/messages/UserCommandMessage.tsx +30 -0
- package/src/components/messages/UserKodingInputMessage.tsx +28 -0
- package/src/components/messages/UserPromptMessage.tsx +35 -0
- package/src/components/messages/UserTextMessage.tsx +39 -0
- package/src/components/messages/UserToolResultMessage/UserToolCanceledMessage.tsx +12 -0
- package/src/components/messages/UserToolResultMessage/UserToolErrorMessage.tsx +36 -0
- package/src/components/messages/UserToolResultMessage/UserToolRejectMessage.tsx +31 -0
- package/src/components/messages/UserToolResultMessage/UserToolResultMessage.tsx +57 -0
- package/src/components/messages/UserToolResultMessage/UserToolSuccessMessage.tsx +35 -0
- package/src/components/messages/UserToolResultMessage/utils.tsx +56 -0
- package/src/components/permissions/BashPermissionRequest/BashPermissionRequest.tsx +121 -0
- package/src/components/permissions/FallbackPermissionRequest.tsx +155 -0
- package/src/components/permissions/FileEditPermissionRequest/FileEditPermissionRequest.tsx +182 -0
- package/src/components/permissions/FileEditPermissionRequest/FileEditToolDiff.tsx +75 -0
- package/src/components/permissions/FileWritePermissionRequest/FileWritePermissionRequest.tsx +164 -0
- package/src/components/permissions/FileWritePermissionRequest/FileWriteToolDiff.tsx +81 -0
- package/src/components/permissions/FilesystemPermissionRequest/FilesystemPermissionRequest.tsx +242 -0
- package/src/components/permissions/PermissionRequest.tsx +103 -0
- package/src/components/permissions/PermissionRequestTitle.tsx +69 -0
- package/src/components/permissions/hooks.ts +44 -0
- package/src/components/permissions/toolUseOptions.ts +59 -0
- package/src/components/permissions/utils.ts +23 -0
- package/src/constants/betas.ts +5 -0
- package/src/constants/claude-asterisk-ascii-art.tsx +238 -0
- package/src/constants/figures.ts +4 -0
- package/src/constants/keys.ts +3 -0
- package/src/constants/macros.ts +6 -0
- package/src/constants/models.ts +935 -0
- package/src/constants/oauth.ts +18 -0
- package/src/constants/product.ts +17 -0
- package/src/constants/prompts.ts +177 -0
- package/src/constants/releaseNotes.ts +7 -0
- package/src/context/PermissionContext.tsx +149 -0
- package/src/context.ts +278 -0
- package/src/cost-tracker.ts +84 -0
- package/src/entrypoints/cli.tsx +1498 -0
- package/src/entrypoints/mcp.ts +176 -0
- package/src/history.ts +25 -0
- package/src/hooks/useApiKeyVerification.ts +59 -0
- package/src/hooks/useArrowKeyHistory.ts +55 -0
- package/src/hooks/useCanUseTool.ts +138 -0
- package/src/hooks/useCancelRequest.ts +39 -0
- package/src/hooks/useDoublePress.ts +42 -0
- package/src/hooks/useExitOnCtrlCD.ts +31 -0
- package/src/hooks/useInterval.ts +25 -0
- package/src/hooks/useLogMessages.ts +16 -0
- package/src/hooks/useLogStartupTime.ts +12 -0
- package/src/hooks/useNotifyAfterTimeout.ts +65 -0
- package/src/hooks/usePermissionRequestLogging.ts +44 -0
- package/src/hooks/useSlashCommandTypeahead.ts +137 -0
- package/src/hooks/useTerminalSize.ts +49 -0
- package/src/hooks/useTextInput.ts +315 -0
- package/src/messages.ts +37 -0
- package/src/permissions.ts +268 -0
- package/src/query.ts +704 -0
- package/src/screens/ConfigureNpmPrefix.tsx +197 -0
- package/src/screens/Doctor.tsx +219 -0
- package/src/screens/LogList.tsx +68 -0
- package/src/screens/REPL.tsx +792 -0
- package/src/screens/ResumeConversation.tsx +68 -0
- package/src/services/browserMocks.ts +66 -0
- package/src/services/claude.ts +1947 -0
- package/src/services/customCommands.ts +683 -0
- package/src/services/fileFreshness.ts +377 -0
- package/src/services/mcpClient.ts +564 -0
- package/src/services/mcpServerApproval.tsx +50 -0
- package/src/services/notifier.ts +40 -0
- package/src/services/oauth.ts +357 -0
- package/src/services/openai.ts +796 -0
- package/src/services/sentry.ts +3 -0
- package/src/services/statsig.ts +171 -0
- package/src/services/statsigStorage.ts +86 -0
- package/src/services/systemReminder.ts +406 -0
- package/src/services/vcr.ts +161 -0
- package/src/tools/ArchitectTool/ArchitectTool.tsx +122 -0
- package/src/tools/ArchitectTool/prompt.ts +15 -0
- package/src/tools/AskExpertModelTool/AskExpertModelTool.tsx +505 -0
- package/src/tools/BashTool/BashTool.tsx +270 -0
- package/src/tools/BashTool/BashToolResultMessage.tsx +38 -0
- package/src/tools/BashTool/OutputLine.tsx +48 -0
- package/src/tools/BashTool/prompt.ts +174 -0
- package/src/tools/BashTool/utils.ts +56 -0
- package/src/tools/FileEditTool/FileEditTool.tsx +316 -0
- package/src/tools/FileEditTool/prompt.ts +51 -0
- package/src/tools/FileEditTool/utils.ts +58 -0
- package/src/tools/FileReadTool/FileReadTool.tsx +371 -0
- package/src/tools/FileReadTool/prompt.ts +7 -0
- package/src/tools/FileWriteTool/FileWriteTool.tsx +297 -0
- package/src/tools/FileWriteTool/prompt.ts +10 -0
- package/src/tools/GlobTool/GlobTool.tsx +119 -0
- package/src/tools/GlobTool/prompt.ts +8 -0
- package/src/tools/GrepTool/GrepTool.tsx +147 -0
- package/src/tools/GrepTool/prompt.ts +11 -0
- package/src/tools/MCPTool/MCPTool.tsx +106 -0
- package/src/tools/MCPTool/prompt.ts +3 -0
- package/src/tools/MemoryReadTool/MemoryReadTool.tsx +127 -0
- package/src/tools/MemoryReadTool/prompt.ts +3 -0
- package/src/tools/MemoryWriteTool/MemoryWriteTool.tsx +89 -0
- package/src/tools/MemoryWriteTool/prompt.ts +3 -0
- package/src/tools/MultiEditTool/MultiEditTool.tsx +366 -0
- package/src/tools/MultiEditTool/prompt.ts +45 -0
- package/src/tools/NotebookEditTool/NotebookEditTool.tsx +298 -0
- package/src/tools/NotebookEditTool/prompt.ts +3 -0
- package/src/tools/NotebookReadTool/NotebookReadTool.tsx +266 -0
- package/src/tools/NotebookReadTool/prompt.ts +3 -0
- package/src/tools/StickerRequestTool/StickerRequestTool.tsx +93 -0
- package/src/tools/StickerRequestTool/prompt.ts +19 -0
- package/src/tools/TaskTool/TaskTool.tsx +382 -0
- package/src/tools/TaskTool/constants.ts +1 -0
- package/src/tools/TaskTool/prompt.ts +56 -0
- package/src/tools/ThinkTool/ThinkTool.tsx +56 -0
- package/src/tools/ThinkTool/prompt.ts +12 -0
- package/src/tools/TodoWriteTool/TodoWriteTool.tsx +289 -0
- package/src/tools/TodoWriteTool/prompt.ts +63 -0
- package/src/tools/lsTool/lsTool.tsx +269 -0
- package/src/tools/lsTool/prompt.ts +2 -0
- package/src/tools.ts +63 -0
- package/src/types/PermissionMode.ts +120 -0
- package/src/types/RequestContext.ts +72 -0
- package/src/utils/Cursor.ts +436 -0
- package/src/utils/PersistentShell.ts +373 -0
- package/src/utils/agentStorage.ts +97 -0
- package/src/utils/array.ts +3 -0
- package/src/utils/ask.tsx +98 -0
- package/src/utils/auth.ts +13 -0
- package/src/utils/autoCompactCore.ts +223 -0
- package/src/utils/autoUpdater.ts +318 -0
- package/src/utils/betas.ts +20 -0
- package/src/utils/browser.ts +14 -0
- package/src/utils/cleanup.ts +72 -0
- package/src/utils/commands.ts +261 -0
- package/src/utils/config.ts +771 -0
- package/src/utils/conversationRecovery.ts +54 -0
- package/src/utils/debugLogger.ts +1123 -0
- package/src/utils/diff.ts +42 -0
- package/src/utils/env.ts +57 -0
- package/src/utils/errors.ts +21 -0
- package/src/utils/exampleCommands.ts +108 -0
- package/src/utils/execFileNoThrow.ts +51 -0
- package/src/utils/expertChatStorage.ts +136 -0
- package/src/utils/file.ts +402 -0
- package/src/utils/fileRecoveryCore.ts +71 -0
- package/src/utils/format.tsx +44 -0
- package/src/utils/generators.ts +62 -0
- package/src/utils/git.ts +92 -0
- package/src/utils/globalLogger.ts +77 -0
- package/src/utils/http.ts +10 -0
- package/src/utils/imagePaste.ts +38 -0
- package/src/utils/json.ts +13 -0
- package/src/utils/log.ts +382 -0
- package/src/utils/markdown.ts +213 -0
- package/src/utils/messageContextManager.ts +289 -0
- package/src/utils/messages.tsx +938 -0
- package/src/utils/model.ts +836 -0
- package/src/utils/permissions/filesystem.ts +118 -0
- package/src/utils/ripgrep.ts +167 -0
- package/src/utils/sessionState.ts +49 -0
- package/src/utils/state.ts +25 -0
- package/src/utils/style.ts +29 -0
- package/src/utils/terminal.ts +49 -0
- package/src/utils/theme.ts +122 -0
- package/src/utils/thinking.ts +144 -0
- package/src/utils/todoStorage.ts +431 -0
- package/src/utils/tokens.ts +43 -0
- package/src/utils/toolExecutionController.ts +163 -0
- package/src/utils/unaryLogging.ts +26 -0
- package/src/utils/user.ts +37 -0
- package/src/utils/validate.ts +165 -0
- package/cli.mjs +0 -1803
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { type Option } from '@inkjs/ui'
|
|
2
|
+
import { optionHeaderKey, type OptionHeader } from './select'
|
|
3
|
+
|
|
4
|
+
type OptionMapItem = (Option | OptionHeader) & {
|
|
5
|
+
previous: OptionMapItem | undefined
|
|
6
|
+
next: OptionMapItem | undefined
|
|
7
|
+
index: number
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export default class OptionMap extends Map<string, OptionMapItem> {
|
|
11
|
+
readonly first: OptionMapItem | undefined
|
|
12
|
+
|
|
13
|
+
constructor(options: (Option | OptionHeader)[]) {
|
|
14
|
+
const items: Array<[string, OptionMapItem]> = []
|
|
15
|
+
let firstItem: OptionMapItem | undefined
|
|
16
|
+
let previous: OptionMapItem | undefined
|
|
17
|
+
let index = 0
|
|
18
|
+
|
|
19
|
+
for (const option of options) {
|
|
20
|
+
const item = {
|
|
21
|
+
...option,
|
|
22
|
+
previous,
|
|
23
|
+
next: undefined,
|
|
24
|
+
index,
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
if (previous) {
|
|
28
|
+
previous.next = item
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
firstItem ||= item
|
|
32
|
+
|
|
33
|
+
const key = 'value' in option ? option.value : optionHeaderKey(option)
|
|
34
|
+
items.push([key, item])
|
|
35
|
+
index++
|
|
36
|
+
previous = item
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
super(items)
|
|
40
|
+
this.first = firstItem
|
|
41
|
+
}
|
|
42
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import figures from 'figures'
|
|
2
|
+
import { Box, Text } from 'ink'
|
|
3
|
+
import React, { type ReactNode } from 'react'
|
|
4
|
+
import { type Theme } from './theme'
|
|
5
|
+
import { useComponentTheme } from '@inkjs/ui'
|
|
6
|
+
|
|
7
|
+
export type SelectOptionProps = {
|
|
8
|
+
/**
|
|
9
|
+
* Determines if option is focused.
|
|
10
|
+
*/
|
|
11
|
+
readonly isFocused: boolean
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Determines if option is selected.
|
|
15
|
+
*/
|
|
16
|
+
readonly isSelected: boolean
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Determines if pointer is shown when selected
|
|
20
|
+
*/
|
|
21
|
+
readonly smallPointer?: boolean
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Option label.
|
|
25
|
+
*/
|
|
26
|
+
readonly children: ReactNode
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function SelectOption({
|
|
30
|
+
isFocused,
|
|
31
|
+
isSelected,
|
|
32
|
+
smallPointer,
|
|
33
|
+
children,
|
|
34
|
+
}: SelectOptionProps) {
|
|
35
|
+
const { styles } = useComponentTheme<Theme>('Select')
|
|
36
|
+
|
|
37
|
+
return (
|
|
38
|
+
<Box {...styles.option({ isFocused })}>
|
|
39
|
+
{isFocused && (
|
|
40
|
+
<Text {...styles.focusIndicator()}>
|
|
41
|
+
{smallPointer ? figures.triangleDownSmall : figures.pointer}
|
|
42
|
+
</Text>
|
|
43
|
+
)}
|
|
44
|
+
|
|
45
|
+
<Text {...styles.label({ isFocused, isSelected })}>{children}</Text>
|
|
46
|
+
|
|
47
|
+
{isSelected && (
|
|
48
|
+
<Text {...styles.selectedIndicator()}>{figures.tick}</Text>
|
|
49
|
+
)}
|
|
50
|
+
</Box>
|
|
51
|
+
)
|
|
52
|
+
}
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import { Box, Text } from 'ink'
|
|
2
|
+
import React, { type ReactNode } from 'react'
|
|
3
|
+
import { SelectOption } from './select-option'
|
|
4
|
+
import { type Theme } from './theme'
|
|
5
|
+
import { useSelectState } from './use-select-state'
|
|
6
|
+
import { useSelect } from './use-select'
|
|
7
|
+
import { Option, useComponentTheme } from '@inkjs/ui'
|
|
8
|
+
|
|
9
|
+
export type OptionSubtree = {
|
|
10
|
+
/**
|
|
11
|
+
* Header to show above sub-options.
|
|
12
|
+
*/
|
|
13
|
+
readonly header?: string
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Options.
|
|
17
|
+
*/
|
|
18
|
+
readonly options: (Option | OptionSubtree)[]
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export type OptionHeader = {
|
|
22
|
+
readonly header: string
|
|
23
|
+
|
|
24
|
+
readonly optionValues: string[]
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export const optionHeaderKey = (optionHeader: OptionHeader): string =>
|
|
28
|
+
`HEADER-${optionHeader.optionValues.join(',')}`
|
|
29
|
+
|
|
30
|
+
export type SelectProps = {
|
|
31
|
+
/**
|
|
32
|
+
* When disabled, user input is ignored.
|
|
33
|
+
*
|
|
34
|
+
* @default false
|
|
35
|
+
*/
|
|
36
|
+
readonly isDisabled?: boolean
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Number of visible options.
|
|
40
|
+
*
|
|
41
|
+
* @default 5
|
|
42
|
+
*/
|
|
43
|
+
readonly visibleOptionCount?: number
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Highlight text in option labels.
|
|
47
|
+
*/
|
|
48
|
+
readonly highlightText?: string
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Options.
|
|
52
|
+
*/
|
|
53
|
+
readonly options: (Option | OptionSubtree)[]
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Default value.
|
|
57
|
+
*/
|
|
58
|
+
readonly defaultValue?: string
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Callback when selected option changes.
|
|
62
|
+
*/
|
|
63
|
+
readonly onChange?: (value: string) => void
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Callback when focused option changes.
|
|
67
|
+
*/
|
|
68
|
+
readonly onFocus?: (value: string) => void
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Value to focus
|
|
72
|
+
*/
|
|
73
|
+
readonly focusValue?: string
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export function Select({
|
|
77
|
+
isDisabled = false,
|
|
78
|
+
visibleOptionCount = 5,
|
|
79
|
+
highlightText,
|
|
80
|
+
options,
|
|
81
|
+
defaultValue,
|
|
82
|
+
onChange,
|
|
83
|
+
onFocus,
|
|
84
|
+
focusValue,
|
|
85
|
+
}: SelectProps) {
|
|
86
|
+
const state = useSelectState({
|
|
87
|
+
visibleOptionCount,
|
|
88
|
+
options,
|
|
89
|
+
defaultValue,
|
|
90
|
+
onChange,
|
|
91
|
+
onFocus,
|
|
92
|
+
focusValue,
|
|
93
|
+
})
|
|
94
|
+
|
|
95
|
+
useSelect({ isDisabled, state })
|
|
96
|
+
|
|
97
|
+
const { styles } = useComponentTheme<Theme>('Select')
|
|
98
|
+
|
|
99
|
+
return (
|
|
100
|
+
<Box {...styles.container()}>
|
|
101
|
+
{state.visibleOptions.map(option => {
|
|
102
|
+
const key = 'value' in option ? option.value : optionHeaderKey(option)
|
|
103
|
+
const isFocused =
|
|
104
|
+
!isDisabled &&
|
|
105
|
+
state.focusedValue !== undefined &&
|
|
106
|
+
('value' in option
|
|
107
|
+
? state.focusedValue === option.value
|
|
108
|
+
: option.optionValues.includes(state.focusedValue))
|
|
109
|
+
const isSelected =
|
|
110
|
+
!!state.value &&
|
|
111
|
+
('value' in option
|
|
112
|
+
? state.value === option.value
|
|
113
|
+
: option.optionValues.includes(state.value))
|
|
114
|
+
const smallPointer = 'header' in option
|
|
115
|
+
const labelText = 'label' in option ? option.label : option.header
|
|
116
|
+
let label: ReactNode = labelText
|
|
117
|
+
|
|
118
|
+
if (highlightText && labelText.includes(highlightText)) {
|
|
119
|
+
const index = labelText.indexOf(highlightText)
|
|
120
|
+
|
|
121
|
+
label = (
|
|
122
|
+
<>
|
|
123
|
+
{labelText.slice(0, index)}
|
|
124
|
+
<Text {...styles.highlightedText()}>{highlightText}</Text>
|
|
125
|
+
{labelText.slice(index + highlightText.length)}
|
|
126
|
+
</>
|
|
127
|
+
)
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
return (
|
|
131
|
+
<SelectOption
|
|
132
|
+
key={key}
|
|
133
|
+
isFocused={isFocused}
|
|
134
|
+
isSelected={isSelected}
|
|
135
|
+
smallPointer={smallPointer}
|
|
136
|
+
>
|
|
137
|
+
{label}
|
|
138
|
+
</SelectOption>
|
|
139
|
+
)
|
|
140
|
+
})}
|
|
141
|
+
</Box>
|
|
142
|
+
)
|
|
143
|
+
}
|
|
@@ -0,0 +1,414 @@
|
|
|
1
|
+
import { isDeepStrictEqual } from 'node:util'
|
|
2
|
+
import {
|
|
3
|
+
useReducer,
|
|
4
|
+
type Reducer,
|
|
5
|
+
useCallback,
|
|
6
|
+
useMemo,
|
|
7
|
+
useState,
|
|
8
|
+
useEffect,
|
|
9
|
+
} from 'react'
|
|
10
|
+
import OptionMap from './option-map'
|
|
11
|
+
import { Option } from '@inkjs/ui'
|
|
12
|
+
import type { OptionHeader, OptionSubtree } from './select'
|
|
13
|
+
|
|
14
|
+
type State = {
|
|
15
|
+
/**
|
|
16
|
+
* Map where key is option's value and value is option's index.
|
|
17
|
+
*/
|
|
18
|
+
optionMap: OptionMap
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Number of visible options.
|
|
22
|
+
*/
|
|
23
|
+
visibleOptionCount: number
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Value of the currently focused option.
|
|
27
|
+
*/
|
|
28
|
+
focusedValue: string | undefined
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Index of the first visible option.
|
|
32
|
+
*/
|
|
33
|
+
visibleFromIndex: number
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Index of the last visible option.
|
|
37
|
+
*/
|
|
38
|
+
visibleToIndex: number
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Value of the previously selected option.
|
|
42
|
+
*/
|
|
43
|
+
previousValue: string | undefined
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Value of the selected option.
|
|
47
|
+
*/
|
|
48
|
+
value: string | undefined
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
type Action =
|
|
52
|
+
| FocusNextOptionAction
|
|
53
|
+
| FocusPreviousOptionAction
|
|
54
|
+
| SelectFocusedOptionAction
|
|
55
|
+
| SetFocusAction
|
|
56
|
+
| ResetAction
|
|
57
|
+
|
|
58
|
+
type SetFocusAction = {
|
|
59
|
+
type: 'set-focus'
|
|
60
|
+
value: string
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
type FocusNextOptionAction = {
|
|
64
|
+
type: 'focus-next-option'
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
type FocusPreviousOptionAction = {
|
|
68
|
+
type: 'focus-previous-option'
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
type SelectFocusedOptionAction = {
|
|
72
|
+
type: 'select-focused-option'
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
type ResetAction = {
|
|
76
|
+
type: 'reset'
|
|
77
|
+
state: State
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const reducer: Reducer<State, Action> = (state, action) => {
|
|
81
|
+
switch (action.type) {
|
|
82
|
+
case 'focus-next-option': {
|
|
83
|
+
if (!state.focusedValue) {
|
|
84
|
+
return state
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const item = state.optionMap.get(state.focusedValue)
|
|
88
|
+
|
|
89
|
+
if (!item) {
|
|
90
|
+
return state
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
let next = item.next
|
|
94
|
+
while (next && !('value' in next)) {
|
|
95
|
+
// Skip headers
|
|
96
|
+
next = next.next
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if (!next) {
|
|
100
|
+
return state
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const needsToScroll = next.index >= state.visibleToIndex
|
|
104
|
+
|
|
105
|
+
if (!needsToScroll) {
|
|
106
|
+
return {
|
|
107
|
+
...state,
|
|
108
|
+
focusedValue: next.value,
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const nextVisibleToIndex = Math.min(
|
|
113
|
+
state.optionMap.size,
|
|
114
|
+
state.visibleToIndex + 1,
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
const nextVisibleFromIndex = nextVisibleToIndex - state.visibleOptionCount
|
|
118
|
+
|
|
119
|
+
return {
|
|
120
|
+
...state,
|
|
121
|
+
focusedValue: next.value,
|
|
122
|
+
visibleFromIndex: nextVisibleFromIndex,
|
|
123
|
+
visibleToIndex: nextVisibleToIndex,
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
case 'focus-previous-option': {
|
|
128
|
+
if (!state.focusedValue) {
|
|
129
|
+
return state
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const item = state.optionMap.get(state.focusedValue)
|
|
133
|
+
|
|
134
|
+
if (!item) {
|
|
135
|
+
return state
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
let previous = item.previous
|
|
139
|
+
while (previous && !('value' in previous)) {
|
|
140
|
+
// Skip headers
|
|
141
|
+
previous = previous.previous
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
if (!previous) {
|
|
145
|
+
return state
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const needsToScroll = previous.index <= state.visibleFromIndex
|
|
149
|
+
|
|
150
|
+
if (!needsToScroll) {
|
|
151
|
+
return {
|
|
152
|
+
...state,
|
|
153
|
+
focusedValue: previous.value,
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const nextVisibleFromIndex = Math.max(0, state.visibleFromIndex - 1)
|
|
158
|
+
|
|
159
|
+
const nextVisibleToIndex = nextVisibleFromIndex + state.visibleOptionCount
|
|
160
|
+
|
|
161
|
+
return {
|
|
162
|
+
...state,
|
|
163
|
+
focusedValue: previous.value,
|
|
164
|
+
visibleFromIndex: nextVisibleFromIndex,
|
|
165
|
+
visibleToIndex: nextVisibleToIndex,
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
case 'select-focused-option': {
|
|
170
|
+
return {
|
|
171
|
+
...state,
|
|
172
|
+
previousValue: state.value,
|
|
173
|
+
value: state.focusedValue,
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
case 'reset': {
|
|
178
|
+
return action.state
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
case 'set-focus': {
|
|
182
|
+
return {
|
|
183
|
+
...state,
|
|
184
|
+
focusedValue: action.value,
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
export type UseSelectStateProps = {
|
|
191
|
+
/**
|
|
192
|
+
* Number of items to display.
|
|
193
|
+
*
|
|
194
|
+
* @default 5
|
|
195
|
+
*/
|
|
196
|
+
visibleOptionCount?: number
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Options.
|
|
200
|
+
*/
|
|
201
|
+
options: (Option | OptionSubtree)[]
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Initially selected option's value.
|
|
205
|
+
*/
|
|
206
|
+
defaultValue?: string
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Callback for selecting an option.
|
|
210
|
+
*/
|
|
211
|
+
onChange?: (value: string) => void
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Callback for focusing an option.
|
|
215
|
+
*/
|
|
216
|
+
onFocus?: (value: string) => void
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* Value to focus
|
|
220
|
+
*/
|
|
221
|
+
focusValue?: string
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
export type SelectState = Pick<
|
|
225
|
+
State,
|
|
226
|
+
'focusedValue' | 'visibleFromIndex' | 'visibleToIndex' | 'value'
|
|
227
|
+
> & {
|
|
228
|
+
/**
|
|
229
|
+
* Visible options.
|
|
230
|
+
*/
|
|
231
|
+
visibleOptions: Array<(Option | OptionHeader) & { index: number }>
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* Focus next option and scroll the list down, if needed.
|
|
235
|
+
*/
|
|
236
|
+
focusNextOption: () => void
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* Focus previous option and scroll the list up, if needed.
|
|
240
|
+
*/
|
|
241
|
+
focusPreviousOption: () => void
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* Select currently focused option.
|
|
245
|
+
*/
|
|
246
|
+
selectFocusedOption: () => void
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
const flattenOptions = (
|
|
250
|
+
options: (Option | OptionSubtree)[],
|
|
251
|
+
): (Option | OptionHeader)[] =>
|
|
252
|
+
options.flatMap(option => {
|
|
253
|
+
if ('options' in option) {
|
|
254
|
+
const flatSubtree = flattenOptions(option.options)
|
|
255
|
+
const optionValues = flatSubtree.flatMap(o =>
|
|
256
|
+
'value' in o ? o.value : [],
|
|
257
|
+
)
|
|
258
|
+
const header =
|
|
259
|
+
option.header !== undefined
|
|
260
|
+
? [{ header: option.header, optionValues }]
|
|
261
|
+
: []
|
|
262
|
+
|
|
263
|
+
return [...header, ...flatSubtree]
|
|
264
|
+
}
|
|
265
|
+
return option
|
|
266
|
+
})
|
|
267
|
+
|
|
268
|
+
const createDefaultState = ({
|
|
269
|
+
visibleOptionCount: customVisibleOptionCount,
|
|
270
|
+
defaultValue,
|
|
271
|
+
options,
|
|
272
|
+
}: Pick<
|
|
273
|
+
UseSelectStateProps,
|
|
274
|
+
'visibleOptionCount' | 'defaultValue' | 'options'
|
|
275
|
+
>) => {
|
|
276
|
+
const flatOptions = flattenOptions(options)
|
|
277
|
+
|
|
278
|
+
const visibleOptionCount =
|
|
279
|
+
typeof customVisibleOptionCount === 'number'
|
|
280
|
+
? Math.min(customVisibleOptionCount, flatOptions.length)
|
|
281
|
+
: flatOptions.length
|
|
282
|
+
|
|
283
|
+
const optionMap = new OptionMap(flatOptions)
|
|
284
|
+
const firstOption = optionMap.first
|
|
285
|
+
|
|
286
|
+
// Use defaultValue for focusedValue if it exists and is valid, otherwise use first option
|
|
287
|
+
let focusedValue: string | undefined
|
|
288
|
+
if (defaultValue && optionMap.get(defaultValue)) {
|
|
289
|
+
focusedValue = defaultValue
|
|
290
|
+
} else {
|
|
291
|
+
focusedValue =
|
|
292
|
+
firstOption && 'value' in firstOption ? firstOption.value : undefined
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
// Calculate visible range based on focused value
|
|
296
|
+
let visibleFromIndex = 0
|
|
297
|
+
let visibleToIndex = visibleOptionCount
|
|
298
|
+
|
|
299
|
+
if (focusedValue && optionMap.get(focusedValue)) {
|
|
300
|
+
const focusedIndex = optionMap.get(focusedValue)!.index
|
|
301
|
+
// Center the focused option in the visible area if possible
|
|
302
|
+
const halfVisible = Math.floor(visibleOptionCount / 2)
|
|
303
|
+
visibleFromIndex = Math.max(0, focusedIndex - halfVisible)
|
|
304
|
+
visibleToIndex = Math.min(
|
|
305
|
+
flatOptions.length,
|
|
306
|
+
visibleFromIndex + visibleOptionCount,
|
|
307
|
+
)
|
|
308
|
+
|
|
309
|
+
// Adjust if we can't show enough items at the end
|
|
310
|
+
if (visibleToIndex - visibleFromIndex < visibleOptionCount) {
|
|
311
|
+
visibleFromIndex = Math.max(0, visibleToIndex - visibleOptionCount)
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
return {
|
|
316
|
+
optionMap,
|
|
317
|
+
visibleOptionCount,
|
|
318
|
+
focusedValue,
|
|
319
|
+
visibleFromIndex,
|
|
320
|
+
visibleToIndex,
|
|
321
|
+
previousValue: defaultValue,
|
|
322
|
+
value: defaultValue,
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
export const useSelectState = ({
|
|
327
|
+
visibleOptionCount = 5,
|
|
328
|
+
options,
|
|
329
|
+
defaultValue,
|
|
330
|
+
onChange,
|
|
331
|
+
onFocus,
|
|
332
|
+
focusValue,
|
|
333
|
+
}: UseSelectStateProps) => {
|
|
334
|
+
const flatOptions = flattenOptions(options)
|
|
335
|
+
|
|
336
|
+
const [state, dispatch] = useReducer(
|
|
337
|
+
reducer,
|
|
338
|
+
{ visibleOptionCount, defaultValue, options },
|
|
339
|
+
createDefaultState,
|
|
340
|
+
)
|
|
341
|
+
|
|
342
|
+
const [lastOptions, setLastOptions] = useState(flatOptions)
|
|
343
|
+
|
|
344
|
+
if (
|
|
345
|
+
flatOptions !== lastOptions &&
|
|
346
|
+
!isDeepStrictEqual(flatOptions, lastOptions)
|
|
347
|
+
) {
|
|
348
|
+
dispatch({
|
|
349
|
+
type: 'reset',
|
|
350
|
+
state: createDefaultState({ visibleOptionCount, defaultValue, options }),
|
|
351
|
+
})
|
|
352
|
+
|
|
353
|
+
setLastOptions(flatOptions)
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
const focusNextOption = useCallback(() => {
|
|
357
|
+
dispatch({
|
|
358
|
+
type: 'focus-next-option',
|
|
359
|
+
})
|
|
360
|
+
}, [])
|
|
361
|
+
|
|
362
|
+
const focusPreviousOption = useCallback(() => {
|
|
363
|
+
dispatch({
|
|
364
|
+
type: 'focus-previous-option',
|
|
365
|
+
})
|
|
366
|
+
}, [])
|
|
367
|
+
|
|
368
|
+
const selectFocusedOption = useCallback(() => {
|
|
369
|
+
dispatch({
|
|
370
|
+
type: 'select-focused-option',
|
|
371
|
+
})
|
|
372
|
+
}, [])
|
|
373
|
+
|
|
374
|
+
const visibleOptions = useMemo(() => {
|
|
375
|
+
return flatOptions
|
|
376
|
+
.map((option, index) => ({
|
|
377
|
+
...option,
|
|
378
|
+
index,
|
|
379
|
+
}))
|
|
380
|
+
.slice(state.visibleFromIndex, state.visibleToIndex)
|
|
381
|
+
}, [flatOptions, state.visibleFromIndex, state.visibleToIndex])
|
|
382
|
+
|
|
383
|
+
useEffect(() => {
|
|
384
|
+
if (state.value && state.previousValue !== state.value) {
|
|
385
|
+
onChange?.(state.value)
|
|
386
|
+
}
|
|
387
|
+
}, [state.previousValue, state.value, options, onChange])
|
|
388
|
+
|
|
389
|
+
useEffect(() => {
|
|
390
|
+
if (state.focusedValue) {
|
|
391
|
+
onFocus?.(state.focusedValue)
|
|
392
|
+
}
|
|
393
|
+
}, [state.focusedValue, onFocus])
|
|
394
|
+
|
|
395
|
+
useEffect(() => {
|
|
396
|
+
if (focusValue) {
|
|
397
|
+
dispatch({
|
|
398
|
+
type: 'set-focus',
|
|
399
|
+
value: focusValue,
|
|
400
|
+
})
|
|
401
|
+
}
|
|
402
|
+
}, [focusValue])
|
|
403
|
+
|
|
404
|
+
return {
|
|
405
|
+
focusedValue: state.focusedValue,
|
|
406
|
+
visibleFromIndex: state.visibleFromIndex,
|
|
407
|
+
visibleToIndex: state.visibleToIndex,
|
|
408
|
+
value: state.value,
|
|
409
|
+
visibleOptions,
|
|
410
|
+
focusNextOption,
|
|
411
|
+
focusPreviousOption,
|
|
412
|
+
selectFocusedOption,
|
|
413
|
+
}
|
|
414
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { useInput } from 'ink'
|
|
2
|
+
import { type SelectState } from './use-select-state'
|
|
3
|
+
|
|
4
|
+
export type UseSelectProps = {
|
|
5
|
+
/**
|
|
6
|
+
* When disabled, user input is ignored.
|
|
7
|
+
*
|
|
8
|
+
* @default false
|
|
9
|
+
*/
|
|
10
|
+
isDisabled?: boolean
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Select state.
|
|
14
|
+
*/
|
|
15
|
+
state: SelectState
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export const useSelect = ({ isDisabled = false, state }: UseSelectProps) => {
|
|
19
|
+
useInput(
|
|
20
|
+
(_input, key) => {
|
|
21
|
+
if (key.downArrow) {
|
|
22
|
+
state.focusNextOption()
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
if (key.upArrow) {
|
|
26
|
+
state.focusPreviousOption()
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
if (key.return) {
|
|
30
|
+
state.selectFocusedOption()
|
|
31
|
+
}
|
|
32
|
+
},
|
|
33
|
+
{ isActive: !isDisabled },
|
|
34
|
+
)
|
|
35
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import * as React from 'react'
|
|
2
|
+
import { getTheme } from '../utils/theme'
|
|
3
|
+
import { Text } from 'ink'
|
|
4
|
+
import { PRODUCT_NAME } from '../constants/product'
|
|
5
|
+
|
|
6
|
+
export function FallbackToolUseRejectedMessage(): React.ReactNode {
|
|
7
|
+
return (
|
|
8
|
+
<Text>
|
|
9
|
+
⎿
|
|
10
|
+
<Text color={getTheme().error}>
|
|
11
|
+
No (tell {PRODUCT_NAME} what to do differently)
|
|
12
|
+
</Text>
|
|
13
|
+
</Text>
|
|
14
|
+
)
|
|
15
|
+
}
|