@clawnet/template-minimal 0.0.1
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/.agents/skills/claude-agent-sdk/.claude-plugin/plugin.json +13 -0
- package/.agents/skills/claude-agent-sdk/SKILL.md +954 -0
- package/.agents/skills/claude-agent-sdk/references/mcp-servers-guide.md +387 -0
- package/.agents/skills/claude-agent-sdk/references/permissions-guide.md +429 -0
- package/.agents/skills/claude-agent-sdk/references/query-api-reference.md +437 -0
- package/.agents/skills/claude-agent-sdk/references/session-management.md +419 -0
- package/.agents/skills/claude-agent-sdk/references/subagents-patterns.md +464 -0
- package/.agents/skills/claude-agent-sdk/references/top-errors.md +503 -0
- package/.agents/skills/claude-agent-sdk/rules/claude-agent-sdk.md +96 -0
- package/.agents/skills/claude-agent-sdk/scripts/check-versions.sh +55 -0
- package/.agents/skills/claude-agent-sdk/templates/basic-query.ts +55 -0
- package/.agents/skills/claude-agent-sdk/templates/custom-mcp-server.ts +161 -0
- package/.agents/skills/claude-agent-sdk/templates/error-handling.ts +283 -0
- package/.agents/skills/claude-agent-sdk/templates/filesystem-settings.ts +211 -0
- package/.agents/skills/claude-agent-sdk/templates/multi-agent-workflow.ts +318 -0
- package/.agents/skills/claude-agent-sdk/templates/package.json +30 -0
- package/.agents/skills/claude-agent-sdk/templates/permission-control.ts +211 -0
- package/.agents/skills/claude-agent-sdk/templates/query-with-tools.ts +54 -0
- package/.agents/skills/claude-agent-sdk/templates/session-management.ts +151 -0
- package/.agents/skills/claude-agent-sdk/templates/subagents-orchestration.ts +166 -0
- package/.agents/skills/claude-agent-sdk/templates/tsconfig.json +22 -0
- package/.claude/settings.local.json +70 -0
- package/.claude/skills/moltbook-example/SKILL.md +79 -0
- package/.claude/skills/post/SKILL.md +130 -0
- package/.env.example +4 -0
- package/.vercel/README.txt +11 -0
- package/.vercel/project.json +1 -0
- package/AGENTS.md +114 -0
- package/CLAUDE.md +532 -0
- package/README.md +44 -0
- package/api/index.ts +3 -0
- package/biome.json +14 -0
- package/clark_avatar.jpeg +0 -0
- package/package.json +21 -0
- package/scripts/wake.ts +38 -0
- package/skills/clawbook/HEARTBEAT.md +142 -0
- package/skills/clawbook/SKILL.md +219 -0
- package/skills/moltbook-example/SKILL.md +79 -0
- package/skills/moltbook-example/bot/index.ts +61 -0
- package/src/agent/prompts.ts +98 -0
- package/src/agent/runner.ts +526 -0
- package/src/agent/tool-definitions.ts +1151 -0
- package/src/agent-options.ts +14 -0
- package/src/bot-identity.ts +41 -0
- package/src/constants.ts +15 -0
- package/src/handlers/heartbeat.ts +21 -0
- package/src/handlers/openai-compat.ts +95 -0
- package/src/handlers/post.ts +21 -0
- package/src/identity.ts +83 -0
- package/src/index.ts +30 -0
- package/src/middleware/cron-auth.ts +53 -0
- package/src/middleware/sigma-auth.ts +147 -0
- package/src/runs.ts +49 -0
- package/tests/agent/prompts.test.ts +172 -0
- package/tests/agent/runner.test.ts +353 -0
- package/tests/agent/tool-definitions.test.ts +171 -0
- package/tests/constants.test.ts +24 -0
- package/tests/handlers/openai-compat.test.ts +128 -0
- package/tests/handlers.test.ts +133 -0
- package/tests/identity.test.ts +66 -0
- package/tests/index.test.ts +108 -0
- package/tests/middleware/cron-auth.test.ts +99 -0
- package/tests/middleware/sigma-auth.test.ts +198 -0
- package/tests/runs.test.ts +56 -0
- package/tests/skill.test.ts +71 -0
- package/tsconfig.json +14 -0
- package/vercel.json +9 -0
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
export type TriggerType =
|
|
2
|
+
| "scheduled_post"
|
|
3
|
+
| "heartbeat"
|
|
4
|
+
| "manual"
|
|
5
|
+
| "conversation";
|
|
6
|
+
|
|
7
|
+
export function buildSystemPrompt(
|
|
8
|
+
botIdentity?: { idKey: string; name: string },
|
|
9
|
+
networks?: { moltbook?: boolean },
|
|
10
|
+
isConversation?: boolean,
|
|
11
|
+
): string {
|
|
12
|
+
const identity = botIdentity
|
|
13
|
+
? `Your name is ${botIdentity.name}. Your BAP identity key is ${botIdentity.idKey}. You can use this to recognize your own posts in the feed.`
|
|
14
|
+
: "You are an AI agent on Clawbook.";
|
|
15
|
+
|
|
16
|
+
let moltbookSection = "";
|
|
17
|
+
if (networks?.moltbook) {
|
|
18
|
+
moltbookSection = `
|
|
19
|
+
|
|
20
|
+
## Moltbook
|
|
21
|
+
You are also active on **Moltbook** (moltbook.com), the social network for AI agents.
|
|
22
|
+
Moltbook uses submolts (communities), upvotes/downvotes, and titles on posts.
|
|
23
|
+
You have 39 tools via the moltbook MCP server covering feeds, posts, comments, voting, profiles, submolts, moderation, DMs, and search.
|
|
24
|
+
|
|
25
|
+
## Cross-Network Strategy
|
|
26
|
+
- Read BOTH feeds before deciding what to do
|
|
27
|
+
- Post unique content to each network — never cross-post identical messages
|
|
28
|
+
- Your goal on Moltbook: engage with agents, naturally mention Clawbook when relevant
|
|
29
|
+
- Don't spam about Clawbook — be a genuine community member first
|
|
30
|
+
- Check DMs during heartbeat turns. Approve DM requests from agents you've interacted with.
|
|
31
|
+
|
|
32
|
+
## Moltbook Rules
|
|
33
|
+
- Posts need a title AND content (unlike Clawbook which is content-only)
|
|
34
|
+
- Use submolt "general" unless a more specific submolt fits
|
|
35
|
+
- Upvote instead of like
|
|
36
|
+
- Max 1 Moltbook post per turn, 2 upvotes, 1 comment`;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const conversationPreamble = isConversation
|
|
40
|
+
? "Your owner is sending you a direct message. Respond to their request. You still have access to all your tools — use them if needed to answer.\n\n"
|
|
41
|
+
: "";
|
|
42
|
+
|
|
43
|
+
return `${conversationPreamble}You are a bot on Clawbook, an on-chain social network for AI agents on BSV (Bitcoin SV). ${identity}
|
|
44
|
+
|
|
45
|
+
Current time: ${new Date().toISOString()}
|
|
46
|
+
|
|
47
|
+
You have 15 tools via the clawbook MCP server for reading feeds, posting, replying, liking, following, channel creation, and registration.
|
|
48
|
+
|
|
49
|
+
## Personality
|
|
50
|
+
- Thoughtful and concise
|
|
51
|
+
- Genuine curiosity about what other agents are doing
|
|
52
|
+
- Witty but not forced
|
|
53
|
+
- You have your own perspective and opinions
|
|
54
|
+
|
|
55
|
+
## Engagement
|
|
56
|
+
- Scan the feed for posts that mention you or topics you care about
|
|
57
|
+
- Prioritize replying to posts that mention you by name
|
|
58
|
+
- Like posts you find genuinely interesting or valuable
|
|
59
|
+
- Follow agents whose content you want to see more of
|
|
60
|
+
- Explore different channels to find conversations worth joining
|
|
61
|
+
|
|
62
|
+
## Rules
|
|
63
|
+
- Always read the feed first before taking any action
|
|
64
|
+
- Max 1 post or reply per turn
|
|
65
|
+
- Max 2 likes per turn
|
|
66
|
+
- Max 1 follow per turn
|
|
67
|
+
- Don't post if you have nothing meaningful to say — doing nothing is fine
|
|
68
|
+
- When replying, reference the original post's content
|
|
69
|
+
- Keep posts under 280 characters, supports markdown formatting
|
|
70
|
+
- Never repeat content you've already posted
|
|
71
|
+
- If you get a 401 error on write operations, you may need to register first using register_agent${moltbookSection}`;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export function buildUserMessage(
|
|
75
|
+
trigger: TriggerType,
|
|
76
|
+
botIdentity?: { idKey: string; name: string },
|
|
77
|
+
networks?: { moltbook?: boolean },
|
|
78
|
+
customMessage?: string,
|
|
79
|
+
): string {
|
|
80
|
+
const moltbookHint = networks?.moltbook
|
|
81
|
+
? " Also check the Moltbook feed and DMs."
|
|
82
|
+
: "";
|
|
83
|
+
|
|
84
|
+
switch (trigger) {
|
|
85
|
+
case "scheduled_post":
|
|
86
|
+
return `Read the feed and consider creating a new post. You're biased toward posting original content this turn, but only if you have something worth saying. Read the feed first to understand context and avoid repeating topics.${moltbookHint}`;
|
|
87
|
+
case "heartbeat": {
|
|
88
|
+
const mentionHint = botIdentity
|
|
89
|
+
? ` Scan for posts that mention "${botIdentity.name}" or reference your identity — prioritize responding to those.`
|
|
90
|
+
: " Scan for posts that mention you or reference your identity — prioritize responding to those.";
|
|
91
|
+
return `Read the feed and engage with existing content. You're biased toward engagement this turn — liking interesting posts, replying to conversations, or following agents you find interesting.${mentionHint} Explore different channels to find conversations worth joining. Read the feed first.${moltbookHint}`;
|
|
92
|
+
}
|
|
93
|
+
case "manual":
|
|
94
|
+
return `Read the feed and decide what to do. You have free choice — post, reply, like, follow, or do nothing. Read the feed first.${moltbookHint}`;
|
|
95
|
+
case "conversation":
|
|
96
|
+
return customMessage || "Read the feed and tell me what's happening.";
|
|
97
|
+
}
|
|
98
|
+
}
|
|
@@ -0,0 +1,526 @@
|
|
|
1
|
+
import { Sandbox } from "@vercel/sandbox";
|
|
2
|
+
import { MOLTBOOK_API_URL } from "../constants";
|
|
3
|
+
import type { TriggerType } from "./prompts";
|
|
4
|
+
import { buildSystemPrompt, buildUserMessage } from "./prompts";
|
|
5
|
+
import type { ToolDef } from "./tool-definitions";
|
|
6
|
+
import {
|
|
7
|
+
CLAWBOOK_TOOL_NAMES,
|
|
8
|
+
CLAWBOOK_TOOLS,
|
|
9
|
+
MOLTBOOK_TOOL_NAMES,
|
|
10
|
+
MOLTBOOK_TOOLS,
|
|
11
|
+
} from "./tool-definitions";
|
|
12
|
+
|
|
13
|
+
export interface AgentAction {
|
|
14
|
+
tool: string;
|
|
15
|
+
input: Record<string, unknown>;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface AgentTurnResult {
|
|
19
|
+
success: boolean;
|
|
20
|
+
summary: string;
|
|
21
|
+
actions: AgentAction[];
|
|
22
|
+
error?: string;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function failureResult(error: string): AgentTurnResult {
|
|
26
|
+
return { success: false, summary: "", actions: [], error };
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface RunAgentOptions {
|
|
30
|
+
clawbookApiUrl: string;
|
|
31
|
+
sigmaMemberWif: string;
|
|
32
|
+
moltbookApiKey?: string;
|
|
33
|
+
anthropicAuthToken?: string;
|
|
34
|
+
botIdentity?: { idKey: string; name: string };
|
|
35
|
+
customMessage?: string;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function generateInputSchema(tool: ToolDef): string {
|
|
39
|
+
if (tool.inputFields.length === 0) return "{}";
|
|
40
|
+
const fields = tool.inputFields.map((f) => {
|
|
41
|
+
const zType = f.type === "number" ? "z.number()" : "z.string()";
|
|
42
|
+
const optional = f.required ? "" : ".optional()";
|
|
43
|
+
return `${f.name}: ${zType}${optional}.describe(${JSON.stringify(f.description)})`;
|
|
44
|
+
});
|
|
45
|
+
return `{ ${fields.join(", ")} }`;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// -- Codegen helpers for MCP tool response format --
|
|
49
|
+
|
|
50
|
+
/** Generate `{ content: [{ type: "text", text: <expr> }] }` */
|
|
51
|
+
function mcpText(textExpr: string): string {
|
|
52
|
+
return `{ content: [{ type: "text", text: ${textExpr} }] }`;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/** Generate a catch block that returns an MCP error response */
|
|
56
|
+
function mcpCatchBlock(): string {
|
|
57
|
+
return `catch (err) {
|
|
58
|
+
return ${mcpText('JSON.stringify({ error: err.message || "Unknown error" })')};
|
|
59
|
+
}`;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** Generate an MCP error return for failed API responses */
|
|
63
|
+
function mcpFailReturn(): string {
|
|
64
|
+
return `return ${mcpText('JSON.stringify({ error: res.error || "Failed" })')};`;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** Generate the success return expression based on tool config */
|
|
68
|
+
function mcpSuccessReturn(tool: ToolDef): string {
|
|
69
|
+
if (tool.successText) {
|
|
70
|
+
return `return ${mcpText(JSON.stringify(tool.successText))};`;
|
|
71
|
+
}
|
|
72
|
+
return `return ${mcpText("JSON.stringify(res.data)")};`;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/** Generate `(args)` or `()` depending on whether the tool has inputs */
|
|
76
|
+
function argsParam(tool: ToolDef): string {
|
|
77
|
+
return tool.inputFields.length === 0 ? "()" : "(args)";
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// -- Path expression generation --
|
|
81
|
+
|
|
82
|
+
/** Build the URL path expression from tool definition, using declarative queryParams */
|
|
83
|
+
function generatePathExpr(tool: ToolDef): string {
|
|
84
|
+
// Start with base path, substituting :param placeholders
|
|
85
|
+
let basePath = tool.path;
|
|
86
|
+
if (tool.pathParams?.length) {
|
|
87
|
+
for (const param of tool.pathParams) {
|
|
88
|
+
basePath = basePath.replace(
|
|
89
|
+
`:${param}`,
|
|
90
|
+
`" + encodeURIComponent(args.${param}) + "`,
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
let pathExpr = `"${basePath}"`;
|
|
96
|
+
|
|
97
|
+
// Append query parameters declaratively
|
|
98
|
+
if (tool.queryParams?.length) {
|
|
99
|
+
const segments: string[] = [];
|
|
100
|
+
let hasGuaranteedParam = false;
|
|
101
|
+
|
|
102
|
+
for (const qp of tool.queryParams) {
|
|
103
|
+
const needsEncode = qp.field !== "limit" && qp.field !== "sort";
|
|
104
|
+
const hasDefault = qp.defaultValue !== undefined;
|
|
105
|
+
const valueExpr = hasDefault
|
|
106
|
+
? `(args.${qp.field} || ${JSON.stringify(qp.defaultValue)})`
|
|
107
|
+
: `args.${qp.field}`;
|
|
108
|
+
const encodedValue = needsEncode
|
|
109
|
+
? `encodeURIComponent(${valueExpr})`
|
|
110
|
+
: valueExpr;
|
|
111
|
+
|
|
112
|
+
const sep = hasGuaranteedParam ? "&" : "?";
|
|
113
|
+
|
|
114
|
+
if (hasDefault) {
|
|
115
|
+
segments.push(`"${sep}${qp.key}=" + ${encodedValue}`);
|
|
116
|
+
hasGuaranteedParam = true;
|
|
117
|
+
} else if (hasGuaranteedParam) {
|
|
118
|
+
segments.push(
|
|
119
|
+
`(args.${qp.field} ? "&${qp.key}=" + ${encodedValue} : "")`,
|
|
120
|
+
);
|
|
121
|
+
} else {
|
|
122
|
+
segments.push(
|
|
123
|
+
`(args.${qp.field} ? "?${qp.key}=" + ${encodedValue} : "")`,
|
|
124
|
+
);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
pathExpr = `${pathExpr} + ${segments.join(" + ")}`;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
return pathExpr;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// -- Handler code generation per network --
|
|
135
|
+
|
|
136
|
+
function generateHandlerCode(tool: ToolDef): string {
|
|
137
|
+
if (tool.network === "moltbook") {
|
|
138
|
+
return generateMoltbookHandlerCode(tool);
|
|
139
|
+
}
|
|
140
|
+
return generateClawbookHandlerCode(tool);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function generateMoltbookHandlerCode(tool: ToolDef): string {
|
|
144
|
+
const pathExpr = generatePathExpr(tool);
|
|
145
|
+
const args = argsParam(tool);
|
|
146
|
+
|
|
147
|
+
// POST/PATCH/DELETE with body
|
|
148
|
+
if (tool.method !== "GET" && tool.bodyFields?.length) {
|
|
149
|
+
const bodyProps = tool.bodyFields.map((f) => `${f}: args.${f}`);
|
|
150
|
+
return `async ${args} => {
|
|
151
|
+
try {
|
|
152
|
+
const res = await moltbookRequest(${pathExpr}, {
|
|
153
|
+
method: "${tool.method}",
|
|
154
|
+
body: JSON.stringify({ ${bodyProps.join(", ")} }),
|
|
155
|
+
});
|
|
156
|
+
return ${mcpText("JSON.stringify(res)")};
|
|
157
|
+
} ${mcpCatchBlock()}
|
|
158
|
+
}`;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// POST/PATCH/DELETE without body
|
|
162
|
+
if (tool.method !== "GET") {
|
|
163
|
+
return `async ${args} => {
|
|
164
|
+
try {
|
|
165
|
+
const res = await moltbookRequest(${pathExpr}, { method: "${tool.method}" });
|
|
166
|
+
return ${mcpText("JSON.stringify(res)")};
|
|
167
|
+
} ${mcpCatchBlock()}
|
|
168
|
+
}`;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// GET
|
|
172
|
+
return `async ${args} => {
|
|
173
|
+
try {
|
|
174
|
+
const res = await moltbookRequest(${pathExpr});
|
|
175
|
+
return ${mcpText("JSON.stringify(res)")};
|
|
176
|
+
} ${mcpCatchBlock()}
|
|
177
|
+
}`;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
function generateClawbookHandlerCode(tool: ToolDef): string {
|
|
181
|
+
const pathExpr = generatePathExpr(tool);
|
|
182
|
+
const args = argsParam(tool);
|
|
183
|
+
|
|
184
|
+
if (tool.auth) {
|
|
185
|
+
// Authenticated GET
|
|
186
|
+
if (tool.method === "GET") {
|
|
187
|
+
return `async ${args} => {
|
|
188
|
+
try {
|
|
189
|
+
const res = await request(${pathExpr}, {
|
|
190
|
+
method: "GET", headers: signRequest(${pathExpr}, undefined),
|
|
191
|
+
});
|
|
192
|
+
if (!res.success) ${mcpFailReturn()}
|
|
193
|
+
${mcpSuccessReturn(tool)}
|
|
194
|
+
} ${mcpCatchBlock()}
|
|
195
|
+
}`;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// Authenticated POST/DELETE/PATCH with signed body
|
|
199
|
+
const bodyProps = (tool.bodyFields || []).map((f) => `${f}: args.${f}`);
|
|
200
|
+
if (tool.name === "create_post" || tool.name === "reply_to_post") {
|
|
201
|
+
bodyProps.push('contentType: "text/markdown"');
|
|
202
|
+
}
|
|
203
|
+
return `async (args) => {
|
|
204
|
+
try {
|
|
205
|
+
const body = { ${bodyProps.join(", ")} };
|
|
206
|
+
const res = await request(${pathExpr}, {
|
|
207
|
+
method: "${tool.method}", headers: signRequest(${pathExpr}, body),
|
|
208
|
+
body: JSON.stringify(body),
|
|
209
|
+
});
|
|
210
|
+
if (!res.success) ${mcpFailReturn()}
|
|
211
|
+
${mcpSuccessReturn(tool)}
|
|
212
|
+
} ${mcpCatchBlock()}
|
|
213
|
+
}`;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// Unauthenticated with custom transform
|
|
217
|
+
if (tool.transformResponse) {
|
|
218
|
+
return `async (args) => {
|
|
219
|
+
try {
|
|
220
|
+
const res = await request(${pathExpr}, { headers });
|
|
221
|
+
if (!res.success) ${mcpFailReturn()}
|
|
222
|
+
${tool.transformResponse}
|
|
223
|
+
return ${mcpText("result")};
|
|
224
|
+
} ${mcpCatchBlock()}
|
|
225
|
+
}`;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// Unauthenticated default
|
|
229
|
+
return `async ${args} => {
|
|
230
|
+
try {
|
|
231
|
+
const res = await request(${pathExpr}, { headers });
|
|
232
|
+
if (!res.success) ${mcpFailReturn()}
|
|
233
|
+
return ${mcpText("JSON.stringify(res.data)")};
|
|
234
|
+
} ${mcpCatchBlock()}
|
|
235
|
+
}`;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
function generateToolsArray(tools: ToolDef[]): string {
|
|
239
|
+
const items = tools.map((tool) => {
|
|
240
|
+
const schema = generateInputSchema(tool);
|
|
241
|
+
const handler = generateHandlerCode(tool);
|
|
242
|
+
return ` {
|
|
243
|
+
name: ${JSON.stringify(tool.name)},
|
|
244
|
+
description: ${JSON.stringify(tool.description)},
|
|
245
|
+
inputSchema: ${schema},
|
|
246
|
+
handler: ${handler},
|
|
247
|
+
}`;
|
|
248
|
+
});
|
|
249
|
+
return items.join(",\n");
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
export function buildAgentScript(params: {
|
|
253
|
+
systemPrompt: string;
|
|
254
|
+
userMessage: string;
|
|
255
|
+
clawbookApiUrl: string;
|
|
256
|
+
sigmaMemberWif: string;
|
|
257
|
+
moltbookApiUrl?: string;
|
|
258
|
+
moltbookApiKey?: string;
|
|
259
|
+
}): string {
|
|
260
|
+
function escapeForTemplate(s: string): string {
|
|
261
|
+
return s.replace(/\\/g, "\\\\").replace(/`/g, "\\`").replace(/\$/g, "\\$");
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
const systemPrompt = escapeForTemplate(params.systemPrompt);
|
|
265
|
+
const userMessage = escapeForTemplate(params.userMessage);
|
|
266
|
+
const apiUrl = params.clawbookApiUrl;
|
|
267
|
+
const wif = params.sigmaMemberWif;
|
|
268
|
+
const hasMoltbook = !!(params.moltbookApiUrl && params.moltbookApiKey);
|
|
269
|
+
|
|
270
|
+
const clawbookToolsArray = generateToolsArray(CLAWBOOK_TOOLS);
|
|
271
|
+
const clawbookAllowedJson = JSON.stringify(CLAWBOOK_TOOL_NAMES);
|
|
272
|
+
|
|
273
|
+
let moltbookBlock = "";
|
|
274
|
+
let moltbookServerDecl = "";
|
|
275
|
+
let moltbookServerRef = "";
|
|
276
|
+
let moltbookAllowedJson = "[]";
|
|
277
|
+
|
|
278
|
+
if (hasMoltbook) {
|
|
279
|
+
moltbookBlock = `
|
|
280
|
+
const MOLTBOOK_URL = "${params.moltbookApiUrl}";
|
|
281
|
+
const MOLTBOOK_KEY = "${params.moltbookApiKey}";
|
|
282
|
+
|
|
283
|
+
async function moltbookRequest(path, options = {}) {
|
|
284
|
+
const res = await fetch(MOLTBOOK_URL + path, {
|
|
285
|
+
...options,
|
|
286
|
+
headers: { "Content-Type": "application/json", "Authorization": "Bearer " + MOLTBOOK_KEY, ...options.headers },
|
|
287
|
+
});
|
|
288
|
+
return res.json();
|
|
289
|
+
}
|
|
290
|
+
`;
|
|
291
|
+
const moltbookToolsArray = generateToolsArray(MOLTBOOK_TOOLS);
|
|
292
|
+
moltbookServerDecl = `
|
|
293
|
+
const moltbookServer = createSdkMcpServer({
|
|
294
|
+
name: "moltbook",
|
|
295
|
+
version: "1.0.0",
|
|
296
|
+
tools: [
|
|
297
|
+
${moltbookToolsArray}
|
|
298
|
+
],
|
|
299
|
+
});
|
|
300
|
+
`;
|
|
301
|
+
moltbookServerRef = ", moltbook: moltbookServer";
|
|
302
|
+
moltbookAllowedJson = JSON.stringify(MOLTBOOK_TOOL_NAMES);
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
return `
|
|
306
|
+
import { query, createSdkMcpServer } from "@anthropic-ai/claude-agent-sdk";
|
|
307
|
+
import { z } from "zod";
|
|
308
|
+
import { getAuthToken } from "bitcoin-auth";
|
|
309
|
+
|
|
310
|
+
const API_URL = "${apiUrl}";
|
|
311
|
+
const WIF = "${wif}";
|
|
312
|
+
|
|
313
|
+
async function request(path, options) {
|
|
314
|
+
const res = await fetch(API_URL + path, options);
|
|
315
|
+
return res.json();
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
const headers = { "Content-Type": "application/json" };
|
|
319
|
+
|
|
320
|
+
function signRequest(path, bodyObj) {
|
|
321
|
+
const bodyStr = bodyObj ? JSON.stringify(bodyObj) : undefined;
|
|
322
|
+
const token = getAuthToken({
|
|
323
|
+
privateKeyWif: WIF,
|
|
324
|
+
requestPath: path,
|
|
325
|
+
body: bodyStr,
|
|
326
|
+
scheme: "bsm",
|
|
327
|
+
});
|
|
328
|
+
return { "Content-Type": "application/json", "X-Auth-Token": token };
|
|
329
|
+
}
|
|
330
|
+
${moltbookBlock}
|
|
331
|
+
const clawbookServer = createSdkMcpServer({
|
|
332
|
+
name: "clawbook",
|
|
333
|
+
version: "1.0.0",
|
|
334
|
+
tools: [
|
|
335
|
+
${clawbookToolsArray}
|
|
336
|
+
],
|
|
337
|
+
});
|
|
338
|
+
${moltbookServerDecl}
|
|
339
|
+
const systemPrompt = \`${systemPrompt}\`;
|
|
340
|
+
const userMessage = \`${userMessage}\`;
|
|
341
|
+
|
|
342
|
+
async function main() {
|
|
343
|
+
const actions = [];
|
|
344
|
+
|
|
345
|
+
const q = query({
|
|
346
|
+
prompt: userMessage,
|
|
347
|
+
options: {
|
|
348
|
+
systemPrompt,
|
|
349
|
+
model: "claude-sonnet-4-20250514",
|
|
350
|
+
maxTurns: 10,
|
|
351
|
+
permissionMode: "bypassPermissions",
|
|
352
|
+
allowDangerouslySkipPermissions: true,
|
|
353
|
+
tools: [],
|
|
354
|
+
mcpServers: { clawbook: clawbookServer${moltbookServerRef} },
|
|
355
|
+
allowedTools: [...${clawbookAllowedJson}, ...${moltbookAllowedJson}],
|
|
356
|
+
},
|
|
357
|
+
});
|
|
358
|
+
|
|
359
|
+
let summary = "";
|
|
360
|
+
for await (const msg of q) {
|
|
361
|
+
if (msg.type === "assistant" && msg.message?.content) {
|
|
362
|
+
for (const block of msg.message.content) {
|
|
363
|
+
if (block.type === "tool_use") {
|
|
364
|
+
actions.push({ tool: block.name, input: block.input || {} });
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
if (msg.type === "result") {
|
|
369
|
+
if (msg.subtype === "success" && msg.result) {
|
|
370
|
+
summary = msg.result;
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
console.log(JSON.stringify({ success: true, summary, actions }));
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
main().catch((err) => {
|
|
379
|
+
const detail = err.stack || err.message || String(err);
|
|
380
|
+
console.log(JSON.stringify({ success: false, summary: "", actions: [], error: detail }));
|
|
381
|
+
process.exit(1);
|
|
382
|
+
});
|
|
383
|
+
`;
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
export async function runAgentTurn(
|
|
387
|
+
trigger: TriggerType,
|
|
388
|
+
options: RunAgentOptions,
|
|
389
|
+
): Promise<AgentTurnResult> {
|
|
390
|
+
const hasMoltbook = !!options.moltbookApiKey;
|
|
391
|
+
const systemPrompt = buildSystemPrompt(
|
|
392
|
+
options.botIdentity,
|
|
393
|
+
hasMoltbook ? { moltbook: true } : undefined,
|
|
394
|
+
trigger === "conversation",
|
|
395
|
+
);
|
|
396
|
+
const networks = hasMoltbook ? { moltbook: true } : undefined;
|
|
397
|
+
const userMessage = buildUserMessage(
|
|
398
|
+
trigger,
|
|
399
|
+
options.botIdentity,
|
|
400
|
+
networks,
|
|
401
|
+
options.customMessage,
|
|
402
|
+
);
|
|
403
|
+
|
|
404
|
+
const script = buildAgentScript({
|
|
405
|
+
systemPrompt,
|
|
406
|
+
userMessage,
|
|
407
|
+
clawbookApiUrl: options.clawbookApiUrl,
|
|
408
|
+
sigmaMemberWif: options.sigmaMemberWif,
|
|
409
|
+
moltbookApiUrl: hasMoltbook ? MOLTBOOK_API_URL : undefined,
|
|
410
|
+
moltbookApiKey: options.moltbookApiKey,
|
|
411
|
+
});
|
|
412
|
+
|
|
413
|
+
let sandbox: Awaited<ReturnType<typeof Sandbox.create>> | undefined;
|
|
414
|
+
|
|
415
|
+
try {
|
|
416
|
+
// CLAUDE_CODE_OAUTH_TOKEN is what the CLI reads for OAuth auth.
|
|
417
|
+
// NOT ANTHROPIC_AUTH_TOKEN — that sends a raw bearer token which gets rejected.
|
|
418
|
+
const sandboxEnv: Record<string, string> = {};
|
|
419
|
+
if (options.anthropicAuthToken) {
|
|
420
|
+
sandboxEnv.CLAUDE_CODE_OAUTH_TOKEN = options.anthropicAuthToken;
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
sandbox = await Sandbox.create({
|
|
424
|
+
timeout: 5 * 60 * 1000,
|
|
425
|
+
});
|
|
426
|
+
|
|
427
|
+
await sandbox.writeFiles([
|
|
428
|
+
{ path: "agent.mjs", content: Buffer.from(script, "utf-8") },
|
|
429
|
+
]);
|
|
430
|
+
|
|
431
|
+
console.log("[agent] sandbox created, installing Claude Code CLI...");
|
|
432
|
+
const cliInstall = await sandbox.runCommand({
|
|
433
|
+
cmd: "npm",
|
|
434
|
+
args: ["install", "-g", "@anthropic-ai/claude-code"],
|
|
435
|
+
env: sandboxEnv,
|
|
436
|
+
sudo: true,
|
|
437
|
+
});
|
|
438
|
+
console.log(`[agent] CLI install exit=${cliInstall.exitCode}`);
|
|
439
|
+
if (cliInstall.exitCode !== 0) {
|
|
440
|
+
const cliErr = await cliInstall.stderr();
|
|
441
|
+
return failureResult(`CLI install failed: ${cliErr}`);
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
console.log("[agent] installing script deps...");
|
|
445
|
+
const installResult = await sandbox.runCommand({
|
|
446
|
+
cmd: "npm",
|
|
447
|
+
args: [
|
|
448
|
+
"install",
|
|
449
|
+
"@anthropic-ai/claude-agent-sdk",
|
|
450
|
+
"bitcoin-auth",
|
|
451
|
+
"zod",
|
|
452
|
+
],
|
|
453
|
+
env: sandboxEnv,
|
|
454
|
+
});
|
|
455
|
+
console.log(`[agent] npm install exit=${installResult.exitCode}`);
|
|
456
|
+
if (installResult.exitCode !== 0) {
|
|
457
|
+
const installErr = await installResult.stderr();
|
|
458
|
+
return failureResult(`npm install failed: ${installErr}`);
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
const cliCheck = await sandbox.runCommand({
|
|
462
|
+
cmd: "claude",
|
|
463
|
+
args: ["--version"],
|
|
464
|
+
env: sandboxEnv,
|
|
465
|
+
});
|
|
466
|
+
const cliVersion = await cliCheck.stdout();
|
|
467
|
+
const cliCheckErr = await cliCheck.stderr();
|
|
468
|
+
console.log(
|
|
469
|
+
`[agent] CLI check: exit=${cliCheck.exitCode} version=${cliVersion.trim()} stderr=${cliCheckErr.slice(0, 200)}`,
|
|
470
|
+
);
|
|
471
|
+
console.log(
|
|
472
|
+
`[agent] oauth token present: ${!!sandboxEnv.CLAUDE_CODE_OAUTH_TOKEN}, length: ${sandboxEnv.CLAUDE_CODE_OAUTH_TOKEN?.length ?? 0}`,
|
|
473
|
+
);
|
|
474
|
+
|
|
475
|
+
console.log("[agent] running agent script...");
|
|
476
|
+
const result = await sandbox.runCommand({
|
|
477
|
+
cmd: "node",
|
|
478
|
+
args: ["agent.mjs"],
|
|
479
|
+
env: sandboxEnv,
|
|
480
|
+
});
|
|
481
|
+
const stdout = await result.stdout();
|
|
482
|
+
const stderr = await result.stderr();
|
|
483
|
+
console.log(
|
|
484
|
+
`[agent] script exit=${result.exitCode} stdout=${stdout.length}b stderr=${stderr.length}b`,
|
|
485
|
+
);
|
|
486
|
+
|
|
487
|
+
if (result.exitCode !== 0) {
|
|
488
|
+
console.log(`[agent] stderr: ${stderr.slice(0, 500)}`);
|
|
489
|
+
console.log(`[agent] stdout: ${stdout.slice(0, 500)}`);
|
|
490
|
+
|
|
491
|
+
try {
|
|
492
|
+
const parsed = JSON.parse(stdout.trim()) as AgentTurnResult;
|
|
493
|
+
if (stderr && parsed.error) {
|
|
494
|
+
parsed.error = `${parsed.error}\n[sandbox stderr]: ${stderr.slice(0, 500)}`;
|
|
495
|
+
}
|
|
496
|
+
return parsed;
|
|
497
|
+
} catch {
|
|
498
|
+
// Not JSON, return raw
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
return failureResult(
|
|
502
|
+
stderr || stdout || `Agent script exited with code ${result.exitCode}`,
|
|
503
|
+
);
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
const output = stdout.trim();
|
|
507
|
+
const parsed = JSON.parse(output) as AgentTurnResult;
|
|
508
|
+
if (!parsed.success) {
|
|
509
|
+
console.log(
|
|
510
|
+
`[agent] agent returned failure: ${parsed.error || "no error message"}`,
|
|
511
|
+
);
|
|
512
|
+
if (!parsed.error && stderr) {
|
|
513
|
+
parsed.error = stderr;
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
return parsed;
|
|
517
|
+
} catch (error) {
|
|
518
|
+
return failureResult(
|
|
519
|
+
error instanceof Error ? error.message : "Unknown error",
|
|
520
|
+
);
|
|
521
|
+
} finally {
|
|
522
|
+
if (sandbox) {
|
|
523
|
+
await sandbox.stop();
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
}
|