init claude-code

This commit is contained in:
2026-04-01 17:32:37 +02:00
commit 73b208c009
1902 changed files with 513237 additions and 0 deletions
File diff suppressed because one or more lines are too long
@@ -0,0 +1,42 @@
import type { ToolInput } from './useFilePermissionDialog.js'
export interface FileEdit {
old_string: string
new_string: string
replace_all?: boolean
}
export interface IDEDiffConfig {
filePath: string
edits?: FileEdit[]
editMode?: 'single' | 'multiple'
}
export interface IDEDiffChangeInput {
file_path: string
edits: FileEdit[]
}
export interface IDEDiffSupport<TInput extends ToolInput> {
getConfig(input: TInput): IDEDiffConfig
applyChanges(input: TInput, modifiedEdits: FileEdit[]): TInput
}
export function createSingleEditDiffConfig(
filePath: string,
oldString: string,
newString: string,
replaceAll?: boolean,
): IDEDiffConfig {
return {
filePath,
edits: [
{
old_string: oldString,
new_string: newString,
replace_all: replaceAll,
},
],
editMode: 'single',
}
}
File diff suppressed because one or more lines are too long
@@ -0,0 +1,212 @@
import { useCallback, useMemo, useState } from 'react'
import { useAppState } from 'src/state/AppState.js'
import { useKeybindings } from '../../../keybindings/useKeybinding.js'
import {
type AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS,
logEvent,
} from '../../../services/analytics/index.js'
import { sanitizeToolNameForAnalytics } from '../../../services/analytics/metadata.js'
import type { PermissionUpdate } from '../../../utils/permissions/PermissionUpdateSchema.js'
import type { CompletionType } from '../../../utils/unaryLogging.js'
import type { ToolUseConfirm } from '../PermissionRequest.js'
import {
type FileOperationType,
getFilePermissionOptions,
type PermissionOption,
type PermissionOptionWithLabel,
} from './permissionOptions.js'
import {
PERMISSION_HANDLERS,
type PermissionHandlerParams,
} from './usePermissionHandler.js'
export interface ToolInput {
[key: string]: unknown
}
export type UseFilePermissionDialogProps<T extends ToolInput> = {
filePath: string
completionType: CompletionType
languageName: string | Promise<string>
toolUseConfirm: ToolUseConfirm
onDone: () => void
onReject: () => void
parseInput: (input: unknown) => T
operationType?: FileOperationType
}
export type UseFilePermissionDialogResult<T> = {
options: PermissionOptionWithLabel[]
onChange: (option: PermissionOption, input: T, feedback?: string) => void
acceptFeedback: string
rejectFeedback: string
focusedOption: string
setFocusedOption: (option: string) => void
handleInputModeToggle: (value: string) => void
yesInputMode: boolean
noInputMode: boolean
}
/**
* Hook for handling file permission dialogs with common logic
*/
export function useFilePermissionDialog<T extends ToolInput>({
filePath,
completionType,
languageName,
toolUseConfirm,
onDone,
onReject,
parseInput,
operationType = 'write',
}: UseFilePermissionDialogProps<T>): UseFilePermissionDialogResult<T> {
const toolPermissionContext = useAppState(s => s.toolPermissionContext)
const [acceptFeedback, setAcceptFeedback] = useState('')
const [rejectFeedback, setRejectFeedback] = useState('')
const [focusedOption, setFocusedOption] = useState('yes')
const [yesInputMode, setYesInputMode] = useState(false)
const [noInputMode, setNoInputMode] = useState(false)
// Track whether user ever entered feedback mode (persists after collapse)
const [yesFeedbackModeEntered, setYesFeedbackModeEntered] = useState(false)
const [noFeedbackModeEntered, setNoFeedbackModeEntered] = useState(false)
// Generate options based on context
const options = useMemo(
() =>
getFilePermissionOptions({
filePath,
toolPermissionContext,
operationType,
onRejectFeedbackChange: setRejectFeedback,
onAcceptFeedbackChange: setAcceptFeedback,
yesInputMode,
noInputMode,
}),
[filePath, toolPermissionContext, operationType, yesInputMode, noInputMode],
)
// Handle option selection using shared handlers
const onChange = useCallback(
(option: PermissionOption, input: T, feedback?: string) => {
const params: PermissionHandlerParams = {
messageId: toolUseConfirm.assistantMessage.message.id,
path: filePath,
toolUseConfirm,
toolPermissionContext,
onDone,
onReject,
completionType,
languageName,
operationType,
}
// Override the input in toolUseConfirm to pass the parsed input
const originalOnAllow = toolUseConfirm.onAllow
toolUseConfirm.onAllow = (
_input: unknown,
permissionUpdates: PermissionUpdate[],
feedback?: string,
) => {
originalOnAllow(input, permissionUpdates, feedback)
}
const handler = PERMISSION_HANDLERS[option.type]
handler(params, {
feedback,
hasFeedback: !!feedback,
enteredFeedbackMode:
option.type === 'accept-once'
? yesFeedbackModeEntered
: noFeedbackModeEntered,
scope: option.type === 'accept-session' ? option.scope : undefined,
})
},
[
filePath,
completionType,
languageName,
toolUseConfirm,
toolPermissionContext,
onDone,
onReject,
operationType,
yesFeedbackModeEntered,
noFeedbackModeEntered,
],
)
// Handler for confirm:cycleMode - select accept-session option
const handleCycleMode = useCallback(() => {
const sessionOption = options.find(o => o.option.type === 'accept-session')
if (sessionOption) {
const parsedInput = parseInput(toolUseConfirm.input)
onChange(sessionOption.option, parsedInput)
}
}, [options, parseInput, toolUseConfirm.input, onChange])
// Register keyboard shortcut handler via keybindings system
useKeybindings(
{ 'confirm:cycleMode': handleCycleMode },
{ context: 'Confirmation' },
)
// Wrap setFocusedOption and reset input mode when navigating away
const handleFocusedOptionChange = useCallback(
(value: string) => {
// Reset input mode when navigating away, but only if no text typed
if (value !== 'yes' && yesInputMode && !acceptFeedback.trim()) {
setYesInputMode(false)
}
if (value !== 'no' && noInputMode && !rejectFeedback.trim()) {
setNoInputMode(false)
}
setFocusedOption(value)
},
[yesInputMode, noInputMode, acceptFeedback, rejectFeedback],
)
// Handle Tab key toggling input mode for Yes/No options
const handleInputModeToggle = useCallback(
(value: string) => {
const analyticsProps = {
toolName: sanitizeToolNameForAnalytics(
toolUseConfirm.tool.name,
) as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS,
isMcp: toolUseConfirm.tool.isMcp ?? false,
}
if (value === 'yes') {
if (yesInputMode) {
setYesInputMode(false)
logEvent('tengu_accept_feedback_mode_collapsed', analyticsProps)
} else {
setYesInputMode(true)
setYesFeedbackModeEntered(true)
logEvent('tengu_accept_feedback_mode_entered', analyticsProps)
}
} else if (value === 'no') {
if (noInputMode) {
setNoInputMode(false)
logEvent('tengu_reject_feedback_mode_collapsed', analyticsProps)
} else {
setNoInputMode(true)
setNoFeedbackModeEntered(true)
logEvent('tengu_reject_feedback_mode_entered', analyticsProps)
}
}
},
[yesInputMode, noInputMode, toolUseConfirm],
)
return {
options,
onChange,
acceptFeedback,
rejectFeedback,
focusedOption,
setFocusedOption: handleFocusedOptionChange,
handleInputModeToggle,
yesInputMode,
noInputMode,
}
}
@@ -0,0 +1,185 @@
import {
type AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS,
logEvent,
} from '../../../services/analytics/index.js'
import { sanitizeToolNameForAnalytics } from '../../../services/analytics/metadata.js'
import type { ToolPermissionContext } from '../../../Tool.js'
import {
CLAUDE_FOLDER_PERMISSION_PATTERN,
FILE_EDIT_TOOL_NAME,
GLOBAL_CLAUDE_FOLDER_PERMISSION_PATTERN,
} from '../../../tools/FileEditTool/constants.js'
import { env } from '../../../utils/env.js'
import { generateSuggestions } from '../../../utils/permissions/filesystem.js'
import type { PermissionUpdate } from '../../../utils/permissions/PermissionUpdateSchema.js'
import {
type CompletionType,
logUnaryEvent,
} from '../../../utils/unaryLogging.js'
import type { ToolUseConfirm } from '../PermissionRequest.js'
import type {
FileOperationType,
PermissionOption,
} from './permissionOptions.js'
function logPermissionEvent(
event: 'accept' | 'reject',
completionType: CompletionType,
languageName: string | Promise<string>,
messageId: string,
hasFeedback?: boolean,
): void {
void logUnaryEvent({
completion_type: completionType,
event,
metadata: {
language_name: languageName,
message_id: messageId,
platform: env.platform,
hasFeedback: hasFeedback ?? false,
},
})
}
export type PermissionHandlerParams = {
messageId: string
path: string | null
toolUseConfirm: ToolUseConfirm
toolPermissionContext: ToolPermissionContext
onDone: () => void
onReject: () => void
completionType: CompletionType
languageName: string | Promise<string>
operationType: FileOperationType
}
export type PermissionHandlerOptions = {
hasFeedback?: boolean
feedback?: string
enteredFeedbackMode?: boolean
scope?: 'claude-folder' | 'global-claude-folder'
}
function handleAcceptOnce(
params: PermissionHandlerParams,
options?: PermissionHandlerOptions,
): void {
const { messageId, toolUseConfirm, onDone, completionType, languageName } =
params
logPermissionEvent('accept', completionType, languageName, messageId)
// Log accept submission with feedback context
logEvent('tengu_accept_submitted', {
toolName: sanitizeToolNameForAnalytics(
toolUseConfirm.tool.name,
) as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS,
isMcp: toolUseConfirm.tool.isMcp ?? false,
has_instructions: !!options?.feedback,
instructions_length: options?.feedback?.length ?? 0,
entered_feedback_mode: options?.enteredFeedbackMode ?? false,
})
onDone()
toolUseConfirm.onAllow(toolUseConfirm.input, [], options?.feedback)
}
function handleAcceptSession(
params: PermissionHandlerParams,
options?: PermissionHandlerOptions,
): void {
const {
messageId,
path,
toolUseConfirm,
toolPermissionContext,
onDone,
completionType,
languageName,
operationType,
} = params
logPermissionEvent('accept', completionType, languageName, messageId)
// For claude-folder scope, grant session-level access to all .claude/ files
if (
options?.scope === 'claude-folder' ||
options?.scope === 'global-claude-folder'
) {
const pattern =
options.scope === 'global-claude-folder'
? GLOBAL_CLAUDE_FOLDER_PERMISSION_PATTERN
: CLAUDE_FOLDER_PERMISSION_PATTERN
const suggestions: PermissionUpdate[] = [
{
type: 'addRules',
rules: [
{
toolName: FILE_EDIT_TOOL_NAME,
ruleContent: pattern,
},
],
behavior: 'allow',
destination: 'session',
},
]
onDone()
toolUseConfirm.onAllow(toolUseConfirm.input, suggestions)
return
}
// Generate permission updates if path is provided
const suggestions = path
? generateSuggestions(path, operationType, toolPermissionContext)
: []
onDone()
// Pass permission updates directly to onAllow
toolUseConfirm.onAllow(toolUseConfirm.input, suggestions)
}
function handleReject(
params: PermissionHandlerParams,
options?: PermissionHandlerOptions,
): void {
const {
messageId,
toolUseConfirm,
onDone,
onReject,
completionType,
languageName,
} = params
logPermissionEvent(
'reject',
completionType,
languageName,
messageId,
options?.hasFeedback,
)
// Log reject submission with feedback context
logEvent('tengu_reject_submitted', {
toolName: sanitizeToolNameForAnalytics(
toolUseConfirm.tool.name,
) as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS,
isMcp: toolUseConfirm.tool.isMcp ?? false,
has_instructions: !!options?.feedback,
instructions_length: options?.feedback?.length ?? 0,
entered_feedback_mode: options?.enteredFeedbackMode ?? false,
})
onDone()
onReject()
toolUseConfirm.onReject(options?.feedback)
}
export const PERMISSION_HANDLERS: Record<
PermissionOption['type'],
(params: PermissionHandlerParams, options?: PermissionHandlerOptions) => void
> = {
'accept-once': handleAcceptOnce,
'accept-session': handleAcceptSession,
reject: handleReject,
}