capability-worker 0.1.0 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -229,7 +229,7 @@ Use `requireCapabilitySignature` when route handlers prefer exception flow:
229
229
 
230
230
  ```ts
231
231
  import { requireCapabilitySignature } from "capability-worker/auth";
232
- import { errorResponse, jsonResponse } from "capability-worker/http";
232
+ import { errorResponse, okJson } from "capability-worker/http";
233
233
 
234
234
  export default {
235
235
  async fetch(request: Request, env: Env): Promise<Response> {
@@ -242,7 +242,7 @@ export default {
242
242
  .bind(accountId)
243
243
  .all();
244
244
 
245
- return jsonResponse({ ok: true, data: rows.results });
245
+ return okJson(rows.results);
246
246
  } catch (error) {
247
247
  return errorResponse(error);
248
248
  }
@@ -254,7 +254,7 @@ Use `verifyCapabilitySignature` when route handlers prefer explicit result handl
254
254
 
255
255
  ```ts
256
256
  import { verifyCapabilitySignature } from "capability-worker/auth";
257
- import { errorJson, jsonResponse } from "capability-worker/http";
257
+ import { errorJson, okJson } from "capability-worker/http";
258
258
 
259
259
  const auth = await verifyCapabilitySignature(request, env);
260
260
 
@@ -262,10 +262,7 @@ if (!auth.ok) {
262
262
  return errorJson(auth.code, auth.message, auth.status);
263
263
  }
264
264
 
265
- return jsonResponse({
266
- ok: true,
267
- accountId: auth.accountId
268
- });
265
+ return okJson({ accountId: auth.accountId });
269
266
  ```
270
267
 
271
268
  Rules for signed routes:
@@ -382,6 +379,7 @@ import {
382
379
  errorJson,
383
380
  errorResponse,
384
381
  jsonResponse,
382
+ okJson,
385
383
  textResponse
386
384
  } from "capability-worker/http";
387
385
 
@@ -392,7 +390,16 @@ export async function readMeeting(request: Request, env: Env) {
392
390
  throw new HttpError(404, "meeting_not_found", "Meeting not found.");
393
391
  }
394
392
 
395
- return jsonResponse({ ok: true, data: meeting });
393
+ return okJson(meeting);
394
+ }
395
+ ```
396
+
397
+ Success shape:
398
+
399
+ ```json
400
+ {
401
+ "ok": true,
402
+ "data": {}
396
403
  }
397
404
  ```
398
405
 
@@ -412,6 +419,7 @@ Useful helpers:
412
419
 
413
420
  ```ts
414
421
  jsonResponse(value, init);
422
+ okJson(data, init);
415
423
  textResponse(value, init);
416
424
  htmlResponse(value, init);
417
425
  redirectResponse(location, status);
@@ -915,6 +923,10 @@ CAPABILITY_PUBLIC_KEYS_URL = "https://host.example.com/capability/public-keys"
915
923
 
916
924
  ## New Capability Worker Recipe
917
925
 
926
+ The npm package includes a small starter at `templates/new-capability-worker`.
927
+ It uses `capability-build-assets`, `requireCapabilitySignature`, `okJson`,
928
+ `errorJson`, and `textResponse` from this package.
929
+
918
930
  1. Create `cli/definition.json`.
919
931
  2. Create `cli/mapping.json`.
920
932
  3. Create `skills/<capability-name>/SKILL.md`.
@@ -932,7 +944,7 @@ Minimal route skeleton:
932
944
 
933
945
  ```ts
934
946
  import { requireCapabilitySignature } from "capability-worker/auth";
935
- import { errorResponse, jsonResponse, textResponse } from "capability-worker/http";
947
+ import { errorJson, errorResponse, okJson, textResponse } from "capability-worker/http";
936
948
 
