Compare commits

..

40 Commits

Author SHA1 Message Date
Xinlu Lai
52cb635512
Merge pull request #135 from RadonX/responses-api-adaptation
feat(openai): Responses API integration with adapter pattern
2025-11-15 20:32:17 +08:00
Radon Co
3d7f81242b test(responses-api): restructure test suite layout 2025-11-11 23:03:31 -08:00
Radon Co
14f9892bb5 feat(responses-api-adapter): Enhanced Tool Call Conversion
WHAT: Enhanced tool call handling in Responses API adapter with better validation, error handling, and test coverage

WHY: The adapter lacked robust tool call parsing and validation, leading to potential issues with malformed tool calls and incomplete test coverage. We needed to improve error handling and add comprehensive tests for real tool call scenarios.

HOW: Enhanced tool call result parsing with defensive null checking; improved assistant tool call parsing with proper validation; enhanced response tool call parsing with better structure and support for multiple tool call types; added validation for streaming tool call handling; updated tests to validate real tool call parsing from API; added multi-turn conversation test with tool result injection

Testing: All 3 integration tests pass with real API calls. Validated tool call parsing and tool result conversion working correctly. Real tool call detected and parsed successfully.
2025-11-11 12:58:30 -08:00
Radon Co
25adc80161 prompt(queryOpenAI): Separate adapter context from API execution
WHAT: Refactored queryOpenAI to prepare adapter context outside withRetry and execute API calls inside withRetry

WHY: The previous implementation mixed adapter preparation and execution, causing type confusion and state management issues

HOW: Created AdapterExecutionContext and QueryResult types, moved adapter context creation before withRetry block, wrapped all API calls (Responses API, Chat Completions, and legacy) inside withRetry with unified return structure, added normalizeUsage() helper to handle token field variations, ensured responseId and content are properly preserved through the unified return path
2025-11-11 00:49:01 -08:00
Radon Co
8288378dbd refactor(claude.ts): Extract adapter path before withRetry for clean separation
Problem: Mixed return types from withRetry callback caused content loss when
adapter returned AssistantMessage but outer code expected ChatCompletion.

Solution: Restructured queryOpenAI to separate adapter and legacy paths:
- Adapter path (responsesAPI): Direct execution, early return, no withRetry
- Legacy path (chat_completions): Uses withRetry for retry logic

Benefits:
 No type confusion - adapter path never enters withRetry
 Clean separation of concerns - adapters handle format, legacy handles retry
 Streaming-ready architecture for future async generator implementation
 Content displays correctly in CLI (fixed empty content bug)
 All 14 tests pass (52 assertions)

Additional changes:
- Added StreamingEvent type to base adapter for future async generators
- Updated UnifiedResponse to support both string and array content
- Added comments explaining architectural decisions and future improvements
- Fixed content loss bug in responses API path
2025-11-10 23:51:09 -08:00
Radon Co
c8ecba04d8 fix: Return AssistantMessage early to prevent content loss
Prevents adapter responses from being overwritten with empty content.
Adds early return check when response.type === 'assistant' to preserve
correctly formatted content from the adapter path.

All tests pass, CLI content now displays correctly.
2025-11-09 23:47:53 -08:00
Radon Co
34cd4e250d feat(responsesAPI): Implement async generator streaming for real-time UI updates
WHAT:
- Refactored ResponsesAPIAdapter to support async generator streaming pattern
- Added parseStreamingResponse() method that yields StreamingEvent incrementally
- Maintained backward compatibility with parseStreamingResponseBuffered() method
- Updated UnifiedResponse type to support both string and array content formats

WHY:
- Aligns Responses API adapter with Kode's three-level streaming architecture (Provider → Query → REPL)
- Enables real-time UI updates with text appearing progressively instead of all at once
- Supports TTFT (Time-To-First-Token) tracking for performance monitoring
- Matches Chat Completions streaming implementation pattern for consistency
- Resolves architectural mismatch between adapter pattern and streaming requirements

HOW:
- responsesAPI.ts: Implemented async *parseStreamingResponse() yielding events (message_start, text_delta, tool_request, usage, message_stop)
- base.ts: Added StreamingEvent type definition and optional parseStreamingResponse() to base class
- modelCapabilities.ts: Updated UnifiedResponse.content to accept string | Array<{type, text?, [key]: any}>
- parseResponse() maintains backward compatibility by calling buffered version
- All 14 tests pass with no regressions
2025-11-09 23:14:16 -08:00
Radon Co
be6477cca7 feat: Fix CLI crash and add OpenAI Responses API integration
WHAT: Fix critical CLI crash with content.filter() error and implement OpenAI Responses API integration with comprehensive testing

WHY: CLI was crashing with 'TypeError: undefined is not an object (evaluating "content.filter")' when using OpenAI models, preventing users from making API calls. Additionally needed proper Responses API support with reasoning tokens.

HOW:
• Fix content extraction from OpenAI response structure in legacy path
• Add JSON/Zod schema detection in responsesAPI adapter
• Create comprehensive test suite for both integration and production scenarios
• Document the new adapter architecture and usage

CRITICAL FIXES:
• claude.ts: Extract content from response.choices[0].message.content instead of undefined response.content
• responsesAPI.ts: Detect if schema is already JSON (has 'type' property) vs Zod schema before conversion

FILES:
• src/services/claude.ts - Critical bug fix for OpenAI response content extraction
• src/services/adapters/responsesAPI.ts - Robust schema detection for tool parameters
• src/test/integration-cli-flow.test.ts - Integration tests for full flow
• src/test/chat-completions-e2e.test.ts - End-to-end Chat Completions compatibility tests
• src/test/production-api-tests.test.ts - Production API tests with environment configuration
• docs/develop/modules/openai-adapters.md - New adapter system documentation
• docs/develop/README.md - Updated development documentation
2025-11-09 18:41:29 -08:00
Radon Co
7069893d14 feat(responses-api): Support OpenAI Responses API with proper parameter mapping
WHAT: Add support for OpenAI Responses API in Kode CLI adapter
WHY: Enable GPT-5 and similar models that require Responses API instead of Chat Completions; fix HTTP 400 errors and schema conversion failures
HOW: Fixed tool format to use flat structure matching API spec; added missing critical parameters (include array, parallel_tool_calls, store, tool_choice); implemented robust schema conversion handling both Zod and pre-built JSON schemas; added array-based content parsing for Anthropic compatibility; created comprehensive integration tests exercising the full claude.ts flow

AFFECTED FILES:
- src/services/adapters/responsesAPI.ts: Complete adapter implementation
- src/services/openai.ts: Simplified request handling
- src/test/integration-cli-flow.test.ts: New integration test suite
- src/test/responses-api-e2e.test.ts: Enhanced with production test capability

VERIFICATION:
- Integration tests pass: bun test src/test/integration-cli-flow.test.ts
- Production tests: PRODUCTION_TEST_MODE=true bun test src/test/responses-api-e2e.test.ts
2025-11-09 14:22:43 -08:00
Radon Co
3c9b0ec9d1 prompt(api): Add OpenAI Responses API support with SSE streaming
WHAT: Implement Responses API adapter with full SSE streaming support to enable Kode CLI working with GPT-5 and other models that require OpenAI Responses API format

WHY: GPT-5 and newer models use OpenAI Responses API (different from Chat Completions) which returns streaming SSE responses. Kode CLI needed a conversion layer to translate between Anthropic API format and OpenAI Responses API format for seamless model integration

HOW: Created ResponsesAPIAdapter that converts Anthropic UnifiedRequestParams to Responses API format (instructions, input array, max_output_tokens, stream=true), added SSE parser to collect streaming chunks and convert back to UnifiedResponse format. Fixed ModelAdapterFactory to properly select Responses API for GPT-5 models. Updated parseResponse to async across all adapters. Added production tests validating end-to-end conversion with actual API calls
2025-11-09 01:29:04 -08:00
Xinlu Lai
a4c3f16c2b
not coding, kode is everything 2025-11-05 03:09:11 +08:00
Xinlu Lai
e50c6f52f6
Merge pull request #107 from majiang213/main
fix: Fixed the issue where the build script would not create cli.js
2025-10-10 01:28:02 +08:00
Xinlu Lai
893112e43c
Merge pull request #110 from Mriris/main
fix: Ollama on Windows
2025-10-10 01:26:37 +08:00
若辰
f934cfa62e
Merge branch 'shareAI-lab:main' into main 2025-10-08 13:14:03 +08:00
Xinlu Lai
1b3b0786ca
Merge pull request #106 from wxhzzsf/wxh/qeury-anthropic-native-mcp-input-schema-fix
fix: add mcp support for anthropic native tool input schema
2025-10-07 13:48:52 +08:00
若辰
f486925c06 fix(PersistentShell): remove login shell arguments for MSYS and ensure correct CWD updates 2025-10-06 14:05:14 +08:00
若辰
ab0d3f26f3 fix(PersistentShell): improve command execution for WSL and MSYS environments 2025-10-06 13:47:28 +08:00
若辰
70f0d6b109 feat(ModelSelector): model context 2025-10-06 13:18:05 +08:00
若辰
451362256c fix(ModelSelector): ollama 2025-10-05 23:59:44 +08:00
MJ
fd1d6e385d fix: Fixed the issue where the build script would not create cli.js 2025-09-29 18:28:28 +08:00
Xiaohan Wang
ce8c8dad63 fix: add mcp support for anthropic native tool input schema 2025-09-29 16:55:43 +08:00
CrazyBoyM
b847352101 refactor: use tsconfig aliases throughout 2025-09-20 15:14:39 +08:00
CrazyBoyM
61a8ce0d22 clean code 2025-09-20 15:14:38 +08:00
CrazyBoyM
59dce97350 fix & remove ugly code 2025-09-20 15:14:38 +08:00
CrazyBoyM
d4abb2abee clean code 2025-09-20 15:14:38 +08:00
Xinlu Lai
78b49355cd
Merge pull request #89 from glide-the/update_build_docker
chore(docker): update Dockerfile to copy built application from dist …
2025-09-14 15:02:03 +08:00
glide-the
fbb2db6963 chore(docker): update Dockerfile to copy built application from dist and set entrypoint correctly\n\n- Adjusted COPY command to reference the new dist directory\n- Updated entrypoint to use the correct path for cli.js 2025-09-13 22:52:39 +08:00
CrazyBoyM
d0d1dca009 upgrade for win & remove some un-use code 2025-09-12 00:44:15 +08:00
CrazyBoyM
6bbaa6c559 fix win 2025-09-11 09:57:38 +08:00
CrazyBoyM
b0d9f58d76 fix(multi-edit): pass structuredPatch to update UI; harden FileEditToolUpdatedMessage against undefined patches\n\n- MultiEditTool: compute diff via getPatch and render standard update message\n- FileEditToolUpdatedMessage: treat missing structuredPatch as empty to avoid crash\n- Preserves existing feature and UI while eliminating reduce-on-undefined 2025-09-10 15:33:58 +08:00
CrazyBoyM
a7af9834ef chore(update): disable auto-update flow; keep version-check banner only\n\n- Remove AutoUpdater UI and NPM prefix/permissions flows\n- Simplify Doctor to passive health-check\n- Keep only getLatestVersion/assertMinVersion/update suggestions\n- Clean REPL/PromptInput to avoid extra renders and flicker\n- No hardcoding; no auto-install; docstrings tidy 2025-09-10 14:22:33 +08:00
Xinlu Lai
53234ba2d9
Merge pull request #67 from xiaomao87/fix-dockerfile
Dockerfile: fix runtime error `Module not found "/app/src/entrypoints/cli.tsx"`
2025-09-03 00:49:12 +08:00
Xinlu Lai
20111a0a26
Update README.zh-CN.md 2025-09-03 00:48:03 +08:00
Xinlu Lai
f857fb9577
Update README.md 2025-09-03 00:47:22 +08:00
dev
da15ca5a5f Dockerfile: fix runtime error Module not found "/app/src/entrypoints/cli.tsx" 2025-09-02 16:22:22 +08:00
Xinlu Lai
f23787f50d
Merge pull request #60 from burncloud/feat/burncloud
 feat: add BurnCloud as new AI model provider
2025-09-01 02:59:33 +08:00
Wei
32a9badecd feat: add new model provider BurnCloud fix default models 2025-08-30 16:01:14 +08:00
Xinlu Lai
5471b2d5fc
Update README.md 2025-08-30 01:38:51 +08:00
Xinlu Lai
97684a3208
Update README.md 2025-08-30 01:29:04 +08:00
Wei
efe00eee3b feat: add new model provider BurnCloud 2025-08-28 17:04:34 +08:00
260 changed files with 4491 additions and 9013 deletions

18
.env.example Normal file
View File

@ -0,0 +1,18 @@
# Environment Variables for Production API Tests
# Copy this file to .env and fill in your actual API keys
# Enable production test mode
PRODUCTION_TEST_MODE=true
# GPT-5 Codex Test Configuration
TEST_GPT5_API_KEY=your_gpt5_api_key_here
TEST_GPT5_BASE_URL=http://127.0.0.1:3000/openai
# MiniMax Codex Test Configuration
TEST_MINIMAX_API_KEY=your_minimax_api_key_here
TEST_MINIMAX_BASE_URL=https://api.minimaxi.com/v1
# WARNING:
# - Never commit .env files to version control!
# - The .env file is already in .gitignore
# - API keys should be kept secret and secure

View File

@ -1,22 +1,19 @@
module.exports = {
root: true,
env: { node: true, es2022: true },
parser: '@typescript-eslint/parser',
parserOptions: { ecmaVersion: 'latest', sourceType: 'module', ecmaFeatures: { jsx: true } },
plugins: ['@typescript-eslint', 'react', 'react-hooks'],
extends: [
'eslint:recommended',
'plugin:@typescript-eslint/recommended',
'plugin:react/recommended',
'plugin:react-hooks/recommended',
'prettier',
'@typescript-eslint/recommended',
],
settings: { react: { version: 'detect' } },
ignorePatterns: ['dist/**', 'node_modules/**'],
rules: {
'react/react-in-jsx-scope': 'off',
'@typescript-eslint/no-explicit-any': 'off',
'@typescript-eslint/ban-ts-comment': 'off',
parser: '@typescript-eslint/parser',
plugins: ['@typescript-eslint'],
root: true,
env: {
node: true,
es2022: true,
},
}
rules: {
'@typescript-eslint/no-unused-vars': ['error', { argsIgnorePattern: '^_' }],
'@typescript-eslint/no-explicit-any': 'warn',
'@typescript-eslint/no-non-null-assertion': 'off',
},
ignorePatterns: ['dist/', 'node_modules/', 'cli.js'],
}

7
.husky/pre-commit Normal file → Executable file
View File

@ -1,8 +1,5 @@
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"
# Temporarily disabled - no lint-staged configuration
# npx lint-staged
echo "Pre-commit hook: skipping lint-staged (not configured)"
bun run format:check
bun run typecheck

View File

@ -1,7 +0,0 @@
{
"singleQuote": true,
"semi": false,
"trailingComma": "all",
"printWidth": 100
}

View File

@ -1,137 +0,0 @@
# TypeScript 错误修复 - 快速参考指南
## 🚨 重要:必须先完成 Step 0
在开始任何并行任务之前,**必须完成 `step_0_foundation_serial.md`**。这是所有其他修复的基础。
## 📁 文件结构
```
.sub_task/
├── README.md # 本文件
├── execution_plan.md # 总体执行计划和依赖关系
├── step_0_foundation_serial.md # ⚠️ 必须首先完成(串行)
├── step_1_parallel_worker_0.md # 工具修复ArchitectTool, FileReadTool
├── step_1_parallel_worker_1.md # 工具修复FileWriteTool, FileEditTool
├── step_1_parallel_worker_2.md # 工具修复TaskTool, MultiEditTool
├── step_1_parallel_worker_3.md # 工具修复:其他工具
├── step_2_parallel_worker_0.md # React 组件修复
├── step_2_parallel_worker_1.md # Hook 系统修复
└── step_2_parallel_worker_2.md # Service 层修复
```
## 🎯 快速开始
### 单人执行
```bash
# 1. 完成基础修复
# 打开 step_0_foundation_serial.md 并按步骤执行
# 2. 检查进度
npx tsc --noEmit 2>&1 | wc -l
# 3. 依次完成 Step 1 的 4 个任务
# 4. 依次完成 Step 2 的 3 个任务
# 5. 最终验证
bun run dev
```
### 多人协作
```bash
# 人员 A负责 Step 0独自完成
# 完成后通知其他人
# Step 0 完成后,分配任务:
# 人员 Bstep_1_parallel_worker_0.md
# 人员 Cstep_1_parallel_worker_1.md
# 人员 Dstep_1_parallel_worker_2.md
# 人员 Estep_1_parallel_worker_3.md
# 人员 Fstep_2_parallel_worker_0.md
# 人员 Gstep_2_parallel_worker_1.md
# 人员 Hstep_2_parallel_worker_2.md
```
## 📊 进度监控
### 实时错误计数
```bash
# 查看当前错误总数
npx tsc --noEmit 2>&1 | wc -l
# 查看错误分布
npx tsc --noEmit 2>&1 | grep -oE "src/[^(]*" | cut -d: -f1 | xargs -I {} dirname {} | sort | uniq -c | sort -rn
```
### 特定文件检查
```bash
# 检查特定工具的错误
npx tsc --noEmit 2>&1 | grep "FileWriteTool"
# 检查特定目录的错误
npx tsc --noEmit 2>&1 | grep "src/tools/"
```
## ✅ 完成标准
每个任务文档都有详细的"完成标志"部分。确保:
1. 所有复选框都已勾选
2. TypeScript 错误减少到预期数量
3. 功能测试通过
## 🔧 常用命令
```bash
# 开发模式运行
bun run dev
# TypeScript 检查
npx tsc --noEmit
# 查看具体错误
npx tsc --noEmit --pretty
# 测试特定功能
bun test
# 格式化代码
bun run format
```
## ⚠️ 注意事项
1. **不要跳过 Step 0** - 这会导致后续所有任务失败
2. **保持原有功能** - 只修复类型,不改变业务逻辑
3. **频繁保存** - 使用 git 定期提交进度
4. **遇到问题** - 参考每个文档的"常见问题"部分
## 📈 预期成果
| 里程碑 | 错误数 | 完成百分比 |
|--------|--------|-----------|
| 初始状态 | 127 | 0% |
| Step 0 完成 | ~80 | 37% |
| Step 1 完成 | ~40 | 69% |
| Step 2 完成 | ~15 | 88% |
| 最终清理 | 0 | 100% |
## 🆘 获取帮助
如果遇到无法解决的问题:
1. 检查对应任务文档的"常见问题"部分
2. 查看 `execution_plan.md` 的风险部分
3. 使用文档中提供的调试技巧
4. 记录问题供高级开发者处理
## 🎉 完成后
所有任务完成后:
1. 运行完整测试套件
2. 检查没有运行时错误
3. 更新 `tasks.md` 标记所有任务完成
4. 庆祝成功!🎊
---
**记住:质量比速度更重要。宁可慢一点,也要确保每个修复都正确。**

View File

@ -1,171 +0,0 @@
# TypeScript 错误修复执行计划
## 任务依赖关系图
```mermaid
graph TD
A[Step 0: Foundation - SERIAL<br/>必须首先完成] --> B1[Step 1 Worker 0<br/>ArchitectTool & FileReadTool]
A --> B2[Step 1 Worker 1<br/>FileWriteTool & FileEditTool]
A --> B3[Step 1 Worker 2<br/>TaskTool & MultiEditTool]
A --> B4[Step 1 Worker 3<br/>Other Tools]
A --> C1[Step 2 Worker 0<br/>React Components]
A --> C2[Step 2 Worker 1<br/>Hook System]
A --> C3[Step 2 Worker 2<br/>Service Layer]
B1 --> D[Step 3: Final Validation]
B2 --> D
B3 --> D
B4 --> D
C1 --> D
C2 --> D
C3 --> D
```
## 执行顺序
### 🔴 Phase 0: 串行任务 (必须先完成)
**时间**: 1-2 小时
**文件**: `step_0_foundation_serial.md`
此任务建立所有其他修复的基础:
- 安装缺失依赖 (sharp)
- 创建类型增强文件
- 修复核心 Message 和 Tool 类型
- 修复 Key 类型扩展
**重要**: 在此任务完成前,不要开始任何其他任务!
### 🟢 Phase 1: 并行任务组 A (Step 1)
**时间**: 2-3 小时(并行执行)
**可同时分配给 4 个工作者**
| Worker | 文件 | 负责内容 | 预计时间 |
|--------|------|---------|----------|
| 0 | `step_1_parallel_worker_0.md` | ArchitectTool, FileReadTool | 60分钟 |
| 1 | `step_1_parallel_worker_1.md` | FileWriteTool, FileEditTool | 60分钟 |
| 2 | `step_1_parallel_worker_2.md` | TaskTool, MultiEditTool | 85分钟 |
| 3 | `step_1_parallel_worker_3.md` | StickerRequestTool, NotebookReadTool, AskExpertModelTool | 70分钟 |
### 🟢 Phase 2: 并行任务组 B (Step 2)
**时间**: 1-2 小时(并行执行)
**可同时分配给 3 个工作者**
| Worker | 文件 | 负责内容 | 预计时间 |
|--------|------|---------|----------|
| 0 | `step_2_parallel_worker_0.md` | React 19/Ink 6 组件修复 | 80分钟 |
| 1 | `step_2_parallel_worker_1.md` | Hook 系统修复 | 60分钟 |
| 2 | `step_2_parallel_worker_2.md` | Service 层和入口点修复 | 65分钟 |
### 🔵 Phase 3: 最终验证
**时间**: 30分钟
**所有并行任务完成后执行**
1. 运行完整的 TypeScript 检查
2. 测试所有主要功能
3. 记录剩余问题(如果有)
## 任务分配建议
### 如果有 1 个开发者
1. 按顺序执行Step 0 → Step 1 (worker 0-3) → Step 2 (worker 0-2)
2. 总时间:约 6-8 小时
### 如果有 2 个开发者
1. 开发者 AStep 0 → Step 1 Worker 0 & 1 → Step 2 Worker 0
2. 开发者 B等待 Step 0 → Step 1 Worker 2 & 3 → Step 2 Worker 1 & 2
3. 总时间:约 4-5 小时
### 如果有 4+ 个开发者
1. 开发者 AStep 0独自完成
2. 其他开发者:等待 Step 0 完成
3. Step 0 完成后:
- 开发者 B-E各自领取 Step 1 的一个 worker 任务
- 开发者 A, F, G各自领取 Step 2 的一个 worker 任务
4. 总时间:约 2-3 小时
## 进度跟踪
使用以下命令跟踪进度:
```bash
# 检查当前错误数量
npx tsc --noEmit 2>&1 | wc -l
# 检查特定步骤的错误
npx tsc --noEmit 2>&1 | grep "FileWriteTool" # 示例
# 运行测试
bun run dev
```
## 预期结果
| 阶段 | 预期错误减少 | 剩余错误 |
|------|------------|----------|
| 初始状态 | - | 127 |
| Step 0 完成 | 40-50 | 77-87 |
| Step 1 完成 | 35-45 | 32-52 |
| Step 2 完成 | 25-35 | 7-27 |
| 最终清理 | 7-27 | 0 |
## 风险和缓解措施
### 风险 1: Step 0 未正确完成
**影响**: 所有后续任务都会遇到类型错误
**缓解**: 严格验证 Step 0 的完成标志
### 风险 2: 并行任务冲突
**影响**: 同时修改相同文件导致冲突
**缓解**: 每个 worker 负责独立的文件集
### 风险 3: 运行时错误
**影响**: 类型修复可能引入运行时问题
**缓解**: 每个阶段后进行功能测试
## 通信协议
### 任务开始
```
Worker X 开始 Step Y Worker Z
预计完成时间HH:MM
```
### 遇到问题
```
Worker X 遇到阻塞问题:
- 问题描述
- 尝试的解决方案
- 需要的帮助
```
### 任务完成
```
Worker X 完成 Step Y Worker Z
- 修复错误数N
- 剩余问题:[列表]
- 测试结果:[通过/失败]
```
## 质量检查清单
每个任务完成后检查:
- [ ] TypeScript 编译无错误(针对负责的文件)
- [ ] 功能测试通过
- [ ] 没有引入新的错误
- [ ] 代码格式正确
- [ ] 没有遗留的 TODO 或临时代码
## 最终交付标准
1. **零 TypeScript 错误**
2. **所有功能正常工作**
3. **没有运行时警告**
4. **代码可维护性未降低**
5. **性能无明显影响**
## 备注
- 每个 worker 文档都是自包含的,可以独立执行
- 如果某个任务比预期复杂,记录问题供后续优化
- 保持 git commits 小而频繁,便于回滚

View File

