@claudeink/mcp-server 0.6.3 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +257 -232
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -6,11 +6,11 @@ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"
|
|
|
6
6
|
|
|
7
7
|
// src/tools/source.ts
|
|
8
8
|
import { z } from "zod";
|
|
9
|
-
import { mkdir as
|
|
10
|
-
import { join as
|
|
9
|
+
import { mkdir as mkdir2, readFile as readFile3 } from "fs/promises";
|
|
10
|
+
import { join as join3 } from "path";
|
|
11
11
|
|
|
12
|
-
// src/lib/
|
|
13
|
-
import { readFile, writeFile, mkdir, chmod
|
|
12
|
+
// src/lib/state.ts
|
|
13
|
+
import { readFile, writeFile, mkdir, chmod } from "fs/promises";
|
|
14
14
|
import { join } from "path";
|
|
15
15
|
var _workDirCache = null;
|
|
16
16
|
function setWorkDir(dir) {
|
|
@@ -22,79 +22,152 @@ function getWorkDir() {
|
|
|
22
22
|
function getClaudeinkDir() {
|
|
23
23
|
return join(getWorkDir(), ".claudeink");
|
|
24
24
|
}
|
|
25
|
-
function
|
|
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
|
-
};
|
|
25
|
+
function getStatePath() {
|
|
26
|
+
return join(getClaudeinkDir(), "state.json");
|
|
35
27
|
}
|
|
36
|
-
var
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
28
|
+
var DEFAULT_STATE = {
|
|
29
|
+
credentials: null,
|
|
30
|
+
config: {
|
|
31
|
+
apiBaseUrl: "https://app.claudeink.com",
|
|
32
|
+
workflowDir: ""
|
|
33
|
+
},
|
|
34
|
+
sources: {},
|
|
35
|
+
drafts: {},
|
|
36
|
+
published: {},
|
|
37
|
+
configs: {},
|
|
38
|
+
crawlerSources: {},
|
|
39
|
+
writingMasters: {},
|
|
40
|
+
tagQueue: {
|
|
41
|
+
queue: [],
|
|
42
|
+
failed: []
|
|
43
|
+
},
|
|
44
|
+
lastSyncAt: ""
|
|
51
45
|
};
|
|
52
46
|
async function ensureDir() {
|
|
53
|
-
const paths = getPaths();
|
|
54
47
|
await mkdir(getClaudeinkDir(), { recursive: true });
|
|
55
|
-
await mkdir(paths.logs, { recursive: true });
|
|
56
|
-
}
|
|
57
|
-
async function fileExists(path) {
|
|
58
|
-
try {
|
|
59
|
-
await access(path);
|
|
60
|
-
return true;
|
|
61
|
-
} catch {
|
|
62
|
-
return false;
|
|
63
|
-
}
|
|
64
48
|
}
|
|
65
|
-
async function
|
|
49
|
+
async function readState() {
|
|
66
50
|
try {
|
|
67
|
-
const content = await readFile(
|
|
68
|
-
|
|
51
|
+
const content = await readFile(getStatePath(), "utf-8");
|
|
52
|
+
const parsed = JSON.parse(content);
|
|
53
|
+
const state = {
|
|
54
|
+
...DEFAULT_STATE,
|
|
55
|
+
...parsed,
|
|
56
|
+
config: { ...DEFAULT_STATE.config, ...parsed.config },
|
|
57
|
+
tagQueue: { ...DEFAULT_STATE.tagQueue, ...parsed.tagQueue }
|
|
58
|
+
};
|
|
59
|
+
state.config.workflowDir = getWorkDir();
|
|
60
|
+
return state;
|
|
69
61
|
} catch {
|
|
70
|
-
|
|
62
|
+
const state = { ...DEFAULT_STATE };
|
|
63
|
+
state.config.workflowDir = getWorkDir();
|
|
64
|
+
return state;
|
|
71
65
|
}
|
|
72
66
|
}
|
|
73
|
-
async function
|
|
67
|
+
async function writeState(state) {
|
|
74
68
|
await ensureDir();
|
|
75
|
-
await writeFile(
|
|
76
|
-
|
|
77
|
-
await chmod(path, 384);
|
|
78
|
-
}
|
|
69
|
+
await writeFile(getStatePath(), JSON.stringify(state, null, 2), "utf-8");
|
|
70
|
+
await chmod(getStatePath(), 384);
|
|
79
71
|
}
|
|
80
72
|
async function getCredentials() {
|
|
81
|
-
const
|
|
82
|
-
if (!
|
|
83
|
-
return
|
|
73
|
+
const state = await readState();
|
|
74
|
+
if (!state.credentials) return null;
|
|
75
|
+
return {
|
|
76
|
+
licenseKey: state.credentials.licenseKey,
|
|
77
|
+
token: state.credentials.token,
|
|
78
|
+
userId: state.credentials.userId,
|
|
79
|
+
plan: state.credentials.plan,
|
|
80
|
+
expiresAt: state.credentials.expiresAt
|
|
81
|
+
};
|
|
84
82
|
}
|
|
85
83
|
async function getConfig() {
|
|
86
|
-
const
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
84
|
+
const state = await readState();
|
|
85
|
+
return state.config;
|
|
86
|
+
}
|
|
87
|
+
async function updateSource(id, data) {
|
|
88
|
+
const state = await readState();
|
|
89
|
+
state.sources[id] = data;
|
|
90
|
+
await writeState(state);
|
|
91
|
+
}
|
|
92
|
+
async function updateSourceTags(id, tags) {
|
|
93
|
+
const state = await readState();
|
|
94
|
+
if (state.sources[id]) {
|
|
95
|
+
state.sources[id].tags = tags;
|
|
96
|
+
state.sources[id].updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
97
|
+
}
|
|
98
|
+
await writeState(state);
|
|
99
|
+
}
|
|
100
|
+
async function updateDraft(id, data) {
|
|
101
|
+
const state = await readState();
|
|
102
|
+
state.drafts[id] = data;
|
|
103
|
+
await writeState(state);
|
|
104
|
+
}
|
|
105
|
+
async function moveDraftToPublished(id, data) {
|
|
106
|
+
const state = await readState();
|
|
107
|
+
delete state.drafts[id];
|
|
108
|
+
state.published[id] = data;
|
|
109
|
+
await writeState(state);
|
|
110
|
+
}
|
|
111
|
+
async function updateCrawlerSource(id, data) {
|
|
112
|
+
const state = await readState();
|
|
113
|
+
state.crawlerSources[id] = data;
|
|
114
|
+
await writeState(state);
|
|
115
|
+
}
|
|
116
|
+
async function removeCrawlerSource(id) {
|
|
117
|
+
const state = await readState();
|
|
118
|
+
delete state.crawlerSources[id];
|
|
119
|
+
await writeState(state);
|
|
120
|
+
}
|
|
121
|
+
async function updateLastSyncAt() {
|
|
122
|
+
const state = await readState();
|
|
123
|
+
state.lastSyncAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
124
|
+
await writeState(state);
|
|
125
|
+
}
|
|
126
|
+
async function replaceState(newState) {
|
|
127
|
+
await writeState(newState);
|
|
90
128
|
}
|
|
91
129
|
async function getTagQueue() {
|
|
92
|
-
const
|
|
93
|
-
return
|
|
130
|
+
const state = await readState();
|
|
131
|
+
return {
|
|
132
|
+
queue: state.tagQueue.queue.map((item) => ({
|
|
133
|
+
file: item.file,
|
|
134
|
+
title: item.title,
|
|
135
|
+
addedAt: item.addedAt,
|
|
136
|
+
source: item.source,
|
|
137
|
+
retryCount: item.retries
|
|
138
|
+
})),
|
|
139
|
+
failed: state.tagQueue.failed.map((item) => ({
|
|
140
|
+
file: item.file,
|
|
141
|
+
title: item.title,
|
|
142
|
+
addedAt: item.failedAt,
|
|
143
|
+
source: item.source
|
|
144
|
+
})),
|
|
145
|
+
history: {
|
|
146
|
+
lastProcessed: null,
|
|
147
|
+
totalProcessed: 0,
|
|
148
|
+
avgTagsPerItem: 0
|
|
149
|
+
}
|
|
150
|
+
};
|
|
94
151
|
}
|
|
95
152
|
async function saveTagQueue(queue) {
|
|
96
|
-
const
|
|
97
|
-
|
|
153
|
+
const state = await readState();
|
|
154
|
+
state.tagQueue = {
|
|
155
|
+
queue: queue.queue.map((item) => ({
|
|
156
|
+
file: item.file,
|
|
157
|
+
title: item.title,
|
|
158
|
+
source: item.source,
|
|
159
|
+
addedAt: item.addedAt,
|
|
160
|
+
retries: item.retryCount || 0
|
|
161
|
+
})),
|
|
162
|
+
failed: queue.failed.map((item) => ({
|
|
163
|
+
file: item.file,
|
|
164
|
+
title: item.title,
|
|
165
|
+
source: item.source,
|
|
166
|
+
failedAt: item.addedAt || (/* @__PURE__ */ new Date()).toISOString(),
|
|
167
|
+
reason: "max retries exceeded"
|
|
168
|
+
}))
|
|
169
|
+
};
|
|
170
|
+
await writeState(state);
|
|
98
171
|
}
|
|
99
172
|
|
|
100
173
|
// src/lib/sources.ts
|
|
@@ -117,7 +190,7 @@ async function writeSourceFile(filePath, meta, content) {
|
|
|
117
190
|
const output = matter.stringify(content, meta);
|
|
118
191
|
await writeFile2(filePath, output, "utf-8");
|
|
119
192
|
}
|
|
120
|
-
async function
|
|
193
|
+
async function updateSourceTags2(filePath, tags) {
|
|
121
194
|
const source = await readSourceFile(filePath);
|
|
122
195
|
source.meta.tags = tags;
|
|
123
196
|
source.meta.taggedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
@@ -141,9 +214,8 @@ async function addToTagQueue(file, title, source) {
|
|
|
141
214
|
await saveTagQueue(queue);
|
|
142
215
|
}
|
|
143
216
|
async function getNextTagBatch() {
|
|
144
|
-
const config = await getConfig();
|
|
145
217
|
const queue = await getTagQueue();
|
|
146
|
-
return queue.queue.slice(0,
|
|
218
|
+
return queue.queue.slice(0, 20);
|
|
147
219
|
}
|
|
148
220
|
async function markTagged(file, tagsCount) {
|
|
149
221
|
const queue = await getTagQueue();
|
|
@@ -168,79 +240,6 @@ async function markTagFailed(file) {
|
|
|
168
240
|
await saveTagQueue(queue);
|
|
169
241
|
}
|
|
170
242
|
|
|
171
|
-
// src/lib/state.ts
|
|
172
|
-
import { readFile as readFile3, writeFile as writeFile3, mkdir as mkdir2 } from "fs/promises";
|
|
173
|
-
import { join as join3 } from "path";
|
|
174
|
-
var DEFAULT_STATE = {
|
|
175
|
-
sources: {},
|
|
176
|
-
drafts: {},
|
|
177
|
-
published: {},
|
|
178
|
-
configs: {},
|
|
179
|
-
crawlerSources: {},
|
|
180
|
-
writingMasters: {},
|
|
181
|
-
lastSyncAt: ""
|
|
182
|
-
};
|
|
183
|
-
async function getStatePath() {
|
|
184
|
-
const dir = join3(getWorkDir(), ".claudeink");
|
|
185
|
-
await mkdir2(dir, { recursive: true });
|
|
186
|
-
return join3(dir, "state.json");
|
|
187
|
-
}
|
|
188
|
-
async function readState() {
|
|
189
|
-
try {
|
|
190
|
-
const path = await getStatePath();
|
|
191
|
-
const content = await readFile3(path, "utf-8");
|
|
192
|
-
return { ...DEFAULT_STATE, ...JSON.parse(content) };
|
|
193
|
-
} catch {
|
|
194
|
-
return { ...DEFAULT_STATE };
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
|
-
async function writeState(state) {
|
|
198
|
-
const path = await getStatePath();
|
|
199
|
-
await writeFile3(path, JSON.stringify(state, null, 2), "utf-8");
|
|
200
|
-
}
|
|
201
|
-
async function updateSource(id, data) {
|
|
202
|
-
const state = await readState();
|
|
203
|
-
state.sources[id] = data;
|
|
204
|
-
await writeState(state);
|
|
205
|
-
}
|
|
206
|
-
async function updateSourceTags2(id, tags) {
|
|
207
|
-
const state = await readState();
|
|
208
|
-
if (state.sources[id]) {
|
|
209
|
-
state.sources[id].tags = tags;
|
|
210
|
-
state.sources[id].updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
211
|
-
}
|
|
212
|
-
await writeState(state);
|
|
213
|
-
}
|
|
214
|
-
async function updateDraft(id, data) {
|
|
215
|
-
const state = await readState();
|
|
216
|
-
state.drafts[id] = data;
|
|
217
|
-
await writeState(state);
|
|
218
|
-
}
|
|
219
|
-
async function moveDraftToPublished(id, data) {
|
|
220
|
-
const state = await readState();
|
|
221
|
-
delete state.drafts[id];
|
|
222
|
-
state.published[id] = data;
|
|
223
|
-
await writeState(state);
|
|
224
|
-
}
|
|
225
|
-
async function updateCrawlerSource(id, data) {
|
|
226
|
-
const state = await readState();
|
|
227
|
-
state.crawlerSources[id] = data;
|
|
228
|
-
await writeState(state);
|
|
229
|
-
}
|
|
230
|
-
async function removeCrawlerSource(id) {
|
|
231
|
-
const state = await readState();
|
|
232
|
-
delete state.crawlerSources[id];
|
|
233
|
-
await writeState(state);
|
|
234
|
-
}
|
|
235
|
-
async function updateLastSyncAt() {
|
|
236
|
-
const state = await readState();
|
|
237
|
-
state.lastSyncAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
238
|
-
await writeState(state);
|
|
239
|
-
}
|
|
240
|
-
async function replaceState(state) {
|
|
241
|
-
await writeState(state);
|
|
242
|
-
}
|
|
243
|
-
|
|
244
243
|
// src/tools/source.ts
|
|
245
244
|
var sourceAddSchema = z.object({
|
|
246
245
|
title: z.string().describe("\u7D20\u6750\u6807\u9898"),
|
|
@@ -252,12 +251,12 @@ var sourceAddSchema = z.object({
|
|
|
252
251
|
async function sourceAdd(input) {
|
|
253
252
|
const config = await getConfig();
|
|
254
253
|
const folder = input.folder || "articles";
|
|
255
|
-
const dir =
|
|
256
|
-
await
|
|
254
|
+
const dir = join3(config.workflowDir, "sources", folder);
|
|
255
|
+
await mkdir2(dir, { recursive: true });
|
|
257
256
|
const date = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
258
257
|
const slug = input.title.replace(/[^\u4e00-\u9fa5a-zA-Z0-9\s-]/g, "").replace(/\s+/g, "-").slice(0, 50);
|
|
259
258
|
const filename = `${date}-${slug}.md`;
|
|
260
|
-
const filePath =
|
|
259
|
+
const filePath = join3(dir, filename);
|
|
261
260
|
const meta = {
|
|
262
261
|
title: input.title,
|
|
263
262
|
source: input.url ? (() => {
|
|
@@ -316,10 +315,10 @@ var sourceCrawlSchema = z.object({
|
|
|
316
315
|
});
|
|
317
316
|
async function sourceCrawl(input) {
|
|
318
317
|
const config = await getConfig();
|
|
319
|
-
const configPath =
|
|
318
|
+
const configPath = join3(config.workflowDir || process.cwd(), "tools", "crawler", "config.json");
|
|
320
319
|
let crawlerConfig;
|
|
321
320
|
try {
|
|
322
|
-
crawlerConfig = JSON.parse(await
|
|
321
|
+
crawlerConfig = JSON.parse(await readFile3(configPath, "utf-8"));
|
|
323
322
|
} catch {
|
|
324
323
|
crawlerConfig = { sources: [] };
|
|
325
324
|
}
|
|
@@ -384,7 +383,7 @@ async function sourceTag(input) {
|
|
|
384
383
|
for (const item of batch) {
|
|
385
384
|
try {
|
|
386
385
|
const config = await getConfig();
|
|
387
|
-
const fullPath =
|
|
386
|
+
const fullPath = join3(config.workflowDir, item.file);
|
|
388
387
|
const source = await readSourceFile(fullPath);
|
|
389
388
|
items.push({
|
|
390
389
|
file: item.file,
|
|
@@ -419,10 +418,10 @@ var sourceTagApplySchema = z.object({
|
|
|
419
418
|
async function sourceTagApply(input) {
|
|
420
419
|
try {
|
|
421
420
|
const config = await getConfig();
|
|
422
|
-
const fullPath = input.file.startsWith("/") ? input.file :
|
|
423
|
-
await
|
|
421
|
+
const fullPath = input.file.startsWith("/") ? input.file : join3(config.workflowDir, input.file);
|
|
422
|
+
await updateSourceTags2(fullPath, input.tags);
|
|
424
423
|
await markTagged(input.file, input.tags.length);
|
|
425
|
-
await
|
|
424
|
+
await updateSourceTags(input.file, input.tags);
|
|
426
425
|
return {
|
|
427
426
|
success: true,
|
|
428
427
|
message: `\u6807\u7B7E\u5DF2\u5199\u5165: ${input.tags.join(", ")}`,
|
|
@@ -439,8 +438,8 @@ async function sourceTagApply(input) {
|
|
|
439
438
|
|
|
440
439
|
// src/tools/subscribe.ts
|
|
441
440
|
import { z as z2 } from "zod";
|
|
442
|
-
import { readFile as
|
|
443
|
-
import { join as
|
|
441
|
+
import { readFile as readFile4, writeFile as writeFile3 } from "fs/promises";
|
|
442
|
+
import { join as join4 } from "path";
|
|
444
443
|
var sourceSubscribeSchema = z2.object({
|
|
445
444
|
action: z2.enum(["add", "remove", "list"]).describe("\u64CD\u4F5C\u7C7B\u578B"),
|
|
446
445
|
id: z2.string().optional().describe("\u8BA2\u9605\u6E90 ID"),
|
|
@@ -451,10 +450,10 @@ var sourceSubscribeSchema = z2.object({
|
|
|
451
450
|
});
|
|
452
451
|
async function sourceSubscribe(input) {
|
|
453
452
|
const config = await getConfig();
|
|
454
|
-
const configPath =
|
|
453
|
+
const configPath = join4(config.workflowDir || process.cwd(), "tools/crawler/config.json");
|
|
455
454
|
let crawlerConfig;
|
|
456
455
|
try {
|
|
457
|
-
crawlerConfig = JSON.parse(await
|
|
456
|
+
crawlerConfig = JSON.parse(await readFile4(configPath, "utf-8"));
|
|
458
457
|
} catch {
|
|
459
458
|
crawlerConfig = { sources: [] };
|
|
460
459
|
}
|
|
@@ -481,7 +480,7 @@ async function sourceSubscribe(input) {
|
|
|
481
480
|
enabled: true
|
|
482
481
|
};
|
|
483
482
|
crawlerConfig.sources.push(newSource);
|
|
484
|
-
await
|
|
483
|
+
await writeFile3(configPath, JSON.stringify(crawlerConfig, null, 2));
|
|
485
484
|
await updateCrawlerSource(input.id, {
|
|
486
485
|
name: input.name,
|
|
487
486
|
url: input.url,
|
|
@@ -496,7 +495,7 @@ async function sourceSubscribe(input) {
|
|
|
496
495
|
return { success: false, message: "\u5220\u9664\u8BA2\u9605\u6E90\u9700\u8981 id \u53C2\u6570" };
|
|
497
496
|
}
|
|
498
497
|
crawlerConfig.sources = crawlerConfig.sources.filter((s) => s.id !== input.id);
|
|
499
|
-
await
|
|
498
|
+
await writeFile3(configPath, JSON.stringify(crawlerConfig, null, 2));
|
|
500
499
|
await removeCrawlerSource(input.id);
|
|
501
500
|
return { success: true, message: `\u8BA2\u9605\u6E90 ${input.id} \u5DF2\u5220\u9664` };
|
|
502
501
|
}
|
|
@@ -507,22 +506,22 @@ async function sourceSubscribe(input) {
|
|
|
507
506
|
import { z as z3 } from "zod";
|
|
508
507
|
|
|
509
508
|
// src/lib/drafts.ts
|
|
510
|
-
import { readFile as
|
|
511
|
-
import { join as
|
|
509
|
+
import { readFile as readFile5, writeFile as writeFile4, mkdir as mkdir3, unlink } from "fs/promises";
|
|
510
|
+
import { join as join5, basename } from "path";
|
|
512
511
|
import matter2 from "gray-matter";
|
|
513
512
|
import { glob as glob2 } from "glob";
|
|
514
513
|
import { randomUUID } from "crypto";
|
|
515
514
|
function getDraftDir(account, workflowDir) {
|
|
516
515
|
if (account.toLowerCase() === "auston") {
|
|
517
|
-
return
|
|
516
|
+
return join5(workflowDir, "drafts");
|
|
518
517
|
}
|
|
519
|
-
return
|
|
518
|
+
return join5(workflowDir, "accounts", account, "drafts");
|
|
520
519
|
}
|
|
521
520
|
function getPublishedDir(account, workflowDir) {
|
|
522
521
|
if (account.toLowerCase() === "auston") {
|
|
523
|
-
return
|
|
522
|
+
return join5(workflowDir, "published");
|
|
524
523
|
}
|
|
525
|
-
return
|
|
524
|
+
return join5(workflowDir, "accounts", account, "published");
|
|
526
525
|
}
|
|
527
526
|
function generateDraftFilename(title) {
|
|
528
527
|
const date = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
@@ -532,10 +531,10 @@ function generateDraftFilename(title) {
|
|
|
532
531
|
async function saveDraft(options) {
|
|
533
532
|
const config = await getConfig();
|
|
534
533
|
const dir = getDraftDir(options.account, config.workflowDir);
|
|
535
|
-
await
|
|
534
|
+
await mkdir3(dir, { recursive: true });
|
|
536
535
|
const id = randomUUID().slice(0, 8);
|
|
537
536
|
const filename = generateDraftFilename(options.title);
|
|
538
|
-
const filePath =
|
|
537
|
+
const filePath = join5(dir, filename);
|
|
539
538
|
const meta = {
|
|
540
539
|
id,
|
|
541
540
|
account: options.account,
|
|
@@ -548,11 +547,11 @@ async function saveDraft(options) {
|
|
|
548
547
|
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
549
548
|
};
|
|
550
549
|
const output = matter2.stringify(options.content, meta);
|
|
551
|
-
await
|
|
550
|
+
await writeFile4(filePath, output, "utf-8");
|
|
552
551
|
return { filePath, meta, content: options.content };
|
|
553
552
|
}
|
|
554
553
|
async function publishDraft(options) {
|
|
555
|
-
const raw = await
|
|
554
|
+
const raw = await readFile5(options.file, "utf-8");
|
|
556
555
|
const { data, content } = matter2(raw);
|
|
557
556
|
const meta = data;
|
|
558
557
|
meta.status = "published";
|
|
@@ -561,10 +560,10 @@ async function publishDraft(options) {
|
|
|
561
560
|
meta.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
562
561
|
const config = await getConfig();
|
|
563
562
|
const publishedDir = getPublishedDir(meta.account, config.workflowDir);
|
|
564
|
-
await
|
|
565
|
-
const newPath =
|
|
563
|
+
await mkdir3(publishedDir, { recursive: true });
|
|
564
|
+
const newPath = join5(publishedDir, basename(options.file));
|
|
566
565
|
const output = matter2.stringify(content.trim(), meta);
|
|
567
|
-
await
|
|
566
|
+
await writeFile4(newPath, output, "utf-8");
|
|
568
567
|
await unlink(options.file);
|
|
569
568
|
return { filePath: newPath, meta, content: content.trim() };
|
|
570
569
|
}
|
|
@@ -736,16 +735,38 @@ async function analyticsReport(input) {
|
|
|
736
735
|
|
|
737
736
|
// src/tools/sync.ts
|
|
738
737
|
import { z as z5 } from "zod";
|
|
739
|
-
import { writeFile as
|
|
740
|
-
import { join as
|
|
741
|
-
var
|
|
738
|
+
import { writeFile as writeFile5, mkdir as mkdir4 } from "fs/promises";
|
|
739
|
+
import { join as join6 } from "path";
|
|
740
|
+
var syncSchema = z5.object({
|
|
741
|
+
action: z5.enum(["push", "pull", "sync"]).describe("push=\u63A8\u9001\u672C\u5730\u5230\u4E91\u7AEF, pull=\u62C9\u53D6\u4E91\u7AEF\u5230\u672C\u5730, sync=\u5148push\u518Dpull"),
|
|
742
742
|
workDir: z5.string().optional().describe("\u5DE5\u4F5C\u76EE\u5F55\uFF08\u9ED8\u8BA4\u4F7F\u7528\u914D\u7F6E\u4E2D\u7684 workflowDir\uFF09")
|
|
743
743
|
});
|
|
744
|
-
async function
|
|
744
|
+
async function sync(input) {
|
|
745
|
+
if (input.workDir) setWorkDir(input.workDir);
|
|
745
746
|
const creds = await getCredentials();
|
|
746
747
|
if (!creds?.token) {
|
|
747
748
|
return { success: false, message: "\u672A\u6FC0\u6D3B\uFF0C\u8BF7\u5148\u4F7F\u7528 workflow.init \u6FC0\u6D3B License" };
|
|
748
749
|
}
|
|
750
|
+
if (input.action === "push") {
|
|
751
|
+
return doPush(creds.token);
|
|
752
|
+
} else if (input.action === "pull") {
|
|
753
|
+
return doPull(creds.token, input.workDir);
|
|
754
|
+
} else {
|
|
755
|
+
const pushResult = await doPush(creds.token);
|
|
756
|
+
const pullResult = await doPull(creds.token, input.workDir);
|
|
757
|
+
const messages = [];
|
|
758
|
+
if (pushResult.success) messages.push(pushResult.message);
|
|
759
|
+
else messages.push(`Push \u5931\u8D25: ${pushResult.message}`);
|
|
760
|
+
if (pullResult.success) messages.push(pullResult.message);
|
|
761
|
+
else messages.push(`Pull \u5931\u8D25: ${pullResult.message}`);
|
|
762
|
+
return {
|
|
763
|
+
success: pushResult.success && pullResult.success,
|
|
764
|
+
message: messages.join("\n\n"),
|
|
765
|
+
data: { push: pushResult.data, pull: pullResult.data }
|
|
766
|
+
};
|
|
767
|
+
}
|
|
768
|
+
}
|
|
769
|
+
async function doPush(token) {
|
|
749
770
|
const config = await getConfig();
|
|
750
771
|
const state = await readState();
|
|
751
772
|
const payload = {
|
|
@@ -778,7 +799,7 @@ async function syncPush(input) {
|
|
|
778
799
|
platformUrl: p.platformUrl,
|
|
779
800
|
publishedAt: p.publishedAt
|
|
780
801
|
})),
|
|
781
|
-
configs: Object.entries(state.configs).map(([
|
|
802
|
+
configs: Object.entries(state.configs).map(([_key, c]) => ({
|
|
782
803
|
type: c.type,
|
|
783
804
|
name: c.name,
|
|
784
805
|
displayName: c.displayName,
|
|
@@ -800,7 +821,7 @@ async function syncPush(input) {
|
|
|
800
821
|
method: "POST",
|
|
801
822
|
headers: {
|
|
802
823
|
"Content-Type": "application/json",
|
|
803
|
-
"Authorization": `Bearer ${
|
|
824
|
+
"Authorization": `Bearer ${token}`
|
|
804
825
|
},
|
|
805
826
|
body: JSON.stringify(payload)
|
|
806
827
|
});
|
|
@@ -810,7 +831,7 @@ async function syncPush(input) {
|
|
|
810
831
|
return {
|
|
811
832
|
success: true,
|
|
812
833
|
message: [
|
|
813
|
-
"\u2705 \
|
|
834
|
+
"\u2705 \u63A8\u9001\u5B8C\u6210",
|
|
814
835
|
` \u7D20\u6750: ${payload.sources.length} \u7BC7`,
|
|
815
836
|
` \u8349\u7A3F: ${payload.drafts.length} \u7BC7`,
|
|
816
837
|
` \u5DF2\u53D1\u5E03: ${payload.published.length} \u7BC7`,
|
|
@@ -821,25 +842,18 @@ async function syncPush(input) {
|
|
|
821
842
|
data: { accepted: result.accepted || 0 }
|
|
822
843
|
};
|
|
823
844
|
} else {
|
|
824
|
-
return { success: false, message: `\
|
|
845
|
+
return { success: false, message: `\u63A8\u9001\u5931\u8D25: HTTP ${res.status}` };
|
|
825
846
|
}
|
|
826
847
|
} catch (err) {
|
|
827
|
-
return { success: false, message: `\
|
|
848
|
+
return { success: false, message: `\u63A8\u9001\u7F51\u7EDC\u9519\u8BEF: ${err instanceof Error ? err.message : err}` };
|
|
828
849
|
}
|
|
829
850
|
}
|
|
830
|
-
|
|
831
|
-
workDir: z5.string().optional().describe("\u5DE5\u4F5C\u76EE\u5F55")
|
|
832
|
-
});
|
|
833
|
-
async function syncPull(input) {
|
|
834
|
-
const creds = await getCredentials();
|
|
835
|
-
if (!creds?.token) {
|
|
836
|
-
return { success: false, message: "\u672A\u6FC0\u6D3B\uFF0C\u8BF7\u5148\u4F7F\u7528 workflow.init \u6FC0\u6D3B License" };
|
|
837
|
-
}
|
|
851
|
+
async function doPull(token, workDir) {
|
|
838
852
|
const config = await getConfig();
|
|
839
|
-
const
|
|
853
|
+
const effectiveWorkDir = workDir || config.workflowDir;
|
|
840
854
|
try {
|
|
841
855
|
const res = await fetch(`${config.apiBaseUrl}/api/sync/pull`, {
|
|
842
|
-
headers: { "Authorization": `Bearer ${
|
|
856
|
+
headers: { "Authorization": `Bearer ${token}` }
|
|
843
857
|
});
|
|
844
858
|
if (!res.ok) {
|
|
845
859
|
if (res.status === 404) {
|
|
@@ -859,14 +873,14 @@ async function syncPull(input) {
|
|
|
859
873
|
if (!localUpdatedAt || cfgItem.updatedAt > localUpdatedAt) {
|
|
860
874
|
let filePath = "";
|
|
861
875
|
if (cfgItem.type === "base_rules") {
|
|
862
|
-
filePath =
|
|
876
|
+
filePath = join6(effectiveWorkDir, "base-rules.md");
|
|
863
877
|
} else if (cfgItem.type === "platform") {
|
|
864
|
-
filePath =
|
|
878
|
+
filePath = join6(effectiveWorkDir, "platforms", `${cfgItem.name}.md`);
|
|
865
879
|
} else if (cfgItem.type === "account") {
|
|
866
|
-
filePath =
|
|
880
|
+
filePath = join6(effectiveWorkDir, "accounts", `${cfgItem.name}.yaml`);
|
|
867
881
|
}
|
|
868
882
|
if (filePath && cfgItem.content) {
|
|
869
|
-
await
|
|
883
|
+
await writeFile5(filePath, cfgItem.content, "utf-8");
|
|
870
884
|
state.configs[stateKey] = {
|
|
871
885
|
type: cfgItem.type,
|
|
872
886
|
name: cfgItem.name,
|
|
@@ -894,13 +908,13 @@ async function syncPull(input) {
|
|
|
894
908
|
enabled: s.enabled !== false
|
|
895
909
|
};
|
|
896
910
|
}
|
|
897
|
-
const crawlerDir =
|
|
898
|
-
await
|
|
899
|
-
const crawlerConfigPath =
|
|
900
|
-
await
|
|
911
|
+
const crawlerDir = join6(effectiveWorkDir, "tools", "crawler");
|
|
912
|
+
await mkdir4(crawlerDir, { recursive: true });
|
|
913
|
+
const crawlerConfigPath = join6(crawlerDir, "config.json");
|
|
914
|
+
await writeFile5(crawlerConfigPath, JSON.stringify({ sources }, null, 2), "utf-8");
|
|
901
915
|
crawlerCount = sources.length;
|
|
902
916
|
} catch (e) {
|
|
903
|
-
console.error("[sync
|
|
917
|
+
console.error("[sync] Failed to parse crawler sources:", e);
|
|
904
918
|
}
|
|
905
919
|
}
|
|
906
920
|
}
|
|
@@ -929,16 +943,24 @@ async function syncPull(input) {
|
|
|
929
943
|
return { success: false, message: `\u62C9\u53D6\u9519\u8BEF: ${err instanceof Error ? err.message : err}` };
|
|
930
944
|
}
|
|
931
945
|
}
|
|
946
|
+
async function syncPull(input) {
|
|
947
|
+
if (input.workDir) setWorkDir(input.workDir);
|
|
948
|
+
const creds = await getCredentials();
|
|
949
|
+
if (!creds?.token) {
|
|
950
|
+
return { success: false, message: "\u672A\u6FC0\u6D3B" };
|
|
951
|
+
}
|
|
952
|
+
return doPull(creds.token, input.workDir);
|
|
953
|
+
}
|
|
932
954
|
|
|
933
955
|
// src/tools/workflow.ts
|
|
934
956
|
import { z as z6 } from "zod";
|
|
935
|
-
import { cp, mkdir as
|
|
936
|
-
import { join as
|
|
957
|
+
import { cp, mkdir as mkdir5, access as access2, writeFile as writeFile6, chmod as chmod2 } from "fs/promises";
|
|
958
|
+
import { join as join7, dirname } from "path";
|
|
937
959
|
import { fileURLToPath } from "url";
|
|
938
960
|
var DEFAULT_API_BASE_URL = "https://app.claudeink.com";
|
|
939
961
|
var __filename = fileURLToPath(import.meta.url);
|
|
940
962
|
var __dirname = dirname(__filename);
|
|
941
|
-
var WORKFLOW_SRC =
|
|
963
|
+
var WORKFLOW_SRC = join7(__dirname, "..", "workflow");
|
|
942
964
|
var workflowInitSchema = z6.object({
|
|
943
965
|
workDir: z6.string().describe("\u5DE5\u4F5C\u6D41\u521D\u59CB\u5316\u76EE\u6807\u76EE\u5F55\uFF08\u7EDD\u5BF9\u8DEF\u5F84\uFF09"),
|
|
944
966
|
licenseKey: z6.string().optional().describe("License Key\uFF08\u53EF\u9009\uFF0C\u4F20\u5165\u5219\u81EA\u52A8\u6FC0\u6D3B\uFF09")
|
|
@@ -950,8 +972,8 @@ async function workflowInit(input) {
|
|
|
950
972
|
setWorkDir(cwd);
|
|
951
973
|
const items = ["CLAUDE.md", "base-rules.md", "platforms", "accounts", "tools"];
|
|
952
974
|
for (const item of items) {
|
|
953
|
-
const src =
|
|
954
|
-
const dest =
|
|
975
|
+
const src = join7(WORKFLOW_SRC, item);
|
|
976
|
+
const dest = join7(cwd, item);
|
|
955
977
|
try {
|
|
956
978
|
await access2(dest);
|
|
957
979
|
results.push(`\u23ED\uFE0F ${item} \u5DF2\u5B58\u5728\uFF0C\u8DF3\u8FC7`);
|
|
@@ -960,7 +982,7 @@ async function workflowInit(input) {
|
|
|
960
982
|
results.push(`\u2705 ${item}`);
|
|
961
983
|
}
|
|
962
984
|
}
|
|
963
|
-
const claudeinkDir =
|
|
985
|
+
const claudeinkDir = join7(cwd, ".claudeink");
|
|
964
986
|
const dirs = [
|
|
965
987
|
"sources/articles",
|
|
966
988
|
"sources/video-transcripts",
|
|
@@ -970,34 +992,35 @@ async function workflowInit(input) {
|
|
|
970
992
|
"templates"
|
|
971
993
|
];
|
|
972
994
|
for (const dir of dirs) {
|
|
973
|
-
await
|
|
995
|
+
await mkdir5(join7(cwd, dir), { recursive: true });
|
|
974
996
|
}
|
|
975
|
-
await
|
|
997
|
+
await mkdir5(claudeinkDir, { recursive: true });
|
|
976
998
|
results.push("\u2705 \u8FD0\u884C\u65F6\u76EE\u5F55\u5DF2\u521B\u5EFA");
|
|
977
|
-
const
|
|
978
|
-
const statePath = join8(claudeinkDir, "state.json");
|
|
979
|
-
await writeFile7(configPath, JSON.stringify({
|
|
980
|
-
apiBaseUrl: DEFAULT_API_BASE_URL,
|
|
981
|
-
syncIntervalMs: 3e5,
|
|
982
|
-
heartbeatIntervalMs: 3e5,
|
|
983
|
-
maxTagQueueBatch: 20,
|
|
984
|
-
workflowDir: cwd
|
|
985
|
-
}, null, 2), "utf-8");
|
|
986
|
-
results.push("\u2705 config.json");
|
|
999
|
+
const statePath = join7(claudeinkDir, "state.json");
|
|
987
1000
|
try {
|
|
988
1001
|
await access2(statePath);
|
|
989
1002
|
results.push("\u23ED\uFE0F state.json \u5DF2\u5B58\u5728\uFF0C\u8DF3\u8FC7");
|
|
990
1003
|
} catch {
|
|
991
|
-
const
|
|
1004
|
+
const initialState = {
|
|
1005
|
+
credentials: null,
|
|
1006
|
+
config: {
|
|
1007
|
+
apiBaseUrl: DEFAULT_API_BASE_URL,
|
|
1008
|
+
workflowDir: cwd
|
|
1009
|
+
},
|
|
992
1010
|
sources: {},
|
|
993
1011
|
drafts: {},
|
|
994
1012
|
published: {},
|
|
995
1013
|
configs: {},
|
|
996
1014
|
crawlerSources: {},
|
|
997
1015
|
writingMasters: {},
|
|
1016
|
+
tagQueue: {
|
|
1017
|
+
queue: [],
|
|
1018
|
+
failed: []
|
|
1019
|
+
},
|
|
998
1020
|
lastSyncAt: ""
|
|
999
1021
|
};
|
|
1000
|
-
await
|
|
1022
|
+
await writeFile6(statePath, JSON.stringify(initialState, null, 2), "utf-8");
|
|
1023
|
+
await chmod2(statePath, 384);
|
|
1001
1024
|
results.push("\u2705 state.json");
|
|
1002
1025
|
}
|
|
1003
1026
|
let activated = false;
|
|
@@ -1011,17 +1034,15 @@ async function workflowInit(input) {
|
|
|
1011
1034
|
});
|
|
1012
1035
|
const data = await res.json();
|
|
1013
1036
|
if (data.userId) {
|
|
1014
|
-
await
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
{ mode: 384 }
|
|
1024
|
-
);
|
|
1037
|
+
const state = await readState();
|
|
1038
|
+
state.credentials = {
|
|
1039
|
+
licenseKey: input.licenseKey,
|
|
1040
|
+
token: data.token,
|
|
1041
|
+
userId: data.userId,
|
|
1042
|
+
plan: data.plan,
|
|
1043
|
+
expiresAt: data.expiresAt
|
|
1044
|
+
};
|
|
1045
|
+
await writeState(state);
|
|
1025
1046
|
results.push(`\u2705 License \u6FC0\u6D3B\u6210\u529F\uFF08\u5957\u9910: ${data.plan}\uFF09`);
|
|
1026
1047
|
activated = true;
|
|
1027
1048
|
} else {
|
|
@@ -1044,10 +1065,10 @@ async function workflowInit(input) {
|
|
|
1044
1065
|
}
|
|
1045
1066
|
}
|
|
1046
1067
|
try {
|
|
1047
|
-
await access2(
|
|
1068
|
+
await access2(join7(cwd, "tools", "crawler", "package.json"));
|
|
1048
1069
|
const { execSync } = await import("child_process");
|
|
1049
1070
|
execSync("npm install --silent", {
|
|
1050
|
-
cwd:
|
|
1071
|
+
cwd: join7(cwd, "tools", "crawler"),
|
|
1051
1072
|
stdio: "pipe"
|
|
1052
1073
|
});
|
|
1053
1074
|
results.push("\u2705 \u722C\u866B\u4F9D\u8D56\u5DF2\u5B89\u88C5");
|
|
@@ -1076,18 +1097,14 @@ async function workflowInit(input) {
|
|
|
1076
1097
|
// src/index.ts
|
|
1077
1098
|
var server = new McpServer({
|
|
1078
1099
|
name: "ClaudeInk",
|
|
1079
|
-
version: "0.
|
|
1100
|
+
version: "0.7.0"
|
|
1080
1101
|
});
|
|
1081
1102
|
server.tool("workflow.init", "\u521D\u59CB\u5316\u5199\u4F5C\u5DE5\u4F5C\u6D41\uFF08\u91CA\u653E\u4E09\u5C42\u914D\u7F6E + \u6FC0\u6D3B License + \u81EA\u52A8\u540C\u6B65\u4E91\u7AEF\u914D\u7F6E\uFF09", workflowInitSchema.shape, async (input) => {
|
|
1082
1103
|
const result = await workflowInit(input);
|
|
1083
1104
|
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
1084
1105
|
});
|
|
1085
|
-
server.tool("sync
|
|
1086
|
-
const result = await
|
|
1087
|
-
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
1088
|
-
});
|
|
1089
|
-
server.tool("sync.pull", "\u4ECE\u4E91\u7AEF\u62C9\u53D6\u914D\u7F6E\u5230\u672C\u5730\uFF08\u65F6\u95F4\u6233\u6BD4\u8F83\uFF0C\u65B0\u7684\u8986\u76D6\u65E7\u7684\uFF09", syncPullSchema.shape, async (input) => {
|
|
1090
|
-
const result = await syncPull(input);
|
|
1106
|
+
server.tool("sync", "\u540C\u6B65\u672C\u5730\u4E0E\u4E91\u7AEF\u6570\u636E\uFF08push=\u63A8\u9001, pull=\u62C9\u53D6, sync=\u53CC\u5411\u540C\u6B65\uFF09", syncSchema.shape, async (input) => {
|
|
1107
|
+
const result = await sync(input);
|
|
1091
1108
|
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
1092
1109
|
});
|
|
1093
1110
|
server.tool("source.add", "\u6DFB\u52A0\u7D20\u6750\u5230\u672C\u5730\u7D20\u6750\u5E93", sourceAddSchema.shape, async (input) => {
|
|
@@ -1127,9 +1144,17 @@ server.tool("analytics.report", "\u83B7\u53D6\u6570\u636E\u5206\u6790\u62A5\u544
|
|
|
1127
1144
|
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
1128
1145
|
});
|
|
1129
1146
|
async function main() {
|
|
1147
|
+
try {
|
|
1148
|
+
const state = await readState();
|
|
1149
|
+
if (state.config.workflowDir) {
|
|
1150
|
+
setWorkDir(state.config.workflowDir);
|
|
1151
|
+
console.error(`[ClaudeInk MCP] workDir restored: ${state.config.workflowDir}`);
|
|
1152
|
+
}
|
|
1153
|
+
} catch {
|
|
1154
|
+
}
|
|
1130
1155
|
const transport = new StdioServerTransport();
|
|
1131
1156
|
await server.connect(transport);
|
|
1132
|
-
console.error("[ClaudeInk MCP] Server started on stdio (
|
|
1157
|
+
console.error("[ClaudeInk MCP] Server started on stdio (11 tools)");
|
|
1133
1158
|
}
|
|
1134
1159
|
main().catch((err) => {
|
|
1135
1160
|
console.error("[ClaudeInk MCP] Fatal error:", err);
|