chatccc 0.2.243 → 0.2.245

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.
Files changed (65) hide show
  1. package/README.md +6 -6
  2. package/bin/cccagent.mjs +17 -17
  3. package/deepccc-agent/README.md +14 -8
  4. package/deepccc-agent/bin/deepccc.mjs +26 -26
  5. package/deepccc-agent/os-prompts/darwin.md +8 -0
  6. package/deepccc-agent/os-prompts/linux.md +8 -0
  7. package/deepccc-agent/os-prompts/win32.md +9 -0
  8. package/deepccc-agent/package-lock.json +2 -2
  9. package/deepccc-agent/package.json +63 -62
  10. package/deepccc-agent/src/__tests__/chat-session.test.ts +682 -578
  11. package/deepccc-agent/src/__tests__/cli-json.test.ts +49 -49
  12. package/deepccc-agent/src/__tests__/config.test.ts +26 -26
  13. package/deepccc-agent/src/__tests__/context.test.ts +319 -319
  14. package/deepccc-agent/src/__tests__/file-tools.test.ts +240 -240
  15. package/deepccc-agent/src/__tests__/permissions.test.ts +195 -195
  16. package/deepccc-agent/src/__tests__/privacy.test.ts +8 -5
  17. package/deepccc-agent/src/__tests__/progress-reducer.test.ts +121 -121
  18. package/deepccc-agent/src/__tests__/session-search.test.ts +262 -262
  19. package/deepccc-agent/src/__tests__/session-select.test.ts +116 -116
  20. package/deepccc-agent/src/__tests__/sigint.test.ts +56 -56
  21. package/deepccc-agent/src/__tests__/skills.test.ts +284 -284
  22. package/deepccc-agent/src/__tests__/terminal-renderer.test.ts +247 -247
  23. package/deepccc-agent/src/__tests__/web-tools.test.ts +220 -220
  24. package/deepccc-agent/src/cli.ts +7 -6
  25. package/deepccc-agent/src/config.ts +88 -84
  26. package/deepccc-agent/src/context.ts +465 -465
  27. package/deepccc-agent/src/file-log.ts +38 -38
  28. package/deepccc-agent/src/index.ts +103 -36
  29. package/deepccc-agent/src/proc-tree-kill.ts +61 -61
  30. package/deepccc-agent/src/progress/cards-helpers.ts +76 -76
  31. package/deepccc-agent/src/progress/reducer.ts +113 -113
  32. package/deepccc-agent/src/progress/terminal-renderer.ts +294 -294
  33. package/deepccc-agent/src/progress/view.ts +77 -77
  34. package/deepccc-agent/src/raw-stream-log.ts +124 -124
  35. package/deepccc-agent/src/session-search.ts +370 -370
  36. package/deepccc-agent/src/session-select.ts +48 -48
  37. package/deepccc-agent/src/sigint.ts +50 -50
  38. package/deepccc-agent/src/skills.ts +205 -205
  39. package/deepccc-agent/src/web-tools.ts +313 -313
  40. package/deepccc-agent/tsconfig.build.json +13 -13
  41. package/deepccc-agent/tsconfig.json +13 -13
  42. package/deepccc-agent/vitest.config.ts +7 -7
  43. package/package.json +1 -1
  44. package/src/__tests__/builtin-chat-session.test.ts +522 -522
  45. package/src/__tests__/builtin-config.test.ts +26 -26
  46. package/src/__tests__/builtin-context.test.ts +319 -319
  47. package/src/__tests__/builtin-file-tools.test.ts +240 -240
  48. package/src/__tests__/builtin-permissions.test.ts +211 -211
  49. package/src/__tests__/builtin-session-search.test.ts +262 -262
  50. package/src/__tests__/builtin-session-select.test.ts +116 -116
  51. package/src/__tests__/builtin-sigint.test.ts +56 -56
  52. package/src/__tests__/builtin-skills.test.ts +284 -284
  53. package/src/__tests__/builtin-web-tools.test.ts +220 -220
  54. package/src/__tests__/ccc-adapter.test.ts +15 -0
  55. package/src/__tests__/config.test.ts +17 -17
  56. package/src/__tests__/progress-reducer.test.ts +121 -121
  57. package/src/__tests__/session-ccc-config.test.ts +45 -45
  58. package/src/__tests__/session.test.ts +369 -306
  59. package/src/adapters/adapter-interface.ts +8 -2
  60. package/src/adapters/ccc-adapter.ts +149 -145
  61. package/src/config-utils.ts +13 -13
  62. package/src/config.ts +13 -13
  63. package/src/progress/reducer.ts +113 -113
  64. package/src/session-chat-binding.ts +83 -83
  65. package/src/session.ts +323 -320
