chatccc 0.2.243 → 0.2.244

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 (59) hide show
  1. package/README.md +5 -5
  2. package/bin/cccagent.mjs +17 -17
  3. package/deepccc-agent/bin/deepccc.mjs +26 -26
  4. package/deepccc-agent/os-prompts/darwin.md +8 -0
  5. package/deepccc-agent/os-prompts/linux.md +8 -0
  6. package/deepccc-agent/os-prompts/win32.md +9 -0
  7. package/deepccc-agent/package.json +2 -1
  8. package/deepccc-agent/src/__tests__/chat-session.test.ts +39 -0
  9. package/deepccc-agent/src/__tests__/cli-json.test.ts +49 -49
  10. package/deepccc-agent/src/__tests__/config.test.ts +26 -26
  11. package/deepccc-agent/src/__tests__/context.test.ts +319 -319
  12. package/deepccc-agent/src/__tests__/file-tools.test.ts +240 -240
  13. package/deepccc-agent/src/__tests__/permissions.test.ts +195 -195
  14. package/deepccc-agent/src/__tests__/progress-reducer.test.ts +121 -121
  15. package/deepccc-agent/src/__tests__/session-search.test.ts +262 -262
  16. package/deepccc-agent/src/__tests__/session-select.test.ts +116 -116
  17. package/deepccc-agent/src/__tests__/sigint.test.ts +56 -56
  18. package/deepccc-agent/src/__tests__/skills.test.ts +284 -284
  19. package/deepccc-agent/src/__tests__/terminal-renderer.test.ts +247 -247
  20. package/deepccc-agent/src/__tests__/web-tools.test.ts +220 -220
  21. package/deepccc-agent/src/config.ts +84 -84
  22. package/deepccc-agent/src/context.ts +465 -465
  23. package/deepccc-agent/src/file-log.ts +38 -38
  24. package/deepccc-agent/src/index.ts +46 -16
  25. package/deepccc-agent/src/proc-tree-kill.ts +61 -61
  26. package/deepccc-agent/src/progress/cards-helpers.ts +76 -76
  27. package/deepccc-agent/src/progress/reducer.ts +113 -113
  28. package/deepccc-agent/src/progress/terminal-renderer.ts +294 -294
  29. package/deepccc-agent/src/progress/view.ts +77 -77
  30. package/deepccc-agent/src/raw-stream-log.ts +124 -124
  31. package/deepccc-agent/src/session-search.ts +370 -370
  32. package/deepccc-agent/src/session-select.ts +48 -48
  33. package/deepccc-agent/src/sigint.ts +50 -50
  34. package/deepccc-agent/src/skills.ts +205 -205
  35. package/deepccc-agent/src/web-tools.ts +313 -313
  36. package/deepccc-agent/tsconfig.build.json +13 -13
  37. package/deepccc-agent/tsconfig.json +13 -13
  38. package/deepccc-agent/vitest.config.ts +7 -7
  39. package/package.json +73 -73
  40. package/src/__tests__/builtin-chat-session.test.ts +522 -522
  41. package/src/__tests__/builtin-config.test.ts +26 -26
  42. package/src/__tests__/builtin-context.test.ts +319 -319
  43. package/src/__tests__/builtin-file-tools.test.ts +240 -240
  44. package/src/__tests__/builtin-permissions.test.ts +211 -211
  45. package/src/__tests__/builtin-session-search.test.ts +262 -262
  46. package/src/__tests__/builtin-session-select.test.ts +116 -116
  47. package/src/__tests__/builtin-sigint.test.ts +56 -56
  48. package/src/__tests__/builtin-skills.test.ts +284 -284
  49. package/src/__tests__/builtin-web-tools.test.ts +220 -220
  50. package/src/__tests__/config.test.ts +17 -17
  51. package/src/__tests__/progress-reducer.test.ts +121 -121
  52. package/src/__tests__/session-ccc-config.test.ts +45 -45
  53. package/src/__tests__/session.test.ts +298 -298
  54. package/src/adapters/ccc-adapter.ts +145 -145
  55. package/src/config-utils.ts +13 -13
  56. package/src/config.ts +13 -13
  57. package/src/progress/reducer.ts +113 -113
  58. package/src/session-chat-binding.ts +83 -83
  59. package/src/session.ts +311 -311
package/README.md CHANGED
@@ -210,11 +210,11 @@ chatccc
210
210
 
