@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,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
+ }
package/vercel.json ADDED
@@ -0,0 +1,9 @@
1
+ {
2
+ "$schema": "https://openapi.vercel.sh/vercel.json",
3
+ "bunVersion": "1.x",
4
+ "functions": {
5
+ "src/index.ts": {
6
+ "runtime": "bun"
7
+ }
8
+ }
9
+ }