@@ -1,220 +1,220 @@
1
- import { describe, expect, it } from "vitest";
2
-
3
- import {
4
- decodeDdgHref,
5
- extractHtmlTitle,
6
- htmlToPlainText,
7
- parseDuckDuckGoHtml,
8
- webFetchForTool,
9
- webSearchForTool,
10
- type FetchLike,
11
- } from "../web-tools.js";
12
-
13
- const DDG_HTML = `<!DOCTYPE html>
14
- <html>
15
- <head><title>Search results</title></head>
16
- <body>
17
- <div class="result results_links results_links_deep web-result">
18
- <h2 class="result__title">
19
- <a rel="nofollow" class="result__a" href="//duckduckgo.com/l/?uddg=https%3A%2F%2Fnodejs.org%2Fen%2Fdocs&rut=abc">Node.js <b>Docs</b></a>
20
- </h2>
21
- <a rel="nofollow" class="result__snippet" href="//duckduckgo.com/l/?uddg=...">Official documentation for Node.js &amp; npm.</a>
22
- </div>
23
- <div class="result">
24
- <a rel="nofollow" class="result__a" href="https://example.com/page">Example &quot;quoted&quot; page</a>
25
- <a rel="nofollow" class="result__snippet" href="https://example.com/page">A plain snippet without highlight.</a>
26
- </div>
27
- </body>
28
- </html>`;
29
-
30
- const WEB_FETCH_HTML = `<!DOCTYPE html>
31
- <html><head><title>Fetch &amp; Me</title></head>
32
- <body>
33
- <style>.x { color: red }</style>
34
- <script>alert("x")</script>
35
- <h1>Hello World</h1>
36
- <p>First paragraph with &amp; entity.</p>
37
- <p>Second paragraph<br>with a break.</p>
38
- <footer>bye</footer>
39
- </body></html>`;
40
-
41
- describe("parseDuckDuckGoHtml", () => {
42
- it("extracts title, decoded url, and snippet per result", () => {
43
- const results = parseDuckDuckGoHtml(DDG_HTML);
44
- expect(results).toHaveLength(2);
45
- expect(results[0]).toEqual({
46
- title: "Node.js Docs",
47
- url: "https://nodejs.org/en/docs",
48
- snippet: "Official documentation for Node.js & npm.",
49
- });
50
- expect(results[1]).toEqual({
51
- title: 'Example "quoted" page',
52
- url: "https://example.com/page",
53
- snippet: "A plain snippet without highlight.",
54
- });
55
- });
56
-
57
- it("returns empty array for pages without results", () => {
58
- expect(parseDuckDuckGoHtml("<html><body>no results here</body></html>")).toEqual([]);
59
- });
60
- });
61
-
62
- describe("decodeDdgHref", () => {
63
- it("decodes uddg redirect params and restores protocol", () => {
64
- expect(
65
- decodeDdgHref("//duckduckgo.com/l/?uddg=https%3A%2F%2Fnodejs.org%2Fen%2Fdocs&rut=abc"),
66
- ).toBe("https://nodejs.org/en/docs");
67
- });
68
-
69
- it("passes through plain urls", () => {
70
- expect(decodeDdgHref("https://example.com/a?b=1")).toBe("https://example.com/a?b=1");
71
- });
72
-
73
- it("returns input untouched when unparseable", () => {
74
- expect(decodeDdgHref("not a url")).toBe("not a url");
75
- });
76
- });
77
-
78
- describe("htmlToPlainText", () => {
79
- it("strips scripts/styles, keeps block boundaries, decodes entities, compresses whitespace", () => {
80
- const text = htmlToPlainText(WEB_FETCH_HTML);
81
- expect(text).not.toContain("alert");
82
- expect(text).not.toContain(".x {");
83
- expect(text).toContain("Hello World");
84
- expect(text).toContain("First paragraph with & entity.");
85
- expect(text).toContain("Second paragraph\nwith a break.");
86
- expect(text).toContain("bye");
87
- // 段落间允许单个空行,但不得出现连续空行
88
- expect(text).not.toContain("\n\n\n");
89
- });
90
- });
91
-
92
- describe("extractHtmlTitle", () => {
93
- it("extracts and decodes the title", () => {
94
- expect(extractHtmlTitle(WEB_FETCH_HTML)).toBe("Fetch & Me");
95
- });
96
-
97
- it("returns empty string when no title", () => {
98
- expect(extractHtmlTitle("<html><body>x</body></html>")).toBe("");
99
- });
100
- });
101
-
102
- describe("webSearchForTool", () => {
103
- function mockFetch(html: string, status = 200): FetchLike {
104
- return async () =>
105
- new Response(html, {
106
- status,
107
- headers: { "content-type": "text/html; charset=utf-8" },
108
- });
109
- }
110
-
111
- it("returns parsed results from the search page", async () => {
112
- const output = await webSearchForTool(
113
- { query: "nodejs docs" },
114
- { fetchImpl: mockFetch(DDG_HTML) },
115
- );
116
- expect(output.query).toBe("nodejs docs");
117
- expect(output.results).toHaveLength(2);
118
- expect(output.results[0].url).toBe("https://nodejs.org/en/docs");
119
- expect(output.results[0].snippet).toContain("Node.js");
120
- expect(typeof output.durationMs).toBe("number");
121
- });
122
-
123
- it("respects maxResults and caps at 10", async () => {
124
- const html = Array.from({ length: 12 }, (_, i) => {
125
- const title = `<a rel="nofollow" class="result__a" href="https://e.com/${i}">R${i}</a>`;
126
- const snippet = `<a rel="nofollow" class="result__snippet" href="https://e.com/${i}">s${i}</a>`;
127
- return `<div>${title}${snippet}</div>`;
128
- }).join("");
129
- const output = await webSearchForTool({ query: "many" }, { fetchImpl: mockFetch(html) });
130
- expect(output.results).toHaveLength(5);
131
- expect(output.truncated).toBe(true);
132
-
133
- const output2 = await webSearchForTool({ query: "many", maxResults: 20 }, { fetchImpl: mockFetch(html) });
134
- expect(output2.results).toHaveLength(10);
135
- });
136
-
137
- it("returns empty results (not an error) when the page has no matches", async () => {
138
- const output = await webSearchForTool(
139
- { query: "zzz-nothing" },
140
- { fetchImpl: mockFetch("<html><body>no results</body></html>") },
141
- );
142
- expect(output.results).toEqual([]);
143
- expect(output.truncated).toBe(false);
144
- });
145
-
146
- it("throws on empty query", async () => {
147
- await expect(webSearchForTool({ query: " " }, { fetchImpl: mockFetch("") })).rejects.toThrow(
148
- "query is required",
149
- );
150
- });
151
-
152
- it("throws on non-2xx response", async () => {
153
- await expect(
154
- webSearchForTool({ query: "x" }, { fetchImpl: mockFetch("blocked", 429) }),
155
- ).rejects.toThrow("HTTP 429");
156
- });
157
- });
158
-
159
- describe("webFetchForTool", () => {
160
- function mockFetch(html: string, status = 200, contentType = "text/html; charset=utf-8"): FetchLike {
161
- return async () =>
162
- new Response(html, {
163
- status,
164
- headers: { "content-type": contentType },
165
- });
166
- }
167
-
168
- it("fetches and converts html to plain text with title", async () => {
169
- const output = await webFetchForTool(
170
- { url: "https://example.com/page" },
171
- { fetchImpl: mockFetch(WEB_FETCH_HTML) },
172
- );
173
- expect(output.title).toBe("Fetch & Me");
174
- expect(output.text).toContain("Hello World");
175
- expect(output.text).toContain("Second paragraph\nwith a break.");
176
- expect(output.text).not.toContain("alert");
177
- expect(output.contentType).toContain("text/html");
178
- expect(output.chars).toBe(output.text.length);
179
- expect(output.truncated).toBe(false);
180
- });
181
-
182
- it("truncates text beyond maxChars", async () => {
183
- const output = await webFetchForTool(
184
- { url: "https://example.com/long", maxChars: 10 },
185
- { fetchImpl: mockFetch("<p>hello world and more</p>") },
186
- );
187
- expect(output.text).toBe("hello worl");
188
- expect(output.truncated).toBe(true);
189
- expect(output.chars).toBe(10);
190
- });
191
-
192
- it("truncates oversized response bodies", async () => {
193
- const big = `<!DOCTYPE html><html><body><p>${"a".repeat(600 * 1024)}</p></body></html>`;
194
- const output = await webFetchForTool(
195
- { url: "https://example.com/big" },
196
- { fetchImpl: mockFetch(big) },
197
- );
198
- expect(output.truncated).toBe(true);
199
- expect(output.text.length).toBeLessThan(600 * 1024);
200
- });
201
-
202
- it("rejects non-http(s) protocols", async () => {
203
- await expect(
204
- webFetchForTool({ url: "file:///etc/passwd" }, { fetchImpl: mockFetch("") }),
205
- ).rejects.toThrow("unsupported protocol");
206
- });
207
-
208
- it("rejects invalid urls", async () => {
209
- await expect(
210
- webFetchForTool({ url: "not a url" }, { fetchImpl: mockFetch("") }),
211
- ).rejects.toThrow("invalid URL");
212
- });
213
-
214
- it("throws on missing url and non-2xx", async () => {
215
- await expect(webFetchForTool({ url: " " })).rejects.toThrow("url is required");
216
- await expect(
217
- webFetchForTool({ url: "https://example.com/404" }, { fetchImpl: mockFetch("nf", 404) }),
218
- ).rejects.toThrow("HTTP 404");
219
- });
220
- });
1
+ import { describe, expect, it } from "vitest";
2
+
3
+ import {
4
+ decodeDdgHref,
5
+ extractHtmlTitle,
6
+ htmlToPlainText,
7
+ parseDuckDuckGoHtml,
8
+ webFetchForTool,
9
+ webSearchForTool,
10
+ type FetchLike,
11
+ } from "../web-tools.js";
12
+
13
+ const DDG_HTML = `<!DOCTYPE html>
14
+ <html>
15
+ <head><title>Search results</title></head>
16
+ <body>
17
+ <div class="result results_links results_links_deep web-result">
18
+ <h2 class="result__title">
19
+ <a rel="nofollow" class="result__a" href="//duckduckgo.com/l/?uddg=https%3A%2F%2Fnodejs.org%2Fen%2Fdocs&rut=abc">Node.js <b>Docs</b></a>
20
+ </h2>
21
+ <a rel="nofollow" class="result__snippet" href="//duckduckgo.com/l/?uddg=...">Official documentation for Node.js &amp; npm.</a>
22
+ </div>
23
+ <div class="result">
24
+ <a rel="nofollow" class="result__a" href="https://example.com/page">Example &quot;quoted&quot; page</a>
25
+ <a rel="nofollow" class="result__snippet" href="https://example.com/page">A plain snippet without highlight.</a>
26
+ </div>
27
+ </body>
28
+ </html>`;
29
+
30
+ const WEB_FETCH_HTML = `<!DOCTYPE html>
31
+ <html><head><title>Fetch &amp; Me</title></head>
32
+ <body>
33
+ <style>.x { color: red }</style>
34
+ <script>alert("x")</script>
35
+ <h1>Hello World</h1>
36
+ <p>First paragraph with &amp; entity.</p>
37
+ <p>Second paragraph<br>with a break.</p>
38
+ <footer>bye</footer>
39
+ </body></html>`;
40
+
41
+ describe("parseDuckDuckGoHtml", () => {
42
+ it("extracts title, decoded url, and snippet per result", () => {
43
+ const results = parseDuckDuckGoHtml(DDG_HTML);
44
+ expect(results).toHaveLength(2);
45
+ expect(results[0]).toEqual({
46
+ title: "Node.js Docs",
47
+ url: "https://nodejs.org/en/docs",
48
+ snippet: "Official documentation for Node.js & npm.",
49
+ });
50
+ expect(results[1]).toEqual({
51
+ title: 'Example "quoted" page',
52
+ url: "https://example.com/page",
53
+ snippet: "A plain snippet without highlight.",
54
+ });
55
+ });
56
+
57
+ it("returns empty array for pages without results", () => {
58
+ expect(parseDuckDuckGoHtml("<html><body>no results here</body></html>")).toEqual([]);
59
+ });
60
+ });
61
+
62
+ describe("decodeDdgHref", () => {
63
+ it("decodes uddg redirect params and restores protocol", () => {
64
+ expect(
65
+ decodeDdgHref("//duckduckgo.com/l/?uddg=https%3A%2F%2Fnodejs.org%2Fen%2Fdocs&rut=abc"),
66
+ ).toBe("https://nodejs.org/en/docs");
67
+ });
68
+
69
+ it("passes through plain urls", () => {
70
+ expect(decodeDdgHref("https://example.com/a?b=1")).toBe("https://example.com/a?b=1");
71
+ });
72
+
73
+ it("returns input untouched when unparseable", () => {
74
+ expect(decodeDdgHref("not a url")).toBe("not a url");
75
+ });
76
+ });
77
+
78
+ describe("htmlToPlainText", () => {
79
+ it("strips scripts/styles, keeps block boundaries, decodes entities, compresses whitespace", () => {
80
+ const text = htmlToPlainText(WEB_FETCH_HTML);
81
+ expect(text).not.toContain("alert");
82
+ expect(text).not.toContain(".x {");
83
+ expect(text).toContain("Hello World");
84
+ expect(text).toContain("First paragraph with & entity.");
85
+ expect(text).toContain("Second paragraph\nwith a break.");
86
+ expect(text).toContain("bye");
87
+ // 段落间允许单个空行,但不得出现连续空行
88
+ expect(text).not.toContain("\n\n\n");
89
+ });
90
+ });
91
+
92
+ describe("extractHtmlTitle", () => {
93
+ it("extracts and decodes the title", () => {
94
+ expect(extractHtmlTitle(WEB_FETCH_HTML)).toBe("Fetch & Me");
95
+ });
96
+
97
+ it("returns empty string when no title", () => {
98
+ expect(extractHtmlTitle("<html><body>x</body></html>")).toBe("");
99
+ });
100
+ });
101
+
102
+ describe("webSearchForTool", () => {
103
+ function mockFetch(html: string, status = 200): FetchLike {
104
+ return async () =>
105
+ new Response(html, {
106
+ status,
107
+ headers: { "content-type": "text/html; charset=utf-8" },
108
+ });
109
+ }
110
+
111
+ it("returns parsed results from the search page", async () => {
112
+ const output = await webSearchForTool(
113
+ { query: "nodejs docs" },
114
+ { fetchImpl: mockFetch(DDG_HTML) },
115
+ );
116
+ expect(output.query).toBe("nodejs docs");
117
+ expect(output.results).toHaveLength(2);
118
+ expect(output.results[0].url).toBe("https://nodejs.org/en/docs");
119
+ expect(output.results[0].snippet).toContain("Node.js");
120
+ expect(typeof output.durationMs).toBe("number");
121
+ });
122
+
123
+ it("respects maxResults and caps at 10", async () => {
124
+ const html = Array.from({ length: 12 }, (_, i) => {
125
+ const title = `<a rel="nofollow" class="result__a" href="https://e.com/${i}">R${i}</a>`;
126
+ const snippet = `<a rel="nofollow" class="result__snippet" href="https://e.com/${i}">s${i}</a>`;
127
+ return `<div>${title}${snippet}</div>`;
128
+ }).join("");
129
+ const output = await webSearchForTool({ query: "many" }, { fetchImpl: mockFetch(html) });
130
+ expect(output.results).toHaveLength(5);
131
+ expect(output.truncated).toBe(true);
132
+
133
+ const output2 = await webSearchForTool({ query: "many", maxResults: 20 }, { fetchImpl: mockFetch(html) });
134
+ expect(output2.results).toHaveLength(10);
135
+ });
136
+
137
+ it("returns empty results (not an error) when the page has no matches", async () => {
138
+ const output = await webSearchForTool(
139
+ { query: "zzz-nothing" },
140
+ { fetchImpl: mockFetch("<html><body>no results</body></html>") },
141
+ );
142
+ expect(output.results).toEqual([]);
143
+ expect(output.truncated).toBe(false);
144
+ });
145
+
146
+ it("throws on empty query", async () => {
147
+ await expect(webSearchForTool({ query: " " }, { fetchImpl: mockFetch("") })).rejects.toThrow(
148
+ "query is required",
149
+ );
150
+ });
151
+
152
+ it("throws on non-2xx response", async () => {
153
+ await expect(
154
+ webSearchForTool({ query: "x" }, { fetchImpl: mockFetch("blocked", 429) }),
155
+ ).rejects.toThrow("HTTP 429");
156
+ });
157
+ });
158
+
159
+ describe("webFetchForTool", () => {
160
+ function mockFetch(html: string, status = 200, contentType = "text/html; charset=utf-8"): FetchLike {
161
+ return async () =>
162
+ new Response(html, {
163
+ status,
164
+ headers: { "content-type": contentType },
165
+ });
166
+ }
167
+
168
+ it("fetches and converts html to plain text with title", async () => {
169
+ const output = await webFetchForTool(
170
+ { url: "https://example.com/page" },
171
+ { fetchImpl: mockFetch(WEB_FETCH_HTML) },
172
+ );
173
+ expect(output.title).toBe("Fetch & Me");
174
+ expect(output.text).toContain("Hello World");
175
+ expect(output.text).toContain("Second paragraph\nwith a break.");
176
+ expect(output.text).not.toContain("alert");
177
+ expect(output.contentType).toContain("text/html");
178
+ expect(output.chars).toBe(output.text.length);
179
+ expect(output.truncated).toBe(false);
180
+ });
181
+
182
+ it("truncates text beyond maxChars", async () => {
183
+ const output = await webFetchForTool(
184
+ { url: "https://example.com/long", maxChars: 10 },
185
+ { fetchImpl: mockFetch("<p>hello world and more</p>") },
186
+ );
187
+ expect(output.text).toBe("hello worl");
188
+ expect(output.truncated).toBe(true);
189
+ expect(output.chars).toBe(10);
190
+ });
191
+
192
+ it("truncates oversized response bodies", async () => {
193
+ const big = `<!DOCTYPE html><html><body><p>${"a".repeat(600 * 1024)}</p></body></html>`;
194
+ const output = await webFetchForTool(
195
+ { url: "https://example.com/big" },
196
+ { fetchImpl: mockFetch(big) },
197
+ );
198
+ expect(output.truncated).toBe(true);
199
+ expect(output.text.length).toBeLessThan(600 * 1024);
200
+ });
201
+
202
+ it("rejects non-http(s) protocols", async () => {
203
+ await expect(
204
+ webFetchForTool({ url: "file:///etc/passwd" }, { fetchImpl: mockFetch("") }),
205
+ ).rejects.toThrow("unsupported protocol");
206
+ });
207
+
208
+ it("rejects invalid urls", async () => {
209
+ await expect(
210
+ webFetchForTool({ url: "not a url" }, { fetchImpl: mockFetch("") }),
211
+ ).rejects.toThrow("invalid URL");
212
+ });
213
+
214
+ it("throws on missing url and non-2xx", async () => {
215
+ await expect(webFetchForTool({ url: " " })).rejects.toThrow("url is required");
216
+ await expect(
217
+ webFetchForTool({ url: "https://example.com/404" }, { fetchImpl: mockFetch("nf", 404) }),
218
+ ).rejects.toThrow("HTTP 404");
219
+ });
220
+ });
@@ -11,8 +11,8 @@
11
11
  * 非 TTY(管道/CI)或 --plain 回退为纯文本流式输出;--stream-json 机器接口不变。