211
211
  Claude Code、Cursor 和 Codex 需要对应的本地工具;CCC Agent 内置于 ChatCCC,开箱即用,**模型接入不限于 DeepSeek**——它走 OpenAI 兼容协议,任意兼容端点都可以直接替换(详见下文 CCC Agent)。
212
212
 
213
- #### CCC Agent
214
-
215
- CCC Agent 是 ChatCCC 内置的编程 Agent,不需要额外安装 CLI,开箱即用。在首次配置向导或 Web 管理页中启用后,填写 API Key、Base URL 和模型即可使用;它可以设为 `/new` 的默认 Agent,也可以通过 `/new ccc` 显式创建会话。
216
-
217
- ChatCCC 会把 `ccc.DEEPSEEK_API_KEY`、`ccc.DEEPSEEK_BASE_URL`、模型和 effort 显式传给内置 Agent;这些配置不会回退读取 `~/.deepccc/config.json`,因此用户无需安装或配置独立的 `deepccc` 包。API Key 为空时 CCC Agent 会自动保持禁用。
213
+ #### CCC Agent
214
+
215
+ CCC Agent 是 ChatCCC 内置的编程 Agent,不需要额外安装 CLI,开箱即用。在首次配置向导或 Web 管理页中启用后,填写 API Key、Base URL 和模型即可使用;它可以设为 `/new` 的默认 Agent,也可以通过 `/new ccc` 显式创建会话。
216
+
217
+ ChatCCC 会把 `ccc.DEEPSEEK_API_KEY`、`ccc.DEEPSEEK_BASE_URL`、模型和 effort 显式传给内置 Agent;这些配置不会回退读取 `~/.deepccc/config.json`,因此用户无需安装或配置独立的 `deepccc` 包。API Key 为空时 CCC Agent 会自动保持禁用。
218
218
 
219
219
  **API 支持不限于 DeepSeek。** CCC Agent 底层使用 OpenAI 兼容协议(`@ai-sdk/openai-compatible`),DeepSeek 只是出厂默认端点。`ccc.DEEPSEEK_API_KEY` / `ccc.DEEPSEEK_BASE_URL` 可以指向**任意 OpenAI 兼容服务**,例如:
220
220
 
