clean code

This commit is contained in:
CrazyBoyM 2025-09-14 19:28:20 +08:00
parent 78b49355cd
commit d4abb2abee
26 changed files with 23 additions and 1503 deletions

View File

@ -123,9 +123,9 @@ kd
### Non-Interactive Mode
Get a quick response:
```bash
kode -p "explain this function" main.js
kode -p "explain this function" path/to/file.js
# or
kwa -p "explain this function" main.js
kwa -p "explain this function" path/to/file.js
```
### Using the @ Mention System

View File

@ -76,9 +76,9 @@ kd
### 非交互模式
获取快速响应:
```bash
kode -p "解释这个函数" main.js
kode -p "解释这个函数" 路径/到/文件.js
# 或
kwa -p "解释这个函数" main.js
kwa -p "解释这个函数" 路径/到/文件.js
```
### Docker 使用说明

View File

@ -1,62 +0,0 @@
const pkg = require('./package.json');
const fs = require('fs');
const declared = new Set(Object.keys(pkg.dependencies || {}));
const used = [
'@anthropic-ai/bedrock-sdk',
'@anthropic-ai/sdk',
'@anthropic-ai/vertex-sdk',
'@commander-js/extra-typings',
'@inkjs/ui',
'@modelcontextprotocol/sdk',
'@statsig/js-client',
'@statsig/client-core',
'ansi-escapes',
'chalk',
'cli-highlight',
'cli-table3',
'debug',
'diff',
'env-paths',
'figures',
'glob',
'gray-matter',
'ink',
'ink-link',
'ink-text-input',
'lodash-es',
'lru-cache',
'marked',
'nanoid',
'node-fetch',
'node-html-parser',
'openai',
'react',
'semver',
'shell-quote',
'spawn-rx',
'turndown',
'undici',
'wrap-ansi',
'zod',
'zod-to-json-schema'
];
const builtins = new Set(['child_process','crypto','fs','fs/promises','http','os','path','process','tty','url','util','module','node:fs','node:os','node:path','node:url','node:util']);
const missing = [];
used.forEach(pkg => {
if (\!builtins.has(pkg) && \!declared.has(pkg)) {
missing.push(pkg);
}
});
console.log('=== MISSING DEPENDENCIES ===');
if (missing.length === 0) {
console.log('No missing dependencies found');
} else {
missing.forEach(pkg => console.log(pkg));
}
console.log('=== DECLARED DEPENDENCIES ===');
Array.from(declared).sort().forEach(pkg => console.log(pkg));

View File

@ -1,152 +0,0 @@
#!/usr/bin/env bun
import { existsSync, rmSync, writeFileSync, chmodSync } from 'fs';
async function build() {
console.log('🚀 Building Kode CLI...\n');
try {
// Clean previous builds
console.log('🧹 Cleaning previous builds...');
['cli.js', '.npmrc'].forEach(file => {
if (existsSync(file)) {
rmSync(file, { recursive: true, force: true });
}
});
// Ensure dist folder exists
if (!existsSync('dist')) {
// @ts-ignore
await import('node:fs/promises').then(m => m.mkdir('dist', { recursive: true }))
}
// Create the CLI wrapper (prefer dist when available, then bun, then node+tsx)
const wrapper = `#!/usr/bin/env node
const { spawn } = require('child_process');
const path = require('path');
const fs = require('fs');
// Prefer dist (pure Node) if available, otherwise try bun, then node+tsx
const args = process.argv.slice(2);
const cliPath = path.join(__dirname, 'src', 'entrypoints', 'cli.tsx');
const distEntrypoint = path.join(__dirname, 'dist', 'entrypoints', 'cli.js');
// 1) Run compiled dist with Node if present (Windows-friendly, no bun/tsx needed)
try {
if (fs.existsSync(distEntrypoint)) {
const child = spawn(process.execPath, [distEntrypoint, ...args], {
stdio: 'inherit',
env: {
...process.env,
YOGA_WASM_PATH: path.join(__dirname, 'yoga.wasm'),
},
});
child.on('exit', code => process.exit(code || 0));
child.on('error', () => runWithBunOrTsx());
return;
}
} catch (_) {
// fallthrough to bun/tsx
}
// 2) Otherwise, try bun first, then fall back to node+tsx
runWithBunOrTsx();
function runWithBunOrTsx() {
// Try bun first
try {
const { execSync } = require('child_process');
execSync('bun --version', { stdio: 'ignore' });
const child = spawn('bun', ['run', cliPath, ...args], {
stdio: 'inherit',
env: {
...process.env,
YOGA_WASM_PATH: path.join(__dirname, 'yoga.wasm'),
},
});
child.on('exit', code => process.exit(code || 0));
child.on('error', () => runWithNodeTsx());
return;
} catch {
// ignore and try tsx path
}
runWithNodeTsx();
}
function runWithNodeTsx() {
// Use local tsx installation; if missing, try PATH-resolved tsx
const binDir = path.join(__dirname, 'node_modules', '.bin')
const tsxPath = process.platform === 'win32'
? path.join(binDir, 'tsx.cmd')
: path.join(binDir, 'tsx')
const runPathTsx = () => {
const child2 = spawn('tsx', [cliPath, ...args], {
stdio: 'inherit',
shell: process.platform === 'win32',
env: {
...process.env,
YOGA_WASM_PATH: path.join(__dirname, 'yoga.wasm'),
TSX_TSCONFIG_PATH: process.platform === 'win32' ? 'noop' : undefined
},
})
child2.on('error', () => {
console.error('\\nError: tsx is required but not found.')
console.error('Please install tsx globally: npm install -g tsx')
process.exit(1)
})
child2.on('exit', (code2) => process.exit(code2 || 0))
}
const child = spawn(tsxPath, [cliPath, ...args], {
stdio: 'inherit',
shell: process.platform === 'win32',
env: {
...process.env,
YOGA_WASM_PATH: path.join(__dirname, 'yoga.wasm'),
TSX_TSCONFIG_PATH: process.platform === 'win32' ? 'noop' : undefined
},
})
child.on('error', () => runPathTsx())
child.on('exit', (code) => {
if (code && code !== 0) return runPathTsx()
process.exit(code || 0)
})
}
`;
writeFileSync('cli.js', wrapper);
chmodSync('cli.js', 0o755);
// Create a slim dist/index.js that imports the real entrypoint
const distIndex = `#!/usr/bin/env node
import './entrypoints/cli.js';
`;
writeFileSync('dist/index.js', distIndex);
chmodSync('dist/index.js', 0o755);
// Create .npmrc
const npmrc = `# Ensure tsx is installed
auto-install-peers=true
`;
writeFileSync('.npmrc', npmrc);
console.log('✅ Build completed successfully!\n');
console.log('📋 Generated files:');
console.log(' - cli.js (Smart CLI wrapper)');
console.log(' - .npmrc (NPM configuration)');
console.log('\n🚀 Ready to publish!');
} catch (error) {
console.error('❌ Build failed:', error);
process.exit(1);
}
}
// Run build if called directly
if (import.meta.main) {
build();
}
export { build };

