czon 0.5.6 → 0.5.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/README.md CHANGED
@@ -5,7 +5,7 @@
5
5
  - **C**: **C**ontent oriented | 内容为王,专注内容
6
6
  - **Z**: **Z**ero Configuration | 零配置写作,减少打扰
7
7
  - **O**: **O**rganic AI-Native | 有机的 AI 原生,深度集成 AI
8
- - **N**: **N**-shape Energy Curve | N 型能量曲线,介入创作-分发-反馈的各个环节
8
+ - **N**: **N**-shaped Energy Curve | N 型能量曲线,介入创作-分发-反馈的各个环节
9
9
 
10
10
  [> Website Demo](https://czon.zccz14.com/)
11
11
 
@@ -26,9 +26,9 @@ AI 时代下,作为网站内容创作者,我们可以有更智能的内容
26
26
  ## 基本功能
27
27
 
28
28
  1. 🌍 **自动多语言翻译**:使用 AI 进行增量翻译,让用户使用母语编写 Markdown,但是用户可以是多语言的。
29
- 1. 💭 **自动摘要提取**:使用 AI 对原始文本进行内容分析和提取。
30
- 1. 🏷️ **自动标签分类**:使用 AI 对内容进行标签和分类的提取和管理。
31
- 1. 🧭 **智能分类导航**:使用 AI 生成站点地图和导航,源文件的位置不敏感。
29
+ 2. 💭 **自动摘要提取**:使用 AI 对原始文本进行内容分析和提取。
30
+ 3. 🏷️ **自动标签分类**:使用 AI 对内容进行标签和分类的提取和管理。
31
+ 4. 🧭 **智能分类导航**:使用 AI 生成站点地图和导航,源文件的位置不敏感。
32
32
 
33
33
  ## 静态站点生成 (SSG)
34
34
 
@@ -73,7 +73,7 @@ async function buildPipeline(options) {
73
73
  // 清理输出目录
74
74
  await fs.rm(paths_1.CZON_DIST_DIR, { recursive: true, force: true });
75
75
  // 确保 .czon/.gitignore 文件
76
- await (0, writeFile_1.writeFile)(path.join(paths_1.CZON_DIR, '.gitignore'), 'dist\n');
76
+ await (0, writeFile_1.writeFile)(path.join(paths_1.CZON_DIR, '.gitignore'), 'dist\ntmp\n');
77
77
  // 扫描源文件
78
78
  await (0, scanSourceFiles_1.scanSourceFiles)();
79
79
  // 写入 .raw 目录用于存储原始文件 (非翻译文件)
@@ -35,6 +35,7 @@ var __importStar = (this && this.__importStar) || (function () {
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
36
  exports.generateRobotsTxt = void 0;
37
37
  const path = __importStar(require("path"));
38
+ const metadata_1 = require("../metadata");
38
39
  const paths_1 = require("../paths");
39
40
  const writeFile_1 = require("../utils/writeFile");
40
41
  const generateRobotsTxt = async () => {
@@ -106,7 +107,7 @@ Disallow:
106
107
 
107
108
  Content-Signal: ai-train=yes, search=yes, ai-input=yes
108
109
 
109
- Sitemap: /sitemap.xml
110
+ ${metadata_1.MetaData.options.baseUrl ? `Sitemap: ${path.join(metadata_1.MetaData.options.baseUrl, 'sitemap.xml')}` : ''}
110
111
  `;
111
112
  const robotsPath = path.join(paths_1.CZON_DIST_DIR, 'robots.txt');
112
113
  await (0, writeFile_1.writeFile)(robotsPath, robotsTxtContent);
@@ -45,18 +45,46 @@ const sha256_1 = require("../utils/sha256");
45
45
  const writeFile_1 = require("../utils/writeFile");
46
46
  async function translateWithOpenCode(sourcePath, targetPath, targetLang, options) {
47
47
  const langName = languages_1.LANGUAGE_NAMES[targetLang];
48
- const prompt = `将 ${sourcePath} 翻译成 ${langName} (${targetLang}) 并保存到 ${targetPath}。`;
49
- await (0, opencode_1.runOpenCode)(prompt, {
50
- ...options,
51
- cwd: (0, path_1.dirname)(sourcePath),
52
- agent: 'czon-markdown-translator',
53
- });
54
- const exists = await (0, promises_1.access)(targetPath).then(() => true, () => false);
55
- if (!exists) {
56
- throw new Error(`OpenCode translation failed: ${targetPath} was not created`);
48
+ const taskId = crypto.randomUUID();
49
+ const COMMENT_FILE = (0, path_1.join)(paths_1.CZON_DIR, 'tmp', `comment-${taskId}.txt`);
50
+ // 清理旧的评论文件
51
+ await (0, writeFile_1.writeFile)(COMMENT_FILE, '');
52
+ // 任务定义:每个任务包含提示、代理名称和描述
53
+ const tasks = [
54
+ {
55
+ description: '翻译任务',
56
+ prompt: `将 ${sourcePath} 翻译成 ${langName} (${targetLang}) 并保存为 ${targetPath}。阅读 ${COMMENT_FILE} (如果存在) 以了解改进建议。`,
57
+ model: 'deepseek/deepseek-chat',
58
+ },
59
+ {
60
+ description: '翻译质量评估任务',
61
+ prompt: `请判断 ${targetPath} 是否是 ${sourcePath} 的优秀翻译。评估标准包括准确性、流畅性、术语一致性、文化适应性。输出评估结果和分数(0-10分)。将评审意见保存到 ${COMMENT_FILE} 文件中。如果通过评估,请写入 "Result: Passed"。否则写入 "Result: Failed" 并附上改进建议。`,
62
+ model: 'deepseek/deepseek-chat',
63
+ },
64
+ ];
65
+ for (let it = 0; it < 10; it++) {
66
+ for (let i = 0; i < tasks.length; i++) {
67
+ const task = tasks[i];
68
+ console.log(`\n=== 任务 ${i + 1}: ${task.description} ===`);
69
+ console.log(`提示: ${task.prompt}`);
70
+ await (0, opencode_1.runOpenCode)(task.prompt, { model: task.model });
71
+ console.log(`✅ 任务 ${i + 1} 完成`);
72
+ }
73
+ const reviewFileContent = await (0, promises_1.readFile)(COMMENT_FILE, 'utf-8').catch(() => '');
74
+ // 检查评审结果是否通过
75
+ if (reviewFileContent.includes('Result: Passed') &&
76
+ !reviewFileContent.includes('Result: Failed')) {
77
+ console.log('\n=== 翻译质量评估通过,所有任务完成 ===');
78
+ return;
79
+ }
57
80
  }
58
- return (0, promises_1.readFile)(targetPath, 'utf-8');
59
81
  }
82
+ const translateWithLLMCall = async (sourcePath, targetPath, lang) => {
83
+ const content = await (0, promises_1.readFile)(sourcePath, 'utf-8');
84
+ const translatedResponse = await (0, translateMarkdown_1.translateMarkdown)(sourcePath, content, lang);
85
+ const translatedContent = translatedResponse.choices?.[0].message.content?.trim() || '';
86
+ await (0, writeFile_1.writeFile)(targetPath, translatedContent);
87
+ };
60
88
  /**
61
89
  * 处理翻译
62
90
  */
@@ -69,7 +97,6 @@ async function processTranslations() {
69
97
  return;
70
98
  }
71
99
  return Promise.all(langs.map(async (lang) => {
72
- var _a;
73
100
  if (verbose)
74
101
  console.info(`📄 Processing file for translation: ${file.path}`);
75
102
  if (!file.metadata) {
@@ -95,12 +122,11 @@ async function processTranslations() {
95
122
  console.info(`ℹ️ Content unchanged for ${file.path}, skipping translation.`);
96
123
  return;
97
124
  }
98
- const translatedResponse = await (0, translateMarkdown_1.translateMarkdown)(sourcePath, content, lang);
99
- const translatedContent = translatedResponse.choices?.[0].message.content?.trim() || '';
100
- const translationMeta = ((_a = (file.translations ?? (file.translations = {})))[lang] ?? (_a[lang] = {}));
101
- translationMeta.content_length = translatedContent.length; // 记录翻译后内容长度
102
- translationMeta.token_used = translatedResponse.usage; // 记录 token 使用情况
103
- await (0, writeFile_1.writeFile)(targetPath, translatedContent);
125
+ // await translateWithLLMCall(sourcePath, targetPath, lang);
126
+ await translateWithOpenCode(sourcePath, targetPath, lang);
127
+ // const translationMeta = ((file.translations ??= {})[lang] ??= {});
128
+ // translationMeta.content_length = translatedContent.length; // 记录翻译后内容长度
129
+ // translationMeta.token_used = translatedResponse.usage; // 记录 token 使用情况
104
130
  // 存储已增强内容的哈希值
105
131
  file.nativeMarkdownHash = hash;
106
132
  if (verbose)
@@ -1,86 +1,117 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.installAgentsToGlobal = exports.runOpenCode = void 0;
4
- const child_process_1 = require("child_process");
5
4
  const promises_1 = require("fs/promises");
6
5
  const path_1 = require("path");
7
6
  const metadata_1 = require("../metadata");
8
7
  const paths_1 = require("../paths");
9
8
  const writeFile_1 = require("../utils/writeFile");
9
+ function parseModelString(model) {
10
+ const parts = model.split('/');
11
+ if (parts.length === 2) {
12
+ return { providerID: parts[0], modelID: parts[1] };
13
+ }
14
+ // Default provider if no slash
15
+ return { providerID: 'opencode', modelID: model };
16
+ }
10
17
  /**
11
- * Run OpenCode CLI to generate AI response for a given prompt.
18
+ * Run OpenCode to generate AI response for a given prompt.
12
19
  *
13
- * This function spawns the OpenCode CLI process and captures its output.
14
- * It serves as a fallback when OpenAI API is not available.
20
+ * This function uses the OpenCode SDK to connect to a running OpenCode server.
21
+ * Assumes an OpenCode server is already running externally.
15
22
  *
16
23
  * @param prompt - The prompt to send to OpenCode
17
24
  * @param options - Optional configuration
18
- * @returns Promise resolving to the raw AI response string
25
+ * @returns Promise that resolves when the operation completes
19
26
  *
20
27
  * @important
21
- * The returned string has **no guaranteed format**. It may be plain text, JSON,
22
- * Markdown, or any other format depending on the model and context.
23
- * Do NOT parse or assume a specific structure. Treat it as raw content to be
24
- * processed by downstream AI components (e.g., pass to another LLM call,
25
- * write to a temp file for a Supervisor Agent, etc.).
26
- *
27
- * @example
28
- * const response = await runOpenCode('Explain closures in JavaScript');
29
- * // response format is unpredictable - pass it to another AI for processing
30
- * const analyzed = await completeMessages([
31
- * { role: 'user', content: `Analyze this AI output:\n${response}` }
32
- * ]);
28
+ * The AI response is handled internally by OpenCode. This function does not
29
+ * return the response content. Any output files or results are managed by
30
+ * the OpenCode agent or session directly.
33
31
  */
34
32
  const runOpenCode = (prompt, options) => {
35
33
  const model = options?.model ?? 'opencode/gpt-5-nano';
36
34
  const signal = options?.signal;
37
- const cwd = options?.cwd;
35
+ const cwd = options?.cwd || process.cwd();
36
+ const agent = options?.agent;
38
37
  const verbose = metadata_1.MetaData.options.verbose;
39
38
  if (verbose) {
40
- console.info(`🛠️ Running OpenCode with model: ${model}, prompt: ${prompt}`);
39
+ console.info(`🛠️ Running OpenCode with model: ${model}, agent: ${agent || 'none'}, prompt: ${prompt}`);
41
40
  }
42
- return new Promise((resolve, reject) => {
43
- console.info(`🚀 Running OpenCode with model ${model}`);
44
- const proc = (0, child_process_1.spawn)('npx', [
45
- 'opencode-ai',
46
- 'run',
47
- prompt,
48
- '--model',
49
- model,
50
- ...(options?.agent ? ['--agent', options.agent] : []),
51
- '--format',
52
- 'json',
53
- ], {
54
- stdio: ['ignore', 'pipe', 'inherit'],
55
- cwd,
56
- env: Object.assign({
57
- OPENCODE_PERMISSION: JSON.stringify({ bash: 'allow', read: 'allow', write: 'allow' }),
58
- }, process.env),
59
- });
60
- let output = '';
61
- proc.stdout.on('data', data => {
62
- const chunk = data.toString();
63
- output += chunk;
64
- if (verbose) {
65
- console.info('OpenCode stdout chunk:', chunk);
66
- }
67
- });
68
- proc.on('error', err => {
69
- reject(new Error(`Failed to start OpenCode process: ${err.message}`));
70
- });
71
- proc.on('close', code => {
72
- if (code === 0) {
73
- resolve(output.trim());
41
+ return new Promise(async (resolve, reject) => {
42
+ const agentInfo = agent ? ` with agent ${agent}` : '';
43
+ console.info(`🚀 Running OpenCode with model ${model}${agentInfo}`);
44
+ let cancelled = false;
45
+ const cleanup = () => {
46
+ if (signal) {
47
+ signal.removeEventListener('abort', onAbort);
74
48
  }
75
- else {
76
- reject(new Error(`OpenCode process exited with code ${code}`));
77
- }
78
- });
49
+ };
50
+ const onAbort = () => {
51
+ cancelled = true;
52
+ cleanup();
53
+ reject(new Error('OpenCode execution was aborted'));
54
+ };
79
55
  if (signal) {
80
- signal.addEventListener('abort', () => {
81
- proc.kill('SIGTERM');
82
- reject(new Error('OpenCode execution was aborted'));
56
+ signal.addEventListener('abort', onAbort);
57
+ if (signal.aborted) {
58
+ onAbort();
59
+ return;
60
+ }
61
+ }
62
+ try {
63
+ const { createOpencodeClient } = await import('@opencode-ai/sdk');
64
+ const baseUrl = 'http://localhost:4096';
65
+ const client = createOpencodeClient({
66
+ baseUrl: baseUrl,
67
+ directory: cwd,
83
68
  });
69
+ const modelObj = parseModelString(model);
70
+ const session = await client.session.create();
71
+ if (!session.data?.id)
72
+ throw new Error('Failed to create OpenCode session', { cause: session.error });
73
+ const directoryBase64 = Buffer.from(session.data.directory).toString('base64');
74
+ const url = `${baseUrl}/${directoryBase64}/session/${session.data.id}`;
75
+ console.info('OpenCode Session Created', url);
76
+ const response = await client.session.prompt({
77
+ path: {
78
+ id: session.data.id,
79
+ },
80
+ body: {
81
+ model: modelObj,
82
+ agent,
83
+ parts: [
84
+ {
85
+ type: 'text',
86
+ text: prompt,
87
+ },
88
+ ],
89
+ },
90
+ query: {
91
+ directory: cwd,
92
+ },
93
+ signal,
94
+ });
95
+ if (cancelled) {
96
+ throw new Error('Cancelled');
97
+ }
98
+ if (response.error) {
99
+ throw new Error(`OpenCode API error: ${JSON.stringify(response.error)}`);
100
+ }
101
+ // await client.session.delete({
102
+ // path: {
103
+ // id: session.data.id,
104
+ // },
105
+ // });
106
+ cleanup();
107
+ resolve();
108
+ }
109
+ catch (err) {
110
+ if (cancelled) {
111
+ return;
112
+ }
113
+ cleanup();
114
+ reject(new Error(`OpenCode SDK error: ${err instanceof Error ? err.message : String(err)}. Make sure an OpenCode server is running.`));
84
115
  }
85
116
  });
86
117
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "czon",
3
- "version": "0.5.6",
3
+ "version": "0.5.7",
4
4
  "description": "CZone - AI enhanced Markdown content engine",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -51,6 +51,7 @@
51
51
  "typescript": "^5.9.3"
52
52
  },
53
53
  "dependencies": {
54
+ "@opencode-ai/sdk": "^1.1.34",
54
55
  "@types/react": "^19.2.7",
55
56
  "@types/react-dom": "^19.2.3",
56
57
  "chokidar": "^5.0.0",
package/tsconfig.json CHANGED
@@ -2,8 +2,9 @@
2
2
  "compilerOptions": {
3
3
  "jsx": "react",
4
4
  "target": "ES2020",
5
- "module": "commonjs",
6
- "lib": ["ES2020"],
5
+ "module": "NodeNext",
6
+ "moduleResolution": "NodeNext",
7
+ "lib": ["ES2020", "ESNext"],
7
8
  "outDir": "./dist",
8
9
  "rootDir": "./src",
9
10
  "strict": true,