@x-all-in-one/coding-helper 0.0.7 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +90 -57
- package/dist/commands/auth.js +1 -1
- package/dist/commands/config.js +11 -7
- package/dist/commands/doctor.js +2 -2
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/lib/config.d.ts +3 -0
- package/dist/lib/config.js +6 -0
- package/dist/lib/model-service.d.ts +49 -0
- package/dist/lib/model-service.js +129 -0
- package/dist/lib/plugins/ccline-plugin.d.ts +40 -0
- package/dist/lib/plugins/ccline-plugin.js +116 -0
- package/dist/lib/plugins/index.d.ts +2 -0
- package/dist/lib/plugins/index.js +3 -0
- package/dist/lib/plugins/oh-my-opencode-plugin.d.ts +41 -0
- package/dist/lib/plugins/oh-my-opencode-plugin.js +123 -0
- package/dist/lib/tool-manager.d.ts +70 -2
- package/dist/lib/tool-manager.js +149 -113
- package/dist/lib/tools/base-tool.d.ts +73 -0
- package/dist/lib/tools/base-tool.js +65 -0
- package/dist/lib/{claude-code-manager.d.ts → tools/claude-code-tool.d.ts} +31 -4
- package/dist/lib/{claude-code-manager.js → tools/claude-code-tool.js} +52 -13
- package/dist/lib/tools/codex-tool.d.ts +58 -0
- package/dist/lib/tools/codex-tool.js +142 -0
- package/dist/lib/tools/index.d.ts +4 -0
- package/dist/lib/tools/index.js +6 -0
- package/dist/lib/{opencode-manager.d.ts → tools/opencode-tool.d.ts} +35 -7
- package/dist/lib/{opencode-manager.js → tools/opencode-tool.js} +63 -14
- package/dist/lib/wizard/flows/api-key-flow.d.ts +17 -0
- package/dist/lib/wizard/flows/api-key-flow.js +120 -0
- package/dist/lib/wizard/flows/index.d.ts +3 -0
- package/dist/lib/wizard/flows/index.js +3 -0
- package/dist/lib/wizard/flows/language-flow.d.ts +14 -0
- package/dist/lib/wizard/flows/language-flow.js +60 -0
- package/dist/lib/wizard/flows/model-selection-flow.d.ts +48 -0
- package/dist/lib/wizard/flows/model-selection-flow.js +148 -0
- package/dist/lib/wizard/index.d.ts +5 -0
- package/dist/lib/wizard/index.js +10 -0
- package/dist/lib/wizard/installers/bun-installer.d.ts +24 -0
- package/dist/lib/wizard/installers/bun-installer.js +90 -0
- package/dist/lib/wizard/installers/index.d.ts +1 -0
- package/dist/lib/wizard/installers/index.js +1 -0
- package/dist/lib/wizard/menus/index.d.ts +3 -0
- package/dist/lib/wizard/menus/index.js +3 -0
- package/dist/lib/wizard/menus/main-menu.d.ts +25 -0
- package/dist/lib/wizard/menus/main-menu.js +105 -0
- package/dist/lib/wizard/menus/plugin-menu.d.ts +26 -0
- package/dist/lib/wizard/menus/plugin-menu.js +131 -0
- package/dist/lib/wizard/menus/tool-menu.d.ts +59 -0
- package/dist/lib/wizard/menus/tool-menu.js +517 -0
- package/dist/lib/wizard/ui/index.d.ts +2 -0
- package/dist/lib/wizard/ui/index.js +2 -0
- package/dist/lib/wizard/ui/prompt-helper.d.ts +27 -0
- package/dist/lib/wizard/ui/prompt-helper.js +63 -0
- package/dist/lib/wizard/ui/ui-renderer.d.ts +24 -0
- package/dist/lib/wizard/ui/ui-renderer.js +71 -0
- package/dist/lib/wizard/wizard.d.ts +63 -0
- package/dist/lib/wizard/wizard.js +209 -0
- package/dist/lib/wizard.d.ts +5 -51
- package/dist/lib/wizard.js +5 -1017
- package/dist/locales/en_US.json +32 -1
- package/dist/locales/zh_CN.json +32 -1
- package/package.json +5 -2
package/README.md
CHANGED
|
@@ -1,91 +1,124 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Coding Helper
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
CLI 工具,用于配置 AI 编程助手(Claude Code、OpenCode、Codex)的 API 端点和模型。
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
## 安装
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
```bash
|
|
8
|
+
npm install -g @x-all-in-one/coding-helper
|
|
9
|
+
# 或
|
|
10
|
+
pnpm add -g @x-all-in-one/coding-helper
|
|
11
|
+
```
|
|
8
12
|
|
|
9
|
-
##
|
|
13
|
+
## 使用
|
|
10
14
|
|
|
11
|
-
|
|
12
|
-
|
|
15
|
+
```bash
|
|
16
|
+
# 启动交互式配置向导
|
|
17
|
+
chelper
|
|
13
18
|
|
|
19
|
+
# 查看帮助
|
|
20
|
+
chelper --help
|
|
21
|
+
|
|
22
|
+
# 切换语言
|
|
23
|
+
chelper lang
|
|
14
24
|
```
|
|
15
|
-
cd existing_repo
|
|
16
|
-
git remote add origin https://gitlab.yeaosound.com/xaio-ai/coding-helper.git
|
|
17
|
-
git branch -M main
|
|
18
|
-
git push -uf origin main
|
|
19
|
-
```
|
|
20
25
|
|
|
21
|
-
##
|
|
26
|
+
## 开发
|
|
27
|
+
|
|
28
|
+
### 环境要求
|
|
29
|
+
|
|
30
|
+
- Node.js >= 18
|
|
31
|
+
- pnpm >= 10
|
|
22
32
|
|
|
23
|
-
|
|
33
|
+
### 常用命令
|
|
24
34
|
|
|
25
|
-
|
|
35
|
+
```bash
|
|
36
|
+
pnpm install # 安装依赖
|
|
37
|
+
pnpm dev # 开发模式(热重载)
|
|
38
|
+
pnpm build # 构建项目
|
|
39
|
+
pnpm lint # 代码检查
|
|
40
|
+
pnpm lint:fix # 自动修复
|
|
41
|
+
```
|
|
26
42
|
|
|
27
|
-
|
|
28
|
-
- [ ] [Create a new merge request](https://docs.gitlab.com/ee/user/project/merge_requests/creating_merge_requests.html)
|
|
29
|
-
- [ ] [Automatically close issues from merge requests](https://docs.gitlab.com/ee/user/project/issues/managing_issues.html#closing-issues-automatically)
|
|
30
|
-
- [ ] [Enable merge request approvals](https://docs.gitlab.com/ee/user/project/merge_requests/approvals/)
|
|
31
|
-
- [ ] [Set auto-merge](https://docs.gitlab.com/user/project/merge_requests/auto_merge/)
|
|
43
|
+
### 目录结构
|
|
32
44
|
|
|
33
|
-
|
|
45
|
+
```
|
|
46
|
+
src/
|
|
47
|
+
├── cli.ts # 入口文件
|
|
48
|
+
├── lib/
|
|
49
|
+
│ ├── config.ts # 配置管理 (~/.chelper/config.yaml)
|
|
50
|
+
│ ├── i18n.ts # 国际化
|
|
51
|
+
│ ├── tool-manager.ts # 工具注册管理
|
|
52
|
+
│ ├── wizard/ # 交互式 TUI
|
|
53
|
+
│ ├── tools/ # 工具实现 (Claude Code, OpenCode, Codex)
|
|
54
|
+
│ └── plugins/ # 插件实现
|
|
55
|
+
└── locales/ # 语言文件
|
|
56
|
+
```
|
|
34
57
|
|
|
35
|
-
|
|
58
|
+
## 发版流程
|
|
36
59
|
|
|
37
|
-
|
|
38
|
-
- [ ] [Analyze your code for known vulnerabilities with Static Application Security Testing (SAST)](https://docs.gitlab.com/ee/user/application_security/sast/)
|
|
39
|
-
- [ ] [Deploy to Kubernetes, Amazon EC2, or Amazon ECS using Auto Deploy](https://docs.gitlab.com/ee/topics/autodevops/requirements.html)
|
|
40
|
-
- [ ] [Use pull-based deployments for improved Kubernetes management](https://docs.gitlab.com/ee/user/clusters/agent/)
|
|
41
|
-
- [ ] [Set up protected environments](https://docs.gitlab.com/ee/ci/environments/protected_environments.html)
|
|
60
|
+
本项目使用 [Changeset](https://github.com/changesets/changesets) 进行版本管理。
|
|
42
61
|
|
|
43
|
-
|
|
62
|
+
### 1. 创建 Changeset
|
|
44
63
|
|
|
45
|
-
|
|
64
|
+
开发完成后,运行以下命令声明版本变更:
|
|
46
65
|
|
|
47
|
-
|
|
66
|
+
```bash
|
|
67
|
+
pnpm changeset
|
|
68
|
+
```
|
|
48
69
|
|
|
49
|
-
|
|
70
|
+
交互式选择:
|
|
71
|
+
- **patch** - 修复 bug (0.0.x)
|
|
72
|
+
- **minor** - 新功能 (0.x.0)
|
|
73
|
+
- **major** - 破坏性变更 (x.0.0)
|
|
50
74
|
|
|
51
|
-
|
|
75
|
+
这会在 `.changeset/` 目录下生成一个 markdown 文件。
|
|
52
76
|
|
|
53
|
-
|
|
54
|
-
Choose a self-explaining name for your project.
|
|
77
|
+
### 2. 提交代码
|
|
55
78
|
|
|
56
|
-
|
|
57
|
-
|
|
79
|
+
```bash
|
|
80
|
+
git add .
|
|
81
|
+
git commit -m "feat: 添加新功能"
|
|
82
|
+
git push origin feature/xxx
|
|
83
|
+
```
|
|
58
84
|
|
|
59
|
-
|
|
60
|
-
On some READMEs, you may see small images that convey metadata, such as whether or not all the tests are passing for the project. You can use Shields to add some to your README. Many services also have instructions for adding a badge.
|
|
85
|
+
### 3. 创建 MR 并合并
|
|
61
86
|
|
|
62
|
-
|
|
63
|
-
Depending on what you are making, it can be a good idea to include screenshots or even a video (you'll frequently see GIFs rather than actual videos). Tools like ttygif can help, but check out Asciinema for a more sophisticated method.
|
|
87
|
+
创建 Merge Request 合并到 `main` 分支。
|
|
64
88
|
|
|
65
|
-
|
|
66
|
-
Within a particular ecosystem, there may be a common way of installing things, such as using Yarn, NuGet, or Homebrew. However, consider the possibility that whoever is reading your README is a novice and would like more guidance. Listing specific steps helps remove ambiguity and gets people to using your project as quickly as possible. If it only runs in a specific context like a particular programming language version or operating system or has dependencies that have to be installed manually, also add a Requirements subsection.
|
|
89
|
+
### 4. 自动发版
|
|
67
90
|
|
|
68
|
-
|
|
69
|
-
|
|
91
|
+
合并后,GitLab CI 会自动:
|
|
92
|
+
1. 执行 `changeset version` 更新版本号和 CHANGELOG
|
|
93
|
+
2. 执行 `changeset publish` 发布到 npm
|
|
94
|
+
3. 提交版本变更并推送
|
|
70
95
|
|
|
71
|
-
##
|
|
72
|
-
Tell people where they can go to for help. It can be any combination of an issue tracker, a chat room, an email address, etc.
|
|
96
|
+
## Commit 规范
|
|
73
97
|
|
|
74
|
-
|
|
75
|
-
If you have ideas for releases in the future, it is a good idea to list them in the README.
|
|
98
|
+
使用 [Conventional Commits](https://www.conventionalcommits.org/) 规范:
|
|
76
99
|
|
|
77
|
-
|
|
78
|
-
|
|
100
|
+
| 类型 | 说明 |
|
|
101
|
+
|------|------|
|
|
102
|
+
| `feat` | 新功能 |
|
|
103
|
+
| `fix` | 修复 bug |
|
|
104
|
+
| `docs` | 文档变更 |
|
|
105
|
+
| `style` | 代码格式(不影响功能) |
|
|
106
|
+
| `refactor` | 重构(不是新功能也不是修复) |
|
|
107
|
+
| `perf` | 性能优化 |
|
|
108
|
+
| `test` | 测试相关 |
|
|
109
|
+
| `chore` | 构建/工具变更 |
|
|
79
110
|
|
|
80
|
-
|
|
111
|
+
## GitLab CI
|
|
81
112
|
|
|
82
|
-
|
|
113
|
+
流水线包含以下阶段:
|
|
83
114
|
|
|
84
|
-
|
|
85
|
-
|
|
115
|
+
| 阶段 | 说明 |
|
|
116
|
+
|------|------|
|
|
117
|
+
| `install` | 安装依赖 |
|
|
118
|
+
| `lint` | 代码检查 |
|
|
119
|
+
| `build` | 构建项目 |
|
|
120
|
+
| `release` | 发版(仅 main 分支) |
|
|
86
121
|
|
|
87
122
|
## License
|
|
88
|
-
For open source projects, say how it is licensed.
|
|
89
123
|
|
|
90
|
-
|
|
91
|
-
If you have run out of energy or time for your project, put a note at the top of the README saying that development has slowed down or stopped completely. Someone may choose to fork your project or volunteer to step in as a maintainer or owner, allowing your project to keep going. You can also make an explicit request for maintainers.
|
|
124
|
+
MIT
|
package/dist/commands/auth.js
CHANGED
|
@@ -2,10 +2,10 @@ import chalk from 'chalk';
|
|
|
2
2
|
import inquirer from 'inquirer';
|
|
3
3
|
import ora from 'ora';
|
|
4
4
|
import { validateApiKey } from '../lib/api-validator.js';
|
|
5
|
-
import { DEFAULT_CONFIG } from '../lib/claude-code-manager.js';
|
|
6
5
|
import { configManager } from '../lib/config.js';
|
|
7
6
|
import { i18n } from '../lib/i18n.js';
|
|
8
7
|
import { toolManager } from '../lib/tool-manager.js';
|
|
8
|
+
import { DEFAULT_CONFIG } from '../lib/tools/claude-code-tool.js';
|
|
9
9
|
/**
|
|
10
10
|
* 支持的工具列表
|
|
11
11
|
*/
|
package/dist/commands/config.js
CHANGED
|
@@ -1,27 +1,29 @@
|
|
|
1
1
|
import chalk from 'chalk';
|
|
2
2
|
import inquirer from 'inquirer';
|
|
3
3
|
import { i18n } from '../lib/i18n.js';
|
|
4
|
-
import {
|
|
4
|
+
import { toolManager, toolRegistry } from '../lib/tool-manager.js';
|
|
5
5
|
import { wizard } from '../lib/wizard.js';
|
|
6
6
|
export async function configCommand(args) {
|
|
7
7
|
const subCommand = args[0];
|
|
8
8
|
const toolName = args[1];
|
|
9
9
|
// 如果没有参数,进入交互式选择
|
|
10
10
|
if (!subCommand) {
|
|
11
|
+
const supportedTools = toolRegistry.getSupportedTools();
|
|
11
12
|
const { selectedTool } = await inquirer.prompt([
|
|
12
13
|
{
|
|
13
14
|
type: 'list',
|
|
14
15
|
name: 'selectedTool',
|
|
15
16
|
message: i18n.t('wizard.select_tool'),
|
|
16
|
-
choices:
|
|
17
|
+
choices: supportedTools.map(tool => ({
|
|
17
18
|
name: `${tool.displayName} (${tool.name})`,
|
|
18
19
|
value: tool.name,
|
|
19
20
|
})),
|
|
20
21
|
},
|
|
21
22
|
]);
|
|
23
|
+
const tool = toolRegistry.getTool(selectedTool);
|
|
22
24
|
// 检查工具是否安装
|
|
23
25
|
if (!toolManager.isToolInstalled(selectedTool)) {
|
|
24
|
-
console.log(chalk.yellow(`\n${i18n.t('wizard.tool_not_installed', { tool:
|
|
26
|
+
console.log(chalk.yellow(`\n${i18n.t('wizard.tool_not_installed', { tool: tool?.displayName || selectedTool })}`));
|
|
25
27
|
const { shouldInstall } = await inquirer.prompt([
|
|
26
28
|
{
|
|
27
29
|
type: 'confirm',
|
|
@@ -116,9 +118,10 @@ export async function configCommand(args) {
|
|
|
116
118
|
return;
|
|
117
119
|
}
|
|
118
120
|
// 其他工具的支持
|
|
119
|
-
|
|
121
|
+
const tool = toolRegistry.getTool(subCommand);
|
|
122
|
+
if (tool) {
|
|
120
123
|
if (!toolManager.isToolInstalled(subCommand)) {
|
|
121
|
-
console.log(chalk.yellow(i18n.t('wizard.tool_not_installed', { tool:
|
|
124
|
+
console.log(chalk.yellow(i18n.t('wizard.tool_not_installed', { tool: tool.displayName })));
|
|
122
125
|
return;
|
|
123
126
|
}
|
|
124
127
|
await wizard.showToolMenu(subCommand);
|
|
@@ -127,7 +130,8 @@ export async function configCommand(args) {
|
|
|
127
130
|
console.error(chalk.red(`Unknown command: ${subCommand}`));
|
|
128
131
|
console.log(chalk.gray('Usage: chelper config [tool-name]'));
|
|
129
132
|
console.log(chalk.gray('Available tools:'));
|
|
130
|
-
|
|
131
|
-
|
|
133
|
+
const supportedTools = toolRegistry.getSupportedTools();
|
|
134
|
+
supportedTools.forEach((t) => {
|
|
135
|
+
console.log(chalk.gray(` - ${t.name}: ${t.displayName}`));
|
|
132
136
|
});
|
|
133
137
|
}
|
package/dist/commands/doctor.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import chalk from 'chalk';
|
|
2
2
|
import ora from 'ora';
|
|
3
3
|
import { validateApiKey } from '../lib/api-validator.js';
|
|
4
|
-
import { claudeCodeManager } from '../lib/claude-code-manager.js';
|
|
5
4
|
import { configManager } from '../lib/config.js';
|
|
6
5
|
import { i18n } from '../lib/i18n.js';
|
|
7
6
|
import { toolManager } from '../lib/tool-manager.js';
|
|
7
|
+
import { claudeCodeTool } from '../lib/tools/claude-code-tool.js';
|
|
8
8
|
export async function doctorCommand() {
|
|
9
9
|
const spinner = ora(i18n.t('doctor.checking')).start();
|
|
10
10
|
const results = [];
|
|
@@ -73,7 +73,7 @@ export async function doctorCommand() {
|
|
|
73
73
|
}
|
|
74
74
|
// Check 4: Configuration status
|
|
75
75
|
spinner.text = i18n.t('doctor.check_config');
|
|
76
|
-
|
|
76
|
+
claudeCodeTool.getModelConfig();
|
|
77
77
|
const models = configManager.getModels();
|
|
78
78
|
const configComplete = !!apiKey && !!(models.haikuModel || models.sonnetModel || models.opusModel);
|
|
79
79
|
let configMessage = '';
|
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
package/dist/lib/config.d.ts
CHANGED
|
@@ -6,6 +6,7 @@ export interface ChelperConfig {
|
|
|
6
6
|
opusModel?: string;
|
|
7
7
|
openCodeModel?: string;
|
|
8
8
|
openCodeSmallModel?: string;
|
|
9
|
+
codexModel?: string;
|
|
9
10
|
}
|
|
10
11
|
export declare class ConfigManager {
|
|
11
12
|
private static instance;
|
|
@@ -43,5 +44,7 @@ export declare class ConfigManager {
|
|
|
43
44
|
openCodeModel?: string;
|
|
44
45
|
openCodeSmallModel?: string;
|
|
45
46
|
}): void;
|
|
47
|
+
getCodexModel(): string | undefined;
|
|
48
|
+
setCodexModel(model: string): void;
|
|
46
49
|
}
|
|
47
50
|
export declare const configManager: ConfigManager;
|
package/dist/lib/config.js
CHANGED
|
@@ -102,5 +102,11 @@ export class ConfigManager {
|
|
|
102
102
|
setOpenCodeModels(models) {
|
|
103
103
|
this.updateConfig(models);
|
|
104
104
|
}
|
|
105
|
+
getCodexModel() {
|
|
106
|
+
return this.config.codexModel;
|
|
107
|
+
}
|
|
108
|
+
setCodexModel(model) {
|
|
109
|
+
this.updateConfig({ codexModel: model });
|
|
110
|
+
}
|
|
105
111
|
}
|
|
106
112
|
export const configManager = ConfigManager.getInstance();
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
export interface ModelInfo {
|
|
2
|
+
id: string;
|
|
3
|
+
name?: string;
|
|
4
|
+
context_length?: number;
|
|
5
|
+
max_output_tokens?: number;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* 模型服务
|
|
9
|
+
* 负责模型列表的获取、缓存和同步
|
|
10
|
+
*/
|
|
11
|
+
export declare class ModelService {
|
|
12
|
+
private static instance;
|
|
13
|
+
private cachedModels;
|
|
14
|
+
private cachedModelsWithInfo;
|
|
15
|
+
private constructor();
|
|
16
|
+
static getInstance(): ModelService;
|
|
17
|
+
/**
|
|
18
|
+
* 获取可用模型列表(带缓存)
|
|
19
|
+
*/
|
|
20
|
+
fetchModels(): Promise<string[]>;
|
|
21
|
+
/**
|
|
22
|
+
* 获取带详细信息的模型列表
|
|
23
|
+
*/
|
|
24
|
+
fetchModelsWithInfo(apiKey: string): Promise<ModelInfo[]>;
|
|
25
|
+
/**
|
|
26
|
+
* 清除缓存
|
|
27
|
+
*/
|
|
28
|
+
clearCache(): void;
|
|
29
|
+
/**
|
|
30
|
+
* 刷新模型列表(仅获取最新数据)
|
|
31
|
+
*/
|
|
32
|
+
refreshModels(): Promise<{
|
|
33
|
+
success: boolean;
|
|
34
|
+
count: number;
|
|
35
|
+
}>;
|
|
36
|
+
/**
|
|
37
|
+
* 刷新并同步模型列表到 OpenCode 配置
|
|
38
|
+
* 只更新 provider.xaio.models,不改变当前选择的模型
|
|
39
|
+
*/
|
|
40
|
+
refreshAndSyncToOpenCode(): Promise<{
|
|
41
|
+
success: boolean;
|
|
42
|
+
count: number;
|
|
43
|
+
}>;
|
|
44
|
+
/**
|
|
45
|
+
* 获取缓存的模型数量
|
|
46
|
+
*/
|
|
47
|
+
getCachedCount(): number;
|
|
48
|
+
}
|
|
49
|
+
export declare const modelService: ModelService;
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import { configManager } from './config.js';
|
|
2
|
+
import { openCodeTool } from './tools/opencode-tool.js';
|
|
3
|
+
/**
|
|
4
|
+
* 模型服务
|
|
5
|
+
* 负责模型列表的获取、缓存和同步
|
|
6
|
+
*/
|
|
7
|
+
export class ModelService {
|
|
8
|
+
static instance;
|
|
9
|
+
cachedModels = [];
|
|
10
|
+
cachedModelsWithInfo = [];
|
|
11
|
+
constructor() { }
|
|
12
|
+
static getInstance() {
|
|
13
|
+
if (!ModelService.instance) {
|
|
14
|
+
ModelService.instance = new ModelService();
|
|
15
|
+
}
|
|
16
|
+
return ModelService.instance;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* 获取可用模型列表(带缓存)
|
|
20
|
+
*/
|
|
21
|
+
async fetchModels() {
|
|
22
|
+
const apiKey = configManager.getApiKey();
|
|
23
|
+
if (!apiKey) {
|
|
24
|
+
return [];
|
|
25
|
+
}
|
|
26
|
+
if (this.cachedModels.length > 0) {
|
|
27
|
+
return this.cachedModels;
|
|
28
|
+
}
|
|
29
|
+
const modelsWithInfo = await this.fetchModelsWithInfo(apiKey);
|
|
30
|
+
this.cachedModels = modelsWithInfo.map(m => m.id);
|
|
31
|
+
return this.cachedModels;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* 获取带详细信息的模型列表
|
|
35
|
+
*/
|
|
36
|
+
async fetchModelsWithInfo(apiKey) {
|
|
37
|
+
if (this.cachedModelsWithInfo.length > 0) {
|
|
38
|
+
return this.cachedModelsWithInfo;
|
|
39
|
+
}
|
|
40
|
+
const controller = new AbortController();
|
|
41
|
+
const timeoutId = setTimeout(() => controller.abort(), 30000);
|
|
42
|
+
try {
|
|
43
|
+
const response = await fetch('https://code-api.x-aio.com/v1/models', {
|
|
44
|
+
method: 'GET',
|
|
45
|
+
headers: {
|
|
46
|
+
'Authorization': `Bearer ${apiKey}`,
|
|
47
|
+
'Content-Type': 'application/json',
|
|
48
|
+
},
|
|
49
|
+
signal: controller.signal,
|
|
50
|
+
});
|
|
51
|
+
clearTimeout(timeoutId);
|
|
52
|
+
if (!response.ok) {
|
|
53
|
+
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
54
|
+
}
|
|
55
|
+
const data = await response.json();
|
|
56
|
+
if (data && data.data && Array.isArray(data.data)) {
|
|
57
|
+
this.cachedModelsWithInfo = data.data.map((m) => ({
|
|
58
|
+
id: m.id,
|
|
59
|
+
name: m.name || m.id,
|
|
60
|
+
context_length: m.context_length,
|
|
61
|
+
max_output_tokens: m.max_output_tokens,
|
|
62
|
+
}));
|
|
63
|
+
this.cachedModels = this.cachedModelsWithInfo.map(m => m.id);
|
|
64
|
+
return this.cachedModelsWithInfo;
|
|
65
|
+
}
|
|
66
|
+
return [];
|
|
67
|
+
}
|
|
68
|
+
catch (error) {
|
|
69
|
+
clearTimeout(timeoutId);
|
|
70
|
+
throw error;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* 清除缓存
|
|
75
|
+
*/
|
|
76
|
+
clearCache() {
|
|
77
|
+
this.cachedModels = [];
|
|
78
|
+
this.cachedModelsWithInfo = [];
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* 刷新模型列表(仅获取最新数据)
|
|
82
|
+
*/
|
|
83
|
+
async refreshModels() {
|
|
84
|
+
const apiKey = configManager.getApiKey();
|
|
85
|
+
if (!apiKey) {
|
|
86
|
+
return { success: false, count: 0 };
|
|
87
|
+
}
|
|
88
|
+
this.clearCache();
|
|
89
|
+
try {
|
|
90
|
+
const models = await this.fetchModelsWithInfo(apiKey);
|
|
91
|
+
return { success: models.length > 0, count: models.length };
|
|
92
|
+
}
|
|
93
|
+
catch {
|
|
94
|
+
return { success: false, count: 0 };
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* 刷新并同步模型列表到 OpenCode 配置
|
|
99
|
+
* 只更新 provider.xaio.models,不改变当前选择的模型
|
|
100
|
+
*/
|
|
101
|
+
async refreshAndSyncToOpenCode() {
|
|
102
|
+
const apiKey = configManager.getApiKey();
|
|
103
|
+
if (!apiKey) {
|
|
104
|
+
return { success: false, count: 0 };
|
|
105
|
+
}
|
|
106
|
+
this.clearCache();
|
|
107
|
+
try {
|
|
108
|
+
// 获取最新模型
|
|
109
|
+
const models = await this.fetchModelsWithInfo(apiKey);
|
|
110
|
+
if (models.length === 0) {
|
|
111
|
+
return { success: false, count: 0 };
|
|
112
|
+
}
|
|
113
|
+
// 同步到 OpenCode 配置
|
|
114
|
+
openCodeTool.syncModelsToConfig(models);
|
|
115
|
+
return { success: true, count: models.length };
|
|
116
|
+
}
|
|
117
|
+
catch {
|
|
118
|
+
return { success: false, count: 0 };
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* 获取缓存的模型数量
|
|
123
|
+
*/
|
|
124
|
+
getCachedCount() {
|
|
125
|
+
return this.cachedModels.length;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
// 单例导出
|
|
129
|
+
export const modelService = ModelService.getInstance();
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { IPlugin } from '../tools/base-tool.js';
|
|
2
|
+
/**
|
|
3
|
+
* CCLine 插件实现
|
|
4
|
+
* CCLine 是 Claude Code 的增强插件
|
|
5
|
+
*/
|
|
6
|
+
export declare class CCLinePlugin implements IPlugin {
|
|
7
|
+
readonly name = "ccline";
|
|
8
|
+
readonly displayName = "CCLine";
|
|
9
|
+
private settingsPath;
|
|
10
|
+
constructor();
|
|
11
|
+
/**
|
|
12
|
+
* 检查 CCLine 是否已安装
|
|
13
|
+
*/
|
|
14
|
+
isInstalled(): boolean;
|
|
15
|
+
/**
|
|
16
|
+
* 安装 CCLine
|
|
17
|
+
*/
|
|
18
|
+
install(): Promise<void>;
|
|
19
|
+
/**
|
|
20
|
+
* 装载 CCLine(添加到 Claude Code 配置)
|
|
21
|
+
*/
|
|
22
|
+
load(): void;
|
|
23
|
+
/**
|
|
24
|
+
* 取消装载 CCLine(从配置中移除)
|
|
25
|
+
*/
|
|
26
|
+
unload(): void;
|
|
27
|
+
/**
|
|
28
|
+
* 检查 CCLine 是否已装载到配置
|
|
29
|
+
*/
|
|
30
|
+
isLoaded(): boolean;
|
|
31
|
+
/**
|
|
32
|
+
* 读取 Claude Code settings.json
|
|
33
|
+
*/
|
|
34
|
+
private getSettings;
|
|
35
|
+
/**
|
|
36
|
+
* 保存 Claude Code settings.json
|
|
37
|
+
*/
|
|
38
|
+
private saveSettings;
|
|
39
|
+
}
|
|
40
|
+
export declare const cclinePlugin: CCLinePlugin;
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { execSync, spawn } from 'node:child_process';
|
|
2
|
+
import { existsSync, readFileSync, writeFileSync } from 'node:fs';
|
|
3
|
+
import { homedir } from 'node:os';
|
|
4
|
+
import { join } from 'node:path';
|
|
5
|
+
/**
|
|
6
|
+
* CCLine 插件实现
|
|
7
|
+
* CCLine 是 Claude Code 的增强插件
|
|
8
|
+
*/
|
|
9
|
+
export class CCLinePlugin {
|
|
10
|
+
name = 'ccline';
|
|
11
|
+
displayName = 'CCLine';
|
|
12
|
+
settingsPath;
|
|
13
|
+
constructor() {
|
|
14
|
+
this.settingsPath = join(homedir(), '.claude', 'settings.json');
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* 检查 CCLine 是否已安装
|
|
18
|
+
*/
|
|
19
|
+
isInstalled() {
|
|
20
|
+
try {
|
|
21
|
+
// 检查 ccline 命令是否可用
|
|
22
|
+
const cmd = process.platform === 'win32' ? 'where ccline' : 'which ccline';
|
|
23
|
+
execSync(cmd, { stdio: 'pipe' });
|
|
24
|
+
return true;
|
|
25
|
+
}
|
|
26
|
+
catch {
|
|
27
|
+
// 如果命令不存在,尝试检查 npm 全局包
|
|
28
|
+
try {
|
|
29
|
+
execSync('npm list -g @cometix/ccline', { stdio: 'pipe' });
|
|
30
|
+
return true;
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
return false;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* 安装 CCLine
|
|
39
|
+
*/
|
|
40
|
+
async install() {
|
|
41
|
+
const isWindows = process.platform === 'win32';
|
|
42
|
+
const command = isWindows ? 'npm.cmd' : 'npm';
|
|
43
|
+
const args = ['install', '-g', '@cometix/ccline'];
|
|
44
|
+
return new Promise((resolve, reject) => {
|
|
45
|
+
const child = spawn(command, args, {
|
|
46
|
+
stdio: 'inherit',
|
|
47
|
+
shell: true,
|
|
48
|
+
});
|
|
49
|
+
child.on('close', (code) => {
|
|
50
|
+
if (code === 0) {
|
|
51
|
+
resolve();
|
|
52
|
+
}
|
|
53
|
+
else {
|
|
54
|
+
reject(new Error(`CCLine install failed with code ${code}`));
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
child.on('error', (error) => {
|
|
58
|
+
reject(error);
|
|
59
|
+
});
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* 装载 CCLine(添加到 Claude Code 配置)
|
|
64
|
+
*/
|
|
65
|
+
load() {
|
|
66
|
+
const settings = this.getSettings();
|
|
67
|
+
// CCLine 使用 statusLine 配置
|
|
68
|
+
settings.statusLine = {
|
|
69
|
+
type: 'command',
|
|
70
|
+
command: 'ccline',
|
|
71
|
+
padding: 0,
|
|
72
|
+
};
|
|
73
|
+
this.saveSettings(settings);
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* 取消装载 CCLine(从配置中移除)
|
|
77
|
+
*/
|
|
78
|
+
unload() {
|
|
79
|
+
const settings = this.getSettings();
|
|
80
|
+
// 检查 statusLine 是否是 ccline 配置
|
|
81
|
+
if (settings.statusLine && settings.statusLine.command === 'ccline') {
|
|
82
|
+
delete settings.statusLine;
|
|
83
|
+
this.saveSettings(settings);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* 检查 CCLine 是否已装载到配置
|
|
88
|
+
*/
|
|
89
|
+
isLoaded() {
|
|
90
|
+
const settings = this.getSettings();
|
|
91
|
+
return !!(settings.statusLine && settings.statusLine.command === 'ccline');
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* 读取 Claude Code settings.json
|
|
95
|
+
*/
|
|
96
|
+
getSettings() {
|
|
97
|
+
try {
|
|
98
|
+
if (existsSync(this.settingsPath)) {
|
|
99
|
+
const content = readFileSync(this.settingsPath, 'utf-8');
|
|
100
|
+
return JSON.parse(content);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
catch {
|
|
104
|
+
// ignore
|
|
105
|
+
}
|
|
106
|
+
return {};
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* 保存 Claude Code settings.json
|
|
110
|
+
*/
|
|
111
|
+
saveSettings(settings) {
|
|
112
|
+
writeFileSync(this.settingsPath, JSON.stringify(settings, null, 2), 'utf-8');
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
// 单例导出
|
|
116
|
+
export const cclinePlugin = new CCLinePlugin();
|