@claudeink/mcp-server 0.4.0 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -4,8 +4,10 @@
4
4
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
5
5
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
6
6
 
7
- // src/tools/auth.ts
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
11
 
10
12
  // src/lib/config.ts
11
13
  import { readFile, writeFile, mkdir, chmod, access } from "fs/promises";
@@ -21,18 +23,13 @@ var PATHS = {
21
23
  logs: join(CLAUDEINK_DIR, "logs")
22
24
  };
23
25
  var DEFAULT_CONFIG = {
24
- apiBaseUrl: "https://api.claudeink.app",
26
+ apiBaseUrl: "https://app.claudeink.com",
25
27
  syncIntervalMs: 3e5,
26
28
  // 5 minutes
27
29
  heartbeatIntervalMs: 3e5,
28
30
  maxTagQueueBatch: 20,
29
31
  workflowDir: ""
30
32
  };
31
- var DEFAULT_SYNC_STATE = {
32
- lastSyncAt: null,
33
- configVersion: "0.0.0",
34
- pendingPushCount: 0
35
- };
36
33
  var DEFAULT_TAG_QUEUE = {
37
34
  queue: [],
38
35
  failed: [],
@@ -42,9 +39,6 @@ var DEFAULT_TAG_QUEUE = {
42
39
  avgTagsPerItem: 0
43
40
  }
44
41
  };
