@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,198 @@
|
|
|
1
|
+
import { PrivateKey } from "@bsv/sdk";
|
|
2
|
+
import { getAuthToken } from "bitcoin-auth";
|
|
3
|
+
import { Hono } from "hono";
|
|
4
|
+
import { afterAll, beforeAll, describe, expect, test, vi } from "vitest";
|
|
5
|
+
import { getOwnerPubkey, sigmaAuth } from "../../src/middleware/sigma-auth";
|
|
6
|
+
|
|
7
|
+
describe("Sigma Auth Middleware", () => {
|
|
8
|
+
let ownerWif: string;
|
|
9
|
+
let otherWif: string;
|
|
10
|
+
let app: Hono;
|
|
11
|
+
|
|
12
|
+
beforeAll(() => {
|
|
13
|
+
// Generate test WIFs
|
|
14
|
+
ownerWif = PrivateKey.fromRandom().toWif();
|
|
15
|
+
otherWif = PrivateKey.fromRandom().toWif();
|
|
16
|
+
|
|
17
|
+
// Set owner WIF in env
|
|
18
|
+
process.env.SIGMA_MEMBER_WIF = ownerWif;
|
|
19
|
+
|
|
20
|
+
// Create test app with middleware
|
|
21
|
+
app = new Hono();
|
|
22
|
+
app.use("/protected/*", sigmaAuth);
|
|
23
|
+
app.get("/protected/test", (c) => c.json({ message: "success" }));
|
|
24
|
+
app.post("/protected/test", async (c) => {
|
|
25
|
+
const body = await c.req.json();
|
|
26
|
+
return c.json({ message: "success", received: body });
|
|
27
|
+
});
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
afterAll(() => {
|
|
31
|
+
// Clean up env
|
|
32
|
+
delete process.env.SIGMA_MEMBER_WIF;
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
test("getOwnerPubkey derives and caches public key", () => {
|
|
36
|
+
const pubkey1 = getOwnerPubkey();
|
|
37
|
+
const pubkey2 = getOwnerPubkey();
|
|
38
|
+
|
|
39
|
+
// Should return same instance (cached)
|
|
40
|
+
expect(pubkey1).toBe(pubkey2);
|
|
41
|
+
|
|
42
|
+
// Should match expected public key
|
|
43
|
+
const expectedPubkey = PrivateKey.fromWif(ownerWif)
|
|
44
|
+
.toPublicKey()
|
|
45
|
+
.toString();
|
|
46
|
+
expect(pubkey1).toBe(expectedPubkey);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
test("allows valid owner token without body", async () => {
|
|
50
|
+
const requestPath = "/protected/test";
|
|
51
|
+
const token = getAuthToken({
|
|
52
|
+
privateKeyWif: ownerWif,
|
|
53
|
+
requestPath,
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
const response = await app.request(`http://localhost${requestPath}`, {
|
|
57
|
+
headers: {
|
|
58
|
+
Authorization: `Bearer ${token}`,
|
|
59
|
+
},
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
expect(response.status).toBe(200);
|
|
63
|
+
const data = await response.json();
|
|
64
|
+
expect(data).toEqual({ message: "success" });
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
test("allows valid owner token with body", async () => {
|
|
68
|
+
const requestPath = "/protected/test";
|
|
69
|
+
const body = JSON.stringify({ key: "value" });
|
|
70
|
+
const token = getAuthToken({
|
|
71
|
+
privateKeyWif: ownerWif,
|
|
72
|
+
requestPath,
|
|
73
|
+
body,
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
const response = await app.request(`http://localhost${requestPath}`, {
|
|
77
|
+
method: "POST",
|
|
78
|
+
headers: {
|
|
79
|
+
Authorization: `Bearer ${token}`,
|
|
80
|
+
"Content-Type": "application/json",
|
|
81
|
+
},
|
|
82
|
+
body,
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
expect(response.status).toBe(200);
|
|
86
|
+
const data = await response.json();
|
|
87
|
+
expect(data.message).toBe("success");
|
|
88
|
+
expect(data.received).toEqual({ key: "value" });
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
test("rejects missing Authorization header", async () => {
|
|
92
|
+
const requestPath = "/protected/test";
|
|
93
|
+
|
|
94
|
+
const response = await app.request(`http://localhost${requestPath}`);
|
|
95
|
+
|
|
96
|
+
expect(response.status).toBe(401);
|
|
97
|
+
const data = await response.json();
|
|
98
|
+
expect(data.error).toBe("Missing or invalid Authorization header");
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
test("rejects malformed token", async () => {
|
|
102
|
+
const requestPath = "/protected/test";
|
|
103
|
+
|
|
104
|
+
const response = await app.request(`http://localhost${requestPath}`, {
|
|
105
|
+
headers: {
|
|
106
|
+
Authorization: "Bearer random-invalid-string",
|
|
107
|
+
},
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
expect(response.status).toBe(401);
|
|
111
|
+
const data = await response.json();
|
|
112
|
+
expect(data.error).toBe("Invalid token format");
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
test("rejects valid token from wrong identity", async () => {
|
|
116
|
+
const requestPath = "/protected/test";
|
|
117
|
+
const token = getAuthToken({
|
|
118
|
+
privateKeyWif: otherWif, // Different identity
|
|
119
|
+
requestPath,
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
const response = await app.request(`http://localhost${requestPath}`, {
|
|
123
|
+
headers: {
|
|
124
|
+
Authorization: `Bearer ${token}`,
|
|
125
|
+
},
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
expect(response.status).toBe(403);
|
|
129
|
+
const data = await response.json();
|
|
130
|
+
expect(data.error).toBe("Forbidden: Identity mismatch");
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
test("rejects expired token", async () => {
|
|
134
|
+
const requestPath = "/protected/test";
|
|
135
|
+
// Create token with timestamp 10 minutes in the past
|
|
136
|
+
const oldTimestamp = new Date(Date.now() - 10 * 60 * 1000).toISOString();
|
|
137
|
+
const token = getAuthToken({
|
|
138
|
+
privateKeyWif: ownerWif,
|
|
139
|
+
requestPath,
|
|
140
|
+
timestamp: oldTimestamp,
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
const response = await app.request(`http://localhost${requestPath}`, {
|
|
144
|
+
headers: {
|
|
145
|
+
Authorization: `Bearer ${token}`,
|
|
146
|
+
},
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
expect(response.status).toBe(401);
|
|
150
|
+
const data = await response.json();
|
|
151
|
+
expect(data.error).toBe("Invalid or expired token");
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
test("returns 500 when parseAuthToken throws", async () => {
|
|
155
|
+
const parseSpy = vi.spyOn(await import("bitcoin-auth"), "parseAuthToken");
|
|
156
|
+
parseSpy.mockImplementation(() => {
|
|
157
|
+
throw new Error("parse explosion");
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
const requestPath = "/protected/test";
|
|
161
|
+
const response = await app.request(`http://localhost${requestPath}`, {
|
|
162
|
+
headers: {
|
|
163
|
+
Authorization: "Bearer some-token-value",
|
|
164
|
+
},
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
expect(response.status).toBe(500);
|
|
168
|
+
const data = await response.json();
|
|
169
|
+
expect(data.error).toBe("Authentication processing error");
|
|
170
|
+
|
|
171
|
+
parseSpy.mockRestore();
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
test("returns 500 when verifyAuthToken throws", async () => {
|
|
175
|
+
const verifySpy = vi.spyOn(await import("bitcoin-auth"), "verifyAuthToken");
|
|
176
|
+
verifySpy.mockImplementation(() => {
|
|
177
|
+
throw new Error("verify explosion");
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
const requestPath = "/protected/test";
|
|
181
|
+
const token = getAuthToken({
|
|
182
|
+
privateKeyWif: ownerWif,
|
|
183
|
+
requestPath,
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
const response = await app.request(`http://localhost${requestPath}`, {
|
|
187
|
+
headers: {
|
|
188
|
+
Authorization: `Bearer ${token}`,
|
|
189
|
+
},
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
expect(response.status).toBe(500);
|
|
193
|
+
const data = await response.json();
|
|
194
|
+
expect(data.error).toBe("Authentication processing error");
|
|
195
|
+
|
|
196
|
+
verifySpy.mockRestore();
|
|
197
|
+
});
|
|
198
|
+
});
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { createRun, getRun, updateRun } from "../src/runs";
|
|
3
|
+
|
|
4
|
+
describe("run store", () => {
|
|
5
|
+
it("createRun returns a RunState with pending status", () => {
|
|
6
|
+
const run = createRun("conversation");
|
|
7
|
+
expect(run.id).toBeDefined();
|
|
8
|
+
expect(run.status).toBe("pending");
|
|
9
|
+
expect(run.trigger).toBe("conversation");
|
|
10
|
+
expect(run.createdAt).toBeDefined();
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
it("getRun finds a created run", () => {
|
|
14
|
+
const run = createRun("heartbeat");
|
|
15
|
+
const found = getRun(run.id);
|
|
16
|
+
expect(found).toBeDefined();
|
|
17
|
+
expect(found?.id).toBe(run.id);
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it("getRun returns undefined for missing id", () => {
|
|
21
|
+
expect(getRun("nonexistent-id")).toBeUndefined();
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it("updateRun changes status and result", () => {
|
|
25
|
+
const run = createRun("manual");
|
|
26
|
+
updateRun(run.id, {
|
|
27
|
+
status: "completed",
|
|
28
|
+
result: { success: true, summary: "Done", actions: [] },
|
|
29
|
+
});
|
|
30
|
+
const updated = getRun(run.id);
|
|
31
|
+
expect(updated?.status).toBe("completed");
|
|
32
|
+
expect(updated?.result?.summary).toBe("Done");
|
|
33
|
+
expect(updated?.completedAt).toBeDefined();
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it("updateRun sets completedAt on failure", () => {
|
|
37
|
+
const run = createRun("conversation");
|
|
38
|
+
updateRun(run.id, { status: "failed" });
|
|
39
|
+
const updated = getRun(run.id);
|
|
40
|
+
expect(updated?.status).toBe("failed");
|
|
41
|
+
expect(updated?.completedAt).toBeDefined();
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it("pruning removes runs older than 1 hour", () => {
|
|
45
|
+
const old = createRun("conversation");
|
|
46
|
+
// Manually backdate the createdAt
|
|
47
|
+
const run = getRun(old.id);
|
|
48
|
+
if (run) {
|
|
49
|
+
run.createdAt = new Date(Date.now() - 2 * 60 * 60 * 1000).toISOString();
|
|
50
|
+
}
|
|
51
|
+
// Creating a new run triggers pruning
|
|
52
|
+
const fresh = createRun("conversation");
|
|
53
|
+
expect(getRun(old.id)).toBeUndefined();
|
|
54
|
+
expect(getRun(fresh.id)).toBeDefined();
|
|
55
|
+
});
|
|
56
|
+
});
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
import { resolve } from "node:path";
|
|
3
|
+
import { describe, expect, it } from "vitest";
|
|
4
|
+
|
|
5
|
+
function parseFrontmatter(content: string): Record<string, unknown> {
|
|
6
|
+
const match = content.match(/^---\n([\s\S]*?)\n---/);
|
|
7
|
+
if (!match) throw new Error("No frontmatter found");
|
|
8
|
+
const yaml = match[1];
|
|
9
|
+
const result: Record<string, unknown> = {};
|
|
10
|
+
for (const line of yaml.split("\n")) {
|
|
11
|
+
const colonIdx = line.indexOf(":");
|
|
12
|
+
if (colonIdx === -1) continue;
|
|
13
|
+
const key = line.slice(0, colonIdx).trim();
|
|
14
|
+
let value: unknown = line.slice(colonIdx + 1).trim();
|
|
15
|
+
if (typeof value === "string" && value.startsWith("{")) {
|
|
16
|
+
value = JSON.parse(value);
|
|
17
|
+
}
|
|
18
|
+
result[key] = value;
|
|
19
|
+
}
|
|
20
|
+
return result;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
describe("SKILL.md", () => {
|
|
24
|
+
const skillPath = resolve("skills/clawbook/SKILL.md");
|
|
25
|
+
const content = readFileSync(skillPath, "utf-8");
|
|
26
|
+
const frontmatter = parseFrontmatter(content);
|
|
27
|
+
|
|
28
|
+
it("has required name field", () => {
|
|
29
|
+
expect(frontmatter.name).toBe("clawbook");
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it("has description", () => {
|
|
33
|
+
expect(typeof frontmatter.description).toBe("string");
|
|
34
|
+
expect((frontmatter.description as string).length).toBeGreaterThan(10);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it("has homepage", () => {
|
|
38
|
+
expect(frontmatter.homepage).toBe("https://clawbook.network");
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it("has openclaw metadata with env requirements", () => {
|
|
42
|
+
const metadata = frontmatter.metadata as Record<string, unknown>;
|
|
43
|
+
expect(metadata).toBeDefined();
|
|
44
|
+
const openclaw = (metadata as Record<string, unknown>).openclaw as Record<
|
|
45
|
+
string,
|
|
46
|
+
unknown
|
|
47
|
+
>;
|
|
48
|
+
expect(openclaw).toBeDefined();
|
|
49
|
+
const requires = openclaw.requires as Record<string, unknown>;
|
|
50
|
+
expect(requires.env).toContain("CLAWBOOK_API_URL");
|
|
51
|
+
expect(requires.env).toContain("SIGMA_MEMBER_WIF");
|
|
52
|
+
});
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
describe("HEARTBEAT.md", () => {
|
|
56
|
+
const heartbeatPath = resolve("skills/clawbook/HEARTBEAT.md");
|
|
57
|
+
const content = readFileSync(heartbeatPath, "utf-8");
|
|
58
|
+
const frontmatter = parseFrontmatter(content);
|
|
59
|
+
|
|
60
|
+
it("has name field", () => {
|
|
61
|
+
expect(frontmatter.name).toBe("clawbook-heartbeat");
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it("has description", () => {
|
|
65
|
+
expect(typeof frontmatter.description).toBe("string");
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it("is not user-invocable", () => {
|
|
69
|
+
expect(frontmatter["user-invocable"]).toBe("false");
|
|
70
|
+
});
|
|
71
|
+
});
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"module": "ESNext",
|
|
5
|
+
"moduleResolution": "bundler",
|
|
6
|
+
"strict": true,
|
|
7
|
+
"esModuleInterop": true,
|
|
8
|
+
"skipLibCheck": true,
|
|
9
|
+
"forceConsistentCasingInFileNames": true,
|
|
10
|
+
"outDir": "./dist",
|
|
11
|
+
"rootDir": "./src"
|
|
12
|
+
},
|
|
13
|
+
"include": ["src/**/*"]
|
|
14
|
+
}
|