agentic-scaffold 0.1.2 → 0.1.4

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/CHANGELOG.md ADDED
@@ -0,0 +1,50 @@
1
+ # Changelog
2
+
3
+ All notable changes to this package are in this file. The format follows
4
+ [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and the package uses
5
+ [Semantic Versioning](https://semver.org/).
6
+
7
+ ## 0.1.4 - 2026-10-29
8
+
9
+ ### Fixed
10
+
11
+ - `agentic-scaffold-mcp mcp --help`, `-h`, `--version` and `-V` print and exit 0 without starting the MCP server. The bin read only its first argument, so the `mcp` word started the server and it waited on stdin.
12
+ - The `agentic-scaffold-mcp` usage line names the real bin. It said `agentic-pack-mcp`.
13
+
14
+ ### Added
15
+
16
+ - Tests run the built mcp bin with the `mcp` forms of these flags and check the usage names `agentic-scaffold-mcp`.
17
+
18
+ ## 0.1.3 - 2026-10-29
19
+
20
+ ### Removed
21
+
22
+ - The Worker route `POST /summarize` is gone. Use `POST /api/v1/summarize` instead.
23
+ The new route needs a bearer key. See the migration section in the
24
+ [README](README.md#migration-worker-auth).
25
+
26
+ ### Security
27
+
28
+ - The `zod` dependency is `^4.6.7`. zod 3.25.76 was below the fixed version for
29
+ GHSA-mrhx-c7xc-m3w5, which allows memory exhaustion when validating oversized
30
+ constrained collections. The package API is unchanged.
31
+ - `POST /api/v1/summarize` requires a bearer key with the `text:read` scope. A missing
32
+ key gets 401, a missing scope gets 403, and a Worker with no `AUTH_TOKENS` secret gets
33
+ 503. Before, the route had no auth.
34
+
35
+ ### Fixed
36
+
37
+ - The deployed Worker builds its Auth from the `AUTH_TOKENS` string secret. Before, a
38
+ Cloudflare binding could not hold an Auth object, so a deployed Worker always answered
39
+ 503. The `AUTH` env field stays as a test seam.
40
+ - `mcp --help`, `-h`, `--version` and `-V` print and exit 0 for the cli and the mcp bin,
41
+ without starting the server.
42
+
43
+ ### Changed
44
+
45
+ - The publish workflow runs one publish at a time for each ref, so two runs cannot
46
+ publish at once.
47
+ - The publish workflow also publishes to ants.land. It skips a version that is already
48
+ there.
49
+ - The repository has a CI workflow (Node 22: typecheck, test, build) and a `ci:local`
50
+ script.
package/README.md CHANGED
@@ -40,10 +40,25 @@ console.log(getCostReport()); // { entries: [], dispatches: 0, units: 0 }
40
40
  - **Utilities:** Brand configuration, stderr logging, dry-run results, and in-memory cost units.
41
41
  - **CLI:** `summary`, `dispatch`, `costs`, `mcp`; help via `--help` and version via `--version`.
42
42
  - **MCP:** Three tools: `summarize_text`, `dispatch_text`, `get_cost_report`.
43
- - **Worker scaffold:** Source handler for `GET /health` and `POST /summarize`; users deploy it themselves.
43
+ - **Worker scaffold:** Source handler for `GET /health` (open) and `POST /api/v1/summarize` (needs a bearer key with the `text:read` scope); users deploy it themselves.
44
44
 
45
45
  Start MCP with `npx agentic-scaffold mcp`. Preview routing with `npx agentic-scaffold dispatch --text hello --dry-run`.
46
46
 
47
+ ## Migration: Worker auth
48
+
49
+ `POST /summarize` is gone. Use `POST /api/v1/summarize`. It needs `Authorization: Bearer <key>` and a principal with the `text:read` scope. A missing or bad key gets 401. A key without the scope gets 403. Set the `AUTH_TOKENS` secret to a JSON object of bearer token (32 bytes or more) to `kind:name`, or to `{ "as": "kind:name", "scopes": ["text:read"] }`. A bad entry refuses the whole table. If `AUTH_TOKENS` is missing, the route answers 503. `GET /health` stays open.
50
+
51
+ ## Smoke test of the published packages
52
+
53
+ `scripts/npm-smoke.mjs` installs each published suite package from npm in a clean temp dir. It runs the CLI and MCP `--help` and `--version`, and imports the main export and the SDK client in Node 22. It prints a pass/fail table and exits 1 on any failure.
54
+
55
+ ```sh
56
+ node scripts/npm-smoke.mjs # all packages
57
+ node scripts/npm-smoke.mjs agentic-link # one package
58
+ ```
59
+
60
+ The `npm-smoke` workflow runs it every day and on demand. The table goes to the job summary.
61
+
47
62
  ## Limits
48
63
 
49
64
  This starter has zero vendor-specific integrations. Local dispatch creates a receipt without external delivery.
@@ -64,10 +79,11 @@ node dist/cli.js summary --text "hello world hello"
64
79
  Replace `__PACK_NAME__`, `__NPM_NAME__`, `__PACK_COMMAND__`, `__PACK_DESCRIPTION__`, `__PACK_KEYWORDS__`, and `__GITHUB_USER__` wherever they appear.
65
80
  Check `AGENTS.md`, `SKILL.md`, `RULES.md`, `llms.txt`, `server.json`, `.mcp.json`, and `wrangler.jsonc`.
66
81
  Update package identity, bin commands, `mcpName`, and `DEFAULT_BRAND` in `src/index.ts`.
82
+ The MCP bin's usage line in `src/mcp-bin.ts` names `agentic-scaffold-mcp` as text, so rename it there too.
67
83
  Set the MCP manifest version and repository ID before publishing your own package.
68
84
 
69
85
  The repository includes a shared core contract and a publish workflow. Inspect that workflow before adopting its release process.
70
- Version 0.1.1 is an early starter release; interfaces can change.
86
+ Version 0.1.4 is an early starter release; interfaces can change. See [CHANGELOG.md](CHANGELOG.md).
71
87
 
72
88
  ## License
73
89
 
@@ -206,7 +206,7 @@ function summarizeText(text, options = {}) {
206
206
 
207
207
  // src/version.ts
208
208
  import { createRequire } from "module";
209
- var VERSION = true ? "0.1.2" : readPackageVersion();
209
+ var VERSION = true ? "0.1.4" : readPackageVersion();
210
210
 
211
211
  export {
212
212
  defineBrand,
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  VERSION,
3
3
  defineBrand
4
- } from "./chunk-USJ2R2T2.js";
4
+ } from "./chunk-END2G5DQ.js";
5
5
 
6
6
  // src/index.ts
7
7
  var DEFAULT_BRAND = defineBrand({
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  DEFAULT_BRAND
3
- } from "./chunk-QWFNPAA4.js";
3
+ } from "./chunk-QERRFPOW.js";
4
4
  import {
5
5
  GuardError,
6
6
  LocalRouterAdapter,
@@ -9,7 +9,7 @@ import {
9
9
  dispatchText,
10
10
  getCostReport,
11
11
  summarizeText
12
- } from "./chunk-USJ2R2T2.js";
12
+ } from "./chunk-END2G5DQ.js";
13
13
 
14
14
  // src/mcp.ts
15
15
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
package/dist/cli.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  DEFAULT_BRAND
4
- } from "./chunk-QWFNPAA4.js";
4
+ } from "./chunk-QERRFPOW.js";
5
5
  import {
6
6
  GuardError,
7
7
  LocalRouterAdapter,
@@ -10,7 +10,7 @@ import {
10
10
  dispatchText,
11
11
  getCostReport,
12
12
  summarizeText
13
- } from "./chunk-USJ2R2T2.js";
13
+ } from "./chunk-END2G5DQ.js";
14
14
 
15
15
  // src/cli.ts
16
16
  import { readFileSync, realpathSync } from "fs";
@@ -36,6 +36,16 @@ function parseArgs(argv) {
36
36
  throw new UsageError(`unknown command "${first}"`);
37
37
  }
38
38
  request.command = first;
39
+ if (first === "mcp" && args.length > 0) {
40
+ if (args[0] === "--help" || args[0] === "-h") {
41
+ request.command = "help";
42
+ return request;
43
+ }
44
+ if (args[0] === "--version" || args[0] === "-V") {
45
+ request.command = "version";
46
+ return request;
47
+ }
48
+ }
39
49
  while (args.length > 0) {
40
50
  const flag = args.shift();
41
51
  if (flag === "--dry-run" && request.command === "dispatch") {
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  DEFAULT_BRAND
3
- } from "./chunk-QWFNPAA4.js";
3
+ } from "./chunk-QERRFPOW.js";
4
4
  import {
5
5
  GuardError,
6
6
  HttpRouterAdapter,
@@ -16,7 +16,7 @@ import {
16
16
  recordCost,
17
17
  resetCosts,
18
18
  summarizeText
19
- } from "./chunk-USJ2R2T2.js";
19
+ } from "./chunk-END2G5DQ.js";
20
20
  export {
21
21
  DEFAULT_BRAND,
22
22
  GuardError,
package/dist/mcp-bin.js CHANGED
@@ -1,11 +1,28 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  startMcp
4
- } from "./chunk-FYTHYRXT.js";
5
- import "./chunk-QWFNPAA4.js";
6
- import "./chunk-USJ2R2T2.js";
4
+ } from "./chunk-RIM2G6BQ.js";
5
+ import {
6
+ DEFAULT_BRAND
7
+ } from "./chunk-QERRFPOW.js";
8
+ import {
9
+ VERSION
10
+ } from "./chunk-END2G5DQ.js";
7
11
 
