@claudeink/mcp-server 0.6.4 → 0.7.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 +252 -240
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -6,11 +6,11 @@ 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
11
 
12
- // src/lib/config.ts
13
- import { readFile, writeFile, mkdir, chmod, access } from "fs/promises";
12
+ // src/lib/state.ts
13
+ import { readFile, writeFile, mkdir, chmod } from "fs/promises";
14
14
  import { join } from "path";
15
15
  var _workDirCache = null;
16
16
  function setWorkDir(dir) {
@@ -22,79 +22,152 @@ function getWorkDir() {
22
22
  function getClaudeinkDir() {
23
23
  return join(getWorkDir(), ".claudeink");
24
24
  }
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
- };
25
+ function getStatePath() {
26
+ return join(getClaudeinkDir(), "state.json");
35
27
  }
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
- }
28
+ var DEFAULT_STATE = {
29
+ credentials: null,
30
+ config: {
31
+ apiBaseUrl: "https://app.claudeink.com",
32
+ workflowDir: ""
33
+ },
34
+ sources: {},
35
+ drafts: {},
36
+ published: {},
37
+ configs: {},
38
+ crawlerSources: {},
39
+ writingMasters: {},
40
+ tagQueue: {
41
+ queue: [],
42
+ failed: []
43
+ },
44
+ lastSyncAt: ""
51
45
  };
52
46
  async function ensureDir() {
53
- const paths = getPaths();
54
47
  await mkdir(getClaudeinkDir(), { recursive: true });
55
- await mkdir(paths.logs, { recursive: true });
56
- }
57
- async function fileExists(path) {
58
- try {
59
- await access(path);
60
- return true;
61
- } catch {
62
- return false;
63
- }
64
48
  }
