chatccc 0.2.23 → 0.2.24
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/package.json
CHANGED
|
@@ -8,10 +8,10 @@
|
|
|
8
8
|
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
9
9
|
import { dirname, join } from "node:path";
|
|
10
10
|
|
|
11
|
-
import {
|
|
11
|
+
import { USER_DATA_DIR } from "../config.ts";
|
|
12
12
|
|
|
13
13
|
export const CODEX_SESSION_META_FILE = join(
|
|
14
|
-
|
|
14
|
+
USER_DATA_DIR,
|
|
15
15
|
"state",
|
|
16
16
|
"codex-session-meta.json",
|
|
17
17
|
);
|
|
@@ -23,11 +23,11 @@
|
|
|
23
23
|
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
24
24
|
import { dirname, join } from "node:path";
|
|
25
25
|
|
|
26
|
-
import {
|
|
26
|
+
import { USER_DATA_DIR } from "../config.ts";
|
|
27
27
|
|
|
28
28
|
/** 持久化文件默认路径(生产)。测试可通过 createCursorSessionMetaStore(filePath) 注入。 */
|
|
29
29
|
export const CURSOR_SESSION_META_FILE = join(
|
|
30
|
-
|
|
30
|
+
USER_DATA_DIR,
|
|
31
31
|
"state",
|
|
32
32
|
"cursor-session-meta.json",
|
|
33
33
|
);
|
package/src/config.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { existsSync, readFileSync, copyFileSync, writeFileSync } from "node:fs";
|
|
1
|
+
import { existsSync, readFileSync, copyFileSync, writeFileSync, mkdirSync, readdirSync, statSync } from "node:fs";
|
|
2
2
|
import { execFileSync } from "node:child_process";
|
|
3
3
|
import { homedir } from "node:os";
|
|
4
4
|
import { dirname, join } from "node:path";
|
|
5
5
|
import { fileURLToPath } from "node:url";
|
|
6
|
-
import { appendFile, mkdir, readFile, stat, writeFile } from "node:fs/promises";
|
|
6
|
+
import { appendFile, cp, mkdir, readFile, stat, writeFile } from "node:fs/promises";
|
|
7
7
|
|
|
8
8
|
import { printServiceDidNotStart } from "./exit-banner.ts";
|
|
9
9
|
import { appendStartupTrace, setupFileLogging } from "./shared.ts";
|
|
@@ -34,12 +34,14 @@ export type { ParsedGitTimeout } from "./config-utils.ts";
|
|
|
34
34
|
|
|
35
35
|
export const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
36
36
|
export const PROJECT_ROOT = join(__dirname, "..");
|
|
37
|
-
|
|
37
|
+
/** 用户持久化数据根目录(不随 npm 升级清空) */
|
|
38
|
+
export const USER_DATA_DIR = join(homedir(), ".chatccc");
|
|
39
|
+
export const PID_FILE = join(USER_DATA_DIR, "state", "runtime.pid");
|
|
38
40
|
|
|
39
|
-
export const LOG_DIR = join(
|
|
41
|
+
export const LOG_DIR = join(USER_DATA_DIR, "logs");
|
|
40
42
|
export const fileLog = setupFileLogging(LOG_DIR, "index");
|
|
41
43
|
|
|
42
|
-
export const CHAT_LOGS_DIR = join(
|
|
44
|
+
export const CHAT_LOGS_DIR = join(USER_DATA_DIR, "state", "chat_logs");
|
|
43
45
|
|
|
44
46
|
export async function appendChatLog(chatId: string, sender: string, text: string): Promise<void> {
|
|
45
47
|
try {
|
|
@@ -104,9 +106,82 @@ export interface AppConfig {
|
|
|
104
106
|
export type AgentTool = "claude" | "cursor" | "codex";
|
|
105
107
|
export const AGENT_TOOLS: AgentTool[] = ["claude", "cursor", "codex"];
|
|
106
108
|
|
|
107
|
-
const CONFIG_FILE = join(
|
|
109
|
+
const CONFIG_FILE = join(USER_DATA_DIR, "config.json");
|
|
108
110
|
const CONFIG_SAMPLE_FILE = join(PROJECT_ROOT, "config.sample.json");
|
|
109
111
|
|
|
112
|
+
/**
|
|
113
|
+
* 将旧位置(PROJECT_ROOT)的持久化数据一次性迁移到 USER_DATA_DIR。
|
|
114
|
+
* 仅当 USER_DATA_DIR 下没有 config.json 且 PROJECT_ROOT 下有旧数据时才执行。
|
|
115
|
+
*/
|
|
116
|
+
function migrateLegacyData(): void {
|
|
117
|
+
const oldConfig = join(PROJECT_ROOT, "config.json");
|
|
118
|
+
const oldState = join(PROJECT_ROOT, "state");
|
|
119
|
+
const oldLogs = join(PROJECT_ROOT, "logs");
|
|
120
|
+
const oldImagesDownloads = join(PROJECT_ROOT, "images", "downloads");
|
|
121
|
+
|
|
122
|
+
if (existsSync(CONFIG_FILE)) return; // 已迁移过或全新安装
|
|
123
|
+
|
|
124
|
+
let migrated = false;
|
|
125
|
+
try {
|
|
126
|
+
mkdirSync(USER_DATA_DIR, { recursive: true });
|
|
127
|
+
|
|
128
|
+
if (existsSync(oldConfig)) {
|
|
129
|
+
copyFileSync(oldConfig, CONFIG_FILE);
|
|
130
|
+
console.log(`[MIGRATE] config.json → ${CONFIG_FILE}`);
|
|
131
|
+
migrated = true;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
if (existsSync(oldState)) {
|
|
135
|
+
const destState = join(USER_DATA_DIR, "state");
|
|
136
|
+
if (!existsSync(destState)) {
|
|
137
|
+
// 同步递归复制(cpSync 不可用,改用 copyFileSync 遍历)
|
|
138
|
+
copyDirSync(oldState, destState);
|
|
139
|
+
console.log(`[MIGRATE] state/ → ${destState}`);
|
|
140
|
+
migrated = true;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
if (existsSync(oldLogs)) {
|
|
145
|
+
const destLogs = join(USER_DATA_DIR, "logs");
|
|
146
|
+
if (!existsSync(destLogs)) {
|
|
147
|
+
copyDirSync(oldLogs, destLogs);
|
|
148
|
+
console.log(`[MIGRATE] logs/ → ${destLogs}`);
|
|
149
|
+
migrated = true;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
if (existsSync(oldImagesDownloads)) {
|
|
154
|
+
const destDownloads = join(USER_DATA_DIR, "images", "downloads");
|
|
155
|
+
if (!existsSync(destDownloads)) {
|
|
156
|
+
copyDirSync(oldImagesDownloads, destDownloads);
|
|
157
|
+
console.log(`[MIGRATE] images/downloads/ → ${destDownloads}`);
|
|
158
|
+
migrated = true;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
} catch (err) {
|
|
162
|
+
console.error(`[MIGRATE] 迁移失败: ${(err as Error).message}`);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
if (migrated) {
|
|
166
|
+
console.log("[MIGRATE] 旧数据已迁移到 ~/.chatccc/,原位置文件保留未删除。");
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/** 递归同步复制目录(仅文件和子目录,不处理符号链接) */
|
|
171
|
+
function copyDirSync(src: string, dest: string): void {
|
|
172
|
+
mkdirSync(dest, { recursive: true });
|
|
173
|
+
for (const entry of readdirSync(src)) {
|
|
174
|
+
const srcPath = join(src, entry);
|
|
175
|
+
const destPath = join(dest, entry);
|
|
176
|
+
const s = statSync(srcPath);
|
|
177
|
+
if (s.isDirectory()) {
|
|
178
|
+
copyDirSync(srcPath, destPath);
|
|
179
|
+
} else if (s.isFile()) {
|
|
180
|
+
copyFileSync(srcPath, destPath);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
110
185
|
/**
|
|
111
186
|
* 是否处于 vitest 测试环境。
|
|
112
187
|
*
|
|
@@ -217,6 +292,10 @@ function loadConfig(): AppConfig {
|
|
|
217
292
|
codex: { enabled: false, defaultAgent: false, path: "", model: "", effort: "" },
|
|
218
293
|
};
|
|
219
294
|
|
|
295
|
+
if (!IS_TEST_ENV) {
|
|
296
|
+
migrateLegacyData();
|
|
297
|
+
}
|
|
298
|
+
|
|
220
299
|
if (!existsSync(CONFIG_FILE)) {
|
|
221
300
|
if (IS_TEST_ENV) {
|
|
222
301
|
// 测试环境下绝不写文件,直接走默认值
|
|
@@ -225,6 +304,7 @@ function loadConfig(): AppConfig {
|
|
|
225
304
|
if (existsSync(CONFIG_SAMPLE_FILE)) {
|
|
226
305
|
console.log(`[CONFIG] config.json 不存在,基于 config.sample.json 创建...`);
|
|
227
306
|
try {
|
|
307
|
+
mkdirSync(dirname(CONFIG_FILE), { recursive: true });
|
|
228
308
|
copyFileSync(CONFIG_SAMPLE_FILE, CONFIG_FILE);
|
|
229
309
|
} catch (err) {
|
|
230
310
|
console.error(`[CONFIG] 无法从 config.sample.json 创建 config.json: ${(err as Error).message}`);
|
|
@@ -469,13 +549,13 @@ export function reloadConfigFromDisk(): void {
|
|
|
469
549
|
|
|
470
550
|
// 新建会话的默认工作路径(/cd 命令设置,持久化到本地文件)
|
|
471
551
|
// 该路径仅影响通过 /new 新建的会话,不影响已有会话的 resume。
|
|
472
|
-
export const DEFAULT_CWD_FILE = join(
|
|
552
|
+
export const DEFAULT_CWD_FILE = join(USER_DATA_DIR, "state", "working_dir.txt");
|
|
473
553
|
|
|
474
554
|
/** 会话工具类型持久化文件 */
|
|
475
|
-
export const SESSIONS_FILE = join(
|
|
555
|
+
export const SESSIONS_FILE = join(USER_DATA_DIR, "state", "sessions.json");
|
|
476
556
|
|
|
477
557
|
/** 最近成功新建会话的工作路径记录(最多 10 条) */
|
|
478
|
-
export const RECENT_DIRS_FILE = join(
|
|
558
|
+
export const RECENT_DIRS_FILE = join(USER_DATA_DIR, "state", "recent_dirs.json");
|
|
479
559
|
export const MAX_RECENT_DIRS = 10;
|
|
480
560
|
|
|
481
561
|
/** 读取最近使用过的工作路径列表(最新的在前) */
|
package/src/feishu-api.ts
CHANGED
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
BASE_URL,
|
|
10
10
|
CHAT_LOGS_DIR,
|
|
11
11
|
PROJECT_ROOT,
|
|
12
|
+
USER_DATA_DIR,
|
|
12
13
|
CLAUDE_SESSION_PREFIX,
|
|
13
14
|
CURSOR_SESSION_PREFIX,
|
|
14
15
|
CODEX_SESSION_PREFIX,
|
|
@@ -262,7 +263,7 @@ export function extractSessionId(description: string): string | null {
|
|
|
262
263
|
|
|
263
264
|
const AVATAR_DIR = resolvePath(PROJECT_ROOT, "images", "avatars");
|
|
264
265
|
const AVATAR_BADGE_DIR = resolvePath(AVATAR_DIR, "badges");
|
|
265
|
-
const AVATAR_KEY_CACHE_FILE = resolvePath(
|
|
266
|
+
const AVATAR_KEY_CACHE_FILE = resolvePath(USER_DATA_DIR, "state", "avatar-image-keys.json");
|
|
266
267
|
const AVATAR_SOURCES: Record<string, string> = {
|
|
267
268
|
new: resolvePath(AVATAR_DIR, "status_new.png"),
|
|
268
269
|
busy: resolvePath(AVATAR_DIR, "status_busy.png"),
|
|
@@ -409,8 +410,8 @@ export async function setChatAvatar(token: string, chatId: string, tool: string,
|
|
|
409
410
|
// Image download & cache
|
|
410
411
|
// ---------------------------------------------------------------------------
|
|
411
412
|
|
|
412
|
-
const IMAGE_DOWNLOAD_DIR = resolvePath(
|
|
413
|
-
const IMAGE_CACHE_FILE = resolvePath(
|
|
413
|
+
const IMAGE_DOWNLOAD_DIR = resolvePath(USER_DATA_DIR, "images", "downloads");
|
|
414
|
+
const IMAGE_CACHE_FILE = resolvePath(USER_DATA_DIR, "state", "image-cache.json");
|
|
414
415
|
|
|
415
416
|
const imageCache = new Map<string, string>();
|
|
416
417
|
let imageCacheLoaded = false;
|
package/src/shared.ts
CHANGED
|
@@ -9,14 +9,14 @@ import {
|
|
|
9
9
|
writeFileSync,
|
|
10
10
|
} from "node:fs";
|
|
11
11
|
import { createServer } from "node:http";
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
12
|
+
import { homedir } from "node:os";
|
|
13
|
+
import { join } from "node:path";
|
|
14
14
|
import { WebSocketServer, WebSocket } from "ws";
|
|
15
15
|
|
|
16
16
|
import { printServiceDidNotStart } from "./exit-banner.ts";
|
|
17
17
|
|
|
18
18
|
/** 与 config.LOG_DIR 一致(避免 shared 依赖 config 造成循环引用) */
|
|
19
|
-
const BANNER_LOG_DIR = join(
|
|
19
|
+
const BANNER_LOG_DIR = join(homedir(), ".chatccc", "logs");
|
|
20
20
|
|
|
21
21
|
const STARTUP_TRACE_FILE = join(BANNER_LOG_DIR, "startup-trace.log");
|
|
22
22
|
|
package/src/web-ui.ts
CHANGED
|
@@ -9,15 +9,17 @@
|
|
|
9
9
|
import { createServer, IncomingMessage, ServerResponse } from "node:http";
|
|
10
10
|
import { existsSync, readFileSync, writeFileSync } from "node:fs";
|
|
11
11
|
import { readFile, writeFile, stat } from "node:fs/promises";
|
|
12
|
+
import { homedir } from "node:os";
|
|
12
13
|
import { join, dirname } from "node:path";
|
|
13
14
|
import { fileURLToPath } from "node:url";
|
|
14
15
|
import { spawn, execSync } from "node:child_process";
|
|
15
16
|
|
|
16
17
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
17
18
|
const PROJECT_ROOT = join(__dirname, "..");
|
|
18
|
-
const
|
|
19
|
+
const USER_DATA_DIR = join(homedir(), ".chatccc");
|
|
20
|
+
const CONFIG_FILE = join(USER_DATA_DIR, "config.json");
|
|
19
21
|
const CONFIG_SAMPLE_FILE = join(PROJECT_ROOT, "config.sample.json");
|
|
20
|
-
const PID_FILE = join(
|
|
22
|
+
const PID_FILE = join(USER_DATA_DIR, "state", "runtime.pid");
|
|
21
23
|
|
|
22
24
|
// ---------------------------------------------------------------------------
|
|
23
25
|
// Helpers — config.json parsing & generation
|