coding-tool-x 3.2.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/CHANGELOG.md +599 -0
- package/LICENSE +21 -0
- package/README.md +439 -0
- package/bin/ctx.js +8 -0
- package/dist/web/assets/Analytics-DN_YsnkW.js +39 -0
- package/dist/web/assets/Analytics-DuYvId7u.css +1 -0
- package/dist/web/assets/ConfigTemplates-Bidwfdf2.css +1 -0
- package/dist/web/assets/ConfigTemplates-DpXIMy0p.js +1 -0
- package/dist/web/assets/Home-38JTUlYt.js +1 -0
- package/dist/web/assets/Home-CjupSEWE.css +1 -0
- package/dist/web/assets/PluginManager-CX2tgq2H.js +1 -0
- package/dist/web/assets/PluginManager-ROyoZ-6m.css +1 -0
- package/dist/web/assets/ProjectList-C1lDcsn6.js +1 -0
- package/dist/web/assets/ProjectList-oJIyIRkP.css +1 -0
- package/dist/web/assets/SessionList-C55tjV7i.css +1 -0
- package/dist/web/assets/SessionList-CZ7T6rVx.js +1 -0
- package/dist/web/assets/SkillManager-D7pd-d_P.css +1 -0
- package/dist/web/assets/SkillManager-DLN9f79y.js +1 -0
- package/dist/web/assets/WorkspaceManager-CrwgQgmP.css +1 -0
- package/dist/web/assets/WorkspaceManager-DxlHZkpZ.js +1 -0
- package/dist/web/assets/icons-DRrXwWZi.js +1 -0
- package/dist/web/assets/index-CetESrXw.css +1 -0
- package/dist/web/assets/index-Cfvn-2Gb.js +2 -0
- package/dist/web/assets/markdown-BfC0goYb.css +10 -0
- package/dist/web/assets/markdown-C9MYpaSi.js +1 -0
- package/dist/web/assets/naive-ui-DlpKk-8M.js +1 -0
- package/dist/web/assets/vendors-DMjSfzlv.js +7 -0
- package/dist/web/assets/vue-vendor-DET08QYg.js +45 -0
- package/dist/web/favicon.ico +0 -0
- package/dist/web/index.html +20 -0
- package/dist/web/logo.png +0 -0
- package/docs/bannel.png +0 -0
- package/docs/home.png +0 -0
- package/docs/logo.png +0 -0
- package/docs/model-redirection.md +251 -0
- package/docs/multi-channel-load-balancing.md +249 -0
- package/package.json +80 -0
- package/src/commands/channels.js +551 -0
- package/src/commands/cli-type.js +101 -0
- package/src/commands/daemon.js +365 -0
- package/src/commands/doctor.js +333 -0
- package/src/commands/export-config.js +205 -0
- package/src/commands/list.js +222 -0
- package/src/commands/logs.js +261 -0
- package/src/commands/plugin.js +585 -0
- package/src/commands/port-config.js +135 -0
- package/src/commands/proxy-control.js +264 -0
- package/src/commands/proxy.js +152 -0
- package/src/commands/resume.js +137 -0
- package/src/commands/search.js +190 -0
- package/src/commands/security.js +37 -0
- package/src/commands/stats.js +398 -0
- package/src/commands/switch.js +48 -0
- package/src/commands/toggle-proxy.js +247 -0
- package/src/commands/ui.js +99 -0
- package/src/commands/update.js +97 -0
- package/src/commands/workspace.js +454 -0
- package/src/config/default.js +69 -0
- package/src/config/loader.js +149 -0
- package/src/config/model-metadata.js +167 -0
- package/src/config/model-metadata.json +125 -0
- package/src/config/model-pricing.js +35 -0
- package/src/config/paths.js +190 -0
- package/src/index.js +680 -0
- package/src/plugins/constants.js +15 -0
- package/src/plugins/event-bus.js +54 -0
- package/src/plugins/manifest-validator.js +129 -0
- package/src/plugins/plugin-api.js +128 -0
- package/src/plugins/plugin-installer.js +601 -0
- package/src/plugins/plugin-loader.js +229 -0
- package/src/plugins/plugin-manager.js +170 -0
- package/src/plugins/registry.js +152 -0
- package/src/plugins/schema/plugin-manifest.json +115 -0
- package/src/reset-config.js +94 -0
- package/src/server/api/agents.js +826 -0
- package/src/server/api/aliases.js +36 -0
- package/src/server/api/channels.js +368 -0
- package/src/server/api/claude-hooks.js +480 -0
- package/src/server/api/codex-channels.js +417 -0
- package/src/server/api/codex-projects.js +104 -0
- package/src/server/api/codex-proxy.js +195 -0
- package/src/server/api/codex-sessions.js +483 -0
- package/src/server/api/codex-statistics.js +57 -0
- package/src/server/api/commands.js +482 -0
- package/src/server/api/config-export.js +212 -0
- package/src/server/api/config-registry.js +357 -0
- package/src/server/api/config-sync.js +155 -0
- package/src/server/api/config-templates.js +248 -0
- package/src/server/api/config.js +521 -0
- package/src/server/api/convert.js +260 -0
- package/src/server/api/dashboard.js +142 -0
- package/src/server/api/env.js +144 -0
- package/src/server/api/favorites.js +77 -0
- package/src/server/api/gemini-channels.js +366 -0
- package/src/server/api/gemini-projects.js +91 -0
- package/src/server/api/gemini-proxy.js +173 -0
- package/src/server/api/gemini-sessions.js +376 -0
- package/src/server/api/gemini-statistics.js +57 -0
- package/src/server/api/health-check.js +31 -0
- package/src/server/api/mcp.js +399 -0
- package/src/server/api/opencode-channels.js +419 -0
- package/src/server/api/opencode-projects.js +99 -0
- package/src/server/api/opencode-proxy.js +207 -0
- package/src/server/api/opencode-sessions.js +327 -0
- package/src/server/api/opencode-statistics.js +57 -0
- package/src/server/api/plugins.js +463 -0
- package/src/server/api/pm2-autostart.js +269 -0
- package/src/server/api/projects.js +124 -0
- package/src/server/api/prompts.js +279 -0
- package/src/server/api/proxy.js +306 -0
- package/src/server/api/security.js +53 -0
- package/src/server/api/sessions.js +514 -0
- package/src/server/api/settings.js +142 -0
- package/src/server/api/skills.js +570 -0
- package/src/server/api/statistics.js +238 -0
- package/src/server/api/ui-config.js +64 -0
- package/src/server/api/workspaces.js +456 -0
- package/src/server/codex-proxy-server.js +681 -0
- package/src/server/dev-server.js +26 -0
- package/src/server/gemini-proxy-server.js +610 -0
- package/src/server/index.js +422 -0
- package/src/server/opencode-proxy-server.js +4771 -0
- package/src/server/proxy-server.js +669 -0
- package/src/server/services/agents-service.js +1137 -0
- package/src/server/services/alias.js +71 -0
- package/src/server/services/channel-health.js +234 -0
- package/src/server/services/channel-scheduler.js +240 -0
- package/src/server/services/channels.js +447 -0
- package/src/server/services/codex-channels.js +705 -0
- package/src/server/services/codex-config.js +90 -0
- package/src/server/services/codex-parser.js +322 -0
- package/src/server/services/codex-sessions.js +936 -0
- package/src/server/services/codex-settings-manager.js +619 -0
- package/src/server/services/codex-speed-test-template.json +24 -0
- package/src/server/services/codex-statistics-service.js +161 -0
- package/src/server/services/commands-service.js +574 -0
- package/src/server/services/config-export-service.js +1165 -0
- package/src/server/services/config-registry-service.js +828 -0
- package/src/server/services/config-sync-manager.js +941 -0
- package/src/server/services/config-sync-service.js +504 -0
- package/src/server/services/config-templates-service.js +913 -0
- package/src/server/services/enhanced-cache.js +196 -0
- package/src/server/services/env-checker.js +409 -0
- package/src/server/services/env-manager.js +436 -0
- package/src/server/services/favorites.js +165 -0
- package/src/server/services/format-converter.js +620 -0
- package/src/server/services/gemini-channels.js +459 -0
- package/src/server/services/gemini-config.js +73 -0
- package/src/server/services/gemini-sessions.js +689 -0
- package/src/server/services/gemini-settings-manager.js +263 -0
- package/src/server/services/gemini-statistics-service.js +157 -0
- package/src/server/services/health-check.js +85 -0
- package/src/server/services/mcp-client.js +790 -0
- package/src/server/services/mcp-service.js +1732 -0
- package/src/server/services/model-detector.js +1245 -0
- package/src/server/services/network-access.js +80 -0
- package/src/server/services/opencode-channels.js +366 -0
- package/src/server/services/opencode-gateway-adapters.js +1168 -0
- package/src/server/services/opencode-gateway-converter.js +639 -0
- package/src/server/services/opencode-sessions.js +931 -0
- package/src/server/services/opencode-settings-manager.js +478 -0
- package/src/server/services/opencode-statistics-service.js +161 -0
- package/src/server/services/plugins-service.js +1268 -0
- package/src/server/services/prompts-service.js +534 -0
- package/src/server/services/proxy-runtime.js +79 -0
- package/src/server/services/repo-scanner-base.js +708 -0
- package/src/server/services/request-logger.js +130 -0
- package/src/server/services/response-decoder.js +21 -0
- package/src/server/services/security-config.js +131 -0
- package/src/server/services/session-cache.js +127 -0
- package/src/server/services/session-converter.js +577 -0
- package/src/server/services/sessions.js +900 -0
- package/src/server/services/settings-manager.js +163 -0
- package/src/server/services/skill-service.js +1482 -0
- package/src/server/services/speed-test.js +1146 -0
- package/src/server/services/statistics-service.js +1043 -0
- package/src/server/services/ui-config.js +132 -0
- package/src/server/services/workspace-service.js +830 -0
- package/src/server/utils/pricing.js +73 -0
- package/src/server/websocket-server.js +513 -0
- package/src/ui/menu.js +139 -0
- package/src/ui/prompts.js +100 -0
- package/src/utils/format.js +43 -0
- package/src/utils/port-helper.js +108 -0
- package/src/utils/session.js +240 -0
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
const express = require('express');
|
|
2
|
+
const { exec } = require('child_process');
|
|
3
|
+
const { promisify } = require('util');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const fs = require('fs');
|
|
6
|
+
const os = require('os');
|
|
7
|
+
const pm2 = require('pm2');
|
|
8
|
+
|
|
9
|
+
const execAsync = promisify(exec);
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Check if PM2 autostart is enabled
|
|
13
|
+
* by looking for PM2 startup script in system
|
|
14
|
+
*/
|
|
15
|
+
async function checkAutoStartStatus() {
|
|
16
|
+
try {
|
|
17
|
+
const platform = process.platform;
|
|
18
|
+
|
|
19
|
+
if (platform === 'darwin') {
|
|
20
|
+
// macOS - check for LaunchDaemon
|
|
21
|
+
const launchDaemonsPath = path.join(os.homedir(), 'Library/LaunchDaemons');
|
|
22
|
+
const pm2Files = fs.existsSync(launchDaemonsPath)
|
|
23
|
+
? fs.readdirSync(launchDaemonsPath).filter(f => f.includes('pm2'))
|
|
24
|
+
: [];
|
|
25
|
+
|
|
26
|
+
return { enabled: pm2Files.length > 0, platform: 'darwin' };
|
|
27
|
+
} else if (platform === 'linux') {
|
|
28
|
+
// Linux - check for systemd service
|
|
29
|
+
const systemdPath = '/etc/systemd/system/pm2-root.service';
|
|
30
|
+
const userSystemdPath = path.join(os.homedir(), '.config/systemd/user/pm2-*.service');
|
|
31
|
+
|
|
32
|
+
const rootExists = fs.existsSync(systemdPath);
|
|
33
|
+
const userExists = fs.existsSync(path.join(os.homedir(), '.config/systemd/user')) &&
|
|
34
|
+
fs.readdirSync(path.join(os.homedir(), '.config/systemd/user')).some(f => f.includes('pm2'));
|
|
35
|
+
|
|
36
|
+
return { enabled: rootExists || userExists, platform: 'linux' };
|
|
37
|
+
} else if (platform === 'win32') {
|
|
38
|
+
// Windows - check for PM2 service in registry (simplified check)
|
|
39
|
+
// For now, assume Windows support via pm2 package manager
|
|
40
|
+
return { enabled: false, platform: 'win32', note: '暂不支持 Windows' };
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return { enabled: false, platform };
|
|
44
|
+
} catch (err) {
|
|
45
|
+
console.error('Error checking autostart status:', err);
|
|
46
|
+
return { enabled: false, error: err.message };
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Enable PM2 autostart
|
|
52
|
+
* Runs: pm2 startup && pm2 save
|
|
53
|
+
*/
|
|
54
|
+
async function enableAutoStart() {
|
|
55
|
+
return new Promise((resolve) => {
|
|
56
|
+
pm2.connect((err) => {
|
|
57
|
+
if (err) {
|
|
58
|
+
console.error('PM2 connect error:', err);
|
|
59
|
+
return resolve({
|
|
60
|
+
success: false,
|
|
61
|
+
message: '无法连接到 PM2:' + (err.message || '未知错误'),
|
|
62
|
+
error: err.message
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Get current process list
|
|
67
|
+
pm2.list((listErr, processes) => {
|
|
68
|
+
if (listErr) {
|
|
69
|
+
pm2.disconnect();
|
|
70
|
+
console.error('PM2 list error:', listErr);
|
|
71
|
+
return resolve({
|
|
72
|
+
success: false,
|
|
73
|
+
message: '无法获取 PM2 进程列表:' + (listErr.message || '未知错误')
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// If no processes are running, we can't really set up autostart
|
|
78
|
+
if (!processes || processes.length === 0) {
|
|
79
|
+
pm2.disconnect();
|
|
80
|
+
return resolve({
|
|
81
|
+
success: false,
|
|
82
|
+
message: '暂无运行中的进程,无法启用开机自启。请先启动服务:ctx start'
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Save current process list
|
|
87
|
+
pm2.save((saveErr) => {
|
|
88
|
+
if (saveErr) {
|
|
89
|
+
pm2.disconnect();
|
|
90
|
+
console.error('PM2 save error:', saveErr);
|
|
91
|
+
return resolve({
|
|
92
|
+
success: false,
|
|
93
|
+
message: '无法保存 PM2 配置:' + (saveErr.message || '未知错误')
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Run startup command
|
|
98
|
+
const platform = process.platform;
|
|
99
|
+
const command = platform === 'darwin'
|
|
100
|
+
? 'pm2 startup launchd -u $(whoami) --hp $(eval echo ~$(whoami))'
|
|
101
|
+
: platform === 'linux'
|
|
102
|
+
? 'pm2 startup systemd -u $(whoami) --hp $(eval echo ~$(whoami))'
|
|
103
|
+
: 'pm2 startup';
|
|
104
|
+
|
|
105
|
+
console.log(`Running startup command: ${command}`);
|
|
106
|
+
|
|
107
|
+
exec(command, { shell: '/bin/bash', timeout: 30000 }, (execErr, stdout, stderr) => {
|
|
108
|
+
pm2.disconnect();
|
|
109
|
+
|
|
110
|
+
if (execErr) {
|
|
111
|
+
console.error('Startup command error:', execErr);
|
|
112
|
+
console.error('stderr:', stderr);
|
|
113
|
+
|
|
114
|
+
// Check if it's already enabled
|
|
115
|
+
if (stderr && stderr.includes('already')) {
|
|
116
|
+
return resolve({
|
|
117
|
+
success: true,
|
|
118
|
+
message: '开机自启已启用(或已存在)'
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
return resolve({
|
|
123
|
+
success: false,
|
|
124
|
+
message: '启用失败。' + (stderr || execErr.message || '请确保已安装 PM2 且有足够权限'),
|
|
125
|
+
error: execErr.message
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
console.log('Startup command output:', stdout);
|
|
130
|
+
return resolve({
|
|
131
|
+
success: true,
|
|
132
|
+
message: '开机自启已启用。重启电脑后自动启动'
|
|
133
|
+
});
|
|
134
|
+
});
|
|
135
|
+
});
|
|
136
|
+
});
|
|
137
|
+
});
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Disable PM2 autostart
|
|
143
|
+
* Runs: pm2 unstartup
|
|
144
|
+
*/
|
|
145
|
+
async function disableAutoStart() {
|
|
146
|
+
return new Promise((resolve) => {
|
|
147
|
+
pm2.connect((err) => {
|
|
148
|
+
if (err) {
|
|
149
|
+
console.error('PM2 connect error:', err);
|
|
150
|
+
return resolve({
|
|
151
|
+
success: false,
|
|
152
|
+
message: '无法连接到 PM2:' + (err.message || '未知错误')
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// Run unstartup command
|
|
157
|
+
const platform = process.platform;
|
|
158
|
+
const command = platform === 'darwin'
|
|
159
|
+
? 'pm2 unstartup launchd -u $(whoami)'
|
|
160
|
+
: platform === 'linux'
|
|
161
|
+
? 'pm2 unstartup systemd -u $(whoami)'
|
|
162
|
+
: 'pm2 unstartup';
|
|
163
|
+
|
|
164
|
+
console.log(`Running unstartup command: ${command}`);
|
|
165
|
+
|
|
166
|
+
exec(command, { shell: '/bin/bash', timeout: 30000 }, (execErr, stdout, stderr) => {
|
|
167
|
+
pm2.disconnect();
|
|
168
|
+
|
|
169
|
+
if (execErr) {
|
|
170
|
+
console.error('Unstartup command error:', execErr);
|
|
171
|
+
console.error('stderr:', stderr);
|
|
172
|
+
|
|
173
|
+
// Check if it's not set up
|
|
174
|
+
if (stderr && stderr.includes('not set')) {
|
|
175
|
+
return resolve({
|
|
176
|
+
success: true,
|
|
177
|
+
message: '开机自启已禁用(或未启用)'
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
return resolve({
|
|
182
|
+
success: false,
|
|
183
|
+
message: '禁用失败。' + (stderr || execErr.message || '请确保已安装 PM2 且有足够权限'),
|
|
184
|
+
error: execErr.message
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
console.log('Unstartup command output:', stdout);
|
|
189
|
+
return resolve({
|
|
190
|
+
success: true,
|
|
191
|
+
message: '开机自启已禁用'
|
|
192
|
+
});
|
|
193
|
+
});
|
|
194
|
+
});
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
module.exports = () => {
|
|
199
|
+
const router = express.Router();
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* GET /api/pm2-autostart
|
|
203
|
+
* Get current PM2 autostart status
|
|
204
|
+
*/
|
|
205
|
+
router.get('/', async (req, res) => {
|
|
206
|
+
try {
|
|
207
|
+
const status = await checkAutoStartStatus();
|
|
208
|
+
res.json({
|
|
209
|
+
success: true,
|
|
210
|
+
data: status
|
|
211
|
+
});
|
|
212
|
+
} catch (err) {
|
|
213
|
+
console.error('Failed to check autostart status:', err);
|
|
214
|
+
// 返回 200 状态码,让前端通过 success 字段判断
|
|
215
|
+
res.json({
|
|
216
|
+
success: false,
|
|
217
|
+
message: 'Failed to check autostart status: ' + err.message
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* POST /api/pm2-autostart
|
|
224
|
+
* Enable or disable PM2 autostart
|
|
225
|
+
* Body: { action: 'enable' | 'disable' }
|
|
226
|
+
*/
|
|
227
|
+
router.post('/', async (req, res) => {
|
|
228
|
+
try {
|
|
229
|
+
const { action } = req.body;
|
|
230
|
+
|
|
231
|
+
if (!action || !['enable', 'disable'].includes(action)) {
|
|
232
|
+
return res.status(400).json({
|
|
233
|
+
success: false,
|
|
234
|
+
message: 'Invalid action. Must be "enable" or "disable"'
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
let result;
|
|
239
|
+
if (action === 'enable') {
|
|
240
|
+
result = await enableAutoStart();
|
|
241
|
+
} else {
|
|
242
|
+
result = await disableAutoStart();
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
if (result.success) {
|
|
246
|
+
res.json({
|
|
247
|
+
success: true,
|
|
248
|
+
message: result.message,
|
|
249
|
+
data: { action, enabled: action === 'enable' }
|
|
250
|
+
});
|
|
251
|
+
} else {
|
|
252
|
+
// 返回 200 状态码,让前端通过 success 字段判断
|
|
253
|
+
res.json({
|
|
254
|
+
success: false,
|
|
255
|
+
message: result.message
|
|
256
|
+
});
|
|
257
|
+
}
|
|
258
|
+
} catch (err) {
|
|
259
|
+
console.error('Failed to configure autostart:', err);
|
|
260
|
+
// 真正的服务器错误才返回 500
|
|
261
|
+
res.status(500).json({
|
|
262
|
+
success: false,
|
|
263
|
+
message: '服务器错误:' + err.message
|
|
264
|
+
});
|
|
265
|
+
}
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
return router;
|
|
269
|
+
};
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
const express = require('express');
|
|
2
|
+
const router = express.Router();
|
|
3
|
+
const { getProjectsWithStats, saveProjectOrder, getProjectOrder, deleteProject } = require('../services/sessions');
|
|
4
|
+
|
|
5
|
+
module.exports = (config) => {
|
|
6
|
+
// GET /api/projects - Get all projects with stats
|
|
7
|
+
router.get('/', async (req, res) => {
|
|
8
|
+
try {
|
|
9
|
+
const projects = await getProjectsWithStats(config);
|
|
10
|
+
const order = getProjectOrder(config);
|
|
11
|
+
|
|
12
|
+
// Sort projects by saved order
|
|
13
|
+
let sortedProjects = projects;
|
|
14
|
+
if (order && order.length > 0) {
|
|
15
|
+
const orderMap = new Map(order.map((name, idx) => [name, idx]));
|
|
16
|
+
sortedProjects = [...projects].sort((a, b) => {
|
|
17
|
+
const aIdx = orderMap.has(a.name) ? orderMap.get(a.name) : 999999;
|
|
18
|
+
const bIdx = orderMap.has(b.name) ? orderMap.get(b.name) : 999999;
|
|
19
|
+
if (aIdx === bIdx) {
|
|
20
|
+
// Both are new, sort by lastUsed
|
|
21
|
+
return (b.lastUsed || 0) - (a.lastUsed || 0);
|
|
22
|
+
}
|
|
23
|
+
return aIdx - bIdx;
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
res.json({
|
|
28
|
+
projects: sortedProjects,
|
|
29
|
+
currentProject: config.currentProject || (sortedProjects[0] ? sortedProjects[0].name : null)
|
|
30
|
+
});
|
|
31
|
+
} catch (error) {
|
|
32
|
+
console.error('Error fetching projects:', error);
|
|
33
|
+
res.status(500).json({ error: error.message });
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
// POST /api/projects/order - Save project order
|
|
38
|
+
router.post('/order', (req, res) => {
|
|
39
|
+
try {
|
|
40
|
+
const { order } = req.body;
|
|
41
|
+
if (!Array.isArray(order)) {
|
|
42
|
+
return res.status(400).json({ error: 'Order must be an array' });
|
|
43
|
+
}
|
|
44
|
+
saveProjectOrder(config, order);
|
|
45
|
+
res.json({ success: true });
|
|
46
|
+
} catch (error) {
|
|
47
|
+
console.error('Error saving project order:', error);
|
|
48
|
+
res.status(500).json({ error: error.message });
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
// POST /api/projects/create - Create a new project
|
|
53
|
+
router.post('/create', (req, res) => {
|
|
54
|
+
try {
|
|
55
|
+
const { projectName, projectPath } = req.body;
|
|
56
|
+
const path = require('path');
|
|
57
|
+
const fs = require('fs');
|
|
58
|
+
|
|
59
|
+
if (!projectName || !projectPath) {
|
|
60
|
+
return res.status(400).json({ error: 'projectName and projectPath are required' });
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// 验证项目路径存在
|
|
64
|
+
if (!fs.existsSync(projectPath)) {
|
|
65
|
+
return res.status(400).json({ error: '项目路径不存在' });
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// 验证是否为目录
|
|
69
|
+
const stats = fs.statSync(projectPath);
|
|
70
|
+
if (!stats.isDirectory()) {
|
|
71
|
+
return res.status(400).json({ error: '项目路径必须是一个目录' });
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// 在项目路径下创建 .claude 目录(与 Claude Code 默认行为一致)
|
|
75
|
+
const claudeDir = path.join(projectPath, '.claude');
|
|
76
|
+
const sessionsDir = path.join(claudeDir, 'sessions');
|
|
77
|
+
|
|
78
|
+
// 检查是否已经初始化过
|
|
79
|
+
if (fs.existsSync(claudeDir)) {
|
|
80
|
+
return res.status(400).json({ error: '该项目已经初始化过 Claude Code' });
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// 创建目录结构
|
|
84
|
+
fs.mkdirSync(sessionsDir, { recursive: true });
|
|
85
|
+
|
|
86
|
+
// 创建 .claude/project.json 配置文件(可选,记录元数据)
|
|
87
|
+
const projectConfig = {
|
|
88
|
+
name: projectName,
|
|
89
|
+
createdAt: new Date().toISOString(),
|
|
90
|
+
version: '1.0'
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
fs.writeFileSync(
|
|
94
|
+
path.join(claudeDir, 'project.json'),
|
|
95
|
+
JSON.stringify(projectConfig, null, 2),
|
|
96
|
+
'utf8'
|
|
97
|
+
);
|
|
98
|
+
|
|
99
|
+
res.json({
|
|
100
|
+
success: true,
|
|
101
|
+
projectName,
|
|
102
|
+
projectPath,
|
|
103
|
+
claudeDir
|
|
104
|
+
});
|
|
105
|
+
} catch (error) {
|
|
106
|
+
console.error('Error creating project:', error);
|
|
107
|
+
res.status(500).json({ error: error.message });
|
|
108
|
+
}
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
// DELETE /api/projects/:projectName - Delete a project
|
|
112
|
+
router.delete('/:projectName', (req, res) => {
|
|
113
|
+
try {
|
|
114
|
+
const { projectName } = req.params;
|
|
115
|
+
deleteProject(config, projectName);
|
|
116
|
+
res.json({ success: true });
|
|
117
|
+
} catch (error) {
|
|
118
|
+
console.error('Error deleting project:', error);
|
|
119
|
+
res.status(500).json({ error: error.message });
|
|
120
|
+
}
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
return router;
|
|
124
|
+
};
|
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Prompts 管理 API 路由
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
const express = require('express');
|
|
6
|
+
const router = express.Router();
|
|
7
|
+
const promptsService = require('../services/prompts-service');
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* GET /api/prompts/presets
|
|
11
|
+
* 获取所有预设
|
|
12
|
+
*/
|
|
13
|
+
router.get('/presets', (req, res) => {
|
|
14
|
+
try {
|
|
15
|
+
const result = promptsService.getAllPresets();
|
|
16
|
+
res.json({
|
|
17
|
+
success: true,
|
|
18
|
+
...result
|
|
19
|
+
});
|
|
20
|
+
} catch (error) {
|
|
21
|
+
console.error('[Prompts API] Get presets failed:', error);
|
|
22
|
+
res.status(500).json({
|
|
23
|
+
success: false,
|
|
24
|
+
error: error.message
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* GET /api/prompts/presets/active
|
|
31
|
+
* 获取当前激活的预设
|
|
32
|
+
*/
|
|
33
|
+
router.get('/presets/active', (req, res) => {
|
|
34
|
+
try {
|
|
35
|
+
const result = promptsService.getActivePreset();
|
|
36
|
+
res.json({
|
|
37
|
+
success: true,
|
|
38
|
+
...result
|
|
39
|
+
});
|
|
40
|
+
} catch (error) {
|
|
41
|
+
console.error('[Prompts API] Get active preset failed:', error);
|
|
42
|
+
res.status(500).json({
|
|
43
|
+
success: false,
|
|
44
|
+
error: error.message
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* GET /api/prompts/presets/:id
|
|
51
|
+
* 获取单个预设
|
|
52
|
+
*/
|
|
53
|
+
router.get('/presets/:id', (req, res) => {
|
|
54
|
+
try {
|
|
55
|
+
const preset = promptsService.getPreset(req.params.id);
|
|
56
|
+
if (!preset) {
|
|
57
|
+
return res.status(404).json({
|
|
58
|
+
success: false,
|
|
59
|
+
error: `预设 "${req.params.id}" 不存在`
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
res.json({
|
|
63
|
+
success: true,
|
|
64
|
+
preset
|
|
65
|
+
});
|
|
66
|
+
} catch (error) {
|
|
67
|
+
console.error('[Prompts API] Get preset failed:', error);
|
|
68
|
+
res.status(500).json({
|
|
69
|
+
success: false,
|
|
70
|
+
error: error.message
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* POST /api/prompts/presets
|
|
77
|
+
* 添加或更新预设
|
|
78
|
+
*/
|
|
79
|
+
router.post('/presets', (req, res) => {
|
|
80
|
+
try {
|
|
81
|
+
const preset = req.body;
|
|
82
|
+
|
|
83
|
+
if (!preset.id) {
|
|
84
|
+
return res.status(400).json({
|
|
85
|
+
success: false,
|
|
86
|
+
error: '预设 ID 不能为空'
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
if (!preset.name) {
|
|
91
|
+
return res.status(400).json({
|
|
92
|
+
success: false,
|
|
93
|
+
error: '预设名称不能为空'
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const result = promptsService.savePreset(preset);
|
|
98
|
+
res.json({
|
|
99
|
+
success: true,
|
|
100
|
+
preset: result
|
|
101
|
+
});
|
|
102
|
+
} catch (error) {
|
|
103
|
+
console.error('[Prompts API] Save preset failed:', error);
|
|
104
|
+
res.status(400).json({
|
|
105
|
+
success: false,
|
|
106
|
+
error: error.message
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* DELETE /api/prompts/presets/:id
|
|
113
|
+
* 删除预设
|
|
114
|
+
*/
|
|
115
|
+
router.delete('/presets/:id', (req, res) => {
|
|
116
|
+
try {
|
|
117
|
+
const deleted = promptsService.deletePreset(req.params.id);
|
|
118
|
+
if (!deleted) {
|
|
119
|
+
return res.status(404).json({
|
|
120
|
+
success: false,
|
|
121
|
+
error: `预设 "${req.params.id}" 不存在`
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
res.json({
|
|
125
|
+
success: true
|
|
126
|
+
});
|
|
127
|
+
} catch (error) {
|
|
128
|
+
console.error('[Prompts API] Delete preset failed:', error);
|
|
129
|
+
res.status(400).json({
|
|
130
|
+
success: false,
|
|
131
|
+
error: error.message
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* POST /api/prompts/presets/:id/activate
|
|
138
|
+
* 激活预设
|
|
139
|
+
*/
|
|
140
|
+
router.post('/presets/:id/activate', async (req, res) => {
|
|
141
|
+
try {
|
|
142
|
+
const preset = await promptsService.activatePreset(req.params.id);
|
|
143
|
+
res.json({
|
|
144
|
+
success: true,
|
|
145
|
+
preset,
|
|
146
|
+
message: `已激活预设 "${preset.name}"`
|
|
147
|
+
});
|
|
148
|
+
} catch (error) {
|
|
149
|
+
console.error('[Prompts API] Activate preset failed:', error);
|
|
150
|
+
res.status(400).json({
|
|
151
|
+
success: false,
|
|
152
|
+
error: error.message
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* POST /api/prompts/deactivate
|
|
159
|
+
* 停用/移除提示词(删除各平台文件)
|
|
160
|
+
*/
|
|
161
|
+
router.post('/deactivate', async (req, res) => {
|
|
162
|
+
try {
|
|
163
|
+
const result = await promptsService.deactivatePrompt();
|
|
164
|
+
res.json({
|
|
165
|
+
success: true,
|
|
166
|
+
result,
|
|
167
|
+
message: '已移除所有平台的提示词'
|
|
168
|
+
});
|
|
169
|
+
} catch (error) {
|
|
170
|
+
console.error('[Prompts API] Deactivate prompt failed:', error);
|
|
171
|
+
res.status(400).json({
|
|
172
|
+
success: false,
|
|
173
|
+
error: error.message
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* GET /api/prompts/platform-status
|
|
180
|
+
* 获取各平台提示词状态
|
|
181
|
+
*/
|
|
182
|
+
router.get('/platform-status', (req, res) => {
|
|
183
|
+
try {
|
|
184
|
+
const status = promptsService.getPlatformStatus();
|
|
185
|
+
res.json({
|
|
186
|
+
success: true,
|
|
187
|
+
status
|
|
188
|
+
});
|
|
189
|
+
} catch (error) {
|
|
190
|
+
console.error('[Prompts API] Get platform status failed:', error);
|
|
191
|
+
res.status(500).json({
|
|
192
|
+
success: false,
|
|
193
|
+
error: error.message
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* GET /api/prompts/platform/:platform
|
|
200
|
+
* 读取指定平台的提示词
|
|
201
|
+
*/
|
|
202
|
+
router.get('/platform/:platform', (req, res) => {
|
|
203
|
+
try {
|
|
204
|
+
const { platform } = req.params;
|
|
205
|
+
|
|
206
|
+
if (!['claude', 'codex', 'gemini', 'opencode'].includes(platform)) {
|
|
207
|
+
return res.status(400).json({
|
|
208
|
+
success: false,
|
|
209
|
+
error: `无效的平台: ${platform}`
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
const content = promptsService.readPlatformPrompt(platform);
|
|
214
|
+
res.json({
|
|
215
|
+
success: true,
|
|
216
|
+
platform,
|
|
217
|
+
content
|
|
218
|
+
});
|
|
219
|
+
} catch (error) {
|
|
220
|
+
console.error('[Prompts API] Read platform prompt failed:', error);
|
|
221
|
+
res.status(500).json({
|
|
222
|
+
success: false,
|
|
223
|
+
error: error.message
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* POST /api/prompts/import/:platform
|
|
230
|
+
* 从指定平台导入提示词
|
|
231
|
+
*/
|
|
232
|
+
router.post('/import/:platform', (req, res) => {
|
|
233
|
+
try {
|
|
234
|
+
const { platform } = req.params;
|
|
235
|
+
const { name } = req.body;
|
|
236
|
+
|
|
237
|
+
if (!['claude', 'codex', 'gemini', 'opencode'].includes(platform)) {
|
|
238
|
+
return res.status(400).json({
|
|
239
|
+
success: false,
|
|
240
|
+
error: `无效的平台: ${platform}`
|
|
241
|
+
});
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
const preset = promptsService.importFromPlatform(platform, name);
|
|
245
|
+
res.json({
|
|
246
|
+
success: true,
|
|
247
|
+
preset,
|
|
248
|
+
message: `成功从 ${platform} 导入提示词`
|
|
249
|
+
});
|
|
250
|
+
} catch (error) {
|
|
251
|
+
console.error('[Prompts API] Import failed:', error);
|
|
252
|
+
res.status(400).json({
|
|
253
|
+
success: false,
|
|
254
|
+
error: error.message
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* GET /api/prompts/stats
|
|
261
|
+
* 获取统计信息
|
|
262
|
+
*/
|
|
263
|
+
router.get('/stats', (req, res) => {
|
|
264
|
+
try {
|
|
265
|
+
const stats = promptsService.getStats();
|
|
266
|
+
res.json({
|
|
267
|
+
success: true,
|
|
268
|
+
stats
|
|
269
|
+
});
|
|
270
|
+
} catch (error) {
|
|
271
|
+
console.error('[Prompts API] Get stats failed:', error);
|
|
272
|
+
res.status(500).json({
|
|
273
|
+
success: false,
|
|
274
|
+
error: error.message
|
|
275
|
+
});
|
|
276
|
+
}
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
module.exports = router;
|