12
12
  */
13
13
 
14
- import * as readline from "node:readline";
15
- import * as process from "node:process";
14
+ import * as readline from "node:readline";
15
+ import process from "node:process";
16
16
  import { appendFileSync, existsSync, mkdirSync, writeFileSync } from "node:fs";
17
17
  import { homedir } from "node:os";
18
18
  import { join, resolve as resolvePath } from "node:path";
@@ -637,10 +637,11 @@ async function main(): Promise<void> {
637
637
 
638
638
  const args = parseArgs();
639
639
 
640
- if (args.streamJson) {
641
- const code = await runStreamJson(args);
642
- process.exit(code);
643
- }
640
+ if (args.streamJson) {
641
+ const code = await runStreamJson(args);
642
+ process.exitCode = code;
643
+ return;
644
+ }
644
645
 
645
646
  if (args.help) {
646
647
  const { appConfig } = await loadRuntime();
@@ -1,84 +1,88 @@
1
- import { existsSync, mkdirSync, readFileSync } from "node:fs";
2
- import { homedir } from "node:os";
3
- import { dirname, join } from "node:path";
4
-
5
- export interface DeepCccConfig {
6
- apiKey: string;
7
- baseURL: string;
8
- model: string;
9
- /** Reasoning effort(none/minimal/low/medium/high/xhigh/max),留空不传 reasoning_effort */
10
- effort: string;
11
- rawStreamLogs: {
12
- enabled: boolean;
13
- maxBytesPerTurn: number;
14
- retentionDays: number;
15
- keepCompleted: boolean;
16
- };
17
- }
18
-
19
- export const DEEPCCC_HOME = join(homedir(), ".deepccc");
20
- export const RAW_STREAM_LOGS_DIR = join(DEEPCCC_HOME, "raw-stream-logs");
21
- const CONFIG_PATH = join(DEEPCCC_HOME, "config.json");
22
-
23
- const DEFAULT_CONFIG: DeepCccConfig = {
24
- apiKey: "",
25
- baseURL: "https://api.deepseek.com/v1",
26
- model: "deepseek-v4-pro",
27
- effort: "",
28
- rawStreamLogs: {
29
- enabled: false,
30
- maxBytesPerTurn: 1024 * 1024,
31
- retentionDays: 7,
32
- keepCompleted: false,
33
- },
34
- };
35
-
36
- function readConfigFile(): Partial<DeepCccConfig> {
37
- if (!existsSync(CONFIG_PATH)) return {};
38
- const raw = JSON.parse(readFileSync(CONFIG_PATH, "utf8")) as Partial<DeepCccConfig>;
39
- return raw && typeof raw === "object" ? raw : {};
40
- }
41
-
42
- function env(name: string): string | undefined {
43
- const value = process.env[name]?.trim();
44
- return value ? value : undefined;
45
- }
46
-
47
- function boolEnv(name: string): boolean | undefined {
48
- const value = env(name)?.toLowerCase();
49
- if (value === undefined) return undefined;
50
- if (["1", "true", "yes", "on"].includes(value)) return true;
51
- if (["0", "false", "no", "off"].includes(value)) return false;
52
- return undefined;
53
- }
54
-
55
- function numberEnv(name: string): number | undefined {
56
- const value = Number(env(name));
57
- return Number.isFinite(value) && value >= 0 ? value : undefined;
58
- }
59
-
60
- function loadConfig(): DeepCccConfig {
61
- const file = readConfigFile();
62
- const rawLogs: Partial<DeepCccConfig["rawStreamLogs"]> = file.rawStreamLogs && typeof file.rawStreamLogs === "object"
63
- ? file.rawStreamLogs
64
- : {};
65
-
66
- return {
67
- apiKey: env("DEEPCCC_API_KEY") ?? env("DEEPSEEK_API_KEY") ?? file.apiKey ?? DEFAULT_CONFIG.apiKey,
68
- baseURL: env("DEEPCCC_BASE_URL") ?? env("DEEPSEEK_BASE_URL") ?? file.baseURL ?? DEFAULT_CONFIG.baseURL,
69
- model: env("DEEPCCC_MODEL") ?? env("DEEPSEEK_MODEL") ?? file.model ?? DEFAULT_CONFIG.model,
70
- effort: env("DEEPCCC_EFFORT") ?? env("DEEPSEEK_EFFORT") ?? file.effort ?? DEFAULT_CONFIG.effort,
71
- rawStreamLogs: {
72
- enabled: boolEnv("DEEPCCC_RAW_STREAM_LOGS") ?? rawLogs.enabled ?? DEFAULT_CONFIG.rawStreamLogs.enabled,
73
- maxBytesPerTurn: numberEnv("DEEPCCC_RAW_STREAM_MAX_BYTES") ?? rawLogs.maxBytesPerTurn ?? DEFAULT_CONFIG.rawStreamLogs.maxBytesPerTurn,
74
- retentionDays: numberEnv("DEEPCCC_RAW_STREAM_RETENTION_DAYS") ?? rawLogs.retentionDays ?? DEFAULT_CONFIG.rawStreamLogs.retentionDays,
75
- keepCompleted: boolEnv("DEEPCCC_RAW_STREAM_KEEP_COMPLETED") ?? rawLogs.keepCompleted ?? DEFAULT_CONFIG.rawStreamLogs.keepCompleted,
76
- },
77
- };
78
- }
79
-
80
- export function ensureConfigDir(): void {
81
- mkdirSync(dirname(CONFIG_PATH), { recursive: true });
82
- }
83
-
84
- export const config = loadConfig();
1
+ import { existsSync, mkdirSync, readFileSync } from "node:fs";
2
+ import { homedir } from "node:os";
3
+ import { dirname, join } from "node:path";
4
+
5
+ export interface DeepCccConfig {
6
+ apiKey: string;
7
+ baseURL: string;
8
+ model: string;
9
+ /** Reasoning effort(none/minimal/low/medium/high/xhigh/max),留空不传 reasoning_effort */
10
+ effort: string;
11
+ /** 主对话是否使用流式请求;默认开启 */
12
+ streaming: boolean;
13
+ rawStreamLogs: {
14
+ enabled: boolean;
15
+ maxBytesPerTurn: number;
16
+ retentionDays: number;
17
+ keepCompleted: boolean;
18
+ };
19
+ }
20
+
21
+ export const DEEPCCC_HOME = join(homedir(), ".deepccc");
22
+ export const RAW_STREAM_LOGS_DIR = join(DEEPCCC_HOME, "raw-stream-logs");
23
+ const CONFIG_PATH = join(DEEPCCC_HOME, "config.json");
24
+
25
+ const DEFAULT_CONFIG: DeepCccConfig = {
26
+ apiKey: "",
27
+ baseURL: "https://api.deepseek.com/v1",
28
+ model: "deepseek-v4-pro",
29
+ effort: "",
30
+ streaming: true,
31
+ rawStreamLogs: {
32
+ enabled: false,
33
+ maxBytesPerTurn: 1024 * 1024,
34
+ retentionDays: 7,
35
+ keepCompleted: false,
36
+ },
37
+ };
38
+
39
+ function readConfigFile(): Partial<DeepCccConfig> {
40
+ if (!existsSync(CONFIG_PATH)) return {};
41
+ const raw = JSON.parse(readFileSync(CONFIG_PATH, "utf8")) as Partial<DeepCccConfig>;
42
+ return raw && typeof raw === "object" ? raw : {};
43
+ }
44
+
45
+ function env(name: string): string | undefined {
46
+ const value = process.env[name]?.trim();
47
+ return value ? value : undefined;
48
+ }
49
+
50
+ function boolEnv(name: string): boolean | undefined {
51
+ const value = env(name)?.toLowerCase();
52
+ if (value === undefined) return undefined;
53
+ if (["1", "true", "yes", "on"].includes(value)) return true;
54
+ if (["0", "false", "no", "off"].includes(value)) return false;
55
+ return undefined;
56
+ }
57
+
58
+ function numberEnv(name: string): number | undefined {
59
+ const value = Number(env(name));
60
+ return Number.isFinite(value) && value >= 0 ? value : undefined;
61
+ }
62
+
63
+ function loadConfig(): DeepCccConfig {
64
+ const file = readConfigFile();
65
+ const rawLogs: Partial<DeepCccConfig["rawStreamLogs"]> = file.rawStreamLogs && typeof file.rawStreamLogs === "object"
66
+ ? file.rawStreamLogs
67
+ : {};
68
+
69
+ return {
70
+ apiKey: env("DEEPCCC_API_KEY") ?? env("DEEPSEEK_API_KEY") ?? file.apiKey ?? DEFAULT_CONFIG.apiKey,
71
+ baseURL: env("DEEPCCC_BASE_URL") ?? env("DEEPSEEK_BASE_URL") ?? file.baseURL ?? DEFAULT_CONFIG.baseURL,
72
+ model: env("DEEPCCC_MODEL") ?? env("DEEPSEEK_MODEL") ?? file.model ?? DEFAULT_CONFIG.model,
73
+ effort: env("DEEPCCC_EFFORT") ?? env("DEEPSEEK_EFFORT") ?? file.effort ?? DEFAULT_CONFIG.effort,
74
+ streaming: boolEnv("DEEPCCC_STREAMING") ?? file.streaming ?? DEFAULT_CONFIG.streaming,
75
+ rawStreamLogs: {
76
+ enabled: boolEnv("DEEPCCC_RAW_STREAM_LOGS") ?? rawLogs.enabled ?? DEFAULT_CONFIG.rawStreamLogs.enabled,
77
+ maxBytesPerTurn: numberEnv("DEEPCCC_RAW_STREAM_MAX_BYTES") ?? rawLogs.maxBytesPerTurn ?? DEFAULT_CONFIG.rawStreamLogs.maxBytesPerTurn,
78
+ retentionDays: numberEnv("DEEPCCC_RAW_STREAM_RETENTION_DAYS") ?? rawLogs.retentionDays ?? DEFAULT_CONFIG.rawStreamLogs.retentionDays,
79
+ keepCompleted: boolEnv("DEEPCCC_RAW_STREAM_KEEP_COMPLETED") ?? rawLogs.keepCompleted ?? DEFAULT_CONFIG.rawStreamLogs.keepCompleted,
80
+ },
81
+ };
82
+ }
83
+
84
+ export function ensureConfigDir(): void {
85
+ mkdirSync(dirname(CONFIG_PATH), { recursive: true });
86
+ }
87
+
88
+ export const config = loadConfig();