growork 1.0.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.
package/dist/index.js ADDED
@@ -0,0 +1,681 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/index.ts
4
+ import { Command } from "commander";
5
+
6
+ // src/commands/init.ts
7
+ import * as fs2 from "fs";
8
+ import * as path2 from "path";
9
+ import chalk from "chalk";
10
+
11
+ // src/utils/config.ts
12
+ import * as fs from "fs";
13
+ import * as path from "path";
14
+ import * as yaml from "yaml";
15
+ var CONFIG_FILE_NAME = "growork.config.yaml";
16
+ function getConfigPath() {
17
+ return path.join(process.cwd(), CONFIG_FILE_NAME);
18
+ }
19
+ function configExists() {
20
+ return fs.existsSync(getConfigPath());
21
+ }
22
+ function loadConfig() {
23
+ const configPath = getConfigPath();
24
+ if (!fs.existsSync(configPath)) {
25
+ throw new Error(`\u914D\u7F6E\u6587\u4EF6\u4E0D\u5B58\u5728: ${configPath}
26
+ \u8BF7\u5148\u8FD0\u884C growork init \u521D\u59CB\u5316\u914D\u7F6E`);
27
+ }
28
+ const content = fs.readFileSync(configPath, "utf-8");
29
+ const config = yaml.parse(content);
30
+ if (!config.docs || config.docs.length === 0) {
31
+ throw new Error("\u914D\u7F6E\u6587\u4EF6\u4E2D\u6CA1\u6709\u914D\u7F6E\u4EFB\u4F55\u6587\u6863");
32
+ }
33
+ const hasFeishuDocs = config.docs.some((d) => d.type === "feishu");
34
+ const hasNotionDocs = config.docs.some((d) => d.type === "notion");
35
+ if (hasFeishuDocs && (!config.feishu?.appId || !config.feishu?.appSecret)) {
36
+ throw new Error("\u914D\u7F6E\u6587\u4EF6\u7F3A\u5C11\u98DE\u4E66\u51ED\u8BC1 (feishu.appId, feishu.appSecret)");
37
+ }
38
+ if (hasNotionDocs && !config.notion?.token) {
39
+ throw new Error("\u914D\u7F6E\u6587\u4EF6\u7F3A\u5C11 Notion \u51ED\u8BC1 (notion.token)");
40
+ }
41
+ return config;
42
+ }
43
+ function getDefaultConfig() {
44
+ return `# Growork \u914D\u7F6E\u6587\u4EF6
45
+
46
+ # \u98DE\u4E66\u5E94\u7528\u51ED\u8BC1 (\u4F7F\u7528\u98DE\u4E66\u6587\u6863\u65F6\u9700\u8981)
47
+ feishu:
48
+ appId: "cli_xxxx"
49
+ appSecret: "xxxx"
50
+ domain: "feishu" # feishu(\u4E2D\u56FD\u7248) \u6216 lark(\u56FD\u9645\u7248)
51
+
52
+ # Notion \u51ED\u8BC1 (\u4F7F\u7528 Notion \u6587\u6863\u65F6\u9700\u8981)
53
+ notion:
54
+ token: "ntn_xxxx" # Notion Integration Token
55
+
56
+ # \u6587\u6863\u540C\u6B65\u914D\u7F6E
57
+ docs:
58
+ # \u98DE\u4E66\u6587\u6863\u793A\u4F8B
59
+ - name: prd
60
+ type: feishu
61
+ url: "https://xxx.feishu.cn/docx/xxxxx"
62
+ output: "docs/product/prd.md"
63
+
64
+ # Notion \u6587\u6863\u793A\u4F8B
65
+ - name: notion-prd
66
+ type: notion
67
+ url: "https://www.notion.so/xxxxx"
68
+ output: "docs/product/notion-prd.md"
69
+ `;
70
+ }
71
+
72
+ // src/commands/init.ts
73
+ var DIRS_TO_CREATE = [
74
+ "docs/product",
75
+ "docs/design",
76
+ "docs/api",
77
+ "docs/tech",
78
+ "docs/test"
79
+ ];
80
+ async function initCommand() {
81
+ console.log(chalk.blue("\u{1F4C1} \u521D\u59CB\u5316 Growork \u9879\u76EE...\n"));
82
+ const cwd = process.cwd();
83
+ for (const dir of DIRS_TO_CREATE) {
84
+ const fullPath = path2.join(cwd, dir);
85
+ if (!fs2.existsSync(fullPath)) {
86
+ fs2.mkdirSync(fullPath, { recursive: true });
87
+ console.log(chalk.green(` \u2713 \u521B\u5EFA\u76EE\u5F55: ${dir}`));
88
+ } else {
89
+ console.log(chalk.gray(` - \u76EE\u5F55\u5DF2\u5B58\u5728: ${dir}`));
90
+ }
91
+ }
92
+ const configPath = getConfigPath();
93
+ if (!configExists()) {
94
+ fs2.writeFileSync(configPath, getDefaultConfig(), "utf-8");
95
+ console.log(chalk.green(` \u2713 \u521B\u5EFA\u914D\u7F6E\u6587\u4EF6: growork.config.yaml`));
96
+ } else {
97
+ console.log(chalk.gray(` - \u914D\u7F6E\u6587\u4EF6\u5DF2\u5B58\u5728: growork.config.yaml`));
98
+ }
99
+ const gitignorePath = path2.join(cwd, ".gitignore");
100
+ const ignoreEntry = "growork.config.yaml";
101
+ if (fs2.existsSync(gitignorePath)) {
102
+ const content = fs2.readFileSync(gitignorePath, "utf-8");
103
+ if (!content.includes(ignoreEntry)) {
104
+ fs2.appendFileSync(gitignorePath, `
105
+ # Growork \u914D\u7F6E\u6587\u4EF6\uFF08\u5305\u542B\u654F\u611F\u51ED\u8BC1\uFF09
106
+ ${ignoreEntry}
107
+ `);
108
+ console.log(chalk.green(` \u2713 \u5DF2\u5C06\u914D\u7F6E\u6587\u4EF6\u6DFB\u52A0\u5230 .gitignore`));
109
+ }
110
+ } else {
111
+ fs2.writeFileSync(gitignorePath, `# Growork \u914D\u7F6E\u6587\u4EF6\uFF08\u5305\u542B\u654F\u611F\u51ED\u8BC1\uFF09
112
+ ${ignoreEntry}
113
+ `);
114
+ console.log(chalk.green(` \u2713 \u521B\u5EFA .gitignore \u5E76\u6DFB\u52A0\u914D\u7F6E\u6587\u4EF6`));
115
+ }
116
+ console.log(chalk.blue("\n\u2705 \u521D\u59CB\u5316\u5B8C\u6210\uFF01\n"));
117
+ console.log(chalk.yellow("\u4E0B\u4E00\u6B65\uFF1A"));
118
+ console.log(" 1. \u7F16\u8F91 growork.config.yaml\uFF0C\u586B\u5165\u98DE\u4E66\u51ED\u8BC1\u548C\u6587\u6863\u94FE\u63A5");
119
+ console.log(" 2. \u8FD0\u884C growork sync \u540C\u6B65\u6587\u6863\n");
120
+ }
121
+
122
+ // src/commands/sync.ts
123
+ import * as fs3 from "fs";
124
+ import * as path3 from "path";
125
+ import chalk2 from "chalk";
126
+
127
+ // src/services/feishu.ts
128
+ import * as lark from "@larksuiteoapi/node-sdk";
129
+ var FeishuService = class {
130
+ constructor(config) {
131
+ const domain = config.domain === "lark" ? lark.Domain.Lark : lark.Domain.Feishu;
132
+ this.client = new lark.Client({
133
+ appId: config.appId,
134
+ appSecret: config.appSecret,
135
+ disableTokenCache: false,
136
+ domain
137
+ });
138
+ }
139
+ parseDocumentId(url) {
140
+ const docxMatch = url.match(/\/docx\/([a-zA-Z0-9]+)/);
141
+ if (docxMatch) {
142
+ return { type: "docx", documentId: docxMatch[1] };
143
+ }
144
+ const wikiMatch = url.match(/\/wiki\/([a-zA-Z0-9]+)/);
145
+ if (wikiMatch) {
146
+ return { type: "wiki", documentId: wikiMatch[1] };
147
+ }
148
+ throw new Error(`\u65E0\u6CD5\u89E3\u6790\u98DE\u4E66\u6587\u6863 URL: ${url}`);
149
+ }
150
+ async getWikiNodeInfo(token) {
151
+ const response = await this.client.wiki.v2.space.getNode({
152
+ params: { token }
153
+ });
154
+ const objToken = response.data?.node?.obj_token;
155
+ const objType = response.data?.node?.obj_type;
156
+ if (!objToken) {
157
+ throw new Error(`\u65E0\u6CD5\u83B7\u53D6 Wiki \u8282\u70B9\u4FE1\u606F\uFF0C\u8BF7\u68C0\u67E5\u6743\u9650\u548C\u94FE\u63A5\u662F\u5426\u6B63\u786E`);
158
+ }
159
+ return { objToken, objType: objType || "docx" };
160
+ }
161
+ async resolveDocumentId(url) {
162
+ const { type, documentId } = this.parseDocumentId(url);
163
+ if (type === "wiki") {
164
+ const nodeInfo = await this.getWikiNodeInfo(documentId);
165
+ return nodeInfo.objToken;
166
+ }
167
+ return documentId;
168
+ }
169
+ async getDocumentAsMarkdown(url) {
170
+ const actualDocumentId = await this.resolveDocumentId(url);
171
+ const docInfo = await this.client.docx.document.get({
172
+ path: { document_id: actualDocumentId }
173
+ });
174
+ const title = docInfo.data?.document?.title || "\u672A\u547D\u540D\u6587\u6863";
175
+ const blocks = await this.getAllBlocks(actualDocumentId);
176
+ const blockMap = /* @__PURE__ */ new Map();
177
+ for (const block of blocks) {
178
+ blockMap.set(block.block_id, block);
179
+ }
180
+ const rootBlock = blocks.find((b) => b.block_type === 1);
181
+ if (!rootBlock) {
182
+ throw new Error("\u65E0\u6CD5\u627E\u5230\u6587\u6863\u6839\u8282\u70B9");
183
+ }
184
+ const lines = [];
185
+ lines.push(`# ${title}`);
186
+ lines.push("");
187
+ this.convertChildrenToMarkdown(rootBlock.children || [], blockMap, lines, 0);
188
+ return lines.join("\n");
189
+ }
190
+ async getAllBlocks(documentId) {
191
+ const allBlocks = [];
192
+ let pageToken;
193
+ do {
194
+ const response = await this.client.docx.documentBlock.list({
195
+ path: { document_id: documentId },
196
+ params: {
197
+ page_size: 500,
198
+ page_token: pageToken
199
+ }
200
+ });
201
+ const blocks = response.data?.items || [];
202
+ allBlocks.push(...blocks);
203
+ pageToken = response.data?.page_token;
204
+ } while (pageToken);
205
+ return allBlocks;
206
+ }
207
+ convertChildrenToMarkdown(childIds, blockMap, lines, indent) {
208
+ for (const childId of childIds) {
209
+ const block = blockMap.get(childId);
210
+ if (!block) continue;
211
+ const md = this.blockToMarkdown(block, blockMap, indent);
212
+ if (md !== null) {
213
+ lines.push(md);
214
+ }
215
+ }
216
+ }
217
+ blockToMarkdown(block, blockMap, indent) {
218
+ const blockType = block.block_type;
219
+ const indentStr = " ".repeat(indent);
220
+ switch (blockType) {
221
+ case 1:
222
+ return null;
223
+ case 2:
224
+ const text = this.textElementsToMarkdown(block.text?.elements);
225
+ return text ? text + "\n" : "";
226
+ case 3:
227
+ return `# ${this.textElementsToMarkdown(block.heading1?.elements)}
228
+ `;
229
+ case 4:
230
+ return `## ${this.textElementsToMarkdown(block.heading2?.elements)}
231
+ `;
232
+ case 5:
233
+ return `### ${this.textElementsToMarkdown(block.heading3?.elements)}
234
+ `;
235
+ case 6:
236
+ return `#### ${this.textElementsToMarkdown(block.heading4?.elements)}
237
+ `;
238
+ case 7:
239
+ return `##### ${this.textElementsToMarkdown(block.heading5?.elements)}
240
+ `;
241
+ case 8:
242
+ return `###### ${this.textElementsToMarkdown(block.heading6?.elements)}
243
+ `;
244
+ case 9:
245
+ // Heading7
246
+ case 10:
247
+ // Heading8
248
+ case 11:
249
+ return `###### ${this.textElementsToMarkdown(block[`heading${blockType - 6}`]?.elements)}
250
+ `;
251
+ case 12:
252
+ // Bullet
253
+ case 13:
254
+ const isBullet = blockType === 12;
255
+ const listText = this.textElementsToMarkdown(block[isBullet ? "bullet" : "ordered"]?.elements);
256
+ const listChildren = block.children || [];
257
+ let listResult = `${indentStr}${isBullet ? "-" : "1."} ${listText}`;
258
+ if (listChildren.length > 0) {
259
+ const childLines = [];
260
+ this.convertChildrenToMarkdown(listChildren, blockMap, childLines, indent + 1);
261
+ if (childLines.length > 0) {
262
+ listResult += "\n" + childLines.join("\n");
263
+ }
264
+ }
265
+ return listResult;
266
+ case 14:
267
+ const lang = this.getLanguageName(block.code?.style?.language || 0);
268
+ const codeText = this.textElementsToMarkdown(block.code?.elements);
269
+ return `\`\`\`${lang}
270
+ ${codeText}
271
+ \`\`\`
272
+ `;
273
+ case 15:
274
+ return `> ${this.textElementsToMarkdown(block.quote?.elements)}
275
+ `;
276
+ case 17:
277
+ const checked = block.todo?.style?.done ? "x" : " ";
278
+ return `${indentStr}- [${checked}] ${this.textElementsToMarkdown(block.todo?.elements)}`;
279
+ case 18:
280
+ return "---\n";
281
+ case 19:
282
+ const imageToken = block.image?.token;
283
+ return imageToken ? `![image](${imageToken})
284
+ ` : "";
285
+ case 27:
286
+ const quoteChildren = block.children || [];
287
+ if (quoteChildren.length > 0) {
288
+ const childLines = [];
289
+ this.convertChildrenToMarkdown(quoteChildren, blockMap, childLines, 0);
290
+ return childLines.map((line) => `> ${line}`).join("\n") + "\n";
291
+ }
292
+ return "";
293
+ case 24:
294
+ const gridChildren = block.children || [];
295
+ if (gridChildren.length > 0) {
296
+ const childLines = [];
297
+ this.convertChildrenToMarkdown(gridChildren, blockMap, childLines, indent);
298
+ return childLines.join("\n");
299
+ }
300
+ return "";
301
+ case 25:
302
+ const calloutChildren = block.children || [];
303
+ if (calloutChildren.length > 0) {
304
+ const childLines = [];
305
+ this.convertChildrenToMarkdown(calloutChildren, blockMap, childLines, indent);
306
+ return childLines.join("\n");
307
+ }
308
+ return "";
309
+ case 30:
310
+ const sheetToken = block.sheet?.token;
311
+ return sheetToken ? `[\u5D4C\u5165\u8868\u683C: ${sheetToken}]
312
+ ` : "";
313
+ case 31:
314
+ return this.tableToMarkdown(block, blockMap);
315
+ case 32:
316
+ return null;
317
+ default:
318
+ return null;
319
+ }
320
+ }
321
+ tableToMarkdown(tableBlock, blockMap) {
322
+ const table = tableBlock.table;
323
+ if (!table) return "";
324
+ const colSize = table.property?.column_size || 0;
325
+ const rowSize = table.property?.row_size || 0;
326
+ const cellIds = table.cells || [];
327
+ if (colSize === 0 || rowSize === 0) return "";
328
+ const rows = [];
329
+ for (let row = 0; row < rowSize; row++) {
330
+ const rowCells = [];
331
+ for (let col = 0; col < colSize; col++) {
332
+ const cellIndex = row * colSize + col;
333
+ const cellId = cellIds[cellIndex];
334
+ const cellBlock = blockMap.get(cellId);
335
+ const cellContent = this.getCellContent(cellBlock, blockMap);
336
+ rowCells.push(cellContent);
337
+ }
338
+ rows.push(rowCells);
339
+ }
340
+ const lines = [];
341
+ if (rows.length > 0) {
342
+ lines.push("| " + rows[0].join(" | ") + " |");
343
+ lines.push("| " + rows[0].map(() => "---").join(" | ") + " |");
344
+ }
345
+ for (let i = 1; i < rows.length; i++) {
346
+ lines.push("| " + rows[i].join(" | ") + " |");
347
+ }
348
+ return lines.join("\n") + "\n";
349
+ }
350
+ getCellContent(cellBlock, blockMap) {
351
+ if (!cellBlock) return "";
352
+ const children = cellBlock.children || [];
353
+ const contents = [];
354
+ for (const childId of children) {
355
+ const child = blockMap.get(childId);
356
+ if (!child) continue;
357
+ if (child.block_type === 2) {
358
+ contents.push(this.textElementsToMarkdown(child.text?.elements));
359
+ }
360
+ }
361
+ return contents.join(" ").replace(/\|/g, "\\|").replace(/\n/g, " ");
362
+ }
363
+ textElementsToMarkdown(elements) {
364
+ if (!elements) return "";
365
+ return elements.map((el) => {
366
+ if (el.text_run) {
367
+ let text = el.text_run.content || "";
368
+ const style = el.text_run.text_element_style;
369
+ if (style?.bold) {
370
+ text = `**${text}**`;
371
+ }
372
+ if (style?.italic) {
373
+ text = `*${text}*`;
374
+ }
375
+ if (style?.strikethrough) {
376
+ text = `~~${text}~~`;
377
+ }
378
+ if (style?.inline_code) {
379
+ text = `\`${text}\``;
380
+ }
381
+ if (style?.link?.url) {
382
+ text = `[${text}](${style.link.url})`;
383
+ }
384
+ return text;
385
+ }
386
+ if (el.mention_user) {
387
+ return `@${el.mention_user.user_id || "user"}`;
388
+ }
389
+ if (el.mention_doc) {
390
+ const title = el.mention_doc.title || "\u6587\u6863";
391
+ const url = el.mention_doc.url || "";
392
+ return `[${title}](${url})`;
393
+ }
394
+ return "";
395
+ }).join("");
396
+ }
397
+ getLanguageName(langCode) {
398
+ const langMap = {
399
+ 1: "plaintext",
400
+ 2: "abap",
401
+ 3: "ada",
402
+ 4: "apache",
403
+ 5: "apex",
404
+ 6: "assembly",
405
+ 7: "bash",
406
+ 8: "csharp",
407
+ 9: "cpp",
408
+ 10: "c",
409
+ 11: "cobol",
410
+ 12: "css",
411
+ 13: "coffeescript",
412
+ 14: "d",
413
+ 15: "dart",
414
+ 16: "delphi",
415
+ 17: "django",
416
+ 18: "dockerfile",
417
+ 19: "erlang",
418
+ 20: "fortran",
419
+ 21: "foxpro",
420
+ 22: "go",
421
+ 23: "groovy",
422
+ 24: "html",
423
+ 25: "htmlbars",
424
+ 26: "http",
425
+ 27: "haskell",
426
+ 28: "json",
427
+ 29: "java",
428
+ 30: "javascript",
429
+ 31: "julia",
430
+ 32: "kotlin",
431
+ 33: "latex",
432
+ 34: "lisp",
433
+ 35: "lua",
434
+ 36: "matlab",
435
+ 37: "makefile",
436
+ 38: "markdown",
437
+ 39: "nginx",
438
+ 40: "objectivec",
439
+ 41: "openedgeabl",
440
+ 42: "php",
441
+ 43: "perl",
442
+ 44: "powershell",
443
+ 45: "prolog",
444
+ 46: "protobuf",
445
+ 47: "python",
446
+ 48: "r",
447
+ 49: "rpm",
448
+ 50: "ruby",
449
+ 51: "rust",
450
+ 52: "sas",
451
+ 53: "scss",
452
+ 54: "sql",
453
+ 55: "scala",
454
+ 56: "scheme",
455
+ 57: "scratch",
456
+ 58: "shell",
457
+ 59: "swift",
458
+ 60: "thrift",
459
+ 61: "typescript",
460
+ 62: "vbscript",
461
+ 63: "visual-basic",
462
+ 64: "xml",
463
+ 65: "yaml"
464
+ };
465
+ if (typeof langCode === "number") {
466
+ return langMap[langCode] || "";
467
+ }
468
+ return String(langCode);
469
+ }
470
+ };
471
+
472
+ // src/services/notion.ts
473
+ import { Client as Client2 } from "@notionhq/client";
474
+ import { NotionToMarkdown } from "notion-to-md";
475
+ var NotionService = class {
476
+ constructor(config) {
477
+ this.client = new Client2({ auth: config.token });
478
+ this.n2m = new NotionToMarkdown({ notionClient: this.client });
479
+ this.setupCustomTransformers();
480
+ }
481
+ setupCustomTransformers() {
482
+ this.n2m.setCustomTransformer("child_database", async (block) => {
483
+ const dbId = block.id;
484
+ try {
485
+ const db = await this.client.databases.retrieve({ database_id: dbId });
486
+ const title = db.title?.[0]?.plain_text || "\u672A\u547D\u540D\u6570\u636E\u5E93";
487
+ if (!db.data_sources || db.data_sources.length === 0) {
488
+ return `### ${title}
489
+
490
+ (\u65E0\u6CD5\u83B7\u53D6\u6570\u636E\u6E90)
491
+ `;
492
+ }
493
+ const dataSourceId = db.data_sources[0].id;
494
+ const result = await this.client.dataSources.query({ data_source_id: dataSourceId });
495
+ if (!result.results || result.results.length === 0) {
496
+ return `### ${title}
497
+
498
+ (\u7A7A\u6570\u636E\u5E93)
499
+ `;
500
+ }
501
+ const columns = Object.keys(result.results[0].properties);
502
+ const rows = result.results.map((page) => {
503
+ const row = {};
504
+ for (const [key, prop] of Object.entries(page.properties)) {
505
+ row[key] = this.extractPropertyValue(prop);
506
+ }
507
+ return row;
508
+ });
509
+ let md = `### ${title}
510
+
511
+ `;
512
+ md += "| " + columns.join(" | ") + " |\n";
513
+ md += "| " + columns.map(() => "---").join(" | ") + " |\n";
514
+ for (const row of rows) {
515
+ const cells = columns.map((col) => {
516
+ const val = row[col] || "";
517
+ return val.replace(/\|/g, "\\|").replace(/\n/g, "<br>").slice(0, 200);
518
+ });
519
+ md += "| " + cells.join(" | ") + " |\n";
520
+ }
521
+ return md;
522
+ } catch (e) {
523
+ return `### ${block.child_database?.title || "\u6570\u636E\u5E93"}
524
+
525
+ (\u5BFC\u51FA\u5931\u8D25: ${e.message})
526
+ `;
527
+ }
528
+ });
529
+ }
530
+ extractPropertyValue(prop) {
531
+ if (!prop) return "";
532
+ switch (prop.type) {
533
+ case "title":
534
+ return prop.title?.map((t) => t.plain_text).join("") || "";
535
+ case "rich_text":
536
+ return prop.rich_text?.map((t) => t.plain_text).join("") || "";
537
+ case "select":
538
+ return prop.select?.name || "";
539
+ case "multi_select":
540
+ return prop.multi_select?.map((s) => s.name).join(", ") || "";
541
+ case "number":
542
+ return prop.number?.toString() ?? "";
543
+ case "checkbox":
544
+ return prop.checkbox ? "\u2713" : "";
545
+ case "date":
546
+ return prop.date?.start || "";
547
+ case "url":
548
+ return prop.url || "";
549
+ case "files":
550
+ return prop.files?.map((f) => f.name || "file").join(", ") || "";
551
+ default:
552
+ return "";
553
+ }
554
+ }
555
+ parsePageId(url) {
556
+ const match = url.match(/([a-f0-9]{32})|([a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12})/i);
557
+ if (!match) {
558
+ throw new Error(`\u65E0\u6CD5\u89E3\u6790 Notion \u9875\u9762 URL: ${url}`);
559
+ }
560
+ return match[0].replace(/-/g, "");
561
+ }
562
+ async getPageAsMarkdown(url) {
563
+ const pageId = this.parsePageId(url);
564
+ const page = await this.client.pages.retrieve({ page_id: pageId });
565
+ const title = this.getPageTitle(page);
566
+ const mdBlocks = await this.n2m.pageToMarkdown(pageId);
567
+ const mdString = this.n2m.toMarkdownString(mdBlocks);
568
+ const lines = [];
569
+ lines.push(`# ${title}`);
570
+ lines.push("");
571
+ lines.push(mdString.parent);
572
+ return lines.join("\n");
573
+ }
574
+ getPageTitle(page) {
575
+ const properties = page.properties;
576
+ if (!properties) return "\u672A\u547D\u540D";
577
+ for (const key of Object.keys(properties)) {
578
+ const prop = properties[key];
579
+ if (prop.type === "title" && prop.title?.length > 0) {
580
+ return prop.title.map((t) => t.plain_text).join("");
581
+ }
582
+ }
583
+ return "\u672A\u547D\u540D";
584
+ }
585
+ };
586
+
587
+ // src/commands/sync.ts
588
+ function clearLine() {
589
+ if (process.stdout.isTTY) {
590
+ process.stdout.clearLine(0);
591
+ process.stdout.cursorTo(0);
592
+ } else {
593
+ console.log("");
594
+ }
595
+ }
596
+ async function syncCommand(docName) {
597
+ const config = loadConfig();
598
+ const feishuService = config.feishu ? new FeishuService(config.feishu) : null;
599
+ const notionService = config.notion ? new NotionService(config.notion) : null;
600
+ let docsToSync = config.docs;
601
+ if (docName) {
602
+ const doc = config.docs.find((d) => d.name === docName);
603
+ if (!doc) {
604
+ console.log(chalk2.red(`\u274C \u672A\u627E\u5230\u540D\u4E3A "${docName}" \u7684\u6587\u6863\u914D\u7F6E`));
605
+ console.log(chalk2.gray(`\u53EF\u7528\u7684\u6587\u6863: ${config.docs.map((d) => d.name).join(", ")}`));
606
+ process.exit(1);
607
+ }
608
+ docsToSync = [doc];
609
+ }
610
+ console.log(chalk2.blue("\u{1F4C4} \u5F00\u59CB\u540C\u6B65\u6587\u6863...\n"));
611
+ let successCount = 0;
612
+ for (const doc of docsToSync) {
613
+ process.stdout.write(chalk2.gray(` \u23F3 ${doc.name.padEnd(15)} \u2192 ${doc.output}`));
614
+ try {
615
+ let content;
616
+ if (doc.type === "feishu") {
617
+ if (!feishuService) throw new Error("\u98DE\u4E66\u670D\u52A1\u672A\u914D\u7F6E");
618
+ content = await feishuService.getDocumentAsMarkdown(doc.url);
619
+ } else if (doc.type === "notion") {
620
+ if (!notionService) throw new Error("Notion \u670D\u52A1\u672A\u914D\u7F6E");
621
+ content = await notionService.getPageAsMarkdown(doc.url);
622
+ } else {
623
+ throw new Error(`\u4E0D\u652F\u6301\u7684\u6587\u6863\u7C7B\u578B: ${doc.type}`);
624
+ }
625
+ const outputPath = path3.join(process.cwd(), doc.output);
626
+ const outputDir = path3.dirname(outputPath);
627
+ if (!fs3.existsSync(outputDir)) {
628
+ fs3.mkdirSync(outputDir, { recursive: true });
629
+ }
630
+ fs3.writeFileSync(outputPath, content, "utf-8");
631
+ clearLine();
632
+ console.log(chalk2.green(` \u2713 ${doc.name.padEnd(15)} \u2192 ${doc.output}`));
633
+ successCount++;
634
+ } catch (error) {
635
+ clearLine();
636
+ const errorMessage = error instanceof Error ? error.message : String(error);
637
+ console.log(chalk2.red(` \u2717 ${doc.name.padEnd(15)} \u2192 ${errorMessage}`));
638
+ }
639
+ }
640
+ console.log("");
641
+ if (successCount === docsToSync.length) {
642
+ console.log(chalk2.green(`\u2705 \u540C\u6B65\u5B8C\u6210\uFF0C\u5171 ${docsToSync.length} \u4E2A\u6587\u6863`));
643
+ } else {
644
+ console.log(chalk2.yellow(`\u26A0\uFE0F \u540C\u6B65\u5B8C\u6210: ${successCount}/${docsToSync.length} \u4E2A\u6587\u6863\u6210\u529F`));
645
+ }
646
+ }
647
+
648
+ // src/commands/list.ts
649
+ import * as fs4 from "fs";
650
+ import * as path4 from "path";
651
+ import chalk3 from "chalk";
652
+ async function listCommand() {
653
+ if (!configExists()) {
654
+ console.log(chalk3.red("\u274C \u914D\u7F6E\u6587\u4EF6\u4E0D\u5B58\u5728\uFF0C\u8BF7\u5148\u8FD0\u884C growork init"));
655
+ process.exit(1);
656
+ }
657
+ const config = loadConfig();
658
+ const cwd = process.cwd();
659
+ console.log(chalk3.blue("\u{1F4CB} \u6587\u6863\u5217\u8868\n"));
660
+ console.log(chalk3.gray(" \u540D\u79F0".padEnd(18) + "\u7C7B\u578B".padEnd(10) + "\u8F93\u51FA\u8DEF\u5F84".padEnd(30) + "\u72B6\u6001"));
661
+ console.log(chalk3.gray(" " + "-".repeat(70)));
662
+ for (const doc of config.docs) {
663
+ const outputPath = path4.join(cwd, doc.output);
664
+ const exists = fs4.existsSync(outputPath);
665
+ const status = exists ? chalk3.green("\u5DF2\u540C\u6B65") : chalk3.yellow("\u672A\u540C\u6B65");
666
+ const name = ` ${doc.name}`.padEnd(18);
667
+ const type = doc.type.padEnd(10);
668
+ const output = doc.output.padEnd(30);
669
+ console.log(`${name}${chalk3.gray(type)}${output}${status}`);
670
+ }
671
+ console.log("");
672
+ console.log(chalk3.gray(`\u5171 ${config.docs.length} \u4E2A\u6587\u6863\u914D\u7F6E`));
673
+ }
674
+
675
+ // src/index.ts
676
+ var program = new Command();
677
+ program.name("growork").description("\u5C06\u98DE\u4E66\u6587\u6863\u540C\u6B65\u5230\u672C\u5730\uFF0C\u4E3A AI Agent \u63D0\u4F9B\u5B8C\u6574\u4E0A\u4E0B\u6587").version("1.0.0");
678
+ program.command("init").description("\u521D\u59CB\u5316 Growork \u914D\u7F6E\u548C\u76EE\u5F55\u7ED3\u6784").action(initCommand);
679
+ program.command("sync [name]").description("\u540C\u6B65\u6587\u6863\uFF0C\u53EF\u9009\u6307\u5B9A\u6587\u6863\u540D\u79F0").action(syncCommand);
680
+ program.command("list").description("\u5217\u51FA\u6240\u6709\u914D\u7F6E\u7684\u6587\u6863").action(listCommand);
681
+ program.parse();