@clawcash/forge 0.1.0
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/README.md +75 -0
- package/dist/index.cjs +283 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +71 -0
- package/dist/index.d.ts +71 -0
- package/dist/index.js +250 -0
- package/dist/index.js.map +1 -0
- package/package.json +50 -0
package/README.md
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# @clawcash/forge
|
|
2
|
+
|
|
3
|
+
Define agent-native services on top of an existing backend. Forge wraps a high-level action, handles polling helpers, mounts Express routes, and attaches x402 payment.
|
|
4
|
+
|
|
5
|
+
Published under the [ClawCash](https://www.npmjs.com/org/clawcash) npm org.
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install @clawcash/forge express
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
For local demo against the sibling folder:
|
|
14
|
+
|
|
15
|
+
```json
|
|
16
|
+
{
|
|
17
|
+
"dependencies": {
|
|
18
|
+
"@clawcash/forge": "file:../forge-sdk"
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Quick start
|
|
24
|
+
|
|
25
|
+
```ts
|
|
26
|
+
import express from "express";
|
|
27
|
+
import { defineAgentService, mountAgentServices } from "@clawcash/forge";
|
|
28
|
+
|
|
29
|
+
const removeBackground = defineAgentService({
|
|
30
|
+
name: "remove_background",
|
|
31
|
+
description: "Remove the background from an image.",
|
|
32
|
+
input: { imageUrl: "url" },
|
|
33
|
+
output: { resultUrl: "url" },
|
|
34
|
+
payment: {
|
|
35
|
+
amount: "0.10",
|
|
36
|
+
currency: "USDC",
|
|
37
|
+
protocols: ["x402"],
|
|
38
|
+
},
|
|
39
|
+
async execute({ imageUrl }, context) {
|
|
40
|
+
// Call your existing multi-step APIs here.
|
|
41
|
+
// Use context.waitUntil to poll jobs.
|
|
42
|
+
return { resultUrl: "https://example.com/out.png" };
|
|
43
|
+
},
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
const app = express();
|
|
47
|
+
app.use(express.json());
|
|
48
|
+
|
|
49
|
+
mountAgentServices(app, [removeBackground], {
|
|
50
|
+
payTo: process.env.PAY_TO!,
|
|
51
|
+
facilitatorUrl: process.env.FACILITATOR_URL,
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
app.listen(3000);
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
Agents call `POST /agent/remove-background` and pay via x402 when challenged with HTTP 402.
|
|
58
|
+
|
|
59
|
+
Default payment network is **Base Sepolia** (`eip155:84532`), which the public `https://x402.org/facilitator` supports. Override per service with `payment.network`.
|
|
60
|
+
|
|
61
|
+
## API
|
|
62
|
+
|
|
63
|
+
| Export | Purpose |
|
|
64
|
+
|--------|---------|
|
|
65
|
+
| `defineAgentService` | Declare name, schemas, payment, and `execute` |
|
|
66
|
+
| `mountAgentServices` | Mount `POST /agent/:name` with x402 |
|
|
67
|
+
| `generateSkillMarkdown` | Build a `SKILL.md` for agents |
|
|
68
|
+
| `waitUntil` / `context.waitUntil` | Poll until a job completes |
|
|
69
|
+
|
|
70
|
+
## Build
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
npm install
|
|
74
|
+
npm run build
|
|
75
|
+
```
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var index_exports = {};
|
|
22
|
+
__export(index_exports, {
|
|
23
|
+
createContext: () => createContext,
|
|
24
|
+
createForgeRouter: () => createForgeRouter,
|
|
25
|
+
defineAgentService: () => defineAgentService,
|
|
26
|
+
generateSkillMarkdown: () => generateSkillMarkdown,
|
|
27
|
+
mountAgentServices: () => mountAgentServices,
|
|
28
|
+
toKebabCase: () => toKebabCase,
|
|
29
|
+
waitUntil: () => waitUntil
|
|
30
|
+
});
|
|
31
|
+
module.exports = __toCommonJS(index_exports);
|
|
32
|
+
|
|
33
|
+
// src/define.ts
|
|
34
|
+
async function waitUntil(options) {
|
|
35
|
+
const intervalMs = options.intervalMs ?? 500;
|
|
36
|
+
const timeoutMs = options.timeoutMs ?? 6e4;
|
|
37
|
+
const started = Date.now();
|
|
38
|
+
for (; ; ) {
|
|
39
|
+
const value = await options.run();
|
|
40
|
+
if (options.completedWhen(value)) {
|
|
41
|
+
return value;
|
|
42
|
+
}
|
|
43
|
+
if (Date.now() - started >= timeoutMs) {
|
|
44
|
+
throw new Error(`waitUntil timed out after ${timeoutMs}ms`);
|
|
45
|
+
}
|
|
46
|
+
await new Promise((resolve) => setTimeout(resolve, intervalMs));
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
function createContext() {
|
|
50
|
+
return {
|
|
51
|
+
waitUntil
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
function toKebabCase(name) {
|
|
55
|
+
return name.replace(/([a-z0-9])([A-Z])/g, "$1-$2").replace(/_/g, "-").toLowerCase();
|
|
56
|
+
}
|
|
57
|
+
function defineAgentService(config) {
|
|
58
|
+
if (!config.name?.trim()) {
|
|
59
|
+
throw new Error("defineAgentService: name is required");
|
|
60
|
+
}
|
|
61
|
+
if (!config.execute) {
|
|
62
|
+
throw new Error("defineAgentService: execute is required");
|
|
63
|
+
}
|
|
64
|
+
return {
|
|
65
|
+
...config,
|
|
66
|
+
__forge: true,
|
|
67
|
+
path: toKebabCase(config.name)
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// src/mount.ts
|
|
72
|
+
var import_express = require("express");
|
|
73
|
+
var import_express2 = require("@x402/express");
|
|
74
|
+
var import_server = require("@x402/core/server");
|
|
75
|
+
var import_server2 = require("@x402/evm/exact/server");
|
|
76
|
+
var DEFAULT_NETWORK = "eip155:84532";
|
|
77
|
+
var DEFAULT_FACILITATOR = "https://x402.org/facilitator";
|
|
78
|
+
function asNetwork(value) {
|
|
79
|
+
return value ?? DEFAULT_NETWORK;
|
|
80
|
+
}
|
|
81
|
+
function validateInput(body, schema) {
|
|
82
|
+
if (!body || typeof body !== "object" || Array.isArray(body)) {
|
|
83
|
+
return { ok: false, error: "Request body must be a JSON object" };
|
|
84
|
+
}
|
|
85
|
+
const input = body;
|
|
86
|
+
const value = {};
|
|
87
|
+
for (const [key, type] of Object.entries(schema)) {
|
|
88
|
+
if (!(key in input) || input[key] === void 0 || input[key] === null) {
|
|
89
|
+
return { ok: false, error: `Missing required field: ${key}` };
|
|
90
|
+
}
|
|
91
|
+
const err = checkType(key, input[key], type);
|
|
92
|
+
if (err) return { ok: false, error: err };
|
|
93
|
+
value[key] = input[key];
|
|
94
|
+
}
|
|
95
|
+
return { ok: true, value };
|
|
96
|
+
}
|
|
97
|
+
function checkType(key, value, type) {
|
|
98
|
+
switch (type) {
|
|
99
|
+
case "string":
|
|
100
|
+
case "url":
|
|
101
|
+
if (typeof value !== "string" || !value.trim()) {
|
|
102
|
+
return `Field ${key} must be a non-empty string`;
|
|
103
|
+
}
|
|
104
|
+
if (type === "url") {
|
|
105
|
+
try {
|
|
106
|
+
new URL(value);
|
|
107
|
+
} catch {
|
|
108
|
+
return `Field ${key} must be a valid URL`;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
return null;
|
|
112
|
+
case "number":
|
|
113
|
+
if (typeof value !== "number" || Number.isNaN(value)) {
|
|
114
|
+
return `Field ${key} must be a number`;
|
|
115
|
+
}
|
|
116
|
+
return null;
|
|
117
|
+
case "boolean":
|
|
118
|
+
if (typeof value !== "boolean") {
|
|
119
|
+
return `Field ${key} must be a boolean`;
|
|
120
|
+
}
|
|
121
|
+
return null;
|
|
122
|
+
default:
|
|
123
|
+
return null;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
function formatPrice(amount) {
|
|
127
|
+
const normalized = amount.startsWith("$") ? amount.slice(1) : amount;
|
|
128
|
+
return `$${normalized}`;
|
|
129
|
+
}
|
|
130
|
+
function mountAgentServices(app, services, options) {
|
|
131
|
+
if (!options.payTo && !options.skipPayment) {
|
|
132
|
+
throw new Error("mountAgentServices: payTo is required unless skipPayment is true");
|
|
133
|
+
}
|
|
134
|
+
const basePath = (options.basePath ?? "/agent").replace(/\/$/, "") || "/agent";
|
|
135
|
+
const router = (0, import_express.Router)();
|
|
136
|
+
const context = createContext();
|
|
137
|
+
const routes = {};
|
|
138
|
+
for (const service of services) {
|
|
139
|
+
const routePath = `/${service.path}`;
|
|
140
|
+
const fullPath = `${basePath}${routePath}`;
|
|
141
|
+
const network = asNetwork(service.payment.network);
|
|
142
|
+
if (!options.skipPayment) {
|
|
143
|
+
routes[`POST ${fullPath}`] = {
|
|
144
|
+
accepts: {
|
|
145
|
+
scheme: "exact",
|
|
146
|
+
price: formatPrice(service.payment.amount),
|
|
147
|
+
network,
|
|
148
|
+
payTo: options.payTo
|
|
149
|
+
},
|
|
150
|
+
description: service.description,
|
|
151
|
+
mimeType: "application/json"
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
router.post(routePath, async (req, res) => {
|
|
155
|
+
try {
|
|
156
|
+
const validated = validateInput(req.body, service.input);
|
|
157
|
+
if (!validated.ok) {
|
|
158
|
+
res.status(400).json({ error: validated.error });
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
const output = await service.execute(validated.value, context);
|
|
162
|
+
res.json(output);
|
|
163
|
+
} catch (err) {
|
|
164
|
+
const message = err instanceof Error ? err.message : "Internal error";
|
|
165
|
+
console.error(`[forge] ${service.name} failed:`, err);
|
|
166
|
+
res.status(500).json({ error: message });
|
|
167
|
+
}
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
if (!options.skipPayment && Object.keys(routes).length > 0) {
|
|
171
|
+
const facilitator = new import_server.HTTPFacilitatorClient({
|
|
172
|
+
url: options.facilitatorUrl ?? DEFAULT_FACILITATOR
|
|
173
|
+
});
|
|
174
|
+
const networks = new Set(
|
|
175
|
+
services.map((s) => asNetwork(s.payment.network))
|
|
176
|
+
);
|
|
177
|
+
const resourceServer = new import_express2.x402ResourceServer(facilitator);
|
|
178
|
+
for (const network of networks) {
|
|
179
|
+
resourceServer.register(network, new import_server2.ExactEvmScheme());
|
|
180
|
+
}
|
|
181
|
+
app.use((0, import_express2.paymentMiddleware)(routes, resourceServer, void 0, void 0, true));
|
|
182
|
+
}
|
|
183
|
+
app.use(basePath, router);
|
|
184
|
+
return router;
|
|
185
|
+
}
|
|
186
|
+
function createForgeRouter(services, options) {
|
|
187
|
+
return (app) => mountAgentServices(app, services, options);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// src/skill.ts
|
|
191
|
+
function generateSkillMarkdown(services, meta) {
|
|
192
|
+
const base = meta.baseUrl.replace(/\/$/, "");
|
|
193
|
+
const lines = [
|
|
194
|
+
`---`,
|
|
195
|
+
`name: ${meta.name}`,
|
|
196
|
+
`description: ${meta.description}`,
|
|
197
|
+
`---`,
|
|
198
|
+
``,
|
|
199
|
+
`# ${meta.name}`,
|
|
200
|
+
``,
|
|
201
|
+
meta.description,
|
|
202
|
+
``
|
|
203
|
+
];
|
|
204
|
+
if (meta.homepage) {
|
|
205
|
+
lines.push(`Homepage: ${meta.homepage}`, ``);
|
|
206
|
+
}
|
|
207
|
+
lines.push(
|
|
208
|
+
`## How to use`,
|
|
209
|
+
``,
|
|
210
|
+
`1. Read this skill.`,
|
|
211
|
+
`2. Call the agent endpoint with a JSON body matching the input schema.`,
|
|
212
|
+
`3. If you receive HTTP 402, pay via x402 (USDC) and retry with the payment header.`,
|
|
213
|
+
`4. Use the returned output fields.`,
|
|
214
|
+
``,
|
|
215
|
+
`## Services`,
|
|
216
|
+
``
|
|
217
|
+
);
|
|
218
|
+
for (const service of services) {
|
|
219
|
+
const path = `${base}/agent/${service.path}`;
|
|
220
|
+
const price = service.payment.amount.startsWith("$") ? service.payment.amount : `$${service.payment.amount}`;
|
|
221
|
+
lines.push(
|
|
222
|
+
`### \`${service.name}\``,
|
|
223
|
+
``,
|
|
224
|
+
service.description,
|
|
225
|
+
``,
|
|
226
|
+
`- **Method:** \`POST\``,
|
|
227
|
+
`- **URL:** \`${path}\``,
|
|
228
|
+
`- **Price:** ${price} ${service.payment.currency} via ${service.payment.protocols.join(", ")}`,
|
|
229
|
+
``,
|
|
230
|
+
`**Input**`,
|
|
231
|
+
``,
|
|
232
|
+
"```json",
|
|
233
|
+
JSON.stringify(
|
|
234
|
+
Object.fromEntries(
|
|
235
|
+
Object.entries(service.input).map(([k, t]) => [k, `<${t}>`])
|
|
236
|
+
),
|
|
237
|
+
null,
|
|
238
|
+
2
|
|
239
|
+
),
|
|
240
|
+
"```",
|
|
241
|
+
``,
|
|
242
|
+
`**Output**`,
|
|
243
|
+
``,
|
|
244
|
+
"```json",
|
|
245
|
+
JSON.stringify(
|
|
246
|
+
Object.fromEntries(
|
|
247
|
+
Object.entries(service.output).map(([k, t]) => [k, `<${t}>`])
|
|
248
|
+
),
|
|
249
|
+
null,
|
|
250
|
+
2
|
|
251
|
+
),
|
|
252
|
+
"```",
|
|
253
|
+
``,
|
|
254
|
+
`**Example**`,
|
|
255
|
+
``,
|
|
256
|
+
"```bash",
|
|
257
|
+
`curl -X POST ${path} \\`,
|
|
258
|
+
` -H "Content-Type: application/json" \\`,
|
|
259
|
+
` -d '${JSON.stringify(
|
|
260
|
+
Object.fromEntries(
|
|
261
|
+
Object.entries(service.input).map(([k, t]) => [
|
|
262
|
+
k,
|
|
263
|
+
t === "url" ? "https://example.com/image.png" : `example-${k}`
|
|
264
|
+
])
|
|
265
|
+
)
|
|
266
|
+
)}'`,
|
|
267
|
+
"```",
|
|
268
|
+
``
|
|
269
|
+
);
|
|
270
|
+
}
|
|
271
|
+
return lines.join("\n");
|
|
272
|
+
}
|
|
273
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
274
|
+
0 && (module.exports = {
|
|
275
|
+
createContext,
|
|
276
|
+
createForgeRouter,
|
|
277
|
+
defineAgentService,
|
|
278
|
+
generateSkillMarkdown,
|
|
279
|
+
mountAgentServices,
|
|
280
|
+
toKebabCase,
|
|
281
|
+
waitUntil
|
|
282
|
+
});
|
|
283
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/define.ts","../src/mount.ts","../src/skill.ts"],"sourcesContent":["export { defineAgentService, waitUntil, createContext, toKebabCase } from \"./define.js\";\nexport { mountAgentServices, createForgeRouter } from \"./mount.js\";\nexport { generateSkillMarkdown } from \"./skill.js\";\nexport type {\n SchemaFieldType,\n InputSchema,\n OutputSchema,\n PaymentConfig,\n WaitUntilOptions,\n AgentServiceContext,\n AgentServiceConfig,\n DefinedAgentService,\n MountOptions,\n SkillMeta,\n} from \"./types.js\";\n","import type {\n AgentServiceConfig,\n AgentServiceContext,\n DefinedAgentService,\n WaitUntilOptions,\n} from \"./types.js\";\n\nexport async function waitUntil<T>(options: WaitUntilOptions<T>): Promise<T> {\n const intervalMs = options.intervalMs ?? 500;\n const timeoutMs = options.timeoutMs ?? 60_000;\n const started = Date.now();\n\n for (;;) {\n const value = await options.run();\n if (options.completedWhen(value)) {\n return value;\n }\n if (Date.now() - started >= timeoutMs) {\n throw new Error(`waitUntil timed out after ${timeoutMs}ms`);\n }\n await new Promise((resolve) => setTimeout(resolve, intervalMs));\n }\n}\n\nexport function createContext(): AgentServiceContext {\n return {\n waitUntil,\n };\n}\n\n/** Convert snake_case or camelCase service name to kebab-case path segment. */\nexport function toKebabCase(name: string): string {\n return name\n .replace(/([a-z0-9])([A-Z])/g, \"$1-$2\")\n .replace(/_/g, \"-\")\n .toLowerCase();\n}\n\nexport function defineAgentService<\n TInput extends Record<string, unknown> = Record<string, unknown>,\n TOutput extends Record<string, unknown> = Record<string, unknown>,\n>(\n config: AgentServiceConfig<TInput, TOutput>,\n): DefinedAgentService<TInput, TOutput> {\n if (!config.name?.trim()) {\n throw new Error(\"defineAgentService: name is required\");\n }\n if (!config.execute) {\n throw new Error(\"defineAgentService: execute is required\");\n }\n\n return {\n ...config,\n __forge: true,\n path: toKebabCase(config.name),\n };\n}\n","import type { Application, Request, Response, NextFunction, Router } from \"express\";\nimport { Router as createRouter } from \"express\";\nimport { paymentMiddleware, x402ResourceServer } from \"@x402/express\";\nimport type { Network } from \"@x402/express\";\nimport { HTTPFacilitatorClient } from \"@x402/core/server\";\nimport type { RoutesConfig } from \"@x402/core/server\";\nimport { ExactEvmScheme } from \"@x402/evm/exact/server\";\nimport { createContext } from \"./define.js\";\nimport type {\n DefinedAgentService,\n InputSchema,\n MountOptions,\n SchemaFieldType,\n} from \"./types.js\";\n\n/** Base Sepolia — supported by the public x402.org facilitator for demos. */\nconst DEFAULT_NETWORK = \"eip155:84532\" as const satisfies Network;\nconst DEFAULT_FACILITATOR = \"https://x402.org/facilitator\";\n\nfunction asNetwork(value: string | undefined): Network {\n return (value ?? DEFAULT_NETWORK) as Network;\n}\n\nfunction validateInput(\n body: unknown,\n schema: InputSchema,\n): { ok: true; value: Record<string, unknown> } | { ok: false; error: string } {\n if (!body || typeof body !== \"object\" || Array.isArray(body)) {\n return { ok: false, error: \"Request body must be a JSON object\" };\n }\n\n const input = body as Record<string, unknown>;\n const value: Record<string, unknown> = {};\n\n for (const [key, type] of Object.entries(schema)) {\n if (!(key in input) || input[key] === undefined || input[key] === null) {\n return { ok: false, error: `Missing required field: ${key}` };\n }\n const err = checkType(key, input[key], type);\n if (err) return { ok: false, error: err };\n value[key] = input[key];\n }\n\n return { ok: true, value };\n}\n\nfunction checkType(key: string, value: unknown, type: SchemaFieldType): string | null {\n switch (type) {\n case \"string\":\n case \"url\":\n if (typeof value !== \"string\" || !value.trim()) {\n return `Field ${key} must be a non-empty string`;\n }\n if (type === \"url\") {\n try {\n new URL(value);\n } catch {\n return `Field ${key} must be a valid URL`;\n }\n }\n return null;\n case \"number\":\n if (typeof value !== \"number\" || Number.isNaN(value)) {\n return `Field ${key} must be a number`;\n }\n return null;\n case \"boolean\":\n if (typeof value !== \"boolean\") {\n return `Field ${key} must be a boolean`;\n }\n return null;\n default:\n return null;\n }\n}\n\nfunction formatPrice(amount: string): string {\n const normalized = amount.startsWith(\"$\") ? amount.slice(1) : amount;\n return `$${normalized}`;\n}\n\n/**\n * Mount Forge agent services on an Express app.\n * Each service is exposed as POST {basePath}/{kebab-name} with optional x402.\n */\nexport function mountAgentServices(\n app: Application,\n services: DefinedAgentService[],\n options: MountOptions,\n): Router {\n if (!options.payTo && !options.skipPayment) {\n throw new Error(\"mountAgentServices: payTo is required unless skipPayment is true\");\n }\n\n const basePath = (options.basePath ?? \"/agent\").replace(/\\/$/, \"\") || \"/agent\";\n const router = createRouter();\n const context = createContext();\n\n const routes: RoutesConfig = {};\n\n for (const service of services) {\n const routePath = `/${service.path}`;\n const fullPath = `${basePath}${routePath}`;\n const network = asNetwork(service.payment.network);\n\n if (!options.skipPayment) {\n routes[`POST ${fullPath}`] = {\n accepts: {\n scheme: \"exact\",\n price: formatPrice(service.payment.amount),\n network,\n payTo: options.payTo,\n },\n description: service.description,\n mimeType: \"application/json\",\n };\n }\n\n router.post(routePath, async (req: Request, res: Response) => {\n try {\n const validated = validateInput(req.body, service.input);\n if (!validated.ok) {\n res.status(400).json({ error: validated.error });\n return;\n }\n\n const output = await service.execute(validated.value, context);\n res.json(output);\n } catch (err) {\n const message = err instanceof Error ? err.message : \"Internal error\";\n console.error(`[forge] ${service.name} failed:`, err);\n res.status(500).json({ error: message });\n }\n });\n }\n\n if (!options.skipPayment && Object.keys(routes).length > 0) {\n const facilitator = new HTTPFacilitatorClient({\n url: options.facilitatorUrl ?? DEFAULT_FACILITATOR,\n });\n const networks = new Set(\n services.map((s) => asNetwork(s.payment.network)),\n );\n const resourceServer = new x402ResourceServer(facilitator);\n for (const network of networks) {\n resourceServer.register(network, new ExactEvmScheme());\n }\n // syncFacilitatorOnStart validates scheme/network against the facilitator\n app.use(paymentMiddleware(routes, resourceServer, undefined, undefined, true));\n }\n\n app.use(basePath, router);\n return router;\n}\n\n/** Express-compatible middleware alias used in docs. */\nexport function createForgeRouter(\n services: DefinedAgentService[],\n options: MountOptions,\n): (app: Application) => Router {\n return (app) => mountAgentServices(app, services, options);\n}\n\n/** Optional no-op next for typing convenience in tests. */\nexport function forgeErrorHandler(\n err: unknown,\n _req: Request,\n res: Response,\n _next: NextFunction,\n): void {\n const message = err instanceof Error ? err.message : \"Internal error\";\n res.status(500).json({ error: message });\n}\n","import type { DefinedAgentService, SkillMeta } from \"./types.js\";\n\n/**\n * Generate a SKILL.md document for agents from defined Forge services.\n */\nexport function generateSkillMarkdown(\n services: DefinedAgentService[],\n meta: SkillMeta,\n): string {\n const base = meta.baseUrl.replace(/\\/$/, \"\");\n const lines: string[] = [\n `---`,\n `name: ${meta.name}`,\n `description: ${meta.description}`,\n `---`,\n ``,\n `# ${meta.name}`,\n ``,\n meta.description,\n ``,\n ];\n\n if (meta.homepage) {\n lines.push(`Homepage: ${meta.homepage}`, ``);\n }\n\n lines.push(\n `## How to use`,\n ``,\n `1. Read this skill.`,\n `2. Call the agent endpoint with a JSON body matching the input schema.`,\n `3. If you receive HTTP 402, pay via x402 (USDC) and retry with the payment header.`,\n `4. Use the returned output fields.`,\n ``,\n `## Services`,\n ``,\n );\n\n for (const service of services) {\n const path = `${base}/agent/${service.path}`;\n const price = service.payment.amount.startsWith(\"$\")\n ? service.payment.amount\n : `$${service.payment.amount}`;\n\n lines.push(\n `### \\`${service.name}\\``,\n ``,\n service.description,\n ``,\n `- **Method:** \\`POST\\``,\n `- **URL:** \\`${path}\\``,\n `- **Price:** ${price} ${service.payment.currency} via ${service.payment.protocols.join(\", \")}`,\n ``,\n `**Input**`,\n ``,\n \"```json\",\n JSON.stringify(\n Object.fromEntries(\n Object.entries(service.input).map(([k, t]) => [k, `<${t}>`]),\n ),\n null,\n 2,\n ),\n \"```\",\n ``,\n `**Output**`,\n ``,\n \"```json\",\n JSON.stringify(\n Object.fromEntries(\n Object.entries(service.output).map(([k, t]) => [k, `<${t}>`]),\n ),\n null,\n 2,\n ),\n \"```\",\n ``,\n `**Example**`,\n ``,\n \"```bash\",\n `curl -X POST ${path} \\\\`,\n ` -H \"Content-Type: application/json\" \\\\`,\n ` -d '${JSON.stringify(\n Object.fromEntries(\n Object.entries(service.input).map(([k, t]) => [\n k,\n t === \"url\" ? \"https://example.com/image.png\" : `example-${k}`,\n ]),\n ),\n )}'`,\n \"```\",\n ``,\n );\n }\n\n return lines.join(\"\\n\");\n}\n"],"mappings":"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","names":["import_express","import_server","createRouter"]}
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { Application, Router } from 'express';
|
|
2
|
+
|
|
3
|
+
type SchemaFieldType = "string" | "url" | "number" | "boolean";
|
|
4
|
+
type InputSchema = Record<string, SchemaFieldType>;
|
|
5
|
+
type OutputSchema = Record<string, SchemaFieldType>;
|
|
6
|
+
interface PaymentConfig {
|
|
7
|
+
/** Dollar amount as a string, e.g. "0.10" */
|
|
8
|
+
amount: string;
|
|
9
|
+
currency: "USDC";
|
|
10
|
+
protocols: Array<"x402" | "mpp">;
|
|
11
|
+
/** CAIP-2 network. Defaults to Base Sepolia (`eip155:84532`). */
|
|
12
|
+
network?: string;
|
|
13
|
+
}
|
|
14
|
+
interface WaitUntilOptions<T> {
|
|
15
|
+
run: () => Promise<T>;
|
|
16
|
+
completedWhen: (value: T) => boolean;
|
|
17
|
+
intervalMs?: number;
|
|
18
|
+
timeoutMs?: number;
|
|
19
|
+
}
|
|
20
|
+
interface AgentServiceContext {
|
|
21
|
+
waitUntil: <T>(options: WaitUntilOptions<T>) => Promise<T>;
|
|
22
|
+
}
|
|
23
|
+
interface AgentServiceConfig<TInput extends Record<string, unknown> = Record<string, unknown>, TOutput extends Record<string, unknown> = Record<string, unknown>> {
|
|
24
|
+
name: string;
|
|
25
|
+
description: string;
|
|
26
|
+
input: InputSchema;
|
|
27
|
+
output: OutputSchema;
|
|
28
|
+
payment: PaymentConfig;
|
|
29
|
+
execute: (input: TInput, context: AgentServiceContext) => Promise<TOutput>;
|
|
30
|
+
}
|
|
31
|
+
interface DefinedAgentService<TInput extends Record<string, unknown> = Record<string, unknown>, TOutput extends Record<string, unknown> = Record<string, unknown>> extends AgentServiceConfig<TInput, TOutput> {
|
|
32
|
+
readonly __forge: true;
|
|
33
|
+
path: string;
|
|
34
|
+
}
|
|
35
|
+
interface MountOptions {
|
|
36
|
+
/** Recipient wallet address for x402 payments */
|
|
37
|
+
payTo: string;
|
|
38
|
+
/** Facilitator URL. Defaults to https://x402.org/facilitator */
|
|
39
|
+
facilitatorUrl?: string;
|
|
40
|
+
/** Path prefix for agent routes. Defaults to /agent */
|
|
41
|
+
basePath?: string;
|
|
42
|
+
/** Skip x402 (local dev only). Defaults to false. */
|
|
43
|
+
skipPayment?: boolean;
|
|
44
|
+
}
|
|
45
|
+
interface SkillMeta {
|
|
46
|
+
name: string;
|
|
47
|
+
description: string;
|
|
48
|
+
baseUrl: string;
|
|
49
|
+
homepage?: string;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
declare function waitUntil<T>(options: WaitUntilOptions<T>): Promise<T>;
|
|
53
|
+
declare function createContext(): AgentServiceContext;
|
|
54
|
+
/** Convert snake_case or camelCase service name to kebab-case path segment. */
|
|
55
|
+
declare function toKebabCase(name: string): string;
|
|
56
|
+
declare function defineAgentService<TInput extends Record<string, unknown> = Record<string, unknown>, TOutput extends Record<string, unknown> = Record<string, unknown>>(config: AgentServiceConfig<TInput, TOutput>): DefinedAgentService<TInput, TOutput>;
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Mount Forge agent services on an Express app.
|
|
60
|
+
* Each service is exposed as POST {basePath}/{kebab-name} with optional x402.
|
|
61
|
+
*/
|
|
62
|
+
declare function mountAgentServices(app: Application, services: DefinedAgentService[], options: MountOptions): Router;
|
|
63
|
+
/** Express-compatible middleware alias used in docs. */
|
|
64
|
+
declare function createForgeRouter(services: DefinedAgentService[], options: MountOptions): (app: Application) => Router;
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Generate a SKILL.md document for agents from defined Forge services.
|
|
68
|
+
*/
|
|
69
|
+
declare function generateSkillMarkdown(services: DefinedAgentService[], meta: SkillMeta): string;
|
|
70
|
+
|
|
71
|
+
export { type AgentServiceConfig, type AgentServiceContext, type DefinedAgentService, type InputSchema, type MountOptions, type OutputSchema, type PaymentConfig, type SchemaFieldType, type SkillMeta, type WaitUntilOptions, createContext, createForgeRouter, defineAgentService, generateSkillMarkdown, mountAgentServices, toKebabCase, waitUntil };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { Application, Router } from 'express';
|
|
2
|
+
|
|
3
|
+
type SchemaFieldType = "string" | "url" | "number" | "boolean";
|
|
4
|
+
type InputSchema = Record<string, SchemaFieldType>;
|
|
5
|
+
type OutputSchema = Record<string, SchemaFieldType>;
|
|
6
|
+
interface PaymentConfig {
|
|
7
|
+
/** Dollar amount as a string, e.g. "0.10" */
|
|
8
|
+
amount: string;
|
|
9
|
+
currency: "USDC";
|
|
10
|
+
protocols: Array<"x402" | "mpp">;
|
|
11
|
+
/** CAIP-2 network. Defaults to Base Sepolia (`eip155:84532`). */
|
|
12
|
+
network?: string;
|
|
13
|
+
}
|
|
14
|
+
interface WaitUntilOptions<T> {
|
|
15
|
+
run: () => Promise<T>;
|
|
16
|
+
completedWhen: (value: T) => boolean;
|
|
17
|
+
intervalMs?: number;
|
|
18
|
+
timeoutMs?: number;
|
|
19
|
+
}
|
|
20
|
+
interface AgentServiceContext {
|
|
21
|
+
waitUntil: <T>(options: WaitUntilOptions<T>) => Promise<T>;
|
|
22
|
+
}
|
|
23
|
+
interface AgentServiceConfig<TInput extends Record<string, unknown> = Record<string, unknown>, TOutput extends Record<string, unknown> = Record<string, unknown>> {
|
|
24
|
+
name: string;
|
|
25
|
+
description: string;
|
|
26
|
+
input: InputSchema;
|
|
27
|
+
output: OutputSchema;
|
|
28
|
+
payment: PaymentConfig;
|
|
29
|
+
execute: (input: TInput, context: AgentServiceContext) => Promise<TOutput>;
|
|
30
|
+
}
|
|
31
|
+
interface DefinedAgentService<TInput extends Record<string, unknown> = Record<string, unknown>, TOutput extends Record<string, unknown> = Record<string, unknown>> extends AgentServiceConfig<TInput, TOutput> {
|
|
32
|
+
readonly __forge: true;
|
|
33
|
+
path: string;
|
|
34
|
+
}
|
|
35
|
+
interface MountOptions {
|
|
36
|
+
/** Recipient wallet address for x402 payments */
|
|
37
|
+
payTo: string;
|
|
38
|
+
/** Facilitator URL. Defaults to https://x402.org/facilitator */
|
|
39
|
+
facilitatorUrl?: string;
|
|
40
|
+
/** Path prefix for agent routes. Defaults to /agent */
|
|
41
|
+
basePath?: string;
|
|
42
|
+
/** Skip x402 (local dev only). Defaults to false. */
|
|
43
|
+
skipPayment?: boolean;
|
|
44
|
+
}
|
|
45
|
+
interface SkillMeta {
|
|
46
|
+
name: string;
|
|
47
|
+
description: string;
|
|
48
|
+
baseUrl: string;
|
|
49
|
+
homepage?: string;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
declare function waitUntil<T>(options: WaitUntilOptions<T>): Promise<T>;
|
|
53
|
+
declare function createContext(): AgentServiceContext;
|
|
54
|
+
/** Convert snake_case or camelCase service name to kebab-case path segment. */
|
|
55
|
+
declare function toKebabCase(name: string): string;
|
|
56
|
+
declare function defineAgentService<TInput extends Record<string, unknown> = Record<string, unknown>, TOutput extends Record<string, unknown> = Record<string, unknown>>(config: AgentServiceConfig<TInput, TOutput>): DefinedAgentService<TInput, TOutput>;
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Mount Forge agent services on an Express app.
|
|
60
|
+
* Each service is exposed as POST {basePath}/{kebab-name} with optional x402.
|
|
61
|
+
*/
|
|
62
|
+
declare function mountAgentServices(app: Application, services: DefinedAgentService[], options: MountOptions): Router;
|
|
63
|
+
/** Express-compatible middleware alias used in docs. */
|
|
64
|
+
declare function createForgeRouter(services: DefinedAgentService[], options: MountOptions): (app: Application) => Router;
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Generate a SKILL.md document for agents from defined Forge services.
|
|
68
|
+
*/
|
|
69
|
+
declare function generateSkillMarkdown(services: DefinedAgentService[], meta: SkillMeta): string;
|
|
70
|
+
|
|
71
|
+
export { type AgentServiceConfig, type AgentServiceContext, type DefinedAgentService, type InputSchema, type MountOptions, type OutputSchema, type PaymentConfig, type SchemaFieldType, type SkillMeta, type WaitUntilOptions, createContext, createForgeRouter, defineAgentService, generateSkillMarkdown, mountAgentServices, toKebabCase, waitUntil };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
// src/define.ts
|
|
2
|
+
async function waitUntil(options) {
|
|
3
|
+
const intervalMs = options.intervalMs ?? 500;
|
|
4
|
+
const timeoutMs = options.timeoutMs ?? 6e4;
|
|
5
|
+
const started = Date.now();
|
|
6
|
+
for (; ; ) {
|
|
7
|
+
const value = await options.run();
|
|
8
|
+
if (options.completedWhen(value)) {
|
|
9
|
+
return value;
|
|
10
|
+
}
|
|
11
|
+
if (Date.now() - started >= timeoutMs) {
|
|
12
|
+
throw new Error(`waitUntil timed out after ${timeoutMs}ms`);
|
|
13
|
+
}
|
|
14
|
+
await new Promise((resolve) => setTimeout(resolve, intervalMs));
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
function createContext() {
|
|
18
|
+
return {
|
|
19
|
+
waitUntil
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
function toKebabCase(name) {
|
|
23
|
+
return name.replace(/([a-z0-9])([A-Z])/g, "$1-$2").replace(/_/g, "-").toLowerCase();
|
|
24
|
+
}
|
|
25
|
+
function defineAgentService(config) {
|
|
26
|
+
if (!config.name?.trim()) {
|
|
27
|
+
throw new Error("defineAgentService: name is required");
|
|
28
|
+
}
|
|
29
|
+
if (!config.execute) {
|
|
30
|
+
throw new Error("defineAgentService: execute is required");
|
|
31
|
+
}
|
|
32
|
+
return {
|
|
33
|
+
...config,
|
|
34
|
+
__forge: true,
|
|
35
|
+
path: toKebabCase(config.name)
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// src/mount.ts
|
|
40
|
+
import { Router as createRouter } from "express";
|
|
41
|
+
import { paymentMiddleware, x402ResourceServer } from "@x402/express";
|
|
42
|
+
import { HTTPFacilitatorClient } from "@x402/core/server";
|
|
43
|
+
import { ExactEvmScheme } from "@x402/evm/exact/server";
|
|
44
|
+
var DEFAULT_NETWORK = "eip155:84532";
|
|
45
|
+
var DEFAULT_FACILITATOR = "https://x402.org/facilitator";
|
|
46
|
+
function asNetwork(value) {
|
|
47
|
+
return value ?? DEFAULT_NETWORK;
|
|
48
|
+
}
|
|
49
|
+
function validateInput(body, schema) {
|
|
50
|
+
if (!body || typeof body !== "object" || Array.isArray(body)) {
|
|
51
|
+
return { ok: false, error: "Request body must be a JSON object" };
|
|
52
|
+
}
|
|
53
|
+
const input = body;
|
|
54
|
+
const value = {};
|
|
55
|
+
for (const [key, type] of Object.entries(schema)) {
|
|
56
|
+
if (!(key in input) || input[key] === void 0 || input[key] === null) {
|
|
57
|
+
return { ok: false, error: `Missing required field: ${key}` };
|
|
58
|
+
}
|
|
59
|
+
const err = checkType(key, input[key], type);
|
|
60
|
+
if (err) return { ok: false, error: err };
|
|
61
|
+
value[key] = input[key];
|
|
62
|
+
}
|
|
63
|
+
return { ok: true, value };
|
|
64
|
+
}
|
|
65
|
+
function checkType(key, value, type) {
|
|
66
|
+
switch (type) {
|
|
67
|
+
case "string":
|
|
68
|
+
case "url":
|
|
69
|
+
if (typeof value !== "string" || !value.trim()) {
|
|
70
|
+
return `Field ${key} must be a non-empty string`;
|
|
71
|
+
}
|
|
72
|
+
if (type === "url") {
|
|
73
|
+
try {
|
|
74
|
+
new URL(value);
|
|
75
|
+
} catch {
|
|
76
|
+
return `Field ${key} must be a valid URL`;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
return null;
|
|
80
|
+
case "number":
|
|
81
|
+
if (typeof value !== "number" || Number.isNaN(value)) {
|
|
82
|
+
return `Field ${key} must be a number`;
|
|
83
|
+
}
|
|
84
|
+
return null;
|
|
85
|
+
case "boolean":
|
|
86
|
+
if (typeof value !== "boolean") {
|
|
87
|
+
return `Field ${key} must be a boolean`;
|
|
88
|
+
}
|
|
89
|
+
return null;
|
|
90
|
+
default:
|
|
91
|
+
return null;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
function formatPrice(amount) {
|
|
95
|
+
const normalized = amount.startsWith("$") ? amount.slice(1) : amount;
|
|
96
|
+
return `$${normalized}`;
|
|
97
|
+
}
|
|
98
|
+
function mountAgentServices(app, services, options) {
|
|
99
|
+
if (!options.payTo && !options.skipPayment) {
|
|
100
|
+
throw new Error("mountAgentServices: payTo is required unless skipPayment is true");
|
|
101
|
+
}
|
|
102
|
+
const basePath = (options.basePath ?? "/agent").replace(/\/$/, "") || "/agent";
|
|
103
|
+
const router = createRouter();
|
|
104
|
+
const context = createContext();
|
|
105
|
+
const routes = {};
|
|
106
|
+
for (const service of services) {
|
|
107
|
+
const routePath = `/${service.path}`;
|
|
108
|
+
const fullPath = `${basePath}${routePath}`;
|
|
109
|
+
const network = asNetwork(service.payment.network);
|
|
110
|
+
if (!options.skipPayment) {
|
|
111
|
+
routes[`POST ${fullPath}`] = {
|
|
112
|
+
accepts: {
|
|
113
|
+
scheme: "exact",
|
|
114
|
+
price: formatPrice(service.payment.amount),
|
|
115
|
+
network,
|
|
116
|
+
payTo: options.payTo
|
|
117
|
+
},
|
|
118
|
+
description: service.description,
|
|
119
|
+
mimeType: "application/json"
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
router.post(routePath, async (req, res) => {
|
|
123
|
+
try {
|
|
124
|
+
const validated = validateInput(req.body, service.input);
|
|
125
|
+
if (!validated.ok) {
|
|
126
|
+
res.status(400).json({ error: validated.error });
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
const output = await service.execute(validated.value, context);
|
|
130
|
+
res.json(output);
|
|
131
|
+
} catch (err) {
|
|
132
|
+
const message = err instanceof Error ? err.message : "Internal error";
|
|
133
|
+
console.error(`[forge] ${service.name} failed:`, err);
|
|
134
|
+
res.status(500).json({ error: message });
|
|
135
|
+
}
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
if (!options.skipPayment && Object.keys(routes).length > 0) {
|
|
139
|
+
const facilitator = new HTTPFacilitatorClient({
|
|
140
|
+
url: options.facilitatorUrl ?? DEFAULT_FACILITATOR
|
|
141
|
+
});
|
|
142
|
+
const networks = new Set(
|
|
143
|
+
services.map((s) => asNetwork(s.payment.network))
|
|
144
|
+
);
|
|
145
|
+
const resourceServer = new x402ResourceServer(facilitator);
|
|
146
|
+
for (const network of networks) {
|
|
147
|
+
resourceServer.register(network, new ExactEvmScheme());
|
|
148
|
+
}
|
|
149
|
+
app.use(paymentMiddleware(routes, resourceServer, void 0, void 0, true));
|
|
150
|
+
}
|
|
151
|
+
app.use(basePath, router);
|
|
152
|
+
return router;
|
|
153
|
+
}
|
|
154
|
+
function createForgeRouter(services, options) {
|
|
155
|
+
return (app) => mountAgentServices(app, services, options);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// src/skill.ts
|
|
159
|
+
function generateSkillMarkdown(services, meta) {
|
|
160
|
+
const base = meta.baseUrl.replace(/\/$/, "");
|
|
161
|
+
const lines = [
|
|
162
|
+
`---`,
|
|
163
|
+
`name: ${meta.name}`,
|
|
164
|
+
`description: ${meta.description}`,
|
|
165
|
+
`---`,
|
|
166
|
+
``,
|
|
167
|
+
`# ${meta.name}`,
|
|
168
|
+
``,
|
|
169
|
+
meta.description,
|
|
170
|
+
``
|
|
171
|
+
];
|
|
172
|
+
if (meta.homepage) {
|
|
173
|
+
lines.push(`Homepage: ${meta.homepage}`, ``);
|
|
174
|
+
}
|
|
175
|
+
lines.push(
|
|
176
|
+
`## How to use`,
|
|
177
|
+
``,
|
|
178
|
+
`1. Read this skill.`,
|
|
179
|
+
`2. Call the agent endpoint with a JSON body matching the input schema.`,
|
|
180
|
+
`3. If you receive HTTP 402, pay via x402 (USDC) and retry with the payment header.`,
|
|
181
|
+
`4. Use the returned output fields.`,
|
|
182
|
+
``,
|
|
183
|
+
`## Services`,
|
|
184
|
+
``
|
|
185
|
+
);
|
|
186
|
+
for (const service of services) {
|
|
187
|
+
const path = `${base}/agent/${service.path}`;
|
|
188
|
+
const price = service.payment.amount.startsWith("$") ? service.payment.amount : `$${service.payment.amount}`;
|
|
189
|
+
lines.push(
|
|
190
|
+
`### \`${service.name}\``,
|
|
191
|
+
``,
|
|
192
|
+
service.description,
|
|
193
|
+
``,
|
|
194
|
+
`- **Method:** \`POST\``,
|
|
195
|
+
`- **URL:** \`${path}\``,
|
|
196
|
+
`- **Price:** ${price} ${service.payment.currency} via ${service.payment.protocols.join(", ")}`,
|
|
197
|
+
``,
|
|
198
|
+
`**Input**`,
|
|
199
|
+
``,
|
|
200
|
+
"```json",
|
|
201
|
+
JSON.stringify(
|
|
202
|
+
Object.fromEntries(
|
|
203
|
+
Object.entries(service.input).map(([k, t]) => [k, `<${t}>`])
|
|
204
|
+
),
|
|
205
|
+
null,
|
|
206
|
+
2
|
|
207
|
+
),
|
|
208
|
+
"```",
|
|
209
|
+
``,
|
|
210
|
+
`**Output**`,
|
|
211
|
+
``,
|
|
212
|
+
"```json",
|
|
213
|
+
JSON.stringify(
|
|
214
|
+
Object.fromEntries(
|
|
215
|
+
Object.entries(service.output).map(([k, t]) => [k, `<${t}>`])
|
|
216
|
+
),
|
|
217
|
+
null,
|
|
218
|
+
2
|
|
219
|
+
),
|
|
220
|
+
"```",
|
|
221
|
+
``,
|
|
222
|
+
`**Example**`,
|
|
223
|
+
``,
|
|
224
|
+
"```bash",
|
|
225
|
+
`curl -X POST ${path} \\`,
|
|
226
|
+
` -H "Content-Type: application/json" \\`,
|
|
227
|
+
` -d '${JSON.stringify(
|
|
228
|
+
Object.fromEntries(
|
|
229
|
+
Object.entries(service.input).map(([k, t]) => [
|
|
230
|
+
k,
|
|
231
|
+
t === "url" ? "https://example.com/image.png" : `example-${k}`
|
|
232
|
+
])
|
|
233
|
+
)
|
|
234
|
+
)}'`,
|
|
235
|
+
"```",
|
|
236
|
+
``
|
|
237
|
+
);
|
|
238
|
+
}
|
|
239
|
+
return lines.join("\n");
|
|
240
|
+
}
|
|
241
|
+
export {
|
|
242
|
+
createContext,
|
|
243
|
+
createForgeRouter,
|
|
244
|
+
defineAgentService,
|
|
245
|
+
generateSkillMarkdown,
|
|
246
|
+
mountAgentServices,
|
|
247
|
+
toKebabCase,
|
|
248
|
+
waitUntil
|
|
249
|
+
};
|
|
250
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/define.ts","../src/mount.ts","../src/skill.ts"],"sourcesContent":["import type {\n AgentServiceConfig,\n AgentServiceContext,\n DefinedAgentService,\n WaitUntilOptions,\n} from \"./types.js\";\n\nexport async function waitUntil<T>(options: WaitUntilOptions<T>): Promise<T> {\n const intervalMs = options.intervalMs ?? 500;\n const timeoutMs = options.timeoutMs ?? 60_000;\n const started = Date.now();\n\n for (;;) {\n const value = await options.run();\n if (options.completedWhen(value)) {\n return value;\n }\n if (Date.now() - started >= timeoutMs) {\n throw new Error(`waitUntil timed out after ${timeoutMs}ms`);\n }\n await new Promise((resolve) => setTimeout(resolve, intervalMs));\n }\n}\n\nexport function createContext(): AgentServiceContext {\n return {\n waitUntil,\n };\n}\n\n/** Convert snake_case or camelCase service name to kebab-case path segment. */\nexport function toKebabCase(name: string): string {\n return name\n .replace(/([a-z0-9])([A-Z])/g, \"$1-$2\")\n .replace(/_/g, \"-\")\n .toLowerCase();\n}\n\nexport function defineAgentService<\n TInput extends Record<string, unknown> = Record<string, unknown>,\n TOutput extends Record<string, unknown> = Record<string, unknown>,\n>(\n config: AgentServiceConfig<TInput, TOutput>,\n): DefinedAgentService<TInput, TOutput> {\n if (!config.name?.trim()) {\n throw new Error(\"defineAgentService: name is required\");\n }\n if (!config.execute) {\n throw new Error(\"defineAgentService: execute is required\");\n }\n\n return {\n ...config,\n __forge: true,\n path: toKebabCase(config.name),\n };\n}\n","import type { Application, Request, Response, NextFunction, Router } from \"express\";\nimport { Router as createRouter } from \"express\";\nimport { paymentMiddleware, x402ResourceServer } from \"@x402/express\";\nimport type { Network } from \"@x402/express\";\nimport { HTTPFacilitatorClient } from \"@x402/core/server\";\nimport type { RoutesConfig } from \"@x402/core/server\";\nimport { ExactEvmScheme } from \"@x402/evm/exact/server\";\nimport { createContext } from \"./define.js\";\nimport type {\n DefinedAgentService,\n InputSchema,\n MountOptions,\n SchemaFieldType,\n} from \"./types.js\";\n\n/** Base Sepolia — supported by the public x402.org facilitator for demos. */\nconst DEFAULT_NETWORK = \"eip155:84532\" as const satisfies Network;\nconst DEFAULT_FACILITATOR = \"https://x402.org/facilitator\";\n\nfunction asNetwork(value: string | undefined): Network {\n return (value ?? DEFAULT_NETWORK) as Network;\n}\n\nfunction validateInput(\n body: unknown,\n schema: InputSchema,\n): { ok: true; value: Record<string, unknown> } | { ok: false; error: string } {\n if (!body || typeof body !== \"object\" || Array.isArray(body)) {\n return { ok: false, error: \"Request body must be a JSON object\" };\n }\n\n const input = body as Record<string, unknown>;\n const value: Record<string, unknown> = {};\n\n for (const [key, type] of Object.entries(schema)) {\n if (!(key in input) || input[key] === undefined || input[key] === null) {\n return { ok: false, error: `Missing required field: ${key}` };\n }\n const err = checkType(key, input[key], type);\n if (err) return { ok: false, error: err };\n value[key] = input[key];\n }\n\n return { ok: true, value };\n}\n\nfunction checkType(key: string, value: unknown, type: SchemaFieldType): string | null {\n switch (type) {\n case \"string\":\n case \"url\":\n if (typeof value !== \"string\" || !value.trim()) {\n return `Field ${key} must be a non-empty string`;\n }\n if (type === \"url\") {\n try {\n new URL(value);\n } catch {\n return `Field ${key} must be a valid URL`;\n }\n }\n return null;\n case \"number\":\n if (typeof value !== \"number\" || Number.isNaN(value)) {\n return `Field ${key} must be a number`;\n }\n return null;\n case \"boolean\":\n if (typeof value !== \"boolean\") {\n return `Field ${key} must be a boolean`;\n }\n return null;\n default:\n return null;\n }\n}\n\nfunction formatPrice(amount: string): string {\n const normalized = amount.startsWith(\"$\") ? amount.slice(1) : amount;\n return `$${normalized}`;\n}\n\n/**\n * Mount Forge agent services on an Express app.\n * Each service is exposed as POST {basePath}/{kebab-name} with optional x402.\n */\nexport function mountAgentServices(\n app: Application,\n services: DefinedAgentService[],\n options: MountOptions,\n): Router {\n if (!options.payTo && !options.skipPayment) {\n throw new Error(\"mountAgentServices: payTo is required unless skipPayment is true\");\n }\n\n const basePath = (options.basePath ?? \"/agent\").replace(/\\/$/, \"\") || \"/agent\";\n const router = createRouter();\n const context = createContext();\n\n const routes: RoutesConfig = {};\n\n for (const service of services) {\n const routePath = `/${service.path}`;\n const fullPath = `${basePath}${routePath}`;\n const network = asNetwork(service.payment.network);\n\n if (!options.skipPayment) {\n routes[`POST ${fullPath}`] = {\n accepts: {\n scheme: \"exact\",\n price: formatPrice(service.payment.amount),\n network,\n payTo: options.payTo,\n },\n description: service.description,\n mimeType: \"application/json\",\n };\n }\n\n router.post(routePath, async (req: Request, res: Response) => {\n try {\n const validated = validateInput(req.body, service.input);\n if (!validated.ok) {\n res.status(400).json({ error: validated.error });\n return;\n }\n\n const output = await service.execute(validated.value, context);\n res.json(output);\n } catch (err) {\n const message = err instanceof Error ? err.message : \"Internal error\";\n console.error(`[forge] ${service.name} failed:`, err);\n res.status(500).json({ error: message });\n }\n });\n }\n\n if (!options.skipPayment && Object.keys(routes).length > 0) {\n const facilitator = new HTTPFacilitatorClient({\n url: options.facilitatorUrl ?? DEFAULT_FACILITATOR,\n });\n const networks = new Set(\n services.map((s) => asNetwork(s.payment.network)),\n );\n const resourceServer = new x402ResourceServer(facilitator);\n for (const network of networks) {\n resourceServer.register(network, new ExactEvmScheme());\n }\n // syncFacilitatorOnStart validates scheme/network against the facilitator\n app.use(paymentMiddleware(routes, resourceServer, undefined, undefined, true));\n }\n\n app.use(basePath, router);\n return router;\n}\n\n/** Express-compatible middleware alias used in docs. */\nexport function createForgeRouter(\n services: DefinedAgentService[],\n options: MountOptions,\n): (app: Application) => Router {\n return (app) => mountAgentServices(app, services, options);\n}\n\n/** Optional no-op next for typing convenience in tests. */\nexport function forgeErrorHandler(\n err: unknown,\n _req: Request,\n res: Response,\n _next: NextFunction,\n): void {\n const message = err instanceof Error ? err.message : \"Internal error\";\n res.status(500).json({ error: message });\n}\n","import type { DefinedAgentService, SkillMeta } from \"./types.js\";\n\n/**\n * Generate a SKILL.md document for agents from defined Forge services.\n */\nexport function generateSkillMarkdown(\n services: DefinedAgentService[],\n meta: SkillMeta,\n): string {\n const base = meta.baseUrl.replace(/\\/$/, \"\");\n const lines: string[] = [\n `---`,\n `name: ${meta.name}`,\n `description: ${meta.description}`,\n `---`,\n ``,\n `# ${meta.name}`,\n ``,\n meta.description,\n ``,\n ];\n\n if (meta.homepage) {\n lines.push(`Homepage: ${meta.homepage}`, ``);\n }\n\n lines.push(\n `## How to use`,\n ``,\n `1. Read this skill.`,\n `2. Call the agent endpoint with a JSON body matching the input schema.`,\n `3. If you receive HTTP 402, pay via x402 (USDC) and retry with the payment header.`,\n `4. Use the returned output fields.`,\n ``,\n `## Services`,\n ``,\n );\n\n for (const service of services) {\n const path = `${base}/agent/${service.path}`;\n const price = service.payment.amount.startsWith(\"$\")\n ? service.payment.amount\n : `$${service.payment.amount}`;\n\n lines.push(\n `### \\`${service.name}\\``,\n ``,\n service.description,\n ``,\n `- **Method:** \\`POST\\``,\n `- **URL:** \\`${path}\\``,\n `- **Price:** ${price} ${service.payment.currency} via ${service.payment.protocols.join(\", \")}`,\n ``,\n `**Input**`,\n ``,\n \"```json\",\n JSON.stringify(\n Object.fromEntries(\n Object.entries(service.input).map(([k, t]) => [k, `<${t}>`]),\n ),\n null,\n 2,\n ),\n \"```\",\n ``,\n `**Output**`,\n ``,\n \"```json\",\n JSON.stringify(\n Object.fromEntries(\n Object.entries(service.output).map(([k, t]) => [k, `<${t}>`]),\n ),\n null,\n 2,\n ),\n \"```\",\n ``,\n `**Example**`,\n ``,\n \"```bash\",\n `curl -X POST ${path} \\\\`,\n ` -H \"Content-Type: application/json\" \\\\`,\n ` -d '${JSON.stringify(\n Object.fromEntries(\n Object.entries(service.input).map(([k, t]) => [\n k,\n t === \"url\" ? \"https://example.com/image.png\" : `example-${k}`,\n ]),\n ),\n )}'`,\n \"```\",\n ``,\n );\n }\n\n return lines.join(\"\\n\");\n}\n"],"mappings":"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","names":[]}
|
package/package.json
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@clawcash/forge",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "ClawCash Forge — define agent-native services with x402 payments",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.cjs",
|
|
7
|
+
"module": "./dist/index.js",
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"types": "./dist/index.d.ts",
|
|
12
|
+
"import": "./dist/index.js",
|
|
13
|
+
"require": "./dist/index.cjs"
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
"files": [
|
|
17
|
+
"dist"
|
|
18
|
+
],
|
|
19
|
+
"scripts": {
|
|
20
|
+
"build": "tsup",
|
|
21
|
+
"dev": "tsup --watch",
|
|
22
|
+
"prepublishOnly": "npm run build"
|
|
23
|
+
},
|
|
24
|
+
"keywords": [
|
|
25
|
+
"clawcash",
|
|
26
|
+
"forge",
|
|
27
|
+
"x402",
|
|
28
|
+
"agents",
|
|
29
|
+
"mcp"
|
|
30
|
+
],
|
|
31
|
+
"publishConfig": {
|
|
32
|
+
"access": "public"
|
|
33
|
+
},
|
|
34
|
+
"license": "MIT",
|
|
35
|
+
"peerDependencies": {
|
|
36
|
+
"express": ">=4.0.0"
|
|
37
|
+
},
|
|
38
|
+
"dependencies": {
|
|
39
|
+
"@x402/core": "^2.18.0",
|
|
40
|
+
"@x402/evm": "^2.18.0",
|
|
41
|
+
"@x402/express": "^2.18.0"
|
|
42
|
+
},
|
|
43
|
+
"devDependencies": {
|
|
44
|
+
"@types/express": "^5.0.6",
|
|
45
|
+
"@types/node": "^22.10.0",
|
|
46
|
+
"express": "^5.2.1",
|
|
47
|
+
"tsup": "^8.5.1",
|
|
48
|
+
"typescript": "^5.8.0"
|
|
49
|
+
}
|
|
50
|
+
}
|