@jimhoyd/urlcode 0.5.5 → 0.5.8
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/.claude/skills/urlcode-authoring/SKILL.md +16 -1
- package/.claude/skills/urlcode-operations/SKILL.md +5 -1
- package/README.md +24 -14
- package/dist/BUILD-MANIFEST.json +55 -54
- package/dist/agents-guide.js +5 -2
- package/dist/assets.js +2 -2
- package/dist/authoring.js +3 -58
- package/dist/aws.js +21 -4
- package/dist/body-schema.js +2 -3
- package/dist/capabilities.js +1 -1
- package/dist/capability-query.js +3 -3
- package/dist/catalog.js +1 -1
- package/dist/cli.js +42 -15
- package/dist/cloudflare.js +26 -1
- package/dist/config.js +1 -1
- package/dist/context.js +6 -6
- package/dist/egress.js +1 -1
- package/dist/examples.js +2 -2
- package/dist/explain.js +4 -6
- package/dist/extension-artifacts.js +8 -8
- package/dist/extension-transport.js +6 -4
- package/dist/function-worker.js +5 -2
- package/dist/functions.js +1 -1
- package/dist/guest-api.js +22 -15
- package/dist/http-policy.js +1 -3
- package/dist/http-response.js +61 -10
- package/dist/init-with.js +8 -8
- package/dist/interchange.js +3 -2
- package/dist/logging.js +4 -4
- package/dist/match.js +1 -1
- package/dist/mcp-authoring.js +1 -2
- package/dist/mcp.js +2 -2
- package/dist/object-guards.js +17 -0
- package/dist/pattern-guard.js +33 -3
- package/dist/policies/agents.js +1 -1
- package/dist/policies/cache.js +16 -4
- package/dist/policies/compression.js +1 -1
- package/dist/policies/security.js +1 -1
- package/dist/policies/throttle.js +1 -1
- package/dist/policies.js +1 -1
- package/dist/policy.js +3 -3
- package/dist/prerender.js +1 -2
- package/dist/project-dependencies.js +8 -8
- package/dist/proxy.js +1 -1
- package/dist/readiness.js +16 -17
- package/dist/review.js +2 -2
- package/dist/route-diff.js +4 -4
- package/dist/router.js +1 -1
- package/dist/runtime.js +4 -1
- package/dist/scaffold.js +1 -1
- package/dist/schema-query.js +1 -1
- package/dist/server.js +49 -7
- package/dist/signals.js +2 -2
- package/dist/site.js +2 -2
- package/dist/trusted-functions.js +1 -1
- package/dist/types/assets.d.ts +3 -2
- package/dist/types/authoring.d.ts +3 -6
- package/dist/types/body-schema.d.ts +2 -1
- package/dist/types/catalog.d.ts +2 -1
- package/dist/types/config.d.ts +0 -8
- package/dist/types/context.d.ts +3 -1
- package/dist/types/egress.d.ts +1 -1
- package/dist/types/explain.d.ts +4 -4
- package/dist/types/extension-artifacts.d.ts +4 -13
- package/dist/types/extension-transport.d.ts +6 -4
- package/dist/types/functions.d.ts +1 -1
- package/dist/types/http-response.d.ts +7 -3
- package/dist/types/init-with.d.ts +3 -9
- package/dist/types/logging.d.ts +4 -3
- package/dist/types/match.d.ts +2 -1
- package/dist/types/object-guards.d.ts +7 -0
- package/dist/types/pattern-guard.d.ts +0 -1
- package/dist/types/policies/cache.d.ts +4 -3
- package/dist/types/policies/compression.d.ts +1 -1
- package/dist/types/policies/throttle.d.ts +1 -1
- package/dist/types/policy.d.ts +4 -3
- package/dist/types/project-dependencies.d.ts +1 -40
- package/dist/types/proxy.d.ts +2 -1
- package/dist/types/readiness.d.ts +12 -16
- package/dist/types/review.d.ts +0 -2
- package/dist/types/route-diff.d.ts +4 -3
- package/dist/types/router.d.ts +1 -1
- package/dist/types/server.d.ts +22 -0
- package/dist/types/signals.d.ts +3 -2
- package/dist/types/site.d.ts +1 -2
- package/dist/types/trusted-functions.d.ts +1 -1
- package/dist/types/types.d.ts +17 -11
- package/dist/types/vercel.d.ts +1 -0
- package/dist/types/verify-deployment.d.ts +1 -1
- package/dist/types.js +19 -12
- package/dist/vercel.js +12 -3
- package/dist/verify-deployment.js +29 -11
- package/docs/AI-AUTHORING.md +10 -1
- package/docs/FUNCTION-SECURITY.md +4 -0
- package/docs/README.md +2 -2
- package/docs/TOOLING.md +27 -1
- package/examples/assets/Makefile +1 -1
- package/examples/assets/example.yaml +3 -3
- package/examples/aws/example.yaml +3 -3
- package/examples/cloudflare/example.yaml +3 -3
- package/examples/compliance/README.md +1 -1
- package/examples/compliance/example.yaml +1 -1
- package/examples/compliance/rules.mjs +2 -2
- package/examples/conditions/example.yaml +3 -3
- package/examples/cookbook/README.md +4 -4
- package/examples/cookbook/example.yaml +3 -3
- package/examples/coverage-waiver/example.yaml +3 -3
- package/examples/egress/example.yaml +2 -2
- package/examples/extensions/example.yaml +1 -1
- package/examples/lifecycle/example.yaml +2 -2
- package/examples/not-found/README.md +2 -2
- package/examples/not-found/example.yaml +3 -3
- package/examples/prerender/README.md +4 -4
- package/examples/prerender/example.yaml +2 -2
- package/examples/provider-conformance/example.yaml +2 -2
- package/examples/shared-blocks/example.yaml +3 -3
- package/examples/vercel/example.yaml +3 -3
- package/llms-full.txt +49 -18
- package/llms.txt +9 -2
- package/package.json +14 -13
- package/skills/urlcode/SKILL.md +7 -7
- package/starters/default/.github/workflows/urlcode.yml +2 -1
- package/starters/default/AGENTS.md +3 -3
- package/starters/default/Makefile +1 -1
- package/starters/default/README.md +15 -13
- package/starters/default/tests/requests.json +0 -50
- package/starters/default/urlcode.yaml +1 -4
- package/dist/scripts/operational-drills.js +0 -33
- package/starters/default/functions/hello.mjs +0 -3
- package/starters/default/middleware/headers.mjs +0 -6
- package/starters/default/routes/functions.yaml +0 -20
- package/starters/default/routes/marketing/links.yaml +0 -7
- package/starters/page/README.md +0 -14
- package/starters/page/public/index.html +0 -12
- package/starters/page/tests/requests.json +0 -17
- package/starters/page/urlcode.yaml +0 -6
package/dist/vercel.js
CHANGED
|
@@ -12,6 +12,17 @@ import { assert, HttpError } from './errors.js';
|
|
|
12
12
|
|
|
13
13
|
const platformOrigins = ['VERCEL_PROJECT_PRODUCTION_URL','VERCEL_URL','VERCEL_BRANCH_URL'];
|
|
14
14
|
|
|
15
|
+
// `x-forwarded-for` is documented as overwritten (not appended to) by
|
|
16
|
+
// Vercel's edge, but only when Vercel itself is the client-facing proxy; a
|
|
17
|
+
// project's own proxy in front of Vercel can still set it before Vercel ever
|
|
18
|
+
// sees the request. `x-vercel-forwarded-for` is Vercel's own copy of the same
|
|
19
|
+
// value and is the header Vercel's docs say to prefer for exactly that reason
|
|
20
|
+
// (https://vercel.com/docs/headers/request-headers#x-vercel-forwarded-for).
|
|
21
|
+
// Exported so the adapter's client-IP resolution can be verified directly.
|
|
22
|
+
export function forwardedClient(headers , headerCounts ) {
|
|
23
|
+
return headerCounts['x-vercel-forwarded-for'] === 1 ? (headers.get('x-vercel-forwarded-for') ?? '').split(',')[0]?.trim() || undefined : undefined;
|
|
24
|
+
}
|
|
25
|
+
|
|
15
26
|
function readBody(req , limit ) {
|
|
16
27
|
if (req.headers['content-length'] && Number(req.headers['content-length']) > limit) return Promise.reject(new HttpError(413,'Request body too large'));
|
|
17
28
|
return new Promise((resolve,reject) => {
|
|
@@ -49,9 +60,7 @@ export function createVercelHandler({ project = process.cwd(), origin, environme
|
|
|
49
60
|
}
|
|
50
61
|
const limit = Math.min(maxBodyBytes, runtime.requestLimit(target) ?? maxBodyBytes);
|
|
51
62
|
const body = await readBody(req,limit);
|
|
52
|
-
|
|
53
|
-
// its leftmost entry is the client; the socket peer is the platform.
|
|
54
|
-
const forwarded = headerCounts['x-forwarded-for'] === 1 ? (headers.get('x-forwarded-for') ?? '').split(',')[0]?.trim() : undefined;
|
|
63
|
+
const forwarded = forwardedClient(headers, headerCounts);
|
|
55
64
|
const publicOrigin = resolveOrigin(origin,environment,platformOrigins) ?? 'http://localhost';
|
|
56
65
|
const result = await runtime.handle({ target, method, headers, headerCounts, body,
|
|
57
66
|
origin: publicOrigin, client: forwarded || req.socket?.remoteAddress });
|
|
@@ -3,6 +3,7 @@ import { request, Agent } from 'node:http';
|
|
|
3
3
|
import { request as secureRequest, Agent as SecureAgent } from 'node:https';
|
|
4
4
|
import { randomBytes } from 'node:crypto';
|
|
5
5
|
import { assert } from './errors.js';
|
|
6
|
+
import { isRecord } from './object-guards.js';
|
|
6
7
|
import { createRuntime } from './runtime.js';
|
|
7
8
|
|
|
8
9
|
import { benchmarkTarget, hit, readFixtures, runFixtures, isStepsFixture, probeAgent } from './readiness.js';
|
|
@@ -26,7 +27,7 @@ import { lists as bundledAgents } from '../data/agents/index.js';
|
|
|
26
27
|
// snapshot would; it says nothing about the host, the proxy or the network.
|
|
27
28
|
|
|
28
29
|
|
|
29
|
-
|
|
30
|
+
|
|
30
31
|
|
|
31
32
|
|
|
32
33
|
|
|
@@ -46,32 +47,49 @@ const MAX_REQUESTS = 10000;
|
|
|
46
47
|
const BODY_LIMIT = 1048576;
|
|
47
48
|
const SNIPPET = 200;
|
|
48
49
|
const siteTypes = { 'site.robots': 'text/plain', 'site.sitemap': 'application/xml', 'site.securityTxt': 'text/plain', 'site.llms': 'text/plain', 'site.notFound': 'text/html' };
|
|
49
|
-
const isRecord = (value ) => value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
50
50
|
const snippet = (body ) => body.length > SNIPPET ? `${body.length} bytes: ${body.subarray(0, SNIPPET).toString('utf8')}` : body.toString('utf8');
|
|
51
51
|
const tlsCode = /CERT|TLS|SSL|SELF_SIGNED/;
|
|
52
52
|
|
|
53
53
|
// One request, one answer. Bodies are read up to 1 MiB and never logged
|
|
54
54
|
// beyond the snippet a failing assertion carries. Redirects are not followed
|
|
55
55
|
// and a transport error is reported as text, never thrown.
|
|
56
|
+
//
|
|
57
|
+
// `timeout` on a Node request/response is an *idle* timer: it only fires
|
|
58
|
+
// once a socket has gone quiet, so a deployment that drips one byte just
|
|
59
|
+
// under that interval keeps resetting it and can hold this probe open
|
|
60
|
+
// indefinitely. A single wall-clock deadline covering the whole request
|
|
61
|
+
// (connect through body) closes that gap. Past `BODY_LIMIT` the response is
|
|
62
|
+
// destroyed rather than left to keep streaming into a discard loop: this
|
|
63
|
+
// probe only ever needs a bounded snippet, never the rest of an oversized
|
|
64
|
+
// or endless body.
|
|
56
65
|
function probe(target , { path, method = 'GET', headers = {} } , agent , timeoutMs ) {
|
|
57
66
|
return new Promise(resolve => {
|
|
58
|
-
|
|
67
|
+
let settled = false;
|
|
68
|
+
const finish = (answer ) => { if (settled) return; settled = true; clearTimeout(deadline); resolve(answer); };
|
|
69
|
+
const fail = (error ) => {
|
|
59
70
|
const code = isRecord(error) && typeof error.code === 'string' ? error.code : error instanceof Error ? error.message : 'transport';
|
|
60
|
-
|
|
71
|
+
finish({ status: 0, headers: {}, body: Buffer.alloc(0), error: code });
|
|
61
72
|
};
|
|
62
73
|
let req ;
|
|
74
|
+
const deadline = setTimeout(() => { req?.destroy(new Error('timeout')); }, timeoutMs);
|
|
63
75
|
try {
|
|
64
76
|
const options = { host: target.hostname, port: target.port, path, method, agent, timeout: timeoutMs,
|
|
65
77
|
headers: { host: target.hostname, 'user-agent': probeAgent, 'accept-encoding': 'identity', ...headers } };
|
|
66
78
|
req = (target.protocol === 'https:' ? secureRequest : request)(options, (res ) => {
|
|
79
|
+
const out = {};
|
|
80
|
+
for (const [key, value] of Object.entries(res.headers)) if (value !== undefined) out[key] = Array.isArray(value) ? value.join(', ') : value;
|
|
67
81
|
const chunks = []; let size = 0;
|
|
68
|
-
|
|
69
|
-
res.on('
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
resolve({ status: res.statusCode ?? 0, headers: out, body: Buffer.concat(chunks) });
|
|
82
|
+
const done = () => finish({ status: res.statusCode ?? 0, headers: out, body: Buffer.concat(chunks) });
|
|
83
|
+
res.on('data', (chunk ) => {
|
|
84
|
+
if (size < BODY_LIMIT) chunks.push(chunk);
|
|
85
|
+
size += chunk.length;
|
|
86
|
+
if (size > BODY_LIMIT) res.destroy();
|
|
74
87
|
});
|
|
88
|
+
res.on('error', fail);
|
|
89
|
+
res.on('end', done);
|
|
90
|
+
// A body cut off by the BODY_LIMIT destroy() above ends here, not on
|
|
91
|
+
// 'end'; the snippet already collected is still a valid answer.
|
|
92
|
+
res.on('close', () => { if (!settled) done(); });
|
|
75
93
|
});
|
|
76
94
|
req.on('error', fail); req.on('timeout', () => req?.destroy(new Error('timeout'))); req.end();
|
|
77
95
|
} catch (error) { req?.destroy(); fail(error); }
|
|
@@ -148,7 +166,7 @@ export async function verifyDeployment(project , { target, origin, expect
|
|
|
148
166
|
const parse = (answer ) => { try { const json = JSON.parse(answer.body.toString('utf8')); return isRecord(json) ? json : undefined; } catch { return undefined; } };
|
|
149
167
|
const healthJson = parse(health);
|
|
150
168
|
check(health.status === 200 && healthJson?.status === 'ok' && typeof healthJson.version === 'string' && typeof healthJson.routes === 'number',
|
|
151
|
-
{ check: 'probes', severity: 'high', message: '/_urlcode/health must answer 200 with {status:"ok",version,routes}', expected: '200', observed: `${health.status} ${snippet(health.body)}` });
|
|
169
|
+
{ check: 'probes', severity: 'high', message: '/_urlcode/health must answer 200 with {status:"ok",version,routes}; start the target with --health-details (or --metrics) if it currently answers {status} only', expected: '200', observed: `${health.status} ${snippet(health.body)}` });
|
|
152
170
|
const ready = await send({ path: '/_urlcode/ready' }), readyJson = parse(ready);
|
|
153
171
|
check(ready.status === 200 && readyJson?.status === 'ok', { check: 'probes', severity: 'high', message: '/_urlcode/ready must answer 200 (the deployment reports itself degraded or is not URLCode)', expected: '200', observed: `${ready.status} ${snippet(ready.body)}` });
|
|
154
172
|
if (typeof readyJson?.version === 'string') version.observed = readyJson.version;
|
package/docs/AI-AUTHORING.md
CHANGED
|
@@ -63,6 +63,11 @@ The root [llms.txt](../llms.txt) is a compact discovery index; the generated
|
|
|
63
63
|
reading order for agents that want complete context in one fetch. It is a convenience,
|
|
64
64
|
not a runtime protocol or a guarantee that AI clients automatically consume it.
|
|
65
65
|
The generated reference is checked against the schema in `npm run verify`.
|
|
66
|
+
[URLCode AI](https://urlcode.ai/) is the optional hosted companion for shared
|
|
67
|
+
skills and LLM tooling. It complements the project-local MCP server; see
|
|
68
|
+
[hosted AI MCP setup](TOOLING.md#optional-hosted-ai-mcp) for its authenticated
|
|
69
|
+
remote connection details. Its machine-readable entry point is
|
|
70
|
+
[`https://urlcode.ai/llms.txt`](https://urlcode.ai/llms.txt).
|
|
66
71
|
|
|
67
72
|
Follow [organization and readability practices](BEST-PRACTICES.md): preserve local
|
|
68
73
|
conventions, use clear names, keep middleware focused and avoid needless layers.
|
|
@@ -138,7 +143,7 @@ urlcode benchmark --project ./my-links --requests 100 --concurrency 2
|
|
|
138
143
|
```
|
|
139
144
|
|
|
140
145
|
Use the intentional actual count, not always 2. Runtime checkout users can replace
|
|
141
|
-
`urlcode` with `node src/cli.ts`. Template users can use the equivalent npm scripts.
|
|
146
|
+
`urlcode` with `node packages/core/src/cli.ts`. Template users can use the equivalent npm scripts.
|
|
142
147
|
External bindings require an already reviewed policy; add `--policy` where needed.
|
|
143
148
|
The benchmark operates locally; it is not a load test of an external deployment.
|
|
144
149
|
|
|
@@ -432,6 +437,10 @@ the operator, never by tool arguments; `--allow-authoring` on the operator's
|
|
|
432
437
|
command line adds project-confined route, recipe, scaffold and runner tools.
|
|
433
438
|
`urlcode init` writes `.mcp.json` so Claude Code and Codex register the read-only
|
|
434
439
|
server for the project ([registering the server](TOOLING.md#registering-the-server)).
|
|
440
|
+
The optional hosted URLCode AI MCP is a separate authenticated connection for
|
|
441
|
+
shared skills and LLM tools; it does not replace the local project server. Its
|
|
442
|
+
endpoint and credential-handling requirements are in
|
|
443
|
+
[hosted AI MCP setup](TOOLING.md#optional-hosted-ai-mcp).
|
|
435
444
|
Inspection is not activation/deployment readiness: real grants, asset snapshots
|
|
436
445
|
and service availability still need normal runtime checks. Provider conformance replay is local evidence; only
|
|
437
446
|
explicit live [deployment observations](PROVIDER-VERIFICATION.md) test ingress.
|
|
@@ -82,6 +82,10 @@ into it; only the default for routes that declare neither option has changed.
|
|
|
82
82
|
- A fresh guest heap/module state per invocation prevents state crossing requests.
|
|
83
83
|
- 32 MiB guest heap, 512 KiB stack, source/input/output/header limits, bounded
|
|
84
84
|
concurrency, guest interruption and an independent worker termination deadline.
|
|
85
|
+
- The guest's result is recorded where guest code cannot rewrite it, and the
|
|
86
|
+
host checks its shape before trusting it. A result that states a body length
|
|
87
|
+
for any method other than HEAD is invalid and answers 502; the runtime frames
|
|
88
|
+
the response by the bytes it sends ([responses](HTTP.md#responses)).
|
|
85
89
|
- External bindings are denied by default. Project YAML cannot self-authorize.
|
|
86
90
|
Operator grants are exact-name, route-scoped and pinned to configuration/source.
|
|
87
91
|
|
package/docs/README.md
CHANGED
|
@@ -8,7 +8,7 @@ Use documentation from the same pinned revision as your runtime.
|
|
|
8
8
|
| Understand what URLCode does | [Framework](FRAMEWORK.md) |
|
|
9
9
|
| Build my first project | [Installation](INSTALL.md), then [YAML guide](YAML-GUIDE.md) |
|
|
10
10
|
| Build a site with UI, accounts and admin | [Composing a site](COMPOSING-A-SITE.md) |
|
|
11
|
-
| Have an AI author a project | [AI authoring](AI-AUTHORING.md), [agent index](../llms.txt) |
|
|
11
|
+
| Have an AI author a project | [AI authoring](AI-AUTHORING.md), [agent index](../llms.txt), [hosted agent guide](https://urlcode.ai/llms.txt) |
|
|
12
12
|
| Deploy and operate a project | [Operations](OPERATIONS.md) |
|
|
13
13
|
| Contribute to URLCode | [Contributing](../CONTRIBUTING.md), [local development](LOCAL-DEVELOPMENT.md) |
|
|
14
14
|
|
|
@@ -26,7 +26,7 @@ The [specification](SPECIFICATION.md) owns implemented semantics; the
|
|
|
26
26
|
| Load authoring/operations rules into an agent | [Authoring skill](../.claude/skills/urlcode-authoring/SKILL.md), [operations skill](../.claude/skills/urlcode-operations/SKILL.md), [how they are distributed](AI-AUTHORING.md#agent-skills) |
|
|
27
27
|
| Understand exact behavior | [Specification](SPECIFICATION.md), [routing](ROUTING.md), [HTTP](HTTP.md) |
|
|
28
28
|
| Run examples | [Executable cookbook](../examples/cookbook/README.md), [prerender recipe](../examples/prerender/README.md), [small starter](STARTERS.md) |
|
|
29
|
-
| Let an AI build routes | [The framework](FRAMEWORK.md), [AI authoring guide](AI-AUTHORING.md), [llms.txt](../llms.txt), [SDK and read-only MCP](TOOLING.md) |
|
|
29
|
+
| Let an AI build routes | [The framework](FRAMEWORK.md), [AI authoring guide](AI-AUTHORING.md), [llms.txt](../llms.txt), [hosted agent guide](https://urlcode.ai/llms.txt), [SDK and read-only MCP](TOOLING.md) |
|
|
30
30
|
| Reuse code around routes | [Middleware](MIDDLEWARE.md), [middleware examples](MIDDLEWARE-EXAMPLES.md) |
|
|
31
31
|
| Handle secrets and decide what to sandbox | [Function security](FUNCTION-SECURITY.md) |
|
|
32
32
|
| Author guest functions in TypeScript | [Build-time guest transpilation](TYPESCRIPT-AUTHORING.md) |
|
package/docs/TOOLING.md
CHANGED
|
@@ -330,7 +330,7 @@ choices added by hand, never by `init` or by an agent.
|
|
|
330
330
|
|
|
331
331
|
- **Claude Code** reads `.mcp.json` in the project directory as a project-scoped
|
|
332
332
|
server and asks for approval on first use. A project that pins the runtime in its
|
|
333
|
-
`package.json` (`--
|
|
333
|
+
`package.json` (`--with`, `--manifest`) gets
|
|
334
334
|
`"command": "npx"` with `--no --package @jimhoyd/urlcode urlcode mcp ...`, which runs the
|
|
335
335
|
installed copy and never fetches (do not use a bare `npx urlcode`: that names an unrelated
|
|
336
336
|
registry package). A project without one keeps the bare `urlcode` command for a global
|
|
@@ -354,6 +354,32 @@ The generated `AGENTS.md` and the packaged skill tell agents to prefer
|
|
|
354
354
|
`get_manifest` when the server is registered and to fall back to the matching
|
|
355
355
|
CLI commands otherwise.
|
|
356
356
|
|
|
357
|
+
## Optional hosted AI MCP
|
|
358
|
+
|
|
359
|
+
The local `urlcode mcp` server remains the project-aware URLCode integration:
|
|
360
|
+
it reads the selected checkout, validates its configuration and never needs a
|
|
361
|
+
network credential. Do not replace its generated `.mcp.json` entry with a
|
|
362
|
+
hosted service.
|
|
363
|
+
|
|
364
|
+
[URLCode AI](https://urlcode.ai/) is a separate, opt-in hosted service for
|
|
365
|
+
shared skills and LLM-assisted work. A client that supports authenticated HTTP
|
|
366
|
+
MCP can add it as a second server with these connection details:
|
|
367
|
+
|
|
368
|
+
- URL: `https://mcp.urlcode.ai/mcp`
|
|
369
|
+
- request header: `Authorization: Bearer <URLCODE_AI_TOKEN>`
|
|
370
|
+
|
|
371
|
+
Store `URLCODE_AI_TOKEN` in the MCP client's secret or environment-variable
|
|
372
|
+
facility. Do not put a literal bearer token in `.mcp.json`, `urlcode.yaml`, a
|
|
373
|
+
checked-in client configuration, or a shell history. Each client has its own
|
|
374
|
+
remote-server configuration syntax, so configure that endpoint explicitly in
|
|
375
|
+
the client rather than asking `urlcode init` to generate it.
|
|
376
|
+
|
|
377
|
+
The hosted tools are not a proxy for this local server: they do not receive the
|
|
378
|
+
project root and do not replace local `get_context`, validation, manifest,
|
|
379
|
+
extension-artifact or authoring tools. Keep the local server registered for
|
|
380
|
+
framework- and project-specific work; add the hosted server only where its
|
|
381
|
+
shared skill catalog or LLM tools are useful.
|
|
382
|
+
|
|
357
383
|
## Authoring mode
|
|
358
384
|
|
|
359
385
|
`urlcode mcp --allow-authoring --project DIR` adds six tools to the twenty-two read
|
package/examples/assets/Makefile
CHANGED
|
@@ -9,9 +9,9 @@ files: [urlcode.yaml, functions/hello.mjs, public/about.html, public/guide.txt,
|
|
|
9
9
|
tests:
|
|
10
10
|
fixtures: tests/requests.json
|
|
11
11
|
commands:
|
|
12
|
-
- node src/cli.ts validate --local --project examples/assets
|
|
13
|
-
- node src/cli.ts test --project examples/assets
|
|
14
|
-
- node src/cli.ts audit --project examples/assets --expect-routes 5
|
|
12
|
+
- node packages/core/src/cli.ts validate --local --project examples/assets
|
|
13
|
+
- node packages/core/src/cli.ts test --project examples/assets
|
|
14
|
+
- node packages/core/src/cli.ts audit --project examples/assets --expect-routes 5
|
|
15
15
|
behavior:
|
|
16
16
|
- native page, static and download routes serve snapshotted files with explicit caching
|
|
17
17
|
- one trusted function shows the two kinds of route side by side
|
|
@@ -12,9 +12,9 @@ files: [urlcode.yaml, handler.mjs, template.yaml, public/index.html, public/note
|
|
|
12
12
|
tests:
|
|
13
13
|
fixtures: tests/requests.json
|
|
14
14
|
commands:
|
|
15
|
-
- node src/cli.ts validate --local --project examples/aws
|
|
16
|
-
- node src/cli.ts test --project examples/aws
|
|
17
|
-
- node src/cli.ts audit --project examples/aws --expect-routes 5
|
|
15
|
+
- node packages/core/src/cli.ts validate --local --project examples/aws
|
|
16
|
+
- node packages/core/src/cli.ts test --project examples/aws
|
|
17
|
+
- node packages/core/src/cli.ts audit --project examples/aws --expect-routes 5
|
|
18
18
|
behavior:
|
|
19
19
|
- every route is native, so the project activates on Lambda without the sandbox
|
|
20
20
|
- handler.mjs wraps the project with createLambdaHandler; deployment itself is unverified evidence
|
|
@@ -12,8 +12,8 @@ files: [urlcode.yaml, wrangler.toml, tests/requests.json, README.md]
|
|
|
12
12
|
tests:
|
|
13
13
|
fixtures: tests/requests.json
|
|
14
14
|
commands:
|
|
15
|
-
- node src/cli.ts validate --local --project examples/cloudflare
|
|
16
|
-
- node src/cli.ts test --project examples/cloudflare
|
|
17
|
-
- node src/cli.ts audit --project examples/cloudflare --expect-routes 4
|
|
15
|
+
- node packages/core/src/cli.ts validate --local --project examples/cloudflare
|
|
16
|
+
- node packages/core/src/cli.ts test --project examples/cloudflare
|
|
17
|
+
- node packages/core/src/cli.ts audit --project examples/cloudflare --expect-routes 4
|
|
18
18
|
behavior:
|
|
19
19
|
- only capabilities the Worker lowering supports appear, so the build succeeds on every target
|
|
@@ -10,7 +10,7 @@ Run it against the cookbook from the runtime checkout. The module must be an
|
|
|
10
10
|
absolute path outside the audited project:
|
|
11
11
|
|
|
12
12
|
```sh
|
|
13
|
-
node src/cli.ts audit --project examples/cookbook \
|
|
13
|
+
node packages/core/src/cli.ts audit --project examples/cookbook \
|
|
14
14
|
--compliance baseline --compliance-rules "$PWD/examples/compliance/rules.mjs" --compliance-warn
|
|
15
15
|
```
|
|
16
16
|
|
|
@@ -6,6 +6,6 @@ runnable: false
|
|
|
6
6
|
files: [rules.mjs, README.md]
|
|
7
7
|
tests:
|
|
8
8
|
commands:
|
|
9
|
-
- node src/cli.ts audit --project examples/cookbook --compliance baseline --compliance-rules "$PWD/examples/compliance/rules.mjs" --compliance-warn
|
|
9
|
+
- node packages/core/src/cli.ts audit --project examples/cookbook --compliance baseline --compliance-rules "$PWD/examples/compliance/rules.mjs" --compliance-warn
|
|
10
10
|
behavior:
|
|
11
11
|
- three custom rules run alongside the built-in profile; one built-in notice is disabled and one severity raised
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// Operator compliance rules for `urlcode audit --compliance-rules`.
|
|
2
|
-
// Runnable against the cookbook from the runtime checkout:
|
|
2
|
+
// Runnable against the cookbook from the runtime checkout (after `npm run build`):
|
|
3
3
|
//
|
|
4
|
-
// node
|
|
4
|
+
// node dist/cli.js audit --project examples/cookbook \
|
|
5
5
|
// --compliance baseline --compliance-rules "$PWD/examples/compliance/rules.mjs" --compliance-warn
|
|
6
6
|
//
|
|
7
7
|
// The file must sit outside the audited project (it is trusted host code, the
|
|
@@ -12,8 +12,8 @@ files: [urlcode.yaml, tests/requests.json, README.md]
|
|
|
12
12
|
tests:
|
|
13
13
|
fixtures: tests/requests.json
|
|
14
14
|
commands:
|
|
15
|
-
- node src/cli.ts validate --local --project examples/conditions --origin https://conditions.example.test
|
|
16
|
-
- node src/cli.ts test --project examples/conditions --origin https://conditions.example.test
|
|
17
|
-
- node src/cli.ts audit --project examples/conditions --expect-routes 4 --origin https://conditions.example.test
|
|
15
|
+
- node packages/core/src/cli.ts validate --local --project examples/conditions --origin https://conditions.example.test
|
|
16
|
+
- node packages/core/src/cli.ts test --project examples/conditions --origin https://conditions.example.test
|
|
17
|
+
- node packages/core/src/cli.ts audit --project examples/conditions --expect-routes 4 --origin https://conditions.example.test
|
|
18
18
|
behavior:
|
|
19
19
|
- cases are provably disjoint and every branch stays no-store; no branch is authentication
|
|
@@ -11,10 +11,10 @@ external services are required. Redirects target example.com; tests never follow
|
|
|
11
11
|
From the runtime checkout:
|
|
12
12
|
|
|
13
13
|
```sh
|
|
14
|
-
node src/cli.ts validate --project examples/cookbook
|
|
15
|
-
node src/cli.ts test --project examples/cookbook
|
|
16
|
-
node src/cli.ts audit --project examples/cookbook --expect-routes 40
|
|
17
|
-
node src/cli.ts dev --project examples/cookbook
|
|
14
|
+
node packages/core/src/cli.ts validate --project examples/cookbook
|
|
15
|
+
node packages/core/src/cli.ts test --project examples/cookbook
|
|
16
|
+
node packages/core/src/cli.ts audit --project examples/cookbook --expect-routes 40
|
|
17
|
+
node packages/core/src/cli.ts dev --project examples/cookbook
|
|
18
18
|
```
|
|
19
19
|
|
|
20
20
|
The [YAML guide](../../docs/YAML-GUIDE.md) explains the recipes and binding policy.
|
|
@@ -9,9 +9,9 @@ files: [urlcode.yaml, routes/code.yaml, routes/redirects.yaml, routes/responses.
|
|
|
9
9
|
tests:
|
|
10
10
|
fixtures: tests/requests.json
|
|
11
11
|
commands:
|
|
12
|
-
- node src/cli.ts validate --local --project examples/cookbook
|
|
13
|
-
- node src/cli.ts test --project examples/cookbook
|
|
14
|
-
- node src/cli.ts audit --project examples/cookbook --expect-routes 40
|
|
12
|
+
- node packages/core/src/cli.ts validate --local --project examples/cookbook
|
|
13
|
+
- node packages/core/src/cli.ts test --project examples/cookbook
|
|
14
|
+
- node packages/core/src/cli.ts audit --project examples/cookbook --expect-routes 40
|
|
15
15
|
behavior:
|
|
16
16
|
- urlcode examples search finds single routes here through the generated route-index.json
|
|
17
17
|
- fifteen routes exercise the fourteen middleware modules that the middleware recipe copies
|
|
@@ -9,8 +9,8 @@ files: [urlcode.yaml, functions/notes.mjs, tests/requests.json, README.md]
|
|
|
9
9
|
tests:
|
|
10
10
|
fixtures: tests/requests.json
|
|
11
11
|
commands:
|
|
12
|
-
- node src/cli.ts validate --local --project examples/coverage-waiver
|
|
13
|
-
- node src/cli.ts test --project examples/coverage-waiver
|
|
14
|
-
- node src/cli.ts audit --project examples/coverage-waiver --expect-routes 1
|
|
12
|
+
- node packages/core/src/cli.ts validate --local --project examples/coverage-waiver
|
|
13
|
+
- node packages/core/src/cli.ts test --project examples/coverage-waiver
|
|
14
|
+
- node packages/core/src/cli.ts audit --project examples/coverage-waiver --expect-routes 1
|
|
15
15
|
behavior:
|
|
16
16
|
- POST /notes is waived from coverage with a reason; GET is asserted by a fixture; the waiver is listed by audit, not hidden
|
|
@@ -13,7 +13,7 @@ grants:
|
|
|
13
13
|
files: [urlcode.yaml, README.md]
|
|
14
14
|
tests:
|
|
15
15
|
commands:
|
|
16
|
-
- node src/cli.ts permissions --project examples/egress
|
|
17
|
-
- node src/cli.ts validate --local --project examples/egress --policy /operator/egress-policy.json
|
|
16
|
+
- node packages/core/src/cli.ts permissions --project examples/egress
|
|
17
|
+
- node packages/core/src/cli.ts validate --local --project examples/egress --policy /operator/egress-policy.json
|
|
18
18
|
behavior:
|
|
19
19
|
- without the policy the project refuses to activate; nothing here reaches the network during authoring inspection
|
|
@@ -16,6 +16,6 @@ grants:
|
|
|
16
16
|
files: [urlcode.yaml, README.md]
|
|
17
17
|
tests:
|
|
18
18
|
commands:
|
|
19
|
-
- node src/cli.ts validate --local --project examples/extensions --host-file /operator/host.mjs --origin https://extensions.example.test
|
|
19
|
+
- node packages/core/src/cli.ts validate --local --project examples/extensions --host-file /operator/host.mjs --origin https://extensions.example.test
|
|
20
20
|
behavior:
|
|
21
21
|
- "/account uses auth: {role: member}; routes and audit show the expanded policies.extensions.auth form"
|
|
@@ -12,8 +12,8 @@ files: [urlcode.yaml, functions/notes.mjs, tests/requests.json, README.md]
|
|
|
12
12
|
tests:
|
|
13
13
|
fixtures: tests/requests.json
|
|
14
14
|
commands:
|
|
15
|
-
- node src/cli.ts test --project examples/lifecycle
|
|
16
|
-
- node src/cli.ts audit --project examples/lifecycle --expect-routes 2
|
|
15
|
+
- node packages/core/src/cli.ts test --project examples/lifecycle
|
|
16
|
+
- node packages/core/src/cli.ts audit --project examples/lifecycle --expect-routes 2
|
|
17
17
|
behavior:
|
|
18
18
|
- a note created by POST /notes is readable, replaceable and still present after the runtime restarts on the same data directory
|
|
19
19
|
- the id and Location the create response returns are captured and substituted into later steps as {{id}} and {{where}}
|
|
@@ -5,6 +5,6 @@ route, with status 404 and `text/html`. See
|
|
|
5
5
|
[site conventions](../../docs/SITE.md#notfound--404html).
|
|
6
6
|
|
|
7
7
|
```sh
|
|
8
|
-
node src/cli.ts test --project examples/not-found
|
|
9
|
-
node src/cli.ts build --target static --project examples/not-found # writes 404.html
|
|
8
|
+
node packages/core/src/cli.ts test --project examples/not-found
|
|
9
|
+
node packages/core/src/cli.ts build --target static --project examples/not-found # writes 404.html
|
|
10
10
|
```
|
|
@@ -9,9 +9,9 @@ files: [urlcode.yaml, public/index.html, public/404.html, tests/requests.json, R
|
|
|
9
9
|
tests:
|
|
10
10
|
fixtures: tests/requests.json
|
|
11
11
|
commands:
|
|
12
|
-
- node src/cli.ts validate --local --project examples/not-found
|
|
13
|
-
- node src/cli.ts test --project examples/not-found
|
|
14
|
-
- node src/cli.ts audit --project examples/not-found --expect-routes 2
|
|
12
|
+
- node packages/core/src/cli.ts validate --local --project examples/not-found
|
|
13
|
+
- node packages/core/src/cli.ts test --project examples/not-found
|
|
14
|
+
- node packages/core/src/cli.ts audit --project examples/not-found --expect-routes 2
|
|
15
15
|
behavior:
|
|
16
16
|
- an unmatched GET or HEAD is answered with the page and status 404
|
|
17
17
|
- other methods keep the plain-text 404
|
|
@@ -14,11 +14,11 @@ runs no code at all.
|
|
|
14
14
|
From the runtime checkout:
|
|
15
15
|
|
|
16
16
|
```sh
|
|
17
|
-
node src/cli.ts dev --project examples/prerender # the dynamic source, live
|
|
18
|
-
node src/cli.ts test --project examples/prerender # 7 fixtures
|
|
17
|
+
node packages/core/src/cli.ts dev --project examples/prerender # the dynamic source, live
|
|
18
|
+
node packages/core/src/cli.ts test --project examples/prerender # 7 fixtures
|
|
19
19
|
node examples/prerender/prerender.mjs examples/prerender /absolute/out
|
|
20
|
-
node src/cli.ts test --project /absolute/out # fixtures the build wrote
|
|
21
|
-
node src/cli.ts audit --project /absolute/out --expect-routes 3
|
|
20
|
+
node packages/core/src/cli.ts test --project /absolute/out # fixtures the build wrote
|
|
21
|
+
node packages/core/src/cli.ts audit --project /absolute/out --expect-routes 3
|
|
22
22
|
```
|
|
23
23
|
|
|
24
24
|
The same three URLs answer identically before and after. The difference is what
|
|
@@ -9,8 +9,8 @@ files: [urlcode.yaml, functions/page.mjs, middleware/template.mjs, prerender.mjs
|
|
|
9
9
|
tests:
|
|
10
10
|
fixtures: tests/requests.json
|
|
11
11
|
commands:
|
|
12
|
-
- node src/cli.ts test --project examples/prerender
|
|
12
|
+
- node packages/core/src/cli.ts test --project examples/prerender
|
|
13
13
|
- node examples/prerender/prerender.mjs examples/prerender /absolute/out
|
|
14
|
-
- node src/cli.ts audit --project /absolute/out --expect-routes 3
|
|
14
|
+
- node packages/core/src/cli.ts audit --project /absolute/out --expect-routes 3
|
|
15
15
|
behavior:
|
|
16
16
|
- the rendered project serves the same bytes from native page routes with no sandbox on the request path
|
|
@@ -8,7 +8,7 @@ routes: 5
|
|
|
8
8
|
files: [urlcode.yaml, README.md]
|
|
9
9
|
tests:
|
|
10
10
|
commands:
|
|
11
|
-
- node src/cli.ts validate --local --project examples/provider-conformance
|
|
12
|
-
- node src/cli.ts verify-provider --target self-hosted --origin https://owned-fixture.example
|
|
11
|
+
- node packages/core/src/cli.ts validate --local --project examples/provider-conformance
|
|
12
|
+
- node packages/core/src/cli.ts verify-provider --target self-hosted --origin https://owned-fixture.example
|
|
13
13
|
behavior:
|
|
14
14
|
- no functions, bindings, assets or policies, so every target activates it; never mix it into a real project
|
|
@@ -10,8 +10,8 @@ files: [urlcode.yaml, tests/requests.json, README.md]
|
|
|
10
10
|
tests:
|
|
11
11
|
fixtures: tests/requests.json
|
|
12
12
|
commands:
|
|
13
|
-
- node src/cli.ts validate --local --project examples/shared-blocks
|
|
14
|
-
- node src/cli.ts test --project examples/shared-blocks
|
|
15
|
-
- node src/cli.ts audit --project examples/shared-blocks --expect-routes 4
|
|
13
|
+
- node packages/core/src/cli.ts validate --local --project examples/shared-blocks
|
|
14
|
+
- node packages/core/src/cli.ts test --project examples/shared-blocks
|
|
15
|
+
- node packages/core/src/cli.ts audit --project examples/shared-blocks --expect-routes 4
|
|
16
16
|
behavior:
|
|
17
17
|
- a route's own request or response key replaces the shared block whole; nothing is deep merged
|
|
@@ -12,8 +12,8 @@ files: [urlcode.yaml, api/index.js, vercel.json, public/index.html, public/notes
|
|
|
12
12
|
tests:
|
|
13
13
|
fixtures: tests/requests.json
|
|
14
14
|
commands:
|
|
15
|
-
- node src/cli.ts validate --local --project examples/vercel
|
|
16
|
-
- node src/cli.ts test --project examples/vercel
|
|
17
|
-
- node src/cli.ts audit --project examples/vercel --expect-routes 5
|
|
15
|
+
- node packages/core/src/cli.ts validate --local --project examples/vercel
|
|
16
|
+
- node packages/core/src/cli.ts test --project examples/vercel
|
|
17
|
+
- node packages/core/src/cli.ts audit --project examples/vercel --expect-routes 5
|
|
18
18
|
behavior:
|
|
19
19
|
- every route is native, so the project activates on Vercel without the sandbox
|