View File

@ -1,57 +0,0 @@
#!/usr/bin/env node
const fs = require('fs');
const { execSync } = require('child_process');
const path = require('path');
async function publish() {
console.log('🚀 Starting publish workaround...\n');
const packagePath = path.join(__dirname, '..', 'package.json');
try {
// Read package.json
const packageJson = JSON.parse(fs.readFileSync(packagePath, 'utf8'));
const originalBundled = packageJson.bundledDependencies;
// Remove bundledDependencies temporarily
console.log('📦 Removing bundledDependencies temporarily...');
delete packageJson.bundledDependencies;
fs.writeFileSync(packagePath, JSON.stringify(packageJson, null, 2));
// Set proxy and publish
console.log('🌍 Setting proxy and publishing...');
process.env.https_proxy = 'http://127.0.0.1:7890';
process.env.http_proxy = 'http://127.0.0.1:7890';
process.env.all_proxy = 'socks5://127.0.0.1:7890';
process.env.SKIP_BUNDLED_CHECK = 'true';
execSync('npm publish --access public', {
stdio: 'inherit',
env: process.env
});
// Restore bundledDependencies
console.log('✅ Restoring bundledDependencies...');
packageJson.bundledDependencies = originalBundled;
fs.writeFileSync(packagePath, JSON.stringify(packageJson, null, 2));
console.log('🎉 Published successfully!');
} catch (error) {
console.error('❌ Publish failed:', error.message);
// Restore package.json on error
try {
const packageJson = JSON.parse(fs.readFileSync(packagePath, 'utf8'));
packageJson.bundledDependencies = ["tsx"];
fs.writeFileSync(packagePath, JSON.stringify(packageJson, null, 2));
} catch (e) {
console.error('Failed to restore package.json');
}
process.exit(1);
}
}
publish();

View File

@ -1,93 +0,0 @@
import React from 'react'
import { Box, Text } from 'ink'
import { getGlobalConfig, saveGlobalConfig } from '../utils/config'
import { getTheme } from '../utils/theme'
import { Select } from './CustomSelect/select'
import { useExitOnCtrlCD } from '../hooks/useExitOnCtrlCD'
import chalk from 'chalk'
type Props = {
customApiKeyTruncated: string
onDone(): void
}
export function ApproveApiKey({
customApiKeyTruncated,
onDone,
}: Props): React.ReactNode {
const theme = getTheme()
function onChange(value: 'yes' | 'no') {
const config = getGlobalConfig()
switch (value) {
case 'yes': {
saveGlobalConfig({
...config,
customApiKeyResponses: {
...config.customApiKeyResponses,
approved: [
...(config.customApiKeyResponses?.approved ?? []),
customApiKeyTruncated,
],
},
})
onDone()
break
}
case 'no': {
saveGlobalConfig({
...config,
customApiKeyResponses: {
...config.customApiKeyResponses,
rejected: [
...(config.customApiKeyResponses?.rejected ?? []),
customApiKeyTruncated,
],
},
})
onDone()
break
}
}
}
const exitState = useExitOnCtrlCD(() => process.exit(0))
return (
<>
<Box
flexDirection="column"
gap={1}
padding={1}
borderStyle="round"
borderColor={theme.warning}
>
<Text bold color={theme.warning}>
Detected a custom API key in your environment
</Text>
<Text>
Your environment sets{' '}
<Text color={theme.warning}>ANTHROPIC_API_KEY</Text>:{' '}
<Text bold>sk-ant-...{customApiKeyTruncated}</Text>
</Text>
<Text>Do you want to use this API key?</Text>
<Select
options={[
{ label: `No (${chalk.bold('recommended')})`, value: 'no' },
{ label: 'Yes', value: 'yes' },
]}
onChange={value => onChange(value as 'yes' | 'no')}
/>
</Box>
<Box marginLeft={3}>
<Text dimColor>
{exitState.pending ? (
<>Press {exitState.keyName} again to exit</>
) : (
<>Enter to confirm</>
)}
</Text>
</Box>
</>
)
}

View File

@ -1,6 +1,5 @@
import { Box, Text, useInput } from 'ink'
import { sample } from 'lodash-es'
import { getExampleCommands } from '../utils/exampleCommands'
import * as React from 'react'
import { type Message } from '../query'
import { processUserInput } from '../utils/messages'

View File

@ -1,16 +0,0 @@
import React from 'react'
export interface FormData {
// Define form data structure as needed
[key: string]: any
}
export interface StickerRequestFormProps {
// Define props as needed
onSubmit?: (data: FormData) => void
}
export const StickerRequestForm: React.FC<StickerRequestFormProps> = () => {
// Minimal component implementation
return null
}

View File

