@claudeink/mcp-server 0.6.4 → 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 +1341 -398
  2. package/package.json +7 -7
package/dist/index.js CHANGED
@@ -6,11 +6,13 @@ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"
6
6
 
7
7
  // src/tools/source.ts
8
8
  import { z } from "zod";
9
- import { mkdir as mkdir3, readFile as readFile4 } from "fs/promises";
10
- import { join as join4 } from "path";
9
+ import { mkdir as mkdir2, readFile as readFile3 } from "fs/promises";
10
+ import { join as join3 } from "path";
11
+ import { execSync } from "child_process";
12
+ import { readdirSync } from "fs";
11
13
 
12
- // src/lib/config.ts
13
- import { readFile, writeFile, mkdir, chmod, access } from "fs/promises";
14
+ // src/lib/state.ts
15
+ import { readFile, writeFile, mkdir, chmod } from "fs/promises";
14
16
  import { join } from "path";
15
17
  var _workDirCache = null;
16
18
  function setWorkDir(dir) {
@@ -22,79 +24,170 @@ function getWorkDir() {
22
24
  function getClaudeinkDir() {
23
25
  return join(getWorkDir(), ".claudeink");
24
26
  }
25
- function getPaths() {
26
- const dir = getClaudeinkDir();
27
- return {
28
- credentials: join(dir, "credentials.json"),
29
- config: join(dir, "config.json"),
30
- syncState: join(dir, "sync-state.json"),
31
- tagQueue: join(dir, "tag-queue.json"),
32
- crawlSchedules: join(dir, "crawl-schedules.json"),
33
- logs: join(dir, "logs")
34
- };
27
+ function getStatePath() {
28
+ return join(getClaudeinkDir(), "state.json");
35
29
  }
36
- var DEFAULT_CONFIG = {
37
- apiBaseUrl: "https://app.claudeink.com",
38
- syncIntervalMs: 3e5,
39
- heartbeatIntervalMs: 3e5,
40
- maxTagQueueBatch: 20,
41
- workflowDir: ""
42
- };
43
- var DEFAULT_TAG_QUEUE = {
44
- queue: [],
45
- failed: [],
46
- history: {
47
- lastProcessed: null,
48
- totalProcessed: 0,
49
- avgTagsPerItem: 0
50
- }
30
+ var DEFAULT_STATE = {
31
+ credentials: null,
32
+ config: {
33
+ apiBaseUrl: "https://app.claudeink.com",
34
+ workflowDir: ""
35
+ },
36
+ sources: {},
37
+ drafts: {},
38
+ published: {},
39
+ configs: {},
40
+ crawlerSources: {},
41
+ writingMasters: {},
42
+ knowledge: {},
43
+ tagQueue: {
44
+ queue: [],
45
+ failed: []
46
+ },
47
+ lastSyncAt: ""
51
48
  };
52
49
  async function ensureDir() {
53
- const paths = getPaths();
54
50
  await mkdir(getClaudeinkDir(), { recursive: true });
55
- await mkdir(paths.logs, { recursive: true });
56
51
  }
57
- async function fileExists(path) {
52
+ async function readState() {
53
+ const statePath = getStatePath();
54
+ let raw;
58
55
  try {
59
- await access(path);
60
- return true;
56
+ raw = await readFile(statePath, "utf-8");
61
57
  } catch {
62
- return false;
58
+ const state = { ...DEFAULT_STATE };
59
+ state.config.workflowDir = getWorkDir();
60
+ return state;
63
61
  }
64
- }
65
- async function readJson(path, defaultValue) {
66
62
  try {
67
- const content = await readFile(path, "utf-8");
68
- return JSON.parse(content);
63
+ const parsed = JSON.parse(raw);
64
+ const state = {
65
+ ...DEFAULT_STATE,
66
+ ...parsed,
67
+ config: { ...DEFAULT_STATE.config, ...parsed.config },
68
+ tagQueue: { ...DEFAULT_STATE.tagQueue, ...parsed.tagQueue }
69
+ };
70
+ state.config.workflowDir = getWorkDir();
71
+ return state;
69
72
  } catch {
70
- return defaultValue;
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}`);
79
+ const state = { ...DEFAULT_STATE };
80
+ state.config.workflowDir = getWorkDir();
81
+ return state;
71
82
  }
72
83
  }
73
- async function writeJson(path, data, secure = false) {
84
+ async function writeState(state) {
74
85
  await ensureDir();
75
- await writeFile(path, JSON.stringify(data, null, 2), "utf-8");
76
- if (secure) {
77
- await chmod(path, 384);
78
- }
86
+ await writeFile(getStatePath(), JSON.stringify(state, null, 2), "utf-8");
87
+ await chmod(getStatePath(), 384);
79
88
  }
80
89
  async function getCredentials() {
81
- const paths = getPaths();
82
- if (!await fileExists(paths.credentials)) return null;
83
- return readJson(paths.credentials, null);
90
+ const state = await readState();
91
+ if (!state.credentials) return null;
92
+ return {
93
+ licenseKey: state.credentials.licenseKey,
94
+ token: state.credentials.token,
95
+ userId: state.credentials.userId,
96
+ plan: state.credentials.plan,
97
+ expiresAt: state.credentials.expiresAt
98
+ };
84
99
  }
85
100
  async function getConfig() {
86
- const paths = getPaths();
87
- const config = await readJson(paths.config, DEFAULT_CONFIG);
88
- config.workflowDir = getWorkDir();
89
- return config;
101
+ const state = await readState();
102
+ return state.config;
103
+ }
104
+ async function updateSource(id, data) {
105
+ const state = await readState();
106
+ state.sources[id] = data;
107
+ await writeState(state);
108
+ }
109
+ async function updateSourceTags(id, tags) {
110
+ const state = await readState();
111
+ if (state.sources[id]) {
112
+ state.sources[id].tags = tags;
113
+ state.sources[id].updatedAt = (/* @__PURE__ */ new Date()).toISOString();
114
+ }
115
+ await writeState(state);
116
+ }
117
+ async function updateDraft(id, data) {
118
+ const state = await readState();
119
+ state.drafts[id] = data;
120
+ await writeState(state);
121
+ }
122
+ async function moveDraftToPublished(id, data) {
123
+ const state = await readState();
124
+ delete state.drafts[id];
125
+ state.published[id] = data;
126
+ await writeState(state);
127
+ }
128
+ async function updateCrawlerSource(id, data) {
129
+ const state = await readState();
130
+ state.crawlerSources[id] = data;
131
+ await writeState(state);
132
+ }
133
+ async function removeCrawlerSource(id) {
134
+ const state = await readState();
135
+ delete state.crawlerSources[id];
136
+ await writeState(state);
137
+ }
138
+ async function mergeCloudData(cloud) {
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;
146
+ state.lastSyncAt = (/* @__PURE__ */ new Date()).toISOString();
147
+ await writeState(state);
90
148
  }
91
149
  async function getTagQueue() {
92
- const paths = getPaths();
93
- return readJson(paths.tagQueue, DEFAULT_TAG_QUEUE);
150
+ const state = await readState();
151
+ return {
152
+ queue: state.tagQueue.queue.map((item) => ({
153
+ file: item.file,
154
+ title: item.title,
155
+ addedAt: item.addedAt,
156
+ source: item.source,
157
+ retryCount: item.retries
158
+ })),
159
+ failed: state.tagQueue.failed.map((item) => ({
160
+ file: item.file,
161
+ title: item.title,
162
+ addedAt: item.failedAt,
163
+ source: item.source
164
+ })),
165
+ history: {
166
+ lastProcessed: null,
167
+ totalProcessed: 0,
168
+ avgTagsPerItem: 0
169
+ }
170
+ };
94
171
  }
95
172
  async function saveTagQueue(queue) {
96
- const paths = getPaths();
97
- await writeJson(paths.tagQueue, queue);
173
+ const state = await readState();
174
+ state.tagQueue = {
175
+ queue: queue.queue.map((item) => ({
176
+ file: item.file,
177
+ title: item.title,
178
+ source: item.source,
179
+ addedAt: item.addedAt,
180
+ retries: item.retryCount || 0
181
+ })),
182
+ failed: queue.failed.map((item) => ({
183
+ file: item.file,
184
+ title: item.title,
185
+ source: item.source,
186
+ failedAt: item.addedAt || (/* @__PURE__ */ new Date()).toISOString(),
187
+ reason: "max retries exceeded"
188
+ }))
189
+ };
190
+ await writeState(state);
98
191
  }
99
192
 
100
193
  // src/lib/sources.ts
@@ -117,7 +210,7 @@ async function writeSourceFile(filePath, meta, content) {
117
210
  const output = matter.stringify(content, meta);
118
211
  await writeFile2(filePath, output, "utf-8");
119
212
  }
120
- async function updateSourceTags(filePath, tags) {
213
+ async function updateSourceTags2(filePath, tags) {
121
214
  const source = await readSourceFile(filePath);
122
215
  source.meta.tags = tags;
123
216
  source.meta.taggedAt = (/* @__PURE__ */ new Date()).toISOString();
@@ -141,9 +234,8 @@ async function addToTagQueue(file, title, source) {
141
234
  await saveTagQueue(queue);
142
235
  }
143
236
  async function getNextTagBatch() {
144
- const config = await getConfig();
145
237
  const queue = await getTagQueue();
146
- return queue.queue.slice(0, config.maxTagQueueBatch);
238
+ return queue.queue.slice(0, 20);
147
239
  }
148
240
  async function markTagged(file, tagsCount) {
149
241
  const queue = await getTagQueue();
@@ -168,77 +260,35 @@ async function markTagFailed(file) {
168
260
  await saveTagQueue(queue);
169
261
  }
170
262
 
171
- // src/lib/state.ts
172
- import { readFile as readFile3, writeFile as writeFile3, mkdir as mkdir2 } from "fs/promises";
173
- import { join as join3 } from "path";
174
- var DEFAULT_STATE = {
175
- sources: {},
176
- drafts: {},
177
- published: {},
178
- configs: {},
179
- crawlerSources: {},
180
- writingMasters: {},
181
- lastSyncAt: ""
182
- };
183
- async function getStatePath() {
184
- const dir = join3(getWorkDir(), ".claudeink");
185
- await mkdir2(dir, { recursive: true });
186
- return join3(dir, "state.json");
263
+ // src/lib/push.ts
264
+ function fireAndForgetPush(payload) {
265
+ doPush(payload).catch(() => {
266
+ });
187
267
  }
188
- async function readState() {
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
+ };
189
280
  try {
190
- const path = await getStatePath();
191
- const content = await readFile3(path, "utf-8");
192
- return { ...DEFAULT_STATE, ...JSON.parse(content) };
193
- } catch {
194
- return { ...DEFAULT_STATE };
195
- }
196
- }
197
- async function writeState(state) {
198
- const path = await getStatePath();
199
- await writeFile3(path, JSON.stringify(state, null, 2), "utf-8");
200
- }
201
- async function updateSource(id, data) {
202
- const state = await readState();
203
- state.sources[id] = data;
204
- await writeState(state);
205
- }
206
- async function updateSourceTags2(id, tags) {
207
- const state = await readState();
208
- if (state.sources[id]) {
209
- state.sources[id].tags = tags;
210
- state.sources[id].updatedAt = (/* @__PURE__ */ new Date()).toISOString();
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);
211
291
  }
212
- await writeState(state);
213
- }
214
- async function updateDraft(id, data) {
215
- const state = await readState();
216
- state.drafts[id] = data;
217
- await writeState(state);
218
- }
219
- async function moveDraftToPublished(id, data) {
220
- const state = await readState();
221
- delete state.drafts[id];
222
- state.published[id] = data;
223
- await writeState(state);
224
- }
225
- async function updateCrawlerSource(id, data) {
226
- const state = await readState();
227
- state.crawlerSources[id] = data;
228
- await writeState(state);
229
- }
230
- async function removeCrawlerSource(id) {
231
- const state = await readState();
232
- delete state.crawlerSources[id];
233
- await writeState(state);
234
- }
235
- async function updateLastSyncAt() {
236
- const state = await readState();
237
- state.lastSyncAt = (/* @__PURE__ */ new Date()).toISOString();
238
- await writeState(state);
239
- }
240
- async function replaceState(state) {
241
- await writeState(state);
242
292
  }
243
293
 
244
294
  // src/tools/source.ts
@@ -252,12 +302,12 @@ var sourceAddSchema = z.object({
252
302
  async function sourceAdd(input) {
253
303
  const config = await getConfig();
254
304
  const folder = input.folder || "articles";
255
- const dir = join4(config.workflowDir, "sources", folder);
256
- await mkdir3(dir, { recursive: true });
305
+ const dir = join3(config.workflowDir, "sources", folder);
306
+ await mkdir2(dir, { recursive: true });
257
307
  const date = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
258
308
  const slug = input.title.replace(/[^\u4e00-\u9fa5a-zA-Z0-9\s-]/g, "").replace(/\s+/g, "-").slice(0, 50);
259
309
  const filename = `${date}-${slug}.md`;
260
- const filePath = join4(dir, filename);
310
+ const filePath = join3(dir, filename);
261
311
  const meta = {
262
312
  title: input.title,
263
313
  source: input.url ? (() => {
@@ -268,7 +318,7 @@ async function sourceAdd(input) {
268
318
  }
269
319
  })() : "manual",
270
320
  published: date,
271
- url: input.url
321
+ ...input.url ? { url: input.url } : {}
272
322
  };
273
323
  if (input.tags && input.tags.length > 0) {
274
324
  meta.tags = input.tags;
@@ -285,6 +335,18 @@ async function sourceAdd(input) {
285
335
  publishedAt: date,
286
336
  updatedAt: (/* @__PURE__ */ new Date()).toISOString()
287
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
+ });
288
350
  return {
289
351
  success: true,
290
352
  message: `\u7D20\u6750\u5DF2\u5165\u5E93: ${filename}\uFF08\u542B ${input.tags.length} \u4E2A\u6807\u7B7E\uFF09`,
@@ -304,6 +366,18 @@ async function sourceAdd(input) {
304
366
  publishedAt: date,
305
367
  updatedAt: (/* @__PURE__ */ new Date()).toISOString()
306
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
+ });
307
381
  return {
308
382
  success: true,
309
383
  message: `\u7D20\u6750\u5DF2\u5165\u5E93: ${filename}\uFF08\u5DF2\u52A0\u5165\u6807\u7B7E\u961F\u5217\uFF0C\u7B49\u5F85 Claude \u6253\u6807\u7B7E\uFF09`,
@@ -316,10 +390,11 @@ var sourceCrawlSchema = z.object({
316
390
  });
317
391
  async function sourceCrawl(input) {
318
392
  const config = await getConfig();
319
- const configPath = join4(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");
320
395
  let crawlerConfig;
321
396
  try {
322
- crawlerConfig = JSON.parse(await readFile4(configPath, "utf-8"));
397
+ crawlerConfig = JSON.parse(await readFile3(configPath, "utf-8"));
323
398
  } catch {
324
399
  crawlerConfig = { sources: [] };
325
400
  }
@@ -329,18 +404,97 @@ async function sourceCrawl(input) {
329
404
  message: "\u6682\u65E0\u914D\u7F6E\u722C\u866B\u6E90\u3002\u8BF7\u5148\u7528 source.subscribe \u6DFB\u52A0\u3002"
330
405
  };
331
406
  }
332
- 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);
333
408
  if (targets.length === 0) {
334
409
  return {
335
410
  success: false,
336
- 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"
337
412
  };
338
413
  }
339
- return {
340
- success: true,
341
- message: `\u5DF2\u89E6\u53D1 ${targets.length} \u4E2A\u722C\u866B\u6E90: ${targets.map((t) => t.name).join(", ")}`,
342
- data: { triggered: targets.map((t) => t.name) }
343
- };
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
+ }
344
498
  }
345
499
  var sourceTagSchema = z.object({
346
500
  mode: z.enum(["queue", "single"]).describe("queue=\u5904\u7406\u6807\u7B7E\u961F\u5217, single=\u5355\u4E2A\u6587\u4EF6\u6253\u6807\u7B7E"),
@@ -384,7 +538,7 @@ async function sourceTag(input) {
384
538
  for (const item of batch) {
385
539
  try {
386
540
  const config = await getConfig();
387
- const fullPath = join4(config.workflowDir, item.file);
541
+ const fullPath = join3(config.workflowDir, item.file);
388
542
  const source = await readSourceFile(fullPath);
389
543
  items.push({
390
544
  file: item.file,
@@ -419,10 +573,23 @@ var sourceTagApplySchema = z.object({
419
573
  async function sourceTagApply(input) {
420
574
  try {
421
575
  const config = await getConfig();
422
- const fullPath = input.file.startsWith("/") ? input.file : join4(config.workflowDir, input.file);
423
- await updateSourceTags(fullPath, input.tags);
576
+ const fullPath = input.file.startsWith("/") ? input.file : join3(config.workflowDir, input.file);
577
+ await updateSourceTags2(fullPath, input.tags);
424
578
  await markTagged(input.file, input.tags.length);
425
- await updateSourceTags2(input.file, input.tags);
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
+ });
426
593
  return {
427
594
  success: true,
428
595
  message: `\u6807\u7B7E\u5DF2\u5199\u5165: ${input.tags.join(", ")}`,
@@ -439,22 +606,33 @@ async function sourceTagApply(input) {
439
606
 
440
607
  // src/tools/subscribe.ts
441
608
  import { z as z2 } from "zod";
442
- import { readFile as readFile5, writeFile as writeFile4 } from "fs/promises";
443
- import { join as join5 } from "path";
609
+ import { readFile as readFile4, writeFile as writeFile3 } from "fs/promises";
610
+ import { join as join4 } from "path";
444
611
  var sourceSubscribeSchema = z2.object({
445
612
  action: z2.enum(["add", "remove", "list"]).describe("\u64CD\u4F5C\u7C7B\u578B"),
446
613
  id: z2.string().optional().describe("\u8BA2\u9605\u6E90 ID"),
447
614
  name: z2.string().optional().describe("\u8BA2\u9605\u6E90\u540D\u79F0"),
448
615
  url: z2.string().optional().describe("RSS/\u535A\u5BA2 URL"),
449
616
  type: z2.enum(["rss", "blog", "sitemap"]).optional().describe("\u6E90\u7C7B\u578B"),
450
- 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")
451
629
  });
452
630
  async function sourceSubscribe(input) {
453
631
  const config = await getConfig();
454
- const configPath = join5(config.workflowDir || process.cwd(), "tools/crawler/config.json");
632
+ const configPath = join4(config.workflowDir || process.cwd(), "tools/crawler/config.json");
455
633
  let crawlerConfig;
456
634
  try {
457
- crawlerConfig = JSON.parse(await readFile5(configPath, "utf-8"));
635
+ crawlerConfig = JSON.parse(await readFile4(configPath, "utf-8"));
458
636
  } catch {
459
637
  crawlerConfig = { sources: [] };
460
638
  }
@@ -475,13 +653,20 @@ async function sourceSubscribe(input) {
475
653
  const newSource = {
476
654
  id: input.id,
477
655
  name: input.name,
478
- url: input.url,
479
- type: input.type || "rss",
656
+ blogUrl: input.url,
657
+ type: input.type === "blog" ? "paginated" : input.type || "rss",
480
658
  icon: input.icon || "",
481
659
  enabled: true
482
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
+ }
483
668
  crawlerConfig.sources.push(newSource);
484
- await writeFile4(configPath, JSON.stringify(crawlerConfig, null, 2));
669
+ await writeFile3(configPath, JSON.stringify(crawlerConfig, null, 2));
485
670
  await updateCrawlerSource(input.id, {
486
671
  name: input.name,
487
672
  url: input.url,
@@ -489,6 +674,18 @@ async function sourceSubscribe(input) {
489
674
  icon: input.icon || "",
490
675
  enabled: true
491
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
+ });
492
689
  return { success: true, message: `\u8BA2\u9605\u6E90 ${input.name} \u5DF2\u6DFB\u52A0` };
493
690
  }
494
691
  if (input.action === "remove") {
@@ -496,8 +693,19 @@ async function sourceSubscribe(input) {
496
693
  return { success: false, message: "\u5220\u9664\u8BA2\u9605\u6E90\u9700\u8981 id \u53C2\u6570" };
497
694
  }
498
695
  crawlerConfig.sources = crawlerConfig.sources.filter((s) => s.id !== input.id);
499
- await writeFile4(configPath, JSON.stringify(crawlerConfig, null, 2));
696
+ await writeFile3(configPath, JSON.stringify(crawlerConfig, null, 2));
500
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
+ });
501
709
  return { success: true, message: `\u8BA2\u9605\u6E90 ${input.id} \u5DF2\u5220\u9664` };
502
710
  }
503
711
  return { success: false, message: "\u672A\u77E5\u64CD\u4F5C" };
@@ -507,22 +715,22 @@ async function sourceSubscribe(input) {
507
715
  import { z as z3 } from "zod";
508
716
 
509
717
  // src/lib/drafts.ts
510
- import { readFile as readFile6, writeFile as writeFile5, mkdir as mkdir4, unlink } from "fs/promises";
511
- import { join as join6, basename } from "path";
718
+ import { readFile as readFile5, writeFile as writeFile4, mkdir as mkdir3, unlink } from "fs/promises";
719
+ import { join as join5, basename } from "path";
512
720
  import matter2 from "gray-matter";
513
721
  import { glob as glob2 } from "glob";
514
722
  import { randomUUID } from "crypto";
515
723
  function getDraftDir(account, workflowDir) {
516
724
  if (account.toLowerCase() === "auston") {
517
- return join6(workflowDir, "drafts");
725
+ return join5(workflowDir, "drafts");
518
726
  }
519
- return join6(workflowDir, "accounts", account, "drafts");
727
+ return join5(workflowDir, "accounts", account, "drafts");
520
728
  }
521
729
  function getPublishedDir(account, workflowDir) {
522
730
  if (account.toLowerCase() === "auston") {
523
- return join6(workflowDir, "published");
731
+ return join5(workflowDir, "published");
524
732
  }
525
- return join6(workflowDir, "accounts", account, "published");
733
+ return join5(workflowDir, "accounts", account, "published");
526
734
  }
527
735
  function generateDraftFilename(title) {
528
736
  const date = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
@@ -532,10 +740,10 @@ function generateDraftFilename(title) {
532
740
  async function saveDraft(options) {
533
741
  const config = await getConfig();
534
742
  const dir = getDraftDir(options.account, config.workflowDir);
535
- await mkdir4(dir, { recursive: true });
743
+ await mkdir3(dir, { recursive: true });
536
744
  const id = randomUUID().slice(0, 8);
537
745
  const filename = generateDraftFilename(options.title);
538
- const filePath = join6(dir, filename);
746
+ const filePath = join5(dir, filename);
539
747
  const meta = {
540
748
  id,
541
749
  account: options.account,
@@ -548,11 +756,25 @@ async function saveDraft(options) {
548
756
  updatedAt: (/* @__PURE__ */ new Date()).toISOString()
549
757
  };
550
758
  const output = matter2.stringify(options.content, meta);
551
- await writeFile5(filePath, output, "utf-8");
759
+ await writeFile4(filePath, output, "utf-8");
552
760
  return { filePath, meta, content: options.content };
553
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
+ }
554
776
  async function publishDraft(options) {
555
- const raw = await readFile6(options.file, "utf-8");
777
+ const raw = await readFile5(options.file, "utf-8");
556
778
  const { data, content } = matter2(raw);
557
779
  const meta = data;
558
780
  meta.status = "published";
@@ -561,15 +783,34 @@ async function publishDraft(options) {
561
783
  meta.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
562
784
  const config = await getConfig();
563
785
  const publishedDir = getPublishedDir(meta.account, config.workflowDir);
564
- await mkdir4(publishedDir, { recursive: true });
565
- const newPath = join6(publishedDir, basename(options.file));
786
+ await mkdir3(publishedDir, { recursive: true });
787
+ const newPath = join5(publishedDir, basename(options.file));
566
788
  const output = matter2.stringify(content.trim(), meta);
567
- await writeFile5(newPath, output, "utf-8");
789
+ await writeFile4(newPath, output, "utf-8");
568
790
  await unlink(options.file);
569
791
  return { filePath: newPath, meta, content: content.trim() };
570
792
  }
571
793
 
572
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
+ }
573
814
  var draftSaveSchema = z3.object({
574
815
  account: z3.string().describe("\u8D26\u53F7\u540D\u79F0"),
575
816
  platform: z3.string().describe("\u5E73\u53F0\u540D\u79F0\uFF0C\u5982 wechat"),
@@ -597,6 +838,20 @@ async function draftSave(input) {
597
838
  createdAt: draft.meta.createdAt,
598
839
  updatedAt: draft.meta.updatedAt
599
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);
600
855
  return {
601
856
  success: true,
602
857
  message: `\u8349\u7A3F\u5DF2\u4FDD\u5B58: ${draft.meta.title} (${draft.meta.wordCount} \u5B57)`,
@@ -607,6 +862,55 @@ async function draftSave(input) {
607
862
  }
608
863
  };
609
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
+ }
610
914
  var draftPublishSchema = z3.object({
611
915
  file: z3.string().describe("\u8349\u7A3F\u6587\u4EF6\u8DEF\u5F84"),
612
916
  platformUrl: z3.string().optional().describe("\u5E73\u53F0\u53D1\u5E03\u94FE\u63A5")
@@ -623,6 +927,16 @@ async function draftPublish(input) {
623
927
  platformUrl: draft.meta.platformUrl || "",
624
928
  publishedAt: draft.meta.publishedAt || (/* @__PURE__ */ new Date()).toISOString()
625
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
+ });
626
940
  return {
627
941
  success: true,
628
942
  message: `\u5DF2\u53D1\u5E03: ${draft.meta.title} \u2192 ${draft.filePath}`,
@@ -736,159 +1050,42 @@ async function analyticsReport(input) {
736
1050
 
737
1051
  // src/tools/sync.ts
738
1052
  import { z as z5 } from "zod";
739
- import { writeFile as writeFile6, mkdir as mkdir5 } from "fs/promises";
740
- import { join as join7 } from "path";
741
- var syncPushSchema = z5.object({
1053
+ import { writeFile as writeFile5, mkdir as mkdir4 } from "fs/promises";
1054
+ import { join as join6 } from "path";
1055
+ var syncSchema = z5.object({
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
- async function syncPush(input) {
1058
+ async function sync(input) {
745
1059
  if (input.workDir) setWorkDir(input.workDir);
746
1060
  const creds = await getCredentials();
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
- const config = await getConfig();
751
- const state = await readState();
752
- const payload = {
753
- sources: Object.entries(state.sources).map(([id, s]) => ({
754
- id,
755
- title: s.title,
756
- source: s.source,
757
- tags: s.tags,
758
- sourceUrl: s.sourceUrl,
759
- coverUrl: s.coverUrl,
760
- sourceIcon: s.sourceIcon,
761
- createdAt: s.publishedAt
762
- })),
763
- drafts: Object.entries(state.drafts).map(([id, d]) => ({
764
- id,
765
- account: d.account,
766
- platform: d.platform,
767
- title: d.title,
768
- status: d.status,
769
- wordCount: d.wordCount,
770
- tags: d.tags,
771
- createdAt: d.createdAt,
772
- updatedAt: d.updatedAt
773
- })),
774
- published: Object.entries(state.published).map(([id, p]) => ({
775
- id,
776
- account: p.account,
777
- platform: p.platform,
778
- title: p.title,
779
- platformUrl: p.platformUrl,
780
- publishedAt: p.publishedAt
781
- })),
782
- configs: Object.entries(state.configs).map(([key, c]) => ({
783
- type: c.type,
784
- name: c.name,
785
- displayName: c.displayName,
786
- content: c.content,
787
- metadata: c.metadata
788
- })),
789
- crawlerSources: Object.entries(state.crawlerSources).map(([id, cs]) => ({
790
- id,
791
- name: cs.name,
792
- url: cs.url,
793
- type: cs.type,
794
- icon: cs.icon,
795
- enabled: cs.enabled
796
- })),
797
- analytics: []
798
- };
799
- try {
800
- const res = await fetch(`${config.apiBaseUrl}/api/sync/batch`, {
801
- method: "POST",
802
- headers: {
803
- "Content-Type": "application/json",
804
- "Authorization": `Bearer ${creds.token}`
805
- },
806
- body: JSON.stringify(payload)
807
- });
808
- const result = await res.json();
809
- if (res.ok) {
810
- await updateLastSyncAt();
811
- return {
812
- success: true,
813
- message: [
814
- "\u2705 \u540C\u6B65\u5B8C\u6210",
815
- ` \u7D20\u6750: ${payload.sources.length} \u7BC7`,
816
- ` \u8349\u7A3F: ${payload.drafts.length} \u7BC7`,
817
- ` \u5DF2\u53D1\u5E03: ${payload.published.length} \u7BC7`,
818
- ` \u914D\u7F6E: ${payload.configs.length} \u4E2A`,
819
- ` \u8BA2\u9605\u6E90: ${payload.crawlerSources.length} \u4E2A`,
820
- ` \u4E91\u7AEF\u63A5\u53D7: ${result.accepted || 0} \u6761`
821
- ].join("\n"),
822
- data: { accepted: result.accepted || 0 }
823
- };
824
- } else {
825
- return { success: false, message: `\u540C\u6B65\u5931\u8D25: HTTP ${res.status}` };
826
- }
827
- } catch (err) {
828
- return { success: false, message: `\u540C\u6B65\u7F51\u7EDC\u9519\u8BEF: ${err instanceof Error ? err.message : err}` };
829
- }
1064
+ return doPull(creds.token, input.workDir);
830
1065
  }
831
- var syncPullSchema = z5.object({
832
- workDir: z5.string().optional().describe("\u5DE5\u4F5C\u76EE\u5F55")
833
- });
834
- async function syncPull(input) {
835
- if (input.workDir) setWorkDir(input.workDir);
836
- const creds = await getCredentials();
837
- if (!creds?.token) {
838
- return { success: false, message: "\u672A\u6FC0\u6D3B\uFF0C\u8BF7\u5148\u4F7F\u7528 workflow.init \u6FC0\u6D3B License" };
839
- }
1066
+ async function doPull(token, workDir) {
840
1067
  const config = await getConfig();
841
- const workDir = input.workDir || config.workflowDir;
1068
+ const effectiveWorkDir = workDir || config.workflowDir;
842
1069
  try {
843
1070
  const res = await fetch(`${config.apiBaseUrl}/api/sync/pull`, {
844
- headers: { "Authorization": `Bearer ${creds.token}` }
1071
+ headers: { Authorization: `Bearer ${token}` }
845
1072
  });
846
1073
  if (!res.ok) {
847
1074
  if (res.status === 404) {
848
- 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" };
849
1076
  }
850
1077
  return { success: false, message: `\u62C9\u53D6\u5931\u8D25: HTTP ${res.status}` };
851
1078
  }
852
1079
  const cloudData = await res.json();
853
- if (!cloudData.configs || cloudData.configs.length === 0) {
854
- return { success: true, message: "\u4E91\u7AEF\u65E0\u914D\u7F6E\u6570\u636E" };
855
- }
856
- let updated = 0;
857
- const state = await readState();
858
- for (const cfgItem of cloudData.configs) {
859
- const stateKey = `${cfgItem.type}:${cfgItem.name}`;
860
- const localUpdatedAt = state.configs[stateKey]?.updatedAt || "";
861
- if (!localUpdatedAt || cfgItem.updatedAt > localUpdatedAt) {
862
- let filePath = "";
863
- if (cfgItem.type === "base_rules") {
864
- filePath = join7(workDir, "base-rules.md");
865
- } else if (cfgItem.type === "platform") {
866
- filePath = join7(workDir, "platforms", `${cfgItem.name}.md`);
867
- } else if (cfgItem.type === "account") {
868
- filePath = join7(workDir, "accounts", `${cfgItem.name}.yaml`);
869
- }
870
- if (filePath && cfgItem.content) {
871
- await writeFile6(filePath, cfgItem.content, "utf-8");
872
- state.configs[stateKey] = {
873
- type: cfgItem.type,
874
- name: cfgItem.name,
875
- displayName: cfgItem.displayName,
876
- content: cfgItem.content,
877
- metadata: cfgItem.metadata || {},
878
- updatedAt: cfgItem.updatedAt
879
- };
880
- updated++;
881
- }
882
- }
883
- }
884
- let crawlerCount = 0;
885
- state.crawlerSources = {};
1080
+ const counts = { configs: 0, sources: 0, drafts: 0, published: 0, crawlerSources: 0, writingMasters: 0 };
1081
+ const configsRecord = {};
1082
+ const crawlerSourcesRecord = {};
886
1083
  for (const cfg of cloudData.configs || []) {
887
1084
  if (cfg.type === "crawler" && cfg.content) {
888
1085
  try {
889
1086
  const sources = JSON.parse(cfg.content);
890
1087
  for (const s of sources) {
891
- state.crawlerSources[s.id] = {
1088
+ crawlerSourcesRecord[s.id] = {
892
1089
  name: s.name,
893
1090
  url: s.url,
894
1091
  type: s.type || "rss",
@@ -896,51 +1093,84 @@ async function syncPull(input) {
896
1093
  enabled: s.enabled !== false
897
1094
  };
898
1095
  }
899
- const crawlerDir = join7(workDir, "tools", "crawler");
900
- await mkdir5(crawlerDir, { recursive: true });
901
- const crawlerConfigPath = join7(crawlerDir, "config.json");
902
- await writeFile6(crawlerConfigPath, JSON.stringify({ sources }, null, 2), "utf-8");
903
- crawlerCount = sources.length;
1096
+ const crawlerDir = join6(effectiveWorkDir, "tools", "crawler");
1097
+ await mkdir4(crawlerDir, { recursive: true });
1098
+ await writeFile5(join6(crawlerDir, "config.json"), JSON.stringify({ sources }, null, 2), "utf-8");
1099
+ counts.crawlerSources = sources.length;
904
1100
  } catch (e) {
905
- console.error("[sync.pull] Failed to parse crawler sources:", e);
1101
+ console.error("[sync] Failed to parse crawler sources:", e);
906
1102
  }
907
- }
908
- }
909
- if (cloudData.writingMasters && Object.keys(cloudData.writingMasters).length > 0) {
910
- state.writingMasters = state.writingMasters || {};
911
- for (const [accountName, master] of Object.entries(cloudData.writingMasters)) {
912
- state.writingMasters[accountName] = {
913
- id: master.id,
914
- name: master.name,
915
- 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
916
1124
  };
917
1125
  }
918
1126
  }
919
- await replaceState(state);
920
- 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);
921
1147
  return {
922
1148
  success: true,
923
- message: updated > 0 || masterCount > 0 || crawlerCount > 0 ? [
924
- `\u2705 \u4ECE\u4E91\u7AEF\u540C\u6B65\u4E86 ${updated} \u4E2A\u914D\u7F6E\u6587\u4EF6`,
925
- crawlerCount > 0 ? ` \u8BA2\u9605\u6E90: ${crawlerCount} \u4E2A` : "",
926
- masterCount > 0 ? ` \u5199\u4F5C\u5927\u5E08: ${masterCount} \u4E2A` : ""
927
- ].filter(Boolean).join("\n") : "\u672C\u5730\u914D\u7F6E\u5DF2\u662F\u6700\u65B0\uFF0C\u65E0\u9700\u66F4\u65B0",
928
- 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
929
1151
  };
930
1152
  } catch (err) {
931
- 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}` };
932
1154
  }
933
1155
  }
1156
+ async function syncPull(input) {
1157
+ if (input.workDir) setWorkDir(input.workDir);
1158
+ const creds = await getCredentials();
1159
+ if (!creds?.token) {
1160
+ return { success: false, message: "\u672A\u6FC0\u6D3B" };
1161
+ }
1162
+ return doPull(creds.token, input.workDir);
1163
+ }
934
1164
 
935
1165
  // src/tools/workflow.ts
936
1166
  import { z as z6 } from "zod";
937
- import { cp, mkdir as mkdir6, access as access2, writeFile as writeFile7 } from "fs/promises";
938
- import { join as join8, dirname } from "path";
1167
+ import { cp, mkdir as mkdir5, access as access2, unlink as unlink2 } from "fs/promises";
1168
+ import { join as join7, dirname } from "path";
939
1169
  import { fileURLToPath } from "url";
940
1170
  var DEFAULT_API_BASE_URL = "https://app.claudeink.com";
941
1171
  var __filename = fileURLToPath(import.meta.url);
942
1172
  var __dirname = dirname(__filename);
943
- var WORKFLOW_SRC = join8(__dirname, "..", "workflow");
1173
+ var WORKFLOW_SRC = join7(__dirname, "..", "workflow");
944
1174
  var workflowInitSchema = z6.object({
945
1175
  workDir: z6.string().describe("\u5DE5\u4F5C\u6D41\u521D\u59CB\u5316\u76EE\u6807\u76EE\u5F55\uFF08\u7EDD\u5BF9\u8DEF\u5F84\uFF09"),
946
1176
  licenseKey: z6.string().optional().describe("License Key\uFF08\u53EF\u9009\uFF0C\u4F20\u5165\u5219\u81EA\u52A8\u6FC0\u6D3B\uFF09")
@@ -952,8 +1182,8 @@ async function workflowInit(input) {
952
1182
  setWorkDir(cwd);
953
1183
  const items = ["CLAUDE.md", "base-rules.md", "platforms", "accounts", "tools"];
954
1184
  for (const item of items) {
955
- const src = join8(WORKFLOW_SRC, item);
956
- const dest = join8(cwd, item);
1185
+ const src = join7(WORKFLOW_SRC, item);
1186
+ const dest = join7(cwd, item);
957
1187
  try {
958
1188
  await access2(dest);
959
1189
  results.push(`\u23ED\uFE0F ${item} \u5DF2\u5B58\u5728\uFF0C\u8DF3\u8FC7`);
@@ -962,68 +1192,39 @@ async function workflowInit(input) {
962
1192
  results.push(`\u2705 ${item}`);
963
1193
  }
964
1194
  }
965
- const claudeinkDir = join8(cwd, ".claudeink");
966
- const dirs = [
967
- "sources/articles",
968
- "sources/video-transcripts",
969
- "sources/notes",
970
- "sources/data",
971
- "sources/daily",
972
- "templates"
973
- ];
1195
+ const claudeinkDir = join7(cwd, ".claudeink");
1196
+ const dirs = ["sources/articles", "sources/video-transcripts", "sources/notes", "sources/data", "sources/daily", "templates"];
974
1197
  for (const dir of dirs) {
975
- await mkdir6(join8(cwd, dir), { recursive: true });
1198
+ await mkdir5(join7(cwd, dir), { recursive: true });
976
1199
  }
977
- await mkdir6(claudeinkDir, { recursive: true });
1200
+ await mkdir5(claudeinkDir, { recursive: true });
978
1201
  results.push("\u2705 \u8FD0\u884C\u65F6\u76EE\u5F55\u5DF2\u521B\u5EFA");
979
- const configPath = join8(claudeinkDir, "config.json");
980
- const statePath = join8(claudeinkDir, "state.json");
981
- await writeFile7(configPath, JSON.stringify({
982
- apiBaseUrl: DEFAULT_API_BASE_URL,
983
- syncIntervalMs: 3e5,
984
- heartbeatIntervalMs: 3e5,
985
- maxTagQueueBatch: 20,
986
- workflowDir: cwd
987
- }, null, 2), "utf-8");
988
- results.push("\u2705 config.json");
1202
+ const state = await readState();
1203
+ state.config.workflowDir = cwd;
1204
+ const oldCredsPath = join7(claudeinkDir, "credentials.json");
989
1205
  try {
990
- await access2(statePath);
991
- 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");
992
1209
  } catch {
993
- const emptyState = {
994
- sources: {},
995
- drafts: {},
996
- published: {},
997
- configs: {},
998
- crawlerSources: {},
999
- writingMasters: {},
1000
- lastSyncAt: ""
1001
- };
1002
- await writeFile7(statePath, JSON.stringify(emptyState, null, 2), "utf-8");
1003
- results.push("\u2705 state.json");
1004
1210
  }
1005
1211
  let activated = false;
1006
1212
  if (input.licenseKey) {
1007
1213
  try {
1008
- const activateUrl = `${DEFAULT_API_BASE_URL}/api/auth/activate`;
1009
- const res = await fetch(activateUrl, {
1214
+ const res = await fetch(`${DEFAULT_API_BASE_URL}/api/auth/activate`, {
1010
1215
  method: "POST",
1011
1216
  headers: { "Content-Type": "application/json" },
1012
1217
  body: JSON.stringify({ key: input.licenseKey })
1013
1218
  });
1014
1219
  const data = await res.json();
1015
1220
  if (data.userId) {
1016
- await writeFile7(
1017
- join8(claudeinkDir, "credentials.json"),
1018
- JSON.stringify({
1019
- licenseKey: input.licenseKey,
1020
- token: data.token,
1021
- userId: data.userId,
1022
- plan: data.plan,
1023
- expiresAt: data.expiresAt
1024
- }, null, 2),
1025
- { mode: 384 }
1026
- );
1221
+ state.credentials = {
1222
+ licenseKey: input.licenseKey,
1223
+ token: data.token,
1224
+ userId: data.userId,
1225
+ plan: data.plan,
1226
+ expiresAt: data.expiresAt
1227
+ };
1027
1228
  results.push(`\u2705 License \u6FC0\u6D3B\u6210\u529F\uFF08\u5957\u9910: ${data.plan}\uFF09`);
1028
1229
  activated = true;
1029
1230
  } else {
@@ -1033,25 +1234,30 @@ async function workflowInit(input) {
1033
1234
  results.push(`\u26A0\uFE0F \u6FC0\u6D3B\u7F51\u7EDC\u9519\u8BEF: ${err instanceof Error ? err.message : err}`);
1034
1235
  }
1035
1236
  }
1237
+ await writeState(state);
1238
+ results.push("\u2705 state.json");
1036
1239
  if (activated) {
1037
1240
  try {
1038
1241
  const pullResult = await syncPull({ workDir: cwd });
1039
- if (pullResult.success && pullResult.data && pullResult.data.updated > 0) {
1040
- 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
+ }
1041
1250
  } else {
1042
- 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");
1043
1252
  }
1044
1253
  } catch {
1045
- 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");
1046
1255
  }
1047
1256
  }
1048
1257
  try {
1049
- await access2(join8(cwd, "tools", "crawler", "package.json"));
1050
- const { execSync } = await import("child_process");
1051
- execSync("npm install --silent", {
1052
- cwd: join8(cwd, "tools", "crawler"),
1053
- stdio: "pipe"
1054
- });
1258
+ await access2(join7(cwd, "tools", "crawler", "package.json"));
1259
+ const { execSync: execSync2 } = await import("child_process");
1260
+ execSync2("npm install --silent", { cwd: join7(cwd, "tools", "crawler"), stdio: "pipe" });
1055
1261
  results.push("\u2705 \u722C\u866B\u4F9D\u8D56\u5DF2\u5B89\u88C5");
1056
1262
  } catch {
1057
1263
  }
@@ -1075,23 +1281,725 @@ async function workflowInit(input) {
1075
1281
  }
1076
1282
  }
1077
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
+
1078
1992
  // src/index.ts
1079
- import { readFile as readFile7 } from "fs/promises";
1080
- import { join as join9 } from "path";
1081
1993
  var server = new McpServer({
1082
1994
  name: "ClaudeInk",
1083
- version: "0.6.4"
1995
+ version: "0.9.0"
1084
1996
  });
1085
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) => {
1086
1998
  const result = await workflowInit(input);
1087
1999
  return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
1088
2000
  });
1089
- server.tool("sync.push", "\u5C06\u672C\u5730\u6570\u636E\u540C\u6B65\u5230 ClaudeInk \u4E91\u7AEF\uFF08\u8BFB\u53D6\u672C\u5730\u72B6\u6001\u8868\u76F4\u63A5\u63A8\u9001\uFF09", syncPushSchema.shape, async (input) => {
1090
- const result = await syncPush(input);
1091
- return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
1092
- });
1093
- server.tool("sync.pull", "\u4ECE\u4E91\u7AEF\u62C9\u53D6\u914D\u7F6E\u5230\u672C\u5730\uFF08\u65F6\u95F4\u6233\u6BD4\u8F83\uFF0C\u65B0\u7684\u8986\u76D6\u65E7\u7684\uFF09", syncPullSchema.shape, async (input) => {
1094
- const result = await syncPull(input);
2001
+ server.tool("sync", "\u4ECE\u4E91\u7AEF\u540C\u6B65\u5168\u91CF\u5143\u6570\u636E\u5230\u672C\u5730", syncSchema.shape, async (input) => {
2002
+ const result = await sync(input);
1095
2003
  return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
1096
2004
  });
1097
2005
  server.tool("source.add", "\u6DFB\u52A0\u7D20\u6750\u5230\u672C\u5730\u7D20\u6750\u5E93", sourceAddSchema.shape, async (input) => {
@@ -1114,10 +2022,14 @@ server.tool("source.subscribe", "\u7BA1\u7406\u8BA2\u9605\u6E90\uFF08\u6DFB\u52A
1114
2022
  const result = await sourceSubscribe(input);
1115
2023
  return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
1116
2024
  });
1117
- 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) => {
1118
2026
  const result = await draftSave(input);
1119
2027
  return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
1120
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
+ });
1121
2033
  server.tool("draft.publish", "\u6807\u8BB0\u8349\u7A3F\u4E3A\u5DF2\u53D1\u5E03", draftPublishSchema.shape, async (input) => {
1122
2034
  const result = await draftPublish(input);
1123
2035
  return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
@@ -1130,19 +2042,50 @@ server.tool("analytics.report", "\u83B7\u53D6\u6570\u636E\u5206\u6790\u62A5\u544
1130
2042
  const result = await analyticsReport(input);
1131
2043
  return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
1132
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
+ });
1133
2077
  async function main() {
1134
2078
  try {
1135
- const configPath = join9(process.cwd(), ".claudeink", "config.json");
1136
- const config = JSON.parse(await readFile7(configPath, "utf-8"));
1137
- if (config.workflowDir) {
1138
- setWorkDir(config.workflowDir);
1139
- console.error(`[ClaudeInk MCP] workDir restored: ${config.workflowDir}`);
2079
+ const state = await readState();
2080
+ if (state.config.workflowDir) {
2081
+ setWorkDir(state.config.workflowDir);
2082
+ console.error(`[ClaudeInk MCP] workDir restored: ${state.config.workflowDir}`);
1140
2083
  }
1141
2084
  } catch {
1142
2085
  }
1143
2086
  const transport = new StdioServerTransport();
1144
2087
  await server.connect(transport);
1145
- console.error("[ClaudeInk MCP] Server started on stdio (12 tools)");
2088
+ console.error("[ClaudeInk MCP] Server started on stdio (17 tools)");
1146
2089
  }
1147
2090
  main().catch((err) => {
1148
2091
  console.error("[ClaudeInk MCP] Fatal error:", err);