@claudeink/mcp-server 0.3.0 → 0.5.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 } 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";
@@ -28,11 +30,6 @@ var DEFAULT_CONFIG = {
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: [],
@@ -76,23 +73,9 @@ async function getCredentials() {
76
73
  if (!await fileExists(PATHS.credentials)) return null;
77
74
  return readJson(PATHS.credentials, null);
78
75
  }
79
- async function saveCredentials(creds) {
80
- await writeJson(PATHS.credentials, creds, true);
81
- }
82
76
  async function getConfig() {
83
77
  return readJson(PATHS.config, DEFAULT_CONFIG);
84
78
  }
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
79
  async function getTagQueue() {
97
80
  return readJson(PATHS.tagQueue, DEFAULT_TAG_QUEUE);
98
81
  }
@@ -102,188 +85,10 @@ async function saveTagQueue(queue) {
102
85
  async function getCrawlSchedules() {
103
86
  return readJson(PATHS.crawlSchedules, DEFAULT_CRAWL_SCHEDULES);
104
87
  }
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
88
 
284
89
  // src/lib/sources.ts
285
- import { readFile as readFile2, writeFile as writeFile3 } from "fs/promises";
286
- import { join as join3, relative } from "path";
90
+ import { readFile as readFile2, writeFile as writeFile2 } from "fs/promises";
91
+ import { join as join2, relative } from "path";
287
92
  import matter from "gray-matter";
288
93
  import { glob } from "glob";
289
94
  async function readSourceFile(filePath) {
@@ -299,7 +104,7 @@ async function readSourceFile(filePath) {
299
104
  }
300
105
  async function writeSourceFile(filePath, meta, content) {
301
106
  const output = matter.stringify(content, meta);
302
- await writeFile3(filePath, output, "utf-8");
107
+ await writeFile2(filePath, output, "utf-8");
303
108
  }
304
109
  async function updateSourceTags(filePath, tags) {
305
110
  const source = await readSourceFile(filePath);
@@ -308,64 +113,6 @@ async function updateSourceTags(filePath, tags) {
308
113
  source.meta.taggedBy = "local-claude";
309
114
  await writeSourceFile(filePath, source.meta, source.content);
310
115
  }
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
116
  async function addToTagQueue(file, title, source) {
370
117
  const queue = await getTagQueue();
371
118
  if (queue.queue.some((item) => item.file === file)) return;
@@ -410,13 +157,91 @@ async function markTagFailed(file) {
410
157
  await saveTagQueue(queue);
411
158
  }
412
159
 
160
+ // src/lib/state.ts
161
+ import { readFile as readFile3, writeFile as writeFile3, mkdir as mkdir2 } from "fs/promises";
162
+ import { join as join3 } from "path";
163
+ var DEFAULT_STATE = {
164
+ sources: {},
165
+ drafts: {},
166
+ published: {},
167
+ configs: {},
168
+ crawlerSources: {},
169
+ lastSyncAt: ""
170
+ };
171
+ async function getStatePath() {
172
+ const config = await getConfig();
173
+ const dir = join3(config.workflowDir || process.cwd(), ".claudeink");
174
+ await mkdir2(dir, { recursive: true });
175
+ return join3(dir, "state.json");
176
+ }
177
+ async function readState() {
178
+ try {
179
+ const path = await getStatePath();
180
+ const content = await readFile3(path, "utf-8");
181
+ return { ...DEFAULT_STATE, ...JSON.parse(content) };
182
+ } catch {
183
+ return { ...DEFAULT_STATE };
184
+ }
185
+ }
186
+ async function writeState(state) {
187
+ const path = await getStatePath();
188
+ await writeFile3(path, JSON.stringify(state, null, 2), "utf-8");
189
+ }
190
+ async function updateSource(id, data) {
191
+ const state = await readState();
192
+ state.sources[id] = data;
193
+ await writeState(state);
194
+ }
195
+ async function updateSourceTags2(id, tags) {
196
+ const state = await readState();
197
+ if (state.sources[id]) {
198
+ state.sources[id].tags = tags;
199
+ state.sources[id].updatedAt = (/* @__PURE__ */ new Date()).toISOString();
200
+ }
201
+ await writeState(state);
202
+ }
203
+ async function updateDraft(id, data) {
204
+ const state = await readState();
205
+ state.drafts[id] = data;
206
+ await writeState(state);
207
+ }
208
+ async function moveDraftToPublished(id, data) {
209
+ const state = await readState();
210
+ delete state.drafts[id];
211
+ state.published[id] = data;
212
+ await writeState(state);
213
+ }
214
+ async function updateConfig(key, data) {
215
+ const state = await readState();
216
+ state.configs[key] = data;
217
+ await writeState(state);
218
+ }
219
+ async function updateCrawlerSource(id, data) {
220
+ const state = await readState();
221
+ state.crawlerSources[id] = data;
222
+ await writeState(state);
223
+ }
224
+ async function removeCrawlerSource(id) {
225
+ const state = await readState();
226
+ delete state.crawlerSources[id];
227
+ await writeState(state);
228
+ }
229
+ async function updateLastSyncAt() {
230
+ const state = await readState();
231
+ state.lastSyncAt = (/* @__PURE__ */ new Date()).toISOString();
232
+ await writeState(state);
233
+ }
234
+ async function replaceState(state) {
235
+ await writeState(state);
236
+ }
237
+
413
238
  // 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")
239
+ var sourceAddSchema = z.object({
240
+ title: z.string().describe("\u7D20\u6750\u6807\u9898"),
241
+ content: z.string().describe("\u7D20\u6750\u6B63\u6587"),
242
+ url: z.string().optional().describe("\u6765\u6E90 URL"),
243
+ 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"),
244
+ folder: z.string().optional().describe("\u5B58\u653E\u5B50\u76EE\u5F55\uFF0C\u5982 articles/techcrunch")
420
245
  });
421
246
  async function sourceAdd(input) {
422
247
  const config = await getConfig();
@@ -438,6 +263,16 @@ async function sourceAdd(input) {
438
263
  meta.taggedAt = (/* @__PURE__ */ new Date()).toISOString();
439
264
  meta.taggedBy = "local-claude";
440
265
  await writeSourceFile(filePath, meta, input.content);
266
+ await updateSource(slug, {
267
+ title: input.title,
268
+ source: meta.source,
269
+ sourceIcon: "",
270
+ sourceUrl: input.url || "",
271
+ coverUrl: "",
272
+ tags: input.tags,
273
+ publishedAt: date,
274
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
275
+ });
441
276
  return {
442
277
  success: true,
443
278
  message: `\u7D20\u6750\u5DF2\u5165\u5E93: ${filename}\uFF08\u542B ${input.tags.length} \u4E2A\u6807\u7B7E\uFF09`,
@@ -447,6 +282,16 @@ async function sourceAdd(input) {
447
282
  await writeSourceFile(filePath, meta, input.content);
448
283
  const relativePath = `sources/${folder}/${filename}`;
449
284
  await addToTagQueue(relativePath, input.title, "manual");
285
+ await updateSource(slug, {
286
+ title: input.title,
287
+ source: meta.source,
288
+ sourceIcon: "",
289
+ sourceUrl: input.url || "",
290
+ coverUrl: "",
291
+ tags: [],
292
+ publishedAt: date,
293
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
294
+ });
450
295
  return {
451
296
  success: true,
452
297
  message: `\u7D20\u6750\u5DF2\u5165\u5E93: ${filename}\uFF08\u5DF2\u52A0\u5165\u6807\u7B7E\u961F\u5217\uFF0C\u7B49\u5F85 Claude \u6253\u6807\u7B7E\uFF09`,
@@ -454,64 +299,15 @@ async function sourceAdd(input) {
454
299
  };
455
300
  }
456
301
  }
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")
302
+ var sourceCrawlSchema = z.object({
303
+ sourceId: z.string().optional().describe("\u6307\u5B9A\u722C\u866B\u6E90\u540D\u79F0\uFF0C\u4E0D\u4F20\u5219\u5168\u91CF")
508
304
  });
509
305
  async function sourceCrawl(input) {
510
306
  const schedules = await getCrawlSchedules();
511
307
  if (schedules.schedules.length === 0) {
512
308
  return {
513
309
  success: false,
514
- message: "\u6682\u65E0\u914D\u7F6E\u722C\u866B\u6E90\u3002\u8BF7\u5148\u7528 source.schedule \u6DFB\u52A0\u3002"
310
+ message: "\u6682\u65E0\u914D\u7F6E\u722C\u866B\u6E90\u3002\u8BF7\u5148\u7528 source.subscribe \u6DFB\u52A0\u3002"
515
311
  };
516
312
  }
517
313
  const targets = input.sourceId ? schedules.schedules.filter((s) => s.name === input.sourceId) : schedules.schedules.filter((s) => s.enabled);
@@ -527,63 +323,9 @@ async function sourceCrawl(input) {
527
323
  data: { triggered: targets.map((t) => t.name) }
528
324
  };
529
325
  }
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")
326
+ var sourceTagSchema = z.object({
327
+ mode: z.enum(["queue", "single"]).describe("queue=\u5904\u7406\u6807\u7B7E\u961F\u5217, single=\u5355\u4E2A\u6587\u4EF6\u6253\u6807\u7B7E"),
328
+ file: z.string().optional().describe("single \u6A21\u5F0F\u9700\u8981\u6307\u5B9A\u6587\u4EF6\u8DEF\u5F84")
587
329
  });
588
330
  async function sourceTag(input) {
589
331
  if (input.mode === "single") {
@@ -651,23 +393,9 @@ async function sourceTag(input) {
651
393
  }
652
394
  };
653
395
  }
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")
396
+ var sourceTagApplySchema = z.object({
397
+ file: z.string().describe("\u7D20\u6750\u6587\u4EF6\u8DEF\u5F84"),
398
+ tags: z.array(z.string()).describe("Claude \u751F\u6210\u7684\u6807\u7B7E")
671
399
  });
672
400
  async function sourceTagApply(input) {
673
401
  try {
@@ -675,6 +403,7 @@ async function sourceTagApply(input) {
675
403
  const fullPath = input.file.startsWith("/") ? input.file : join4(config.workflowDir, input.file);
676
404
  await updateSourceTags(fullPath, input.tags);
677
405
  await markTagged(input.file, input.tags.length);
406
+ await updateSourceTags2(input.file, input.tags);
678
407
  return {
679
408
  success: true,
680
409
  message: `\u6807\u7B7E\u5DF2\u5199\u5165: ${input.tags.join(", ")}`,
@@ -689,26 +418,92 @@ async function sourceTagApply(input) {
689
418
  }
690
419
  }
691
420
 
421
+ // src/tools/subscribe.ts
422
+ import { z as z2 } from "zod";
423
+ import { readFile as readFile4, writeFile as writeFile4 } from "fs/promises";
424
+ import { join as join5 } from "path";
425
+ var sourceSubscribeSchema = z2.object({
426
+ action: z2.enum(["add", "remove", "list"]).describe("\u64CD\u4F5C\u7C7B\u578B"),
427
+ id: z2.string().optional().describe("\u8BA2\u9605\u6E90 ID"),
428
+ name: z2.string().optional().describe("\u8BA2\u9605\u6E90\u540D\u79F0"),
429
+ url: z2.string().optional().describe("RSS/\u535A\u5BA2 URL"),
430
+ type: z2.enum(["rss", "blog", "sitemap"]).optional().describe("\u6E90\u7C7B\u578B"),
431
+ icon: z2.string().optional().describe("\u6765\u6E90 icon URL")
432
+ });
433
+ async function sourceSubscribe(input) {
434
+ const config = await getConfig();
435
+ const configPath = join5(config.workflowDir || process.cwd(), "tools/crawler/config.json");
436
+ let crawlerConfig;
437
+ try {
438
+ crawlerConfig = JSON.parse(await readFile4(configPath, "utf-8"));
439
+ } catch {
440
+ crawlerConfig = { sources: [] };
441
+ }
442
+ if (input.action === "list") {
443
+ return {
444
+ success: true,
445
+ message: `\u5171 ${crawlerConfig.sources.length} \u4E2A\u8BA2\u9605\u6E90`,
446
+ data: crawlerConfig.sources
447
+ };
448
+ }
449
+ if (input.action === "add") {
450
+ if (!input.id || !input.name || !input.url) {
451
+ return { success: false, message: "\u6DFB\u52A0\u8BA2\u9605\u6E90\u9700\u8981 id, name, url \u53C2\u6570" };
452
+ }
453
+ if (crawlerConfig.sources.some((s) => s.id === input.id)) {
454
+ return { success: false, message: `\u8BA2\u9605\u6E90 ${input.id} \u5DF2\u5B58\u5728` };
455
+ }
456
+ const newSource = {
457
+ id: input.id,
458
+ name: input.name,
459
+ url: input.url,
460
+ type: input.type || "rss",
461
+ icon: input.icon || "",
462
+ enabled: true
463
+ };
464
+ crawlerConfig.sources.push(newSource);
465
+ await writeFile4(configPath, JSON.stringify(crawlerConfig, null, 2));
466
+ await updateCrawlerSource(input.id, {
467
+ name: input.name,
468
+ url: input.url,
469
+ type: input.type || "rss",
470
+ icon: input.icon || "",
471
+ enabled: true
472
+ });
473
+ return { success: true, message: `\u8BA2\u9605\u6E90 ${input.name} \u5DF2\u6DFB\u52A0` };
474
+ }
475
+ if (input.action === "remove") {
476
+ if (!input.id) {
477
+ return { success: false, message: "\u5220\u9664\u8BA2\u9605\u6E90\u9700\u8981 id \u53C2\u6570" };
478
+ }
479
+ crawlerConfig.sources = crawlerConfig.sources.filter((s) => s.id !== input.id);
480
+ await writeFile4(configPath, JSON.stringify(crawlerConfig, null, 2));
481
+ await removeCrawlerSource(input.id);
482
+ return { success: true, message: `\u8BA2\u9605\u6E90 ${input.id} \u5DF2\u5220\u9664` };
483
+ }
484
+ return { success: false, message: "\u672A\u77E5\u64CD\u4F5C" };
485
+ }
486
+
692
487
  // src/tools/draft.ts
693
488
  import { z as z3 } from "zod";
694
489
 
695
490
  // 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";
491
+ import { readFile as readFile5, writeFile as writeFile5, mkdir as mkdir4, unlink } from "fs/promises";
492
+ import { join as join6, basename } from "path";
698
493
  import matter2 from "gray-matter";
699
494
  import { glob as glob2 } from "glob";
700
495
  import { randomUUID } from "crypto";
701
496
  function getDraftDir(account, workflowDir) {
702
497
  if (account.toLowerCase() === "auston") {
703
- return join5(workflowDir, "drafts");
498
+ return join6(workflowDir, "drafts");
704
499
  }
705
- return join5(workflowDir, "accounts", account, "drafts");
500
+ return join6(workflowDir, "accounts", account, "drafts");
706
501
  }
707
502
  function getPublishedDir(account, workflowDir) {
708
503
  if (account.toLowerCase() === "auston") {
709
- return join5(workflowDir, "published");
504
+ return join6(workflowDir, "published");
710
505
  }
711
- return join5(workflowDir, "accounts", account, "published");
506
+ return join6(workflowDir, "accounts", account, "published");
712
507
  }
713
508
  function generateDraftFilename(title) {
714
509
  const date = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
@@ -721,7 +516,7 @@ async function saveDraft(options) {
721
516
  await mkdir4(dir, { recursive: true });
722
517
  const id = randomUUID().slice(0, 8);
723
518
  const filename = generateDraftFilename(options.title);
724
- const filePath = join5(dir, filename);
519
+ const filePath = join6(dir, filename);
725
520
  const meta = {
726
521
  id,
727
522
  account: options.account,
@@ -737,62 +532,8 @@ async function saveDraft(options) {
737
532
  await writeFile5(filePath, output, "utf-8");
738
533
  return { filePath, meta, content: options.content };
739
534
  }
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
535
  async function publishDraft(options) {
795
- const raw = await readFile3(options.file, "utf-8");
536
+ const raw = await readFile5(options.file, "utf-8");
796
537
  const { data, content } = matter2(raw);
797
538
  const meta = data;
798
539
  meta.status = "published";
@@ -802,7 +543,7 @@ async function publishDraft(options) {
802
543
  const config = await getConfig();
803
544
  const publishedDir = getPublishedDir(meta.account, config.workflowDir);
804
545
  await mkdir4(publishedDir, { recursive: true });
805
- const newPath = join5(publishedDir, basename(options.file));
546
+ const newPath = join6(publishedDir, basename(options.file));
806
547
  const output = matter2.stringify(content.trim(), meta);
807
548
  await writeFile5(newPath, output, "utf-8");
808
549
  await unlink(options.file);
@@ -827,6 +568,16 @@ async function draftSave(input) {
827
568
  tags: input.tags,
828
569
  status: input.status
829
570
  });
571
+ await updateDraft(draft.meta.id, {
572
+ title: draft.meta.title,
573
+ account: input.account,
574
+ platform: input.platform,
575
+ status: draft.meta.status,
576
+ wordCount: draft.meta.wordCount,
577
+ tags: input.tags || [],
578
+ createdAt: draft.meta.createdAt,
579
+ updatedAt: draft.meta.updatedAt
580
+ });
830
581
  return {
831
582
  success: true,
832
583
  message: `\u8349\u7A3F\u5DF2\u4FDD\u5B58: ${draft.meta.title} (${draft.meta.wordCount} \u5B57)`,
@@ -837,73 +588,21 @@ async function draftSave(input) {
837
588
  }
838
589
  };
839
590
  }
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")
591
+ var draftPublishSchema = z3.object({
592
+ file: z3.string().describe("\u8349\u7A3F\u6587\u4EF6\u8DEF\u5F84"),
593
+ platformUrl: z3.string().optional().describe("\u5E73\u53F0\u53D1\u5E03\u94FE\u63A5")
843
594
  });
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")
902
- });
903
- async function draftPublish(input) {
904
- const draft = await publishDraft({
905
- file: input.file,
906
- platformUrl: input.platformUrl
595
+ async function draftPublish(input) {
596
+ const draft = await publishDraft({
597
+ file: input.file,
598
+ platformUrl: input.platformUrl
599
+ });
600
+ await moveDraftToPublished(draft.meta.id, {
601
+ title: draft.meta.title,
602
+ account: draft.meta.account,
603
+ platform: draft.meta.platform,
604
+ platformUrl: draft.meta.platformUrl || "",
605
+ publishedAt: draft.meta.publishedAt || (/* @__PURE__ */ new Date()).toISOString()
907
606
  });
908
607
  return {
909
608
  success: true,
@@ -918,6 +617,52 @@ async function draftPublish(input) {
918
617
 
919
618
  // src/tools/analytics.ts
920
619
  import { z as z4 } from "zod";
620
+
621
+ // src/lib/api-client.ts
622
+ async function request(path, options = {}) {
623
+ const config = await getConfig();
624
+ const url = `${config.apiBaseUrl}${path}`;
625
+ try {
626
+ const headers = {
627
+ "Content-Type": "application/json",
628
+ "User-Agent": "ClaudeInk-MCP/0.1.0"
629
+ };
630
+ if (options.token) {
631
+ headers["Authorization"] = `Bearer ${options.token}`;
632
+ } else {
633
+ const creds = await getCredentials();
634
+ if (creds?.token) {
635
+ headers["Authorization"] = `Bearer ${creds.token}`;
636
+ }
637
+ }
638
+ const res = await fetch(url, {
639
+ method: options.method || "GET",
640
+ headers,
641
+ body: options.body ? JSON.stringify(options.body) : void 0
642
+ });
643
+ const data = await res.json().catch(() => null);
644
+ return {
645
+ ok: res.ok,
646
+ status: res.status,
647
+ data: data ?? void 0,
648
+ error: res.ok ? void 0 : `HTTP ${res.status}`
649
+ };
650
+ } catch (err) {
651
+ return {
652
+ ok: false,
653
+ status: 0,
654
+ error: err instanceof Error ? err.message : "Unknown error"
655
+ };
656
+ }
657
+ }
658
+ async function pushSyncBatch(payload) {
659
+ return request("/api/sync/batch", {
660
+ method: "POST",
661
+ body: payload
662
+ });
663
+ }
664
+
665
+ // src/tools/analytics.ts
921
666
  var analyticsPushSchema = z4.object({
922
667
  articleId: z4.string().describe("\u6587\u7AE0 ID"),
923
668
  metrics: z4.object({
@@ -970,108 +715,51 @@ async function analyticsReport(input) {
970
715
 
971
716
  // src/tools/account.ts
972
717
  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() {
977
- 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;
993
- }
994
- }
995
- return {
996
- success: true,
997
- message: `\u5171 ${accounts.length} \u4E2A\u8D26\u53F7`,
998
- data: accounts
999
- };
1000
- }
1001
- var accountSwitchSchema = z5.object({
1002
- name: z5.string().describe("\u8981\u5207\u6362\u5230\u7684\u8D26\u53F7\u540D\u79F0")
1003
- });
1004
- async function accountSwitch(input) {
1005
- const config = await getConfig();
1006
- const yamlPath = join6(config.workflowDir, "accounts", `${input.name}.yaml`);
1007
- 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]";
1019
- }
1020
- }
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
1032
- }
1033
- };
1034
- } catch {
1035
- return {
1036
- success: false,
1037
- message: `\u8D26\u53F7 ${input.name} \u4E0D\u5B58\u5728\u3002\u8BF7\u68C0\u67E5 accounts/ \u76EE\u5F55\u3002`
1038
- };
1039
- }
1040
- }
718
+ import { readFile as readFile6, writeFile as writeFile6, mkdir as mkdir5 } from "fs/promises";
719
+ import { join as join7 } from "path";
1041
720
  var accountCreateSchema = z5.object({
1042
721
  name: z5.string().describe("\u8D26\u53F7\u540D\u79F0\uFF08\u82F1\u6587\u5C0F\u5199\uFF0C\u7528\u4F5C\u6587\u4EF6\u540D\uFF09"),
1043
722
  platform: z5.string().describe("\u5E73\u53F0\u540D\u79F0\uFF0C\u5982 wechat, xiaohongshu"),
1044
723
  displayName: z5.string().optional().describe("\u663E\u793A\u540D\u79F0"),
1045
724
  domains: z5.array(z5.string()).describe("\u5185\u5BB9\u9886\u57DF"),
1046
- description: z5.string().optional().describe("\u8D26\u53F7\u5B9A\u4F4D\u63CF\u8FF0")
725
+ description: z5.string().optional().describe("\u8D26\u53F7\u5B9A\u4F4D\u63CF\u8FF0"),
726
+ profileUrl: z5.string().optional().describe("\u8D26\u53F7\u5728\u5E73\u53F0\u4E0A\u7684\u4E3B\u9875 URL")
1047
727
  });
1048
728
  async function accountCreate(input) {
1049
729
  const config = await getConfig();
1050
- const yamlPath = join6(config.workflowDir, "accounts", `${input.name}.yaml`);
730
+ const yamlPath = join7(config.workflowDir, "accounts", `${input.name}.yaml`);
1051
731
  try {
1052
- await readFile4(yamlPath);
732
+ await readFile6(yamlPath);
1053
733
  return { success: false, message: `\u8D26\u53F7 ${input.name} \u5DF2\u5B58\u5728` };
1054
734
  } catch {
1055
735
  }
1056
736
  let template = "";
1057
737
  try {
1058
- template = await readFile4(
1059
- join6(config.workflowDir, "accounts", "_template.yaml"),
738
+ template = await readFile6(
739
+ join7(config.workflowDir, "accounts", "_template.yaml"),
1060
740
  "utf-8"
1061
741
  );
1062
742
  } catch {
1063
743
  template = defaultTemplate();
1064
744
  }
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*\[\]/,
745
+ const yamlContent = template.replace(/name:\s*""/, `name: "${input.displayName || input.name}"`).replace(/id:\s*""/, `id: "${input.name}"`).replace(/platform:\s*""/, `platform: "${input.platform}"`).replace(/profile_url:\s*""/, `profile_url: "${input.profileUrl || ""}"`).replace(/description:\s*""/, `description: "${input.description || ""}"`).replace(
746
+ /domains:\s*\n\s*- ""/,
1067
747
  `domains:
1068
748
  ${input.domains.map((d) => ` - "${d}"`).join("\n")}`
1069
749
  );
1070
750
  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 });
751
+ const accountDir = join7(config.workflowDir, "accounts", input.name);
752
+ await mkdir5(join7(accountDir, "drafts"), { recursive: true });
753
+ await mkdir5(join7(accountDir, "published"), { recursive: true });
754
+ await mkdir5(join7(accountDir, "assets"), { recursive: true });
755
+ await updateConfig(`account:${input.name}`, {
756
+ type: "account",
757
+ name: input.name,
758
+ displayName: input.displayName || input.name,
759
+ content: yamlContent,
760
+ metadata: { platform: input.platform, profileUrl: input.profileUrl || "" },
761
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
762
+ });
1075
763
  return {
1076
764
  success: true,
1077
765
  message: `\u8D26\u53F7 ${input.name} \u5DF2\u521B\u5EFA
@@ -1087,22 +775,16 @@ ${input.domains.map((d) => ` - "${d}"`).join("\n")}`
1087
775
  }
1088
776
  };
1089
777
  }
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
778
  function defaultTemplate() {
1096
779
  return `# ClaudeInk \u8D26\u53F7\u914D\u7F6E
1097
780
  name: ""
781
+ id: ""
1098
782
  platform: ""
783
+ profile_url: ""
1099
784
  description: ""
1100
785
 
1101
- domains: []
1102
-
1103
- audience:
1104
- age_range: ""
1105
- interests: []
786
+ domains:
787
+ - ""
1106
788
 
1107
789
  style:
1108
790
  tone: "\u5E73\u5B9E"
@@ -1112,33 +794,185 @@ style:
1112
794
  humor: "\u5076\u5C14"
1113
795
  language: "zh-CN"
1114
796
 
1115
- persona: ""
1116
-
1117
- content_rules:
1118
- min_words: null
1119
- max_words: null
1120
- paragraph_style: null
1121
-
1122
- templates: []
797
+ persona:
798
+ role: ""
799
+ background: ""
800
+ first_person: "\u6211"
1123
801
 
1124
802
  paths:
1125
- drafts: null
1126
- published: null
1127
- assets: null
803
+ drafts: "accounts/{id}/drafts/"
804
+ published: "accounts/{id}/published/"
805
+ assets: "accounts/{id}/assets/"
1128
806
  `;
1129
807
  }
1130
808
 
1131
- // src/tools/workflow.ts
809
+ // src/tools/sync.ts
1132
810
  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";
811
+ import { writeFile as writeFile7 } from "fs/promises";
812
+ import { join as join8 } from "path";
813
+ var syncPushSchema = z6.object({
814
+ workDir: z6.string().optional().describe("\u5DE5\u4F5C\u76EE\u5F55\uFF08\u9ED8\u8BA4\u4F7F\u7528\u914D\u7F6E\u4E2D\u7684 workflowDir\uFF09")
815
+ });
816
+ async function syncPush(input) {
817
+ const creds = await getCredentials();
818
+ if (!creds?.token) {
819
+ return { success: false, message: "\u672A\u6FC0\u6D3B\uFF0C\u8BF7\u5148\u4F7F\u7528 workflow.init \u6FC0\u6D3B License" };
820
+ }
821
+ const config = await getConfig();
822
+ const state = await readState();
823
+ const payload = {
824
+ sources: Object.entries(state.sources).map(([id, s]) => ({
825
+ id,
826
+ title: s.title,
827
+ source: s.source,
828
+ tags: s.tags,
829
+ sourceUrl: s.sourceUrl,
830
+ coverUrl: s.coverUrl,
831
+ sourceIcon: s.sourceIcon,
832
+ createdAt: s.publishedAt
833
+ })),
834
+ drafts: Object.entries(state.drafts).map(([id, d]) => ({
835
+ id,
836
+ account: d.account,
837
+ platform: d.platform,
838
+ title: d.title,
839
+ status: d.status,
840
+ wordCount: d.wordCount,
841
+ tags: d.tags,
842
+ createdAt: d.createdAt,
843
+ updatedAt: d.updatedAt
844
+ })),
845
+ published: Object.entries(state.published).map(([id, p]) => ({
846
+ id,
847
+ account: p.account,
848
+ platform: p.platform,
849
+ title: p.title,
850
+ platformUrl: p.platformUrl,
851
+ publishedAt: p.publishedAt
852
+ })),
853
+ configs: Object.entries(state.configs).map(([key, c]) => ({
854
+ type: c.type,
855
+ name: c.name,
856
+ displayName: c.displayName,
857
+ content: c.content,
858
+ metadata: c.metadata
859
+ })),
860
+ crawlerSources: Object.entries(state.crawlerSources).map(([id, cs]) => ({
861
+ id,
862
+ name: cs.name,
863
+ url: cs.url,
864
+ type: cs.type,
865
+ icon: cs.icon,
866
+ enabled: cs.enabled
867
+ })),
868
+ analytics: []
869
+ };
870
+ try {
871
+ const res = await fetch(`${config.apiBaseUrl}/api/sync/batch`, {
872
+ method: "POST",
873
+ headers: {
874
+ "Content-Type": "application/json",
875
+ "Authorization": `Bearer ${creds.token}`
876
+ },
877
+ body: JSON.stringify(payload)
878
+ });
879
+ const result = await res.json();
880
+ if (res.ok) {
881
+ await updateLastSyncAt();
882
+ return {
883
+ success: true,
884
+ message: [
885
+ "\u2705 \u540C\u6B65\u5B8C\u6210",
886
+ ` \u7D20\u6750: ${payload.sources.length} \u7BC7`,
887
+ ` \u8349\u7A3F: ${payload.drafts.length} \u7BC7`,
888
+ ` \u5DF2\u53D1\u5E03: ${payload.published.length} \u7BC7`,
889
+ ` \u914D\u7F6E: ${payload.configs.length} \u4E2A`,
890
+ ` \u8BA2\u9605\u6E90: ${payload.crawlerSources.length} \u4E2A`,
891
+ ` \u4E91\u7AEF\u63A5\u53D7: ${result.accepted || 0} \u6761`
892
+ ].join("\n"),
893
+ data: { accepted: result.accepted || 0 }
894
+ };
895
+ } else {
896
+ return { success: false, message: `\u540C\u6B65\u5931\u8D25: HTTP ${res.status}` };
897
+ }
898
+ } catch (err) {
899
+ return { success: false, message: `\u540C\u6B65\u7F51\u7EDC\u9519\u8BEF: ${err instanceof Error ? err.message : err}` };
900
+ }
901
+ }
902
+ var syncPullSchema = z6.object({
903
+ workDir: z6.string().optional().describe("\u5DE5\u4F5C\u76EE\u5F55")
904
+ });
905
+ async function syncPull(input) {
906
+ const creds = await getCredentials();
907
+ if (!creds?.token) {
908
+ return { success: false, message: "\u672A\u6FC0\u6D3B\uFF0C\u8BF7\u5148\u4F7F\u7528 workflow.init \u6FC0\u6D3B License" };
909
+ }
910
+ const config = await getConfig();
911
+ const workDir = input.workDir || config.workflowDir;
912
+ try {
913
+ const res = await fetch(`${config.apiBaseUrl}/api/sync/pull`, {
914
+ headers: { "Authorization": `Bearer ${creds.token}` }
915
+ });
916
+ if (!res.ok) {
917
+ if (res.status === 404) {
918
+ return { success: true, message: "\u4E91\u7AEF\u65E0\u5DF2\u6709\u914D\u7F6E\uFF0C\u4F7F\u7528\u672C\u5730\u9ED8\u8BA4" };
919
+ }
920
+ return { success: false, message: `\u62C9\u53D6\u5931\u8D25: HTTP ${res.status}` };
921
+ }
922
+ const cloudData = await res.json();
923
+ if (!cloudData.configs || cloudData.configs.length === 0) {
924
+ return { success: true, message: "\u4E91\u7AEF\u65E0\u914D\u7F6E\u6570\u636E" };
925
+ }
926
+ let updated = 0;
927
+ const state = await readState();
928
+ for (const config2 of cloudData.configs) {
929
+ const stateKey = `${config2.type}:${config2.name}`;
930
+ const localUpdatedAt = state.configs[stateKey]?.updatedAt || "";
931
+ if (!localUpdatedAt || config2.updatedAt > localUpdatedAt) {
932
+ let filePath = "";
933
+ if (config2.type === "base_rules") {
934
+ filePath = join8(workDir, "base-rules.md");
935
+ } else if (config2.type === "platform") {
936
+ filePath = join8(workDir, "platforms", `${config2.name}.md`);
937
+ } else if (config2.type === "account") {
938
+ filePath = join8(workDir, "accounts", `${config2.name}.yaml`);
939
+ }
940
+ if (filePath && config2.content) {
941
+ await writeFile7(filePath, config2.content, "utf-8");
942
+ state.configs[stateKey] = {
943
+ type: config2.type,
944
+ name: config2.name,
945
+ displayName: config2.displayName,
946
+ content: config2.content,
947
+ metadata: config2.metadata || {},
948
+ updatedAt: config2.updatedAt
949
+ };
950
+ updated++;
951
+ }
952
+ }
953
+ }
954
+ await replaceState(state);
955
+ return {
956
+ success: true,
957
+ message: updated > 0 ? `\u2705 \u4ECE\u4E91\u7AEF\u540C\u6B65\u4E86 ${updated} \u4E2A\u914D\u7F6E\u6587\u4EF6` : "\u672C\u5730\u914D\u7F6E\u5DF2\u662F\u6700\u65B0\uFF0C\u65E0\u9700\u66F4\u65B0",
958
+ data: { updated }
959
+ };
960
+ } catch (err) {
961
+ return { success: false, message: `\u62C9\u53D6\u9519\u8BEF: ${err instanceof Error ? err.message : err}` };
962
+ }
963
+ }
964
+
965
+ // src/tools/workflow.ts
966
+ import { z as z7 } from "zod";
967
+ import { cp, mkdir as mkdir6, access as access2, writeFile as writeFile8 } from "fs/promises";
968
+ import { join as join9, dirname } from "path";
1135
969
  import { fileURLToPath } from "url";
1136
970
  var __filename = fileURLToPath(import.meta.url);
1137
- var __dirname = dirname2(__filename);
1138
- var WORKFLOW_SRC = join7(__dirname, "..", "workflow");
1139
- var workflowInitSchema = z6.object({
1140
- workDir: z6.string().describe("\u5DE5\u4F5C\u6D41\u521D\u59CB\u5316\u76EE\u6807\u76EE\u5F55\uFF08\u7EDD\u5BF9\u8DEF\u5F84\uFF09"),
1141
- licenseKey: z6.string().optional().describe("License Key\uFF08\u53EF\u9009\uFF0C\u4F20\u5165\u5219\u81EA\u52A8\u6FC0\u6D3B\uFF09")
971
+ var __dirname = dirname(__filename);
972
+ var WORKFLOW_SRC = join9(__dirname, "..", "workflow");
973
+ var workflowInitSchema = z7.object({
974
+ workDir: z7.string().describe("\u5DE5\u4F5C\u6D41\u521D\u59CB\u5316\u76EE\u6807\u76EE\u5F55\uFF08\u7EDD\u5BF9\u8DEF\u5F84\uFF09"),
975
+ licenseKey: z7.string().optional().describe("License Key\uFF08\u53EF\u9009\uFF0C\u4F20\u5165\u5219\u81EA\u52A8\u6FC0\u6D3B\uFF09")
1142
976
  });
1143
977
  async function workflowInit(input) {
1144
978
  const cwd = input.workDir;
@@ -1146,8 +980,8 @@ async function workflowInit(input) {
1146
980
  try {
1147
981
  const items = ["CLAUDE.md", "base-rules.md", "platforms", "accounts", "tools"];
1148
982
  for (const item of items) {
1149
- const src = join7(WORKFLOW_SRC, item);
1150
- const dest = join7(cwd, item);
983
+ const src = join9(WORKFLOW_SRC, item);
984
+ const dest = join9(cwd, item);
1151
985
  try {
1152
986
  await access2(dest);
1153
987
  results.push(`\u23ED\uFE0F ${item} \u5DF2\u5B58\u5728\uFF0C\u8DF3\u8FC7`);
@@ -1166,9 +1000,20 @@ async function workflowInit(input) {
1166
1000
  ".claudeink"
1167
1001
  ];
1168
1002
  for (const dir of dirs) {
1169
- await mkdir6(join7(cwd, dir), { recursive: true });
1003
+ await mkdir6(join9(cwd, dir), { recursive: true });
1170
1004
  }
1171
- results.push("\u2705 \u8FD0\u884C\u65F6\u76EE\u5F55\u5DF2\u521B\u5EFA\uFF08sources/, templates/, .claudeink/\uFF09");
1005
+ results.push("\u2705 \u8FD0\u884C\u65F6\u76EE\u5F55\u5DF2\u521B\u5EFA");
1006
+ const emptyState = {
1007
+ sources: {},
1008
+ drafts: {},
1009
+ published: {},
1010
+ configs: {},
1011
+ crawlerSources: {},
1012
+ lastSyncAt: ""
1013
+ };
1014
+ await replaceState(emptyState);
1015
+ results.push("\u2705 \u672C\u5730\u72B6\u6001\u8868\u5DF2\u521D\u59CB\u5316");
1016
+ let activated = false;
1172
1017
  if (input.licenseKey) {
1173
1018
  try {
1174
1019
  const res = await fetch("https://app.claudeink.com/api/auth/activate", {
@@ -1178,13 +1023,13 @@ async function workflowInit(input) {
1178
1023
  });
1179
1024
  const data = await res.json();
1180
1025
  if (data.userId) {
1181
- const { writeFile: writeFile7 } = await import("fs/promises");
1182
- await writeFile7(
1183
- join7(cwd, ".claudeink", "credentials.json"),
1026
+ await writeFile8(
1027
+ join9(cwd, ".claudeink", "credentials.json"),
1184
1028
  JSON.stringify(data, null, 2),
1185
1029
  { mode: 384 }
1186
1030
  );
1187
- results.push(`\u2705 License \u6FC0\u6D3B\u6210\u529F\uFF08\u5957\u9910: ${data.plan}\uFF0C\u5230\u671F: ${data.expiresAt}\uFF09`);
1031
+ results.push(`\u2705 License \u6FC0\u6D3B\u6210\u529F\uFF08\u5957\u9910: ${data.plan}\uFF09`);
1032
+ activated = true;
1188
1033
  } else {
1189
1034
  results.push(`\u26A0\uFE0F License \u6FC0\u6D3B\u5931\u8D25: ${JSON.stringify(data)}`);
1190
1035
  }
@@ -1192,6 +1037,28 @@ async function workflowInit(input) {
1192
1037
  results.push(`\u26A0\uFE0F \u6FC0\u6D3B\u7F51\u7EDC\u9519\u8BEF: ${err instanceof Error ? err.message : err}`);
1193
1038
  }
1194
1039
  }
1040
+ if (activated) {
1041
+ try {
1042
+ const pullResult = await syncPull({ workDir: cwd });
1043
+ if (pullResult.success && pullResult.data && pullResult.data.updated > 0) {
1044
+ results.push("\u2705 \u5DF2\u4ECE\u4E91\u7AEF\u540C\u6B65\u914D\u7F6E\uFF08\u8DE8\u8BBE\u5907\u6062\u590D\uFF09");
1045
+ } else {
1046
+ results.push("\u2139\uFE0F \u4E91\u7AEF\u65E0\u5DF2\u6709\u914D\u7F6E\uFF0C\u4F7F\u7528\u9ED8\u8BA4\u6A21\u677F");
1047
+ }
1048
+ } catch {
1049
+ results.push("\u2139\uFE0F \u4E91\u7AEF\u65E0\u5DF2\u6709\u914D\u7F6E\uFF0C\u4F7F\u7528\u9ED8\u8BA4\u6A21\u677F");
1050
+ }
1051
+ }
1052
+ try {
1053
+ await access2(join9(cwd, "tools", "crawler", "package.json"));
1054
+ const { execSync } = await import("child_process");
1055
+ execSync("npm install --silent", {
1056
+ cwd: join9(cwd, "tools", "crawler"),
1057
+ stdio: "pipe"
1058
+ });
1059
+ results.push("\u2705 \u722C\u866B\u4F9D\u8D56\u5DF2\u5B89\u88C5");
1060
+ } catch {
1061
+ }
1195
1062
  return {
1196
1063
  success: true,
1197
1064
  message: [
@@ -1199,14 +1066,6 @@ async function workflowInit(input) {
1199
1066
  "",
1200
1067
  ...results,
1201
1068
  "",
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
1069
  "\u{1F3AF} \u4E0B\u4E00\u6B65\uFF1A",
1211
1070
  "1. \u8BFB\u53D6 CLAUDE.md \u4E86\u89E3\u4E09\u5C42\u914D\u7F6E\u67B6\u6784",
1212
1071
  "2. \u4F7F\u7528 /\u65B0\u5EFA\u8D26\u53F7 \u521B\u5EFA\u7B2C\u4E00\u4E2A\u81EA\u5A92\u4F53\u8D26\u53F7",
@@ -1220,286 +1079,48 @@ async function workflowInit(input) {
1220
1079
  };
1221
1080
  }
1222
1081
  }
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 payload = { sources, drafts, published, analytics: [] };
1393
- try {
1394
- const res = await fetch(`${config.apiBaseUrl}/api/sync/batch`, {
1395
- method: "POST",
1396
- headers: {
1397
- "Content-Type": "application/json",
1398
- "Authorization": `Bearer ${creds.token}`
1399
- },
1400
- body: JSON.stringify(payload)
1401
- });
1402
- const result = await res.json();
1403
- if (res.ok) {
1404
- return {
1405
- success: true,
1406
- message: [
1407
- `\u2705 \u540C\u6B65\u5B8C\u6210`,
1408
- ` \u7D20\u6750: ${sources.length} \u7BC7`,
1409
- ` \u8349\u7A3F: ${drafts.length} \u7BC7`,
1410
- ` \u5DF2\u53D1\u5E03: ${published.length} \u7BC7`,
1411
- ` \u4E91\u7AEF\u63A5\u53D7: ${result.accepted || 0} \u6761`
1412
- ].join("\n"),
1413
- data: {
1414
- sources: sources.length,
1415
- drafts: drafts.length,
1416
- published: published.length,
1417
- accepted: result.accepted || 0
1418
- }
1419
- };
1420
- } else {
1421
- return {
1422
- success: false,
1423
- message: `\u540C\u6B65\u5931\u8D25: HTTP ${res.status} \u2014 ${JSON.stringify(result)}`
1424
- };
1425
- }
1426
- } catch (err) {
1427
- return {
1428
- success: false,
1429
- message: `\u540C\u6B65\u7F51\u7EDC\u9519\u8BEF: ${err instanceof Error ? err.message : err}`
1430
- };
1431
- }
1432
- }
1433
1082
 
1434
1083
  // src/index.ts
1435
1084
  var server = new McpServer({
1436
1085
  name: "ClaudeInk",
1437
- version: "0.1.0"
1438
- });
1439
- server.tool("auth.activate", "\u9A8C\u8BC1 license key \u5E76\u6FC0\u6D3B ClaudeInk", activateSchema.shape, async (input) => {
1440
- const result = await authActivate(input);
1441
- return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
1086
+ version: "0.5.0"
1442
1087
  });
1443
- server.tool("auth.status", "\u67E5\u770B\u5F53\u524D\u6388\u6743\u72B6\u6001", {}, async () => {
1444
- const result = await authStatus();
1088
+ 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) => {
1089
+ const result = await workflowInit(input);
1445
1090
  return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
1446
1091
  });
1447
- server.tool("config.sync", "\u62C9\u53D6\u4E91\u7AEF\u6700\u65B0\u5DE5\u4F5C\u6D41\u914D\u7F6E", {}, async () => {
1448
- const result = await configSync();
1092
+ 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) => {
1093
+ const result = await syncPush(input);
1449
1094
  return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
1450
1095
  });
1451
- server.tool("config.version", "\u67E5\u770B\u672C\u5730\u914D\u7F6E\u7248\u672C", {}, async () => {
1452
- const result = await configVersion();
1096
+ 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) => {
1097
+ const result = await syncPull(input);
1453
1098
  return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
1454
1099
  });
1455
1100
  server.tool("source.add", "\u6DFB\u52A0\u7D20\u6750\u5230\u672C\u5730\u7D20\u6750\u5E93", sourceAddSchema.shape, async (input) => {
1456
1101
  const result = await sourceAdd(input);
1457
1102
  return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
1458
1103
  });
1459
- server.tool("source.search", "\u641C\u7D22\u672C\u5730\u7D20\u6750", sourceSearchSchema.shape, async (input) => {
1460
- const result = await sourceSearch(input);
1461
- return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
1462
- });
1463
- server.tool("source.list", "\u5217\u51FA\u7D20\u6750\u76EE\u5F55", sourceListSchema.shape, async (input) => {
1464
- const result = await sourceList(input);
1465
- return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
1466
- });
1467
1104
  server.tool("source.crawl", "\u89E6\u53D1\u722C\u866B\u6293\u53D6", sourceCrawlSchema.shape, async (input) => {
1468
1105
  const result = await sourceCrawl(input);
1469
1106
  return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
1470
1107
  });
1471
- server.tool("source.schedule", "\u7BA1\u7406\u5B9A\u65F6\u722C\u866B\u4EFB\u52A1", sourceScheduleSchema.shape, async (input) => {
1472
- const result = await sourceSchedule(input);
1473
- return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
1474
- });
1475
- server.tool("source.tag", "\u5904\u7406\u7D20\u6750\u6807\u7B7E\uFF08\u961F\u5217\u6216\u5355\u4E2A\uFF09", sourceTagSchema.shape, async (input) => {
1108
+ server.tool("source.tag", "\u83B7\u53D6\u5F85\u6807\u7B7E\u7D20\u6750\u5185\u5BB9\u4F9B AI \u5206\u6790", sourceTagSchema.shape, async (input) => {
1476
1109
  const result = await sourceTag(input);
1477
1110
  return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
1478
1111
  });
1479
- server.tool("source.tag_status", "\u67E5\u770B\u6807\u7B7E\u961F\u5217\u72B6\u6001", {}, async () => {
1480
- const result = await sourceTagStatus();
1112
+ server.tool("source.tag_apply", "\u5199\u5165 AI \u751F\u6210\u7684\u6807\u7B7E\u5230\u7D20\u6750\u6587\u4EF6", sourceTagApplySchema.shape, async (input) => {
1113
+ const result = await sourceTagApply(input);
1481
1114
  return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
1482
1115
  });
1483
- server.tool("source.tag_apply", "\u5199\u5165 Claude \u751F\u6210\u7684\u6807\u7B7E\u5230\u7D20\u6750\u6587\u4EF6", sourceTagApplySchema.shape, async (input) => {
1484
- const result = await sourceTagApply(input);
1116
+ server.tool("source.subscribe", "\u7BA1\u7406\u8BA2\u9605\u6E90\uFF08\u6DFB\u52A0/\u5220\u9664/\u5217\u51FA\u722C\u866B\u6E90\uFF09", sourceSubscribeSchema.shape, async (input) => {
1117
+ const result = await sourceSubscribe(input);
1485
1118
  return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
1486
1119
  });
1487
1120
  server.tool("draft.save", "\u4FDD\u5B58\u8349\u7A3F", draftSaveSchema.shape, async (input) => {
1488
1121
  const result = await draftSave(input);
1489
1122
  return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
1490
1123
  });
1491
- server.tool("draft.list", "\u5217\u51FA\u8349\u7A3F", draftListSchema.shape, async (input) => {
1492
- const result = await draftList(input);
1493
- return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
1494
- });
1495
- server.tool("draft.update", "\u66F4\u65B0\u8349\u7A3F", draftUpdateSchema.shape, async (input) => {
1496
- const result = await draftUpdate(input);
1497
- return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
1498
- });
1499
- server.tool("draft.delete", "\u5220\u9664\u8349\u7A3F", draftDeleteSchema.shape, async (input) => {
1500
- const result = await draftDelete(input);
1501
- return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
1502
- });
1503
1124
  server.tool("draft.publish", "\u6807\u8BB0\u8349\u7A3F\u4E3A\u5DF2\u53D1\u5E03", draftPublishSchema.shape, async (input) => {
1504
1125
  const result = await draftPublish(input);
1505
1126
  return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
@@ -1512,34 +1133,14 @@ server.tool("analytics.report", "\u83B7\u53D6\u6570\u636E\u5206\u6790\u62A5\u544
1512
1133
  const result = await analyticsReport(input);
1513
1134
  return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
1514
1135
  });
1515
- server.tool("account.list", "\u5217\u51FA\u6240\u6709\u8D26\u53F7", {}, async () => {
1516
- const result = await accountList();
1517
- return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
1518
- });
1519
- server.tool("account.switch", "\u5207\u6362\u5F53\u524D\u8D26\u53F7", accountSwitchSchema.shape, async (input) => {
1520
- const result = await accountSwitch(input);
1521
- return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
1522
- });
1523
- server.tool("account.create", "\u521B\u5EFA\u65B0\u8D26\u53F7", accountCreateSchema.shape, async (input) => {
1136
+ server.tool("account.create", "\u521B\u5EFA\u65B0\u81EA\u5A92\u4F53\u8D26\u53F7", accountCreateSchema.shape, async (input) => {
1524
1137
  const result = await accountCreate(input);
1525
1138
  return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
1526
1139
  });
1527
- 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) => {
1528
- const result = await syncPush(input);
1529
- return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
1530
- });
1531
- 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) => {
1532
- const result = await workflowInit(input);
1533
- return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
1534
- });
1535
- 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 () => {
1536
- const result = await workflowStatus();
1537
- return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
1538
- });
1539
1140
  async function main() {
1540
1141
  const transport = new StdioServerTransport();
1541
1142
  await server.connect(transport);
1542
- console.error("[ClaudeInk MCP] Server started on stdio");
1143
+ console.error("[ClaudeInk MCP] Server started on stdio (13 tools)");
1543
1144
  }
1544
1145
  main().catch((err) => {
1545
1146
  console.error("[ClaudeInk MCP] Fatal error:", err);