937
949
  export default {
938
950
  async fetch(request: Request, env: Env): Promise<Response> {
@@ -940,7 +952,7 @@ export default {
940
952
  const url = new URL(request.url);
941
953
 
942
954
  if (url.pathname === "/api/health") {
943
- return jsonResponse({ ok: true });
955
+ return okJson({ status: "ok" });
944
956
  }
945
957
 
946
958
  if (url.pathname === "/api/meetings" && request.method === "GET") {
@@ -954,22 +966,13 @@ export default {
954
966
  .all();
955
967
 
956
968
  if (wantsJson) {
957
- return jsonResponse({ ok: true, data: rows.results });
969
+ return okJson(rows.results);
958
970
  }
959
971
 
960
972
  return textResponse(formatMeetingsTable(rows.results));
961
973
  }
962
974
 
963
- return jsonResponse(
964
- {
965
- ok: false,
966
- error: {
967
- code: "not_found",
968
- message: "Not found."
969
- }
970
- },
971
- 404
972
- );
975
+ return errorJson("not_found", "Not found.", 404);
973
976
  } catch (error) {
974
977
  return errorResponse(error);
975
978
  }
@@ -7,6 +7,7 @@ declare class HttpError extends Error {
7
7
  });
8
8
  }
9
9
  declare function jsonResponse(value: unknown, init?: ResponseInit | number): Response;
10
+ declare function okJson(data: unknown, init?: ResponseInit | number): Response;
10
11
  declare function textResponse(value: string, init?: ResponseInit | number): Response;
11
12
  declare function htmlResponse(value: string, init?: ResponseInit | number): Response;
12
13
  declare function redirectResponse(location: string, status?: number): Response;
@@ -21,4 +22,4 @@ declare function booleanParam(value: unknown): boolean;
21
22
  declare function limitParam(value: unknown, defaultValue?: number, max?: number): number;
22
23
  declare function getRepeated(url: URL, key: string): string[];
23
24
 
24
- export { HttpError, booleanParam, errorJson, errorResponse, getRepeated, htmlResponse, isJsonRequested, jsonResponse, limitParam, parseJsonBody, readJsonObject, redirectResponse, stringArrayParam, stringParam, textResponse };
25
+ export { HttpError, booleanParam, errorJson, errorResponse, getRepeated, htmlResponse, isJsonRequested, jsonResponse, limitParam, okJson, parseJsonBody, readJsonObject, redirectResponse, stringArrayParam, stringParam, textResponse };
@@ -1 +1 @@
1
- class t extends Error{status;code;expose;constructor(t,e,n,r){super(n),this.name="HttpError",this.status=t,this.code=e,this.expose=r?.expose??t<500}}function e(t,e=200){const n=d(e);return new Response(JSON.stringify(t,null,2),{...n,headers:m(n.headers,"content-type","application/json; charset=utf-8")})}function n(t,e=200){return new Response(t.endsWith("\n")?t:`${t}\n`,{...d(e),headers:m(d(e).headers,"content-type","text/plain; charset=utf-8")})}function r(t,e=200){const n=d(e);return new Response(t,{...n,headers:m(n.headers,"content-type","text/html; charset=utf-8")})}function o(t,e=302){return new Response(null,{status:e,headers:{location:t,"cache-control":"no-store"}})}function s(t,n,r=400,o={}){return e({ok:!1,error:{code:t,message:n},...o},r)}function a(e){return e instanceof t?s(e.code,e.expose?e.message:"Internal error.",e.status):s("internal_error","Internal error.",500)}async function c(t){if(!t.body)return{};try{const e=await t.json();return!e||"object"!=typeof e||Array.isArray(e)?{}:e}catch{return{}}}async function u(e){const n=await e.json().catch(()=>null);if(!n||"object"!=typeof n||Array.isArray(n))throw new t(400,"invalid_json","Expected a JSON object.");return n}function i(t,e){const n=t.searchParams.get("json");return""===n||"1"===n||"true"===n||!0===e?.json}function f(t){if("string"!=typeof t)return;const e=t.trim();return e.length>0?e:void 0}function h(t){if(Array.isArray(t))return t.filter(t=>"string"==typeof t).map(t=>t.trim()).filter(Boolean);const e=f(t);return e?[e]:[]}function p(t){return!0===t||"true"===t||"1"===t||""===t}function l(t,e=20,n=100){const r="number"==typeof t?t:Number(t);return Number.isFinite(r)?Math.max(1,Math.min(n,Math.floor(r))):e}function y(t,e){return t.searchParams.getAll(e).map(t=>t.trim()).filter(Boolean)}function d(t){return"number"==typeof t?{status:t}:t}function m(t,e,n){const r=new Headers(t);return r.has(e)||r.set(e,n),r}export{t as HttpError,p as booleanParam,s as errorJson,a as errorResponse,y as getRepeated,r as htmlResponse,i as isJsonRequested,e as jsonResponse,l as limitParam,c as parseJsonBody,u as readJsonObject,o as redirectResponse,h as stringArrayParam,f as stringParam,n as textResponse};
1
+ class t extends Error{status;code;expose;constructor(t,e,n,r){super(n),this.name="HttpError",this.status=t,this.code=e,this.expose=r?.expose??t<500}}function e(t,e=200){const n=m(e);return new Response(JSON.stringify(t,null,2),{...n,headers:x(n.headers,"content-type","application/json; charset=utf-8")})}function n(t,n=200){return e({ok:!0,data:t},n)}function r(t,e=200){return new Response(t.endsWith("\n")?t:`${t}\n`,{...m(e),headers:x(m(e).headers,"content-type","text/plain; charset=utf-8")})}function o(t,e=200){const n=m(e);return new Response(t,{...n,headers:x(n.headers,"content-type","text/html; charset=utf-8")})}function s(t,e=302){return new Response(null,{status:e,headers:{location:t,"cache-control":"no-store"}})}function a(t,n,r=400,o={}){return e({ok:!1,error:{code:t,message:n},...o},r)}function c(e){return e instanceof t?a(e.code,e.expose?e.message:"Internal error.",e.status):a("internal_error","Internal error.",500)}async function u(t){if(!t.body)return{};try{const e=await t.json();return!e||"object"!=typeof e||Array.isArray(e)?{}:e}catch{return{}}}async function i(e){const n=await e.json().catch(()=>null);if(!n||"object"!=typeof n||Array.isArray(n))throw new t(400,"invalid_json","Expected a JSON object.");return n}function f(t,e){const n=t.searchParams.get("json");return""===n||"1"===n||"true"===n||!0===e?.json}function h(t){if("string"!=typeof t)return;const e=t.trim();return e.length>0?e:void 0}function p(t){if(Array.isArray(t))return t.filter(t=>"string"==typeof t).map(t=>t.trim()).filter(Boolean);const e=h(t);return e?[e]:[]}function l(t){return!0===t||"true"===t||"1"===t||""===t}function y(t,e=20,n=100){const r="number"==typeof t?t:Number(t);return Number.isFinite(r)?Math.max(1,Math.min(n,Math.floor(r))):e}function d(t,e){return t.searchParams.getAll(e).map(t=>t.trim()).filter(Boolean)}function m(t){return"number"==typeof t?{status:t}:t}function x(t,e,n){const r=new Headers(t);return r.has(e)||r.set(e,n),r}export{t as HttpError,l as booleanParam,a as errorJson,c as errorResponse,d as getRepeated,o as htmlResponse,f as isJsonRequested,e as jsonResponse,y as limitParam,n as okJson,u as parseJsonBody,i as readJsonObject,s as redirectResponse,p as stringArrayParam,h as stringParam,r as textResponse};
package/package.json CHANGED
@@ -1,11 +1,12 @@
1
1
  {
2
2
  "name": "capability-worker",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "Shared toolkit for Cloudflare Workers that expose CLI-like capabilities over HTTP.",
5
5
  "type": "module",
6
6
  "sideEffects": false,
7
7
  "files": [
8
8
  "dist",
9
+ "templates",
9
10
  "README.md",
10
11
  "LICENSE"
11
12
  ],
@@ -0,0 +1,15 @@
1
+ # New Capability Worker
2
+
3
+ Small starter for a Cloudflare Worker capability that uses the shared `capability-worker` package.
4
+
5
+ ## Setup
6
+
7
+ ```sh
8
+ pnpm install
9
+ CAPABILITY_BASE_URL=https://starter.example.com/api pnpm -C worker run build:capability-assets
10
+ pnpm -C worker run typecheck
11
+ pnpm -C worker run dev
12
+ ```
13
+
14
+ Replace `starter` names, command definitions, mapping routes, and domain behavior before deploying.
15
+
@@ -0,0 +1,48 @@
1
+ {
2
+ "schemaVersion": "cli.definition.v1",
3
+ "cli": {
4
+ "name": "starter",
5
+ "summary": "Run starter capability commands.",
6
+ "description": "A minimal capability definition to replace with product-specific commands."
7
+ },
8
+ "commandGroups": [
9
+ {
10
+ "id": "starter",
11
+ "title": "Starter",
12
+ "description": "Starter commands."
13
+ }
14
+ ],
15
+ "commands": [
16
+ {
17
+ "id": "hello",
18
+ "group": "starter",
19
+ "command": "starter hello",
20
+ "summary": "Return a signed hello response.",
21
+ "description": "Verifies the capability signature and returns a small account-scoped response.",
22
+ "usage": "starter hello [--json]",
23
+ "risk": "read",
24
+ "requiresConfirmation": false,
25
+ "arguments": [],
26
+ "options": [
27
+ {
28
+ "name": "--json",
29
+ "key": "json",
30
+ "type": "boolean",
31
+ "description": "Return structured JSON instead of text."
32
+ }
33
+ ],
34
+ "examples": [
35
+ {
36
+ "description": "Return a text response.",
37
+ "command": "starter hello"
38
+ },
39
+ {
40
+ "description": "Return a JSON response.",
41
+ "command": "starter hello --json"
42
+ }
43
+ ],
44
+ "output": "Without --json, prints a short text response. With --json, returns a standard { ok, data } JSON response."
45
+ }
46
+ ]
47
+ }
48
+
@@ -0,0 +1,8 @@
1
+ {
2
+ "hello": {
3
+ "method": "GET",
4
+ "path": "/hello",
5
+ "query": ["json"]
6
+ }
7
+ }
8
+
@@ -0,0 +1,13 @@
1
+ {
2
+ "name": "new-capability-worker",
3
+ "private": true,
4
+ "type": "module",
5
+ "packageManager": "pnpm@11.9.0",
6
+ "scripts": {
7
+ "build:capability-assets": "pnpm -C worker run build:capability-assets",
8
+ "typecheck": "pnpm -C worker run typecheck",
9
+ "dev": "pnpm -C worker run dev",
10
+ "deploy": "pnpm -C worker run deploy"
11
+ }
12
+ }
13
+
@@ -0,0 +1,6 @@
1
+ packages:
2
+ - worker
3
+
4
+ minimumReleaseAgeExclude:
5
+ - capability-worker@0.1.1
6
+
@@ -0,0 +1,9 @@
1
+ # Starter
2
+
3
+ Use the `starter` command to verify that the capability is installed and signed requests are working.
4
+
5
+ ## Commands
6
+
7
+ - `starter hello`: returns a short text response.
8
+ - `starter hello --json`: returns the same response as structured JSON.
9
+
@@ -0,0 +1,36 @@
1
+ {
2
+ "schemaVersion": "capability.info.v1",
3
+ "id": "starter",
4
+ "name": "starter",
5
+ "displayName": "Starter",
6
+ "summary": "Run starter capability commands.",
7
+ "description": "A minimal capability definition to replace with product-specific commands.",
8
+ "homepage": "${CAPABILITY_BASE_URL}",
9
+ "apiBaseUrl": "${CAPABILITY_BASE_URL}",
10
+ "endpoints": {
11
+ "definition": "definition",
12
+ "mapping": "mapping"
13
+ },
14
+ "skill": {
15
+ "name": "starter",
16
+ "entrypoint": {
17
+ "path": "SKILL.md",
18
+ "url": "skill/SKILL.md",
19
+ "contentType": "text/markdown"
20
+ },
21
+ "resources": []
22
+ },
23
+ "auth": {
24
+ "scheme": "capability-signature-v1",
25
+ "accountHeader": "x-capability-account-id",
26
+ "timestampHeader": "x-capability-timestamp",
27
+ "keyIdHeader": "x-capability-key-id",
28
+ "signatureHeader": "x-capability-signature"
29
+ },
30
+ "version": "${APP_ASSET_VERSION}",
31
+ "updatedAt": "2026-07-06T00:00:00Z",
32
+ "cache": {
33
+ "maxAgeSeconds": 86400
34
+ }
35
+ }
36
+
@@ -0,0 +1,20 @@
1
+ {
2
+ "name": "starter-capability-worker",
3
+ "private": true,
4
+ "type": "module",
5
+ "scripts": {
6
+ "build:capability-assets": "capability-build-assets --worker-root . --repo-root .. --validate",
7
+ "typecheck": "tsc --noEmit",
8
+ "dev": "wrangler dev",
9
+ "deploy": "pnpm run build:capability-assets && wrangler deploy"
10
+ },
11
+ "dependencies": {
12
+ "capability-worker": "^0.1.1"
13
+ },
14
+ "devDependencies": {
15
+ "@cloudflare/workers-types": "^4.20260627.0",
16
+ "typescript": "^5.9.0",
17
+ "wrangler": "^4.51.0"
18
+ }
19
+ }
20
+
@@ -0,0 +1,51 @@
1
+ import { isCapabilityAssetPath } from "capability-worker";
2
+ import { requireCapabilitySignature } from "capability-worker/auth";
3
+ import {
4
+ errorJson,
5
+ errorResponse,
6
+ isJsonRequested,
7
+ okJson,
8
+ textResponse
9
+ } from "capability-worker/http";
10
+
11
+ export interface Env {
12
+ ASSETS: Fetcher;
13
+ CAPABILITY_PUBLIC_KEYS_JSON?: string;
14
+ CAPABILITY_PUBLIC_KEYS_URL?: string;
15
+ CAPABILITY_PUBLIC_KEYS_SERVICE?: Fetcher;
16
+ }
17
+
18
+ export default {
19
+ async fetch(request: Request, env: Env): Promise<Response> {
20
+ try {
21
+ const url = new URL(request.url);
22
+
23
+ if (isCapabilityAssetPath(url.pathname, { basePath: "/api" })) {
24
+ return env.ASSETS.fetch(request);
25
+ }
26
+
27
+ if (url.pathname === "/api/health" && request.method === "GET") {
28
+ return okJson({ status: "ok" });
29
+ }
30
+
31
+ if (url.pathname === "/api/hello" && request.method === "GET") {
32
+ const { accountId } = await requireCapabilitySignature(request, env);
33
+ const data = {
34
+ accountId,
35
+ message: "Hello from the starter capability."
36
+ };
37
+
38
+ if (isJsonRequested(url)) {
39
+ return okJson(data);
40
+ }
41
+
42
+ return textResponse(`Hello ${accountId}.`);
43
+ }
44
+
45
+ return errorJson("not_found", "Not found.", 404);
46
+ } catch (error) {
47
+ return errorResponse(error);
48
+ }
49
+ }
50
+ };
51
+
@@ -0,0 +1,12 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "ESNext",
5
+ "moduleResolution": "Bundler",
6
+ "strict": true,
7
+ "skipLibCheck": true,
8
+ "types": ["@cloudflare/workers-types"]
9
+ },
10
+ "include": ["src/**/*.ts"]
11
+ }
12
+
@@ -0,0 +1,17 @@
1
+ {
2
+ "name": "starter-capability-worker",
3
+ "main": "src/worker.ts",
4
+ "compatibility_date": "2026-07-06",
5
+ "assets": {
6
+ "directory": "./public",
7
+ "binding": "ASSETS",
8
+ "html_handling": "none",
9
+ "not_found_handling": "none"
10
+ },
11
+ "vars": {
12
+ "CAPABILITY_BASE_URL": "https://starter.example.com/api",
13
+ "APP_ASSET_VERSION": "0.1.1",
14
+ "CAPABILITY_PUBLIC_KEYS_URL": "https://example.com/capability/public-keys"
15
+ }
16
+ }
17
+