@ -1,239 +0,0 @@
# Step 0: Foundation Type System Fix (MUST COMPLETE FIRST - SERIAL)
## 项目背景
本项目是 Kode CLI 工具,基于 TypeScript + React (Ink 6) 构建的命令行界面。最近升级到 React 19 和 Ink 6 后出现了 127 个 TypeScript 编译错误。本任务是修复基础类型系统,为后续并行修复打下基础。
## 任务目标
修复核心类型定义,使得其他所有模块可以基于正确的类型定义进行修复。
## 系统架构概览
```
Kode CLI
├── src/
│ ├── messages.ts - 消息类型定义
│ ├── Tool.ts - 工具基类接口
│ ├── types/ - 类型定义目录
│ ├── query.ts - 查询系统
│ ├── utils/ - 工具函数
│ │ └── messageContextManager.ts
│ └── hooks/ - React hooks
│ └── useTextInput.ts
```
## 施工步骤
### Phase 1: 安装缺失依赖 (5分钟)
#### Step 1.1: 安装 sharp 图像处理库
**文件**: package.json
**执行命令**:
```bash
bun add sharp
bun add -d @types/sharp
```
**验证**: 运行 `bun install` 确认无错误
### Phase 2: 创建类型增强文件 (10分钟)
#### Step 2.1: 创建 Ink 类型增强
**创建文件**: `src/types/ink-augmentation.d.ts`
**精确内容**:
```typescript
// Ink Key 类型增强 - 添加缺失的键盘属性
declare module 'ink' {
interface Key {
fn?: boolean;
home?: boolean;
end?: boolean;
space?: boolean;
}
}
```
#### Step 2.2: 创建通用类型定义
**创建文件**: `src/types/common.d.ts`
**精确内容**:
```typescript
// UUID 类型定义
export type UUID = `${string}-${string}-${string}-${string}-${string}`;
// 扩展的工具上下文
export interface ExtendedToolUseContext extends ToolUseContext {
setToolJSX: (jsx: React.ReactElement) => void;
}
```
### Phase 3: 修复消息类型系统 (20分钟)
#### Step 3.1: 修复 Message 类型定义
**修改文件**: `src/messages.ts`
**查找内容** (大约在 50-100 行之间):
```typescript
export interface ProgressMessage {
type: 'progress'
// ... existing properties
}
```
**替换为**:
```typescript
export interface ProgressMessage {
type: 'progress'
message?: any // 添加可选的 message 属性以兼容旧代码
content: AssistantMessage
normalizedMessages: NormalizedMessage[]
siblingToolUseIDs: Set<string>
tools: Tool<any, any>[]
toolUseID: string
uuid: UUID
}
```
#### Step 3.2: 修复 query.ts 中的消息访问
**修改文件**: `src/query.ts`
**查找内容** (第 203-210 行):
```typescript
message: msg.message
```
**替换为**:
```typescript
// 使用类型守卫安全访问
...(msg.type !== 'progress' && 'message' in msg ? { message: msg.message } : {})
```
#### Step 3.3: 修复 messageContextManager.ts
**修改文件**: `src/utils/messageContextManager.ts`
**查找内容** (第 136 行附近):
```typescript
return {
type: "assistant",
message: { role: "assistant", content: [...] }
}
```
**替换为**:
```typescript
return {
type: "assistant",
message: { role: "assistant", content: [...] },
costUSD: 0,
durationMs: 0,
uuid: crypto.randomUUID() as UUID
}
```
**注意**: 需要在文件顶部添加导入:
```typescript
import type { UUID } from '../types/common';
```
### Phase 4: 修复 Tool 接口定义 (15分钟)
#### Step 4.1: 更新 Tool 基础接口
**修改文件**: `src/Tool.ts`
**查找内容**:
```typescript
export interface Tool<TInput, TOutput> {
renderResultForAssistant(output: TOutput): string;
renderToolUseRejectedMessage(): React.ReactElement;
// ...
}
```
**替换为**:
```typescript
export interface Tool<TInput, TOutput> {
renderResultForAssistant(output: TOutput): string | any[];
renderToolUseRejectedMessage?(...args: any[]): React.ReactElement;
// ...其他属性保持不变
}
```
#### Step 4.2: 更新 ToolUseContext
**修改文件**: `src/Tool.ts` (或者在同一文件中查找 ToolUseContext)
**查找内容**:
```typescript
export interface ToolUseContext {
// existing properties
}
```
**替换为**:
```typescript
export interface ToolUseContext {
// ...existing properties
setToolJSX?: (jsx: React.ReactElement) => void;
messageId?: string;
agentId?: string;
}
```
### Phase 5: 修复其他基础类型问题 (10分钟)
#### Step 5.1: 修复 thinking.ts 枚举值
**修改文件**: `src/utils/thinking.ts`
**查找内容** (第 115 行):
```typescript
"low" | "medium" | "high" | "minimal"
```
**替换为**:
```typescript
"low" | "medium" | "high"
```
#### Step 5.2: 移除无用的 @ts-expect-error
**修改文件列表及行号**:
1. `src/entrypoints/cli.tsx` - 删除第 318 行
2. `src/hooks/useDoublePress.ts` - 删除第 33 行
3. `src/hooks/useTextInput.ts` - 删除第 143 行
4. `src/utils/messages.tsx` - 删除第 301 行
**操作**: 直接删除包含 `// @ts-expect-error` 的整行
### Phase 6: 验证基础修复 (5分钟)
#### Step 6.1: 运行类型检查
```bash
npx tsc --noEmit 2>&1 | wc -l
```
**预期结果**: 错误数量应该从 127 减少到 70-80 左右
#### Step 6.2: 检查特定文件错误
```bash
npx tsc --noEmit 2>&1 | grep "src/messages.ts"
npx tsc --noEmit 2>&1 | grep "src/query.ts"
npx tsc --noEmit 2>&1 | grep "src/Tool.ts"
```
**预期结果**: 这些文件不应再有错误
#### Step 6.3: 测试运行时
```bash
bun run dev
# 输入 /help 测试基础功能
```
**预期结果**: CLI 应该能启动,基础命令能运行
## 完成标志
- [ ] sharp 依赖已安装
- [ ] ink-augmentation.d.ts 已创建
- [ ] Message 类型错误已修复
- [ ] Tool 接口已更新
- [ ] 无用的 @ts-expect-error 已移除
- [ ] TypeScript 错误减少 40% 以上
- [ ] 基础 CLI 功能可运行
## 注意事项
1. **不要修改功能逻辑**,只修复类型定义
2. **保留原有注释**,只添加必要的类型注释
3. **使用 git diff 检查改动**,确保没有意外修改
4. **每个文件修改后立即保存**,避免丢失工作
## 如果遇到问题
1. 检查文件路径是否正确
2. 确认 bun 和 npm 都已安装
3. 如果找不到指定代码,使用 grep 搜索:
```bash
grep -n "ProgressMessage" src/messages.ts
```
4. 保存修改前的文件备份:
```bash
cp src/messages.ts src/messages.ts.backup
```
## 完成后
将此文档标记为完成,然后可以开始 Step 1 的并行任务。

View File

@ -1,237 +0,0 @@
# Step 1 - Worker 0: ArchitectTool & FileReadTool 修复
## 前置条件
**必须先完成 step_0_foundation_serial.md 的所有任务**
## 项目背景
Kode CLI 的工具系统采用插件架构,每个工具都实现 Tool 接口。本任务负责修复 ArchitectTool 和 FileReadTool 的类型错误。
## 系统架构上下文
```
src/tools/
├── ArchitectTool/
│ ├── ArchitectTool.tsx - 架构分析工具
│ └── prompt.ts - 工具提示词
├── FileReadTool/
│ ├── FileReadTool.tsx - 文件读取工具
│ └── prompt.ts - 工具提示词
```
## 任务目标
1. 修复 ArchitectTool 的返回类型不匹配问题
2. 修复 FileReadTool 的图像处理返回类型问题
3. 确保两个工具都能正确编译和运行
## 详细施工步骤
### Phase 1: 修复 ArchitectTool (30分钟)
#### Step 1.1: 修复 renderResultForAssistant 返回类型
**文件**: `src/tools/ArchitectTool/ArchitectTool.tsx`
**定位方法**: 搜索 `renderResultForAssistant`
**当前问题**: 返回 string | array但接口期望只返回 string
**查找代码** (大约在第 100-150 行):
```typescript
renderResultForAssistant(data: { type: "text"; file: {...} } | { type: "image"; file: {...} }) {
if (data.type === "image") {
return [{
type: "image",
source: {
type: "base64",
data: data.file.base64,
media_type: data.file.type,
},
}];
}
return `File content...`;
}
```
**修复方案**: 由于 Tool 接口已在 Step 0 中更新为允许 string | any[],这里不需要修改,只需确认导入正确。
#### Step 1.2: 修复 renderToolUseRejectedMessage 签名
**文件**: `src/tools/ArchitectTool/ArchitectTool.tsx`
**定位**: 搜索第二个工具定义FileWriteTool 部分)
**查找代码** (大约在第 200-250 行):
```typescript
renderToolUseRejectedMessage({ file_path, content }, { columns, verbose }) {
// ...
}
```
**替换为**:
```typescript
renderToolUseRejectedMessage({ file_path, content }: any = {}, { columns, verbose }: any = {}) {
// 如果函数体使用了这些参数,保持不变
// 如果没使用,可以简化为:
// renderToolUseRejectedMessage() {
}
```
#### Step 1.3: 修复 call 方法签名
**文件**: `src/tools/ArchitectTool/ArchitectTool.tsx`
**定位**: 搜索第三个工具定义(通常在文件末尾)
**查找代码** (大约在第 60 行):
```typescript
call: async function* ({ prompt, context }, toolUseContext, canUseTool) {
```
**替换为**:
```typescript
call: async function* ({ prompt, context }: any, toolUseContext: any) {
// 移除第三个参数 canUseTool如果函数体内使用了它需要调整逻辑
```
#### Step 1.4: 修复第三个工具的 renderResultForAssistant
**文件**: `src/tools/ArchitectTool/ArchitectTool.tsx`
**查找代码** (大约在第 101 行):
```typescript
renderResultForAssistant: (data: TextBlock[]) => data,
```
**替换为**:
```typescript
renderResultForAssistant: (data: TextBlock[]) => JSON.stringify(data),
```
### Phase 2: 修复 FileReadTool (30分钟)
#### Step 2.1: 修复图像返回类型
**文件**: `src/tools/FileReadTool/FileReadTool.tsx`
**定位**: 搜索 `renderResultForAssistant`
**查找代码** (大约在第 255 行):
```typescript
renderResultForAssistant(data) {
if (data.type === "image") {
return [{
type: "image",
source: {
type: "base64",
data: data.file.base64,
media_type: data.file.type,
},
}];
}
// ... text handling
}
```
**修复**: 由于 Tool 接口已支持 string | any[],此处不需要修改
#### Step 2.2: 处理 sharp 模块导入
**文件**: `src/tools/FileReadTool/FileReadTool.tsx`
**定位**: 搜索 `import.*sharp` (大约在第 319 行)
**查找代码**:
```typescript
import sharp from 'sharp';
```
**修复方案 1 - 动态导入** (推荐):
```typescript
// 删除顶部的 import sharp from 'sharp';
// 在使用处改为动态导入
const sharp = await import('sharp').catch(() => null);
if (!sharp) {
throw new Error('Sharp module not available');
}
```
**修复方案 2 - 条件导入**:
```typescript
let sharp: any;
try {
sharp = require('sharp');
} catch {
sharp = null;
}
```
#### Step 2.3: 添加类型声明
**文件**: `src/tools/FileReadTool/FileReadTool.tsx`
**在文件顶部添加**:
```typescript
import type { ImageBlockParam } from '@anthropic-ai/sdk';
// 如果 ImageBlockParam 不存在,使用:
type ImageBlockParam = {
Source: 'image/jpeg' | 'image/png' | 'image/gif' | 'image/webp';
};
```
### Phase 3: 验证修复 (10分钟)
#### Step 3.1: 单独检查 ArchitectTool 错误
```bash
npx tsc --noEmit 2>&1 | grep "ArchitectTool"
```
**预期结果**: 无输出或错误数量显著减少
#### Step 3.2: 单独检查 FileReadTool 错误
```bash
npx tsc --noEmit 2>&1 | grep "FileReadTool"
```
**预期结果**: 无输出或只有 sharp 相关警告
#### Step 3.3: 测试工具功能
```bash
# 启动 CLI
bun run dev
# 测试文件读取
# 输入: read package.json
# 测试架构分析(如果有此命令)
# 输入: analyze src/Tool.ts
```
### Phase 4: 处理遗留问题 (10分钟)
#### Step 4.1: 如果还有类型错误
1. 检查是否正确导入了更新后的 Tool 接口:
```typescript
import { Tool } from '../../Tool';
```
2. 确认 ToolUseContext 类型正确:
```typescript
import type { ToolUseContext } from '../../Tool';
```
3. 对于复杂的类型错误,可以临时使用 any
```typescript
// 临时解决方案,标记 TODO
// TODO: 正确类型化此处
const result = someComplexOperation() as any;
```
## 完成标志
- [ ] ArchitectTool 编译无错误
- [ ] FileReadTool 编译无错误
- [ ] sharp 导入问题已解决
- [ ] 两个工具的基础功能可运行
- [ ] TypeScript 错误减少至少 10 个
## 注意事项
1. **保持功能不变** - 只修复类型,不改变业务逻辑
2. **保留原有注释** - 不要删除现有的代码注释
3. **测试每个修改** - 每次修改后运行 tsc 检查
4. **使用版本控制** - 定期 git add 保存进度
## 常见问题解决
### Q: 找不到 Tool 接口定义?
```bash
find src -name "*.ts" -o -name "*.tsx" | xargs grep "export interface Tool"
```
### Q: ImageBlockParam 类型不存在?
创建本地类型定义:
```typescript
// 在文件顶部添加
interface ImageBlockParam {
Source: string;
}
```
### Q: sharp 模块一直报错?
确认已安装:
```bash
bun add sharp
bun add -d @types/sharp
```
## 完成后
标记此任务完成,可以继续其他 worker 的并行任务。

View File

@ -1,286 +0,0 @@
# Step 1 - Worker 1: FileWriteTool & FileEditTool 修复
## 前置条件
**必须先完成 step_0_foundation_serial.md 的所有任务**
## 项目背景
这两个工具负责文件的写入和编辑操作,是 Kode CLI 的核心功能。它们都有相似的权限请求界面和错误处理逻辑。
## 系统架构上下文
```
src/tools/
├── FileWriteTool/
│ ├── FileWriteTool.tsx - 文件写入工具
│ └── prompt.ts - 工具提示词
├── FileEditTool/
│ ├── FileEditTool.tsx - 文件编辑工具
│ ├── prompt.ts - 工具提示词
│ └── utils.ts - 工具函数
```
## 任务目标
1. 修复两个工具的 renderToolUseRejectedMessage 签名问题
2. 修复 renderToolResultMessage 签名问题
3. 确保文件操作权限流程正常
## 详细施工步骤
### Phase 1: 修复 FileWriteTool (25分钟)
#### Step 1.1: 修复 renderToolUseRejectedMessage 签名
**文件**: `src/tools/FileWriteTool/FileWriteTool.tsx`
**定位**: 搜索 `renderToolUseRejectedMessage` (大约第 70 行)
**当前代码**:
```typescript
renderToolUseRejectedMessage({ file_path, content }, { columns, verbose }) {
return <FileWritePermissionRejected ... />;
}
```
**修复为**:
```typescript
renderToolUseRejectedMessage(input?: any, options?: any) {
// 如果函数体需要这些参数
const { file_path, content } = input || {};
const { columns, verbose } = options || {};
return <FileWritePermissionRejected ... />;
}
```
**或者如果接口允许可选**:
```typescript
renderToolUseRejectedMessage() {
// 简化版本,如果不需要参数
return <FileWritePermissionRejected />;
}
```
#### Step 1.2: 修复 renderToolResultMessage 签名
**文件**: `src/tools/FileWriteTool/FileWriteTool.tsx`
**定位**: 搜索 `renderToolResultMessage` (大约第 122 行)
**当前代码**:
```typescript
renderToolResultMessage({ filePath, content, structuredPatch, type }, { verbose }) {
return <FileWriteResultMessage ... />;
}
```
**修复为**:
```typescript
renderToolResultMessage(output: any) {
const { filePath, content, structuredPatch, type } = output;
// 注意:第二个参数 verbose 可能需要从其他地方获取
return <FileWriteResultMessage ... />;
}
```
#### Step 1.3: 导入必要的类型
**文件**: `src/tools/FileWriteTool/FileWriteTool.tsx`
**在文件顶部添加**:
```typescript
import type { Tool, ToolUseContext } from '../../Tool';
import type { Hunk } from 'diff';
```
#### Step 1.4: 修复组件导入
**检查导入部分**:
```typescript
// 确保这些组件存在并正确导入
import { FileWritePermissionRejected } from '../../components/permissions/FileWritePermissionRequest';
import { FileWriteResultMessage } from '../../components/messages/FileWriteResultMessage';
```
### Phase 2: 修复 FileEditTool (25分钟)
#### Step 2.1: 修复 renderToolUseRejectedMessage 签名
**文件**: `src/tools/FileEditTool/FileEditTool.tsx`
**定位**: 搜索 `renderToolUseRejectedMessage` (大约第 78 行)
**当前代码**:
```typescript
renderToolUseRejectedMessage({ file_path, old_string, new_string }, { columns, verbose }) {
return <FileEditPermissionRejected ... />;
}
```
**修复为**:
```typescript
renderToolUseRejectedMessage(input?: any, options?: any) {
const { file_path, old_string, new_string } = input || {};
const { columns, verbose } = options || {};
return <FileEditPermissionRejected ... />;
}
```
#### Step 2.2: 检查 validateInput 方法
**文件**: `src/tools/FileEditTool/FileEditTool.tsx`
**定位**: 搜索 `validateInput`
**确保签名正确**:
```typescript
async validateInput(
{ file_path, old_string, new_string }: any,
{ readFileTimestamps }: ToolUseContext
): Promise<{ result: boolean; message?: string }> {
// 实现...
}
```
#### Step 2.3: 检查 call 方法
**文件**: `src/tools/FileEditTool/FileEditTool.tsx`
**定位**: 搜索 `call:`
**确保异步生成器签名正确**:
```typescript
async *call(
{ file_path, old_string, new_string }: any,
context: ToolUseContext
): AsyncGenerator<{ type: "result"; data: any; resultForAssistant: string }> {
// 实现...
yield {
type: "result",
data: result,
resultForAssistant: `File ${file_path} updated successfully`
};
}
```
#### Step 2.4: 修复工具导出
**文件**: `src/tools/FileEditTool/FileEditTool.tsx`
**在文件末尾确认**:
```typescript
export const FileEditTool: Tool<any, any> = {
name: "file_edit",
description: async () => "Edit files",
// ... 所有必需的方法
};
```
### Phase 3: 修复共享组件和工具函数 (15分钟)
#### Step 3.1: 检查权限组件
**文件**: `src/components/permissions/FileWritePermissionRequest/index.tsx`
**确保导出了**:
```typescript
export { FileWritePermissionRejected } from './FileWritePermissionRejected';
```
**文件**: `src/components/permissions/FileEditPermissionRequest/index.tsx`
**确保导出了**:
```typescript
export { FileEditPermissionRejected } from './FileEditPermissionRejected';
```
#### Step 3.2: 修复 utils.ts (如果存在)
**文件**: `src/tools/FileEditTool/utils.ts`
**检查函数签名**:
```typescript
export function applyEdit(
content: string,
oldString: string,
newString: string,
replaceAll: boolean = false
): { updatedContent: string; occurrences: number } {
// 确保返回值包含所需属性
}
```
### Phase 4: 验证修复 (10分钟)
#### Step 4.1: 检查 FileWriteTool 错误
```bash
npx tsc --noEmit 2>&1 | grep "FileWriteTool"
```
**预期**: 无错误或错误显著减少
#### Step 4.2: 检查 FileEditTool 错误
```bash
npx tsc --noEmit 2>&1 | grep "FileEditTool"
```
**预期**: 无错误或错误显著减少
#### Step 4.3: 功能测试
```bash
# 启动 CLI
bun run dev
# 测试文件写入(创建测试文件)
# 输入: write test.txt "Hello World"
# 测试文件编辑(如果上面创建成功)
# 输入: edit test.txt "Hello" "Hi"
# 清理测试文件
rm test.txt
```
### Phase 5: 处理边缘情况 (10分钟)
#### Step 5.1: 如果组件不存在
创建临时占位组件:
```typescript
// 临时解决方案
const FileWritePermissionRejected = () => <Text>Permission rejected</Text>;
const FileEditPermissionRejected = () => <Text>Permission rejected</Text>;
```
#### Step 5.2: 如果类型仍然不匹配
使用类型断言:
```typescript
const tool = {
// ... tool implementation
} as Tool<any, any>;
export const FileWriteTool = tool;
```
## 完成标志
- [ ] FileWriteTool 编译无错误
- [ ] FileEditTool 编译无错误
- [ ] 权限拒绝消息正确显示
- [ ] 文件写入功能正常
- [ ] 文件编辑功能正常
- [ ] TypeScript 错误减少至少 8 个
## 注意事项
1. **保持权限检查逻辑** - 不要跳过权限验证
2. **保留错误处理** - 确保所有错误情况都有处理
3. **测试文件操作** - 使用临时文件测试,避免修改重要文件
4. **备份修改** - 定期 git add 保存进度
## 常见问题
### Q: 找不到权限组件?
```bash
find src -name "*Permission*" -type f | grep -E "(Write|Edit)"
```
### Q: Hunk 类型不存在?
```typescript
// 添加类型定义
type Hunk = {
oldStart: number;
oldLines: number;
newStart: number;
newLines: number;
lines: string[];
};
```
### Q: 组件导入路径错误?
检查实际路径:
```bash
ls -la src/components/permissions/
```
## 调试技巧
1. 使用 `console.log` 临时调试:
```typescript
console.log('FileWriteTool input:', input);
```
2. 检查运行时类型:
```typescript
console.log('Type of input:', typeof input);
```
3. 使用 TypeScript 编译器获取详细错误:
```bash
npx tsc --noEmit --pretty 2>&1 | less
```
## 完成后
标记此任务完成,继续其他并行任务。记录任何未解决的问题供后续处理。

View File

@ -1,355 +0,0 @@
# Step 1 - Worker 2: TaskTool & MultiEditTool 修复
## 前置条件
**必须先完成 step_0_foundation_serial.md 的所有任务**
## 项目背景
TaskTool 是 Kode CLI 的核心调度工具负责创建子任务和代理。MultiEditTool 允许批量编辑文件。这两个工具都涉及复杂的异步操作。
## 系统架构上下文
```
src/tools/
├── TaskTool/
│ ├── TaskTool.tsx - 任务调度工具
│ └── prompt.ts - 工具提示词
├── MultiEditTool/
│ ├── MultiEditTool.tsx - 批量编辑工具
│ └── prompt.ts - 工具提示词
```
## 任务目标
1. 修复 TaskTool 的 AsyncGenerator 类型不匹配
2. 修复 ExtendedToolUseContext 缺失属性
3. 修复 MultiEditTool 的参数和返回值问题
## 详细施工步骤
### Phase 1: 修复 TaskTool (40分钟)
#### Step 1.1: 修复 AsyncGenerator 返回类型
**文件**: `src/tools/TaskTool/TaskTool.tsx`
**定位**: 搜索 `call:` 方法 (大约第 68 行)
**当前问题**: AsyncGenerator 返回类型包含 progress 和 result但接口只期望 result
**当前代码结构**:
```typescript
async *call(
{ description, prompt, model_name, subagent_type },
context
): AsyncGenerator<
| { type: "result"; data: { error: string }; resultForAssistant: string }
| { type: "progress"; content: any; normalizedMessages: any[]; tools: any[] }
| { type: "result"; data: any; normalizedMessages: any[]; resultForAssistant: any }
> {
// 实现
}
```
**修复方案 1 - 简化返回类型**:
```typescript
async *call(
{ description, prompt, model_name, subagent_type }: any,
context: ToolUseContext
): AsyncGenerator<{ type: "result"; data: any; resultForAssistant?: string }> {
// 修改实现,只 yield result 类型
// 将 progress 类型改为内部处理或日志
try {
// ... 执行逻辑
// 不再 yield progress改为
// console.log('Progress:', progressData);
// 只 yield result
yield {
type: "result",
data: resultData,
resultForAssistant: "Task completed"
};
} catch (error) {
yield {
type: "result",
data: { error: error.message },
resultForAssistant: `Error: ${error.message}`
};
}
}
```
**修复方案 2 - 使用类型断言**:
```typescript
async *call(
input: any,
context: ToolUseContext
) {
// 保持原有逻辑,但在导出时断言
} as Tool<any, any>['call']
```
#### Step 1.2: 修复 ExtendedToolUseContext 问题
**文件**: `src/tools/TaskTool/TaskTool.tsx`
**定位**: 搜索使用 ExtendedToolUseContext 的地方 (大约第 191 行)
**错误**: 缺少 setToolJSX 属性
**查找代码**:
```typescript
const extendedContext: ExtendedToolUseContext = {
abortController: ...,
options: ...,
messageId: ...,
agentId: ...,
readFileTimestamps: ...
// 缺少 setToolJSX
};
```
**修复为**:
```typescript
const extendedContext: ExtendedToolUseContext = {
abortController: context.abortController,
options: {
...context.options,
// 确保包含所需属性
},
messageId: context.messageId || '',
agentId: context.agentId || '',
readFileTimestamps: context.readFileTimestamps || {},
setToolJSX: context.setToolJSX || (() => {}) // 添加默认实现
};
```
#### Step 1.3: 导入 ExtendedToolUseContext 类型
**文件**: `src/tools/TaskTool/TaskTool.tsx`
**在文件顶部添加**:
```typescript
import type { Tool, ToolUseContext } from '../../Tool';
import type { ExtendedToolUseContext } from '../../types/common';
```
#### Step 1.4: 修复 TextBlock 类型
**文件**: `src/tools/TaskTool/TaskTool.tsx`
**添加类型定义**:
```typescript
// 在文件顶部
type TextBlock = {
type: 'text';
text: string;
};
```
### Phase 2: 修复 MultiEditTool (30分钟)
#### Step 2.1: 修复 applyEdit 调用参数
**文件**: `src/tools/MultiEditTool/MultiEditTool.tsx`
**定位**: 搜索 `applyEdit` 调用 (大约第 281 行)
**错误**: 期望 3 个参数,但传了 4 个
**查找代码**:
```typescript
const result = applyEdit(currentContent, old_string, new_string, replace_all);
```
**修复方案 1 - 检查 applyEdit 函数签名**:
```typescript
// 查找 applyEdit 的定义
// 如果它确实只接受 3 个参数,修改调用:
const result = applyEdit(currentContent, old_string, new_string);
// 单独处理 replace_all 逻辑
```
**修复方案 2 - 更新 applyEdit 函数**:
```typescript
// 如果 applyEdit 应该接受 4 个参数,更新其定义:
function applyEdit(
content: string,
oldString: string,
newString: string,
replaceAll: boolean = false
) {
// 实现
}
```
#### Step 2.2: 修复返回值属性
**文件**: `src/tools/MultiEditTool/MultiEditTool.tsx`
**定位**: 使用 result.newContent 和 result.occurrences 的地方 (第 283, 289 行)
**错误**: 属性不存在
**查找代码**:
```typescript
currentContent = result.newContent;
// 和
if (result.occurrences === 0) {
```
**修复为**:
```typescript
// 确保 applyEdit 返回正确的结构
interface EditResult {
updatedFile: string; // 或 newContent
patch: any[];
occurrences?: number; // 添加此属性
}
// 使用时:
currentContent = result.updatedFile || result.newContent;
if ((result.occurrences || 0) === 0) {
```
#### Step 2.3: 修复函数参数数量
**文件**: `src/tools/MultiEditTool/MultiEditTool.tsx`
**定位**: 第 306 行的函数调用
**错误**: 期望 4 个参数,提供了 3 个
**可能的修复**:
```typescript
// 查找函数定义,添加缺失的参数
// 或者提供默认值
someFunction(arg1, arg2, arg3, undefined);
```
### Phase 3: 创建或修复辅助类型 (15分钟)
#### Step 3.1: 创建 types 文件(如果不存在)
**创建文件**: `src/tools/types.ts`
```typescript
// 工具系统的共享类型定义
export interface EditResult {
updatedFile: string;
newContent?: string;
patch: any[];
occurrences: number;
}
export interface TaskProgress {
type: 'progress';
content: any;
normalizedMessages: any[];
tools: any[];
}
export interface TaskResult {
type: 'result';
data: any;
resultForAssistant?: string;
}
export type TaskOutput = TaskProgress | TaskResult;
```
#### Step 3.2: 更新工具导入
**在两个工具文件中添加**:
```typescript
import type { EditResult, TaskResult } from '../types';
```
### Phase 4: 验证和测试 (15分钟)
#### Step 4.1: 检查 TaskTool 错误
```bash
npx tsc --noEmit 2>&1 | grep "TaskTool"
```
#### Step 4.2: 检查 MultiEditTool 错误
```bash
npx tsc --noEmit 2>&1 | grep "MultiEditTool"
```
#### Step 4.3: 功能测试
```bash
# 启动 CLI
bun run dev
# 测试任务创建(如果有相关命令)
# 输入: task "Create a simple function"
# 测试批量编辑(创建测试文件)
echo "old text\nold text\nold text" > test.txt
# 输入: multiedit test.txt "old" "new"
rm test.txt
```
### Phase 5: 处理复杂情况 (10分钟)
#### Step 5.1: 如果 AsyncGenerator 太复杂
使用包装函数:
```typescript
const taskToolCall = async function* (input: any, context: any) {
// 原始复杂逻辑
} as any;
export const TaskTool: Tool<any, any> = {
name: "task",
call: taskToolCall,
// ... 其他属性
};
```
#### Step 5.2: 如果类型冲突无法解决
创建适配器:
```typescript
class TaskToolAdapter {
static adaptOutput(output: any): { type: "result"; data: any } {
if (output.type === "progress") {
// 转换 progress 为某种 result 格式
return {
type: "result",
data: { progress: output }
};
}
return output;
}
}
```
## 完成标志
- [ ] TaskTool AsyncGenerator 类型匹配
- [ ] ExtendedToolUseContext 完整
- [ ] MultiEditTool 参数正确
- [ ] 返回值属性存在
- [ ] 两个工具都能编译
- [ ] TypeScript 错误减少至少 15 个
## 注意事项
1. **保持异步逻辑** - 不要改变 async/await 模式
2. **保留错误处理** - 确保 try/catch 完整
3. **测试并发场景** - TaskTool 可能同时运行多个任务
4. **注意内存泄漏** - AsyncGenerator 需要正确清理
## 调试建议
### 追踪 AsyncGenerator 问题
```typescript
async *debugGenerator() {
console.log('Generator started');
try {
yield { type: "result", data: "test" };
console.log('Yielded result');
} finally {
console.log('Generator cleanup');
}
}
```
### 类型检查技巧
```typescript
// 获取函数的返回类型
type ReturnTypeOf<T> = T extends (...args: any[]) => infer R ? R : never;
type CallReturnType = ReturnTypeOf<typeof TaskTool.call>;
```
## 常见错误解决
### AsyncGenerator 类型不兼容
1. 检查所有 yield 语句
2. 确保都返回相同的类型结构
3. 使用联合类型时要一致
### 属性不存在
1. 检查对象的实际结构
2. 添加可选链操作符 `?.`
3. 使用类型守卫
## 完成后
记录任何未解决的复杂问题,特别是关于 AsyncGenerator 的类型问题,供高级开发者后续优化。

