@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 +520 -1005
- package/package.json +1 -1
- package/workflow/accounts/_template.yaml +1 -0
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/
|
|
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://
|
|
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
|
|
286
|
-
import { join as
|
|
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
|
|
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 =
|
|
415
|
-
title:
|
|
416
|
-
content:
|
|
417
|
-
url:
|
|
418
|
-
tags:
|
|
419
|
-
folder:
|
|
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 ?
|
|
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
|
|
458
|
-
|
|
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
|
|
511
|
-
|
|
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.
|
|
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 ?
|
|
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
|
|
531
|
-
|
|
532
|
-
|
|
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
|
-
|
|
655
|
-
|
|
656
|
-
|
|
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
|
|
697
|
-
import { join as
|
|
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
|
|
501
|
+
return join6(workflowDir, "drafts");
|
|
704
502
|
}
|
|
705
|
-
return
|
|
503
|
+
return join6(workflowDir, "accounts", account, "drafts");
|
|
706
504
|
}
|
|
707
505
|
function getPublishedDir(account, workflowDir) {
|
|
708
506
|
if (account.toLowerCase() === "auston") {
|
|
709
|
-
return
|
|
507
|
+
return join6(workflowDir, "published");
|
|
710
508
|
}
|
|
711
|
-
return
|
|
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 =
|
|
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
|
|
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 =
|
|
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
|
|
841
|
-
|
|
842
|
-
|
|
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.
|
|
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.
|
|
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/
|
|
721
|
+
// src/tools/sync.ts
|
|
972
722
|
import { z as z5 } from "zod";
|
|
973
|
-
import {
|
|
974
|
-
import { join as
|
|
975
|
-
|
|
976
|
-
|
|
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
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
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
|
|
1002
|
-
|
|
814
|
+
var syncPullSchema = z5.object({
|
|
815
|
+
workDir: z5.string().optional().describe("\u5DE5\u4F5C\u76EE\u5F55")
|
|
1003
816
|
});
|
|
1004
|
-
async function
|
|
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
|
|
823
|
+
const workDir = input.workDir || config.workflowDir;
|
|
1007
824
|
try {
|
|
1008
|
-
const
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
if (
|
|
1012
|
-
|
|
1013
|
-
|
|
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
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
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
|
-
|
|
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:
|
|
1037
|
-
message:
|
|
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,
|
|
1134
|
-
import { join as
|
|
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 =
|
|
1138
|
-
var WORKFLOW_SRC =
|
|
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 =
|
|
1150
|
-
const dest =
|
|
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(
|
|
956
|
+
await mkdir6(join8(cwd, dir), { recursive: true });
|
|
1170
957
|
}
|
|
1171
|
-
results.push("\u2705 \u8FD0\u884C\u65F6\u76EE\u5F55\u5DF2\u521B\u5EFA
|
|
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
|
|
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
|
-
|
|
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}\
|
|
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.
|
|
1041
|
+
version: "0.6.0"
|
|
1475
1042
|
});
|
|
1476
|
-
server.tool("
|
|
1477
|
-
const result = await
|
|
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("
|
|
1485
|
-
const result = await
|
|
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("
|
|
1489
|
-
const result = await
|
|
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.
|
|
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.
|
|
1517
|
-
const result = await
|
|
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.
|
|
1521
|
-
const result = await
|
|
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);
|