65
- async function readJson(path, defaultValue) {
49
+ async function readState() {
66
50
  try {
67
- const content = await readFile(path, "utf-8");
68
- return JSON.parse(content);
51
+ const content = await readFile(getStatePath(), "utf-8");
52
+ const parsed = JSON.parse(content);
53
+ const state = {
54
+ ...DEFAULT_STATE,
55
+ ...parsed,
56
+ config: { ...DEFAULT_STATE.config, ...parsed.config },
57
+ tagQueue: { ...DEFAULT_STATE.tagQueue, ...parsed.tagQueue }
58
+ };
59
+ state.config.workflowDir = getWorkDir();
60
+ return state;
69
61
  } catch {
70
- return defaultValue;
62
+ const state = { ...DEFAULT_STATE };
63
+ state.config.workflowDir = getWorkDir();
64
+ return state;
71
65
  }
72
66
  }
73
- async function writeJson(path, data, secure = false) {
67
+ async function writeState(state) {
74
68
  await ensureDir();
75
- await writeFile(path, JSON.stringify(data, null, 2), "utf-8");
76
- if (secure) {
77
- await chmod(path, 384);
78
- }
69
+ await writeFile(getStatePath(), JSON.stringify(state, null, 2), "utf-8");
70
+ await chmod(getStatePath(), 384);
79
71
  }
80
72
  async function getCredentials() {
81
- const paths = getPaths();
82
- if (!await fileExists(paths.credentials)) return null;
83
- return readJson(paths.credentials, null);
73
+ const state = await readState();
74
+ if (!state.credentials) return null;
75
+ return {
76
+ licenseKey: state.credentials.licenseKey,
77
+ token: state.credentials.token,
78
+ userId: state.credentials.userId,
79
+ plan: state.credentials.plan,
80
+ expiresAt: state.credentials.expiresAt
81
+ };
84
82
  }
85
83
  async function getConfig() {
86
- const paths = getPaths();
87
- const config = await readJson(paths.config, DEFAULT_CONFIG);
88
- config.workflowDir = getWorkDir();
89
- return config;
84
+ const state = await readState();
85
+ return state.config;
86
+ }
87
+ async function updateSource(id, data) {
88
+ const state = await readState();
89
+ state.sources[id] = data;
90
+ await writeState(state);
91
+ }
92
+ async function updateSourceTags(id, tags) {
93
+ const state = await readState();
94
+ if (state.sources[id]) {
95
+ state.sources[id].tags = tags;
96
+ state.sources[id].updatedAt = (/* @__PURE__ */ new Date()).toISOString();
97
+ }
98
+ await writeState(state);
99
+ }
100
+ async function updateDraft(id, data) {
101
+ const state = await readState();
102
+ state.drafts[id] = data;
103
+ await writeState(state);
104
+ }
105
+ async function moveDraftToPublished(id, data) {
106
+ const state = await readState();
107
+ delete state.drafts[id];
108
+ state.published[id] = data;
109
+ await writeState(state);
110
+ }
111
+ async function updateCrawlerSource(id, data) {
112
+ const state = await readState();
113
+ state.crawlerSources[id] = data;
114
+ await writeState(state);
115
+ }
116
+ async function removeCrawlerSource(id) {
117
+ const state = await readState();
118
+ delete state.crawlerSources[id];
119
+ await writeState(state);
120
+ }
121
+ async function updateLastSyncAt() {
122
+ const state = await readState();
123
+ state.lastSyncAt = (/* @__PURE__ */ new Date()).toISOString();
124
+ await writeState(state);
125
+ }
126
+ async function replaceState(newState) {
127
+ await writeState(newState);
90
128
  }
91
129
  async function getTagQueue() {
92
- const paths = getPaths();
93
- return readJson(paths.tagQueue, DEFAULT_TAG_QUEUE);
130
+ const state = await readState();
131
+ return {
132
+ queue: state.tagQueue.queue.map((item) => ({
133
+ file: item.file,
134
+ title: item.title,
135
+ addedAt: item.addedAt,
136
+ source: item.source,
137
+ retryCount: item.retries
138
+ })),
139
+ failed: state.tagQueue.failed.map((item) => ({
140
+ file: item.file,
141
+ title: item.title,
142
+ addedAt: item.failedAt,
143
+ source: item.source
144
+ })),
145
+ history: {
146
+ lastProcessed: null,
147
+ totalProcessed: 0,
148
+ avgTagsPerItem: 0
149
+ }
150
+ };
94
151
  }
95
152
  async function saveTagQueue(queue) {
96
- const paths = getPaths();
97
- await writeJson(paths.tagQueue, queue);
153
+ const state = await readState();
154
+ state.tagQueue = {
155
+ queue: queue.queue.map((item) => ({
156
+ file: item.file,
157
+ title: item.title,
158
+ source: item.source,
159
+ addedAt: item.addedAt,
160
+ retries: item.retryCount || 0
161
+ })),
162
+ failed: queue.failed.map((item) => ({
163
+ file: item.file,
164
+ title: item.title,
165
+ source: item.source,
166
+ failedAt: item.addedAt || (/* @__PURE__ */ new Date()).toISOString(),
167
+ reason: "max retries exceeded"
168
+ }))
169
+ };
170
+ await writeState(state);
98
171
  }
99
172
 
100
173
  // src/lib/sources.ts
@@ -117,7 +190,7 @@ async function writeSourceFile(filePath, meta, content) {
117
190
  const output = matter.stringify(content, meta);
118
191
  await writeFile2(filePath, output, "utf-8");
119
192
  }
120
- async function updateSourceTags(filePath, tags) {
193
+ async function updateSourceTags2(filePath, tags) {
121
194
  const source = await readSourceFile(filePath);
122
195
  source.meta.tags = tags;
123
196
  source.meta.taggedAt = (/* @__PURE__ */ new Date()).toISOString();
@@ -141,9 +214,8 @@ async function addToTagQueue(file, title, source) {
141
214
  await saveTagQueue(queue);
142
215
  }
143
216
  async function getNextTagBatch() {
144
- const config = await getConfig();
145
217
  const queue = await getTagQueue();
146
- return queue.queue.slice(0, config.maxTagQueueBatch);
218
+ return queue.queue.slice(0, 20);
147
219
  }
148
220
  async function markTagged(file, tagsCount) {
149
221
  const queue = await getTagQueue();
@@ -168,79 +240,6 @@ async function markTagFailed(file) {
168
240
  await saveTagQueue(queue);
169
241
  }
170
242
 
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");
187
- }
188
- async function readState() {
189
- 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();
211
- }
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
- }
243
-
244
243
  // src/tools/source.ts
245
244
  var sourceAddSchema = z.object({
246
245
  title: z.string().describe("\u7D20\u6750\u6807\u9898"),
@@ -252,12 +251,12 @@ var sourceAddSchema = z.object({
252
251
  async function sourceAdd(input) {
253
252
  const config = await getConfig();
254
253
  const folder = input.folder || "articles";
255
- const dir = join4(config.workflowDir, "sources", folder);
256
- await mkdir3(dir, { recursive: true });
254
+ const dir = join3(config.workflowDir, "sources", folder);
255
+ await mkdir2(dir, { recursive: true });
257
256
  const date = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
258
257
  const slug = input.title.replace(/[^\u4e00-\u9fa5a-zA-Z0-9\s-]/g, "").replace(/\s+/g, "-").slice(0, 50);
259
258
  const filename = `${date}-${slug}.md`;
260
- const filePath = join4(dir, filename);
259
+ const filePath = join3(dir, filename);
261
260
  const meta = {
262
261
  title: input.title,
263
262
  source: input.url ? (() => {
@@ -316,10 +315,10 @@ var sourceCrawlSchema = z.object({
316
315
  });
317
316
  async function sourceCrawl(input) {
318
317
  const config = await getConfig();
319
- const configPath = join4(config.workflowDir || process.cwd(), "tools", "crawler", "config.json");
318
+ const configPath = join3(config.workflowDir || process.cwd(), "tools", "crawler", "config.json");
320
319
  let crawlerConfig;
321
320
  try {
322
- crawlerConfig = JSON.parse(await readFile4(configPath, "utf-8"));
321
+ crawlerConfig = JSON.parse(await readFile3(configPath, "utf-8"));
323
322
  } catch {
324
323
  crawlerConfig = { sources: [] };
325
324
  }
@@ -384,7 +383,7 @@ async function sourceTag(input) {
384
383
  for (const item of batch) {
385
384
  try {
386
385
  const config = await getConfig();
387
- const fullPath = join4(config.workflowDir, item.file);
386
+ const fullPath = join3(config.workflowDir, item.file);
388
387
  const source = await readSourceFile(fullPath);
389
388
  items.push({
390
389
  file: item.file,
@@ -419,10 +418,10 @@ var sourceTagApplySchema = z.object({
419
418
  async function sourceTagApply(input) {
420
419
  try {
421
420
  const config = await getConfig();
422
- const fullPath = input.file.startsWith("/") ? input.file : join4(config.workflowDir, input.file);
423
- await updateSourceTags(fullPath, input.tags);
421
+ const fullPath = input.file.startsWith("/") ? input.file : join3(config.workflowDir, input.file);
422
+ await updateSourceTags2(fullPath, input.tags);
424
423
  await markTagged(input.file, input.tags.length);
425
- await updateSourceTags2(input.file, input.tags);
424
+ await updateSourceTags(input.file, input.tags);
426
425
  return {
427
426
  success: true,
428
427
  message: `\u6807\u7B7E\u5DF2\u5199\u5165: ${input.tags.join(", ")}`,
@@ -439,8 +438,8 @@ async function sourceTagApply(input) {
439
438
 
440
439
  // src/tools/subscribe.ts
441
440
  import { z as z2 } from "zod";
442
- import { readFile as readFile5, writeFile as writeFile4 } from "fs/promises";
443
- import { join as join5 } from "path";
441
+ import { readFile as readFile4, writeFile as writeFile3 } from "fs/promises";
442
+ import { join as join4 } from "path";
444
443
  var sourceSubscribeSchema = z2.object({
445
444
  action: z2.enum(["add", "remove", "list"]).describe("\u64CD\u4F5C\u7C7B\u578B"),
446
445
  id: z2.string().optional().describe("\u8BA2\u9605\u6E90 ID"),
@@ -451,10 +450,10 @@ var sourceSubscribeSchema = z2.object({
451
450
  });
452
451
  async function sourceSubscribe(input) {
453
452
  const config = await getConfig();
454
- const configPath = join5(config.workflowDir || process.cwd(), "tools/crawler/config.json");
453
+ const configPath = join4(config.workflowDir || process.cwd(), "tools/crawler/config.json");
455
454
  let crawlerConfig;
456
455
  try {
457
- crawlerConfig = JSON.parse(await readFile5(configPath, "utf-8"));
456
+ crawlerConfig = JSON.parse(await readFile4(configPath, "utf-8"));
458
457
  } catch {
459
458
  crawlerConfig = { sources: [] };
460
459
  }
@@ -481,7 +480,7 @@ async function sourceSubscribe(input) {
481
480
  enabled: true
482
481
  };
483
482
  crawlerConfig.sources.push(newSource);
484
- await writeFile4(configPath, JSON.stringify(crawlerConfig, null, 2));
483
+ await writeFile3(configPath, JSON.stringify(crawlerConfig, null, 2));
485
484
  await updateCrawlerSource(input.id, {
486
485
  name: input.name,
487
486
  url: input.url,
@@ -496,7 +495,7 @@ async function sourceSubscribe(input) {
496
495
  return { success: false, message: "\u5220\u9664\u8BA2\u9605\u6E90\u9700\u8981 id \u53C2\u6570" };
497
496
  }
498
497
  crawlerConfig.sources = crawlerConfig.sources.filter((s) => s.id !== input.id);
499
- await writeFile4(configPath, JSON.stringify(crawlerConfig, null, 2));
498
+ await writeFile3(configPath, JSON.stringify(crawlerConfig, null, 2));
500
499
  await removeCrawlerSource(input.id);
501
500
  return { success: true, message: `\u8BA2\u9605\u6E90 ${input.id} \u5DF2\u5220\u9664` };
502
501
  }
@@ -507,22 +506,22 @@ async function sourceSubscribe(input) {
507
506
  import { z as z3 } from "zod";
508
507
 
509
508
  // 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";
509
+ import { readFile as readFile5, writeFile as writeFile4, mkdir as mkdir3, unlink } from "fs/promises";
510
+ import { join as join5, basename } from "path";
512
511
  import matter2 from "gray-matter";
513
512
  import { glob as glob2 } from "glob";
514
513
  import { randomUUID } from "crypto";
515
514
  function getDraftDir(account, workflowDir) {
516
515
  if (account.toLowerCase() === "auston") {
517
- return join6(workflowDir, "drafts");
516
+ return join5(workflowDir, "drafts");
518
517
  }
519
- return join6(workflowDir, "accounts", account, "drafts");
518
+ return join5(workflowDir, "accounts", account, "drafts");
520
519
  }
521
520
  function getPublishedDir(account, workflowDir) {
522
521
  if (account.toLowerCase() === "auston") {
523
- return join6(workflowDir, "published");
522
+ return join5(workflowDir, "published");
524
523
  }
525
- return join6(workflowDir, "accounts", account, "published");
524
+ return join5(workflowDir, "accounts", account, "published");
526
525
  }
527
526
  function generateDraftFilename(title) {
528
527
  const date = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
@@ -532,10 +531,10 @@ function generateDraftFilename(title) {
532
531
  async function saveDraft(options) {
533
532
  const config = await getConfig();
534
533
  const dir = getDraftDir(options.account, config.workflowDir);
535
- await mkdir4(dir, { recursive: true });
534
+ await mkdir3(dir, { recursive: true });
536
535
  const id = randomUUID().slice(0, 8);
537
536
  const filename = generateDraftFilename(options.title);
538
- const filePath = join6(dir, filename);
537
+ const filePath = join5(dir, filename);
539
538
  const meta = {
540
539
  id,
541
540
  account: options.account,
@@ -548,11 +547,11 @@ async function saveDraft(options) {
548
547
  updatedAt: (/* @__PURE__ */ new Date()).toISOString()
549
548
  };
550
549
  const output = matter2.stringify(options.content, meta);
551
- await writeFile5(filePath, output, "utf-8");
550
+ await writeFile4(filePath, output, "utf-8");
552
551
  return { filePath, meta, content: options.content };
553
552
  }
554
553
  async function publishDraft(options) {
555
- const raw = await readFile6(options.file, "utf-8");
554
+ const raw = await readFile5(options.file, "utf-8");
556
555
  const { data, content } = matter2(raw);
557
556
  const meta = data;
558
557
  meta.status = "published";
@@ -561,10 +560,10 @@ async function publishDraft(options) {
561
560
  meta.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
562
561
  const config = await getConfig();
563
562
  const publishedDir = getPublishedDir(meta.account, config.workflowDir);
564
- await mkdir4(publishedDir, { recursive: true });
565
- const newPath = join6(publishedDir, basename(options.file));
563
+ await mkdir3(publishedDir, { recursive: true });
564
+ const newPath = join5(publishedDir, basename(options.file));
566
565
  const output = matter2.stringify(content.trim(), meta);
567
- await writeFile5(newPath, output, "utf-8");
566
+ await writeFile4(newPath, output, "utf-8");
568
567
  await unlink(options.file);
569
568
  return { filePath: newPath, meta, content: content.trim() };
570
569
  }
@@ -736,17 +735,38 @@ async function analyticsReport(input) {
736
735
 
737
736
  // src/tools/sync.ts
738
737
  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({
738
+ import { writeFile as writeFile5, mkdir as mkdir4 } from "fs/promises";
739
+ import { join as join6 } from "path";
740
+ 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
742
  workDir: z5.string().optional().describe("\u5DE5\u4F5C\u76EE\u5F55\uFF08\u9ED8\u8BA4\u4F7F\u7528\u914D\u7F6E\u4E2D\u7684 workflowDir\uFF09")
743
743
  });
744
- async function syncPush(input) {
744
+ async function sync(input) {
745
745
  if (input.workDir) setWorkDir(input.workDir);
746
746
  const creds = await getCredentials();
747
747
  if (!creds?.token) {
748
748
  return { success: false, message: "\u672A\u6FC0\u6D3B\uFF0C\u8BF7\u5148\u4F7F\u7528 workflow.init \u6FC0\u6D3B License" };
749
749
  }
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) {
750
770
  const config = await getConfig();
751
771
  const state = await readState();
752
772
  const payload = {
@@ -779,7 +799,7 @@ async function syncPush(input) {
779
799
  platformUrl: p.platformUrl,
780
800
  publishedAt: p.publishedAt
781
801
  })),
782
- configs: Object.entries(state.configs).map(([key, c]) => ({
802
+ configs: Object.entries(state.configs).map(([_key, c]) => ({
783
803
  type: c.type,
784
804
  name: c.name,
785
805
  displayName: c.displayName,
@@ -801,7 +821,7 @@ async function syncPush(input) {
801
821
  method: "POST",
802
822
  headers: {
803
823
  "Content-Type": "application/json",
804
- "Authorization": `Bearer ${creds.token}`
824
+ "Authorization": `Bearer ${token}`
805
825
  },
806
826
  body: JSON.stringify(payload)
807
827
  });
@@ -811,7 +831,7 @@ async function syncPush(input) {
811
831
  return {
812
832
  success: true,
813
833
  message: [
814
- "\u2705 \u540C\u6B65\u5B8C\u6210",
834
+ "\u2705 \u63A8\u9001\u5B8C\u6210",
815
835
  ` \u7D20\u6750: ${payload.sources.length} \u7BC7`,
816
836
  ` \u8349\u7A3F: ${payload.drafts.length} \u7BC7`,
817
837
  ` \u5DF2\u53D1\u5E03: ${payload.published.length} \u7BC7`,
@@ -822,26 +842,18 @@ async function syncPush(input) {
822
842
  data: { accepted: result.accepted || 0 }
823
843
  };
824
844
  } else {
825
- return { success: false, message: `\u540C\u6B65\u5931\u8D25: HTTP ${res.status}` };
845
+ return { success: false, message: `\u63A8\u9001\u5931\u8D25: HTTP ${res.status}` };
826
846
  }
827
847
  } catch (err) {
828
- return { success: false, message: `\u540C\u6B65\u7F51\u7EDC\u9519\u8BEF: ${err instanceof Error ? err.message : err}` };
848
+ return { success: false, message: `\u63A8\u9001\u7F51\u7EDC\u9519\u8BEF: ${err instanceof Error ? err.message : err}` };
829
849
  }
830
850
  }
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
- }
851
+ async function doPull(token, workDir) {
840
852
  const config = await getConfig();
841
- const workDir = input.workDir || config.workflowDir;
853
+ const effectiveWorkDir = workDir || config.workflowDir;
842
854
  try {
843
855
  const res = await fetch(`${config.apiBaseUrl}/api/sync/pull`, {
844
- headers: { "Authorization": `Bearer ${creds.token}` }
856
+ headers: { "Authorization": `Bearer ${token}` }
845
857
  });
846
858
  if (!res.ok) {
847
859
  if (res.status === 404) {
@@ -861,14 +873,14 @@ async function syncPull(input) {
861
873
  if (!localUpdatedAt || cfgItem.updatedAt > localUpdatedAt) {
862
874
  let filePath = "";
863
875
  if (cfgItem.type === "base_rules") {
864
- filePath = join7(workDir, "base-rules.md");
876
+ filePath = join6(effectiveWorkDir, "base-rules.md");
865
877
  } else if (cfgItem.type === "platform") {
866
- filePath = join7(workDir, "platforms", `${cfgItem.name}.md`);
878
+ filePath = join6(effectiveWorkDir, "platforms", `${cfgItem.name}.md`);
867
879
  } else if (cfgItem.type === "account") {
868
- filePath = join7(workDir, "accounts", `${cfgItem.name}.yaml`);
880
+ filePath = join6(effectiveWorkDir, "accounts", `${cfgItem.name}.yaml`);
869
881
  }
870
882
  if (filePath && cfgItem.content) {
871
- await writeFile6(filePath, cfgItem.content, "utf-8");
883
+ await writeFile5(filePath, cfgItem.content, "utf-8");
872
884
  state.configs[stateKey] = {
873
885
  type: cfgItem.type,
874
886
  name: cfgItem.name,
@@ -896,13 +908,13 @@ async function syncPull(input) {
896
908
  enabled: s.enabled !== false
897
909
  };
898
910
  }
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");
911
+ const crawlerDir = join6(effectiveWorkDir, "tools", "crawler");
912
+ await mkdir4(crawlerDir, { recursive: true });
913
+ const crawlerConfigPath = join6(crawlerDir, "config.json");
914
+ await writeFile5(crawlerConfigPath, JSON.stringify({ sources }, null, 2), "utf-8");
903
915
  crawlerCount = sources.length;
904
916
  } catch (e) {
905
- console.error("[sync.pull] Failed to parse crawler sources:", e);
917
+ console.error("[sync] Failed to parse crawler sources:", e);
906
918
  }
907
919
  }
908
920
  }
@@ -931,16 +943,24 @@ async function syncPull(input) {
931
943
  return { success: false, message: `\u62C9\u53D6\u9519\u8BEF: ${err instanceof Error ? err.message : err}` };
932
944
  }
933
945
  }
946
+ async function syncPull(input) {
947
+ if (input.workDir) setWorkDir(input.workDir);
948
+ const creds = await getCredentials();
949
+ if (!creds?.token) {
950
+ return { success: false, message: "\u672A\u6FC0\u6D3B" };
951
+ }
952
+ return doPull(creds.token, input.workDir);
953
+ }
934
954
 
935
955
  // src/tools/workflow.ts
936
956
  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";
957
+ import { cp, mkdir as mkdir5, access as access2, writeFile as writeFile6, chmod as chmod2 } from "fs/promises";
958
+ import { join as join7, dirname } from "path";
939
959
  import { fileURLToPath } from "url";
940
960
  var DEFAULT_API_BASE_URL = "https://app.claudeink.com";
941
961
  var __filename = fileURLToPath(import.meta.url);
942
962
  var __dirname = dirname(__filename);
943
- var WORKFLOW_SRC = join8(__dirname, "..", "workflow");
963
+ var WORKFLOW_SRC = join7(__dirname, "..", "workflow");
944
964
  var workflowInitSchema = z6.object({
945
965
  workDir: z6.string().describe("\u5DE5\u4F5C\u6D41\u521D\u59CB\u5316\u76EE\u6807\u76EE\u5F55\uFF08\u7EDD\u5BF9\u8DEF\u5F84\uFF09"),
946
966
  licenseKey: z6.string().optional().describe("License Key\uFF08\u53EF\u9009\uFF0C\u4F20\u5165\u5219\u81EA\u52A8\u6FC0\u6D3B\uFF09")
@@ -952,8 +972,8 @@ async function workflowInit(input) {
952
972
  setWorkDir(cwd);
953
973
  const items = ["CLAUDE.md", "base-rules.md", "platforms", "accounts", "tools"];
954
974
  for (const item of items) {
955
- const src = join8(WORKFLOW_SRC, item);
956
- const dest = join8(cwd, item);
975
+ const src = join7(WORKFLOW_SRC, item);
976
+ const dest = join7(cwd, item);
957
977
  try {
958
978
  await access2(dest);
959
979
  results.push(`\u23ED\uFE0F ${item} \u5DF2\u5B58\u5728\uFF0C\u8DF3\u8FC7`);
@@ -962,7 +982,7 @@ async function workflowInit(input) {
962
982
  results.push(`\u2705 ${item}`);
963
983
  }
964
984
  }
965
- const claudeinkDir = join8(cwd, ".claudeink");
985
+ const claudeinkDir = join7(cwd, ".claudeink");
966
986
  const dirs = [
967
987
  "sources/articles",
968
988
  "sources/video-transcripts",
@@ -972,34 +992,35 @@ async function workflowInit(input) {
972
992
  "templates"
973
993
  ];
974
994
  for (const dir of dirs) {
975
- await mkdir6(join8(cwd, dir), { recursive: true });
995
+ await mkdir5(join7(cwd, dir), { recursive: true });
976
996
  }
977
- await mkdir6(claudeinkDir, { recursive: true });
997
+ await mkdir5(claudeinkDir, { recursive: true });
978
998
  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");
999
+ const statePath = join7(claudeinkDir, "state.json");
989
1000
  try {
990
1001
  await access2(statePath);
991
1002
  results.push("\u23ED\uFE0F state.json \u5DF2\u5B58\u5728\uFF0C\u8DF3\u8FC7");
992
1003
  } catch {
993
- const emptyState = {
1004
+ const initialState = {
1005
+ credentials: null,
1006
+ config: {
1007
+ apiBaseUrl: DEFAULT_API_BASE_URL,
1008
+ workflowDir: cwd
1009
+ },
994
1010
  sources: {},
995
1011
  drafts: {},
996
1012
  published: {},
997
1013
  configs: {},
998
1014
  crawlerSources: {},
999
1015
  writingMasters: {},
1016
+ tagQueue: {
1017
+ queue: [],
1018
+ failed: []
1019
+ },
1000
1020
  lastSyncAt: ""
1001
1021
  };
1002
- await writeFile7(statePath, JSON.stringify(emptyState, null, 2), "utf-8");
1022
+ await writeFile6(statePath, JSON.stringify(initialState, null, 2), "utf-8");
1023
+ await chmod2(statePath, 384);
1003
1024
  results.push("\u2705 state.json");
1004
1025
  }
1005
1026
  let activated = false;
@@ -1013,17 +1034,15 @@ async function workflowInit(input) {
1013
1034
  });
1014
1035
  const data = await res.json();
1015
1036
  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
- );
1037
+ const state = await readState();
1038
+ state.credentials = {
1039
+ licenseKey: input.licenseKey,
1040
+ token: data.token,
1041
+ userId: data.userId,
1042
+ plan: data.plan,
1043
+ expiresAt: data.expiresAt
1044
+ };
1045
+ await writeState(state);
1027
1046
  results.push(`\u2705 License \u6FC0\u6D3B\u6210\u529F\uFF08\u5957\u9910: ${data.plan}\uFF09`);
1028
1047
  activated = true;
1029
1048
  } else {
@@ -1046,10 +1065,10 @@ async function workflowInit(input) {
1046
1065
  }
1047
1066
  }
1048
1067
  try {
1049
- await access2(join8(cwd, "tools", "crawler", "package.json"));
1068
+ await access2(join7(cwd, "tools", "crawler", "package.json"));
1050
1069
  const { execSync } = await import("child_process");
1051
1070
  execSync("npm install --silent", {
1052
- cwd: join8(cwd, "tools", "crawler"),
1071
+ cwd: join7(cwd, "tools", "crawler"),
1053
1072
  stdio: "pipe"
1054
1073
  });
1055
1074
  results.push("\u2705 \u722C\u866B\u4F9D\u8D56\u5DF2\u5B89\u88C5");
@@ -1076,22 +1095,16 @@ async function workflowInit(input) {
1076
1095
  }
1077
1096
 
1078
1097
  // src/index.ts
1079
- import { readFile as readFile7 } from "fs/promises";
1080
- import { join as join9 } from "path";
1081
1098
  var server = new McpServer({
1082
1099
  name: "ClaudeInk",
1083
- version: "0.6.4"
1100
+ version: "0.7.0"
1084
1101
  });
1085
1102
  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
1103
  const result = await workflowInit(input);
1087
1104
  return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
1088
1105
  });
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);
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) => {
1107
+ const result = await sync(input);
1095
1108
  return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
1096
1109
  });
1097
1110
  server.tool("source.add", "\u6DFB\u52A0\u7D20\u6750\u5230\u672C\u5730\u7D20\u6750\u5E93", sourceAddSchema.shape, async (input) => {
@@ -1132,17 +1145,16 @@ server.tool("analytics.report", "\u83B7\u53D6\u6570\u636E\u5206\u6790\u62A5\u544
1132
1145
  });
1133
1146
  async function main() {
1134
1147
  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}`);
1148
+ const state = await readState();
1149
+ if (state.config.workflowDir) {
1150
+ setWorkDir(state.config.workflowDir);
1151
+ console.error(`[ClaudeInk MCP] workDir restored: ${state.config.workflowDir}`);
1140
1152
  }
1141
1153
  } catch {
1142
1154
  }
1143
1155
  const transport = new StdioServerTransport();
1144
1156
  await server.connect(transport);
1145
- console.error("[ClaudeInk MCP] Server started on stdio (12 tools)");
1157
+ console.error("[ClaudeInk MCP] Server started on stdio (11 tools)");
1146
1158
  }
1147
1159
  main().catch((err) => {
1148
1160
  console.error("[ClaudeInk MCP] Fatal error:", err);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@claudeink/mcp-server",
3
- "version": "0.6.4",
3
+ "version": "0.7.0",
4
4
  "description": "ClaudeInk MCP Server - 自媒体多平台写作系统的本地 MCP 服务,连接 Claude 与云端后台",
5
5
  "mcpName": "io.github.weekdmond/claudeink",
6
6
  "type": "module",