View File

@ -1,347 +0,0 @@
# Step 1 - Worker 3: Other Tools 修复 (StickerRequestTool, NotebookReadTool, AskExpertModelTool)
## 前置条件
**必须先完成 step_0_foundation_serial.md 的所有任务**
## 项目背景
这组工具包含特殊功能StickerRequestTool 处理贴纸请求NotebookReadTool 读取 Jupyter 笔记本AskExpertModelTool 调用专家模型。
## 系统架构上下文
```
src/tools/
├── StickerRequestTool/
│ └── StickerRequestTool.tsx
├── NotebookReadTool/
│ └── NotebookReadTool.tsx
├── AskExpertModelTool/
│ └── AskExpertModelTool.tsx
```
## 任务目标
1. 修复 StickerRequestTool 的 setToolJSX 属性问题
2. 修复 NotebookReadTool 的类型转换问题
3. 修复 AskExpertModelTool 的 debugLogger 调用问题
## 详细施工步骤
### Phase 1: 修复 StickerRequestTool (20分钟)
#### Step 1.1: 处理 setToolJSX 缺失问题
**文件**: `src/tools/StickerRequestTool/StickerRequestTool.tsx`
**定位**: 搜索 `setToolJSX` (第 41, 51, 57 行)
**问题**: ToolUseContext 不包含 setToolJSX
**查找代码**:
```typescript
context.setToolJSX(<StickerUI .../>);
```
**修复方案 1 - 条件调用**:
```typescript
// 检查属性是否存在
if (context.setToolJSX) {
context.setToolJSX(<StickerUI .../>);
} else {
// 备用方案:记录日志或使用其他方式
console.log('Sticker UI would be displayed here');
}
```
**修复方案 2 - 类型守卫**:
```typescript
// 在文件顶部添加类型守卫
function hasSetToolJSX(ctx: any): ctx is ExtendedToolUseContext {
return 'setToolJSX' in ctx && typeof ctx.setToolJSX === 'function';
}
// 使用时
if (hasSetToolJSX(context)) {
context.setToolJSX(<StickerUI .../>);
}
```
#### Step 1.2: 修复 renderToolUseRejectedMessage
**文件**: `src/tools/StickerRequestTool/StickerRequestTool.tsx`
**定位**: 第 85 行
**问题**: 签名不匹配
**查找代码**:
```typescript
renderToolUseRejectedMessage(_input: any) {
return <Text>...</Text>;
}
```
**修复为**:
```typescript
renderToolUseRejectedMessage() {
// 移除参数或设为可选
return <Text color="red">Sticker request rejected</Text>;
}
```
#### Step 1.3: 确保组件导入正确
**文件**: `src/tools/StickerRequestTool/StickerRequestTool.tsx`
**检查导入**:
```typescript
import React from 'react';
import { Text, Box } from 'ink';
import type { Tool, ToolUseContext } from '../../Tool';
```
### Phase 2: 修复 NotebookReadTool (20分钟)
#### Step 2.1: 修复类型转换问题
**文件**: `src/tools/NotebookReadTool/NotebookReadTool.tsx`
**定位**: 第 179 行
**问题**: unknown 不能赋值给 string | string[]
**查找代码**:
```typescript
someFunction(unknownValue);
```
**修复方案 1 - 类型断言**:
```typescript
someFunction(unknownValue as string);
// 或者如果可能是数组
someFunction(unknownValue as string | string[]);
```
**修复方案 2 - 类型检查**:
```typescript
// 安全的类型检查
if (typeof unknownValue === 'string') {
someFunction(unknownValue);
} else if (Array.isArray(unknownValue)) {
someFunction(unknownValue);
} else {
// 处理其他情况
someFunction(String(unknownValue));
}
```
#### Step 2.2: 处理 Jupyter 笔记本类型
**文件**: `src/tools/NotebookReadTool/NotebookReadTool.tsx`
**添加类型定义**:
```typescript
// 在文件顶部
interface NotebookCell {
cell_type: 'code' | 'markdown';
source: string | string[];
outputs?: any[];
}
interface NotebookData {
cells: NotebookCell[];
metadata?: any;
}
```
#### Step 2.3: 修复解析逻辑
**确保正确处理 source 字段**:
```typescript
function parseSource(source: unknown): string {
if (typeof source === 'string') {
return source;
}
if (Array.isArray(source)) {
return source.join('');
}
return String(source);
}
// 使用
const content = parseSource(cell.source);
```
### Phase 3: 修复 AskExpertModelTool (25分钟)
#### Step 3.1: 修复 debugLogger 调用
**文件**: `src/tools/AskExpertModelTool/AskExpertModelTool.tsx`
**定位**: 第 149, 172, 306, 319, 327, 344, 358, 417, 499, 508, 533 行
**问题**: debugLogger 不是函数或参数数量错误
**查找 debugLogger 的使用**:
```typescript
debugLogger('phase', 'data', 'requestId');
```
**修复方案 1 - 检查导入**:
```typescript
// 确保正确导入
import { debugLogger } from '../../utils/debugLogger';
// 如果 debugLogger 是对象,使用正确的方法
debugLogger.log('phase', 'data');
// 或
debugLogger.info('phase', { data, requestId });
```
**修复方案 2 - 创建包装函数**:
```typescript
// 如果 debugLogger 结构复杂
const log = (phase: string, data: any, requestId?: string) => {
if (typeof debugLogger === 'function') {
debugLogger(phase, data, requestId);
} else if (debugLogger && debugLogger.log) {
debugLogger.log(phase, { data, requestId });
} else {
console.log(`[${phase}]`, data, requestId);
}
};
// 替换所有 debugLogger 调用为 log
log('api-call', responseData, requestId);
```
#### Step 3.2: 修复每个 debugLogger 调用
**系统性替换所有出错的行**:
1. **第 149 行**:
```typescript
// 原始debugLogger(...)
// 修改为:
log('expert-model-start', { input }, requestId);
```
2. **第 172 行** (2 参数变 1 参数):
```typescript
// 原始debugLogger.something(arg1, arg2)
// 修改为:
debugLogger.api?.('phase', data) || console.log('API:', data);
```
3. **继续修复其他行**,使用相同模式
#### Step 3.3: 处理模型调用逻辑
**确保异步调用正确**:
```typescript
async function callExpertModel(prompt: string, model: string) {
try {
log('model-call-start', { prompt, model });
const response = await modelService.complete({
prompt,
model,
});
log('model-call-success', response);
return response;
} catch (error) {
log('model-call-error', error);
throw error;
}
}
```
### Phase 4: 通用修复和优化 (10分钟)
#### Step 4.1: 添加通用类型定义
**创建文件**: `src/tools/utils/types.ts`
```typescript
// 工具系统的辅助类型
export type DebugLogger = {
log: (phase: string, data: any) => void;
info: (phase: string, data: any) => void;
warn: (phase: string, data: any) => void;
error: (phase: string, data: any) => void;
api?: (phase: string, data: any, requestId?: string) => void;
flow?: (phase: string, data: any, requestId?: string) => void;
};
export interface ToolContext extends ToolUseContext {
setToolJSX?: (jsx: React.ReactElement) => void;
}
```
#### Step 4.2: 统一导入语句
**在所有三个工具文件的顶部**:
```typescript
import type { Tool } from '../../Tool';
import type { ToolContext } from '../utils/types';
```
### Phase 5: 验证和测试 (15分钟)
#### Step 5.1: 检查各工具错误
```bash
# StickerRequestTool
npx tsc --noEmit 2>&1 | grep "StickerRequestTool"
# NotebookReadTool
npx tsc --noEmit 2>&1 | grep "NotebookReadTool"
# AskExpertModelTool
npx tsc --noEmit 2>&1 | grep "AskExpertModelTool"
```
#### Step 5.2: 功能测试
```bash
# 启动 CLI
bun run dev
# 测试笔记本读取(如果有 .ipynb 文件)
# 输入: read notebook.ipynb
# 测试专家模型(如果配置了)
# 输入: ask "What is TypeScript?"
```
## 完成标志
- [ ] StickerRequestTool 编译无错误
- [ ] NotebookReadTool 类型转换正确
- [ ] AskExpertModelTool debugLogger 调用修复
- [ ] 所有工具都能加载
- [ ] TypeScript 错误减少至少 20 个
## 注意事项
1. **保持功能完整** - 不要删除功能代码
2. **日志很重要** - 保留或改进日志记录
3. **处理边缘情况** - 考虑 undefined/null 值
4. **测试特殊文件** - 如 .ipynb 文件的解析
## 调试技巧
### 检查对象结构
```typescript
console.log('debugLogger type:', typeof debugLogger);
console.log('debugLogger keys:', Object.keys(debugLogger || {}));
```
### 类型调试
```typescript
// 临时添加以查看类型
type DebugType = typeof debugLogger;
const checkType: DebugType = null!;
```
### 运行时检查
```typescript
if (!context.setToolJSX) {
console.warn('setToolJSX not available in context');
}
```
## 常见问题
### Q: debugLogger 的正确用法?
```bash
# 查找其他使用示例
grep -r "debugLogger" src --include="*.ts" --include="*.tsx" | head -20
```
### Q: setToolJSX 在哪里定义?
```bash
# 查找定义
grep -r "setToolJSX" src --include="*.ts" --include="*.tsx"
```
### Q: Notebook 类型定义?
```bash
# 查找 Jupyter 相关类型
find src -name "*.d.ts" | xargs grep -l "notebook\|jupyter"
```
## 完成后
这是 Step 1 的最后一个并行任务。完成后,可以进入 Step 2 的并行任务React 组件、Hooks、Services 的修复)。

View File

@ -1,331 +0,0 @@
# Step 2 - Worker 0: React 19 / Ink 6 组件修复
## 前置条件
**必须先完成 step_0_foundation_serial.md 的所有任务**
## 项目背景
React 19 和 Ink 6 引入了破坏性更改,特别是关于 props 的处理。主要问题是 `children` prop 现在是必需的,以及 `key` prop 不能作为组件 props 传递。
## 系统架构上下文
```
src/
├── commands/
│ └── agents.tsx - 代理命令界面
├── components/
│ └── messages/
│ └── AssistantToolUseMessage.tsx
├── screens/
│ ├── REPL.tsx - 主交互界面
│ └── Doctor.tsx - 诊断界面
```
## 任务目标
1. 修复所有 React 19 的 children prop 问题
2. 修复 key prop 传递问题
3. 修复导入路径问题
4. 确保所有组件正确渲染
## 详细施工步骤
### Phase 1: 修复 key prop 问题 (20分钟)
#### Step 1.1: 修复 agents.tsx 中的 key prop
**文件**: `src/commands/agents.tsx`
**定位**: 第 2357, 2832, 3137, 3266 行
**问题**: key 作为 props 传递而不是 JSX 属性
**查找模式**:
```typescript
<Text {...{key: index, color: 'gray'}}>
```
**修复为**:
```typescript
<Text key={index} color="gray">
```
**具体修复**:
1. **第 2357 行**:
```typescript
// 原始
<Text {...{key: index, color: 'gray'}}>
// 修复
<Text key={index} color="gray">
```
2. **第 3137 行**:
```typescript
// 原始
<Text {...{key: someKey, color: 'blue'}}>
// 修复
<Text key={someKey} color="blue">
```
3. **第 3266 行**:
```typescript
// 原始
<Text {...{key: i, color: 'green'}}>
// 修复
<Text key={i} color="green">
```
#### Step 1.2: 修复 isContinue 属性访问
**文件**: `src/commands/agents.tsx`
**定位**: 第 2832 行
**问题**: 属性在某些类型上不存在
**查找代码**:
```typescript
if (option.isContinue) {
```
**修复为**:
```typescript
if ('isContinue' in option && option.isContinue) {
// 处理 continue 选项
}
```
### Phase 2: 修复 children prop 问题 (25分钟)
#### Step 2.1: AssistantToolUseMessage 组件
**文件**: `src/components/messages/AssistantToolUseMessage.tsx`
**定位**: 第 65, 91 行
**第 65 行 - 函数调用参数**:
```typescript
// 查找
someFunction(argument)
// 如果函数不期望参数,修改为
someFunction()
```
**第 91 行 - 缺少 children**:
```typescript
// 原始
<Text agentType={agentType} bold />
// 修复
<Text bold>
{agentType ? `[${agentType}]` : 'Processing...'}
</Text>
```
#### Step 2.2: REPL.tsx 组件
**文件**: `src/screens/REPL.tsx`
**定位**: 第 526, 621, 625 行
**第 526 行 - TodoProvider**:
```typescript
// 原始
<TodoProvider />
// 修复
<TodoProvider>
{/* 子组件内容 */}
<Box>{/* 实际的 TODO 界面 */}</Box>
</TodoProvider>
```
**第 621 行 - PermissionProvider**:
```typescript
// 原始
<PermissionProvider isBypassPermissionsModeAvailable={...} />
// 修复
<PermissionProvider isBypassPermissionsModeAvailable={...}>
{children}
</PermissionProvider>
```
**第 625 行 - Generic Provider**:
```typescript
// 原始
<SomeProvider items={items} />
// 修复
<SomeProvider items={items}>
{/* 查找原始代码中应该包含的子元素 */}
{renderContent()}
</SomeProvider>
```
### Phase 3: 修复导入路径问题 (10分钟)
#### Step 3.1: Doctor.tsx 导入路径
**文件**: `src/screens/Doctor.tsx`
**定位**: 第 5 行
**问题**: TypeScript 不允许 .tsx 扩展名
**查找代码**:
```typescript
import Something from '../path/to/file.tsx'
```
**修复为**:
```typescript
import Something from '../path/to/file'
```
### Phase 4: 修复 React 19 特定问题 (15分钟)
#### Step 4.1: 检查所有 Text 组件
**全局搜索并修复**:
```bash
# 查找所有可能缺少 children 的 Text 组件
grep -n "<Text.*\/>" src/commands/agents.tsx
```
**修复模式**:
```typescript
// 如果发现自闭合的 Text 没有内容
<Text color="gray" />
// 修复为
<Text color="gray">{' '}</Text>
// 或
<Text color="gray"></Text> // 零宽空格
```
#### Step 4.2: 检查所有 Box 组件
```typescript
// 确保 Box 组件有内容或明确表示为空
<Box />
// 修复为
<Box>{/* intentionally empty */}</Box>
```
### Phase 5: 处理 React 19 严格模式 (10分钟)
#### Step 5.1: 添加类型声明(如果需要)
**创建文件**: `src/types/react-overrides.d.ts`
```typescript
import 'react';
declare module 'react' {
interface PropsWithChildren {
children?: React.ReactNode;
}
}
```
#### Step 5.2: 更新组件接口
**对于自定义组件,确保 props 类型正确**:
```typescript
interface MyComponentProps {
children: React.ReactNode; // 如果需要 children
// 或
children?: React.ReactNode; // 如果 children 可选
}
```
### Phase 6: 验证和测试 (20分钟)
#### Step 6.1: 检查组件错误
```bash
# 检查 agents.tsx
npx tsc --noEmit 2>&1 | grep "agents.tsx"
# 检查消息组件
npx tsc --noEmit 2>&1 | grep "AssistantToolUseMessage"
# 检查 REPL
npx tsc --noEmit 2>&1 | grep "REPL.tsx"
# 检查 Doctor
npx tsc --noEmit 2>&1 | grep "Doctor.tsx"
```
#### Step 6.2: 运行时测试
```bash
# 启动 CLI
bun run dev
# 测试代理命令
/agents
# 测试帮助
/help
# 测试诊断
/doctor
```
#### Step 6.3: 视觉检查
确保:
- 文本正确显示
- 列表项有正确的 key
- 没有 React 警告在控制台
## 完成标志
- [ ] 所有 key prop 正确传递
- [ ] 所有组件都有必需的 children
- [ ] 导入路径没有 .tsx 扩展名
- [ ] agents.tsx 无类型错误
- [ ] REPL.tsx 无类型错误
- [ ] 所有组件正确渲染
- [ ] TypeScript 错误减少至少 15 个
## 注意事项
1. **保持布局** - 不要改变组件的视觉布局
2. **保留功能** - 确保交互功能正常
3. **React 19 兼容** - 遵循新的严格规则
4. **Ink 6 特性** - 利用新的 Ink 特性如果适用
## 调试技巧
### 查找缺少 children 的组件
```typescript
// 添加临时 linter 规则
/* eslint-disable react/no-children-prop */
```
### 调试 key prop
```typescript
// 在开发模式下查看 key
{items.map((item, index) => {
console.log('Rendering item with key:', index);
return <Text key={index}>{item}</Text>;
})}
```
### 检查组件 props
```typescript
// 临时添加 props 日志
const MyComponent: React.FC<Props> = (props) => {
console.log('Component props:', props);
// ...
};
```
## 常见问题
### Q: children 应该是什么类型?
```typescript
// 对于 Ink 组件
children: React.ReactNode
// 对于文本组件
children: string | number
// 对于容器
children: React.ReactElement | React.ReactElement[]
```
### Q: key 应该如何传递?
```typescript
// 正确 ✅
<Component key={id} otherProp="value" />
// 错误 ❌
<Component {...{key: id, otherProp: "value"}} />
<Component key={id} {...props} /> // 如果 props 包含 key
```
### Q: 如何处理条件渲染的 children
```typescript
<Container>
{condition ? <Child /> : null}
{/* 或 */}
{condition && <Child />}
</Container>
```
## 完成后
此任务完成后React 组件相关的错误应该大幅减少。可以继续进行其他 Step 2 的并行任务。

View File

@ -1,399 +0,0 @@
# Step 2 - Worker 1: Hook 系统修复
## 前置条件
**必须先完成 step_0_foundation_serial.md 的所有任务**
## 项目背景
Kode CLI 使用自定义 React Hooks 处理终端输入和用户交互。主要问题是 Ink 的 Key 类型缺少某些属性,以及一些未使用的 @ts-expect-error 指令。
## 系统架构上下文
```
src/hooks/
├── useDoublePress.ts - 双击检测
├── useTextInput.ts - 文本输入处理
├── useUnifiedCompletion.ts - 自动完成功能
└── ...其他 hooks
```
## 任务目标
1. 修复 Key 类型属性缺失问题
2. 移除未使用的 @ts-expect-error 指令
3. 确保所有输入处理正常工作
## 详细施工步骤
### Phase 1: 修复 Key 类型问题 (25分钟)
#### Step 1.1: 修复 useTextInput.ts
**文件**: `src/hooks/useTextInput.ts`
**问题位置**: 第 143, 266, 268, 272, 274 行
**第 143 行 - 移除未使用的指令**:
```typescript
// 删除这一行
// @ts-expect-error
```
**第 266, 268 行 - fn 属性不存在**:
```typescript
// 原始代码
if (input.fn) {
// 处理功能键
}
// 修复方案 1 - 类型断言
if ((input as any).fn) {
// 处理功能键
}
// 修复方案 2 - 属性检查
if ('fn' in input && input.fn) {
// 处理功能键
}
// 修复方案 3 - 扩展类型(如果 step_0 已创建类型增强)
// 确保导入了增强类型
import type { Key } from 'ink';
// Key 类型应该已经包含 fn 属性
```
**第 272, 274 行 - home 和 end 属性**:
```typescript
// 原始代码
if (input.home) {
// 光标移到开始
}
if (input.end) {
// 光标移到结束
}
// 修复 - 使用扩展的 Key 类型或类型守卫
if ('home' in input && input.home) {
setCursorPosition(0);
}
if ('end' in input && input.end) {
setCursorPosition(value.length);
}
```
#### Step 1.2: 创建 Key 类型辅助函数
**在 useTextInput.ts 顶部添加**:
```typescript
// Key 类型辅助函数
interface ExtendedKey extends Key {
fn?: boolean;
home?: boolean;
end?: boolean;
}
function isExtendedKey(key: Key): key is ExtendedKey {
return true; // 因为我们已经扩展了类型
}
// 或者更安全的检查
function hasProperty<T extends object, K extends PropertyKey>(
obj: T,
key: K
): obj is T & Record<K, unknown> {
return key in obj;
}
```
#### Step 1.3: 重构键盘处理逻辑
**优化键盘输入处理**:
```typescript
const handleKeyPress = (input: string, key: Key) => {
// 功能键处理
if (hasProperty(key, 'fn') && key.fn) {
handleFunctionKey(input);
return;
}
// Home 键
if (hasProperty(key, 'home') && key.home) {
setCursorPosition(0);
return;
}
// End 键
if (hasProperty(key, 'end') && key.end) {
setCursorPosition(value.length);
return;
}
// 普通按键处理
if (key.return) {
handleSubmit();
} else if (key.backspace) {
handleBackspace();
} else if (key.delete) {
handleDelete();
} else if (key.leftArrow) {
moveCursorLeft();
} else if (key.rightArrow) {
moveCursorRight();
} else {
insertText(input);
}
};
```
### Phase 2: 修复 useUnifiedCompletion.ts (20分钟)
#### Step 2.1: 修复 space 属性
**文件**: `src/hooks/useUnifiedCompletion.ts`
**定位**: 第 1151 行
**问题**: space 属性不存在
**查找代码**:
```typescript
if (key.space) {
// 处理空格键
}
```
**修复为**:
```typescript
// 方案 1 - 检查输入字符
if (input === ' ') {
// 处理空格键
}
// 方案 2 - 扩展属性检查
if ('space' in key && key.space) {
// 处理空格键
}
// 方案 3 - 组合检查
if (input === ' ' || (hasProperty(key, 'space') && key.space)) {
// 处理空格键
}
```
#### Step 2.2: 优化自动完成逻辑
**改进类型安全**:
```typescript
interface CompletionKey extends Key {
space?: boolean;
tab?: boolean;
}
const handleCompletionKey = (input: string, key: Key) => {
const extKey = key as CompletionKey;
// Tab 完成
if (key.tab) {
return performCompletion();
}
// 空格触发
if (input === ' ' || extKey.space) {
return checkForCompletion();
}
// Escape 取消
if (key.escape) {
return cancelCompletion();
}
};
```
### Phase 3: 修复 useDoublePress.ts (10分钟)
#### Step 3.1: 移除未使用的指令
**文件**: `src/hooks/useDoublePress.ts`
**定位**: 第 33 行
**操作**:
```typescript
// 删除这一行
// @ts-expect-error
```
#### Step 3.2: 检查相关代码
**确保删除指令后代码仍然正确**:
```typescript
// 检查第 33 行附近的代码
// 如果有类型问题,正确修复而不是使用 @ts-expect-error
```
### Phase 4: 创建通用 Hook 工具函数 (15分钟)
#### Step 4.1: 创建 hook 工具文件
**创建文件**: `src/hooks/utils.ts`
```typescript
import type { Key } from 'ink';
// 扩展的 Key 类型
export interface ExtendedKey extends Key {
fn?: boolean;
home?: boolean;
end?: boolean;
space?: boolean;
pageUp?: boolean;
pageDown?: boolean;
}
// 类型守卫
export function isExtendedKey(key: Key): key is ExtendedKey {
return true;
}
// 属性检查
export function hasKeyProperty<K extends keyof ExtendedKey>(
key: Key,
property: K
): key is Key & Record<K, ExtendedKey[K]> {
return property in key;
}
// 键盘事件标准化
export function normalizeKey(input: string, key: Key): ExtendedKey {
return {
...key,
space: input === ' ',
// 添加其他标准化逻辑
} as ExtendedKey;
}
```
#### Step 4.2: 更新 hooks 使用工具函数
**在需要的 hooks 中导入**:
```typescript
import { hasKeyProperty, normalizeKey } from './utils';
// 使用
const normalizedKey = normalizeKey(input, key);
if (normalizedKey.space) {
// 处理空格
}
```
### Phase 5: 验证和测试 (15分钟)
#### Step 5.1: 检查 Hook 错误
```bash
# useTextInput
npx tsc --noEmit 2>&1 | grep "useTextInput"
# useUnifiedCompletion
npx tsc --noEmit 2>&1 | grep "useUnifiedCompletion"
# useDoublePress
npx tsc --noEmit 2>&1 | grep "useDoublePress"
# 所有 hooks
npx tsc --noEmit 2>&1 | grep "src/hooks/"
```
#### Step 5.2: 功能测试
```bash
# 启动 CLI
bun run dev
# 测试文本输入
# 输入一些文本,测试:
# - 光标移动 (箭头键)
# - Home/End 键
# - 退格/删除
# - 自动完成 (Tab)
# 测试双击
# 快速按两次相同的键
```
#### Step 5.3: 创建测试脚本
**创建文件**: `test-hooks.js`
```javascript
// 简单的键盘输入测试
const readline = require('readline');
readline.emitKeypressEvents(process.stdin);
process.stdin.setRawMode(true);
console.log('Press keys to test (Ctrl+C to exit):');
process.stdin.on('keypress', (str, key) => {
console.log('Input:', str, 'Key:', key);
if (key && key.ctrl && key.name === 'c') {
process.exit();
}
});
```
## 完成标志
- [ ] useTextInput.ts 无类型错误
- [ ] useUnifiedCompletion.ts 无类型错误
- [ ] useDoublePress.ts 无未使用指令
- [ ] 所有键盘输入正常工作
- [ ] 自动完成功能正常
- [ ] TypeScript 错误减少至少 10 个
## 注意事项
1. **保持输入响应** - 不要引入延迟
2. **处理边缘情况** - 考虑特殊键组合
3. **保留快捷键** - 确保所有快捷键仍然工作
4. **浏览器兼容性** - 如果有 web 版本,考虑兼容性
## 调试技巧
### 监控键盘输入
```typescript
useInput((input, key) => {
console.log('Raw input:', { input, key });
console.log('Key properties:', Object.keys(key));
});
```
### 测试特殊键
```typescript
const testKeys = {
'Ctrl+C': { ctrl: true, name: 'c' },
'Home': { home: true },
'End': { end: true },
'F1': { fn: true, name: 'f1' },
};
```
### 性能监控
```typescript
const handleInput = (input: string, key: Key) => {
const start = performance.now();
// 处理逻辑
const end = performance.now();
if (end - start > 16) { // 超过一帧
console.warn('Slow input handling:', end - start);
}
};
```
## 常见问题
### Q: Key 类型从哪里来?
```bash
# 查看 ink 的类型定义
cat node_modules/ink/build/index.d.ts | grep "interface Key"
```
### Q: 如何处理组合键?
```typescript
if (key.ctrl && key.name === 'a') {
// Ctrl+A: 全选
}
if (key.meta && key.name === 'v') {
// Cmd+V (Mac) / Win+V: 粘贴
}
```
### Q: 输入延迟问题?
```typescript
// 使用防抖
const debouncedHandler = useMemo(
() => debounce(handleInput, 50),
[]
);
```
## 完成后
Hook 系统修复完成后,用户输入处理应该完全正常。这是用户体验的关键部分,确保充分测试。

View File

