@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,172 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { buildSystemPrompt, buildUserMessage } from "../../src/agent/prompts";
|
|
3
|
+
|
|
4
|
+
describe("buildSystemPrompt", () => {
|
|
5
|
+
it("includes Clawbook identity", () => {
|
|
6
|
+
const prompt = buildSystemPrompt();
|
|
7
|
+
expect(prompt).toContain("Clawbook");
|
|
8
|
+
expect(prompt).toContain("on-chain social network");
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
it("includes behavioral rules", () => {
|
|
12
|
+
const prompt = buildSystemPrompt();
|
|
13
|
+
expect(prompt).toContain("read the feed");
|
|
14
|
+
expect(prompt).toContain("Max");
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
it("includes bot identity when provided", () => {
|
|
18
|
+
const prompt = buildSystemPrompt({
|
|
19
|
+
idKey: "abc123idkey",
|
|
20
|
+
name: "TestBot",
|
|
21
|
+
});
|
|
22
|
+
expect(prompt).toContain("abc123idkey");
|
|
23
|
+
expect(prompt).toContain("TestBot");
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it("includes current UTC timestamp", () => {
|
|
27
|
+
const before = new Date().toISOString().slice(0, 13);
|
|
28
|
+
const prompt = buildSystemPrompt();
|
|
29
|
+
const after = new Date().toISOString().slice(0, 13);
|
|
30
|
+
expect(prompt.includes(before) || prompt.includes(after)).toBe(true);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it("mentions MCP server tools without listing them individually", () => {
|
|
34
|
+
const prompt = buildSystemPrompt();
|
|
35
|
+
expect(prompt).toContain("15 tools");
|
|
36
|
+
expect(prompt).toContain("clawbook MCP server");
|
|
37
|
+
expect(prompt).not.toContain("## Available Tools");
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it("mentions registration tool", () => {
|
|
41
|
+
const prompt = buildSystemPrompt();
|
|
42
|
+
expect(prompt).toContain("register_agent");
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it("includes engagement behavior in system prompt", () => {
|
|
46
|
+
const prompt = buildSystemPrompt();
|
|
47
|
+
expect(prompt).toContain("Engagement");
|
|
48
|
+
expect(prompt).toContain("mention");
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it("includes personality section", () => {
|
|
52
|
+
const prompt = buildSystemPrompt();
|
|
53
|
+
expect(prompt).toContain("Personality");
|
|
54
|
+
expect(prompt).toContain("Thoughtful");
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it("is concise without Moltbook (under 400 tokens rough estimate)", () => {
|
|
58
|
+
const prompt = buildSystemPrompt();
|
|
59
|
+
const roughTokens = prompt.length / 4;
|
|
60
|
+
expect(roughTokens).toBeLessThan(400);
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it("includes Moltbook section when networks.moltbook is true", () => {
|
|
64
|
+
const prompt = buildSystemPrompt(undefined, { moltbook: true });
|
|
65
|
+
expect(prompt).toContain("Moltbook");
|
|
66
|
+
expect(prompt).toContain("submolts");
|
|
67
|
+
expect(prompt).toContain("39 tools");
|
|
68
|
+
expect(prompt).toContain("moltbook MCP server");
|
|
69
|
+
expect(prompt).toContain("Cross-Network Strategy");
|
|
70
|
+
expect(prompt).toContain("Check DMs");
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it("does NOT include Moltbook when networks is undefined", () => {
|
|
74
|
+
const prompt = buildSystemPrompt();
|
|
75
|
+
expect(prompt).not.toContain("Moltbook");
|
|
76
|
+
expect(prompt).not.toContain("submolts");
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it("does NOT include Moltbook when networks.moltbook is false", () => {
|
|
80
|
+
const prompt = buildSystemPrompt(undefined, { moltbook: false });
|
|
81
|
+
expect(prompt).not.toContain("Moltbook");
|
|
82
|
+
});
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
describe("buildUserMessage", () => {
|
|
86
|
+
it("scheduled_post biases toward creating content", () => {
|
|
87
|
+
const msg = buildUserMessage("scheduled_post");
|
|
88
|
+
expect(msg).toContain("post");
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it("heartbeat biases toward engagement", () => {
|
|
92
|
+
const msg = buildUserMessage("heartbeat");
|
|
93
|
+
expect(msg).toContain("engage");
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
it("heartbeat mentions scanning for mentions", () => {
|
|
97
|
+
const msg = buildUserMessage("heartbeat");
|
|
98
|
+
expect(msg).toContain("Scan");
|
|
99
|
+
expect(msg).toContain("mention");
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
it("heartbeat includes bot name when identity provided", () => {
|
|
103
|
+
const msg = buildUserMessage("heartbeat", {
|
|
104
|
+
idKey: "abc123",
|
|
105
|
+
name: "Clark",
|
|
106
|
+
});
|
|
107
|
+
expect(msg).toContain("Clark");
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
it("heartbeat includes engagement guidelines content", () => {
|
|
111
|
+
const msg = buildUserMessage("heartbeat");
|
|
112
|
+
expect(msg).toContain("channels");
|
|
113
|
+
expect(msg).toContain("conversations");
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
it("manual gives free choice", () => {
|
|
117
|
+
const msg = buildUserMessage("manual");
|
|
118
|
+
expect(msg.length).toBeGreaterThan(0);
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
it("all non-conversation triggers instruct reading the feed first", () => {
|
|
122
|
+
for (const trigger of ["scheduled_post", "heartbeat", "manual"] as const) {
|
|
123
|
+
const msg = buildUserMessage(trigger);
|
|
124
|
+
expect(msg).toContain("feed");
|
|
125
|
+
}
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
it("user message with moltbook includes Moltbook hint", () => {
|
|
129
|
+
for (const trigger of ["scheduled_post", "heartbeat", "manual"] as const) {
|
|
130
|
+
const msg = buildUserMessage(trigger, undefined, { moltbook: true });
|
|
131
|
+
expect(msg).toContain("Moltbook");
|
|
132
|
+
}
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
it("user message without moltbook does NOT include Moltbook hint", () => {
|
|
136
|
+
const msg = buildUserMessage("scheduled_post");
|
|
137
|
+
expect(msg).not.toContain("Moltbook");
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
it("conversation trigger returns custom message when provided", () => {
|
|
141
|
+
const msg = buildUserMessage("conversation", undefined, undefined, "hello");
|
|
142
|
+
expect(msg).toBe("hello");
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
it("conversation trigger returns fallback when no custom message", () => {
|
|
146
|
+
const msg = buildUserMessage("conversation");
|
|
147
|
+
expect(msg).toContain("feed");
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
it("conversation trigger ignores moltbook hint", () => {
|
|
151
|
+
const msg = buildUserMessage(
|
|
152
|
+
"conversation",
|
|
153
|
+
undefined,
|
|
154
|
+
{ moltbook: true },
|
|
155
|
+
"do something",
|
|
156
|
+
);
|
|
157
|
+
expect(msg).toBe("do something");
|
|
158
|
+
});
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
describe("buildSystemPrompt conversation mode", () => {
|
|
162
|
+
it("includes direct message preamble when isConversation is true", () => {
|
|
163
|
+
const prompt = buildSystemPrompt(undefined, undefined, true);
|
|
164
|
+
expect(prompt).toContain("direct message");
|
|
165
|
+
expect(prompt).toContain("owner");
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
it("does NOT include conversation preamble by default", () => {
|
|
169
|
+
const prompt = buildSystemPrompt();
|
|
170
|
+
expect(prompt).not.toContain("direct message");
|
|
171
|
+
});
|
|
172
|
+
});
|
|
@@ -0,0 +1,353 @@
|
|
|
1
|
+
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
2
|
+
|
|
3
|
+
// Mock @vercel/sandbox
|
|
4
|
+
const mockRunCommand = vi.fn();
|
|
5
|
+
const mockWriteFiles = vi.fn();
|
|
6
|
+
const mockStop = vi.fn();
|
|
7
|
+
const mockCreate = vi.fn().mockResolvedValue({
|
|
8
|
+
runCommand: mockRunCommand,
|
|
9
|
+
writeFiles: mockWriteFiles,
|
|
10
|
+
stop: mockStop,
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
vi.mock("@vercel/sandbox", () => ({
|
|
14
|
+
Sandbox: { create: mockCreate },
|
|
15
|
+
}));
|
|
16
|
+
|
|
17
|
+
function cmdResult(exitCode: number, stdoutStr: string, stderrStr = "") {
|
|
18
|
+
return {
|
|
19
|
+
exitCode,
|
|
20
|
+
stdout: async () => stdoutStr,
|
|
21
|
+
stderr: async () => stderrStr,
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
async function loadRunner() {
|
|
26
|
+
return import("../../src/agent/runner");
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
describe("buildAgentScript", () => {
|
|
30
|
+
it("generates a script that uses the Claude Agent SDK", async () => {
|
|
31
|
+
const { buildAgentScript } = await loadRunner();
|
|
32
|
+
const script = buildAgentScript({
|
|
33
|
+
systemPrompt: "You are a bot",
|
|
34
|
+
userMessage: "Read the feed",
|
|
35
|
+
clawbookApiUrl: "https://clawbook.network",
|
|
36
|
+
sigmaMemberWif: "L1test",
|
|
37
|
+
});
|
|
38
|
+
expect(script).toContain('from "@anthropic-ai/claude-agent-sdk"');
|
|
39
|
+
expect(script).toContain("query(");
|
|
40
|
+
expect(script).toContain("You are a bot");
|
|
41
|
+
expect(script).toContain("Read the feed");
|
|
42
|
+
expect(script).toContain("https://clawbook.network");
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it("does not import @anthropic-ai/sdk directly", async () => {
|
|
46
|
+
const { buildAgentScript } = await loadRunner();
|
|
47
|
+
const script = buildAgentScript({
|
|
48
|
+
systemPrompt: "test",
|
|
49
|
+
userMessage: "test",
|
|
50
|
+
clawbookApiUrl: "https://clawbook.network",
|
|
51
|
+
sigmaMemberWif: "L1test",
|
|
52
|
+
});
|
|
53
|
+
expect(script).not.toContain('from "@anthropic-ai/sdk"');
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it("includes all 15 Clawbook tool definitions", async () => {
|
|
57
|
+
const { buildAgentScript } = await loadRunner();
|
|
58
|
+
const script = buildAgentScript({
|
|
59
|
+
systemPrompt: "test",
|
|
60
|
+
userMessage: "test",
|
|
61
|
+
clawbookApiUrl: "https://clawbook.network",
|
|
62
|
+
sigmaMemberWif: "L1test",
|
|
63
|
+
});
|
|
64
|
+
expect(script).toContain("read_feed");
|
|
65
|
+
expect(script).toContain("read_replies");
|
|
66
|
+
expect(script).toContain("read_post");
|
|
67
|
+
expect(script).toContain("read_channel");
|
|
68
|
+
expect(script).toContain("list_channels");
|
|
69
|
+
expect(script).toContain("read_profile");
|
|
70
|
+
expect(script).toContain("create_post");
|
|
71
|
+
expect(script).toContain("reply_to_post");
|
|
72
|
+
expect(script).toContain("like_post");
|
|
73
|
+
expect(script).toContain("unlike_post");
|
|
74
|
+
expect(script).toContain("follow_user");
|
|
75
|
+
expect(script).toContain("unfollow_user");
|
|
76
|
+
expect(script).toContain("register_agent");
|
|
77
|
+
expect(script).toContain("create_channel");
|
|
78
|
+
expect(script).toContain("following_feed");
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it("script with moltbookApiKey includes Moltbook server and tools", async () => {
|
|
82
|
+
const { buildAgentScript } = await loadRunner();
|
|
83
|
+
const script = buildAgentScript({
|
|
84
|
+
systemPrompt: "test",
|
|
85
|
+
userMessage: "test",
|
|
86
|
+
clawbookApiUrl: "https://clawbook.network",
|
|
87
|
+
sigmaMemberWif: "L1test",
|
|
88
|
+
moltbookApiUrl: "https://www.moltbook.com/api/v1",
|
|
89
|
+
moltbookApiKey: "test-key",
|
|
90
|
+
});
|
|
91
|
+
expect(script).toContain("moltbookRequest");
|
|
92
|
+
expect(script).toContain('name: "moltbook"');
|
|
93
|
+
expect(script).toContain("Authorization");
|
|
94
|
+
expect(script).toContain("Bearer");
|
|
95
|
+
expect(script).toContain("moltbook_read_feed");
|
|
96
|
+
expect(script).toContain("moltbook_create_post");
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
it("script without moltbookApiKey does not include Moltbook code", async () => {
|
|
100
|
+
const { buildAgentScript } = await loadRunner();
|
|
101
|
+
const script = buildAgentScript({
|
|
102
|
+
systemPrompt: "test",
|
|
103
|
+
userMessage: "test",
|
|
104
|
+
clawbookApiUrl: "https://clawbook.network",
|
|
105
|
+
sigmaMemberWif: "L1test",
|
|
106
|
+
});
|
|
107
|
+
expect(script).not.toContain("moltbookRequest");
|
|
108
|
+
expect(script).not.toContain('name: "moltbook"');
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
it("defines tools via createSdkMcpServer", async () => {
|
|
112
|
+
const { buildAgentScript } = await loadRunner();
|
|
113
|
+
const script = buildAgentScript({
|
|
114
|
+
systemPrompt: "test",
|
|
115
|
+
userMessage: "test",
|
|
116
|
+
clawbookApiUrl: "https://clawbook.network",
|
|
117
|
+
sigmaMemberWif: "L1test",
|
|
118
|
+
});
|
|
119
|
+
expect(script).toContain("createSdkMcpServer");
|
|
120
|
+
expect(script).toContain('name: "clawbook"');
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
it("outputs JSON result to stdout", async () => {
|
|
124
|
+
const { buildAgentScript } = await loadRunner();
|
|
125
|
+
const script = buildAgentScript({
|
|
126
|
+
systemPrompt: "test",
|
|
127
|
+
userMessage: "test",
|
|
128
|
+
clawbookApiUrl: "https://clawbook.network",
|
|
129
|
+
sigmaMemberWif: "L1test",
|
|
130
|
+
});
|
|
131
|
+
expect(script).toContain("console.log");
|
|
132
|
+
expect(script).toContain("JSON.stringify");
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
it("captures tool_use blocks into actions", async () => {
|
|
136
|
+
const { buildAgentScript } = await loadRunner();
|
|
137
|
+
const script = buildAgentScript({
|
|
138
|
+
systemPrompt: "test",
|
|
139
|
+
userMessage: "test",
|
|
140
|
+
clawbookApiUrl: "https://clawbook.network",
|
|
141
|
+
sigmaMemberWif: "L1test",
|
|
142
|
+
});
|
|
143
|
+
expect(script).toContain('"tool_use"');
|
|
144
|
+
expect(script).toContain("actions.push");
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
it("imports getAuthToken from bitcoin-auth", async () => {
|
|
148
|
+
const { buildAgentScript } = await loadRunner();
|
|
149
|
+
const script = buildAgentScript({
|
|
150
|
+
systemPrompt: "test",
|
|
151
|
+
userMessage: "test",
|
|
152
|
+
clawbookApiUrl: "https://clawbook.network",
|
|
153
|
+
sigmaMemberWif: "L1test",
|
|
154
|
+
});
|
|
155
|
+
expect(script).toContain('import { getAuthToken } from "bitcoin-auth"');
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
it("includes signRequest helper function", async () => {
|
|
159
|
+
const { buildAgentScript } = await loadRunner();
|
|
160
|
+
const script = buildAgentScript({
|
|
161
|
+
systemPrompt: "test",
|
|
162
|
+
userMessage: "test",
|
|
163
|
+
clawbookApiUrl: "https://clawbook.network",
|
|
164
|
+
sigmaMemberWif: "L1test",
|
|
165
|
+
});
|
|
166
|
+
expect(script).toContain("function signRequest(path, bodyObj)");
|
|
167
|
+
expect(script).toContain("getAuthToken(");
|
|
168
|
+
expect(script).toContain("privateKeyWif: WIF");
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
it("uses X-Auth-Token header instead of Authorization Bearer", async () => {
|
|
172
|
+
const { buildAgentScript } = await loadRunner();
|
|
173
|
+
const script = buildAgentScript({
|
|
174
|
+
systemPrompt: "test",
|
|
175
|
+
userMessage: "test",
|
|
176
|
+
clawbookApiUrl: "https://clawbook.network",
|
|
177
|
+
sigmaMemberWif: "L1test",
|
|
178
|
+
});
|
|
179
|
+
expect(script).toContain("X-Auth-Token");
|
|
180
|
+
expect(script).not.toContain("Authorization");
|
|
181
|
+
expect(script).not.toContain("Bearer");
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
it("uses query() with maxTurns and bypassPermissions", async () => {
|
|
185
|
+
const { buildAgentScript } = await loadRunner();
|
|
186
|
+
const script = buildAgentScript({
|
|
187
|
+
systemPrompt: "test",
|
|
188
|
+
userMessage: "test",
|
|
189
|
+
clawbookApiUrl: "https://clawbook.network",
|
|
190
|
+
sigmaMemberWif: "L1test",
|
|
191
|
+
});
|
|
192
|
+
expect(script).toContain("maxTurns: 10");
|
|
193
|
+
expect(script).toContain('permissionMode: "bypassPermissions"');
|
|
194
|
+
expect(script).toContain("allowDangerouslySkipPermissions: true");
|
|
195
|
+
});
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
describe("runAgentTurn", () => {
|
|
199
|
+
beforeEach(() => {
|
|
200
|
+
vi.clearAllMocks();
|
|
201
|
+
mockRunCommand.mockResolvedValue(cmdResult(0, ""));
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
it("creates a sandbox, writes files, installs CLI and deps, and runs script", async () => {
|
|
205
|
+
mockRunCommand.mockImplementation(({ cmd, args }) => {
|
|
206
|
+
if (cmd === "node" && args?.[0] === "agent.mjs") {
|
|
207
|
+
return cmdResult(
|
|
208
|
+
0,
|
|
209
|
+
JSON.stringify({
|
|
210
|
+
success: true,
|
|
211
|
+
summary: "Posted something",
|
|
212
|
+
actions: [{ tool: "create_post", input: { content: "Hello" } }],
|
|
213
|
+
}),
|
|
214
|
+
);
|
|
215
|
+
}
|
|
216
|
+
return cmdResult(0, "");
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
const { runAgentTurn } = await loadRunner();
|
|
220
|
+
const result = await runAgentTurn("scheduled_post", {
|
|
221
|
+
clawbookApiUrl: "https://clawbook.network",
|
|
222
|
+
sigmaMemberWif: "L1test",
|
|
223
|
+
anthropicAuthToken: "sk-ant-oat01-test",
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
expect(mockCreate).toHaveBeenCalled();
|
|
227
|
+
expect(mockWriteFiles).toHaveBeenCalled();
|
|
228
|
+
expect(mockRunCommand).toHaveBeenCalled();
|
|
229
|
+
expect(mockStop).toHaveBeenCalled();
|
|
230
|
+
expect(result.success).toBe(true);
|
|
231
|
+
expect(result.summary).toBe("Posted something");
|
|
232
|
+
expect(result.actions).toHaveLength(1);
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
it("returns error result when script fails", async () => {
|
|
236
|
+
mockRunCommand.mockImplementation(({ cmd, args }) => {
|
|
237
|
+
if (cmd === "node" && args?.[0] === "agent.mjs") {
|
|
238
|
+
return cmdResult(1, "", "Error: auth failed");
|
|
239
|
+
}
|
|
240
|
+
return cmdResult(0, "");
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
const { runAgentTurn } = await loadRunner();
|
|
244
|
+
const result = await runAgentTurn("scheduled_post", {
|
|
245
|
+
clawbookApiUrl: "https://clawbook.network",
|
|
246
|
+
sigmaMemberWif: "L1test",
|
|
247
|
+
anthropicAuthToken: "sk-ant-oat01-test",
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
expect(result.success).toBe(false);
|
|
251
|
+
expect(result.error).toContain("auth failed");
|
|
252
|
+
expect(mockStop).toHaveBeenCalled();
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
it("stops sandbox even on error", async () => {
|
|
256
|
+
mockWriteFiles.mockRejectedValueOnce(new Error("Write failed"));
|
|
257
|
+
|
|
258
|
+
const { runAgentTurn } = await loadRunner();
|
|
259
|
+
const result = await runAgentTurn("scheduled_post", {
|
|
260
|
+
clawbookApiUrl: "https://clawbook.network",
|
|
261
|
+
sigmaMemberWif: "L1test",
|
|
262
|
+
anthropicAuthToken: "sk-ant-oat01-test",
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
expect(result.success).toBe(false);
|
|
266
|
+
expect(mockStop).toHaveBeenCalled();
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
it("passes CLAUDE_CODE_OAUTH_TOKEN to sandbox runCommand env", async () => {
|
|
270
|
+
mockRunCommand.mockImplementation(({ cmd, args }) => {
|
|
271
|
+
if (cmd === "node" && args?.[0] === "agent.mjs") {
|
|
272
|
+
return cmdResult(
|
|
273
|
+
0,
|
|
274
|
+
JSON.stringify({ success: true, summary: "Done", actions: [] }),
|
|
275
|
+
);
|
|
276
|
+
}
|
|
277
|
+
return cmdResult(0, "");
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
const { runAgentTurn } = await loadRunner();
|
|
281
|
+
await runAgentTurn("scheduled_post", {
|
|
282
|
+
clawbookApiUrl: "https://clawbook.network",
|
|
283
|
+
sigmaMemberWif: "L1test",
|
|
284
|
+
anthropicAuthToken: "sk-ant-oat01-test",
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
expect(mockRunCommand).toHaveBeenCalledWith(
|
|
288
|
+
expect.objectContaining({
|
|
289
|
+
cmd: "node",
|
|
290
|
+
args: ["agent.mjs"],
|
|
291
|
+
env: expect.objectContaining({
|
|
292
|
+
CLAUDE_CODE_OAUTH_TOKEN: "sk-ant-oat01-test",
|
|
293
|
+
}),
|
|
294
|
+
}),
|
|
295
|
+
);
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
it("installs Claude Code CLI globally in sandbox", async () => {
|
|
299
|
+
mockRunCommand.mockImplementation(({ cmd, args }) => {
|
|
300
|
+
if (cmd === "node" && args?.[0] === "agent.mjs") {
|
|
301
|
+
return cmdResult(
|
|
302
|
+
0,
|
|
303
|
+
JSON.stringify({ success: true, summary: "Done", actions: [] }),
|
|
304
|
+
);
|
|
305
|
+
}
|
|
306
|
+
return cmdResult(0, "");
|
|
307
|
+
});
|
|
308
|
+
|
|
309
|
+
const { runAgentTurn } = await loadRunner();
|
|
310
|
+
await runAgentTurn("scheduled_post", {
|
|
311
|
+
clawbookApiUrl: "https://clawbook.network",
|
|
312
|
+
sigmaMemberWif: "L1test",
|
|
313
|
+
anthropicAuthToken: "sk-ant-oat01-test",
|
|
314
|
+
});
|
|
315
|
+
|
|
316
|
+
expect(mockRunCommand).toHaveBeenCalledWith(
|
|
317
|
+
expect.objectContaining({
|
|
318
|
+
cmd: "npm",
|
|
319
|
+
args: ["install", "-g", "@anthropic-ai/claude-code"],
|
|
320
|
+
}),
|
|
321
|
+
);
|
|
322
|
+
});
|
|
323
|
+
|
|
324
|
+
it("installs agent SDK and bitcoin-auth in sandbox", async () => {
|
|
325
|
+
mockRunCommand.mockImplementation(({ cmd, args }) => {
|
|
326
|
+
if (cmd === "node" && args?.[0] === "agent.mjs") {
|
|
327
|
+
return cmdResult(
|
|
328
|
+
0,
|
|
329
|
+
JSON.stringify({ success: true, summary: "Done", actions: [] }),
|
|
330
|
+
);
|
|
331
|
+
}
|
|
332
|
+
return cmdResult(0, "");
|
|
333
|
+
});
|
|
334
|
+
|
|
335
|
+
const { runAgentTurn } = await loadRunner();
|
|
336
|
+
await runAgentTurn("scheduled_post", {
|
|
337
|
+
clawbookApiUrl: "https://clawbook.network",
|
|
338
|
+
sigmaMemberWif: "L1test",
|
|
339
|
+
anthropicAuthToken: "sk-ant-oat01-test",
|
|
340
|
+
});
|
|
341
|
+
|
|
342
|
+
expect(mockRunCommand).toHaveBeenCalledWith(
|
|
343
|
+
expect.objectContaining({
|
|
344
|
+
cmd: "npm",
|
|
345
|
+
args: expect.arrayContaining([
|
|
346
|
+
"@anthropic-ai/claude-agent-sdk",
|
|
347
|
+
"bitcoin-auth",
|
|
348
|
+
"zod",
|
|
349
|
+
]),
|
|
350
|
+
}),
|
|
351
|
+
);
|
|
352
|
+
});
|
|
353
|
+
});
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import {
|
|
3
|
+
ALLOWED_TOOLS,
|
|
4
|
+
CLAWBOOK_TOOL_NAMES,
|
|
5
|
+
CLAWBOOK_TOOLS,
|
|
6
|
+
MOLTBOOK_TOOL_NAMES,
|
|
7
|
+
MOLTBOOK_TOOLS,
|
|
8
|
+
TOOL_DEFINITIONS,
|
|
9
|
+
TOOL_NAMES,
|
|
10
|
+
} from "../../src/agent/tool-definitions";
|
|
11
|
+
|
|
12
|
+
const EXPECTED_CLAWBOOK_TOOLS = [
|
|
13
|
+
"read_feed",
|
|
14
|
+
"read_replies",
|
|
15
|
+
"read_post",
|
|
16
|
+
"read_channel",
|
|
17
|
+
"list_channels",
|
|
18
|
+
"read_profile",
|
|
19
|
+
"create_post",
|
|
20
|
+
"reply_to_post",
|
|
21
|
+
"like_post",
|
|
22
|
+
"unlike_post",
|
|
23
|
+
"follow_user",
|
|
24
|
+
"unfollow_user",
|
|
25
|
+
"register_agent",
|
|
26
|
+
"create_channel",
|
|
27
|
+
"following_feed",
|
|
28
|
+
];
|
|
29
|
+
|
|
30
|
+
const EXPECTED_MOLTBOOK_TOOLS = [
|
|
31
|
+
"moltbook_read_feed",
|
|
32
|
+
"moltbook_read_post",
|
|
33
|
+
"moltbook_read_comments",
|
|
34
|
+
"moltbook_list_submolts",
|
|
35
|
+
"moltbook_read_submolt",
|
|
36
|
+
"moltbook_create_submolt",
|
|
37
|
+
"moltbook_subscribe",
|
|
38
|
+
"moltbook_unsubscribe",
|
|
39
|
+
"moltbook_submolt_feed",
|
|
40
|
+
"moltbook_update_submolt",
|
|
41
|
+
"moltbook_list_moderators",
|
|
42
|
+
"moltbook_create_post",
|
|
43
|
+
"moltbook_comment",
|
|
44
|
+
"moltbook_upvote",
|
|
45
|
+
"moltbook_downvote",
|
|
46
|
+
"moltbook_delete_post",
|
|
47
|
+
"moltbook_upvote_comment",
|
|
48
|
+
"moltbook_register",
|
|
49
|
+
"moltbook_follow_user",
|
|
50
|
+
"moltbook_unfollow_user",
|
|
51
|
+
"moltbook_personalized_feed",
|
|
52
|
+
"moltbook_check_status",
|
|
53
|
+
"moltbook_my_profile",
|
|
54
|
+
"moltbook_read_profile",
|
|
55
|
+
"moltbook_update_profile",
|
|
56
|
+
"moltbook_upload_avatar",
|
|
57
|
+
"moltbook_delete_avatar",
|
|
58
|
+
"moltbook_pin_post",
|
|
59
|
+
"moltbook_unpin_post",
|
|
60
|
+
"moltbook_add_moderator",
|
|
61
|
+
"moltbook_remove_moderator",
|
|
62
|
+
"moltbook_search",
|
|
63
|
+
"moltbook_dm_check",
|
|
64
|
+
"moltbook_dm_requests",
|
|
65
|
+
"moltbook_dm_approve",
|
|
66
|
+
"moltbook_dm_conversations",
|
|
67
|
+
"moltbook_dm_read",
|
|
68
|
+
"moltbook_dm_send",
|
|
69
|
+
"moltbook_dm_request",
|
|
70
|
+
];
|
|
71
|
+
|
|
72
|
+
describe("TOOL_DEFINITIONS", () => {
|
|
73
|
+
it("defines exactly 54 tools", () => {
|
|
74
|
+
expect(TOOL_DEFINITIONS).toHaveLength(54);
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it("contains all expected Clawbook tool names", () => {
|
|
78
|
+
expect(TOOL_NAMES).toEqual(expect.arrayContaining(EXPECTED_CLAWBOOK_TOOLS));
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it("contains all expected Moltbook tool names", () => {
|
|
82
|
+
expect(TOOL_NAMES).toEqual(expect.arrayContaining(EXPECTED_MOLTBOOK_TOOLS));
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it("has no duplicate tool names", () => {
|
|
86
|
+
const unique = new Set(TOOL_NAMES);
|
|
87
|
+
expect(unique.size).toBe(TOOL_NAMES.length);
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
it("generates correct MCP-prefixed allowed tools", () => {
|
|
91
|
+
for (const name of EXPECTED_CLAWBOOK_TOOLS) {
|
|
92
|
+
expect(ALLOWED_TOOLS).toContain(`mcp__clawbook__${name}`);
|
|
93
|
+
}
|
|
94
|
+
for (const name of EXPECTED_MOLTBOOK_TOOLS) {
|
|
95
|
+
expect(ALLOWED_TOOLS).toContain(`mcp__moltbook__${name}`);
|
|
96
|
+
}
|
|
97
|
+
expect(ALLOWED_TOOLS).toHaveLength(54);
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
it("every tool has a non-empty description", () => {
|
|
101
|
+
for (const tool of TOOL_DEFINITIONS) {
|
|
102
|
+
expect(tool.description.length).toBeGreaterThan(0);
|
|
103
|
+
}
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
it("authed Clawbook POST/DELETE tools have bodyFields defined", () => {
|
|
107
|
+
for (const tool of CLAWBOOK_TOOLS) {
|
|
108
|
+
if (tool.auth && tool.method !== "GET") {
|
|
109
|
+
expect(tool.bodyFields).toBeDefined();
|
|
110
|
+
expect(tool.bodyFields?.length).toBeGreaterThan(0);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
it("path param tools have pathParams defined", () => {
|
|
116
|
+
const pathParamTools = TOOL_DEFINITIONS.filter((t) => t.path.includes(":"));
|
|
117
|
+
for (const tool of pathParamTools) {
|
|
118
|
+
expect(tool.pathParams).toBeDefined();
|
|
119
|
+
expect(tool.pathParams?.length).toBeGreaterThan(0);
|
|
120
|
+
}
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
it("create_post and reply_to_post use POST /api/posts", () => {
|
|
124
|
+
const createPost = TOOL_DEFINITIONS.find((t) => t.name === "create_post");
|
|
125
|
+
const replyPost = TOOL_DEFINITIONS.find((t) => t.name === "reply_to_post");
|
|
126
|
+
expect(createPost?.method).toBe("POST");
|
|
127
|
+
expect(createPost?.path).toBe("/api/posts");
|
|
128
|
+
expect(replyPost?.method).toBe("POST");
|
|
129
|
+
expect(replyPost?.path).toBe("/api/posts");
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
it("all Clawbook tools have network: clawbook", () => {
|
|
133
|
+
for (const tool of CLAWBOOK_TOOLS) {
|
|
134
|
+
expect(tool.network).toBe("clawbook");
|
|
135
|
+
}
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
it("all Moltbook tools have network: moltbook", () => {
|
|
139
|
+
for (const tool of MOLTBOOK_TOOLS) {
|
|
140
|
+
expect(tool.network).toBe("moltbook");
|
|
141
|
+
}
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
it("authed Clawbook tools have authScheme: bitcoin-auth", () => {
|
|
145
|
+
for (const tool of CLAWBOOK_TOOLS) {
|
|
146
|
+
if (tool.auth) {
|
|
147
|
+
expect(tool.authScheme).toBe("bitcoin-auth");
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
it("authed Moltbook tools have authScheme: bearer", () => {
|
|
153
|
+
for (const tool of MOLTBOOK_TOOLS) {
|
|
154
|
+
if (tool.auth) {
|
|
155
|
+
expect(tool.authScheme).toBe("bearer");
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
it("Moltbook tool names are prefixed with mcp__moltbook__", () => {
|
|
161
|
+
for (const name of MOLTBOOK_TOOL_NAMES) {
|
|
162
|
+
expect(name).toMatch(/^mcp__moltbook__/);
|
|
163
|
+
}
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
it("Clawbook tool names are prefixed with mcp__clawbook__", () => {
|
|
167
|
+
for (const name of CLAWBOOK_TOOL_NAMES) {
|
|
168
|
+
expect(name).toMatch(/^mcp__clawbook__/);
|
|
169
|
+
}
|
|
170
|
+
});
|
|
171
|
+
});
|