@clawcash/forge 0.1.4 → 0.1.7
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 +13 -2
- package/dist/index.cjs +216 -4
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +97 -6
- package/dist/index.d.ts +97 -6
- package/dist/index.js +205 -4
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
# @clawcash/forge
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
The fastest way to make an **existing** SaaS/API app agent-native.
|
|
4
|
+
|
|
5
|
+
Define a high-level agent action on top of your backend, mount Express routes, attach x402, and register with the ClawCash gateway. Humans keep the same product; agents get a paid one-shot.
|
|
4
6
|
|
|
5
7
|
Published under the [ClawCash](https://www.npmjs.com/org/clawcash) npm org.
|
|
6
8
|
|
|
9
|
+
**Stack note:** `defineAgentService` / payTo / heartbeat are framework-agnostic. `mountAgentServices` is the **Express** adapter today. Next/Nest ship later as thin adapters (`@clawcash/forge/next`, etc.) — not one mega environment-agnostic package. A separate greenfield “build agent-native apps” SDK is a future product, not this one.
|
|
10
|
+
|
|
7
11
|
## Install
|
|
8
12
|
|
|
9
13
|
```bash
|
|
@@ -51,6 +55,12 @@ Resolution order: `PAY_TO` env (optional override) → live Forge API → `payTo
|
|
|
51
55
|
|
|
52
56
|
Agents call `POST /agent/remove-background` and pay via x402 when challenged with HTTP 402.
|
|
53
57
|
|
|
58
|
+
After mount, the merchant also exposes liveliness probes:
|
|
59
|
+
|
|
60
|
+
- `GET /forge/health` — process up + Forge mounted
|
|
61
|
+
- `GET /forge/status` — readiness (`ready` needs fulfill secret + public URL + actions)
|
|
62
|
+
- `GET /.well-known/clawcash.json` — discovery
|
|
63
|
+
|
|
54
64
|
Default payment network is **Base mainnet** (`eip155:8453`) via the public **XPay** facilitator (`https://facilitator.xpay.sh`) — no API keys.
|
|
55
65
|
|
|
56
66
|
## API
|
|
@@ -58,10 +68,11 @@ Default payment network is **Base mainnet** (`eip155:8453`) via the public **XPa
|
|
|
58
68
|
| Export | Purpose |
|
|
59
69
|
|--------|---------|
|
|
60
70
|
| `defineAgentService` | Declare name, schemas, payment, and `execute` |
|
|
61
|
-
| `mountAgentServices` | Async mount
|
|
71
|
+
| `mountAgentServices` | Async mount agent + fulfill + health/status routes |
|
|
62
72
|
| `resolvePayTo` | Resolve merchant wallet (env → Forge → fallback) |
|
|
63
73
|
| `generateSkillMarkdown` | Build a `SKILL.md` for agents |
|
|
64
74
|
| `waitUntil` / `context.waitUntil` | Poll until a job completes |
|
|
75
|
+
| `heartbeatGateway` | Push live base URL to the ClawCash gateway |
|
|
65
76
|
|
|
66
77
|
## Build
|
|
67
78
|
|
package/dist/index.cjs
CHANGED
|
@@ -20,13 +20,24 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
20
20
|
// src/index.ts
|
|
21
21
|
var index_exports = {};
|
|
22
22
|
__export(index_exports, {
|
|
23
|
+
FORGE_GATEWAY_USER_ID: () => FORGE_GATEWAY_USER_ID,
|
|
24
|
+
buildForgeSurfaceStatus: () => buildForgeSurfaceStatus,
|
|
25
|
+
buildWellKnownDocument: () => buildWellKnownDocument,
|
|
23
26
|
createContext: () => createContext,
|
|
24
27
|
createForgeRouter: () => createForgeRouter,
|
|
25
28
|
defineAgentService: () => defineAgentService,
|
|
29
|
+
forgeGatewayLoopbackHeaders: () => forgeGatewayLoopbackHeaders,
|
|
30
|
+
forgeSdkVersion: () => forgeSdkVersion,
|
|
26
31
|
generateSkillMarkdown: () => generateSkillMarkdown,
|
|
32
|
+
getForgeApiKey: () => getForgeApiKey,
|
|
33
|
+
getForgeGatewaySecret: () => getForgeGatewaySecret,
|
|
34
|
+
getLastHeartbeat: () => getLastHeartbeat,
|
|
27
35
|
heartbeatGateway: () => heartbeatGateway,
|
|
36
|
+
isForgeGatewayRequest: () => isForgeGatewayRequest,
|
|
28
37
|
isZeroAddress: () => isZeroAddress,
|
|
29
38
|
mountAgentServices: () => mountAgentServices,
|
|
39
|
+
recordHeartbeatResult: () => recordHeartbeatResult,
|
|
40
|
+
requireForgeGateway: () => requireForgeGateway,
|
|
30
41
|
resolvePayTo: () => resolvePayTo,
|
|
31
42
|
toKebabCase: () => toKebabCase,
|
|
32
43
|
waitUntil: () => waitUntil
|
|
@@ -99,6 +110,44 @@ async function heartbeatGateway(opts) {
|
|
|
99
110
|
}
|
|
100
111
|
}
|
|
101
112
|
|
|
113
|
+
// src/gateway-auth.ts
|
|
114
|
+
function getForgeApiKey(explicit) {
|
|
115
|
+
const value = (explicit ?? process.env.FORGE_API_KEY ?? process.env.GATEWAY_REGISTER_SECRET)?.trim();
|
|
116
|
+
if (!value || value.length < 16) return null;
|
|
117
|
+
return value;
|
|
118
|
+
}
|
|
119
|
+
function getForgeGatewaySecret(explicit) {
|
|
120
|
+
return getForgeApiKey(explicit);
|
|
121
|
+
}
|
|
122
|
+
function isForgeGatewayRequest(req, secret) {
|
|
123
|
+
const expected = getForgeApiKey(secret);
|
|
124
|
+
if (!expected) return false;
|
|
125
|
+
const header = req.header("authorization") ?? "";
|
|
126
|
+
const bearer = header.toLowerCase().startsWith("bearer ") ? header.slice(7).trim() : "";
|
|
127
|
+
const alt = req.header("x-forge-api-key")?.trim() || req.header("x-clawcash-gateway")?.trim() || "";
|
|
128
|
+
return bearer === expected || alt === expected;
|
|
129
|
+
}
|
|
130
|
+
var FORGE_GATEWAY_USER_ID = "forge_gateway_agent";
|
|
131
|
+
function requireForgeGateway(secret) {
|
|
132
|
+
return (req, res, next) => {
|
|
133
|
+
if (!isForgeGatewayRequest(req, secret)) {
|
|
134
|
+
res.status(401).json({
|
|
135
|
+
error: "Unauthorized \u2014 Forge API key required (FORGE_API_KEY from Forge dashboard)"
|
|
136
|
+
});
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
next();
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
function forgeGatewayLoopbackHeaders(secret) {
|
|
143
|
+
const expected = getForgeApiKey(secret);
|
|
144
|
+
if (!expected) return {};
|
|
145
|
+
return {
|
|
146
|
+
Authorization: `Bearer ${expected}`,
|
|
147
|
+
"X-Forge-Api-Key": expected
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
|
|
102
151
|
// src/payto.ts
|
|
103
152
|
var ZERO = /^0x0{40}$/i;
|
|
104
153
|
function isZeroAddress(address) {
|
|
@@ -159,6 +208,66 @@ async function resolvePayTo(opts) {
|
|
|
159
208
|
);
|
|
160
209
|
}
|
|
161
210
|
|
|
211
|
+
// src/status.ts
|
|
212
|
+
var PACKAGE_VERSION = "0.1.7";
|
|
213
|
+
var lastHeartbeat = {
|
|
214
|
+
attempted: false,
|
|
215
|
+
ok: null,
|
|
216
|
+
at: null,
|
|
217
|
+
error: null
|
|
218
|
+
};
|
|
219
|
+
function recordHeartbeatResult(result) {
|
|
220
|
+
lastHeartbeat = {
|
|
221
|
+
attempted: true,
|
|
222
|
+
ok: result.ok,
|
|
223
|
+
at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
224
|
+
error: result.ok ? null : result.error
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
function getLastHeartbeat() {
|
|
228
|
+
return { ...lastHeartbeat };
|
|
229
|
+
}
|
|
230
|
+
function buildForgeSurfaceStatus(opts) {
|
|
231
|
+
const fulfillPath = opts.fulfillPath.replace(/\/$/, "") || "/forge/fulfill";
|
|
232
|
+
const agentBasePath = opts.agentBasePath.replace(/\/$/, "") || "/agent";
|
|
233
|
+
const ready = opts.fulfillMounted && Boolean(opts.publicBaseUrl) && opts.actions.length > 0;
|
|
234
|
+
return {
|
|
235
|
+
ok: true,
|
|
236
|
+
forge: true,
|
|
237
|
+
version: PACKAGE_VERSION,
|
|
238
|
+
handle: opts.handle,
|
|
239
|
+
publicBaseUrl: opts.publicBaseUrl,
|
|
240
|
+
gatewayUrl: opts.gatewayUrl,
|
|
241
|
+
fulfillMounted: opts.fulfillMounted,
|
|
242
|
+
fulfillPath,
|
|
243
|
+
agentBasePath,
|
|
244
|
+
skipPayment: opts.skipPayment,
|
|
245
|
+
actions: opts.actions,
|
|
246
|
+
ready,
|
|
247
|
+
heartbeat: getLastHeartbeat(),
|
|
248
|
+
endpoints: {
|
|
249
|
+
health: "/forge/health",
|
|
250
|
+
status: "/forge/status",
|
|
251
|
+
wellKnown: "/.well-known/clawcash.json",
|
|
252
|
+
fulfill: fulfillPath,
|
|
253
|
+
agent: agentBasePath
|
|
254
|
+
}
|
|
255
|
+
};
|
|
256
|
+
}
|
|
257
|
+
function buildWellKnownDocument(status) {
|
|
258
|
+
return {
|
|
259
|
+
forge: true,
|
|
260
|
+
version: status.version,
|
|
261
|
+
health: status.endpoints.health,
|
|
262
|
+
status: status.endpoints.status,
|
|
263
|
+
handle: status.handle,
|
|
264
|
+
ready: status.ready
|
|
265
|
+
};
|
|
266
|
+
}
|
|
267
|
+
function forgeSdkVersion() {
|
|
268
|
+
return PACKAGE_VERSION;
|
|
269
|
+
}
|
|
270
|
+
|
|
162
271
|
// src/mount.ts
|
|
163
272
|
var DEFAULT_NETWORK = "eip155:8453";
|
|
164
273
|
var DEFAULT_FACILITATOR_URL = "https://facilitator.xpay.sh";
|
|
@@ -220,6 +329,81 @@ function formatPrice(amount) {
|
|
|
220
329
|
const normalized = amount.startsWith("$") ? amount.slice(1) : amount;
|
|
221
330
|
return `$${normalized}`;
|
|
222
331
|
}
|
|
332
|
+
function mountFulfillmentRouter(app, services, options) {
|
|
333
|
+
const secret = getForgeApiKey(options.gateway?.registerSecret);
|
|
334
|
+
if (!secret) {
|
|
335
|
+
console.warn(
|
|
336
|
+
"[forge] /forge/fulfill not mounted \u2014 set FORGE_API_KEY (from Forge dashboard, min 16 chars) so the gateway can fulfill after payment"
|
|
337
|
+
);
|
|
338
|
+
return;
|
|
339
|
+
}
|
|
340
|
+
const fulfillPath = (options.fulfillPath ?? "/forge/fulfill").replace(/\/$/, "") || "/forge/fulfill";
|
|
341
|
+
const router = (0, import_express.Router)();
|
|
342
|
+
const context = createContext();
|
|
343
|
+
const gate = requireForgeGateway(secret);
|
|
344
|
+
for (const service of services) {
|
|
345
|
+
router.post(
|
|
346
|
+
`/${service.path}`,
|
|
347
|
+
gate,
|
|
348
|
+
async (req, res) => {
|
|
349
|
+
try {
|
|
350
|
+
const validated = validateInput(req.body, service.input);
|
|
351
|
+
if (!validated.ok) {
|
|
352
|
+
res.status(400).json({ error: validated.error });
|
|
353
|
+
return;
|
|
354
|
+
}
|
|
355
|
+
const output = await service.execute(validated.value, context);
|
|
356
|
+
res.json(output);
|
|
357
|
+
} catch (err) {
|
|
358
|
+
const message = err instanceof Error ? err.message : "Internal error";
|
|
359
|
+
console.error(`[forge] fulfill ${service.name} failed:`, err);
|
|
360
|
+
res.status(500).json({ error: message });
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
);
|
|
364
|
+
}
|
|
365
|
+
app.use(fulfillPath, router);
|
|
366
|
+
console.log(
|
|
367
|
+
`[forge] fulfillment mounted at POST ${fulfillPath}/{action} (gateway secret)`
|
|
368
|
+
);
|
|
369
|
+
}
|
|
370
|
+
function mountForgeStatusRoutes(app, services, options, opts) {
|
|
371
|
+
const getStatus = () => buildForgeSurfaceStatus({
|
|
372
|
+
handle: opts.handle,
|
|
373
|
+
publicBaseUrl: opts.publicBaseUrl,
|
|
374
|
+
gatewayUrl: opts.gatewayUrl,
|
|
375
|
+
fulfillMounted: opts.fulfillMounted,
|
|
376
|
+
fulfillPath: opts.fulfillPath,
|
|
377
|
+
agentBasePath: opts.agentBasePath,
|
|
378
|
+
skipPayment: Boolean(options.skipPayment),
|
|
379
|
+
actions: services.map((s) => ({
|
|
380
|
+
name: s.name,
|
|
381
|
+
path: s.path,
|
|
382
|
+
price: s.payment.amount
|
|
383
|
+
}))
|
|
384
|
+
});
|
|
385
|
+
app.get("/forge/health", (_req, res) => {
|
|
386
|
+
const status = getStatus();
|
|
387
|
+
res.json({
|
|
388
|
+
ok: true,
|
|
389
|
+
forge: true,
|
|
390
|
+
ready: status.ready,
|
|
391
|
+
handle: status.handle,
|
|
392
|
+
version: status.version,
|
|
393
|
+
actions: status.actions.map((a) => a.name)
|
|
394
|
+
});
|
|
395
|
+
});
|
|
396
|
+
app.get("/forge/status", (_req, res) => {
|
|
397
|
+
const status = getStatus();
|
|
398
|
+
res.status(status.ready ? 200 : 503).json(status);
|
|
399
|
+
});
|
|
400
|
+
app.get("/.well-known/clawcash.json", (_req, res) => {
|
|
401
|
+
res.json(buildWellKnownDocument(getStatus()));
|
|
402
|
+
});
|
|
403
|
+
console.log(
|
|
404
|
+
"[forge] liveliness: GET /forge/health, GET /forge/status, GET /.well-known/clawcash.json"
|
|
405
|
+
);
|
|
406
|
+
}
|
|
223
407
|
async function mountAgentServices(app, services, options) {
|
|
224
408
|
let payTo = "";
|
|
225
409
|
if (!options.skipPayment) {
|
|
@@ -236,6 +420,7 @@ async function mountAgentServices(app, services, options) {
|
|
|
236
420
|
}
|
|
237
421
|
}
|
|
238
422
|
const basePath = (options.basePath ?? "/agent").replace(/\/$/, "") || "/agent";
|
|
423
|
+
const fulfillPath = (options.fulfillPath ?? "/forge/fulfill").replace(/\/$/, "") || "/forge/fulfill";
|
|
239
424
|
const router = (0, import_express.Router)();
|
|
240
425
|
const context = createContext();
|
|
241
426
|
const routes = {};
|
|
@@ -283,17 +468,33 @@ async function mountAgentServices(app, services, options) {
|
|
|
283
468
|
app.use((0, import_express2.paymentMiddleware)(routes, resourceServer, void 0, void 0, true));
|
|
284
469
|
}
|
|
285
470
|
app.use(basePath, router);
|
|
286
|
-
const
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
471
|
+
const fulfillMounted = Boolean(
|
|
472
|
+
getForgeApiKey(options.gateway?.registerSecret)
|
|
473
|
+
);
|
|
474
|
+
mountFulfillmentRouter(app, services, options);
|
|
475
|
+
const gatewayUrl = options.gateway?.url?.trim() || process.env.GATEWAY_URL?.trim() || null;
|
|
476
|
+
const handle = options.gateway?.handle?.trim() || process.env.FORGE_HANDLE?.trim() || null;
|
|
477
|
+
const registerSecret = options.gateway?.registerSecret?.trim() || process.env.FORGE_API_KEY?.trim() || process.env.GATEWAY_REGISTER_SECRET?.trim();
|
|
478
|
+
const publicBaseUrl = options.gateway?.publicBaseUrl?.trim() || process.env.PUBLIC_BASE_URL?.trim() || (process.env.RAILWAY_PUBLIC_DOMAIN ? `https://${process.env.RAILWAY_PUBLIC_DOMAIN}` : "") || null;
|
|
479
|
+
mountForgeStatusRoutes(app, services, options, {
|
|
480
|
+
fulfillMounted,
|
|
481
|
+
fulfillPath,
|
|
482
|
+
agentBasePath: basePath,
|
|
483
|
+
handle,
|
|
484
|
+
publicBaseUrl,
|
|
485
|
+
gatewayUrl
|
|
486
|
+
});
|
|
290
487
|
if (gatewayUrl && handle && registerSecret && publicBaseUrl) {
|
|
291
488
|
void heartbeatGateway({
|
|
292
489
|
gatewayUrl,
|
|
293
490
|
handle,
|
|
294
491
|
baseUrl: publicBaseUrl,
|
|
295
492
|
registerSecret
|
|
493
|
+
}).then(() => {
|
|
494
|
+
recordHeartbeatResult({ ok: true });
|
|
296
495
|
}).catch((err) => {
|
|
496
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
497
|
+
recordHeartbeatResult({ ok: false, error: message });
|
|
297
498
|
console.warn("[forge] gateway heartbeat skipped:", err);
|
|
298
499
|
});
|
|
299
500
|
}
|
|
@@ -388,13 +589,24 @@ function generateSkillMarkdown(services, meta) {
|
|
|
388
589
|
}
|
|
389
590
|
// Annotate the CommonJS export names for ESM import in node:
|
|
390
591
|
0 && (module.exports = {
|
|
592
|
+
FORGE_GATEWAY_USER_ID,
|
|
593
|
+
buildForgeSurfaceStatus,
|
|
594
|
+
buildWellKnownDocument,
|
|
391
595
|
createContext,
|
|
392
596
|
createForgeRouter,
|
|
393
597
|
defineAgentService,
|
|
598
|
+
forgeGatewayLoopbackHeaders,
|
|
599
|
+
forgeSdkVersion,
|
|
394
600
|
generateSkillMarkdown,
|
|
601
|
+
getForgeApiKey,
|
|
602
|
+
getForgeGatewaySecret,
|
|
603
|
+
getLastHeartbeat,
|
|
395
604
|
heartbeatGateway,
|
|
605
|
+
isForgeGatewayRequest,
|
|
396
606
|
isZeroAddress,
|
|
397
607
|
mountAgentServices,
|
|
608
|
+
recordHeartbeatResult,
|
|
609
|
+
requireForgeGateway,
|
|
398
610
|
resolvePayTo,
|
|
399
611
|
toKebabCase,
|
|
400
612
|
waitUntil
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/define.ts","../src/mount.ts","../src/gateway.ts","../src/payto.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 { resolvePayTo, isZeroAddress } from \"./payto.js\";\nexport { heartbeatGateway } from \"./gateway.js\";\nexport type {\n SchemaFieldType,\n InputSchema,\n OutputSchema,\n PaymentConfig,\n WaitUntilOptions,\n AgentServiceContext,\n AgentServiceConfig,\n DefinedAgentService,\n MountOptions,\n ForgePayToSource,\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 { heartbeatGateway } from \"./gateway.js\";\nimport { isZeroAddress, resolvePayTo } from \"./payto.js\";\nimport type {\n DefinedAgentService,\n InputSchema,\n MountOptions,\n SchemaFieldType,\n} from \"./types.js\";\n\n/** Base mainnet — settled via the public XPay facilitator (no API keys). */\nconst DEFAULT_NETWORK = \"eip155:8453\" as const satisfies Network;\n/** Open facilitator: Base mainnet + Sepolia, no API keys. https://github.com/xpaysh/xpay-x402 */\nconst DEFAULT_FACILITATOR_URL = \"https://facilitator.xpay.sh\";\n\nfunction asNetwork(value: string | undefined): Network {\n return (value ?? DEFAULT_NETWORK) as Network;\n}\n\nfunction createFacilitatorClient(options: MountOptions): HTTPFacilitatorClient {\n return new HTTPFacilitatorClient({\n url: options.facilitatorUrl ?? DEFAULT_FACILITATOR_URL,\n createAuthHeaders: options.createAuthHeaders,\n });\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 *\n * payTo resolves automatically: PAY_TO env (optional) → Forge API → baked fallback.\n * Merchants should not need to configure PAY_TO on Railway for the happy path.\n */\nexport async function mountAgentServices(\n app: Application,\n services: DefinedAgentService[],\n options: MountOptions,\n): Promise<Router> {\n let payTo = \"\";\n if (!options.skipPayment) {\n const fallback =\n options.payToFallback?.trim() || options.payTo?.trim() || \"\";\n payTo = await resolvePayTo({\n fallback,\n payTo: options.payTo,\n forge: options.forge,\n });\n if (isZeroAddress(payTo)) {\n throw new Error(\n \"mountAgentServices: resolved payTo is the zero address. Re-run Forge to provision a wallet.\",\n );\n }\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,\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 = createFacilitatorClient(options);\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 app.use(paymentMiddleware(routes, resourceServer, undefined, undefined, true));\n }\n\n app.use(basePath, router);\n\n // Fire-and-forget baseUrl heartbeat so gateway fulfillment stays current.\n const gatewayUrl =\n options.gateway?.url?.trim() || process.env.GATEWAY_URL?.trim();\n const handle =\n options.gateway?.handle?.trim() || process.env.FORGE_HANDLE?.trim();\n const registerSecret =\n options.gateway?.registerSecret?.trim() ||\n process.env.GATEWAY_REGISTER_SECRET?.trim();\n const publicBaseUrl =\n options.gateway?.publicBaseUrl?.trim() ||\n process.env.PUBLIC_BASE_URL?.trim() ||\n (process.env.RAILWAY_PUBLIC_DOMAIN\n ? `https://${process.env.RAILWAY_PUBLIC_DOMAIN}`\n : \"\");\n\n if (gatewayUrl && handle && registerSecret && publicBaseUrl) {\n void heartbeatGateway({\n gatewayUrl,\n handle,\n baseUrl: publicBaseUrl,\n registerSecret,\n }).catch((err) => {\n console.warn(\"[forge] gateway heartbeat skipped:\", err);\n });\n }\n\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) => Promise<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","/**\n * Notify ClawCash gateway of this merchant's live public base URL.\n * Full action registration still happens from Forge \"Go live\".\n */\nexport async function heartbeatGateway(opts: {\n gatewayUrl: string;\n handle: string;\n baseUrl: string;\n registerSecret: string;\n}): Promise<void> {\n const gateway = opts.gatewayUrl.replace(/\\/$/, \"\");\n const res = await fetch(`${gateway}/register/heartbeat`, {\n method: \"POST\",\n headers: {\n \"content-type\": \"application/json\",\n authorization: `Bearer ${opts.registerSecret}`,\n },\n body: JSON.stringify({\n handle: opts.handle.trim().toLowerCase(),\n baseUrl: opts.baseUrl.replace(/\\/$/, \"\"),\n }),\n });\n if (!res.ok) {\n const text = await res.text();\n throw new Error(\n `gateway heartbeat failed (${res.status}): ${text.slice(0, 240)}`,\n );\n }\n}\n","import type { ForgePayToSource } from \"./types.js\";\n\nexport type { ForgePayToSource };\n\nconst ZERO = /^0x0{40}$/i;\n\nexport function isZeroAddress(address: string | undefined | null): boolean {\n if (!address) return true;\n return ZERO.test(address.trim());\n}\n\nfunction normalizeBaseUrl(url: string): string {\n return url.replace(/\\/$/, \"\");\n}\n\nfunction pickForgeBaseUrl(source?: ForgePayToSource): string | null {\n const fromEnv = process.env.FORGE_URL?.trim();\n if (fromEnv) return normalizeBaseUrl(fromEnv);\n\n const fromOpts = source?.baseUrl?.trim();\n if (!fromOpts) return null;\n\n const normalized = normalizeBaseUrl(fromOpts);\n // Never call a laptop Forge from a deployed merchant app.\n if (\n /localhost|127\\.0\\.0\\.1/i.test(normalized) &&\n process.env.NODE_ENV === \"production\"\n ) {\n return null;\n }\n return normalized;\n}\n\n/**\n * Resolve merchant payTo with minimal host config:\n * 1. `PAY_TO` env (optional override)\n * 2. Live address from Forge public API (when reachable)\n * 3. Baked-in fallback from the Forge PR\n */\nexport async function resolvePayTo(opts: {\n /** Baked-in address from Forge codegen (required fallback). */\n fallback: string;\n /** Optional explicit override (usually process.env.PAY_TO). */\n payTo?: string;\n forge?: ForgePayToSource;\n /** Fetch timeout ms. Defaults to 2500. */\n timeoutMs?: number;\n}): Promise<string> {\n const envOverride = process.env.PAY_TO?.trim();\n if (envOverride && !isZeroAddress(envOverride)) {\n return envOverride;\n }\n\n const explicit = opts.payTo?.trim();\n if (explicit && !isZeroAddress(explicit) && !opts.forge) {\n return explicit;\n }\n\n const baseUrl = pickForgeBaseUrl(opts.forge);\n if (baseUrl && opts.forge) {\n const url = `${baseUrl}/api/public/pay-to/${encodeURIComponent(opts.forge.owner)}/${encodeURIComponent(opts.forge.repo)}`;\n try {\n const controller = new AbortController();\n const timer = setTimeout(\n () => controller.abort(),\n opts.timeoutMs ?? 2500,\n );\n const res = await fetch(url, {\n signal: controller.signal,\n headers: { accept: \"application/json\" },\n });\n clearTimeout(timer);\n if (res.ok) {\n const json = (await res.json()) as { address?: string };\n if (json.address && !isZeroAddress(json.address)) {\n return json.address.trim();\n }\n }\n } catch (err) {\n console.warn(\"[forge] live payTo fetch failed, using fallback:\", err);\n }\n }\n\n if (explicit && !isZeroAddress(explicit)) return explicit;\n if (opts.fallback && !isZeroAddress(opts.fallback)) return opts.fallback;\n\n throw new Error(\n \"resolvePayTo: no usable address (set Forge wallet / PAY_TO, or re-run Forge PR)\",\n );\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"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/define.ts","../src/mount.ts","../src/gateway.ts","../src/gateway-auth.ts","../src/payto.ts","../src/status.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 { resolvePayTo, isZeroAddress } from \"./payto.js\";\nexport { heartbeatGateway } from \"./gateway.js\";\nexport {\n buildForgeSurfaceStatus,\n buildWellKnownDocument,\n forgeSdkVersion,\n getLastHeartbeat,\n recordHeartbeatResult,\n} from \"./status.js\";\nexport type { ForgeHeartbeatState, ForgeSurfaceStatus } from \"./status.js\";\nexport {\n isForgeGatewayRequest,\n requireForgeGateway,\n getForgeApiKey,\n getForgeGatewaySecret,\n forgeGatewayLoopbackHeaders,\n FORGE_GATEWAY_USER_ID,\n} from \"./gateway-auth.js\";\nexport type {\n SchemaFieldType,\n InputSchema,\n OutputSchema,\n PaymentConfig,\n WaitUntilOptions,\n AgentServiceContext,\n AgentServiceConfig,\n DefinedAgentService,\n MountOptions,\n ForgePayToSource,\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 { heartbeatGateway } from \"./gateway.js\";\nimport {\n getForgeApiKey,\n requireForgeGateway,\n} from \"./gateway-auth.js\";\nimport { isZeroAddress, resolvePayTo } from \"./payto.js\";\nimport {\n buildForgeSurfaceStatus,\n buildWellKnownDocument,\n recordHeartbeatResult,\n} from \"./status.js\";\nimport type {\n DefinedAgentService,\n InputSchema,\n MountOptions,\n SchemaFieldType,\n} from \"./types.js\";\n\n/** Base mainnet — settled via the public XPay facilitator (no API keys). */\nconst DEFAULT_NETWORK = \"eip155:8453\" as const satisfies Network;\n/** Open facilitator: Base mainnet + Sepolia, no API keys. https://github.com/xpaysh/xpay-x402 */\nconst DEFAULT_FACILITATOR_URL = \"https://facilitator.xpay.sh\";\n\nfunction asNetwork(value: string | undefined): Network {\n return (value ?? DEFAULT_NETWORK) as Network;\n}\n\nfunction createFacilitatorClient(options: MountOptions): HTTPFacilitatorClient {\n return new HTTPFacilitatorClient({\n url: options.facilitatorUrl ?? DEFAULT_FACILITATOR_URL,\n createAuthHeaders: options.createAuthHeaders,\n });\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\nfunction mountFulfillmentRouter(\n app: Application,\n services: DefinedAgentService[],\n options: MountOptions,\n): void {\n const secret = getForgeApiKey(options.gateway?.registerSecret);\n if (!secret) {\n console.warn(\n \"[forge] /forge/fulfill not mounted — set FORGE_API_KEY (from Forge dashboard, min 16 chars) so the gateway can fulfill after payment\",\n );\n return;\n }\n\n const fulfillPath =\n (options.fulfillPath ?? \"/forge/fulfill\").replace(/\\/$/, \"\") ||\n \"/forge/fulfill\";\n const router = createRouter();\n const context = createContext();\n const gate = requireForgeGateway(secret);\n\n for (const service of services) {\n router.post(\n `/${service.path}`,\n gate,\n 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 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] fulfill ${service.name} failed:`, err);\n res.status(500).json({ error: message });\n }\n },\n );\n }\n\n app.use(fulfillPath, router);\n console.log(\n `[forge] fulfillment mounted at POST ${fulfillPath}/{action} (gateway secret)`,\n );\n}\n\nfunction mountForgeStatusRoutes(\n app: Application,\n services: DefinedAgentService[],\n options: MountOptions,\n opts: {\n fulfillMounted: boolean;\n fulfillPath: string;\n agentBasePath: string;\n handle: string | null;\n publicBaseUrl: string | null;\n gatewayUrl: string | null;\n },\n): void {\n const getStatus = () =>\n buildForgeSurfaceStatus({\n handle: opts.handle,\n publicBaseUrl: opts.publicBaseUrl,\n gatewayUrl: opts.gatewayUrl,\n fulfillMounted: opts.fulfillMounted,\n fulfillPath: opts.fulfillPath,\n agentBasePath: opts.agentBasePath,\n skipPayment: Boolean(options.skipPayment),\n actions: services.map((s) => ({\n name: s.name,\n path: s.path,\n price: s.payment.amount,\n })),\n });\n\n app.get(\"/forge/health\", (_req: Request, res: Response) => {\n const status = getStatus();\n res.json({\n ok: true,\n forge: true as const,\n ready: status.ready,\n handle: status.handle,\n version: status.version,\n actions: status.actions.map((a) => a.name),\n });\n });\n\n app.get(\"/forge/status\", (_req: Request, res: Response) => {\n const status = getStatus();\n res.status(status.ready ? 200 : 503).json(status);\n });\n\n app.get(\"/.well-known/clawcash.json\", (_req: Request, res: Response) => {\n res.json(buildWellKnownDocument(getStatus()));\n });\n\n console.log(\n \"[forge] liveliness: GET /forge/health, GET /forge/status, GET /.well-known/clawcash.json\",\n );\n}\n\n/**\n * Mount Forge agent services on an Express app.\n * - GET /forge/health, GET /forge/status, GET /.well-known/clawcash.json — liveliness\n * - POST {basePath}/{kebab-name} — optional public x402 agent surface\n * - POST /forge/fulfill/{kebab-name} — private gateway fulfillment after payment\n *\n * payTo resolves automatically: PAY_TO env (optional) → Forge API → baked fallback.\n */\nexport async function mountAgentServices(\n app: Application,\n services: DefinedAgentService[],\n options: MountOptions,\n): Promise<Router> {\n let payTo = \"\";\n if (!options.skipPayment) {\n const fallback =\n options.payToFallback?.trim() || options.payTo?.trim() || \"\";\n payTo = await resolvePayTo({\n fallback,\n payTo: options.payTo,\n forge: options.forge,\n });\n if (isZeroAddress(payTo)) {\n throw new Error(\n \"mountAgentServices: resolved payTo is the zero address. Re-run Forge to provision a wallet.\",\n );\n }\n }\n\n const basePath = (options.basePath ?? \"/agent\").replace(/\\/$/, \"\") || \"/agent\";\n const fulfillPath =\n (options.fulfillPath ?? \"/forge/fulfill\").replace(/\\/$/, \"\") ||\n \"/forge/fulfill\";\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,\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 = createFacilitatorClient(options);\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 app.use(paymentMiddleware(routes, resourceServer, undefined, undefined, true));\n }\n\n app.use(basePath, router);\n const fulfillMounted = Boolean(\n getForgeApiKey(options.gateway?.registerSecret),\n );\n mountFulfillmentRouter(app, services, options);\n\n const gatewayUrl =\n options.gateway?.url?.trim() || process.env.GATEWAY_URL?.trim() || null;\n const handle =\n options.gateway?.handle?.trim() || process.env.FORGE_HANDLE?.trim() || null;\n const registerSecret =\n options.gateway?.registerSecret?.trim() ||\n process.env.FORGE_API_KEY?.trim() ||\n process.env.GATEWAY_REGISTER_SECRET?.trim();\n const publicBaseUrl =\n options.gateway?.publicBaseUrl?.trim() ||\n process.env.PUBLIC_BASE_URL?.trim() ||\n (process.env.RAILWAY_PUBLIC_DOMAIN\n ? `https://${process.env.RAILWAY_PUBLIC_DOMAIN}`\n : \"\") ||\n null;\n\n mountForgeStatusRoutes(app, services, options, {\n fulfillMounted,\n fulfillPath,\n agentBasePath: basePath,\n handle,\n publicBaseUrl,\n gatewayUrl,\n });\n\n if (gatewayUrl && handle && registerSecret && publicBaseUrl) {\n void heartbeatGateway({\n gatewayUrl,\n handle,\n baseUrl: publicBaseUrl,\n registerSecret,\n })\n .then(() => {\n recordHeartbeatResult({ ok: true });\n })\n .catch((err) => {\n const message = err instanceof Error ? err.message : String(err);\n recordHeartbeatResult({ ok: false, error: message });\n console.warn(\"[forge] gateway heartbeat skipped:\", err);\n });\n }\n\n return router;\n}\n\nexport function createForgeRouter(\n services: DefinedAgentService[],\n options: MountOptions,\n): (app: Application) => Promise<Router> {\n return (app) => mountAgentServices(app, services, options);\n}\n\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","/**\n * Notify ClawCash gateway of this merchant's live public base URL.\n * Full action registration still happens from Forge \"Go live\".\n */\nexport async function heartbeatGateway(opts: {\n gatewayUrl: string;\n handle: string;\n baseUrl: string;\n registerSecret: string;\n}): Promise<void> {\n const gateway = opts.gatewayUrl.replace(/\\/$/, \"\");\n const res = await fetch(`${gateway}/register/heartbeat`, {\n method: \"POST\",\n headers: {\n \"content-type\": \"application/json\",\n authorization: `Bearer ${opts.registerSecret}`,\n },\n body: JSON.stringify({\n handle: opts.handle.trim().toLowerCase(),\n baseUrl: opts.baseUrl.replace(/\\/$/, \"\"),\n }),\n });\n if (!res.ok) {\n const text = await res.text();\n throw new Error(\n `gateway heartbeat failed (${res.status}): ${text.slice(0, 240)}`,\n );\n }\n}\n","import type { NextFunction, Request, Response } from \"express\";\n\n/**\n * Per-project Forge API key for gateway → merchant fulfillment.\n * Merchants set FORGE_API_KEY from the Forge dashboard (never the shared\n * GATEWAY_REGISTER_SECRET — that is Forge↔gateway platform auth only).\n *\n * GATEWAY_REGISTER_SECRET is accepted as a deprecated fallback for older deploys.\n */\nexport function getForgeApiKey(explicit?: string | null): string | null {\n const value =\n (explicit ?? process.env.FORGE_API_KEY ?? process.env.GATEWAY_REGISTER_SECRET)?.trim();\n if (!value || value.length < 16) return null;\n return value;\n}\n\n/** @deprecated Use getForgeApiKey */\nexport function getForgeGatewaySecret(\n explicit?: string | null,\n): string | null {\n return getForgeApiKey(explicit);\n}\n\nexport function isForgeGatewayRequest(\n req: Request,\n secret?: string | null,\n): boolean {\n const expected = getForgeApiKey(secret);\n if (!expected) return false;\n const header = req.header(\"authorization\") ?? \"\";\n const bearer = header.toLowerCase().startsWith(\"bearer \")\n ? header.slice(7).trim()\n : \"\";\n const alt =\n req.header(\"x-forge-api-key\")?.trim() ||\n req.header(\"x-clawcash-gateway\")?.trim() ||\n \"\";\n return bearer === expected || alt === expected;\n}\n\n/** Synthetic user id for DB rows created by gateway-driven fulfillment. */\nexport const FORGE_GATEWAY_USER_ID = \"forge_gateway_agent\";\n\nexport function requireForgeGateway(\n secret?: string | null,\n): (req: Request, res: Response, next: NextFunction) => void {\n return (req, res, next) => {\n if (!isForgeGatewayRequest(req, secret)) {\n res.status(401).json({\n error:\n \"Unauthorized — Forge API key required (FORGE_API_KEY from Forge dashboard)\",\n });\n return;\n }\n next();\n };\n}\n\n/** Headers merchant execute() should send on loopback calls during fulfillment. */\nexport function forgeGatewayLoopbackHeaders(\n secret?: string | null,\n): Record<string, string> {\n const expected = getForgeApiKey(secret);\n if (!expected) return {};\n return {\n Authorization: `Bearer ${expected}`,\n \"X-Forge-Api-Key\": expected,\n };\n}\n","import type { ForgePayToSource } from \"./types.js\";\n\nexport type { ForgePayToSource };\n\nconst ZERO = /^0x0{40}$/i;\n\nexport function isZeroAddress(address: string | undefined | null): boolean {\n if (!address) return true;\n return ZERO.test(address.trim());\n}\n\nfunction normalizeBaseUrl(url: string): string {\n return url.replace(/\\/$/, \"\");\n}\n\nfunction pickForgeBaseUrl(source?: ForgePayToSource): string | null {\n const fromEnv = process.env.FORGE_URL?.trim();\n if (fromEnv) return normalizeBaseUrl(fromEnv);\n\n const fromOpts = source?.baseUrl?.trim();\n if (!fromOpts) return null;\n\n const normalized = normalizeBaseUrl(fromOpts);\n // Never call a laptop Forge from a deployed merchant app.\n if (\n /localhost|127\\.0\\.0\\.1/i.test(normalized) &&\n process.env.NODE_ENV === \"production\"\n ) {\n return null;\n }\n return normalized;\n}\n\n/**\n * Resolve merchant payTo with minimal host config:\n * 1. `PAY_TO` env (optional override)\n * 2. Live address from Forge public API (when reachable)\n * 3. Baked-in fallback from the Forge PR\n */\nexport async function resolvePayTo(opts: {\n /** Baked-in address from Forge codegen (required fallback). */\n fallback: string;\n /** Optional explicit override (usually process.env.PAY_TO). */\n payTo?: string;\n forge?: ForgePayToSource;\n /** Fetch timeout ms. Defaults to 2500. */\n timeoutMs?: number;\n}): Promise<string> {\n const envOverride = process.env.PAY_TO?.trim();\n if (envOverride && !isZeroAddress(envOverride)) {\n return envOverride;\n }\n\n const explicit = opts.payTo?.trim();\n if (explicit && !isZeroAddress(explicit) && !opts.forge) {\n return explicit;\n }\n\n const baseUrl = pickForgeBaseUrl(opts.forge);\n if (baseUrl && opts.forge) {\n const url = `${baseUrl}/api/public/pay-to/${encodeURIComponent(opts.forge.owner)}/${encodeURIComponent(opts.forge.repo)}`;\n try {\n const controller = new AbortController();\n const timer = setTimeout(\n () => controller.abort(),\n opts.timeoutMs ?? 2500,\n );\n const res = await fetch(url, {\n signal: controller.signal,\n headers: { accept: \"application/json\" },\n });\n clearTimeout(timer);\n if (res.ok) {\n const json = (await res.json()) as { address?: string };\n if (json.address && !isZeroAddress(json.address)) {\n return json.address.trim();\n }\n }\n } catch (err) {\n console.warn(\"[forge] live payTo fetch failed, using fallback:\", err);\n }\n }\n\n if (explicit && !isZeroAddress(explicit)) return explicit;\n if (opts.fallback && !isZeroAddress(opts.fallback)) return opts.fallback;\n\n throw new Error(\n \"resolvePayTo: no usable address (set Forge wallet / PAY_TO, or re-run Forge PR)\",\n );\n}\n","/**\n * Merchant-side Forge surface status — public probes for deploy / go-live checks.\n */\n\nexport type ForgeHeartbeatState = {\n attempted: boolean;\n ok: boolean | null;\n at: string | null;\n error: string | null;\n};\n\nexport type ForgeSurfaceStatus = {\n ok: boolean;\n forge: true;\n version: string;\n handle: string | null;\n publicBaseUrl: string | null;\n gatewayUrl: string | null;\n /** True when FORGE_API_KEY is set so /forge/fulfill is mounted. */\n fulfillMounted: boolean;\n fulfillPath: string;\n agentBasePath: string;\n skipPayment: boolean;\n actions: Array<{ name: string; path: string; price: string }>;\n /** Env + fulfill ready — merchant can accept gateway fulfillment. */\n ready: boolean;\n heartbeat: ForgeHeartbeatState;\n endpoints: {\n health: string;\n status: string;\n wellKnown: string;\n fulfill: string;\n agent: string;\n };\n};\n\nconst PACKAGE_VERSION = \"0.1.7\";\n\nlet lastHeartbeat: ForgeHeartbeatState = {\n attempted: false,\n ok: null,\n at: null,\n error: null,\n};\n\nexport function recordHeartbeatResult(\n result: { ok: true } | { ok: false; error: string },\n): void {\n lastHeartbeat = {\n attempted: true,\n ok: result.ok,\n at: new Date().toISOString(),\n error: result.ok ? null : result.error,\n };\n}\n\nexport function getLastHeartbeat(): ForgeHeartbeatState {\n return { ...lastHeartbeat };\n}\n\nexport function buildForgeSurfaceStatus(opts: {\n handle: string | null;\n publicBaseUrl: string | null;\n gatewayUrl: string | null;\n fulfillMounted: boolean;\n fulfillPath: string;\n agentBasePath: string;\n skipPayment: boolean;\n actions: Array<{ name: string; path: string; price: string }>;\n}): ForgeSurfaceStatus {\n const fulfillPath = opts.fulfillPath.replace(/\\/$/, \"\") || \"/forge/fulfill\";\n const agentBasePath = opts.agentBasePath.replace(/\\/$/, \"\") || \"/agent\";\n const ready =\n opts.fulfillMounted &&\n Boolean(opts.publicBaseUrl) &&\n opts.actions.length > 0;\n\n return {\n ok: true,\n forge: true,\n version: PACKAGE_VERSION,\n handle: opts.handle,\n publicBaseUrl: opts.publicBaseUrl,\n gatewayUrl: opts.gatewayUrl,\n fulfillMounted: opts.fulfillMounted,\n fulfillPath,\n agentBasePath,\n skipPayment: opts.skipPayment,\n actions: opts.actions,\n ready,\n heartbeat: getLastHeartbeat(),\n endpoints: {\n health: \"/forge/health\",\n status: \"/forge/status\",\n wellKnown: \"/.well-known/clawcash.json\",\n fulfill: fulfillPath,\n agent: agentBasePath,\n },\n };\n}\n\nexport function buildWellKnownDocument(status: ForgeSurfaceStatus): {\n forge: true;\n version: string;\n health: string;\n status: string;\n handle: string | null;\n ready: boolean;\n} {\n return {\n forge: true,\n version: status.version,\n health: status.endpoints.health,\n status: status.endpoints.status,\n handle: status.handle,\n ready: status.ready,\n };\n}\n\nexport function forgeSdkVersion(): string {\n return PACKAGE_VERSION;\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
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Application, Router } from 'express';
|
|
1
|
+
import { Application, Router, Request, Response, NextFunction } from 'express';
|
|
2
2
|
|
|
3
3
|
type SchemaFieldType = "string" | "url" | "number" | "boolean";
|
|
4
4
|
type InputSchema = Record<string, SchemaFieldType>;
|
|
@@ -73,15 +73,21 @@ interface MountOptions {
|
|
|
73
73
|
skipPayment?: boolean;
|
|
74
74
|
/**
|
|
75
75
|
* Optional gateway heartbeat after mount.
|
|
76
|
-
* Prefer env: GATEWAY_URL, FORGE_HANDLE,
|
|
76
|
+
* Prefer env: GATEWAY_URL, FORGE_HANDLE, FORGE_API_KEY, PUBLIC_BASE_URL.
|
|
77
77
|
*/
|
|
78
78
|
gateway?: {
|
|
79
79
|
url?: string;
|
|
80
80
|
handle?: string;
|
|
81
|
+
/** @deprecated Prefer FORGE_API_KEY env — per-project key from Forge dashboard. */
|
|
81
82
|
registerSecret?: string;
|
|
82
83
|
/** Merchant public base URL for fulfillment. */
|
|
83
84
|
publicBaseUrl?: string;
|
|
84
85
|
};
|
|
86
|
+
/**
|
|
87
|
+
* Path prefix for private fulfillment (gateway → merchant after x402).
|
|
88
|
+
* Defaults to /forge/fulfill. Not end-user / x402 routes.
|
|
89
|
+
*/
|
|
90
|
+
fulfillPath?: string;
|
|
85
91
|
}
|
|
86
92
|
interface SkillMeta {
|
|
87
93
|
name: string;
|
|
@@ -98,13 +104,13 @@ declare function defineAgentService<TInput extends Record<string, unknown> = Rec
|
|
|
98
104
|
|
|
99
105
|
/**
|
|
100
106
|
* Mount Forge agent services on an Express app.
|
|
101
|
-
*
|
|
107
|
+
* - GET /forge/health, GET /forge/status, GET /.well-known/clawcash.json — liveliness
|
|
108
|
+
* - POST {basePath}/{kebab-name} — optional public x402 agent surface
|
|
109
|
+
* - POST /forge/fulfill/{kebab-name} — private gateway fulfillment after payment
|
|
102
110
|
*
|
|
103
111
|
* payTo resolves automatically: PAY_TO env (optional) → Forge API → baked fallback.
|
|
104
|
-
* Merchants should not need to configure PAY_TO on Railway for the happy path.
|
|
105
112
|
*/
|
|
106
113
|
declare function mountAgentServices(app: Application, services: DefinedAgentService[], options: MountOptions): Promise<Router>;
|
|
107
|
-
/** Express-compatible middleware alias used in docs. */
|
|
108
114
|
declare function createForgeRouter(services: DefinedAgentService[], options: MountOptions): (app: Application) => Promise<Router>;
|
|
109
115
|
|
|
110
116
|
/**
|
|
@@ -140,4 +146,89 @@ declare function heartbeatGateway(opts: {
|
|
|
140
146
|
registerSecret: string;
|
|
141
147
|
}): Promise<void>;
|
|
142
148
|
|
|
143
|
-
|
|
149
|
+
/**
|
|
150
|
+
* Merchant-side Forge surface status — public probes for deploy / go-live checks.
|
|
151
|
+
*/
|
|
152
|
+
type ForgeHeartbeatState = {
|
|
153
|
+
attempted: boolean;
|
|
154
|
+
ok: boolean | null;
|
|
155
|
+
at: string | null;
|
|
156
|
+
error: string | null;
|
|
157
|
+
};
|
|
158
|
+
type ForgeSurfaceStatus = {
|
|
159
|
+
ok: boolean;
|
|
160
|
+
forge: true;
|
|
161
|
+
version: string;
|
|
162
|
+
handle: string | null;
|
|
163
|
+
publicBaseUrl: string | null;
|
|
164
|
+
gatewayUrl: string | null;
|
|
165
|
+
/** True when FORGE_API_KEY is set so /forge/fulfill is mounted. */
|
|
166
|
+
fulfillMounted: boolean;
|
|
167
|
+
fulfillPath: string;
|
|
168
|
+
agentBasePath: string;
|
|
169
|
+
skipPayment: boolean;
|
|
170
|
+
actions: Array<{
|
|
171
|
+
name: string;
|
|
172
|
+
path: string;
|
|
173
|
+
price: string;
|
|
174
|
+
}>;
|
|
175
|
+
/** Env + fulfill ready — merchant can accept gateway fulfillment. */
|
|
176
|
+
ready: boolean;
|
|
177
|
+
heartbeat: ForgeHeartbeatState;
|
|
178
|
+
endpoints: {
|
|
179
|
+
health: string;
|
|
180
|
+
status: string;
|
|
181
|
+
wellKnown: string;
|
|
182
|
+
fulfill: string;
|
|
183
|
+
agent: string;
|
|
184
|
+
};
|
|
185
|
+
};
|
|
186
|
+
declare function recordHeartbeatResult(result: {
|
|
187
|
+
ok: true;
|
|
188
|
+
} | {
|
|
189
|
+
ok: false;
|
|
190
|
+
error: string;
|
|
191
|
+
}): void;
|
|
192
|
+
declare function getLastHeartbeat(): ForgeHeartbeatState;
|
|
193
|
+
declare function buildForgeSurfaceStatus(opts: {
|
|
194
|
+
handle: string | null;
|
|
195
|
+
publicBaseUrl: string | null;
|
|
196
|
+
gatewayUrl: string | null;
|
|
197
|
+
fulfillMounted: boolean;
|
|
198
|
+
fulfillPath: string;
|
|
199
|
+
agentBasePath: string;
|
|
200
|
+
skipPayment: boolean;
|
|
201
|
+
actions: Array<{
|
|
202
|
+
name: string;
|
|
203
|
+
path: string;
|
|
204
|
+
price: string;
|
|
205
|
+
}>;
|
|
206
|
+
}): ForgeSurfaceStatus;
|
|
207
|
+
declare function buildWellKnownDocument(status: ForgeSurfaceStatus): {
|
|
208
|
+
forge: true;
|
|
209
|
+
version: string;
|
|
210
|
+
health: string;
|
|
211
|
+
status: string;
|
|
212
|
+
handle: string | null;
|
|
213
|
+
ready: boolean;
|
|
214
|
+
};
|
|
215
|
+
declare function forgeSdkVersion(): string;
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* Per-project Forge API key for gateway → merchant fulfillment.
|
|
219
|
+
* Merchants set FORGE_API_KEY from the Forge dashboard (never the shared
|
|
220
|
+
* GATEWAY_REGISTER_SECRET — that is Forge↔gateway platform auth only).
|
|
221
|
+
*
|
|
222
|
+
* GATEWAY_REGISTER_SECRET is accepted as a deprecated fallback for older deploys.
|
|
223
|
+
*/
|
|
224
|
+
declare function getForgeApiKey(explicit?: string | null): string | null;
|
|
225
|
+
/** @deprecated Use getForgeApiKey */
|
|
226
|
+
declare function getForgeGatewaySecret(explicit?: string | null): string | null;
|
|
227
|
+
declare function isForgeGatewayRequest(req: Request, secret?: string | null): boolean;
|
|
228
|
+
/** Synthetic user id for DB rows created by gateway-driven fulfillment. */
|
|
229
|
+
declare const FORGE_GATEWAY_USER_ID = "forge_gateway_agent";
|
|
230
|
+
declare function requireForgeGateway(secret?: string | null): (req: Request, res: Response, next: NextFunction) => void;
|
|
231
|
+
/** Headers merchant execute() should send on loopback calls during fulfillment. */
|
|
232
|
+
declare function forgeGatewayLoopbackHeaders(secret?: string | null): Record<string, string>;
|
|
233
|
+
|
|
234
|
+
export { type AgentServiceConfig, type AgentServiceContext, type DefinedAgentService, FORGE_GATEWAY_USER_ID, type ForgeHeartbeatState, type ForgePayToSource, type ForgeSurfaceStatus, type InputSchema, type MountOptions, type OutputSchema, type PaymentConfig, type SchemaFieldType, type SkillMeta, type WaitUntilOptions, buildForgeSurfaceStatus, buildWellKnownDocument, createContext, createForgeRouter, defineAgentService, forgeGatewayLoopbackHeaders, forgeSdkVersion, generateSkillMarkdown, getForgeApiKey, getForgeGatewaySecret, getLastHeartbeat, heartbeatGateway, isForgeGatewayRequest, isZeroAddress, mountAgentServices, recordHeartbeatResult, requireForgeGateway, resolvePayTo, toKebabCase, waitUntil };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Application, Router } from 'express';
|
|
1
|
+
import { Application, Router, Request, Response, NextFunction } from 'express';
|
|
2
2
|
|
|
3
3
|
type SchemaFieldType = "string" | "url" | "number" | "boolean";
|
|
4
4
|
type InputSchema = Record<string, SchemaFieldType>;
|
|
@@ -73,15 +73,21 @@ interface MountOptions {
|
|
|
73
73
|
skipPayment?: boolean;
|
|
74
74
|
/**
|
|
75
75
|
* Optional gateway heartbeat after mount.
|
|
76
|
-
* Prefer env: GATEWAY_URL, FORGE_HANDLE,
|
|
76
|
+
* Prefer env: GATEWAY_URL, FORGE_HANDLE, FORGE_API_KEY, PUBLIC_BASE_URL.
|
|
77
77
|
*/
|
|
78
78
|
gateway?: {
|
|
79
79
|
url?: string;
|
|
80
80
|
handle?: string;
|
|
81
|
+
/** @deprecated Prefer FORGE_API_KEY env — per-project key from Forge dashboard. */
|
|
81
82
|
registerSecret?: string;
|
|
82
83
|
/** Merchant public base URL for fulfillment. */
|
|
83
84
|
publicBaseUrl?: string;
|
|
84
85
|
};
|
|
86
|
+
/**
|
|
87
|
+
* Path prefix for private fulfillment (gateway → merchant after x402).
|
|
88
|
+
* Defaults to /forge/fulfill. Not end-user / x402 routes.
|
|
89
|
+
*/
|
|
90
|
+
fulfillPath?: string;
|
|
85
91
|
}
|
|
86
92
|
interface SkillMeta {
|
|
87
93
|
name: string;
|
|
@@ -98,13 +104,13 @@ declare function defineAgentService<TInput extends Record<string, unknown> = Rec
|
|
|
98
104
|
|
|
99
105
|
/**
|
|
100
106
|
* Mount Forge agent services on an Express app.
|
|
101
|
-
*
|
|
107
|
+
* - GET /forge/health, GET /forge/status, GET /.well-known/clawcash.json — liveliness
|
|
108
|
+
* - POST {basePath}/{kebab-name} — optional public x402 agent surface
|
|
109
|
+
* - POST /forge/fulfill/{kebab-name} — private gateway fulfillment after payment
|
|
102
110
|
*
|
|
103
111
|
* payTo resolves automatically: PAY_TO env (optional) → Forge API → baked fallback.
|
|
104
|
-
* Merchants should not need to configure PAY_TO on Railway for the happy path.
|
|
105
112
|
*/
|
|
106
113
|
declare function mountAgentServices(app: Application, services: DefinedAgentService[], options: MountOptions): Promise<Router>;
|
|
107
|
-
/** Express-compatible middleware alias used in docs. */
|
|
108
114
|
declare function createForgeRouter(services: DefinedAgentService[], options: MountOptions): (app: Application) => Promise<Router>;
|
|
109
115
|
|
|
110
116
|
/**
|
|
@@ -140,4 +146,89 @@ declare function heartbeatGateway(opts: {
|
|
|
140
146
|
registerSecret: string;
|
|
141
147
|
}): Promise<void>;
|
|
142
148
|
|
|
143
|
-
|
|
149
|
+
/**
|
|
150
|
+
* Merchant-side Forge surface status — public probes for deploy / go-live checks.
|
|
151
|
+
*/
|
|
152
|
+
type ForgeHeartbeatState = {
|
|
153
|
+
attempted: boolean;
|
|
154
|
+
ok: boolean | null;
|
|
155
|
+
at: string | null;
|
|
156
|
+
error: string | null;
|
|
157
|
+
};
|
|
158
|
+
type ForgeSurfaceStatus = {
|
|
159
|
+
ok: boolean;
|
|
160
|
+
forge: true;
|
|
161
|
+
version: string;
|
|
162
|
+
handle: string | null;
|
|
163
|
+
publicBaseUrl: string | null;
|
|
164
|
+
gatewayUrl: string | null;
|
|
165
|
+
/** True when FORGE_API_KEY is set so /forge/fulfill is mounted. */
|
|
166
|
+
fulfillMounted: boolean;
|
|
167
|
+
fulfillPath: string;
|
|
168
|
+
agentBasePath: string;
|
|
169
|
+
skipPayment: boolean;
|
|
170
|
+
actions: Array<{
|
|
171
|
+
name: string;
|
|
172
|
+
path: string;
|
|
173
|
+
price: string;
|
|
174
|
+
}>;
|
|
175
|
+
/** Env + fulfill ready — merchant can accept gateway fulfillment. */
|
|
176
|
+
ready: boolean;
|
|
177
|
+
heartbeat: ForgeHeartbeatState;
|
|
178
|
+
endpoints: {
|
|
179
|
+
health: string;
|
|
180
|
+
status: string;
|
|
181
|
+
wellKnown: string;
|
|
182
|
+
fulfill: string;
|
|
183
|
+
agent: string;
|
|
184
|
+
};
|
|
185
|
+
};
|
|
186
|
+
declare function recordHeartbeatResult(result: {
|
|
187
|
+
ok: true;
|
|
188
|
+
} | {
|
|
189
|
+
ok: false;
|
|
190
|
+
error: string;
|
|
191
|
+
}): void;
|
|
192
|
+
declare function getLastHeartbeat(): ForgeHeartbeatState;
|
|
193
|
+
declare function buildForgeSurfaceStatus(opts: {
|
|
194
|
+
handle: string | null;
|
|
195
|
+
publicBaseUrl: string | null;
|
|
196
|
+
gatewayUrl: string | null;
|
|
197
|
+
fulfillMounted: boolean;
|
|
198
|
+
fulfillPath: string;
|
|
199
|
+
agentBasePath: string;
|
|
200
|
+
skipPayment: boolean;
|
|
201
|
+
actions: Array<{
|
|
202
|
+
name: string;
|
|
203
|
+
path: string;
|
|
204
|
+
price: string;
|
|
205
|
+
}>;
|
|
206
|
+
}): ForgeSurfaceStatus;
|
|
207
|
+
declare function buildWellKnownDocument(status: ForgeSurfaceStatus): {
|
|
208
|
+
forge: true;
|
|
209
|
+
version: string;
|
|
210
|
+
health: string;
|
|
211
|
+
status: string;
|
|
212
|
+
handle: string | null;
|
|
213
|
+
ready: boolean;
|
|
214
|
+
};
|
|
215
|
+
declare function forgeSdkVersion(): string;
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* Per-project Forge API key for gateway → merchant fulfillment.
|
|
219
|
+
* Merchants set FORGE_API_KEY from the Forge dashboard (never the shared
|
|
220
|
+
* GATEWAY_REGISTER_SECRET — that is Forge↔gateway platform auth only).
|
|
221
|
+
*
|
|
222
|
+
* GATEWAY_REGISTER_SECRET is accepted as a deprecated fallback for older deploys.
|
|
223
|
+
*/
|
|
224
|
+
declare function getForgeApiKey(explicit?: string | null): string | null;
|
|
225
|
+
/** @deprecated Use getForgeApiKey */
|
|
226
|
+
declare function getForgeGatewaySecret(explicit?: string | null): string | null;
|
|
227
|
+
declare function isForgeGatewayRequest(req: Request, secret?: string | null): boolean;
|
|
228
|
+
/** Synthetic user id for DB rows created by gateway-driven fulfillment. */
|
|
229
|
+
declare const FORGE_GATEWAY_USER_ID = "forge_gateway_agent";
|
|
230
|
+
declare function requireForgeGateway(secret?: string | null): (req: Request, res: Response, next: NextFunction) => void;
|
|
231
|
+
/** Headers merchant execute() should send on loopback calls during fulfillment. */
|
|
232
|
+
declare function forgeGatewayLoopbackHeaders(secret?: string | null): Record<string, string>;
|
|
233
|
+
|
|
234
|
+
export { type AgentServiceConfig, type AgentServiceContext, type DefinedAgentService, FORGE_GATEWAY_USER_ID, type ForgeHeartbeatState, type ForgePayToSource, type ForgeSurfaceStatus, type InputSchema, type MountOptions, type OutputSchema, type PaymentConfig, type SchemaFieldType, type SkillMeta, type WaitUntilOptions, buildForgeSurfaceStatus, buildWellKnownDocument, createContext, createForgeRouter, defineAgentService, forgeGatewayLoopbackHeaders, forgeSdkVersion, generateSkillMarkdown, getForgeApiKey, getForgeGatewaySecret, getLastHeartbeat, heartbeatGateway, isForgeGatewayRequest, isZeroAddress, mountAgentServices, recordHeartbeatResult, requireForgeGateway, resolvePayTo, toKebabCase, waitUntil };
|
package/dist/index.js
CHANGED
|
@@ -64,6 +64,44 @@ async function heartbeatGateway(opts) {
|
|
|
64
64
|
}
|
|
65
65
|
}
|
|
66
66
|
|
|
67
|
+
// src/gateway-auth.ts
|
|
68
|
+
function getForgeApiKey(explicit) {
|
|
69
|
+
const value = (explicit ?? process.env.FORGE_API_KEY ?? process.env.GATEWAY_REGISTER_SECRET)?.trim();
|
|
70
|
+
if (!value || value.length < 16) return null;
|
|
71
|
+
return value;
|
|
72
|
+
}
|
|
73
|
+
function getForgeGatewaySecret(explicit) {
|
|
74
|
+
return getForgeApiKey(explicit);
|
|
75
|
+
}
|
|
76
|
+
function isForgeGatewayRequest(req, secret) {
|
|
77
|
+
const expected = getForgeApiKey(secret);
|
|
78
|
+
if (!expected) return false;
|
|
79
|
+
const header = req.header("authorization") ?? "";
|
|
80
|
+
const bearer = header.toLowerCase().startsWith("bearer ") ? header.slice(7).trim() : "";
|
|
81
|
+
const alt = req.header("x-forge-api-key")?.trim() || req.header("x-clawcash-gateway")?.trim() || "";
|
|
82
|
+
return bearer === expected || alt === expected;
|
|
83
|
+
}
|
|
84
|
+
var FORGE_GATEWAY_USER_ID = "forge_gateway_agent";
|
|
85
|
+
function requireForgeGateway(secret) {
|
|
86
|
+
return (req, res, next) => {
|
|
87
|
+
if (!isForgeGatewayRequest(req, secret)) {
|
|
88
|
+
res.status(401).json({
|
|
89
|
+
error: "Unauthorized \u2014 Forge API key required (FORGE_API_KEY from Forge dashboard)"
|
|
90
|
+
});
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
next();
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
function forgeGatewayLoopbackHeaders(secret) {
|
|
97
|
+
const expected = getForgeApiKey(secret);
|
|
98
|
+
if (!expected) return {};
|
|
99
|
+
return {
|
|
100
|
+
Authorization: `Bearer ${expected}`,
|
|
101
|
+
"X-Forge-Api-Key": expected
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
|
|
67
105
|
// src/payto.ts
|
|
68
106
|
var ZERO = /^0x0{40}$/i;
|
|
69
107
|
function isZeroAddress(address) {
|
|
@@ -124,6 +162,66 @@ async function resolvePayTo(opts) {
|
|
|
124
162
|
);
|
|
125
163
|
}
|
|
126
164
|
|
|
165
|
+
// src/status.ts
|
|
166
|
+
var PACKAGE_VERSION = "0.1.7";
|
|
167
|
+
var lastHeartbeat = {
|
|
168
|
+
attempted: false,
|
|
169
|
+
ok: null,
|
|
170
|
+
at: null,
|
|
171
|
+
error: null
|
|
172
|
+
};
|
|
173
|
+
function recordHeartbeatResult(result) {
|
|
174
|
+
lastHeartbeat = {
|
|
175
|
+
attempted: true,
|
|
176
|
+
ok: result.ok,
|
|
177
|
+
at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
178
|
+
error: result.ok ? null : result.error
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
function getLastHeartbeat() {
|
|
182
|
+
return { ...lastHeartbeat };
|
|
183
|
+
}
|
|
184
|
+
function buildForgeSurfaceStatus(opts) {
|
|
185
|
+
const fulfillPath = opts.fulfillPath.replace(/\/$/, "") || "/forge/fulfill";
|
|
186
|
+
const agentBasePath = opts.agentBasePath.replace(/\/$/, "") || "/agent";
|
|
187
|
+
const ready = opts.fulfillMounted && Boolean(opts.publicBaseUrl) && opts.actions.length > 0;
|
|
188
|
+
return {
|
|
189
|
+
ok: true,
|
|
190
|
+
forge: true,
|
|
191
|
+
version: PACKAGE_VERSION,
|
|
192
|
+
handle: opts.handle,
|
|
193
|
+
publicBaseUrl: opts.publicBaseUrl,
|
|
194
|
+
gatewayUrl: opts.gatewayUrl,
|
|
195
|
+
fulfillMounted: opts.fulfillMounted,
|
|
196
|
+
fulfillPath,
|
|
197
|
+
agentBasePath,
|
|
198
|
+
skipPayment: opts.skipPayment,
|
|
199
|
+
actions: opts.actions,
|
|
200
|
+
ready,
|
|
201
|
+
heartbeat: getLastHeartbeat(),
|
|
202
|
+
endpoints: {
|
|
203
|
+
health: "/forge/health",
|
|
204
|
+
status: "/forge/status",
|
|
205
|
+
wellKnown: "/.well-known/clawcash.json",
|
|
206
|
+
fulfill: fulfillPath,
|
|
207
|
+
agent: agentBasePath
|
|
208
|
+
}
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
function buildWellKnownDocument(status) {
|
|
212
|
+
return {
|
|
213
|
+
forge: true,
|
|
214
|
+
version: status.version,
|
|
215
|
+
health: status.endpoints.health,
|
|
216
|
+
status: status.endpoints.status,
|
|
217
|
+
handle: status.handle,
|
|
218
|
+
ready: status.ready
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
function forgeSdkVersion() {
|
|
222
|
+
return PACKAGE_VERSION;
|
|
223
|
+
}
|
|
224
|
+
|
|
127
225
|
// src/mount.ts
|
|
128
226
|
var DEFAULT_NETWORK = "eip155:8453";
|
|
129
227
|
var DEFAULT_FACILITATOR_URL = "https://facilitator.xpay.sh";
|
|
@@ -185,6 +283,81 @@ function formatPrice(amount) {
|
|
|
185
283
|
const normalized = amount.startsWith("$") ? amount.slice(1) : amount;
|
|
186
284
|
return `$${normalized}`;
|
|
187
285
|
}
|
|
286
|
+
function mountFulfillmentRouter(app, services, options) {
|
|
287
|
+
const secret = getForgeApiKey(options.gateway?.registerSecret);
|
|
288
|
+
if (!secret) {
|
|
289
|
+
console.warn(
|
|
290
|
+
"[forge] /forge/fulfill not mounted \u2014 set FORGE_API_KEY (from Forge dashboard, min 16 chars) so the gateway can fulfill after payment"
|
|
291
|
+
);
|
|
292
|
+
return;
|
|
293
|
+
}
|
|
294
|
+
const fulfillPath = (options.fulfillPath ?? "/forge/fulfill").replace(/\/$/, "") || "/forge/fulfill";
|
|
295
|
+
const router = createRouter();
|
|
296
|
+
const context = createContext();
|
|
297
|
+
const gate = requireForgeGateway(secret);
|
|
298
|
+
for (const service of services) {
|
|
299
|
+
router.post(
|
|
300
|
+
`/${service.path}`,
|
|
301
|
+
gate,
|
|
302
|
+
async (req, res) => {
|
|
303
|
+
try {
|
|
304
|
+
const validated = validateInput(req.body, service.input);
|
|
305
|
+
if (!validated.ok) {
|
|
306
|
+
res.status(400).json({ error: validated.error });
|
|
307
|
+
return;
|
|
308
|
+
}
|
|
309
|
+
const output = await service.execute(validated.value, context);
|
|
310
|
+
res.json(output);
|
|
311
|
+
} catch (err) {
|
|
312
|
+
const message = err instanceof Error ? err.message : "Internal error";
|
|
313
|
+
console.error(`[forge] fulfill ${service.name} failed:`, err);
|
|
314
|
+
res.status(500).json({ error: message });
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
);
|
|
318
|
+
}
|
|
319
|
+
app.use(fulfillPath, router);
|
|
320
|
+
console.log(
|
|
321
|
+
`[forge] fulfillment mounted at POST ${fulfillPath}/{action} (gateway secret)`
|
|
322
|
+
);
|
|
323
|
+
}
|
|
324
|
+
function mountForgeStatusRoutes(app, services, options, opts) {
|
|
325
|
+
const getStatus = () => buildForgeSurfaceStatus({
|
|
326
|
+
handle: opts.handle,
|
|
327
|
+
publicBaseUrl: opts.publicBaseUrl,
|
|
328
|
+
gatewayUrl: opts.gatewayUrl,
|
|
329
|
+
fulfillMounted: opts.fulfillMounted,
|
|
330
|
+
fulfillPath: opts.fulfillPath,
|
|
331
|
+
agentBasePath: opts.agentBasePath,
|
|
332
|
+
skipPayment: Boolean(options.skipPayment),
|
|
333
|
+
actions: services.map((s) => ({
|
|
334
|
+
name: s.name,
|
|
335
|
+
path: s.path,
|
|
336
|
+
price: s.payment.amount
|
|
337
|
+
}))
|
|
338
|
+
});
|
|
339
|
+
app.get("/forge/health", (_req, res) => {
|
|
340
|
+
const status = getStatus();
|
|
341
|
+
res.json({
|
|
342
|
+
ok: true,
|
|
343
|
+
forge: true,
|
|
344
|
+
ready: status.ready,
|
|
345
|
+
handle: status.handle,
|
|
346
|
+
version: status.version,
|
|
347
|
+
actions: status.actions.map((a) => a.name)
|
|
348
|
+
});
|
|
349
|
+
});
|
|
350
|
+
app.get("/forge/status", (_req, res) => {
|
|
351
|
+
const status = getStatus();
|
|
352
|
+
res.status(status.ready ? 200 : 503).json(status);
|
|
353
|
+
});
|
|
354
|
+
app.get("/.well-known/clawcash.json", (_req, res) => {
|
|
355
|
+
res.json(buildWellKnownDocument(getStatus()));
|
|
356
|
+
});
|
|
357
|
+
console.log(
|
|
358
|
+
"[forge] liveliness: GET /forge/health, GET /forge/status, GET /.well-known/clawcash.json"
|
|
359
|
+
);
|
|
360
|
+
}
|
|
188
361
|
async function mountAgentServices(app, services, options) {
|
|
189
362
|
let payTo = "";
|
|
190
363
|
if (!options.skipPayment) {
|
|
@@ -201,6 +374,7 @@ async function mountAgentServices(app, services, options) {
|
|
|
201
374
|
}
|
|
202
375
|
}
|
|
203
376
|
const basePath = (options.basePath ?? "/agent").replace(/\/$/, "") || "/agent";
|
|
377
|
+
const fulfillPath = (options.fulfillPath ?? "/forge/fulfill").replace(/\/$/, "") || "/forge/fulfill";
|
|
204
378
|
const router = createRouter();
|
|
205
379
|
const context = createContext();
|
|
206
380
|
const routes = {};
|
|
@@ -248,17 +422,33 @@ async function mountAgentServices(app, services, options) {
|
|
|
248
422
|
app.use(paymentMiddleware(routes, resourceServer, void 0, void 0, true));
|
|
249
423
|
}
|
|
250
424
|
app.use(basePath, router);
|
|
251
|
-
const
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
425
|
+
const fulfillMounted = Boolean(
|
|
426
|
+
getForgeApiKey(options.gateway?.registerSecret)
|
|
427
|
+
);
|
|
428
|
+
mountFulfillmentRouter(app, services, options);
|
|
429
|
+
const gatewayUrl = options.gateway?.url?.trim() || process.env.GATEWAY_URL?.trim() || null;
|
|
430
|
+
const handle = options.gateway?.handle?.trim() || process.env.FORGE_HANDLE?.trim() || null;
|
|
431
|
+
const registerSecret = options.gateway?.registerSecret?.trim() || process.env.FORGE_API_KEY?.trim() || process.env.GATEWAY_REGISTER_SECRET?.trim();
|
|
432
|
+
const publicBaseUrl = options.gateway?.publicBaseUrl?.trim() || process.env.PUBLIC_BASE_URL?.trim() || (process.env.RAILWAY_PUBLIC_DOMAIN ? `https://${process.env.RAILWAY_PUBLIC_DOMAIN}` : "") || null;
|
|
433
|
+
mountForgeStatusRoutes(app, services, options, {
|
|
434
|
+
fulfillMounted,
|
|
435
|
+
fulfillPath,
|
|
436
|
+
agentBasePath: basePath,
|
|
437
|
+
handle,
|
|
438
|
+
publicBaseUrl,
|
|
439
|
+
gatewayUrl
|
|
440
|
+
});
|
|
255
441
|
if (gatewayUrl && handle && registerSecret && publicBaseUrl) {
|
|
256
442
|
void heartbeatGateway({
|
|
257
443
|
gatewayUrl,
|
|
258
444
|
handle,
|
|
259
445
|
baseUrl: publicBaseUrl,
|
|
260
446
|
registerSecret
|
|
447
|
+
}).then(() => {
|
|
448
|
+
recordHeartbeatResult({ ok: true });
|
|
261
449
|
}).catch((err) => {
|
|
450
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
451
|
+
recordHeartbeatResult({ ok: false, error: message });
|
|
262
452
|
console.warn("[forge] gateway heartbeat skipped:", err);
|
|
263
453
|
});
|
|
264
454
|
}
|
|
@@ -352,13 +542,24 @@ function generateSkillMarkdown(services, meta) {
|
|
|
352
542
|
return lines.join("\n");
|
|
353
543
|
}
|
|
354
544
|
export {
|
|
545
|
+
FORGE_GATEWAY_USER_ID,
|
|
546
|
+
buildForgeSurfaceStatus,
|
|
547
|
+
buildWellKnownDocument,
|
|
355
548
|
createContext,
|
|
356
549
|
createForgeRouter,
|
|
357
550
|
defineAgentService,
|
|
551
|
+
forgeGatewayLoopbackHeaders,
|
|
552
|
+
forgeSdkVersion,
|
|
358
553
|
generateSkillMarkdown,
|
|
554
|
+
getForgeApiKey,
|
|
555
|
+
getForgeGatewaySecret,
|
|
556
|
+
getLastHeartbeat,
|
|
359
557
|
heartbeatGateway,
|
|
558
|
+
isForgeGatewayRequest,
|
|
360
559
|
isZeroAddress,
|
|
361
560
|
mountAgentServices,
|
|
561
|
+
recordHeartbeatResult,
|
|
562
|
+
requireForgeGateway,
|
|
362
563
|
resolvePayTo,
|
|
363
564
|
toKebabCase,
|
|
364
565
|
waitUntil
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/define.ts","../src/mount.ts","../src/gateway.ts","../src/payto.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 { heartbeatGateway } from \"./gateway.js\";\nimport { isZeroAddress, resolvePayTo } from \"./payto.js\";\nimport type {\n DefinedAgentService,\n InputSchema,\n MountOptions,\n SchemaFieldType,\n} from \"./types.js\";\n\n/** Base mainnet — settled via the public XPay facilitator (no API keys). */\nconst DEFAULT_NETWORK = \"eip155:8453\" as const satisfies Network;\n/** Open facilitator: Base mainnet + Sepolia, no API keys. https://github.com/xpaysh/xpay-x402 */\nconst DEFAULT_FACILITATOR_URL = \"https://facilitator.xpay.sh\";\n\nfunction asNetwork(value: string | undefined): Network {\n return (value ?? DEFAULT_NETWORK) as Network;\n}\n\nfunction createFacilitatorClient(options: MountOptions): HTTPFacilitatorClient {\n return new HTTPFacilitatorClient({\n url: options.facilitatorUrl ?? DEFAULT_FACILITATOR_URL,\n createAuthHeaders: options.createAuthHeaders,\n });\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 *\n * payTo resolves automatically: PAY_TO env (optional) → Forge API → baked fallback.\n * Merchants should not need to configure PAY_TO on Railway for the happy path.\n */\nexport async function mountAgentServices(\n app: Application,\n services: DefinedAgentService[],\n options: MountOptions,\n): Promise<Router> {\n let payTo = \"\";\n if (!options.skipPayment) {\n const fallback =\n options.payToFallback?.trim() || options.payTo?.trim() || \"\";\n payTo = await resolvePayTo({\n fallback,\n payTo: options.payTo,\n forge: options.forge,\n });\n if (isZeroAddress(payTo)) {\n throw new Error(\n \"mountAgentServices: resolved payTo is the zero address. Re-run Forge to provision a wallet.\",\n );\n }\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,\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 = createFacilitatorClient(options);\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 app.use(paymentMiddleware(routes, resourceServer, undefined, undefined, true));\n }\n\n app.use(basePath, router);\n\n // Fire-and-forget baseUrl heartbeat so gateway fulfillment stays current.\n const gatewayUrl =\n options.gateway?.url?.trim() || process.env.GATEWAY_URL?.trim();\n const handle =\n options.gateway?.handle?.trim() || process.env.FORGE_HANDLE?.trim();\n const registerSecret =\n options.gateway?.registerSecret?.trim() ||\n process.env.GATEWAY_REGISTER_SECRET?.trim();\n const publicBaseUrl =\n options.gateway?.publicBaseUrl?.trim() ||\n process.env.PUBLIC_BASE_URL?.trim() ||\n (process.env.RAILWAY_PUBLIC_DOMAIN\n ? `https://${process.env.RAILWAY_PUBLIC_DOMAIN}`\n : \"\");\n\n if (gatewayUrl && handle && registerSecret && publicBaseUrl) {\n void heartbeatGateway({\n gatewayUrl,\n handle,\n baseUrl: publicBaseUrl,\n registerSecret,\n }).catch((err) => {\n console.warn(\"[forge] gateway heartbeat skipped:\", err);\n });\n }\n\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) => Promise<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","/**\n * Notify ClawCash gateway of this merchant's live public base URL.\n * Full action registration still happens from Forge \"Go live\".\n */\nexport async function heartbeatGateway(opts: {\n gatewayUrl: string;\n handle: string;\n baseUrl: string;\n registerSecret: string;\n}): Promise<void> {\n const gateway = opts.gatewayUrl.replace(/\\/$/, \"\");\n const res = await fetch(`${gateway}/register/heartbeat`, {\n method: \"POST\",\n headers: {\n \"content-type\": \"application/json\",\n authorization: `Bearer ${opts.registerSecret}`,\n },\n body: JSON.stringify({\n handle: opts.handle.trim().toLowerCase(),\n baseUrl: opts.baseUrl.replace(/\\/$/, \"\"),\n }),\n });\n if (!res.ok) {\n const text = await res.text();\n throw new Error(\n `gateway heartbeat failed (${res.status}): ${text.slice(0, 240)}`,\n );\n }\n}\n","import type { ForgePayToSource } from \"./types.js\";\n\nexport type { ForgePayToSource };\n\nconst ZERO = /^0x0{40}$/i;\n\nexport function isZeroAddress(address: string | undefined | null): boolean {\n if (!address) return true;\n return ZERO.test(address.trim());\n}\n\nfunction normalizeBaseUrl(url: string): string {\n return url.replace(/\\/$/, \"\");\n}\n\nfunction pickForgeBaseUrl(source?: ForgePayToSource): string | null {\n const fromEnv = process.env.FORGE_URL?.trim();\n if (fromEnv) return normalizeBaseUrl(fromEnv);\n\n const fromOpts = source?.baseUrl?.trim();\n if (!fromOpts) return null;\n\n const normalized = normalizeBaseUrl(fromOpts);\n // Never call a laptop Forge from a deployed merchant app.\n if (\n /localhost|127\\.0\\.0\\.1/i.test(normalized) &&\n process.env.NODE_ENV === \"production\"\n ) {\n return null;\n }\n return normalized;\n}\n\n/**\n * Resolve merchant payTo with minimal host config:\n * 1. `PAY_TO` env (optional override)\n * 2. Live address from Forge public API (when reachable)\n * 3. Baked-in fallback from the Forge PR\n */\nexport async function resolvePayTo(opts: {\n /** Baked-in address from Forge codegen (required fallback). */\n fallback: string;\n /** Optional explicit override (usually process.env.PAY_TO). */\n payTo?: string;\n forge?: ForgePayToSource;\n /** Fetch timeout ms. Defaults to 2500. */\n timeoutMs?: number;\n}): Promise<string> {\n const envOverride = process.env.PAY_TO?.trim();\n if (envOverride && !isZeroAddress(envOverride)) {\n return envOverride;\n }\n\n const explicit = opts.payTo?.trim();\n if (explicit && !isZeroAddress(explicit) && !opts.forge) {\n return explicit;\n }\n\n const baseUrl = pickForgeBaseUrl(opts.forge);\n if (baseUrl && opts.forge) {\n const url = `${baseUrl}/api/public/pay-to/${encodeURIComponent(opts.forge.owner)}/${encodeURIComponent(opts.forge.repo)}`;\n try {\n const controller = new AbortController();\n const timer = setTimeout(\n () => controller.abort(),\n opts.timeoutMs ?? 2500,\n );\n const res = await fetch(url, {\n signal: controller.signal,\n headers: { accept: \"application/json\" },\n });\n clearTimeout(timer);\n if (res.ok) {\n const json = (await res.json()) as { address?: string };\n if (json.address && !isZeroAddress(json.address)) {\n return json.address.trim();\n }\n }\n } catch (err) {\n console.warn(\"[forge] live payTo fetch failed, using fallback:\", err);\n }\n }\n\n if (explicit && !isZeroAddress(explicit)) return explicit;\n if (opts.fallback && !isZeroAddress(opts.fallback)) return opts.fallback;\n\n throw new Error(\n \"resolvePayTo: no usable address (set Forge wallet / PAY_TO, or re-run Forge PR)\",\n );\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":[]}
|
|
1
|
+
{"version":3,"sources":["../src/define.ts","../src/mount.ts","../src/gateway.ts","../src/gateway-auth.ts","../src/payto.ts","../src/status.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 { heartbeatGateway } from \"./gateway.js\";\nimport {\n getForgeApiKey,\n requireForgeGateway,\n} from \"./gateway-auth.js\";\nimport { isZeroAddress, resolvePayTo } from \"./payto.js\";\nimport {\n buildForgeSurfaceStatus,\n buildWellKnownDocument,\n recordHeartbeatResult,\n} from \"./status.js\";\nimport type {\n DefinedAgentService,\n InputSchema,\n MountOptions,\n SchemaFieldType,\n} from \"./types.js\";\n\n/** Base mainnet — settled via the public XPay facilitator (no API keys). */\nconst DEFAULT_NETWORK = \"eip155:8453\" as const satisfies Network;\n/** Open facilitator: Base mainnet + Sepolia, no API keys. https://github.com/xpaysh/xpay-x402 */\nconst DEFAULT_FACILITATOR_URL = \"https://facilitator.xpay.sh\";\n\nfunction asNetwork(value: string | undefined): Network {\n return (value ?? DEFAULT_NETWORK) as Network;\n}\n\nfunction createFacilitatorClient(options: MountOptions): HTTPFacilitatorClient {\n return new HTTPFacilitatorClient({\n url: options.facilitatorUrl ?? DEFAULT_FACILITATOR_URL,\n createAuthHeaders: options.createAuthHeaders,\n });\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\nfunction mountFulfillmentRouter(\n app: Application,\n services: DefinedAgentService[],\n options: MountOptions,\n): void {\n const secret = getForgeApiKey(options.gateway?.registerSecret);\n if (!secret) {\n console.warn(\n \"[forge] /forge/fulfill not mounted — set FORGE_API_KEY (from Forge dashboard, min 16 chars) so the gateway can fulfill after payment\",\n );\n return;\n }\n\n const fulfillPath =\n (options.fulfillPath ?? \"/forge/fulfill\").replace(/\\/$/, \"\") ||\n \"/forge/fulfill\";\n const router = createRouter();\n const context = createContext();\n const gate = requireForgeGateway(secret);\n\n for (const service of services) {\n router.post(\n `/${service.path}`,\n gate,\n 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 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] fulfill ${service.name} failed:`, err);\n res.status(500).json({ error: message });\n }\n },\n );\n }\n\n app.use(fulfillPath, router);\n console.log(\n `[forge] fulfillment mounted at POST ${fulfillPath}/{action} (gateway secret)`,\n );\n}\n\nfunction mountForgeStatusRoutes(\n app: Application,\n services: DefinedAgentService[],\n options: MountOptions,\n opts: {\n fulfillMounted: boolean;\n fulfillPath: string;\n agentBasePath: string;\n handle: string | null;\n publicBaseUrl: string | null;\n gatewayUrl: string | null;\n },\n): void {\n const getStatus = () =>\n buildForgeSurfaceStatus({\n handle: opts.handle,\n publicBaseUrl: opts.publicBaseUrl,\n gatewayUrl: opts.gatewayUrl,\n fulfillMounted: opts.fulfillMounted,\n fulfillPath: opts.fulfillPath,\n agentBasePath: opts.agentBasePath,\n skipPayment: Boolean(options.skipPayment),\n actions: services.map((s) => ({\n name: s.name,\n path: s.path,\n price: s.payment.amount,\n })),\n });\n\n app.get(\"/forge/health\", (_req: Request, res: Response) => {\n const status = getStatus();\n res.json({\n ok: true,\n forge: true as const,\n ready: status.ready,\n handle: status.handle,\n version: status.version,\n actions: status.actions.map((a) => a.name),\n });\n });\n\n app.get(\"/forge/status\", (_req: Request, res: Response) => {\n const status = getStatus();\n res.status(status.ready ? 200 : 503).json(status);\n });\n\n app.get(\"/.well-known/clawcash.json\", (_req: Request, res: Response) => {\n res.json(buildWellKnownDocument(getStatus()));\n });\n\n console.log(\n \"[forge] liveliness: GET /forge/health, GET /forge/status, GET /.well-known/clawcash.json\",\n );\n}\n\n/**\n * Mount Forge agent services on an Express app.\n * - GET /forge/health, GET /forge/status, GET /.well-known/clawcash.json — liveliness\n * - POST {basePath}/{kebab-name} — optional public x402 agent surface\n * - POST /forge/fulfill/{kebab-name} — private gateway fulfillment after payment\n *\n * payTo resolves automatically: PAY_TO env (optional) → Forge API → baked fallback.\n */\nexport async function mountAgentServices(\n app: Application,\n services: DefinedAgentService[],\n options: MountOptions,\n): Promise<Router> {\n let payTo = \"\";\n if (!options.skipPayment) {\n const fallback =\n options.payToFallback?.trim() || options.payTo?.trim() || \"\";\n payTo = await resolvePayTo({\n fallback,\n payTo: options.payTo,\n forge: options.forge,\n });\n if (isZeroAddress(payTo)) {\n throw new Error(\n \"mountAgentServices: resolved payTo is the zero address. Re-run Forge to provision a wallet.\",\n );\n }\n }\n\n const basePath = (options.basePath ?? \"/agent\").replace(/\\/$/, \"\") || \"/agent\";\n const fulfillPath =\n (options.fulfillPath ?? \"/forge/fulfill\").replace(/\\/$/, \"\") ||\n \"/forge/fulfill\";\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,\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 = createFacilitatorClient(options);\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 app.use(paymentMiddleware(routes, resourceServer, undefined, undefined, true));\n }\n\n app.use(basePath, router);\n const fulfillMounted = Boolean(\n getForgeApiKey(options.gateway?.registerSecret),\n );\n mountFulfillmentRouter(app, services, options);\n\n const gatewayUrl =\n options.gateway?.url?.trim() || process.env.GATEWAY_URL?.trim() || null;\n const handle =\n options.gateway?.handle?.trim() || process.env.FORGE_HANDLE?.trim() || null;\n const registerSecret =\n options.gateway?.registerSecret?.trim() ||\n process.env.FORGE_API_KEY?.trim() ||\n process.env.GATEWAY_REGISTER_SECRET?.trim();\n const publicBaseUrl =\n options.gateway?.publicBaseUrl?.trim() ||\n process.env.PUBLIC_BASE_URL?.trim() ||\n (process.env.RAILWAY_PUBLIC_DOMAIN\n ? `https://${process.env.RAILWAY_PUBLIC_DOMAIN}`\n : \"\") ||\n null;\n\n mountForgeStatusRoutes(app, services, options, {\n fulfillMounted,\n fulfillPath,\n agentBasePath: basePath,\n handle,\n publicBaseUrl,\n gatewayUrl,\n });\n\n if (gatewayUrl && handle && registerSecret && publicBaseUrl) {\n void heartbeatGateway({\n gatewayUrl,\n handle,\n baseUrl: publicBaseUrl,\n registerSecret,\n })\n .then(() => {\n recordHeartbeatResult({ ok: true });\n })\n .catch((err) => {\n const message = err instanceof Error ? err.message : String(err);\n recordHeartbeatResult({ ok: false, error: message });\n console.warn(\"[forge] gateway heartbeat skipped:\", err);\n });\n }\n\n return router;\n}\n\nexport function createForgeRouter(\n services: DefinedAgentService[],\n options: MountOptions,\n): (app: Application) => Promise<Router> {\n return (app) => mountAgentServices(app, services, options);\n}\n\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","/**\n * Notify ClawCash gateway of this merchant's live public base URL.\n * Full action registration still happens from Forge \"Go live\".\n */\nexport async function heartbeatGateway(opts: {\n gatewayUrl: string;\n handle: string;\n baseUrl: string;\n registerSecret: string;\n}): Promise<void> {\n const gateway = opts.gatewayUrl.replace(/\\/$/, \"\");\n const res = await fetch(`${gateway}/register/heartbeat`, {\n method: \"POST\",\n headers: {\n \"content-type\": \"application/json\",\n authorization: `Bearer ${opts.registerSecret}`,\n },\n body: JSON.stringify({\n handle: opts.handle.trim().toLowerCase(),\n baseUrl: opts.baseUrl.replace(/\\/$/, \"\"),\n }),\n });\n if (!res.ok) {\n const text = await res.text();\n throw new Error(\n `gateway heartbeat failed (${res.status}): ${text.slice(0, 240)}`,\n );\n }\n}\n","import type { NextFunction, Request, Response } from \"express\";\n\n/**\n * Per-project Forge API key for gateway → merchant fulfillment.\n * Merchants set FORGE_API_KEY from the Forge dashboard (never the shared\n * GATEWAY_REGISTER_SECRET — that is Forge↔gateway platform auth only).\n *\n * GATEWAY_REGISTER_SECRET is accepted as a deprecated fallback for older deploys.\n */\nexport function getForgeApiKey(explicit?: string | null): string | null {\n const value =\n (explicit ?? process.env.FORGE_API_KEY ?? process.env.GATEWAY_REGISTER_SECRET)?.trim();\n if (!value || value.length < 16) return null;\n return value;\n}\n\n/** @deprecated Use getForgeApiKey */\nexport function getForgeGatewaySecret(\n explicit?: string | null,\n): string | null {\n return getForgeApiKey(explicit);\n}\n\nexport function isForgeGatewayRequest(\n req: Request,\n secret?: string | null,\n): boolean {\n const expected = getForgeApiKey(secret);\n if (!expected) return false;\n const header = req.header(\"authorization\") ?? \"\";\n const bearer = header.toLowerCase().startsWith(\"bearer \")\n ? header.slice(7).trim()\n : \"\";\n const alt =\n req.header(\"x-forge-api-key\")?.trim() ||\n req.header(\"x-clawcash-gateway\")?.trim() ||\n \"\";\n return bearer === expected || alt === expected;\n}\n\n/** Synthetic user id for DB rows created by gateway-driven fulfillment. */\nexport const FORGE_GATEWAY_USER_ID = \"forge_gateway_agent\";\n\nexport function requireForgeGateway(\n secret?: string | null,\n): (req: Request, res: Response, next: NextFunction) => void {\n return (req, res, next) => {\n if (!isForgeGatewayRequest(req, secret)) {\n res.status(401).json({\n error:\n \"Unauthorized — Forge API key required (FORGE_API_KEY from Forge dashboard)\",\n });\n return;\n }\n next();\n };\n}\n\n/** Headers merchant execute() should send on loopback calls during fulfillment. */\nexport function forgeGatewayLoopbackHeaders(\n secret?: string | null,\n): Record<string, string> {\n const expected = getForgeApiKey(secret);\n if (!expected) return {};\n return {\n Authorization: `Bearer ${expected}`,\n \"X-Forge-Api-Key\": expected,\n };\n}\n","import type { ForgePayToSource } from \"./types.js\";\n\nexport type { ForgePayToSource };\n\nconst ZERO = /^0x0{40}$/i;\n\nexport function isZeroAddress(address: string | undefined | null): boolean {\n if (!address) return true;\n return ZERO.test(address.trim());\n}\n\nfunction normalizeBaseUrl(url: string): string {\n return url.replace(/\\/$/, \"\");\n}\n\nfunction pickForgeBaseUrl(source?: ForgePayToSource): string | null {\n const fromEnv = process.env.FORGE_URL?.trim();\n if (fromEnv) return normalizeBaseUrl(fromEnv);\n\n const fromOpts = source?.baseUrl?.trim();\n if (!fromOpts) return null;\n\n const normalized = normalizeBaseUrl(fromOpts);\n // Never call a laptop Forge from a deployed merchant app.\n if (\n /localhost|127\\.0\\.0\\.1/i.test(normalized) &&\n process.env.NODE_ENV === \"production\"\n ) {\n return null;\n }\n return normalized;\n}\n\n/**\n * Resolve merchant payTo with minimal host config:\n * 1. `PAY_TO` env (optional override)\n * 2. Live address from Forge public API (when reachable)\n * 3. Baked-in fallback from the Forge PR\n */\nexport async function resolvePayTo(opts: {\n /** Baked-in address from Forge codegen (required fallback). */\n fallback: string;\n /** Optional explicit override (usually process.env.PAY_TO). */\n payTo?: string;\n forge?: ForgePayToSource;\n /** Fetch timeout ms. Defaults to 2500. */\n timeoutMs?: number;\n}): Promise<string> {\n const envOverride = process.env.PAY_TO?.trim();\n if (envOverride && !isZeroAddress(envOverride)) {\n return envOverride;\n }\n\n const explicit = opts.payTo?.trim();\n if (explicit && !isZeroAddress(explicit) && !opts.forge) {\n return explicit;\n }\n\n const baseUrl = pickForgeBaseUrl(opts.forge);\n if (baseUrl && opts.forge) {\n const url = `${baseUrl}/api/public/pay-to/${encodeURIComponent(opts.forge.owner)}/${encodeURIComponent(opts.forge.repo)}`;\n try {\n const controller = new AbortController();\n const timer = setTimeout(\n () => controller.abort(),\n opts.timeoutMs ?? 2500,\n );\n const res = await fetch(url, {\n signal: controller.signal,\n headers: { accept: \"application/json\" },\n });\n clearTimeout(timer);\n if (res.ok) {\n const json = (await res.json()) as { address?: string };\n if (json.address && !isZeroAddress(json.address)) {\n return json.address.trim();\n }\n }\n } catch (err) {\n console.warn(\"[forge] live payTo fetch failed, using fallback:\", err);\n }\n }\n\n if (explicit && !isZeroAddress(explicit)) return explicit;\n if (opts.fallback && !isZeroAddress(opts.fallback)) return opts.fallback;\n\n throw new Error(\n \"resolvePayTo: no usable address (set Forge wallet / PAY_TO, or re-run Forge PR)\",\n );\n}\n","/**\n * Merchant-side Forge surface status — public probes for deploy / go-live checks.\n */\n\nexport type ForgeHeartbeatState = {\n attempted: boolean;\n ok: boolean | null;\n at: string | null;\n error: string | null;\n};\n\nexport type ForgeSurfaceStatus = {\n ok: boolean;\n forge: true;\n version: string;\n handle: string | null;\n publicBaseUrl: string | null;\n gatewayUrl: string | null;\n /** True when FORGE_API_KEY is set so /forge/fulfill is mounted. */\n fulfillMounted: boolean;\n fulfillPath: string;\n agentBasePath: string;\n skipPayment: boolean;\n actions: Array<{ name: string; path: string; price: string }>;\n /** Env + fulfill ready — merchant can accept gateway fulfillment. */\n ready: boolean;\n heartbeat: ForgeHeartbeatState;\n endpoints: {\n health: string;\n status: string;\n wellKnown: string;\n fulfill: string;\n agent: string;\n };\n};\n\nconst PACKAGE_VERSION = \"0.1.7\";\n\nlet lastHeartbeat: ForgeHeartbeatState = {\n attempted: false,\n ok: null,\n at: null,\n error: null,\n};\n\nexport function recordHeartbeatResult(\n result: { ok: true } | { ok: false; error: string },\n): void {\n lastHeartbeat = {\n attempted: true,\n ok: result.ok,\n at: new Date().toISOString(),\n error: result.ok ? null : result.error,\n };\n}\n\nexport function getLastHeartbeat(): ForgeHeartbeatState {\n return { ...lastHeartbeat };\n}\n\nexport function buildForgeSurfaceStatus(opts: {\n handle: string | null;\n publicBaseUrl: string | null;\n gatewayUrl: string | null;\n fulfillMounted: boolean;\n fulfillPath: string;\n agentBasePath: string;\n skipPayment: boolean;\n actions: Array<{ name: string; path: string; price: string }>;\n}): ForgeSurfaceStatus {\n const fulfillPath = opts.fulfillPath.replace(/\\/$/, \"\") || \"/forge/fulfill\";\n const agentBasePath = opts.agentBasePath.replace(/\\/$/, \"\") || \"/agent\";\n const ready =\n opts.fulfillMounted &&\n Boolean(opts.publicBaseUrl) &&\n opts.actions.length > 0;\n\n return {\n ok: true,\n forge: true,\n version: PACKAGE_VERSION,\n handle: opts.handle,\n publicBaseUrl: opts.publicBaseUrl,\n gatewayUrl: opts.gatewayUrl,\n fulfillMounted: opts.fulfillMounted,\n fulfillPath,\n agentBasePath,\n skipPayment: opts.skipPayment,\n actions: opts.actions,\n ready,\n heartbeat: getLastHeartbeat(),\n endpoints: {\n health: \"/forge/health\",\n status: \"/forge/status\",\n wellKnown: \"/.well-known/clawcash.json\",\n fulfill: fulfillPath,\n agent: agentBasePath,\n },\n };\n}\n\nexport function buildWellKnownDocument(status: ForgeSurfaceStatus): {\n forge: true;\n version: string;\n health: string;\n status: string;\n handle: string | null;\n ready: boolean;\n} {\n return {\n forge: true,\n version: status.version,\n health: status.endpoints.health,\n status: status.endpoints.status,\n handle: status.handle,\n ready: status.ready,\n };\n}\n\nexport function forgeSdkVersion(): string {\n return PACKAGE_VERSION;\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
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@clawcash/forge",
|
|
3
|
-
"version": "0.1.
|
|
4
|
-
"description": "ClawCash Forge —
|
|
3
|
+
"version": "0.1.7",
|
|
4
|
+
"description": "ClawCash Forge — make existing SaaS agent-native (define, mount, x402, gateway fulfill, health)",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.cjs",
|
|
7
7
|
"module": "./dist/index.js",
|