package/bin/cccagent.mjs CHANGED
@@ -1,17 +1,17 @@
1
- #!/usr/bin/env node
2
- import { spawnSync } from "node:child_process";
3
- import { createRequire } from "node:module";
4
- import { dirname, join } from "node:path";
5
-
6
- const require = createRequire(import.meta.url);
7
- const pkgRoot = dirname(require.resolve("../package.json"));
8
- const cliTs = join(pkgRoot, "deepccc-agent", "src", "cli.ts");
9
- const tsxCli = require.resolve("tsx/cli");
10
-
11
- const result = spawnSync(process.execPath, [tsxCli, cliTs, ...process.argv.slice(2)], {
12
- stdio: "inherit",
13
- cwd: process.cwd(),
14
- env: process.env,
15
- });
16
-
17
- process.exit(result.status === null ? 1 : result.status);
1
+ #!/usr/bin/env node
2
+ import { spawnSync } from "node:child_process";
3
+ import { createRequire } from "node:module";
4
+ import { dirname, join } from "node:path";
5
+
6
+ const require = createRequire(import.meta.url);
7
+ const pkgRoot = dirname(require.resolve("../package.json"));
8
+ const cliTs = join(pkgRoot, "deepccc-agent", "src", "cli.ts");
9
+ const tsxCli = require.resolve("tsx/cli");
10
+
11
+ const result = spawnSync(process.execPath, [tsxCli, cliTs, ...process.argv.slice(2)], {
12
+ stdio: "inherit",
13
+ cwd: process.cwd(),
14
+ env: process.env,
15
+ });
16
+
17
+ process.exit(result.status === null ? 1 : result.status);
@@ -1,26 +1,26 @@
1
- #!/usr/bin/env node
2
- import { existsSync } from "node:fs";
3
- import { spawnSync } from "node:child_process";
4
- import { createRequire } from "node:module";
5
- import { dirname, join } from "node:path";
6
-
7
- const require = createRequire(import.meta.url);
8
- const pkgRoot = dirname(require.resolve("../package.json"));
9
- const distCli = join(pkgRoot, "dist", "cli.js");
10
-
11
- let args;
12
- if (existsSync(distCli)) {
13
- args = [distCli, ...process.argv.slice(2)];
14
- } else {
15
- const tsxCli = require.resolve("tsx/cli");
16
- const sourceCli = join(pkgRoot, "src", "cli.ts");
17
- args = [tsxCli, sourceCli, ...process.argv.slice(2)];
18
- }
19
-
20
- const result = spawnSync(process.execPath, args, {
21
- stdio: "inherit",
22
- cwd: process.cwd(),
23
- env: process.env,
24
- });
25
-
26
- process.exit(result.status === null ? 1 : result.status);
1
+ #!/usr/bin/env node
2
+ import { existsSync } from "node:fs";
3
+ import { spawnSync } from "node:child_process";
4
+ import { createRequire } from "node:module";
5
+ import { dirname, join } from "node:path";
6
+
7
+ const require = createRequire(import.meta.url);
8
+ const pkgRoot = dirname(require.resolve("../package.json"));
9
+ const distCli = join(pkgRoot, "dist", "cli.js");
10
+
11
+ let args;
12
+ if (existsSync(distCli)) {
13
+ args = [distCli, ...process.argv.slice(2)];
14
+ } else {
15
+ const tsxCli = require.resolve("tsx/cli");
16
+ const sourceCli = join(pkgRoot, "src", "cli.ts");
17
+ args = [tsxCli, sourceCli, ...process.argv.slice(2)];
18
+ }
19
+
20
+ const result = spawnSync(process.execPath, args, {
21
+ stdio: "inherit",
22
+ cwd: process.cwd(),
23
+ env: process.env,
24
+ });
25
+
26
+ process.exit(result.status === null ? 1 : result.status);
@@ -0,0 +1,8 @@
1
+ ## macOS Command-Line Notes
2
+
3
+ You are running on macOS. run_command executes through zsh, a POSIX shell. Quoting follows standard POSIX conventions (the same habits you already know from bash):
4
+
5
+ - Double quotes are stripped and group an argument containing spaces: `echo "hello world"` prints `hello world`.
6
+ - Single quotes are literal quoting characters: `'a b'` is a single argument `a b`.
7
+ - Backticks and `$(...)` perform command substitution; quote them if you need literal text.
8
+ - Paths use `/` separators and `~` expands to the home directory.
@@ -0,0 +1,8 @@
1
+ ## Linux Command-Line Notes
2
+
3
+ You are running on Linux. run_command executes through a POSIX shell (bash/sh). Quoting follows standard POSIX conventions (the same habits you already know from bash):
4
+
5
+ - Double quotes are stripped and group an argument containing spaces: `echo "hello world"` prints `hello world`.
6
+ - Single quotes are literal quoting characters: `'a b'` is a single argument `a b`.
7
+ - Backticks and `$(...)` perform command substitution; quote them if you need literal text.
8
+ - Paths use `/` separators and `~` expands to the home directory.
@@ -0,0 +1,9 @@
1
+ ## Windows Command-Line Notes
2
+
3
+ You are running on Windows. run_command executes through cmd.exe, not bash. cmd quoting differs from bash and breaks common habits:
4
+
5
+ - Double quotes are NOT stripped: `echo "hello world"` prints `"hello world"` (quotes included), and `"a b" "c"` passes the literal arguments `"a b"` and `"c"` (quotes included) to the program.
6
+ - Single quotes are NOT quoting characters in cmd.exe: `'a b'` is parsed as two arguments (`'a` and `b'`).
7
+ - Multi-line or quote-heavy inline scripts (python -c "...\n...", ssh host "bash -c '...'") frequently break under cmd quoting; write the script to a temporary file and execute that file instead.
8
+ - PowerShell-only syntax (Get-Item, 2>$null, Select-Object) is unavailable; the shell is cmd.exe unless you explicitly invoke powershell.
9
+ - To pass an argument containing spaces, use double quotes and expect the quotes to reach the program literally; when the target accepts file input, prefer writing the value to a file.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "deepccc",
3
- "version": "0.1.13",
3
+ "version": "0.1.14",
4
4
  "description": "A lightweight DeepSeek-optimized coding agent with OpenAI-compatible model support.",
5
5
  "license": "Apache-2.0",