@ -1,496 +0,0 @@
# Step 2 - Worker 2: Service 层修复
## 前置条件
**必须先完成 step_0_foundation_serial.md 的所有任务**
## 项目背景
Service 层处理与外部 API 的通信,包括 OpenAI、Claude 等模型服务。主要问题是对 unknown 类型的不安全访问。
## 系统架构上下文
```
src/services/
├── openai.ts - OpenAI API 集成
├── claude.ts - Claude API 集成
├── modelAdapterFactory.ts - 模型适配器工厂
└── mcpClient.ts - MCP 客户端
src/entrypoints/
├── cli.tsx - CLI 入口
└── mcp.ts - MCP 入口
```
## 任务目标
1. 修复 openai.ts 中的类型安全问题
2. 修复 entrypoints 中的函数调用问题
3. 添加适当的错误处理和类型守卫
## 详细施工步骤
### Phase 1: 修复 OpenAI Service (30分钟)
#### Step 1.1: 添加响应类型定义
**文件**: `src/services/openai.ts`
**在文件顶部添加类型定义**:
```typescript
// OpenAI API 响应类型
interface OpenAIErrorResponse {
error?: {
message: string;
type: string;
code?: string;
};
}
interface OpenAIModel {
id: string;
object: string;
created: number;
owned_by: string;
}
interface OpenAIModelsResponse {
data?: OpenAIModel[];
models?: OpenAIModel[]; // 某些 API 兼容服务使用此字段
object?: string;
}
interface OpenAIChatResponse {
id: string;
object: string;
created: number;
choices: Array<{
message: {
role: string;
content: string;
};
finish_reason: string;
}>;
usage?: {
prompt_tokens: number;
completion_tokens: number;
total_tokens: number;
};
}
```
#### Step 1.2: 修复错误处理 (第 611, 743 行)
**文件**: `src/services/openai.ts`
**定位**: 第 611, 743 行
**修复错误访问**:
```typescript
// 原始代码
if (error.error && error.message) {
// 处理错误
}
// 修复为 - 添加类型守卫
function isOpenAIError(error: unknown): error is OpenAIErrorResponse {
return (
typeof error === 'object' &&
error !== null &&
'error' in error &&
typeof (error as any).error === 'object'
);
}
// 使用类型守卫
try {
// API 调用
} catch (error) {
if (isOpenAIError(error) && error.error) {
const errorMessage = error.error.message || 'Unknown error';
console.error('OpenAI API error:', errorMessage);
throw new Error(errorMessage);
} else if (error instanceof Error) {
throw error;
} else {
throw new Error('Unknown error occurred');
}
}
```
#### Step 1.3: 修复模型列表处理 (第 1291-1299 行)
**文件**: `src/services/openai.ts`
**定位**: 第 1291-1299 行
**修复数据访问**:
```typescript
// 原始代码
if (response.data || response.models) {
const models = response.data || response.models;
}
// 修复为 - 添加类型安全
async function fetchModels(): Promise<OpenAIModel[]> {
try {
const response = await fetch('/v1/models');
const data: unknown = await response.json();
// 类型验证
if (!isModelsResponse(data)) {
throw new Error('Invalid models response');
}
// 安全访问
const models = data.data || data.models || [];
return models;
} catch (error) {
console.error('Failed to fetch models:', error);
return [];
}
}
// 类型守卫
function isModelsResponse(data: unknown): data is OpenAIModelsResponse {
return (
typeof data === 'object' &&
data !== null &&
(Array.isArray((data as any).data) || Array.isArray((data as any).models))
);
}
```
#### Step 1.4: 创建通用 API 调用包装器
**添加安全的 API 调用函数**:
```typescript
class OpenAIService {
private async safeApiCall<T>(
endpoint: string,
options?: RequestInit
): Promise<T> {
try {
const response = await fetch(endpoint, options);
if (!response.ok) {
const errorData = await response.json().catch(() => ({}));
if (isOpenAIError(errorData)) {
throw new Error(errorData.error?.message || 'API request failed');
}
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
const data = await response.json();
return data as T;
} catch (error) {
// 统一错误处理
if (error instanceof Error) {
throw error;
}
throw new Error('Unknown error in API call');
}
}
async listModels(): Promise<OpenAIModel[]> {
const response = await this.safeApiCall<OpenAIModelsResponse>('/v1/models');
return response.data || response.models || [];
}
async createChatCompletion(params: any): Promise<OpenAIChatResponse> {
return this.safeApiCall<OpenAIChatResponse>('/v1/chat/completions', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(params),
});
}
}
```
### Phase 2: 修复 CLI 入口点 (20分钟)
#### Step 2.1: 修复 cli.tsx
**文件**: `src/entrypoints/cli.tsx`
**第 318 行 - 移除未使用的 @ts-expect-error**:
```typescript
// 删除这一行
// @ts-expect-error
```
**第 543 行 - 修复 getConfig 重载**:
```typescript
// 原始代码
const config = getConfig(isGlobal);
// 修复方案 1 - 明确类型
const config = isGlobal ? getConfig(true) : getConfig(false);
// 修复方案 2 - 类型断言
const config = getConfig(isGlobal as true);
// 修复方案 3 - 修改函数签名(如果可以)
function getConfig(global?: boolean): Config {
// 统一处理
}
```
**第 1042 行 - 修复无类型函数调用**:
```typescript
// 原始代码
someFunction<Type>(args);
// 如果函数不是泛型,移除类型参数
someFunction(args);
// 或者确保函数是泛型
const someFunction = <T,>(arg: T): T => {
return arg;
};
```
### Phase 3: 修复 MCP 入口点 (15分钟)
#### Step 3.1: 修复 mcp.ts
**文件**: `src/entrypoints/mcp.ts`
**第 70 行 - 修复参数数量**:
```typescript
// 查找函数定义,确认期望的参数数量
// 如果函数不期望参数
someFunction();
// 如果参数是可选的
someFunction(undefined);
```
**第 130 行 - 修复参数数量**:
```typescript
// 原始3 个参数,期望 2 个
someFunction(arg1, arg2, arg3);
// 修复方案 1 - 移除多余参数
someFunction(arg1, arg2);
// 修复方案 2 - 合并参数
someFunction(arg1, { arg2, arg3 });
// 修复方案 3 - 检查函数签名是否正确
// 可能函数签名已更改,需要更新调用
```
### Phase 4: 创建类型安全的服务层 (15分钟)
#### Step 4.1: 创建服务基类
**创建文件**: `src/services/base.ts`
```typescript
// 基础服务类,提供通用功能
export abstract class BaseService {
protected apiKey?: string;
protected baseUrl: string;
constructor(config: { apiKey?: string; baseUrl: string }) {
this.apiKey = config.apiKey;
this.baseUrl = config.baseUrl;
}
protected async request<T>(
endpoint: string,
options?: RequestInit
): Promise<T> {
const url = `${this.baseUrl}${endpoint}`;
const headers = {
...options?.headers,
...(this.apiKey && { 'Authorization': `Bearer ${this.apiKey}` }),
};
try {
const response = await fetch(url, { ...options, headers });
if (!response.ok) {
await this.handleErrorResponse(response);
}
return await response.json();
} catch (error) {
this.handleError(error);
throw error;
}
}
protected async handleErrorResponse(response: Response): Promise<never> {
const error = await response.json().catch(() => ({
message: `HTTP ${response.status}`
}));
throw new Error(error.message || 'Request failed');
}
protected handleError(error: unknown): void {
console.error('Service error:', error);
}
}
```
#### Step 4.2: 更新服务使用基类
```typescript
// 在 openai.ts 中
import { BaseService } from './base';
export class OpenAIService extends BaseService {
constructor(apiKey?: string) {
super({
apiKey,
baseUrl: process.env.OPENAI_BASE_URL || 'https://api.openai.com',
});
}
async listModels(): Promise<OpenAIModel[]> {
const response = await this.request<OpenAIModelsResponse>('/v1/models');
return response.data || [];
}
}
```
### Phase 5: 验证和测试 (20分钟)
#### Step 5.1: 检查服务层错误
```bash
# OpenAI service
npx tsc --noEmit 2>&1 | grep "openai.ts"
# CLI entrypoint
npx tsc --noEmit 2>&1 | grep "cli.tsx"
# MCP entrypoint
npx tsc --noEmit 2>&1 | grep "mcp.ts"
# 所有服务
npx tsc --noEmit 2>&1 | grep "src/services/"
```
#### Step 5.2: 测试 API 调用
```bash
# 启动 CLI
bun run dev
# 如果配置了 OpenAI
export OPENAI_API_KEY="your-key"
# 测试模型列表(如果有此功能)
/models
# 测试基本功能
/help
```
#### Step 5.3: 创建测试脚本
**创建文件**: `test-services.ts`
```typescript
import { OpenAIService } from './src/services/openai';
async function testServices() {
console.log('Testing services...');
// 测试 OpenAI
try {
const openai = new OpenAIService(process.env.OPENAI_API_KEY);
const models = await openai.listModels();
console.log('OpenAI models:', models.length);
} catch (error) {
console.error('OpenAI test failed:', error);
}
console.log('Tests complete');
}
testServices();
```
## 完成标志
- [ ] OpenAI service 类型安全
- [ ] 所有 unknown 类型正确处理
- [ ] CLI 入口点无错误
- [ ] MCP 入口点无错误
- [ ] API 调用有错误处理
- [ ] TypeScript 错误减少至少 12 个
## 注意事项
1. **保护 API 密钥** - 不要记录敏感信息
2. **处理网络错误** - 考虑超时和重试
3. **向后兼容** - 保持现有 API 接口
4. **性能考虑** - 避免不必要的 API 调用
## 调试技巧
### API 响应调试
```typescript
const debugResponse = async (response: Response) => {
const text = await response.text();
console.log('Response:', {
status: response.status,
headers: Object.fromEntries(response.headers),
body: text.substring(0, 500),
});
return JSON.parse(text);
};
```
### 类型检查
```typescript
// 运行时类型检查
console.log('Type of response:', typeof response);
console.log('Response keys:', Object.keys(response || {}));
```
### 网络请求监控
```typescript
const fetchWithLogging = async (url: string, options?: RequestInit) => {
console.log(`[FETCH] ${options?.method || 'GET'} ${url}`);
const start = Date.now();
try {
const response = await fetch(url, options);
console.log(`[FETCH] ${response.status} in ${Date.now() - start}ms`);
return response;
} catch (error) {
console.error(`[FETCH] Error after ${Date.now() - start}ms:`, error);
throw error;
}
};
```
## 常见问题
### Q: 如何处理不同的 API 响应格式?
```typescript
// 使用联合类型
type APIResponse = OpenAIResponse | AnthropicResponse | CustomResponse;
// 使用类型守卫区分
function isOpenAIResponse(r: APIResponse): r is OpenAIResponse {
return 'choices' in r;
}
```
### Q: 如何处理 API 版本差异?
```typescript
class VersionedAPI {
private version: string;
constructor(version: string = 'v1') {
this.version = version;
}
getEndpoint(path: string): string {
return `/${this.version}${path}`;
}
}
```
### Q: 重试逻辑?
```typescript
async function retryableRequest<T>(
fn: () => Promise<T>,
retries: number = 3
): Promise<T> {
for (let i = 0; i < retries; i++) {
try {
return await fn();
} catch (error) {
if (i === retries - 1) throw error;
await new Promise(r => setTimeout(r, 1000 * Math.pow(2, i)));
}
}
throw new Error('Max retries exceeded');
}
```
## 完成后
Service 层修复完成后API 通信应该类型安全且稳定。这是系统可靠性的关键部分。

View File