8
12
  // src/mcp-bin.ts
13
+ var argv = process.argv.slice(2);
14
+ var args = argv[0] === "mcp" ? argv.slice(1) : argv;
15
+ var flag = args[0];
16
+ if (flag === "--help" || flag === "-h") {
17
+ console.log(`${DEFAULT_BRAND.name} ${VERSION}
18
+ Usage: agentic-scaffold-mcp [mcp] [--help | --version]
19
+ Starts the MCP server on stdio.`);
20
+ process.exit(0);
21
+ }
22
+ if (flag === "--version" || flag === "-V") {
23
+ console.log(VERSION);
24
+ process.exit(0);
25
+ }
9
26
  startMcp().catch((error) => {
10
27
  console.error(error instanceof Error ? error.message : String(error));
11
28
  process.exit(1);
package/dist/mcp.js CHANGED
@@ -1,9 +1,9 @@
1
1
  import {
2
2
  createMcpServer,
3
3
  startMcp
4
- } from "./chunk-FYTHYRXT.js";
5
- import "./chunk-QWFNPAA4.js";
6
- import "./chunk-USJ2R2T2.js";
4
+ } from "./chunk-RIM2G6BQ.js";
5
+ import "./chunk-QERRFPOW.js";
6
+ import "./chunk-END2G5DQ.js";
7
7
  export {
8
8
  createMcpServer,
9
9
  startMcp
package/dist/worker.d.ts CHANGED
@@ -1,8 +1,19 @@
1
+ import { Auth } from 'agentic-identity';
2
+
1
3
  interface WorkerEnv {
2
4
  PACK_NAME?: string;
5
+ /**
6
+ * Secret. A JSON object of bearer token to `kind:name` (see `createTokenAuth`).
7
+ * Cloudflare bindings hold strings, so the Worker builds its Auth from this.
8
+ * Without it, protected routes answer 503.
9
+ */
10
+ AUTH_TOKENS?: string;
11
+ /** Test seam: an Auth port that replaces `AUTH_TOKENS`. A Cloudflare binding cannot hold an object. */
12
+ AUTH?: Auth;
3
13
  }
14
+ declare const SUMMARIZE_SCOPE = "text:read";
4
15
  declare const _default: {
5
16
  fetch(request: Request, env: WorkerEnv): Promise<Response>;
6
17
  };
7
18
 
8
- export { type WorkerEnv, _default as default };
19
+ export { SUMMARIZE_SCOPE, type WorkerEnv, _default as default };
package/dist/worker.js CHANGED
@@ -1,9 +1,73 @@
1
1
  import {
2
2
  VERSION,
3
3
  summarizeText
4
- } from "./chunk-USJ2R2T2.js";
4
+ } from "./chunk-END2G5DQ.js";
5
5
 
6
6
  // src/worker.ts
7
+ import { requirePrincipal } from "agentic-identity/http";
8
+
9
+ // src/auth.ts
10
+ import { isScope, parsePrincipal } from "agentic-identity";
11
+ var MIN_TOKEN_BYTES = 32;
12
+ var TOKEN_SHAPE = /^[A-Za-z0-9_-]{1,512}$/;
13
+ var DEFAULT_SCOPES = ["text:read"];
14
+ var warned = /* @__PURE__ */ new Set();
15
+ function refuse(reason) {
16
+ if (!warned.has(reason)) {
17
+ warned.add(reason);
18
+ console.error(`AUTH_TOKENS is refused: ${reason}`);
19
+ }
20
+ }
21
+ function same(a, b) {
22
+ let diff = a.length ^ b.length;
23
+ for (let i = 0; i < Math.max(a.length, b.length); i++) diff |= (a.charCodeAt(i) || 0) ^ (b.charCodeAt(i) || 0);
24
+ return diff === 0;
25
+ }
26
+ function contextOf(entry) {
27
+ const object = typeof entry === "object" && entry !== null && !Array.isArray(entry) ? entry : void 0;
28
+ const who = typeof entry === "string" ? entry : object?.as;
29
+ const cut = typeof who === "string" ? who.indexOf(":") : -1;
30
+ if (typeof who !== "string" || cut < 1 || cut === who.length - 1) return void 0;
31
+ const given = object?.scopes;
32
+ if (given !== void 0 && !(Array.isArray(given) && given.every(isScope))) return void 0;
33
+ try {
34
+ const { kind, id } = parsePrincipal({ kind: who.slice(0, cut), id: who.slice(cut + 1) });
35
+ return { principalId: id, kind, chain: [{ id, kind }], scopes: given ?? [...DEFAULT_SCOPES] };
36
+ } catch {
37
+ return void 0;
38
+ }
39
+ }
40
+ function createTokenAuth(raw) {
41
+ let table;
42
+ try {
43
+ const parsed = JSON.parse(raw ?? "");
44
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) throw new Error("not a JSON object");
45
+ table = /* @__PURE__ */ new Map();
46
+ for (const [token, entry] of Object.entries(parsed)) {
47
+ if (new TextEncoder().encode(token).length < MIN_TOKEN_BYTES) throw new Error(`a token is shorter than ${MIN_TOKEN_BYTES} bytes`);
48
+ if (!TOKEN_SHAPE.test(token)) throw new Error("a token holds a character outside A-Z a-z 0-9 _ - or is over 512 bytes");
49
+ const context = contextOf(entry);
50
+ if (!context) throw new Error("an entry is not a valid kind:name or { as, scopes }");
51
+ table.set(token, context);
52
+ }
53
+ } catch (error) {
54
+ table = void 0;
55
+ refuse(error instanceof SyntaxError ? "not valid JSON" : error.message);
56
+ }
57
+ const known = table;
58
+ return {
59
+ async authenticate(request) {
60
+ const bearer = /^Bearer (.+)$/.exec(request.headers.get("authorization") ?? "")?.[1];
61
+ if (bearer === void 0 || !known) return null;
62
+ let found = null;
63
+ for (const [token, context] of known) if (same(token, bearer)) found = context;
64
+ return found;
65
+ }
66
+ };
67
+ }
68
+
69
+ // src/worker.ts
70
+ var SUMMARIZE_SCOPE = "text:read";
7
71
  function json(value, status = 200) {
8
72
  return new Response(JSON.stringify(value), {
9
73
  status,
@@ -16,7 +80,15 @@ var worker_default = {
16
80
  if (request.method === "GET" && url.pathname === "/health") {
17
81
  return json({ ok: true, pack: env.PACK_NAME ?? "agentic-pack", version: VERSION });
18
82
  }
19
- if (request.method === "POST" && url.pathname === "/summarize") {
83
+ if (request.method === "POST" && url.pathname === "/api/v1/summarize") {
84
+ if (!env.AUTH && !env.AUTH_TOKENS) {
85
+ return json({ error: "auth is not configured" }, 503);
86
+ }
87
+ const auth = env.AUTH ?? createTokenAuth(env.AUTH_TOKENS);
88
+ const principal = await requirePrincipal(request, auth, { scope: SUMMARIZE_SCOPE });
89
+ if (principal instanceof Response) {
90
+ return principal;
91
+ }
20
92
  let body;
21
93
  try {
22
94
  body = await request.json();
@@ -36,5 +108,6 @@ var worker_default = {
36
108
  }
37
109
  };
38
110
  export {
111
+ SUMMARIZE_SCOPE,
39
112
  worker_default as default
40
113
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentic-scaffold",
3
- "version": "0.1.2",
3
+ "version": "0.1.4",
4
4
  "description": "TypeScript scaffold for a Node.js library, CLI, and MCP server with Zod schemas, text statistics, routing adapters, and dispatch guards.",
5
5
  "keywords": [
6
6
  "typescript",
@@ -29,7 +29,8 @@
29
29
  "node": ">=18"
30
30
  },
31
31
  "files": [
32
- "dist"
32
+ "dist",
33
+ "CHANGELOG.md"
33
34
  ],
34
35
  "publishConfig": {
35
36
  "access": "public"
@@ -51,11 +52,13 @@
51
52
  "build": "tsup",
52
53
  "typecheck": "tsc --noEmit",
53
54
  "test": "vitest run",
54
- "clean": "rm -rf dist"
55
+ "clean": "rm -rf dist",
56
+ "ci:local": "WRANGLER_SEND_METRICS=false npm run typecheck && npm test && npm run build"
55
57
  },
56
58
  "dependencies": {
57
59
  "@modelcontextprotocol/sdk": "^1.12.0",
58
- "zod": "^3.25.0"
60
+ "agentic-identity": "^0.7.0",
61
+ "zod": "^4.6.7"
59
62
  },
60
63
  "devDependencies": {
61
64
  "@types/node": "^22.0.0",