@jimhoyd/urlcode 0.5.6 → 0.5.9
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 +15 -0
- package/.claude/skills/urlcode-operations/SKILL.md +4 -0
- package/README.md +24 -14
- package/dist/BUILD-MANIFEST.json +51 -50
- package/dist/agent-context.js +9 -0
- 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/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 +6 -3
- package/dist/object-guards.js +17 -0
- package/dist/pattern-guard.js +33 -3
- package/dist/policies/cache.js +16 -4
- package/dist/policies/compression.js +1 -1
- package/dist/policies/throttle.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 +18 -11
- package/dist/vercel.js +12 -3
- package/dist/verify-deployment.js +29 -11
- package/docs/AI-AUTHORING.md +9 -0
- package/docs/FUNCTION-SECURITY.md +4 -0
- package/docs/README.md +2 -2
- package/docs/TOOLING.md +35 -1
- package/examples/assets/Makefile +1 -1
- package/examples/compliance/rules.mjs +2 -2
- package/llms-full.txt +44 -13
- package/llms.txt +9 -2
- package/package.json +8 -3
- package/skills/urlcode/SKILL.md +6 -6
- 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 +13 -11
- 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.
|
|
@@ -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
|
@@ -281,6 +281,14 @@ documentation and example tools read only a fixed package-owned manifest; no
|
|
|
281
281
|
tool argument names an arbitrary local path or remote URL. The CLI equivalent of `search_docs` is
|
|
282
282
|
`urlcode docs search TEXT [--json]`, which returns the same at most three bounded excerpts. `validate_yaml` checks supplied
|
|
283
283
|
YAML syntax and schema only, while `validate` compiles the selected local project.
|
|
284
|
+
The `list_skills`, `get_skill`, `search_docs`, `get_example`, `validate_yaml` and
|
|
285
|
+
`explain_error` tools are thin wrappers over `@jimhoyd/urlcode/agent-context`
|
|
286
|
+
(`listSkills`, `getSkill`, `searchDocs`, `getExample`, `validateYaml`,
|
|
287
|
+
`explainError`), a public package export — not an internal detail of this
|
|
288
|
+
server. A host building its own MCP server, or any other agent-tooling
|
|
289
|
+
integration, can import that module directly instead of reimplementing this
|
|
290
|
+
behavior or reaching into `dist/agent-context.js`; see
|
|
291
|
+
[TypeScript](TYPESCRIPT.md).
|
|
284
292
|
`get_extension_artifacts` validates the project-selected
|
|
285
293
|
`urlcode.extensions.lock.json` and cache, then returns artifact metadata,
|
|
286
294
|
status and allowlisted member paths. `get_extension_artifact` accepts only a
|
|
@@ -330,7 +338,7 @@ choices added by hand, never by `init` or by an agent.
|
|
|
330
338
|
|
|
331
339
|
- **Claude Code** reads `.mcp.json` in the project directory as a project-scoped
|
|
332
340
|
server and asks for approval on first use. A project that pins the runtime in its
|
|
333
|
-
`package.json` (`--
|
|
341
|
+
`package.json` (`--with`, `--manifest`) gets
|
|
334
342
|
`"command": "npx"` with `--no --package @jimhoyd/urlcode urlcode mcp ...`, which runs the
|
|
335
343
|
installed copy and never fetches (do not use a bare `npx urlcode`: that names an unrelated
|
|
336
344
|
registry package). A project without one keeps the bare `urlcode` command for a global
|
|
@@ -354,6 +362,32 @@ The generated `AGENTS.md` and the packaged skill tell agents to prefer
|
|
|
354
362
|
`get_manifest` when the server is registered and to fall back to the matching
|
|
355
363
|
CLI commands otherwise.
|
|
356
364
|
|
|
365
|
+
## Optional hosted AI MCP
|
|
366
|
+
|
|
367
|
+
The local `urlcode mcp` server remains the project-aware URLCode integration:
|
|
368
|
+
it reads the selected checkout, validates its configuration and never needs a
|
|
369
|
+
network credential. Do not replace its generated `.mcp.json` entry with a
|
|
370
|
+
hosted service.
|
|
371
|
+
|
|
372
|
+
[URLCode AI](https://urlcode.ai/) is a separate, opt-in hosted service for
|
|
373
|
+
shared skills and LLM-assisted work. A client that supports authenticated HTTP
|
|
374
|
+
MCP can add it as a second server with these connection details:
|
|
375
|
+
|
|
376
|
+
- URL: `https://mcp.urlcode.ai/mcp`
|
|
377
|
+
- request header: `Authorization: Bearer <URLCODE_AI_TOKEN>`
|
|
378
|
+
|
|
379
|
+
Store `URLCODE_AI_TOKEN` in the MCP client's secret or environment-variable
|
|
380
|
+
facility. Do not put a literal bearer token in `.mcp.json`, `urlcode.yaml`, a
|
|
381
|
+
checked-in client configuration, or a shell history. Each client has its own
|
|
382
|
+
remote-server configuration syntax, so configure that endpoint explicitly in
|
|
383
|
+
the client rather than asking `urlcode init` to generate it.
|
|
384
|
+
|
|
385
|
+
The hosted tools are not a proxy for this local server: they do not receive the
|
|
386
|
+
project root and do not replace local `get_context`, validation, manifest,
|
|
387
|
+
extension-artifact or authoring tools. Keep the local server registered for
|
|
388
|
+
framework- and project-specific work; add the hosted server only where its
|
|
389
|
+
shared skill catalog or LLM tools are useful.
|
|
390
|
+
|
|
357
391
|
## Authoring mode
|
|
358
392
|
|
|
359
393
|
`urlcode mcp --allow-authoring --project DIR` adds six tools to the twenty-two read
|
package/examples/assets/Makefile
CHANGED
|
@@ -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
|
package/llms-full.txt
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<!-- Generated by scripts/build-llms-full.ts (npm run docs:llms). Do not edit; edit the source documents. -->
|
|
2
|
-
<!-- Consolidated URLCode authoring reference: 16 documents, about 61,
|
|
2
|
+
<!-- Consolidated URLCode authoring reference: 16 documents, about 61,970 tokens (estimate: characters / 4). -->
|
|
3
3
|
|
|
4
4
|
<!-- urlcode-current-version:start -->
|
|
5
5
|
# URLCode
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
> A portable runtime for programmable URL behavior, and the framework that grows
|
|
8
8
|
> from it: routes in YAML, functions and middleware, then accounts, administration and
|
|
9
9
|
> stored links as operator-installed extensions. Stable project format
|
|
10
|
-
> `version: "1"`. Core is Apache-2.0; this revision is `0.5.
|
|
10
|
+
> `version: "1"`. Core is Apache-2.0; this revision is `0.5.9`. `function`/`middleware`
|
|
11
11
|
> routes are trusted by default with `sandbox: true` as the per-route opt-in. The
|
|
12
12
|
> auth, admin, ui and store workspace packages are bundle sources. New sites install
|
|
13
13
|
> core from npm and verified executable extensions from an immutable GitHub Release;
|
|
@@ -114,8 +114,10 @@ Each rung's YAML is valid on every rung above it.
|
|
|
114
114
|
|
|
115
115
|
Stored short links previously sat here as a native `link` route; that handler
|
|
116
116
|
was removed from core. A `urlcode-dynamic-link` package owned them the same way
|
|
117
|
-
`auth`/`admin` own their mounts, but it has been retired and unpublished
|
|
118
|
-
|
|
117
|
+
`auth`/`admin` own their mounts, but it has been retired and unpublished. It has
|
|
118
|
+
no direct successor; a project that wants stored short links declares a
|
|
119
|
+
collection through the `store` extension above (see [docs/STORE.md](https://github.com/jimhoyd-com/urlcode/blob/main/docs/STORE.md))
|
|
120
|
+
rather than a native `link` route.
|
|
119
121
|
|
|
120
122
|
Rungs 1 to 3 need only the core package. Rungs 4 to 7 need a verified extension
|
|
121
123
|
bundle installed into an explicit operator host, once its source package appears
|
|
@@ -364,6 +366,11 @@ The root [llms.txt](https://github.com/jimhoyd-com/urlcode/blob/main/llms.txt) i
|
|
|
364
366
|
reading order for agents that want complete context in one fetch. It is a convenience,
|
|
365
367
|
not a runtime protocol or a guarantee that AI clients automatically consume it.
|
|
366
368
|
The generated reference is checked against the schema in `npm run verify`.
|
|
369
|
+
[URLCode AI](https://urlcode.ai/) is the optional hosted companion for shared
|
|
370
|
+
skills and LLM tooling. It complements the project-local MCP server; see
|
|
371
|
+
[hosted AI MCP setup](https://github.com/jimhoyd-com/urlcode/blob/main/docs/TOOLING.md#optional-hosted-ai-mcp) for its authenticated
|
|
372
|
+
remote connection details. Its machine-readable entry point is
|
|
373
|
+
[`https://urlcode.ai/llms.txt`](https://urlcode.ai/llms.txt).
|
|
367
374
|
|
|
368
375
|
Follow [organization and readability practices](https://github.com/jimhoyd-com/urlcode/blob/main/docs/BEST-PRACTICES.md): preserve local
|
|
369
376
|
conventions, use clear names, keep middleware focused and avoid needless layers.
|
|
@@ -733,6 +740,10 @@ the operator, never by tool arguments; `--allow-authoring` on the operator's
|
|
|
733
740
|
command line adds project-confined route, recipe, scaffold and runner tools.
|
|
734
741
|
`urlcode init` writes `.mcp.json` so Claude Code and Codex register the read-only
|
|
735
742
|
server for the project ([registering the server](https://github.com/jimhoyd-com/urlcode/blob/main/docs/TOOLING.md#registering-the-server)).
|
|
743
|
+
The optional hosted URLCode AI MCP is a separate authenticated connection for
|
|
744
|
+
shared skills and LLM tools; it does not replace the local project server. Its
|
|
745
|
+
endpoint and credential-handling requirements are in
|
|
746
|
+
[hosted AI MCP setup](https://github.com/jimhoyd-com/urlcode/blob/main/docs/TOOLING.md#optional-hosted-ai-mcp).
|
|
736
747
|
Inspection is not activation/deployment readiness: real grants, asset snapshots
|
|
737
748
|
and service availability still need normal runtime checks. Provider conformance replay is local evidence; only
|
|
738
749
|
explicit live [deployment observations](https://github.com/jimhoyd-com/urlcode/blob/main/docs/PROVIDER-VERIFICATION.md) test ingress.
|
|
@@ -773,7 +784,7 @@ programmatic compatibility analysis and provider verification limits.
|
|
|
773
784
|
Source: https://github.com/jimhoyd-com/urlcode/blob/main/docs/YAML-GUIDE.md
|
|
774
785
|
|
|
775
786
|
<!-- urlcode-current-version:start -->
|
|
776
|
-
This guide targets URLCode 0.5.
|
|
787
|
+
This guide targets URLCode 0.5.9. Start with the function example below,
|
|
777
788
|
then add only the fields your route needs. The authoritative machine-readable
|
|
778
789
|
shape is [JSON Schema](https://github.com/jimhoyd-com/urlcode/blob/main/schemas/urlcode.schema.json); semantic rules are in the
|
|
779
790
|
[specification](https://github.com/jimhoyd-com/urlcode/blob/main/docs/SPECIFICATION.md). Unsupported fields fail validation.
|
|
@@ -795,8 +806,9 @@ node packages/core/src/cli.ts dev --project examples/cookbook
|
|
|
795
806
|
```
|
|
796
807
|
|
|
797
808
|
The cookbook is a larger learning project. The normal `urlcode init ../my-links`
|
|
798
|
-
|
|
799
|
-
runtime dependency, clone
|
|
809
|
+
creates a bare zero-route scaffold; add only the routes the application needs.
|
|
810
|
+
For an independent application with a pinned runtime dependency, clone
|
|
811
|
+
[urlcode-template](https://github.com/jimhoyd-com/urlcode-template).
|
|
800
812
|
|
|
801
813
|
### Pages
|
|
802
814
|
|
|
@@ -1920,10 +1932,18 @@ values into header literals. Header configuration applies to handler responses,
|
|
|
1920
1932
|
including declared error statuses, but not runtime validation/errors (400, 404,
|
|
1921
1933
|
405, 413, 415, 500, etc.). Defaults remain `no-store`, `nosniff` and a request ID.
|
|
1922
1934
|
Header policy is bounded to 64 keys/16 KiB; merged function headers remain bounded.
|
|
1935
|
+
A header repeated in the result the runtime writes (from a policy, or from a
|
|
1936
|
+
handler result that carries more than one pair for the same name) is sent as
|
|
1937
|
+
separate wire lines, the same as a declared `Set-Cookie` list; it is never
|
|
1938
|
+
collapsed to only its last value.
|
|
1923
1939
|
|
|
1924
1940
|
Framing, hop-by-hop headers, Location, Allow, range/cache validators,
|
|
1925
1941
|
Content-Encoding, X-Request-ID and X-Content-Type-Options are reserved to the
|
|
1926
|
-
runtime/handler.
|
|
1942
|
+
runtime/handler. The runtime frames every response the same way on every host:
|
|
1943
|
+
Content-Length is the UTF-8 byte length of the body it sends, whatever length a
|
|
1944
|
+
handler states. Only a HEAD answer carries a stated length, the one GET would
|
|
1945
|
+
send, and no body. The self-hosted and Vercel writers also make Node refuse a
|
|
1946
|
+
body that differs from the stated length. Configure redirect URLs/status on `redirect`; asset content type,
|
|
1927
1947
|
cache and disposition on its own handler. Asset metadata cannot be overridden by
|
|
1928
1948
|
`response.headers`. On functions/declared responses, Content-Type may be configured;
|
|
1929
1949
|
JSON declarations require a JSON type. No response header secret interpolation.
|
|
@@ -2393,9 +2413,11 @@ cannot enforce`; the Cloudflare build reports
|
|
|
2393
2413
|
build time and carried in the Worker artifact; the Worker has no filesystem and
|
|
2394
2414
|
no Node imports, so only modules free of both qualify. The Worker's client
|
|
2395
2415
|
identity is the platform's `cf-connecting-ip`; the serverless adapters use the
|
|
2396
|
-
platform-set source address (`sourceIp` on Lambda,
|
|
2397
|
-
`X-Forwarded-For`
|
|
2398
|
-
|
|
2416
|
+
platform-set source address (`sourceIp` on Lambda, Vercel's own
|
|
2417
|
+
`X-Vercel-Forwarded-For` — Vercel's documented copy of the client IP that
|
|
2418
|
+
survives even when a project puts another proxy in front of Vercel, unlike
|
|
2419
|
+
plain `X-Forwarded-For`, which that outer proxy can overwrite before Vercel
|
|
2420
|
+
ever sees it). None of these read a forwarded header a client could have set.
|
|
2399
2421
|
|
|
2400
2422
|
The cross-request state a policy keeps (throttle counters, the origin cache) is
|
|
2401
2423
|
per runtime instance on every target, never shared between replicas or
|
|
@@ -2760,6 +2782,11 @@ portable scalar header contract. Upstream redirects are returned as responses;
|
|
|
2760
2782
|
Location is forwarded only if explicitly selected and is never fetched. Headers
|
|
2761
2783
|
nominated by an incoming or upstream Connection field are removed even if selected.
|
|
2762
2784
|
Header names are normalized to lowercase; array-valued upstream fields are omitted.
|
|
2785
|
+
A proxy route reads incoming headers from the same credential-free projection a
|
|
2786
|
+
trusted function receives: a header an installed plugin or extension declares
|
|
2787
|
+
as a credential is withheld before `requestHeaders` selection runs, even when a
|
|
2788
|
+
project's own `requestHeaders` names it explicitly, so a route cannot forward a
|
|
2789
|
+
credential header upstream by declaring it.
|
|
2763
2790
|
An encoded request body requires explicitly selecting its Content-Encoding
|
|
2764
2791
|
header; literal header injection cannot change or replace that coding. Bodies
|
|
2765
2792
|
remain raw bytes, including content encoding; select Content-Encoding when
|
|
@@ -3673,9 +3700,9 @@ navigation or labels to an auth/admin view without editing either package.
|
|
|
3673
3700
|
|
|
3674
3701
|
| Hook | Input | Returns | Called |
|
|
3675
3702
|
|---|---|---|---|
|
|
3676
|
-
| `beforeRoleChange` | `{accountId, currentRoles, requestedRoles, actorId, reason}` | `{allow: boolean, reason?}` | Before roles are applied, after the administrator's permission check. A veto means the auth service is never asked. |
|
|
3703
|
+
| `beforeRoleChange` | `{accountId, currentRoles, requestedRoles, actorId, reason}` | `{allow: boolean, reason?}` | Before roles are applied, after the administrator's permission check, on every path that assigns roles: `/users/roles`, bulk role assignment through `/account-operations`, and a role-change support case's creation and approval. A veto means the auth service is never asked; for a bulk assignment it runs once per affected account before anything is staged. |
|
|
3677
3704
|
| `onRegistrationApproved` | `{requestId, accountId, email, actorId, reason}` | ignored | After a registration request is approved. |
|
|
3678
|
-
| `onAccountStatusChanged` | `{accountId, status, actorId, reason}` | ignored | After an account is locked or unlocked. |
|
|
3705
|
+
| `onAccountStatusChanged` | `{accountId, status, actorId, reason}` | ignored | After an account is locked or unlocked, on every path: `/users/status`, `/users/bulk` (once per affected account), and an approved lock/unlock support case. |
|
|
3679
3706
|
|
|
3680
3707
|
#### Verdicts and failure
|
|
3681
3708
|
|
|
@@ -3816,6 +3843,10 @@ into it; only the default for routes that declare neither option has changed.
|
|
|
3816
3843
|
- A fresh guest heap/module state per invocation prevents state crossing requests.
|
|
3817
3844
|
- 32 MiB guest heap, 512 KiB stack, source/input/output/header limits, bounded
|
|
3818
3845
|
concurrency, guest interruption and an independent worker termination deadline.
|
|
3846
|
+
- The guest's result is recorded where guest code cannot rewrite it, and the
|
|
3847
|
+
host checks its shape before trusting it. A result that states a body length
|
|
3848
|
+
for any method other than HEAD is invalid and answers 502; the runtime frames
|
|
3849
|
+
the response by the bytes it sends ([responses](https://github.com/jimhoyd-com/urlcode/blob/main/docs/HTTP.md#responses)).
|
|
3819
3850
|
- External bindings are denied by default. Project YAML cannot self-authorize.
|
|
3820
3851
|
Operator grants are exact-name, route-scoped and pinned to configuration/source.
|
|
3821
3852
|
|
package/llms.txt
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
> A portable runtime for programmable URL behavior, and the framework that grows
|
|
5
5
|
> from it: routes in YAML, functions and middleware, then accounts, administration and
|
|
6
6
|
> stored links as operator-installed extensions. Stable project format
|
|
7
|
-
> `version: "1"`. Core is Apache-2.0; this revision is `0.5.
|
|
7
|
+
> `version: "1"`. Core is Apache-2.0; this revision is `0.5.9`. `function`/`middleware`
|
|
8
8
|
> routes are trusted by default with `sandbox: true` as the per-route opt-in. The
|
|
9
9
|
> auth, admin, ui and store workspace packages are bundle sources. New sites install
|
|
10
10
|
> core from npm and verified executable extensions from an immutable GitHub Release;
|
|
@@ -21,9 +21,16 @@ grants. Unsupported features fail with the route named; nothing degrades silentl
|
|
|
21
21
|
1. Run `npx --no --package @jimhoyd/urlcode urlcode context --project DIR` (MCP `get_context`). For redirect-only work run
|
|
22
22
|
`npx --no --package @jimhoyd/urlcode urlcode context --project DIR --task redirects`: supported shapes with exact YAML, gaps
|
|
23
23
|
with their exact validation error, a paste-ready `starter`, and this project's own redirects.
|
|
24
|
-
2. After installing `@jimhoyd/urlcode`, run `npx --no --package @jimhoyd/urlcode urlcode init
|
|
24
|
+
2. After installing `@jimhoyd/urlcode`, run `npx --no --package @jimhoyd/urlcode urlcode init .` (it works in a directory holding only `package.json`, `package-lock.json`, `node_modules` or `.git` and preserves existing package metadata). It writes a bare agent-ready scaffold: `urlcode.yaml`, a minimal 404 fixture, `AGENTS.md`, local `.mcp.json`, and project CI. Then run `npx --no --package @jimhoyd/urlcode urlcode validate --local --project DIR` and `npx --no --package @jimhoyd/urlcode urlcode test`. `--no` refuses a registry fetch, so it always uses the installed scoped runtime.
|
|
25
25
|
3. Do not read or grep [llms-full.txt](llms-full.txt) (about 60k tokens) for a routine task; it is the
|
|
26
26
|
deliberate fallback when the steps above do not answer the question.
|
|
27
|
+
4. Hosted skills and LLM assistance are optional: connect a compatible HTTP MCP
|
|
28
|
+
client to `https://mcp.urlcode.ai/mcp` with
|
|
29
|
+
`Authorization: Bearer <URLCODE_AI_TOKEN>`. Keep `urlcode mcp` registered
|
|
30
|
+
for project-aware inspection and validation, and store the hosted token in
|
|
31
|
+
the client's secret facility rather than in project files. Start with the
|
|
32
|
+
hosted [agent guide](https://urlcode.ai/llms.txt), then see
|
|
33
|
+
[hosted AI MCP setup](https://github.com/jimhoyd-com/urlcode/blob/main/docs/TOOLING.md#optional-hosted-ai-mcp).
|
|
27
34
|
|
|
28
35
|
| Shape | Supported? | Use |
|
|
29
36
|
|---|---|---|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jimhoyd/urlcode",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.9",
|
|
4
4
|
"description": "Portable runtime for programmable URL behavior",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"workspaces": [
|
|
@@ -25,6 +25,10 @@
|
|
|
25
25
|
"types": "./dist/types/index.d.ts",
|
|
26
26
|
"default": "./dist/index.js"
|
|
27
27
|
},
|
|
28
|
+
"./agent-context": {
|
|
29
|
+
"types": "./dist/types/agent-context.d.ts",
|
|
30
|
+
"default": "./dist/agent-context.js"
|
|
31
|
+
},
|
|
28
32
|
"./aws": {
|
|
29
33
|
"types": "./dist/types/aws.d.ts",
|
|
30
34
|
"default": "./dist/aws.js"
|
|
@@ -75,6 +79,7 @@
|
|
|
75
79
|
"files": [
|
|
76
80
|
".claude/skills",
|
|
77
81
|
"dist",
|
|
82
|
+
"!dist/scripts",
|
|
78
83
|
"schemas",
|
|
79
84
|
"data",
|
|
80
85
|
"starters",
|
|
@@ -155,9 +160,9 @@
|
|
|
155
160
|
"@changesets/cli": "3.0.3",
|
|
156
161
|
"@eslint/js": "10.0.1",
|
|
157
162
|
"@types/mime-types": "3.0.1",
|
|
158
|
-
"@types/node": "26.
|
|
163
|
+
"@types/node": "26.6.2",
|
|
159
164
|
"@types/semver": "7.8.0",
|
|
160
|
-
"eslint": "10.
|
|
165
|
+
"eslint": "10.11.0",
|
|
161
166
|
"globals": "17.12.0",
|
|
162
167
|
"semver": "7.8.5",
|
|
163
168
|
"typescript": "6.0.3",
|
package/skills/urlcode/SKILL.md
CHANGED
|
@@ -12,24 +12,23 @@ A URLCode project declares URL behavior in YAML; the installed runtime serves it
|
|
|
12
12
|
> Use URLCode's highest-level declarative features whenever possible. Generate custom code only when the framework cannot express the requirement.
|
|
13
13
|
|
|
14
14
|
Check installed primitives, YAML configuration, policies, extensions and recipes/templates before writing custom code. Keep it focused and report the gap; never invent fields or bypass target limits or operator grants. Source checkouts have `docs/PROJECT-DIRECTION.md`; npm installations have it in `llms-full.txt`.
|
|
15
|
-
|
|
16
15
|
## 1. Recognize the project
|
|
17
16
|
|
|
17
|
+
- No `urlcode.yaml` yet? `npm install @jimhoyd/urlcode` (scoped; no unscoped `urlcode` package exists), then `npx urlcode init .`.
|
|
18
18
|
- The root has `urlcode.yaml` with `version: "1"`. Included route files are
|
|
19
|
-
listed under `includes`; functions, middleware and assets are
|
|
20
|
-
the project root.
|
|
19
|
+
listed under `includes`; functions, middleware and assets are project-relative.
|
|
21
20
|
- Read the project's `AGENTS.md` first if present; it lists the handlers,
|
|
22
21
|
policies and commands this runtime version supports.
|
|
23
22
|
- Find the runtime: `urlcode` on the PATH, or
|
|
24
23
|
`node node_modules/@jimhoyd/urlcode/dist/cli.js`, or
|
|
25
24
|
`node /path/to/urlcode/packages/core/src/cli.ts` for a source checkout. Use one form for
|
|
26
25
|
every command below.
|
|
27
|
-
|
|
28
26
|
## 2. Retrieve the minimum, do not read everything
|
|
29
27
|
|
|
30
28
|
**First step: one bounded query.** MCP `get_context` when the `urlcode` server is registered, otherwise `urlcode context --project DIR` (add `--budget N` to cap it). It returns a compact summary, constraints and exact commands, never a schema dump. Then retrieve only what the task needs: `capabilities NAME`/`get_capability` (its limits), `get_schema`, `recipes search`/`search_recipes`, `explain`, and `get_extensions` when an operator host file exists. The bare `urlcode capabilities` and `recipes list` catalogs, `llms.txt` and `llms-full.txt` are deliberate fallback/reference, not the opening move.
|
|
31
29
|
|
|
32
30
|
If the project carries `.mcp.json` (written by `urlcode init`) and your client has the `urlcode` server, prefer its tools: `get_context` (project summary, constraints, exact commands), `get_capability` and `get_schema` (one capability or YAML fragment), `search_recipes`, `search_examples`, `explain` (a route's effective behavior) and `get_manifest`. For framework discovery, use `list_skills` before `get_skill`, `search_docs` for a short package-owned excerpt, and `get_example` for one runnable example. Use `validate_yaml` for pasted YAML syntax/schema feedback only; use `validate` for the actual project. The server is read-only; `--allow-authoring` is an operator opt-in you never add yourself.
|
|
31
|
+
[URLCode AI](https://urlcode.ai/) is an optional, separate hosted service for shared skills and LLM tooling. Its remote MCP supplements this local project server; never replace `.mcp.json` or put its bearer token in project files. Its machine-readable entry point is `https://urlcode.ai/llms.txt`; connection details belong to the MCP client's secret facility and are documented in the URLCode tooling guide.
|
|
33
32
|
When the MCP server was started with an operator host file, `get_extensions`
|
|
34
33
|
returns installed extension configuration/policy schemas, declared project
|
|
35
34
|
hook contracts, supported authoring surfaces and fast checks. Otherwise use `urlcode extensions --project DIR --host-file
|
|
@@ -118,8 +117,9 @@ urlcode audit --project DIR --expect-routes N
|
|
|
118
117
|
```
|
|
119
118
|
|
|
120
119
|
Add fixtures to `tests/requests.json` for each new route: positive and negative
|
|
121
|
-
cases, every active method, `HEAD`.
|
|
122
|
-
|
|
120
|
+
cases, every active method, `HEAD`. `N` is declared routes plus one per active
|
|
121
|
+
`site.*` convention; an audit mismatch reports the declared/generated split. Update it
|
|
122
|
+
deliberately when routes are added or removed, and update any `--expect-routes` in the project's README,
|
|
123
123
|
Makefile or CI workflow to match. A failing validation names the route; fix
|
|
124
124
|
the declaration rather than working around it.
|
|
125
125
|
|
|
@@ -19,5 +19,6 @@ jobs:
|
|
|
19
19
|
# Pin to a release tag or commit of the runtime you depend on.
|
|
20
20
|
- uses: jimhoyd-com/urlcode/action@main
|
|
21
21
|
with:
|
|
22
|
-
expect-routes:
|
|
22
|
+
expect-routes: 0 # update when adding or removing routes
|
|
23
|
+
allow-empty-project: true # remove after adding the first active route
|
|
23
24
|
compliance: baseline
|
|
@@ -22,7 +22,7 @@ static serving and authentication. Read this file before changing anything.
|
|
|
22
22
|
|
|
23
23
|
When present, `.mcp.json` registers the read-only `urlcode mcp` server; prefer its
|
|
24
24
|
tools (also `get_manifest`) to reading documents. Inspect `get_extensions` before
|
|
25
|
-
replacing extension behavior. `--allow-authoring` is an operator opt-in; never add it. For a committed artifact lock, use `get_extension_artifacts`/`get_extension_artifact`; they expose verified inert data and never activate an extension.
|
|
25
|
+
replacing extension behavior. `--allow-authoring` is an operator opt-in; never add it. For a committed artifact lock, use `get_extension_artifacts`/`get_extension_artifact`; they expose verified inert data and never activate an extension. [URLCode AI](https://urlcode.ai/) is a separate optional hosted service for shared skills and LLM tooling; its remote MCP never replaces this local project server, and its credential belongs only in a client secret facility, never project files. Its machine-readable entry point is `https://urlcode.ai/llms.txt`.
|
|
26
26
|
|
|
27
27
|
## What the runtime provides (this version)
|
|
28
28
|
|
|
@@ -56,10 +56,10 @@ record the reason in `sandboxReason`. Try `redirect` (relative or `/**`) or `res
|
|
|
56
56
|
```sh
|
|
57
57
|
urlcode validate --local
|
|
58
58
|
urlcode test
|
|
59
|
-
urlcode audit --expect-routes
|
|
59
|
+
urlcode audit --expect-routes 0
|
|
60
60
|
```
|
|
61
61
|
|
|
62
|
-
|
|
62
|
+
With no active routes, this initial audit intentionally exits nonzero with `no-active-routes`. Add the first route and its fixture, then make the audit pass; remove `allow-empty-project: true` from the generated GitHub workflow at that point. `N` counts declared routes plus one route for each active `site.*` convention; an audit mismatch reports the declared/generated split. Update it deliberately and add `tests/requests.json` fixtures for every new route (positive/negative, every active method, HEAD). No global install: use `node /path/to/urlcode/packages/core/src/cli.ts`.
|
|
63
63
|
|
|
64
64
|
## Feedback
|
|
65
65
|
|
|
@@ -1,29 +1,31 @@
|
|
|
1
1
|
# Your URLCode project
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
This is a bare, agent-ready URLCode scaffold. It starts with no routes so your
|
|
4
|
+
application's YAML and tests describe only the behavior you intend to ship. Its
|
|
5
|
+
single fixture proves the empty project returns 404.
|
|
5
6
|
|
|
6
7
|
```sh
|
|
7
8
|
urlcode dev
|
|
8
9
|
# In another terminal:
|
|
9
10
|
urlcode test
|
|
10
|
-
urlcode audit --expect-routes 2
|
|
11
|
-
urlcode benchmark --requests 1000 --concurrency 2
|
|
12
11
|
```
|
|
13
12
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
13
|
+
`urlcode audit --expect-routes 0` intentionally reports `no-active-routes` until
|
|
14
|
+
you add the first route. The included GitHub workflow permits only that initial
|
|
15
|
+
audit result; remove `allow-empty-project: true` after adding a route.
|
|
16
|
+
|
|
17
|
+
Start with the local MCP `get_context` tool (or `urlcode context --project .`),
|
|
18
|
+
then add the smallest declarative route or custom code the task requires. Keep
|
|
19
|
+
`tests/requests.json` aligned with every route you add and update the audit
|
|
20
|
+
count deliberately. `AGENTS.md` explains the workflow and points to the
|
|
21
|
+
optional hosted shared tooling at https://urlcode.ai/llms.txt.
|
|
20
22
|
|
|
21
23
|
This app uses the runtime you installed separately (compatible with 0.1.0).
|
|
22
24
|
Without a global install, invoke `node /path/to/urlcode/packages/core/src/cli.ts` instead of
|
|
23
25
|
`urlcode`. Optional Make shortcuts accept `URLCODE='node /path/to/urlcode/packages/core/src/cli.ts'`.
|
|
24
26
|
For a cloneable project with a pinned npm runtime dependency, use
|
|
25
27
|
[urlcode-template](https://github.com/jimhoyd-com/urlcode-template). Both start
|
|
26
|
-
with the same
|
|
28
|
+
with the same bare scaffold. No runtime fork or provider account is needed.
|
|
27
29
|
|
|
28
30
|
Keep this app in your own Git repository. Secrets stay in ignored `.env.local`
|
|
29
31
|
or provider environment values, with external operator policy for function grants.
|