@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,14 @@
1
+ import type { RunAgentOptions } from "./agent/runner";
2
+ import { getBotIdentity } from "./bot-identity";
3
+ import { CLAWBOOK_API_URL } from "./constants";
4
+
5
+ export async function agentOptions(): Promise<RunAgentOptions> {
6
+ return {
7
+ clawbookApiUrl: CLAWBOOK_API_URL,
8
+ sigmaMemberWif: process.env.SIGMA_MEMBER_WIF ?? "",
9
+ moltbookApiKey: process.env.MOLTBOOK_API_KEY,
10
+ anthropicAuthToken:
11
+ process.env.CLAUDE_CODE_OAUTH_TOKEN ?? process.env.ANTHROPIC_AUTH_TOKEN,
12
+ botIdentity: await getBotIdentity(),
13
+ };
14
+ }
@@ -0,0 +1,41 @@
1
+ import { CLAWBOOK_API_URL } from "./constants";
2
+ import { createIdentity, getIdKey } from "./identity";
3
+
4
+ let _cached: { idKey: string; name: string } | undefined | null = null;
5
+
6
+ /**
7
+ * Derive bot identity from SIGMA_MEMBER_WIF.
8
+ * Fetches display name from on-chain BAP identity via profile API.
9
+ * Cached after first successful call. Returns undefined if WIF is missing or invalid.
10
+ */
11
+ export async function getBotIdentity(): Promise<
12
+ { idKey: string; name: string } | undefined
13
+ > {
14
+ if (_cached !== null) return _cached;
15
+ const wif = process.env.SIGMA_MEMBER_WIF;
16
+ if (!wif) {
17
+ _cached = undefined;
18
+ return undefined;
19
+ }
20
+ try {
21
+ const identity = createIdentity(wif);
22
+ const idKey = getIdKey(identity);
23
+ let name = idKey.slice(0, 8);
24
+ try {
25
+ const res = await fetch(
26
+ `${CLAWBOOK_API_URL}/api/profiles/${encodeURIComponent(idKey)}`,
27
+ );
28
+ const data = await res.json();
29
+ if (data?.data?.identity?.alternateName) {
30
+ name = data.data.identity.alternateName;
31
+ }
32
+ } catch {
33
+ /* use truncated idKey as fallback */
34
+ }
35
+ _cached = { idKey, name };
36
+ return _cached;
37
+ } catch {
38
+ _cached = undefined;
39
+ return undefined;
40
+ }
41
+ }
@@ -0,0 +1,15 @@
1
+ export const APP_NAME = "clawbook";
2
+ export const BAP_PREFIX = "1BAPSuaPnfGnSBM3GLV9yhxUdYe4vGbdMT";
3
+ export const B_PREFIX = "19HxigV4QyBv3tHpQVcUEQyq1pzZVdoAut";
4
+ export const MAP_PREFIX = "1PuQa7K62MiKCtssSLKy1kh56WWU7MtUR5";
5
+ export const AIP_PREFIX = "15PciHG22SNLQJXMoSUaWVi7WSqc7hCfva";
6
+ export const BMAP_URL = "https://bmap-api-production.up.railway.app";
7
+ export const SIGMA_API_URL = "https://api.sigmaidentity.com/v1";
8
+ export const SIGMA_AUTH_URL = "https://auth.sigmaidentity.com";
9
+ export const ORDFS_URL = "https://ordfs.network";
10
+ export const BSOCIAL_API_URL =
11
+ "https://bsocial-overlay-production.up.railway.app/api/v1";
12
+ export const CLAWBOOK_API_URL =
13
+ process.env.CLAWBOOK_API_URL || "https://clawbook.network";
14
+ export const MOLTBOOK_API_URL = "https://www.moltbook.com/api/v1";
15
+ export const SITE_DESCRIPTION = "The front page of the agent blockchain";
@@ -0,0 +1,21 @@
1
+ import type { AgentTurnResult } from "../agent/runner";
2
+ import { runAgentTurn } from "../agent/runner";
3
+ import { getBotIdentity } from "../bot-identity";
4
+ import { CLAWBOOK_API_URL } from "../constants";
5
+
6
+ export async function handleHeartbeat(): Promise<AgentTurnResult> {
7
+ try {
8
+ return await runAgentTurn("heartbeat", {
9
+ clawbookApiUrl: CLAWBOOK_API_URL,
10
+ sigmaMemberWif: process.env.SIGMA_MEMBER_WIF ?? "",
11
+ moltbookApiKey: process.env.MOLTBOOK_API_KEY,
12
+ anthropicAuthToken:
13
+ process.env.CLAUDE_CODE_OAUTH_TOKEN ?? process.env.ANTHROPIC_AUTH_TOKEN,
14
+ botIdentity: await getBotIdentity(),
15
+ });
16
+ } catch (err) {
17
+ const msg = err instanceof Error ? err.message : String(err);
18
+ console.error(`[heartbeat] error: ${msg}`);
19
+ return { success: false, summary: "", actions: [], error: msg };
20
+ }
21
+ }
@@ -0,0 +1,95 @@
1
+ import type { Context } from "hono";
2
+ import { stream } from "hono/streaming";
3
+ import { runAgentTurn } from "../agent/runner";
4
+ import { agentOptions } from "../agent-options";
5
+
6
+ interface ChatMessage {
7
+ role: string;
8
+ content: string;
9
+ }
10
+
11
+ interface ChatCompletionRequest {
12
+ messages?: ChatMessage[];
13
+ stream?: boolean;
14
+ }
15
+
16
+ function extractLastUserMessage(messages: ChatMessage[]): string | null {
17
+ for (let i = messages.length - 1; i >= 0; i--) {
18
+ if (messages[i].role === "user" && messages[i].content) {
19
+ return messages[i].content;
20
+ }
21
+ }
22
+ return null;
23
+ }
24
+
25
+ export async function handleChatCompletions(c: Context) {
26
+ const body = await c.req.json<ChatCompletionRequest>();
27
+
28
+ if (
29
+ !body.messages ||
30
+ !Array.isArray(body.messages) ||
31
+ body.messages.length === 0
32
+ ) {
33
+ return c.json(
34
+ { error: "messages array is required and must not be empty" },
35
+ 400,
36
+ );
37
+ }
38
+
39
+ const userMessage = extractLastUserMessage(body.messages);
40
+ if (!userMessage) {
41
+ return c.json({ error: "No user message found in messages array" }, 400);
42
+ }
43
+
44
+ const opts = await agentOptions();
45
+ const result = await runAgentTurn("conversation", {
46
+ ...opts,
47
+ customMessage: userMessage,
48
+ });
49
+
50
+ const runId = `run_${crypto.randomUUID()}`;
51
+ const created = Math.floor(Date.now() / 1000);
52
+ const content = result.success
53
+ ? result.summary || "Agent completed with no summary."
54
+ : `Error: ${result.error || "Agent failed"}`;
55
+
56
+ if (body.stream) {
57
+ return stream(c, async (s) => {
58
+ c.header("Content-Type", "text/event-stream");
59
+ c.header("Cache-Control", "no-cache");
60
+ c.header("Connection", "keep-alive");
61
+
62
+ const chunk = JSON.stringify({
63
+ id: runId,
64
+ object: "chat.completion.chunk",
65
+ created,
66
+ model: "clarkling",
67
+ choices: [
68
+ {
69
+ index: 0,
70
+ delta: { role: "assistant", content },
71
+ finish_reason: "stop",
72
+ },
73
+ ],
74
+ });
75
+
76
+ await s.write(`data: ${chunk}\n\n`);
77
+ await s.write("data: [DONE]\n\n");
78
+ });
79
+ }
80
+
81
+ return c.json({
82
+ id: runId,
83
+ object: "chat.completion",
84
+ created,
85
+ model: "clarkling",
86
+ choices: [
87
+ {
88
+ index: 0,
89
+ message: { role: "assistant", content },
90
+ finish_reason: "stop",
91
+ },
92
+ ],
93
+ usage: { prompt_tokens: 0, completion_tokens: 0, total_tokens: 0 },
94
+ });
95
+ }
@@ -0,0 +1,21 @@
1
+ import type { AgentTurnResult } from "../agent/runner";
2
+ import { runAgentTurn } from "../agent/runner";
3
+ import { getBotIdentity } from "../bot-identity";
4
+ import { CLAWBOOK_API_URL } from "../constants";
5
+
6
+ export async function handlePost(): Promise<AgentTurnResult> {
7
+ try {
8
+ return await runAgentTurn("scheduled_post", {
9
+ clawbookApiUrl: CLAWBOOK_API_URL,
10
+ sigmaMemberWif: process.env.SIGMA_MEMBER_WIF ?? "",
11
+ moltbookApiKey: process.env.MOLTBOOK_API_KEY,
12
+ anthropicAuthToken:
13
+ process.env.CLAUDE_CODE_OAUTH_TOKEN ?? process.env.ANTHROPIC_AUTH_TOKEN,
14
+ botIdentity: await getBotIdentity(),
15
+ });
16
+ } catch (err) {
17
+ const msg = err instanceof Error ? err.message : String(err);
18
+ console.error(`[post] error: ${msg}`);
19
+ return { success: false, summary: "", actions: [], error: msg };
20
+ }
21
+ }
@@ -0,0 +1,83 @@
1
+ import { BAP, type MasterID } from "bsv-bap";
2
+
3
+ /**
4
+ * BotIdentity wrapper around BAP MasterID
5
+ * Provides simplified interface for clawbook-bot
6
+ */
7
+ export interface BotIdentity {
8
+ bap: BAP;
9
+ masterId: MasterID;
10
+ }
11
+
12
+ /**
13
+ * Create a BAP identity from a WIF key
14
+ * Uses Type 42 format (recommended over deprecated BIP32)
15
+ *
16
+ * @param wif - Wallet Import Format private key
17
+ * @returns BotIdentity object containing BAP instance and MasterID
18
+ */
19
+ export function createIdentity(wif: string): BotIdentity {
20
+ const bap = new BAP({ rootPk: wif });
21
+
22
+ // Create a new identity
23
+ // For a bot, we'll use a simple name like "clawbook-bot"
24
+ const masterId = bap.newId("clawbook-bot");
25
+
26
+ return {
27
+ bap,
28
+ masterId,
29
+ };
30
+ }
31
+
32
+ /**
33
+ * Get the BAP identity key (idKey) for this identity
34
+ *
35
+ * @param identity - BotIdentity object
36
+ * @returns BAP identity key string
37
+ */
38
+ export function getIdKey(identity: BotIdentity): string {
39
+ return identity.masterId.getIdentityKey();
40
+ }
41
+
42
+ /**
43
+ * Sign OP_RETURN data with AIP (Author Identity Protocol)
44
+ *
45
+ * Takes hex-encoded data arrays and returns signed data including AIP signature.
46
+ * The returned array includes the original data plus AIP protocol elements:
47
+ * [AIP_PREFIX, "BITCOIN_ECDSA", address, signature, indices]
48
+ *
49
+ * @param identity - BotIdentity object
50
+ * @param opReturnData - Array of number arrays (hex-encoded data)
51
+ * @returns Signed OP_RETURN data with AIP signature appended
52
+ */
53
+ export function signOpReturn(
54
+ identity: BotIdentity,
55
+ opReturnData: number[][],
56
+ ): number[][] {
57
+ return identity.masterId.signOpReturnWithAIP(opReturnData);
58
+ }
59
+
60
+ /**
61
+ * Get the current signing address for this identity
62
+ *
63
+ * @param identity - BotIdentity object
64
+ * @returns Bitcoin address (base58check)
65
+ */
66
+ export function getCurrentAddress(identity: BotIdentity): string {
67
+ return identity.masterId.getCurrentAddress();
68
+ }
69
+
70
+ /**
71
+ * Sign a message with this identity
72
+ * Returns signature and address for verification
73
+ *
74
+ * @param identity - BotIdentity object
75
+ * @param message - Message as number array (UTF-8 bytes or hex)
76
+ * @returns Object containing address and signature
77
+ */
78
+ export function signMessage(
79
+ identity: BotIdentity,
80
+ message: number[],
81
+ ): { address: string; signature: string } {
82
+ return identity.masterId.signMessage(message);
83
+ }
package/src/index.ts ADDED
@@ -0,0 +1,30 @@
1
+ import { Hono } from "hono";
2
+
3
+ const app = new Hono();
4
+
5
+ app.get("/", (c) => {
6
+ return c.json({
7
+ name: "clawnet-minimal",
8
+ version: "0.1.0",
9
+ status: "ok",
10
+ });
11
+ });
12
+
13
+ app.post("/api/agent", async (c) => {
14
+ const body = await c.req.json();
15
+ console.log("Agent request:", body);
16
+
17
+ return c.json({
18
+ success: true,
19
+ message: "Agent endpoint ready",
20
+ timestamp: new Date().toISOString(),
21
+ });
22
+ });
23
+
24
+ const port = process.env.PORT ? parseInt(process.env.PORT) : 3000;
25
+ console.log(`Server starting on port ${port}`);
26
+
27
+ export default {
28
+ port,
29
+ fetch: app.fetch,
30
+ };
@@ -0,0 +1,53 @@
1
+ import { timingSafeEqual } from "node:crypto";
2
+ import type { MiddlewareHandler } from "hono";
3
+
4
+ /**
5
+ * Middleware to authenticate cron endpoints using CRON_SECRET bearer token.
6
+ *
7
+ * If CRON_SECRET is not set (dev mode), logs a warning and allows through.
8
+ * Otherwise, requires Authorization: Bearer <CRON_SECRET> header with timing-safe comparison.
9
+ */
10
+ export const cronAuth: MiddlewareHandler = async (c, next) => {
11
+ const secret = process.env.CRON_SECRET;
12
+
13
+ // Dev mode: no secret set
14
+ if (!secret) {
15
+ console.warn(
16
+ "[cronAuth] CRON_SECRET not set - allowing request (dev mode)",
17
+ );
18
+ return next();
19
+ }
20
+
21
+ // Extract bearer token
22
+ const authHeader = c.req.header("Authorization");
23
+ if (!authHeader) {
24
+ return c.json({ error: "Missing Authorization header" }, 401);
25
+ }
26
+
27
+ const [scheme, token] = authHeader.split(" ");
28
+ if (scheme !== "Bearer" || !token) {
29
+ return c.json({ error: "Invalid Authorization header format" }, 401);
30
+ }
31
+
32
+ // Timing-safe comparison
33
+ // timingSafeEqual requires equal-length buffers, so check lengths first
34
+ if (token.length !== secret.length) {
35
+ return c.json({ error: "Unauthorized" }, 401);
36
+ }
37
+
38
+ const encoder = new TextEncoder();
39
+ const tokenBuf = encoder.encode(token);
40
+ const secretBuf = encoder.encode(secret);
41
+
42
+ try {
43
+ if (!timingSafeEqual(tokenBuf, secretBuf)) {
44
+ return c.json({ error: "Unauthorized" }, 401);
45
+ }
46
+ } catch {
47
+ // timingSafeEqual can throw if buffers are different lengths
48
+ // (though we pre-check, this is defensive)
49
+ return c.json({ error: "Unauthorized" }, 401);
50
+ }
51
+
52
+ return next();
53
+ };
@@ -0,0 +1,147 @@
1
+ import { PrivateKey } from "@bsv/sdk";
2
+ import { parseAuthToken, verifyAuthToken } from "bitcoin-auth";
3
+ import type { MiddlewareHandler } from "hono";
4
+
5
+ // Cache the owner public key to avoid repeated derivation
6
+ let cachedOwnerPubkey: string | null = null;
7
+
8
+ /**
9
+ * Derives the owner's public key from SIGMA_MEMBER_WIF env var.
10
+ * Result is cached after first derivation.
11
+ */
12
+ export function getOwnerPubkey(): string {
13
+ if (cachedOwnerPubkey) {
14
+ return cachedOwnerPubkey;
15
+ }
16
+
17
+ const wif = process.env.SIGMA_MEMBER_WIF;
18
+ if (!wif) {
19
+ throw new Error("SIGMA_MEMBER_WIF environment variable not set");
20
+ }
21
+
22
+ try {
23
+ const privateKey = PrivateKey.fromWif(wif);
24
+ cachedOwnerPubkey = privateKey.toPublicKey().toString();
25
+ return cachedOwnerPubkey;
26
+ } catch (error) {
27
+ throw new Error(
28
+ `Failed to derive public key from SIGMA_MEMBER_WIF: ${error}`,
29
+ );
30
+ }
31
+ }
32
+
33
+ /**
34
+ * Hono middleware that verifies Bitcoin Signed Message authentication tokens.
35
+ *
36
+ * Extracts and verifies the Authorization: Bearer <token> header:
37
+ * 1. Parses the token using bitcoin-auth parseAuthToken()
38
+ * 2. Verifies the signature using bitcoin-auth verifyAuthToken()
39
+ * 3. Compares the pubkey against the owner's public key (derived from SIGMA_MEMBER_WIF)
40
+ *
41
+ * Returns:
42
+ * - 401 if token is missing, malformed, invalid signature, or expired
43
+ * - 403 if token is valid but from wrong identity (pubkey mismatch)
44
+ * - Calls next() if valid and matches owner identity
45
+ */
46
+ export const sigmaAuth: MiddlewareHandler = async (c, next) => {
47
+ const path = new URL(c.req.url).pathname;
48
+ console.log(`[sigma-auth] ${path} start method=${c.req.method}`);
49
+
50
+ // Extract Authorization header
51
+ const authHeader = c.req.header("Authorization");
52
+ if (!authHeader || !authHeader.startsWith("Bearer ")) {
53
+ return c.json({ error: "Missing or invalid Authorization header" }, 401);
54
+ }
55
+
56
+ const token = authHeader.substring(7); // Remove "Bearer " prefix
57
+
58
+ try {
59
+ // Parse token
60
+ const parsed = parseAuthToken(token);
61
+ if (!parsed) {
62
+ console.log(`[sigma-auth] ${path} REJECT: invalid token format`);
63
+ return c.json({ error: "Invalid token format" }, 401);
64
+ }
65
+
66
+ console.log(
67
+ `[sigma-auth] ${path} parsed token OK, pubkey=${parsed.pubkey.slice(0, 12)}...`,
68
+ );
69
+
70
+ const requestPath = path;
71
+
72
+ // Read body if present (for POST/PUT/PATCH requests)
73
+ let bodyText: string | undefined;
74
+ if (
75
+ c.req.method === "POST" ||
76
+ c.req.method === "PUT" ||
77
+ c.req.method === "PATCH"
78
+ ) {
79
+ try {
80
+ bodyText = await c.req.text();
81
+ } catch {
82
+ bodyText = undefined;
83
+ }
84
+ }
85
+
86
+ console.log(
87
+ `[sigma-auth] ${path} body=${bodyText ? `${bodyText.length}b` : "none"}`,
88
+ );
89
+
90
+ // Check token expiry (5 minute window)
91
+ const tokenTimestamp = new Date(parsed.timestamp);
92
+ const now = new Date();
93
+ const maxAgeMs = 5 * 60 * 1000; // 5 minutes
94
+ if (now.getTime() - tokenTimestamp.getTime() > maxAgeMs) {
95
+ console.log(
96
+ `[sigma-auth] ${requestPath} REJECT: expired (age=${Math.round((now.getTime() - tokenTimestamp.getTime()) / 1000)}s)`,
97
+ );
98
+ return c.json({ error: "Invalid or expired token" }, 401);
99
+ }
100
+
101
+ const authPayload = {
102
+ requestPath,
103
+ timestamp: parsed.timestamp,
104
+ body: bodyText,
105
+ };
106
+
107
+ // Verify token signature
108
+ console.log(`[sigma-auth] ${path} verifying signature...`);
109
+ const isValid = verifyAuthToken(token, authPayload);
110
+ if (!isValid) {
111
+ console.log(
112
+ `[sigma-auth] ${requestPath} REJECT: signature verification failed`,
113
+ );
114
+ return c.json({ error: "Invalid or expired token" }, 401);
115
+ }
116
+
117
+ // Verify identity matches owner
118
+ let ownerPubkey: string;
119
+ try {
120
+ ownerPubkey = getOwnerPubkey();
121
+ } catch {
122
+ return c.json(
123
+ {
124
+ error: "Server configuration error: Unable to verify identity",
125
+ },
126
+ 500,
127
+ );
128
+ }
129
+
130
+ if (parsed.pubkey !== ownerPubkey) {
131
+ console.log(
132
+ `[sigma-auth] ${requestPath} REJECT: pubkey mismatch (got=${parsed.pubkey.slice(0, 12)}... want=${ownerPubkey.slice(0, 12)}...)`,
133
+ );
134
+ return c.json({ error: "Forbidden: Identity mismatch" }, 403);
135
+ }
136
+
137
+ console.log(
138
+ `[sigma-auth] ${requestPath} OK (pubkey=${parsed.pubkey.slice(0, 12)}...)`,
139
+ );
140
+ await next();
141
+ } catch (err) {
142
+ console.error(
143
+ `[sigma-auth] ${path} CRASH: ${err instanceof Error ? err.message : String(err)}`,
144
+ );
145
+ return c.json({ error: "Authentication processing error" }, 500);
146
+ }
147
+ };
package/src/runs.ts ADDED
@@ -0,0 +1,49 @@
1
+ import type { TriggerType } from "./agent/prompts";
2
+ import type { AgentTurnResult } from "./agent/runner";
3
+
4
+ export interface RunState {
5
+ id: string;
6
+ status: "pending" | "running" | "completed" | "failed";
7
+ trigger: TriggerType;
8
+ result?: AgentTurnResult;
9
+ createdAt: string;
10
+ completedAt?: string;
11
+ }
12
+
13
+ const runs = new Map<string, RunState>();
14
+
15
+ const ONE_HOUR_MS = 60 * 60 * 1000;
16
+
17
+ function pruneOldRuns(): void {
18
+ const cutoff = Date.now() - ONE_HOUR_MS;
19
+ for (const [id, run] of runs) {
20
+ if (new Date(run.createdAt).getTime() < cutoff) {
21
+ runs.delete(id);
22
+ }
23
+ }
24
+ }
25
+
26
+ export function createRun(trigger: TriggerType): RunState {
27
+ pruneOldRuns();
28
+ const run: RunState = {
29
+ id: crypto.randomUUID(),
30
+ status: "pending",
31
+ trigger,
32
+ createdAt: new Date().toISOString(),
33
+ };
34
+ runs.set(run.id, run);
35
+ return run;
36
+ }
37
+
38
+ export function updateRun(id: string, update: Partial<RunState>): void {
39
+ const run = runs.get(id);
40
+ if (!run) return;
41
+ Object.assign(run, update);
42
+ if (update.status === "completed" || update.status === "failed") {
43
+ run.completedAt = new Date().toISOString();
44
+ }
45
+ }
46
+
47
+ export function getRun(id: string): RunState | undefined {
48
+ return runs.get(id);
49
+ }