@claudeink/mcp-server 0.7.0 → 0.9.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.
Files changed (2) hide show
  1. package/dist/index.js +1157 -226
  2. package/package.json +7 -7
package/dist/index.js CHANGED
@@ -8,6 +8,8 @@ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"
8
8
  import { z } from "zod";
9
9
  import { mkdir as mkdir2, readFile as readFile3 } from "fs/promises";
10
10
  import { join as join3 } from "path";
11
+ import { execSync } from "child_process";
12
+ import { readdirSync } from "fs";
11
13
 
12
14
  // src/lib/state.ts
13
15
  import { readFile, writeFile, mkdir, chmod } from "fs/promises";
@@ -37,6 +39,7 @@ var DEFAULT_STATE = {
37
39
  configs: {},
38
40
  crawlerSources: {},
39
41
  writingMasters: {},
42
+ knowledge: {},
40
43
  tagQueue: {
41
44
  queue: [],
42
45
  failed: []
@@ -47,9 +50,17 @@ async function ensureDir() {
47
50
  await mkdir(getClaudeinkDir(), { recursive: true });
48
51
  }
49
52
  async function readState() {
53
+ const statePath = getStatePath();
54
+ let raw;
50
55
  try {
51
- const content = await readFile(getStatePath(), "utf-8");
52
- const parsed = JSON.parse(content);
56
+ raw = await readFile(statePath, "utf-8");
57
+ } catch {
58
+ const state = { ...DEFAULT_STATE };
59
+ state.config.workflowDir = getWorkDir();
60
+ return state;
61
+ }
62
+ try {
63
+ const parsed = JSON.parse(raw);
53
64
  const state = {
54
65
  ...DEFAULT_STATE,
55
66
  ...parsed,
@@ -59,6 +70,12 @@ async function readState() {
59
70
  state.config.workflowDir = getWorkDir();
60
71
  return state;
61
72
  } catch {
73
+ const backupPath = statePath + ".corrupted." + Date.now();
74
+ try {
75
+ await writeFile(backupPath, raw, "utf-8");
76
+ } catch {
77
+ }
78
+ console.error(`[ClaudeInk] state.json corrupted, backed up to ${backupPath}`);
62
79
  const state = { ...DEFAULT_STATE };
63
80
  state.config.workflowDir = getWorkDir();
64
81
  return state;
@@ -118,14 +135,17 @@ async function removeCrawlerSource(id) {
118
135
  delete state.crawlerSources[id];
119
136
  await writeState(state);
120
137
  }
121
- async function updateLastSyncAt() {
138
+ async function mergeCloudData(cloud) {
122
139
  const state = await readState();
140
+ if (cloud.sources !== void 0) state.sources = cloud.sources;
141
+ if (cloud.drafts !== void 0) state.drafts = cloud.drafts;
142
+ if (cloud.published !== void 0) state.published = cloud.published;
143
+ if (cloud.configs !== void 0) state.configs = cloud.configs;
144
+ if (cloud.crawlerSources !== void 0) state.crawlerSources = cloud.crawlerSources;
145
+ if (cloud.writingMasters !== void 0) state.writingMasters = cloud.writingMasters;
123
146
  state.lastSyncAt = (/* @__PURE__ */ new Date()).toISOString();
124
147
  await writeState(state);
125
148
  }
126
- async function replaceState(newState) {
127
- await writeState(newState);
128
- }
129
149
  async function getTagQueue() {
130
150
  const state = await readState();
131
151
  return {
@@ -240,6 +260,37 @@ async function markTagFailed(file) {
240
260
  await saveTagQueue(queue);
241
261
  }
242
262
 
263
+ // src/lib/push.ts
264
+ function fireAndForgetPush(payload) {
265
+ doPush(payload).catch(() => {
266
+ });
267
+ }
268
+ async function doPush(partial) {
269
+ const creds = await getCredentials();
270
+ if (!creds?.token) return;
271
+ const config = await getConfig();
272
+ const payload = {
273
+ sources: partial.sources || [],
274
+ drafts: partial.drafts || [],
275
+ published: partial.published || [],
276
+ configs: partial.configs || [],
277
+ crawlerSources: partial.crawlerSources || [],
278
+ analytics: partial.analytics || []
279
+ };
280
+ try {
281
+ await fetch(`${config.apiBaseUrl}/api/sync/batch`, {
282
+ method: "POST",
283
+ headers: {
284
+ "Content-Type": "application/json",
285
+ Authorization: `Bearer ${creds.token}`
286
+ },
287
+ body: JSON.stringify(payload)
288
+ });
289
+ } catch (err) {
290
+ console.error("[ClaudeInk] auto-push failed:", err instanceof Error ? err.message : err);
291
+ }
292
+ }
293
+
243
294
  // src/tools/source.ts
244
295
  var sourceAddSchema = z.object({
245
296
  title: z.string().describe("\u7D20\u6750\u6807\u9898"),
@@ -267,7 +318,7 @@ async function sourceAdd(input) {
267
318
  }
268
319
  })() : "manual",
269
320
  published: date,
270
- url: input.url
321
+ ...input.url ? { url: input.url } : {}
271
322
  };
272
323
  if (input.tags && input.tags.length > 0) {
273
324
  meta.tags = input.tags;
@@ -284,6 +335,18 @@ async function sourceAdd(input) {
284
335
  publishedAt: date,
285
336
  updatedAt: (/* @__PURE__ */ new Date()).toISOString()
286
337
  });
338
+ fireAndForgetPush({
339
+ sources: [{
340
+ id: slug,
341
+ title: input.title,
342
+ source: meta.source,
343
+ tags: input.tags || [],
344
+ sourceUrl: input.url || "",
345
+ coverUrl: "",
346
+ sourceIcon: "",
347
+ createdAt: date
348
+ }]
349
+ });
287
350
  return {
288
351
  success: true,
289
352
  message: `\u7D20\u6750\u5DF2\u5165\u5E93: ${filename}\uFF08\u542B ${input.tags.length} \u4E2A\u6807\u7B7E\uFF09`,
@@ -303,6 +366,18 @@ async function sourceAdd(input) {
303
366
  publishedAt: date,
304
367
  updatedAt: (/* @__PURE__ */ new Date()).toISOString()
305
368
  });
369
+ fireAndForgetPush({
370
+ sources: [{
371
+ id: slug,
372
+ title: input.title,
373
+ source: meta.source,
374
+ tags: input.tags || [],
375
+ sourceUrl: input.url || "",
376
+ coverUrl: "",
377
+ sourceIcon: "",
378
+ createdAt: date
379
+ }]
380
+ });
306
381
  return {
307
382
  success: true,
308
383
  message: `\u7D20\u6750\u5DF2\u5165\u5E93: ${filename}\uFF08\u5DF2\u52A0\u5165\u6807\u7B7E\u961F\u5217\uFF0C\u7B49\u5F85 Claude \u6253\u6807\u7B7E\uFF09`,
@@ -315,7 +390,8 @@ var sourceCrawlSchema = z.object({
315
390
  });
316
391
  async function sourceCrawl(input) {
317
392
  const config = await getConfig();
318
- const configPath = join3(config.workflowDir || process.cwd(), "tools", "crawler", "config.json");
393
+ const crawlerDir = join3(config.workflowDir || process.cwd(), "tools", "crawler");
394
+ const configPath = join3(crawlerDir, "config.json");
319
395
  let crawlerConfig;
320
396
  try {
321
397
  crawlerConfig = JSON.parse(await readFile3(configPath, "utf-8"));
@@ -328,18 +404,97 @@ async function sourceCrawl(input) {
328
404
  message: "\u6682\u65E0\u914D\u7F6E\u722C\u866B\u6E90\u3002\u8BF7\u5148\u7528 source.subscribe \u6DFB\u52A0\u3002"
329
405
  };
330
406
  }
331
- const targets = input.sourceId ? crawlerConfig.sources.filter((s) => s.name === input.sourceId) : crawlerConfig.sources.filter((s) => s.enabled !== false);
407
+ const targets = input.sourceId ? crawlerConfig.sources.filter((s) => s.id === input.sourceId) : crawlerConfig.sources.filter((s) => s.enabled !== false);
332
408
  if (targets.length === 0) {
333
409
  return {
334
410
  success: false,
335
- message: `\u672A\u627E\u5230\u722C\u866B\u6E90: ${input.sourceId}`
411
+ message: input.sourceId ? `\u672A\u627E\u5230\u722C\u866B\u6E90: ${input.sourceId}` : "\u6CA1\u6709\u5DF2\u542F\u7528\u7684\u722C\u866B\u6E90"
336
412
  };
337
413
  }
338
- return {
339
- success: true,
340
- message: `\u5DF2\u89E6\u53D1 ${targets.length} \u4E2A\u722C\u866B\u6E90: ${targets.map((t) => t.name).join(", ")}`,
341
- data: { triggered: targets.map((t) => t.name) }
342
- };
414
+ const crawlScript = join3(crawlerDir, "crawl.mjs");
415
+ const args = input.sourceId ? `--source ${input.sourceId}` : "";
416
+ try {
417
+ execSync(`node "${crawlScript}" ${args}`, {
418
+ cwd: crawlerDir,
419
+ encoding: "utf-8",
420
+ timeout: 5 * 60 * 1e3,
421
+ // 5 minute timeout
422
+ stdio: ["pipe", "pipe", "pipe"]
423
+ });
424
+ let newFiles = [];
425
+ for (const target of targets) {
426
+ const sourceDir = join3(config.workflowDir, "sources", "articles", target.id);
427
+ try {
428
+ const files = readdirSync(sourceDir).filter((f) => f.endsWith(".md"));
429
+ for (const f of files) {
430
+ const filePath = `sources/articles/${target.id}/${f}`;
431
+ newFiles.push({
432
+ file: filePath,
433
+ title: f.replace(/^\d{4}-\d{2}-\d{2}-/, "").replace(/\.md$/, "").replace(/-/g, " "),
434
+ sourceId: target.id
435
+ });
436
+ }
437
+ } catch {
438
+ }
439
+ }
440
+ let queued = 0;
441
+ for (const item of newFiles) {
442
+ try {
443
+ const fullPath = join3(config.workflowDir, item.file);
444
+ const source = await readSourceFile(fullPath);
445
+ const slug = item.file.replace(/^.*\//, "").replace(/\.md$/, "");
446
+ const date = source.meta.published || (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
447
+ await updateSource(slug, {
448
+ title: source.meta.title || item.title,
449
+ source: item.sourceId,
450
+ sourceIcon: "",
451
+ sourceUrl: source.meta.url || "",
452
+ coverUrl: "",
453
+ tags: source.meta.tags || [],
454
+ publishedAt: date,
455
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
456
+ });
457
+ if (!source.meta.tags || source.meta.tags.length === 0) {
458
+ await addToTagQueue(item.file, source.meta.title || item.title, "crawl");
459
+ queued++;
460
+ }
461
+ fireAndForgetPush({
462
+ sources: [{
463
+ id: slug,
464
+ title: source.meta.title || item.title,
465
+ source: item.sourceId,
466
+ tags: source.meta.tags || [],
467
+ sourceUrl: source.meta.url || "",
468
+ coverUrl: "",
469
+ sourceIcon: "",
470
+ createdAt: date
471
+ }]
472
+ });
473
+ } catch {
474
+ }
475
+ }
476
+ return {
477
+ success: true,
478
+ message: [
479
+ `\u2705 \u722C\u53D6\u5B8C\u6210`,
480
+ ` \u76EE\u6807\u6E90: ${targets.map((t) => t.name).join(", ")}`,
481
+ ` \u53D1\u73B0\u6587\u7AE0: ${newFiles.length} \u7BC7`,
482
+ queued > 0 ? ` \u52A0\u5165\u6807\u7B7E\u961F\u5217: ${queued} \u7BC7` : "",
483
+ ` \u5143\u6570\u636E\u5DF2\u540C\u6B65\u5230\u4E91\u7AEF`
484
+ ].filter(Boolean).join("\n"),
485
+ data: {
486
+ sources: targets.map((t) => t.name),
487
+ articles: newFiles.length,
488
+ queued
489
+ }
490
+ };
491
+ } catch (err) {
492
+ const message = err instanceof Error ? err.message : String(err);
493
+ if (message.includes("ETIMEDOUT") || message.includes("timed out")) {
494
+ return { success: false, message: "\u722C\u53D6\u8D85\u65F6\uFF085 \u5206\u949F\u9650\u5236\uFF09\u3002\u8BF7\u5C1D\u8BD5\u6307\u5B9A\u5355\u4E2A\u6E90: source.crawl({ sourceId: 'xxx' })" };
495
+ }
496
+ return { success: false, message: `\u722C\u53D6\u5931\u8D25: ${message.slice(0, 200)}` };
497
+ }
343
498
  }
344
499
  var sourceTagSchema = z.object({
345
500
  mode: z.enum(["queue", "single"]).describe("queue=\u5904\u7406\u6807\u7B7E\u961F\u5217, single=\u5355\u4E2A\u6587\u4EF6\u6253\u6807\u7B7E"),
@@ -422,6 +577,19 @@ async function sourceTagApply(input) {
422
577
  await updateSourceTags2(fullPath, input.tags);
423
578
  await markTagged(input.file, input.tags.length);
424
579
  await updateSourceTags(input.file, input.tags);
580
+ const slug = input.file.replace(/^.*\//, "").replace(/\.md$/, "");
581
+ fireAndForgetPush({
582
+ sources: [{
583
+ id: slug,
584
+ title: "",
585
+ source: "",
586
+ tags: input.tags,
587
+ sourceUrl: "",
588
+ coverUrl: "",
589
+ sourceIcon: "",
590
+ createdAt: ""
591
+ }]
592
+ });
425
593
  return {
426
594
  success: true,
427
595
  message: `\u6807\u7B7E\u5DF2\u5199\u5165: ${input.tags.join(", ")}`,
@@ -446,7 +614,18 @@ var sourceSubscribeSchema = z2.object({
446
614
  name: z2.string().optional().describe("\u8BA2\u9605\u6E90\u540D\u79F0"),
447
615
  url: z2.string().optional().describe("RSS/\u535A\u5BA2 URL"),
448
616
  type: z2.enum(["rss", "blog", "sitemap"]).optional().describe("\u6E90\u7C7B\u578B"),
449
- icon: z2.string().optional().describe("\u6765\u6E90 icon URL")
617
+ icon: z2.string().optional().describe("\u6765\u6E90 icon URL"),
618
+ crawlConfig: z2.object({
619
+ articleSelector: z2.string(),
620
+ articleUrlPattern: z2.string().optional(),
621
+ pagination: z2.object({
622
+ pattern: z2.string(),
623
+ startPage: z2.number(),
624
+ maxPages: z2.number()
625
+ }).optional(),
626
+ excludePatterns: z2.array(z2.string()).optional(),
627
+ maxArticles: z2.number().optional()
628
+ }).optional().describe("\u722C\u53D6\u9002\u914D\u89C4\u5219\uFF08Claude \u5206\u6790\u751F\u6210\uFF09")
450
629
  });
451
630
  async function sourceSubscribe(input) {
452
631
  const config = await getConfig();
@@ -474,11 +653,18 @@ async function sourceSubscribe(input) {
474
653
  const newSource = {
475
654
  id: input.id,
476
655
  name: input.name,
477
- url: input.url,
478
- type: input.type || "rss",
656
+ blogUrl: input.url,
657
+ type: input.type === "blog" ? "paginated" : input.type || "rss",
479
658
  icon: input.icon || "",
480
659
  enabled: true
481
660
  };
661
+ if (input.crawlConfig) {
662
+ newSource.articleSelector = input.crawlConfig.articleSelector;
663
+ if (input.crawlConfig.articleUrlPattern) newSource.articleUrlPattern = input.crawlConfig.articleUrlPattern;
664
+ if (input.crawlConfig.pagination) newSource.pagination = input.crawlConfig.pagination;
665
+ if (input.crawlConfig.excludePatterns) newSource.excludePatterns = input.crawlConfig.excludePatterns;
666
+ if (input.crawlConfig.maxArticles) newSource.maxArticles = input.crawlConfig.maxArticles;
667
+ }
482
668
  crawlerConfig.sources.push(newSource);
483
669
  await writeFile3(configPath, JSON.stringify(crawlerConfig, null, 2));
484
670
  await updateCrawlerSource(input.id, {
@@ -488,6 +674,18 @@ async function sourceSubscribe(input) {
488
674
  icon: input.icon || "",
489
675
  enabled: true
490
676
  });
677
+ const stateAfterAdd = await readState();
678
+ fireAndForgetPush({
679
+ crawlerSources: Object.entries(stateAfterAdd.crawlerSources).map(([id, cs]) => ({
680
+ id,
681
+ name: cs.name,
682
+ url: cs.url,
683
+ type: cs.type,
684
+ icon: cs.icon,
685
+ enabled: cs.enabled,
686
+ ...id === input.id && input.crawlConfig ? { crawlConfig: input.crawlConfig } : {}
687
+ }))
688
+ });
491
689
  return { success: true, message: `\u8BA2\u9605\u6E90 ${input.name} \u5DF2\u6DFB\u52A0` };
492
690
  }
493
691
  if (input.action === "remove") {
@@ -497,6 +695,17 @@ async function sourceSubscribe(input) {
497
695
  crawlerConfig.sources = crawlerConfig.sources.filter((s) => s.id !== input.id);
498
696
  await writeFile3(configPath, JSON.stringify(crawlerConfig, null, 2));
499
697
  await removeCrawlerSource(input.id);
698
+ const stateAfterRemove = await readState();
699
+ fireAndForgetPush({
700
+ crawlerSources: Object.entries(stateAfterRemove.crawlerSources).map(([id, cs]) => ({
701
+ id,
702
+ name: cs.name,
703
+ url: cs.url,
704
+ type: cs.type,
705
+ icon: cs.icon,
706
+ enabled: cs.enabled
707
+ }))
708
+ });
500
709
  return { success: true, message: `\u8BA2\u9605\u6E90 ${input.id} \u5DF2\u5220\u9664` };
501
710
  }
502
711
  return { success: false, message: "\u672A\u77E5\u64CD\u4F5C" };
@@ -550,6 +759,20 @@ async function saveDraft(options) {
550
759
  await writeFile4(filePath, output, "utf-8");
551
760
  return { filePath, meta, content: options.content };
552
761
  }
762
+ async function updateDraft2(options) {
763
+ const raw = await readFile5(options.file, "utf-8");
764
+ const { data, content } = matter2(raw);
765
+ const meta = data;
766
+ if (options.title) meta.title = options.title;
767
+ if (options.status) meta.status = options.status;
768
+ if (options.tags) meta.tags = options.tags;
769
+ const newContent = options.content ?? content.trim();
770
+ meta.wordCount = newContent.length;
771
+ meta.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
772
+ const output = matter2.stringify(newContent, meta);
773
+ await writeFile4(options.file, output, "utf-8");
774
+ return { filePath: options.file, meta, content: newContent };
775
+ }
553
776
  async function publishDraft(options) {
554
777
  const raw = await readFile5(options.file, "utf-8");
555
778
  const { data, content } = matter2(raw);
@@ -569,6 +792,25 @@ async function publishDraft(options) {
569
792
  }
570
793
 
571
794
  // src/tools/draft.ts
795
+ function fireAndForgetDraftUpload(draftId, account, title, content) {
796
+ (async () => {
797
+ const creds = await getCredentials();
798
+ if (!creds?.token) return;
799
+ const config = await getConfig();
800
+ try {
801
+ await fetch(`${config.apiBaseUrl}/api/drafts/upload`, {
802
+ method: "POST",
803
+ headers: {
804
+ "Content-Type": "application/json",
805
+ Authorization: `Bearer ${creds.token}`
806
+ },
807
+ body: JSON.stringify({ draftId, account, title, content })
808
+ });
809
+ } catch {
810
+ }
811
+ })().catch(() => {
812
+ });
813
+ }
572
814
  var draftSaveSchema = z3.object({
573
815
  account: z3.string().describe("\u8D26\u53F7\u540D\u79F0"),
574
816
  platform: z3.string().describe("\u5E73\u53F0\u540D\u79F0\uFF0C\u5982 wechat"),
@@ -596,6 +838,20 @@ async function draftSave(input) {
596
838
  createdAt: draft.meta.createdAt,
597
839
  updatedAt: draft.meta.updatedAt
598
840
  });
841
+ fireAndForgetPush({
842
+ drafts: [{
843
+ id: draft.meta.id,
844
+ account: input.account,
845
+ platform: input.platform,
846
+ title: draft.meta.title,
847
+ status: draft.meta.status,
848
+ wordCount: draft.meta.wordCount,
849
+ tags: input.tags || [],
850
+ createdAt: draft.meta.createdAt,
851
+ updatedAt: draft.meta.updatedAt
852
+ }]
853
+ });
854
+ fireAndForgetDraftUpload(draft.meta.id, input.account, draft.meta.title, input.content);
599
855
  return {
600
856
  success: true,
601
857
  message: `\u8349\u7A3F\u5DF2\u4FDD\u5B58: ${draft.meta.title} (${draft.meta.wordCount} \u5B57)`,
@@ -606,6 +862,55 @@ async function draftSave(input) {
606
862
  }
607
863
  };
608
864
  }
865
+ var draftUpdateSchema = z3.object({
866
+ file: z3.string().describe("\u8349\u7A3F\u6587\u4EF6\u8DEF\u5F84"),
867
+ content: z3.string().optional().describe("\u66F4\u65B0\u540E\u7684\u6B63\u6587\uFF08Markdown\uFF09"),
868
+ title: z3.string().optional().describe("\u66F4\u65B0\u540E\u7684\u6807\u9898"),
869
+ status: z3.enum(["draft", "review", "ready"]).optional().describe("\u66F4\u65B0\u72B6\u6001"),
870
+ tags: z3.array(z3.string()).optional().describe("\u66F4\u65B0\u6807\u7B7E")
871
+ });
872
+ async function draftUpdate(input) {
873
+ const draft = await updateDraft2({
874
+ file: input.file,
875
+ content: input.content,
876
+ title: input.title,
877
+ status: input.status,
878
+ tags: input.tags
879
+ });
880
+ await updateDraft(draft.meta.id, {
881
+ title: draft.meta.title,
882
+ account: draft.meta.account,
883
+ platform: draft.meta.platform,
884
+ status: draft.meta.status,
885
+ wordCount: draft.meta.wordCount,
886
+ tags: draft.meta.tags || [],
887
+ createdAt: draft.meta.createdAt,
888
+ updatedAt: draft.meta.updatedAt
889
+ });
890
+ fireAndForgetPush({
891
+ drafts: [{
892
+ id: draft.meta.id,
893
+ account: draft.meta.account,
894
+ platform: draft.meta.platform,
895
+ title: draft.meta.title,
896
+ status: draft.meta.status,
897
+ wordCount: draft.meta.wordCount,
898
+ tags: draft.meta.tags || [],
899
+ createdAt: draft.meta.createdAt,
900
+ updatedAt: draft.meta.updatedAt
901
+ }]
902
+ });
903
+ fireAndForgetDraftUpload(draft.meta.id, draft.meta.account, draft.meta.title, draft.content);
904
+ return {
905
+ success: true,
906
+ message: `\u8349\u7A3F\u5DF2\u66F4\u65B0: ${draft.meta.title} (${draft.meta.wordCount} \u5B57)`,
907
+ data: {
908
+ file: draft.filePath,
909
+ id: draft.meta.id,
910
+ status: draft.meta.status
911
+ }
912
+ };
913
+ }
609
914
  var draftPublishSchema = z3.object({
610
915
  file: z3.string().describe("\u8349\u7A3F\u6587\u4EF6\u8DEF\u5F84"),
611
916
  platformUrl: z3.string().optional().describe("\u5E73\u53F0\u53D1\u5E03\u94FE\u63A5")
@@ -622,6 +927,16 @@ async function draftPublish(input) {
622
927
  platformUrl: draft.meta.platformUrl || "",
623
928
  publishedAt: draft.meta.publishedAt || (/* @__PURE__ */ new Date()).toISOString()
624
929
  });
930
+ fireAndForgetPush({
931
+ published: [{
932
+ id: draft.meta.id,
933
+ account: draft.meta.account,
934
+ platform: draft.meta.platform,
935
+ title: draft.meta.title,
936
+ platformUrl: draft.meta.platformUrl,
937
+ publishedAt: draft.meta.publishedAt || (/* @__PURE__ */ new Date()).toISOString()
938
+ }]
939
+ });
625
940
  return {
626
941
  success: true,
627
942
  message: `\u5DF2\u53D1\u5E03: ${draft.meta.title} \u2192 ${draft.filePath}`,
@@ -738,7 +1053,6 @@ import { z as z5 } from "zod";
738
1053
  import { writeFile as writeFile5, mkdir as mkdir4 } from "fs/promises";
739
1054
  import { join as join6 } from "path";
740
1055
  var syncSchema = z5.object({
741
- action: z5.enum(["push", "pull", "sync"]).describe("push=\u63A8\u9001\u672C\u5730\u5230\u4E91\u7AEF, pull=\u62C9\u53D6\u4E91\u7AEF\u5230\u672C\u5730, sync=\u5148push\u518Dpull"),
742
1056
  workDir: z5.string().optional().describe("\u5DE5\u4F5C\u76EE\u5F55\uFF08\u9ED8\u8BA4\u4F7F\u7528\u914D\u7F6E\u4E2D\u7684 workflowDir\uFF09")
743
1057
  });
744
1058
  async function sync(input) {
@@ -747,160 +1061,31 @@ async function sync(input) {
747
1061
  if (!creds?.token) {
748
1062
  return { success: false, message: "\u672A\u6FC0\u6D3B\uFF0C\u8BF7\u5148\u4F7F\u7528 workflow.init \u6FC0\u6D3B License" };
749
1063
  }
750
- if (input.action === "push") {
751
- return doPush(creds.token);
752
- } else if (input.action === "pull") {
753
- return doPull(creds.token, input.workDir);
754
- } else {
755
- const pushResult = await doPush(creds.token);
756
- const pullResult = await doPull(creds.token, input.workDir);
757
- const messages = [];
758
- if (pushResult.success) messages.push(pushResult.message);
759
- else messages.push(`Push \u5931\u8D25: ${pushResult.message}`);
760
- if (pullResult.success) messages.push(pullResult.message);
761
- else messages.push(`Pull \u5931\u8D25: ${pullResult.message}`);
762
- return {
763
- success: pushResult.success && pullResult.success,
764
- message: messages.join("\n\n"),
765
- data: { push: pushResult.data, pull: pullResult.data }
766
- };
767
- }
768
- }
769
- async function doPush(token) {
770
- const config = await getConfig();
771
- const state = await readState();
772
- const payload = {
773
- sources: Object.entries(state.sources).map(([id, s]) => ({
774
- id,
775
- title: s.title,
776
- source: s.source,
777
- tags: s.tags,
778
- sourceUrl: s.sourceUrl,
779
- coverUrl: s.coverUrl,
780
- sourceIcon: s.sourceIcon,
781
- createdAt: s.publishedAt
782
- })),
783
- drafts: Object.entries(state.drafts).map(([id, d]) => ({
784
- id,
785
- account: d.account,
786
- platform: d.platform,
787
- title: d.title,
788
- status: d.status,
789
- wordCount: d.wordCount,
790
- tags: d.tags,
791
- createdAt: d.createdAt,
792
- updatedAt: d.updatedAt
793
- })),
794
- published: Object.entries(state.published).map(([id, p]) => ({
795
- id,
796
- account: p.account,
797
- platform: p.platform,
798
- title: p.title,
799
- platformUrl: p.platformUrl,
800
- publishedAt: p.publishedAt
801
- })),
802
- configs: Object.entries(state.configs).map(([_key, c]) => ({
803
- type: c.type,
804
- name: c.name,
805
- displayName: c.displayName,
806
- content: c.content,
807
- metadata: c.metadata
808
- })),
809
- crawlerSources: Object.entries(state.crawlerSources).map(([id, cs]) => ({
810
- id,
811
- name: cs.name,
812
- url: cs.url,
813
- type: cs.type,
814
- icon: cs.icon,
815
- enabled: cs.enabled
816
- })),
817
- analytics: []
818
- };
819
- try {
820
- const res = await fetch(`${config.apiBaseUrl}/api/sync/batch`, {
821
- method: "POST",
822
- headers: {
823
- "Content-Type": "application/json",
824
- "Authorization": `Bearer ${token}`
825
- },
826
- body: JSON.stringify(payload)
827
- });
828
- const result = await res.json();
829
- if (res.ok) {
830
- await updateLastSyncAt();
831
- return {
832
- success: true,
833
- message: [
834
- "\u2705 \u63A8\u9001\u5B8C\u6210",
835
- ` \u7D20\u6750: ${payload.sources.length} \u7BC7`,
836
- ` \u8349\u7A3F: ${payload.drafts.length} \u7BC7`,
837
- ` \u5DF2\u53D1\u5E03: ${payload.published.length} \u7BC7`,
838
- ` \u914D\u7F6E: ${payload.configs.length} \u4E2A`,
839
- ` \u8BA2\u9605\u6E90: ${payload.crawlerSources.length} \u4E2A`,
840
- ` \u4E91\u7AEF\u63A5\u53D7: ${result.accepted || 0} \u6761`
841
- ].join("\n"),
842
- data: { accepted: result.accepted || 0 }
843
- };
844
- } else {
845
- return { success: false, message: `\u63A8\u9001\u5931\u8D25: HTTP ${res.status}` };
846
- }
847
- } catch (err) {
848
- return { success: false, message: `\u63A8\u9001\u7F51\u7EDC\u9519\u8BEF: ${err instanceof Error ? err.message : err}` };
849
- }
1064
+ return doPull(creds.token, input.workDir);
850
1065
  }
851
1066
  async function doPull(token, workDir) {
852
1067
  const config = await getConfig();
853
1068
  const effectiveWorkDir = workDir || config.workflowDir;
854
1069
  try {
855
1070
  const res = await fetch(`${config.apiBaseUrl}/api/sync/pull`, {
856
- headers: { "Authorization": `Bearer ${token}` }
1071
+ headers: { Authorization: `Bearer ${token}` }
857
1072
  });
858
1073
  if (!res.ok) {
859
1074
  if (res.status === 404) {
860
- return { success: true, message: "\u4E91\u7AEF\u65E0\u5DF2\u6709\u914D\u7F6E\uFF0C\u4F7F\u7528\u672C\u5730\u9ED8\u8BA4" };
1075
+ return { success: true, message: "\u4E91\u7AEF\u65E0\u6570\u636E" };
861
1076
  }
862
1077
  return { success: false, message: `\u62C9\u53D6\u5931\u8D25: HTTP ${res.status}` };
863
1078
  }
864
1079
  const cloudData = await res.json();
865
- if (!cloudData.configs || cloudData.configs.length === 0) {
866
- return { success: true, message: "\u4E91\u7AEF\u65E0\u914D\u7F6E\u6570\u636E" };
867
- }
868
- let updated = 0;
869
- const state = await readState();
870
- for (const cfgItem of cloudData.configs) {
871
- const stateKey = `${cfgItem.type}:${cfgItem.name}`;
872
- const localUpdatedAt = state.configs[stateKey]?.updatedAt || "";
873
- if (!localUpdatedAt || cfgItem.updatedAt > localUpdatedAt) {
874
- let filePath = "";
875
- if (cfgItem.type === "base_rules") {
876
- filePath = join6(effectiveWorkDir, "base-rules.md");
877
- } else if (cfgItem.type === "platform") {
878
- filePath = join6(effectiveWorkDir, "platforms", `${cfgItem.name}.md`);
879
- } else if (cfgItem.type === "account") {
880
- filePath = join6(effectiveWorkDir, "accounts", `${cfgItem.name}.yaml`);
881
- }
882
- if (filePath && cfgItem.content) {
883
- await writeFile5(filePath, cfgItem.content, "utf-8");
884
- state.configs[stateKey] = {
885
- type: cfgItem.type,
886
- name: cfgItem.name,
887
- displayName: cfgItem.displayName,
888
- content: cfgItem.content,
889
- metadata: cfgItem.metadata || {},
890
- updatedAt: cfgItem.updatedAt
891
- };
892
- updated++;
893
- }
894
- }
895
- }
896
- let crawlerCount = 0;
897
- state.crawlerSources = {};
1080
+ const counts = { configs: 0, sources: 0, drafts: 0, published: 0, crawlerSources: 0, writingMasters: 0 };
1081
+ const configsRecord = {};
1082
+ const crawlerSourcesRecord = {};
898
1083
  for (const cfg of cloudData.configs || []) {
899
1084
  if (cfg.type === "crawler" && cfg.content) {
900
1085
  try {
901
1086
  const sources = JSON.parse(cfg.content);
902
1087
  for (const s of sources) {
903
- state.crawlerSources[s.id] = {
1088
+ crawlerSourcesRecord[s.id] = {
904
1089
  name: s.name,
905
1090
  url: s.url,
906
1091
  type: s.type || "rss",
@@ -910,37 +1095,62 @@ async function doPull(token, workDir) {
910
1095
  }
911
1096
  const crawlerDir = join6(effectiveWorkDir, "tools", "crawler");
912
1097
  await mkdir4(crawlerDir, { recursive: true });
913
- const crawlerConfigPath = join6(crawlerDir, "config.json");
914
- await writeFile5(crawlerConfigPath, JSON.stringify({ sources }, null, 2), "utf-8");
915
- crawlerCount = sources.length;
1098
+ await writeFile5(join6(crawlerDir, "config.json"), JSON.stringify({ sources }, null, 2), "utf-8");
1099
+ counts.crawlerSources = sources.length;
916
1100
  } catch (e) {
917
1101
  console.error("[sync] Failed to parse crawler sources:", e);
918
1102
  }
919
- }
920
- }
921
- if (cloudData.writingMasters && Object.keys(cloudData.writingMasters).length > 0) {
922
- state.writingMasters = state.writingMasters || {};
923
- for (const [accountName, master] of Object.entries(cloudData.writingMasters)) {
924
- state.writingMasters[accountName] = {
925
- id: master.id,
926
- name: master.name,
927
- stylePrompt: master.stylePrompt
1103
+ } else {
1104
+ const stateKey = `${cfg.type}:${cfg.name}`;
1105
+ const state = await readState();
1106
+ const localUpdatedAt = state.configs[stateKey]?.updatedAt || "";
1107
+ if (!localUpdatedAt || cfg.updatedAt > localUpdatedAt) {
1108
+ let filePath = "";
1109
+ if (cfg.type === "base_rules") filePath = join6(effectiveWorkDir, "base-rules.md");
1110
+ else if (cfg.type === "platform") filePath = join6(effectiveWorkDir, "platforms", `${cfg.name}.md`);
1111
+ else if (cfg.type === "account") filePath = join6(effectiveWorkDir, "accounts", `${cfg.name}.yaml`);
1112
+ if (filePath && cfg.content) {
1113
+ await writeFile5(filePath, cfg.content, "utf-8");
1114
+ counts.configs++;
1115
+ }
1116
+ }
1117
+ configsRecord[stateKey] = {
1118
+ type: cfg.type,
1119
+ name: cfg.name,
1120
+ displayName: cfg.displayName,
1121
+ content: cfg.content,
1122
+ metadata: cfg.metadata || {},
1123
+ updatedAt: cfg.updatedAt
928
1124
  };
929
1125
  }
930
1126
  }
931
- await replaceState(state);
932
- const masterCount = cloudData.writingMasters ? Object.keys(cloudData.writingMasters).length : 0;
1127
+ counts.sources = Object.keys(cloudData.sources || {}).length;
1128
+ counts.drafts = Object.keys(cloudData.drafts || {}).length;
1129
+ counts.published = Object.keys(cloudData.published || {}).length;
1130
+ counts.writingMasters = Object.keys(cloudData.writingMasters || {}).length;
1131
+ await mergeCloudData({
1132
+ sources: cloudData.sources || {},
1133
+ drafts: cloudData.drafts || {},
1134
+ published: cloudData.published || {},
1135
+ configs: configsRecord,
1136
+ crawlerSources: crawlerSourcesRecord,
1137
+ writingMasters: cloudData.writingMasters || {}
1138
+ });
1139
+ const parts = [
1140
+ counts.sources > 0 ? `\u7D20\u6750 ${counts.sources}` : "",
1141
+ counts.drafts > 0 ? `\u8349\u7A3F ${counts.drafts}` : "",
1142
+ counts.published > 0 ? `\u5DF2\u53D1\u5E03 ${counts.published}` : "",
1143
+ counts.configs > 0 ? `\u914D\u7F6E\u66F4\u65B0 ${counts.configs}` : "",
1144
+ counts.crawlerSources > 0 ? `\u8BA2\u9605\u6E90 ${counts.crawlerSources}` : "",
1145
+ counts.writingMasters > 0 ? `\u5199\u4F5C\u5927\u5E08 ${counts.writingMasters}` : ""
1146
+ ].filter(Boolean);
933
1147
  return {
934
1148
  success: true,
935
- message: updated > 0 || masterCount > 0 || crawlerCount > 0 ? [
936
- `\u2705 \u4ECE\u4E91\u7AEF\u540C\u6B65\u4E86 ${updated} \u4E2A\u914D\u7F6E\u6587\u4EF6`,
937
- crawlerCount > 0 ? ` \u8BA2\u9605\u6E90: ${crawlerCount} \u4E2A` : "",
938
- masterCount > 0 ? ` \u5199\u4F5C\u5927\u5E08: ${masterCount} \u4E2A` : ""
939
- ].filter(Boolean).join("\n") : "\u672C\u5730\u914D\u7F6E\u5DF2\u662F\u6700\u65B0\uFF0C\u65E0\u9700\u66F4\u65B0",
940
- data: { updated, crawlerSources: crawlerCount, writingMasters: masterCount }
1149
+ message: parts.length > 0 ? `\u2705 \u5DF2\u540C\u6B65: ${parts.join(", ")}` : "\u4E91\u7AEF\u65E0\u6570\u636E",
1150
+ data: counts
941
1151
  };
942
1152
  } catch (err) {
943
- return { success: false, message: `\u62C9\u53D6\u9519\u8BEF: ${err instanceof Error ? err.message : err}` };
1153
+ return { success: false, message: `\u540C\u6B65\u5931\u8D25: ${err instanceof Error ? err.message : err}` };
944
1154
  }
945
1155
  }
946
1156
  async function syncPull(input) {
@@ -954,7 +1164,7 @@ async function syncPull(input) {
954
1164
 
955
1165
  // src/tools/workflow.ts
956
1166
  import { z as z6 } from "zod";
957
- import { cp, mkdir as mkdir5, access as access2, writeFile as writeFile6, chmod as chmod2 } from "fs/promises";
1167
+ import { cp, mkdir as mkdir5, access as access2, unlink as unlink2 } from "fs/promises";
958
1168
  import { join as join7, dirname } from "path";
959
1169
  import { fileURLToPath } from "url";
960
1170
  var DEFAULT_API_BASE_URL = "https://app.claudeink.com";
@@ -983,58 +1193,31 @@ async function workflowInit(input) {
983
1193
  }
984
1194
  }
985
1195
  const claudeinkDir = join7(cwd, ".claudeink");
986
- const dirs = [
987
- "sources/articles",
988
- "sources/video-transcripts",
989
- "sources/notes",
990
- "sources/data",
991
- "sources/daily",
992
- "templates"
993
- ];
1196
+ const dirs = ["sources/articles", "sources/video-transcripts", "sources/notes", "sources/data", "sources/daily", "templates"];
994
1197
  for (const dir of dirs) {
995
1198
  await mkdir5(join7(cwd, dir), { recursive: true });
996
1199
  }
997
1200
  await mkdir5(claudeinkDir, { recursive: true });
998
1201
  results.push("\u2705 \u8FD0\u884C\u65F6\u76EE\u5F55\u5DF2\u521B\u5EFA");
999
- const statePath = join7(claudeinkDir, "state.json");
1202
+ const state = await readState();
1203
+ state.config.workflowDir = cwd;
1204
+ const oldCredsPath = join7(claudeinkDir, "credentials.json");
1000
1205
  try {
1001
- await access2(statePath);
1002
- results.push("\u23ED\uFE0F state.json \u5DF2\u5B58\u5728\uFF0C\u8DF3\u8FC7");
1206
+ await access2(oldCredsPath);
1207
+ await unlink2(oldCredsPath);
1208
+ results.push("\u{1F9F9} \u5DF2\u6E05\u7406\u65E7\u7248 credentials.json");
1003
1209
  } catch {
1004
- const initialState = {
1005
- credentials: null,
1006
- config: {
1007
- apiBaseUrl: DEFAULT_API_BASE_URL,
1008
- workflowDir: cwd
1009
- },
1010
- sources: {},
1011
- drafts: {},
1012
- published: {},
1013
- configs: {},
1014
- crawlerSources: {},
1015
- writingMasters: {},
1016
- tagQueue: {
1017
- queue: [],
1018
- failed: []
1019
- },
1020
- lastSyncAt: ""
1021
- };
1022
- await writeFile6(statePath, JSON.stringify(initialState, null, 2), "utf-8");
1023
- await chmod2(statePath, 384);
1024
- results.push("\u2705 state.json");
1025
1210
  }
1026
1211
  let activated = false;
1027
1212
  if (input.licenseKey) {
1028
1213
  try {
1029
- const activateUrl = `${DEFAULT_API_BASE_URL}/api/auth/activate`;
1030
- const res = await fetch(activateUrl, {
1214
+ const res = await fetch(`${DEFAULT_API_BASE_URL}/api/auth/activate`, {
1031
1215
  method: "POST",
1032
1216
  headers: { "Content-Type": "application/json" },
1033
1217
  body: JSON.stringify({ key: input.licenseKey })
1034
1218
  });
1035
1219
  const data = await res.json();
1036
1220
  if (data.userId) {
1037
- const state = await readState();
1038
1221
  state.credentials = {
1039
1222
  licenseKey: input.licenseKey,
1040
1223
  token: data.token,
@@ -1042,7 +1225,6 @@ async function workflowInit(input) {
1042
1225
  plan: data.plan,
1043
1226
  expiresAt: data.expiresAt
1044
1227
  };
1045
- await writeState(state);
1046
1228
  results.push(`\u2705 License \u6FC0\u6D3B\u6210\u529F\uFF08\u5957\u9910: ${data.plan}\uFF09`);
1047
1229
  activated = true;
1048
1230
  } else {
@@ -1052,25 +1234,30 @@ async function workflowInit(input) {
1052
1234
  results.push(`\u26A0\uFE0F \u6FC0\u6D3B\u7F51\u7EDC\u9519\u8BEF: ${err instanceof Error ? err.message : err}`);
1053
1235
  }
1054
1236
  }
1237
+ await writeState(state);
1238
+ results.push("\u2705 state.json");
1055
1239
  if (activated) {
1056
1240
  try {
1057
1241
  const pullResult = await syncPull({ workDir: cwd });
1058
- if (pullResult.success && pullResult.data && pullResult.data.updated > 0) {
1059
- results.push("\u2705 \u5DF2\u4ECE\u4E91\u7AEF\u540C\u6B65\u914D\u7F6E\uFF08\u8DE8\u8BBE\u5907\u6062\u590D\uFF09");
1242
+ if (pullResult.success && pullResult.data) {
1243
+ const d = pullResult.data;
1244
+ const total = (d.sources || 0) + (d.drafts || 0) + (d.published || 0) + (d.configs || 0);
1245
+ if (total > 0) {
1246
+ results.push("\u2705 \u5DF2\u4ECE\u4E91\u7AEF\u540C\u6B65\u6570\u636E");
1247
+ } else {
1248
+ results.push("\u2139\uFE0F \u4E91\u7AEF\u65E0\u5DF2\u6709\u6570\u636E\uFF0C\u4F7F\u7528\u9ED8\u8BA4\u6A21\u677F");
1249
+ }
1060
1250
  } else {
1061
- results.push("\u2139\uFE0F \u4E91\u7AEF\u65E0\u5DF2\u6709\u914D\u7F6E\uFF0C\u4F7F\u7528\u9ED8\u8BA4\u6A21\u677F");
1251
+ results.push("\u2139\uFE0F \u4E91\u7AEF\u65E0\u5DF2\u6709\u6570\u636E\uFF0C\u4F7F\u7528\u9ED8\u8BA4\u6A21\u677F");
1062
1252
  }
1063
1253
  } catch {
1064
- results.push("\u2139\uFE0F \u4E91\u7AEF\u65E0\u5DF2\u6709\u914D\u7F6E\uFF0C\u4F7F\u7528\u9ED8\u8BA4\u6A21\u677F");
1254
+ results.push("\u2139\uFE0F \u4E91\u7AEF\u540C\u6B65\u8DF3\u8FC7");
1065
1255
  }
1066
1256
  }
1067
1257
  try {
1068
1258
  await access2(join7(cwd, "tools", "crawler", "package.json"));
1069
- const { execSync } = await import("child_process");
1070
- execSync("npm install --silent", {
1071
- cwd: join7(cwd, "tools", "crawler"),
1072
- stdio: "pipe"
1073
- });
1259
+ const { execSync: execSync2 } = await import("child_process");
1260
+ execSync2("npm install --silent", { cwd: join7(cwd, "tools", "crawler"), stdio: "pipe" });
1074
1261
  results.push("\u2705 \u722C\u866B\u4F9D\u8D56\u5DF2\u5B89\u88C5");
1075
1262
  } catch {
1076
1263
  }
@@ -1094,16 +1281,724 @@ async function workflowInit(input) {
1094
1281
  }
1095
1282
  }
1096
1283
 
1284
+ // src/tools/ink.ts
1285
+ import { z as z7 } from "zod";
1286
+
1287
+ // src/lib/knowledge.ts
1288
+ import { readFile as readFile6, writeFile as writeFile6, mkdir as mkdir6, unlink as unlink3 } from "fs/promises";
1289
+ import { join as join8 } from "path";
1290
+ import matter3 from "gray-matter";
1291
+ function getKnowledgeDir() {
1292
+ return join8(getWorkDir(), ".claudeink", "knowledge");
1293
+ }
1294
+ function getIndexPath() {
1295
+ return join8(getWorkDir(), ".claudeink", "knowledge", "index.json");
1296
+ }
1297
+ async function generateId() {
1298
+ const now = /* @__PURE__ */ new Date();
1299
+ const dateStr = now.toISOString().slice(0, 10).replace(/-/g, "");
1300
+ const index = await readIndex();
1301
+ let maxSeq = 0;
1302
+ const prefix = `k_${dateStr}_`;
1303
+ for (const id of Object.keys(index.entries)) {
1304
+ if (id.startsWith(prefix)) {
1305
+ const seq = parseInt(id.slice(prefix.length), 10);
1306
+ if (seq > maxSeq) maxSeq = seq;
1307
+ }
1308
+ }
1309
+ const nextSeq = String(maxSeq + 1).padStart(3, "0");
1310
+ return `${prefix}${nextSeq}`;
1311
+ }
1312
+ function toSlug(title) {
1313
+ return title.replace(/[^\u4e00-\u9fa5a-zA-Z0-9\s-]/g, "").replace(/\s+/g, "-").slice(0, 50).replace(/-+$/, "");
1314
+ }
1315
+ async function readIndex() {
1316
+ try {
1317
+ const raw = await readFile6(getIndexPath(), "utf-8");
1318
+ return JSON.parse(raw);
1319
+ } catch {
1320
+ return { entries: {}, updated_at: (/* @__PURE__ */ new Date()).toISOString() };
1321
+ }
1322
+ }
1323
+ async function saveIndex(index) {
1324
+ index.updated_at = (/* @__PURE__ */ new Date()).toISOString();
1325
+ const dir = getKnowledgeDir();
1326
+ await mkdir6(dir, { recursive: true });
1327
+ await writeFile6(getIndexPath(), JSON.stringify(index, null, 2), "utf-8");
1328
+ }
1329
+ async function addToIndex(entry) {
1330
+ const index = await readIndex();
1331
+ index.entries[entry.id] = entry;
1332
+ await saveIndex(index);
1333
+ }
1334
+ async function removeFromIndex(id) {
1335
+ const index = await readIndex();
1336
+ delete index.entries[id];
1337
+ await saveIndex(index);
1338
+ }
1339
+ async function writeKnowledgeFile(meta, content) {
1340
+ const now = /* @__PURE__ */ new Date();
1341
+ const monthDir = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, "0")}`;
1342
+ const dir = join8(getKnowledgeDir(), monthDir);
1343
+ await mkdir6(dir, { recursive: true });
1344
+ const slug = toSlug(meta.title) || meta.id;
1345
+ const filename = `${now.toISOString().slice(0, 10)}-${slug}.md`;
1346
+ const filePath = join8(dir, filename);
1347
+ const frontmatter = {
1348
+ id: meta.id,
1349
+ title: meta.title,
1350
+ tags: meta.tags,
1351
+ category: meta.category,
1352
+ source_type: meta.source_type,
1353
+ created_at: meta.created_at,
1354
+ updated_at: meta.updated_at
1355
+ };
1356
+ if (meta.url) frontmatter.url = meta.url;
1357
+ const output = matter3.stringify(content, frontmatter);
1358
+ await writeFile6(filePath, output, "utf-8");
1359
+ return join8(monthDir, filename);
1360
+ }
1361
+ async function readKnowledgeFile(id) {
1362
+ const index = await readIndex();
1363
+ const entry = index.entries[id];
1364
+ if (!entry) return null;
1365
+ const filePath = join8(getKnowledgeDir(), entry.file_path);
1366
+ try {
1367
+ const raw = await readFile6(filePath, "utf-8");
1368
+ const { data, content } = matter3(raw);
1369
+ return {
1370
+ meta: data,
1371
+ content: content.trim(),
1372
+ filePath
1373
+ };
1374
+ } catch {
1375
+ return null;
1376
+ }
1377
+ }
1378
+ async function updateKnowledgeFile(id, updates) {
1379
+ const file = await readKnowledgeFile(id);
1380
+ if (!file) return null;
1381
+ const now = (/* @__PURE__ */ new Date()).toISOString();
1382
+ const meta = { ...file.meta };
1383
+ if (updates.title !== void 0) meta.title = updates.title;
1384
+ if (updates.tags !== void 0) meta.tags = updates.tags;
1385
+ if (updates.category !== void 0) meta.category = updates.category;
1386
+ meta.updated_at = now;
1387
+ const content = updates.content !== void 0 ? updates.content : file.content;
1388
+ const frontmatter = {
1389
+ id: meta.id,
1390
+ title: meta.title,
1391
+ tags: meta.tags,
1392
+ category: meta.category,
1393
+ source_type: meta.source_type,
1394
+ created_at: meta.created_at,
1395
+ updated_at: meta.updated_at
1396
+ };
1397
+ if (meta.url) frontmatter.url = meta.url;
1398
+ const output = matter3.stringify(content, frontmatter);
1399
+ await writeFile6(file.filePath, output, "utf-8");
1400
+ const index = await readIndex();
1401
+ const entry = index.entries[id];
1402
+ if (entry) {
1403
+ entry.title = meta.title;
1404
+ entry.tags = meta.tags;
1405
+ entry.category = meta.category;
1406
+ entry.preview = generatePreview(content);
1407
+ entry.updated_at = now;
1408
+ await saveIndex(index);
1409
+ }
1410
+ return { meta, content, filePath: file.filePath };
1411
+ }
1412
+ async function deleteKnowledgeFile(id) {
1413
+ const index = await readIndex();
1414
+ const entry = index.entries[id];
1415
+ if (!entry) return false;
1416
+ const filePath = join8(getKnowledgeDir(), entry.file_path);
1417
+ try {
1418
+ await unlink3(filePath);
1419
+ } catch {
1420
+ }
1421
+ await removeFromIndex(id);
1422
+ return true;
1423
+ }
1424
+ function generatePreview(content, maxLen = 200) {
1425
+ return content.replace(/^#+\s.+$/gm, "").replace(/\n+/g, " ").trim().slice(0, maxLen);
1426
+ }
1427
+ function searchKnowledge(index, filters) {
1428
+ let entries = Object.values(index.entries);
1429
+ if (filters.tags && filters.tags.length > 0) {
1430
+ const filterTags = filters.tags.map((t) => t.toLowerCase());
1431
+ entries = entries.filter(
1432
+ (e) => e.tags.some((t) => filterTags.includes(t.toLowerCase()))
1433
+ );
1434
+ }
1435
+ if (filters.category) {
1436
+ entries = entries.filter((e) => e.category === filters.category);
1437
+ }
1438
+ if (filters.date_from) {
1439
+ entries = entries.filter((e) => e.created_at >= filters.date_from);
1440
+ }
1441
+ if (filters.date_to) {
1442
+ entries = entries.filter((e) => e.created_at <= filters.date_to);
1443
+ }
1444
+ if (filters.query) {
1445
+ const q = filters.query.toLowerCase();
1446
+ entries = entries.map((e) => {
1447
+ let score = 0;
1448
+ if (e.title.toLowerCase().includes(q)) score += 10;
1449
+ if (e.preview.toLowerCase().includes(q)) score += 5;
1450
+ if (e.tags.some((t) => t.toLowerCase().includes(q))) score += 3;
1451
+ return { ...e, _score: score };
1452
+ }).filter((e) => e._score > 0).sort((a, b) => b._score - a._score);
1453
+ } else {
1454
+ entries.sort((a, b) => b.created_at.localeCompare(a.created_at));
1455
+ }
1456
+ const total = entries.length;
1457
+ const limit = filters.limit || 10;
1458
+ const results = entries.slice(0, limit).map((e) => ({
1459
+ id: e.id,
1460
+ title: e.title,
1461
+ tags: e.tags,
1462
+ category: e.category,
1463
+ created_at: e.created_at,
1464
+ preview: e.preview
1465
+ }));
1466
+ return { results, total };
1467
+ }
1468
+ async function saveKnowledge(params) {
1469
+ const id = await generateId();
1470
+ const now = (/* @__PURE__ */ new Date()).toISOString();
1471
+ const meta = {
1472
+ id,
1473
+ title: params.title,
1474
+ tags: params.tags,
1475
+ category: params.category,
1476
+ source_type: params.source_type,
1477
+ url: params.url,
1478
+ created_at: now,
1479
+ updated_at: now
1480
+ };
1481
+ const relativePath = await writeKnowledgeFile(meta, params.content);
1482
+ await addToIndex({
1483
+ id,
1484
+ title: params.title,
1485
+ tags: params.tags,
1486
+ category: params.category,
1487
+ source_type: params.source_type,
1488
+ preview: generatePreview(params.content),
1489
+ file_path: relativePath,
1490
+ created_at: now,
1491
+ updated_at: now
1492
+ });
1493
+ return { id, filePath: relativePath };
1494
+ }
1495
+ async function readMultipleKnowledgeFiles(ids) {
1496
+ const results = [];
1497
+ for (const id of ids) {
1498
+ const file = await readKnowledgeFile(id);
1499
+ if (file) results.push(file);
1500
+ }
1501
+ return results;
1502
+ }
1503
+ function getAllTags(index) {
1504
+ const tagCounts = {};
1505
+ for (const entry of Object.values(index.entries)) {
1506
+ for (const tag of entry.tags) {
1507
+ tagCounts[tag] = (tagCounts[tag] || 0) + 1;
1508
+ }
1509
+ }
1510
+ return Object.entries(tagCounts).map(([name, count]) => ({ name, count })).sort((a, b) => b.count - a.count);
1511
+ }
1512
+ async function renameTag(oldTag, newTag) {
1513
+ const index = await readIndex();
1514
+ let count = 0;
1515
+ for (const entry of Object.values(index.entries)) {
1516
+ const idx = entry.tags.indexOf(oldTag);
1517
+ if (idx !== -1) {
1518
+ entry.tags[idx] = newTag;
1519
+ count++;
1520
+ const file = await readKnowledgeFile(entry.id);
1521
+ if (file) {
1522
+ const tagIdx = file.meta.tags.indexOf(oldTag);
1523
+ if (tagIdx !== -1) {
1524
+ file.meta.tags[tagIdx] = newTag;
1525
+ await updateKnowledgeFile(entry.id, { tags: file.meta.tags });
1526
+ }
1527
+ }
1528
+ }
1529
+ }
1530
+ if (count > 0) {
1531
+ await saveIndex(index);
1532
+ }
1533
+ return count;
1534
+ }
1535
+ async function mergeTags(sourceTags, targetTag) {
1536
+ const index = await readIndex();
1537
+ let count = 0;
1538
+ for (const entry of Object.values(index.entries)) {
1539
+ const hasSource = entry.tags.some((t) => sourceTags.includes(t));
1540
+ if (hasSource) {
1541
+ const newTags = entry.tags.filter((t) => !sourceTags.includes(t));
1542
+ if (!newTags.includes(targetTag)) {
1543
+ newTags.push(targetTag);
1544
+ }
1545
+ entry.tags = newTags;
1546
+ count++;
1547
+ await updateKnowledgeFile(entry.id, { tags: newTags });
1548
+ }
1549
+ }
1550
+ if (count > 0) {
1551
+ await saveIndex(index);
1552
+ }
1553
+ return count;
1554
+ }
1555
+
1556
+ // src/tools/ink.ts
1557
+ var inkSaveSchema = z7.object({
1558
+ content: z7.string().describe("\u8981\u4FDD\u5B58\u7684\u5185\u5BB9\uFF08\u5FC5\u586B\uFF09"),
1559
+ title: z7.string().describe("\u6807\u9898\uFF08Claude \u81EA\u52A8\u751F\u6210\uFF09"),
1560
+ tags: z7.array(z7.string()).optional().default([]).describe("\u6807\u7B7E\uFF08Claude \u81EA\u52A8\u751F\u6210\uFF0C\u7528\u6237\u53EF\u6307\u5B9A\uFF09"),
1561
+ category: z7.enum(["insight", "decision", "analysis", "idea", "reference", "action"]).optional().default("reference").describe("\u5206\u7C7B\uFF1Ainsight / decision / analysis / idea / reference / action"),
1562
+ source_type: z7.enum(["conversation", "url", "manual"]).optional().default("conversation").describe("\u6765\u6E90\u7C7B\u578B\uFF1Aconversation / url / manual"),
1563
+ url: z7.string().optional().describe("\u6765\u6E90 URL\uFF08\u5982\u679C\u662F\u5916\u90E8\u7D20\u6750\uFF09")
1564
+ });
1565
+ async function inkSave(input) {
1566
+ try {
1567
+ const { id } = await saveKnowledge({
1568
+ content: input.content,
1569
+ title: input.title,
1570
+ tags: input.tags,
1571
+ category: input.category,
1572
+ source_type: input.source_type,
1573
+ url: input.url
1574
+ });
1575
+ const categoryNames = {
1576
+ insight: "\u6D1E\u5BDF",
1577
+ decision: "\u51B3\u7B56",
1578
+ analysis: "\u5206\u6790",
1579
+ idea: "\u60F3\u6CD5",
1580
+ reference: "\u53C2\u8003",
1581
+ action: "\u884C\u52A8"
1582
+ };
1583
+ const catName = categoryNames[input.category] || input.category;
1584
+ const tagStr = input.tags.length > 0 ? input.tags.join("\u3001") : "\u65E0\u6807\u7B7E";
1585
+ return {
1586
+ success: true,
1587
+ message: `\u5DF2\u4FDD\u5B58\u5230\u300C${catName}\u300D\uFF0C\u6807\u7B7E\uFF1A${tagStr}`,
1588
+ data: { id }
1589
+ };
1590
+ } catch (err) {
1591
+ return {
1592
+ success: false,
1593
+ message: `\u4FDD\u5B58\u5931\u8D25\uFF1A${err.message}`
1594
+ };
1595
+ }
1596
+ }
1597
+ var inkSearchSchema = z7.object({
1598
+ query: z7.string().optional().describe("\u641C\u7D22\u5173\u952E\u8BCD"),
1599
+ tags: z7.array(z7.string()).optional().describe("\u6309\u6807\u7B7E\u8FC7\u6EE4"),
1600
+ category: z7.enum(["insight", "decision", "analysis", "idea", "reference", "action"]).optional().describe("\u6309\u5206\u7C7B\u8FC7\u6EE4"),
1601
+ date_from: z7.string().optional().describe("\u8D77\u59CB\u65E5\u671F\uFF08ISO \u683C\u5F0F\uFF09"),
1602
+ date_to: z7.string().optional().describe("\u622A\u6B62\u65E5\u671F\uFF08ISO \u683C\u5F0F\uFF09"),
1603
+ limit: z7.number().optional().default(10).describe("\u8FD4\u56DE\u6570\u91CF\uFF0C\u9ED8\u8BA4 10")
1604
+ });
1605
+ async function inkSearch(input) {
1606
+ try {
1607
+ const index = await readIndex();
1608
+ const { results, total } = searchKnowledge(index, {
1609
+ query: input.query,
1610
+ tags: input.tags,
1611
+ category: input.category,
1612
+ date_from: input.date_from,
1613
+ date_to: input.date_to,
1614
+ limit: input.limit
1615
+ });
1616
+ if (total === 0) {
1617
+ return {
1618
+ success: true,
1619
+ message: "\u6CA1\u6709\u627E\u5230\u5339\u914D\u7684\u77E5\u8BC6\u7247\u6BB5",
1620
+ data: { results: [], total: 0 }
1621
+ };
1622
+ }
1623
+ return {
1624
+ success: true,
1625
+ message: `\u627E\u5230 ${total} \u6761\u76F8\u5173\u8BB0\u5F55${total > results.length ? `\uFF0C\u663E\u793A\u524D ${results.length} \u6761` : ""}`,
1626
+ data: { results, total }
1627
+ };
1628
+ } catch (err) {
1629
+ return {
1630
+ success: false,
1631
+ message: `\u641C\u7D22\u5931\u8D25\uFF1A${err.message}`
1632
+ };
1633
+ }
1634
+ }
1635
+ var inkGetSchema = z7.object({
1636
+ id: z7.string().describe("\u77E5\u8BC6\u7247\u6BB5 ID")
1637
+ });
1638
+ async function inkGet(input) {
1639
+ try {
1640
+ const file = await readKnowledgeFile(input.id);
1641
+ if (!file) {
1642
+ return {
1643
+ success: false,
1644
+ message: `\u672A\u627E\u5230 ID \u4E3A ${input.id} \u7684\u77E5\u8BC6\u7247\u6BB5`
1645
+ };
1646
+ }
1647
+ return {
1648
+ success: true,
1649
+ message: file.meta.title,
1650
+ data: {
1651
+ id: file.meta.id,
1652
+ title: file.meta.title,
1653
+ content: file.content,
1654
+ tags: file.meta.tags,
1655
+ category: file.meta.category,
1656
+ source_type: file.meta.source_type,
1657
+ url: file.meta.url,
1658
+ created_at: file.meta.created_at,
1659
+ updated_at: file.meta.updated_at
1660
+ }
1661
+ };
1662
+ } catch (err) {
1663
+ return {
1664
+ success: false,
1665
+ message: `\u83B7\u53D6\u5931\u8D25\uFF1A${err.message}`
1666
+ };
1667
+ }
1668
+ }
1669
+ var inkReviewSchema = z7.object({
1670
+ period: z7.enum(["today", "week", "month", "custom"]).optional().default("week").describe("\u56DE\u987E\u65F6\u95F4\u6BB5\uFF1Atoday / week / month / custom"),
1671
+ date_from: z7.string().optional().describe("\u81EA\u5B9A\u4E49\u8D77\u59CB\u65E5\u671F\uFF08ISO \u683C\u5F0F\uFF0Cperiod=custom \u65F6\u4F7F\u7528\uFF09"),
1672
+ date_to: z7.string().optional().describe("\u81EA\u5B9A\u4E49\u622A\u6B62\u65E5\u671F\uFF08ISO \u683C\u5F0F\uFF0Cperiod=custom \u65F6\u4F7F\u7528\uFF09"),
1673
+ category: z7.enum(["insight", "decision", "analysis", "idea", "reference", "action"]).optional().describe("\u6309\u5206\u7C7B\u8FC7\u6EE4")
1674
+ });
1675
+ async function inkReview(input) {
1676
+ try {
1677
+ const index = await readIndex();
1678
+ const entries = Object.values(index.entries);
1679
+ const now = /* @__PURE__ */ new Date();
1680
+ let dateFrom;
1681
+ let dateTo = now.toISOString();
1682
+ switch (input.period) {
1683
+ case "today":
1684
+ dateFrom = new Date(now.getFullYear(), now.getMonth(), now.getDate()).toISOString();
1685
+ break;
1686
+ case "week": {
1687
+ const weekAgo = new Date(now);
1688
+ weekAgo.setDate(weekAgo.getDate() - 7);
1689
+ dateFrom = weekAgo.toISOString();
1690
+ break;
1691
+ }
1692
+ case "month": {
1693
+ const monthAgo = new Date(now);
1694
+ monthAgo.setMonth(monthAgo.getMonth() - 1);
1695
+ dateFrom = monthAgo.toISOString();
1696
+ break;
1697
+ }
1698
+ case "custom":
1699
+ dateFrom = input.date_from || (/* @__PURE__ */ new Date(0)).toISOString();
1700
+ dateTo = input.date_to || now.toISOString();
1701
+ break;
1702
+ }
1703
+ let filtered = entries.filter(
1704
+ (e) => e.created_at >= dateFrom && e.created_at <= dateTo
1705
+ );
1706
+ if (input.category) {
1707
+ filtered = filtered.filter((e) => e.category === input.category);
1708
+ }
1709
+ filtered.sort((a, b) => b.created_at.localeCompare(a.created_at));
1710
+ const byCategory = {};
1711
+ for (const e of filtered) {
1712
+ byCategory[e.category] = (byCategory[e.category] || 0) + 1;
1713
+ }
1714
+ const tagCounts = {};
1715
+ for (const e of filtered) {
1716
+ for (const t of e.tags) {
1717
+ tagCounts[t] = (tagCounts[t] || 0) + 1;
1718
+ }
1719
+ }
1720
+ const topTags = Object.entries(tagCounts).sort((a, b) => b[1] - a[1]).slice(0, 10).map(([tag, count]) => ({ tag, count }));
1721
+ const categoryNames = {
1722
+ insight: "\u6D1E\u5BDF",
1723
+ decision: "\u51B3\u7B56",
1724
+ analysis: "\u5206\u6790",
1725
+ idea: "\u60F3\u6CD5",
1726
+ reference: "\u53C2\u8003",
1727
+ action: "\u884C\u52A8"
1728
+ };
1729
+ const periodNames = {
1730
+ today: "\u4ECA\u65E5",
1731
+ week: "\u672C\u5468",
1732
+ month: "\u672C\u6708",
1733
+ custom: "\u81EA\u5B9A\u4E49\u65F6\u6BB5"
1734
+ };
1735
+ return {
1736
+ success: true,
1737
+ message: `${periodNames[input.period]}\u77E5\u8BC6\u56DE\u987E\uFF1A\u5171 ${filtered.length} \u6761\u8BB0\u5F55`,
1738
+ data: {
1739
+ period: input.period,
1740
+ date_from: dateFrom,
1741
+ date_to: dateTo,
1742
+ total: filtered.length,
1743
+ by_category: Object.entries(byCategory).map(([cat, count]) => ({
1744
+ category: cat,
1745
+ label: categoryNames[cat] || cat,
1746
+ count
1747
+ })),
1748
+ top_tags: topTags,
1749
+ items: filtered.map((e) => ({
1750
+ id: e.id,
1751
+ title: e.title,
1752
+ category: e.category,
1753
+ tags: e.tags,
1754
+ created_at: e.created_at,
1755
+ preview: e.preview
1756
+ }))
1757
+ }
1758
+ };
1759
+ } catch (err) {
1760
+ return {
1761
+ success: false,
1762
+ message: `\u56DE\u987E\u5931\u8D25\uFF1A${err.message}`
1763
+ };
1764
+ }
1765
+ }
1766
+ var inkUpdateSchema = z7.object({
1767
+ id: z7.string().describe("\u77E5\u8BC6\u7247\u6BB5 ID"),
1768
+ title: z7.string().optional().describe("\u65B0\u6807\u9898"),
1769
+ content: z7.string().optional().describe("\u65B0\u5185\u5BB9"),
1770
+ tags: z7.array(z7.string()).optional().describe("\u65B0\u6807\u7B7E\uFF08\u5B8C\u6574\u66FF\u6362\uFF09"),
1771
+ category: z7.enum(["insight", "decision", "analysis", "idea", "reference", "action"]).optional().describe("\u65B0\u5206\u7C7B")
1772
+ });
1773
+ async function inkUpdate(input) {
1774
+ try {
1775
+ const { id, ...updates } = input;
1776
+ if (!updates.title && !updates.content && !updates.tags && !updates.category) {
1777
+ return {
1778
+ success: false,
1779
+ message: "\u8BF7\u81F3\u5C11\u63D0\u4F9B\u4E00\u4E2A\u8981\u66F4\u65B0\u7684\u5B57\u6BB5\uFF08title / content / tags / category\uFF09"
1780
+ };
1781
+ }
1782
+ const result = await updateKnowledgeFile(id, updates);
1783
+ if (!result) {
1784
+ return {
1785
+ success: false,
1786
+ message: `\u672A\u627E\u5230 ID \u4E3A ${id} \u7684\u77E5\u8BC6\u7247\u6BB5`
1787
+ };
1788
+ }
1789
+ const changed = Object.keys(updates).filter(
1790
+ (k) => updates[k] !== void 0
1791
+ );
1792
+ return {
1793
+ success: true,
1794
+ message: `\u5DF2\u66F4\u65B0\u300C${result.meta.title}\u300D\u7684 ${changed.join("\u3001")}`,
1795
+ data: {
1796
+ id: result.meta.id,
1797
+ title: result.meta.title,
1798
+ tags: result.meta.tags,
1799
+ category: result.meta.category,
1800
+ updated_at: result.meta.updated_at
1801
+ }
1802
+ };
1803
+ } catch (err) {
1804
+ return {
1805
+ success: false,
1806
+ message: `\u66F4\u65B0\u5931\u8D25\uFF1A${err.message}`
1807
+ };
1808
+ }
1809
+ }
1810
+ var inkDeleteSchema = z7.object({
1811
+ id: z7.string().describe("\u77E5\u8BC6\u7247\u6BB5 ID"),
1812
+ confirm: z7.boolean().optional().default(false).describe("\u786E\u8BA4\u5220\u9664\uFF08\u5FC5\u987B\u4E3A true \u624D\u4F1A\u6267\u884C\u5220\u9664\uFF09")
1813
+ });
1814
+ async function inkDelete(input) {
1815
+ try {
1816
+ if (!input.confirm) {
1817
+ const index = await readIndex();
1818
+ const entry = index.entries[input.id];
1819
+ if (!entry) {
1820
+ return {
1821
+ success: false,
1822
+ message: `\u672A\u627E\u5230 ID \u4E3A ${input.id} \u7684\u77E5\u8BC6\u7247\u6BB5`
1823
+ };
1824
+ }
1825
+ return {
1826
+ success: false,
1827
+ message: `\u786E\u8BA4\u5220\u9664\u300C${entry.title}\u300D\uFF1F\u8BF7\u5C06 confirm \u8BBE\u4E3A true \u4EE5\u786E\u8BA4\u5220\u9664\u3002`,
1828
+ data: {
1829
+ id: entry.id,
1830
+ title: entry.title,
1831
+ category: entry.category,
1832
+ tags: entry.tags,
1833
+ created_at: entry.created_at
1834
+ }
1835
+ };
1836
+ }
1837
+ const deleted = await deleteKnowledgeFile(input.id);
1838
+ if (!deleted) {
1839
+ return {
1840
+ success: false,
1841
+ message: `\u672A\u627E\u5230 ID \u4E3A ${input.id} \u7684\u77E5\u8BC6\u7247\u6BB5`
1842
+ };
1843
+ }
1844
+ return {
1845
+ success: true,
1846
+ message: `\u5DF2\u5220\u9664\u77E5\u8BC6\u7247\u6BB5 ${input.id}`,
1847
+ data: { id: input.id }
1848
+ };
1849
+ } catch (err) {
1850
+ return {
1851
+ success: false,
1852
+ message: `\u5220\u9664\u5931\u8D25\uFF1A${err.message}`
1853
+ };
1854
+ }
1855
+ }
1856
+ var inkCompileSchema = z7.object({
1857
+ ids: z7.array(z7.string()).optional().describe("\u6307\u5B9A\u8981\u6574\u5408\u7684\u77E5\u8BC6\u7247\u6BB5 ID \u5217\u8868"),
1858
+ query: z7.string().optional().describe("\u641C\u7D22\u6761\u4EF6\uFF08\u4E0E ids \u4E8C\u9009\u4E00\uFF09"),
1859
+ tags: z7.array(z7.string()).optional().describe("\u6309\u6807\u7B7E\u7B5B\u9009\uFF08\u4E0E query \u642D\u914D\uFF09"),
1860
+ format: z7.enum(["outline", "article", "summary"]).optional().default("summary").describe("\u8F93\u51FA\u683C\u5F0F\uFF1Aoutline\uFF08\u5927\u7EB2\uFF09/ article\uFF08\u6587\u7AE0\uFF09/ summary\uFF08\u6458\u8981\uFF09"),
1861
+ instruction: z7.string().optional().describe("\u989D\u5916\u6574\u5408\u6307\u4EE4")
1862
+ });
1863
+ async function inkCompile(input) {
1864
+ try {
1865
+ let ids = [];
1866
+ if (input.ids && input.ids.length > 0) {
1867
+ ids = input.ids;
1868
+ } else if (input.query || input.tags && input.tags.length > 0) {
1869
+ const index = await readIndex();
1870
+ const { results } = searchKnowledge(index, {
1871
+ query: input.query,
1872
+ tags: input.tags,
1873
+ limit: 20
1874
+ });
1875
+ ids = results.map((r) => r.id);
1876
+ }
1877
+ if (ids.length === 0) {
1878
+ return {
1879
+ success: false,
1880
+ message: "\u6CA1\u6709\u627E\u5230\u8981\u6574\u5408\u7684\u77E5\u8BC6\u7247\u6BB5\uFF0C\u8BF7\u6307\u5B9A ids \u6216\u63D0\u4F9B\u641C\u7D22\u6761\u4EF6"
1881
+ };
1882
+ }
1883
+ const files = await readMultipleKnowledgeFiles(ids);
1884
+ if (files.length === 0) {
1885
+ return {
1886
+ success: false,
1887
+ message: "\u672A\u80FD\u8BFB\u53D6\u4EFB\u4F55\u77E5\u8BC6\u7247\u6BB5\u5185\u5BB9"
1888
+ };
1889
+ }
1890
+ const sections = files.map((f, i) => {
1891
+ const tagStr = f.meta.tags.length > 0 ? ` [${f.meta.tags.join(", ")}]` : "";
1892
+ return `### ${i + 1}. ${f.meta.title}${tagStr}
1893
+ > ${f.meta.category} | ${f.meta.created_at.slice(0, 10)}
1894
+
1895
+ ${f.content}`;
1896
+ });
1897
+ const formatLabels = {
1898
+ outline: "\u5927\u7EB2",
1899
+ article: "\u6587\u7AE0",
1900
+ summary: "\u6458\u8981"
1901
+ };
1902
+ const compiled = sections.join("\n\n---\n\n");
1903
+ return {
1904
+ success: true,
1905
+ message: `\u5DF2\u6574\u5408 ${files.length} \u6761\u77E5\u8BC6\u7247\u6BB5\uFF0C\u683C\u5F0F\uFF1A${formatLabels[input.format]}`,
1906
+ data: {
1907
+ source_count: files.length,
1908
+ source_ids: files.map((f) => f.meta.id),
1909
+ format: input.format,
1910
+ instruction: input.instruction || null,
1911
+ compiled_content: compiled
1912
+ }
1913
+ };
1914
+ } catch (err) {
1915
+ return {
1916
+ success: false,
1917
+ message: `\u6574\u5408\u5931\u8D25\uFF1A${err.message}`
1918
+ };
1919
+ }
1920
+ }
1921
+ var inkTagsSchema = z7.object({
1922
+ action: z7.enum(["list", "rename", "merge"]).describe("\u64CD\u4F5C\uFF1Alist\uFF08\u5217\u51FA\u6240\u6709\u6807\u7B7E\uFF09/ rename\uFF08\u91CD\u547D\u540D\uFF09/ merge\uFF08\u5408\u5E76\uFF09"),
1923
+ tag: z7.string().optional().describe("\u76EE\u6807\u6807\u7B7E\uFF08rename/merge \u65F6\u4F7F\u7528\uFF09"),
1924
+ new_tag: z7.string().optional().describe("\u65B0\u6807\u7B7E\u540D\uFF08rename \u65F6\u4F7F\u7528\uFF09"),
1925
+ source_tags: z7.array(z7.string()).optional().describe("\u8981\u5408\u5E76\u7684\u6E90\u6807\u7B7E\u5217\u8868\uFF08merge \u65F6\u4F7F\u7528\uFF09")
1926
+ });
1927
+ async function inkTags(input) {
1928
+ try {
1929
+ const index = await readIndex();
1930
+ switch (input.action) {
1931
+ case "list": {
1932
+ const tags = getAllTags(index);
1933
+ return {
1934
+ success: true,
1935
+ message: tags.length > 0 ? `\u5171 ${tags.length} \u4E2A\u6807\u7B7E` : "\u6682\u65E0\u6807\u7B7E",
1936
+ data: { tags }
1937
+ };
1938
+ }
1939
+ case "rename": {
1940
+ if (!input.tag || !input.new_tag) {
1941
+ return {
1942
+ success: false,
1943
+ message: "rename \u64CD\u4F5C\u9700\u8981\u63D0\u4F9B tag\uFF08\u539F\u6807\u7B7E\uFF09\u548C new_tag\uFF08\u65B0\u6807\u7B7E\u540D\uFF09"
1944
+ };
1945
+ }
1946
+ const count = await renameTag(input.tag, input.new_tag);
1947
+ if (count === 0) {
1948
+ return {
1949
+ success: false,
1950
+ message: `\u672A\u627E\u5230\u4F7F\u7528\u6807\u7B7E\u300C${input.tag}\u300D\u7684\u77E5\u8BC6\u7247\u6BB5`
1951
+ };
1952
+ }
1953
+ return {
1954
+ success: true,
1955
+ message: `\u5DF2\u5C06 ${count} \u6761\u77E5\u8BC6\u7247\u6BB5\u7684\u6807\u7B7E\u300C${input.tag}\u300D\u91CD\u547D\u540D\u4E3A\u300C${input.new_tag}\u300D`,
1956
+ data: { old_tag: input.tag, new_tag: input.new_tag, affected: count }
1957
+ };
1958
+ }
1959
+ case "merge": {
1960
+ if (!input.source_tags || input.source_tags.length === 0 || !input.new_tag) {
1961
+ return {
1962
+ success: false,
1963
+ message: "merge \u64CD\u4F5C\u9700\u8981\u63D0\u4F9B source_tags\uFF08\u6E90\u6807\u7B7E\u5217\u8868\uFF09\u548C new_tag\uFF08\u76EE\u6807\u6807\u7B7E\uFF09"
1964
+ };
1965
+ }
1966
+ const count = await mergeTags(input.source_tags, input.new_tag);
1967
+ if (count === 0) {
1968
+ return {
1969
+ success: false,
1970
+ message: `\u672A\u627E\u5230\u4F7F\u7528\u8FD9\u4E9B\u6807\u7B7E\u7684\u77E5\u8BC6\u7247\u6BB5`
1971
+ };
1972
+ }
1973
+ return {
1974
+ success: true,
1975
+ message: `\u5DF2\u5C06 ${count} \u6761\u77E5\u8BC6\u7247\u6BB5\u7684\u6807\u7B7E\u5408\u5E76\u4E3A\u300C${input.new_tag}\u300D`,
1976
+ data: {
1977
+ source_tags: input.source_tags,
1978
+ target_tag: input.new_tag,
1979
+ affected: count
1980
+ }
1981
+ };
1982
+ }
1983
+ }
1984
+ } catch (err) {
1985
+ return {
1986
+ success: false,
1987
+ message: `\u6807\u7B7E\u64CD\u4F5C\u5931\u8D25\uFF1A${err.message}`
1988
+ };
1989
+ }
1990
+ }
1991
+
1097
1992
  // src/index.ts
1098
1993
  var server = new McpServer({
1099
1994
  name: "ClaudeInk",
1100
- version: "0.7.0"
1995
+ version: "0.9.0"
1101
1996
  });
1102
1997
  server.tool("workflow.init", "\u521D\u59CB\u5316\u5199\u4F5C\u5DE5\u4F5C\u6D41\uFF08\u91CA\u653E\u4E09\u5C42\u914D\u7F6E + \u6FC0\u6D3B License + \u81EA\u52A8\u540C\u6B65\u4E91\u7AEF\u914D\u7F6E\uFF09", workflowInitSchema.shape, async (input) => {
1103
1998
  const result = await workflowInit(input);
1104
1999
  return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
1105
2000
  });
1106
- server.tool("sync", "\u540C\u6B65\u672C\u5730\u4E0E\u4E91\u7AEF\u6570\u636E\uFF08push=\u63A8\u9001, pull=\u62C9\u53D6, sync=\u53CC\u5411\u540C\u6B65\uFF09", syncSchema.shape, async (input) => {
2001
+ server.tool("sync", "\u4ECE\u4E91\u7AEF\u540C\u6B65\u5168\u91CF\u5143\u6570\u636E\u5230\u672C\u5730", syncSchema.shape, async (input) => {
1107
2002
  const result = await sync(input);
1108
2003
  return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
1109
2004
  });
@@ -1127,10 +2022,14 @@ server.tool("source.subscribe", "\u7BA1\u7406\u8BA2\u9605\u6E90\uFF08\u6DFB\u52A
1127
2022
  const result = await sourceSubscribe(input);
1128
2023
  return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
1129
2024
  });
1130
- server.tool("draft.save", "\u4FDD\u5B58\u8349\u7A3F", draftSaveSchema.shape, async (input) => {
2025
+ server.tool("draft.save", "\u65B0\u5EFA\u8349\u7A3F", draftSaveSchema.shape, async (input) => {
1131
2026
  const result = await draftSave(input);
1132
2027
  return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
1133
2028
  });
2029
+ server.tool("draft.update", "\u66F4\u65B0\u5DF2\u6709\u8349\u7A3F\uFF08\u5185\u5BB9/\u6807\u9898/\u72B6\u6001/\u6807\u7B7E\uFF09", draftUpdateSchema.shape, async (input) => {
2030
+ const result = await draftUpdate(input);
2031
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
2032
+ });
1134
2033
  server.tool("draft.publish", "\u6807\u8BB0\u8349\u7A3F\u4E3A\u5DF2\u53D1\u5E03", draftPublishSchema.shape, async (input) => {
1135
2034
  const result = await draftPublish(input);
1136
2035
  return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
@@ -1143,6 +2042,38 @@ server.tool("analytics.report", "\u83B7\u53D6\u6570\u636E\u5206\u6790\u62A5\u544
1143
2042
  const result = await analyticsReport(input);
1144
2043
  return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
1145
2044
  });
2045
+ server.tool("ink.save", "\u4FDD\u5B58\u77E5\u8BC6\u7247\u6BB5\u5230\u672C\u5730\u77E5\u8BC6\u5E93\uFF08\u5BF9\u8BDD\u6D1E\u5BDF\u3001\u51B3\u7B56\u3001\u5206\u6790\u7B49\uFF09", inkSaveSchema.shape, async (input) => {
2046
+ const result = await inkSave(input);
2047
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
2048
+ });
2049
+ server.tool("ink.search", "\u641C\u7D22\u77E5\u8BC6\u5E93\uFF08\u652F\u6301\u5173\u952E\u8BCD\u3001\u6807\u7B7E\u3001\u5206\u7C7B\u3001\u65F6\u95F4\u8303\u56F4\u8FC7\u6EE4\uFF09", inkSearchSchema.shape, async (input) => {
2050
+ const result = await inkSearch(input);
2051
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
2052
+ });
2053
+ server.tool("ink.get", "\u83B7\u53D6\u77E5\u8BC6\u7247\u6BB5\u5B8C\u6574\u5185\u5BB9", inkGetSchema.shape, async (input) => {
2054
+ const result = await inkGet(input);
2055
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
2056
+ });
2057
+ server.tool("ink.review", "\u6309\u65F6\u95F4\u6BB5\u56DE\u987E\u77E5\u8BC6\u5E93\uFF08\u7EDF\u8BA1 + \u5217\u8868\uFF09", inkReviewSchema.shape, async (input) => {
2058
+ const result = await inkReview(input);
2059
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
2060
+ });
2061
+ server.tool("ink.update", "\u66F4\u65B0\u5DF2\u4FDD\u5B58\u7684\u77E5\u8BC6\u7247\u6BB5\uFF08\u6807\u9898/\u5185\u5BB9/\u6807\u7B7E/\u5206\u7C7B\uFF09", inkUpdateSchema.shape, async (input) => {
2062
+ const result = await inkUpdate(input);
2063
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
2064
+ });
2065
+ server.tool("ink.delete", "\u5220\u9664\u77E5\u8BC6\u7247\u6BB5\uFF08\u9700\u786E\u8BA4\uFF09", inkDeleteSchema.shape, async (input) => {
2066
+ const result = await inkDelete(input);
2067
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
2068
+ });
2069
+ server.tool("ink.compile", "\u6574\u5408\u591A\u6761\u77E5\u8BC6\u7247\u6BB5\u4E3A\u7ED3\u6784\u5316\u5185\u5BB9\uFF08\u6309 ID \u6216\u641C\u7D22\u6761\u4EF6\uFF09", inkCompileSchema.shape, async (input) => {
2070
+ const result = await inkCompile(input);
2071
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
2072
+ });
2073
+ server.tool("ink.tags", "\u6807\u7B7E\u7BA1\u7406\uFF08\u5217\u51FA / \u91CD\u547D\u540D / \u5408\u5E76\u6807\u7B7E\uFF09", inkTagsSchema.shape, async (input) => {
2074
+ const result = await inkTags(input);
2075
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
2076
+ });
1146
2077
  async function main() {
1147
2078
  try {
1148
2079
  const state = await readState();
@@ -1154,7 +2085,7 @@ async function main() {
1154
2085
  }
1155
2086
  const transport = new StdioServerTransport();
1156
2087
  await server.connect(transport);
1157
- console.error("[ClaudeInk MCP] Server started on stdio (11 tools)");
2088
+ console.error("[ClaudeInk MCP] Server started on stdio (17 tools)");
1158
2089
  }
1159
2090
  main().catch((err) => {
1160
2091
  console.error("[ClaudeInk MCP] Fatal error:", err);