@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.
Files changed (67) hide show
  1. package/.agents/skills/claude-agent-sdk/.claude-plugin/plugin.json +13 -0
  2. package/.agents/skills/claude-agent-sdk/SKILL.md +954 -0
  3. package/.agents/skills/claude-agent-sdk/references/mcp-servers-guide.md +387 -0
  4. package/.agents/skills/claude-agent-sdk/references/permissions-guide.md +429 -0
  5. package/.agents/skills/claude-agent-sdk/references/query-api-reference.md +437 -0
  6. package/.agents/skills/claude-agent-sdk/references/session-management.md +419 -0
  7. package/.agents/skills/claude-agent-sdk/references/subagents-patterns.md +464 -0
  8. package/.agents/skills/claude-agent-sdk/references/top-errors.md +503 -0
  9. package/.agents/skills/claude-agent-sdk/rules/claude-agent-sdk.md +96 -0
  10. package/.agents/skills/claude-agent-sdk/scripts/check-versions.sh +55 -0
  11. package/.agents/skills/claude-agent-sdk/templates/basic-query.ts +55 -0
  12. package/.agents/skills/claude-agent-sdk/templates/custom-mcp-server.ts +161 -0
  13. package/.agents/skills/claude-agent-sdk/templates/error-handling.ts +283 -0
  14. package/.agents/skills/claude-agent-sdk/templates/filesystem-settings.ts +211 -0
  15. package/.agents/skills/claude-agent-sdk/templates/multi-agent-workflow.ts +318 -0
  16. package/.agents/skills/claude-agent-sdk/templates/package.json +30 -0
  17. package/.agents/skills/claude-agent-sdk/templates/permission-control.ts +211 -0
  18. package/.agents/skills/claude-agent-sdk/templates/query-with-tools.ts +54 -0
  19. package/.agents/skills/claude-agent-sdk/templates/session-management.ts +151 -0
  20. package/.agents/skills/claude-agent-sdk/templates/subagents-orchestration.ts +166 -0
  21. package/.agents/skills/claude-agent-sdk/templates/tsconfig.json +22 -0
  22. package/.claude/settings.local.json +70 -0
  23. package/.claude/skills/moltbook-example/SKILL.md +79 -0
  24. package/.claude/skills/post/SKILL.md +130 -0
  25. package/.env.example +4 -0
  26. package/.vercel/README.txt +11 -0
  27. package/.vercel/project.json +1 -0
  28. package/AGENTS.md +114 -0
  29. package/CLAUDE.md +532 -0
  30. package/README.md +44 -0
  31. package/api/index.ts +3 -0
  32. package/biome.json +14 -0
  33. package/clark_avatar.jpeg +0 -0
  34. package/package.json +21 -0
  35. package/scripts/wake.ts +38 -0
  36. package/skills/clawbook/HEARTBEAT.md +142 -0
  37. package/skills/clawbook/SKILL.md +219 -0
  38. package/skills/moltbook-example/SKILL.md +79 -0
  39. package/skills/moltbook-example/bot/index.ts +61 -0
  40. package/src/agent/prompts.ts +98 -0
  41. package/src/agent/runner.ts +526 -0
  42. package/src/agent/tool-definitions.ts +1151 -0
  43. package/src/agent-options.ts +14 -0
  44. package/src/bot-identity.ts +41 -0
  45. package/src/constants.ts +15 -0
  46. package/src/handlers/heartbeat.ts +21 -0
  47. package/src/handlers/openai-compat.ts +95 -0
  48. package/src/handlers/post.ts +21 -0
  49. package/src/identity.ts +83 -0
  50. package/src/index.ts +30 -0
  51. package/src/middleware/cron-auth.ts +53 -0
  52. package/src/middleware/sigma-auth.ts +147 -0
  53. package/src/runs.ts +49 -0
  54. package/tests/agent/prompts.test.ts +172 -0
  55. package/tests/agent/runner.test.ts +353 -0
  56. package/tests/agent/tool-definitions.test.ts +171 -0
  57. package/tests/constants.test.ts +24 -0
  58. package/tests/handlers/openai-compat.test.ts +128 -0
  59. package/tests/handlers.test.ts +133 -0
  60. package/tests/identity.test.ts +66 -0
  61. package/tests/index.test.ts +108 -0
  62. package/tests/middleware/cron-auth.test.ts +99 -0
  63. package/tests/middleware/sigma-auth.test.ts +198 -0
  64. package/tests/runs.test.ts +56 -0
  65. package/tests/skill.test.ts +71 -0
  66. package/tsconfig.json +14 -0
  67. 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
+ }