czon 0.4.4 → 0.5.0

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,90 @@
1
+ ---
2
+ description: >-
3
+ Use this agent when you need to translate markdown files from one language to
4
+ another while preserving the markdown formatting structure. Examples:
5
+
6
+
7
+ - <example>
8
+ Context: User wants to translate documentation files from English to Chinese.
9
+ user: "Translate the README.md file to Japanese, preserving all headers, links, and code blocks"
10
+ assistant: "I'll use the czon-markdown-translator agent to handle this translation task"
11
+ <commentary>
12
+ Since the user explicitly asked to translate a markdown file (README.md) while preserving formatting, the czon-markdown-translator agent is the appropriate choice.
13
+ </commentary>
14
+ </example>
15
+ - <example>
16
+ Context: User needs to localize technical documentation for a new market.
17
+ user: "Please translate all .md files in the docs/ directory to German"
18
+ assistant: "The czon-markdown-translator agent can read and translate the markdown files while maintaining proper structure"
19
+ <commentary>
20
+ When the user mentions translating multiple markdown files, this agent should be used to ensure consistent formatting preservation.
21
+ </commentary>
22
+ </example>
23
+ - <example>
24
+ Context: User wants to translate a markdown blog post.
25
+ user: "Convert this markdown article to Spanish, keeping all formatting intact"
26
+ assistant: "Let me invoke the czon-markdown-translator agent to handle the translation while preserving markdown syntax"
27
+ <commentary>
28
+ When the user emphasizes preserving formatting during translation, this specialized agent is ideal.
29
+ </commentary>
30
+ </example>
31
+ mode: all
32
+ ---
33
+
34
+ You are a specialized Markdown Translator with expertise in technical documentation localization. Your primary responsibility is to accurately translate markdown files while meticulously preserving their structural integrity.
35
+
36
+ ## Core Responsibilities
37
+
38
+ 1. **Read and Parse Files**: Load markdown files from specified paths, understanding their full structure including frontmatter, headers, lists, code blocks, tables, links, images, and inline formatting.
39
+
40
+ 2. **Translate Content**: Translate the textual content to the target language while:
41
+ - Preserving all markdown syntax (headings with #, bold \*_, italic _, code blocks with ```, etc.)
42
+ - Maintaining links, image references, and their alt text
43
+ - Keeping tables intact and translating only cell content
44
+ - Preserving code examples, file paths, and command snippets untranslated
45
+ - Handling frontmatter metadata appropriately (translate values only, keep keys)
46
+
47
+ 3. **Handle Special Content**:
48
+ - **Code blocks**: Never translate content inside triple-backtick blocks
49
+ - **Inline code**: Keep backtick-enclosed text untranslated
50
+ - **Links**: Translate link text but preserve URLs
51
+ - **Images**: Translate alt text but preserve image paths
52
+ - **URLs and paths**: Keep untranslated (e.g., `/api/users`, `https://example.com`)
53
+ - **Frontmatter**: Translate string values, preserve boolean/numeric values and keys
54
+
55
+ 4. **Output Management**: Write translated content to the specified output file, maintaining UTF-8 encoding and original line endings where possible.
56
+
57
+ ## Translation Quality Standards
58
+
59
+ - Maintain consistent terminology throughout the document
60
+ - Preserve the original tone (technical, casual, formal, etc.)
61
+ - Ensure translations are natural and idiomatic in the target language
62
+ - Keep headings and subheadings properly nested and meaningful
63
+ - For list items, ensure parallel structure is maintained
64
+
65
+ ## Workflow
66
+
67
+ 1. Confirm the source file path and target language with the user if unclear
68
+ 2. Read and parse the complete markdown file
69
+ 3. Create a translation strategy (identify sections, special content types)
70
+ 4. Translate section by section, marking translated content
71
+ 5. Verify markdown syntax integrity after translation
72
+ 6. Write to output file or return translated content as requested
73
+
74
+ ## Error Handling
75
+
76
+ - If a file doesn't exist or cannot be read, report the error clearly
77
+ - If the target language is ambiguous, ask for clarification
78
+ - If encoding issues are detected, attempt to resolve or report
79
+ - If markdown parsing fails, identify the problematic section
80
+
81
+ ## Output Format
82
+
83
+ When completing the task:
84
+
85
+ - Confirm the file was translated successfully
86
+ - Report the character/word count if relevant
87
+ - Note any sections that were preserved (code blocks, etc.)
88
+ - Suggest any follow-up actions if needed (proofreading, formatting review)
89
+
90
+ Remember: Your goal is to produce a translated markdown file that looks like it was originally written in the target language, with all formatting intact and functional.
@@ -44,6 +44,7 @@ const extractMetadataByAI_1 = require("../process/extractMetadataByAI");
44
44
  const processTranslations_1 = require("../process/processTranslations");
45
45
  const scanSourceFiles_1 = require("../process/scanSourceFiles");
46
46
  const template_1 = require("../process/template");
47
+ const opencode_1 = require("../services/opencode");
47
48
  const writeFile_1 = require("../utils/writeFile");
48
49
  const robots_1 = require("./robots");
49
50
  const sitemap_1 = require("./sitemap");
@@ -65,6 +66,8 @@ async function validateConfig(options) {
65
66
  * 构建管道(函数组合)
66
67
  */
67
68
  async function buildPipeline(options) {
69
+ // 安装 OpenCode 代理到全局目录
70
+ await (0, opencode_1.installAgentsToGlobal)();
68
71
  // 验证配置
69
72
  await validateConfig(options);
70
73
  // 清理输出目录
@@ -24,7 +24,7 @@ const findMarkdownEntries = async (dirPath) => {
24
24
  const files = stdout
25
25
  .split('\0') // 按空字符分割文件名
26
26
  .filter(line => line.trim() !== '') // 移除空行
27
- .filter(file => !file.startsWith('.czon')) // 过滤掉.czon目录下的文件
27
+ .filter(file => !file.startsWith('.')) // 过滤掉隐藏目录下的文件
28
28
  .filter(file => file.endsWith('.md')); // 只保留.md文件
29
29
  return files;
30
30
  }
package/dist/paths.js CHANGED
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.CZON_META_PATH = exports.CZON_SRC_DIR = exports.CZON_DIST_RAW_CONTENT_DIR = exports.CZON_DIST_DIR = exports.CZON_DIR = exports.INPUT_DIR = void 0;
3
+ exports.LOCAL_OPENCODE_AGENT_DIR = exports.GLOBAL_OPENCODE_AGENT_DIR = exports.CZON_META_PATH = exports.CZON_SRC_DIR = exports.CZON_DIST_RAW_CONTENT_DIR = exports.CZON_DIST_DIR = exports.CZON_DIR = exports.INPUT_DIR = void 0;
4
+ const os_1 = require("os");
4
5
  const path_1 = require("path");
5
6
  exports.INPUT_DIR = process.cwd();
6
7
  exports.CZON_DIR = (0, path_1.join)(process.cwd(), '.czon');
@@ -8,4 +9,9 @@ exports.CZON_DIST_DIR = (0, path_1.join)(exports.CZON_DIR, 'dist');
8
9
  exports.CZON_DIST_RAW_CONTENT_DIR = (0, path_1.join)(exports.CZON_DIST_DIR, '__raw__');
9
10
  exports.CZON_SRC_DIR = (0, path_1.join)(exports.CZON_DIR, 'src');
10
11
  exports.CZON_META_PATH = (0, path_1.join)(exports.CZON_DIR, 'meta.json');
12
+ /**
13
+ * ~/.config/opencode/agents/
14
+ */
15
+ exports.GLOBAL_OPENCODE_AGENT_DIR = (0, path_1.join)((0, os_1.homedir)(), '.config', 'opencode', 'agents');
16
+ exports.LOCAL_OPENCODE_AGENT_DIR = (0, path_1.join)(__dirname, '..', 'agents');
11
17
  //# sourceMappingURL=paths.js.map
@@ -1,16 +1,61 @@
1
1
  "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
4
- };
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
5
35
  Object.defineProperty(exports, "__esModule", { value: true });
6
36
  exports.processTranslations = processTranslations;
7
37
  const promises_1 = require("fs/promises");
8
- const path_1 = __importDefault(require("path"));
9
- const translateMarkdown_1 = require("../ai/translateMarkdown");
38
+ const path_1 = __importStar(require("path"));
39
+ const languages_1 = require("../languages");
10
40
  const metadata_1 = require("../metadata");
11
41
  const paths_1 = require("../paths");
42
+ const opencode_1 = require("../services/opencode");
12
43
  const sha256_1 = require("../utils/sha256");
13
44
  const writeFile_1 = require("../utils/writeFile");
45
+ async function translateWithOpenCode(sourcePath, targetPath, targetLang, options) {
46
+ const langName = languages_1.LANGUAGE_NAMES[targetLang];
47
+ const prompt = `将 ${sourcePath} 翻译成 ${langName} (${targetLang}) 并保存到 ${targetPath}。`;
48
+ await (0, opencode_1.runOpenCode)(prompt, {
49
+ ...options,
50
+ cwd: (0, path_1.dirname)(sourcePath),
51
+ agent: 'czon-markdown-translator',
52
+ });
53
+ const exists = await (0, promises_1.access)(targetPath).then(() => true, () => false);
54
+ if (!exists) {
55
+ throw new Error(`OpenCode translation failed: ${targetPath} was not created`);
56
+ }
57
+ return (0, promises_1.readFile)(targetPath, 'utf-8');
58
+ }
14
59
  /**
15
60
  * 处理翻译
16
61
  */
@@ -49,11 +94,9 @@ async function processTranslations() {
49
94
  console.info(`ℹ️ Content unchanged for ${file.path}, skipping translation.`);
50
95
  return;
51
96
  }
52
- const translatedResponse = await (0, translateMarkdown_1.translateMarkdown)(sourcePath, content, lang);
53
- const translatedContent = translatedResponse.choices?.[0].message.content?.trim() || '';
97
+ const translatedContent = await translateWithOpenCode(sourcePath, targetPath, lang);
54
98
  const translationMeta = ((_a = (file.translations ?? (file.translations = {})))[lang] ?? (_a[lang] = {}));
55
- translationMeta.content_length = translatedContent.length; // 记录翻译后内容长度
56
- translationMeta.token_used = translatedResponse.usage; // 记录 token 使用情况
99
+ translationMeta.content_length = translatedContent.length;
57
100
  await (0, writeFile_1.writeFile)(targetPath, translatedContent);
58
101
  // 存储已增强内容的哈希值
59
102
  file.nativeMarkdownHash = hash;
@@ -0,0 +1,105 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.installAgentsToGlobal = exports.runOpenCode = void 0;
4
+ const child_process_1 = require("child_process");
5
+ const promises_1 = require("fs/promises");
6
+ const path_1 = require("path");
7
+ const metadata_1 = require("../metadata");
8
+ const paths_1 = require("../paths");
9
+ const writeFile_1 = require("../utils/writeFile");
10
+ /**
11
+ * Run OpenCode CLI to generate AI response for a given prompt.
12
+ *
13
+ * This function spawns the OpenCode CLI process and captures its output.
14
+ * It serves as a fallback when OpenAI API is not available.
15
+ *
16
+ * @param prompt - The prompt to send to OpenCode
17
+ * @param options - Optional configuration
18
+ * @returns Promise resolving to the raw AI response string
19
+ *
20
+ * @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
+ * ]);
33
+ */
34
+ const runOpenCode = (prompt, options) => {
35
+ const model = options?.model ?? 'opencode/minimax-m2.1-free';
36
+ const signal = options?.signal;
37
+ const cwd = options?.cwd;
38
+ const verbose = metadata_1.MetaData.options.verbose;
39
+ if (verbose) {
40
+ console.info(`🛠️ Running OpenCode with model: ${model}, prompt: ${prompt}`);
41
+ }
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', 'pipe'],
55
+ cwd,
56
+ });
57
+ let output = '';
58
+ proc.stdout.on('data', data => {
59
+ const chunk = data.toString();
60
+ output += chunk;
61
+ if (verbose) {
62
+ console.info('OpenCode stdout chunk:', chunk);
63
+ }
64
+ });
65
+ proc.stderr.on('data', data => {
66
+ console.error('OpenCode stderr:', data.toString());
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());
74
+ }
75
+ else {
76
+ reject(new Error(`OpenCode process exited with code ${code}`));
77
+ }
78
+ });
79
+ if (signal) {
80
+ signal.addEventListener('abort', () => {
81
+ proc.kill('SIGTERM');
82
+ reject(new Error('OpenCode execution was aborted'));
83
+ });
84
+ }
85
+ });
86
+ };
87
+ exports.runOpenCode = runOpenCode;
88
+ const installAgentsToGlobal = async () => {
89
+ const { verbose } = metadata_1.MetaData.options;
90
+ const installedAgents = await (0, promises_1.readdir)(paths_1.LOCAL_OPENCODE_AGENT_DIR)
91
+ .then(files => files.filter(f => f.startsWith('czon-')))
92
+ .catch(() => []);
93
+ // 3. Copy local agents from .opencode/agent to global directory
94
+ for (const agentFile of installedAgents) {
95
+ if (verbose) {
96
+ console.log(`📁 Installing OpenCode agent: ${agentFile} to global directory...`);
97
+ }
98
+ await (0, writeFile_1.writeFile)((0, path_1.join)(paths_1.GLOBAL_OPENCODE_AGENT_DIR, agentFile), await (0, promises_1.readFile)((0, path_1.join)(paths_1.LOCAL_OPENCODE_AGENT_DIR, agentFile)));
99
+ }
100
+ if (verbose) {
101
+ console.log(`✅ Installed ${installedAgents.length} OpenCode agents to global directory.`);
102
+ }
103
+ };
104
+ exports.installAgentsToGlobal = installAgentsToGlobal;
105
+ //# sourceMappingURL=opencode.js.map
@@ -0,0 +1,34 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const node_test_1 = require("node:test");
7
+ const node_assert_1 = __importDefault(require("node:assert"));
8
+ const opencode_1 = require("./opencode");
9
+ (0, node_test_1.describe)('runOpenCode', () => {
10
+ (0, node_test_1.it)('should return response from OpenCode', async () => {
11
+ const result = await (0, opencode_1.runOpenCode)('hello');
12
+ node_assert_1.default.ok(result.length > 0, 'Should return non-empty response');
13
+ });
14
+ (0, node_test_1.it)('should use default model when not specified', async () => {
15
+ const result = await (0, opencode_1.runOpenCode)('test prompt');
16
+ node_assert_1.default.ok(typeof result === 'string');
17
+ });
18
+ (0, node_test_1.it)('should accept custom model in options', async () => {
19
+ const options = { model: 'opencode/glm-4.7-free' };
20
+ const result = await (0, opencode_1.runOpenCode)('test', options);
21
+ node_assert_1.default.ok(typeof result === 'string');
22
+ });
23
+ (0, node_test_1.it)('should handle abort signal', async () => {
24
+ const controller = new AbortController();
25
+ setTimeout(() => controller.abort(), 10);
26
+ await node_assert_1.default.rejects(async () => await (0, opencode_1.runOpenCode)('long running prompt', { signal: controller.signal }), /OpenCode execution was aborted/);
27
+ });
28
+ (0, node_test_1.it)('should accept cwd option', async () => {
29
+ const options = { cwd: '/tmp' };
30
+ const result = await (0, opencode_1.runOpenCode)('test', options);
31
+ node_assert_1.default.ok(typeof result === 'string');
32
+ });
33
+ });
34
+ //# sourceMappingURL=opencode.test.js.map
@@ -23,9 +23,7 @@ const IndexPage = props => {
23
23
  return (react_1.default.createElement("html", null,
24
24
  react_1.default.createElement("head", null,
25
25
  react_1.default.createElement("meta", { charSet: "UTF-8" }),
26
- react_1.default.createElement("title", null,
27
- "Index of ",
28
- props.lang),
26
+ react_1.default.createElement("title", null, `Index of ${props.lang.toString()}`),
29
27
  react_1.default.createElement("meta", { name: "viewport", content: "width=device-width, initial-scale=1.0" }),
30
28
  react_1.default.createElement("meta", { name: "description", content: `Index page for language ${props.lang}` }),
31
29
  react_1.default.createElement("style", null, style_1.style),
@@ -24,7 +24,7 @@ const RootPage = props => {
24
24
  react_1.default.createElement("meta", { name: "viewport", content: "width=device-width, initial-scale=1.0" }),
25
25
  react_1.default.createElement("title", null, "CZON Multilingual Site Navigator"),
26
26
  react_1.default.createElement("meta", { name: "description", content: "Select your preferred language to explore our content." }),
27
- props.ctx.site.options.langs.map(lang => (react_1.default.createElement("link", { rel: "alternate", hrefLang: lang, href: `${lang}/index.html` }))),
27
+ props.ctx.site.options.langs.map(lang => (react_1.default.createElement("link", { key: lang, rel: "alternate", hrefLang: lang, href: `${lang}/index.html` }))),
28
28
  react_1.default.createElement("link", { rel: "alternate", hrefLang: "x-default", href: `${props.ctx.site.options.langs[0]}/index.html` }),
29
29
  react_1.default.createElement("script", { dangerouslySetInnerHTML: {
30
30
  __html: `
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "czon",
3
- "version": "0.4.4",
3
+ "version": "0.5.0",
4
4
  "description": "CZone - AI enhanced Markdown content engine",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",