coding-tool-x 3.7.6 → 3.7.7
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/dist/web/assets/{Analytics-wCRLv26m.js → Analytics-B6FzPvdJ.js} +1 -1
- package/dist/web/assets/{ConfigTemplates-aa1blEHh.js → ConfigTemplates-DXVnqR5c.js} +1 -1
- package/dist/web/assets/{Home-eIxO50-k.js → Home-FHJB7hYI.js} +1 -1
- package/dist/web/assets/{PluginManager-BAwGFNjN.js → PluginManager-D8RToURX.js} +1 -1
- package/dist/web/assets/ProjectList-Cdz4jnOg.js +1 -0
- package/dist/web/assets/ProjectList-VeMNXfgO.css +1 -0
- package/dist/web/assets/SessionList-C4chCPxK.css +1 -0
- package/dist/web/assets/SessionList-XYydFlQg.js +1 -0
- package/dist/web/assets/{SkillManager-Bhzs1O5S.js → SkillManager-Bu9p7mMb.js} +1 -1
- package/dist/web/assets/{WorkspaceManager-CLdX-pTT.js → WorkspaceManager-Bx0Gj3Mk.js} +1 -1
- package/dist/web/assets/index-DxkAvn4g.js +2 -0
- package/dist/web/assets/index-S8wrU2Nf.css +1 -0
- package/dist/web/index.html +2 -2
- package/package.json +1 -1
- package/src/commands/daemon.js +3 -1
- package/src/commands/port-config.js +0 -2
- package/src/commands/switch.js +1 -5
- package/src/commands/ui.js +4 -3
- package/src/commands/workspace.js +35 -3
- package/src/config/loader.js +76 -5
- package/src/index.js +11 -0
- package/src/server/api/config.js +0 -3
- package/src/server/api/settings.js +0 -2
- package/src/server/api/workspaces.js +39 -1
- package/src/server/services/dashboard-snapshot-worker.js +1 -0
- package/src/server/services/https-cert.js +3 -2
- package/src/server/services/omp-auth-providers.js +1 -0
- package/src/server/services/session-snapshots.js +1 -0
- package/src/server/services/sessions.js +35 -19
- package/src/server/services/web-build.js +2 -1
- package/src/server/services/workspace-service.js +204 -98
- package/src/utils/cli-flags.js +7 -0
- package/src/utils/session.js +10 -4
- package/dist/web/assets/ProjectList-CRiAEixW.js +0 -1
- package/dist/web/assets/ProjectList-Dw2omgOH.css +0 -1
- package/dist/web/assets/SessionList-BQ0FW6Gd.js +0 -1
- package/dist/web/assets/SessionList-kYJ6-Ies.css +0 -1
- package/dist/web/assets/index-B_nFqixU.css +0 -1
- package/dist/web/assets/index-ClMNA9Hf.js +0 -2
package/src/config/loader.js
CHANGED
|
@@ -3,7 +3,7 @@ const fs = require('fs');
|
|
|
3
3
|
const path = require('path');
|
|
4
4
|
const os = require('os');
|
|
5
5
|
const DEFAULT_CONFIG = require('./default');
|
|
6
|
-
const { PATHS, ensureStorageDirMigrated } = require('./paths');
|
|
6
|
+
const { PATHS, NATIVE_PATHS, ensureStorageDirMigrated } = require('./paths');
|
|
7
7
|
const { resolvePreferredHomeDir } = require('../utils/home-dir');
|
|
8
8
|
const eventBus = require('../plugins/event-bus');
|
|
9
9
|
|
|
@@ -23,12 +23,79 @@ function getConfigFilePath() {
|
|
|
23
23
|
* 展开 ~ 为用户主目录
|
|
24
24
|
*/
|
|
25
25
|
function expandHome(filepath) {
|
|
26
|
+
if (typeof filepath !== 'string') {
|
|
27
|
+
return filepath;
|
|
28
|
+
}
|
|
26
29
|
if (filepath.startsWith('~')) {
|
|
27
30
|
return path.join(HOME_DIR, filepath.slice(1));
|
|
28
31
|
}
|
|
29
32
|
return filepath;
|
|
30
33
|
}
|
|
31
34
|
|
|
35
|
+
function collapseHome(filepath) {
|
|
36
|
+
if (typeof filepath !== 'string') {
|
|
37
|
+
return filepath;
|
|
38
|
+
}
|
|
39
|
+
if (filepath === HOME_DIR) {
|
|
40
|
+
return '~';
|
|
41
|
+
}
|
|
42
|
+
const prefix = `${HOME_DIR}${path.sep}`;
|
|
43
|
+
if (filepath.startsWith(prefix)) {
|
|
44
|
+
return path.join('~', filepath.slice(prefix.length));
|
|
45
|
+
}
|
|
46
|
+
return filepath;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function getNativeClaudeProjectsDir() {
|
|
50
|
+
return NATIVE_PATHS?.claude?.projects || path.join(HOME_DIR, '.claude', 'projects');
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function hasExplicitProjectsDir(config = {}) {
|
|
54
|
+
return Object.prototype.hasOwnProperty.call(config, 'projectsDir') &&
|
|
55
|
+
typeof config.projectsDir === 'string' &&
|
|
56
|
+
config.projectsDir.trim() !== '';
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function resolveClaudeProjectsDir(config = {}) {
|
|
60
|
+
if (!hasExplicitProjectsDir(config) || isDefaultProjectsDir(config.projectsDir)) {
|
|
61
|
+
return getNativeClaudeProjectsDir();
|
|
62
|
+
}
|
|
63
|
+
return expandHome(config.projectsDir);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function normalizeComparablePath(filepath) {
|
|
67
|
+
if (typeof filepath !== 'string' || !filepath.trim()) {
|
|
68
|
+
return '';
|
|
69
|
+
}
|
|
70
|
+
return path.normalize(expandHome(filepath.trim()));
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function isDefaultProjectsDir(projectsDir) {
|
|
74
|
+
const normalized = normalizeComparablePath(projectsDir);
|
|
75
|
+
if (!normalized) {
|
|
76
|
+
return true;
|
|
77
|
+
}
|
|
78
|
+
const nativeDefault = normalizeComparablePath(getNativeClaudeProjectsDir());
|
|
79
|
+
const legacyHomeDefault = normalizeComparablePath(path.join(HOME_DIR, '.claude', 'projects'));
|
|
80
|
+
if (normalized === nativeDefault || normalized === legacyHomeDefault) {
|
|
81
|
+
return true;
|
|
82
|
+
}
|
|
83
|
+
const normalizedParts = normalized.replace(/\\/g, '/').split('/').filter(Boolean);
|
|
84
|
+
const last = normalizedParts[normalizedParts.length - 1];
|
|
85
|
+
const previous = normalizedParts[normalizedParts.length - 2];
|
|
86
|
+
return previous === '.claude' && last === 'projects' && !fs.existsSync(normalized);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function normalizeConfigForSave(config = {}) {
|
|
90
|
+
const normalized = { ...config };
|
|
91
|
+
if (isDefaultProjectsDir(normalized.projectsDir)) {
|
|
92
|
+
delete normalized.projectsDir;
|
|
93
|
+
} else if (typeof normalized.projectsDir === 'string') {
|
|
94
|
+
normalized.projectsDir = collapseHome(normalized.projectsDir);
|
|
95
|
+
}
|
|
96
|
+
return normalized;
|
|
97
|
+
}
|
|
98
|
+
|
|
32
99
|
function mergePricing(defaultPricing, overrides = {}) {
|
|
33
100
|
const merged = {};
|
|
34
101
|
Object.keys(defaultPricing).forEach((key) => {
|
|
@@ -96,7 +163,7 @@ function loadConfig() {
|
|
|
96
163
|
if (fs.existsSync(configFilePath)) {
|
|
97
164
|
const userConfig = readJsonFile(configFilePath);
|
|
98
165
|
const config = { ...DEFAULT_CONFIG, ...userConfig };
|
|
99
|
-
config.projectsDir =
|
|
166
|
+
config.projectsDir = resolveClaudeProjectsDir(userConfig);
|
|
100
167
|
|
|
101
168
|
// 合并 ports 配置
|
|
102
169
|
config.ports = { ...DEFAULT_CONFIG.ports, ...userConfig.ports };
|
|
@@ -120,7 +187,7 @@ function loadConfig() {
|
|
|
120
187
|
}
|
|
121
188
|
const defaultConfig = {
|
|
122
189
|
...DEFAULT_CONFIG,
|
|
123
|
-
projectsDir:
|
|
190
|
+
projectsDir: resolveClaudeProjectsDir(),
|
|
124
191
|
currentProject: DEFAULT_CONFIG.defaultProject
|
|
125
192
|
};
|
|
126
193
|
eventBus.emitSync('config:loaded', { config: defaultConfig });
|
|
@@ -137,8 +204,9 @@ function saveConfig(config) {
|
|
|
137
204
|
if (!fs.existsSync(dir)) {
|
|
138
205
|
fs.mkdirSync(dir, { recursive: true });
|
|
139
206
|
}
|
|
140
|
-
|
|
141
|
-
|
|
207
|
+
const normalizedConfig = normalizeConfigForSave(config);
|
|
208
|
+
fs.writeFileSync(configFilePath, JSON.stringify(normalizedConfig, null, 2), 'utf8');
|
|
209
|
+
eventBus.emitSync('config:saved', { config: normalizedConfig });
|
|
142
210
|
} catch (error) {
|
|
143
211
|
console.error(`保存配置失败 (${configFilePath}):`, error.message);
|
|
144
212
|
}
|
|
@@ -148,5 +216,8 @@ module.exports = {
|
|
|
148
216
|
loadConfig,
|
|
149
217
|
saveConfig,
|
|
150
218
|
expandHome,
|
|
219
|
+
resolveClaudeProjectsDir,
|
|
220
|
+
normalizeConfigForSave,
|
|
221
|
+
isDefaultProjectsDir,
|
|
151
222
|
getConfigFilePath
|
|
152
223
|
};
|
package/src/index.js
CHANGED
|
@@ -17,6 +17,7 @@ const { ensureStorageDirMigrated } = require('./config/paths');
|
|
|
17
17
|
const { normalizePlatformKey } = require('./shared/platforms');
|
|
18
18
|
const PluginManager = require('./plugins/plugin-manager');
|
|
19
19
|
const eventBus = require('./plugins/event-bus');
|
|
20
|
+
const { hasHostFlag } = require('./utils/cli-flags');
|
|
20
21
|
const chalk = require('chalk');
|
|
21
22
|
const path = require('path');
|
|
22
23
|
const fs = require('fs');
|
|
@@ -54,9 +55,12 @@ function showHelp() {
|
|
|
54
55
|
console.log(' ctx ui 前台启动 Web UI(仅本地访问)');
|
|
55
56
|
console.log(' ctx ui --https 前台启动 Web UI(本地 HTTPS)');
|
|
56
57
|
console.log(' ctx ui --host 前台启动 Web UI(允许 LAN 访问,默认允许远程写操作)');
|
|
58
|
+
console.log(' ctx ui --hosts 前台启动 Web UI(同 --host)');
|
|
59
|
+
console.log(' ctx --hosts 前台启动 Web UI(同 ctx ui --host)');
|
|
57
60
|
console.log(' ctx ui start 后台启动 Web UI');
|
|
58
61
|
console.log(' ctx ui start --https 后台启动 Web UI(本地 HTTPS)');
|
|
59
62
|
console.log(' ctx ui start --host 后台启动 Web UI(允许 LAN 访问,默认允许远程写操作)');
|
|
63
|
+
console.log(' ctx ui start --hosts 后台启动 Web UI(同 --host)');
|
|
60
64
|
console.log(' ctx ui stop 停止 Web UI');
|
|
61
65
|
console.log(' ctx ui restart 重启 Web UI\n');
|
|
62
66
|
console.log(chalk.gray(' 提示: 如需禁止 LAN 远程写操作,可设置 CC_TOOL_ALLOW_REMOTE_WRITE=false\n'));
|
|
@@ -149,6 +153,13 @@ async function main() {
|
|
|
149
153
|
// 处理命令行参数
|
|
150
154
|
const args = process.argv.slice(2);
|
|
151
155
|
|
|
156
|
+
// LAN 快捷入口,等同于 ctx ui --host/--hosts
|
|
157
|
+
if (hasHostFlag(process.argv) && !args.some((arg) => arg && !arg.startsWith('-'))) {
|
|
158
|
+
const { handleUI } = require('./commands/ui');
|
|
159
|
+
await handleUI();
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
|
|
152
163
|
// --version 或 -v - 显示版本号
|
|
153
164
|
if (args[0] === '--version' || args[0] === '-v') {
|
|
154
165
|
console.log(getVersion());
|
package/src/server/api/config.js
CHANGED
|
@@ -330,7 +330,6 @@ router.post('/default-models', (req, res) => {
|
|
|
330
330
|
// Save config
|
|
331
331
|
const newConfig = {
|
|
332
332
|
...config,
|
|
333
|
-
projectsDir: config.projectsDir.replace(require('os').homedir(), '~'),
|
|
334
333
|
defaultModels: newDefaultModels
|
|
335
334
|
};
|
|
336
335
|
|
|
@@ -378,7 +377,6 @@ router.post('/default-models/reset', (req, res) => {
|
|
|
378
377
|
// Save config
|
|
379
378
|
const newConfig = {
|
|
380
379
|
...config,
|
|
381
|
-
projectsDir: config.projectsDir.replace(require('os').homedir(), '~'),
|
|
382
380
|
defaultModels: newDefaultModels
|
|
383
381
|
};
|
|
384
382
|
|
|
@@ -490,7 +488,6 @@ router.post('/advanced', (req, res) => {
|
|
|
490
488
|
// 更新配置
|
|
491
489
|
const newConfig = {
|
|
492
490
|
...config,
|
|
493
|
-
projectsDir: config.projectsDir.replace(require('os').homedir(), '~'),
|
|
494
491
|
ports: normalizedPorts,
|
|
495
492
|
maxLogs: maxLogs !== undefined ? parseInt(maxLogs) : config.maxLogs,
|
|
496
493
|
statsInterval: statsInterval !== undefined ? parseInt(statsInterval) : config.statsInterval,
|
|
@@ -90,7 +90,6 @@ function handleSaveModelSettings(req, res) {
|
|
|
90
90
|
const config = loadConfig();
|
|
91
91
|
const newConfig = {
|
|
92
92
|
...config,
|
|
93
|
-
projectsDir: config.projectsDir.replace(require('os').homedir(), '~'),
|
|
94
93
|
modelMetadataOverrides: overrides && typeof overrides === 'object'
|
|
95
94
|
? overrides
|
|
96
95
|
: (config.modelMetadataOverrides || {})
|
|
@@ -124,7 +123,6 @@ function handleDeleteModelOverride(req, res) {
|
|
|
124
123
|
|
|
125
124
|
const newConfig = {
|
|
126
125
|
...config,
|
|
127
|
-
projectsDir: config.projectsDir.replace(require('os').homedir(), '~'),
|
|
128
126
|
modelMetadataOverrides: overrides
|
|
129
127
|
};
|
|
130
128
|
saveConfig(newConfig);
|
|
@@ -33,6 +33,24 @@ function validateBranchName(branchName) {
|
|
|
33
33
|
}
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
+
function normalizeBranchMode(branchMode) {
|
|
37
|
+
if (typeof branchMode !== 'string' || !branchMode.trim()) {
|
|
38
|
+
return '';
|
|
39
|
+
}
|
|
40
|
+
return branchMode.trim();
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function validateBranchMode(branchMode) {
|
|
44
|
+
const normalized = normalizeBranchMode(branchMode);
|
|
45
|
+
if (!normalized) {
|
|
46
|
+
return { valid: true, normalized: '' };
|
|
47
|
+
}
|
|
48
|
+
if (normalized !== 'existing' && normalized !== 'new') {
|
|
49
|
+
return { valid: false, normalized, message: `分支模式不合法: ${normalized}` };
|
|
50
|
+
}
|
|
51
|
+
return { valid: true, normalized };
|
|
52
|
+
}
|
|
53
|
+
|
|
36
54
|
/**
|
|
37
55
|
* GET /api/workspaces
|
|
38
56
|
* 获取所有工作区列表
|
|
@@ -226,6 +244,17 @@ router.post('/', (req, res) => {
|
|
|
226
244
|
});
|
|
227
245
|
}
|
|
228
246
|
|
|
247
|
+
const branchModeValidation = validateBranchMode(proj.branchMode);
|
|
248
|
+
if (!branchModeValidation.valid) {
|
|
249
|
+
return res.status(400).json({
|
|
250
|
+
success: false,
|
|
251
|
+
message: branchModeValidation.message
|
|
252
|
+
});
|
|
253
|
+
}
|
|
254
|
+
if (branchModeValidation.normalized) {
|
|
255
|
+
proj.branchMode = branchModeValidation.normalized;
|
|
256
|
+
}
|
|
257
|
+
|
|
229
258
|
const normalizedBranch = normalizeBranchName(proj.branch);
|
|
230
259
|
if (normalizedBranch) {
|
|
231
260
|
const branchValidation = validateBranchName(normalizedBranch);
|
|
@@ -329,9 +358,10 @@ router.put('/:id/last-used', (req, res) => {
|
|
|
329
358
|
router.post('/:id/projects', (req, res) => {
|
|
330
359
|
try {
|
|
331
360
|
const { id } = req.params;
|
|
332
|
-
const { sourcePath, name, createWorktree, branch, baseBranch } = req.body;
|
|
361
|
+
const { sourcePath, name, createWorktree, branch, baseBranch, branchMode } = req.body;
|
|
333
362
|
const normalizedBranch = normalizeBranchName(branch);
|
|
334
363
|
const normalizedBaseBranch = normalizeBranchName(baseBranch);
|
|
364
|
+
const branchModeValidation = validateBranchMode(branchMode);
|
|
335
365
|
|
|
336
366
|
if (!sourcePath || !sourcePath.trim()) {
|
|
337
367
|
return res.status(400).json({
|
|
@@ -340,6 +370,13 @@ router.post('/:id/projects', (req, res) => {
|
|
|
340
370
|
});
|
|
341
371
|
}
|
|
342
372
|
|
|
373
|
+
if (!branchModeValidation.valid) {
|
|
374
|
+
return res.status(400).json({
|
|
375
|
+
success: false,
|
|
376
|
+
message: branchModeValidation.message
|
|
377
|
+
});
|
|
378
|
+
}
|
|
379
|
+
|
|
343
380
|
if (normalizedBranch) {
|
|
344
381
|
const branchValidation = validateBranchName(normalizedBranch);
|
|
345
382
|
if (!branchValidation.valid) {
|
|
@@ -364,6 +401,7 @@ router.post('/:id/projects', (req, res) => {
|
|
|
364
401
|
sourcePath,
|
|
365
402
|
name,
|
|
366
403
|
createWorktree,
|
|
404
|
+
branchMode: branchModeValidation.normalized || branchMode,
|
|
367
405
|
branch: normalizedBranch || branch,
|
|
368
406
|
baseBranch: normalizedBaseBranch || baseBranch
|
|
369
407
|
});
|
|
@@ -130,6 +130,7 @@ function runDashboardSnapshotWorker(kind, source, config = {}, options = {}) {
|
|
|
130
130
|
return new Promise((resolve, reject) => {
|
|
131
131
|
const child = fork(__filename, [], {
|
|
132
132
|
stdio: ['ignore', 'ignore', 'pipe', 'ipc'],
|
|
133
|
+
windowsHide: true,
|
|
133
134
|
env: {
|
|
134
135
|
...process.env,
|
|
135
136
|
CC_TOOL_DASHBOARD_SNAPSHOT_WORKER: '1'
|
|
@@ -42,7 +42,7 @@ function resolveOpenSslCommand() {
|
|
|
42
42
|
|
|
43
43
|
for (const command of commands) {
|
|
44
44
|
try {
|
|
45
|
-
execFileSync(command, ['version'], { stdio: 'ignore' });
|
|
45
|
+
execFileSync(command, ['version'], { stdio: 'ignore', windowsHide: true });
|
|
46
46
|
return command;
|
|
47
47
|
} catch {
|
|
48
48
|
// Try next candidate.
|
|
@@ -96,7 +96,8 @@ function generatePemCertificateWithOpenSsl(command) {
|
|
|
96
96
|
'-config',
|
|
97
97
|
configPath
|
|
98
98
|
], {
|
|
99
|
-
stdio: 'ignore'
|
|
99
|
+
stdio: 'ignore',
|
|
100
|
+
windowsHide: true
|
|
100
101
|
});
|
|
101
102
|
|
|
102
103
|
writeMeta({
|
|
@@ -46,6 +46,7 @@ function runSessionSnapshotWorker(source, projectName, config = {}, options = {}
|
|
|
46
46
|
const workerPath = path.join(__dirname, 'session-snapshot-worker.js');
|
|
47
47
|
const child = fork(workerPath, [], {
|
|
48
48
|
stdio: ['ignore', 'ignore', 'pipe', 'ipc'],
|
|
49
|
+
windowsHide: true,
|
|
49
50
|
env: {
|
|
50
51
|
...process.env,
|
|
51
52
|
CC_TOOL_SESSION_SNAPSHOT_WORKER: '1'
|
|
@@ -17,6 +17,17 @@ const CLAUDE_PROJECTS_DIR = NATIVE_PATHS.claude.projects;
|
|
|
17
17
|
const CODEX_PROJECTS_DIR = path.join(path.dirname(NATIVE_PATHS.codex.config), 'projects');
|
|
18
18
|
const GEMINI_PROJECTS_DIR = path.join(path.dirname(NATIVE_PATHS.gemini.env), 'projects');
|
|
19
19
|
|
|
20
|
+
function resolveProjectsDir(config = {}) {
|
|
21
|
+
return config.projectsDir || CLAUDE_PROJECTS_DIR;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function withResolvedProjectsDir(config = {}) {
|
|
25
|
+
return {
|
|
26
|
+
...config,
|
|
27
|
+
projectsDir: resolveProjectsDir(config)
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
|
|
20
31
|
// Base directory for cc-tool data
|
|
21
32
|
function getCcToolDir() {
|
|
22
33
|
return PATHS.base;
|
|
@@ -182,7 +193,7 @@ function sliceClaudeSessionContentByUserMessage(content, afterUserMessageNumber)
|
|
|
182
193
|
|
|
183
194
|
// Get all projects with stats (async version)
|
|
184
195
|
async function getProjects(config) {
|
|
185
|
-
const projectsDir = config
|
|
196
|
+
const projectsDir = resolveProjectsDir(config);
|
|
186
197
|
|
|
187
198
|
if (!fs.existsSync(projectsDir)) {
|
|
188
199
|
return [];
|
|
@@ -383,16 +394,17 @@ function extractCwdFromSessionHeader(sessionFile) {
|
|
|
383
394
|
|
|
384
395
|
// Get projects with detailed stats (with caching) - async version
|
|
385
396
|
async function getProjectsWithStats(config, options = {}) {
|
|
397
|
+
const resolvedConfig = withResolvedProjectsDir(config);
|
|
386
398
|
if (!options.force) {
|
|
387
399
|
// Check enhanced cache first
|
|
388
|
-
const cacheKey = `${CacheKeys.PROJECTS}${
|
|
400
|
+
const cacheKey = `${CacheKeys.PROJECTS}${resolvedConfig.projectsDir}`;
|
|
389
401
|
const enhancedCached = globalCache.get(cacheKey);
|
|
390
402
|
if (enhancedCached) {
|
|
391
403
|
return enhancedCached;
|
|
392
404
|
}
|
|
393
405
|
|
|
394
406
|
// Check old cache
|
|
395
|
-
const cached = getCachedProjects(
|
|
407
|
+
const cached = getCachedProjects(resolvedConfig);
|
|
396
408
|
if (cached) {
|
|
397
409
|
globalCache.set(cacheKey, cached, 300000); // 5分钟
|
|
398
410
|
return cached;
|
|
@@ -400,22 +412,22 @@ async function getProjectsWithStats(config, options = {}) {
|
|
|
400
412
|
}
|
|
401
413
|
|
|
402
414
|
try {
|
|
403
|
-
const data = await buildProjectsWithStats(
|
|
415
|
+
const data = await buildProjectsWithStats(resolvedConfig);
|
|
404
416
|
if (!Array.isArray(data)) {
|
|
405
|
-
console.warn(`[getProjectsWithStats] Unexpected non-array result for ${
|
|
417
|
+
console.warn(`[getProjectsWithStats] Unexpected non-array result for ${resolvedConfig.projectsDir}, returning empty array.`);
|
|
406
418
|
return [];
|
|
407
419
|
}
|
|
408
|
-
setCachedProjects(
|
|
409
|
-
globalCache.set(`${CacheKeys.PROJECTS}${
|
|
420
|
+
setCachedProjects(resolvedConfig, data);
|
|
421
|
+
globalCache.set(`${CacheKeys.PROJECTS}${resolvedConfig.projectsDir}`, data, 300000);
|
|
410
422
|
return data;
|
|
411
423
|
} catch (err) {
|
|
412
|
-
console.error(`[getProjectsWithStats] Failed to build projects for ${
|
|
424
|
+
console.error(`[getProjectsWithStats] Failed to build projects for ${resolvedConfig.projectsDir}:`, err);
|
|
413
425
|
return [];
|
|
414
426
|
}
|
|
415
427
|
}
|
|
416
428
|
|
|
417
429
|
async function buildProjectsWithStats(config) {
|
|
418
|
-
const projectsDir = config
|
|
430
|
+
const projectsDir = resolveProjectsDir(config);
|
|
419
431
|
|
|
420
432
|
if (!fs.existsSync(projectsDir)) {
|
|
421
433
|
return [];
|
|
@@ -482,7 +494,7 @@ async function buildProjectsWithStats(config) {
|
|
|
482
494
|
|
|
483
495
|
// 获取 Claude 项目/会话数量(轻量统计)
|
|
484
496
|
function getProjectAndSessionCounts(config) {
|
|
485
|
-
const projectsDir = config
|
|
497
|
+
const projectsDir = resolveProjectsDir(config);
|
|
486
498
|
if (!fs.existsSync(projectsDir)) {
|
|
487
499
|
return { projectCount: 0, sessionCount: 0 };
|
|
488
500
|
}
|
|
@@ -672,7 +684,8 @@ async function getSessionsForProject(config, projectName, options = {}) {
|
|
|
672
684
|
|
|
673
685
|
// Delete a session
|
|
674
686
|
function deleteSession(config, projectName, sessionId) {
|
|
675
|
-
const
|
|
687
|
+
const resolvedConfig = withResolvedProjectsDir(config);
|
|
688
|
+
const projectDir = path.join(resolvedConfig.projectsDir, projectName);
|
|
676
689
|
const sessionFile = path.join(projectDir, sessionId + '.jsonl');
|
|
677
690
|
|
|
678
691
|
if (!fs.existsSync(sessionFile)) {
|
|
@@ -680,14 +693,15 @@ function deleteSession(config, projectName, sessionId) {
|
|
|
680
693
|
}
|
|
681
694
|
|
|
682
695
|
fs.unlinkSync(sessionFile);
|
|
683
|
-
invalidateProjectsCache(
|
|
696
|
+
invalidateProjectsCache(resolvedConfig);
|
|
684
697
|
invalidateSessionResultCache(projectName);
|
|
685
698
|
return { success: true };
|
|
686
699
|
}
|
|
687
700
|
|
|
688
701
|
// Fork a session
|
|
689
702
|
function forkSession(config, projectName, sessionId, options = {}) {
|
|
690
|
-
const
|
|
703
|
+
const resolvedConfig = withResolvedProjectsDir(config);
|
|
704
|
+
const projectDir = path.join(resolvedConfig.projectsDir, projectName);
|
|
691
705
|
const sessionFile = path.join(projectDir, sessionId + '.jsonl');
|
|
692
706
|
|
|
693
707
|
if (!fs.existsSync(sessionFile)) {
|
|
@@ -716,7 +730,7 @@ function forkSession(config, projectName, sessionId, options = {}) {
|
|
|
716
730
|
const { setAlias } = require('./alias');
|
|
717
731
|
setAlias(newSessionId, options.alias);
|
|
718
732
|
}
|
|
719
|
-
invalidateProjectsCache(
|
|
733
|
+
invalidateProjectsCache(resolvedConfig);
|
|
720
734
|
invalidateSessionResultCache(projectName);
|
|
721
735
|
|
|
722
736
|
return {
|
|
@@ -768,7 +782,8 @@ function saveSessionOrder(projectName, order) {
|
|
|
768
782
|
|
|
769
783
|
// Delete a project (remove the entire project directory)
|
|
770
784
|
function deleteProject(config, projectName) {
|
|
771
|
-
const
|
|
785
|
+
const resolvedConfig = withResolvedProjectsDir(config);
|
|
786
|
+
const projectDir = path.join(resolvedConfig.projectsDir, projectName);
|
|
772
787
|
|
|
773
788
|
if (!fs.existsSync(projectDir)) {
|
|
774
789
|
throw new Error('Project not found');
|
|
@@ -778,19 +793,20 @@ function deleteProject(config, projectName) {
|
|
|
778
793
|
fs.rmSync(projectDir, { recursive: true, force: true });
|
|
779
794
|
|
|
780
795
|
// Remove from order file if exists
|
|
781
|
-
const order = getProjectOrder(
|
|
796
|
+
const order = getProjectOrder(resolvedConfig);
|
|
782
797
|
const newOrder = order.filter(name => name !== projectName);
|
|
783
798
|
if (newOrder.length !== order.length) {
|
|
784
|
-
saveProjectOrder(
|
|
799
|
+
saveProjectOrder(resolvedConfig, newOrder);
|
|
785
800
|
}
|
|
786
801
|
|
|
787
|
-
invalidateProjectsCache(
|
|
802
|
+
invalidateProjectsCache(resolvedConfig);
|
|
788
803
|
return { success: true };
|
|
789
804
|
}
|
|
790
805
|
|
|
791
806
|
// Search sessions for keyword
|
|
792
807
|
function searchSessions(config, projectName, keyword, contextLength = 15) {
|
|
793
|
-
const
|
|
808
|
+
const resolvedConfig = withResolvedProjectsDir(config);
|
|
809
|
+
const projectDir = path.join(resolvedConfig.projectsDir, projectName);
|
|
794
810
|
|
|
795
811
|
if (!fs.existsSync(projectDir)) {
|
|
796
812
|
return [];
|