capability-worker 0.1.0 → 0.1.3
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 +30 -21
- package/dist/node/build-capability-assets-cli.js +0 -0
- package/dist/node/validate-cli.js +0 -0
- package/dist/runtime/http/index.d.ts +2 -1
- package/dist/runtime/http/index.js +1 -1
- package/package.json +17 -10
- package/templates/new-capability-worker/README.md +15 -0
- package/templates/new-capability-worker/cli/definition.json +48 -0
- package/templates/new-capability-worker/cli/mapping.json +8 -0
- package/templates/new-capability-worker/package.json +13 -0
- package/templates/new-capability-worker/pnpm-workspace.yaml +6 -0
- package/templates/new-capability-worker/skills/starter/SKILL.md +9 -0
- package/templates/new-capability-worker/worker/capability/info.template.json +36 -0
- package/templates/new-capability-worker/worker/package.json +20 -0
- package/templates/new-capability-worker/worker/src/worker.ts +51 -0
- package/templates/new-capability-worker/worker/tsconfig.json +12 -0
- package/templates/new-capability-worker/worker/wrangler.jsonc +17 -0
package/README.md
CHANGED
|
@@ -41,6 +41,12 @@ pnpm exec capability-build-assets --validate
|
|
|
41
41
|
pnpm exec capability-validate
|
|
42
42
|
```
|
|
43
43
|
|
|
44
|
+
## Publishing
|
|
45
|
+
|
|
46
|
+
Pushes to `main` are released by `.github/workflows/publish.yml`. The workflow prepares a new `v<version>` tag and publishes to npm through Trusted Publishing in the same GitHub Actions run, without an npm token.
|
|
47
|
+
|
|
48
|
+
Configure the npm package Trusted Publisher before relying on the workflow. The required npmjs.com settings are documented in [trusted-publishing.md](https://github.com/Iliasnolsson/capability-worker/blob/main/trusted-publishing.md).
|
|
49
|
+
|
|
44
50
|
## Mental Model
|
|
45
51
|
|
|
46
52
|
A capability worker has two parts:
|
|
@@ -229,7 +235,7 @@ Use `requireCapabilitySignature` when route handlers prefer exception flow:
|
|
|
229
235
|
|
|
230
236
|
```ts
|
|
231
237
|
import { requireCapabilitySignature } from "capability-worker/auth";
|
|
232
|
-
import { errorResponse,
|
|
238
|
+
import { errorResponse, okJson } from "capability-worker/http";
|
|
233
239
|
|
|
234
240
|
export default {
|
|
235
241
|
async fetch(request: Request, env: Env): Promise<Response> {
|
|
@@ -242,7 +248,7 @@ export default {
|
|
|
242
248
|
.bind(accountId)
|
|
243
249
|
.all();
|
|
244
250
|
|
|
245
|
-
return
|
|
251
|
+
return okJson(rows.results);
|
|
246
252
|
} catch (error) {
|
|
247
253
|
return errorResponse(error);
|
|
248
254
|
}
|
|
@@ -254,7 +260,7 @@ Use `verifyCapabilitySignature` when route handlers prefer explicit result handl
|
|
|
254
260
|
|
|
255
261
|
```ts
|
|
256
262
|
import { verifyCapabilitySignature } from "capability-worker/auth";
|
|
257
|
-
import { errorJson,
|
|
263
|
+
import { errorJson, okJson } from "capability-worker/http";
|
|
258
264
|
|
|
259
265
|
const auth = await verifyCapabilitySignature(request, env);
|
|
260
266
|
|
|
@@ -262,10 +268,7 @@ if (!auth.ok) {
|
|
|
262
268
|
return errorJson(auth.code, auth.message, auth.status);
|
|
263
269
|
}
|
|
264
270
|
|
|
265
|
-
return
|
|
266
|
-
ok: true,
|
|
267
|
-
accountId: auth.accountId
|
|
268
|
-
});
|
|
271
|
+
return okJson({ accountId: auth.accountId });
|
|
269
272
|
```
|
|
270
273
|
|
|
271
274
|
Rules for signed routes:
|
|
@@ -382,6 +385,7 @@ import {
|
|
|
382
385
|
errorJson,
|
|
383
386
|
errorResponse,
|
|
384
387
|
jsonResponse,
|
|
388
|
+
okJson,
|
|
385
389
|
textResponse
|
|
386
390
|
} from "capability-worker/http";
|
|
387
391
|
|
|
@@ -392,7 +396,16 @@ export async function readMeeting(request: Request, env: Env) {
|
|
|
392
396
|
throw new HttpError(404, "meeting_not_found", "Meeting not found.");
|
|
393
397
|
}
|
|
394
398
|
|
|
395
|
-
return
|
|
399
|
+
return okJson(meeting);
|
|
400
|
+
}
|
|
401
|
+
```
|
|
402
|
+
|
|
403
|
+
Success shape:
|
|
404
|
+
|
|
405
|
+
```json
|
|
406
|
+
{
|
|
407
|
+
"ok": true,
|
|
408
|
+
"data": {}
|
|
396
409
|
}
|
|
397
410
|
```
|
|
398
411
|
|
|
@@ -412,6 +425,7 @@ Useful helpers:
|
|
|
412
425
|
|
|
413
426
|
```ts
|
|
414
427
|
jsonResponse(value, init);
|
|
428
|
+
okJson(data, init);
|
|
415
429
|
textResponse(value, init);
|
|
416
430
|
htmlResponse(value, init);
|
|
417
431
|
redirectResponse(location, status);
|
|
@@ -915,6 +929,10 @@ CAPABILITY_PUBLIC_KEYS_URL = "https://host.example.com/capability/public-keys"
|
|
|
915
929
|
|
|
916
930
|
## New Capability Worker Recipe
|
|
917
931
|
|
|
932
|
+
The npm package includes a small starter at `templates/new-capability-worker`.
|
|
933
|
+
It uses `capability-build-assets`, `requireCapabilitySignature`, `okJson`,
|
|
934
|
+
`errorJson`, and `textResponse` from this package.
|
|
935
|
+
|
|
918
936
|
1. Create `cli/definition.json`.
|
|
919
937
|
2. Create `cli/mapping.json`.
|
|
920
938
|
3. Create `skills/<capability-name>/SKILL.md`.
|
|
@@ -932,7 +950,7 @@ Minimal route skeleton:
|
|
|
932
950
|
|
|
933
951
|
```ts
|
|
934
952
|
import { requireCapabilitySignature } from "capability-worker/auth";
|
|
935
|
-
import { errorResponse,
|
|
953
|
+
import { errorJson, errorResponse, okJson, textResponse } from "capability-worker/http";
|
|
936
954
|
|
|
937
955
|
export default {
|
|
938
956
|
async fetch(request: Request, env: Env): Promise<Response> {
|
|
@@ -940,7 +958,7 @@ export default {
|
|
|
940
958
|
const url = new URL(request.url);
|
|
941
959
|
|
|
942
960
|
if (url.pathname === "/api/health") {
|
|
943
|
-
return
|
|
961
|
+
return okJson({ status: "ok" });
|
|
944
962
|
}
|
|
945
963
|
|
|
946
964
|
if (url.pathname === "/api/meetings" && request.method === "GET") {
|
|
@@ -954,22 +972,13 @@ export default {
|
|
|
954
972
|
.all();
|
|
955
973
|
|
|
956
974
|
if (wantsJson) {
|
|
957
|
-
return
|
|
975
|
+
return okJson(rows.results);
|
|
958
976
|
}
|
|
959
977
|
|
|
960
978
|
return textResponse(formatMeetingsTable(rows.results));
|
|
961
979
|
}
|
|
962
980
|
|
|
963
|
-
return
|
|
964
|
-
{
|
|
965
|
-
ok: false,
|
|
966
|
-
error: {
|
|
967
|
-
code: "not_found",
|
|
968
|
-
message: "Not found."
|
|
969
|
-
}
|
|
970
|
-
},
|
|
971
|
-
404
|
|
972
|
-
);
|
|
981
|
+
return errorJson("not_found", "Not found.", 404);
|
|
973
982
|
} catch (error) {
|
|
974
983
|
return errorResponse(error);
|
|
975
984
|
}
|
|
File without changes
|
|
File without changes
|
|
@@ -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=
|
|
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,14 +1,29 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "capability-worker",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
4
4
|
"description": "Shared toolkit for Cloudflare Workers that expose CLI-like capabilities over HTTP.",
|
|
5
5
|
"type": "module",
|
|
6
|
+
"packageManager": "pnpm@11.9.0",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "git+https://github.com/Iliasnolsson/capability-worker.git"
|
|
10
|
+
},
|
|
6
11
|
"sideEffects": false,
|
|
7
12
|
"files": [
|
|
8
13
|
"dist",
|
|
14
|
+
"templates",
|
|
9
15
|
"README.md",
|
|
10
16
|
"LICENSE"
|
|
11
17
|
],
|
|
18
|
+
"scripts": {
|
|
19
|
+
"clean": "rm -rf dist",
|
|
20
|
+
"build": "pnpm clean && rollup -c",
|
|
21
|
+
"typecheck": "tsc --noEmit",
|
|
22
|
+
"test": "vitest run",
|
|
23
|
+
"prepack": "pnpm build",
|
|
24
|
+
"pack:check": "pnpm build && pnpm pack --dry-run",
|
|
25
|
+
"publish:npm": "pnpm typecheck && pnpm test && pnpm pack:check && pnpm publish --access public"
|
|
26
|
+
},
|
|
12
27
|
"bin": {
|
|
13
28
|
"capability-build-assets": "dist/node/build-capability-assets-cli.js",
|
|
14
29
|
"capability-validate": "dist/node/validate-cli.js"
|
|
@@ -65,13 +80,5 @@
|
|
|
65
80
|
"rollup-plugin-esbuild": "^6.0.0",
|
|
66
81
|
"typescript": "^5.9.0",
|
|
67
82
|
"vitest": "^4.1.0"
|
|
68
|
-
},
|
|
69
|
-
"scripts": {
|
|
70
|
-
"clean": "rm -rf dist",
|
|
71
|
-
"build": "pnpm clean && rollup -c",
|
|
72
|
-
"typecheck": "tsc --noEmit",
|
|
73
|
-
"test": "vitest run",
|
|
74
|
-
"pack:check": "pnpm build && pnpm pack --dry-run",
|
|
75
|
-
"publish:npm": "pnpm typecheck && pnpm test && pnpm pack:check && pnpm publish --access public"
|
|
76
83
|
}
|
|
77
|
-
}
|
|
84
|
+
}
|
|
@@ -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,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,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,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
|
+
|