crewx 0.8.1 → 0.8.2-rc.2

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 (125) hide show
  1. package/README.md +268 -268
  2. package/bin/cli-commands.js +34 -0
  3. package/bin/crewx-lib.js +213 -108
  4. package/bin/crewx-ui.js +83 -83
  5. package/bin/crewx.js +219 -147
  6. package/bin/launcher-flags.js +29 -0
  7. package/bin/package.json +1 -1
  8. package/dist/assets/MarketPage-DptjaFpT.js +36 -0
  9. package/dist/assets/{PromptTab-DVKc7hJY.js → PromptTab-DZha2_v1.js} +1 -1
  10. package/dist/assets/{_baseUniq-wjlVo2E6.js → _baseUniq-jd6NubI3.js} +1 -1
  11. package/dist/assets/{arc-BfPgRtzW.js → arc-C2te3-8P.js} +1 -1
  12. package/dist/assets/{architectureDiagram-Q4EWVU46-ewcueFAG.js → architectureDiagram-Q4EWVU46-CbIQua02.js} +1 -1
  13. package/dist/assets/{blockDiagram-DXYQGD6D-TxlbbvKn.js → blockDiagram-DXYQGD6D-Cg7qkpSM.js} +1 -1
  14. package/dist/assets/{c4Diagram-AHTNJAMY-C1lT_bl_.js → c4Diagram-AHTNJAMY-BkffDY1F.js} +1 -1
  15. package/dist/assets/channel-beae0DeI.js +1 -0
  16. package/dist/assets/chatgpt-logo-dark.svg +15 -15
  17. package/dist/assets/chatgpt-logo.svg +15 -15
  18. package/dist/assets/{chunk-4BX2VUAB-C41j2mCL.js → chunk-4BX2VUAB-BxHe9wPE.js} +1 -1
  19. package/dist/assets/{chunk-4TB4RGXK-HNNsUbz0.js → chunk-4TB4RGXK--f1tN90O.js} +1 -1
  20. package/dist/assets/{chunk-55IACEB6-qtCgO0r2.js → chunk-55IACEB6-B5QXfPXQ.js} +1 -1
  21. package/dist/assets/{chunk-EDXVE4YY-BSnDYtsd.js → chunk-EDXVE4YY-DqKhblOg.js} +1 -1
  22. package/dist/assets/{chunk-FMBD7UC4-DyHRLQqX.js → chunk-FMBD7UC4-DZ1w_G02.js} +1 -1
  23. package/dist/assets/{chunk-OYMX7WX6-CCjfi6WS.js → chunk-OYMX7WX6-BqAgQpv8.js} +1 -1
  24. package/dist/assets/{chunk-QZHKN3VN-COLty8kd.js → chunk-QZHKN3VN-DPqnGqVi.js} +1 -1
  25. package/dist/assets/{chunk-YZCP3GAM-CHUUnGeN.js → chunk-YZCP3GAM-x-ZYSQLd.js} +1 -1
  26. package/dist/assets/classDiagram-6PBFFD2Q-BquWrs1y.js +1 -0
  27. package/dist/assets/classDiagram-v2-HSJHXN6E-BquWrs1y.js +1 -0
  28. package/dist/assets/clone-C9wSPtDN.js +1 -0
  29. package/dist/assets/{cose-bilkent-S5V4N54A-CSip-V2g.js → cose-bilkent-S5V4N54A-4VCNRP-E.js} +1 -1
  30. package/dist/assets/{dagre-KV5264BT-DkdpnWhv.js → dagre-KV5264BT-DucVi1QS.js} +1 -1
  31. package/dist/assets/{diagram-5BDNPKRD-PH4qc6PV.js → diagram-5BDNPKRD-ChdRA8bE.js} +1 -1
  32. package/dist/assets/{diagram-G4DWMVQ6-Cg5xZcjx.js → diagram-G4DWMVQ6-B1-97yHr.js} +1 -1
  33. package/dist/assets/{diagram-MMDJMWI5-soKmeTCW.js → diagram-MMDJMWI5-CQs3cO7G.js} +1 -1
  34. package/dist/assets/{diagram-TYMM5635-Daq5Mihu.js → diagram-TYMM5635-CuNCxDfO.js} +1 -1
  35. package/dist/assets/{erDiagram-SMLLAGMA-kr2OtY0Y.js → erDiagram-SMLLAGMA-DdR8v8g-.js} +1 -1
  36. package/dist/assets/{flowDiagram-DWJPFMVM-DQZCb8gm.js → flowDiagram-DWJPFMVM-Dt02upId.js} +1 -1
  37. package/dist/assets/{ganttDiagram-T4ZO3ILL-BHkn485T.js → ganttDiagram-T4ZO3ILL-cG_k9VOa.js} +1 -1
  38. package/dist/assets/{gitGraphDiagram-UUTBAWPF-FaCyYFmC.js → gitGraphDiagram-UUTBAWPF-Dz1DjhKq.js} +1 -1
  39. package/dist/assets/{graph-BVJlrP6V.js → graph-CF2NtM33.js} +1 -1
  40. package/dist/assets/{infoDiagram-42DDH7IO-DJOWkKdM.js → infoDiagram-42DDH7IO-tQWKrYM6.js} +1 -1
  41. package/dist/assets/{ishikawaDiagram-UXIWVN3A-VfpvNaIf.js → ishikawaDiagram-UXIWVN3A-CLuUMkF0.js} +1 -1
  42. package/dist/assets/{journeyDiagram-VCZTEJTY-CPzsak-v.js → journeyDiagram-VCZTEJTY-a6JenLCk.js} +1 -1
  43. package/dist/assets/{kanban-definition-6JOO6SKY-DFqLDBU0.js → kanban-definition-6JOO6SKY-rqOxTzYb.js} +1 -1
  44. package/dist/assets/{layout-CCSbNPHm.js → layout-Dvic1Hpy.js} +1 -1
  45. package/dist/assets/{linear-C4T7PCKE.js → linear-CfMV1S6a.js} +1 -1
  46. package/dist/assets/main-05K4ggqd.css +10 -0
  47. package/dist/assets/main-CCM1gtr8.js +1165 -0
  48. package/dist/assets/{min-CGQNEYGh.js → min-GpF3DZux.js} +1 -1
  49. package/dist/assets/{mindmap-definition-QFDTVHPH-AuU1EqwS.js → mindmap-definition-QFDTVHPH-Cg80z0Jx.js} +1 -1
  50. package/dist/assets/{pieDiagram-DEJITSTG-CopkCZwp.js → pieDiagram-DEJITSTG-BrtK7lAq.js} +1 -1
  51. package/dist/assets/{quadrantDiagram-34T5L4WZ-lMKrSv_t.js → quadrantDiagram-34T5L4WZ-BL2txAAS.js} +1 -1
  52. package/dist/assets/{requirementDiagram-MS252O5E-dWUpHOFb.js → requirementDiagram-MS252O5E-Co3wpBnu.js} +1 -1
  53. package/dist/assets/{sankeyDiagram-XADWPNL6-C8UQx9Bb.js → sankeyDiagram-XADWPNL6-B4KJXdQ4.js} +1 -1
  54. package/dist/assets/{sequenceDiagram-FGHM5R23-CUVNIItJ.js → sequenceDiagram-FGHM5R23-xs5OuzvV.js} +1 -1
  55. package/dist/assets/{stateDiagram-FHFEXIEX-Ct0GamGl.js → stateDiagram-FHFEXIEX-bbEP20JD.js} +1 -1
  56. package/dist/assets/stateDiagram-v2-QKLJ7IA2-XYh9U1A7.js +1 -0
  57. package/dist/assets/{timeline-definition-GMOUNBTQ-ul8Po7f7.js → timeline-definition-GMOUNBTQ-CTc2wVwC.js} +1 -1
  58. package/dist/assets/{vennDiagram-DHZGUBPP-B4AOWQnP.js → vennDiagram-DHZGUBPP-oJpXott7.js} +1 -1
  59. package/dist/assets/{wardley-RL74JXVD-Dr7Wp3AJ.js → wardley-RL74JXVD-aTmOXkKh.js} +1 -1
  60. package/dist/assets/{wardleyDiagram-NUSXRM2D-Ck70faXX.js → wardleyDiagram-NUSXRM2D-C83SOkig.js} +1 -1
  61. package/dist/assets/{xychartDiagram-5P7HB3ND-Bsy5-cNt.js → xychartDiagram-5P7HB3ND-BScws0tF.js} +1 -1
  62. package/dist/index.html +13 -13
  63. package/dist-electron/main.js +153 -116
  64. package/dist-electron/overlay.js +102 -65
  65. package/dist-electron/package.json +1 -0
  66. package/dist-electron/preload.js +8 -8
  67. package/dist-server/bootstrap/crewx-server.js +19 -10
  68. package/dist-server/domain/agent/agent.service.js +12 -0
  69. package/dist-server/domain/agent/dto/update-agent.dto.js +20 -1
  70. package/dist-server/domain/mcp/crewx-tool.factory.js +44 -113
  71. package/dist-server/domain/mcp/mcp.module.js +2 -0
  72. package/dist-server/domain/mcp/mcp.service.js +37 -12
  73. package/dist-server/domain/message/message.service.js +21 -13
  74. package/dist-server/domain/skill/skill.service.js +63 -43
  75. package/dist-server/domain/task/task.module.js +2 -0
  76. package/dist-server/domain/task/task.service.js +17 -10
  77. package/dist-server/domain/thread/dto/update-thread.dto.js +23 -0
  78. package/dist-server/domain/thread/thread.controller.js +16 -0
  79. package/dist-server/domain/thread/thread.service.js +9 -0
  80. package/dist-server/main.js +1 -1
  81. package/dist-server/modules/crewx.module.js +16 -1
  82. package/dist-server/repository/box.repository.js +20 -20
  83. package/dist-server/repository/project.repository.js +13 -13
  84. package/dist-server/repository/request-log.repository.js +10 -10
  85. package/dist-server/repository/task.repository.js +72 -72
  86. package/dist-server/repository/thread.repository.js +78 -58
  87. package/package.json +6 -6
  88. package/packages/cli/dist/bootstrap/crewx-cli.js +12 -0
  89. package/packages/cli/dist/commands/agent.js +23 -23
  90. package/packages/cli/dist/commands/init.js +19 -19
  91. package/packages/cli/dist/commands/parse-common-flags.d.ts +19 -3
  92. package/packages/cli/dist/commands/parse-common-flags.js +46 -6
  93. package/packages/cli/dist/commands/registry.d.ts +13 -0
  94. package/packages/cli/dist/commands/registry.js +29 -0
  95. package/packages/cli/dist/commands/task-db.js +7 -7
  96. package/packages/cli/dist/examples/deny-secrets-plugin.d.ts +22 -0
  97. package/packages/cli/dist/examples/deny-secrets-plugin.js +40 -0
  98. package/packages/cli/dist/main.js +134 -68
  99. package/packages/cli/dist/plugins/examples/echo-hook.d.ts +24 -0
  100. package/packages/cli/dist/plugins/examples/echo-hook.js +60 -0
  101. package/packages/cli/dist/plugins/examples/verify-echo-hook.d.ts +8 -0
  102. package/packages/cli/dist/plugins/examples/verify-echo-hook.js +47 -0
  103. package/packages/cli/dist/plugins/sqlite-tracing.d.ts +11 -0
  104. package/packages/cli/dist/plugins/sqlite-tracing.js +19 -0
  105. package/packages/cli/dist/schema/tasks.d.ts +7 -0
  106. package/packages/cli/dist/schema/tasks.js +48 -0
  107. package/packages/cli/package.json +52 -52
  108. package/scripts/analyze-task-logs.mjs +569 -0
  109. package/scripts/build-manual.mjs +266 -266
  110. package/scripts/emit-dist-server-package-json.mjs +7 -7
  111. package/scripts/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -0
  112. package/scripts/postinstall.mjs +44 -44
  113. package/scripts/smoke-tarball.mjs +285 -285
  114. package/scripts/snapshot-msg-list.sh +52 -52
  115. package/server.js +167 -164
  116. package/dist/assets/MarketPage-Dwsg6K-B.js +0 -31
  117. package/dist/assets/channel-BP4PNMmz.js +0 -1
  118. package/dist/assets/classDiagram-6PBFFD2Q-Upr3UAcM.js +0 -1
  119. package/dist/assets/classDiagram-v2-HSJHXN6E-Upr3UAcM.js +0 -1
  120. package/dist/assets/clone-B8BP7ReZ.js +0 -1
  121. package/dist/assets/main-CELBpK6r.js +0 -1166
  122. package/dist/assets/main-CmP-VosD.css +0 -10
  123. package/dist/assets/stateDiagram-v2-QKLJ7IA2-CFBLQDEx.js +0 -1
  124. package/dist-server/domain/task/dto/project-usage.dto.js +0 -38
  125. package/dist-server/domain/thread/dto/send-message.dto.js +0 -10
