@zhongqian97-code/ecode 0.5.33 → 0.5.35

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.
@@ -0,0 +1,60 @@
1
+ #!/usr/bin/env node
2
+ const _ew=process.emitWarning.bind(process);process.emitWarning=function(w,...a){if((w?.message??w)?.includes?.('punycode'))return;_ew(w,...a);};
3
+ import {
4
+ findSession,
5
+ listSessions
6
+ } from "./chunk-O4YFKL3N.js";
7
+
8
+ // src/sessions/command.ts
9
+ import * as path from "path";
10
+ function cmdSessionsList(logDir) {
11
+ const sessions = listSessions(logDir);
12
+ if (sessions.length === 0) return "(no sessions found)";
13
+ const lines = sessions.map((s) => {
14
+ const id = s.id.slice(0, 8);
15
+ const date = new Date(s.lastActivity).toLocaleString();
16
+ const title = s.title || "(no title)";
17
+ const tokens = s.totalTokens.toLocaleString();
18
+ return `${id} ${date} ${tokens}t ${title}`;
19
+ });
20
+ return `Sessions (${sessions.length}):
21
+ ` + lines.join("\n");
22
+ }
23
+ function cmdSessionsInspect(logDir, idOrPrefix) {
24
+ const session = findSession(logDir, idOrPrefix);
25
+ if (!session) return `Session not found: ${idOrPrefix}`;
26
+ return formatInspect(session, logDir);
27
+ }
28
+ function cmdSessionsReplay(logDir, idOrPrefix) {
29
+ const session = findSession(logDir, idOrPrefix);
30
+ if (!session) return `Session not found: ${idOrPrefix}`;
31
+ const logPath = path.join(logDir, session.logFile);
32
+ return `ecode --replay ${logPath}`;
33
+ }
34
+ function cmdSessionsFork(logDir, idOrPrefix, turn) {
35
+ const session = findSession(logDir, idOrPrefix);
36
+ if (!session) return `Session not found: ${idOrPrefix}`;
37
+ const logPath = path.join(logDir, session.logFile);
38
+ const turnStr = turn !== void 0 ? turn : session.turnCount;
39
+ return `ecode --fork ${logPath}:${turnStr}`;
40
+ }
41
+ function formatInspect(session, logDir) {
42
+ return [
43
+ `id: ${session.id}`,
44
+ `title: ${session.title || "(no title)"}`,
45
+ `model: ${session.model}`,
46
+ `cwd: ${session.cwd}`,
47
+ `started: ${new Date(session.startTime).toLocaleString()}`,
48
+ `last active: ${new Date(session.lastActivity).toLocaleString()}`,
49
+ `turns: ${session.turnCount}`,
50
+ `tokens: ${session.totalTokens.toLocaleString()}`,
51
+ `log file: ${path.join(logDir, session.logFile)}`
52
+ ].join("\n");
53
+ }
54
+
55
+ export {
56
+ cmdSessionsList,
57
+ cmdSessionsInspect,
58
+ cmdSessionsReplay,
59
+ cmdSessionsFork
60
+ };
@@ -0,0 +1,46 @@
1
+ #!/usr/bin/env node
2
+ const _ew=process.emitWarning.bind(process);process.emitWarning=function(w,...a){if((w?.message??w)?.includes?.('punycode'))return;_ew(w,...a);};
3
+
4
+ // src/automation/store.ts
5
+ import { readFile, writeFile, mkdir } from "fs/promises";
6
+ import { join } from "path";
7
+ function jobsFilePath(dataDir) {
8
+ return join(dataDir, "jobs.json");
9
+ }
10
+ async function loadJobs(dataDir) {
11
+ try {
12
+ const content = await readFile(jobsFilePath(dataDir), "utf-8");
13
+ const parsed = JSON.parse(content);
14
+ if (!Array.isArray(parsed)) return [];
15
+ return parsed;
16
+ } catch {
17
+ return [];
18
+ }
19
+ }
20
+ async function saveJobs(dataDir, jobs) {
21
+ await mkdir(dataDir, { recursive: true });
22
+ await writeFile(jobsFilePath(dataDir), JSON.stringify(jobs, null, 2), "utf-8");
23
+ }
24
+ async function upsertJob(dataDir, job) {
25
+ const jobs = await loadJobs(dataDir);
26
+ const idx = jobs.findIndex((j) => j.id === job.id);
27
+ if (idx >= 0) {
28
+ const updated = [...jobs.slice(0, idx), job, ...jobs.slice(idx + 1)];
29
+ await saveJobs(dataDir, updated);
30
+ } else {
31
+ await saveJobs(dataDir, [...jobs, job]);
32
+ }
33
+ }
34
+ async function removeJob(dataDir, id) {
35
+ const jobs = await loadJobs(dataDir);
36
+ const target = jobs.find((j) => j.id === id);
37
+ if (!target) return void 0;
38
+ await saveJobs(dataDir, jobs.filter((j) => j.id !== id));
39
+ return target;
40
+ }
41
+
42
+ export {
43
+ loadJobs,
44
+ upsertJob,
45
+ removeJob
46
+ };
@@ -0,0 +1,265 @@
1
+ #!/usr/bin/env node
2
+ const _ew=process.emitWarning.bind(process);process.emitWarning=function(w,...a){if((w?.message??w)?.includes?.('punycode'))return;_ew(w,...a);};
3
+
4
+ // src/config.ts
5
+ import { existsSync, readFileSync, writeFileSync, mkdirSync } from "fs";
6
+ import { homedir } from "os";
7
+ import { join } from "path";
8
+ var MODEL_CONTEXT_LIMITS = {
9
+ // OpenAI GPT 系列
10
+ "gpt-4o": 128e3,
11
+ "gpt-4o-mini": 128e3,
12
+ "gpt-4-turbo": 128e3,
13
+ "gpt-4": 8192,
14
+ // 老版 GPT-4 上下文极小,需特别注意
15
+ "gpt-3.5-turbo": 16385,
16
+ // OpenAI o 系列推理模型
17
+ "o1": 2e5,
18
+ "o1-mini": 128e3,
19
+ "o1-preview": 128e3,
20
+ "o3": 2e5,
21
+ "o3-mini": 2e5,
22
+ // Anthropic Claude 系列(全线支持 200K)
23
+ "claude-3-5-sonnet-20241022": 2e5,
24
+ "claude-3-5-haiku-20241022": 2e5,
25
+ "claude-3-opus-20240229": 2e5,
26
+ "claude-3-sonnet-20240229": 2e5,
27
+ "claude-3-haiku-20240307": 2e5,
28
+ "claude-sonnet-4-6": 2e5,
29
+ "claude-opus-4-7": 2e5,
30
+ "claude-haiku-4-5-20251001": 2e5,
31
+ // DeepSeek 系列(上下文较小,截断策略需更激进)
32
+ "deepseek-chat": 65536,
33
+ "deepseek-reasoner": 65536,
34
+ // MiniMax 系列(baseUrl: https://api.minimax.chat/v1)
35
+ "MiniMax-M2.5": 1e6,
36
+ "MiniMax-M2.5-highspeed": 192e3,
37
+ "MiniMax-Text-01": 1e6
38
+ };
39
+ var DEFAULT_CONTEXT_LIMIT = 128e3;
40
+ function getContextLimit(model, override) {
41
+ if (override !== void 0) return override;
42
+ return MODEL_CONTEXT_LIMITS[model] ?? DEFAULT_CONTEXT_LIMIT;
43
+ }
44
+ var DEFAULTS = {
45
+ baseUrl: "https://api.openai.com/v1",
46
+ apiKey: "",
47
+ model: "gpt-4o",
48
+ dangerousPatterns: [
49
+ "rm -rf",
50
+ "sudo",
51
+ "chmod",
52
+ "chown",
53
+ "mkfs",
54
+ "dd",
55
+ "fdisk",
56
+ "kill",
57
+ "pkill",
58
+ "killall",
59
+ "reboot",
60
+ "shutdown",
61
+ "halt",
62
+ "curl -X DELETE",
63
+ "wget --delete-after"
64
+ ],
65
+ logDir: void 0
66
+ };
67
+ function loadConfig() {
68
+ const configPath = join(homedir(), ".ecode", "config.json");
69
+ let fileConfig = {};
70
+ if (existsSync(configPath)) {
71
+ try {
72
+ const raw = readFileSync(configPath, "utf-8");
73
+ fileConfig = JSON.parse(raw);
74
+ } catch (err) {
75
+ const msg = err instanceof Error ? err.message : String(err);
76
+ process.stderr.write(`[ecode] warning: ${configPath} parse failed (${msg}), falling back to defaults/env
77
+ `);
78
+ }
79
+ }
80
+ return {
81
+ // ?? 运算符:仅在左侧为 null / undefined 时才取右侧值,
82
+ // 与 || 的区别在于不会跳过空字符串,保证显式设置 "" 也能生效
83
+ baseUrl: process.env.ECODE_BASE_URL ?? fileConfig.baseUrl ?? DEFAULTS.baseUrl,
84
+ apiKey: process.env.ECODE_API_KEY ?? fileConfig.apiKey ?? DEFAULTS.apiKey,
85
+ model: process.env.ECODE_MODEL ?? fileConfig.model ?? DEFAULTS.model,
86
+ // dangerousPatterns 不支持环境变量注入:命令数组通过单个环境变量传递需要转义,
87
+ // 容易引入歧义,因此仅支持配置文件覆盖
88
+ dangerousPatterns: fileConfig.dangerousPatterns ?? DEFAULTS.dangerousPatterns,
89
+ // logDir: ECODE_LOG_DIR 环境变量 > 配置文件 > undefined(禁用日志)
90
+ logDir: process.env.ECODE_LOG_DIR ?? fileConfig.logDir ?? DEFAULTS.logDir,
91
+ // contextLimit 仅支持配置文件配置:数值类型在文件中更直观,
92
+ // 环境变量还需要 parseInt 转换,增加出错风险
93
+ contextLimit: fileConfig.contextLimit,
94
+ // ECODE_SYSTEM_PROMPT 环境变量优先;未设时从配置文件读取;
95
+ // 两者均未设时保持 undefined(由 resolveSystemPrompt 决定使用内置默认值)。
96
+ // 注意:空字符串 "" 是有效值(表示禁用),?? 不会跳过空字符串。
97
+ systemPrompt: process.env.ECODE_SYSTEM_PROMPT ?? fileConfig.systemPrompt,
98
+ // providers/defaultProvider 仅支持配置文件配置,不支持环境变量注入
99
+ providers: fileConfig.providers,
100
+ defaultProvider: fileConfig.defaultProvider
101
+ };
102
+ }
103
+ function saveConfig(partial) {
104
+ const configPath = join(homedir(), ".ecode", "config.json");
105
+ const configDir = join(homedir(), ".ecode");
106
+ mkdirSync(configDir, { recursive: true });
107
+ let existing = {};
108
+ if (existsSync(configPath)) {
109
+ try {
110
+ const raw = readFileSync(configPath, "utf-8");
111
+ existing = JSON.parse(raw);
112
+ } catch (err) {
113
+ const msg = err instanceof Error ? err.message : String(err);
114
+ process.stderr.write(`[ecode] warning: ${configPath} parse failed (${msg}), overwriting with new config
115
+ `);
116
+ }
117
+ }
118
+ const merged = { ...existing, ...partial };
119
+ writeFileSync(configPath, JSON.stringify(merged, null, 2), "utf-8");
120
+ }
121
+
122
+ // src/sessions/metadata.ts
123
+ import * as crypto from "crypto";
124
+ import * as fs from "fs";
125
+ import * as path from "path";
126
+ function metadataPathFromLogFile(logFilePath) {
127
+ const base = path.basename(logFilePath, ".jsonl");
128
+ const dir = path.dirname(logFilePath);
129
+ return path.join(dir, `${base}-session.json`);
130
+ }
131
+ function createSessionMetadata(logFilePath, model, id) {
132
+ const now = (/* @__PURE__ */ new Date()).toISOString();
133
+ return {
134
+ id: id ?? crypto.randomUUID(),
135
+ startTime: now,
136
+ lastActivity: now,
137
+ cwd: process.cwd(),
138
+ model,
139
+ title: "",
140
+ turnCount: 0,
141
+ totalTokens: 0,
142
+ logFile: path.basename(logFilePath)
143
+ };
144
+ }
145
+ function writeSessionMetadata(logFilePath, metadata) {
146
+ const metaPath = metadataPathFromLogFile(logFilePath);
147
+ try {
148
+ fs.writeFileSync(metaPath, JSON.stringify(metadata, null, 2) + "\n");
149
+ } catch (err) {
150
+ process.stderr.write(`[sessions] Failed to write metadata: ${err}
151
+ `);
152
+ }
153
+ }
154
+ function readSessionMetadata(metaFilePath) {
155
+ try {
156
+ const raw = fs.readFileSync(metaFilePath, "utf-8");
157
+ return JSON.parse(raw);
158
+ } catch {
159
+ return null;
160
+ }
161
+ }
162
+ function updateSessionMetadata(logFilePath, partial) {
163
+ const metaPath = metadataPathFromLogFile(logFilePath);
164
+ let existing = null;
165
+ try {
166
+ const raw = fs.readFileSync(metaPath, "utf-8");
167
+ existing = JSON.parse(raw);
168
+ } catch {
169
+ return;
170
+ }
171
+ writeSessionMetadata(logFilePath, { ...existing, ...partial });
172
+ }
173
+ function listSessions(logDir) {
174
+ try {
175
+ const files = fs.readdirSync(logDir);
176
+ const metaFiles = files.filter((f) => f.endsWith("-session.json"));
177
+ const sessions = [];
178
+ for (const file of metaFiles) {
179
+ const meta = readSessionMetadata(path.join(logDir, file));
180
+ if (meta) sessions.push(meta);
181
+ }
182
+ return sessions.sort(
183
+ (a, b) => b.lastActivity.localeCompare(a.lastActivity)
184
+ );
185
+ } catch {
186
+ return [];
187
+ }
188
+ }
189
+ function findSession(logDir, idOrPrefix) {
190
+ const sessions = listSessions(logDir);
191
+ return sessions.find(
192
+ (s) => s.id === idOrPrefix || s.id.startsWith(idOrPrefix)
193
+ ) ?? null;
194
+ }
195
+ function generateTitle(firstUserMessage) {
196
+ const oneLine = firstUserMessage.replace(/\n+/g, " ").trim();
197
+ return oneLine.length > 50 ? oneLine.slice(0, 47) + "..." : oneLine;
198
+ }
199
+ function deleteSessionFiles(logDir, id) {
200
+ const session = findSession(logDir, id);
201
+ if (!session) return false;
202
+ const logFilePath = path.join(logDir, session.logFile);
203
+ const metaFilePath = metadataPathFromLogFile(logFilePath);
204
+ let deleted = false;
205
+ try {
206
+ fs.unlinkSync(logFilePath);
207
+ deleted = true;
208
+ } catch {
209
+ }
210
+ try {
211
+ fs.unlinkSync(metaFilePath);
212
+ deleted = true;
213
+ } catch {
214
+ }
215
+ return deleted;
216
+ }
217
+ function loadMessagesFromJsonl(logFilePath) {
218
+ try {
219
+ const content = fs.readFileSync(logFilePath, "utf-8");
220
+ const lines = content.split("\n").filter((l) => l.trim());
221
+ const messages = [];
222
+ for (const line of lines) {
223
+ try {
224
+ const entry = JSON.parse(line);
225
+ if (!entry.role || entry.role === "system") continue;
226
+ if (entry.role === "user") {
227
+ messages.push({ role: "user", content: entry.content ?? "" });
228
+ } else if (entry.role === "assistant") {
229
+ const msg = {
230
+ role: "assistant",
231
+ content: entry.content ?? null
232
+ };
233
+ if (entry.tool_calls) {
234
+ msg.tool_calls = entry.tool_calls;
235
+ }
236
+ messages.push(msg);
237
+ } else if (entry.role === "tool") {
238
+ messages.push({
239
+ role: "tool",
240
+ tool_call_id: entry.tool_call_id ?? "",
241
+ content: entry.content ?? ""
242
+ });
243
+ }
244
+ } catch {
245
+ }
246
+ }
247
+ return messages;
248
+ } catch {
249
+ return [];
250
+ }
251
+ }
252
+
253
+ export {
254
+ getContextLimit,
255
+ loadConfig,
256
+ saveConfig,
257
+ createSessionMetadata,
258
+ writeSessionMetadata,
259
+ updateSessionMetadata,
260
+ listSessions,
261
+ findSession,
262
+ generateTitle,
263
+ deleteSessionFiles,
264
+ loadMessagesFromJsonl
265
+ };