6
6
  "keywords": [
@@ -34,6 +34,7 @@
34
34
  "dist/",
35
35
  "bin/",
36
36
  "docs/",
37
+ "os-prompts/",
37
38
  "package.json",
38
39
  "README.md",
39
40
  "LICENSE"
@@ -1,6 +1,7 @@
1
1
  import { mkdir, mkdtemp, readFile, writeFile } from "node:fs/promises";
2
2
  import { tmpdir } from "node:os";
3
3
  import { join } from "node:path";
4
+ import { fileURLToPath } from "node:url";
4
5
 
5
6
  import { afterEach, describe, expect, it, vi } from "vitest";
6
7
 
@@ -576,3 +577,41 @@ describe("estimateBuiltinContextTokens", () => {
576
577
  expect(estimate).toBeLessThan(340);
577
578
  });
578
579
  });
580
+
581
+ describe("loadPlatformCommandPrompt", () => {
582
+ // 测试文件在 src/__tests__/,上两级即包根 os-prompts/
583
+ const builtinDir = fileURLToPath(new URL("../../os-prompts/", import.meta.url));
584
+
585
+ async function loadPrompt(
586
+ platform: string,
587
+ dirs?: { builtinDir?: string; userDir?: string },
588
+ ): Promise<string> {
589
+ const mod = (await import("../index.js")) as {
590
+ loadPlatformCommandPrompt: (
591
+ platform: string,
592
+ dirs?: { builtinDir?: string; userDir?: string },
593
+ ) => string;
594
+ };
595
+ return mod.loadPlatformCommandPrompt(platform, dirs);
596
+ }
597
+
598
+ it("loads builtin guidance from os-prompts/<platform>.md", async () => {
599
+ const text = await loadPrompt("win32", { builtinDir });
600
+ expect(text).toContain("## Windows Command-Line Notes");
601
+ expect(text).toContain("cmd.exe");
602
+ expect(text).toMatch(/double quotes/i);
603
+ expect(text).toMatch(/single quotes/i);
604
+ });
605
+
606
+ it("prefers the user override at ~/.deepccc/prompts/<platform>.md", async () => {
607
+ const userDir = await mkdtemp(join(tmpdir(), "deepccc-os-prompt-override-"));
608
+ await writeFile(join(userDir, "win32.md"), "CUSTOM OVERRIDE GUIDANCE", "utf-8");
609
+ const text = await loadPrompt("win32", { builtinDir, userDir });
610
+ expect(text).toBe("CUSTOM OVERRIDE GUIDANCE");
611
+ });
612
+
613
+ it("returns empty string for unknown platforms or missing files", async () => {
614
+ expect(await loadPrompt("freebsd", { builtinDir })).toBe("");
615
+ expect(await loadPrompt("linux", { builtinDir: join(builtinDir, "missing") })).toBe("");
616
+ });
617
+ });
@@ -1,49 +1,49 @@
1
- import { execFile } from "node:child_process";
2
- import { existsSync } from "node:fs";
3
- import { dirname, join } from "node:path";
4
- import { fileURLToPath } from "node:url";
5
- import { promisify } from "node:util";
6
-
7
- import { describe, expect, it } from "vitest";
8
-
9
- const execFileAsync = promisify(execFile);
10
-
11
- // 兼容两种布局:<root>/deepccc-agent/src/__tests__(chatccc 子目录)或 <root>/src/__tests__(deepccc 镜像)
12
- const here = dirname(fileURLToPath(import.meta.url));
13
- const cliBin = [
14
- join(here, "..", "..", "..", "bin", "deepccc.mjs"),
15
- join(here, "..", "..", "bin", "deepccc.mjs"),
16
- ].find(existsSync);
17
-
18
- describe("deepccc cli --stream-json", () => {
19
- it("writes only JSON lines to stdout when startup fails", async () => {
20
- let caught: unknown;
21
- try {
22
- await execFileAsync(process.execPath, [
23
- cliBin!,
24
- "--stream-json",
25
- "--prompt",
26
- "hello",
27
- "--api-key",
28
- "",
29
- ], {
30
- cwd: dirname(cliBin!),
31
- timeout: 10_000,
32
- windowsHide: true,
33
- });
34
- } catch (err) {
35
- caught = err;
36
- }
37
-
38
- expect(caught).toMatchObject({ code: 1 });
39
- const stdout = (caught as { stdout?: string }).stdout ?? "";
40
- const lines = stdout.trim().split(/\r?\n/).filter(Boolean);
41
- expect(lines.length).toBeGreaterThan(0);
42
- for (const line of lines) {
43
- expect(() => JSON.parse(line)).not.toThrow();
44
- }
45
- expect(JSON.parse(lines.at(-1)!)).toEqual(expect.objectContaining({
46
- type: "error",
47
- }));
48
- });
49
- });
1
+ import { execFile } from "node:child_process";
2
+ import { existsSync } from "node:fs";
3
+ import { dirname, join } from "node:path";
4
+ import { fileURLToPath } from "node:url";
5
+ import { promisify } from "node:util";
6
+
7
+ import { describe, expect, it } from "vitest";
8
+
9
+ const execFileAsync = promisify(execFile);
10
+
11
+ // 兼容两种布局:<root>/deepccc-agent/src/__tests__(chatccc 子目录)或 <root>/src/__tests__(deepccc 镜像)
12
+ const here = dirname(fileURLToPath(import.meta.url));
13
+ const cliBin = [
14
+ join(here, "..", "..", "..", "bin", "deepccc.mjs"),
15
+ join(here, "..", "..", "bin", "deepccc.mjs"),
16
+ ].find(existsSync);
17
+
18
+ describe("deepccc cli --stream-json", () => {
19
+ it("writes only JSON lines to stdout when startup fails", async () => {
20
+ let caught: unknown;
21
+ try {
22
+ await execFileAsync(process.execPath, [
23
+ cliBin!,
24
+ "--stream-json",
25
+ "--prompt",
26
+ "hello",
27
+ "--api-key",
28
+ "",
29
+ ], {
30
+ cwd: dirname(cliBin!),
31
+ timeout: 10_000,
32
+ windowsHide: true,
33
+ });
34
+ } catch (err) {
35
+ caught = err;
36
+ }
37
+
38
+ expect(caught).toMatchObject({ code: 1 });
39
+ const stdout = (caught as { stdout?: string }).stdout ?? "";
40
+ const lines = stdout.trim().split(/\r?\n/).filter(Boolean);
41
+ expect(lines.length).toBeGreaterThan(0);
42
+ for (const line of lines) {
43
+ expect(() => JSON.parse(line)).not.toThrow();
44
+ }
45
+ expect(JSON.parse(lines.at(-1)!)).toEqual(expect.objectContaining({
46
+ type: "error",
47
+ }));
48
+ });
49
+ });
@@ -1,26 +1,26 @@
1
- import { afterEach, describe, expect, it } from "vitest";
2
-
3
- import { ChatSession } from "../index.js";
4
- import { config } from "../config.js";
5
-
6
- const originalDeepSeekApiKey = process.env.DEEPSEEK_API_KEY;
7
- const originalDeepCccApiKey = config.apiKey;
8
-
9
- afterEach(() => {
10
- if (originalDeepSeekApiKey === undefined) {
11
- delete process.env.DEEPSEEK_API_KEY;
12
- } else {
13
- process.env.DEEPSEEK_API_KEY = originalDeepSeekApiKey;
14
- }
15
- config.apiKey = originalDeepCccApiKey;
16
- });
17
-
18
- describe("builtin ChatSession config", () => {
19
- it("uses the builtin ~/.deepccc config when no apiKey is passed", () => {
20
- expect(() => new ChatSession()).not.toThrow();
21
- });
22
-
23
- it("allows constructor parameters to override config defaults", () => {
24
- expect(() => new ChatSession({ apiKey: "sk-test" })).not.toThrow();
25
- });
26
- });
1
+ import { afterEach, describe, expect, it } from "vitest";
2
+
3
+ import { ChatSession } from "../index.js";
4
+ import { config } from "../config.js";
5
+
6
+ const originalDeepSeekApiKey = process.env.DEEPSEEK_API_KEY;
7
+ const originalDeepCccApiKey = config.apiKey;
8
+
9
+ afterEach(() => {
10
+ if (originalDeepSeekApiKey === undefined) {
11
+ delete process.env.DEEPSEEK_API_KEY;
12
+ } else {
13
+ process.env.DEEPSEEK_API_KEY = originalDeepSeekApiKey;
14
+ }
15
+ config.apiKey = originalDeepCccApiKey;
16
+ });
17
+
18
+ describe("builtin ChatSession config", () => {
19
+ it("uses the builtin ~/.deepccc config when no apiKey is passed", () => {
20
+ expect(() => new ChatSession()).not.toThrow();
21
+ });
22
+
23
+ it("allows constructor parameters to override config defaults", () => {
24
+ expect(() => new ChatSession({ apiKey: "sk-test" })).not.toThrow();
25
+ });
26
+ });