@@ -1,266 +1,266 @@
1
- #!/usr/bin/env node
2
- /**
3
- * Build manual: Compile docs/manual/ markdown files into a single crewx-manual.md
4
- *
5
- * 1. Reads docs/manual/INDEX.md, resolves internal .md links inline,
6
- * strips image references → merged Korean markdown
7
- * 2. Translates to English via `crewx q '@tech_writer ...' --raw`
8
- * 3. Saves result to packages/cli/templates/documents/crewx-manual.md
9
- *
10
- * Caching: computes hash of docs/manual/ files, skips translation if unchanged.
11
- * Use --force to bypass cache and force re-translation.
12
- */
13
-
14
- import { readFileSync, writeFileSync, existsSync, readdirSync, mkdirSync } from 'fs';
15
- import { join, dirname, resolve } from 'path';
16
- import { fileURLToPath } from 'url';
17
- import { createHash } from 'crypto';
18
- import { execSync } from 'child_process';
19
-
20
- const __dirname = dirname(fileURLToPath(import.meta.url));
21
- const ROOT = resolve(__dirname, '..');
22
- const MANUAL_DIR = join(ROOT, 'docs', 'manual');
23
- const INDEX_PATH = join(MANUAL_DIR, 'INDEX.md');
24
- const OUTPUT_PATH = join(ROOT, 'packages', 'cli', 'templates', 'documents', 'crewx-manual.md');
25
- const HASH_DIR = join(ROOT, '.crewx');
26
- const HASH_PATH = join(HASH_DIR, 'manual-hash');
27
- const CREWX_BIN = join(ROOT, 'crewx');
28
-
29
- const forceFlag = process.argv.includes('--force');
30
-
31
- // Track already-inlined files to avoid duplicates
32
- const inlined = new Set();
33
-
34
- /**
35
- * Read a markdown file relative to MANUAL_DIR
36
- */
37
- function readManualFile(relPath) {
38
- const abs = join(MANUAL_DIR, relPath);
39
- if (!existsSync(abs)) {
40
- console.warn(` [WARN] File not found: ${relPath}`);
41
- return null;
42
- }
43
- return readFileSync(abs, 'utf-8');
44
- }
45
-
46
- /**
47
- * Strip image links: ![alt](path) and standalone <img> tags
48
- */
49
- function stripImages(content) {
50
- return content
51
- .replace(/!\[[^\]]*\]\([^)]+\)/g, '')
52
- .replace(/<img[^>]*>/gi, '');
53
- }
54
-
55
- /**
56
- * Extract .md links from a line.
57
- * Matches [text](path.md) patterns where path is a relative .md file.
58
- */
59
- function extractMdLinks(content) {
60
- const links = [];
61
- const re = /\[([^\]]+)\]\(([^)]+\.md)\)/g;
62
- let m;
63
- while ((m = re.exec(content)) !== null) {
64
- links.push({ text: m[1], path: m[2] });
65
- }
66
- return links;
67
- }
68
-
69
- /**
70
- * Process a markdown file: inline linked .md files, strip images.
71
- * For INDEX files, replace link references with the actual file content.
72
- * For regular files, just replace internal .md links with anchors.
73
- */
74
- function processFile(relPath, depth = 0) {
75
- if (inlined.has(relPath)) {
76
- return `<!-- Already included: ${relPath} -->\n`;
77
- }
78
- inlined.add(relPath);
79
-
80
- const content = readManualFile(relPath);
81
- if (content === null) return '';
82
-
83
- const dir = dirname(relPath);
84
- let result = stripImages(content);
85
-
86
- // Find all .md links and inline them
87
- const links = extractMdLinks(result);
88
-
89
- for (const link of links) {
90
- // Resolve relative path from current file's directory
91
- const linkedPath = dir === '.' ? link.path : join(dir, link.path);
92
- const normalizedPath = linkedPath.replace(/\\/g, '/');
93
-
94
- // Replace the markdown link with just the link text (as a reference)
95
- const linkPattern = `[${link.text}](${link.path})`;
96
-
97
- if (inlined.has(normalizedPath)) {
98
- // Already inlined — just replace link with text
99
- result = result.replace(linkPattern, `**${link.text}**`);
100
- continue;
101
- }
102
-
103
- // Read and inline the linked file
104
- const linkedContent = readManualFile(normalizedPath);
105
- if (linkedContent === null) {
106
- // Leave the link text but note it was not found
107
- result = result.replace(linkPattern, `**${link.text}** _(문서 없음)_`);
108
- continue;
109
- }
110
-
111
- inlined.add(normalizedPath);
112
-
113
- // Process the linked file content (strip images, but don't recurse too deep)
114
- let processed = stripImages(linkedContent);
115
- if (depth < 2) {
116
- // Recursively resolve links in linked files (max 2 levels)
117
- const subLinks = extractMdLinks(processed);
118
- for (const sub of subLinks) {
119
- const subPath = dirname(normalizedPath) === '.'
120
- ? sub.path
121
- : join(dirname(normalizedPath), sub.path);
122
- const normalizedSub = subPath.replace(/\\/g, '/');
123
-
124
- const subPattern = `[${sub.text}](${sub.path})`;
125
- if (inlined.has(normalizedSub)) {
126
- processed = processed.replace(subPattern, `**${sub.text}**`);
127
- } else {
128
- // Just convert to text reference (don't deep-inline sub-sub files)
129
- processed = processed.replace(subPattern, `**${sub.text}**`);
130
- }
131
- }
132
- }
133
-
134
- // Replace the link in the parent with the full content
135
- result = result.replace(linkPattern, `**${link.text}**`);
136
-
137
- // Append the file content after the current section
138
- result += `\n---\n\n${processed}\n`;
139
- }
140
-
141
- // Clean up excessive blank lines
142
- result = result.replace(/\n{4,}/g, '\n\n\n');
143
-
144
- return result;
145
- }
146
-
147
- /**
148
- * Recursively collect all .md files under a directory and compute a combined hash.
149
- */
150
- function collectMdFiles(dir) {
151
- const files = [];
152
- for (const entry of readdirSync(dir, { withFileTypes: true })) {
153
- const full = join(dir, entry.name);
154
- if (entry.isDirectory()) {
155
- files.push(...collectMdFiles(full));
156
- } else if (entry.name.endsWith('.md')) {
157
- files.push(full);
158
- }
159
- }
160
- return files.sort();
161
- }
162
-
163
- function computeManualHash() {
164
- const hash = createHash('sha256');
165
- for (const f of collectMdFiles(MANUAL_DIR)) {
166
- hash.update(readFileSync(f));
167
- }
168
- return hash.digest('hex');
169
- }
170
-
171
- function readSavedHash() {
172
- if (!existsSync(HASH_PATH)) return null;
173
- return readFileSync(HASH_PATH, 'utf-8').trim();
174
- }
175
-
176
- function saveHash(h) {
177
- mkdirSync(HASH_DIR, { recursive: true });
178
- writeFileSync(HASH_PATH, h + '\n', 'utf-8');
179
- }
180
-
181
- /**
182
- * Translate Korean markdown to English via crewx CLI.
183
- * Returns translated text, or null on failure.
184
- */
185
- function translateToEnglish(koreanMd) {
186
- const prompt = `@tech_writer Translate the following Korean CrewX manual to English. Keep all markdown formatting, code blocks, YAML examples, and technical terms (crewx, CLI commands, flag names) exactly as-is. Only translate Korean prose to natural English. Do NOT add any commentary or explanation — output ONLY the translated markdown.`;
187
-
188
- try {
189
- console.log(' Translating to English via crewx q ...');
190
- const translated = execSync(
191
- `"${CREWX_BIN}" q '${prompt.replace(/'/g, "'\\''")}' --raw 2>/dev/null`,
192
- {
193
- input: koreanMd,
194
- cwd: ROOT,
195
- encoding: 'utf-8',
196
- maxBuffer: 50 * 1024 * 1024, // 50MB
197
- timeout: 15 * 60 * 1000, // 15 min
198
- env: { ...process.env, CLAUDECODE: undefined },
199
- }
200
- );
201
- if (translated && translated.trim().length > 100) {
202
- // Strip crewx runtime log lines from output
203
- const cleaned = translated.split('\n').filter(line =>
204
- !line.startsWith('Loaded ') &&
205
- !line.startsWith('[dotenv') &&
206
- !line.startsWith('Registered ') &&
207
- !line.startsWith('Updated ') &&
208
- !line.startsWith('[AgentRuntime]') &&
209
- !line.startsWith('npm ')
210
- ).join('\n').trim();
211
- return cleaned;
212
- }
213
- console.warn(' [WARN] Translation output too short, using Korean fallback.');
214
- return null;
215
- } catch (err) {
216
- console.warn(` [WARN] Translation failed: ${err.message}`);
217
- console.warn(' Using Korean merged version as fallback.');
218
- return null;
219
- }
220
- }
221
-
222
- // --- Main ---
223
- console.log('Building CrewX manual...');
224
- console.log(` Source: ${INDEX_PATH}`);
225
- console.log(` Output: ${OUTPUT_PATH}`);
226
-
227
- if (!existsSync(INDEX_PATH)) {
228
- console.error(`ERROR: INDEX.md not found at ${INDEX_PATH}`);
229
- process.exit(1);
230
- }
231
-
232
- // Check cache
233
- const currentHash = computeManualHash();
234
- const savedHash = readSavedHash();
235
-
236
- if (!forceFlag && currentHash === savedHash && existsSync(OUTPUT_PATH)) {
237
- console.log('\n No changes detected in docs/manual/. Skipping build.');
238
- console.log(' Use --force to rebuild anyway.');
239
- process.exit(0);
240
- }
241
-
242
- if (forceFlag) {
243
- console.log(' --force flag set, bypassing cache.');
244
- }
245
-
246
- // Merge
247
- const merged = processFile('INDEX.md');
248
- const fileCount = inlined.size;
249
- const mergedLineCount = merged.split('\n').length;
250
- console.log(`\n Merged: ${fileCount} files, ${mergedLineCount} lines.`);
251
-
252
- // Translate
253
- const translated = translateToEnglish(merged);
254
- const finalContent = translated || merged;
255
- const wasTranslated = translated !== null;
256
-
257
- // Write output
258
- const header = `<!-- AUTO-GENERATED: Do not edit manually. Run "npm run build:manual" to regenerate. -->\n\n`;
259
- writeFileSync(OUTPUT_PATH, header + finalContent.trim() + '\n', 'utf-8');
260
-
261
- // Save hash
262
- saveHash(currentHash);
263
-
264
- const outputLineCount = finalContent.split('\n').length;
265
- console.log(` Output: ${OUTPUT_PATH}`);
266
- console.log(` ${outputLineCount} lines written${wasTranslated ? ' (English)' : ' (Korean fallback)'}`);
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Build manual: Compile docs/manual/ markdown files into a single crewx-manual.md
4
+ *
5
+ * 1. Reads docs/manual/INDEX.md, resolves internal .md links inline,
6
+ * strips image references → merged Korean markdown
7
+ * 2. Translates to English via `crewx q '@tech_writer ...' --raw`
8
+ * 3. Saves result to packages/cli/templates/documents/crewx-manual.md
9
+ *
10
+ * Caching: computes hash of docs/manual/ files, skips translation if unchanged.
11
+ * Use --force to bypass cache and force re-translation.
12
+ */
13
+
14
+ import { readFileSync, writeFileSync, existsSync, readdirSync, mkdirSync } from 'fs';
15
+ import { join, dirname, resolve } from 'path';
16
+ import { fileURLToPath } from 'url';
17
+ import { createHash } from 'crypto';
18
+ import { execSync } from 'child_process';
19
+
20
+ const __dirname = dirname(fileURLToPath(import.meta.url));
21
+ const ROOT = resolve(__dirname, '..');
22
+ const MANUAL_DIR = join(ROOT, 'docs', 'manual');
23
+ const INDEX_PATH = join(MANUAL_DIR, 'INDEX.md');
24
+ const OUTPUT_PATH = join(ROOT, 'packages', 'cli', 'templates', 'documents', 'crewx-manual.md');
25
+ const HASH_DIR = join(ROOT, '.crewx');
26
+ const HASH_PATH = join(HASH_DIR, 'manual-hash');
27
+ const CREWX_BIN = join(ROOT, 'crewx');
28
+
29
+ const forceFlag = process.argv.includes('--force');
30
+
31
+ // Track already-inlined files to avoid duplicates
32
+ const inlined = new Set();
33
+
34
+ /**
35
+ * Read a markdown file relative to MANUAL_DIR
36
+ */
37
+ function readManualFile(relPath) {
38
+ const abs = join(MANUAL_DIR, relPath);
39
+ if (!existsSync(abs)) {
40
+ console.warn(` [WARN] File not found: ${relPath}`);
41
+ return null;
42
+ }
43
+ return readFileSync(abs, 'utf-8');
44
+ }
45
+
46
+ /**
47
+ * Strip image links: ![alt](path) and standalone <img> tags
48
+ */
49
+ function stripImages(content) {
50
+ return content
51
+ .replace(/!\[[^\]]*\]\([^)]+\)/g, '')
52
+ .replace(/<img[^>]*>/gi, '');
53
+ }
54
+
55
+ /**
56
+ * Extract .md links from a line.
57
+ * Matches [text](path.md) patterns where path is a relative .md file.
58
+ */
59
+ function extractMdLinks(content) {
60
+ const links = [];
61
+ const re = /\[([^\]]+)\]\(([^)]+\.md)\)/g;
62
+ let m;
63
+ while ((m = re.exec(content)) !== null) {
64
+ links.push({ text: m[1], path: m[2] });
65
+ }
66
+ return links;
67
+ }
68
+
69
+ /**
70
+ * Process a markdown file: inline linked .md files, strip images.
71
+ * For INDEX files, replace link references with the actual file content.
72
+ * For regular files, just replace internal .md links with anchors.
73
+ */
74
+ function processFile(relPath, depth = 0) {
75
+ if (inlined.has(relPath)) {
76
+ return `<!-- Already included: ${relPath} -->\n`;
77
+ }
78
+ inlined.add(relPath);
79
+
80
+ const content = readManualFile(relPath);
81
+ if (content === null) return '';
82
+
83
+ const dir = dirname(relPath);
84
+ let result = stripImages(content);
85
+
86
+ // Find all .md links and inline them
87
+ const links = extractMdLinks(result);
88
+
89
+ for (const link of links) {
90
+ // Resolve relative path from current file's directory
91
+ const linkedPath = dir === '.' ? link.path : join(dir, link.path);
92
+ const normalizedPath = linkedPath.replace(/\\/g, '/');
93
+
94
+ // Replace the markdown link with just the link text (as a reference)
95
+ const linkPattern = `[${link.text}](${link.path})`;
96
+
97
+ if (inlined.has(normalizedPath)) {
98
+ // Already inlined — just replace link with text
99
+ result = result.replace(linkPattern, `**${link.text}**`);
100
+ continue;
101
+ }
102
+
103
+ // Read and inline the linked file
104
+ const linkedContent = readManualFile(normalizedPath);
105
+ if (linkedContent === null) {
106
+ // Leave the link text but note it was not found
107
+ result = result.replace(linkPattern, `**${link.text}** _(문서 없음)_`);
108
+ continue;
109
+ }
110
+
111
+ inlined.add(normalizedPath);
112
+
113
+ // Process the linked file content (strip images, but don't recurse too deep)
114
+ let processed = stripImages(linkedContent);
115
+ if (depth < 2) {
116
+ // Recursively resolve links in linked files (max 2 levels)
117
+ const subLinks = extractMdLinks(processed);
118
+ for (const sub of subLinks) {
119
+ const subPath = dirname(normalizedPath) === '.'
120
+ ? sub.path
121
+ : join(dirname(normalizedPath), sub.path);
122
+ const normalizedSub = subPath.replace(/\\/g, '/');
123
+
124
+ const subPattern = `[${sub.text}](${sub.path})`;
125
+ if (inlined.has(normalizedSub)) {
126
+ processed = processed.replace(subPattern, `**${sub.text}**`);
127
+ } else {
128
+ // Just convert to text reference (don't deep-inline sub-sub files)
129
+ processed = processed.replace(subPattern, `**${sub.text}**`);
130
+ }
131
+ }
132
+ }
133
+
134
+ // Replace the link in the parent with the full content
135
+ result = result.replace(linkPattern, `**${link.text}**`);
136
+
137
+ // Append the file content after the current section
138
+ result += `\n---\n\n${processed}\n`;
139
+ }
140
+
141
+ // Clean up excessive blank lines
142
+ result = result.replace(/\n{4,}/g, '\n\n\n');
143
+
144
+ return result;
145
+ }
146
+
147
+ /**
148
+ * Recursively collect all .md files under a directory and compute a combined hash.
149
+ */
150
+ function collectMdFiles(dir) {
151
+ const files = [];
152
+ for (const entry of readdirSync(dir, { withFileTypes: true })) {
153
+ const full = join(dir, entry.name);
154
+ if (entry.isDirectory()) {
155
+ files.push(...collectMdFiles(full));
156
+ } else if (entry.name.endsWith('.md')) {
157
+ files.push(full);
158
+ }
159
+ }
160
+ return files.sort();
161
+ }
162
+
163
+ function computeManualHash() {
164
+ const hash = createHash('sha256');
165
+ for (const f of collectMdFiles(MANUAL_DIR)) {
166
+ hash.update(readFileSync(f));
167
+ }
168
+ return hash.digest('hex');
169
+ }
170
+
171
+ function readSavedHash() {
172
+ if (!existsSync(HASH_PATH)) return null;
173
+ return readFileSync(HASH_PATH, 'utf-8').trim();
174
+ }
175
+
176
+ function saveHash(h) {
177
+ mkdirSync(HASH_DIR, { recursive: true });
178
+ writeFileSync(HASH_PATH, h + '\n', 'utf-8');
179
+ }
180
+
181
+ /**
182
+ * Translate Korean markdown to English via crewx CLI.
183
+ * Returns translated text, or null on failure.
184
+ */
185
+ function translateToEnglish(koreanMd) {
186
+ const prompt = `@tech_writer Translate the following Korean CrewX manual to English. Keep all markdown formatting, code blocks, YAML examples, and technical terms (crewx, CLI commands, flag names) exactly as-is. Only translate Korean prose to natural English. Do NOT add any commentary or explanation — output ONLY the translated markdown.`;
187
+
188
+ try {
189
+ console.log(' Translating to English via crewx q ...');
190
+ const translated = execSync(
191
+ `"${CREWX_BIN}" q '${prompt.replace(/'/g, "'\\''")}' --raw 2>/dev/null`,
192
+ {
193
+ input: koreanMd,
194
+ cwd: ROOT,
195
+ encoding: 'utf-8',
196
+ maxBuffer: 50 * 1024 * 1024, // 50MB
197
+ timeout: 15 * 60 * 1000, // 15 min
198
+ env: { ...process.env, CLAUDECODE: undefined },
199
+ }
200
+ );
201
+ if (translated && translated.trim().length > 100) {
202
+ // Strip crewx runtime log lines from output
203
+ const cleaned = translated.split('\n').filter(line =>
204
+ !line.startsWith('Loaded ') &&
205
+ !line.startsWith('[dotenv') &&
206
+ !line.startsWith('Registered ') &&
207
+ !line.startsWith('Updated ') &&
208
+ !line.startsWith('[AgentRuntime]') &&
209
+ !line.startsWith('npm ')
210
+ ).join('\n').trim();
211
+ return cleaned;
212
+ }
213
+ console.warn(' [WARN] Translation output too short, using Korean fallback.');
214
+ return null;
215
+ } catch (err) {
216
+ console.warn(` [WARN] Translation failed: ${err.message}`);
217
+ console.warn(' Using Korean merged version as fallback.');
218
+ return null;
219
+ }
220
+ }
221
+
222
+ // --- Main ---
223
+ console.log('Building CrewX manual...');
224
+ console.log(` Source: ${INDEX_PATH}`);
225
+ console.log(` Output: ${OUTPUT_PATH}`);
226
+
227
+ if (!existsSync(INDEX_PATH)) {
228
+ console.error(`ERROR: INDEX.md not found at ${INDEX_PATH}`);
229
+ process.exit(1);
230
+ }
231
+
232
+ // Check cache
233
+ const currentHash = computeManualHash();
234
+ const savedHash = readSavedHash();
235
+
236
+ if (!forceFlag && currentHash === savedHash && existsSync(OUTPUT_PATH)) {
237
+ console.log('\n No changes detected in docs/manual/. Skipping build.');
238
+ console.log(' Use --force to rebuild anyway.');
239
+ process.exit(0);
240
+ }
241
+
242
+ if (forceFlag) {
243
+ console.log(' --force flag set, bypassing cache.');
244
+ }
245
+
246
+ // Merge
247
+ const merged = processFile('INDEX.md');
248
+ const fileCount = inlined.size;
249
+ const mergedLineCount = merged.split('\n').length;
250
+ console.log(`\n Merged: ${fileCount} files, ${mergedLineCount} lines.`);
251
+
252
+ // Translate
253
+ const translated = translateToEnglish(merged);
254
+ const finalContent = translated || merged;
255
+ const wasTranslated = translated !== null;
256
+
257
+ // Write output
258
+ const header = `<!-- AUTO-GENERATED: Do not edit manually. Run "npm run build:manual" to regenerate. -->\n\n`;
259
+ writeFileSync(OUTPUT_PATH, header + finalContent.trim() + '\n', 'utf-8');
260
+
261
+ // Save hash
262
+ saveHash(currentHash);
263
+
264
+ const outputLineCount = finalContent.split('\n').length;
265
+ console.log(` Output: ${OUTPUT_PATH}`);
266
+ console.log(` ${outputLineCount} lines written${wasTranslated ? ' (English)' : ' (Korean fallback)'}`);
@@ -1,7 +1,7 @@
1
- import { writeFileSync, mkdirSync, existsSync } from 'node:fs';
2
- import { join } from 'node:path';
3
-
4
- const target = join('dist-server', 'package.json');
5
- if (!existsSync('dist-server')) mkdirSync('dist-server', { recursive: true });
6
- writeFileSync(target, JSON.stringify({ type: 'commonjs' }, null, 2));
7
- console.log('[emit-dist-server-package-json]', target, '→ type: commonjs');
1
+ import { writeFileSync, mkdirSync, existsSync } from 'node:fs';
2
+ import { join } from 'node:path';
3
+
4
+ const target = join('dist-server', 'package.json');
5
+ if (!existsSync('dist-server')) mkdirSync('dist-server', { recursive: true });
6
+ writeFileSync(target, JSON.stringify({ type: 'commonjs' }, null, 2));
7
+ console.log('[emit-dist-server-package-json]', target, '→ type: commonjs');
@@ -0,0 +1 @@
1
+ {"version":"4.0.18","results":[[":__tests__/migrate-frontmatter-v2.test.ts",{"duration":5.302541999999988,"failed":false}]]}
@@ -1,44 +1,44 @@
1
- #!/usr/bin/env node
2
- /**
3
- * Cross-platform postinstall: symlink bin/crewx.js → node_modules/.bin/crewx
4
- * Works on Windows (junction/symlink), macOS, and Linux.
5
- *
6
- * NOTE: When installed globally via `npm i -g`, npm uses the `bin` field in
7
- * package.json to create the executable — this script only handles the local
8
- * monorepo dev case (npm install in the workspace root).
9
- */
10
- import { existsSync, symlinkSync, unlinkSync, lstatSync } from 'fs';
11
- import { join, dirname } from 'path';
12
- import { fileURLToPath } from 'url';
13
-
14
- const __dirname = dirname(fileURLToPath(import.meta.url));
15
- const root = join(__dirname, '..');
16
- const binDir = join(root, 'node_modules', '.bin');
17
- const linkPath = join(binDir, 'crewx');
18
- const target = join('..', '..', 'bin', 'crewx.js');
19
-
20
- if (!existsSync(binDir)) {
21
- // node_modules/.bin doesn't exist yet — nothing to do
22
- process.exit(0);
23
- }
24
-
25
- try {
26
- // Remove stale link if present
27
- if (existsSync(linkPath) || lstatExistsSafe(linkPath)) {
28
- unlinkSync(linkPath);
29
- }
30
- symlinkSync(target, linkPath);
31
- console.log('✅ crewx dev symlink created');
32
- } catch (err) {
33
- // Non-fatal: global installs use the bin field, so this is optional
34
- console.warn('⚠️ postinstall symlink skipped:', err.message);
35
- }
36
-
37
- function lstatExistsSafe(p) {
38
- try {
39
- lstatSync(p);
40
- return true;
41
- } catch {
42
- return false;
43
- }
44
- }
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Cross-platform postinstall: symlink bin/crewx.js → node_modules/.bin/crewx
4
+ * Works on Windows (junction/symlink), macOS, and Linux.
5
+ *
6
+ * NOTE: When installed globally via `npm i -g`, npm uses the `bin` field in
7
+ * package.json to create the executable — this script only handles the local
8
+ * monorepo dev case (npm install in the workspace root).
9
+ */
10
+ import { existsSync, symlinkSync, unlinkSync, lstatSync } from 'fs';
11
+ import { join, dirname } from 'path';
12
+ import { fileURLToPath } from 'url';
13
+
14
+ const __dirname = dirname(fileURLToPath(import.meta.url));
15
+ const root = join(__dirname, '..');
16
+ const binDir = join(root, 'node_modules', '.bin');
17
+ const linkPath = join(binDir, 'crewx');
18
+ const target = join('..', '..', 'bin', 'crewx.js');
19
+
20
+ if (!existsSync(binDir)) {
21
+ // node_modules/.bin doesn't exist yet — nothing to do
22
+ process.exit(0);
23
+ }
24
+
25
+ try {
26
+ // Remove stale link if present
27
+ if (existsSync(linkPath) || lstatExistsSafe(linkPath)) {
28
+ unlinkSync(linkPath);
29
+ }
30
+ symlinkSync(target, linkPath);
31
+ console.log('✅ crewx dev symlink created');
32
+ } catch (err) {
33
+ // Non-fatal: global installs use the bin field, so this is optional
34
+ console.warn('⚠️ postinstall symlink skipped:', err.message);
35
+ }
36
+
37
+ function lstatExistsSafe(p) {
38
+ try {
39
+ lstatSync(p);
40
+ return true;
41
+ } catch {
42
+ return false;
43
+ }
44
+ }