@claudeink/mcp-server 0.6.4 → 0.9.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 +1341 -398
- package/package.json +7 -7
package/dist/index.js
CHANGED
|
@@ -6,11 +6,13 @@ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"
|
|
|
6
6
|
|
|
7
7
|
// src/tools/source.ts
|
|
8
8
|
import { z } from "zod";
|
|
9
|
-
import { mkdir as
|
|
10
|
-
import { join as
|
|
9
|
+
import { mkdir as mkdir2, readFile as readFile3 } from "fs/promises";
|
|
10
|
+
import { join as join3 } from "path";
|
|
11
|
+
import { execSync } from "child_process";
|
|
12
|
+
import { readdirSync } from "fs";
|
|
11
13
|
|
|
12
|
-
// src/lib/
|
|
13
|
-
import { readFile, writeFile, mkdir, chmod
|
|
14
|
+
// src/lib/state.ts
|
|
15
|
+
import { readFile, writeFile, mkdir, chmod } from "fs/promises";
|
|
14
16
|
import { join } from "path";
|
|
15
17
|
var _workDirCache = null;
|
|
16
18
|
function setWorkDir(dir) {
|
|
@@ -22,79 +24,170 @@ function getWorkDir() {
|
|
|
22
24
|
function getClaudeinkDir() {
|
|
23
25
|
return join(getWorkDir(), ".claudeink");
|
|
24
26
|
}
|
|
25
|
-
function
|
|
26
|
-
|
|
27
|
-
return {
|
|
28
|
-
credentials: join(dir, "credentials.json"),
|
|
29
|
-
config: join(dir, "config.json"),
|
|
30
|
-
syncState: join(dir, "sync-state.json"),
|
|
31
|
-
tagQueue: join(dir, "tag-queue.json"),
|
|
32
|
-
crawlSchedules: join(dir, "crawl-schedules.json"),
|
|
33
|
-
logs: join(dir, "logs")
|
|
34
|
-
};
|
|
27
|
+
function getStatePath() {
|
|
28
|
+
return join(getClaudeinkDir(), "state.json");
|
|
35
29
|
}
|
|
36
|
-
var
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
30
|
+
var DEFAULT_STATE = {
|
|
31
|
+
credentials: null,
|
|
32
|
+
config: {
|
|
33
|
+
apiBaseUrl: "https://app.claudeink.com",
|
|
34
|
+
workflowDir: ""
|
|
35
|
+
},
|
|
36
|
+
sources: {},
|
|
37
|
+
drafts: {},
|
|
38
|
+
published: {},
|
|
39
|
+
configs: {},
|
|
40
|
+
crawlerSources: {},
|
|
41
|
+
writingMasters: {},
|
|
42
|
+
knowledge: {},
|
|
43
|
+
tagQueue: {
|
|
44
|
+
queue: [],
|
|
45
|
+
failed: []
|
|
46
|
+
},
|
|
47
|
+
lastSyncAt: ""
|
|
51
48
|
};
|
|
52
49
|
async function ensureDir() {
|
|
53
|
-
const paths = getPaths();
|
|
54
50
|
await mkdir(getClaudeinkDir(), { recursive: true });
|
|
55
|
-
await mkdir(paths.logs, { recursive: true });
|
|
56
51
|
}
|
|
57
|
-
async function
|
|
52
|
+
async function readState() {
|
|
53
|
+
const statePath = getStatePath();
|
|
54
|
+
let raw;
|
|
58
55
|
try {
|
|
59
|
-
await
|
|
60
|
-
return true;
|
|
56
|
+
raw = await readFile(statePath, "utf-8");
|
|
61
57
|
} catch {
|
|
62
|
-
|
|
58
|
+
const state = { ...DEFAULT_STATE };
|
|
59
|
+
state.config.workflowDir = getWorkDir();
|
|
60
|
+
return state;
|
|
63
61
|
}
|
|
64
|
-
}
|
|
65
|
-
async function readJson(path, defaultValue) {
|
|
66
62
|
try {
|
|
67
|
-
const
|
|
68
|
-
|
|
63
|
+
const parsed = JSON.parse(raw);
|
|
64
|
+
const state = {
|
|
65
|
+
...DEFAULT_STATE,
|
|
66
|
+
...parsed,
|
|
67
|
+
config: { ...DEFAULT_STATE.config, ...parsed.config },
|
|
68
|
+
tagQueue: { ...DEFAULT_STATE.tagQueue, ...parsed.tagQueue }
|
|
69
|
+
};
|
|
70
|
+
state.config.workflowDir = getWorkDir();
|
|
71
|
+
return state;
|
|
69
72
|
} catch {
|
|
70
|
-
|
|
73
|
+
const backupPath = statePath + ".corrupted." + Date.now();
|
|
74
|
+
try {
|
|
75
|
+
await writeFile(backupPath, raw, "utf-8");
|
|
76
|
+
} catch {
|
|
77
|
+
}
|
|
78
|
+
console.error(`[ClaudeInk] state.json corrupted, backed up to ${backupPath}`);
|
|
79
|
+
const state = { ...DEFAULT_STATE };
|
|
80
|
+
state.config.workflowDir = getWorkDir();
|
|
81
|
+
return state;
|
|
71
82
|
}
|
|
72
83
|
}
|
|
73
|
-
async function
|
|
84
|
+
async function writeState(state) {
|
|
74
85
|
await ensureDir();
|
|
75
|
-
await writeFile(
|
|
76
|
-
|
|
77
|
-
await chmod(path, 384);
|
|
78
|
-
}
|
|
86
|
+
await writeFile(getStatePath(), JSON.stringify(state, null, 2), "utf-8");
|
|
87
|
+
await chmod(getStatePath(), 384);
|
|
79
88
|
}
|
|
80
89
|
async function getCredentials() {
|
|
81
|
-
const
|
|
82
|
-
if (!
|
|
83
|
-
return
|
|
90
|
+
const state = await readState();
|
|
91
|
+
if (!state.credentials) return null;
|
|
92
|
+
return {
|
|
93
|
+
licenseKey: state.credentials.licenseKey,
|
|
94
|
+
token: state.credentials.token,
|
|
95
|
+
userId: state.credentials.userId,
|
|
96
|
+
plan: state.credentials.plan,
|
|
97
|
+
expiresAt: state.credentials.expiresAt
|
|
98
|
+
};
|
|
84
99
|
}
|
|
85
100
|
async function getConfig() {
|
|
86
|
-
const
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
101
|
+
const state = await readState();
|
|
102
|
+
return state.config;
|
|
103
|
+
}
|
|
104
|
+
async function updateSource(id, data) {
|
|
105
|
+
const state = await readState();
|
|
106
|
+
state.sources[id] = data;
|
|
107
|
+
await writeState(state);
|
|
108
|
+
}
|
|
109
|
+
async function updateSourceTags(id, tags) {
|
|
110
|
+
const state = await readState();
|
|
111
|
+
if (state.sources[id]) {
|
|
112
|
+
state.sources[id].tags = tags;
|
|
113
|
+
state.sources[id].updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
114
|
+
}
|
|
115
|
+
await writeState(state);
|
|
116
|
+
}
|
|
117
|
+
async function updateDraft(id, data) {
|
|
118
|
+
const state = await readState();
|
|
119
|
+
state.drafts[id] = data;
|
|
120
|
+
await writeState(state);
|
|
121
|
+
}
|
|
122
|
+
async function moveDraftToPublished(id, data) {
|
|
123
|
+
const state = await readState();
|
|
124
|
+
delete state.drafts[id];
|
|
125
|
+
state.published[id] = data;
|
|
126
|
+
await writeState(state);
|
|
127
|
+
}
|
|
128
|
+
async function updateCrawlerSource(id, data) {
|
|
129
|
+
const state = await readState();
|
|
130
|
+
state.crawlerSources[id] = data;
|
|
131
|
+
await writeState(state);
|
|
132
|
+
}
|
|
133
|
+
async function removeCrawlerSource(id) {
|
|
134
|
+
const state = await readState();
|
|
135
|
+
delete state.crawlerSources[id];
|
|
136
|
+
await writeState(state);
|
|
137
|
+
}
|
|
138
|
+
async function mergeCloudData(cloud) {
|
|
139
|
+
const state = await readState();
|
|
140
|
+
if (cloud.sources !== void 0) state.sources = cloud.sources;
|
|
141
|
+
if (cloud.drafts !== void 0) state.drafts = cloud.drafts;
|
|
142
|
+
if (cloud.published !== void 0) state.published = cloud.published;
|
|
143
|
+
if (cloud.configs !== void 0) state.configs = cloud.configs;
|
|
144
|
+
if (cloud.crawlerSources !== void 0) state.crawlerSources = cloud.crawlerSources;
|
|
145
|
+
if (cloud.writingMasters !== void 0) state.writingMasters = cloud.writingMasters;
|
|
146
|
+
state.lastSyncAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
147
|
+
await writeState(state);
|
|
90
148
|
}
|
|
91
149
|
async function getTagQueue() {
|
|
92
|
-
const
|
|
93
|
-
return
|
|
150
|
+
const state = await readState();
|
|
151
|
+
return {
|
|
152
|
+
queue: state.tagQueue.queue.map((item) => ({
|
|
153
|
+
file: item.file,
|
|
154
|
+
title: item.title,
|
|
155
|
+
addedAt: item.addedAt,
|
|
156
|
+
source: item.source,
|
|
157
|
+
retryCount: item.retries
|
|
158
|
+
})),
|
|
159
|
+
failed: state.tagQueue.failed.map((item) => ({
|
|
160
|
+
file: item.file,
|
|
161
|
+
title: item.title,
|
|
162
|
+
addedAt: item.failedAt,
|
|
163
|
+
source: item.source
|
|
164
|
+
})),
|
|
165
|
+
history: {
|
|
166
|
+
lastProcessed: null,
|
|
167
|
+
totalProcessed: 0,
|
|
168
|
+
avgTagsPerItem: 0
|
|
169
|
+
}
|
|
170
|
+
};
|
|
94
171
|
}
|
|
95
172
|
async function saveTagQueue(queue) {
|
|
96
|
-
const
|
|
97
|
-
|
|
173
|
+
const state = await readState();
|
|
174
|
+
state.tagQueue = {
|
|
175
|
+
queue: queue.queue.map((item) => ({
|
|
176
|
+
file: item.file,
|
|
177
|
+
title: item.title,
|
|
178
|
+
source: item.source,
|
|
179
|
+
addedAt: item.addedAt,
|
|
180
|
+
retries: item.retryCount || 0
|
|
181
|
+
})),
|
|
182
|
+
failed: queue.failed.map((item) => ({
|
|
183
|
+
file: item.file,
|
|
184
|
+
title: item.title,
|
|
185
|
+
source: item.source,
|
|
186
|
+
failedAt: item.addedAt || (/* @__PURE__ */ new Date()).toISOString(),
|
|
187
|
+
reason: "max retries exceeded"
|
|
188
|
+
}))
|
|
189
|
+
};
|
|
190
|
+
await writeState(state);
|
|
98
191
|
}
|
|
99
192
|
|
|
100
193
|
// src/lib/sources.ts
|
|
@@ -117,7 +210,7 @@ async function writeSourceFile(filePath, meta, content) {
|
|
|
117
210
|
const output = matter.stringify(content, meta);
|
|
118
211
|
await writeFile2(filePath, output, "utf-8");
|
|
119
212
|
}
|
|
120
|
-
async function
|
|
213
|
+
async function updateSourceTags2(filePath, tags) {
|
|
121
214
|
const source = await readSourceFile(filePath);
|
|
122
215
|
source.meta.tags = tags;
|
|
123
216
|
source.meta.taggedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
@@ -141,9 +234,8 @@ async function addToTagQueue(file, title, source) {
|
|
|
141
234
|
await saveTagQueue(queue);
|
|
142
235
|
}
|
|
143
236
|
async function getNextTagBatch() {
|
|
144
|
-
const config = await getConfig();
|
|
145
237
|
const queue = await getTagQueue();
|
|
146
|
-
return queue.queue.slice(0,
|
|
238
|
+
return queue.queue.slice(0, 20);
|
|
147
239
|
}
|
|
148
240
|
async function markTagged(file, tagsCount) {
|
|
149
241
|
const queue = await getTagQueue();
|
|
@@ -168,77 +260,35 @@ async function markTagFailed(file) {
|
|
|
168
260
|
await saveTagQueue(queue);
|
|
169
261
|
}
|
|
170
262
|
|
|
171
|
-
// src/lib/
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
sources: {},
|
|
176
|
-
drafts: {},
|
|
177
|
-
published: {},
|
|
178
|
-
configs: {},
|
|
179
|
-
crawlerSources: {},
|
|
180
|
-
writingMasters: {},
|
|
181
|
-
lastSyncAt: ""
|
|
182
|
-
};
|
|
183
|
-
async function getStatePath() {
|
|
184
|
-
const dir = join3(getWorkDir(), ".claudeink");
|
|
185
|
-
await mkdir2(dir, { recursive: true });
|
|
186
|
-
return join3(dir, "state.json");
|
|
263
|
+
// src/lib/push.ts
|
|
264
|
+
function fireAndForgetPush(payload) {
|
|
265
|
+
doPush(payload).catch(() => {
|
|
266
|
+
});
|
|
187
267
|
}
|
|
188
|
-
async function
|
|
268
|
+
async function doPush(partial) {
|
|
269
|
+
const creds = await getCredentials();
|
|
270
|
+
if (!creds?.token) return;
|
|
271
|
+
const config = await getConfig();
|
|
272
|
+
const payload = {
|
|
273
|
+
sources: partial.sources || [],
|
|
274
|
+
drafts: partial.drafts || [],
|
|
275
|
+
published: partial.published || [],
|
|
276
|
+
configs: partial.configs || [],
|
|
277
|
+
crawlerSources: partial.crawlerSources || [],
|
|
278
|
+
analytics: partial.analytics || []
|
|
279
|
+
};
|
|
189
280
|
try {
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
}
|
|
201
|
-
async function updateSource(id, data) {
|
|
202
|
-
const state = await readState();
|
|
203
|
-
state.sources[id] = data;
|
|
204
|
-
await writeState(state);
|
|
205
|
-
}
|
|
206
|
-
async function updateSourceTags2(id, tags) {
|
|
207
|
-
const state = await readState();
|
|
208
|
-
if (state.sources[id]) {
|
|
209
|
-
state.sources[id].tags = tags;
|
|
210
|
-
state.sources[id].updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
281
|
+
await fetch(`${config.apiBaseUrl}/api/sync/batch`, {
|
|
282
|
+
method: "POST",
|
|
283
|
+
headers: {
|
|
284
|
+
"Content-Type": "application/json",
|
|
285
|
+
Authorization: `Bearer ${creds.token}`
|
|
286
|
+
},
|
|
287
|
+
body: JSON.stringify(payload)
|
|
288
|
+
});
|
|
289
|
+
} catch (err) {
|
|
290
|
+
console.error("[ClaudeInk] auto-push failed:", err instanceof Error ? err.message : err);
|
|
211
291
|
}
|
|
212
|
-
await writeState(state);
|
|
213
|
-
}
|
|
214
|
-
async function updateDraft(id, data) {
|
|
215
|
-
const state = await readState();
|
|
216
|
-
state.drafts[id] = data;
|
|
217
|
-
await writeState(state);
|
|
218
|
-
}
|
|
219
|
-
async function moveDraftToPublished(id, data) {
|
|
220
|
-
const state = await readState();
|
|
221
|
-
delete state.drafts[id];
|
|
222
|
-
state.published[id] = data;
|
|
223
|
-
await writeState(state);
|
|
224
|
-
}
|
|
225
|
-
async function updateCrawlerSource(id, data) {
|
|
226
|
-
const state = await readState();
|
|
227
|
-
state.crawlerSources[id] = data;
|
|
228
|
-
await writeState(state);
|
|
229
|
-
}
|
|
230
|
-
async function removeCrawlerSource(id) {
|
|
231
|
-
const state = await readState();
|
|
232
|
-
delete state.crawlerSources[id];
|
|
233
|
-
await writeState(state);
|
|
234
|
-
}
|
|
235
|
-
async function updateLastSyncAt() {
|
|
236
|
-
const state = await readState();
|
|
237
|
-
state.lastSyncAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
238
|
-
await writeState(state);
|
|
239
|
-
}
|
|
240
|
-
async function replaceState(state) {
|
|
241
|
-
await writeState(state);
|
|
242
292
|
}
|
|
243
293
|
|
|
244
294
|
// src/tools/source.ts
|
|
@@ -252,12 +302,12 @@ var sourceAddSchema = z.object({
|
|
|
252
302
|
async function sourceAdd(input) {
|
|
253
303
|
const config = await getConfig();
|
|
254
304
|
const folder = input.folder || "articles";
|
|
255
|
-
const dir =
|
|
256
|
-
await
|
|
305
|
+
const dir = join3(config.workflowDir, "sources", folder);
|
|
306
|
+
await mkdir2(dir, { recursive: true });
|
|
257
307
|
const date = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
258
308
|
const slug = input.title.replace(/[^\u4e00-\u9fa5a-zA-Z0-9\s-]/g, "").replace(/\s+/g, "-").slice(0, 50);
|
|
259
309
|
const filename = `${date}-${slug}.md`;
|
|
260
|
-
const filePath =
|
|
310
|
+
const filePath = join3(dir, filename);
|
|
261
311
|
const meta = {
|
|
262
312
|
title: input.title,
|
|
263
313
|
source: input.url ? (() => {
|
|
@@ -268,7 +318,7 @@ async function sourceAdd(input) {
|
|
|
268
318
|
}
|
|
269
319
|
})() : "manual",
|
|
270
320
|
published: date,
|
|
271
|
-
url: input.url
|
|
321
|
+
...input.url ? { url: input.url } : {}
|
|
272
322
|
};
|
|
273
323
|
if (input.tags && input.tags.length > 0) {
|
|
274
324
|
meta.tags = input.tags;
|
|
@@ -285,6 +335,18 @@ async function sourceAdd(input) {
|
|
|
285
335
|
publishedAt: date,
|
|
286
336
|
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
287
337
|
});
|
|
338
|
+
fireAndForgetPush({
|
|
339
|
+
sources: [{
|
|
340
|
+
id: slug,
|
|
341
|
+
title: input.title,
|
|
342
|
+
source: meta.source,
|
|
343
|
+
tags: input.tags || [],
|
|
344
|
+
sourceUrl: input.url || "",
|
|
345
|
+
coverUrl: "",
|
|
346
|
+
sourceIcon: "",
|
|
347
|
+
createdAt: date
|
|
348
|
+
}]
|
|
349
|
+
});
|
|
288
350
|
return {
|
|
289
351
|
success: true,
|
|
290
352
|
message: `\u7D20\u6750\u5DF2\u5165\u5E93: ${filename}\uFF08\u542B ${input.tags.length} \u4E2A\u6807\u7B7E\uFF09`,
|
|
@@ -304,6 +366,18 @@ async function sourceAdd(input) {
|
|
|
304
366
|
publishedAt: date,
|
|
305
367
|
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
306
368
|
});
|
|
369
|
+
fireAndForgetPush({
|
|
370
|
+
sources: [{
|
|
371
|
+
id: slug,
|
|
372
|
+
title: input.title,
|
|
373
|
+
source: meta.source,
|
|
374
|
+
tags: input.tags || [],
|
|
375
|
+
sourceUrl: input.url || "",
|
|
376
|
+
coverUrl: "",
|
|
377
|
+
sourceIcon: "",
|
|
378
|
+
createdAt: date
|
|
379
|
+
}]
|
|
380
|
+
});
|
|
307
381
|
return {
|
|
308
382
|
success: true,
|
|
309
383
|
message: `\u7D20\u6750\u5DF2\u5165\u5E93: ${filename}\uFF08\u5DF2\u52A0\u5165\u6807\u7B7E\u961F\u5217\uFF0C\u7B49\u5F85 Claude \u6253\u6807\u7B7E\uFF09`,
|
|
@@ -316,10 +390,11 @@ var sourceCrawlSchema = z.object({
|
|
|
316
390
|
});
|
|
317
391
|
async function sourceCrawl(input) {
|
|
318
392
|
const config = await getConfig();
|
|
319
|
-
const
|
|
393
|
+
const crawlerDir = join3(config.workflowDir || process.cwd(), "tools", "crawler");
|
|
394
|
+
const configPath = join3(crawlerDir, "config.json");
|
|
320
395
|
let crawlerConfig;
|
|
321
396
|
try {
|
|
322
|
-
crawlerConfig = JSON.parse(await
|
|
397
|
+
crawlerConfig = JSON.parse(await readFile3(configPath, "utf-8"));
|
|
323
398
|
} catch {
|
|
324
399
|
crawlerConfig = { sources: [] };
|
|
325
400
|
}
|
|
@@ -329,18 +404,97 @@ async function sourceCrawl(input) {
|
|
|
329
404
|
message: "\u6682\u65E0\u914D\u7F6E\u722C\u866B\u6E90\u3002\u8BF7\u5148\u7528 source.subscribe \u6DFB\u52A0\u3002"
|
|
330
405
|
};
|
|
331
406
|
}
|
|
332
|
-
const targets = input.sourceId ? crawlerConfig.sources.filter((s) => s.
|
|
407
|
+
const targets = input.sourceId ? crawlerConfig.sources.filter((s) => s.id === input.sourceId) : crawlerConfig.sources.filter((s) => s.enabled !== false);
|
|
333
408
|
if (targets.length === 0) {
|
|
334
409
|
return {
|
|
335
410
|
success: false,
|
|
336
|
-
message: `\u672A\u627E\u5230\u722C\u866B\u6E90: ${input.sourceId}`
|
|
411
|
+
message: input.sourceId ? `\u672A\u627E\u5230\u722C\u866B\u6E90: ${input.sourceId}` : "\u6CA1\u6709\u5DF2\u542F\u7528\u7684\u722C\u866B\u6E90"
|
|
337
412
|
};
|
|
338
413
|
}
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
414
|
+
const crawlScript = join3(crawlerDir, "crawl.mjs");
|
|
415
|
+
const args = input.sourceId ? `--source ${input.sourceId}` : "";
|
|
416
|
+
try {
|
|
417
|
+
execSync(`node "${crawlScript}" ${args}`, {
|
|
418
|
+
cwd: crawlerDir,
|
|
419
|
+
encoding: "utf-8",
|
|
420
|
+
timeout: 5 * 60 * 1e3,
|
|
421
|
+
// 5 minute timeout
|
|
422
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
423
|
+
});
|
|
424
|
+
let newFiles = [];
|
|
425
|
+
for (const target of targets) {
|
|
426
|
+
const sourceDir = join3(config.workflowDir, "sources", "articles", target.id);
|
|
427
|
+
try {
|
|
428
|
+
const files = readdirSync(sourceDir).filter((f) => f.endsWith(".md"));
|
|
429
|
+
for (const f of files) {
|
|
430
|
+
const filePath = `sources/articles/${target.id}/${f}`;
|
|
431
|
+
newFiles.push({
|
|
432
|
+
file: filePath,
|
|
433
|
+
title: f.replace(/^\d{4}-\d{2}-\d{2}-/, "").replace(/\.md$/, "").replace(/-/g, " "),
|
|
434
|
+
sourceId: target.id
|
|
435
|
+
});
|
|
436
|
+
}
|
|
437
|
+
} catch {
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
let queued = 0;
|
|
441
|
+
for (const item of newFiles) {
|
|
442
|
+
try {
|
|
443
|
+
const fullPath = join3(config.workflowDir, item.file);
|
|
444
|
+
const source = await readSourceFile(fullPath);
|
|
445
|
+
const slug = item.file.replace(/^.*\//, "").replace(/\.md$/, "");
|
|
446
|
+
const date = source.meta.published || (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
447
|
+
await updateSource(slug, {
|
|
448
|
+
title: source.meta.title || item.title,
|
|
449
|
+
source: item.sourceId,
|
|
450
|
+
sourceIcon: "",
|
|
451
|
+
sourceUrl: source.meta.url || "",
|
|
452
|
+
coverUrl: "",
|
|
453
|
+
tags: source.meta.tags || [],
|
|
454
|
+
publishedAt: date,
|
|
455
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
456
|
+
});
|
|
457
|
+
if (!source.meta.tags || source.meta.tags.length === 0) {
|
|
458
|
+
await addToTagQueue(item.file, source.meta.title || item.title, "crawl");
|
|
459
|
+
queued++;
|
|
460
|
+
}
|
|
461
|
+
fireAndForgetPush({
|
|
462
|
+
sources: [{
|
|
463
|
+
id: slug,
|
|
464
|
+
title: source.meta.title || item.title,
|
|
465
|
+
source: item.sourceId,
|
|
466
|
+
tags: source.meta.tags || [],
|
|
467
|
+
sourceUrl: source.meta.url || "",
|
|
468
|
+
coverUrl: "",
|
|
469
|
+
sourceIcon: "",
|
|
470
|
+
createdAt: date
|
|
471
|
+
}]
|
|
472
|
+
});
|
|
473
|
+
} catch {
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
return {
|
|
477
|
+
success: true,
|
|
478
|
+
message: [
|
|
479
|
+
`\u2705 \u722C\u53D6\u5B8C\u6210`,
|
|
480
|
+
` \u76EE\u6807\u6E90: ${targets.map((t) => t.name).join(", ")}`,
|
|
481
|
+
` \u53D1\u73B0\u6587\u7AE0: ${newFiles.length} \u7BC7`,
|
|
482
|
+
queued > 0 ? ` \u52A0\u5165\u6807\u7B7E\u961F\u5217: ${queued} \u7BC7` : "",
|
|
483
|
+
` \u5143\u6570\u636E\u5DF2\u540C\u6B65\u5230\u4E91\u7AEF`
|
|
484
|
+
].filter(Boolean).join("\n"),
|
|
485
|
+
data: {
|
|
486
|
+
sources: targets.map((t) => t.name),
|
|
487
|
+
articles: newFiles.length,
|
|
488
|
+
queued
|
|
489
|
+
}
|
|
490
|
+
};
|
|
491
|
+
} catch (err) {
|
|
492
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
493
|
+
if (message.includes("ETIMEDOUT") || message.includes("timed out")) {
|
|
494
|
+
return { success: false, message: "\u722C\u53D6\u8D85\u65F6\uFF085 \u5206\u949F\u9650\u5236\uFF09\u3002\u8BF7\u5C1D\u8BD5\u6307\u5B9A\u5355\u4E2A\u6E90: source.crawl({ sourceId: 'xxx' })" };
|
|
495
|
+
}
|
|
496
|
+
return { success: false, message: `\u722C\u53D6\u5931\u8D25: ${message.slice(0, 200)}` };
|
|
497
|
+
}
|
|
344
498
|
}
|
|
345
499
|
var sourceTagSchema = z.object({
|
|
346
500
|
mode: z.enum(["queue", "single"]).describe("queue=\u5904\u7406\u6807\u7B7E\u961F\u5217, single=\u5355\u4E2A\u6587\u4EF6\u6253\u6807\u7B7E"),
|
|
@@ -384,7 +538,7 @@ async function sourceTag(input) {
|
|
|
384
538
|
for (const item of batch) {
|
|
385
539
|
try {
|
|
386
540
|
const config = await getConfig();
|
|
387
|
-
const fullPath =
|
|
541
|
+
const fullPath = join3(config.workflowDir, item.file);
|
|
388
542
|
const source = await readSourceFile(fullPath);
|
|
389
543
|
items.push({
|
|
390
544
|
file: item.file,
|
|
@@ -419,10 +573,23 @@ var sourceTagApplySchema = z.object({
|
|
|
419
573
|
async function sourceTagApply(input) {
|
|
420
574
|
try {
|
|
421
575
|
const config = await getConfig();
|
|
422
|
-
const fullPath = input.file.startsWith("/") ? input.file :
|
|
423
|
-
await
|
|
576
|
+
const fullPath = input.file.startsWith("/") ? input.file : join3(config.workflowDir, input.file);
|
|
577
|
+
await updateSourceTags2(fullPath, input.tags);
|
|
424
578
|
await markTagged(input.file, input.tags.length);
|
|
425
|
-
await
|
|
579
|
+
await updateSourceTags(input.file, input.tags);
|
|
580
|
+
const slug = input.file.replace(/^.*\//, "").replace(/\.md$/, "");
|
|
581
|
+
fireAndForgetPush({
|
|
582
|
+
sources: [{
|
|
583
|
+
id: slug,
|
|
584
|
+
title: "",
|
|
585
|
+
source: "",
|
|
586
|
+
tags: input.tags,
|
|
587
|
+
sourceUrl: "",
|
|
588
|
+
coverUrl: "",
|
|
589
|
+
sourceIcon: "",
|
|
590
|
+
createdAt: ""
|
|
591
|
+
}]
|
|
592
|
+
});
|
|
426
593
|
return {
|
|
427
594
|
success: true,
|
|
428
595
|
message: `\u6807\u7B7E\u5DF2\u5199\u5165: ${input.tags.join(", ")}`,
|
|
@@ -439,22 +606,33 @@ async function sourceTagApply(input) {
|
|
|
439
606
|
|
|
440
607
|
// src/tools/subscribe.ts
|
|
441
608
|
import { z as z2 } from "zod";
|
|
442
|
-
import { readFile as
|
|
443
|
-
import { join as
|
|
609
|
+
import { readFile as readFile4, writeFile as writeFile3 } from "fs/promises";
|
|
610
|
+
import { join as join4 } from "path";
|
|
444
611
|
var sourceSubscribeSchema = z2.object({
|
|
445
612
|
action: z2.enum(["add", "remove", "list"]).describe("\u64CD\u4F5C\u7C7B\u578B"),
|
|
446
613
|
id: z2.string().optional().describe("\u8BA2\u9605\u6E90 ID"),
|
|
447
614
|
name: z2.string().optional().describe("\u8BA2\u9605\u6E90\u540D\u79F0"),
|
|
448
615
|
url: z2.string().optional().describe("RSS/\u535A\u5BA2 URL"),
|
|
449
616
|
type: z2.enum(["rss", "blog", "sitemap"]).optional().describe("\u6E90\u7C7B\u578B"),
|
|
450
|
-
icon: z2.string().optional().describe("\u6765\u6E90 icon URL")
|
|
617
|
+
icon: z2.string().optional().describe("\u6765\u6E90 icon URL"),
|
|
618
|
+
crawlConfig: z2.object({
|
|
619
|
+
articleSelector: z2.string(),
|
|
620
|
+
articleUrlPattern: z2.string().optional(),
|
|
621
|
+
pagination: z2.object({
|
|
622
|
+
pattern: z2.string(),
|
|
623
|
+
startPage: z2.number(),
|
|
624
|
+
maxPages: z2.number()
|
|
625
|
+
}).optional(),
|
|
626
|
+
excludePatterns: z2.array(z2.string()).optional(),
|
|
627
|
+
maxArticles: z2.number().optional()
|
|
628
|
+
}).optional().describe("\u722C\u53D6\u9002\u914D\u89C4\u5219\uFF08Claude \u5206\u6790\u751F\u6210\uFF09")
|
|
451
629
|
});
|
|
452
630
|
async function sourceSubscribe(input) {
|
|
453
631
|
const config = await getConfig();
|
|
454
|
-
const configPath =
|
|
632
|
+
const configPath = join4(config.workflowDir || process.cwd(), "tools/crawler/config.json");
|
|
455
633
|
let crawlerConfig;
|
|
456
634
|
try {
|
|
457
|
-
crawlerConfig = JSON.parse(await
|
|
635
|
+
crawlerConfig = JSON.parse(await readFile4(configPath, "utf-8"));
|
|
458
636
|
} catch {
|
|
459
637
|
crawlerConfig = { sources: [] };
|
|
460
638
|
}
|
|
@@ -475,13 +653,20 @@ async function sourceSubscribe(input) {
|
|
|
475
653
|
const newSource = {
|
|
476
654
|
id: input.id,
|
|
477
655
|
name: input.name,
|
|
478
|
-
|
|
479
|
-
type: input.type || "rss",
|
|
656
|
+
blogUrl: input.url,
|
|
657
|
+
type: input.type === "blog" ? "paginated" : input.type || "rss",
|
|
480
658
|
icon: input.icon || "",
|
|
481
659
|
enabled: true
|
|
482
660
|
};
|
|
661
|
+
if (input.crawlConfig) {
|
|
662
|
+
newSource.articleSelector = input.crawlConfig.articleSelector;
|
|
663
|
+
if (input.crawlConfig.articleUrlPattern) newSource.articleUrlPattern = input.crawlConfig.articleUrlPattern;
|
|
664
|
+
if (input.crawlConfig.pagination) newSource.pagination = input.crawlConfig.pagination;
|
|
665
|
+
if (input.crawlConfig.excludePatterns) newSource.excludePatterns = input.crawlConfig.excludePatterns;
|
|
666
|
+
if (input.crawlConfig.maxArticles) newSource.maxArticles = input.crawlConfig.maxArticles;
|
|
667
|
+
}
|
|
483
668
|
crawlerConfig.sources.push(newSource);
|
|
484
|
-
await
|
|
669
|
+
await writeFile3(configPath, JSON.stringify(crawlerConfig, null, 2));
|
|
485
670
|
await updateCrawlerSource(input.id, {
|
|
486
671
|
name: input.name,
|
|
487
672
|
url: input.url,
|
|
@@ -489,6 +674,18 @@ async function sourceSubscribe(input) {
|
|
|
489
674
|
icon: input.icon || "",
|
|
490
675
|
enabled: true
|
|
491
676
|
});
|
|
677
|
+
const stateAfterAdd = await readState();
|
|
678
|
+
fireAndForgetPush({
|
|
679
|
+
crawlerSources: Object.entries(stateAfterAdd.crawlerSources).map(([id, cs]) => ({
|
|
680
|
+
id,
|
|
681
|
+
name: cs.name,
|
|
682
|
+
url: cs.url,
|
|
683
|
+
type: cs.type,
|
|
684
|
+
icon: cs.icon,
|
|
685
|
+
enabled: cs.enabled,
|
|
686
|
+
...id === input.id && input.crawlConfig ? { crawlConfig: input.crawlConfig } : {}
|
|
687
|
+
}))
|
|
688
|
+
});
|
|
492
689
|
return { success: true, message: `\u8BA2\u9605\u6E90 ${input.name} \u5DF2\u6DFB\u52A0` };
|
|
493
690
|
}
|
|
494
691
|
if (input.action === "remove") {
|
|
@@ -496,8 +693,19 @@ async function sourceSubscribe(input) {
|
|
|
496
693
|
return { success: false, message: "\u5220\u9664\u8BA2\u9605\u6E90\u9700\u8981 id \u53C2\u6570" };
|
|
497
694
|
}
|
|
498
695
|
crawlerConfig.sources = crawlerConfig.sources.filter((s) => s.id !== input.id);
|
|
499
|
-
await
|
|
696
|
+
await writeFile3(configPath, JSON.stringify(crawlerConfig, null, 2));
|
|
500
697
|
await removeCrawlerSource(input.id);
|
|
698
|
+
const stateAfterRemove = await readState();
|
|
699
|
+
fireAndForgetPush({
|
|
700
|
+
crawlerSources: Object.entries(stateAfterRemove.crawlerSources).map(([id, cs]) => ({
|
|
701
|
+
id,
|
|
702
|
+
name: cs.name,
|
|
703
|
+
url: cs.url,
|
|
704
|
+
type: cs.type,
|
|
705
|
+
icon: cs.icon,
|
|
706
|
+
enabled: cs.enabled
|
|
707
|
+
}))
|
|
708
|
+
});
|
|
501
709
|
return { success: true, message: `\u8BA2\u9605\u6E90 ${input.id} \u5DF2\u5220\u9664` };
|
|
502
710
|
}
|
|
503
711
|
return { success: false, message: "\u672A\u77E5\u64CD\u4F5C" };
|
|
@@ -507,22 +715,22 @@ async function sourceSubscribe(input) {
|
|
|
507
715
|
import { z as z3 } from "zod";
|
|
508
716
|
|
|
509
717
|
// src/lib/drafts.ts
|
|
510
|
-
import { readFile as
|
|
511
|
-
import { join as
|
|
718
|
+
import { readFile as readFile5, writeFile as writeFile4, mkdir as mkdir3, unlink } from "fs/promises";
|
|
719
|
+
import { join as join5, basename } from "path";
|
|
512
720
|
import matter2 from "gray-matter";
|
|
513
721
|
import { glob as glob2 } from "glob";
|
|
514
722
|
import { randomUUID } from "crypto";
|
|
515
723
|
function getDraftDir(account, workflowDir) {
|
|
516
724
|
if (account.toLowerCase() === "auston") {
|
|
517
|
-
return
|
|
725
|
+
return join5(workflowDir, "drafts");
|
|
518
726
|
}
|
|
519
|
-
return
|
|
727
|
+
return join5(workflowDir, "accounts", account, "drafts");
|
|
520
728
|
}
|
|
521
729
|
function getPublishedDir(account, workflowDir) {
|
|
522
730
|
if (account.toLowerCase() === "auston") {
|
|
523
|
-
return
|
|
731
|
+
return join5(workflowDir, "published");
|
|
524
732
|
}
|
|
525
|
-
return
|
|
733
|
+
return join5(workflowDir, "accounts", account, "published");
|
|
526
734
|
}
|
|
527
735
|
function generateDraftFilename(title) {
|
|
528
736
|
const date = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
@@ -532,10 +740,10 @@ function generateDraftFilename(title) {
|
|
|
532
740
|
async function saveDraft(options) {
|
|
533
741
|
const config = await getConfig();
|
|
534
742
|
const dir = getDraftDir(options.account, config.workflowDir);
|
|
535
|
-
await
|
|
743
|
+
await mkdir3(dir, { recursive: true });
|
|
536
744
|
const id = randomUUID().slice(0, 8);
|
|
537
745
|
const filename = generateDraftFilename(options.title);
|
|
538
|
-
const filePath =
|
|
746
|
+
const filePath = join5(dir, filename);
|
|
539
747
|
const meta = {
|
|
540
748
|
id,
|
|
541
749
|
account: options.account,
|
|
@@ -548,11 +756,25 @@ async function saveDraft(options) {
|
|
|
548
756
|
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
549
757
|
};
|
|
550
758
|
const output = matter2.stringify(options.content, meta);
|
|
551
|
-
await
|
|
759
|
+
await writeFile4(filePath, output, "utf-8");
|
|
552
760
|
return { filePath, meta, content: options.content };
|
|
553
761
|
}
|
|
762
|
+
async function updateDraft2(options) {
|
|
763
|
+
const raw = await readFile5(options.file, "utf-8");
|
|
764
|
+
const { data, content } = matter2(raw);
|
|
765
|
+
const meta = data;
|
|
766
|
+
if (options.title) meta.title = options.title;
|
|
767
|
+
if (options.status) meta.status = options.status;
|
|
768
|
+
if (options.tags) meta.tags = options.tags;
|
|
769
|
+
const newContent = options.content ?? content.trim();
|
|
770
|
+
meta.wordCount = newContent.length;
|
|
771
|
+
meta.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
772
|
+
const output = matter2.stringify(newContent, meta);
|
|
773
|
+
await writeFile4(options.file, output, "utf-8");
|
|
774
|
+
return { filePath: options.file, meta, content: newContent };
|
|
775
|
+
}
|
|
554
776
|
async function publishDraft(options) {
|
|
555
|
-
const raw = await
|
|
777
|
+
const raw = await readFile5(options.file, "utf-8");
|
|
556
778
|
const { data, content } = matter2(raw);
|
|
557
779
|
const meta = data;
|
|
558
780
|
meta.status = "published";
|
|
@@ -561,15 +783,34 @@ async function publishDraft(options) {
|
|
|
561
783
|
meta.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
562
784
|
const config = await getConfig();
|
|
563
785
|
const publishedDir = getPublishedDir(meta.account, config.workflowDir);
|
|
564
|
-
await
|
|
565
|
-
const newPath =
|
|
786
|
+
await mkdir3(publishedDir, { recursive: true });
|
|
787
|
+
const newPath = join5(publishedDir, basename(options.file));
|
|
566
788
|
const output = matter2.stringify(content.trim(), meta);
|
|
567
|
-
await
|
|
789
|
+
await writeFile4(newPath, output, "utf-8");
|
|
568
790
|
await unlink(options.file);
|
|
569
791
|
return { filePath: newPath, meta, content: content.trim() };
|
|
570
792
|
}
|
|
571
793
|
|
|
572
794
|
// src/tools/draft.ts
|
|
795
|
+
function fireAndForgetDraftUpload(draftId, account, title, content) {
|
|
796
|
+
(async () => {
|
|
797
|
+
const creds = await getCredentials();
|
|
798
|
+
if (!creds?.token) return;
|
|
799
|
+
const config = await getConfig();
|
|
800
|
+
try {
|
|
801
|
+
await fetch(`${config.apiBaseUrl}/api/drafts/upload`, {
|
|
802
|
+
method: "POST",
|
|
803
|
+
headers: {
|
|
804
|
+
"Content-Type": "application/json",
|
|
805
|
+
Authorization: `Bearer ${creds.token}`
|
|
806
|
+
},
|
|
807
|
+
body: JSON.stringify({ draftId, account, title, content })
|
|
808
|
+
});
|
|
809
|
+
} catch {
|
|
810
|
+
}
|
|
811
|
+
})().catch(() => {
|
|
812
|
+
});
|
|
813
|
+
}
|
|
573
814
|
var draftSaveSchema = z3.object({
|
|
574
815
|
account: z3.string().describe("\u8D26\u53F7\u540D\u79F0"),
|
|
575
816
|
platform: z3.string().describe("\u5E73\u53F0\u540D\u79F0\uFF0C\u5982 wechat"),
|
|
@@ -597,6 +838,20 @@ async function draftSave(input) {
|
|
|
597
838
|
createdAt: draft.meta.createdAt,
|
|
598
839
|
updatedAt: draft.meta.updatedAt
|
|
599
840
|
});
|
|
841
|
+
fireAndForgetPush({
|
|
842
|
+
drafts: [{
|
|
843
|
+
id: draft.meta.id,
|
|
844
|
+
account: input.account,
|
|
845
|
+
platform: input.platform,
|
|
846
|
+
title: draft.meta.title,
|
|
847
|
+
status: draft.meta.status,
|
|
848
|
+
wordCount: draft.meta.wordCount,
|
|
849
|
+
tags: input.tags || [],
|
|
850
|
+
createdAt: draft.meta.createdAt,
|
|
851
|
+
updatedAt: draft.meta.updatedAt
|
|
852
|
+
}]
|
|
853
|
+
});
|
|
854
|
+
fireAndForgetDraftUpload(draft.meta.id, input.account, draft.meta.title, input.content);
|
|
600
855
|
return {
|
|
601
856
|
success: true,
|
|
602
857
|
message: `\u8349\u7A3F\u5DF2\u4FDD\u5B58: ${draft.meta.title} (${draft.meta.wordCount} \u5B57)`,
|
|
@@ -607,6 +862,55 @@ async function draftSave(input) {
|
|
|
607
862
|
}
|
|
608
863
|
};
|
|
609
864
|
}
|
|
865
|
+
var draftUpdateSchema = z3.object({
|
|
866
|
+
file: z3.string().describe("\u8349\u7A3F\u6587\u4EF6\u8DEF\u5F84"),
|
|
867
|
+
content: z3.string().optional().describe("\u66F4\u65B0\u540E\u7684\u6B63\u6587\uFF08Markdown\uFF09"),
|
|
868
|
+
title: z3.string().optional().describe("\u66F4\u65B0\u540E\u7684\u6807\u9898"),
|
|
869
|
+
status: z3.enum(["draft", "review", "ready"]).optional().describe("\u66F4\u65B0\u72B6\u6001"),
|
|
870
|
+
tags: z3.array(z3.string()).optional().describe("\u66F4\u65B0\u6807\u7B7E")
|
|
871
|
+
});
|
|
872
|
+
async function draftUpdate(input) {
|
|
873
|
+
const draft = await updateDraft2({
|
|
874
|
+
file: input.file,
|
|
875
|
+
content: input.content,
|
|
876
|
+
title: input.title,
|
|
877
|
+
status: input.status,
|
|
878
|
+
tags: input.tags
|
|
879
|
+
});
|
|
880
|
+
await updateDraft(draft.meta.id, {
|
|
881
|
+
title: draft.meta.title,
|
|
882
|
+
account: draft.meta.account,
|
|
883
|
+
platform: draft.meta.platform,
|
|
884
|
+
status: draft.meta.status,
|
|
885
|
+
wordCount: draft.meta.wordCount,
|
|
886
|
+
tags: draft.meta.tags || [],
|
|
887
|
+
createdAt: draft.meta.createdAt,
|
|
888
|
+
updatedAt: draft.meta.updatedAt
|
|
889
|
+
});
|
|
890
|
+
fireAndForgetPush({
|
|
891
|
+
drafts: [{
|
|
892
|
+
id: draft.meta.id,
|
|
893
|
+
account: draft.meta.account,
|
|
894
|
+
platform: draft.meta.platform,
|
|
895
|
+
title: draft.meta.title,
|
|
896
|
+
status: draft.meta.status,
|
|
897
|
+
wordCount: draft.meta.wordCount,
|
|
898
|
+
tags: draft.meta.tags || [],
|
|
899
|
+
createdAt: draft.meta.createdAt,
|
|
900
|
+
updatedAt: draft.meta.updatedAt
|
|
901
|
+
}]
|
|
902
|
+
});
|
|
903
|
+
fireAndForgetDraftUpload(draft.meta.id, draft.meta.account, draft.meta.title, draft.content);
|
|
904
|
+
return {
|
|
905
|
+
success: true,
|
|
906
|
+
message: `\u8349\u7A3F\u5DF2\u66F4\u65B0: ${draft.meta.title} (${draft.meta.wordCount} \u5B57)`,
|
|
907
|
+
data: {
|
|
908
|
+
file: draft.filePath,
|
|
909
|
+
id: draft.meta.id,
|
|
910
|
+
status: draft.meta.status
|
|
911
|
+
}
|
|
912
|
+
};
|
|
913
|
+
}
|
|
610
914
|
var draftPublishSchema = z3.object({
|
|
611
915
|
file: z3.string().describe("\u8349\u7A3F\u6587\u4EF6\u8DEF\u5F84"),
|
|
612
916
|
platformUrl: z3.string().optional().describe("\u5E73\u53F0\u53D1\u5E03\u94FE\u63A5")
|
|
@@ -623,6 +927,16 @@ async function draftPublish(input) {
|
|
|
623
927
|
platformUrl: draft.meta.platformUrl || "",
|
|
624
928
|
publishedAt: draft.meta.publishedAt || (/* @__PURE__ */ new Date()).toISOString()
|
|
625
929
|
});
|
|
930
|
+
fireAndForgetPush({
|
|
931
|
+
published: [{
|
|
932
|
+
id: draft.meta.id,
|
|
933
|
+
account: draft.meta.account,
|
|
934
|
+
platform: draft.meta.platform,
|
|
935
|
+
title: draft.meta.title,
|
|
936
|
+
platformUrl: draft.meta.platformUrl,
|
|
937
|
+
publishedAt: draft.meta.publishedAt || (/* @__PURE__ */ new Date()).toISOString()
|
|
938
|
+
}]
|
|
939
|
+
});
|
|
626
940
|
return {
|
|
627
941
|
success: true,
|
|
628
942
|
message: `\u5DF2\u53D1\u5E03: ${draft.meta.title} \u2192 ${draft.filePath}`,
|
|
@@ -736,159 +1050,42 @@ async function analyticsReport(input) {
|
|
|
736
1050
|
|
|
737
1051
|
// src/tools/sync.ts
|
|
738
1052
|
import { z as z5 } from "zod";
|
|
739
|
-
import { writeFile as
|
|
740
|
-
import { join as
|
|
741
|
-
var
|
|
1053
|
+
import { writeFile as writeFile5, mkdir as mkdir4 } from "fs/promises";
|
|
1054
|
+
import { join as join6 } from "path";
|
|
1055
|
+
var syncSchema = z5.object({
|
|
742
1056
|
workDir: z5.string().optional().describe("\u5DE5\u4F5C\u76EE\u5F55\uFF08\u9ED8\u8BA4\u4F7F\u7528\u914D\u7F6E\u4E2D\u7684 workflowDir\uFF09")
|
|
743
1057
|
});
|
|
744
|
-
async function
|
|
1058
|
+
async function sync(input) {
|
|
745
1059
|
if (input.workDir) setWorkDir(input.workDir);
|
|
746
1060
|
const creds = await getCredentials();
|
|
747
1061
|
if (!creds?.token) {
|
|
748
1062
|
return { success: false, message: "\u672A\u6FC0\u6D3B\uFF0C\u8BF7\u5148\u4F7F\u7528 workflow.init \u6FC0\u6D3B License" };
|
|
749
1063
|
}
|
|
750
|
-
|
|
751
|
-
const state = await readState();
|
|
752
|
-
const payload = {
|
|
753
|
-
sources: Object.entries(state.sources).map(([id, s]) => ({
|
|
754
|
-
id,
|
|
755
|
-
title: s.title,
|
|
756
|
-
source: s.source,
|
|
757
|
-
tags: s.tags,
|
|
758
|
-
sourceUrl: s.sourceUrl,
|
|
759
|
-
coverUrl: s.coverUrl,
|
|
760
|
-
sourceIcon: s.sourceIcon,
|
|
761
|
-
createdAt: s.publishedAt
|
|
762
|
-
})),
|
|
763
|
-
drafts: Object.entries(state.drafts).map(([id, d]) => ({
|
|
764
|
-
id,
|
|
765
|
-
account: d.account,
|
|
766
|
-
platform: d.platform,
|
|
767
|
-
title: d.title,
|
|
768
|
-
status: d.status,
|
|
769
|
-
wordCount: d.wordCount,
|
|
770
|
-
tags: d.tags,
|
|
771
|
-
createdAt: d.createdAt,
|
|
772
|
-
updatedAt: d.updatedAt
|
|
773
|
-
})),
|
|
774
|
-
published: Object.entries(state.published).map(([id, p]) => ({
|
|
775
|
-
id,
|
|
776
|
-
account: p.account,
|
|
777
|
-
platform: p.platform,
|
|
778
|
-
title: p.title,
|
|
779
|
-
platformUrl: p.platformUrl,
|
|
780
|
-
publishedAt: p.publishedAt
|
|
781
|
-
})),
|
|
782
|
-
configs: Object.entries(state.configs).map(([key, c]) => ({
|
|
783
|
-
type: c.type,
|
|
784
|
-
name: c.name,
|
|
785
|
-
displayName: c.displayName,
|
|
786
|
-
content: c.content,
|
|
787
|
-
metadata: c.metadata
|
|
788
|
-
})),
|
|
789
|
-
crawlerSources: Object.entries(state.crawlerSources).map(([id, cs]) => ({
|
|
790
|
-
id,
|
|
791
|
-
name: cs.name,
|
|
792
|
-
url: cs.url,
|
|
793
|
-
type: cs.type,
|
|
794
|
-
icon: cs.icon,
|
|
795
|
-
enabled: cs.enabled
|
|
796
|
-
})),
|
|
797
|
-
analytics: []
|
|
798
|
-
};
|
|
799
|
-
try {
|
|
800
|
-
const res = await fetch(`${config.apiBaseUrl}/api/sync/batch`, {
|
|
801
|
-
method: "POST",
|
|
802
|
-
headers: {
|
|
803
|
-
"Content-Type": "application/json",
|
|
804
|
-
"Authorization": `Bearer ${creds.token}`
|
|
805
|
-
},
|
|
806
|
-
body: JSON.stringify(payload)
|
|
807
|
-
});
|
|
808
|
-
const result = await res.json();
|
|
809
|
-
if (res.ok) {
|
|
810
|
-
await updateLastSyncAt();
|
|
811
|
-
return {
|
|
812
|
-
success: true,
|
|
813
|
-
message: [
|
|
814
|
-
"\u2705 \u540C\u6B65\u5B8C\u6210",
|
|
815
|
-
` \u7D20\u6750: ${payload.sources.length} \u7BC7`,
|
|
816
|
-
` \u8349\u7A3F: ${payload.drafts.length} \u7BC7`,
|
|
817
|
-
` \u5DF2\u53D1\u5E03: ${payload.published.length} \u7BC7`,
|
|
818
|
-
` \u914D\u7F6E: ${payload.configs.length} \u4E2A`,
|
|
819
|
-
` \u8BA2\u9605\u6E90: ${payload.crawlerSources.length} \u4E2A`,
|
|
820
|
-
` \u4E91\u7AEF\u63A5\u53D7: ${result.accepted || 0} \u6761`
|
|
821
|
-
].join("\n"),
|
|
822
|
-
data: { accepted: result.accepted || 0 }
|
|
823
|
-
};
|
|
824
|
-
} else {
|
|
825
|
-
return { success: false, message: `\u540C\u6B65\u5931\u8D25: HTTP ${res.status}` };
|
|
826
|
-
}
|
|
827
|
-
} catch (err) {
|
|
828
|
-
return { success: false, message: `\u540C\u6B65\u7F51\u7EDC\u9519\u8BEF: ${err instanceof Error ? err.message : err}` };
|
|
829
|
-
}
|
|
1064
|
+
return doPull(creds.token, input.workDir);
|
|
830
1065
|
}
|
|
831
|
-
|
|
832
|
-
workDir: z5.string().optional().describe("\u5DE5\u4F5C\u76EE\u5F55")
|
|
833
|
-
});
|
|
834
|
-
async function syncPull(input) {
|
|
835
|
-
if (input.workDir) setWorkDir(input.workDir);
|
|
836
|
-
const creds = await getCredentials();
|
|
837
|
-
if (!creds?.token) {
|
|
838
|
-
return { success: false, message: "\u672A\u6FC0\u6D3B\uFF0C\u8BF7\u5148\u4F7F\u7528 workflow.init \u6FC0\u6D3B License" };
|
|
839
|
-
}
|
|
1066
|
+
async function doPull(token, workDir) {
|
|
840
1067
|
const config = await getConfig();
|
|
841
|
-
const
|
|
1068
|
+
const effectiveWorkDir = workDir || config.workflowDir;
|
|
842
1069
|
try {
|
|
843
1070
|
const res = await fetch(`${config.apiBaseUrl}/api/sync/pull`, {
|
|
844
|
-
headers: {
|
|
1071
|
+
headers: { Authorization: `Bearer ${token}` }
|
|
845
1072
|
});
|
|
846
1073
|
if (!res.ok) {
|
|
847
1074
|
if (res.status === 404) {
|
|
848
|
-
return { success: true, message: "\u4E91\u7AEF\u65E0\
|
|
1075
|
+
return { success: true, message: "\u4E91\u7AEF\u65E0\u6570\u636E" };
|
|
849
1076
|
}
|
|
850
1077
|
return { success: false, message: `\u62C9\u53D6\u5931\u8D25: HTTP ${res.status}` };
|
|
851
1078
|
}
|
|
852
1079
|
const cloudData = await res.json();
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
}
|
|
856
|
-
let updated = 0;
|
|
857
|
-
const state = await readState();
|
|
858
|
-
for (const cfgItem of cloudData.configs) {
|
|
859
|
-
const stateKey = `${cfgItem.type}:${cfgItem.name}`;
|
|
860
|
-
const localUpdatedAt = state.configs[stateKey]?.updatedAt || "";
|
|
861
|
-
if (!localUpdatedAt || cfgItem.updatedAt > localUpdatedAt) {
|
|
862
|
-
let filePath = "";
|
|
863
|
-
if (cfgItem.type === "base_rules") {
|
|
864
|
-
filePath = join7(workDir, "base-rules.md");
|
|
865
|
-
} else if (cfgItem.type === "platform") {
|
|
866
|
-
filePath = join7(workDir, "platforms", `${cfgItem.name}.md`);
|
|
867
|
-
} else if (cfgItem.type === "account") {
|
|
868
|
-
filePath = join7(workDir, "accounts", `${cfgItem.name}.yaml`);
|
|
869
|
-
}
|
|
870
|
-
if (filePath && cfgItem.content) {
|
|
871
|
-
await writeFile6(filePath, cfgItem.content, "utf-8");
|
|
872
|
-
state.configs[stateKey] = {
|
|
873
|
-
type: cfgItem.type,
|
|
874
|
-
name: cfgItem.name,
|
|
875
|
-
displayName: cfgItem.displayName,
|
|
876
|
-
content: cfgItem.content,
|
|
877
|
-
metadata: cfgItem.metadata || {},
|
|
878
|
-
updatedAt: cfgItem.updatedAt
|
|
879
|
-
};
|
|
880
|
-
updated++;
|
|
881
|
-
}
|
|
882
|
-
}
|
|
883
|
-
}
|
|
884
|
-
let crawlerCount = 0;
|
|
885
|
-
state.crawlerSources = {};
|
|
1080
|
+
const counts = { configs: 0, sources: 0, drafts: 0, published: 0, crawlerSources: 0, writingMasters: 0 };
|
|
1081
|
+
const configsRecord = {};
|
|
1082
|
+
const crawlerSourcesRecord = {};
|
|
886
1083
|
for (const cfg of cloudData.configs || []) {
|
|
887
1084
|
if (cfg.type === "crawler" && cfg.content) {
|
|
888
1085
|
try {
|
|
889
1086
|
const sources = JSON.parse(cfg.content);
|
|
890
1087
|
for (const s of sources) {
|
|
891
|
-
|
|
1088
|
+
crawlerSourcesRecord[s.id] = {
|
|
892
1089
|
name: s.name,
|
|
893
1090
|
url: s.url,
|
|
894
1091
|
type: s.type || "rss",
|
|
@@ -896,51 +1093,84 @@ async function syncPull(input) {
|
|
|
896
1093
|
enabled: s.enabled !== false
|
|
897
1094
|
};
|
|
898
1095
|
}
|
|
899
|
-
const crawlerDir =
|
|
900
|
-
await
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
crawlerCount = sources.length;
|
|
1096
|
+
const crawlerDir = join6(effectiveWorkDir, "tools", "crawler");
|
|
1097
|
+
await mkdir4(crawlerDir, { recursive: true });
|
|
1098
|
+
await writeFile5(join6(crawlerDir, "config.json"), JSON.stringify({ sources }, null, 2), "utf-8");
|
|
1099
|
+
counts.crawlerSources = sources.length;
|
|
904
1100
|
} catch (e) {
|
|
905
|
-
console.error("[sync
|
|
1101
|
+
console.error("[sync] Failed to parse crawler sources:", e);
|
|
906
1102
|
}
|
|
907
|
-
}
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
1103
|
+
} else {
|
|
1104
|
+
const stateKey = `${cfg.type}:${cfg.name}`;
|
|
1105
|
+
const state = await readState();
|
|
1106
|
+
const localUpdatedAt = state.configs[stateKey]?.updatedAt || "";
|
|
1107
|
+
if (!localUpdatedAt || cfg.updatedAt > localUpdatedAt) {
|
|
1108
|
+
let filePath = "";
|
|
1109
|
+
if (cfg.type === "base_rules") filePath = join6(effectiveWorkDir, "base-rules.md");
|
|
1110
|
+
else if (cfg.type === "platform") filePath = join6(effectiveWorkDir, "platforms", `${cfg.name}.md`);
|
|
1111
|
+
else if (cfg.type === "account") filePath = join6(effectiveWorkDir, "accounts", `${cfg.name}.yaml`);
|
|
1112
|
+
if (filePath && cfg.content) {
|
|
1113
|
+
await writeFile5(filePath, cfg.content, "utf-8");
|
|
1114
|
+
counts.configs++;
|
|
1115
|
+
}
|
|
1116
|
+
}
|
|
1117
|
+
configsRecord[stateKey] = {
|
|
1118
|
+
type: cfg.type,
|
|
1119
|
+
name: cfg.name,
|
|
1120
|
+
displayName: cfg.displayName,
|
|
1121
|
+
content: cfg.content,
|
|
1122
|
+
metadata: cfg.metadata || {},
|
|
1123
|
+
updatedAt: cfg.updatedAt
|
|
916
1124
|
};
|
|
917
1125
|
}
|
|
918
1126
|
}
|
|
919
|
-
|
|
920
|
-
|
|
1127
|
+
counts.sources = Object.keys(cloudData.sources || {}).length;
|
|
1128
|
+
counts.drafts = Object.keys(cloudData.drafts || {}).length;
|
|
1129
|
+
counts.published = Object.keys(cloudData.published || {}).length;
|
|
1130
|
+
counts.writingMasters = Object.keys(cloudData.writingMasters || {}).length;
|
|
1131
|
+
await mergeCloudData({
|
|
1132
|
+
sources: cloudData.sources || {},
|
|
1133
|
+
drafts: cloudData.drafts || {},
|
|
1134
|
+
published: cloudData.published || {},
|
|
1135
|
+
configs: configsRecord,
|
|
1136
|
+
crawlerSources: crawlerSourcesRecord,
|
|
1137
|
+
writingMasters: cloudData.writingMasters || {}
|
|
1138
|
+
});
|
|
1139
|
+
const parts = [
|
|
1140
|
+
counts.sources > 0 ? `\u7D20\u6750 ${counts.sources}` : "",
|
|
1141
|
+
counts.drafts > 0 ? `\u8349\u7A3F ${counts.drafts}` : "",
|
|
1142
|
+
counts.published > 0 ? `\u5DF2\u53D1\u5E03 ${counts.published}` : "",
|
|
1143
|
+
counts.configs > 0 ? `\u914D\u7F6E\u66F4\u65B0 ${counts.configs}` : "",
|
|
1144
|
+
counts.crawlerSources > 0 ? `\u8BA2\u9605\u6E90 ${counts.crawlerSources}` : "",
|
|
1145
|
+
counts.writingMasters > 0 ? `\u5199\u4F5C\u5927\u5E08 ${counts.writingMasters}` : ""
|
|
1146
|
+
].filter(Boolean);
|
|
921
1147
|
return {
|
|
922
1148
|
success: true,
|
|
923
|
-
message:
|
|
924
|
-
|
|
925
|
-
crawlerCount > 0 ? ` \u8BA2\u9605\u6E90: ${crawlerCount} \u4E2A` : "",
|
|
926
|
-
masterCount > 0 ? ` \u5199\u4F5C\u5927\u5E08: ${masterCount} \u4E2A` : ""
|
|
927
|
-
].filter(Boolean).join("\n") : "\u672C\u5730\u914D\u7F6E\u5DF2\u662F\u6700\u65B0\uFF0C\u65E0\u9700\u66F4\u65B0",
|
|
928
|
-
data: { updated, crawlerSources: crawlerCount, writingMasters: masterCount }
|
|
1149
|
+
message: parts.length > 0 ? `\u2705 \u5DF2\u540C\u6B65: ${parts.join(", ")}` : "\u4E91\u7AEF\u65E0\u6570\u636E",
|
|
1150
|
+
data: counts
|
|
929
1151
|
};
|
|
930
1152
|
} catch (err) {
|
|
931
|
-
return { success: false, message: `\
|
|
1153
|
+
return { success: false, message: `\u540C\u6B65\u5931\u8D25: ${err instanceof Error ? err.message : err}` };
|
|
932
1154
|
}
|
|
933
1155
|
}
|
|
1156
|
+
async function syncPull(input) {
|
|
1157
|
+
if (input.workDir) setWorkDir(input.workDir);
|
|
1158
|
+
const creds = await getCredentials();
|
|
1159
|
+
if (!creds?.token) {
|
|
1160
|
+
return { success: false, message: "\u672A\u6FC0\u6D3B" };
|
|
1161
|
+
}
|
|
1162
|
+
return doPull(creds.token, input.workDir);
|
|
1163
|
+
}
|
|
934
1164
|
|
|
935
1165
|
// src/tools/workflow.ts
|
|
936
1166
|
import { z as z6 } from "zod";
|
|
937
|
-
import { cp, mkdir as
|
|
938
|
-
import { join as
|
|
1167
|
+
import { cp, mkdir as mkdir5, access as access2, unlink as unlink2 } from "fs/promises";
|
|
1168
|
+
import { join as join7, dirname } from "path";
|
|
939
1169
|
import { fileURLToPath } from "url";
|
|
940
1170
|
var DEFAULT_API_BASE_URL = "https://app.claudeink.com";
|
|
941
1171
|
var __filename = fileURLToPath(import.meta.url);
|
|
942
1172
|
var __dirname = dirname(__filename);
|
|
943
|
-
var WORKFLOW_SRC =
|
|
1173
|
+
var WORKFLOW_SRC = join7(__dirname, "..", "workflow");
|
|
944
1174
|
var workflowInitSchema = z6.object({
|
|
945
1175
|
workDir: z6.string().describe("\u5DE5\u4F5C\u6D41\u521D\u59CB\u5316\u76EE\u6807\u76EE\u5F55\uFF08\u7EDD\u5BF9\u8DEF\u5F84\uFF09"),
|
|
946
1176
|
licenseKey: z6.string().optional().describe("License Key\uFF08\u53EF\u9009\uFF0C\u4F20\u5165\u5219\u81EA\u52A8\u6FC0\u6D3B\uFF09")
|
|
@@ -952,8 +1182,8 @@ async function workflowInit(input) {
|
|
|
952
1182
|
setWorkDir(cwd);
|
|
953
1183
|
const items = ["CLAUDE.md", "base-rules.md", "platforms", "accounts", "tools"];
|
|
954
1184
|
for (const item of items) {
|
|
955
|
-
const src =
|
|
956
|
-
const dest =
|
|
1185
|
+
const src = join7(WORKFLOW_SRC, item);
|
|
1186
|
+
const dest = join7(cwd, item);
|
|
957
1187
|
try {
|
|
958
1188
|
await access2(dest);
|
|
959
1189
|
results.push(`\u23ED\uFE0F ${item} \u5DF2\u5B58\u5728\uFF0C\u8DF3\u8FC7`);
|
|
@@ -962,68 +1192,39 @@ async function workflowInit(input) {
|
|
|
962
1192
|
results.push(`\u2705 ${item}`);
|
|
963
1193
|
}
|
|
964
1194
|
}
|
|
965
|
-
const claudeinkDir =
|
|
966
|
-
const dirs = [
|
|
967
|
-
"sources/articles",
|
|
968
|
-
"sources/video-transcripts",
|
|
969
|
-
"sources/notes",
|
|
970
|
-
"sources/data",
|
|
971
|
-
"sources/daily",
|
|
972
|
-
"templates"
|
|
973
|
-
];
|
|
1195
|
+
const claudeinkDir = join7(cwd, ".claudeink");
|
|
1196
|
+
const dirs = ["sources/articles", "sources/video-transcripts", "sources/notes", "sources/data", "sources/daily", "templates"];
|
|
974
1197
|
for (const dir of dirs) {
|
|
975
|
-
await
|
|
1198
|
+
await mkdir5(join7(cwd, dir), { recursive: true });
|
|
976
1199
|
}
|
|
977
|
-
await
|
|
1200
|
+
await mkdir5(claudeinkDir, { recursive: true });
|
|
978
1201
|
results.push("\u2705 \u8FD0\u884C\u65F6\u76EE\u5F55\u5DF2\u521B\u5EFA");
|
|
979
|
-
const
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
apiBaseUrl: DEFAULT_API_BASE_URL,
|
|
983
|
-
syncIntervalMs: 3e5,
|
|
984
|
-
heartbeatIntervalMs: 3e5,
|
|
985
|
-
maxTagQueueBatch: 20,
|
|
986
|
-
workflowDir: cwd
|
|
987
|
-
}, null, 2), "utf-8");
|
|
988
|
-
results.push("\u2705 config.json");
|
|
1202
|
+
const state = await readState();
|
|
1203
|
+
state.config.workflowDir = cwd;
|
|
1204
|
+
const oldCredsPath = join7(claudeinkDir, "credentials.json");
|
|
989
1205
|
try {
|
|
990
|
-
await access2(
|
|
991
|
-
|
|
1206
|
+
await access2(oldCredsPath);
|
|
1207
|
+
await unlink2(oldCredsPath);
|
|
1208
|
+
results.push("\u{1F9F9} \u5DF2\u6E05\u7406\u65E7\u7248 credentials.json");
|
|
992
1209
|
} catch {
|
|
993
|
-
const emptyState = {
|
|
994
|
-
sources: {},
|
|
995
|
-
drafts: {},
|
|
996
|
-
published: {},
|
|
997
|
-
configs: {},
|
|
998
|
-
crawlerSources: {},
|
|
999
|
-
writingMasters: {},
|
|
1000
|
-
lastSyncAt: ""
|
|
1001
|
-
};
|
|
1002
|
-
await writeFile7(statePath, JSON.stringify(emptyState, null, 2), "utf-8");
|
|
1003
|
-
results.push("\u2705 state.json");
|
|
1004
1210
|
}
|
|
1005
1211
|
let activated = false;
|
|
1006
1212
|
if (input.licenseKey) {
|
|
1007
1213
|
try {
|
|
1008
|
-
const
|
|
1009
|
-
const res = await fetch(activateUrl, {
|
|
1214
|
+
const res = await fetch(`${DEFAULT_API_BASE_URL}/api/auth/activate`, {
|
|
1010
1215
|
method: "POST",
|
|
1011
1216
|
headers: { "Content-Type": "application/json" },
|
|
1012
1217
|
body: JSON.stringify({ key: input.licenseKey })
|
|
1013
1218
|
});
|
|
1014
1219
|
const data = await res.json();
|
|
1015
1220
|
if (data.userId) {
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
expiresAt: data.expiresAt
|
|
1024
|
-
}, null, 2),
|
|
1025
|
-
{ mode: 384 }
|
|
1026
|
-
);
|
|
1221
|
+
state.credentials = {
|
|
1222
|
+
licenseKey: input.licenseKey,
|
|
1223
|
+
token: data.token,
|
|
1224
|
+
userId: data.userId,
|
|
1225
|
+
plan: data.plan,
|
|
1226
|
+
expiresAt: data.expiresAt
|
|
1227
|
+
};
|
|
1027
1228
|
results.push(`\u2705 License \u6FC0\u6D3B\u6210\u529F\uFF08\u5957\u9910: ${data.plan}\uFF09`);
|
|
1028
1229
|
activated = true;
|
|
1029
1230
|
} else {
|
|
@@ -1033,25 +1234,30 @@ async function workflowInit(input) {
|
|
|
1033
1234
|
results.push(`\u26A0\uFE0F \u6FC0\u6D3B\u7F51\u7EDC\u9519\u8BEF: ${err instanceof Error ? err.message : err}`);
|
|
1034
1235
|
}
|
|
1035
1236
|
}
|
|
1237
|
+
await writeState(state);
|
|
1238
|
+
results.push("\u2705 state.json");
|
|
1036
1239
|
if (activated) {
|
|
1037
1240
|
try {
|
|
1038
1241
|
const pullResult = await syncPull({ workDir: cwd });
|
|
1039
|
-
if (pullResult.success && pullResult.data
|
|
1040
|
-
|
|
1242
|
+
if (pullResult.success && pullResult.data) {
|
|
1243
|
+
const d = pullResult.data;
|
|
1244
|
+
const total = (d.sources || 0) + (d.drafts || 0) + (d.published || 0) + (d.configs || 0);
|
|
1245
|
+
if (total > 0) {
|
|
1246
|
+
results.push("\u2705 \u5DF2\u4ECE\u4E91\u7AEF\u540C\u6B65\u6570\u636E");
|
|
1247
|
+
} else {
|
|
1248
|
+
results.push("\u2139\uFE0F \u4E91\u7AEF\u65E0\u5DF2\u6709\u6570\u636E\uFF0C\u4F7F\u7528\u9ED8\u8BA4\u6A21\u677F");
|
|
1249
|
+
}
|
|
1041
1250
|
} else {
|
|
1042
|
-
results.push("\u2139\uFE0F \u4E91\u7AEF\u65E0\u5DF2\u6709\
|
|
1251
|
+
results.push("\u2139\uFE0F \u4E91\u7AEF\u65E0\u5DF2\u6709\u6570\u636E\uFF0C\u4F7F\u7528\u9ED8\u8BA4\u6A21\u677F");
|
|
1043
1252
|
}
|
|
1044
1253
|
} catch {
|
|
1045
|
-
results.push("\u2139\uFE0F \u4E91\u7AEF\
|
|
1254
|
+
results.push("\u2139\uFE0F \u4E91\u7AEF\u540C\u6B65\u8DF3\u8FC7");
|
|
1046
1255
|
}
|
|
1047
1256
|
}
|
|
1048
1257
|
try {
|
|
1049
|
-
await access2(
|
|
1050
|
-
const { execSync } = await import("child_process");
|
|
1051
|
-
|
|
1052
|
-
cwd: join8(cwd, "tools", "crawler"),
|
|
1053
|
-
stdio: "pipe"
|
|
1054
|
-
});
|
|
1258
|
+
await access2(join7(cwd, "tools", "crawler", "package.json"));
|
|
1259
|
+
const { execSync: execSync2 } = await import("child_process");
|
|
1260
|
+
execSync2("npm install --silent", { cwd: join7(cwd, "tools", "crawler"), stdio: "pipe" });
|
|
1055
1261
|
results.push("\u2705 \u722C\u866B\u4F9D\u8D56\u5DF2\u5B89\u88C5");
|
|
1056
1262
|
} catch {
|
|
1057
1263
|
}
|
|
@@ -1075,23 +1281,725 @@ async function workflowInit(input) {
|
|
|
1075
1281
|
}
|
|
1076
1282
|
}
|
|
1077
1283
|
|
|
1284
|
+
// src/tools/ink.ts
|
|
1285
|
+
import { z as z7 } from "zod";
|
|
1286
|
+
|
|
1287
|
+
// src/lib/knowledge.ts
|
|
1288
|
+
import { readFile as readFile6, writeFile as writeFile6, mkdir as mkdir6, unlink as unlink3 } from "fs/promises";
|
|
1289
|
+
import { join as join8 } from "path";
|
|
1290
|
+
import matter3 from "gray-matter";
|
|
1291
|
+
function getKnowledgeDir() {
|
|
1292
|
+
return join8(getWorkDir(), ".claudeink", "knowledge");
|
|
1293
|
+
}
|
|
1294
|
+
function getIndexPath() {
|
|
1295
|
+
return join8(getWorkDir(), ".claudeink", "knowledge", "index.json");
|
|
1296
|
+
}
|
|
1297
|
+
async function generateId() {
|
|
1298
|
+
const now = /* @__PURE__ */ new Date();
|
|
1299
|
+
const dateStr = now.toISOString().slice(0, 10).replace(/-/g, "");
|
|
1300
|
+
const index = await readIndex();
|
|
1301
|
+
let maxSeq = 0;
|
|
1302
|
+
const prefix = `k_${dateStr}_`;
|
|
1303
|
+
for (const id of Object.keys(index.entries)) {
|
|
1304
|
+
if (id.startsWith(prefix)) {
|
|
1305
|
+
const seq = parseInt(id.slice(prefix.length), 10);
|
|
1306
|
+
if (seq > maxSeq) maxSeq = seq;
|
|
1307
|
+
}
|
|
1308
|
+
}
|
|
1309
|
+
const nextSeq = String(maxSeq + 1).padStart(3, "0");
|
|
1310
|
+
return `${prefix}${nextSeq}`;
|
|
1311
|
+
}
|
|
1312
|
+
function toSlug(title) {
|
|
1313
|
+
return title.replace(/[^\u4e00-\u9fa5a-zA-Z0-9\s-]/g, "").replace(/\s+/g, "-").slice(0, 50).replace(/-+$/, "");
|
|
1314
|
+
}
|
|
1315
|
+
async function readIndex() {
|
|
1316
|
+
try {
|
|
1317
|
+
const raw = await readFile6(getIndexPath(), "utf-8");
|
|
1318
|
+
return JSON.parse(raw);
|
|
1319
|
+
} catch {
|
|
1320
|
+
return { entries: {}, updated_at: (/* @__PURE__ */ new Date()).toISOString() };
|
|
1321
|
+
}
|
|
1322
|
+
}
|
|
1323
|
+
async function saveIndex(index) {
|
|
1324
|
+
index.updated_at = (/* @__PURE__ */ new Date()).toISOString();
|
|
1325
|
+
const dir = getKnowledgeDir();
|
|
1326
|
+
await mkdir6(dir, { recursive: true });
|
|
1327
|
+
await writeFile6(getIndexPath(), JSON.stringify(index, null, 2), "utf-8");
|
|
1328
|
+
}
|
|
1329
|
+
async function addToIndex(entry) {
|
|
1330
|
+
const index = await readIndex();
|
|
1331
|
+
index.entries[entry.id] = entry;
|
|
1332
|
+
await saveIndex(index);
|
|
1333
|
+
}
|
|
1334
|
+
async function removeFromIndex(id) {
|
|
1335
|
+
const index = await readIndex();
|
|
1336
|
+
delete index.entries[id];
|
|
1337
|
+
await saveIndex(index);
|
|
1338
|
+
}
|
|
1339
|
+
async function writeKnowledgeFile(meta, content) {
|
|
1340
|
+
const now = /* @__PURE__ */ new Date();
|
|
1341
|
+
const monthDir = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, "0")}`;
|
|
1342
|
+
const dir = join8(getKnowledgeDir(), monthDir);
|
|
1343
|
+
await mkdir6(dir, { recursive: true });
|
|
1344
|
+
const slug = toSlug(meta.title) || meta.id;
|
|
1345
|
+
const filename = `${now.toISOString().slice(0, 10)}-${slug}.md`;
|
|
1346
|
+
const filePath = join8(dir, filename);
|
|
1347
|
+
const frontmatter = {
|
|
1348
|
+
id: meta.id,
|
|
1349
|
+
title: meta.title,
|
|
1350
|
+
tags: meta.tags,
|
|
1351
|
+
category: meta.category,
|
|
1352
|
+
source_type: meta.source_type,
|
|
1353
|
+
created_at: meta.created_at,
|
|
1354
|
+
updated_at: meta.updated_at
|
|
1355
|
+
};
|
|
1356
|
+
if (meta.url) frontmatter.url = meta.url;
|
|
1357
|
+
const output = matter3.stringify(content, frontmatter);
|
|
1358
|
+
await writeFile6(filePath, output, "utf-8");
|
|
1359
|
+
return join8(monthDir, filename);
|
|
1360
|
+
}
|
|
1361
|
+
async function readKnowledgeFile(id) {
|
|
1362
|
+
const index = await readIndex();
|
|
1363
|
+
const entry = index.entries[id];
|
|
1364
|
+
if (!entry) return null;
|
|
1365
|
+
const filePath = join8(getKnowledgeDir(), entry.file_path);
|
|
1366
|
+
try {
|
|
1367
|
+
const raw = await readFile6(filePath, "utf-8");
|
|
1368
|
+
const { data, content } = matter3(raw);
|
|
1369
|
+
return {
|
|
1370
|
+
meta: data,
|
|
1371
|
+
content: content.trim(),
|
|
1372
|
+
filePath
|
|
1373
|
+
};
|
|
1374
|
+
} catch {
|
|
1375
|
+
return null;
|
|
1376
|
+
}
|
|
1377
|
+
}
|
|
1378
|
+
async function updateKnowledgeFile(id, updates) {
|
|
1379
|
+
const file = await readKnowledgeFile(id);
|
|
1380
|
+
if (!file) return null;
|
|
1381
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1382
|
+
const meta = { ...file.meta };
|
|
1383
|
+
if (updates.title !== void 0) meta.title = updates.title;
|
|
1384
|
+
if (updates.tags !== void 0) meta.tags = updates.tags;
|
|
1385
|
+
if (updates.category !== void 0) meta.category = updates.category;
|
|
1386
|
+
meta.updated_at = now;
|
|
1387
|
+
const content = updates.content !== void 0 ? updates.content : file.content;
|
|
1388
|
+
const frontmatter = {
|
|
1389
|
+
id: meta.id,
|
|
1390
|
+
title: meta.title,
|
|
1391
|
+
tags: meta.tags,
|
|
1392
|
+
category: meta.category,
|
|
1393
|
+
source_type: meta.source_type,
|
|
1394
|
+
created_at: meta.created_at,
|
|
1395
|
+
updated_at: meta.updated_at
|
|
1396
|
+
};
|
|
1397
|
+
if (meta.url) frontmatter.url = meta.url;
|
|
1398
|
+
const output = matter3.stringify(content, frontmatter);
|
|
1399
|
+
await writeFile6(file.filePath, output, "utf-8");
|
|
1400
|
+
const index = await readIndex();
|
|
1401
|
+
const entry = index.entries[id];
|
|
1402
|
+
if (entry) {
|
|
1403
|
+
entry.title = meta.title;
|
|
1404
|
+
entry.tags = meta.tags;
|
|
1405
|
+
entry.category = meta.category;
|
|
1406
|
+
entry.preview = generatePreview(content);
|
|
1407
|
+
entry.updated_at = now;
|
|
1408
|
+
await saveIndex(index);
|
|
1409
|
+
}
|
|
1410
|
+
return { meta, content, filePath: file.filePath };
|
|
1411
|
+
}
|
|
1412
|
+
async function deleteKnowledgeFile(id) {
|
|
1413
|
+
const index = await readIndex();
|
|
1414
|
+
const entry = index.entries[id];
|
|
1415
|
+
if (!entry) return false;
|
|
1416
|
+
const filePath = join8(getKnowledgeDir(), entry.file_path);
|
|
1417
|
+
try {
|
|
1418
|
+
await unlink3(filePath);
|
|
1419
|
+
} catch {
|
|
1420
|
+
}
|
|
1421
|
+
await removeFromIndex(id);
|
|
1422
|
+
return true;
|
|
1423
|
+
}
|
|
1424
|
+
function generatePreview(content, maxLen = 200) {
|
|
1425
|
+
return content.replace(/^#+\s.+$/gm, "").replace(/\n+/g, " ").trim().slice(0, maxLen);
|
|
1426
|
+
}
|
|
1427
|
+
function searchKnowledge(index, filters) {
|
|
1428
|
+
let entries = Object.values(index.entries);
|
|
1429
|
+
if (filters.tags && filters.tags.length > 0) {
|
|
1430
|
+
const filterTags = filters.tags.map((t) => t.toLowerCase());
|
|
1431
|
+
entries = entries.filter(
|
|
1432
|
+
(e) => e.tags.some((t) => filterTags.includes(t.toLowerCase()))
|
|
1433
|
+
);
|
|
1434
|
+
}
|
|
1435
|
+
if (filters.category) {
|
|
1436
|
+
entries = entries.filter((e) => e.category === filters.category);
|
|
1437
|
+
}
|
|
1438
|
+
if (filters.date_from) {
|
|
1439
|
+
entries = entries.filter((e) => e.created_at >= filters.date_from);
|
|
1440
|
+
}
|
|
1441
|
+
if (filters.date_to) {
|
|
1442
|
+
entries = entries.filter((e) => e.created_at <= filters.date_to);
|
|
1443
|
+
}
|
|
1444
|
+
if (filters.query) {
|
|
1445
|
+
const q = filters.query.toLowerCase();
|
|
1446
|
+
entries = entries.map((e) => {
|
|
1447
|
+
let score = 0;
|
|
1448
|
+
if (e.title.toLowerCase().includes(q)) score += 10;
|
|
1449
|
+
if (e.preview.toLowerCase().includes(q)) score += 5;
|
|
1450
|
+
if (e.tags.some((t) => t.toLowerCase().includes(q))) score += 3;
|
|
1451
|
+
return { ...e, _score: score };
|
|
1452
|
+
}).filter((e) => e._score > 0).sort((a, b) => b._score - a._score);
|
|
1453
|
+
} else {
|
|
1454
|
+
entries.sort((a, b) => b.created_at.localeCompare(a.created_at));
|
|
1455
|
+
}
|
|
1456
|
+
const total = entries.length;
|
|
1457
|
+
const limit = filters.limit || 10;
|
|
1458
|
+
const results = entries.slice(0, limit).map((e) => ({
|
|
1459
|
+
id: e.id,
|
|
1460
|
+
title: e.title,
|
|
1461
|
+
tags: e.tags,
|
|
1462
|
+
category: e.category,
|
|
1463
|
+
created_at: e.created_at,
|
|
1464
|
+
preview: e.preview
|
|
1465
|
+
}));
|
|
1466
|
+
return { results, total };
|
|
1467
|
+
}
|
|
1468
|
+
async function saveKnowledge(params) {
|
|
1469
|
+
const id = await generateId();
|
|
1470
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1471
|
+
const meta = {
|
|
1472
|
+
id,
|
|
1473
|
+
title: params.title,
|
|
1474
|
+
tags: params.tags,
|
|
1475
|
+
category: params.category,
|
|
1476
|
+
source_type: params.source_type,
|
|
1477
|
+
url: params.url,
|
|
1478
|
+
created_at: now,
|
|
1479
|
+
updated_at: now
|
|
1480
|
+
};
|
|
1481
|
+
const relativePath = await writeKnowledgeFile(meta, params.content);
|
|
1482
|
+
await addToIndex({
|
|
1483
|
+
id,
|
|
1484
|
+
title: params.title,
|
|
1485
|
+
tags: params.tags,
|
|
1486
|
+
category: params.category,
|
|
1487
|
+
source_type: params.source_type,
|
|
1488
|
+
preview: generatePreview(params.content),
|
|
1489
|
+
file_path: relativePath,
|
|
1490
|
+
created_at: now,
|
|
1491
|
+
updated_at: now
|
|
1492
|
+
});
|
|
1493
|
+
return { id, filePath: relativePath };
|
|
1494
|
+
}
|
|
1495
|
+
async function readMultipleKnowledgeFiles(ids) {
|
|
1496
|
+
const results = [];
|
|
1497
|
+
for (const id of ids) {
|
|
1498
|
+
const file = await readKnowledgeFile(id);
|
|
1499
|
+
if (file) results.push(file);
|
|
1500
|
+
}
|
|
1501
|
+
return results;
|
|
1502
|
+
}
|
|
1503
|
+
function getAllTags(index) {
|
|
1504
|
+
const tagCounts = {};
|
|
1505
|
+
for (const entry of Object.values(index.entries)) {
|
|
1506
|
+
for (const tag of entry.tags) {
|
|
1507
|
+
tagCounts[tag] = (tagCounts[tag] || 0) + 1;
|
|
1508
|
+
}
|
|
1509
|
+
}
|
|
1510
|
+
return Object.entries(tagCounts).map(([name, count]) => ({ name, count })).sort((a, b) => b.count - a.count);
|
|
1511
|
+
}
|
|
1512
|
+
async function renameTag(oldTag, newTag) {
|
|
1513
|
+
const index = await readIndex();
|
|
1514
|
+
let count = 0;
|
|
1515
|
+
for (const entry of Object.values(index.entries)) {
|
|
1516
|
+
const idx = entry.tags.indexOf(oldTag);
|
|
1517
|
+
if (idx !== -1) {
|
|
1518
|
+
entry.tags[idx] = newTag;
|
|
1519
|
+
count++;
|
|
1520
|
+
const file = await readKnowledgeFile(entry.id);
|
|
1521
|
+
if (file) {
|
|
1522
|
+
const tagIdx = file.meta.tags.indexOf(oldTag);
|
|
1523
|
+
if (tagIdx !== -1) {
|
|
1524
|
+
file.meta.tags[tagIdx] = newTag;
|
|
1525
|
+
await updateKnowledgeFile(entry.id, { tags: file.meta.tags });
|
|
1526
|
+
}
|
|
1527
|
+
}
|
|
1528
|
+
}
|
|
1529
|
+
}
|
|
1530
|
+
if (count > 0) {
|
|
1531
|
+
await saveIndex(index);
|
|
1532
|
+
}
|
|
1533
|
+
return count;
|
|
1534
|
+
}
|
|
1535
|
+
async function mergeTags(sourceTags, targetTag) {
|
|
1536
|
+
const index = await readIndex();
|
|
1537
|
+
let count = 0;
|
|
1538
|
+
for (const entry of Object.values(index.entries)) {
|
|
1539
|
+
const hasSource = entry.tags.some((t) => sourceTags.includes(t));
|
|
1540
|
+
if (hasSource) {
|
|
1541
|
+
const newTags = entry.tags.filter((t) => !sourceTags.includes(t));
|
|
1542
|
+
if (!newTags.includes(targetTag)) {
|
|
1543
|
+
newTags.push(targetTag);
|
|
1544
|
+
}
|
|
1545
|
+
entry.tags = newTags;
|
|
1546
|
+
count++;
|
|
1547
|
+
await updateKnowledgeFile(entry.id, { tags: newTags });
|
|
1548
|
+
}
|
|
1549
|
+
}
|
|
1550
|
+
if (count > 0) {
|
|
1551
|
+
await saveIndex(index);
|
|
1552
|
+
}
|
|
1553
|
+
return count;
|
|
1554
|
+
}
|
|
1555
|
+
|
|
1556
|
+
// src/tools/ink.ts
|
|
1557
|
+
var inkSaveSchema = z7.object({
|
|
1558
|
+
content: z7.string().describe("\u8981\u4FDD\u5B58\u7684\u5185\u5BB9\uFF08\u5FC5\u586B\uFF09"),
|
|
1559
|
+
title: z7.string().describe("\u6807\u9898\uFF08Claude \u81EA\u52A8\u751F\u6210\uFF09"),
|
|
1560
|
+
tags: z7.array(z7.string()).optional().default([]).describe("\u6807\u7B7E\uFF08Claude \u81EA\u52A8\u751F\u6210\uFF0C\u7528\u6237\u53EF\u6307\u5B9A\uFF09"),
|
|
1561
|
+
category: z7.enum(["insight", "decision", "analysis", "idea", "reference", "action"]).optional().default("reference").describe("\u5206\u7C7B\uFF1Ainsight / decision / analysis / idea / reference / action"),
|
|
1562
|
+
source_type: z7.enum(["conversation", "url", "manual"]).optional().default("conversation").describe("\u6765\u6E90\u7C7B\u578B\uFF1Aconversation / url / manual"),
|
|
1563
|
+
url: z7.string().optional().describe("\u6765\u6E90 URL\uFF08\u5982\u679C\u662F\u5916\u90E8\u7D20\u6750\uFF09")
|
|
1564
|
+
});
|
|
1565
|
+
async function inkSave(input) {
|
|
1566
|
+
try {
|
|
1567
|
+
const { id } = await saveKnowledge({
|
|
1568
|
+
content: input.content,
|
|
1569
|
+
title: input.title,
|
|
1570
|
+
tags: input.tags,
|
|
1571
|
+
category: input.category,
|
|
1572
|
+
source_type: input.source_type,
|
|
1573
|
+
url: input.url
|
|
1574
|
+
});
|
|
1575
|
+
const categoryNames = {
|
|
1576
|
+
insight: "\u6D1E\u5BDF",
|
|
1577
|
+
decision: "\u51B3\u7B56",
|
|
1578
|
+
analysis: "\u5206\u6790",
|
|
1579
|
+
idea: "\u60F3\u6CD5",
|
|
1580
|
+
reference: "\u53C2\u8003",
|
|
1581
|
+
action: "\u884C\u52A8"
|
|
1582
|
+
};
|
|
1583
|
+
const catName = categoryNames[input.category] || input.category;
|
|
1584
|
+
const tagStr = input.tags.length > 0 ? input.tags.join("\u3001") : "\u65E0\u6807\u7B7E";
|
|
1585
|
+
return {
|
|
1586
|
+
success: true,
|
|
1587
|
+
message: `\u5DF2\u4FDD\u5B58\u5230\u300C${catName}\u300D\uFF0C\u6807\u7B7E\uFF1A${tagStr}`,
|
|
1588
|
+
data: { id }
|
|
1589
|
+
};
|
|
1590
|
+
} catch (err) {
|
|
1591
|
+
return {
|
|
1592
|
+
success: false,
|
|
1593
|
+
message: `\u4FDD\u5B58\u5931\u8D25\uFF1A${err.message}`
|
|
1594
|
+
};
|
|
1595
|
+
}
|
|
1596
|
+
}
|
|
1597
|
+
var inkSearchSchema = z7.object({
|
|
1598
|
+
query: z7.string().optional().describe("\u641C\u7D22\u5173\u952E\u8BCD"),
|
|
1599
|
+
tags: z7.array(z7.string()).optional().describe("\u6309\u6807\u7B7E\u8FC7\u6EE4"),
|
|
1600
|
+
category: z7.enum(["insight", "decision", "analysis", "idea", "reference", "action"]).optional().describe("\u6309\u5206\u7C7B\u8FC7\u6EE4"),
|
|
1601
|
+
date_from: z7.string().optional().describe("\u8D77\u59CB\u65E5\u671F\uFF08ISO \u683C\u5F0F\uFF09"),
|
|
1602
|
+
date_to: z7.string().optional().describe("\u622A\u6B62\u65E5\u671F\uFF08ISO \u683C\u5F0F\uFF09"),
|
|
1603
|
+
limit: z7.number().optional().default(10).describe("\u8FD4\u56DE\u6570\u91CF\uFF0C\u9ED8\u8BA4 10")
|
|
1604
|
+
});
|
|
1605
|
+
async function inkSearch(input) {
|
|
1606
|
+
try {
|
|
1607
|
+
const index = await readIndex();
|
|
1608
|
+
const { results, total } = searchKnowledge(index, {
|
|
1609
|
+
query: input.query,
|
|
1610
|
+
tags: input.tags,
|
|
1611
|
+
category: input.category,
|
|
1612
|
+
date_from: input.date_from,
|
|
1613
|
+
date_to: input.date_to,
|
|
1614
|
+
limit: input.limit
|
|
1615
|
+
});
|
|
1616
|
+
if (total === 0) {
|
|
1617
|
+
return {
|
|
1618
|
+
success: true,
|
|
1619
|
+
message: "\u6CA1\u6709\u627E\u5230\u5339\u914D\u7684\u77E5\u8BC6\u7247\u6BB5",
|
|
1620
|
+
data: { results: [], total: 0 }
|
|
1621
|
+
};
|
|
1622
|
+
}
|
|
1623
|
+
return {
|
|
1624
|
+
success: true,
|
|
1625
|
+
message: `\u627E\u5230 ${total} \u6761\u76F8\u5173\u8BB0\u5F55${total > results.length ? `\uFF0C\u663E\u793A\u524D ${results.length} \u6761` : ""}`,
|
|
1626
|
+
data: { results, total }
|
|
1627
|
+
};
|
|
1628
|
+
} catch (err) {
|
|
1629
|
+
return {
|
|
1630
|
+
success: false,
|
|
1631
|
+
message: `\u641C\u7D22\u5931\u8D25\uFF1A${err.message}`
|
|
1632
|
+
};
|
|
1633
|
+
}
|
|
1634
|
+
}
|
|
1635
|
+
var inkGetSchema = z7.object({
|
|
1636
|
+
id: z7.string().describe("\u77E5\u8BC6\u7247\u6BB5 ID")
|
|
1637
|
+
});
|
|
1638
|
+
async function inkGet(input) {
|
|
1639
|
+
try {
|
|
1640
|
+
const file = await readKnowledgeFile(input.id);
|
|
1641
|
+
if (!file) {
|
|
1642
|
+
return {
|
|
1643
|
+
success: false,
|
|
1644
|
+
message: `\u672A\u627E\u5230 ID \u4E3A ${input.id} \u7684\u77E5\u8BC6\u7247\u6BB5`
|
|
1645
|
+
};
|
|
1646
|
+
}
|
|
1647
|
+
return {
|
|
1648
|
+
success: true,
|
|
1649
|
+
message: file.meta.title,
|
|
1650
|
+
data: {
|
|
1651
|
+
id: file.meta.id,
|
|
1652
|
+
title: file.meta.title,
|
|
1653
|
+
content: file.content,
|
|
1654
|
+
tags: file.meta.tags,
|
|
1655
|
+
category: file.meta.category,
|
|
1656
|
+
source_type: file.meta.source_type,
|
|
1657
|
+
url: file.meta.url,
|
|
1658
|
+
created_at: file.meta.created_at,
|
|
1659
|
+
updated_at: file.meta.updated_at
|
|
1660
|
+
}
|
|
1661
|
+
};
|
|
1662
|
+
} catch (err) {
|
|
1663
|
+
return {
|
|
1664
|
+
success: false,
|
|
1665
|
+
message: `\u83B7\u53D6\u5931\u8D25\uFF1A${err.message}`
|
|
1666
|
+
};
|
|
1667
|
+
}
|
|
1668
|
+
}
|
|
1669
|
+
var inkReviewSchema = z7.object({
|
|
1670
|
+
period: z7.enum(["today", "week", "month", "custom"]).optional().default("week").describe("\u56DE\u987E\u65F6\u95F4\u6BB5\uFF1Atoday / week / month / custom"),
|
|
1671
|
+
date_from: z7.string().optional().describe("\u81EA\u5B9A\u4E49\u8D77\u59CB\u65E5\u671F\uFF08ISO \u683C\u5F0F\uFF0Cperiod=custom \u65F6\u4F7F\u7528\uFF09"),
|
|
1672
|
+
date_to: z7.string().optional().describe("\u81EA\u5B9A\u4E49\u622A\u6B62\u65E5\u671F\uFF08ISO \u683C\u5F0F\uFF0Cperiod=custom \u65F6\u4F7F\u7528\uFF09"),
|
|
1673
|
+
category: z7.enum(["insight", "decision", "analysis", "idea", "reference", "action"]).optional().describe("\u6309\u5206\u7C7B\u8FC7\u6EE4")
|
|
1674
|
+
});
|
|
1675
|
+
async function inkReview(input) {
|
|
1676
|
+
try {
|
|
1677
|
+
const index = await readIndex();
|
|
1678
|
+
const entries = Object.values(index.entries);
|
|
1679
|
+
const now = /* @__PURE__ */ new Date();
|
|
1680
|
+
let dateFrom;
|
|
1681
|
+
let dateTo = now.toISOString();
|
|
1682
|
+
switch (input.period) {
|
|
1683
|
+
case "today":
|
|
1684
|
+
dateFrom = new Date(now.getFullYear(), now.getMonth(), now.getDate()).toISOString();
|
|
1685
|
+
break;
|
|
1686
|
+
case "week": {
|
|
1687
|
+
const weekAgo = new Date(now);
|
|
1688
|
+
weekAgo.setDate(weekAgo.getDate() - 7);
|
|
1689
|
+
dateFrom = weekAgo.toISOString();
|
|
1690
|
+
break;
|
|
1691
|
+
}
|
|
1692
|
+
case "month": {
|
|
1693
|
+
const monthAgo = new Date(now);
|
|
1694
|
+
monthAgo.setMonth(monthAgo.getMonth() - 1);
|
|
1695
|
+
dateFrom = monthAgo.toISOString();
|
|
1696
|
+
break;
|
|
1697
|
+
}
|
|
1698
|
+
case "custom":
|
|
1699
|
+
dateFrom = input.date_from || (/* @__PURE__ */ new Date(0)).toISOString();
|
|
1700
|
+
dateTo = input.date_to || now.toISOString();
|
|
1701
|
+
break;
|
|
1702
|
+
}
|
|
1703
|
+
let filtered = entries.filter(
|
|
1704
|
+
(e) => e.created_at >= dateFrom && e.created_at <= dateTo
|
|
1705
|
+
);
|
|
1706
|
+
if (input.category) {
|
|
1707
|
+
filtered = filtered.filter((e) => e.category === input.category);
|
|
1708
|
+
}
|
|
1709
|
+
filtered.sort((a, b) => b.created_at.localeCompare(a.created_at));
|
|
1710
|
+
const byCategory = {};
|
|
1711
|
+
for (const e of filtered) {
|
|
1712
|
+
byCategory[e.category] = (byCategory[e.category] || 0) + 1;
|
|
1713
|
+
}
|
|
1714
|
+
const tagCounts = {};
|
|
1715
|
+
for (const e of filtered) {
|
|
1716
|
+
for (const t of e.tags) {
|
|
1717
|
+
tagCounts[t] = (tagCounts[t] || 0) + 1;
|
|
1718
|
+
}
|
|
1719
|
+
}
|
|
1720
|
+
const topTags = Object.entries(tagCounts).sort((a, b) => b[1] - a[1]).slice(0, 10).map(([tag, count]) => ({ tag, count }));
|
|
1721
|
+
const categoryNames = {
|
|
1722
|
+
insight: "\u6D1E\u5BDF",
|
|
1723
|
+
decision: "\u51B3\u7B56",
|
|
1724
|
+
analysis: "\u5206\u6790",
|
|
1725
|
+
idea: "\u60F3\u6CD5",
|
|
1726
|
+
reference: "\u53C2\u8003",
|
|
1727
|
+
action: "\u884C\u52A8"
|
|
1728
|
+
};
|
|
1729
|
+
const periodNames = {
|
|
1730
|
+
today: "\u4ECA\u65E5",
|
|
1731
|
+
week: "\u672C\u5468",
|
|
1732
|
+
month: "\u672C\u6708",
|
|
1733
|
+
custom: "\u81EA\u5B9A\u4E49\u65F6\u6BB5"
|
|
1734
|
+
};
|
|
1735
|
+
return {
|
|
1736
|
+
success: true,
|
|
1737
|
+
message: `${periodNames[input.period]}\u77E5\u8BC6\u56DE\u987E\uFF1A\u5171 ${filtered.length} \u6761\u8BB0\u5F55`,
|
|
1738
|
+
data: {
|
|
1739
|
+
period: input.period,
|
|
1740
|
+
date_from: dateFrom,
|
|
1741
|
+
date_to: dateTo,
|
|
1742
|
+
total: filtered.length,
|
|
1743
|
+
by_category: Object.entries(byCategory).map(([cat, count]) => ({
|
|
1744
|
+
category: cat,
|
|
1745
|
+
label: categoryNames[cat] || cat,
|
|
1746
|
+
count
|
|
1747
|
+
})),
|
|
1748
|
+
top_tags: topTags,
|
|
1749
|
+
items: filtered.map((e) => ({
|
|
1750
|
+
id: e.id,
|
|
1751
|
+
title: e.title,
|
|
1752
|
+
category: e.category,
|
|
1753
|
+
tags: e.tags,
|
|
1754
|
+
created_at: e.created_at,
|
|
1755
|
+
preview: e.preview
|
|
1756
|
+
}))
|
|
1757
|
+
}
|
|
1758
|
+
};
|
|
1759
|
+
} catch (err) {
|
|
1760
|
+
return {
|
|
1761
|
+
success: false,
|
|
1762
|
+
message: `\u56DE\u987E\u5931\u8D25\uFF1A${err.message}`
|
|
1763
|
+
};
|
|
1764
|
+
}
|
|
1765
|
+
}
|
|
1766
|
+
var inkUpdateSchema = z7.object({
|
|
1767
|
+
id: z7.string().describe("\u77E5\u8BC6\u7247\u6BB5 ID"),
|
|
1768
|
+
title: z7.string().optional().describe("\u65B0\u6807\u9898"),
|
|
1769
|
+
content: z7.string().optional().describe("\u65B0\u5185\u5BB9"),
|
|
1770
|
+
tags: z7.array(z7.string()).optional().describe("\u65B0\u6807\u7B7E\uFF08\u5B8C\u6574\u66FF\u6362\uFF09"),
|
|
1771
|
+
category: z7.enum(["insight", "decision", "analysis", "idea", "reference", "action"]).optional().describe("\u65B0\u5206\u7C7B")
|
|
1772
|
+
});
|
|
1773
|
+
async function inkUpdate(input) {
|
|
1774
|
+
try {
|
|
1775
|
+
const { id, ...updates } = input;
|
|
1776
|
+
if (!updates.title && !updates.content && !updates.tags && !updates.category) {
|
|
1777
|
+
return {
|
|
1778
|
+
success: false,
|
|
1779
|
+
message: "\u8BF7\u81F3\u5C11\u63D0\u4F9B\u4E00\u4E2A\u8981\u66F4\u65B0\u7684\u5B57\u6BB5\uFF08title / content / tags / category\uFF09"
|
|
1780
|
+
};
|
|
1781
|
+
}
|
|
1782
|
+
const result = await updateKnowledgeFile(id, updates);
|
|
1783
|
+
if (!result) {
|
|
1784
|
+
return {
|
|
1785
|
+
success: false,
|
|
1786
|
+
message: `\u672A\u627E\u5230 ID \u4E3A ${id} \u7684\u77E5\u8BC6\u7247\u6BB5`
|
|
1787
|
+
};
|
|
1788
|
+
}
|
|
1789
|
+
const changed = Object.keys(updates).filter(
|
|
1790
|
+
(k) => updates[k] !== void 0
|
|
1791
|
+
);
|
|
1792
|
+
return {
|
|
1793
|
+
success: true,
|
|
1794
|
+
message: `\u5DF2\u66F4\u65B0\u300C${result.meta.title}\u300D\u7684 ${changed.join("\u3001")}`,
|
|
1795
|
+
data: {
|
|
1796
|
+
id: result.meta.id,
|
|
1797
|
+
title: result.meta.title,
|
|
1798
|
+
tags: result.meta.tags,
|
|
1799
|
+
category: result.meta.category,
|
|
1800
|
+
updated_at: result.meta.updated_at
|
|
1801
|
+
}
|
|
1802
|
+
};
|
|
1803
|
+
} catch (err) {
|
|
1804
|
+
return {
|
|
1805
|
+
success: false,
|
|
1806
|
+
message: `\u66F4\u65B0\u5931\u8D25\uFF1A${err.message}`
|
|
1807
|
+
};
|
|
1808
|
+
}
|
|
1809
|
+
}
|
|
1810
|
+
var inkDeleteSchema = z7.object({
|
|
1811
|
+
id: z7.string().describe("\u77E5\u8BC6\u7247\u6BB5 ID"),
|
|
1812
|
+
confirm: z7.boolean().optional().default(false).describe("\u786E\u8BA4\u5220\u9664\uFF08\u5FC5\u987B\u4E3A true \u624D\u4F1A\u6267\u884C\u5220\u9664\uFF09")
|
|
1813
|
+
});
|
|
1814
|
+
async function inkDelete(input) {
|
|
1815
|
+
try {
|
|
1816
|
+
if (!input.confirm) {
|
|
1817
|
+
const index = await readIndex();
|
|
1818
|
+
const entry = index.entries[input.id];
|
|
1819
|
+
if (!entry) {
|
|
1820
|
+
return {
|
|
1821
|
+
success: false,
|
|
1822
|
+
message: `\u672A\u627E\u5230 ID \u4E3A ${input.id} \u7684\u77E5\u8BC6\u7247\u6BB5`
|
|
1823
|
+
};
|
|
1824
|
+
}
|
|
1825
|
+
return {
|
|
1826
|
+
success: false,
|
|
1827
|
+
message: `\u786E\u8BA4\u5220\u9664\u300C${entry.title}\u300D\uFF1F\u8BF7\u5C06 confirm \u8BBE\u4E3A true \u4EE5\u786E\u8BA4\u5220\u9664\u3002`,
|
|
1828
|
+
data: {
|
|
1829
|
+
id: entry.id,
|
|
1830
|
+
title: entry.title,
|
|
1831
|
+
category: entry.category,
|
|
1832
|
+
tags: entry.tags,
|
|
1833
|
+
created_at: entry.created_at
|
|
1834
|
+
}
|
|
1835
|
+
};
|
|
1836
|
+
}
|
|
1837
|
+
const deleted = await deleteKnowledgeFile(input.id);
|
|
1838
|
+
if (!deleted) {
|
|
1839
|
+
return {
|
|
1840
|
+
success: false,
|
|
1841
|
+
message: `\u672A\u627E\u5230 ID \u4E3A ${input.id} \u7684\u77E5\u8BC6\u7247\u6BB5`
|
|
1842
|
+
};
|
|
1843
|
+
}
|
|
1844
|
+
return {
|
|
1845
|
+
success: true,
|
|
1846
|
+
message: `\u5DF2\u5220\u9664\u77E5\u8BC6\u7247\u6BB5 ${input.id}`,
|
|
1847
|
+
data: { id: input.id }
|
|
1848
|
+
};
|
|
1849
|
+
} catch (err) {
|
|
1850
|
+
return {
|
|
1851
|
+
success: false,
|
|
1852
|
+
message: `\u5220\u9664\u5931\u8D25\uFF1A${err.message}`
|
|
1853
|
+
};
|
|
1854
|
+
}
|
|
1855
|
+
}
|
|
1856
|
+
var inkCompileSchema = z7.object({
|
|
1857
|
+
ids: z7.array(z7.string()).optional().describe("\u6307\u5B9A\u8981\u6574\u5408\u7684\u77E5\u8BC6\u7247\u6BB5 ID \u5217\u8868"),
|
|
1858
|
+
query: z7.string().optional().describe("\u641C\u7D22\u6761\u4EF6\uFF08\u4E0E ids \u4E8C\u9009\u4E00\uFF09"),
|
|
1859
|
+
tags: z7.array(z7.string()).optional().describe("\u6309\u6807\u7B7E\u7B5B\u9009\uFF08\u4E0E query \u642D\u914D\uFF09"),
|
|
1860
|
+
format: z7.enum(["outline", "article", "summary"]).optional().default("summary").describe("\u8F93\u51FA\u683C\u5F0F\uFF1Aoutline\uFF08\u5927\u7EB2\uFF09/ article\uFF08\u6587\u7AE0\uFF09/ summary\uFF08\u6458\u8981\uFF09"),
|
|
1861
|
+
instruction: z7.string().optional().describe("\u989D\u5916\u6574\u5408\u6307\u4EE4")
|
|
1862
|
+
});
|
|
1863
|
+
async function inkCompile(input) {
|
|
1864
|
+
try {
|
|
1865
|
+
let ids = [];
|
|
1866
|
+
if (input.ids && input.ids.length > 0) {
|
|
1867
|
+
ids = input.ids;
|
|
1868
|
+
} else if (input.query || input.tags && input.tags.length > 0) {
|
|
1869
|
+
const index = await readIndex();
|
|
1870
|
+
const { results } = searchKnowledge(index, {
|
|
1871
|
+
query: input.query,
|
|
1872
|
+
tags: input.tags,
|
|
1873
|
+
limit: 20
|
|
1874
|
+
});
|
|
1875
|
+
ids = results.map((r) => r.id);
|
|
1876
|
+
}
|
|
1877
|
+
if (ids.length === 0) {
|
|
1878
|
+
return {
|
|
1879
|
+
success: false,
|
|
1880
|
+
message: "\u6CA1\u6709\u627E\u5230\u8981\u6574\u5408\u7684\u77E5\u8BC6\u7247\u6BB5\uFF0C\u8BF7\u6307\u5B9A ids \u6216\u63D0\u4F9B\u641C\u7D22\u6761\u4EF6"
|
|
1881
|
+
};
|
|
1882
|
+
}
|
|
1883
|
+
const files = await readMultipleKnowledgeFiles(ids);
|
|
1884
|
+
if (files.length === 0) {
|
|
1885
|
+
return {
|
|
1886
|
+
success: false,
|
|
1887
|
+
message: "\u672A\u80FD\u8BFB\u53D6\u4EFB\u4F55\u77E5\u8BC6\u7247\u6BB5\u5185\u5BB9"
|
|
1888
|
+
};
|
|
1889
|
+
}
|
|
1890
|
+
const sections = files.map((f, i) => {
|
|
1891
|
+
const tagStr = f.meta.tags.length > 0 ? ` [${f.meta.tags.join(", ")}]` : "";
|
|
1892
|
+
return `### ${i + 1}. ${f.meta.title}${tagStr}
|
|
1893
|
+
> ${f.meta.category} | ${f.meta.created_at.slice(0, 10)}
|
|
1894
|
+
|
|
1895
|
+
${f.content}`;
|
|
1896
|
+
});
|
|
1897
|
+
const formatLabels = {
|
|
1898
|
+
outline: "\u5927\u7EB2",
|
|
1899
|
+
article: "\u6587\u7AE0",
|
|
1900
|
+
summary: "\u6458\u8981"
|
|
1901
|
+
};
|
|
1902
|
+
const compiled = sections.join("\n\n---\n\n");
|
|
1903
|
+
return {
|
|
1904
|
+
success: true,
|
|
1905
|
+
message: `\u5DF2\u6574\u5408 ${files.length} \u6761\u77E5\u8BC6\u7247\u6BB5\uFF0C\u683C\u5F0F\uFF1A${formatLabels[input.format]}`,
|
|
1906
|
+
data: {
|
|
1907
|
+
source_count: files.length,
|
|
1908
|
+
source_ids: files.map((f) => f.meta.id),
|
|
1909
|
+
format: input.format,
|
|
1910
|
+
instruction: input.instruction || null,
|
|
1911
|
+
compiled_content: compiled
|
|
1912
|
+
}
|
|
1913
|
+
};
|
|
1914
|
+
} catch (err) {
|
|
1915
|
+
return {
|
|
1916
|
+
success: false,
|
|
1917
|
+
message: `\u6574\u5408\u5931\u8D25\uFF1A${err.message}`
|
|
1918
|
+
};
|
|
1919
|
+
}
|
|
1920
|
+
}
|
|
1921
|
+
var inkTagsSchema = z7.object({
|
|
1922
|
+
action: z7.enum(["list", "rename", "merge"]).describe("\u64CD\u4F5C\uFF1Alist\uFF08\u5217\u51FA\u6240\u6709\u6807\u7B7E\uFF09/ rename\uFF08\u91CD\u547D\u540D\uFF09/ merge\uFF08\u5408\u5E76\uFF09"),
|
|
1923
|
+
tag: z7.string().optional().describe("\u76EE\u6807\u6807\u7B7E\uFF08rename/merge \u65F6\u4F7F\u7528\uFF09"),
|
|
1924
|
+
new_tag: z7.string().optional().describe("\u65B0\u6807\u7B7E\u540D\uFF08rename \u65F6\u4F7F\u7528\uFF09"),
|
|
1925
|
+
source_tags: z7.array(z7.string()).optional().describe("\u8981\u5408\u5E76\u7684\u6E90\u6807\u7B7E\u5217\u8868\uFF08merge \u65F6\u4F7F\u7528\uFF09")
|
|
1926
|
+
});
|
|
1927
|
+
async function inkTags(input) {
|
|
1928
|
+
try {
|
|
1929
|
+
const index = await readIndex();
|
|
1930
|
+
switch (input.action) {
|
|
1931
|
+
case "list": {
|
|
1932
|
+
const tags = getAllTags(index);
|
|
1933
|
+
return {
|
|
1934
|
+
success: true,
|
|
1935
|
+
message: tags.length > 0 ? `\u5171 ${tags.length} \u4E2A\u6807\u7B7E` : "\u6682\u65E0\u6807\u7B7E",
|
|
1936
|
+
data: { tags }
|
|
1937
|
+
};
|
|
1938
|
+
}
|
|
1939
|
+
case "rename": {
|
|
1940
|
+
if (!input.tag || !input.new_tag) {
|
|
1941
|
+
return {
|
|
1942
|
+
success: false,
|
|
1943
|
+
message: "rename \u64CD\u4F5C\u9700\u8981\u63D0\u4F9B tag\uFF08\u539F\u6807\u7B7E\uFF09\u548C new_tag\uFF08\u65B0\u6807\u7B7E\u540D\uFF09"
|
|
1944
|
+
};
|
|
1945
|
+
}
|
|
1946
|
+
const count = await renameTag(input.tag, input.new_tag);
|
|
1947
|
+
if (count === 0) {
|
|
1948
|
+
return {
|
|
1949
|
+
success: false,
|
|
1950
|
+
message: `\u672A\u627E\u5230\u4F7F\u7528\u6807\u7B7E\u300C${input.tag}\u300D\u7684\u77E5\u8BC6\u7247\u6BB5`
|
|
1951
|
+
};
|
|
1952
|
+
}
|
|
1953
|
+
return {
|
|
1954
|
+
success: true,
|
|
1955
|
+
message: `\u5DF2\u5C06 ${count} \u6761\u77E5\u8BC6\u7247\u6BB5\u7684\u6807\u7B7E\u300C${input.tag}\u300D\u91CD\u547D\u540D\u4E3A\u300C${input.new_tag}\u300D`,
|
|
1956
|
+
data: { old_tag: input.tag, new_tag: input.new_tag, affected: count }
|
|
1957
|
+
};
|
|
1958
|
+
}
|
|
1959
|
+
case "merge": {
|
|
1960
|
+
if (!input.source_tags || input.source_tags.length === 0 || !input.new_tag) {
|
|
1961
|
+
return {
|
|
1962
|
+
success: false,
|
|
1963
|
+
message: "merge \u64CD\u4F5C\u9700\u8981\u63D0\u4F9B source_tags\uFF08\u6E90\u6807\u7B7E\u5217\u8868\uFF09\u548C new_tag\uFF08\u76EE\u6807\u6807\u7B7E\uFF09"
|
|
1964
|
+
};
|
|
1965
|
+
}
|
|
1966
|
+
const count = await mergeTags(input.source_tags, input.new_tag);
|
|
1967
|
+
if (count === 0) {
|
|
1968
|
+
return {
|
|
1969
|
+
success: false,
|
|
1970
|
+
message: `\u672A\u627E\u5230\u4F7F\u7528\u8FD9\u4E9B\u6807\u7B7E\u7684\u77E5\u8BC6\u7247\u6BB5`
|
|
1971
|
+
};
|
|
1972
|
+
}
|
|
1973
|
+
return {
|
|
1974
|
+
success: true,
|
|
1975
|
+
message: `\u5DF2\u5C06 ${count} \u6761\u77E5\u8BC6\u7247\u6BB5\u7684\u6807\u7B7E\u5408\u5E76\u4E3A\u300C${input.new_tag}\u300D`,
|
|
1976
|
+
data: {
|
|
1977
|
+
source_tags: input.source_tags,
|
|
1978
|
+
target_tag: input.new_tag,
|
|
1979
|
+
affected: count
|
|
1980
|
+
}
|
|
1981
|
+
};
|
|
1982
|
+
}
|
|
1983
|
+
}
|
|
1984
|
+
} catch (err) {
|
|
1985
|
+
return {
|
|
1986
|
+
success: false,
|
|
1987
|
+
message: `\u6807\u7B7E\u64CD\u4F5C\u5931\u8D25\uFF1A${err.message}`
|
|
1988
|
+
};
|
|
1989
|
+
}
|
|
1990
|
+
}
|
|
1991
|
+
|
|
1078
1992
|
// src/index.ts
|
|
1079
|
-
import { readFile as readFile7 } from "fs/promises";
|
|
1080
|
-
import { join as join9 } from "path";
|
|
1081
1993
|
var server = new McpServer({
|
|
1082
1994
|
name: "ClaudeInk",
|
|
1083
|
-
version: "0.
|
|
1995
|
+
version: "0.9.0"
|
|
1084
1996
|
});
|
|
1085
1997
|
server.tool("workflow.init", "\u521D\u59CB\u5316\u5199\u4F5C\u5DE5\u4F5C\u6D41\uFF08\u91CA\u653E\u4E09\u5C42\u914D\u7F6E + \u6FC0\u6D3B License + \u81EA\u52A8\u540C\u6B65\u4E91\u7AEF\u914D\u7F6E\uFF09", workflowInitSchema.shape, async (input) => {
|
|
1086
1998
|
const result = await workflowInit(input);
|
|
1087
1999
|
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
1088
2000
|
});
|
|
1089
|
-
server.tool("sync
|
|
1090
|
-
const result = await
|
|
1091
|
-
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
1092
|
-
});
|
|
1093
|
-
server.tool("sync.pull", "\u4ECE\u4E91\u7AEF\u62C9\u53D6\u914D\u7F6E\u5230\u672C\u5730\uFF08\u65F6\u95F4\u6233\u6BD4\u8F83\uFF0C\u65B0\u7684\u8986\u76D6\u65E7\u7684\uFF09", syncPullSchema.shape, async (input) => {
|
|
1094
|
-
const result = await syncPull(input);
|
|
2001
|
+
server.tool("sync", "\u4ECE\u4E91\u7AEF\u540C\u6B65\u5168\u91CF\u5143\u6570\u636E\u5230\u672C\u5730", syncSchema.shape, async (input) => {
|
|
2002
|
+
const result = await sync(input);
|
|
1095
2003
|
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
1096
2004
|
});
|
|
1097
2005
|
server.tool("source.add", "\u6DFB\u52A0\u7D20\u6750\u5230\u672C\u5730\u7D20\u6750\u5E93", sourceAddSchema.shape, async (input) => {
|
|
@@ -1114,10 +2022,14 @@ server.tool("source.subscribe", "\u7BA1\u7406\u8BA2\u9605\u6E90\uFF08\u6DFB\u52A
|
|
|
1114
2022
|
const result = await sourceSubscribe(input);
|
|
1115
2023
|
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
1116
2024
|
});
|
|
1117
|
-
server.tool("draft.save", "\
|
|
2025
|
+
server.tool("draft.save", "\u65B0\u5EFA\u8349\u7A3F", draftSaveSchema.shape, async (input) => {
|
|
1118
2026
|
const result = await draftSave(input);
|
|
1119
2027
|
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
1120
2028
|
});
|
|
2029
|
+
server.tool("draft.update", "\u66F4\u65B0\u5DF2\u6709\u8349\u7A3F\uFF08\u5185\u5BB9/\u6807\u9898/\u72B6\u6001/\u6807\u7B7E\uFF09", draftUpdateSchema.shape, async (input) => {
|
|
2030
|
+
const result = await draftUpdate(input);
|
|
2031
|
+
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
2032
|
+
});
|
|
1121
2033
|
server.tool("draft.publish", "\u6807\u8BB0\u8349\u7A3F\u4E3A\u5DF2\u53D1\u5E03", draftPublishSchema.shape, async (input) => {
|
|
1122
2034
|
const result = await draftPublish(input);
|
|
1123
2035
|
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
@@ -1130,19 +2042,50 @@ server.tool("analytics.report", "\u83B7\u53D6\u6570\u636E\u5206\u6790\u62A5\u544
|
|
|
1130
2042
|
const result = await analyticsReport(input);
|
|
1131
2043
|
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
1132
2044
|
});
|
|
2045
|
+
server.tool("ink.save", "\u4FDD\u5B58\u77E5\u8BC6\u7247\u6BB5\u5230\u672C\u5730\u77E5\u8BC6\u5E93\uFF08\u5BF9\u8BDD\u6D1E\u5BDF\u3001\u51B3\u7B56\u3001\u5206\u6790\u7B49\uFF09", inkSaveSchema.shape, async (input) => {
|
|
2046
|
+
const result = await inkSave(input);
|
|
2047
|
+
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
2048
|
+
});
|
|
2049
|
+
server.tool("ink.search", "\u641C\u7D22\u77E5\u8BC6\u5E93\uFF08\u652F\u6301\u5173\u952E\u8BCD\u3001\u6807\u7B7E\u3001\u5206\u7C7B\u3001\u65F6\u95F4\u8303\u56F4\u8FC7\u6EE4\uFF09", inkSearchSchema.shape, async (input) => {
|
|
2050
|
+
const result = await inkSearch(input);
|
|
2051
|
+
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
2052
|
+
});
|
|
2053
|
+
server.tool("ink.get", "\u83B7\u53D6\u77E5\u8BC6\u7247\u6BB5\u5B8C\u6574\u5185\u5BB9", inkGetSchema.shape, async (input) => {
|
|
2054
|
+
const result = await inkGet(input);
|
|
2055
|
+
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
2056
|
+
});
|
|
2057
|
+
server.tool("ink.review", "\u6309\u65F6\u95F4\u6BB5\u56DE\u987E\u77E5\u8BC6\u5E93\uFF08\u7EDF\u8BA1 + \u5217\u8868\uFF09", inkReviewSchema.shape, async (input) => {
|
|
2058
|
+
const result = await inkReview(input);
|
|
2059
|
+
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
2060
|
+
});
|
|
2061
|
+
server.tool("ink.update", "\u66F4\u65B0\u5DF2\u4FDD\u5B58\u7684\u77E5\u8BC6\u7247\u6BB5\uFF08\u6807\u9898/\u5185\u5BB9/\u6807\u7B7E/\u5206\u7C7B\uFF09", inkUpdateSchema.shape, async (input) => {
|
|
2062
|
+
const result = await inkUpdate(input);
|
|
2063
|
+
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
2064
|
+
});
|
|
2065
|
+
server.tool("ink.delete", "\u5220\u9664\u77E5\u8BC6\u7247\u6BB5\uFF08\u9700\u786E\u8BA4\uFF09", inkDeleteSchema.shape, async (input) => {
|
|
2066
|
+
const result = await inkDelete(input);
|
|
2067
|
+
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
2068
|
+
});
|
|
2069
|
+
server.tool("ink.compile", "\u6574\u5408\u591A\u6761\u77E5\u8BC6\u7247\u6BB5\u4E3A\u7ED3\u6784\u5316\u5185\u5BB9\uFF08\u6309 ID \u6216\u641C\u7D22\u6761\u4EF6\uFF09", inkCompileSchema.shape, async (input) => {
|
|
2070
|
+
const result = await inkCompile(input);
|
|
2071
|
+
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
2072
|
+
});
|
|
2073
|
+
server.tool("ink.tags", "\u6807\u7B7E\u7BA1\u7406\uFF08\u5217\u51FA / \u91CD\u547D\u540D / \u5408\u5E76\u6807\u7B7E\uFF09", inkTagsSchema.shape, async (input) => {
|
|
2074
|
+
const result = await inkTags(input);
|
|
2075
|
+
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
2076
|
+
});
|
|
1133
2077
|
async function main() {
|
|
1134
2078
|
try {
|
|
1135
|
-
const
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
console.error(`[ClaudeInk MCP] workDir restored: ${config.workflowDir}`);
|
|
2079
|
+
const state = await readState();
|
|
2080
|
+
if (state.config.workflowDir) {
|
|
2081
|
+
setWorkDir(state.config.workflowDir);
|
|
2082
|
+
console.error(`[ClaudeInk MCP] workDir restored: ${state.config.workflowDir}`);
|
|
1140
2083
|
}
|
|
1141
2084
|
} catch {
|
|
1142
2085
|
}
|
|
1143
2086
|
const transport = new StdioServerTransport();
|
|
1144
2087
|
await server.connect(transport);
|
|
1145
|
-
console.error("[ClaudeInk MCP] Server started on stdio (
|
|
2088
|
+
console.error("[ClaudeInk MCP] Server started on stdio (17 tools)");
|
|
1146
2089
|
}
|
|
1147
2090
|
main().catch((err) => {
|
|
1148
2091
|
console.error("[ClaudeInk MCP] Fatal error:", err);
|