@claudeink/mcp-server 0.6.4 → 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 +252 -240
- 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,17 +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
745
|
if (input.workDir) setWorkDir(input.workDir);
|
|
746
746
|
const creds = await getCredentials();
|
|
747
747
|
if (!creds?.token) {
|
|
748
748
|
return { success: false, message: "\u672A\u6FC0\u6D3B\uFF0C\u8BF7\u5148\u4F7F\u7528 workflow.init \u6FC0\u6D3B License" };
|
|
749
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) {
|
|
750
770
|
const config = await getConfig();
|
|
751
771
|
const state = await readState();
|
|
752
772
|
const payload = {
|
|
@@ -779,7 +799,7 @@ async function syncPush(input) {
|
|
|
779
799
|
platformUrl: p.platformUrl,
|
|
780
800
|
publishedAt: p.publishedAt
|
|
781
801
|
})),
|
|
782
|
-
configs: Object.entries(state.configs).map(([
|
|
802
|
+
configs: Object.entries(state.configs).map(([_key, c]) => ({
|
|
783
803
|
type: c.type,
|
|
784
804
|
name: c.name,
|
|
785
805
|
displayName: c.displayName,
|
|
@@ -801,7 +821,7 @@ async function syncPush(input) {
|
|
|
801
821
|
method: "POST",
|
|
802
822
|
headers: {
|
|
803
823
|
"Content-Type": "application/json",
|
|
804
|
-
"Authorization": `Bearer ${
|
|
824
|
+
"Authorization": `Bearer ${token}`
|
|
805
825
|
},
|
|
806
826
|
body: JSON.stringify(payload)
|
|
807
827
|
});
|
|
@@ -811,7 +831,7 @@ async function syncPush(input) {
|
|
|
811
831
|
return {
|
|
812
832
|
success: true,
|
|
813
833
|
message: [
|
|
814
|
-
"\u2705 \
|
|
834
|
+
"\u2705 \u63A8\u9001\u5B8C\u6210",
|
|
815
835
|
` \u7D20\u6750: ${payload.sources.length} \u7BC7`,
|
|
816
836
|
` \u8349\u7A3F: ${payload.drafts.length} \u7BC7`,
|
|
817
837
|
` \u5DF2\u53D1\u5E03: ${payload.published.length} \u7BC7`,
|
|
@@ -822,26 +842,18 @@ async function syncPush(input) {
|
|
|
822
842
|
data: { accepted: result.accepted || 0 }
|
|
823
843
|
};
|
|
824
844
|
} else {
|
|
825
|
-
return { success: false, message: `\
|
|
845
|
+
return { success: false, message: `\u63A8\u9001\u5931\u8D25: HTTP ${res.status}` };
|
|
826
846
|
}
|
|
827
847
|
} catch (err) {
|
|
828
|
-
return { success: false, message: `\
|
|
848
|
+
return { success: false, message: `\u63A8\u9001\u7F51\u7EDC\u9519\u8BEF: ${err instanceof Error ? err.message : err}` };
|
|
829
849
|
}
|
|
830
850
|
}
|
|
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
|
-
}
|
|
851
|
+
async function doPull(token, workDir) {
|
|
840
852
|
const config = await getConfig();
|
|
841
|
-
const
|
|
853
|
+
const effectiveWorkDir = workDir || config.workflowDir;
|
|
842
854
|
try {
|
|
843
855
|
const res = await fetch(`${config.apiBaseUrl}/api/sync/pull`, {
|
|
844
|
-
headers: { "Authorization": `Bearer ${
|
|
856
|
+
headers: { "Authorization": `Bearer ${token}` }
|
|
845
857
|
});
|
|
846
858
|
if (!res.ok) {
|
|
847
859
|
if (res.status === 404) {
|
|
@@ -861,14 +873,14 @@ async function syncPull(input) {
|
|
|
861
873
|
if (!localUpdatedAt || cfgItem.updatedAt > localUpdatedAt) {
|
|
862
874
|
let filePath = "";
|
|
863
875
|
if (cfgItem.type === "base_rules") {
|
|
864
|
-
filePath =
|
|
876
|
+
filePath = join6(effectiveWorkDir, "base-rules.md");
|
|
865
877
|
} else if (cfgItem.type === "platform") {
|
|
866
|
-
filePath =
|
|
878
|
+
filePath = join6(effectiveWorkDir, "platforms", `${cfgItem.name}.md`);
|
|
867
879
|
} else if (cfgItem.type === "account") {
|
|
868
|
-
filePath =
|
|
880
|
+
filePath = join6(effectiveWorkDir, "accounts", `${cfgItem.name}.yaml`);
|
|
869
881
|
}
|
|
870
882
|
if (filePath && cfgItem.content) {
|
|
871
|
-
await
|
|
883
|
+
await writeFile5(filePath, cfgItem.content, "utf-8");
|
|
872
884
|
state.configs[stateKey] = {
|
|
873
885
|
type: cfgItem.type,
|
|
874
886
|
name: cfgItem.name,
|
|
@@ -896,13 +908,13 @@ async function syncPull(input) {
|
|
|
896
908
|
enabled: s.enabled !== false
|
|
897
909
|
};
|
|
898
910
|
}
|
|
899
|
-
const crawlerDir =
|
|
900
|
-
await
|
|
901
|
-
const crawlerConfigPath =
|
|
902
|
-
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");
|
|
903
915
|
crawlerCount = sources.length;
|
|
904
916
|
} catch (e) {
|
|
905
|
-
console.error("[sync
|
|
917
|
+
console.error("[sync] Failed to parse crawler sources:", e);
|
|
906
918
|
}
|
|
907
919
|
}
|
|
908
920
|
}
|
|
@@ -931,16 +943,24 @@ async function syncPull(input) {
|
|
|
931
943
|
return { success: false, message: `\u62C9\u53D6\u9519\u8BEF: ${err instanceof Error ? err.message : err}` };
|
|
932
944
|
}
|
|
933
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
|
+
}
|
|
934
954
|
|
|
935
955
|
// src/tools/workflow.ts
|
|
936
956
|
import { z as z6 } from "zod";
|
|
937
|
-
import { cp, mkdir as
|
|
938
|
-
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";
|
|
939
959
|
import { fileURLToPath } from "url";
|
|
940
960
|
var DEFAULT_API_BASE_URL = "https://app.claudeink.com";
|
|
941
961
|
var __filename = fileURLToPath(import.meta.url);
|
|
942
962
|
var __dirname = dirname(__filename);
|
|
943
|
-
var WORKFLOW_SRC =
|
|
963
|
+
var WORKFLOW_SRC = join7(__dirname, "..", "workflow");
|
|
944
964
|
var workflowInitSchema = z6.object({
|
|
945
965
|
workDir: z6.string().describe("\u5DE5\u4F5C\u6D41\u521D\u59CB\u5316\u76EE\u6807\u76EE\u5F55\uFF08\u7EDD\u5BF9\u8DEF\u5F84\uFF09"),
|
|
946
966
|
licenseKey: z6.string().optional().describe("License Key\uFF08\u53EF\u9009\uFF0C\u4F20\u5165\u5219\u81EA\u52A8\u6FC0\u6D3B\uFF09")
|
|
@@ -952,8 +972,8 @@ async function workflowInit(input) {
|
|
|
952
972
|
setWorkDir(cwd);
|
|
953
973
|
const items = ["CLAUDE.md", "base-rules.md", "platforms", "accounts", "tools"];
|
|
954
974
|
for (const item of items) {
|
|
955
|
-
const src =
|
|
956
|
-
const dest =
|
|
975
|
+
const src = join7(WORKFLOW_SRC, item);
|
|
976
|
+
const dest = join7(cwd, item);
|
|
957
977
|
try {
|
|
958
978
|
await access2(dest);
|
|
959
979
|
results.push(`\u23ED\uFE0F ${item} \u5DF2\u5B58\u5728\uFF0C\u8DF3\u8FC7`);
|
|
@@ -962,7 +982,7 @@ async function workflowInit(input) {
|
|
|
962
982
|
results.push(`\u2705 ${item}`);
|
|
963
983
|
}
|
|
964
984
|
}
|
|
965
|
-
const claudeinkDir =
|
|
985
|
+
const claudeinkDir = join7(cwd, ".claudeink");
|
|
966
986
|
const dirs = [
|
|
967
987
|
"sources/articles",
|
|
968
988
|
"sources/video-transcripts",
|
|
@@ -972,34 +992,35 @@ async function workflowInit(input) {
|
|
|
972
992
|
"templates"
|
|
973
993
|
];
|
|
974
994
|
for (const dir of dirs) {
|
|
975
|
-
await
|
|
995
|
+
await mkdir5(join7(cwd, dir), { recursive: true });
|
|
976
996
|
}
|
|
977
|
-
await
|
|
997
|
+
await mkdir5(claudeinkDir, { recursive: true });
|
|
978
998
|
results.push("\u2705 \u8FD0\u884C\u65F6\u76EE\u5F55\u5DF2\u521B\u5EFA");
|
|
979
|
-
const
|
|
980
|
-
const statePath = join8(claudeinkDir, "state.json");
|
|
981
|
-
await writeFile7(configPath, JSON.stringify({
|
|
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");
|
|
999
|
+
const statePath = join7(claudeinkDir, "state.json");
|
|
989
1000
|
try {
|
|
990
1001
|
await access2(statePath);
|
|
991
1002
|
results.push("\u23ED\uFE0F state.json \u5DF2\u5B58\u5728\uFF0C\u8DF3\u8FC7");
|
|
992
1003
|
} catch {
|
|
993
|
-
const
|
|
1004
|
+
const initialState = {
|
|
1005
|
+
credentials: null,
|
|
1006
|
+
config: {
|
|
1007
|
+
apiBaseUrl: DEFAULT_API_BASE_URL,
|
|
1008
|
+
workflowDir: cwd
|
|
1009
|
+
},
|
|
994
1010
|
sources: {},
|
|
995
1011
|
drafts: {},
|
|
996
1012
|
published: {},
|
|
997
1013
|
configs: {},
|
|
998
1014
|
crawlerSources: {},
|
|
999
1015
|
writingMasters: {},
|
|
1016
|
+
tagQueue: {
|
|
1017
|
+
queue: [],
|
|
1018
|
+
failed: []
|
|
1019
|
+
},
|
|
1000
1020
|
lastSyncAt: ""
|
|
1001
1021
|
};
|
|
1002
|
-
await
|
|
1022
|
+
await writeFile6(statePath, JSON.stringify(initialState, null, 2), "utf-8");
|
|
1023
|
+
await chmod2(statePath, 384);
|
|
1003
1024
|
results.push("\u2705 state.json");
|
|
1004
1025
|
}
|
|
1005
1026
|
let activated = false;
|
|
@@ -1013,17 +1034,15 @@ async function workflowInit(input) {
|
|
|
1013
1034
|
});
|
|
1014
1035
|
const data = await res.json();
|
|
1015
1036
|
if (data.userId) {
|
|
1016
|
-
await
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
{ mode: 384 }
|
|
1026
|
-
);
|
|
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);
|
|
1027
1046
|
results.push(`\u2705 License \u6FC0\u6D3B\u6210\u529F\uFF08\u5957\u9910: ${data.plan}\uFF09`);
|
|
1028
1047
|
activated = true;
|
|
1029
1048
|
} else {
|
|
@@ -1046,10 +1065,10 @@ async function workflowInit(input) {
|
|
|
1046
1065
|
}
|
|
1047
1066
|
}
|
|
1048
1067
|
try {
|
|
1049
|
-
await access2(
|
|
1068
|
+
await access2(join7(cwd, "tools", "crawler", "package.json"));
|
|
1050
1069
|
const { execSync } = await import("child_process");
|
|
1051
1070
|
execSync("npm install --silent", {
|
|
1052
|
-
cwd:
|
|
1071
|
+
cwd: join7(cwd, "tools", "crawler"),
|
|
1053
1072
|
stdio: "pipe"
|
|
1054
1073
|
});
|
|
1055
1074
|
results.push("\u2705 \u722C\u866B\u4F9D\u8D56\u5DF2\u5B89\u88C5");
|
|
@@ -1076,22 +1095,16 @@ async function workflowInit(input) {
|
|
|
1076
1095
|
}
|
|
1077
1096
|
|
|
1078
1097
|
// src/index.ts
|
|
1079
|
-
import { readFile as readFile7 } from "fs/promises";
|
|
1080
|
-
import { join as join9 } from "path";
|
|
1081
1098
|
var server = new McpServer({
|
|
1082
1099
|
name: "ClaudeInk",
|
|
1083
|
-
version: "0.
|
|
1100
|
+
version: "0.7.0"
|
|
1084
1101
|
});
|
|
1085
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) => {
|
|
1086
1103
|
const result = await workflowInit(input);
|
|
1087
1104
|
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
1088
1105
|
});
|
|
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);
|
|
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);
|
|
1095
1108
|
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
1096
1109
|
});
|
|
1097
1110
|
server.tool("source.add", "\u6DFB\u52A0\u7D20\u6750\u5230\u672C\u5730\u7D20\u6750\u5E93", sourceAddSchema.shape, async (input) => {
|
|
@@ -1132,17 +1145,16 @@ server.tool("analytics.report", "\u83B7\u53D6\u6570\u636E\u5206\u6790\u62A5\u544
|
|
|
1132
1145
|
});
|
|
1133
1146
|
async function main() {
|
|
1134
1147
|
try {
|
|
1135
|
-
const
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
console.error(`[ClaudeInk MCP] workDir restored: ${config.workflowDir}`);
|
|
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}`);
|
|
1140
1152
|
}
|
|
1141
1153
|
} catch {
|
|
1142
1154
|
}
|
|
1143
1155
|
const transport = new StdioServerTransport();
|
|
1144
1156
|
await server.connect(transport);
|
|
1145
|
-
console.error("[ClaudeInk MCP] Server started on stdio (
|
|
1157
|
+
console.error("[ClaudeInk MCP] Server started on stdio (11 tools)");
|
|
1146
1158
|
}
|
|
1147
1159
|
main().catch((err) => {
|
|
1148
1160
|
console.error("[ClaudeInk MCP] Fatal error:", err);
|