Compare commits
40 Commits
v1.1.16-de
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
52cb635512 | ||
|
|
3d7f81242b | ||
|
|
14f9892bb5 | ||
|
|
25adc80161 | ||
|
|
8288378dbd | ||
|
|
c8ecba04d8 | ||
|
|
34cd4e250d | ||
|
|
be6477cca7 | ||
|
|
7069893d14 | ||
|
|
3c9b0ec9d1 | ||
|
|
a4c3f16c2b | ||
|
|
e50c6f52f6 | ||
|
|
893112e43c | ||
|
|
f934cfa62e | ||
|
|
1b3b0786ca | ||
|
|
f486925c06 | ||
|
|
ab0d3f26f3 | ||
|
|
70f0d6b109 | ||
|
|
451362256c | ||
|
|
fd1d6e385d | ||
|
|
ce8c8dad63 | ||
|
|
b847352101 | ||
|
|
61a8ce0d22 | ||
|
|
59dce97350 | ||
|
|
d4abb2abee | ||
|
|
78b49355cd | ||
|
|
fbb2db6963 | ||
|
|
d0d1dca009 | ||
|
|
6bbaa6c559 | ||
|
|
b0d9f58d76 | ||
|
|
a7af9834ef | ||
|
|
53234ba2d9 | ||
|
|
20111a0a26 | ||
|
|
f857fb9577 | ||
|
|
da15ca5a5f | ||
|
|
f23787f50d | ||
|
|
32a9badecd | ||
|
|
5471b2d5fc | ||
|
|
97684a3208 | ||
|
|
efe00eee3b |
18
.env.example
Normal file
18
.env.example
Normal 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
|
||||
@ -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
7
.husky/pre-commit
Normal file → Executable 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
|
||||
@ -1,7 +0,0 @@
|
||||
{
|
||||
"singleQuote": true,
|
||||
"semi": false,
|
||||
"trailingComma": "all",
|
||||
"printWidth": 100
|
||||
}
|
||||
|
||||
@ -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 完成后,分配任务:
|
||||
# 人员 B:step_1_parallel_worker_0.md
|
||||
# 人员 C:step_1_parallel_worker_1.md
|
||||
# 人员 D:step_1_parallel_worker_2.md
|
||||
# 人员 E:step_1_parallel_worker_3.md
|
||||
# 人员 F:step_2_parallel_worker_0.md
|
||||
# 人员 G:step_2_parallel_worker_1.md
|
||||
# 人员 H:step_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. 庆祝成功!🎊
|
||||
|
||||
---
|
||||
|
||||
**记住:质量比速度更重要。宁可慢一点,也要确保每个修复都正确。**
|
||||
@ -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. 开发者 A:Step 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. 开发者 A:Step 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 小而频繁,便于回滚
|
||||
@ -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 的并行任务。
|
||||
@ -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 的并行任务。
|
||||
@ -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
|
||||
```
|
||||
|
||||
## 完成后
|
||||
标记此任务完成,继续其他并行任务。记录任何未解决的问题供后续处理。
|
||||
@ -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 的类型问题,供高级开发者后续优化。
|
||||
@ -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 的修复)。
|
||||
@ -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 的并行任务。
|
||||
@ -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 系统修复完成后,用户输入处理应该完全正常。这是用户体验的关键部分,确保充分测试。
|
||||
@ -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 通信应该类型安全且稳定。这是系统可靠性的关键部分。
|
||||
12
AGENTS.md
12
AGENTS.md
@ -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
|
||||
|
||||
44
CLAUDE.md
44
CLAUDE.md
@ -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
|
||||
|
||||
15
Dockerfile
15
Dockerfile
@ -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"]
|
||||
|
||||
169
PUBLISH_GUIDE.md
169
PUBLISH_GUIDE.md
@ -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
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
通过这套双发包系统,你可以:
|
||||
- 🚀 快速发布开发版本进行内部测试
|
||||
- 🛡️ 安全发布正式版本给最终用户
|
||||
- 📈 保持清晰的版本管理和发布历史
|
||||
- ⚡ 自动化大部分重复操作,减少人为错误
|
||||
56
README.md
56
README.md
@ -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>
|
||||
[](https://www.npmjs.com/package/@shareai-lab/kode)
|
||||
[](https://opensource.org/licenses/Apache-2.0)
|
||||
[](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 (Unix‑like) 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 Code’s 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
|
||||
|
||||
@ -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
202
docs/PUBLISH_GUIDE.md
Normal 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操作完全可控
|
||||
- 🔄 灵活的版本回滚和恢复机制
|
||||
@ -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
|
||||
|
||||
@ -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.
|
||||
|
||||
63
docs/develop/modules/openai-adapters.md
Normal file
63
docs/develop/modules/openai-adapters.md
Normal file
@ -0,0 +1,63 @@
|
||||
# OpenAI Adapter Layer
|
||||
|
||||
This module explains how Kode’s 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 provider’s 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 Anthropic’s 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`.
|
||||
@ -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>
|
||||
@ -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
|
||||
59
package.json
59
package.json
@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
146
phase1-fixes.md
146
phase1-fixes.md
@ -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
|
||||
```
|
||||
@ -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.
|
||||
@ -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)
|
||||
})
|
||||
})
|
||||
|
||||
152
scripts/build.ts
152
scripts/build.ts
@ -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 };
|
||||
@ -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);
|
||||
|
||||
@ -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();
|
||||
|
||||
@ -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();
|
||||
@ -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
|
||||
|
||||
@ -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}>
|
||||
|
||||
@ -2,7 +2,7 @@ import {
|
||||
ProjectConfig,
|
||||
getCurrentProjectConfig as getCurrentProjectConfigDefault,
|
||||
saveCurrentProjectConfig as saveCurrentProjectConfigDefault,
|
||||
} from '../utils/config.js'
|
||||
} from '@utils/config'
|
||||
|
||||
export type ProjectConfigHandler = {
|
||||
getCurrentProjectConfig: () => ProjectConfig
|
||||
|
||||
@ -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',
|
||||
|
||||
@ -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: (
|
||||
|
||||
@ -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:
|
||||
|
||||
|
||||
@ -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 = {
|
||||
|
||||
@ -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',
|
||||
|
||||
@ -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
|
||||
|
||||
|
||||
@ -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',
|
||||
|
||||
@ -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 = {
|
||||
|
||||
@ -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',
|
||||
|
||||
@ -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' &&
|
||||
|
||||
@ -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'
|
||||
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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',
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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',
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { Command } from '../commands'
|
||||
import { Command } from '@commands'
|
||||
|
||||
export default {
|
||||
type: 'prompt',
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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',
|
||||
|
||||
@ -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',
|
||||
|
||||
@ -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',
|
||||
|
||||
@ -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',
|
||||
|
||||
@ -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>
|
||||
</>
|
||||
)
|
||||
}
|
||||
@ -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()
|
||||
|
||||
@ -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} · 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} · 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 · Restart to apply
|
||||
</Text>
|
||||
) : null}
|
||||
{(autoUpdaterResult?.status === 'install_failed' ||
|
||||
autoUpdaterResult?.status === 'no_permissions') && (
|
||||
<Text color={theme.error}>
|
||||
✗ Auto-update failed · Try{' '}
|
||||
<Text bold>{PRODUCT_COMMAND} doctor</Text> or{' '}
|
||||
<Text bold>npm i -g {MACRO.PACKAGE_URL}</Text>
|
||||
</Text>
|
||||
)}
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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])
|
||||
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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 = {
|
||||
/**
|
||||
|
||||
@ -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 = {
|
||||
/**
|
||||
|
||||
@ -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 (
|
||||
|
||||
@ -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>
|
||||
)
|
||||
}
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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[]
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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 (
|
||||
|
||||
@ -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[]
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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()
|
||||
}
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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'
|
||||
|
||||
|
||||
@ -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 = {
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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'
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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}>
|
||||
@ -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>
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import * as React from 'react'
|
||||
import { captureException } from '../services/sentry'
|
||||
import { captureException } from '@services/sentry'
|
||||
|
||||
interface Props {
|
||||
children: React.ReactNode
|
||||
|
||||
@ -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 =
|
||||
|
||||
@ -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
|
||||
}
|
||||
@ -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
|
||||
|
||||
@ -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 = {
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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({
|
||||
|
||||
@ -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
Loading…
x
Reference in New Issue
Block a user