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.
- package/README.md +268 -268
- package/bin/cli-commands.js +34 -0
- package/bin/crewx-lib.js +213 -108
- package/bin/crewx-ui.js +83 -83
- package/bin/crewx.js +219 -147
- package/bin/launcher-flags.js +29 -0
- package/bin/package.json +1 -1
- package/dist/assets/MarketPage-DptjaFpT.js +36 -0
- package/dist/assets/{PromptTab-DVKc7hJY.js → PromptTab-DZha2_v1.js} +1 -1
- package/dist/assets/{_baseUniq-wjlVo2E6.js → _baseUniq-jd6NubI3.js} +1 -1
- package/dist/assets/{arc-BfPgRtzW.js → arc-C2te3-8P.js} +1 -1
- package/dist/assets/{architectureDiagram-Q4EWVU46-ewcueFAG.js → architectureDiagram-Q4EWVU46-CbIQua02.js} +1 -1
- package/dist/assets/{blockDiagram-DXYQGD6D-TxlbbvKn.js → blockDiagram-DXYQGD6D-Cg7qkpSM.js} +1 -1
- package/dist/assets/{c4Diagram-AHTNJAMY-C1lT_bl_.js → c4Diagram-AHTNJAMY-BkffDY1F.js} +1 -1
- package/dist/assets/channel-beae0DeI.js +1 -0
- package/dist/assets/chatgpt-logo-dark.svg +15 -15
- package/dist/assets/chatgpt-logo.svg +15 -15
- package/dist/assets/{chunk-4BX2VUAB-C41j2mCL.js → chunk-4BX2VUAB-BxHe9wPE.js} +1 -1
- package/dist/assets/{chunk-4TB4RGXK-HNNsUbz0.js → chunk-4TB4RGXK--f1tN90O.js} +1 -1
- package/dist/assets/{chunk-55IACEB6-qtCgO0r2.js → chunk-55IACEB6-B5QXfPXQ.js} +1 -1
- package/dist/assets/{chunk-EDXVE4YY-BSnDYtsd.js → chunk-EDXVE4YY-DqKhblOg.js} +1 -1
- package/dist/assets/{chunk-FMBD7UC4-DyHRLQqX.js → chunk-FMBD7UC4-DZ1w_G02.js} +1 -1
- package/dist/assets/{chunk-OYMX7WX6-CCjfi6WS.js → chunk-OYMX7WX6-BqAgQpv8.js} +1 -1
- package/dist/assets/{chunk-QZHKN3VN-COLty8kd.js → chunk-QZHKN3VN-DPqnGqVi.js} +1 -1
- package/dist/assets/{chunk-YZCP3GAM-CHUUnGeN.js → chunk-YZCP3GAM-x-ZYSQLd.js} +1 -1
- package/dist/assets/classDiagram-6PBFFD2Q-BquWrs1y.js +1 -0
- package/dist/assets/classDiagram-v2-HSJHXN6E-BquWrs1y.js +1 -0
- package/dist/assets/clone-C9wSPtDN.js +1 -0
- package/dist/assets/{cose-bilkent-S5V4N54A-CSip-V2g.js → cose-bilkent-S5V4N54A-4VCNRP-E.js} +1 -1
- package/dist/assets/{dagre-KV5264BT-DkdpnWhv.js → dagre-KV5264BT-DucVi1QS.js} +1 -1
- package/dist/assets/{diagram-5BDNPKRD-PH4qc6PV.js → diagram-5BDNPKRD-ChdRA8bE.js} +1 -1
- package/dist/assets/{diagram-G4DWMVQ6-Cg5xZcjx.js → diagram-G4DWMVQ6-B1-97yHr.js} +1 -1
- package/dist/assets/{diagram-MMDJMWI5-soKmeTCW.js → diagram-MMDJMWI5-CQs3cO7G.js} +1 -1
- package/dist/assets/{diagram-TYMM5635-Daq5Mihu.js → diagram-TYMM5635-CuNCxDfO.js} +1 -1
- package/dist/assets/{erDiagram-SMLLAGMA-kr2OtY0Y.js → erDiagram-SMLLAGMA-DdR8v8g-.js} +1 -1
- package/dist/assets/{flowDiagram-DWJPFMVM-DQZCb8gm.js → flowDiagram-DWJPFMVM-Dt02upId.js} +1 -1
- package/dist/assets/{ganttDiagram-T4ZO3ILL-BHkn485T.js → ganttDiagram-T4ZO3ILL-cG_k9VOa.js} +1 -1
- package/dist/assets/{gitGraphDiagram-UUTBAWPF-FaCyYFmC.js → gitGraphDiagram-UUTBAWPF-Dz1DjhKq.js} +1 -1
- package/dist/assets/{graph-BVJlrP6V.js → graph-CF2NtM33.js} +1 -1
- package/dist/assets/{infoDiagram-42DDH7IO-DJOWkKdM.js → infoDiagram-42DDH7IO-tQWKrYM6.js} +1 -1
- package/dist/assets/{ishikawaDiagram-UXIWVN3A-VfpvNaIf.js → ishikawaDiagram-UXIWVN3A-CLuUMkF0.js} +1 -1
- package/dist/assets/{journeyDiagram-VCZTEJTY-CPzsak-v.js → journeyDiagram-VCZTEJTY-a6JenLCk.js} +1 -1
- package/dist/assets/{kanban-definition-6JOO6SKY-DFqLDBU0.js → kanban-definition-6JOO6SKY-rqOxTzYb.js} +1 -1
- package/dist/assets/{layout-CCSbNPHm.js → layout-Dvic1Hpy.js} +1 -1
- package/dist/assets/{linear-C4T7PCKE.js → linear-CfMV1S6a.js} +1 -1
- package/dist/assets/main-05K4ggqd.css +10 -0
- package/dist/assets/main-CCM1gtr8.js +1165 -0
- package/dist/assets/{min-CGQNEYGh.js → min-GpF3DZux.js} +1 -1
- package/dist/assets/{mindmap-definition-QFDTVHPH-AuU1EqwS.js → mindmap-definition-QFDTVHPH-Cg80z0Jx.js} +1 -1
- package/dist/assets/{pieDiagram-DEJITSTG-CopkCZwp.js → pieDiagram-DEJITSTG-BrtK7lAq.js} +1 -1
- package/dist/assets/{quadrantDiagram-34T5L4WZ-lMKrSv_t.js → quadrantDiagram-34T5L4WZ-BL2txAAS.js} +1 -1
- package/dist/assets/{requirementDiagram-MS252O5E-dWUpHOFb.js → requirementDiagram-MS252O5E-Co3wpBnu.js} +1 -1
- package/dist/assets/{sankeyDiagram-XADWPNL6-C8UQx9Bb.js → sankeyDiagram-XADWPNL6-B4KJXdQ4.js} +1 -1
- package/dist/assets/{sequenceDiagram-FGHM5R23-CUVNIItJ.js → sequenceDiagram-FGHM5R23-xs5OuzvV.js} +1 -1
- package/dist/assets/{stateDiagram-FHFEXIEX-Ct0GamGl.js → stateDiagram-FHFEXIEX-bbEP20JD.js} +1 -1
- package/dist/assets/stateDiagram-v2-QKLJ7IA2-XYh9U1A7.js +1 -0
- package/dist/assets/{timeline-definition-GMOUNBTQ-ul8Po7f7.js → timeline-definition-GMOUNBTQ-CTc2wVwC.js} +1 -1
- package/dist/assets/{vennDiagram-DHZGUBPP-B4AOWQnP.js → vennDiagram-DHZGUBPP-oJpXott7.js} +1 -1
- package/dist/assets/{wardley-RL74JXVD-Dr7Wp3AJ.js → wardley-RL74JXVD-aTmOXkKh.js} +1 -1
- package/dist/assets/{wardleyDiagram-NUSXRM2D-Ck70faXX.js → wardleyDiagram-NUSXRM2D-C83SOkig.js} +1 -1
- package/dist/assets/{xychartDiagram-5P7HB3ND-Bsy5-cNt.js → xychartDiagram-5P7HB3ND-BScws0tF.js} +1 -1
- package/dist/index.html +13 -13
- package/dist-electron/main.js +153 -116
- package/dist-electron/overlay.js +102 -65
- package/dist-electron/package.json +1 -0
- package/dist-electron/preload.js +8 -8
- package/dist-server/bootstrap/crewx-server.js +19 -10
- package/dist-server/domain/agent/agent.service.js +12 -0
- package/dist-server/domain/agent/dto/update-agent.dto.js +20 -1
- package/dist-server/domain/mcp/crewx-tool.factory.js +44 -113
- package/dist-server/domain/mcp/mcp.module.js +2 -0
- package/dist-server/domain/mcp/mcp.service.js +37 -12
- package/dist-server/domain/message/message.service.js +21 -13
- package/dist-server/domain/skill/skill.service.js +63 -43
- package/dist-server/domain/task/task.module.js +2 -0
- package/dist-server/domain/task/task.service.js +17 -10
- package/dist-server/domain/thread/dto/update-thread.dto.js +23 -0
- package/dist-server/domain/thread/thread.controller.js +16 -0
- package/dist-server/domain/thread/thread.service.js +9 -0
- package/dist-server/main.js +1 -1
- package/dist-server/modules/crewx.module.js +16 -1
- package/dist-server/repository/box.repository.js +20 -20
- package/dist-server/repository/project.repository.js +13 -13
- package/dist-server/repository/request-log.repository.js +10 -10
- package/dist-server/repository/task.repository.js +72 -72
- package/dist-server/repository/thread.repository.js +78 -58
- package/package.json +6 -6
- package/packages/cli/dist/bootstrap/crewx-cli.js +12 -0
- package/packages/cli/dist/commands/agent.js +23 -23
- package/packages/cli/dist/commands/init.js +19 -19
- package/packages/cli/dist/commands/parse-common-flags.d.ts +19 -3
- package/packages/cli/dist/commands/parse-common-flags.js +46 -6
- package/packages/cli/dist/commands/registry.d.ts +13 -0
- package/packages/cli/dist/commands/registry.js +29 -0
- package/packages/cli/dist/commands/task-db.js +7 -7
- package/packages/cli/dist/examples/deny-secrets-plugin.d.ts +22 -0
- package/packages/cli/dist/examples/deny-secrets-plugin.js +40 -0
- package/packages/cli/dist/main.js +134 -68
- package/packages/cli/dist/plugins/examples/echo-hook.d.ts +24 -0
- package/packages/cli/dist/plugins/examples/echo-hook.js +60 -0
- package/packages/cli/dist/plugins/examples/verify-echo-hook.d.ts +8 -0
- package/packages/cli/dist/plugins/examples/verify-echo-hook.js +47 -0
- package/packages/cli/dist/plugins/sqlite-tracing.d.ts +11 -0
- package/packages/cli/dist/plugins/sqlite-tracing.js +19 -0
- package/packages/cli/dist/schema/tasks.d.ts +7 -0
- package/packages/cli/dist/schema/tasks.js +48 -0
- package/packages/cli/package.json +52 -52
- package/scripts/analyze-task-logs.mjs +569 -0
- package/scripts/build-manual.mjs +266 -266
- package/scripts/emit-dist-server-package-json.mjs +7 -7
- package/scripts/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -0
- package/scripts/postinstall.mjs +44 -44
- package/scripts/smoke-tarball.mjs +285 -285
- package/scripts/snapshot-msg-list.sh +52 -52
- package/server.js +167 -164
- package/dist/assets/MarketPage-Dwsg6K-B.js +0 -31
- package/dist/assets/channel-BP4PNMmz.js +0 -1
- package/dist/assets/classDiagram-6PBFFD2Q-Upr3UAcM.js +0 -1
- package/dist/assets/classDiagram-v2-HSJHXN6E-Upr3UAcM.js +0 -1
- package/dist/assets/clone-B8BP7ReZ.js +0 -1
- package/dist/assets/main-CELBpK6r.js +0 -1166
- package/dist/assets/main-CmP-VosD.css +0 -10
- package/dist/assets/stateDiagram-v2-QKLJ7IA2-CFBLQDEx.js +0 -1
- package/dist-server/domain/task/dto/project-usage.dto.js +0 -38
- package/dist-server/domain/thread/dto/send-message.dto.js +0 -10
package/scripts/build-manual.mjs
CHANGED
|
@@ -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:  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:  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');
|
package/scripts/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":"4.0.18","results":[[":__tests__/migrate-frontmatter-v2.test.ts",{"duration":5.302541999999988,"failed":false}]]}
|
package/scripts/postinstall.mjs
CHANGED
|
@@ -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
|
+
}
|