@claudeink/mcp-server 0.6.3 → 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 +257 -232
  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,16 +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
+ if (input.workDir) setWorkDir(input.workDir);
745
746
  const creds = await getCredentials();
746
747
  if (!creds?.token) {
747
748
  return { success: false, message: "\u672A\u6FC0\u6D3B\uFF0C\u8BF7\u5148\u4F7F\u7528 workflow.init \u6FC0\u6D3B License" };
748
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) {
749
770
  const config = await getConfig();
750
771
  const state = await readState();
751
772
  const payload = {
@@ -778,7 +799,7 @@ async function syncPush(input) {
778
799
  platformUrl: p.platformUrl,
779
800
  publishedAt: p.publishedAt
780
801
  })),
781
- configs: Object.entries(state.configs).map(([key, c]) => ({
802
+ configs: Object.entries(state.configs).map(([_key, c]) => ({
782
803
  type: c.type,
783
804
  name: c.name,
784
805
  displayName: c.displayName,
@@ -800,7 +821,7 @@ async function syncPush(input) {
800
821
  method: "POST",
801
822
  headers: {
802
823
  "Content-Type": "application/json",
803
- "Authorization": `Bearer ${creds.token}`
824
+ "Authorization": `Bearer ${token}`
804
825
  },
805
826
  body: JSON.stringify(payload)
806
827
  });
@@ -810,7 +831,7 @@ async function syncPush(input) {
810
831
  return {
811
832
  success: true,
812
833
  message: [
813
- "\u2705 \u540C\u6B65\u5B8C\u6210",
834
+ "\u2705 \u63A8\u9001\u5B8C\u6210",
814
835
  ` \u7D20\u6750: ${payload.sources.length} \u7BC7`,
815
836
  ` \u8349\u7A3F: ${payload.drafts.length} \u7BC7`,
816
837
  ` \u5DF2\u53D1\u5E03: ${payload.published.length} \u7BC7`,
@@ -821,25 +842,18 @@ async function syncPush(input) {
821
842
  data: { accepted: result.accepted || 0 }
822
843
  };
823
844
  } else {
824
- return { success: false, message: `\u540C\u6B65\u5931\u8D25: HTTP ${res.status}` };
845
+ return { success: false, message: `\u63A8\u9001\u5931\u8D25: HTTP ${res.status}` };
825
846
  }
826
847
  } catch (err) {
827
- 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}` };
828
849
  }
829
850
  }
830
- var syncPullSchema = z5.object({
831
- workDir: z5.string().optional().describe("\u5DE5\u4F5C\u76EE\u5F55")
832
- });
833
- async function syncPull(input) {
834
- const creds = await getCredentials();
835
- if (!creds?.token) {
836
- return { success: false, message: "\u672A\u6FC0\u6D3B\uFF0C\u8BF7\u5148\u4F7F\u7528 workflow.init \u6FC0\u6D3B License" };
837
- }
851
+ async function doPull(token, workDir) {
838
852
  const config = await getConfig();
839
- const workDir = input.workDir || config.workflowDir;
853
+ const effectiveWorkDir = workDir || config.workflowDir;
840
854
  try {
841
855
  const res = await fetch(`${config.apiBaseUrl}/api/sync/pull`, {
842
- headers: { "Authorization": `Bearer ${creds.token}` }
856
+ headers: { "Authorization": `Bearer ${token}` }
843
857
  });
844
858
  if (!res.ok) {
845
859
  if (res.status === 404) {
@@ -859,14 +873,14 @@ async function syncPull(input) {
859
873
  if (!localUpdatedAt || cfgItem.updatedAt > localUpdatedAt) {
860
874
  let filePath = "";
861
875
  if (cfgItem.type === "base_rules") {
862
- filePath = join7(workDir, "base-rules.md");
876
+ filePath = join6(effectiveWorkDir, "base-rules.md");
863
877
  } else if (cfgItem.type === "platform") {
864
- filePath = join7(workDir, "platforms", `${cfgItem.name}.md`);
878
+ filePath = join6(effectiveWorkDir, "platforms", `${cfgItem.name}.md`);
865
879
  } else if (cfgItem.type === "account") {
866
- filePath = join7(workDir, "accounts", `${cfgItem.name}.yaml`);
880
+ filePath = join6(effectiveWorkDir, "accounts", `${cfgItem.name}.yaml`);
867
881
  }
868
882
  if (filePath && cfgItem.content) {
869
- await writeFile6(filePath, cfgItem.content, "utf-8");
883
+ await writeFile5(filePath, cfgItem.content, "utf-8");
870
884
  state.configs[stateKey] = {
871
885
  type: cfgItem.type,
872
886
  name: cfgItem.name,
@@ -894,13 +908,13 @@ async function syncPull(input) {
894
908
  enabled: s.enabled !== false
895
909
  };
896
910
  }
897
- const crawlerDir = join7(workDir, "tools", "crawler");
898
- await mkdir5(crawlerDir, { recursive: true });
899
- const crawlerConfigPath = join7(crawlerDir, "config.json");
900
- 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");
901
915
  crawlerCount = sources.length;
902
916
  } catch (e) {
903
- console.error("[sync.pull] Failed to parse crawler sources:", e);
917
+ console.error("[sync] Failed to parse crawler sources:", e);
904
918
  }
905
919
  }
906
920
  }
@@ -929,16 +943,24 @@ async function syncPull(input) {
929
943
  return { success: false, message: `\u62C9\u53D6\u9519\u8BEF: ${err instanceof Error ? err.message : err}` };
930
944
  }
931
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
+ }
932
954
 
933
955
  // src/tools/workflow.ts
934
956
  import { z as z6 } from "zod";
935
- import { cp, mkdir as mkdir6, access as access2, writeFile as writeFile7 } from "fs/promises";
936
- 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";
937
959
  import { fileURLToPath } from "url";
938
960
  var DEFAULT_API_BASE_URL = "https://app.claudeink.com";
939
961
  var __filename = fileURLToPath(import.meta.url);
940
962
  var __dirname = dirname(__filename);
941
- var WORKFLOW_SRC = join8(__dirname, "..", "workflow");
963
+ var WORKFLOW_SRC = join7(__dirname, "..", "workflow");
942
964
  var workflowInitSchema = z6.object({
943
965
  workDir: z6.string().describe("\u5DE5\u4F5C\u6D41\u521D\u59CB\u5316\u76EE\u6807\u76EE\u5F55\uFF08\u7EDD\u5BF9\u8DEF\u5F84\uFF09"),
944
966
  licenseKey: z6.string().optional().describe("License Key\uFF08\u53EF\u9009\uFF0C\u4F20\u5165\u5219\u81EA\u52A8\u6FC0\u6D3B\uFF09")
@@ -950,8 +972,8 @@ async function workflowInit(input) {
950
972
  setWorkDir(cwd);
951
973
  const items = ["CLAUDE.md", "base-rules.md", "platforms", "accounts", "tools"];
952
974
  for (const item of items) {
953
- const src = join8(WORKFLOW_SRC, item);
954
- const dest = join8(cwd, item);
975
+ const src = join7(WORKFLOW_SRC, item);
976
+ const dest = join7(cwd, item);
955
977
  try {
956
978
  await access2(dest);
957
979
  results.push(`\u23ED\uFE0F ${item} \u5DF2\u5B58\u5728\uFF0C\u8DF3\u8FC7`);
@@ -960,7 +982,7 @@ async function workflowInit(input) {
960
982
  results.push(`\u2705 ${item}`);
961
983
  }
962
984
  }
963
- const claudeinkDir = join8(cwd, ".claudeink");
985
+ const claudeinkDir = join7(cwd, ".claudeink");
964
986
  const dirs = [
965
987
  "sources/articles",
966
988
  "sources/video-transcripts",
@@ -970,34 +992,35 @@ async function workflowInit(input) {
970
992
  "templates"
971
993
  ];
972
994
  for (const dir of dirs) {
973
- await mkdir6(join8(cwd, dir), { recursive: true });
995
+ await mkdir5(join7(cwd, dir), { recursive: true });
974
996
  }
975
- await mkdir6(claudeinkDir, { recursive: true });
997
+ await mkdir5(claudeinkDir, { recursive: true });
976
998
  results.push("\u2705 \u8FD0\u884C\u65F6\u76EE\u5F55\u5DF2\u521B\u5EFA");
977
- const configPath = join8(claudeinkDir, "config.json");
978
- const statePath = join8(claudeinkDir, "state.json");
979
- await writeFile7(configPath, JSON.stringify({
980
- apiBaseUrl: DEFAULT_API_BASE_URL,
981
- syncIntervalMs: 3e5,
982
- heartbeatIntervalMs: 3e5,
983
- maxTagQueueBatch: 20,
984
- workflowDir: cwd
985
- }, null, 2), "utf-8");
986
- results.push("\u2705 config.json");
999
+ const statePath = join7(claudeinkDir, "state.json");
987
1000
  try {
988
1001
  await access2(statePath);
989
1002
  results.push("\u23ED\uFE0F state.json \u5DF2\u5B58\u5728\uFF0C\u8DF3\u8FC7");
990
1003
  } catch {
991
- const emptyState = {
1004
+ const initialState = {
1005
+ credentials: null,
1006
+ config: {
1007
+ apiBaseUrl: DEFAULT_API_BASE_URL,
1008
+ workflowDir: cwd
1009
+ },
992
1010
  sources: {},
993
1011
  drafts: {},
994
1012
  published: {},
995
1013
  configs: {},
996
1014
  crawlerSources: {},
997
1015
  writingMasters: {},
1016
+ tagQueue: {
1017
+ queue: [],
1018
+ failed: []
1019
+ },
998
1020
  lastSyncAt: ""
999
1021
  };
1000
- 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);
1001
1024
  results.push("\u2705 state.json");
1002
1025
  }
1003
1026
  let activated = false;
@@ -1011,17 +1034,15 @@ async function workflowInit(input) {
1011
1034
  });
1012
1035
  const data = await res.json();
1013
1036
  if (data.userId) {
1014
- await writeFile7(
1015
- join8(claudeinkDir, "credentials.json"),
1016
- JSON.stringify({
1017
- licenseKey: input.licenseKey,
1018
- token: data.token,
1019
- userId: data.userId,
1020
- plan: data.plan,
1021
- expiresAt: data.expiresAt
1022
- }, null, 2),
1023
- { mode: 384 }
1024
- );
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);
1025
1046
  results.push(`\u2705 License \u6FC0\u6D3B\u6210\u529F\uFF08\u5957\u9910: ${data.plan}\uFF09`);
1026
1047
  activated = true;
1027
1048
  } else {
@@ -1044,10 +1065,10 @@ async function workflowInit(input) {
1044
1065
  }
1045
1066
  }
1046
1067
  try {
1047
- await access2(join8(cwd, "tools", "crawler", "package.json"));
1068
+ await access2(join7(cwd, "tools", "crawler", "package.json"));
1048
1069
  const { execSync } = await import("child_process");
1049
1070
  execSync("npm install --silent", {
1050
- cwd: join8(cwd, "tools", "crawler"),
1071
+ cwd: join7(cwd, "tools", "crawler"),
1051
1072
  stdio: "pipe"
1052
1073
  });
1053
1074
  results.push("\u2705 \u722C\u866B\u4F9D\u8D56\u5DF2\u5B89\u88C5");
@@ -1076,18 +1097,14 @@ async function workflowInit(input) {
1076
1097
  // src/index.ts
1077
1098
  var server = new McpServer({
1078
1099
  name: "ClaudeInk",
1079
- version: "0.6.3"
1100
+ version: "0.7.0"
1080
1101
  });
1081
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) => {
1082
1103
  const result = await workflowInit(input);
1083
1104
  return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
1084
1105
  });
1085
- 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) => {
1086
- const result = await syncPush(input);
1087
- return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
1088
- });
1089
- 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) => {
1090
- 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);
1091
1108
  return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
1092
1109
  });
1093
1110
  server.tool("source.add", "\u6DFB\u52A0\u7D20\u6750\u5230\u672C\u5730\u7D20\u6750\u5E93", sourceAddSchema.shape, async (input) => {
@@ -1127,9 +1144,17 @@ server.tool("analytics.report", "\u83B7\u53D6\u6570\u636E\u5206\u6790\u62A5\u544
1127
1144
  return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
1128
1145
  });
1129
1146
  async function main() {
1147
+ try {
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}`);
1152
+ }
1153
+ } catch {
1154
+ }
1130
1155
  const transport = new StdioServerTransport();
1131
1156
  await server.connect(transport);
1132
- console.error("[ClaudeInk MCP] Server started on stdio (12 tools)");
1157
+ console.error("[ClaudeInk MCP] Server started on stdio (11 tools)");
1133
1158
  }
1134
1159
  main().catch((err) => {
1135
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.3",
3
+ "version": "0.7.0",
4
4
  "description": "ClaudeInk MCP Server - 自媒体多平台写作系统的本地 MCP 服务,连接 Claude 与云端后台",
5
5
  "mcpName": "io.github.weekdmond/claudeink",
6
6
  "type": "module",