@noelclaw/mcp 2.3.1 → 2.4.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 +10 -9
- package/dist/server.js +38 -28
- package/dist/tools/automation.js +38 -0
- package/dist/tools/coder.js +62 -0
- package/dist/tools/defi.js +127 -0
- package/dist/tools/humanizer.js +143 -2
- package/dist/tools/insight.js +197 -19
- package/dist/tools/market.js +182 -7
- package/dist/tools/memory.js +159 -43
- package/dist/tools/miroshark.js +15 -4
- package/dist/tools/os.js +223 -0
- package/dist/tools/scanner.js +183 -52
- package/dist/tools/swarm.js +327 -14
- package/dist/tools/vault.js +89 -144
- package/package.json +5 -2
- package/dist/tools/news.js +0 -6
- package/dist/tools/research.js +0 -8
- package/dist/tools/twitter.js +0 -67
package/dist/tools/vault.js
CHANGED
|
@@ -13,7 +13,8 @@ exports.VAULT_TOOLS = [
|
|
|
13
13
|
description: "Save or update an artifact in Noel-Vault — the persistent memory layer for agents. " +
|
|
14
14
|
"Use this to store research outputs, execution logs, workflows, versioned prompts, " +
|
|
15
15
|
"generated files, or long-term memory. Each save creates a new version automatically. " +
|
|
16
|
-
"Same key = update existing (git-style). Types: research | execution | workflow | prompt | file | memory."
|
|
16
|
+
"Same key = update existing (git-style). Types: research | execution | workflow | prompt | file | memory. " +
|
|
17
|
+
"For a quick note or preference, use type='memory' with just content — title is optional.",
|
|
17
18
|
inputSchema: {
|
|
18
19
|
type: "object",
|
|
19
20
|
properties: {
|
|
@@ -100,7 +101,7 @@ exports.VAULT_TOOLS = [
|
|
|
100
101
|
{
|
|
101
102
|
name: "vault_export",
|
|
102
103
|
description: "Export your entire Noel-Vault or a specific type as a structured bundle. " +
|
|
103
|
-
"Useful for archiving, syncing to
|
|
104
|
+
"Useful for archiving, syncing to GitHub, or passing context to another agent.",
|
|
104
105
|
inputSchema: {
|
|
105
106
|
type: "object",
|
|
106
107
|
properties: {
|
|
@@ -109,38 +110,6 @@ exports.VAULT_TOOLS = [
|
|
|
109
110
|
required: [],
|
|
110
111
|
},
|
|
111
112
|
},
|
|
112
|
-
{
|
|
113
|
-
name: "vault_remember",
|
|
114
|
-
description: "One-liner: save anything to Noel-Vault without specifying type or title. " +
|
|
115
|
-
"Just pass content — Noel infers the title and saves as a memory entry. " +
|
|
116
|
-
"Use this for quick notes, preferences, decisions, and things to remember across sessions. " +
|
|
117
|
-
"Example: vault_remember content: 'User prefers low-risk DeFi, confirmed multiple times'",
|
|
118
|
-
inputSchema: {
|
|
119
|
-
type: "object",
|
|
120
|
-
properties: {
|
|
121
|
-
content: { type: "string", description: "What to remember — plain text, markdown, or a short note" },
|
|
122
|
-
title: { type: "string", description: "Optional title. Auto-inferred from content if omitted." },
|
|
123
|
-
tags: { type: "array", items: { type: "string" }, description: "Optional tags for later retrieval" },
|
|
124
|
-
},
|
|
125
|
-
required: ["content"],
|
|
126
|
-
},
|
|
127
|
-
},
|
|
128
|
-
{
|
|
129
|
-
name: "vault_context",
|
|
130
|
-
description: "Load semantically relevant vault entries for a topic — formatted context ready to inject into a prompt. " +
|
|
131
|
-
"Uses Supermemory vector search when available (understands meaning, not just keywords), " +
|
|
132
|
-
"with automatic fallback to full-text search. " +
|
|
133
|
-
"Call this at the start of any research or analysis task to prime your AI with all past knowledge on the topic. " +
|
|
134
|
-
"Returns full content of matching entries. Credentials are never included.",
|
|
135
|
-
inputSchema: {
|
|
136
|
-
type: "object",
|
|
137
|
-
properties: {
|
|
138
|
-
topic: { type: "string", description: "Topic or query to load context for, e.g. 'ETH DeFi strategy' or 'BTC market thesis'" },
|
|
139
|
-
limit: { type: "number", description: "Max entries to return (default 8, max 20)" },
|
|
140
|
-
},
|
|
141
|
-
required: ["topic"],
|
|
142
|
-
},
|
|
143
|
-
},
|
|
144
113
|
{
|
|
145
114
|
name: "vault_store_credential",
|
|
146
115
|
description: "Securely store an API key, token, or secret in your vault. " +
|
|
@@ -199,20 +168,56 @@ exports.VAULT_TOOLS = [
|
|
|
199
168
|
},
|
|
200
169
|
},
|
|
201
170
|
{
|
|
202
|
-
name: "
|
|
203
|
-
description: "
|
|
204
|
-
"
|
|
205
|
-
|
|
206
|
-
|
|
171
|
+
name: "vault_pin",
|
|
172
|
+
description: "Pin or unpin a Noel-Vault entry. Pinned entries always appear first in vault_list and are " +
|
|
173
|
+
"prioritized in memory_context and search results. Use for your most important research, key prompts, or canonical references.",
|
|
174
|
+
inputSchema: {
|
|
175
|
+
type: "object",
|
|
176
|
+
properties: {
|
|
177
|
+
key: { type: "string", description: "Entry key to pin or unpin" },
|
|
178
|
+
pinned: { type: "boolean", description: "true to pin, false to unpin (default: true)" },
|
|
179
|
+
},
|
|
180
|
+
required: ["key"],
|
|
181
|
+
},
|
|
182
|
+
},
|
|
183
|
+
{
|
|
184
|
+
name: "vault_delete",
|
|
185
|
+
description: "Permanently delete a Noel-Vault entry including all its version history. This cannot be undone. " +
|
|
186
|
+
"Use vault_list to browse entries before deleting.",
|
|
187
|
+
inputSchema: {
|
|
188
|
+
type: "object",
|
|
189
|
+
properties: {
|
|
190
|
+
key: { type: "string", description: "Entry key to delete permanently" },
|
|
191
|
+
},
|
|
192
|
+
required: ["key"],
|
|
193
|
+
},
|
|
194
|
+
},
|
|
195
|
+
{
|
|
196
|
+
name: "vault_link",
|
|
197
|
+
description: "Create a semantic link between two Noel-Vault entries. Links appear in vault_read output and " +
|
|
198
|
+
"help agents navigate related knowledge. Relations: references | derived_from | supersedes | related | continues.",
|
|
199
|
+
inputSchema: {
|
|
200
|
+
type: "object",
|
|
201
|
+
properties: {
|
|
202
|
+
fromKey: { type: "string", description: "Source entry key" },
|
|
203
|
+
toKey: { type: "string", description: "Target entry key" },
|
|
204
|
+
relation: { type: "string", enum: [...LINK_RELATIONS], description: "Relationship type (default: related)" },
|
|
205
|
+
},
|
|
206
|
+
required: ["fromKey", "toKey"],
|
|
207
|
+
},
|
|
208
|
+
},
|
|
209
|
+
{
|
|
210
|
+
name: "vault_tag",
|
|
211
|
+
description: "Add or replace tags on an existing Noel-Vault entry without modifying its content. " +
|
|
212
|
+
"Useful for organizing entries retroactively. Set replace=true to overwrite all existing tags.",
|
|
207
213
|
inputSchema: {
|
|
208
214
|
type: "object",
|
|
209
215
|
properties: {
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
tags: { type: "array", items: { type: "string" }, description: "Tags for filtering" },
|
|
216
|
+
key: { type: "string", description: "Entry key to update tags on" },
|
|
217
|
+
tags: { type: "array", items: { type: "string" }, description: "Tags to add (or replace if replace=true)" },
|
|
218
|
+
replace: { type: "boolean", description: "If true, replaces all existing tags. If false (default), merges with existing." },
|
|
214
219
|
},
|
|
215
|
-
required: ["
|
|
220
|
+
required: ["key", "tags"],
|
|
216
221
|
},
|
|
217
222
|
},
|
|
218
223
|
];
|
|
@@ -243,13 +248,14 @@ const SearchSchema = zod_1.z.object({
|
|
|
243
248
|
const HistorySchema = zod_1.z.object({ key: zod_1.z.string().min(1) });
|
|
244
249
|
const DiffSchema = zod_1.z.object({ key: zod_1.z.string().min(1), fromVersion: zod_1.z.number(), toVersion: zod_1.z.number() });
|
|
245
250
|
const ExportSchema = zod_1.z.object({ type: zod_1.z.enum(VAULT_TYPES).optional() });
|
|
246
|
-
const RememberSchema = zod_1.z.object({ content: zod_1.z.string().min(1), title: zod_1.z.string().optional(), tags: zod_1.z.array(zod_1.z.string()).optional() });
|
|
247
|
-
const ContextSchema = zod_1.z.object({ topic: zod_1.z.string().min(1), limit: zod_1.z.number().optional() });
|
|
248
251
|
const StoreCredentialSchema = zod_1.z.object({ name: zod_1.z.string().min(1), value: zod_1.z.string().min(1), description: zod_1.z.string().optional() });
|
|
249
252
|
const GetCredentialSchema = zod_1.z.object({ name: zod_1.z.string().min(1) });
|
|
250
253
|
const PublishSchema = zod_1.z.object({ key: zod_1.z.string().min(1), isPublic: zod_1.z.boolean().optional(), authorName: zod_1.z.string().optional() });
|
|
251
254
|
const ExploreSchema = zod_1.z.object({ type: zod_1.z.enum(["research", "execution", "workflow", "prompt", "file", "memory"]).optional(), search: zod_1.z.string().optional(), limit: zod_1.z.number().optional() });
|
|
252
|
-
const
|
|
255
|
+
const PinSchema = zod_1.z.object({ key: zod_1.z.string().min(1), pinned: zod_1.z.boolean().optional() });
|
|
256
|
+
const DeleteSchema = zod_1.z.object({ key: zod_1.z.string().min(1) });
|
|
257
|
+
const LinkSchema = zod_1.z.object({ fromKey: zod_1.z.string().min(1), toKey: zod_1.z.string().min(1), relation: zod_1.z.enum(LINK_RELATIONS).optional() });
|
|
258
|
+
const TagSchema = zod_1.z.object({ key: zod_1.z.string().min(1), tags: zod_1.z.array(zod_1.z.string()).min(1), replace: zod_1.z.boolean().optional() });
|
|
253
259
|
// ─── Helpers ─────────────────────────────────────────────────────────────────
|
|
254
260
|
function formatBytes(n) {
|
|
255
261
|
if (!n)
|
|
@@ -437,69 +443,6 @@ async function handleVaultTool(name, args) {
|
|
|
437
443
|
const rows = entries.map((e) => `**[\`${e.key}\`]** ${e.title} (${e.type} · v${e.version})\n${e.content.slice(0, 500)}${e.content.length > 500 ? "\n…" : ""}`);
|
|
438
444
|
return { content: [{ type: "text", text: [...header, ...rows.join("\n\n---\n\n").split("\n")].join("\n") }] };
|
|
439
445
|
}
|
|
440
|
-
case "vault_remember": {
|
|
441
|
-
const parsed = RememberSchema.safeParse(args);
|
|
442
|
-
if (!parsed.success)
|
|
443
|
-
return { content: [{ type: "text", text: `Invalid input: ${parsed.error.issues[0].message}` }], isError: true };
|
|
444
|
-
const { content, title, tags } = parsed.data;
|
|
445
|
-
const inferredTitle = title ?? content.slice(0, 60).replace(/\n/g, " ").trim() + (content.length > 60 ? "…" : "");
|
|
446
|
-
const data = await (0, convex_js_1.callConvex)("/vault/save", "POST", {
|
|
447
|
-
type: "memory",
|
|
448
|
-
title: inferredTitle,
|
|
449
|
-
content,
|
|
450
|
-
tags,
|
|
451
|
-
commitMsg: "vault_remember",
|
|
452
|
-
}, "vault_remember");
|
|
453
|
-
if (data.error)
|
|
454
|
-
return { content: [{ type: "text", text: `Error: ${data.error}` }], isError: true };
|
|
455
|
-
// Mirror to semantic memory
|
|
456
|
-
(0, memory_js_1.syncToSupermemory)(content, {
|
|
457
|
-
vaultKey: data.key, title: inferredTitle, type: "memory",
|
|
458
|
-
tags, version: data.version, source: "vault_remember",
|
|
459
|
-
});
|
|
460
|
-
const smNote = " 🧠 Synced to semantic memory";
|
|
461
|
-
return { content: [{ type: "text", text: `✅ Remembered — key: \`${data.key}\` (v${data.version})${smNote}\nRetrieve later with: \`vault_context topic: "${inferredTitle.slice(0, 40)}"\`` }] };
|
|
462
|
-
}
|
|
463
|
-
case "vault_context": {
|
|
464
|
-
const parsed = ContextSchema.safeParse(args);
|
|
465
|
-
if (!parsed.success)
|
|
466
|
-
return { content: [{ type: "text", text: `Invalid input: ${parsed.error.issues[0].message}` }], isError: true };
|
|
467
|
-
const limit = parsed.data.limit ?? 8;
|
|
468
|
-
// Semantic context (proxied through Convex)
|
|
469
|
-
{
|
|
470
|
-
const smResults = await (0, memory_js_1.searchSupermemory)(parsed.data.topic, limit);
|
|
471
|
-
if (smResults.length > 0) {
|
|
472
|
-
const contextParts = smResults.map((r, i) => {
|
|
473
|
-
const title = r.metadata?.title ?? `Entry ${i + 1}`;
|
|
474
|
-
return `### ${title}\n${r.content}`;
|
|
475
|
-
});
|
|
476
|
-
const summary = smResults.map(r => r.metadata?.title ?? r.content.slice(0, 50)).join(", ");
|
|
477
|
-
return {
|
|
478
|
-
content: [{
|
|
479
|
-
type: "text",
|
|
480
|
-
text: [
|
|
481
|
-
`📚 **Vault Context** [Semantic] for: "${parsed.data.topic}"`,
|
|
482
|
-
`Entries: ${summary}`,
|
|
483
|
-
``,
|
|
484
|
-
`---`,
|
|
485
|
-
``,
|
|
486
|
-
contextParts.join("\n\n---\n\n"),
|
|
487
|
-
].join("\n"),
|
|
488
|
-
}],
|
|
489
|
-
};
|
|
490
|
-
}
|
|
491
|
-
}
|
|
492
|
-
// Fallback: Convex full-text context
|
|
493
|
-
const params = new URLSearchParams({ q: parsed.data.topic });
|
|
494
|
-
params.set("limit", String(limit));
|
|
495
|
-
const data = await (0, convex_js_1.callConvex)(`/vault/context?${params}`, "GET", undefined, "vault_context");
|
|
496
|
-
if (data.error)
|
|
497
|
-
return { content: [{ type: "text", text: `Error: ${data.error}` }], isError: true };
|
|
498
|
-
if (!data.context)
|
|
499
|
-
return { content: [{ type: "text", text: `No vault context found for: "${parsed.data.topic}"\nStart building your knowledge base with vault_remember or vault_save.` }] };
|
|
500
|
-
const summary = data.entries.map((e) => `[${e.type}] ${e.title}`).join(", ");
|
|
501
|
-
return { content: [{ type: "text", text: `📚 **Vault Context** loaded for: "${parsed.data.topic}"\nEntries: ${summary}\n\n---\n\n${data.context}` }] };
|
|
502
|
-
}
|
|
503
446
|
case "vault_store_credential": {
|
|
504
447
|
const parsed = StoreCredentialSchema.safeParse(args);
|
|
505
448
|
if (!parsed.success)
|
|
@@ -572,42 +515,44 @@ async function handleVaultTool(name, args) {
|
|
|
572
515
|
}],
|
|
573
516
|
};
|
|
574
517
|
}
|
|
575
|
-
case "
|
|
576
|
-
const parsed =
|
|
518
|
+
case "vault_pin": {
|
|
519
|
+
const parsed = PinSchema.safeParse(args);
|
|
577
520
|
if (!parsed.success)
|
|
578
521
|
return { content: [{ type: "text", text: `Invalid input: ${parsed.error.issues[0].message}` }], isError: true };
|
|
579
|
-
const {
|
|
580
|
-
const
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
522
|
+
const { key, pinned = true } = parsed.data;
|
|
523
|
+
const data = await (0, convex_js_1.callConvex)("/vault/pin", "POST", { key, pinned }, "vault_pin");
|
|
524
|
+
if (data.error)
|
|
525
|
+
return { content: [{ type: "text", text: `Error: ${data.error}` }], isError: true };
|
|
526
|
+
return { content: [{ type: "text", text: pinned ? `📌 Pinned: \`${key}\`` : `📌 Unpinned: \`${key}\`` }] };
|
|
527
|
+
}
|
|
528
|
+
case "vault_delete": {
|
|
529
|
+
const parsed = DeleteSchema.safeParse(args);
|
|
530
|
+
if (!parsed.success)
|
|
531
|
+
return { content: [{ type: "text", text: `Invalid input: ${parsed.error.issues[0].message}` }], isError: true };
|
|
532
|
+
const data = await (0, convex_js_1.callConvex)("/vault/delete", "POST", { key: parsed.data.key }, "vault_delete");
|
|
533
|
+
if (data.error)
|
|
534
|
+
return { content: [{ type: "text", text: `Error: ${data.error}` }], isError: true };
|
|
535
|
+
return { content: [{ type: "text", text: `🗑️ Deleted: \`${parsed.data.key}\` (${data.versionsRemoved ?? 0} versions removed)` }] };
|
|
536
|
+
}
|
|
537
|
+
case "vault_link": {
|
|
538
|
+
const parsed = LinkSchema.safeParse(args);
|
|
539
|
+
if (!parsed.success)
|
|
540
|
+
return { content: [{ type: "text", text: `Invalid input: ${parsed.error.issues[0].message}` }], isError: true };
|
|
541
|
+
const { fromKey, toKey, relation = "related" } = parsed.data;
|
|
542
|
+
const data = await (0, convex_js_1.callConvex)("/vault/link", "POST", { fromKey, toKey, relation }, "vault_link");
|
|
543
|
+
if (data.error)
|
|
544
|
+
return { content: [{ type: "text", text: `Error: ${data.error}` }], isError: true };
|
|
545
|
+
return { content: [{ type: "text", text: `🔗 Linked: \`${fromKey}\` → [${relation}] → \`${toKey}\`` }] };
|
|
546
|
+
}
|
|
547
|
+
case "vault_tag": {
|
|
548
|
+
const parsed = TagSchema.safeParse(args);
|
|
549
|
+
if (!parsed.success)
|
|
550
|
+
return { content: [{ type: "text", text: `Invalid input: ${parsed.error.issues[0].message}` }], isError: true };
|
|
551
|
+
const { key, tags, replace = false } = parsed.data;
|
|
552
|
+
const data = await (0, convex_js_1.callConvex)("/vault/tag", "POST", { key, tags, replace }, "vault_tag");
|
|
553
|
+
if (data.error)
|
|
554
|
+
return { content: [{ type: "text", text: `Error: ${data.error}` }], isError: true };
|
|
555
|
+
return { content: [{ type: "text", text: `🏷️ Tags ${replace ? "set" : "updated"} on \`${key}\`: ${(data.tags ?? tags).join(", ")}` }] };
|
|
611
556
|
}
|
|
612
557
|
default:
|
|
613
558
|
return null;
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@noelclaw/mcp",
|
|
3
|
-
"version": "2.
|
|
4
|
-
"description": "Noelclaw
|
|
3
|
+
"version": "2.4.0",
|
|
4
|
+
"description": "Noelclaw AI Operating System — persistent memory, multi-agent swarm, DeFi execution, market intelligence, and Sentinel-gated playbooks.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"bin": {
|
|
7
7
|
"noelclaw-mcp": "dist/index.js"
|
|
@@ -52,5 +52,8 @@
|
|
|
52
52
|
},
|
|
53
53
|
"engines": {
|
|
54
54
|
"node": ">=18"
|
|
55
|
+
},
|
|
56
|
+
"publishConfig": {
|
|
57
|
+
"access": "public"
|
|
55
58
|
}
|
|
56
59
|
}
|
package/dist/tools/news.js
DELETED
package/dist/tools/research.js
DELETED
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.RESEARCH_TOOLS = void 0;
|
|
4
|
-
exports.handleResearchTool = handleResearchTool;
|
|
5
|
-
exports.RESEARCH_TOOLS = [];
|
|
6
|
-
async function handleResearchTool(_name, _args) {
|
|
7
|
-
return null;
|
|
8
|
-
}
|
package/dist/tools/twitter.js
DELETED
|
@@ -1,67 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.TWITTER_TOOLS = void 0;
|
|
4
|
-
exports.handleTwitterTool = handleTwitterTool;
|
|
5
|
-
const zod_1 = require("zod");
|
|
6
|
-
exports.TWITTER_TOOLS = [
|
|
7
|
-
{
|
|
8
|
-
name: "post_tweet",
|
|
9
|
-
description: "Post a tweet on X (Twitter) via Ayrshare. Requires AYRSHARE_API_KEY env var.",
|
|
10
|
-
inputSchema: {
|
|
11
|
-
type: "object",
|
|
12
|
-
properties: {
|
|
13
|
-
text: { type: "string", description: "Tweet content (max 280 characters)" },
|
|
14
|
-
reply_to: { type: "string", description: "Optional: tweet ID to reply to" },
|
|
15
|
-
},
|
|
16
|
-
required: ["text"],
|
|
17
|
-
},
|
|
18
|
-
},
|
|
19
|
-
];
|
|
20
|
-
const PostTweetSchema = zod_1.z.object({
|
|
21
|
-
text: zod_1.z.string().min(1).max(280),
|
|
22
|
-
reply_to: zod_1.z.string().optional(),
|
|
23
|
-
});
|
|
24
|
-
async function handleTwitterTool(name, args) {
|
|
25
|
-
if (name !== "post_tweet")
|
|
26
|
-
return null;
|
|
27
|
-
const parsed = PostTweetSchema.safeParse(args);
|
|
28
|
-
if (!parsed.success)
|
|
29
|
-
return { content: [{ type: "text", text: `Invalid input: ${parsed.error.issues[0].message}` }], isError: true };
|
|
30
|
-
const apiKey = process.env.AYRSHARE_API_KEY;
|
|
31
|
-
if (!apiKey) {
|
|
32
|
-
return {
|
|
33
|
-
content: [{ type: "text", text: "AYRSHARE_API_KEY not set.\n\nGet your key at ayrshare.com → Profile → API Key" }],
|
|
34
|
-
isError: true,
|
|
35
|
-
};
|
|
36
|
-
}
|
|
37
|
-
const body = {
|
|
38
|
-
post: parsed.data.text,
|
|
39
|
-
platforms: ["twitter"],
|
|
40
|
-
};
|
|
41
|
-
if (parsed.data.reply_to)
|
|
42
|
-
body.twitterOptions = { inReplyToStatusId: parsed.data.reply_to };
|
|
43
|
-
try {
|
|
44
|
-
const res = await fetch("https://app.ayrshare.com/api/post", {
|
|
45
|
-
method: "POST",
|
|
46
|
-
headers: {
|
|
47
|
-
"Content-Type": "application/json",
|
|
48
|
-
Authorization: `Bearer ${apiKey}`,
|
|
49
|
-
},
|
|
50
|
-
body: JSON.stringify(body),
|
|
51
|
-
signal: AbortSignal.timeout(15000),
|
|
52
|
-
});
|
|
53
|
-
const data = await res.json();
|
|
54
|
-
if (!res.ok || data.status === "error") {
|
|
55
|
-
const msg = data?.message ?? data?.errors?.[0] ?? JSON.stringify(data);
|
|
56
|
-
return { content: [{ type: "text", text: `Ayrshare error: ${msg}` }], isError: true };
|
|
57
|
-
}
|
|
58
|
-
const tweetId = data?.postIds?.find((p) => p.platform === "twitter")?.id;
|
|
59
|
-
const tweetUrl = tweetId ? `https://x.com/i/web/status/${tweetId}` : "";
|
|
60
|
-
return {
|
|
61
|
-
content: [{ type: "text", text: `✅ Tweet posted!\n\n"${parsed.data.text}"${tweetUrl ? `\n\n${tweetUrl}` : ""}` }],
|
|
62
|
-
};
|
|
63
|
-
}
|
|
64
|
-
catch (err) {
|
|
65
|
-
return { content: [{ type: "text", text: `Failed to post tweet: ${err.message}` }], isError: true };
|
|
66
|
-
}
|
|
67
|
-
}
|