@ -1,6 +1,6 @@
# AGENTS.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
This file provides guidance to Kode automation agents (including those compatible with Claude Code's `.claude` ecosystem) when working with code in this repository.
## Development Commands
@ -46,7 +46,7 @@ SKIP_BUNDLED_CHECK=true npm publish
## High-Level Architecture
### Core System Design
Kode implements a **three-layer parallel architecture** inspired by Claude Code:
Kode implements a **three-layer parallel architecture** refined for fast iteration across terminal workflows while remaining compatible with the Claude Code agent ecosystem:
1. **User Interaction Layer** (`src/screens/REPL.tsx`)
- Interactive terminal interface using Ink (React for CLI)
@ -74,9 +74,9 @@ Kode implements a **three-layer parallel architecture** inspired by Claude Code:
### Agent System (`src/utils/agentLoader.ts`)
**Dynamic Agent Configuration Loading** with 5-tier priority system:
1. Built-in (code-embedded)
2. `~/.claude/agents/` (Claude user)
2. `~/.claude/agents/` (Claude Code user directory compatibility)
3. `~/.kode/agents/` (Kode user)
4. `./.claude/agents/` (Claude project)
4. `./.claude/agents/` (Claude Code project directory compatibility)
5. `./.kode/agents/` (Kode project)
Agents are defined as markdown files with YAML frontmatter:
@ -99,7 +99,7 @@ Each tool follows a consistent pattern in `src/tools/[ToolName]/`:
- Permission-aware execution
### Service Layer
- **Claude Service** (`src/services/claude.ts`): Primary AI model interface
- **Anthropic Service** (`src/services/claude.ts`): Claude API integration
- **OpenAI Service** (`src/services/openai.ts`): OpenAI-compatible models
- **Model Adapter Factory** (`src/services/modelAdapterFactory.ts`): Unified model interface
- **MCP Client** (`src/services/mcpClient.ts`): Model Context Protocol for tool extensions
@ -204,4 +204,4 @@ const description = typeof tool.description === 'function'
1. Create `.md` file with proper YAML frontmatter
2. Place in appropriate directory based on scope
3. Test with `/agents` command
4. Verify tool permissions work correctly
4. Verify tool permissions work correctly

View File

@ -1,10 +1,11 @@
# CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
This file provides guidance to Kode automation agents (including those compatible with Claude Code's `.claude` ecosystem) when working with code in this repository.
## Development Commands
### Essential Development Workflow
```bash
# Install dependencies
bun install
@ -15,6 +16,9 @@ bun run dev
# Build the CLI wrapper for distribution
bun run build
# Pre-release integration testing
bun link
# Clean build artifacts
bun run clean
@ -30,12 +34,17 @@ bun run format:check
```
### Build System Details
- **Primary Build Tool**: Bun (required for development)
- **Distribution**: Smart CLI wrapper (`cli.js`) that prefers Bun but falls back to Node.js with tsx loader
- **Entry Point**: `src/entrypoints/cli.tsx`
- **Build Output**: `cli.js` (executable wrapper) and `.npmrc` (npm configuration)
- **Build Output**:
- `cli.js` - Cross-platform executable wrapper that uses `process.cwd()` as working directory
- `.npmrc` - npm configuration file with `package-lock=false` and `save-exact=true`
- `dist/` - ESM modules compiled from TypeScript sources
### Publishing
```bash
# Publish to npm (requires build first)
npm publish
@ -46,7 +55,7 @@ SKIP_BUNDLED_CHECK=true npm publish
## High-Level Architecture
### Core System Design
Kode implements a **three-layer parallel architecture** inspired by Claude Code:
Kode implements a **three-layer parallel architecture** refined for fast iteration across terminal workflows while remaining compatible with the Claude Code agent ecosystem:
1. **User Interaction Layer** (`src/screens/REPL.tsx`)
- Interactive terminal interface using Ink (React for CLI)
@ -74,9 +83,9 @@ Kode implements a **three-layer parallel architecture** inspired by Claude Code:
### Agent System (`src/utils/agentLoader.ts`)
**Dynamic Agent Configuration Loading** with 5-tier priority system:
1. Built-in (code-embedded)
2. `~/.claude/agents/` (Claude user)
2. `~/.claude/agents/` (Claude Code user directory compatibility)
3. `~/.kode/agents/` (Kode user)
4. `./.claude/agents/` (Claude project)
4. `./.claude/agents/` (Claude Code project directory compatibility)
5. `./.kode/agents/` (Kode project)
Agents are defined as markdown files with YAML frontmatter:
@ -99,7 +108,7 @@ Each tool follows a consistent pattern in `src/tools/[ToolName]/`:
- Permission-aware execution
### Service Layer
- **Claude Service** (`src/services/claude.ts`): Primary AI model interface
- **Anthropic Service** (`src/services/claude.ts`): Claude API integration
- **OpenAI Service** (`src/services/openai.ts`): OpenAI-compatible models
- **Model Adapter Factory** (`src/services/modelAdapterFactory.ts`): Unified model interface
- **MCP Client** (`src/services/mcpClient.ts`): Model Context Protocol for tool extensions
@ -112,6 +121,7 @@ Each tool follows a consistent pattern in `src/tools/[ToolName]/`:
- CLI parameter overrides
- Multi-model profile management
### Context Management
- **Message Context Manager** (`src/utils/messageContextManager.ts`): Intelligent context window handling
- **Memory Tools** (`src/tools/MemoryReadTool/`, `src/tools/MemoryWriteTool/`): Persistent memory across sessions
@ -204,4 +214,24 @@ const description = typeof tool.description === 'function'
1. Create `.md` file with proper YAML frontmatter
2. Place in appropriate directory based on scope
3. Test with `/agents` command
4. Verify tool permissions work correctly
4. Verify tool permissions work correctly
### Testing in Other Projects
After making changes, test the CLI in different environments:
1. **Development Testing**:
```bash
bun run build # Build with updated cli.js wrapper
bun link # Link globally for testing
```
2. **Test in External Project**:
```bash
cd /path/to/other/project
kode --help # Verify CLI works and uses correct working directory
```
3. **Verify Working Directory**:
- CLI wrapper uses `process.cwd()` to ensure commands run in user's current directory
- Not in Kode's installation directory
- Essential for file operations and project context

View File

@ -69,18 +69,19 @@ RUN npm install -g tsx
WORKDIR /workspace
# Copy built application from builder stage
COPY --from=builder /app/cli.js /app/cli.js
COPY --from=builder /app/dist /app/dist
COPY --from=builder /app/package.json /app/package.json
COPY --from=builder /app/node_modules /app/node_modules
COPY --from=builder /app/src /app/src
# Create the entrypoint script
RUN cat << 'EOF' > /entrypoint.sh
#!/bin/sh
# RUN cat << 'EOF' > /entrypoint.sh
# #!/bin/sh
/root/.bun/bin/bun /app/cli.js -c /workspace "$@"
EOF
# /root/.bun/bin/bun /app/dist/entrypoints/cli.js -c /workspace "$@"
# EOF
RUN chmod +x /entrypoint.sh
# RUN chmod +x /entrypoint.sh
# Set the entrypoint
ENTRYPOINT ["/entrypoint.sh"]
ENTRYPOINT ["/root/.bun/bin/bun", "/app/dist/entrypoints/cli.js", "-c", "/workspace"]

View File

@ -1,169 +0,0 @@
# 发包脚本使用指南
Kode 项目提供了两套发包流程,用于不同的发布场景:
## 🚀 快速使用
### 开发版本发布 (测试用)
```bash
npm run publish:dev
```
### 正式版本发布
```bash
npm run publish:release
```
## 📦 发包策略
### 1. 开发版本 (`dev` tag)
- **目的**: 内部测试和预发布验证
- **版本格式**: `1.1.16-dev.1`, `1.1.16-dev.2`
- **安装方式**: `npm install -g @shareai-lab/kode@dev`
- **特点**:
- 自动递增 dev 版本号
- 不影响正式版本的用户
- 可以快速迭代测试
### 2. 正式版本 (`latest` tag)
- **目的**: 面向最终用户的稳定版本
- **版本格式**: `1.1.16`, `1.1.17`, `1.2.0`
- **安装方式**: `npm install -g @shareai-lab/kode` (默认)
- **特点**:
- 语义化版本控制
- 严格的发布流程
- 包含完整的测试和检查
## 🛠️ 脚本功能详解
### 开发版本发布 (`scripts/publish-dev.js`)
**自动化流程**:
1. ✅ 检查当前分支和工作区状态
2. 🔢 自动生成递增的 dev 版本号
3. 🔨 构建项目
4. 🔍 运行预发布检查
5. 📤 发布到 npm 的 `dev` tag
6. 🏷️ 创建 git tag
7. 🔄 恢复 package.json (不提交版本变更)
**使用场景**:
- 功能开发完成,需要内部测试
- PR 合并前的最终验证
- 快速修复验证
**安全特性**:
- 临时修改 package.json发布后自动恢复
- 失败时自动回滚
- 不污染主分支版本号
### 正式版本发布 (`scripts/publish-release.js`)
**交互式流程**:
1. 🔍 检查分支 (建议在 main/master)
2. 🧹 确保工作区干净
3. 📡 拉取最新代码
4. 🔢 选择版本升级类型:
- **patch** (1.1.16 → 1.1.17): 修复 bug
- **minor** (1.1.16 → 1.2.0): 新功能
- **major** (1.1.16 → 2.0.0): 破坏性变更
- **custom**: 自定义版本号
5. ✅ 确认发布信息
6. 🧪 运行测试和类型检查
7. 🔨 构建项目
8. 📝 提交版本更新
9. 🏷️ 创建 git tag
10. 📤 发布到 npm (默认 `latest` tag)
11. 📡 推送到 git 仓库
**安全特性**:
- 交互式确认,避免误发布
- 测试失败时自动回滚版本号
- 完整的 git 历史记录
## 🎯 最佳实践
### 开发流程建议
```bash
# 1. 开发功能
git checkout -b feature/new-feature
# ... 开发代码 ...
git commit -am "feat: add new feature"
# 2. 发布开发版本测试
npm run publish:dev
# 安装测试: npm install -g @shareai-lab/kode@dev
# 3. 测试通过后合并到主分支
git checkout main
git merge feature/new-feature
# 4. 发布正式版本
npm run publish:release
```
### 版本号管理
- **开发版**: 基于当前正式版本自动递增
- **正式版**: 遵循 [语义化版本](https://semver.org/lang/zh-CN/) 规范
- **Git 标签**: 自动创建,格式 `v1.1.16`
### 标签管理
```bash
# 查看所有版本
npm view @shareai-lab/kode versions --json
# 查看 dev 版本
npm view @shareai-lab/kode@dev version
# 查看最新正式版本
npm view @shareai-lab/kode@latest version
```
## 🔧 故障排除
### 常见问题
**发布失败怎么办?**
- 脚本会自动回滚 package.json
- 检查错误信息,修复后重新运行
**版本号冲突?**
- 开发版本会自动递增,不会冲突
- 正式版本发布前会检查是否已存在
**权限问题?**
- 确保已登录 npm: `npm whoami`
- 确保有包的发布权限
**Git 相关错误?**
- 确保有 git 推送权限
- 检查远程仓库配置: `git remote -v`
### 手动清理
```bash
# 如果发布过程中断,可能需要手动清理
git tag -d v1.1.16-dev.1 # 删除本地标签
git push origin :v1.1.16-dev.1 # 删除远程标签
```
## 📊 监控和分析
```bash
# 查看包下载统计
npm view @shareai-lab/kode
# 查看所有版本的详细信息
npm view @shareai-lab/kode versions --json
# 测试安装
npm install -g @shareai-lab/kode@dev
kode --version
```
---
通过这套双发包系统,你可以:
- 🚀 快速发布开发版本进行内部测试
- 🛡️ 安全发布正式版本给最终用户
- 📈 保持清晰的版本管理和发布历史
- ⚡ 自动化大部分重复操作,减少人为错误

View File

@ -1,30 +1,26 @@
# Kode - AI Assistant for Your Terminal
# Kode - AI Coding
<img width="991" height="479" alt="image" src="https://github.com/user-attachments/assets/c1751e92-94dc-4e4a-9558-8cd2d058c1a1" /> <br>
[![npm version](https://badge.fury.io/js/@shareai-lab%2Fkode.svg)](https://www.npmjs.com/package/@shareai-lab/kode)
[![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0)
[![AGENTS.md](https://img.shields.io/badge/AGENTS.md-Compatible-brightgreen)](https://agents.md)
[中文文档](README.zh-CN.md) | [Contributing](CONTRIBUTING.md) | [Documentation](docs/)
## 🎉 Big Announcement: We're Now Apache 2.0 Licensed!
<img width="90%" alt="image" src="https://github.com/user-attachments/assets/fdce7017-8095-429d-b74e-07f43a6919e1" />
**Great news for the developer community!** In our commitment to democratizing AI agent technology and fostering a vibrant ecosystem of innovation, we're thrilled to announce that Kode has transitioned from AGPLv3 to the **Apache 2.0 license**.
<img width="90%" alt="2c0ad8540f2872d197c7b17ae23d74f5" src="https://github.com/user-attachments/assets/f220cc27-084d-468e-a3f4-d5bc44d84fac" />
### What This Means for You:
- ✅ **Complete Freedom**: Use Kode in any project - personal, commercial, or enterprise
- ✅ **Build Without Barriers**: Create proprietary solutions without open-sourcing requirements
- ✅ **Simple Attribution**: Just maintain copyright notices and license info
- ✅ **Join a Movement**: Be part of accelerating the world's transition to AI-powered development
<img width="90%" alt="f266d316d90ddd0db5a3d640c1126930" src="https://github.com/user-attachments/assets/90ec7399-1349-4607-b689-96613b3dc3e2" />
<img width="90%" alt="image" src="https://github.com/user-attachments/assets/b30696ce-5ab1-40a0-b741-c7ef3945dba0" />
This change reflects our belief that the future of software development is collaborative, open, and augmented by AI. By removing licensing barriers, we're empowering developers worldwide to build the next generation of AI-assisted tools and workflows. Let's build the future together! 🚀
## 📢 Update Log
**2025-08-29**: We've added Windows support! All Windows users can now run Kode using Git Bash, Unix subsystems, or WSL (Windows Subsystem for Linux) on their computers.
<img width="606" height="303" alt="image" src="https://github.com/user-attachments/assets/6cf50553-aacd-4241-a579-6e935b6c62b5" />
## 🤝 AGENTS.md Standard Support
**Kode proudly supports the [AGENTS.md standard protocol](https://agents.md) initiated by OpenAI** - a simple, open format for guiding programming agents that's used by 20k+ open source projects.
@ -32,17 +28,17 @@ This change reflects our belief that the future of software development is colla
### Full Compatibility with Multiple Standards
- ✅ **AGENTS.md** - Native support for the OpenAI-initiated standard format
- ✅ **CLAUDE.md** - Full backward compatibility with Claude Code configurations
- ✅ **CLAUDE.md** - Full backward compatibility with Claude Code `.claude` configurations
- ✅ **Subagent System** - Advanced agent delegation and task orchestration
- ✅ **Cross-platform** - Works with 20+ AI models and providers
Use `# Your documentation request` to generate and maintain your AGENTS.md file automatically, while maintaining full compatibility with existing Claude Code workflows.
Use `# Your documentation request` to generate and maintain your AGENTS.md file automatically, while preserving compatibility with existing `.claude` workflows.
## Overview
Kode is a powerful AI assistant that lives in your terminal. It can understand your codebase, edit files, run commands, and handle entire workflows for you.
> **⚠️ Security Notice**: Kode runs in YOLO mode by default (equivalent to Claude's `--dangerously-skip-permissions` flag), bypassing all permission checks for maximum productivity. YOLO mode is recommended only for trusted, secure environments when working on non-critical projects. If you're working with important files or using models of questionable capability, we strongly recommend using `kode --safe` to enable permission checks and manual approval for all operations.
> **⚠️ Security Notice**: Kode runs in YOLO mode by default (equivalent to Claude Code's `--dangerously-skip-permissions` flag), bypassing all permission checks for maximum productivity. YOLO mode is recommended only for trusted, secure environments when working on non-critical projects. If you're working with important files or using models of questionable capability, we strongly recommend using `kode --safe` to enable permission checks and manual approval for all operations.
>
> **📊 Model Performance**: For optimal performance, we recommend using newer, more capable models designed for autonomous task completion. Avoid older Q&A-focused models like GPT-4o or Gemini 2.5 Pro, which are optimized for answering questions rather than sustained independent task execution. Choose models specifically trained for agentic workflows and extended reasoning capabilities.
@ -89,20 +85,6 @@ Our state-of-the-art completion system provides unparalleled coding assistance:
## Installation
### Recommended: Using Bun (Fastest)
First install Bun if you haven't already:
```bash
curl -fsSL https://bun.sh/install | bash
```
Then install Kode:
```bash
bun add -g @shareai-lab/kode
```
### Alternative: Using npm
```bash
npm install -g @shareai-lab/kode
```
@ -112,6 +94,18 @@ After installation, you can use any of these commands:
- `kwa` - Kode With Agent (alternative)
- `kd` - Ultra-short alias
### Windows Notes
- Install Git for Windows to provide a Bash (Unixlike) environment: https://git-scm.com/download/win
- Kode automatically prefers Git Bash/MSYS or WSL Bash when available.
- If neither is available, it will fall back to your default shell, but many features work best with Bash.
- Use VS Codes integrated terminal rather than legacy Command Prompt (cmd):
- Better font rendering and icon support.
- Fewer path and encoding quirks compared to cmd.
- Select “Git Bash” as the VS Code terminal shell when possible.
- Optional: If you install globally via npm, avoid spaces in the global prefix path to prevent shim issues.
- Example: `npm config set prefix "C:\\npm"` and reinstall global packages.
## Usage
### Interactive Mode
@ -127,9 +121,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

@ -23,7 +23,7 @@
Kode 是一个强大的 AI 助手,运行在你的终端中。它能理解你的代码库、编辑文件、运行命令,并为你处理整个开发工作流。
> **⚠️ 安全提示**Kode 默认以 YOLO 模式运行(等同于 Claude 的 `--dangerously-skip-permissions` 标志跳过所有权限检查以获得最大生产力。YOLO 模式仅建议在安全可信的环境中处理非重要项目时使用。如果您正在处理重要文件或使用能力存疑的模型,我们强烈建议使用 `kode --safe` 启用权限检查和手动审批所有操作。
> **⚠️ 安全提示**Kode 默认以 YOLO 模式运行(等同于 Claude Code `--dangerously-skip-permissions` 标志跳过所有权限检查以获得最大生产力。YOLO 模式仅建议在安全可信的环境中处理非重要项目时使用。如果您正在处理重要文件或使用能力存疑的模型,我们强烈建议使用 `kode --safe` 启用权限检查和手动审批所有操作。
>
> **📊 模型性能建议**:为获得最佳体验,建议使用专为自主任务完成设计的新一代强大模型。避免使用 GPT-4o、Gemini 2.5 Pro 等较老的问答型模型,它们主要针对回答问题进行优化,而非持续的独立任务执行。请选择专门训练用于智能体工作流和扩展推理能力的模型。
@ -41,20 +41,6 @@ Kode 是一个强大的 AI 助手,运行在你的终端中。它能理解你
## 安装
### 推荐方式:使用 Bun最快
首先安装 Bun如果尚未安装
```bash
curl -fsSL https://bun.sh/install | bash
```
然后安装 Kode
```bash
bun add -g @shareai-lab/kode
```
### 备选方式:使用 npm
```bash
npm install -g @shareai-lab/kode
```
@ -64,6 +50,17 @@ npm install -g @shareai-lab/kode
- `kwa` - Kode With Agent备选
- `kd` - 超短别名
### Windows 提示
- 请安装 Git for Windows包含 Git Bash 类 Unix 终端https://git-scm.com/download/win
- Kode 会优先使用 Git Bash/MSYS 或 WSL Bash没有时会回退到默认终端但在 Bash 下体验更佳。
- 推荐在 VS Code 的集成终端中运行(而非系统默认的 cmd
- 字体与图标显示更稳定UI 体验更好。
- 相比 cmd 路径/编码等兼容性问题更少。
- 在 VS Code 终端中选择 “Git Bash” 作为默认 Shell。
- 可选:若通过 npm 全局安装,建议避免将 npm 全局 prefix 设置在含空格的路径,以免生成的可执行 shim 出现路径解析问题。
- 示例:`npm config set prefix "C:\\npm"`,然后重新安装全局包。
## 使用方法
### 交互模式
@ -79,9 +76,9 @@ kd
### 非交互模式
获取快速响应:
```bash
kode -p "解释这个函数" main.js
kode -p "解释这个函数" 路径/到/文件.js
# 或
kwa -p "解释这个函数" main.js
kwa -p "解释这个函数" 路径/到/文件.js
```
### Docker 使用说明

202
docs/PUBLISH_GUIDE.md Normal file
View File

@ -0,0 +1,202 @@
# 发包脚本使用指南
Kode 项目提供了两套发包流程专注于npm发布不涉及git操作
## 🚀 快速使用
### 开发版本发布 (测试用)
```bash
npm run publish:dev
```
### 正式版本发布
```bash
npm run publish:release
```
## 📦 发包策略
### 1. 开发版本 (`dev` tag)
- **目的**: 内部测试和预发布验证
- **版本格式**: `1.1.16-dev.1`, `1.1.16-dev.2`
- **安装方式**: `npm install -g @shareai-lab/kode@dev`
- **特点**:
- 自动递增 dev 版本号
- 不影响正式版本的用户
- 可以快速迭代测试
- 临时修改package.json发布后自动恢复
### 2. 正式版本 (`latest` tag)
- **目的**: 面向最终用户的稳定版本
- **版本格式**: `1.1.16`, `1.1.17`, `1.2.0`
- **安装方式**: `npm install -g @shareai-lab/kode` (默认)
- **特点**:
- 语义化版本控制
- 严格的发布流程
- 包含完整的测试和检查
- 永久更新package.json版本号
## 🛠️ 脚本功能详解
### 开发版本发布 (`scripts/publish-dev.js`)
**自动化流程**:
1. 🔢 读取当前基础版本
2. 📊 查询npm上现有的dev版本自动递增
3. 📝 临时更新package.json版本号
4. 🔨 构建项目
5. 🔍 运行预发布检查
6. 📤 发布到npm的`dev` tag
7. 🔄 恢复package.json到原始版本
**使用场景**:
- 功能开发完成,需要内部测试
- PR验证前的最终测试
- 快速修复验证
**安全特性**:
- 临时修改package.json发布后自动恢复
- 失败时自动回滚
- 不会改变本地版本状态
### 正式版本发布 (`scripts/publish-release.js`)
**交互式流程**:
1. 📦 显示当前版本
2. 🔢 选择版本升级类型:
- **patch** (1.1.16 → 1.1.17): 修复 bug
- **minor** (1.1.16 → 1.2.0): 新功能
- **major** (1.1.16 → 2.0.0): 破坏性变更
- **custom**: 自定义版本号
3. ✅ 检查版本是否已在npm上存在
4. 🤔 确认发布信息
5. 📝 更新package.json版本号永久
6. 🧪 运行测试和类型检查
7. 🔨 构建项目
8. 🔍 运行预发布检查
9. 📤 发布到npm (默认`latest` tag)
10. 💡 提示后续git操作建议
**安全特性**:
- 交互式确认,避免误发布
- 检查版本冲突
- 测试失败时自动回滚版本号
- 永久保留版本更改供git提交
## 🎯 最佳实践
### 开发流程建议
```bash
# 1. 开发功能
# ... 开发代码 ...
# 2. 发布开发版本测试
npm run publish:dev
# 安装测试: npm install -g @shareai-lab/kode@dev
# 3. 测试通过后发布正式版本
npm run publish:release
# 4. 手动处理git操作如需要
git add package.json
git commit -m "chore: bump version to x.x.x"
git tag vx.x.x
git push origin main
git push origin vx.x.x
```
### 版本号管理
- **开发版**: 基于当前正式版本自动递增 (1.1.16-dev.1 → 1.1.16-dev.2)
- **正式版**: 遵循 [语义化版本](https://semver.org/lang/zh-CN/) 规范
- **版本检查**: 自动检查npm上是否已存在相同版本
### 标签管理
```bash
# 查看所有版本
npm view @shareai-lab/kode versions --json
# 查看 dev 版本
npm view @shareai-lab/kode@dev version
# 查看最新正式版本
npm view @shareai-lab/kode@latest version
```
## 🔧 故障排除
### 常见问题
**发布失败怎么办?**
- 开发版本脚本会自动回滚package.json
- 正式版本:检查错误信息,修复后重新运行
**版本号冲突?**
- 开发版本会自动递增,不会冲突
- 正式版本发布前会检查是否已存在
**权限问题?**
- 确保已登录npm: `npm whoami`
- 确保有包的发布权限: `npm owner ls @shareai-lab/kode`
**测试失败?**
- 正式版本发布会自动回滚版本号
- 修复测试问题后重新运行
### 手动操作
```bash
# 查看当前登录用户
npm whoami
# 登录npm
npm login
# 查看包信息
npm view @shareai-lab/kode
# 手动发布(不推荐)
npm publish --tag dev # 开发版本
npm publish # 正式版本
```
## 📊 监控和分析
```bash
# 查看包下载统计
npm view @shareai-lab/kode
# 查看所有版本的详细信息
npm view @shareai-lab/kode versions --json
# 测试安装
npm install -g @shareai-lab/kode@dev
kode --version
```
## ⚙️ Git集成建议
虽然发包脚本不包含git操作但建议的git工作流程
**开发版本发布后**:
```bash
# 开发版本不需要git操作package.json已自动恢复
# 继续开发即可
```
**正式版本发布后**:
```bash
# package.json已更新版本号建议提交
git add package.json
git commit -m "chore: bump version to $(node -p "require('./package.json').version")"
git tag "v$(node -p "require('./package.json').version")"
git push origin main
git push origin --tags
```
---
通过这套纯npm发包系统你可以
- 🚀 快速发布开发版本进行内部测试
- 🛡️ 安全发布正式版本给最终用户
- 📈 保持清晰的版本管理
- ⚡ 专注于包发布git操作完全可控
- 🔄 灵活的版本回滚和恢复机制

View File

@ -74,8 +74,8 @@ Agents can be defined at five levels with priority order (later overrides earlie
- Provided with Kode
- Cannot be modified
2. **Claude User** (`~/.claude/agents/`)
- Claude Code compatible user-level agents
2. **.claude User (Claude Code)** (`~/.claude/agents/`)
- Compatibility with Claude Code user directories
- Personal agents available across all projects
3. **Kode User** (`~/.kode/agents/`)
@ -83,8 +83,8 @@ Agents can be defined at five levels with priority order (later overrides earlie
- Overrides Claude user agents with same name
- Create with `/agents` command or manually
4. **Claude Project** (`./.claude/agents/`)
- Claude Code compatible project-specific agents
4. **.claude Project (Claude Code)** (`./.claude/agents/`)
- Compatibility with Claude Code project directories
- Overrides user-level agents
5. **Kode Project** (`./.kode/agents/`)
@ -227,4 +227,4 @@ Planned improvements:
- Performance metrics per agent
- Agent composition (agents using other agents)
- Cloud-based agent sharing
- Agent versioning and rollback
- Agent versioning and rollback

View File

@ -17,6 +17,7 @@ This comprehensive documentation provides a complete understanding of the Kode c
- **[Model Management](./modules/model-management.md)** - Multi-provider AI model integration and intelligent switching
- **[MCP Integration](./modules/mcp-integration.md)** - Model Context Protocol for third-party tool integration
- **[Custom Commands](./modules/custom-commands.md)** - Markdown-based extensible command system
- **[OpenAI Adapter Layer](./modules/openai-adapters.md)** - Anthropic-to-OpenAI request translation for Chat Completions and Responses API
### Core Modules
@ -216,4 +217,4 @@ For questions or issues:
---
This documentation represents the complete technical understanding of the Kode system as of the current version. It serves as the authoritative reference for developers working on or with the Kode codebase.
This documentation represents the complete technical understanding of the Kode system as of the current version. It serves as the authoritative reference for developers working on or with the Kode codebase.

View File

@ -0,0 +1,63 @@
# OpenAI Adapter Layer
This module explains how Kodes Anthropic-first conversation engine can selectively route requests through OpenAI Chat Completions or the new Responses API without exposing that complexity to the rest of the system. The adapter layer only runs when `USE_NEW_ADAPTERS !== 'false'` and a `ModelProfile` is available.
## Goals
- Preserve Anthropic-native data structures (`AssistantMessage`, `MessageParam`, tool blocks) everywhere outside the adapter layer.
- Translate those structures into a provider-neutral `UnifiedRequestParams` shape so different adapters can share logic.
- Map the unified format onto each providers transport (Chat Completions vs Responses API) and back into Anthropic-style `AssistantMessage` objects.
## Request Flow
1. **Anthropic Messages → Unified Params**
`queryOpenAI` (`src/services/claude.ts`) converts the existing Anthropic message history into OpenAI-style role/content pairs via `convertAnthropicMessagesToOpenAIMessages`, flattens system prompts, and builds a `UnifiedRequestParams` bundle (see `src/types/modelCapabilities.ts`). This bundle captures:
- `messages`: already normalized to OpenAI format but still provider-neutral inside the adapters.
- `systemPrompt`: array of strings, preserving multi-block Anthropic system prompts.
- `tools`: tool metadata (names, descriptions, JSON schema) fetched once so adapters can reshape it.
- `maxTokens`, `stream`, `reasoningEffort`, `verbosity`, `previousResponseId`, and `temperature` flags.
2. **Adapter Selection**
`ModelAdapterFactory` inspects the `ModelProfile` and capability table (`src/constants/modelCapabilities.ts`) to choose either:
- `ChatCompletionsAdapter` for classic `/chat/completions` style providers.
- `ResponsesAPIAdapter` when the provider natively supports `/responses`.
3. **Adapter-Specific Request Construction**
- **Chat Completions (`src/services/adapters/chatCompletions.ts`)**
- Reassembles a single message list including system prompts.
- Picks the correct max-token field (`max_tokens` vs `max_completion_tokens`).
- Attaches OpenAI function-calling tool descriptors, optional `stream_options`, reasoning effort, and verbosity when supported.
- Handles model quirks (e.g., removes unsupported fields for `o1` models).
- **Responses API (`src/services/adapters/responsesAPI.ts`)**
- Converts chat-style messages into `input` items (message blocks, function-call outputs, images).
- Moves system prompts into the `instructions` string.
- Uses `max_output_tokens`, always enables streaming, and adds `include` entries for reasoning envelopes.
- Emits the flat `tools` array expected by `/responses`, `tool_choice`, `parallel_tool_calls`, state IDs, verbosity controls, etc.
4. **Transport**
Both adapters delegate the actual network call to helpers in `src/services/openai.ts`:
- Chat Completions requests use `getCompletionWithProfile` (legacy path) or the same helper `queryOpenAI` previously relied on.
- Responses API requests go through `callGPT5ResponsesAPI`, which POSTs the adapter-built payload and returns the raw `Response` object for streaming support.
## Response Flow
1. **Raw Response → Unified Response**
- `ChatCompletionsAdapter.parseResponse` pulls the first `choice`, extracts tool calls, and normalizes usage counts.
- `ResponsesAPIAdapter.parseResponse` distinguishes between streaming vs JSON responses:
- Streaming: incrementally decode SSE chunks, concatenate `response.output_text.delta`, and capture completed tool calls.
- JSON: fold `output` message items into text blocks, gather tool-call items, and preserve `usage`/`response.id` for stateful follow-ups.
- Both return a `UnifiedResponse` containing `content`, `toolCalls`, token usage, and optional `responseId`.
2. **Unified Response → Anthropic AssistantMessage**
Back in `queryOpenAI`, the unified response is wrapped in Anthropics schema: `content` becomes Ink-ready blocks, tool calls become `tool_use` entries, and usage numbers flow into `AssistantMessage.message.usage`. Consumers (UI, TaskTool, etc.) continue to see only Anthropic-style messages.
## Legacy Fallbacks
- If `USE_NEW_ADAPTERS === 'false'` or no `ModelProfile` is available, the system bypasses adapters entirely and hits `getCompletionWithProfile` / `getGPT5CompletionWithProfile`. These paths still rely on helper utilities in `src/services/openai.ts`.
- `ResponsesAPIAdapter` also carries compatibility flags (e.g., `previousResponseId`, `parallel_tool_calls`) so a single unified params structure works across official OpenAI and third-party providers.
## When to Extend This Layer
- **New OpenAI-style providers**: add capability metadata and, if necessary, a specialized adapter that extends `ModelAPIAdapter`.
- **Model-specific quirks**: keep conversions inside the adapter so upstream Anthropic abstractions stay untouched.
- **Stateful Responses**: leverage the `responseId` surfaced by `UnifiedResponse` to support follow-up calls that require `previous_response_id`.

View File

@ -1,451 +0,0 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>喷火蛇游戏</title>
<style>
body {
margin: 0;
padding: 20px;
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
background: linear-gradient(45deg, #1a1a2e, #16213e);
font-family: 'Courier New', monospace;
color: #fff;
}
.game-container {
text-align: center;
background: rgba(0, 0, 0, 0.3);
border-radius: 15px;
padding: 20px;
box-shadow: 0 10px 30px rgba(255, 100, 100, 0.3);
}
h1 {
color: #ff6b6b;
margin-bottom: 10px;
text-shadow: 2px 2px 4px rgba(255, 100, 100, 0.5);
}
#gameCanvas {
border: 3px solid #ff6b6b;
border-radius: 10px;
background: #000;
box-shadow: 0 0 20px rgba(255, 100, 100, 0.4);
}
.controls {
margin: 15px 0;
color: #ffa500;
}
.score {
font-size: 18px;
color: #00ff00;
margin: 10px 0;
}
.game-over {
color: #ff4444;
font-size: 24px;
margin: 15px 0;
}
button {
background: linear-gradient(45deg, #ff6b6b, #ff8e53);
border: none;
color: white;
padding: 10px 20px;
border-radius: 25px;
cursor: pointer;
font-family: inherit;
font-size: 16px;
margin: 5px;
transition: transform 0.2s;
}
button:hover {
transform: scale(1.1);
}
</style>
</head>
<body>
<div class="game-container">
<h1>🐍 喷火蛇游戏 🔥</h1>
<div class="controls">
使用方向键移动 | 空格键喷火 | R键重新开始
</div>
<div class="score" id="score">分数: 0</div>
<canvas id="gameCanvas" width="600" height="400"></canvas>
<div id="gameOver" class="game-over" style="display: none;">
游戏结束按R键重新开始
</div>
<button onclick="startGame()">开始游戏</button>
<button onclick="togglePause()">暂停/继续</button>
</div>
<script>
const canvas = document.getElementById('gameCanvas');
const ctx = canvas.getContext('2d');
const scoreElement = document.getElementById('score');
const gameOverElement = document.getElementById('gameOver');
// 游戏状态
let gameRunning = false;
let gamePaused = false;
let score = 0;
// 网格大小
const gridSize = 20;
const canvasWidth = canvas.width;
const canvasHeight = canvas.height;
// 蛇的初始状态
let snake = [
{x: 10, y: 10}
];
let direction = {x: 0, y: 0};
// 食物
let food = generateFood();
// 火焰系统
let flames = [];
let fireBreathing = false;
// 粒子效果
let particles = [];
// 生成食物
function generateFood() {
return {
x: Math.floor(Math.random() * (canvasWidth / gridSize)),
y: Math.floor(Math.random() * (canvasHeight / gridSize))
};
}
// 火焰粒子类
class Flame {
constructor(x, y, angle, speed) {
this.x = x;
this.y = y;
this.vx = Math.cos(angle) * speed;
this.vy = Math.sin(angle) * speed;
this.life = 30;
this.maxLife = 30;
this.size = Math.random() * 8 + 4;
}
update() {
this.x += this.vx;
this.y += this.vy;
this.life--;
this.size *= 0.96;
}
draw() {
if (this.life <= 0) return;
const alpha = this.life / this.maxLife;
const hue = 60 - (1 - alpha) * 60; // 从黄色到红色
ctx.save();
ctx.globalAlpha = alpha;
ctx.fillStyle = `hsl(${hue}, 100%, 50%)`;
ctx.beginPath();
ctx.arc(this.x, this.y, this.size, 0, Math.PI * 2);
ctx.fill();
ctx.restore();
}
}
// 粒子类(用于特效)
class Particle {
constructor(x, y, color) {
this.x = x;
this.y = y;
this.vx = (Math.random() - 0.5) * 4;
this.vy = (Math.random() - 0.5) * 4;
this.life = 20;
this.color = color;
}
update() {
this.x += this.vx;
this.y += this.vy;
this.life--;
}
draw() {
if (this.life <= 0) return;
ctx.save();
ctx.globalAlpha = this.life / 20;
ctx.fillStyle = this.color;
ctx.fillRect(this.x, this.y, 4, 4);
ctx.restore();
}
}
// 键盘事件处理
document.addEventListener('keydown', (e) => {
if (!gameRunning || gamePaused) {
if (e.code === 'KeyR') {
startGame();
}
return;
}
switch(e.code) {
case 'ArrowUp':
if (direction.y === 0) direction = {x: 0, y: -1};
break;
case 'ArrowDown':
if (direction.y === 0) direction = {x: 0, y: 1};
break;
case 'ArrowLeft':
if (direction.x === 0) direction = {x: -1, y: 0};
break;
case 'ArrowRight':
if (direction.x === 0) direction = {x: 1, y: 0};
break;
case 'Space':
e.preventDefault();
breatheFire();
break;
case 'KeyR':
startGame();
break;
}
});
// 喷火功能
function breatheFire() {
if (!gameRunning || fireBreathing) return;
fireBreathing = true;
setTimeout(() => fireBreathing = false, 200);
const head = snake[0];
const headX = head.x * gridSize + gridSize / 2;
const headY = head.y * gridSize + gridSize / 2;
// 计算喷火方向
let fireAngle = 0;
if (direction.x === 1) fireAngle = 0;
else if (direction.x === -1) fireAngle = Math.PI;
else if (direction.y === -1) fireAngle = -Math.PI / 2;
else if (direction.y === 1) fireAngle = Math.PI / 2;
// 创建火焰粒子
for (let i = 0; i < 15; i++) {
const angle = fireAngle + (Math.random() - 0.5) * 0.8;
const speed = Math.random() * 8 + 4;
flames.push(new Flame(headX, headY, angle, speed));
}
}
// 检查火焰碰撞
function checkFlameCollisions() {
flames.forEach(flame => {
// 检查是否击中食物
const foodX = food.x * gridSize + gridSize / 2;
const foodY = food.y * gridSize + gridSize / 2;
const distance = Math.sqrt((flame.x - foodX) ** 2 + (flame.y - foodY) ** 2);
if (distance < gridSize / 2 + flame.size) {
// 火焰击中食物,获得额外分数
score += 5;
food = generateFood();
// 添加特效
for (let i = 0; i < 10; i++) {
particles.push(new Particle(foodX, foodY, '#ffff00'));
}
}
});
}
// 绘制蛇
function drawSnake() {
snake.forEach((segment, index) => {
ctx.fillStyle = index === 0 ? '#ff6b6b' : '#ff8e53'; // 头部更红
ctx.fillRect(segment.x * gridSize, segment.y * gridSize, gridSize - 2, gridSize - 2);
// 蛇头添加眼睛
if (index === 0) {
ctx.fillStyle = '#fff';
ctx.fillRect(segment.x * gridSize + 4, segment.y * gridSize + 4, 4, 4);
ctx.fillRect(segment.x * gridSize + 12, segment.y * gridSize + 4, 4, 4);
ctx.fillStyle = '#000';
ctx.fillRect(segment.x * gridSize + 5, segment.y * gridSize + 5, 2, 2);
ctx.fillRect(segment.x * gridSize + 13, segment.y * gridSize + 5, 2, 2);
}
});
}
// 绘制食物
function drawFood() {
ctx.fillStyle = '#00ff00';
ctx.fillRect(food.x * gridSize, food.y * gridSize, gridSize - 2, gridSize - 2);
// 食物闪烁效果
if (Math.floor(Date.now() / 200) % 2) {
ctx.fillStyle = '#88ff88';
ctx.fillRect(food.x * gridSize + 4, food.y * gridSize + 4, gridSize - 10, gridSize - 10);
}
}
// 移动蛇
function moveSnake() {
if (direction.x === 0 && direction.y === 0) return;
const head = {
x: snake[0].x + direction.x,
y: snake[0].y + direction.y
};
snake.unshift(head);
// 检查是否吃到食物
if (head.x === food.x && head.y === food.y) {
score += 10;
food = generateFood();
// 添加粒子特效
for (let i = 0; i < 8; i++) {
particles.push(new Particle(
head.x * gridSize + gridSize / 2,
head.y * gridSize + gridSize / 2,
'#00ff00'
));
}
} else {
snake.pop();
}
}
// 检查碰撞
function checkCollisions() {
const head = snake[0];
// 墙壁碰撞
if (head.x < 0 || head.x >= canvasWidth / gridSize ||
head.y < 0 || head.y >= canvasHeight / gridSize) {
gameOver();
return;
}
// 自身碰撞
for (let i = 1; i < snake.length; i++) {
if (head.x === snake[i].x && head.y === snake[i].y) {
gameOver();
return;
}
}
}
// 游戏结束
function gameOver() {
gameRunning = false;
gameOverElement.style.display = 'block';
}
// 更新游戏
function update() {
if (!gameRunning || gamePaused) return;
moveSnake();
checkCollisions();
checkFlameCollisions();
// 更新火焰
flames = flames.filter(flame => {
flame.update();
return flame.life > 0;
});
// 更新粒子
particles = particles.filter(particle => {
particle.update();
return particle.life > 0;
});
scoreElement.textContent = `分数: ${score}`;
}
// 绘制游戏
function draw() {
// 清空画布
ctx.fillStyle = '#000';
ctx.fillRect(0, 0, canvasWidth, canvasHeight);
// 绘制网格(可选)
ctx.strokeStyle = '#111';
for (let i = 0; i < canvasWidth; i += gridSize) {
ctx.beginPath();
ctx.moveTo(i, 0);
ctx.lineTo(i, canvasHeight);
ctx.stroke();
}
for (let i = 0; i < canvasHeight; i += gridSize) {
ctx.beginPath();
ctx.moveTo(0, i);
ctx.lineTo(canvasWidth, i);
ctx.stroke();
}
drawFood();
drawSnake();
// 绘制火焰
flames.forEach(flame => flame.draw());
// 绘制粒子
particles.forEach(particle => particle.draw());
}
// 游戏主循环
function gameLoop() {
update();
draw();
requestAnimationFrame(gameLoop);
}
// 开始游戏
function startGame() {
gameRunning = true;
gamePaused = false;
score = 0;
snake = [{x: 10, y: 10}];
direction = {x: 0, y: 0};
food = generateFood();
flames = [];
particles = [];
gameOverElement.style.display = 'none';
}
// 暂停/继续游戏
function togglePause() {
if (gameRunning) {
gamePaused = !gamePaused;
}
}
// 启动游戏循环
gameLoop();
// 自动开始游戏
setTimeout(() => {
if (!gameRunning) {
startGame();
}
}, 1000);
</script>
</body>
</html>

View File

@ -1,114 +0,0 @@
#!/bin/bash
# TypeScript Error Fix Execution Script
# This script helps track progress through the fix phases
set -e
echo "TypeScript Error Fix Script - 100% Confidence Plan"
echo "=================================================="
echo ""
# Color codes for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m' # No Color
# Function to run TypeScript check
check_typescript() {
echo -e "${YELLOW}Running TypeScript compilation check...${NC}"
if npx tsc --noEmit 2>&1 | tee typescript-errors.log; then
echo -e "${GREEN}✓ No TypeScript errors found!${NC}"
return 0
else
ERROR_COUNT=$(npx tsc --noEmit 2>&1 | wc -l)
echo -e "${RED}✗ Found $ERROR_COUNT lines of errors${NC}"
return 1
fi
}
# Function to show current phase
show_phase() {
echo ""
echo -e "${GREEN}════════════════════════════════════════${NC}"
echo -e "${GREEN} PHASE $1: $2${NC}"
echo -e "${GREEN}════════════════════════════════════════${NC}"
echo ""
}
# Initial check
echo "Initial TypeScript Error Count:"
check_typescript || true
INITIAL_ERRORS=$(wc -l < typescript-errors.log)
echo ""
# Phase tracking
CURRENT_PHASE=1
PHASES_COMPLETED=0
while true; do
echo -e "${YELLOW}Current Phase: $CURRENT_PHASE${NC}"
echo "Select an action:"
echo "1) Check current TypeScript errors"
echo "2) Mark current phase as complete"
echo "3) View specific error category"
echo "4) Generate error summary"
echo "5) Exit"
read -p "Choice: " choice
case $choice in
1)
check_typescript || true
CURRENT_ERRORS=$(wc -l < typescript-errors.log)
FIXED=$((INITIAL_ERRORS - CURRENT_ERRORS))
echo ""
echo -e "${GREEN}Progress: Fixed $FIXED errors (from $INITIAL_ERRORS to $CURRENT_ERRORS)${NC}"
;;
2)
PHASES_COMPLETED=$((PHASES_COMPLETED + 1))
echo -e "${GREEN}✓ Phase $CURRENT_PHASE completed!${NC}"
CURRENT_PHASE=$((CURRENT_PHASE + 1))
case $CURRENT_PHASE in
2) show_phase 2 "Tool System Implementation" ;;
3) show_phase 3 "React 19 / Ink 6 Components" ;;
4) show_phase 4 "Service Layer Fixes" ;;
5) show_phase 5 "Hook System Updates" ;;
6) show_phase 6 "Utility Functions" ;;
7) show_phase 7 "Dependency Management" ;;
8) show_phase 8 "Validation & Testing" ;;
*)
echo -e "${GREEN}🎉 All phases completed!${NC}"
check_typescript && echo -e "${GREEN}✨ TypeScript compilation successful!${NC}"
exit 0
;;
esac
;;
3)
echo "Error categories:"
echo "1) Tool errors"
echo "2) Component errors"
echo "3) Hook errors"
echo "4) Service errors"
read -p "Select category: " cat
case $cat in
1) grep -E "src/tools/" typescript-errors.log | head -20 ;;
2) grep -E "src/components/|src/screens/" typescript-errors.log | head -20 ;;
3) grep -E "src/hooks/" typescript-errors.log | head -20 ;;
4) grep -E "src/services/" typescript-errors.log | head -20 ;;
esac
;;
4)
echo "Error Summary by Directory:"
echo "----------------------------"
npx tsc --noEmit 2>&1 | grep -oE "src/[^(]*" | cut -d: -f1 | xargs -I {} dirname {} | sort | uniq -c | sort -rn
;;
5)
echo "Exiting..."
exit 0
;;
esac
echo ""
done