45
- var DEFAULT_CRAWL_SCHEDULES = {
46
- schedules: []
47
- };
48
42
  async function ensureDir() {
49
43
  await mkdir(CLAUDEINK_DIR, { recursive: true });
50
44
  await mkdir(PATHS.logs, { recursive: true });
@@ -76,214 +70,19 @@ async function getCredentials() {
76
70
  if (!await fileExists(PATHS.credentials)) return null;
77
71
  return readJson(PATHS.credentials, null);
78
72
  }
79
- async function saveCredentials(creds) {
80
- await writeJson(PATHS.credentials, creds, true);
81
- }
82
73
  async function getConfig() {
83
74
  return readJson(PATHS.config, DEFAULT_CONFIG);
84
75
  }
85
- async function saveConfig(config) {
86
- const current = await getConfig();
87
- await writeJson(PATHS.config, { ...current, ...config });
88
- }
89
- async function getSyncState() {
90
- return readJson(PATHS.syncState, DEFAULT_SYNC_STATE);
91
- }
92
- async function saveSyncState(state) {
93
- const current = await getSyncState();
94
- await writeJson(PATHS.syncState, { ...current, ...state });
95
- }
96
76
  async function getTagQueue() {
97
77
  return readJson(PATHS.tagQueue, DEFAULT_TAG_QUEUE);
98
78
  }
99
79
  async function saveTagQueue(queue) {
100
80
  await writeJson(PATHS.tagQueue, queue);
101
81
  }
102
- async function getCrawlSchedules() {
103
- return readJson(PATHS.crawlSchedules, DEFAULT_CRAWL_SCHEDULES);
104
- }
105
- async function saveCrawlSchedules(schedules) {
106
- await writeJson(PATHS.crawlSchedules, schedules);
107
- }
108
-
109
- // src/lib/api-client.ts
110
- async function request(path, options = {}) {
111
- const config = await getConfig();
112
- const url = `${config.apiBaseUrl}${path}`;
113
- try {
114
- const headers = {
115
- "Content-Type": "application/json",
116
- "User-Agent": "ClaudeInk-MCP/0.1.0"
117
- };
118
- if (options.token) {
119
- headers["Authorization"] = `Bearer ${options.token}`;
120
- } else {
121
- const creds = await getCredentials();
122
- if (creds?.token) {
123
- headers["Authorization"] = `Bearer ${creds.token}`;
124
- }
125
- }
126
- const res = await fetch(url, {
127
- method: options.method || "GET",
128
- headers,
129
- body: options.body ? JSON.stringify(options.body) : void 0
130
- });
131
- const data = await res.json().catch(() => null);
132
- return {
133
- ok: res.ok,
134
- status: res.status,
135
- data: data ?? void 0,
136
- error: res.ok ? void 0 : `HTTP ${res.status}`
137
- };
138
- } catch (err) {
139
- return {
140
- ok: false,
141
- status: 0,
142
- error: err instanceof Error ? err.message : "Unknown error"
143
- };
144
- }
145
- }
146
- async function activateLicense(key) {
147
- return request("/api/auth/activate", {
148
- method: "POST",
149
- body: { key }
150
- });
151
- }
152
- async function fetchLatestConfig(localVersion) {
153
- return request(
154
- `/api/config/latest?version=${localVersion}`
155
- );
156
- }
157
- async function pushSyncBatch(payload) {
158
- return request("/api/sync/batch", {
159
- method: "POST",
160
- body: payload
161
- });
162
- }
163
- async function checkAuthStatus() {
164
- return request(
165
- "/api/auth/status"
166
- );
167
- }
168
-
169
- // src/tools/auth.ts
170
- import { writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
171
- import { join as join2 } from "path";
172
- var activateSchema = z.object({
173
- key: z.string().describe("License key, e.g. WF-XXXX-XXXX-XXXX"),
174
- workflowDir: z.string().describe("\u5DE5\u4F5C\u6D41\u6839\u76EE\u5F55\u8DEF\u5F84")
175
- });
176
- async function authActivate(input) {
177
- const res = await activateLicense(input.key);
178
- if (!res.ok || !res.data) {
179
- return {
180
- success: false,
181
- message: `License key \u9A8C\u8BC1\u5931\u8D25: ${res.error || "\u672A\u77E5\u9519\u8BEF"}`
182
- };
183
- }
184
- await saveCredentials({
185
- licenseKey: input.key,
186
- token: res.data.token,
187
- userId: res.data.userId,
188
- plan: res.data.plan,
189
- expiresAt: res.data.expiresAt
190
- });
191
- await saveConfig({ workflowDir: input.workflowDir });
192
- const configRes = await fetchLatestConfig("0.0.0");
193
- if (configRes.ok && configRes.data) {
194
- for (const [filePath, content] of Object.entries(configRes.data.files)) {
195
- const fullPath = join2(input.workflowDir, filePath);
196
- const dir = fullPath.substring(0, fullPath.lastIndexOf("/"));
197
- await mkdir2(dir, { recursive: true });
198
- await writeFile2(fullPath, content, "utf-8");
199
- }
200
- await saveSyncState({ configVersion: configRes.data.version });
201
- }
202
- return {
203
- success: true,
204
- message: `\u6FC0\u6D3B\u6210\u529F\uFF01\u5957\u9910: ${res.data.plan}\uFF0C\u5230\u671F: ${res.data.expiresAt}`,
205
- data: {
206
- plan: res.data.plan,
207
- expiresAt: res.data.expiresAt,
208
- configVersion: configRes.data?.version || "unknown"
209
- }
210
- };
211
- }
212
- async function authStatus() {
213
- const creds = await getCredentials();
214
- if (!creds) {
215
- return {
216
- success: false,
217
- message: "\u672A\u6FC0\u6D3B\u3002\u8BF7\u5148\u4F7F\u7528 auth.activate \u6FC0\u6D3B license key\u3002"
218
- };
219
- }
220
- const res = await checkAuthStatus();
221
- if (res.ok && res.data) {
222
- return {
223
- success: true,
224
- message: `\u5DF2\u6FC0\u6D3B\u3002\u5957\u9910: ${res.data.plan}\uFF0C\u5230\u671F: ${res.data.expiresAt}\uFF0C\u72B6\u6001: ${res.data.valid ? "\u6709\u6548" : "\u5DF2\u8FC7\u671F"}`,
225
- data: res.data
226
- };
227
- }
228
- const expired = new Date(creds.expiresAt) < /* @__PURE__ */ new Date();
229
- return {
230
- success: true,
231
- message: `\u5DF2\u6FC0\u6D3B\uFF08\u79BB\u7EBF\u6A21\u5F0F\uFF09\u3002\u5957\u9910: ${creds.plan}\uFF0C\u5230\u671F: ${creds.expiresAt}${expired ? "\uFF08\u5DF2\u8FC7\u671F\uFF09" : ""}`,
232
- data: {
233
- plan: creds.plan,
234
- expiresAt: creds.expiresAt,
235
- valid: !expired,
236
- offline: true
237
- }
238
- };
239
- }
240
- async function configSync() {
241
- const syncState = await getSyncState();
242
- const res = await fetchLatestConfig(syncState.configVersion);
243
- if (!res.ok || !res.data) {
244
- return {
245
- success: false,
246
- message: `\u914D\u7F6E\u540C\u6B65\u5931\u8D25: ${res.error || "\u65E0\u6CD5\u8FDE\u63A5\u4E91\u7AEF"}`
247
- };
248
- }
249
- if (res.data.version === syncState.configVersion) {
250
- return {
251
- success: true,
252
- message: `\u914D\u7F6E\u5DF2\u662F\u6700\u65B0\u7248\u672C (${syncState.configVersion})`
253
- };
254
- }
255
- const config = await getConfig();
256
- for (const [filePath, content] of Object.entries(res.data.files)) {
257
- const fullPath = join2(config.workflowDir, filePath);
258
- const dir = fullPath.substring(0, fullPath.lastIndexOf("/"));
259
- await mkdir2(dir, { recursive: true });
260
- await writeFile2(fullPath, content, "utf-8");
261
- }
262
- await saveSyncState({ configVersion: res.data.version });
263
- return {
264
- success: true,
265
- message: `\u914D\u7F6E\u5DF2\u66F4\u65B0: ${syncState.configVersion} \u2192 ${res.data.version}
266
- \u53D8\u66F4: ${res.data.changelog}`,
267
- data: { version: res.data.version, changelog: res.data.changelog }
268
- };
269
- }
270
- async function configVersion() {
271
- const syncState = await getSyncState();
272
- return {
273
- success: true,
274
- message: `\u5F53\u524D\u672C\u5730\u914D\u7F6E\u7248\u672C: ${syncState.configVersion}`,
275
- data: { version: syncState.configVersion }
276
- };
277
- }
278
-
279
- // src/tools/source.ts
280
- import { z as z2 } from "zod";
281
- import { mkdir as mkdir3 } from "fs/promises";
282
- import { join as join4 } from "path";
283
82
 
284
83
  // src/lib/sources.ts
285
- import { readFile as readFile2, writeFile as writeFile3 } from "fs/promises";
286
- import { join as join3, relative } from "path";
84
+ import { readFile as readFile2, writeFile as writeFile2 } from "fs/promises";
85
+ import { join as join2, relative } from "path";
287
86
  import matter from "gray-matter";
288
87
  import { glob } from "glob";
289
88
  async function readSourceFile(filePath) {
@@ -299,7 +98,7 @@ async function readSourceFile(filePath) {
299
98
  }
300
99
  async function writeSourceFile(filePath, meta, content) {
301
100
  const output = matter.stringify(content, meta);
302
- await writeFile3(filePath, output, "utf-8");
101
+ await writeFile2(filePath, output, "utf-8");
303
102
  }
304
103
  async function updateSourceTags(filePath, tags) {
305
104
  const source = await readSourceFile(filePath);
@@ -308,64 +107,6 @@ async function updateSourceTags(filePath, tags) {
308
107
  source.meta.taggedBy = "local-claude";
309
108
  await writeSourceFile(filePath, source.meta, source.content);
310
109
  }
311
- async function listSources(options) {
312
- const config = await getConfig();
313
- const baseDir = options.folder ? join3(config.workflowDir, "sources", options.folder) : join3(config.workflowDir, "sources");
314
- const files = await glob("**/*.md", { cwd: baseDir, absolute: true });
315
- const sources = [];
316
- for (const file of files) {
317
- try {
318
- const source = await readSourceFile(file);
319
- if (options.tagged === true && (!source.meta.tags || source.meta.tags.length === 0)) {
320
- continue;
321
- }
322
- if (options.tagged === false && source.meta.tags && source.meta.tags.length > 0) {
323
- continue;
324
- }
325
- sources.push(source);
326
- } catch {
327
- continue;
328
- }
329
- }
330
- sources.sort(
331
- (a, b) => new Date(b.meta.published || 0).getTime() - new Date(a.meta.published || 0).getTime()
332
- );
333
- if (options.limit && options.limit > 0) {
334
- return sources.slice(0, options.limit);
335
- }
336
- return sources;
337
- }
338
- async function searchSources(options) {
339
- const all = await listSources({ tagged: true });
340
- let results = all;
341
- if (options.tags && options.tags.length > 0) {
342
- results = results.filter(
343
- (s) => options.tags.some((tag) => s.meta.tags?.includes(tag))
344
- );
345
- }
346
- if (options.dateFrom) {
347
- const from = new Date(options.dateFrom).getTime();
348
- results = results.filter(
349
- (s) => new Date(s.meta.published || 0).getTime() >= from
350
- );
351
- }
352
- if (options.dateTo) {
353
- const to = new Date(options.dateTo).getTime();
354
- results = results.filter(
355
- (s) => new Date(s.meta.published || 0).getTime() <= to
356
- );
357
- }
358
- if (options.query) {
359
- const q = options.query.toLowerCase();
360
- results = results.filter(
361
- (s) => s.meta.title.toLowerCase().includes(q) || s.content.toLowerCase().includes(q)
362
- );
363
- }
364
- if (options.limit && options.limit > 0) {
365
- return results.slice(0, options.limit);
366
- }
367
- return results;
368
- }
369
110
  async function addToTagQueue(file, title, source) {
370
111
  const queue = await getTagQueue();
371
112
  if (queue.queue.some((item) => item.file === file)) return;
@@ -410,13 +151,87 @@ async function markTagFailed(file) {
410
151
  await saveTagQueue(queue);
411
152
  }
412
153
 
154
+ // src/lib/state.ts
155
+ import { readFile as readFile3, writeFile as writeFile3, mkdir as mkdir2 } from "fs/promises";
156
+ import { join as join3 } from "path";
157
+ var DEFAULT_STATE = {
158
+ sources: {},
159
+ drafts: {},
160
+ published: {},
161
+ configs: {},
162
+ crawlerSources: {},
163
+ writingMasters: {},
164
+ lastSyncAt: ""
165
+ };
166
+ async function getStatePath() {
167
+ const config = await getConfig();
168
+ const dir = join3(config.workflowDir || process.cwd(), ".claudeink");
169
+ await mkdir2(dir, { recursive: true });
170
+ return join3(dir, "state.json");
171
+ }
172
+ async function readState() {
173
+ try {
174
+ const path = await getStatePath();
175
+ const content = await readFile3(path, "utf-8");
176
+ return { ...DEFAULT_STATE, ...JSON.parse(content) };
177
+ } catch {
178
+ return { ...DEFAULT_STATE };
179
+ }
180
+ }
181
+ async function writeState(state) {
182
+ const path = await getStatePath();
183
+ await writeFile3(path, JSON.stringify(state, null, 2), "utf-8");
184
+ }
185
+ async function updateSource(id, data) {
186
+ const state = await readState();
187
+ state.sources[id] = data;
188
+ await writeState(state);
189
+ }
190
+ async function updateSourceTags2(id, tags) {
191
+ const state = await readState();
192
+ if (state.sources[id]) {
193
+ state.sources[id].tags = tags;
194
+ state.sources[id].updatedAt = (/* @__PURE__ */ new Date()).toISOString();
195
+ }
196
+ await writeState(state);
197
+ }
198
+ async function updateDraft(id, data) {
199
+ const state = await readState();
200
+ state.drafts[id] = data;
201
+ await writeState(state);
202
+ }
203
+ async function moveDraftToPublished(id, data) {
204
+ const state = await readState();
205
+ delete state.drafts[id];
206
+ state.published[id] = data;
207
+ await writeState(state);
208
+ }
209
+ async function updateCrawlerSource(id, data) {
210
+ const state = await readState();
211
+ state.crawlerSources[id] = data;
212
+ await writeState(state);
213
+ }
214
+ async function removeCrawlerSource(id) {
215
+ const state = await readState();
216
+ delete state.crawlerSources[id];
217
+ await writeState(state);
218
+ }
219
+ async function updateLastSyncAt() {
220
+ const state = await readState();
221
+ state.lastSyncAt = (/* @__PURE__ */ new Date()).toISOString();
222
+ await writeState(state);
223
+ }
224
+ async function replaceState(state) {
225
+ await writeState(state);
226
+ }
227
+
413
228
  // src/tools/source.ts
414
- var sourceAddSchema = z2.object({
415
- title: z2.string().describe("\u7D20\u6750\u6807\u9898"),
416
- content: z2.string().describe("\u7D20\u6750\u6B63\u6587"),
417
- url: z2.string().optional().describe("\u6765\u6E90 URL"),
418
- tags: z2.array(z2.string()).optional().describe("\u6807\u7B7E\uFF08\u5982\u4E0D\u63D0\u4F9B\u5219\u52A0\u5165\u6807\u7B7E\u961F\u5217\u7531 Claude \u540E\u7EED\u6253\u6807\u7B7E\uFF09"),
419
- folder: z2.string().optional().describe("\u5B58\u653E\u5B50\u76EE\u5F55\uFF0C\u5982 articles/techcrunch")
229
+ var sourceAddSchema = z.object({
230
+ title: z.string().describe("\u7D20\u6750\u6807\u9898"),
231
+ content: z.string().describe("\u7D20\u6750\u6B63\u6587"),
232
+ url: z.string().optional().describe("\u6765\u6E90 URL"),
233
+ tags: z.array(z.string()).optional().describe("\u6807\u7B7E\uFF08\u5982\u4E0D\u63D0\u4F9B\u5219\u52A0\u5165\u6807\u7B7E\u961F\u5217\u7531 Claude \u540E\u7EED\u6253\u6807\u7B7E\uFF09"),
234
+ folder: z.string().optional().describe("\u5B58\u653E\u5B50\u76EE\u5F55\uFF0C\u5982 articles/techcrunch")
420
235
  });
421
236
  async function sourceAdd(input) {
422
237
  const config = await getConfig();
@@ -429,7 +244,13 @@ async function sourceAdd(input) {
429
244
  const filePath = join4(dir, filename);
430
245
  const meta = {
431
246
  title: input.title,
432
- source: input.url ? new URL(input.url).hostname : "manual",
247
+ source: input.url ? (() => {
248
+ try {
249
+ return new URL(input.url).hostname;
250
+ } catch {
251
+ return input.url;
252
+ }
253
+ })() : "manual",
433
254
  published: date,
434
255
  url: input.url
435
256
  };
@@ -438,6 +259,16 @@ async function sourceAdd(input) {
438
259
  meta.taggedAt = (/* @__PURE__ */ new Date()).toISOString();
439
260
  meta.taggedBy = "local-claude";
440
261
  await writeSourceFile(filePath, meta, input.content);
262
+ await updateSource(slug, {
263
+ title: input.title,
264
+ source: meta.source,
265
+ sourceIcon: "",
266
+ sourceUrl: input.url || "",
267
+ coverUrl: "",
268
+ tags: input.tags,
269
+ publishedAt: date,
270
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
271
+ });
441
272
  return {
442
273
  success: true,
443
274
  message: `\u7D20\u6750\u5DF2\u5165\u5E93: ${filename}\uFF08\u542B ${input.tags.length} \u4E2A\u6807\u7B7E\uFF09`,
@@ -447,6 +278,16 @@ async function sourceAdd(input) {
447
278
  await writeSourceFile(filePath, meta, input.content);
448
279
  const relativePath = `sources/${folder}/${filename}`;
449
280
  await addToTagQueue(relativePath, input.title, "manual");
281
+ await updateSource(slug, {
282
+ title: input.title,
283
+ source: meta.source,
284
+ sourceIcon: "",
285
+ sourceUrl: input.url || "",
286
+ coverUrl: "",
287
+ tags: [],
288
+ publishedAt: date,
289
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
290
+ });
450
291
  return {
451
292
  success: true,
452
293
  message: `\u7D20\u6750\u5DF2\u5165\u5E93: ${filename}\uFF08\u5DF2\u52A0\u5165\u6807\u7B7E\u961F\u5217\uFF0C\u7B49\u5F85 Claude \u6253\u6807\u7B7E\uFF09`,
@@ -454,67 +295,25 @@ async function sourceAdd(input) {
454
295
  };
455
296
  }
456
297
  }
457
- var sourceSearchSchema = z2.object({
458
- query: z2.string().optional().describe("\u5173\u952E\u8BCD\u641C\u7D22"),
459
- tags: z2.array(z2.string()).optional().describe("\u6309\u6807\u7B7E\u7B5B\u9009"),
460
- dateFrom: z2.string().optional().describe("\u8D77\u59CB\u65E5\u671F YYYY-MM-DD"),
461
- dateTo: z2.string().optional().describe("\u622A\u6B62\u65E5\u671F YYYY-MM-DD"),
462
- limit: z2.number().optional().describe("\u8FD4\u56DE\u6570\u91CF\u4E0A\u9650")
463
- });
464
- async function sourceSearch(input) {
465
- const results = await searchSources({
466
- query: input.query,
467
- tags: input.tags,
468
- dateFrom: input.dateFrom,
469
- dateTo: input.dateTo,
470
- limit: input.limit
471
- });
472
- return {
473
- success: true,
474
- message: `\u627E\u5230 ${results.length} \u7BC7\u76F8\u5173\u7D20\u6750`,
475
- data: results.map((s) => ({
476
- file: s.relativePath,
477
- title: s.meta.title,
478
- tags: s.meta.tags,
479
- published: s.meta.published,
480
- source: s.meta.source
481
- }))
482
- };
483
- }
484
- var sourceListSchema = z2.object({
485
- folder: z2.string().optional().describe("\u5B50\u76EE\u5F55"),
486
- tagged: z2.boolean().optional().describe("true=\u5DF2\u6253\u6807\u7B7E, false=\u672A\u6253\u6807\u7B7E, \u4E0D\u4F20=\u5168\u90E8"),
487
- limit: z2.number().optional().describe("\u8FD4\u56DE\u6570\u91CF\u4E0A\u9650")
488
- });
489
- async function sourceList(input) {
490
- const results = await listSources({
491
- folder: input.folder,
492
- tagged: input.tagged,
493
- limit: input.limit
494
- });
495
- return {
496
- success: true,
497
- message: `\u5171 ${results.length} \u7BC7\u7D20\u6750`,
498
- data: results.map((s) => ({
499
- file: s.relativePath,
500
- title: s.meta.title,
501
- tags: s.meta.tags || [],
502
- published: s.meta.published
503
- }))
504
- };
505
- }
506
- var sourceCrawlSchema = z2.object({
507
- sourceId: z2.string().optional().describe("\u6307\u5B9A\u722C\u866B\u6E90\u540D\u79F0\uFF0C\u4E0D\u4F20\u5219\u5168\u91CF")
298
+ var sourceCrawlSchema = z.object({
299
+ sourceId: z.string().optional().describe("\u6307\u5B9A\u722C\u866B\u6E90\u540D\u79F0\uFF0C\u4E0D\u4F20\u5219\u5168\u91CF")
508
300
  });
509
301
  async function sourceCrawl(input) {
510
- const schedules = await getCrawlSchedules();
511
- if (schedules.schedules.length === 0) {
302
+ const config = await getConfig();
303
+ const configPath = join4(config.workflowDir || process.cwd(), "tools", "crawler", "config.json");
304
+ let crawlerConfig;
305
+ try {
306
+ crawlerConfig = JSON.parse(await readFile4(configPath, "utf-8"));
307
+ } catch {
308
+ crawlerConfig = { sources: [] };
309
+ }
310
+ if (crawlerConfig.sources.length === 0) {
512
311
  return {
513
312
  success: false,
514
- message: "\u6682\u65E0\u914D\u7F6E\u722C\u866B\u6E90\u3002\u8BF7\u5148\u7528 source.schedule \u6DFB\u52A0\u3002"
313
+ message: "\u6682\u65E0\u914D\u7F6E\u722C\u866B\u6E90\u3002\u8BF7\u5148\u7528 source.subscribe \u6DFB\u52A0\u3002"
515
314
  };
516
315
  }
517
- const targets = input.sourceId ? schedules.schedules.filter((s) => s.name === input.sourceId) : schedules.schedules.filter((s) => s.enabled);
316
+ const targets = input.sourceId ? crawlerConfig.sources.filter((s) => s.name === input.sourceId) : crawlerConfig.sources.filter((s) => s.enabled !== false);
518
317
  if (targets.length === 0) {
519
318
  return {
520
319
  success: false,
@@ -527,63 +326,9 @@ async function sourceCrawl(input) {
527
326
  data: { triggered: targets.map((t) => t.name) }
528
327
  };
529
328
  }
530
- var sourceScheduleSchema = z2.object({
531
- action: z2.enum(["add", "remove", "list"]).describe("\u64CD\u4F5C\u7C7B\u578B"),
532
- name: z2.string().optional().describe("\u722C\u866B\u6E90\u540D\u79F0"),
533
- url: z2.string().optional().describe("RSS/Sitemap URL"),
534
- type: z2.enum(["rss", "sitemap", "custom"]).optional().describe("\u6E90\u7C7B\u578B"),
535
- cron: z2.string().optional().describe("Cron \u8868\u8FBE\u5F0F"),
536
- targetFolder: z2.string().optional().describe("\u76EE\u6807\u5B58\u653E\u76EE\u5F55")
537
- });
538
- async function sourceSchedule(input) {
539
- const schedules = await getCrawlSchedules();
540
- if (input.action === "list") {
541
- return {
542
- success: true,
543
- message: `\u5171 ${schedules.schedules.length} \u4E2A\u722C\u866B\u6E90`,
544
- data: schedules.schedules
545
- };
546
- }
547
- if (input.action === "add") {
548
- if (!input.name || !input.url || !input.cron) {
549
- return {
550
- success: false,
551
- message: "\u6DFB\u52A0\u722C\u866B\u6E90\u9700\u8981 name, url, cron \u53C2\u6570"
552
- };
553
- }
554
- if (schedules.schedules.some((s) => s.name === input.name)) {
555
- return { success: false, message: `\u722C\u866B\u6E90 ${input.name} \u5DF2\u5B58\u5728` };
556
- }
557
- schedules.schedules.push({
558
- name: input.name,
559
- url: input.url,
560
- type: input.type || "rss",
561
- cron: input.cron,
562
- targetFolder: input.targetFolder || `sources/articles/${input.name}/`,
563
- enabled: true,
564
- lastRun: null
565
- });
566
- await saveCrawlSchedules(schedules);
567
- return {
568
- success: true,
569
- message: `\u722C\u866B\u6E90 ${input.name} \u5DF2\u6DFB\u52A0\uFF0CCron: ${input.cron}`
570
- };
571
- }
572
- if (input.action === "remove") {
573
- if (!input.name) {
574
- return { success: false, message: "\u5220\u9664\u722C\u866B\u6E90\u9700\u8981 name \u53C2\u6570" };
575
- }
576
- schedules.schedules = schedules.schedules.filter(
577
- (s) => s.name !== input.name
578
- );
579
- await saveCrawlSchedules(schedules);
580
- return { success: true, message: `\u722C\u866B\u6E90 ${input.name} \u5DF2\u5220\u9664` };
581
- }
582
- return { success: false, message: "\u672A\u77E5\u64CD\u4F5C" };
583
- }
584
- var sourceTagSchema = z2.object({
585
- mode: z2.enum(["queue", "single"]).describe("queue=\u5904\u7406\u6807\u7B7E\u961F\u5217, single=\u5355\u4E2A\u6587\u4EF6\u6253\u6807\u7B7E"),
586
- file: z2.string().optional().describe("single \u6A21\u5F0F\u9700\u8981\u6307\u5B9A\u6587\u4EF6\u8DEF\u5F84")
329
+ var sourceTagSchema = z.object({
330
+ mode: z.enum(["queue", "single"]).describe("queue=\u5904\u7406\u6807\u7B7E\u961F\u5217, single=\u5355\u4E2A\u6587\u4EF6\u6253\u6807\u7B7E"),
331
+ file: z.string().optional().describe("single \u6A21\u5F0F\u9700\u8981\u6307\u5B9A\u6587\u4EF6\u8DEF\u5F84")
587
332
  });
588
333
  async function sourceTag(input) {
589
334
  if (input.mode === "single") {
@@ -651,23 +396,9 @@ async function sourceTag(input) {
651
396
  }
652
397
  };
653
398
  }
654
- async function sourceTagStatus() {
655
- const queue = await getTagQueue();
656
- return {
657
- success: true,
658
- message: `\u5F85\u5904\u7406: ${queue.queue.length} \u7BC7 | \u5931\u8D25: ${queue.failed.length} \u7BC7 | \u7D2F\u8BA1\u5DF2\u5904\u7406: ${queue.history.totalProcessed} \u7BC7 | \u5E73\u5747\u6807\u7B7E\u6570: ${queue.history.avgTagsPerItem.toFixed(1)}`,
659
- data: {
660
- pending: queue.queue.length,
661
- failed: queue.failed.length,
662
- totalProcessed: queue.history.totalProcessed,
663
- avgTagsPerItem: queue.history.avgTagsPerItem,
664
- lastProcessed: queue.history.lastProcessed
665
- }
666
- };
667
- }
668
- var sourceTagApplySchema = z2.object({
669
- file: z2.string().describe("\u7D20\u6750\u6587\u4EF6\u8DEF\u5F84"),
670
- tags: z2.array(z2.string()).describe("Claude \u751F\u6210\u7684\u6807\u7B7E")
399
+ var sourceTagApplySchema = z.object({
400
+ file: z.string().describe("\u7D20\u6750\u6587\u4EF6\u8DEF\u5F84"),
401
+ tags: z.array(z.string()).describe("Claude \u751F\u6210\u7684\u6807\u7B7E")
671
402
  });
672
403
  async function sourceTagApply(input) {
673
404
  try {
@@ -675,6 +406,7 @@ async function sourceTagApply(input) {
675
406
  const fullPath = input.file.startsWith("/") ? input.file : join4(config.workflowDir, input.file);
676
407
  await updateSourceTags(fullPath, input.tags);
677
408
  await markTagged(input.file, input.tags.length);
409
+ await updateSourceTags2(input.file, input.tags);
678
410
  return {
679
411
  success: true,
680
412
  message: `\u6807\u7B7E\u5DF2\u5199\u5165: ${input.tags.join(", ")}`,
@@ -689,26 +421,92 @@ async function sourceTagApply(input) {
689
421
  }
690
422
  }
691
423
 
424
+ // src/tools/subscribe.ts
425
+ import { z as z2 } from "zod";
426
+ import { readFile as readFile5, writeFile as writeFile4 } from "fs/promises";
427
+ import { join as join5 } from "path";
428
+ var sourceSubscribeSchema = z2.object({
429
+ action: z2.enum(["add", "remove", "list"]).describe("\u64CD\u4F5C\u7C7B\u578B"),
430
+ id: z2.string().optional().describe("\u8BA2\u9605\u6E90 ID"),
431
+ name: z2.string().optional().describe("\u8BA2\u9605\u6E90\u540D\u79F0"),
432
+ url: z2.string().optional().describe("RSS/\u535A\u5BA2 URL"),
433
+ type: z2.enum(["rss", "blog", "sitemap"]).optional().describe("\u6E90\u7C7B\u578B"),
434
+ icon: z2.string().optional().describe("\u6765\u6E90 icon URL")
435
+ });
436
+ async function sourceSubscribe(input) {
437
+ const config = await getConfig();
438
+ const configPath = join5(config.workflowDir || process.cwd(), "tools/crawler/config.json");
439
+ let crawlerConfig;
440
+ try {
441
+ crawlerConfig = JSON.parse(await readFile5(configPath, "utf-8"));
442
+ } catch {
443
+ crawlerConfig = { sources: [] };
444
+ }
445
+ if (input.action === "list") {
446
+ return {
447
+ success: true,
448
+ message: `\u5171 ${crawlerConfig.sources.length} \u4E2A\u8BA2\u9605\u6E90`,
449
+ data: crawlerConfig.sources
450
+ };
451
+ }
452
+ if (input.action === "add") {
453
+ if (!input.id || !input.name || !input.url) {
454
+ return { success: false, message: "\u6DFB\u52A0\u8BA2\u9605\u6E90\u9700\u8981 id, name, url \u53C2\u6570" };
455
+ }
456
+ if (crawlerConfig.sources.some((s) => s.id === input.id)) {
457
+ return { success: false, message: `\u8BA2\u9605\u6E90 ${input.id} \u5DF2\u5B58\u5728` };
458
+ }
459
+ const newSource = {
460
+ id: input.id,
461
+ name: input.name,
462
+ url: input.url,
463
+ type: input.type || "rss",
464
+ icon: input.icon || "",
465
+ enabled: true
466
+ };
467
+ crawlerConfig.sources.push(newSource);
468
+ await writeFile4(configPath, JSON.stringify(crawlerConfig, null, 2));
469
+ await updateCrawlerSource(input.id, {
470
+ name: input.name,
471
+ url: input.url,
472
+ type: input.type || "rss",
473
+ icon: input.icon || "",
474
+ enabled: true
475
+ });
476
+ return { success: true, message: `\u8BA2\u9605\u6E90 ${input.name} \u5DF2\u6DFB\u52A0` };
477
+ }
478
+ if (input.action === "remove") {
479
+ if (!input.id) {
480
+ return { success: false, message: "\u5220\u9664\u8BA2\u9605\u6E90\u9700\u8981 id \u53C2\u6570" };
481
+ }
482
+ crawlerConfig.sources = crawlerConfig.sources.filter((s) => s.id !== input.id);
483
+ await writeFile4(configPath, JSON.stringify(crawlerConfig, null, 2));
484
+ await removeCrawlerSource(input.id);
485
+ return { success: true, message: `\u8BA2\u9605\u6E90 ${input.id} \u5DF2\u5220\u9664` };
486
+ }
487
+ return { success: false, message: "\u672A\u77E5\u64CD\u4F5C" };
488
+ }
489
+
692
490
  // src/tools/draft.ts
693
491
  import { z as z3 } from "zod";
694
492
 
695
493
  // src/lib/drafts.ts
696
- import { readFile as readFile3, writeFile as writeFile5, mkdir as mkdir4, unlink } from "fs/promises";
697
- import { join as join5, basename } from "path";
494
+ import { readFile as readFile6, writeFile as writeFile5, mkdir as mkdir4, unlink } from "fs/promises";
495
+ import { join as join6, basename } from "path";
698
496
  import matter2 from "gray-matter";
699
497
  import { glob as glob2 } from "glob";
700
498
  import { randomUUID } from "crypto";
701
499
  function getDraftDir(account, workflowDir) {
702
500
  if (account.toLowerCase() === "auston") {
703
- return join5(workflowDir, "drafts");
501
+ return join6(workflowDir, "drafts");
704
502
  }
705
- return join5(workflowDir, "accounts", account, "drafts");
503
+ return join6(workflowDir, "accounts", account, "drafts");
706
504
  }
707
505
  function getPublishedDir(account, workflowDir) {
708
506
  if (account.toLowerCase() === "auston") {
709
- return join5(workflowDir, "published");
507
+ return join6(workflowDir, "published");
710
508
  }
711
- return join5(workflowDir, "accounts", account, "published");
509
+ return join6(workflowDir, "accounts", account, "published");
712
510
  }
713
511
  function generateDraftFilename(title) {
714
512
  const date = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
@@ -721,7 +519,7 @@ async function saveDraft(options) {
721
519
  await mkdir4(dir, { recursive: true });
722
520
  const id = randomUUID().slice(0, 8);
723
521
  const filename = generateDraftFilename(options.title);
724
- const filePath = join5(dir, filename);
522
+ const filePath = join6(dir, filename);
725
523
  const meta = {
726
524
  id,
727
525
  account: options.account,
@@ -737,62 +535,8 @@ async function saveDraft(options) {
737
535
  await writeFile5(filePath, output, "utf-8");
738
536
  return { filePath, meta, content: options.content };
739
537
  }
740
- async function listDrafts(options) {
741
- const config = await getConfig();
742
- const drafts = [];
743
- const dirs = [];
744
- if (options?.account) {
745
- dirs.push(getDraftDir(options.account, config.workflowDir));
746
- } else {
747
- dirs.push(join5(config.workflowDir, "drafts"));
748
- const accountGlob = await glob2("accounts/*/drafts", {
749
- cwd: config.workflowDir,
750
- absolute: true
751
- });
752
- dirs.push(...accountGlob);
753
- }
754
- for (const dir of dirs) {
755
- try {
756
- const files = await glob2("*.md", { cwd: dir, absolute: true });
757
- for (const file of files) {
758
- try {
759
- const raw = await readFile3(file, "utf-8");
760
- const { data, content } = matter2(raw);
761
- const meta = data;
762
- if (options?.status && meta.status !== options.status) continue;
763
- drafts.push({ filePath: file, meta, content: content.trim() });
764
- } catch {
765
- continue;
766
- }
767
- }
768
- } catch {
769
- continue;
770
- }
771
- }
772
- drafts.sort(
773
- (a, b) => new Date(b.meta.updatedAt).getTime() - new Date(a.meta.updatedAt).getTime()
774
- );
775
- return drafts;
776
- }
777
- async function updateDraft(options) {
778
- const raw = await readFile3(options.file, "utf-8");
779
- const { data, content } = matter2(raw);
780
- const meta = data;
781
- if (options.title) meta.title = options.title;
782
- if (options.status) meta.status = options.status;
783
- if (options.tags) meta.tags = options.tags;
784
- const newContent = options.content ?? content.trim();
785
- meta.wordCount = newContent.length;
786
- meta.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
787
- const output = matter2.stringify(newContent, meta);
788
- await writeFile5(options.file, output, "utf-8");
789
- return { filePath: options.file, meta, content: newContent };
790
- }
791
- async function deleteDraft(file) {
792
- await unlink(file);
793
- }
794
538
  async function publishDraft(options) {
795
- const raw = await readFile3(options.file, "utf-8");
539
+ const raw = await readFile6(options.file, "utf-8");
796
540
  const { data, content } = matter2(raw);
797
541
  const meta = data;
798
542
  meta.status = "published";
@@ -802,7 +546,7 @@ async function publishDraft(options) {
802
546
  const config = await getConfig();
803
547
  const publishedDir = getPublishedDir(meta.account, config.workflowDir);
804
548
  await mkdir4(publishedDir, { recursive: true });
805
- const newPath = join5(publishedDir, basename(options.file));
549
+ const newPath = join6(publishedDir, basename(options.file));
806
550
  const output = matter2.stringify(content.trim(), meta);
807
551
  await writeFile5(newPath, output, "utf-8");
808
552
  await unlink(options.file);
@@ -827,6 +571,16 @@ async function draftSave(input) {
827
571
  tags: input.tags,
828
572
  status: input.status
829
573
  });
574
+ await updateDraft(draft.meta.id, {
575
+ title: draft.meta.title,
576
+ account: input.account,
577
+ platform: input.platform,
578
+ status: draft.meta.status,
579
+ wordCount: draft.meta.wordCount,
580
+ tags: input.tags || [],
581
+ createdAt: draft.meta.createdAt,
582
+ updatedAt: draft.meta.updatedAt
583
+ });
830
584
  return {
831
585
  success: true,
832
586
  message: `\u8349\u7A3F\u5DF2\u4FDD\u5B58: ${draft.meta.title} (${draft.meta.wordCount} \u5B57)`,
@@ -837,74 +591,22 @@ async function draftSave(input) {
837
591
  }
838
592
  };
839
593
  }
840
- var draftListSchema = z3.object({
841
- account: z3.string().optional().describe("\u6309\u8D26\u53F7\u7B5B\u9009"),
842
- status: z3.enum(["draft", "review", "ready", "published", "archived"]).optional().describe("\u6309\u72B6\u6001\u7B5B\u9009")
843
- });
844
- async function draftList(input) {
845
- const drafts = await listDrafts({
846
- account: input.account,
847
- status: input.status
848
- });
849
- return {
850
- success: true,
851
- message: `\u627E\u5230 ${drafts.length} \u7BC7\u8349\u7A3F`,
852
- data: drafts.map((d) => ({
853
- file: d.filePath,
854
- id: d.meta.id,
855
- account: d.meta.account,
856
- platform: d.meta.platform,
857
- title: d.meta.title,
858
- status: d.meta.status,
859
- wordCount: d.meta.wordCount,
860
- updatedAt: d.meta.updatedAt
861
- }))
862
- };
863
- }
864
- var draftUpdateSchema = z3.object({
865
- file: z3.string().describe("\u8349\u7A3F\u6587\u4EF6\u8DEF\u5F84"),
866
- content: z3.string().optional().describe("\u66F4\u65B0\u7684\u6B63\u6587"),
867
- title: z3.string().optional().describe("\u66F4\u65B0\u7684\u6807\u9898"),
868
- status: z3.enum(["draft", "review", "ready"]).optional().describe("\u66F4\u65B0\u72B6\u6001"),
869
- tags: z3.array(z3.string()).optional().describe("\u66F4\u65B0\u6807\u7B7E")
870
- });
871
- async function draftUpdate(input) {
872
- const draft = await updateDraft({
873
- file: input.file,
874
- content: input.content,
875
- title: input.title,
876
- status: input.status,
877
- tags: input.tags
878
- });
879
- return {
880
- success: true,
881
- message: `\u8349\u7A3F\u5DF2\u66F4\u65B0: ${draft.meta.title}`,
882
- data: {
883
- file: draft.filePath,
884
- status: draft.meta.status,
885
- wordCount: draft.meta.wordCount
886
- }
887
- };
888
- }
889
- var draftDeleteSchema = z3.object({
890
- file: z3.string().describe("\u8349\u7A3F\u6587\u4EF6\u8DEF\u5F84")
891
- });
892
- async function draftDelete(input) {
893
- await deleteDraft(input.file);
894
- return {
895
- success: true,
896
- message: `\u8349\u7A3F\u5DF2\u5220\u9664: ${input.file}`
897
- };
898
- }
899
- var draftPublishSchema = z3.object({
900
- file: z3.string().describe("\u8349\u7A3F\u6587\u4EF6\u8DEF\u5F84"),
901
- platformUrl: z3.string().optional().describe("\u5E73\u53F0\u53D1\u5E03\u94FE\u63A5")
594
+ var draftPublishSchema = z3.object({
595
+ file: z3.string().describe("\u8349\u7A3F\u6587\u4EF6\u8DEF\u5F84"),
596
+ platformUrl: z3.string().optional().describe("\u5E73\u53F0\u53D1\u5E03\u94FE\u63A5")
902
597
  });
903
598
  async function draftPublish(input) {
904
599
  const draft = await publishDraft({
905
600
  file: input.file,
906
601
  platformUrl: input.platformUrl
907
602
  });
603
+ await moveDraftToPublished(draft.meta.id, {
604
+ title: draft.meta.title,
605
+ account: draft.meta.account,
606
+ platform: draft.meta.platform,
607
+ platformUrl: draft.meta.platformUrl || "",
608
+ publishedAt: draft.meta.publishedAt || (/* @__PURE__ */ new Date()).toISOString()
609
+ });
908
610
  return {
909
611
  success: true,
910
612
  message: `\u5DF2\u53D1\u5E03: ${draft.meta.title} \u2192 ${draft.filePath}`,
@@ -918,6 +620,52 @@ async function draftPublish(input) {
918
620
 
919
621
  // src/tools/analytics.ts
920
622
  import { z as z4 } from "zod";
623
+
624
+ // src/lib/api-client.ts
625
+ async function request(path, options = {}) {
626
+ const config = await getConfig();
627
+ const url = `${config.apiBaseUrl}${path}`;
628
+ try {
629
+ const headers = {
630
+ "Content-Type": "application/json",
631
+ "User-Agent": "ClaudeInk-MCP/0.1.0"
632
+ };
633
+ if (options.token) {
634
+ headers["Authorization"] = `Bearer ${options.token}`;
635
+ } else {
636
+ const creds = await getCredentials();
637
+ if (creds?.token) {
638
+ headers["Authorization"] = `Bearer ${creds.token}`;
639
+ }
640
+ }
641
+ const res = await fetch(url, {
642
+ method: options.method || "GET",
643
+ headers,
644
+ body: options.body ? JSON.stringify(options.body) : void 0
645
+ });
646
+ const data = await res.json().catch(() => null);
647
+ return {
648
+ ok: res.ok,
649
+ status: res.status,
650
+ data: data ?? void 0,
651
+ error: res.ok ? void 0 : `HTTP ${res.status}`
652
+ };
653
+ } catch (err) {
654
+ return {
655
+ ok: false,
656
+ status: 0,
657
+ error: err instanceof Error ? err.message : "Unknown error"
658
+ };
659
+ }
660
+ }
661
+ async function pushSyncBatch(payload) {
662
+ return request("/api/sync/batch", {
663
+ method: "POST",
664
+ body: payload
665
+ });
666
+ }
667
+
668
+ // src/tools/analytics.ts
921
669
  var analyticsPushSchema = z4.object({
922
670
  articleId: z4.string().describe("\u6587\u7AE0 ID"),
923
671
  metrics: z4.object({
@@ -938,6 +686,8 @@ async function analyticsPush(input) {
938
686
  sources: [],
939
687
  drafts: [],
940
688
  published: [],
689
+ configs: [],
690
+ crawlerSources: [],
941
691
  analytics: [payload]
942
692
  });
943
693
  if (!res.ok) {
@@ -959,183 +709,220 @@ var analyticsReportSchema = z4.object({
959
709
  async function analyticsReport(input) {
960
710
  return {
961
711
  success: true,
962
- message: "\u6570\u636E\u62A5\u544A\u529F\u80FD\u9700\u8981\u4E91\u7AEF Dashboard \u652F\u6301\uFF0C\u8BF7\u8BBF\u95EE https://app.claudeink.app \u67E5\u770B\u8BE6\u7EC6\u6570\u636E\u5206\u6790\u3002",
712
+ message: "\u6570\u636E\u62A5\u544A\u529F\u80FD\u9700\u8981\u4E91\u7AEF Dashboard \u652F\u6301\uFF0C\u8BF7\u8BBF\u95EE https://app.claudeink.com \u67E5\u770B\u8BE6\u7EC6\u6570\u636E\u5206\u6790\u3002",
963
713
  data: {
964
- dashboardUrl: "https://app.claudeink.app/analytics",
714
+ dashboardUrl: "https://app.claudeink.com/analytics",
965
715
  account: input.account,
966
716
  period: input.period || "30d"
967
717
  }
968
718
  };
969
719
  }
970
720
 
971
- // src/tools/account.ts
721
+ // src/tools/sync.ts
972
722
  import { z as z5 } from "zod";
973
- import { readFile as readFile4, writeFile as writeFile6, mkdir as mkdir5 } from "fs/promises";
974
- import { join as join6 } from "path";
975
- import { glob as glob3 } from "glob";
976
- async function accountList() {
723
+ import { writeFile as writeFile6, mkdir as mkdir5 } from "fs/promises";
724
+ import { join as join7 } from "path";
725
+ var syncPushSchema = z5.object({
726
+ workDir: z5.string().optional().describe("\u5DE5\u4F5C\u76EE\u5F55\uFF08\u9ED8\u8BA4\u4F7F\u7528\u914D\u7F6E\u4E2D\u7684 workflowDir\uFF09")
727
+ });
728
+ async function syncPush(input) {
729
+ const creds = await getCredentials();
730
+ if (!creds?.token) {
731
+ return { success: false, message: "\u672A\u6FC0\u6D3B\uFF0C\u8BF7\u5148\u4F7F\u7528 workflow.init \u6FC0\u6D3B License" };
732
+ }
977
733
  const config = await getConfig();
978
- const yamlFiles = await glob3("accounts/*.yaml", {
979
- cwd: config.workflowDir,
980
- absolute: true
981
- });
982
- const accounts = [];
983
- for (const file of yamlFiles) {
984
- if (file.endsWith("_template.yaml")) continue;
985
- try {
986
- const content = await readFile4(file, "utf-8");
987
- const name = extractYamlField(content, "name");
988
- const platform = extractYamlField(content, "platform");
989
- const desc = extractYamlField(content, "description");
990
- accounts.push({ name, platform, description: desc, file });
991
- } catch {
992
- continue;
734
+ const state = await readState();
735
+ const payload = {
736
+ sources: Object.entries(state.sources).map(([id, s]) => ({
737
+ id,
738
+ title: s.title,
739
+ source: s.source,
740
+ tags: s.tags,
741
+ sourceUrl: s.sourceUrl,
742
+ coverUrl: s.coverUrl,
743
+ sourceIcon: s.sourceIcon,
744
+ createdAt: s.publishedAt
745
+ })),
746
+ drafts: Object.entries(state.drafts).map(([id, d]) => ({
747
+ id,
748
+ account: d.account,
749
+ platform: d.platform,
750
+ title: d.title,
751
+ status: d.status,
752
+ wordCount: d.wordCount,
753
+ tags: d.tags,
754
+ createdAt: d.createdAt,
755
+ updatedAt: d.updatedAt
756
+ })),
757
+ published: Object.entries(state.published).map(([id, p]) => ({
758
+ id,
759
+ account: p.account,
760
+ platform: p.platform,
761
+ title: p.title,
762
+ platformUrl: p.platformUrl,
763
+ publishedAt: p.publishedAt
764
+ })),
765
+ configs: Object.entries(state.configs).map(([key, c]) => ({
766
+ type: c.type,
767
+ name: c.name,
768
+ displayName: c.displayName,
769
+ content: c.content,
770
+ metadata: c.metadata
771
+ })),
772
+ crawlerSources: Object.entries(state.crawlerSources).map(([id, cs]) => ({
773
+ id,
774
+ name: cs.name,
775
+ url: cs.url,
776
+ type: cs.type,
777
+ icon: cs.icon,
778
+ enabled: cs.enabled
779
+ })),
780
+ analytics: []
781
+ };
782
+ try {
783
+ const res = await fetch(`${config.apiBaseUrl}/api/sync/batch`, {
784
+ method: "POST",
785
+ headers: {
786
+ "Content-Type": "application/json",
787
+ "Authorization": `Bearer ${creds.token}`
788
+ },
789
+ body: JSON.stringify(payload)
790
+ });
791
+ const result = await res.json();
792
+ if (res.ok) {
793
+ await updateLastSyncAt();
794
+ return {
795
+ success: true,
796
+ message: [
797
+ "\u2705 \u540C\u6B65\u5B8C\u6210",
798
+ ` \u7D20\u6750: ${payload.sources.length} \u7BC7`,
799
+ ` \u8349\u7A3F: ${payload.drafts.length} \u7BC7`,
800
+ ` \u5DF2\u53D1\u5E03: ${payload.published.length} \u7BC7`,
801
+ ` \u914D\u7F6E: ${payload.configs.length} \u4E2A`,
802
+ ` \u8BA2\u9605\u6E90: ${payload.crawlerSources.length} \u4E2A`,
803
+ ` \u4E91\u7AEF\u63A5\u53D7: ${result.accepted || 0} \u6761`
804
+ ].join("\n"),
805
+ data: { accepted: result.accepted || 0 }
806
+ };
807
+ } else {
808
+ return { success: false, message: `\u540C\u6B65\u5931\u8D25: HTTP ${res.status}` };
993
809
  }
810
+ } catch (err) {
811
+ return { success: false, message: `\u540C\u6B65\u7F51\u7EDC\u9519\u8BEF: ${err instanceof Error ? err.message : err}` };
994
812
  }
995
- return {
996
- success: true,
997
- message: `\u5171 ${accounts.length} \u4E2A\u8D26\u53F7`,
998
- data: accounts
999
- };
1000
813
  }
1001
- var accountSwitchSchema = z5.object({
1002
- name: z5.string().describe("\u8981\u5207\u6362\u5230\u7684\u8D26\u53F7\u540D\u79F0")
814
+ var syncPullSchema = z5.object({
815
+ workDir: z5.string().optional().describe("\u5DE5\u4F5C\u76EE\u5F55")
1003
816
  });
1004
- async function accountSwitch(input) {
817
+ async function syncPull(input) {
818
+ const creds = await getCredentials();
819
+ if (!creds?.token) {
820
+ return { success: false, message: "\u672A\u6FC0\u6D3B\uFF0C\u8BF7\u5148\u4F7F\u7528 workflow.init \u6FC0\u6D3B License" };
821
+ }
1005
822
  const config = await getConfig();
1006
- const yamlPath = join6(config.workflowDir, "accounts", `${input.name}.yaml`);
823
+ const workDir = input.workDir || config.workflowDir;
1007
824
  try {
1008
- const content = await readFile4(yamlPath, "utf-8");
1009
- const platform = extractYamlField(content, "platform");
1010
- let platformRules = "";
1011
- if (platform) {
1012
- try {
1013
- platformRules = await readFile4(
1014
- join6(config.workflowDir, "platforms", `${platform}.md`),
1015
- "utf-8"
1016
- );
1017
- } catch {
1018
- platformRules = "[\u5E73\u53F0\u89C4\u5219\u6587\u4EF6\u4E0D\u5B58\u5728]";
825
+ const res = await fetch(`${config.apiBaseUrl}/api/sync/pull`, {
826
+ headers: { "Authorization": `Bearer ${creds.token}` }
827
+ });
828
+ if (!res.ok) {
829
+ if (res.status === 404) {
830
+ return { success: true, message: "\u4E91\u7AEF\u65E0\u5DF2\u6709\u914D\u7F6E\uFF0C\u4F7F\u7528\u672C\u5730\u9ED8\u8BA4" };
1019
831
  }
832
+ return { success: false, message: `\u62C9\u53D6\u5931\u8D25: HTTP ${res.status}` };
1020
833
  }
1021
- return {
1022
- success: true,
1023
- message: `\u5DF2\u5207\u6362\u5230\u8D26\u53F7: ${input.name} (${platform || "\u672A\u77E5\u5E73\u53F0"})\u3002\u8BF7\u6309\u987A\u5E8F\u52A0\u8F7D\uFF1A
1024
- 1. base-rules.md
1025
- 2. platforms/${platform}.md
1026
- 3. accounts/${input.name}.yaml`,
1027
- data: {
1028
- account: input.name,
1029
- platform,
1030
- configFile: yamlPath,
1031
- platformRulesLoaded: platformRules.length > 0
834
+ const cloudData = await res.json();
835
+ if (!cloudData.configs || cloudData.configs.length === 0) {
836
+ return { success: true, message: "\u4E91\u7AEF\u65E0\u914D\u7F6E\u6570\u636E" };
837
+ }
838
+ let updated = 0;
839
+ const state = await readState();
840
+ for (const cfgItem of cloudData.configs) {
841
+ const stateKey = `${cfgItem.type}:${cfgItem.name}`;
842
+ const localUpdatedAt = state.configs[stateKey]?.updatedAt || "";
843
+ if (!localUpdatedAt || cfgItem.updatedAt > localUpdatedAt) {
844
+ let filePath = "";
845
+ if (cfgItem.type === "base_rules") {
846
+ filePath = join7(workDir, "base-rules.md");
847
+ } else if (cfgItem.type === "platform") {
848
+ filePath = join7(workDir, "platforms", `${cfgItem.name}.md`);
849
+ } else if (cfgItem.type === "account") {
850
+ filePath = join7(workDir, "accounts", `${cfgItem.name}.yaml`);
851
+ }
852
+ if (filePath && cfgItem.content) {
853
+ await writeFile6(filePath, cfgItem.content, "utf-8");
854
+ state.configs[stateKey] = {
855
+ type: cfgItem.type,
856
+ name: cfgItem.name,
857
+ displayName: cfgItem.displayName,
858
+ content: cfgItem.content,
859
+ metadata: cfgItem.metadata || {},
860
+ updatedAt: cfgItem.updatedAt
861
+ };
862
+ updated++;
863
+ }
1032
864
  }
1033
- };
1034
- } catch {
865
+ }
866
+ let crawlerCount = 0;
867
+ state.crawlerSources = {};
868
+ for (const cfg of cloudData.configs || []) {
869
+ if (cfg.type === "crawler" && cfg.content) {
870
+ try {
871
+ const sources = JSON.parse(cfg.content);
872
+ for (const s of sources) {
873
+ state.crawlerSources[s.id] = {
874
+ name: s.name,
875
+ url: s.url,
876
+ type: s.type || "rss",
877
+ icon: s.icon || "",
878
+ enabled: s.enabled !== false
879
+ };
880
+ }
881
+ const crawlerDir = join7(workDir, "tools", "crawler");
882
+ await mkdir5(crawlerDir, { recursive: true });
883
+ const crawlerConfigPath = join7(crawlerDir, "config.json");
884
+ await writeFile6(crawlerConfigPath, JSON.stringify({ sources }, null, 2), "utf-8");
885
+ crawlerCount = sources.length;
886
+ } catch (e) {
887
+ console.error("[sync.pull] Failed to parse crawler sources:", e);
888
+ }
889
+ }
890
+ }
891
+ if (cloudData.writingMasters && Object.keys(cloudData.writingMasters).length > 0) {
892
+ state.writingMasters = state.writingMasters || {};
893
+ for (const [accountName, master] of Object.entries(cloudData.writingMasters)) {
894
+ state.writingMasters[accountName] = {
895
+ id: master.id,
896
+ name: master.name,
897
+ stylePrompt: master.stylePrompt
898
+ };
899
+ }
900
+ }
901
+ await replaceState(state);
902
+ const masterCount = cloudData.writingMasters ? Object.keys(cloudData.writingMasters).length : 0;
1035
903
  return {
1036
- success: false,
1037
- message: `\u8D26\u53F7 ${input.name} \u4E0D\u5B58\u5728\u3002\u8BF7\u68C0\u67E5 accounts/ \u76EE\u5F55\u3002`
904
+ success: true,
905
+ message: updated > 0 || masterCount > 0 || crawlerCount > 0 ? [
906
+ `\u2705 \u4ECE\u4E91\u7AEF\u540C\u6B65\u4E86 ${updated} \u4E2A\u914D\u7F6E\u6587\u4EF6`,
907
+ crawlerCount > 0 ? ` \u8BA2\u9605\u6E90: ${crawlerCount} \u4E2A` : "",
908
+ masterCount > 0 ? ` \u5199\u4F5C\u5927\u5E08: ${masterCount} \u4E2A` : ""
909
+ ].filter(Boolean).join("\n") : "\u672C\u5730\u914D\u7F6E\u5DF2\u662F\u6700\u65B0\uFF0C\u65E0\u9700\u66F4\u65B0",
910
+ data: { updated, crawlerSources: crawlerCount, writingMasters: masterCount }
1038
911
  };
912
+ } catch (err) {
913
+ return { success: false, message: `\u62C9\u53D6\u9519\u8BEF: ${err instanceof Error ? err.message : err}` };
1039
914
  }
1040
915
  }
1041
- var accountCreateSchema = z5.object({
1042
- name: z5.string().describe("\u8D26\u53F7\u540D\u79F0\uFF08\u82F1\u6587\u5C0F\u5199\uFF0C\u7528\u4F5C\u6587\u4EF6\u540D\uFF09"),
1043
- platform: z5.string().describe("\u5E73\u53F0\u540D\u79F0\uFF0C\u5982 wechat, xiaohongshu"),
1044
- displayName: z5.string().optional().describe("\u663E\u793A\u540D\u79F0"),
1045
- domains: z5.array(z5.string()).describe("\u5185\u5BB9\u9886\u57DF"),
1046
- description: z5.string().optional().describe("\u8D26\u53F7\u5B9A\u4F4D\u63CF\u8FF0")
1047
- });
1048
- async function accountCreate(input) {
1049
- const config = await getConfig();
1050
- const yamlPath = join6(config.workflowDir, "accounts", `${input.name}.yaml`);
1051
- try {
1052
- await readFile4(yamlPath);
1053
- return { success: false, message: `\u8D26\u53F7 ${input.name} \u5DF2\u5B58\u5728` };
1054
- } catch {
1055
- }
1056
- let template = "";
1057
- try {
1058
- template = await readFile4(
1059
- join6(config.workflowDir, "accounts", "_template.yaml"),
1060
- "utf-8"
1061
- );
1062
- } catch {
1063
- template = defaultTemplate();
1064
- }
1065
- const yamlContent = template.replace(/name:\s*""/, `name: "${input.displayName || input.name}"`).replace(/platform:\s*""/, `platform: "${input.platform}"`).replace(/description:\s*""/, `description: "${input.description || ""}"`).replace(
1066
- /domains:\s*\[\]/,
1067
- `domains:
1068
- ${input.domains.map((d) => ` - "${d}"`).join("\n")}`
1069
- );
1070
- await writeFile6(yamlPath, yamlContent, "utf-8");
1071
- const accountDir = join6(config.workflowDir, "accounts", input.name);
1072
- await mkdir5(join6(accountDir, "drafts"), { recursive: true });
1073
- await mkdir5(join6(accountDir, "published"), { recursive: true });
1074
- await mkdir5(join6(accountDir, "assets"), { recursive: true });
1075
- return {
1076
- success: true,
1077
- message: `\u8D26\u53F7 ${input.name} \u5DF2\u521B\u5EFA
1078
- \u914D\u7F6E: accounts/${input.name}.yaml
1079
- \u76EE\u5F55: accounts/${input.name}/drafts/, published/, assets/`,
1080
- data: {
1081
- config: yamlPath,
1082
- directories: [
1083
- `accounts/${input.name}/drafts/`,
1084
- `accounts/${input.name}/published/`,
1085
- `accounts/${input.name}/assets/`
1086
- ]
1087
- }
1088
- };
1089
- }
1090
- function extractYamlField(yaml, field) {
1091
- const regex = new RegExp(`^${field}:\\s*["']?([^"'\\n]+)["']?`, "m");
1092
- const match = yaml.match(regex);
1093
- return match ? match[1].trim() : "";
1094
- }
1095
- function defaultTemplate() {
1096
- return `# ClaudeInk \u8D26\u53F7\u914D\u7F6E
1097
- name: ""
1098
- platform: ""
1099
- description: ""
1100
-
1101
- domains: []
1102
-
1103
- audience:
1104
- age_range: ""
1105
- interests: []
1106
-
1107
- style:
1108
- tone: "\u5E73\u5B9E"
1109
- voice: "\u53E3\u8BED\u5316"
1110
- formality: "medium"
1111
- emotion: "\u9002\u5EA6"
1112
- humor: "\u5076\u5C14"
1113
- language: "zh-CN"
1114
-
1115
- persona: ""
1116
-
1117
- content_rules:
1118
- min_words: null
1119
- max_words: null
1120
- paragraph_style: null
1121
-
1122
- templates: []
1123
-
1124
- paths:
1125
- drafts: null
1126
- published: null
1127
- assets: null
1128
- `;
1129
- }
1130
916
 
1131
917
  // src/tools/workflow.ts
1132
918
  import { z as z6 } from "zod";
1133
- import { cp, mkdir as mkdir6, access as access2, readFile as readFile5 } from "fs/promises";
1134
- import { join as join7, dirname as dirname2 } from "path";
919
+ import { cp, mkdir as mkdir6, access as access2, writeFile as writeFile7 } from "fs/promises";
920
+ import { join as join8, dirname } from "path";
1135
921
  import { fileURLToPath } from "url";
922
+ var DEFAULT_API_BASE_URL = "https://app.claudeink.com";
1136
923
  var __filename = fileURLToPath(import.meta.url);
1137
- var __dirname = dirname2(__filename);
1138
- var WORKFLOW_SRC = join7(__dirname, "..", "workflow");
924
+ var __dirname = dirname(__filename);
925
+ var WORKFLOW_SRC = join8(__dirname, "..", "workflow");
1139
926
  var workflowInitSchema = z6.object({
1140
927
  workDir: z6.string().describe("\u5DE5\u4F5C\u6D41\u521D\u59CB\u5316\u76EE\u6807\u76EE\u5F55\uFF08\u7EDD\u5BF9\u8DEF\u5F84\uFF09"),
1141
928
  licenseKey: z6.string().optional().describe("License Key\uFF08\u53EF\u9009\uFF0C\u4F20\u5165\u5219\u81EA\u52A8\u6FC0\u6D3B\uFF09")
@@ -1146,8 +933,8 @@ async function workflowInit(input) {
1146
933
  try {
1147
934
  const items = ["CLAUDE.md", "base-rules.md", "platforms", "accounts", "tools"];
1148
935
  for (const item of items) {
1149
- const src = join7(WORKFLOW_SRC, item);
1150
- const dest = join7(cwd, item);
936
+ const src = join8(WORKFLOW_SRC, item);
937
+ const dest = join8(cwd, item);
1151
938
  try {
1152
939
  await access2(dest);
1153
940
  results.push(`\u23ED\uFE0F ${item} \u5DF2\u5B58\u5728\uFF0C\u8DF3\u8FC7`);
@@ -1166,25 +953,38 @@ async function workflowInit(input) {
1166
953
  ".claudeink"
1167
954
  ];
1168
955
  for (const dir of dirs) {
1169
- await mkdir6(join7(cwd, dir), { recursive: true });
956
+ await mkdir6(join8(cwd, dir), { recursive: true });
1170
957
  }
1171
- results.push("\u2705 \u8FD0\u884C\u65F6\u76EE\u5F55\u5DF2\u521B\u5EFA\uFF08sources/, templates/, .claudeink/\uFF09");
958
+ results.push("\u2705 \u8FD0\u884C\u65F6\u76EE\u5F55\u5DF2\u521B\u5EFA");
959
+ const emptyState = {
960
+ sources: {},
961
+ drafts: {},
962
+ published: {},
963
+ configs: {},
964
+ crawlerSources: {},
965
+ writingMasters: {},
966
+ lastSyncAt: ""
967
+ };
968
+ await replaceState(emptyState);
969
+ results.push("\u2705 \u672C\u5730\u72B6\u6001\u8868\u5DF2\u521D\u59CB\u5316");
970
+ let activated = false;
1172
971
  if (input.licenseKey) {
1173
972
  try {
1174
- const res = await fetch("https://app.claudeink.com/api/auth/activate", {
973
+ const activateUrl = `${DEFAULT_API_BASE_URL}/api/auth/activate`;
974
+ const res = await fetch(activateUrl, {
1175
975
  method: "POST",
1176
976
  headers: { "Content-Type": "application/json" },
1177
977
  body: JSON.stringify({ key: input.licenseKey })
1178
978
  });
1179
979
  const data = await res.json();
1180
980
  if (data.userId) {
1181
- const { writeFile: writeFile7 } = await import("fs/promises");
1182
981
  await writeFile7(
1183
- join7(cwd, ".claudeink", "credentials.json"),
982
+ join8(cwd, ".claudeink", "credentials.json"),
1184
983
  JSON.stringify(data, null, 2),
1185
984
  { mode: 384 }
1186
985
  );
1187
- results.push(`\u2705 License \u6FC0\u6D3B\u6210\u529F\uFF08\u5957\u9910: ${data.plan}\uFF0C\u5230\u671F: ${data.expiresAt}\uFF09`);
986
+ results.push(`\u2705 License \u6FC0\u6D3B\u6210\u529F\uFF08\u5957\u9910: ${data.plan}\uFF09`);
987
+ activated = true;
1188
988
  } else {
1189
989
  results.push(`\u26A0\uFE0F License \u6FC0\u6D3B\u5931\u8D25: ${JSON.stringify(data)}`);
1190
990
  }
@@ -1192,6 +992,28 @@ async function workflowInit(input) {
1192
992
  results.push(`\u26A0\uFE0F \u6FC0\u6D3B\u7F51\u7EDC\u9519\u8BEF: ${err instanceof Error ? err.message : err}`);
1193
993
  }
1194
994
  }
995
+ if (activated) {
996
+ try {
997
+ const pullResult = await syncPull({ workDir: cwd });
998
+ if (pullResult.success && pullResult.data && pullResult.data.updated > 0) {
999
+ results.push("\u2705 \u5DF2\u4ECE\u4E91\u7AEF\u540C\u6B65\u914D\u7F6E\uFF08\u8DE8\u8BBE\u5907\u6062\u590D\uFF09");
1000
+ } else {
1001
+ results.push("\u2139\uFE0F \u4E91\u7AEF\u65E0\u5DF2\u6709\u914D\u7F6E\uFF0C\u4F7F\u7528\u9ED8\u8BA4\u6A21\u677F");
1002
+ }
1003
+ } catch {
1004
+ results.push("\u2139\uFE0F \u4E91\u7AEF\u65E0\u5DF2\u6709\u914D\u7F6E\uFF0C\u4F7F\u7528\u9ED8\u8BA4\u6A21\u677F");
1005
+ }
1006
+ }
1007
+ try {
1008
+ await access2(join8(cwd, "tools", "crawler", "package.json"));
1009
+ const { execSync } = await import("child_process");
1010
+ execSync("npm install --silent", {
1011
+ cwd: join8(cwd, "tools", "crawler"),
1012
+ stdio: "pipe"
1013
+ });
1014
+ results.push("\u2705 \u722C\u866B\u4F9D\u8D56\u5DF2\u5B89\u88C5");
1015
+ } catch {
1016
+ }
1195
1017
  return {
1196
1018
  success: true,
1197
1019
  message: [
@@ -1199,14 +1021,6 @@ async function workflowInit(input) {
1199
1021
  "",
1200
1022
  ...results,
1201
1023
  "",
1202
- "\u{1F4C2} \u5DF2\u91CA\u653E\u6587\u4EF6\uFF1A",
1203
- "\u251C\u2500\u2500 CLAUDE.md \uFF08\u7CFB\u7EDF\u7D22\u5F15\uFF0C\u8BF7\u5148\u8BFB\u53D6\uFF09",
1204
- "\u251C\u2500\u2500 base-rules.md \uFF08\u901A\u7528\u5199\u4F5C\u5E95\u5EA7\uFF09",
1205
- "\u251C\u2500\u2500 platforms/ \uFF085 \u4E2A\u5E73\u53F0\u89C4\u5219\uFF09",
1206
- "\u251C\u2500\u2500 accounts/ \uFF08\u8D26\u53F7\u914D\u7F6E\u6A21\u677F\uFF09",
1207
- "\u251C\u2500\u2500 sources/ \uFF08\u5171\u4EAB\u7D20\u6750\u5E93\uFF09",
1208
- "\u2514\u2500\u2500 tools/ \uFF08\u722C\u866B\u7B49\u5DE5\u5177\uFF09",
1209
- "",
1210
1024
  "\u{1F3AF} \u4E0B\u4E00\u6B65\uFF1A",
1211
1025
  "1. \u8BFB\u53D6 CLAUDE.md \u4E86\u89E3\u4E09\u5C42\u914D\u7F6E\u67B6\u6784",
1212
1026
  "2. \u4F7F\u7528 /\u65B0\u5EFA\u8D26\u53F7 \u521B\u5EFA\u7B2C\u4E00\u4E2A\u81EA\u5A92\u4F53\u8D26\u53F7",
@@ -1220,323 +1034,48 @@ async function workflowInit(input) {
1220
1034
  };
1221
1035
  }
1222
1036
  }
1223
- async function workflowStatus() {
1224
- const cwd = process.cwd();
1225
- const checks = [];
1226
- const requiredFiles = [
1227
- { path: "CLAUDE.md", label: "\u7CFB\u7EDF\u7D22\u5F15" },
1228
- { path: "base-rules.md", label: "\u901A\u7528\u5199\u4F5C\u5E95\u5EA7" },
1229
- { path: "platforms/wechat.md", label: "\u5FAE\u4FE1\u516C\u4F17\u53F7\u89C4\u5219" },
1230
- { path: "accounts/_template.yaml", label: "\u8D26\u53F7\u6A21\u677F" }
1231
- ];
1232
- let allOk = true;
1233
- for (const file of requiredFiles) {
1234
- try {
1235
- await access2(join7(cwd, file.path));
1236
- checks.push(`\u2705 ${file.label}\uFF08${file.path}\uFF09`);
1237
- } catch {
1238
- checks.push(`\u274C ${file.label}\uFF08${file.path}\uFF09\u2014 \u7F3A\u5931`);
1239
- allOk = false;
1240
- }
1241
- }
1242
- try {
1243
- const creds = await readFile5(join7(cwd, ".claudeink/credentials.json"), "utf-8");
1244
- const data = JSON.parse(creds);
1245
- if (data.userId) {
1246
- checks.push(`\u2705 License \u5DF2\u6FC0\u6D3B\uFF08\u5957\u9910: ${data.plan}\uFF09`);
1247
- } else {
1248
- checks.push("\u26A0\uFE0F \u51ED\u8BC1\u6587\u4EF6\u5B58\u5728\u4F46\u672A\u6FC0\u6D3B");
1249
- }
1250
- } catch {
1251
- checks.push("\u274C \u672A\u627E\u5230\u51ED\u8BC1\u6587\u4EF6\uFF08.claudeink/credentials.json\uFF09");
1252
- allOk = false;
1253
- }
1254
- try {
1255
- const { glob: glob5 } = await import("glob");
1256
- const yamlFiles = await glob5("accounts/*.yaml", { cwd, ignore: "accounts/_template.yaml" });
1257
- if (yamlFiles.length > 0) {
1258
- checks.push(`\u2705 ${yamlFiles.length} \u4E2A\u8D26\u53F7\u914D\u7F6E`);
1259
- } else {
1260
- checks.push("\u2139\uFE0F \u6682\u65E0\u8D26\u53F7\uFF0C\u4F7F\u7528 /\u65B0\u5EFA\u8D26\u53F7 \u521B\u5EFA");
1261
- }
1262
- } catch {
1263
- checks.push("\u2139\uFE0F \u6682\u65E0\u8D26\u53F7");
1264
- }
1265
- return {
1266
- success: true,
1267
- message: [
1268
- allOk ? "\u2705 \u5DE5\u4F5C\u6D41\u72B6\u6001\uFF1A\u5C31\u7EEA" : "\u26A0\uFE0F \u5DE5\u4F5C\u6D41\u72B6\u6001\uFF1A\u9700\u8981\u521D\u59CB\u5316",
1269
- "",
1270
- ...checks,
1271
- "",
1272
- allOk ? "" : "\u8BF7\u8C03\u7528 workflow.init \u5B8C\u6210\u521D\u59CB\u5316\u3002"
1273
- ].join("\n")
1274
- };
1275
- }
1276
-
1277
- // src/tools/sync.ts
1278
- import { z as z7 } from "zod";
1279
- import { readFile as readFile6, stat as stat2 } from "fs/promises";
1280
- import { join as join8, basename as basename2, extname as extname2 } from "path";
1281
- import matter3 from "gray-matter";
1282
- import { glob as glob4 } from "glob";
1283
- var syncPushSchema = z7.object({
1284
- workDir: z7.string().optional().describe("\u5DE5\u4F5C\u76EE\u5F55\uFF08\u9ED8\u8BA4\u4F7F\u7528\u914D\u7F6E\u4E2D\u7684 workflowDir\uFF09")
1285
- });
1286
- async function syncPush(input) {
1287
- const config = await getConfig();
1288
- const creds = await getCredentials();
1289
- if (!creds?.token) {
1290
- return { success: false, message: "\u672A\u6FC0\u6D3B\uFF0C\u8BF7\u5148\u4F7F\u7528 auth.activate \u6FC0\u6D3B License" };
1291
- }
1292
- const workDir = input.workDir || config.workflowDir;
1293
- if (!workDir) {
1294
- return { success: false, message: "\u672A\u8BBE\u7F6E\u5DE5\u4F5C\u76EE\u5F55\uFF0C\u8BF7\u5728 config.json \u4E2D\u914D\u7F6E workflowDir" };
1295
- }
1296
- const sources = [];
1297
- const drafts = [];
1298
- const published = [];
1299
- try {
1300
- const sourceFiles = await glob4("sources/**/*.md", { cwd: workDir });
1301
- for (const file of sourceFiles) {
1302
- try {
1303
- const raw = await readFile6(join8(workDir, file), "utf-8");
1304
- const { data } = matter3(raw);
1305
- const id = basename2(file, extname2(file));
1306
- sources.push({
1307
- id,
1308
- title: data.title || id,
1309
- source: data.source || "unknown",
1310
- tags: Array.isArray(data.tags) ? data.tags : [],
1311
- sourceUrl: data.url || null,
1312
- createdAt: data.published || (/* @__PURE__ */ new Date()).toISOString()
1313
- });
1314
- } catch {
1315
- }
1316
- }
1317
- } catch {
1318
- }
1319
- const accounts = {};
1320
- try {
1321
- const yamlFiles = await glob4("accounts/*.yaml", { cwd: workDir, ignore: "accounts/_template.yaml" });
1322
- for (const file of yamlFiles) {
1323
- try {
1324
- const content = await readFile6(join8(workDir, file), "utf-8");
1325
- const nameMatch = content.match(/^name:\s*"?([^"\n]+)"?/m);
1326
- const idMatch = content.match(/^id:\s*"?([^"\n]+)"?/m);
1327
- const platformMatch = content.match(/^platform:\s*"?([^"\n]+)"?/m);
1328
- const draftsMatch = content.match(/drafts:\s*"?([^"\n]+)"?/m);
1329
- const publishedMatch = content.match(/published:\s*"?([^"\n]+)"?/m);
1330
- if (idMatch && nameMatch) {
1331
- const id = idMatch[1].trim();
1332
- const name = nameMatch[1].trim();
1333
- const platform = platformMatch?.[1]?.trim() || "unknown";
1334
- const dp = (draftsMatch?.[1] || `accounts/${id}/drafts/`).replace("{id}", id).trim();
1335
- const pp = (publishedMatch?.[1] || `accounts/${id}/published/`).replace("{id}", id).trim();
1336
- accounts[id] = { name, platform, draftsPath: dp, publishedPath: pp };
1337
- }
1338
- } catch {
1339
- }
1340
- }
1341
- } catch {
1342
- }
1343
- for (const [accId, acc] of Object.entries(accounts)) {
1344
- try {
1345
- const draftFiles = await glob4("**/*.md", { cwd: join8(workDir, acc.draftsPath) });
1346
- for (const file of draftFiles) {
1347
- try {
1348
- const fullPath = join8(workDir, acc.draftsPath, file);
1349
- const raw = await readFile6(fullPath, "utf-8");
1350
- const { data, content } = matter3(raw);
1351
- const fileStat = await stat2(fullPath);
1352
- const id = basename2(file, extname2(file));
1353
- drafts.push({
1354
- id,
1355
- account: acc.name,
1356
- platform: acc.platform,
1357
- title: data.title || id,
1358
- status: data.status || "draft",
1359
- wordCount: content.trim().length,
1360
- tags: Array.isArray(data.tags) ? data.tags : [],
1361
- createdAt: data.created || fileStat.birthtime.toISOString(),
1362
- updatedAt: fileStat.mtime.toISOString()
1363
- });
1364
- } catch {
1365
- }
1366
- }
1367
- } catch {
1368
- }
1369
- }
1370
- for (const [accId, acc] of Object.entries(accounts)) {
1371
- try {
1372
- const pubFiles = await glob4("**/*.md", { cwd: join8(workDir, acc.publishedPath) });
1373
- for (const file of pubFiles) {
1374
- try {
1375
- const raw = await readFile6(join8(workDir, acc.publishedPath, file), "utf-8");
1376
- const { data } = matter3(raw);
1377
- const id = basename2(file, extname2(file));
1378
- published.push({
1379
- id,
1380
- account: acc.name,
1381
- platform: acc.platform,
1382
- title: data.title || id,
1383
- platformUrl: data.url || data.platform_url || null,
1384
- publishedAt: data.published_at || data.publishedAt || (/* @__PURE__ */ new Date()).toISOString()
1385
- });
1386
- } catch {
1387
- }
1388
- }
1389
- } catch {
1390
- }
1391
- }
1392
- const configs = [];
1393
- try {
1394
- const baseRules = await readFile6(join8(workDir, "base-rules.md"), "utf-8");
1395
- configs.push({ type: "base_rules", name: "base", displayName: "\u901A\u7528\u5E95\u5EA7", content: baseRules });
1396
- } catch {
1397
- }
1398
- try {
1399
- const platformFiles = await glob4("platforms/*.md", { cwd: workDir });
1400
- for (const file of platformFiles) {
1401
- const content = await readFile6(join8(workDir, file), "utf-8");
1402
- const name = basename2(file, ".md");
1403
- const displayNames = {
1404
- wechat: "\u5FAE\u4FE1\u516C\u4F17\u53F7",
1405
- xiaohongshu: "\u5C0F\u7EA2\u4E66",
1406
- "x-twitter": "X (Twitter)",
1407
- toutiao: "\u5934\u6761\u53F7",
1408
- blog: "\u4E2A\u4EBA\u535A\u5BA2"
1409
- };
1410
- configs.push({ type: "platform", name, displayName: displayNames[name] || name, content });
1411
- }
1412
- } catch {
1413
- }
1414
- for (const [accId, acc] of Object.entries(accounts)) {
1415
- try {
1416
- const content = await readFile6(join8(workDir, `accounts/${accId}.yaml`), "utf-8");
1417
- configs.push({
1418
- type: "account",
1419
- name: accId,
1420
- displayName: acc.name,
1421
- content,
1422
- metadata: { platform: acc.platform }
1423
- });
1424
- } catch {
1425
- }
1426
- }
1427
- const payload = { sources, drafts, published, analytics: [], configs };
1428
- try {
1429
- const res = await fetch(`${config.apiBaseUrl}/api/sync/batch`, {
1430
- method: "POST",
1431
- headers: {
1432
- "Content-Type": "application/json",
1433
- "Authorization": `Bearer ${creds.token}`
1434
- },
1435
- body: JSON.stringify(payload)
1436
- });
1437
- const result = await res.json();
1438
- if (res.ok) {
1439
- return {
1440
- success: true,
1441
- message: [
1442
- `\u2705 \u540C\u6B65\u5B8C\u6210`,
1443
- ` \u7D20\u6750: ${sources.length} \u7BC7`,
1444
- ` \u8349\u7A3F: ${drafts.length} \u7BC7`,
1445
- ` \u5DF2\u53D1\u5E03: ${published.length} \u7BC7`,
1446
- ` \u914D\u7F6E: ${configs.length} \u4E2A\uFF08\u901A\u7528\u5E95\u5EA7 + \u5E73\u53F0\u89C4\u5219 + \u8D26\u53F7\u914D\u7F6E\uFF09`,
1447
- ` \u4E91\u7AEF\u63A5\u53D7: ${result.accepted || 0} \u6761`
1448
- ].join("\n"),
1449
- data: {
1450
- sources: sources.length,
1451
- drafts: drafts.length,
1452
- published: published.length,
1453
- configs: configs.length,
1454
- accepted: result.accepted || 0
1455
- }
1456
- };
1457
- } else {
1458
- return {
1459
- success: false,
1460
- message: `\u540C\u6B65\u5931\u8D25: HTTP ${res.status} \u2014 ${JSON.stringify(result)}`
1461
- };
1462
- }
1463
- } catch (err) {
1464
- return {
1465
- success: false,
1466
- message: `\u540C\u6B65\u7F51\u7EDC\u9519\u8BEF: ${err instanceof Error ? err.message : err}`
1467
- };
1468
- }
1469
- }
1470
1037
 
1471
1038
  // src/index.ts
1472
1039
  var server = new McpServer({
1473
1040
  name: "ClaudeInk",
1474
- version: "0.1.0"
1041
+ version: "0.6.0"
1475
1042
  });
1476
- server.tool("auth.activate", "\u9A8C\u8BC1 license key \u5E76\u6FC0\u6D3B ClaudeInk", activateSchema.shape, async (input) => {
1477
- const result = await authActivate(input);
1478
- return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
1479
- });
1480
- server.tool("auth.status", "\u67E5\u770B\u5F53\u524D\u6388\u6743\u72B6\u6001", {}, async () => {
1481
- const result = await authStatus();
1043
+ 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) => {
1044
+ const result = await workflowInit(input);
1482
1045
  return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
1483
1046
  });
1484
- server.tool("config.sync", "\u62C9\u53D6\u4E91\u7AEF\u6700\u65B0\u5DE5\u4F5C\u6D41\u914D\u7F6E", {}, async () => {
1485
- const result = await configSync();
1047
+ 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) => {
1048
+ const result = await syncPush(input);
1486
1049
  return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
1487
1050
  });
1488
- server.tool("config.version", "\u67E5\u770B\u672C\u5730\u914D\u7F6E\u7248\u672C", {}, async () => {
1489
- const result = await configVersion();
1051
+ 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) => {
1052
+ const result = await syncPull(input);
1490
1053
  return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
1491
1054
  });
1492
1055
  server.tool("source.add", "\u6DFB\u52A0\u7D20\u6750\u5230\u672C\u5730\u7D20\u6750\u5E93", sourceAddSchema.shape, async (input) => {
1493
1056
  const result = await sourceAdd(input);
1494
1057
  return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
1495
1058
  });
1496
- server.tool("source.search", "\u641C\u7D22\u672C\u5730\u7D20\u6750", sourceSearchSchema.shape, async (input) => {
1497
- const result = await sourceSearch(input);
1498
- return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
1499
- });
1500
- server.tool("source.list", "\u5217\u51FA\u7D20\u6750\u76EE\u5F55", sourceListSchema.shape, async (input) => {
1501
- const result = await sourceList(input);
1502
- return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
1503
- });
1504
1059
  server.tool("source.crawl", "\u89E6\u53D1\u722C\u866B\u6293\u53D6", sourceCrawlSchema.shape, async (input) => {
1505
1060
  const result = await sourceCrawl(input);
1506
1061
  return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
1507
1062
  });
1508
- server.tool("source.schedule", "\u7BA1\u7406\u5B9A\u65F6\u722C\u866B\u4EFB\u52A1", sourceScheduleSchema.shape, async (input) => {
1509
- const result = await sourceSchedule(input);
1510
- return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
1511
- });
1512
- server.tool("source.tag", "\u5904\u7406\u7D20\u6750\u6807\u7B7E\uFF08\u961F\u5217\u6216\u5355\u4E2A\uFF09", sourceTagSchema.shape, async (input) => {
1063
+ server.tool("source.tag", "\u83B7\u53D6\u5F85\u6807\u7B7E\u7D20\u6750\u5185\u5BB9\u4F9B AI \u5206\u6790", sourceTagSchema.shape, async (input) => {
1513
1064
  const result = await sourceTag(input);
1514
1065
  return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
1515
1066
  });
1516
- server.tool("source.tag_status", "\u67E5\u770B\u6807\u7B7E\u961F\u5217\u72B6\u6001", {}, async () => {
1517
- const result = await sourceTagStatus();
1067
+ server.tool("source.tag_apply", "\u5199\u5165 AI \u751F\u6210\u7684\u6807\u7B7E\u5230\u7D20\u6750\u6587\u4EF6", sourceTagApplySchema.shape, async (input) => {
1068
+ const result = await sourceTagApply(input);
1518
1069
  return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
1519
1070
  });
1520
- server.tool("source.tag_apply", "\u5199\u5165 Claude \u751F\u6210\u7684\u6807\u7B7E\u5230\u7D20\u6750\u6587\u4EF6", sourceTagApplySchema.shape, async (input) => {
1521
- const result = await sourceTagApply(input);
1071
+ server.tool("source.subscribe", "\u7BA1\u7406\u8BA2\u9605\u6E90\uFF08\u6DFB\u52A0/\u5220\u9664/\u5217\u51FA\u722C\u866B\u6E90\uFF09", sourceSubscribeSchema.shape, async (input) => {
1072
+ const result = await sourceSubscribe(input);
1522
1073
  return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
1523
1074
  });
1524
1075
  server.tool("draft.save", "\u4FDD\u5B58\u8349\u7A3F", draftSaveSchema.shape, async (input) => {
1525
1076
  const result = await draftSave(input);
1526
1077
  return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
1527
1078
  });
1528
- server.tool("draft.list", "\u5217\u51FA\u8349\u7A3F", draftListSchema.shape, async (input) => {
1529
- const result = await draftList(input);
1530
- return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
1531
- });
1532
- server.tool("draft.update", "\u66F4\u65B0\u8349\u7A3F", draftUpdateSchema.shape, async (input) => {
1533
- const result = await draftUpdate(input);
1534
- return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
1535
- });
1536
- server.tool("draft.delete", "\u5220\u9664\u8349\u7A3F", draftDeleteSchema.shape, async (input) => {
1537
- const result = await draftDelete(input);
1538
- return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
1539
- });
1540
1079
  server.tool("draft.publish", "\u6807\u8BB0\u8349\u7A3F\u4E3A\u5DF2\u53D1\u5E03", draftPublishSchema.shape, async (input) => {
1541
1080
  const result = await draftPublish(input);
1542
1081
  return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
@@ -1549,34 +1088,10 @@ server.tool("analytics.report", "\u83B7\u53D6\u6570\u636E\u5206\u6790\u62A5\u544
1549
1088
  const result = await analyticsReport(input);
1550
1089
  return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
1551
1090
  });
1552
- server.tool("account.list", "\u5217\u51FA\u6240\u6709\u8D26\u53F7", {}, async () => {
1553
- const result = await accountList();
1554
- return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
1555
- });
1556
- server.tool("account.switch", "\u5207\u6362\u5F53\u524D\u8D26\u53F7", accountSwitchSchema.shape, async (input) => {
1557
- const result = await accountSwitch(input);
1558
- return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
1559
- });
1560
- server.tool("account.create", "\u521B\u5EFA\u65B0\u8D26\u53F7", accountCreateSchema.shape, async (input) => {
1561
- const result = await accountCreate(input);
1562
- return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
1563
- });
1564
- server.tool("sync.push", "\u5C06\u672C\u5730\u7D20\u6750\u3001\u8349\u7A3F\u3001\u5DF2\u53D1\u5E03\u6587\u7AE0\u7684\u5143\u6570\u636E\u540C\u6B65\u5230 ClaudeInk \u4E91\u7AEF\u63A7\u5236\u53F0", syncPushSchema.shape, async (input) => {
1565
- const result = await syncPush(input);
1566
- return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
1567
- });
1568
- server.tool("workflow.init", "\u521D\u59CB\u5316\u5199\u4F5C\u5DE5\u4F5C\u6D41\uFF08\u91CA\u653E\u4E09\u5C42\u914D\u7F6E + \u5E73\u53F0\u89C4\u5219 + \u8D26\u53F7\u6A21\u677F + \u722C\u866B\u5DE5\u5177\u5230\u5DE5\u4F5C\u76EE\u5F55\uFF09", workflowInitSchema.shape, async (input) => {
1569
- const result = await workflowInit(input);
1570
- return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
1571
- });
1572
- server.tool("workflow.status", "\u68C0\u67E5\u5199\u4F5C\u5DE5\u4F5C\u6D41\u72B6\u6001\uFF08\u914D\u7F6E\u6587\u4EF6\u3001\u51ED\u8BC1\u3001\u8D26\u53F7\u662F\u5426\u5C31\u7EEA\uFF09", {}, async () => {
1573
- const result = await workflowStatus();
1574
- return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
1575
- });
1576
1091
  async function main() {
1577
1092
  const transport = new StdioServerTransport();
1578
1093
  await server.connect(transport);
1579
- console.error("[ClaudeInk MCP] Server started on stdio");
1094
+ console.error("[ClaudeInk MCP] Server started on stdio (12 tools)");
1580
1095
  }
1581
1096
  main().catch((err) => {
1582
1097
  console.error("[ClaudeInk MCP] Fatal error:", err);