@ -17,8 +17,7 @@ type BinaryFeedbackConfig = {
sampleFrequency: number
}
async function getBinaryFeedbackStatsigConfig(): Promise<BinaryFeedbackConfig> {
async function getBinaryFeedbackConfig(): Promise<BinaryFeedbackConfig> {
return { sampleFrequency: 0 }
}
@ -30,32 +29,7 @@ function getMessageBlockSequence(m: AssistantMessage) {
})
}
export async function logBinaryFeedbackEvent(
m1: AssistantMessage,
m2: AssistantMessage,
choice: BinaryFeedbackChoice,
): Promise<void> {
const modelA = m1.message.model
const modelB = m2.message.model
const gitState = await getGitState()
}
export async function logBinaryFeedbackSamplingDecision(
decision: boolean,
reason?: string,
): Promise<void> {
}
export async function logBinaryFeedbackDisplayDecision(
decision: boolean,
m1: AssistantMessage,
m2: AssistantMessage,
reason?: string,
): Promise<void> {
}
// Logging removed to minimize runtime surface area; behavior unaffected
function textContentBlocksEqual(cb1: TextBlock, cb2: TextBlock): boolean {
return cb1.text === cb2.text
@ -89,34 +63,27 @@ function allContentBlocksEqual(
export async function shouldUseBinaryFeedback(): Promise<boolean> {
if (process.env.DISABLE_BINARY_FEEDBACK) {
logBinaryFeedbackSamplingDecision(false, 'disabled_by_env_var')
return false
}
if (process.env.FORCE_BINARY_FEEDBACK) {
logBinaryFeedbackSamplingDecision(true, 'forced_by_env_var')
return true
}
if (process.env.USER_TYPE !== 'ant') {
logBinaryFeedbackSamplingDecision(false, 'not_ant')
return false
}
if (process.env.NODE_ENV === 'test') {
// Binary feedback breaks a couple tests related to checking for permission,
// so we have to disable it in tests at the risk of hiding bugs
logBinaryFeedbackSamplingDecision(false, 'test')
return false
}
const config = await getBinaryFeedbackStatsigConfig()
const config = await getBinaryFeedbackConfig()
if (config.sampleFrequency === 0) {
logBinaryFeedbackSamplingDecision(false, 'top_level_frequency_zero')
return false
}
if (Math.random() > config.sampleFrequency) {
logBinaryFeedbackSamplingDecision(false, 'top_level_frequency_rng')
return false
}
logBinaryFeedbackSamplingDecision(true)
return true
}
@ -124,9 +91,8 @@ export function messagePairValidForBinaryFeedback(
m1: AssistantMessage,
m2: AssistantMessage,
): boolean {
const logPass = () => logBinaryFeedbackDisplayDecision(true, m1, m2)
const logFail = (reason: string) =>
logBinaryFeedbackDisplayDecision(false, m1, m2, reason)
const logPass = () => {}
const logFail = (_reason: string) => {}
// Ignore thinking blocks, on the assumption that users don't find them very relevant
// compared to other content types
@ -185,3 +151,9 @@ export function getBinaryFeedbackResultForChoice(
return { message: null, shouldSkipPermissionCheck: false }
}
}
// Keep a minimal exported stub to satisfy imports without side effects
export async function logBinaryFeedbackEvent(
_m1: AssistantMessage,
_m2: AssistantMessage,
_choice: BinaryFeedbackChoice,
): Promise<void> {}

View File

@ -10,8 +10,7 @@ type UnaryEventType = {
}
/**
* Logs permission request events using Statsig and unary logging.
* Handles both the Statsig event and the unary event logging.
* Logs permission request events via unary logging.
* Can handle either a string or Promise<string> for language_name.
*/
export function usePermissionRequestLogging(

View File

@ -61,7 +61,6 @@ import { dateToFilename, logError, parseLogFilename } from '../utils/log'
import { initDebugLogger } from '../utils/debugLogger'
import { Onboarding } from '../components/Onboarding'
import { Doctor } from '../screens/Doctor'
import { ApproveApiKey } from '../components/ApproveApiKey'
import { TrustDialog } from '../components/TrustDialog'
import { checkHasTrustDialogAccepted, McpServerConfig } from '../utils/config'
import { isDefaultSlowAndCapableModel } from '../utils/model'
@ -90,14 +89,12 @@ import {
} from '../services/mcpClient'
import { handleMcprcServerApprovals } from '../services/mcpServerApproval'
import { getExampleCommands } from '../utils/exampleCommands'
import { cursorShow } from 'ansi-escapes'
import { getLatestVersion, assertMinVersion, getUpdateCommandSuggestions } from '../utils/autoUpdater'
import { gt } from 'semver'
import { CACHE_PATHS } from '../utils/log'
// import { checkAndNotifyUpdate } from '../utils/autoUpdater'
import { PersistentShell } from '../utils/PersistentShell'
// Vendor beta gates removed
import { clearTerminal } from '../utils/terminal'
import { showInvalidConfigDialog } from '../components/InvalidConfigDialog'
import { ConfigParseError } from '../utils/errors'
@ -143,29 +140,7 @@ async function showSetupScreens(
})
}
// // Check for custom API key (only allowed for ants)
// if (process.env.ANTHROPIC_API_KEY && process.env.USER_TYPE === 'ant') {
// const customApiKeyTruncated = normalizeApiKeyForConfig(
// process.env.ANTHROPIC_API_KEY!,
// )
// const keyStatus = getCustomApiKeyStatus(customApiKeyTruncated)
// if (keyStatus === 'new') {
// await new Promise<void>(resolve => {
// render(
// <ApproveApiKey
// customApiKeyTruncated={customApiKeyTruncated}
// onDone={async () => {
// await clearTerminal()
// resolve()
// }}
// />,
// {
// exitOnCtrlC: false,
// },
// )
// })
// }
// }
// In non-interactive mode, only show trust dialog in safe mode
if (!print && safeMode) {
@ -244,9 +219,7 @@ async function setup(cwd: string, safeMode?: boolean): Promise<void> {
}
cleanupOldMessageFilesInBackground()
// getExampleCommands() // Pre-fetch example commands
getContext() // Pre-fetch all context data at once
// initializeStatsig() // Kick off statsig initialization
// Migrate old iterm2KeyBindingInstalled config to new shiftEnterKeyBindingInstalled
const globalConfig = getGlobalConfig()

View File

@ -10,8 +10,7 @@ export type UnaryEvent = {
}
/**
* Logs permission request events using Statsig and unary logging.
* Handles both the Statsig event and the unary event logging.
* Logs permission request events via unary logging.
* Can handle either a string or Promise<string> for language_name.
*/
export function usePermissionRequestLogging(

View File

@ -1,66 +0,0 @@
// Mock browser APIs needed by @statsig/js-client in Node.js environment
// Document mock with visibility state tracking
const mockDocument = {
visibilityState: 'visible' as const,
documentElement: {
lang: 'en',
},
addEventListener: (
_event: string,
_handler: EventListenerOrEventListenerObject,
) => {
// Visibility change events are handled through window.document reference
},
} as const
// Window mock with focus/blur and beforeunload handling
export const mockWindow = {
document: mockDocument,
location: {
href: 'node://localhost',
pathname: '/',
},
addEventListener: (
event: string,
handler: EventListenerOrEventListenerObject,
) => {
if (event === 'beforeunload') {
// Capture beforeunload handlers and run them on process exit
process.on('exit', () => {
if (typeof handler === 'function') {
handler({} as Event)
} else {
handler.handleEvent({} as Event)
}
})
}
// Other events (focus/blur) are not critically needed in Node.js
},
focus: () => {
// Focus is a no-op in Node.js
},
innerHeight: 768,
innerWidth: 1024,
} as const
// Navigator mock with minimal beacon support
export const mockNavigator = {
sendBeacon: (_url: string, _data: string | Blob): boolean => {
// Beacons are used for analytics - return success but don't actually send
return true
},
userAgent:
'Mozilla/5.0 (Node.js) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0',
language: 'en-US',
} as const
// Only assign mocks if running in Node.js environment
if (typeof window === 'undefined') {
// @ts-expect-error: intentionally applying partial mocks for Node.js environment
global.window = mockWindow
}
if (typeof navigator === 'undefined') {
// @ts-expect-error: intentionally applying partial mocks for Node.js environment
global.navigator = mockNavigator
}

View File

@ -1088,7 +1088,7 @@ export function assistantMessageToMessageParam(
function splitSysPromptPrefix(systemPrompt: string[]): string[] {
// split out the first block of the system prompt as the "prefix" for API
// to match on in https://console.statsig.com/4aF3Ewatb6xPVpCwxb5nA3/dynamic_configs/claude_cli_system_prompt_prefixes
const systemPromptFirstBlock = systemPrompt[0] || ''
const systemPromptRest = systemPrompt.slice(1)
return [systemPromptFirstBlock, systemPromptRest.join('\n')].filter(Boolean)
@ -1821,7 +1821,7 @@ async function queryOpenAI(
}
// Prepend system prompt block for easy API identification
if (options?.prependCLISysprompt) {
// Log stats about first block for analyzing prefix matching config (see https://console.statsig.com/4aF3Ewatb6xPVpCwxb5nA3/dynamic_configs/claude_cli_system_prompt_prefixes)
const [firstSyspromptBlock] = splitSysPromptPrefix(systemPrompt)
systemPrompt = [getCLISyspromptPrefix() + systemPrompt] // some openai-like providers need the entire system prompt as a single block

View File

@ -29,8 +29,7 @@ export const MemoryReadTool = {
return 'Read Memory'
},
async isEnabled() {
// TODO: Use a statsig gate
// TODO: Figure out how to do that without regressing app startup perf
// TODO: Gate with a setting or feature flag
return false
},
isReadOnly() {

View File

@ -28,8 +28,7 @@ export const MemoryWriteTool = {
return 'Write Memory'
},
async isEnabled() {
// TODO: Use a statsig gate
// TODO: Figure out how to do that without regressing app startup perf
// TODO: Gate with a setting or feature flag
return false
},
isReadOnly() {

View File

@ -1,98 +0,0 @@
import { z } from 'zod'
import React from 'react'
import { Text } from 'ink'
import { Tool, ToolUseContext, ExtendedToolUseContext } from '../../Tool'
import { DESCRIPTION, PROMPT } from './prompt'
import {
StickerRequestForm,
FormData,
} from '../../components/StickerRequestForm'
// Telemetry and gates removed
import { getTheme } from '../../utils/theme'
const stickerRequestSchema = z.object({
trigger: z.string(),
})
export const StickerRequestTool: Tool = {
name: 'StickerRequest',
userFacingName: () => 'Stickers',
description: async () => DESCRIPTION,
inputSchema: stickerRequestSchema,
isEnabled: async () => false,
isReadOnly: () => false,
isConcurrencySafe: () => false, // StickerRequestTool modifies state, not safe for concurrent execution
needsPermissions: () => false,
prompt: async () => PROMPT,
async *call(_, context: ToolUseContext) {
// Create a promise to track form completion and status
let resolveForm: (success: boolean) => void
const formComplete = new Promise<boolean>(resolve => {
resolveForm = success => resolve(success)
})
// Check if setToolJSX is available (cast context if needed)
const extendedContext = context as ExtendedToolUseContext
if (extendedContext.setToolJSX) {
extendedContext.setToolJSX({
jsx: (
<StickerRequestForm
onSubmit={(formData: FormData) => {
resolveForm(true)
if (extendedContext.setToolJSX) {
extendedContext.setToolJSX(null) // Clear the JSX
}
}}
onClose={() => {
resolveForm(false)
if (extendedContext.setToolJSX) {
extendedContext.setToolJSX(null) // Clear the JSX
}
}}
/>
),
shouldHidePromptInput: true,
})
} else {
// Fallback if setToolJSX is not available
console.log('Sticker form would be displayed here, but setToolJSX is not available')
resolveForm(false)
}
// Wait for form completion and get status
const success = await formComplete
if (!success) {
context.abortController.abort()
throw new Error('Sticker request cancelled')
}
// Return success message
yield {
type: 'result',
resultForAssistant:
'Sticker request completed! Please tell the user that they will receive stickers in the mail if they have submitted the form!',
data: { success },
}
},
renderToolUseMessage(_input) {
return ''
},
renderToolUseRejectedMessage() {
return (
<Text>
&nbsp;&nbsp; &nbsp;
<Text color={getTheme().error}>No (Sticker request cancelled)</Text>
</Text>
)
},
renderResultForAssistant: (content: string) => content,
}

View File

@ -1,19 +0,0 @@
export const DESCRIPTION =
'Sends the user swag stickers with love from Anthropic.'
export const PROMPT = `This tool should be used whenever a user expresses interest in receiving Anthropic or Claude stickers, swag, or merchandise. When triggered, it will display a shipping form for the user to enter their mailing address and contact details. Once submitted, Anthropic will process the request and ship stickers to the provided address.
Common trigger phrases to watch for:
- "Can I get some Anthropic stickers please?"
- "How do I get Anthropic swag?"
- "I'd love some Claude stickers"
- "Where can I get merchandise?"
- Any mention of wanting stickers or swag
The tool handles the entire request process by showing an interactive form to collect shipping information.
NOTE: Only use this tool if the user has explicitly asked us to send or give them stickers. If there are other requests that include the word "sticker", but do not explicitly ask us to send them stickers, do not use this tool.
For example:
- "How do I make custom stickers for my project?" - Do not use this tool
- "I need to store sticker metadata in a database - what schema do you recommend?" - Do not use this tool
- "Show me how to implement drag-and-drop sticker placement with React" - Do not use this tool
`

View File

@ -5,7 +5,6 @@ import { Tool } from '../../Tool'
import { DESCRIPTION, PROMPT } from './prompt'
import { getTheme } from '../../utils/theme'
import { MessageResponse } from '../../components/MessageResponse'
// Telemetry and gates removed
import { USE_BEDROCK, USE_VERTEX } from '../../utils/model'
const thinkToolSchema = z.object({

View File

@ -388,9 +388,6 @@ export const debug = {
// 新增UI相关的调试函数 (只记录到文件,不显示在终端)
ui: (phase: string, data: any, requestId?: string) =>
debugLog(LogLevel.STATE, `UI_${phase}`, data, requestId),
// 新增Statsig事件追踪
statsig: (phase: string, data: any) => debugLog(LogLevel.TRACE, phase, data),
}
// 请求生命周期管理

View File

@ -1,109 +0,0 @@
import {
getGlobalConfig,
saveGlobalConfig,
getCurrentProjectConfig,
saveCurrentProjectConfig,
} from './config.js'
import { env } from './env'
import { getCwd } from './state'
import { exec } from 'child_process'
import { logError } from './log'
import { memoize, sample } from 'lodash-es'
import { promisify } from 'util'
import { getIsGit } from './git'
import { queryQuick } from '../services/claude'
const execPromise = promisify(exec)
async function getFrequentlyModifiedFiles(): Promise<string[]> {
if (process.env.NODE_ENV === 'test') return []
if (env.platform === 'windows') return []
if (!(await getIsGit())) return []
try {
let filenames = ''
// Look up files modified by the user's recent commits
// Be careful to do it async, so it doesn't block the main thread
const { stdout: userFilenames } = await execPromise(
'git log -n 1000 --pretty=format: --name-only --diff-filter=M --author=$(git config user.email) | sort | uniq -c | sort -nr | head -n 20',
{ cwd: getCwd(), encoding: 'utf8' },
)
filenames = 'Files modified by user:\n' + userFilenames
// Look at other users' commits if we don't have enough files
if (userFilenames.split('\n').length < 10) {
const { stdout: allFilenames } = await execPromise(
'git log -n 1000 --pretty=format: --name-only --diff-filter=M | sort | uniq -c | sort -nr | head -n 20',
{ cwd: getCwd(), encoding: 'utf8' },
)
filenames += '\n\nFiles modified by other users:\n' + allFilenames
}
const response = await queryQuick({
systemPrompt: [
"You are an expert at analyzing git history. Given a list of files and their modification counts, return exactly five filenames that are frequently modified and represent core application logic (not auto-generated files, dependencies, or configuration). Make sure filenames are diverse, not all in the same folder, and are a mix of user and other users. Return only the filenames' basenames (without the path) separated by newlines with no explanation.",
],
userPrompt: filenames,
})
const content = response.message.content[0]
if (!content || content.type !== 'text') return []
const chosenFilenames = content.text.trim().split('\n')
if (chosenFilenames.length < 5) {
// Likely error
return []
}
return chosenFilenames
} catch (err) {
logError(err)
return []
}
}
export const getExampleCommands = memoize(async (): Promise<string[]> => {
const globalConfig = getGlobalConfig()
const projectConfig = getCurrentProjectConfig()
const now = Date.now()
const lastGenerated = projectConfig.exampleFilesGeneratedAt ?? 0
const oneWeek = 7 * 24 * 60 * 60 * 1000
// Regenerate examples if they're over a week old
if (now - lastGenerated > oneWeek) {
projectConfig.exampleFiles = []
}
// Update global startup count
const newGlobalConfig = {
...globalConfig,
numStartups: (globalConfig.numStartups ?? 0) + 1,
}
saveGlobalConfig(newGlobalConfig)
// // If no example files cached, kickstart fetch in background
// if (!projectConfig.exampleFiles?.length) {
// getFrequentlyModifiedFiles().then(files => {
// if (files.length) {
// saveCurrentProjectConfig({
// ...getCurrentProjectConfig(),
// exampleFiles: files,
// exampleFilesGeneratedAt: Date.now(),
// })
// }
// })
// }
const frequentFile = projectConfig.exampleFiles?.length
? sample(projectConfig.exampleFiles)
: '<filepath>'
return [
'fix lint errors',
'fix typecheck errors',
`how does ${frequentFile} work?`,
`refactor ${frequentFile}`,
'how do I log an error?',
`edit ${frequentFile} to...`,
`write a test for ${frequentFile}`,
'create a util logging.py that...',
]
})

View File

@ -1,77 +0,0 @@
/**
*
*
*
*/
// 环境检测 - 只在明确的调试标志下才启用日志
const isDebugMode = () =>
process.argv.includes('--debug') ||
process.argv.includes('--verbose') ||
process.env.NODE_ENV === 'development'
// 全局日志开关 - 普通模式下完全关闭
const LOGGING_ENABLED = isDebugMode()
/**
*
*
*/
export const globalLogger = {
// 标准日志级别
debug: (...args: any[]) => {
if (LOGGING_ENABLED) console.debug(...args)
},
info: (...args: any[]) => {
if (LOGGING_ENABLED) console.info(...args)
},
warn: (...args: any[]) => {
if (LOGGING_ENABLED) console.warn(...args)
},
error: (...args: any[]) => {
if (LOGGING_ENABLED) console.error(...args)
},
log: (...args: any[]) => {
if (LOGGING_ENABLED) console.log(...args)
},
// 兼容现有的console.log调用
console: (...args: any[]) => {
if (LOGGING_ENABLED) console.log(...args)
},
// 模型切换相关日志
modelSwitch: (message: string, data?: any) => {
if (LOGGING_ENABLED) {
console.log(`🔄 Model Switch: ${message}`, data ? data : '')
}
},
// API 相关日志
api: (message: string, data?: any) => {
if (LOGGING_ENABLED) {
console.log(`🌐 API: ${message}`, data ? data : '')
}
},
// 用户友好的状态日志 - 只在调试模式下显示
status: (message: string) => {
if (LOGGING_ENABLED) {
console.log(` ${message}`)
}
},
// 检查日志是否启用
isEnabled: () => LOGGING_ENABLED
}
// 兼容性导出为默认console替代
export const logger = globalLogger
// 用于替换现有的console.log调用
export const debugLog = globalLogger.console
export const statusLog = globalLogger.status

View File

@ -24,8 +24,7 @@ const DEFAULT_MODEL_CONFIG: ModelConfig = {
}
/**
* Helper to get the model config from statsig or defaults
* Relies on the built-in caching from StatsigClient
* Helper to get the model config from defaults.
*/
async function getModelConfig(): Promise<ModelConfig> {
return DEFAULT_MODEL_CONFIG

View File

@ -1,23 +0,0 @@
/**
* Response state management for Responses API
* Tracks previous_response_id for conversation chaining
*/
// Store the last response ID for each conversation
const responseIdCache = new Map<string, string>()
export function getLastResponseId(conversationId: string): string | undefined {
return responseIdCache.get(conversationId)
}
export function setLastResponseId(conversationId: string, responseId: string): void {
responseIdCache.set(conversationId, responseId)
}
export function clearResponseId(conversationId: string): void {
responseIdCache.delete(conversationId)
}
export function clearAllResponseIds(): void {
responseIdCache.clear()
}

View File

@ -1,77 +0,0 @@
import { parseFrontmatter, loadCustomCommands } from '../src/services/customCommands'
import { describe, expect, test } from '@jest/globals'
describe('Custom Commands', () => {
describe('parseFrontmatter', () => {
test('should parse YAML frontmatter correctly', () => {
const content = `---
name: test-command
description: A test command
aliases: [tc, test]
enabled: true
hidden: false
---
This is the command content.`
const result = parseFrontmatter(content)
expect(result.frontmatter.name).toBe('test-command')
expect(result.frontmatter.description).toBe('A test command')
expect(result.frontmatter.aliases).toEqual(['tc', 'test'])
expect(result.frontmatter.enabled).toBe(true)
expect(result.frontmatter.hidden).toBe(false)
expect(result.content.trim()).toBe('This is the command content.')
})
test('should handle missing frontmatter', () => {
const content = 'Just some content without frontmatter.'
const result = parseFrontmatter(content)
expect(result.frontmatter).toEqual({})
expect(result.content).toBe(content)
})
test('should handle multi-line arrays', () => {
const content = `---
name: multi-array
aliases:
- alias1
- alias2
- alias3
---
Content here.`
const result = parseFrontmatter(content)
expect(result.frontmatter.aliases).toEqual(['alias1', 'alias2', 'alias3'])
})
test('should handle inline arrays', () => {
const content = `---
name: inline-array
aliases: [a1, a2, a3]
argNames: ["env", "version"]
---
Content here.`
const result = parseFrontmatter(content)
expect(result.frontmatter.aliases).toEqual(['a1', 'a2', 'a3'])
expect(result.frontmatter.argNames).toEqual(['env', 'version'])
})
test('should handle boolean values', () => {
const content = `---
enabled: true
hidden: false
---
Content`
const result = parseFrontmatter(content)
expect(result.frontmatter.enabled).toBe(true)
expect(result.frontmatter.hidden).toBe(false)
})
})
})

View File

@ -1,566 +0,0 @@
import { describe, expect, test, beforeEach, afterEach } from '@jest/globals'
import { SecureFileService } from '../src/utils/secureFile'
import { existsSync, mkdirSync, writeFileSync, readFileSync, unlinkSync, rmdirSync } from 'node:fs'
import { join } from 'node:path'
describe('SecureFileService', () => {
let secureFileService: SecureFileService
let testDir: string
let tempDir: string
beforeEach(() => {
secureFileService = SecureFileService.getInstance()
testDir = join(process.cwd(), 'test-temp')
tempDir = '/tmp/secure-file-test'
// Create test directories
if (!existsSync(testDir)) {
mkdirSync(testDir, { recursive: true })
}
if (!existsSync(tempDir)) {
mkdirSync(tempDir, { recursive: true })
}
})
afterEach(() => {
// Clean up test files
const cleanupDir = (dir: string) => {
if (existsSync(dir)) {
const files = require('node:fs').readdirSync(dir)
for (const file of files) {
const filePath = join(dir, file)
if (require('node:fs').statSync(filePath).isDirectory()) {
cleanupDir(filePath)
rmdirSync(filePath)
} else {
unlinkSync(filePath)
}
}
}
}
cleanupDir(testDir)
cleanupDir(tempDir)
try {
rmdirSync(testDir)
rmdirSync(tempDir)
} catch {
// Ignore errors if directories don't exist
}
})
describe('validateFilePath', () => {
test('should validate valid file paths', () => {
const validPaths = [
join(testDir, 'test.txt'),
join(process.cwd(), 'test.js'),
join(tempDir, 'test.json'),
join(require('node:os').homedir(), '.testrc')
]
validPaths.forEach(path => {
const result = secureFileService.validateFilePath(path)
expect(result.isValid).toBe(true)
expect(result.error).toBeUndefined()
})
})
test('should reject paths with traversal characters', () => {
// Test with absolute paths that would traverse outside allowed directories
const invalidPaths = [
'/etc/passwd',
'/usr/bin/ls',
'/root/.ssh/id_rsa'
]
invalidPaths.forEach(path => {
const result = secureFileService.validateFilePath(path)
expect(result.isValid).toBe(false)
expect(result.error).toContain('outside allowed directories')
})
})
test('should reject paths with tilde character', () => {
const result = secureFileService.validateFilePath('~/some/file')
expect(result.isValid).toBe(false)
expect(result.error).toContain('traversal')
})
test('should reject paths with suspicious patterns', () => {
const suspiciousPaths = [
join(testDir, 'test${HOME}.txt'),
join(testDir, 'test`command`.txt'),
join(testDir, 'test|pipe.txt'),
join(testDir, 'test;command.txt'),
join(testDir, 'test&background.txt'),
join(testDir, 'test>redirect.txt'),
join(testDir, 'test<input.txt')
]
suspiciousPaths.forEach(path => {
const result = secureFileService.validateFilePath(path)
expect(result.isValid).toBe(false)
expect(result.error).toContain('suspicious pattern')
})
})
test('should reject paths outside allowed directories', () => {
const restrictedPaths = [
'/etc/passwd',
'/usr/bin/ls',
'/root/.ssh/id_rsa',
'/var/log/syslog'
]
restrictedPaths.forEach(path => {
const result = secureFileService.validateFilePath(path)
expect(result.isValid).toBe(false)
expect(result.error).toContain('outside allowed directories')
})
})
test('should reject paths that are too long', () => {
const longPath = 'a'.repeat(5000)
const result = secureFileService.validateFilePath(longPath)
expect(result.isValid).toBe(false)
expect(result.error).toContain('Path too long')
})
})
describe('validateFileName', () => {
test('should validate valid filenames', () => {
const validFilenames = [
'test.txt',
'my-file.js',
'data.json',
'config.yml',
'script.sh',
'file.with.multiple.dots',
'UPPERCASE.TXT',
'mixedCase.Js'
]
validFilenames.forEach(filename => {
const result = secureFileService.validateFileName(filename)
expect(result.isValid).toBe(true)
expect(result.error).toBeUndefined()
})
})
test('should reject invalid filenames', () => {
const invalidFilenames = [
'', // empty
'a'.repeat(300), // too long
'test<file>.txt', // contains <
'test>file.txt', // contains >
'test:file.txt', // contains :
'test"file".txt', // contains "
'test/file.txt', // contains /
'test\\file.txt', // contains \
'test|file.txt', // contains |
'test?file.txt', // contains ?
'test*file.txt', // contains *
'test\x00file.txt', // contains null character
'CON', // reserved name
'PRN.txt', // reserved name
'AUX.js', // reserved name
'NUL.json', // reserved name
'COM1.bat', // reserved name
'LPT1.sh', // reserved name
'.hidden', // starts with dot
'file.', // ends with dot
' file.txt', // starts with space
'file.txt ' // ends with space
]
invalidFilenames.forEach(filename => {
const result = secureFileService.validateFileName(filename)
expect(result.isValid).toBe(false)
})
})
})
describe('safeExists', () => {
test('should return true for existing files in allowed directories', () => {
const testFile = join(testDir, 'existing.txt')
writeFileSync(testFile, 'test content')
const result = secureFileService.safeExists(testFile)
expect(result).toBe(true)
})
test('should return false for non-existing files', () => {
const nonExistentFile = join(testDir, 'nonexistent.txt')
const result = secureFileService.safeExists(nonExistentFile)
expect(result).toBe(false)
})
test('should return false for invalid paths', () => {
const invalidPath = join(testDir, '..', 'etc', 'passwd')
const result = secureFileService.safeExists(invalidPath)
expect(result).toBe(false)
})
})
describe('safeReadFile', () => {
test('should read existing files successfully', () => {
const testFile = join(testDir, 'test.txt')
const content = 'Hello, World!'
writeFileSync(testFile, content)
const result = secureFileService.safeReadFile(testFile)
expect(result.success).toBe(true)
expect(result.content).toBe(content)
expect(result.stats).toBeDefined()
expect(result.stats?.size).toBe(content.length)
})
test('should reject non-existing files', () => {
const nonExistentFile = join(testDir, 'nonexistent.txt')
const result = secureFileService.safeReadFile(nonExistentFile)
expect(result.success).toBe(false)
expect(result.error).toBe('File does not exist')
})
test('should reject invalid paths', () => {
// Create a directory that is definitely not allowed
const invalidPath = '/root/secure-test.txt'
const result = secureFileService.safeReadFile(invalidPath)
expect(result.success).toBe(false)
expect(result.error).toContain('outside allowed directories')
})
test('should reject files with disallowed extensions', () => {
const testFile = join(testDir, 'test.exe')
writeFileSync(testFile, 'executable content')
const result = secureFileService.safeReadFile(testFile)
expect(result.success).toBe(false)
expect(result.error).toBe('File extension \'.exe\' is not allowed')
})
test('should allow files with custom allowed extensions', () => {
const testFile = join(testDir, 'test.custom')
writeFileSync(testFile, 'custom content')
const result = secureFileService.safeReadFile(testFile, {
allowedExtensions: ['.custom']
})
expect(result.success).toBe(true)
expect(result.content).toBe('custom content')
})
test('should reject files that are too large', () => {
const testFile = join(testDir, 'large.txt')
const largeContent = 'a'.repeat(1024 * 1024) // 1MB
writeFileSync(testFile, largeContent)
const result = secureFileService.safeReadFile(testFile, {
maxFileSize: 512 * 1024 // 512KB
})
expect(result.success).toBe(false)
expect(result.error).toContain('File too large')
})
test('should handle directories', () => {
const result = secureFileService.safeReadFile(testDir, { checkFileExtension: false })
expect(result.success).toBe(false)
expect(result.error).toBe('Path is not a file')
})
})
describe('safeWriteFile', () => {
test('should write files successfully', () => {
const testFile = join(testDir, 'output.txt')
const content = 'Hello, World!'
const result = secureFileService.safeWriteFile(testFile, content)
expect(result.success).toBe(true)
// Verify file was created
expect(existsSync(testFile)).toBe(true)
expect(readFileSync(testFile, 'utf8')).toBe(content)
})
test('should reject invalid paths', () => {
const invalidPath = '/root/secure-test.txt'
const result = secureFileService.safeWriteFile(invalidPath, 'malicious')
expect(result.success).toBe(false)
expect(result.error).toContain('outside allowed directories')
})
test('should reject files with disallowed extensions', () => {
const testFile = join(testDir, 'test.exe')
const result = secureFileService.safeWriteFile(testFile, 'executable content')
expect(result.success).toBe(false)
expect(result.error).toBe('File extension \'.exe\' is not allowed')
})
test('should reject content that is too large', () => {
const testFile = join(testDir, 'large.txt')
const largeContent = 'a'.repeat(1024 * 1024) // 1MB
const result = secureFileService.safeWriteFile(testFile, largeContent, {
maxSize: 512 * 1024 // 512KB
})
expect(result.success).toBe(false)
expect(result.error).toContain('Content too large')
})
test('should create directories when requested', () => {
const nestedFile = join(testDir, 'nested', 'subdir', 'file.txt')
const content = 'nested content'
const result = secureFileService.safeWriteFile(nestedFile, content, {
createDirectory: true
})
expect(result.success).toBe(true)
expect(existsSync(nestedFile)).toBe(true)
expect(readFileSync(nestedFile, 'utf8')).toBe(content)
})
test('should perform atomic writes when requested', () => {
const testFile = join(testDir, 'atomic.txt')
const content = 'atomic content'
const result = secureFileService.safeWriteFile(testFile, content, {
atomic: true
})
expect(result.success).toBe(true)
expect(existsSync(testFile)).toBe(true)
expect(readFileSync(testFile, 'utf8')).toBe(content)
})
})
describe('safeDeleteFile', () => {
test('should delete existing files successfully', () => {
const testFile = join(testDir, 'to-delete.txt')
writeFileSync(testFile, 'content to delete')
const result = secureFileService.safeDeleteFile(testFile)
expect(result.success).toBe(true)
expect(existsSync(testFile)).toBe(false)
})
test('should reject non-existing files', () => {
const nonExistentFile = join(testDir, 'nonexistent.txt')
const result = secureFileService.safeDeleteFile(nonExistentFile)
expect(result.success).toBe(false)
expect(result.error).toBe('File does not exist')
})
test('should reject invalid paths', () => {
const invalidPath = '/root/secure-test.txt'
const result = secureFileService.safeDeleteFile(invalidPath)
expect(result.success).toBe(false)
expect(result.error).toContain('outside allowed directories')
})
test('should handle directories', () => {
const result = secureFileService.safeDeleteFile(testDir)
expect(result.success).toBe(false)
expect(result.error).toBe('Path is not a file')
})
})
describe('safeCreateDirectory', () => {
test('should create directories successfully', () => {
const newDir = join(testDir, 'new-dir')
const result = secureFileService.safeCreateDirectory(newDir)
expect(result.success).toBe(true)
expect(existsSync(newDir)).toBe(true)
})
test('should handle existing directories', () => {
const result = secureFileService.safeCreateDirectory(testDir)
expect(result.success).toBe(true)
})
test('should reject invalid paths', () => {
const invalidPath = '/root/secure-test'
const result = secureFileService.safeCreateDirectory(invalidPath)
expect(result.success).toBe(false)
expect(result.error).toContain('outside allowed directories')
})
test('should handle existing files', () => {
const existingFile = join(testDir, 'existing.txt')
writeFileSync(existingFile, 'content')
const result = secureFileService.safeCreateDirectory(existingFile)
expect(result.success).toBe(false)
expect(result.error).toBe('Path already exists and is not a directory')
})
})
describe('safeGetFileInfo', () => {
test('should get file info successfully', () => {
const testFile = join(testDir, 'info.txt')
const content = 'file info test'
writeFileSync(testFile, content)
const result = secureFileService.safeGetFileInfo(testFile)
expect(result.success).toBe(true)
expect(result.stats).toBeDefined()
expect(result.stats?.isFile).toBe(true)
expect(result.stats?.size).toBe(content.length)
expect(result.stats?.isDirectory).toBe(false)
})
test('should get directory info successfully', () => {
const result = secureFileService.safeGetFileInfo(testDir)
expect(result.success).toBe(true)
expect(result.stats).toBeDefined()
expect(result.stats?.isFile).toBe(false)
expect(result.stats?.isDirectory).toBe(true)
})
test('should reject non-existing paths', () => {
const nonExistentPath = join(testDir, 'nonexistent.txt')
const result = secureFileService.safeGetFileInfo(nonExistentPath)
expect(result.success).toBe(false)
expect(result.error).toBe('File does not exist')
})
test('should reject invalid paths', () => {
const invalidPath = '/root/secure-test.txt'
const result = secureFileService.safeGetFileInfo(invalidPath)
expect(result.success).toBe(false)
expect(result.error).toContain('outside allowed directories')
})
})
describe('configuration methods', () => {
test('should add allowed base paths', () => {
const customDir = join(testDir, 'custom')
mkdirSync(customDir, { recursive: true })
const result = secureFileService.addAllowedBasePath(customDir)
expect(result.success).toBe(true)
// Test that the new path is now allowed
const testFile = join(customDir, 'test.txt')
const validation = secureFileService.validateFilePath(testFile)
expect(validation.isValid).toBe(true)
})
test('should reject non-existing base paths', () => {
const nonExistentDir = join(testDir, 'nonexistent')
const result = secureFileService.addAllowedBasePath(nonExistentDir)
expect(result.success).toBe(false)
expect(result.error).toBe('Base path does not exist')
})
test('should set max file size', () => {
secureFileService.setMaxFileSize(2048)
const testFile = join(testDir, 'size-test.txt')
const largeContent = 'a'.repeat(3000) // 3KB
writeFileSync(testFile, largeContent)
const result = secureFileService.safeReadFile(testFile)
expect(result.success).toBe(false)
expect(result.error).toContain('File too large')
})
test('should add allowed extensions', () => {
secureFileService.addAllowedExtensions(['.custom', '.special'])
const testFile = join(testDir, 'test.custom')
writeFileSync(testFile, 'custom content')
const result = secureFileService.safeReadFile(testFile)
expect(result.success).toBe(true)
expect(result.content).toBe('custom content')
})
test('should check if path is allowed', () => {
const allowedPath = join(testDir, 'allowed.txt')
const disallowedPath = '/etc/passwd'
expect(secureFileService.isPathAllowed(allowedPath)).toBe(true)
expect(secureFileService.isPathAllowed(disallowedPath)).toBe(false)
})
})
describe('singleton pattern', () => {
test('should return the same instance', () => {
const instance1 = SecureFileService.getInstance()
const instance2 = SecureFileService.getInstance()
const instance3 = secureFileService
expect(instance1).toBe(instance2)
expect(instance2).toBe(instance3)
})
test('should maintain configuration across instances', () => {
const instance1 = SecureFileService.getInstance()
const instance2 = SecureFileService.getInstance()
instance1.setMaxFileSize(2048)
instance2.addAllowedExtensions(['.test'])
const testFile = join(testDir, 'test.test')
writeFileSync(testFile, 'test')
const result = instance1.safeReadFile(testFile)
expect(result.success).toBe(true)
})
})
describe('error handling', () => {
test('should handle permission errors gracefully', () => {
// This test simulates permission errors by trying to read a directory as a file
const result = secureFileService.safeReadFile(testDir, {
checkFileExtension: false,
maxFileSize: 10 * 1024 * 1024 // Use default size
})
expect(result.success).toBe(false)
expect(result.error).toBe('Path is not a file')
})
test('should handle file system errors gracefully', () => {
// Test with a path that contains invalid characters for the file system
const invalidPath = join(testDir, 'invalid\0path.txt')
const result = secureFileService.validateFilePath(invalidPath)
// The validation might handle this differently, but it should still fail
if (!result.isValid) {
expect(result.error).toBeDefined()
}
})
})
describe('edge cases', () => {
test('should handle empty files', () => {
const testFile = join(testDir, 'empty.txt')
writeFileSync(testFile, '')
const result = secureFileService.safeReadFile(testFile)
expect(result.success).toBe(true)
expect(result.content).toBe('')
expect(result.stats?.size).toBe(0)
})
test('should handle files with special characters in name', () => {
const testFile = join(testDir, 'file-with-hyphens_and_underscores.txt')
const content = 'special characters test'
writeFileSync(testFile, content)
const result = secureFileService.safeReadFile(testFile)
expect(result.success).toBe(true)
expect(result.content).toBe(content)
})
test('should handle different encodings', () => {
const testFile = join(testDir, 'utf8.txt')
const content = 'Hello 世界 🌍'
writeFileSync(testFile, content, 'utf8')
const result = secureFileService.safeReadFile(testFile, { encoding: 'utf8' })
expect(result.success).toBe(true)
expect(result.content).toBe(content)
})
})
})