View File

@ -1,6 +1,6 @@
{
"name": "@shareai-lab/kode",
"version": "1.1.16",
"version": "1.1.23",
"bin": {
"kode": "cli.js",
"kwa": "cli.js",
@ -43,5 +43,60 @@
"prepare": "",
"publish:dev": "node scripts/publish-dev.js",
"publish:release": "node scripts/publish-release.js"
},
"dependencies": {
"@anthropic-ai/bedrock-sdk": "^0.12.6",
"@anthropic-ai/sdk": "^0.39.0",
"@anthropic-ai/vertex-sdk": "^0.7.0",
"@commander-js/extra-typings": "^13.1.0",
"@inkjs/ui": "^2.0.0",
"@modelcontextprotocol/sdk": "^1.15.1",
"@types/lodash-es": "^4.17.12",
"@types/react": "^19.1.8",
"ansi-escapes": "^7.0.0",
"chalk": "^5.4.1",
"cli-highlight": "^2.1.11",
"cli-table3": "^0.6.5",
"commander": "^13.1.0",
"debug": "^4.4.1",
"diff": "^7.0.0",
"dotenv": "^16.6.1",
"env-paths": "^3.0.0",
"figures": "^6.1.0",
"glob": "^11.0.3",
"gray-matter": "^4.0.3",
"highlight.js": "^11.11.1",
"ink": "^5.2.1",
"ink-link": "^4.1.0",
"ink-select-input": "^6.2.0",
"ink-text-input": "^6.0.0",
"lodash-es": "^4.17.21",
"lru-cache": "^11.1.0",
"marked": "^15.0.12",
"nanoid": "^5.1.5",
"node-fetch": "^3.3.2",
"node-html-parser": "^7.0.1",
"openai": "^4.104.0",
"react": "18.3.1",
"semver": "^7.7.2",
"shell-quote": "^1.8.3",
"spawn-rx": "^5.1.2",
"string-width": "^7.2.0",
"strip-ansi": "^7.1.0",
"tsx": "^4.20.3",
"turndown": "^7.2.0",
"undici": "^7.11.0",
"wrap-ansi": "^9.0.0",
"zod": "^3.25.76",
"zod-to-json-schema": "^3.24.6"
},
"devDependencies": {
"@types/bun": "latest",
"@types/jest": "^30.0.0",
"@types/node": "^24.1.0",
"bun-types": "latest",
"esbuild": "^0.25.9",
"prettier": "^3.6.2",
"typescript": "^5.9.2"
}
}
}

View File

@ -1,146 +0,0 @@
# Phase 1: Core Type System Foundation - Detailed Fix Guide
## 1.1 Message Type System Fix
### Problem Analysis
The `Message` type union has inconsistent property access. Some code expects a `message` property that doesn't exist on all union members.
### Fix Location: `src/messages.ts`
```typescript
// Current problematic union
export type Message = AssistantMessage | UserMessage | ProgressMessage
// The issue: Code in query.ts accesses .message property which doesn't exist on ProgressMessage
```
### Solution
```typescript
// Option 1: Add message property to ProgressMessage
export interface ProgressMessage {
type: 'progress'
message?: any // Add this
// ... existing properties
}
// Option 2: Fix access pattern in query.ts
// Instead of directly accessing .message, use type guards:
if (msg.type !== 'progress' && 'message' in msg) {
// Safe to access msg.message
}
```
### Fix in `src/utils/messageContextManager.ts` (line 136)
```typescript
// Current
return {
type: "assistant",
message: { role: "assistant", content: [...] }
}
// Fixed
return {
type: "assistant",
message: { role: "assistant", content: [...] },
costUSD: 0, // Add required property
durationMs: 0, // Add required property
uuid: crypto.randomUUID() as UUID // Add required property
}
```
## 1.2 Tool Interface Alignment
### Problem Analysis
The Tool interface expects specific return types, but implementations return different types.
### Fix Location: `src/Tool.ts`
```typescript
// Current interface (approximate)
export interface Tool<TInput, TOutput> {
renderResultForAssistant(output: TOutput): string
renderToolUseRejectedMessage(): React.ReactElement
// ...
}
// Fixed interface
export interface Tool<TInput, TOutput> {
renderResultForAssistant(output: TOutput): string | any[] // Allow arrays
renderToolUseRejectedMessage(...args: any[]): React.ReactElement // Allow optional params
// ...
}
```
### Add to ToolUseContext
```typescript
// In src/types.ts or wherever ToolUseContext is defined
export interface ToolUseContext {
// ... existing properties
setToolJSX?: (jsx: React.ReactElement) => void // Add as optional
}
export interface ExtendedToolUseContext extends ToolUseContext {
setToolJSX: (jsx: React.ReactElement) => void // Required in extended version
}
```
## 1.3 Key Type Extensions
### Problem Analysis
The Key type from Ink doesn't have all properties that the code expects.
### Fix Location: Create `src/types/ink-augmentation.d.ts`
```typescript
// Type augmentation for ink
declare module 'ink' {
interface Key {
fn?: boolean
home?: boolean
end?: boolean
space?: boolean
}
}
```
### Alternative: Create wrapper type
```typescript
// In src/types/input.ts
import { Key as InkKey } from 'ink'
export interface ExtendedKey extends InkKey {
fn?: boolean
home?: boolean
end?: boolean
space?: boolean
}
// Then update all usages from Key to ExtendedKey
```
## Verification Steps
After each fix:
1. Run `npx tsc --noEmit` to check error count
2. Verify no runtime errors with `bun run dev`
3. Test affected functionality
## Expected Outcome
After Phase 1 completion:
- Message type errors in query.ts resolved
- Tool interface matches all implementations
- Key type has all required properties
- Error count reduced by approximately 40-50 errors
## Commands to Run
```bash
# After each file change
npx tsc --noEmit 2>&1 | grep -c "error TS"
# Check specific file errors
npx tsc --noEmit 2>&1 | grep "src/query.ts"
npx tsc --noEmit 2>&1 | grep "src/messages.ts"
npx tsc --noEmit 2>&1 | grep "Tool.ts"
# Test runtime
bun run dev
```

View File

@ -1,94 +0,0 @@
# Quick Fix Checklist - Start Here! 🚀
## Immediate Actions (Fix These First)
### 1. Install Missing Dependencies (2 min)
```bash
bun add sharp
bun add -d @types/sharp
```
### 2. Create Type Augmentation File (5 min)
Create `src/types/ink-augmentation.d.ts`:
```typescript
declare module 'ink' {
interface Key {
fn?: boolean
home?: boolean
end?: boolean
space?: boolean
}
}
```
### 3. Fix Critical Type Errors (15 min)
#### A. Fix src/query.ts (lines 203-210)
Replace direct `.message` access with type guard:
```typescript
// Before: msg.message
// After:
if (msg.type !== 'progress' && 'message' in msg) {
// use msg.message
}
```
#### B. Fix src/utils/messageContextManager.ts (line 136)
Add missing properties:
```typescript
return {
type: "assistant",
message: { role: "assistant", content: [...] },
costUSD: 0,
durationMs: 0,
uuid: crypto.randomUUID() as UUID
}
```
#### C. Fix src/utils/thinking.ts (line 115)
Remove 'minimal' from type:
```typescript
// Change from: "low" | "medium" | "high" | "minimal"
// To: "low" | "medium" | "high"
```
### 4. Quick Component Fixes (10 min)
#### A. Fix key prop issues in src/commands/agents.tsx
```typescript
// Instead of: <Text {...{key: index, color: 'gray'}}>
// Use: <Text key={index} color="gray">
```
#### B. Add children to components
```typescript
// src/components/messages/AssistantToolUseMessage.tsx (line 91)
<Text agentType={agentType} bold>{/* Add content here */}</Text>
// src/screens/REPL.tsx (line 526)
<TodoProvider>{/* Add children */}</TodoProvider>
```
### 5. Remove Unused Directives (5 min)
Remove these lines:
- src/entrypoints/cli.tsx:318
- src/hooks/useDoublePress.ts:33
- src/hooks/useTextInput.ts:143
- src/utils/messages.tsx:301
## Verify Progress
```bash
# Check error count
npx tsc --noEmit 2>&1 | wc -l
# Should see significant reduction after these fixes
```
## Next Steps
Once these quick fixes are done:
1. Run full TypeScript check
2. Move to Phase 2 (Tool implementations)
3. Use tasks.md for detailed tracking
## Expected Result
These quick fixes should eliminate ~40-50% of errors, making the remaining issues much clearer.

View File

@ -1,7 +1,7 @@
#!/usr/bin/env node
import { build } from 'esbuild'
import { existsSync, mkdirSync, writeFileSync, cpSync, readFileSync, readdirSync, statSync } from 'node:fs'
import { join, extname, dirname } from 'node:path'
import { existsSync, mkdirSync, writeFileSync, cpSync, readFileSync, readdirSync, statSync, chmodSync } from 'node:fs'
import { join } from 'node:path'
const SRC_DIR = 'src'
const OUT_DIR = 'dist'
@ -70,7 +70,9 @@ async function main() {
target: ['node20'],
sourcemap: true,
legalComments: 'none',
logLevel: 'info' })
logLevel: 'info',
tsconfig: 'tsconfig.json',
})
// Fix relative import specifiers to include .js extension for ESM
fixRelativeImports(OUT_DIR)
@ -98,15 +100,96 @@ import('./entrypoints/cli.js').catch(err => {
console.warn('⚠️ Could not copy yoga.wasm:', err.message)
}
// Create cross-platform CLI wrapper
const cliWrapper = `#!/usr/bin/env node
// Cross-platform CLI wrapper for Kode
// Prefers Bun but falls back to Node.js with tsx loader
const { spawn } = require('child_process');
const { existsSync } = require('fs');
const path = require('path');
// Get the directory where this CLI script is installed
const kodeDir = __dirname;
const distPath = path.join(kodeDir, 'dist', 'index.js');
// Check if we have a built version
if (!existsSync(distPath)) {
console.error('❌ Built files not found. Run "bun run build" first.');
process.exit(1);
}
// Try to use Bun first, then fallback to Node.js with tsx
const runWithBun = () => {
const proc = spawn('bun', ['run', distPath, ...process.argv.slice(2)], {
stdio: 'inherit',
cwd: process.cwd() // Use current working directory, not kode installation directory
});
proc.on('error', (err) => {
if (err.code === 'ENOENT') {
// Bun not found, try Node.js
runWithNode();
} else {
console.error('❌ Failed to start with Bun:', err.message);
process.exit(1);
}
});
proc.on('close', (code) => {
process.exit(code);
});
};
const runWithNode = () => {
const proc = spawn('node', [distPath, ...process.argv.slice(2)], {
stdio: 'inherit',
cwd: process.cwd() // Use current working directory, not kode installation directory
});
proc.on('error', (err) => {
console.error('❌ Failed to start with Node.js:', err.message);
process.exit(1);
});
proc.on('close', (code) => {
process.exit(code);
});
};
// Start with Bun preference
runWithBun();
`;
writeFileSync('cli.js', cliWrapper);
// Make cli.js executable
try {
chmodSync('cli.js', 0o755);
console.log('✅ cli.js made executable');
} catch (err) {
console.warn('⚠️ Could not make cli.js executable:', err.message);
}
// Create .npmrc file
const npmrcContent = `# Kode npm configuration
package-lock=false
save-exact=true
`;
writeFileSync('.npmrc', npmrcContent);
console.log('✅ Build completed for cross-platform compatibility!')
console.log('📋 Generated files:')
console.log(' - dist/ (CommonJS modules)')
console.log(' - dist/ (ESM modules)')
console.log(' - dist/index.js (main entrypoint)')
console.log(' - dist/entrypoints/cli.js (CLI main)')
console.log(' - cli.js (cross-platform wrapper)')
console.log(' - .npmrc (npm configuration)')
}
main().catch(err => {
console.error('❌ Build failed:', err)
process.exit(1)
})
})

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

