diff --git a/README.md b/README.md index 08a2d25..bcd1a51 100644 --- a/README.md +++ b/README.md @@ -29,7 +29,7 @@ Markdown file with frontmatter. Here is a contrived example: verify: hmac: header: X-My-Header-Signature - prefix: "sha256=" # optional: stripped before comparing the digest + prefix: "sha256=" # optional: stripped before comparing the digest secret: $MY_WEBHOOK_SECRET payload: contentType: json @@ -116,7 +116,7 @@ generated by an LLM. A `create-lure` skill is available in ### Per Request -1. The requested path is checked against registered lure paths. +1. The requested path is checked against registered lure paths. 2. On a hit, we immediately return a 204 response, to keep the response time as low as possible. 3. Webhook requests are copied and added to an in-memory queue for processing. diff --git a/SKILL.md b/SKILL.md index c9780a7..1635ab9 100644 --- a/SKILL.md +++ b/SKILL.md @@ -37,9 +37,9 @@ HMAC (e.g. GitHub, GitLab — computes SHA-256 over the request body): ```yaml verify: hmac: - header: X-Hub-Signature-256 # or query: param-name - prefix: "sha256=" # optional: stripped before comparing the digest - secret: $ENV_VAR_NAME # must be an environment variable reference + header: X-Hub-Signature-256 # or query: param-name + prefix: "sha256=" # optional: stripped before comparing the digest + secret: $ENV_VAR_NAME # must be an environment variable reference ``` Literal (e.g. Forgejo, Omi — compares value directly against the secret): @@ -47,15 +47,15 @@ Literal (e.g. Forgejo, Omi — compares value directly against the secret): ```yaml verify: literal: - header: Authorization # or query: uid - secret: $ENV_VAR_NAME # must be an environment variable reference + header: Authorization # or query: uid + secret: $ENV_VAR_NAME # must be an environment variable reference ``` ### `payload` (optional) ```yaml payload: - contentType: json # currently the only supported value + contentType: json # currently the only supported value ``` ### `config` (optional) diff --git a/package.json b/package.json index d90110e..b6b75f8 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,13 @@ "name": "lure", "version": "1.0.0", "description": "Process webhook events into LLM-consumable prompts", - "keywords": ["webhook", "llm", "prompt", "liquid", "template"], + "keywords": [ + "liquid", + "llm", + "prompt", + "template", + "webhook" + ], "license": "ISC", "author": "Graham Barber", "type": "module", diff --git a/packages/core/package.json b/packages/core/package.json index 40b143b..0a25fe1 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -2,9 +2,20 @@ "name": "@lure-hooks/core", "version": "1.0.0", "description": "Core webhook processing logic for Lure — verification, Liquid templating, and queue management", - "keywords": ["webhook", "llm", "prompt", "liquid", "template", "hmac", "verification"], + "keywords": [ + "hmac", + "liquid", + "llm", + "prompt", + "template", + "verification", + "webhook" + ], "license": "ISC", "author": "Graham Barber", + "files": [ + "dist" + ], "type": "module", "main": "./dist/index.js", "types": "./dist/index.d.ts", @@ -14,12 +25,10 @@ "types": "./dist/index.d.ts" } }, - "files": ["dist"], "scripts": { "build": "tsc --build", "test": "vitest run" }, - "packageManager": "pnpm@11.0.0-dev.1005", "dependencies": { "@standard-schema/spec": "^1.1.0", "arktype": "^2.2.0", @@ -30,5 +39,6 @@ }, "devDependencies": { "vitest": "^4.1.0" - } + }, + "packageManager": "pnpm@11.0.0-dev.1005" } diff --git a/packages/core/src/__tests__/loader.test.ts b/packages/core/src/__tests__/loader.test.ts index 74ea13e..2d37d29 100644 --- a/packages/core/src/__tests__/loader.test.ts +++ b/packages/core/src/__tests__/loader.test.ts @@ -70,7 +70,10 @@ describe("LureCache", () => { expect(original).toBeDefined(); // Write invalid content - await fs.writeFile(filePath, "---\nverify:\n hmac:\n secret: invalid_no_dollar\n header: X-Sig\n---\n"); + await fs.writeFile( + filePath, + "---\nverify:\n hmac:\n secret: invalid_no_dollar\n header: X-Sig\n---\n", + ); await expect(cache.set(filePath)).rejects.toThrow(); // Original entry retained diff --git a/packages/core/src/__tests__/render.test.ts b/packages/core/src/__tests__/render.test.ts index 578b47d..c9bf7ae 100644 --- a/packages/core/src/__tests__/render.test.ts +++ b/packages/core/src/__tests__/render.test.ts @@ -62,10 +62,9 @@ describe("renderTemplate", () => { }); it("renders conditional blocks", async () => { - const lure = makeLure( - "{% if payload.merged %}Merged!{% else %}Not merged{% endif %}", - { frontmatter: { payload: { contentType: "json" } } }, - ); + const lure = makeLure("{% if payload.merged %}Merged!{% else %}Not merged{% endif %}", { + frontmatter: { payload: { contentType: "json" } }, + }); const body = JSON.stringify({ merged: true }); const req = makeRequest({ rawBody: new Uint8Array(Buffer.from(body)), diff --git a/packages/core/src/__tests__/schema.test.ts b/packages/core/src/__tests__/schema.test.ts index e9f778b..f834a17 100644 --- a/packages/core/src/__tests__/schema.test.ts +++ b/packages/core/src/__tests__/schema.test.ts @@ -23,30 +23,22 @@ describe("validateFrontmatter", () => { it("rejects invalid secret pattern (no $ prefix)", async () => { const data = { verify: { hmac: { secret: "plain_secret", header: "X-Sig" } } }; - await expect(validateFrontmatter(data, FILE, undefined)).rejects.toThrow( - LureParseError, - ); + await expect(validateFrontmatter(data, FILE, undefined)).rejects.toThrow(LureParseError); }); it("rejects secret with lowercase letters", async () => { const data = { verify: { hmac: { secret: "$my_secret", header: "X-Sig" } } }; - await expect(validateFrontmatter(data, FILE, undefined)).rejects.toThrow( - LureParseError, - ); + await expect(validateFrontmatter(data, FILE, undefined)).rejects.toThrow(LureParseError); }); it("rejects verify block with both header and query", async () => { const data = { verify: { hmac: { secret: "$MY_SECRET", header: "X-Sig", query: "sig" } } }; - await expect(validateFrontmatter(data, FILE, undefined)).rejects.toThrow( - LureParseError, - ); + await expect(validateFrontmatter(data, FILE, undefined)).rejects.toThrow(LureParseError); }); it("rejects verify block with neither header nor query", async () => { const data = { verify: { literal: { secret: "$MY_SECRET" } } }; - await expect(validateFrontmatter(data, FILE, undefined)).rejects.toThrow( - LureParseError, - ); + await expect(validateFrontmatter(data, FILE, undefined)).rejects.toThrow(LureParseError); }); it("accepts literal strategy via query", async () => { @@ -57,15 +49,11 @@ describe("validateFrontmatter", () => { it("rejects invalid contentType", async () => { const data = { payload: { contentType: "xml" } }; - await expect(validateFrontmatter(data, FILE, undefined)).rejects.toThrow( - LureParseError, - ); + await expect(validateFrontmatter(data, FILE, undefined)).rejects.toThrow(LureParseError); }); it("rejects non-object input", async () => { - await expect(validateFrontmatter("invalid", FILE, undefined)).rejects.toThrow( - LureParseError, - ); + await expect(validateFrontmatter("invalid", FILE, undefined)).rejects.toThrow(LureParseError); }); it("validates config with configSchema when present", async () => { @@ -96,8 +84,6 @@ describe("validateFrontmatter", () => { }, }; const data = { config: { bad: true } }; - await expect( - validateFrontmatter(data, FILE, configSchema), - ).rejects.toThrow(LureParseError); + await expect(validateFrontmatter(data, FILE, configSchema)).rejects.toThrow(LureParseError); }); }); diff --git a/packages/core/src/handler.ts b/packages/core/src/handler.ts index 4fea8a8..a3cb51f 100644 --- a/packages/core/src/handler.ts +++ b/packages/core/src/handler.ts @@ -5,9 +5,9 @@ import { watchLures } from "./watcher.js"; import { createQueue } from "./queue.js"; import type { FSWatcher } from "chokidar"; -export async function createLureHandler< - TSchema extends StandardSchemaV1 | undefined = undefined, ->(options: LureHandlerOptions): Promise { +export async function createLureHandler( + options: LureHandlerOptions, +): Promise { const { basePath: rawBasePath, luresDir, @@ -18,8 +18,7 @@ export async function createLureHandler< watch = false, } = options; - const basePath = - "/" + rawBasePath.replace(/^\/+/, "").replace(/\/+$/, ""); + const basePath = "/" + rawBasePath.replace(/^\/+/, "").replace(/\/+$/, ""); const cache = new LureCache( luresDir, @@ -43,17 +42,11 @@ export async function createLureHandler< handle(req) { if (basePath === "/") { // root basePath: every path matches; lurePath is the full path - } else if ( - req.path !== basePath && - !req.path.startsWith(basePath + "/") - ) { + } else if (req.path !== basePath && !req.path.startsWith(basePath + "/")) { return false; } - const lurePath = - basePath === "/" - ? req.path - : req.path.slice(basePath.length) || "/"; + const lurePath = basePath === "/" ? req.path : req.path.slice(basePath.length) || "/"; const lure = cache.get(lurePath); if (lure === undefined) { return false; diff --git a/packages/core/src/loader.ts b/packages/core/src/loader.ts index d132810..7ed8baa 100644 --- a/packages/core/src/loader.ts +++ b/packages/core/src/loader.ts @@ -35,24 +35,14 @@ export class LureCache { const resolved = nodePath.resolve(filePath); const resolvedDir = nodePath.resolve(this.luresDir); - if ( - !resolved.startsWith(resolvedDir + nodePath.sep) && - resolved !== resolvedDir - ) { - throw new LureParseError( - `Path traversal detected: ${filePath}`, - filePath, - ); + if (!resolved.startsWith(resolvedDir + nodePath.sep) && resolved !== resolvedDir) { + throw new LureParseError(`Path traversal detected: ${filePath}`, filePath); } const content = await fs.readFile(filePath, "utf-8"); const { data, content: template } = matter(content); - const frontmatter = await validateFrontmatter( - data, - filePath, - this.configSchema, - ); + const frontmatter = await validateFrontmatter(data, filePath, this.configSchema); if (!this.allowUnverified && frontmatter.verify === undefined) { throw new LureParseError( @@ -61,12 +51,8 @@ export class LureCache { ); } - const relative = nodePath.relative( - resolvedDir, - nodePath.resolve(filePath), - ); - const lurePath = - "/" + relative.replace(/\.lure$/, "").replace(/\\/g, "/"); + const relative = nodePath.relative(resolvedDir, nodePath.resolve(filePath)); + const lurePath = "/" + relative.replace(/\.lure$/, "").replace(/\\/g, "/"); const parsed: ParsedLure = { filePath, diff --git a/packages/core/src/queue.ts b/packages/core/src/queue.ts index 5305c93..f87575e 100644 --- a/packages/core/src/queue.ts +++ b/packages/core/src/queue.ts @@ -42,10 +42,7 @@ export function createQueue(options: QueueOptions): { } } - console.error( - `Callback failed after ${maxAttempts} attempts for ${lurePath}:`, - lastError, - ); + console.error(`Callback failed after ${maxAttempts} attempts for ${lurePath}:`, lastError); }); }, diff --git a/packages/core/src/render.ts b/packages/core/src/render.ts index 8a089b5..3da769a 100644 --- a/packages/core/src/render.ts +++ b/packages/core/src/render.ts @@ -3,10 +3,7 @@ import type { ParsedLure, LureRequest } from "./types.js"; const engine = new Liquid({ strictVariables: false, strictFilters: false }); -export async function renderTemplate( - lure: ParsedLure, - req: LureRequest, -): Promise { +export async function renderTemplate(lure: ParsedLure, req: LureRequest): Promise { let payload: unknown = null; if (lure.frontmatter.payload?.contentType === "json") { diff --git a/packages/core/src/schema.ts b/packages/core/src/schema.ts index 99480ad..f8fbbcd 100644 --- a/packages/core/src/schema.ts +++ b/packages/core/src/schema.ts @@ -43,15 +43,11 @@ export async function validateFrontmatter( const result = frontmatterValidator(data); if (result instanceof type.errors) { - throw new LureParseError( - `Invalid frontmatter: ${result.summary}`, - filePath, - ); + throw new LureParseError(`Invalid frontmatter: ${result.summary}`, filePath); } if (result.verify !== undefined) { - const params = - "hmac" in result.verify ? result.verify.hmac : result.verify.literal; + const params = "hmac" in result.verify ? result.verify.hmac : result.verify.literal; if (!SECRET_PATTERN.test(params.secret)) { throw new LureParseError( @@ -70,19 +66,14 @@ export async function validateFrontmatter( ); } if (!hasHeader && !hasQuery) { - throw new LureParseError( - "verify must specify one of header or query", - filePath, - ); + throw new LureParseError("verify must specify one of header or query", filePath); } } let config: unknown = result.config; if (configSchema !== undefined && result.config !== undefined) { - const configResult = await configSchema["~standard"].validate( - result.config, - ); + const configResult = await configSchema["~standard"].validate(result.config); if (configResult.issues !== undefined) { const messages = configResult.issues.map((i) => i.message).join(", "); throw new LureParseError(`Invalid config: ${messages}`, filePath); diff --git a/packages/core/src/types.ts b/packages/core/src/types.ts index 88f6282..563c652 100644 --- a/packages/core/src/types.ts +++ b/packages/core/src/types.ts @@ -20,9 +20,7 @@ export interface LureLiteralParams { query?: string; } -export type LureVerify = - | { hmac: LureHmacParams } - | { literal: LureLiteralParams }; +export type LureVerify = { hmac: LureHmacParams } | { literal: LureLiteralParams }; export interface LurePayload { contentType?: "json"; @@ -41,14 +39,11 @@ export interface ParsedLure { template: string; } -type ConfigOf = - TSchema extends StandardSchemaV1 - ? StandardSchemaV1.InferOutput - : unknown; +type ConfigOf = TSchema extends StandardSchemaV1 + ? StandardSchemaV1.InferOutput + : unknown; -export interface LureHandlerOptions< - TSchema extends StandardSchemaV1 | undefined = undefined, -> { +export interface LureHandlerOptions { basePath: string; luresDir: string; callback: (prompt: string, config: ConfigOf) => Promise; diff --git a/packages/core/src/verify.ts b/packages/core/src/verify.ts index 4dbe05b..514dfb6 100644 --- a/packages/core/src/verify.ts +++ b/packages/core/src/verify.ts @@ -47,10 +47,7 @@ function verifyLiteral(secretValue: string, signature: string): boolean { return safeEqual(signature, secretValue); } -export async function verifyRequest( - lure: ParsedLure, - req: LureRequest, -): Promise { +export async function verifyRequest(lure: ParsedLure, req: LureRequest): Promise { if (lure.frontmatter.verify === undefined) { return true; } diff --git a/packages/core/src/watcher.ts b/packages/core/src/watcher.ts index 8c54143..f9a42b5 100644 --- a/packages/core/src/watcher.ts +++ b/packages/core/src/watcher.ts @@ -4,9 +4,7 @@ import * as nodePath from "node:path"; import type { LureCache } from "./loader.js"; export function watchLures(luresDir: string, cache: LureCache): FSWatcher { - const pattern = nodePath - .join(nodePath.resolve(luresDir), "**", "*.lure") - .replace(/\\/g, "/"); + const pattern = nodePath.join(nodePath.resolve(luresDir), "**", "*.lure").replace(/\\/g, "/"); const watcher = chokidar.watch(pattern, { ignoreInitial: true }); diff --git a/packages/express/README.md b/packages/express/README.md index 6e5c6e9..96faa3b 100644 --- a/packages/express/README.md +++ b/packages/express/README.md @@ -12,14 +12,14 @@ npm install @lure-hooks/express ## Usage ```ts -import express from 'express'; -import { createLureHandler } from '@lure-hooks/express'; +import express from "express"; +import { createLureHandler } from "@lure-hooks/express"; const app = express(); const lure = await createLureHandler({ - basePath: '/webhooks', - luresDir: './lures', + basePath: "/webhooks", + luresDir: "./lures", callback: async (prompt, config) => { // Send the prompt to your LLM of choice }, @@ -33,15 +33,15 @@ so it can be composed freely with other Express middleware and routes. ### With config schema ```ts -import express from 'express'; -import { createLureHandler } from '@lure-hooks/express'; -import * as v from 'valibot'; +import express from "express"; +import { createLureHandler } from "@lure-hooks/express"; +import * as v from "valibot"; const app = express(); const lure = await createLureHandler({ - basePath: '/webhooks', - luresDir: './lures', + basePath: "/webhooks", + luresDir: "./lures", configSchema: v.object({ channel: v.string(), }), @@ -54,12 +54,12 @@ app.use(lure); ### Options -| Option | Type | Default | Description | -|---|---|---|---| -| `basePath` | `string` | — | URL path prefix for all lure endpoints | -| `luresDir` | `string` | — | Path to the directory containing `.lure` files | -| `callback` | `(prompt: string, config: TConfig) => Promise` | — | Called with the rendered prompt on each verified webhook. `TConfig` is inferred from `configSchema`, or `unknown` if omitted | -| `configSchema` | Standard Schema | — | Schema for validating the `config` frontmatter block. Informs the type of `config` in `callback` | -| `maxAttempts` | `number` | `1` | Number of times to attempt `callback` before dropping the webhook | -| `allowUnverified` | `boolean` | `true` | Whether to allow `.lure` files without a `verify` block | -| `watch` | `boolean` | `false` | Watch `luresDir` for changes and reload lures automatically | +| Option | Type | Default | Description | +| ----------------- | ---------------------------------------------------- | ------- | ---------------------------------------------------------------------------------------------------------------------------- | +| `basePath` | `string` | — | URL path prefix for all lure endpoints | +| `luresDir` | `string` | — | Path to the directory containing `.lure` files | +| `callback` | `(prompt: string, config: TConfig) => Promise` | — | Called with the rendered prompt on each verified webhook. `TConfig` is inferred from `configSchema`, or `unknown` if omitted | +| `configSchema` | Standard Schema | — | Schema for validating the `config` frontmatter block. Informs the type of `config` in `callback` | +| `maxAttempts` | `number` | `1` | Number of times to attempt `callback` before dropping the webhook | +| `allowUnverified` | `boolean` | `true` | Whether to allow `.lure` files without a `verify` block | +| `watch` | `boolean` | `false` | Watch `luresDir` for changes and reload lures automatically | diff --git a/packages/express/package.json b/packages/express/package.json index fdd6a4c..22b7228 100644 --- a/packages/express/package.json +++ b/packages/express/package.json @@ -2,9 +2,18 @@ "name": "@lure-hooks/express", "version": "1.0.0", "description": "Express middleware adapter for Lure — process webhook events into LLM prompts", - "keywords": ["webhook", "llm", "prompt", "express", "middleware"], + "keywords": [ + "express", + "llm", + "middleware", + "prompt", + "webhook" + ], "license": "ISC", "author": "Graham Barber", + "files": [ + "dist" + ], "type": "module", "main": "./dist/index.js", "types": "./dist/index.d.ts", @@ -14,14 +23,10 @@ "types": "./dist/index.d.ts" } }, - "files": [ - "dist" - ], "scripts": { "build": "tsc --build", "test": "vitest run" }, - "packageManager": "pnpm@11.0.0-dev.1005", "dependencies": { "@lure-hooks/core": "workspace:^", "express": "^5.2.1" @@ -33,5 +38,6 @@ "supertest": "^7.2.2", "typescript": "^5.9.3", "vitest": "^4.1.0" - } + }, + "packageManager": "pnpm@11.0.0-dev.1005" } diff --git a/packages/express/src/__tests__/index.test.ts b/packages/express/src/__tests__/index.test.ts index d2ddc4d..b2ec95c 100644 --- a/packages/express/src/__tests__/index.test.ts +++ b/packages/express/src/__tests__/index.test.ts @@ -109,10 +109,7 @@ describe("createLureHandler (express)", () => { }); it("forwards request headers to the template", async () => { - await writeLure( - "event.lure", - "---\n---\nToken: {{ headers['x-token'] }}\n", - ); + await writeLure("event.lure", "---\n---\nToken: {{ headers['x-token'] }}\n"); let resolveCallback!: () => void; const callbackDone = new Promise((r) => { resolveCallback = r; diff --git a/packages/express/src/index.ts b/packages/express/src/index.ts index 044df79..7c875f5 100644 --- a/packages/express/src/index.ts +++ b/packages/express/src/index.ts @@ -3,22 +3,17 @@ import { createLureHandler as coreCreateLureHandler } from "@lure-hooks/core"; import type { LureHandlerOptions } from "@lure-hooks/core"; import type { StandardSchemaV1 } from "@standard-schema/spec"; -export async function createLureHandler< - TSchema extends StandardSchemaV1 | undefined = undefined, ->(options: LureHandlerOptions): Promise { - const basePath = - "/" + options.basePath.replace(/^\/+/, "").replace(/\/+$/, ""); +export async function createLureHandler( + options: LureHandlerOptions, +): Promise { + const basePath = "/" + options.basePath.replace(/^\/+/, "").replace(/\/+$/, ""); const handler = await coreCreateLureHandler(options); const rawBodyMiddleware = express.raw({ type: "*/*" }); return (req, res, next) => { - if ( - basePath !== "/" && - req.path !== basePath && - !req.path.startsWith(basePath + "/") - ) { + if (basePath !== "/" && req.path !== basePath && !req.path.startsWith(basePath + "/")) { next(); return; } @@ -29,8 +24,7 @@ export async function createLureHandler< return; } - const rawBody = - req.body instanceof Buffer ? new Uint8Array(req.body) : new Uint8Array(0); + const rawBody = req.body instanceof Buffer ? new Uint8Array(req.body) : new Uint8Array(0); const headers = new Headers(); for (const [key, value] of Object.entries(req.headers)) { diff --git a/packages/fetch/README.md b/packages/fetch/README.md index f4ab9ed..00768e9 100644 --- a/packages/fetch/README.md +++ b/packages/fetch/README.md @@ -13,11 +13,11 @@ npm install @lure-hooks/fetch ## Usage ```ts -import { createLureHandler } from '@lure-hooks/fetch'; +import { createLureHandler } from "@lure-hooks/fetch"; const lure = await createLureHandler({ - basePath: '/webhooks', - luresDir: './lures', + basePath: "/webhooks", + luresDir: "./lures", callback: async (prompt, config) => { // Send the prompt to your LLM of choice }, @@ -37,12 +37,12 @@ validate the `config` block in your `.lure` files. Any schema library that implements the Standard Schema spec (Zod, Valibot, Arktype, etc.) will work. ```ts -import { createLureHandler } from '@lure-hooks/fetch'; -import * as v from 'valibot'; +import { createLureHandler } from "@lure-hooks/fetch"; +import * as v from "valibot"; const lure = await createLureHandler({ - basePath: '/webhooks', - luresDir: './lures', + basePath: "/webhooks", + luresDir: "./lures", configSchema: v.object({ channel: v.string(), }), @@ -54,12 +54,12 @@ const lure = await createLureHandler({ ### Options -| Option | Type | Default | Description | -|---|---|---|---| -| `basePath` | `string` | — | URL path prefix for all lure endpoints | -| `luresDir` | `string` | — | Path to the directory containing `.lure` files | -| `callback` | `(prompt: string, config: TConfig) => Promise` | — | Called with the rendered prompt on each verified webhook. `TConfig` is inferred from `configSchema`, or `unknown` if omitted | -| `configSchema` | Standard Schema | — | Schema for validating the `config` frontmatter block. Informs the type of `config` in `callback` | -| `maxAttempts` | `number` | `1` | Number of times to attempt `callback` before dropping the webhook | -| `allowUnverified` | `boolean` | `true` | Whether to allow `.lure` files without a `verify` block | -| `watch` | `boolean` | `false` | Watch `luresDir` for changes and reload lures automatically | +| Option | Type | Default | Description | +| ----------------- | ---------------------------------------------------- | ------- | ---------------------------------------------------------------------------------------------------------------------------- | +| `basePath` | `string` | — | URL path prefix for all lure endpoints | +| `luresDir` | `string` | — | Path to the directory containing `.lure` files | +| `callback` | `(prompt: string, config: TConfig) => Promise` | — | Called with the rendered prompt on each verified webhook. `TConfig` is inferred from `configSchema`, or `unknown` if omitted | +| `configSchema` | Standard Schema | — | Schema for validating the `config` frontmatter block. Informs the type of `config` in `callback` | +| `maxAttempts` | `number` | `1` | Number of times to attempt `callback` before dropping the webhook | +| `allowUnverified` | `boolean` | `true` | Whether to allow `.lure` files without a `verify` block | +| `watch` | `boolean` | `false` | Watch `luresDir` for changes and reload lures automatically | diff --git a/packages/fetch/package.json b/packages/fetch/package.json index d751ec7..a56f025 100644 --- a/packages/fetch/package.json +++ b/packages/fetch/package.json @@ -2,9 +2,20 @@ "name": "@lure-hooks/fetch", "version": "1.0.0", "description": "Fetch API adapter for Lure — process webhook events into LLM prompts with Deno, Bun, and Cloudflare Workers", - "keywords": ["webhook", "llm", "prompt", "fetch", "deno", "bun", "cloudflare-workers"], + "keywords": [ + "bun", + "cloudflare-workers", + "deno", + "fetch", + "llm", + "prompt", + "webhook" + ], "license": "ISC", "author": "Graham Barber", + "files": [ + "dist" + ], "type": "module", "main": "./dist/index.js", "types": "./dist/index.d.ts", @@ -14,14 +25,10 @@ "types": "./dist/index.d.ts" } }, - "files": [ - "dist" - ], "scripts": { "build": "tsc --build", "test": "vitest run" }, - "packageManager": "pnpm@11.0.0-dev.1005", "dependencies": { "@lure-hooks/core": "workspace:^" }, @@ -30,5 +37,6 @@ "@types/node": "^25.5.0", "typescript": "^5.9.3", "vitest": "^4.1.0" - } + }, + "packageManager": "pnpm@11.0.0-dev.1005" } diff --git a/packages/fetch/src/__tests__/index.test.ts b/packages/fetch/src/__tests__/index.test.ts index 4f3d69d..8ddd8d2 100644 --- a/packages/fetch/src/__tests__/index.test.ts +++ b/packages/fetch/src/__tests__/index.test.ts @@ -128,10 +128,7 @@ describe("createLureHandler (fetch)", () => { }); it("forwards request headers to the template", async () => { - await writeLure( - "event.lure", - "---\n---\nToken: {{ headers['x-token'] }}\n", - ); + await writeLure("event.lure", "---\n---\nToken: {{ headers['x-token'] }}\n"); let resolveCallback!: () => void; const callbackDone = new Promise((r) => { resolveCallback = r; @@ -156,7 +153,10 @@ describe("createLureHandler (fetch)", () => { }); it("reads and forwards body as rawBody", async () => { - await writeLure("data.lure", "---\npayload:\n contentType: json\n---\nAction: {{ payload.action }}\n"); + await writeLure( + "data.lure", + "---\npayload:\n contentType: json\n---\nAction: {{ payload.action }}\n", + ); let resolveCallback!: () => void; const callbackDone = new Promise((r) => { resolveCallback = r; diff --git a/packages/fetch/src/index.ts b/packages/fetch/src/index.ts index e6d7339..2f996a3 100644 --- a/packages/fetch/src/index.ts +++ b/packages/fetch/src/index.ts @@ -2,24 +2,17 @@ import { createLureHandler as coreCreateLureHandler } from "@lure-hooks/core"; import type { LureHandlerOptions } from "@lure-hooks/core"; import type { StandardSchemaV1 } from "@standard-schema/spec"; -export async function createLureHandler< - TSchema extends StandardSchemaV1 | undefined = undefined, ->( +export async function createLureHandler( options: LureHandlerOptions, ): Promise<(req: Request) => Promise> { - const basePath = - "/" + options.basePath.replace(/^\/+/, "").replace(/\/+$/, ""); + const basePath = "/" + options.basePath.replace(/^\/+/, "").replace(/\/+$/, ""); const handler = await coreCreateLureHandler(options); return async (req: Request): Promise => { const url = new URL(req.url); - if ( - basePath !== "/" && - url.pathname !== basePath && - !url.pathname.startsWith(basePath + "/") - ) { + if (basePath !== "/" && url.pathname !== basePath && !url.pathname.startsWith(basePath + "/")) { return null; }