@ -7,31 +7,20 @@ const path = require('path');
/**
* 发布开发版本到 npm
* 使用 -dev tag版本号自动递增 dev 后缀
* 不涉及 git 操作专注于 npm 发布
*/
async function publishDev() {
try {
console.log('🚀 Starting dev version publish process...\n');
// 1. 确保在正确的分支
const currentBranch = execSync('git rev-parse --abbrev-ref HEAD', { encoding: 'utf8' }).trim();
console.log(`📍 Current branch: ${currentBranch}`);
// 2. 检查工作区是否干净
try {
execSync('git diff --exit-code', { stdio: 'ignore' });
execSync('git diff --cached --exit-code', { stdio: 'ignore' });
} catch {
console.log('⚠️ Working directory has uncommitted changes, committing...');
execSync('git add .');
execSync('git commit -m "chore: prepare dev release"');
}
// 3. 读取当前版本
// 1. 读取当前版本
const packagePath = path.join(process.cwd(), 'package.json');
const packageJson = JSON.parse(readFileSync(packagePath, 'utf8'));
const baseVersion = packageJson.version;
// 4. 生成开发版本号
console.log(`📦 Current base version: ${baseVersion}`);
// 2. 生成开发版本号
let devVersion;
try {
// 获取当前 dev tag 的最新版本
@ -51,35 +40,31 @@ async function publishDev() {
console.log(`📦 Publishing version: ${devVersion} with tag 'dev'`);
// 5. 临时更新 package.json 版本号
// 3. 临时更新 package.json 版本号
const originalPackageJson = { ...packageJson };
packageJson.version = devVersion;
writeFileSync(packagePath, JSON.stringify(packageJson, null, 2));
// 6. 构建项目
// 4. 构建项目
console.log('🔨 Building project...');
execSync('npm run build', { stdio: 'inherit' });
// 7. 运行预发布检查
// 5. 运行预发布检查
console.log('🔍 Running pre-publish checks...');
execSync('node scripts/prepublish-check.js', { stdio: 'inherit' });
// 8. 发布到 npm 的 dev tag
// 6. 发布到 npm 的 dev tag
console.log('📤 Publishing to npm...');
execSync(`npm publish --tag dev --access public`, { stdio: 'inherit' });
// 9. 恢复原始 package.json
// 7. 恢复原始 package.json
writeFileSync(packagePath, JSON.stringify(originalPackageJson, null, 2));
// 10. 创建 git tag
console.log('🏷️ Creating git tag...');
execSync(`git tag -a v${devVersion} -m "Dev release ${devVersion}"`);
execSync(`git push origin v${devVersion}`);
console.log('\n✅ Dev version published successfully!');
console.log(`📦 Version: ${devVersion}`);
console.log(`🔗 Install with: npm install -g @shareai-lab/kode@dev`);
console.log(`🔗 Or: npm install -g @shareai-lab/kode@${devVersion}`);
console.log(`📊 View on npm: https://www.npmjs.com/package/@shareai-lab/kode/v/${devVersion}`);
} catch (error) {
console.error('❌ Dev publish failed:', error.message);

View File

@ -8,6 +8,7 @@ const readline = require('readline');
/**
* 发布正式版本到 npm
* 使用 latest tag支持语义化版本升级
* 不涉及 git 操作专注于 npm 发布
*/
async function publishRelease() {
const rl = readline.createInterface({
@ -20,40 +21,14 @@ async function publishRelease() {
try {
console.log('🚀 Starting production release process...\n');
// 1. 确保在主分支
const currentBranch = execSync('git rev-parse --abbrev-ref HEAD', { encoding: 'utf8' }).trim();
if (currentBranch !== 'main' && currentBranch !== 'master') {
console.log('⚠️ Not on main/master branch. Current branch:', currentBranch);
const proceed = await question('Continue anyway? (y/N): ');
if (proceed.toLowerCase() !== 'y') {
console.log('❌ Cancelled');
process.exit(0);
}
}
// 2. 检查工作区是否干净
try {
execSync('git diff --exit-code', { stdio: 'ignore' });
execSync('git diff --cached --exit-code', { stdio: 'ignore' });
console.log('✅ Working directory is clean');
} catch {
console.log('❌ Working directory has uncommitted changes');
console.log('Please commit or stash your changes before releasing');
process.exit(1);
}
// 3. 拉取最新代码
console.log('📡 Pulling latest changes...');
execSync('git pull origin ' + currentBranch, { stdio: 'inherit' });
// 4. 读取当前版本
// 1. 读取当前版本
const packagePath = path.join(process.cwd(), 'package.json');
const packageJson = JSON.parse(readFileSync(packagePath, 'utf8'));
const currentVersion = packageJson.version;
console.log(`📦 Current version: ${currentVersion}`);
// 5. 选择版本升级类型
// 2. 选择版本升级类型
console.log('\n🔢 Version bump options:');
const versionParts = currentVersion.split('.');
const major = parseInt(versionParts[0]);
@ -86,11 +61,19 @@ async function publishRelease() {
process.exit(1);
}
// 6. 确认发布
// 3. 检查版本是否已存在
try {
execSync(`npm view @shareai-lab/kode@${newVersion} version`, { stdio: 'ignore' });
console.log(`❌ Version ${newVersion} already exists on npm`);
process.exit(1);
} catch {
// 版本不存在,可以继续
}
// 4. 确认发布
console.log(`\n📋 Release Summary:`);
console.log(` Current: ${currentVersion}`);
console.log(` New: ${newVersion}`);
console.log(` Branch: ${currentBranch}`);
console.log(` Tag: latest`);
const confirm = await question('\n🤔 Proceed with release? (y/N): ');
@ -99,57 +82,57 @@ async function publishRelease() {
process.exit(0);
}
// 7. 更新版本号
// 5. 更新版本号
console.log('📝 Updating version...');
const originalPackageJson = { ...packageJson };
packageJson.version = newVersion;
writeFileSync(packagePath, JSON.stringify(packageJson, null, 2));
// 8. 运行测试
// 6. 运行测试
console.log('🧪 Running tests...');
try {
execSync('npm run typecheck', { stdio: 'inherit' });
execSync('npm test', { stdio: 'inherit' });
} catch (error) {
console.log('❌ Tests failed, rolling back version...');
packageJson.version = currentVersion;
writeFileSync(packagePath, JSON.stringify(packageJson, null, 2));
writeFileSync(packagePath, JSON.stringify(originalPackageJson, null, 2));
process.exit(1);
}
// 9. 构建项目
// 7. 构建项目
console.log('🔨 Building project...');
execSync('npm run build', { stdio: 'inherit' });
// 10. 运行预发布检查
// 8. 运行预发布检查
console.log('🔍 Running pre-publish checks...');
execSync('node scripts/prepublish-check.js', { stdio: 'inherit' });
// 11. 提交版本更新
console.log('📝 Committing version update...');
execSync('git add package.json');
execSync(`git commit -m "chore: bump version to ${newVersion}"`);
// 12. 创建 git tag
console.log('🏷️ Creating git tag...');
execSync(`git tag -a v${newVersion} -m "Release ${newVersion}"`);
// 13. 发布到 npm
// 9. 发布到 npm
console.log('📤 Publishing to npm...');
execSync('npm publish --access public', { stdio: 'inherit' });
// 14. 推送到 git
console.log('📡 Pushing to git...');
execSync(`git push origin ${currentBranch}`);
execSync(`git push origin v${newVersion}`);
console.log('\n🎉 Production release published successfully!');
console.log(`📦 Version: ${newVersion}`);
console.log(`🔗 Install with: npm install -g @shareai-lab/kode`);
console.log(`🔗 Or: npm install -g @shareai-lab/kode@${newVersion}`);
console.log(`📊 View on npm: https://www.npmjs.com/package/@shareai-lab/kode`);
console.log('\n💡 Next steps:');
console.log(' - Commit the version change to git');
console.log(' - Create a git tag for this release');
console.log(' - Push changes to the repository');
} catch (error) {
console.error('❌ Production release failed:', error.message);
// 尝试恢复 package.json
try {
const packagePath = path.join(process.cwd(), 'package.json');
const originalContent = readFileSync(packagePath, 'utf8');
// 如果版本被修改了,尝试恢复(这里简化处理)
console.log('🔄 Please manually restore package.json if needed');
} catch {}
process.exit(1);
} finally {
rl.close();

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,8 +1,10 @@
import { z } from 'zod'
import * as React from 'react'
// DEPRECATED: Use domain/tool/Tool.interface.ts for new implementations
// This interface will be maintained for compatibility during transition
/**
* Core Tool interface for Kode's extensible tool system
* Provides standardized contract for all tool implementations
*/
export type SetToolJSXFn = (jsx: {
jsx: React.ReactNode | null

View File

@ -1,26 +1,26 @@
import React, { useState, useEffect, useMemo, useCallback, useReducer, Fragment } from 'react'
import { Box, Text, useInput } from 'ink'
import InkTextInput from 'ink-text-input'
import { getActiveAgents, clearAgentCache } from '../utils/agentLoader'
import { AgentConfig } from '../utils/agentLoader'
import { getActiveAgents, clearAgentCache } from '@utils/agentLoader'
import { AgentConfig } from '@utils/agentLoader'
import { writeFileSync, unlinkSync, mkdirSync, existsSync, readFileSync, renameSync } from 'fs'
import { join } from 'path'
import * as path from 'path'
import { homedir } from 'os'
import * as os from 'os'
import { getCwd } from '../utils/state'
import { getTheme } from '../utils/theme'
import { getCwd } from '@utils/state'
import { getTheme } from '@utils/theme'
import matter from 'gray-matter'
import { exec, spawn } from 'child_process'
import { promisify } from 'util'
import { watch, FSWatcher } from 'fs'
import { getMCPTools } from '../services/mcpClient'
import { getModelManager } from '../utils/model'
import { getMCPTools } from '@services/mcpClient'
import { getModelManager } from '@utils/model'
import { randomUUID } from 'crypto'
const execAsync = promisify(exec)
// Core constants aligned with Claude Code architecture
// Core constants aligned with the Claude Code agent architecture
const AGENT_LOCATIONS = {
USER: "user",
PROJECT: "project",
@ -119,7 +119,7 @@ type GeneratedAgent = {
// AI generation function (use main pointer model)
async function generateAgentWithClaude(prompt: string): Promise<GeneratedAgent> {
// Import Claude service dynamically to avoid circular dependencies
const { queryModel } = await import('../services/claude')
const { queryModel } = await import('@services/claude')
const systemPrompt = `You are an expert at creating AI agent configurations. Based on the user's description, generate a specialized agent configuration.
@ -324,7 +324,7 @@ function validateAgentConfig(config: Partial<CreateState>, existingAgents: Agent
}
}
// File system operations with Claude Code alignment
// File system operations retained for Claude Code parity
function getAgentDirectory(location: AgentLocation): string {
if (location === AGENT_LOCATIONS.BUILT_IN || location === AGENT_LOCATIONS.ALL) {
throw new Error(`Cannot get directory path for ${location} agents`)
@ -545,7 +545,7 @@ async function updateAgent(
writeFileSync(filePath, content, { encoding: 'utf-8', flag: 'w' })
}
// Enhanced UI Components with Claude Code alignment
// Enhanced UI components retained for Claude Code parity
interface HeaderProps {
title: string
@ -1574,7 +1574,7 @@ function AgentListView({
<Box marginBottom={1}>
<Text bold color={theme.primary}>💭 What are agents?</Text>
</Box>
<Text>Specialized AI assistants that Claude can delegate to for specific tasks.</Text>
<Text>Specialized AI assistants that Kode can delegate to for specific tasks, compatible with Claude Code `.claude` agent packs.</Text>
<Text>Each agent has its own context, prompt, and tools.</Text>
<Box marginTop={1} marginBottom={1}>

View File

@ -2,7 +2,7 @@ import {
ProjectConfig,
getCurrentProjectConfig as getCurrentProjectConfigDefault,
saveCurrentProjectConfig as saveCurrentProjectConfigDefault,
} from '../utils/config.js'
} from '@utils/config'
export type ProjectConfigHandler = {
getCurrentProjectConfig: () => ProjectConfig

View File

@ -1,7 +1,7 @@
import { Command } from '../commands'
import { Bug } from '../components/Bug'
import { Command } from '@commands'
import { Bug } from '@components/Bug'
import * as React from 'react'
import { PRODUCT_NAME } from '../constants/product'
import { PRODUCT_NAME } from '@constants/product'
const bug = {
type: 'local-jsx',

View File

@ -1,12 +1,12 @@
import { Command } from '../commands'
import { getMessagesSetter } from '../messages'
import { getContext } from '../context'
import { getCodeStyle } from '../utils/style'
import { clearTerminal } from '../utils/terminal'
import { getOriginalCwd, setCwd } from '../utils/state'
import { Message } from '../query'
import { resetReminderSession } from '../services/systemReminder'
import { resetFileFreshnessSession } from '../services/fileFreshness'
import { Command } from '@commands'
import { getMessagesSetter } from '@messages'
import { getContext } from '@context'
import { getCodeStyle } from '@utils/style'
import { clearTerminal } from '@utils/terminal'
import { getOriginalCwd, setCwd } from '@utils/state'
import { Message } from '@query'
import { resetReminderSession } from '@services/systemReminder'
import { resetFileFreshnessSession } from '@services/fileFreshness'
export async function clearConversation(context: {
setForkConvoWithMessagesOnTheNextRender: (

View File

@ -1,15 +1,15 @@
import { Command } from '../commands'
import { getContext } from '../context'
import { getMessagesGetter, getMessagesSetter } from '../messages'
import { API_ERROR_MESSAGE_PREFIX, queryLLM } from '../services/claude'
import { Command } from '@commands'
import { getContext } from '@context'
import { getMessagesGetter, getMessagesSetter } from '@messages'
import { API_ERROR_MESSAGE_PREFIX, queryLLM } from '@services/claude'
import {
createUserMessage,
normalizeMessagesForAPI,
} from '../utils/messages.js'
import { getCodeStyle } from '../utils/style'
import { clearTerminal } from '../utils/terminal'
import { resetReminderSession } from '../services/systemReminder'
import { resetFileFreshnessSession } from '../services/fileFreshness'
} from '@utils/messages'
import { getCodeStyle } from '@utils/style'
import { clearTerminal } from '@utils/terminal'
import { resetReminderSession } from '@services/systemReminder'
import { resetFileFreshnessSession } from '@services/fileFreshness'
const COMPRESSION_PROMPT = `Please provide a comprehensive summary of our conversation structured as follows:

View File

@ -1,5 +1,5 @@
import { Command } from '../commands'
import { Config } from '../components/Config'
import { Command } from '@commands'
import { Config } from '@components/Config'
import * as React from 'react'
const config = {

View File

@ -1,5 +1,5 @@
import type { Command } from '../commands'
import { formatTotalCost } from '../cost-tracker'
import type { Command } from '@commands'
import { formatTotalCost } from '@costTracker'
const cost = {
type: 'local',

View File

@ -1,11 +1,11 @@
import type { Command } from '../commands'
import type { Tool } from '../Tool'
import type { Command } from '@commands'
import type { Tool } from '@tool'
import Table from 'cli-table3'
import { getSystemPrompt } from '../constants/prompts'
import { getContext } from '../context'
import { getSystemPrompt } from '@constants/prompts'
import { getContext } from '@context'
import { zodToJsonSchema } from 'zod-to-json-schema'
import { getMessagesGetter } from '../messages'
import { PROJECT_FILE } from '../constants/product'
import { getMessagesGetter } from '@messages'
import { PROJECT_FILE } from '@constants/product'
// Quick and dirty estimate of bytes per token for rough token counts
const BYTES_PER_TOKEN = 4

View File

@ -1,7 +1,7 @@
import React from 'react'
import type { Command } from '../commands'
import { Doctor } from '../screens/Doctor'
import { PRODUCT_NAME } from '../constants/product'
import type { Command } from '@commands'
import { Doctor } from '@screens/Doctor'
import { PRODUCT_NAME } from '@constants/product'
const doctor: Command = {
name: 'doctor',

View File

@ -1,5 +1,5 @@
import { Command } from '../commands'
import { Help } from '../components/Help'
import { Command } from '@commands'
import { Help } from '@components/Help'
import * as React from 'react'
const help = {

View File

@ -1,6 +1,6 @@
import type { Command } from '../commands'
import { markProjectOnboardingComplete } from '../ProjectOnboarding'
import { PROJECT_FILE } from '../constants/product'
import type { Command } from '@commands'
import { markProjectOnboardingComplete } from '@components/ProjectOnboarding'
import { PROJECT_FILE } from '@constants/product'
const command = {
type: 'prompt',
name: 'init',

View File

@ -1,6 +1,6 @@
import { Command } from '../commands'
import { logError } from '../utils/log'
import { execFileNoThrow } from '../utils/execFileNoThrow'
import { Command } from '@commands'
import { logError } from '@utils/log'
import { execFileNoThrow } from '@utils/execFileNoThrow'
const isEnabled =
process.platform === 'darwin' &&

View File

@ -1,9 +1,9 @@
import * as React from 'react'
import type { Command } from '../commands'
import { ConsoleOAuthFlow } from '../components/ConsoleOAuthFlow'
import { clearTerminal } from '../utils/terminal'
import { isLoggedInToAnthropic } from '../utils/auth'
import { useExitOnCtrlCD } from '../hooks/useExitOnCtrlCD'
import type { Command } from '@commands'
import { ConsoleOAuthFlow } from '@components/ConsoleOAuthFlow'
import { clearTerminal } from '@utils/terminal'
import { isLoggedInToAnthropic } from '@utils/auth'
import { useExitOnCtrlCD } from '@hooks/useExitOnCtrlCD'
import { Box, Text } from 'ink'
import { clearConversation } from './clear'

View File

@ -1,7 +1,7 @@
import * as React from 'react'
import type { Command } from '../commands'
import { getGlobalConfig, saveGlobalConfig } from '../utils/config'
import { clearTerminal } from '../utils/terminal'
import type { Command } from '@commands'
import { getGlobalConfig, saveGlobalConfig } from '@utils/config'
import { clearTerminal } from '@utils/terminal'
import { Text } from 'ink'
export default {

View File

@ -1,8 +1,8 @@
import type { Command } from '../commands'
import { listMCPServers, getClients } from '../services/mcpClient'
import { PRODUCT_COMMAND } from '../constants/product'
import type { Command } from '@commands'
import { listMCPServers, getClients } from '@services/mcpClient'
import { PRODUCT_COMMAND } from '@constants/product'
import chalk from 'chalk'
import { getTheme } from '../utils/theme'
import { getTheme } from '@utils/theme'
const mcp = {
type: 'local',

View File

@ -1,8 +1,8 @@
import React from 'react'
import { render } from 'ink'
import { ModelConfig } from '../components/ModelConfig'
import { enableConfigs } from '../utils/config'
import { triggerModelConfigChange } from '../messages'
import { ModelConfig } from '@components/ModelConfig'
import { enableConfigs } from '@utils/config'
import { triggerModelConfigChange } from '@messages'
export const help = 'Change your AI provider and model settings'
export const description = 'Change your AI provider and model settings'
@ -26,7 +26,7 @@ export async function call(
<ModelConfig
onClose={() => {
// Force ModelManager reload to ensure UI sync - wait for completion before closing
import('../utils/model').then(({ reloadModelManager }) => {
import('@utils/model').then(({ reloadModelManager }) => {
reloadModelManager()
// 🔧 Critical fix: Trigger global UI refresh after model config changes
// This ensures PromptInput component detects ModelManager singleton state changes

View File

@ -1,6 +1,6 @@
import React from 'react'
import type { Command } from '../commands'
import { ModelStatusDisplay } from '../components/ModelStatusDisplay'
import type { Command } from '@commands'
import { ModelStatusDisplay } from '@components/ModelStatusDisplay'
const modelstatus: Command = {
name: 'modelstatus',

View File

@ -1,8 +1,8 @@
import * as React from 'react'
import type { Command } from '../commands'
import { Onboarding } from '../components/Onboarding'
import { clearTerminal } from '../utils/terminal'
import { getGlobalConfig, saveGlobalConfig } from '../utils/config'
import type { Command } from '@commands'
import { Onboarding } from '@components/Onboarding'
import { clearTerminal } from '@utils/terminal'
import { getGlobalConfig, saveGlobalConfig } from '@utils/config'
import { clearConversation } from './clear'
export default {

View File

@ -1,4 +1,4 @@
import { Command } from '../commands'
import { Command } from '@commands'
export default {
type: 'prompt',

View File

@ -1,6 +1,6 @@
import { Command } from '../commands'
import { reloadCustomCommands } from '../services/customCommands'
import { getCommands } from '../commands'
import { Command } from '@commands'
import { reloadCustomCommands } from '@services/customCommands'
import { getCommands } from '@commands'
/**
* Refresh Commands - Reload custom commands from filesystem

View File

@ -1,6 +1,6 @@
import { MACRO } from '../constants/macros.js'
import type { Command } from '../commands'
import { RELEASE_NOTES } from '../constants/releaseNotes'
import { MACRO } from '@constants/macros'
import type { Command } from '@commands'
import { RELEASE_NOTES } from '@constants/releaseNotes'
const releaseNotes: Command = {
description: 'Show release notes for the current or specified version',

View File

@ -1,8 +1,8 @@
import * as React from 'react'
import type { Command } from '../commands'
import { ResumeConversation } from '../screens/ResumeConversation'
import type { Command } from '@commands'
import { ResumeConversation } from '@screens/ResumeConversation'
import { render } from 'ink'
import { CACHE_PATHS, loadLogList } from '../utils/log'
import { CACHE_PATHS, loadLogList } from '@utils/log'
export default {
type: 'local-jsx',

View File

@ -1,5 +1,5 @@
import { Command } from '../commands'
import { BashTool } from '../tools/BashTool/BashTool'
import { Command } from '@commands'
import { BashTool } from '@tools/BashTool/BashTool'
export default {
type: 'prompt',

View File

@ -1,15 +1,15 @@
import { Command } from '../commands'
import { Command } from '@commands'
import { EOL, platform, homedir } from 'os'
import { execFileNoThrow } from '../utils/execFileNoThrow'
import { execFileNoThrow } from '@utils/execFileNoThrow'
import chalk from 'chalk'
import { getTheme } from '../utils/theme'
import { env } from '../utils/env'
import { getGlobalConfig, saveGlobalConfig } from '../utils/config'
import { markProjectOnboardingComplete } from '../ProjectOnboarding'
import { getTheme } from '@utils/theme'
import { env } from '@utils/env'
import { getGlobalConfig, saveGlobalConfig } from '@utils/config'
import { markProjectOnboardingComplete } from '@components/ProjectOnboarding'
import { readFileSync, writeFileSync } from 'fs'
import { join } from 'path'
import { safeParseJSON } from '../utils/json'
import { logError } from '../utils/log'
import { safeParseJSON } from '@utils/json'
import { logError } from '@utils/log'
const terminalSetup: Command = {
type: 'local',

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,7 +1,7 @@
import { Box, Text } from 'ink'
import React from 'react'
import { getTheme } from '../utils/theme'
import { ASCII_LOGO } from '../constants/product'
import { getTheme } from '@utils/theme'
import { ASCII_LOGO } from '@constants/product'
export function AsciiLogo(): React.ReactNode {
const theme = getTheme()

View File

@ -1,148 +0,0 @@
import { Box, Text } from 'ink'
import * as React from 'react'
import { getTheme } from '../utils/theme'
import { gte } from 'semver'
import { useEffect, useState } from 'react'
import { isAutoUpdaterDisabled } from '../utils/config'
import {
AutoUpdaterResult,
getLatestVersion,
installGlobalPackage,
} from '../utils/autoUpdater.js'
import { useInterval } from '../hooks/useInterval'
import { logEvent } from '../services/statsig'
import { MACRO } from '../constants/macros'
import { PRODUCT_COMMAND } from '../constants/product'
type Props = {
debug: boolean
isUpdating: boolean
onChangeIsUpdating: (isUpdating: boolean) => void
onAutoUpdaterResult: (autoUpdaterResult: AutoUpdaterResult) => void
autoUpdaterResult: AutoUpdaterResult | null
}
export function AutoUpdater({
debug,
isUpdating,
onChangeIsUpdating,
onAutoUpdaterResult,
autoUpdaterResult,
}: Props): React.ReactNode {
const theme = getTheme()
const [versions, setVersions] = useState<{
global?: string | null
latest?: string | null
}>({})
const checkForUpdates = React.useCallback(async () => {
if (process.env.NODE_ENV === 'test' || process.env.NODE_ENV === 'dev') {
return
}
if (isUpdating) {
return
}
// Get versions
const globalVersion = MACRO.VERSION
const latestVersion = await getLatestVersion()
const isDisabled = true //await isAutoUpdaterDisabled()
setVersions({ global: globalVersion, latest: latestVersion })
// Check if update needed and perform update
if (
!isDisabled &&
globalVersion &&
latestVersion &&
!gte(globalVersion, latestVersion)
) {
const startTime = Date.now()
onChangeIsUpdating(true)
const installStatus = await installGlobalPackage()
onChangeIsUpdating(false)
if (installStatus === 'success') {
logEvent('tengu_auto_updater_success', {
fromVersion: globalVersion,
toVersion: latestVersion,
durationMs: String(Date.now() - startTime),
})
} else {
logEvent('tengu_auto_updater_fail', {
fromVersion: globalVersion,
attemptedVersion: latestVersion,
status: installStatus,
durationMs: String(Date.now() - startTime),
})
}
onAutoUpdaterResult({
version: latestVersion!,
status: installStatus,
})
}
// Don't re-render when isUpdating changes
// TODO: Find a cleaner way to do this
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [onAutoUpdaterResult])
// Initial check
useEffect(() => {
// checkForUpdates()
}, [checkForUpdates])
// Check every 30 minutes
// useInterval(checkForUpdates, 30 * 60 * 1000)
if (debug) {
return (
<Box flexDirection="row">
<Text dimColor>
globalVersion: {versions.global} &middot; latestVersion:{' '}
{versions.latest}
</Text>
</Box>
)
}
if (!autoUpdaterResult?.version && (!versions.global || !versions.latest)) {
return null
}
if (!autoUpdaterResult?.version && !isUpdating) {
return null
}
return (
<Box flexDirection="row">
{debug && (
<Text dimColor>
globalVersion: {versions.global} &middot; latestVersion:{' '}
{versions.latest}
</Text>
)}
{isUpdating && (
<>
<Box>
<Text color={theme.secondaryText} dimColor wrap="end">
Auto-updating to v{versions.latest}
</Text>
</Box>
</>
)}
{autoUpdaterResult?.status === 'success' && autoUpdaterResult?.version ? (
<Text color={theme.success}>
Update installed &middot; Restart to apply
</Text>
) : null}
{(autoUpdaterResult?.status === 'install_failed' ||
autoUpdaterResult?.status === 'no_permissions') && (
<Text color={theme.error}>
Auto-update failed &middot; Try{' '}
<Text bold>{PRODUCT_COMMAND} doctor</Text> or{' '}
<Text bold>npm i -g {MACRO.PACKAGE_URL}</Text>
</Text>
)}
</Box>
)
}

View File

@ -1,23 +1,22 @@
import { Box, Text, useInput } from 'ink'
import * as React from 'react'
import { useState, useCallback, useEffect } from 'react'
import { getTheme } from '../utils/theme'
import { getMessagesGetter } from '../messages'
import type { Message } from '../query'
import { getTheme } from '@utils/theme'
import { getMessagesGetter } from '@messages'
import type { Message } from '@query'
import TextInput from './TextInput'
import { logError, getInMemoryErrors } from '../utils/log'
import { env } from '../utils/env'
import { getGitState, getIsGit, GitRepoState } from '../utils/git'
import { useTerminalSize } from '../hooks/useTerminalSize'
import { getAnthropicApiKey, getGlobalConfig } from '../utils/config'
import { USER_AGENT } from '../utils/http'
import { logEvent } from '../services/statsig'
import { PRODUCT_NAME } from '../constants/product'
import { API_ERROR_MESSAGE_PREFIX, queryQuick } from '../services/claude'
import { openBrowser } from '../utils/browser'
import { useExitOnCtrlCD } from '../hooks/useExitOnCtrlCD'
import { MACRO } from '../constants/macros'
import { GITHUB_ISSUES_REPO_URL } from '../constants/product'
import { logError, getInMemoryErrors } from '@utils/log'
import { env } from '@utils/env'
import { getGitState, getIsGit, GitRepoState } from '@utils/git'
import { useTerminalSize } from '@hooks/useTerminalSize'
import { getAnthropicApiKey, getGlobalConfig } from '@utils/config'
import { USER_AGENT } from '@utils/http'
import { PRODUCT_NAME } from '@constants/product'
import { API_ERROR_MESSAGE_PREFIX, queryQuick } from '@services/claude'
import { openBrowser } from '@utils/browser'
import { useExitOnCtrlCD } from '@hooks/useExitOnCtrlCD'
import { MACRO } from '@constants/macros'
import { GITHUB_ISSUES_REPO_URL } from '@constants/product'
type Props = {
onDone(result: string): void

View File

@ -2,15 +2,15 @@ import { Box, Text, useInput } from 'ink'
import * as React from 'react'
import { useState } from 'react'
import figures from 'figures'
import { getTheme } from '../utils/theme'
import { getTheme } from '@utils/theme'
import {
GlobalConfig,
saveGlobalConfig,
getGlobalConfig,
} from '../utils/config.js'
} from '@utils/config'
import chalk from 'chalk'
import { useExitOnCtrlCD } from '../hooks/useExitOnCtrlCD'
import { getModelManager } from '../utils/model'
import { useExitOnCtrlCD } from '@hooks/useExitOnCtrlCD'
import { getModelManager } from '@utils/model'
type Props = {
onClose: () => void

View File

@ -1,17 +1,16 @@
import React, { useEffect, useState, useCallback } from 'react'
import { Static, Box, Text, useInput } from 'ink'
import TextInput from './TextInput'
import { OAuthService, createAndStoreApiKey } from '../services/oauth'
import { getTheme } from '../utils/theme'
import { logEvent } from '../services/statsig'
import { OAuthService, createAndStoreApiKey } from '@services/oauth'
import { getTheme } from '@utils/theme'
import { AsciiLogo } from './AsciiLogo'
import { useTerminalSize } from '../hooks/useTerminalSize'
import { logError } from '../utils/log'
import { clearTerminal } from '../utils/terminal'
import { useTerminalSize } from '@hooks/useTerminalSize'
import { logError } from '@utils/log'
import { clearTerminal } from '@utils/terminal'
import { SimpleSpinner } from './Spinner'
import { WelcomeBox } from './Onboarding'
import { PRODUCT_NAME } from '../constants/product'
import { sendNotification } from '../services/notifier'
import { PRODUCT_NAME } from '@constants/product'
import { sendNotification } from '@services/notifier'
type Props = {
onDone(): void
@ -70,10 +69,10 @@ export function ConsoleOAuthFlow({ onDone }: Props): React.ReactNode {
useInput(async (_, key) => {
if (key.return) {
if (oauthStatus.state === 'idle') {
logEvent('tengu_oauth_start', {})
setOAuthStatus({ state: 'ready_to_start' })
} else if (oauthStatus.state === 'success') {
logEvent('tengu_oauth_success', {})
await clearTerminal() // needed to clear out Static components
onDone()
} else if (oauthStatus.state === 'error' && oauthStatus.toRetry) {
@ -101,7 +100,7 @@ export function ConsoleOAuthFlow({ onDone }: Props): React.ReactNode {
}
// Track which path the user is taking (manual code entry)
logEvent('tengu_oauth_manual_entry', {})
oauthService.processCallback({
authorizationCode,
state,
@ -133,7 +132,7 @@ export function ConsoleOAuthFlow({ onDone }: Props): React.ReactNode {
'Failed to exchange authorization code for access token. Please try again.',
toRetry: { state: 'ready_to_start' },
})
logEvent('tengu_oauth_token_exchange_error', { error: err.message })
} else {
// Handle other errors
setOAuthStatus({
@ -154,7 +153,7 @@ export function ConsoleOAuthFlow({ onDone }: Props): React.ReactNode {
message: 'Failed to create API key: ' + err.message,
toRetry: { state: 'ready_to_start' },
})
logEvent('tengu_oauth_api_key_error', { error: err.message })
throw err
},
)
@ -169,13 +168,10 @@ export function ConsoleOAuthFlow({ onDone }: Props): React.ReactNode {
"Unable to create API key. The server accepted the request but didn't return a key.",
toRetry: { state: 'ready_to_start' },
})
logEvent('tengu_oauth_api_key_error', {
error: 'server_returned_no_key',
})
}
} catch (err) {
const errorMessage = (err as Error).message
logEvent('tengu_oauth_error', { error: errorMessage })
}
}, [oauthService, setShowPastePrompt])

View File

@ -1,7 +1,7 @@
import { Box, Text, useInput } from 'ink'
import React from 'react'
import { Select } from './CustomSelect/select'
import { getTheme } from '../utils/theme'
import { getTheme } from '@utils/theme'
import Link from './Link'
interface Props {

View File

@ -2,7 +2,7 @@ import figures from 'figures'
import { Box, Text } from 'ink'
import React, { type ReactNode } from 'react'
import { type Theme } from './theme'
import { getTheme } from '../../utils/theme'
import { getTheme } from '@utils/theme'
export type SelectOptionProps = {
/**

View File

@ -5,7 +5,7 @@ import { type Theme } from './theme'
import { useSelectState } from './use-select-state'
import { useSelect } from './use-select'
import { Option } from '@inkjs/ui'
import { getTheme } from '../../utils/theme'
import { getTheme } from '@utils/theme'
export type OptionSubtree = {
/**

View File

@ -1,7 +1,7 @@
import * as React from 'react'
import { getTheme } from '../utils/theme'
import { getTheme } from '@utils/theme'
import { Text } from 'ink'
import { PRODUCT_NAME } from '../constants/product'
import { PRODUCT_NAME } from '@constants/product'
export function FallbackToolUseRejectedMessage(): React.ReactNode {
return (

View File

@ -1,16 +1,16 @@
import { Hunk } from 'diff'
import { Box, Text } from 'ink'
import * as React from 'react'
import { intersperse } from '../utils/array'
import { intersperse } from '@utils/array'
import { StructuredDiff } from './StructuredDiff'
import { getTheme } from '../utils/theme'
import { getCwd } from '../utils/state'
import { getTheme } from '@utils/theme'
import { getCwd } from '@utils/state'
import { relative } from 'path'
import { useTerminalSize } from '../hooks/useTerminalSize'
import { useTerminalSize } from '@hooks/useTerminalSize'
type Props = {
filePath: string
structuredPatch: Hunk[]
structuredPatch?: Hunk[]
verbose: boolean
}
@ -20,11 +20,12 @@ export function FileEditToolUpdatedMessage({
verbose,
}: Props): React.ReactNode {
const { columns } = useTerminalSize()
const numAdditions = structuredPatch.reduce(
const patches = Array.isArray(structuredPatch) ? structuredPatch : []
const numAdditions = patches.reduce(
(count, hunk) => count + hunk.lines.filter(_ => _.startsWith('+')).length,
0,
)
const numRemovals = structuredPatch.reduce(
const numRemovals = patches.reduce(
(count, hunk) => count + hunk.lines.filter(_ => _.startsWith('-')).length,
0,
)
@ -49,18 +50,19 @@ export function FileEditToolUpdatedMessage({
</>
) : null}
</Text>
{intersperse(
structuredPatch.map(_ => (
<Box flexDirection="column" paddingLeft={5} key={_.newStart}>
<StructuredDiff patch={_} dim={false} width={columns - 12} />
</Box>
)),
i => (
<Box paddingLeft={5} key={`ellipsis-${i}`}>
<Text color={getTheme().secondaryText}>...</Text>
</Box>
),
)}
{patches.length > 0 &&
intersperse(
patches.map(_ => (
<Box flexDirection="column" paddingLeft={5} key={_.newStart}>
<StructuredDiff patch={_} dim={false} width={columns - 12} />
</Box>
)),
i => (
<Box paddingLeft={5} key={`ellipsis-${i}`}>
<Text color={getTheme().secondaryText}>...</Text>
</Box>
),
)}
</Box>
)
}

View File

@ -1,15 +1,15 @@
import { Command } from '../commands'
import { PRODUCT_COMMAND, PRODUCT_NAME } from '../constants/product'
import { Command } from '@commands'
import { PRODUCT_COMMAND, PRODUCT_NAME } from '@constants/product'
import {
getCustomCommandDirectories,
hasCustomCommands,
type CustomCommandWithScope,
} from '../services/customCommands'
} from '@services/customCommands'
import * as React from 'react'
import { Box, Text, useInput } from 'ink'
import { getTheme } from '../utils/theme'
import { getTheme } from '@utils/theme'
import { PressEnterToContinue } from './PressEnterToContinue'
import { MACRO } from '../constants/macros'
import { MACRO } from '@constants/macros'
/**
* Help Component - Interactive help system with progressive disclosure
@ -175,10 +175,10 @@ export function Help({
Custom commands loaded from:
</Text>
<Text color={theme.secondaryText}>
{getCustomCommandDirectories().userClaude} (user: prefix)
{getCustomCommandDirectories().userClaude} (Claude `.claude` user scope)
</Text>
<Text color={theme.secondaryText}>
{getCustomCommandDirectories().projectClaude} (project: prefix)
{getCustomCommandDirectories().projectClaude} (Claude `.claude` project scope)
</Text>
<Text color={theme.secondaryText}>
Use /refresh-commands to reload after changes
@ -190,10 +190,10 @@ export function Help({
Create custom commands by adding .md files to:
</Text>
<Text color={theme.secondaryText}>
{getCustomCommandDirectories().userClaude} (user: prefix)
{getCustomCommandDirectories().userClaude} (Claude `.claude` user scope)
</Text>
<Text color={theme.secondaryText}>
{getCustomCommandDirectories().projectClaude} (project: prefix)
{getCustomCommandDirectories().projectClaude} (Claude `.claude` project scope)
</Text>
<Text color={theme.secondaryText}>
Use /refresh-commands to reload after creation

View File

@ -1,7 +1,7 @@
import { highlight, supportsLanguage } from 'cli-highlight'
import { Text } from 'ink'
import React, { useMemo } from 'react'
import { logError } from '../utils/log'
import { logError } from '@utils/log'
type Props = {
code: string

View File

@ -1,11 +1,11 @@
import React from 'react'
import { Box, Newline, Text, useInput } from 'ink'
import { getTheme } from '../utils/theme'
import { getTheme } from '@utils/theme'
import { Select } from './CustomSelect/select'
import { render } from 'ink'
import { writeFileSync } from 'fs'
import { ConfigParseError } from '../utils/errors'
import { useExitOnCtrlCD } from '../hooks/useExitOnCtrlCD'
import { ConfigParseError } from '@utils/errors'
import { useExitOnCtrlCD } from '@hooks/useExitOnCtrlCD'
interface InvalidConfigHandlerProps {
error: ConfigParseError
}
@ -18,7 +18,7 @@ interface InvalidConfigDialogProps {
}
/**
* Dialog shown when the Claude config file contains invalid JSON
* Dialog shown when the Kode config file contains invalid JSON
*/
function InvalidConfigDialog({
filePath,

View File

@ -1,7 +1,7 @@
import InkLink from 'ink-link'
import { Text } from 'ink'
import React from 'react'
import { env } from '../utils/env'
import { env } from '@utils/env'
type LinkProps = {
url: string

View File

@ -1,10 +1,10 @@
import React from 'react'
import { Box, Text } from 'ink'
import { Select } from './CustomSelect/select'
import type { LogOption } from '../types/logs'
import { getTheme } from '../utils/theme'
import { useTerminalSize } from '../hooks/useTerminalSize'
import { formatDate } from '../utils/log'
import type { LogOption } from '@kode-types/logs'
import { getTheme } from '@utils/theme'
import { useTerminalSize } from '@hooks/useTerminalSize'
import { formatDate } from '@utils/log'
type LogSelectorProps = {
logs: LogOption[]

View File

@ -1,15 +1,21 @@
import { Box, Text, Newline } from 'ink'
import * as React from 'react'
import { getTheme } from '../utils/theme'
import { PRODUCT_NAME } from '../constants/product'
import { getAnthropicApiKey, getGlobalConfig } from '../utils/config'
import { getCwd } from '../utils/state'
import { getTheme } from '@utils/theme'
import { PRODUCT_NAME } from '@constants/product'
import { getAnthropicApiKey, getGlobalConfig } from '@utils/config'
import { getCwd } from '@utils/state'
import { AsciiLogo } from './AsciiLogo'
import type { WrappedClient } from '../services/mcpClient'
import { getModelManager } from '../utils/model'
import type { WrappedClient } from '@services/mcpClient'
import { getModelManager } from '@utils/model'
import { MACRO } from '@constants/macros'
export const MIN_LOGO_WIDTH = 50
const DEFAULT_UPDATE_COMMANDS = [
'bun add -g @shareai-lab/kode@latest',
'npm install -g @shareai-lab/kode@latest',
] as const
export function Logo({
mcpClients,
isDefaultModel = false,
@ -49,16 +55,11 @@ export function Logo({
>
{updateBannerVersion ? (
<Box flexDirection="column">
<Text color="yellow">New version available: {updateBannerVersion}</Text>
<Text color="yellow">New version available: {updateBannerVersion} (current: {MACRO.VERSION})</Text>
<Text>Run the following command to update:</Text>
<Text>
{' '}
{updateBannerCommands?.[0] ?? 'bun add -g @shareai-lab/kode@latest'}
</Text>
<Text>Or:</Text>
<Text>
{' '}
{updateBannerCommands?.[1] ?? 'npm install -g @shareai-lab/kode@latest'}
{updateBannerCommands?.[1] ?? DEFAULT_UPDATE_COMMANDS[1]}
</Text>
{process.platform !== 'win32' && (
<Text dimColor>

View File

@ -1,13 +1,13 @@
import React from 'react'
import { Box, Text, useInput } from 'ink'
import { getTheme } from '../utils/theme'
import { getTheme } from '@utils/theme'
import { Select } from './CustomSelect/select'
import {
saveCurrentProjectConfig,
getCurrentProjectConfig,
} from '../utils/config.js'
} from '@utils/config'
import { MCPServerDialogCopy } from './MCPServerDialogCopy'
import { useExitOnCtrlCD } from '../hooks/useExitOnCtrlCD'
import { useExitOnCtrlCD } from '@hooks/useExitOnCtrlCD'
type Props = {
serverName: string

View File

@ -1,7 +1,7 @@
import React from 'react'
import { Text } from 'ink'
import Link from 'ink-link'
import { PRODUCT_NAME, PRODUCT_COMMAND } from '../constants/product'
import { PRODUCT_NAME, PRODUCT_COMMAND } from '@constants/product'
export function MCPServerDialogCopy(): React.ReactNode {
return (

View File

@ -1,14 +1,14 @@
import React from 'react'
import { Box, Text, useInput } from 'ink'
import { getTheme } from '../utils/theme'
import { getTheme } from '@utils/theme'
import { MultiSelect } from '@inkjs/ui'
import {
saveCurrentProjectConfig,
getCurrentProjectConfig,
} from '../utils/config.js'
} from '@utils/config'
import { partition } from 'lodash-es'
import { MCPServerDialogCopy } from './MCPServerDialogCopy'
import { useExitOnCtrlCD } from '../hooks/useExitOnCtrlCD'
import { useExitOnCtrlCD } from '@hooks/useExitOnCtrlCD'
type Props = {
serverNames: string[]

View File

@ -1,6 +1,6 @@
import { Box } from 'ink'
import * as React from 'react'
import type { AssistantMessage, Message, UserMessage } from '../query'
import type { AssistantMessage, Message, UserMessage } from '@query'
import type {
ContentBlock,
DocumentBlockParam,
@ -10,16 +10,16 @@ import type {
ToolResultBlockParam,
ToolUseBlockParam,
} from '@anthropic-ai/sdk/resources/index.mjs'
import { Tool } from '../Tool'
import { logError } from '../utils/log'
import { Tool } from '@tool'
import { logError } from '@utils/log'
import { UserToolResultMessage } from './messages/UserToolResultMessage/UserToolResultMessage'
import { AssistantToolUseMessage } from './messages/AssistantToolUseMessage'
import { AssistantTextMessage } from './messages/AssistantTextMessage'
import { UserTextMessage } from './messages/UserTextMessage'
import { NormalizedMessage } from '../utils/messages'
import { NormalizedMessage } from '@utils/messages'
import { AssistantThinkingMessage } from './messages/AssistantThinkingMessage'
import { AssistantRedactedThinkingMessage } from './messages/AssistantRedactedThinkingMessage'
import { useTerminalSize } from '../hooks/useTerminalSize'
import { useTerminalSize } from '@hooks/useTerminalSize'
type Props = {
message: UserMessage | AssistantMessage

View File

@ -2,19 +2,18 @@ import { Box, Text, useInput } from 'ink'
import * as React from 'react'
import { useMemo, useState, useEffect } from 'react'
import figures from 'figures'
import { getTheme } from '../utils/theme'
import { getTheme } from '@utils/theme'
import { Message as MessageComponent } from './Message'
import { randomUUID } from 'crypto'
import { type Tool } from '../Tool'
import { type Tool } from '@tool'
import {
createUserMessage,
isEmptyMessageText,
isNotEmptyMessage,
normalizeMessages,
} from '../utils/messages.js'
import { logEvent } from '../services/statsig'
import type { AssistantMessage, UserMessage } from '../query'
import { useExitOnCtrlCD } from '../hooks/useExitOnCtrlCD'
} from '@utils/messages'
import type { AssistantMessage, UserMessage } from '@query'
import { useExitOnCtrlCD } from '@hooks/useExitOnCtrlCD'
type Props = {
erroredToolUseIDs: Set<string>
@ -37,23 +36,14 @@ export function MessageSelector({
}: Props): React.ReactNode {
const currentUUID = useMemo(randomUUID, [])
// Log when selector is opened
useEffect(() => {
logEvent('tengu_message_selector_opened', {})
}, [])
useEffect(() => {}, [])
function handleSelect(message: UserMessage) {
const indexFromEnd = messages.length - 1 - messages.indexOf(message)
logEvent('tengu_message_selector_selected', {
index_from_end: indexFromEnd.toString(),
message_type: message.type,
is_current_prompt: (message.uuid === currentUUID).toString(),
})
onSelect(message)
}
function handleEscape() {
logEvent('tengu_message_selector_cancelled', {})
onEscape()
}

View File

@ -1,7 +1,7 @@
import React from 'react'
import { Box, Text } from 'ink'
import { usePermissionContext } from '../context/PermissionContext'
import { getTheme } from '../utils/theme'
import { usePermissionContext } from '@context/PermissionContext'
import { getTheme } from '@utils/theme'
interface ModeIndicatorProps {
showTransitionCount?: boolean

View File

@ -2,15 +2,15 @@ import { Box, Text, useInput } from 'ink'
import * as React from 'react'
import { useState, useCallback, useEffect, useRef } from 'react'
import figures from 'figures'
import { getTheme } from '../utils/theme'
import { getTheme } from '@utils/theme'
import {
getGlobalConfig,
saveGlobalConfig,
ModelPointerType,
setModelPointer,
} from '../utils/config.js'
import { getModelManager } from '../utils/model'
import { useExitOnCtrlCD } from '../hooks/useExitOnCtrlCD'
} from '@utils/config'
import { getModelManager } from '@utils/model'
import { useExitOnCtrlCD } from '@hooks/useExitOnCtrlCD'
import { ModelSelector } from './ModelSelector'
import { ModelListManager } from './ModelListManager'

View File

@ -2,10 +2,10 @@ import { Box, Text, useInput } from 'ink'
import * as React from 'react'
import { useState, useCallback } from 'react'
import figures from 'figures'
import { getTheme } from '../utils/theme'
import { getGlobalConfig, ModelPointerType } from '../utils/config.js'
import { getModelManager } from '../utils/model'
import { useExitOnCtrlCD } from '../hooks/useExitOnCtrlCD'
import { getTheme } from '@utils/theme'
import { getGlobalConfig, ModelPointerType } from '@utils/config'
import { getModelManager } from '@utils/model'
import { useExitOnCtrlCD } from '@hooks/useExitOnCtrlCD'
import { ModelSelector } from './ModelSelector'
type Props = {

View File

@ -1,9 +1,9 @@
import React, { useState, useEffect, useCallback, useRef } from 'react'
import { Box, Text, useInput } from 'ink'
import { getTheme } from '../utils/theme'
import { getTheme } from '@utils/theme'
import { Select } from './CustomSelect/select'
import { Newline } from 'ink'
import { getModelManager } from '../utils/model'
import { getModelManager } from '@utils/model'
// 共享的屏幕容器组件,避免重复边框
function ScreenContainer({
@ -33,8 +33,8 @@ function ScreenContainer({
</Box>
)
}
import { PRODUCT_NAME } from '../constants/product'
import { useExitOnCtrlCD } from '../hooks/useExitOnCtrlCD'
import { PRODUCT_NAME } from '@constants/product'
import { useExitOnCtrlCD } from '@hooks/useExitOnCtrlCD'
import {
getGlobalConfig,
saveGlobalConfig,
@ -42,14 +42,14 @@ import {
ModelPointerType,
setAllPointersToModel,
setModelPointer,
} from '../utils/config.js'
import models, { providers } from '../constants/models'
} from '@utils/config'
import models, { providers } from '@constants/models'
import TextInput from './TextInput'
import OpenAI from 'openai'
import chalk from 'chalk'
import { fetchAnthropicModels, verifyApiKey } from '../services/claude'
import { fetchCustomModels, getModelFeatures } from '../services/openai'
import { testGPT5Connection, validateGPT5Config } from '../services/gpt5ConnectionTest'
import { fetchAnthropicModels, verifyApiKey } from '@services/claude'
import { fetchCustomModels, getModelFeatures } from '@services/openai'
import { testGPT5Connection, validateGPT5Config } from '@services/gpt5ConnectionTest'
type Props = {
onDone: () => void
abortController?: AbortController
@ -419,7 +419,10 @@ export function ModelSelector({
function getModelDetails(model: ModelInfo): string {
const details = []
if (model.max_tokens) {
// Show context_length if available (Ollama models), otherwise max_tokens
if (model.context_length) {
details.push(`${formatNumber(model.context_length)} tokens`)
} else if (model.max_tokens) {
details.push(`${formatNumber(model.max_tokens)} tokens`)
}
@ -1040,13 +1043,15 @@ export function ModelSelector({
}
// Transform Ollama models to our format
// Note: max_tokens here is for OUTPUT tokens, not context length
const ollamaModels = models.map((model: any) => ({
model:
model.id ??
model.name ??
model.modelName ??
(typeof model === 'string' ? model : ''),
provider: 'ollama',
max_tokens: 4096, // Default value
max_tokens: DEFAULT_MAX_TOKENS, // Default output tokens (8K is reasonable)
supports_vision: false,
supports_function_calling: true,
supports_reasoning_effort: false,
@ -1055,16 +1060,102 @@ export function ModelSelector({
// Filter out models with empty names
const validModels = ollamaModels.filter(model => model.model)
setAvailableModels(validModels)
// Helper: normalize Ollama server root for /api/show (strip trailing /v1)
const normalizeOllamaRoot = (url: string): string => {
try {
const u = new URL(url)
let pathname = u.pathname.replace(/\/+$|^$/, '')
if (pathname.endsWith('/v1')) {
pathname = pathname.slice(0, -3)
}
u.pathname = pathname
return u.toString().replace(/\/+$/, '')
} catch {
return url.replace(/\/v1\/?$/, '')
}
}
// Helper: extract num_ctx/context_length from /api/show response
const extractContextTokens = (data: any): number | null => {
if (!data || typeof data !== 'object') return null
// First check model_info for architecture-specific context_length fields
// Example: qwen2.context_length, llama.context_length, etc.
if (data.model_info && typeof data.model_info === 'object') {
const modelInfo = data.model_info
for (const key of Object.keys(modelInfo)) {
if (key.endsWith('.context_length') || key.endsWith('_context_length')) {
const val = modelInfo[key]
if (typeof val === 'number' && isFinite(val) && val > 0) {
return val
}
}
}
}
// Fallback to other common fields
const candidates = [
(data as any)?.parameters?.num_ctx,
(data as any)?.model_info?.num_ctx,
(data as any)?.config?.num_ctx,
(data as any)?.details?.context_length,
(data as any)?.context_length,
(data as any)?.num_ctx,
(data as any)?.max_tokens,
(data as any)?.max_new_tokens
].filter((v: any) => typeof v === 'number' && isFinite(v) && v > 0)
if (candidates.length > 0) {
return Math.max(...candidates)
}
// parameters may be a string like "num_ctx=4096 ..."
if (typeof (data as any)?.parameters === 'string') {
const m = (data as any).parameters.match(/num_ctx\s*[:=]\s*(\d+)/i)
if (m) {
const n = parseInt(m[1], 10)
if (Number.isFinite(n) && n > 0) return n
}
}
return null
}
// Enrich each model via /api/show to get accurate context length
// Store context length separately from max_tokens (output limit)
const ollamaRoot = normalizeOllamaRoot(ollamaBaseUrl)
const enrichedModels = await Promise.all(
validModels.map(async (m: any) => {
try {
const showResp = await fetch(`${ollamaRoot}/api/show`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ name: m.model })
})
if (showResp.ok) {
const showData = await showResp.json()
const ctx = extractContextTokens(showData)
if (typeof ctx === 'number' && isFinite(ctx) && ctx > 0) {
// Store context_length separately, don't override max_tokens
return { ...m, context_length: ctx }
}
}
// Fallback to default if missing
return m
} catch {
return m
}
})
)
setAvailableModels(enrichedModels)
// Only navigate if we have models
if (validModels.length > 0) {
if (enrichedModels.length > 0) {
navigateTo('model')
} else {
setModelLoadError('No models found in your Ollama installation')
}
return validModels
return enrichedModels
} catch (error) {
const errorMessage =
error instanceof Error ? error.message : String(error)
@ -1403,7 +1494,15 @@ export function ModelSelector({
setReasoningEffort(null)
}
// Set context length if available (from Ollama /api/show)
if (modelInfo?.context_length) {
setContextLength(modelInfo.context_length)
} else {
setContextLength(DEFAULT_CONTEXT_LENGTH)
}
// Set max tokens based on model info or default
// Note: max_tokens is for OUTPUT, not context window
if (modelInfo?.max_tokens) {
const modelMaxTokens = modelInfo.max_tokens
// Check if the model's max tokens matches any of our presets

View File

@ -1,9 +1,9 @@
import React from 'react'
import { Text, Box } from 'ink'
import { getModelManager } from '../utils/model'
import { getGlobalConfig } from '../utils/config'
import { useExitOnCtrlCD } from '../hooks/useExitOnCtrlCD'
import { getTheme } from '../utils/theme'
import { getModelManager } from '@utils/model'
import { getGlobalConfig } from '@utils/config'
import { useExitOnCtrlCD } from '@hooks/useExitOnCtrlCD'
import { getTheme } from '@utils/theme'
type Props = {
onClose: () => void

View File

@ -1,19 +1,19 @@
import React, { useState } from 'react'
import { PRODUCT_NAME } from '../constants/product'
import { PRODUCT_NAME } from '@constants/product'
import { Box, Newline, Text, useInput } from 'ink'
import {
getGlobalConfig,
saveGlobalConfig,
DEFAULT_GLOBAL_CONFIG,
ProviderType,
} from '../utils/config.js'
} from '@utils/config'
import { OrderedList } from '@inkjs/ui'
import { useExitOnCtrlCD } from '../hooks/useExitOnCtrlCD'
import { useExitOnCtrlCD } from '@hooks/useExitOnCtrlCD'
import { MIN_LOGO_WIDTH } from './Logo'
import { Select } from './CustomSelect/select'
import { StructuredDiff } from './StructuredDiff'
import { getTheme, type ThemeNames } from '../utils/theme'
import { clearTerminal } from '../utils/terminal'
import { getTheme, type ThemeNames } from '@utils/theme'
import { clearTerminal } from '@utils/terminal'
import { PressEnterToContinue } from './PressEnterToContinue'
import { ModelSelector } from './ModelSelector'
type StepId = 'theme' | 'usage' | 'providers' | 'model'

View File

@ -1,5 +1,5 @@
import * as React from 'react'
import { getTheme } from '../utils/theme'
import { getTheme } from '@utils/theme'
import { Text } from 'ink'
export function PressEnterToContinue(): React.ReactNode {

View File

@ -6,17 +6,17 @@ import {
getGlobalConfig,
saveCurrentProjectConfig,
saveGlobalConfig,
} from './utils/config.js'
} from '@utils/config'
import { existsSync } from 'fs'
import { join } from 'path'
import { homedir } from 'os'
import terminalSetup from './commands/terminalSetup'
import { getTheme } from './utils/theme'
import { RELEASE_NOTES } from './constants/releaseNotes'
import terminalSetup from '@commands/terminalSetup'
import { getTheme } from '@utils/theme'
import { RELEASE_NOTES } from '@constants/releaseNotes'
import { gt } from 'semver'
import { isDirEmpty } from './utils/file'
import { MACRO } from './constants/macros'
import { PROJECT_FILE, PRODUCT_NAME } from './constants/product'
import { isDirEmpty } from '@utils/file'
import { MACRO } from '@constants/macros'
import { PROJECT_FILE, PRODUCT_NAME } from '@constants/product'
// Function to mark onboarding as complete
export function markProjectOnboardingComplete(): void {
@ -74,9 +74,9 @@ export default function ProjectOnboarding({
// Load what we need for onboarding
// NOTE: This whole component is statically rendered Once
const hasClaudeMd = existsSync(join(workspaceDir, PROJECT_FILE))
const workspaceHasProjectGuide = existsSync(join(workspaceDir, PROJECT_FILE))
const isWorkspaceDirEmpty = isDirEmpty(workspaceDir)
const needsClaudeMd = !hasClaudeMd && !isWorkspaceDirEmpty
const shouldRecommendProjectGuide = !workspaceHasProjectGuide && !isWorkspaceDirEmpty
const showTerminalTip =
terminalSetup.isEnabled && !getGlobalConfig().shiftEnterKeyBindingInstalled
@ -106,9 +106,9 @@ export default function ProjectOnboarding({
</React.Fragment>,
)
}
if (needsClaudeMd) {
if (shouldRecommendProjectGuide) {
items.push(
<React.Fragment key="claudemd">
<React.Fragment key="projectGuide">
{/* @ts-expect-error - OrderedList.Item children prop issue */}
<OrderedList.Item>
<Text color={theme.secondaryText}>

View File

@ -1,37 +1,34 @@
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'
import { useArrowKeyHistory } from '../hooks/useArrowKeyHistory'
import { useUnifiedCompletion } from '../hooks/useUnifiedCompletion'
import { addToHistory } from '../history'
import { type Message } from '@query'
import { processUserInput } from '@utils/messages'
import { useArrowKeyHistory } from '@hooks/useArrowKeyHistory'
import { useUnifiedCompletion } from '@hooks/useUnifiedCompletion'
import { addToHistory } from '@history'
import TextInput from './TextInput'
import { memo, useCallback, useEffect, useMemo, useState } from 'react'
import { countTokens } from '../utils/tokens'
import { countTokens } from '@utils/tokens'
import { SentryErrorBoundary } from './SentryErrorBoundary'
import { AutoUpdater } from './AutoUpdater'
import type { AutoUpdaterResult } from '../utils/autoUpdater'
import type { Command } from '../commands'
import type { SetToolJSXFn, Tool } from '../Tool'
import type { Command } from '@commands'
import type { SetToolJSXFn, Tool } from '@tool'
import { TokenWarning, WARNING_THRESHOLD } from './TokenWarning'
import { useTerminalSize } from '../hooks/useTerminalSize'
import { getTheme } from '../utils/theme'
import { getModelManager, reloadModelManager } from '../utils/model'
import { saveGlobalConfig } from '../utils/config'
import { setTerminalTitle } from '../utils/terminal'
import { useTerminalSize } from '@hooks/useTerminalSize'
import { getTheme } from '@utils/theme'
import { getModelManager, reloadModelManager } from '@utils/model'
import { saveGlobalConfig } from '@utils/config'
import { setTerminalTitle } from '@utils/terminal'
import terminalSetup, {
isShiftEnterKeyBindingInstalled,
handleHashCommand,
} from '../commands/terminalSetup'
import { usePermissionContext } from '../context/PermissionContext'
} from '@commands/terminalSetup'
import { usePermissionContext } from '@context/PermissionContext'
// Async function to interpret the '#' command input using AI
async function interpretHashCommand(input: string): Promise<string> {
// Use the AI to interpret the input
try {
const { queryQuick } = await import('../services/claude')
const { queryQuick } = await import('@services/claude')
// Create a prompt for the model to interpret the hash command
const systemPrompt = [
@ -79,8 +76,6 @@ type Props = {
verbose: boolean
messages: Message[]
setToolJSX: SetToolJSXFn
onAutoUpdaterResult: (result: AutoUpdaterResult) => void
autoUpdaterResult: AutoUpdaterResult | null
tools: Tool[]
input: string
onInputChange: (value: string) => void
@ -114,8 +109,6 @@ function PromptInput({
verbose,
messages,
setToolJSX,
onAutoUpdaterResult,
autoUpdaterResult,
tools,
input,
onInputChange,
@ -131,7 +124,6 @@ function PromptInput({
readFileTimestamps,
onModelChange,
}: Props): React.ReactNode {
const [isAutoUpdating, setIsAutoUpdating] = useState(false)
const [exitMessage, setExitMessage] = useState<{
show: boolean
key?: string
@ -316,7 +308,7 @@ function PromptInput({
addToHistory(mode === 'koding' ? `#${input}` : input)
onInputChange('')
// Create additional context to inform Claude this is for KODING.md
// Create additional context to inform the assistant this is for KODING.md
const kodingContext =
'The user is using Koding mode. Format your response as a comprehensive, well-structured document suitable for adding to AGENTS.md. Use proper markdown formatting with headings, lists, code blocks, etc. The response should be complete and ready to add to AGENTS.md documentation.'
@ -362,8 +354,8 @@ function PromptInput({
if (messages.length) {
await onQuery(messages)
// After query completes, the last message should be Claude's response
// We'll set up a one-time listener to capture and save Claude's response
// After query completes, the last message should be the assistant's response
// We'll set up a one-time listener to capture and save that response
// This will be handled by the REPL component or message handler
}
@ -532,7 +524,7 @@ function PromptInput({
onShowMessageSelector()
}
// Shift+Tab for mode cycling (matching original Claude Code implementation)
// Shift+Tab for mode cycling (retains legacy keyboard behavior)
if (key.shift && key.tab) {
cycleMode()
return true // Explicitly handled
@ -681,9 +673,7 @@ function PromptInput({
</Box>
<SentryErrorBoundary children={
<Box justifyContent="flex-end" gap={1}>
{!autoUpdaterResult &&
!isAutoUpdating &&
!debug &&
{!debug &&
tokenUsage < WARNING_THRESHOLD && (
<Text dimColor>
{terminalSetup.isEnabled &&
@ -693,13 +683,6 @@ function PromptInput({
</Text>
)}
<TokenWarning tokenUsage={tokenUsage} />
{/* <AutoUpdater
debug={debug}
onAutoUpdaterResult={onAutoUpdaterResult}
autoUpdaterResult={autoUpdaterResult}
isUpdating={isAutoUpdating}
onChangeIsUpdating={setIsAutoUpdating}
/> */}
</Box>
} />
</Box>
@ -737,13 +720,6 @@ function PromptInput({
<SentryErrorBoundary children={
<Box justifyContent="flex-end" gap={1}>
<TokenWarning tokenUsage={countTokens(messages)} />
<AutoUpdater
debug={debug}
onAutoUpdaterResult={onAutoUpdaterResult}
autoUpdaterResult={autoUpdaterResult}
isUpdating={isAutoUpdating}
onChangeIsUpdating={setIsAutoUpdating}
/>
</Box>
} />
</Box>

View File

@ -1,5 +1,5 @@
import * as React from 'react'
import { captureException } from '../services/sentry'
import { captureException } from '@services/sentry'
interface Props {
children: React.ReactNode

View File

@ -1,9 +1,9 @@
import { Box, Text } from 'ink'
import * as React from 'react'
import { useEffect, useRef, useState } from 'react'
import { getTheme } from '../utils/theme'
import { getTheme } from '@utils/theme'
import { sample } from 'lodash-es'
import { getSessionState } from '../utils/sessionState'
import { getSessionState } from '@utils/sessionState'
// NB: The third character in this string is an emoji that
// renders on Windows consoles with a green background
const CHARACTERS =

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

@ -1,9 +1,9 @@
import { Box, Text } from 'ink'
import * as React from 'react'
import { Hunk } from 'diff'
import { getTheme, ThemeNames } from '../utils/theme'
import { getTheme, ThemeNames } from '@utils/theme'
import { useMemo } from 'react'
import { wrapText } from '../utils/format'
import { wrapText } from '@utils/format'
type Props = {
patch: Hunk

View File

@ -1,8 +1,8 @@
import React from 'react'
import { Text, useInput } from 'ink'
import chalk from 'chalk'
import { useTextInput } from '../hooks/useTextInput'
import { getTheme } from '../utils/theme'
import { useTextInput } from '@hooks/useTextInput'
import { getTheme } from '@utils/theme'
import { type Key } from 'ink'
export type Props = {

View File

@ -1,6 +1,6 @@
import React from 'react'
import { Box, Text } from 'ink'
import type { TodoItem as TodoItemType } from '../utils/todoStorage'
import type { TodoItem as TodoItemType } from '@utils/todoStorage'
export interface TodoItemProps {
todo: TodoItemType

View File

@ -1,6 +1,6 @@
import { Box, Text } from 'ink'
import * as React from 'react'
import { getTheme } from '../utils/theme'
import { getTheme } from '@utils/theme'
type Props = {
tokenUsage: number

View File

@ -1,8 +1,8 @@
import { Box, Text } from 'ink'
import React from 'react'
import { useInterval } from '../hooks/useInterval'
import { getTheme } from '../utils/theme'
import { BLACK_CIRCLE } from '../constants/figures'
import { useInterval } from '@hooks/useInterval'
import { getTheme } from '@utils/theme'
import { BLACK_CIRCLE } from '@constants/figures'
type Props = {
isError: boolean

View File

@ -1,16 +1,15 @@
import React from 'react'
import { Box, Text, useInput } from 'ink'
import { getTheme } from '../utils/theme'
import { getTheme } from '@utils/theme'
import { Select } from './CustomSelect/select'
import {
saveCurrentProjectConfig,
getCurrentProjectConfig,
} from '../utils/config.js'
import { PRODUCT_NAME } from '../constants/product'
import { logEvent } from '../services/statsig'
import { useExitOnCtrlCD } from '../hooks/useExitOnCtrlCD'
} from '@utils/config'
import { PRODUCT_NAME } from '@constants/product'
import { useExitOnCtrlCD } from '@hooks/useExitOnCtrlCD'
import { homedir } from 'os'
import { getCwd } from '../utils/state'
import { getCwd } from '@utils/state'
import Link from './Link'
type Props = {
@ -19,20 +18,13 @@ type Props = {
export function TrustDialog({ onDone }: Props): React.ReactNode {
const theme = getTheme()
React.useEffect(() => {
// Log when dialog is shown
logEvent('trust_dialog_shown', {})
}, [])
React.useEffect(() => {}, [])
function onChange(value: 'yes' | 'no') {
const config = getCurrentProjectConfig()
switch (value) {
case 'yes': {
// Log when user accepts
const isHomeDir = homedir() === getCwd()
logEvent('trust_dialog_accept', {
isHomeDir: String(isHomeDir),
})
if (!isHomeDir) {
saveCurrentProjectConfig({

View File

@ -1,15 +1,15 @@
import { default as React, useCallback } from 'react'
import { useNotifyAfterTimeout } from '../../hooks/useNotifyAfterTimeout'
import { AssistantMessage, BinaryFeedbackResult } from '../../query'
import type { Tool } from '../../Tool'
import type { NormalizedMessage } from '../../utils/messages'
import { useNotifyAfterTimeout } from '@hooks/useNotifyAfterTimeout'
import { AssistantMessage, BinaryFeedbackResult } from '@query'
import type { Tool } from '@tool'
import type { NormalizedMessage } from '@utils/messages'
import { BinaryFeedbackView } from './BinaryFeedbackView'
import {
type BinaryFeedbackChoose,
getBinaryFeedbackResultForChoice,
logBinaryFeedbackEvent,
} from './utils.js'
import { PRODUCT_NAME } from '../../constants/product'
} from './utils'
import { PRODUCT_NAME } from '@constants/product'
type Props = {
m1: AssistantMessage

Some files were not shown because too many files have changed in this diff Show More