@valbuild/server 0.116.0 → 0.117.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md ADDED
@@ -0,0 +1,75 @@
1
+ # @valbuild/server
2
+
3
+ ## 0.117.1
4
+
5
+ ### Patch Changes
6
+
7
+ - Updated dependencies [[`0ae7bac`](https://github.com/valbuild/val/commit/0ae7bac8a186460bc2b31f2ded89b00027bafb55)]:
8
+ - @valbuild/ui@0.117.1
9
+
10
+ ## 0.117.0
11
+
12
+ ### Minor Changes
13
+
14
+ - [#582](https://github.com/valbuild/val/pull/582) [`fca3efa`](https://github.com/valbuild/val/commit/fca3efa389e2817401f55ea3dd184af7c611b807) Thanks [@freekh](https://github.com/freekh)! - Accept OAuth access tokens on the MCP endpoint, so editors can authorize as themselves
15
+
16
+ `initValMcp` takes an optional `oauth` config. Give it the authorization server's
17
+ URL and this endpoint's own URL, and every MCP call must then present an access
18
+ token that Val's authorization server issued:
19
+
20
+ ```ts
21
+ const { valMcpAuthorize, valMcpTools, valMcpMetadata } = initValMcp(
22
+ valModules,
23
+ config,
24
+ {
25
+ oauth: {
26
+ issuer: "https://admin.val.build",
27
+ resource: "https://your-app.com/api/mcp",
28
+ },
29
+ },
30
+ );
31
+ ```
32
+
33
+ The token is verified in your app — signature against the issuer's published
34
+ keys, plus issuer, audience and expiry — so the caller's identity is checked
35
+ rather than claimed. **Patches created over MCP now carry that profile as their
36
+ author**, which is what makes an edit made from a phone show up in the review
37
+ screen as somebody's rather than nobody's. Scopes are enforced too: a token
38
+ without `val:write` cannot reach a tool that writes.
39
+
40
+ Mount the discovery document so clients can find where to authorize:
41
+
42
+ ```ts
43
+ // app/.well-known/oauth-protected-resource/route.ts
44
+ import { valMcpMetadata } from "../../../val/mcp";
45
+ export const { GET, OPTIONS } = valMcpMetadata!;
46
+ ```
47
+
48
+ `valMcpMetadata` is `null` when no `oauth` config is given.
49
+
50
+ **Nothing changes if you leave `oauth` out.** Local development still works with
51
+ no authorization server, and an app already using a personal access token keeps
52
+ working as before.
53
+
54
+ One breaking change if you built your own host on `createValTools`:
55
+ `ValToolContext.auth` is now a tagged union, so `{ pat }` becomes
56
+ `{ type: "pat", pat }`. The new variant is
57
+ `{ type: "verified-profile", profileId, scopes }`, for a host that verified a
58
+ token itself.
59
+
60
+ ### Patch Changes
61
+
62
+ - [#579](https://github.com/valbuild/val/pull/579) [`b2812ae`](https://github.com/valbuild/val/commit/b2812ae4ee03e005ecead3365f49c625e536f94d) Thanks [@freekh](https://github.com/freekh)! - Every release now ships a changelog. Each package's `CHANGELOG.md` records what
63
+ changed under the version that shipped it — with a link to the pull request, the
64
+ commit and the author — and the same entry becomes the body of the GitHub
65
+ Release for the tag. The file is included in the npm tarball, so it is also
66
+ readable from an installed copy.
67
+
68
+ Up to now those changelogs were generated empty, and the GitHub Releases with
69
+ them, so there was no record of what any given version contained. Releases from
70
+ this one on have one; earlier versions stay blank.
71
+
72
+ - Updated dependencies [[`d94a40f`](https://github.com/valbuild/val/commit/d94a40f8bd11027636d183e293aced820b6f341f), [`b2812ae`](https://github.com/valbuild/val/commit/b2812ae4ee03e005ecead3365f49c625e536f94d)]:
73
+ - @valbuild/core@0.117.0
74
+ - @valbuild/shared@0.117.0
75
+ - @valbuild/ui@0.117.0
@@ -1,7 +1,8 @@
1
1
  export { createService, Service } from "./Service.js";
2
2
  export { createValApiRouter, createValServer, safeReadGit } from "./ValRouter.js";
3
3
  export { createValTools } from "./tools/index.js";
4
- export type { ValToolContext, ValToolDefinition, ValToolDefinitionJson, ValToolErrorCode, ValToolResult, ValTools, ValToolsOptions, } from "./tools/index.js";
4
+ export { VAL_SCOPE_READ, VAL_SCOPE_WRITE, authorIdFromVerifiedSubject, } from "./tools/index.js";
5
+ export type { ValScope, ValToolAuth, ValToolContext, ValToolDefinition, ValToolDefinitionJson, ValToolErrorCode, ValToolResult, ValTools, ValToolsOptions, } from "./tools/index.js";
5
6
  export { initHandlerOptions, createValOps } from "./valServerConfig.js";
6
7
  export { ValModuleLoader } from "./ValModuleLoader.js";
7
8
  export { getCompilerOptions } from "./getCompilerOptions.js";
@@ -1,6 +1,6 @@
1
1
  import type { ValModules } from "@valbuild/core";
2
2
  import type { ValServerConfig } from "../ValServer.js";
3
- import type { ValTools } from "./types.js";
3
+ import { type ValTools } from "./types.js";
4
4
  export type ValToolsOptions = ValServerConfig;
5
5
  /**
6
6
  * Val's server-side tool registry.
@@ -1,2 +1,3 @@
1
1
  export { createValTools, type ValToolsOptions } from "./createValTools.js";
2
- export type { ValToolContext, ValToolDefinition, ValToolDefinitionJson, ValToolErrorCode, ValToolResult, ValTools, } from "./types.js";
2
+ export { VAL_SCOPE_READ, VAL_SCOPE_WRITE, authorIdFromVerifiedSubject, } from "./types.js";
3
+ export type { ValScope, ValToolAuth, ValToolContext, ValToolDefinition, ValToolDefinitionJson, ValToolErrorCode, ValToolResult, ValTools, } from "./types.js";
@@ -1,3 +1,4 @@
1
+ import type { AuthorId } from "../ValOps.js";
1
2
  import type { Json } from "@valbuild/core";
2
3
  import type { z } from "zod";
3
4
  /**
@@ -74,14 +75,56 @@ export type ValToolDefinitionJson = Omit<ValToolDefinition, "inputSchema"> & {
74
75
  inputSchema: ReturnType<typeof z.toJSONSchema<z.ZodType>>;
75
76
  };
76
77
  /**
77
- * Who is calling, established once per request by the host.
78
+ * How the caller was established, and it is a union because there are two
79
+ * genuinely different answers — with different consequences downstream.
78
80
  *
79
- * There is deliberately no identity field here, only the credential. In proxy
80
- * mode the caller's own personal access token authenticates every downstream
81
- * call, so the backend decides who the caller is (`docs/plans/mcp.md` D.2). An
82
- * `authorId` alongside it would have to be filled in by the host, and the host
83
- * has no way to verify a PAT — so the field would be an unverified claim that
84
- * looks like a checked one, which is the confused-deputy shape D.6 rejects.
81
+ * The distinction that matters is **who checked**. A PAT is forwarded to the
82
+ * backend unchecked, because the app cannot resolve one; an access token is
83
+ * verified by the app itself, against a public key it does not hold and
84
+ * therefore cannot forge. The first is a credential being relayed. The second
85
+ * is a signature that has already been checked.
86
+ */
87
+ export type ValToolAuth = {
88
+ type: "pat";
89
+ /**
90
+ * The caller's PAT. Never log this, never put it in a URL, and never let
91
+ * it reach a tool result.
92
+ *
93
+ * Relayed to the backend as-is: this app is not the authority on what the
94
+ * token may do, and the backend that is decides. Nothing is derived from
95
+ * it here — see `docs/plans/mcp.md` D.2.
96
+ */
97
+ pat: string;
98
+ } | {
99
+ type: "verified-profile";
100
+ /**
101
+ * The profile the host **verified** — the `sub` of an access token whose
102
+ * signature, issuer, audience and expiry were all checked against the
103
+ * authorization server's published key.
104
+ *
105
+ * This field is the reason this type became a union, and an earlier
106
+ * version of this file argued no identity field should exist at all. That
107
+ * argument was about a specific case and stated too broadly: an id the
108
+ * host *asserts* on the strength of a credential it cannot check is an
109
+ * unverified claim dressed as a checked one, and that is still refused —
110
+ * it is why the `pat` variant carries no profile. An id the host
111
+ * *verified* cryptographically is a different thing, and it is the same
112
+ * standing the Studio has when it re-signs a session it established
113
+ * itself.
114
+ */
115
+ profileId: AuthorId;
116
+ /**
117
+ * The token's granted scopes, as the authorization server issued them.
118
+ *
119
+ * Enforced here as well as by the backend, deliberately. Two checks on
120
+ * one grant is not redundancy for its own sake: this one can refuse a
121
+ * write before it is attempted, so a token that may only read never
122
+ * reaches the code that builds a patch.
123
+ */
124
+ scopes: string[];
125
+ };
126
+ /**
127
+ * Who is calling, established once per request by the host.
85
128
  *
86
129
  * `null` means local fs mode, where there is no credential to hold and patches
87
130
  * are written with no author, exactly as the Studio does locally (D.1). In
@@ -90,16 +133,29 @@ export type ValToolDefinitionJson = Omit<ValToolDefinition, "inputSchema"> & {
90
133
  * would turn a missing credential into full access.
91
134
  */
92
135
  export type ValToolContext = {
93
- auth: {
94
- /**
95
- * The caller's PAT. Never log this, never put it in a URL, and never let
96
- * it reach a tool result.
97
- */
98
- pat: string;
99
- } | null;
136
+ auth: ValToolAuth | null;
100
137
  /** Groups a run of related edits, when the host has such a notion. */
101
138
  sessionId: string | null;
102
139
  };
140
+ /**
141
+ * Brand a verified subject as an {@link AuthorId}.
142
+ *
143
+ * `AuthorId` is a branded string so that an id cannot be conjured from any
144
+ * string that happens to be lying around — which is exactly the mistake this
145
+ * type is guarding against. That makes one assertion unavoidable at the boundary
146
+ * where a real id enters the system, so it lives here, once, with a name that
147
+ * says what makes it legitimate: the caller has *verified* this subject, not
148
+ * received it.
149
+ *
150
+ * Do not reach for this to satisfy a type. If you are holding a string you did
151
+ * not verify, the honest value is `null`.
152
+ */
153
+ export declare function authorIdFromVerifiedSubject(subject: string): AuthorId;
154
+ /** Read access. Every call needs it, the writes included. */
155
+ export declare const VAL_SCOPE_READ = "val:read";
156
+ /** Write access. Needed *in addition* by any tool not marked `readOnlyHint`. */
157
+ export declare const VAL_SCOPE_WRITE = "val:write";
158
+ export type ValScope = typeof VAL_SCOPE_READ | typeof VAL_SCOPE_WRITE;
103
159
  export type ValToolResult = {
104
160
  status: "ok";
105
161
  data: Json;
@@ -11425,6 +11425,7 @@ function describeErrors(errors) {
11425
11425
  * failing clearly.
11426
11426
  */
11427
11427
  async function savePatch(deps, moduleFilePath, patch, onInvalid = "reject") {
11428
+ var _ctx$auth;
11428
11429
  const {
11429
11430
  ops,
11430
11431
  ctx,
@@ -11460,13 +11461,24 @@ async function savePatch(deps, moduleFilePath, patch, onInvalid = "reject") {
11460
11461
  unresolved = speculative.errors;
11461
11462
  }
11462
11463
 
11463
- // Always null, and deliberately so. The app cannot resolve a PAT to a
11464
- // profile, so any id it put here would be an unverified claim dressed up as a
11465
- // checked one — and the request already carries the caller's own token, which
11466
- // is a better answer to "who did this" than anything the app could assert.
11467
- // Attributing the patch from that token is a backend concern; see
11468
- // `docs/plans/mcp.md` D.3.
11469
- const authorId = null;
11464
+ /**
11465
+ * Null on the PAT path, and the verified profile on the token path.
11466
+ *
11467
+ * The PAT case is unchanged and still deliberate: the app cannot resolve a
11468
+ * PAT, so any id it wrote here would be an unverified claim dressed up as a
11469
+ * checked one — and the request already carries the caller's own token, which
11470
+ * is a better answer to "who did this" than anything the app could assert.
11471
+ * Attributing that patch is the backend's job.
11472
+ *
11473
+ * The token case is the opposite situation, which is why it gets the opposite
11474
+ * answer. The host verified a signature over a key it does not hold, so the
11475
+ * profile is checked rather than claimed, and the backend has no token of its
11476
+ * own to attribute from — the call reaches it under the app's API key. If this
11477
+ * stayed null, every edit made through a signed-in editor's own session would
11478
+ * land with no author at all, which is worse than useless on a CMS whose
11479
+ * review screen is organised by who changed what.
11480
+ */
11481
+ const authorId = ((_ctx$auth = ctx.auth) === null || _ctx$auth === void 0 ? void 0 : _ctx$auth.type) === "verified-profile" ? ctx.auth.profileId : null;
11470
11482
  for (let attempt = 0; attempt < 2; attempt++) {
11471
11483
  const patchId = mintPatchId();
11472
11484
  // Re-derived on the retry rather than reused: reusing the ref that just
@@ -11690,6 +11702,80 @@ function rejectFileOps(patch) {
11690
11702
  };
11691
11703
  }
11692
11704
 
11705
+ /**
11706
+ * The public surface of Val's server-side tool registry.
11707
+ *
11708
+ * Types only, deliberately: this file is the contract that the MCP hosts, the
11709
+ * CLI's stdio transport and the tools themselves are all written against, and
11710
+ * keeping it free of implementation means those can be built in any order
11711
+ * without one of them owning the shape.
11712
+ *
11713
+ * The design this implements is `docs/plans/mcp.md`, Part A. Two constraints
11714
+ * from it are load-bearing and easy to break by accident:
11715
+ *
11716
+ * 1. **Nothing here may import an MCP SDK.** That is what lets hosts other
11717
+ * than the template consume these tools, and it is not hypothetical
11718
+ * hygiene — the TypeScript SDK reorganised itself at v2.0.0, and a registry
11719
+ * coupled to it would have moved with it.
11720
+ * 2. **The result type is deliberately not MCP's `CallToolResult`.** Each host
11721
+ * adapts {@link ValToolResult} at its own edge, which is also where an
11722
+ * error becomes an in-band `isError` result the model can recover from
11723
+ * rather than a transport failure.
11724
+ */
11725
+
11726
+ /** Why a tool call failed, in a form a host can map onto its own errors. */
11727
+
11728
+ /**
11729
+ * The same definition with `inputSchema` as JSON Schema, for hosts that want the
11730
+ * wire shape rather than a Standard Schema.
11731
+ *
11732
+ * Typed as whatever zod's own converter produces, so deriving it needs no cast
11733
+ * and no second hand-written description of the same input.
11734
+ */
11735
+
11736
+ /**
11737
+ * How the caller was established, and it is a union because there are two
11738
+ * genuinely different answers — with different consequences downstream.
11739
+ *
11740
+ * The distinction that matters is **who checked**. A PAT is forwarded to the
11741
+ * backend unchecked, because the app cannot resolve one; an access token is
11742
+ * verified by the app itself, against a public key it does not hold and
11743
+ * therefore cannot forge. The first is a credential being relayed. The second
11744
+ * is a signature that has already been checked.
11745
+ */
11746
+
11747
+ /**
11748
+ * Who is calling, established once per request by the host.
11749
+ *
11750
+ * `null` means local fs mode, where there is no credential to hold and patches
11751
+ * are written with no author, exactly as the Studio does locally (D.1). In
11752
+ * proxy mode `null` is refused rather than falling back to the app's own API
11753
+ * key: that key can do more than any single user, and quietly substituting it
11754
+ * would turn a missing credential into full access.
11755
+ */
11756
+
11757
+ /**
11758
+ * Brand a verified subject as an {@link AuthorId}.
11759
+ *
11760
+ * `AuthorId` is a branded string so that an id cannot be conjured from any
11761
+ * string that happens to be lying around — which is exactly the mistake this
11762
+ * type is guarding against. That makes one assertion unavoidable at the boundary
11763
+ * where a real id enters the system, so it lives here, once, with a name that
11764
+ * says what makes it legitimate: the caller has *verified* this subject, not
11765
+ * received it.
11766
+ *
11767
+ * Do not reach for this to satisfy a type. If you are holding a string you did
11768
+ * not verify, the honest value is `null`.
11769
+ */
11770
+ function authorIdFromVerifiedSubject(subject) {
11771
+ return subject;
11772
+ }
11773
+
11774
+ /** Read access. Every call needs it, the writes included. */
11775
+ const VAL_SCOPE_READ = "val:read";
11776
+ /** Write access. Needed *in addition* by any tool not marked `readOnlyHint`. */
11777
+ const VAL_SCOPE_WRITE = "val:write";
11778
+
11693
11779
  /**
11694
11780
  * How many callers' data layers to keep around in proxy mode.
11695
11781
  *
@@ -11756,6 +11842,10 @@ function createValTools(valModules, options) {
11756
11842
  message: describeZodError(parsed.error)
11757
11843
  };
11758
11844
  }
11845
+ const insufficient = refuseInsufficientScope(tool, ctx);
11846
+ if (insufficient) {
11847
+ return insufficient;
11848
+ }
11759
11849
  const resolved = resolveOps(ctx);
11760
11850
  if (resolved.status === "error") {
11761
11851
  return resolved.result;
@@ -11840,6 +11930,13 @@ function createOpsResolver(valModules, options) {
11840
11930
  // instance holds the token regardless — but it keeps credentials out of the
11841
11931
  // key set, which is the thing that ends up in a heap dump or an error dump.
11842
11932
  const byPatHash = new Map();
11933
+ /**
11934
+ * One instance for every verified caller, and unlike the PAT map that is
11935
+ * correct rather than a shortcut: this instance authenticates with the app's
11936
+ * own API key, so there is nothing per-caller in it to keep apart. Who did
11937
+ * what travels as the patch's `authorId` instead — see `writePath`.
11938
+ */
11939
+ let sharedOps = null;
11843
11940
  return ctx => {
11844
11941
  if (!ctx.auth) {
11845
11942
  return {
@@ -11847,10 +11944,32 @@ function createOpsResolver(valModules, options) {
11847
11944
  result: {
11848
11945
  status: "error",
11849
11946
  code: "forbidden",
11850
- message: "This Val project talks to the Val content backend, so every call needs the caller's own personal access token. Run `val login` to get one."
11947
+ message: "This Val project talks to the Val content backend, so every call needs a credential: an access token from the Val authorization server, or the caller's own personal access token from `val login`."
11851
11948
  }
11852
11949
  };
11853
11950
  }
11951
+ if (ctx.auth.type === "verified-profile") {
11952
+ if (!options.apiKey) {
11953
+ // Proxy mode is inferred from the api key being present, so this is
11954
+ // unreachable through `initHandlerOptions`. It stays because the
11955
+ // alternative to refusing is building ops with no credential at all.
11956
+ return {
11957
+ status: "error",
11958
+ result: {
11959
+ status: "error",
11960
+ code: "forbidden",
11961
+ message: "This Val project has no API key configured, so a verified access token cannot be exchanged for backend access."
11962
+ }
11963
+ };
11964
+ }
11965
+ if (!sharedOps) {
11966
+ sharedOps = createValOps(valModules, options);
11967
+ }
11968
+ return {
11969
+ status: "ok",
11970
+ ops: sharedOps
11971
+ };
11972
+ }
11854
11973
  const key = node_crypto.createHash("sha256").update(ctx.auth.pat).digest("hex");
11855
11974
  const cached = byPatHash.get(key);
11856
11975
  if (cached) {
@@ -11963,6 +12082,42 @@ function describeZodError(error) {
11963
12082
  }).join("; ");
11964
12083
  }
11965
12084
 
12085
+ /**
12086
+ * Refuse a call the token was not granted, before anything is attempted.
12087
+ *
12088
+ * Derived from `readOnlyHint` rather than from a second list of tool names,
12089
+ * because a second list is a thing that drifts. The derivation also fails in
12090
+ * the safe direction: a tool that forgets the hint is treated as a write and
12091
+ * demands the wider scope, rather than a write slipping through as a read.
12092
+ *
12093
+ * Only the verified-token path is checked. A PAT carries no scopes here by
12094
+ * design — the backend resolves it and decides — so there is nothing to
12095
+ * enforce, and inventing a default would be this app claiming an authority it
12096
+ * does not have.
12097
+ */
12098
+ function refuseInsufficientScope(tool, ctx) {
12099
+ var _ctx$auth, _tool$annotations;
12100
+ if (((_ctx$auth = ctx.auth) === null || _ctx$auth === void 0 ? void 0 : _ctx$auth.type) !== "verified-profile") {
12101
+ return null;
12102
+ }
12103
+ // Read is needed by every call, including the writes: a tool that changes
12104
+ // content reads it first, and `ValToolAuth` says as much. Checking only the
12105
+ // wider scope would let a write-but-not-read token through here — today's
12106
+ // verifier refuses such a token before this point, but `createValTools` is
12107
+ // exported and another host may not.
12108
+ const needed = (_tool$annotations = tool.annotations) !== null && _tool$annotations !== void 0 && _tool$annotations.readOnlyHint ? [VAL_SCOPE_READ] : [VAL_SCOPE_READ, VAL_SCOPE_WRITE];
12109
+ const granted = ctx.auth.scopes;
12110
+ const missing = needed.filter(scope => !granted.includes(scope));
12111
+ if (missing.length === 0) {
12112
+ return null;
12113
+ }
12114
+ return {
12115
+ status: "error",
12116
+ code: "forbidden",
12117
+ message: `This access token does not have the ${missing.join(" and ")} scope, which ${tool.name} requires. Granted: ${granted.length > 0 ? granted.join(" ") : "(none)"}.`
12118
+ };
12119
+ }
12120
+
11966
12121
  const JsFileLookupMapping = [
11967
12122
  // NOTE: first one matching will be used
11968
12123
  [".cjs.d.ts", [".esm.js", ".mjs.js"]], [".cjs.js", [".esm.js", ".mjs.js"]], [".cjs", [".mjs"]], [".d.ts", [".js", ".esm.js", ".mjs.js"]]];
@@ -14150,6 +14305,8 @@ exports.DEFAULT_LOGIN_EXPIRES_IN_SECONDS = DEFAULT_LOGIN_EXPIRES_IN_SECONDS;
14150
14305
  exports.DEFAULT_LOGIN_HOST = DEFAULT_LOGIN_HOST;
14151
14306
  exports.DEFAULT_LOGIN_POLL_INTERVAL_SECONDS = DEFAULT_LOGIN_POLL_INTERVAL_SECONDS;
14152
14307
  exports.Service = Service;
14308
+ exports.VAL_SCOPE_READ = VAL_SCOPE_READ;
14309
+ exports.VAL_SCOPE_WRITE = VAL_SCOPE_WRITE;
14153
14310
  exports.ValFSHost = ValFSHost;
14154
14311
  exports.ValLoginError = ValLoginError;
14155
14312
  exports.ValModuleLoader = ValModuleLoader;
@@ -14157,6 +14314,7 @@ exports.ValOpsFS = ValOpsFS;
14157
14314
  exports.ValOpsHttp = ValOpsHttp;
14158
14315
  exports.ValSourceFileHandler = ValSourceFileHandler;
14159
14316
  exports.analyzeValModule = analyzeValModule;
14317
+ exports.authorIdFromVerifiedSubject = authorIdFromVerifiedSubject;
14160
14318
  exports.awaitValLoginConfirmation = awaitValLoginConfirmation;
14161
14319
  exports.checkRemoteRef = checkRemoteRef;
14162
14320
  exports.classifyJsonValuesOp = classifyJsonValuesOp;
@@ -11425,6 +11425,7 @@ function describeErrors(errors) {
11425
11425
  * failing clearly.
11426
11426
  */
11427
11427
  async function savePatch(deps, moduleFilePath, patch, onInvalid = "reject") {
11428
+ var _ctx$auth;
11428
11429
  const {
11429
11430
  ops,
11430
11431
  ctx,
@@ -11460,13 +11461,24 @@ async function savePatch(deps, moduleFilePath, patch, onInvalid = "reject") {
11460
11461
  unresolved = speculative.errors;
11461
11462
  }
11462
11463
 
11463
- // Always null, and deliberately so. The app cannot resolve a PAT to a
11464
- // profile, so any id it put here would be an unverified claim dressed up as a
11465
- // checked one — and the request already carries the caller's own token, which
11466
- // is a better answer to "who did this" than anything the app could assert.
11467
- // Attributing the patch from that token is a backend concern; see
11468
- // `docs/plans/mcp.md` D.3.
11469
- const authorId = null;
11464
+ /**
11465
+ * Null on the PAT path, and the verified profile on the token path.
11466
+ *
11467
+ * The PAT case is unchanged and still deliberate: the app cannot resolve a
11468
+ * PAT, so any id it wrote here would be an unverified claim dressed up as a
11469
+ * checked one — and the request already carries the caller's own token, which
11470
+ * is a better answer to "who did this" than anything the app could assert.
11471
+ * Attributing that patch is the backend's job.
11472
+ *
11473
+ * The token case is the opposite situation, which is why it gets the opposite
11474
+ * answer. The host verified a signature over a key it does not hold, so the
11475
+ * profile is checked rather than claimed, and the backend has no token of its
11476
+ * own to attribute from — the call reaches it under the app's API key. If this
11477
+ * stayed null, every edit made through a signed-in editor's own session would
11478
+ * land with no author at all, which is worse than useless on a CMS whose
11479
+ * review screen is organised by who changed what.
11480
+ */
11481
+ const authorId = ((_ctx$auth = ctx.auth) === null || _ctx$auth === void 0 ? void 0 : _ctx$auth.type) === "verified-profile" ? ctx.auth.profileId : null;
11470
11482
  for (let attempt = 0; attempt < 2; attempt++) {
11471
11483
  const patchId = mintPatchId();
11472
11484
  // Re-derived on the retry rather than reused: reusing the ref that just
@@ -11690,6 +11702,80 @@ function rejectFileOps(patch) {
11690
11702
  };
11691
11703
  }
11692
11704
 
11705
+ /**
11706
+ * The public surface of Val's server-side tool registry.
11707
+ *
11708
+ * Types only, deliberately: this file is the contract that the MCP hosts, the
11709
+ * CLI's stdio transport and the tools themselves are all written against, and
11710
+ * keeping it free of implementation means those can be built in any order
11711
+ * without one of them owning the shape.
11712
+ *
11713
+ * The design this implements is `docs/plans/mcp.md`, Part A. Two constraints
11714
+ * from it are load-bearing and easy to break by accident:
11715
+ *
11716
+ * 1. **Nothing here may import an MCP SDK.** That is what lets hosts other
11717
+ * than the template consume these tools, and it is not hypothetical
11718
+ * hygiene — the TypeScript SDK reorganised itself at v2.0.0, and a registry
11719
+ * coupled to it would have moved with it.
11720
+ * 2. **The result type is deliberately not MCP's `CallToolResult`.** Each host
11721
+ * adapts {@link ValToolResult} at its own edge, which is also where an
11722
+ * error becomes an in-band `isError` result the model can recover from
11723
+ * rather than a transport failure.
11724
+ */
11725
+
11726
+ /** Why a tool call failed, in a form a host can map onto its own errors. */
11727
+
11728
+ /**
11729
+ * The same definition with `inputSchema` as JSON Schema, for hosts that want the
11730
+ * wire shape rather than a Standard Schema.
11731
+ *
11732
+ * Typed as whatever zod's own converter produces, so deriving it needs no cast
11733
+ * and no second hand-written description of the same input.
11734
+ */
11735
+
11736
+ /**
11737
+ * How the caller was established, and it is a union because there are two
11738
+ * genuinely different answers — with different consequences downstream.
11739
+ *
11740
+ * The distinction that matters is **who checked**. A PAT is forwarded to the
11741
+ * backend unchecked, because the app cannot resolve one; an access token is
11742
+ * verified by the app itself, against a public key it does not hold and
11743
+ * therefore cannot forge. The first is a credential being relayed. The second
11744
+ * is a signature that has already been checked.
11745
+ */
11746
+
11747
+ /**
11748
+ * Who is calling, established once per request by the host.
11749
+ *
11750
+ * `null` means local fs mode, where there is no credential to hold and patches
11751
+ * are written with no author, exactly as the Studio does locally (D.1). In
11752
+ * proxy mode `null` is refused rather than falling back to the app's own API
11753
+ * key: that key can do more than any single user, and quietly substituting it
11754
+ * would turn a missing credential into full access.
11755
+ */
11756
+
11757
+ /**
11758
+ * Brand a verified subject as an {@link AuthorId}.
11759
+ *
11760
+ * `AuthorId` is a branded string so that an id cannot be conjured from any
11761
+ * string that happens to be lying around — which is exactly the mistake this
11762
+ * type is guarding against. That makes one assertion unavoidable at the boundary
11763
+ * where a real id enters the system, so it lives here, once, with a name that
11764
+ * says what makes it legitimate: the caller has *verified* this subject, not
11765
+ * received it.
11766
+ *
11767
+ * Do not reach for this to satisfy a type. If you are holding a string you did
11768
+ * not verify, the honest value is `null`.
11769
+ */
11770
+ function authorIdFromVerifiedSubject(subject) {
11771
+ return subject;
11772
+ }
11773
+
11774
+ /** Read access. Every call needs it, the writes included. */
11775
+ const VAL_SCOPE_READ = "val:read";
11776
+ /** Write access. Needed *in addition* by any tool not marked `readOnlyHint`. */
11777
+ const VAL_SCOPE_WRITE = "val:write";
11778
+
11693
11779
  /**
11694
11780
  * How many callers' data layers to keep around in proxy mode.
11695
11781
  *
@@ -11756,6 +11842,10 @@ function createValTools(valModules, options) {
11756
11842
  message: describeZodError(parsed.error)
11757
11843
  };
11758
11844
  }
11845
+ const insufficient = refuseInsufficientScope(tool, ctx);
11846
+ if (insufficient) {
11847
+ return insufficient;
11848
+ }
11759
11849
  const resolved = resolveOps(ctx);
11760
11850
  if (resolved.status === "error") {
11761
11851
  return resolved.result;
@@ -11840,6 +11930,13 @@ function createOpsResolver(valModules, options) {
11840
11930
  // instance holds the token regardless — but it keeps credentials out of the
11841
11931
  // key set, which is the thing that ends up in a heap dump or an error dump.
11842
11932
  const byPatHash = new Map();
11933
+ /**
11934
+ * One instance for every verified caller, and unlike the PAT map that is
11935
+ * correct rather than a shortcut: this instance authenticates with the app's
11936
+ * own API key, so there is nothing per-caller in it to keep apart. Who did
11937
+ * what travels as the patch's `authorId` instead — see `writePath`.
11938
+ */
11939
+ let sharedOps = null;
11843
11940
  return ctx => {
11844
11941
  if (!ctx.auth) {
11845
11942
  return {
@@ -11847,10 +11944,32 @@ function createOpsResolver(valModules, options) {
11847
11944
  result: {
11848
11945
  status: "error",
11849
11946
  code: "forbidden",
11850
- message: "This Val project talks to the Val content backend, so every call needs the caller's own personal access token. Run `val login` to get one."
11947
+ message: "This Val project talks to the Val content backend, so every call needs a credential: an access token from the Val authorization server, or the caller's own personal access token from `val login`."
11851
11948
  }
11852
11949
  };
11853
11950
  }
11951
+ if (ctx.auth.type === "verified-profile") {
11952
+ if (!options.apiKey) {
11953
+ // Proxy mode is inferred from the api key being present, so this is
11954
+ // unreachable through `initHandlerOptions`. It stays because the
11955
+ // alternative to refusing is building ops with no credential at all.
11956
+ return {
11957
+ status: "error",
11958
+ result: {
11959
+ status: "error",
11960
+ code: "forbidden",
11961
+ message: "This Val project has no API key configured, so a verified access token cannot be exchanged for backend access."
11962
+ }
11963
+ };
11964
+ }
11965
+ if (!sharedOps) {
11966
+ sharedOps = createValOps(valModules, options);
11967
+ }
11968
+ return {
11969
+ status: "ok",
11970
+ ops: sharedOps
11971
+ };
11972
+ }
11854
11973
  const key = node_crypto.createHash("sha256").update(ctx.auth.pat).digest("hex");
11855
11974
  const cached = byPatHash.get(key);
11856
11975
  if (cached) {
@@ -11963,6 +12082,42 @@ function describeZodError(error) {
11963
12082
  }).join("; ");
11964
12083
  }
11965
12084
 
12085
+ /**
12086
+ * Refuse a call the token was not granted, before anything is attempted.
12087
+ *
12088
+ * Derived from `readOnlyHint` rather than from a second list of tool names,
12089
+ * because a second list is a thing that drifts. The derivation also fails in
12090
+ * the safe direction: a tool that forgets the hint is treated as a write and
12091
+ * demands the wider scope, rather than a write slipping through as a read.
12092
+ *
12093
+ * Only the verified-token path is checked. A PAT carries no scopes here by
12094
+ * design — the backend resolves it and decides — so there is nothing to
12095
+ * enforce, and inventing a default would be this app claiming an authority it
12096
+ * does not have.
12097
+ */
12098
+ function refuseInsufficientScope(tool, ctx) {
12099
+ var _ctx$auth, _tool$annotations;
12100
+ if (((_ctx$auth = ctx.auth) === null || _ctx$auth === void 0 ? void 0 : _ctx$auth.type) !== "verified-profile") {
12101
+ return null;
12102
+ }
12103
+ // Read is needed by every call, including the writes: a tool that changes
12104
+ // content reads it first, and `ValToolAuth` says as much. Checking only the
12105
+ // wider scope would let a write-but-not-read token through here — today's
12106
+ // verifier refuses such a token before this point, but `createValTools` is
12107
+ // exported and another host may not.
12108
+ const needed = (_tool$annotations = tool.annotations) !== null && _tool$annotations !== void 0 && _tool$annotations.readOnlyHint ? [VAL_SCOPE_READ] : [VAL_SCOPE_READ, VAL_SCOPE_WRITE];
12109
+ const granted = ctx.auth.scopes;
12110
+ const missing = needed.filter(scope => !granted.includes(scope));
12111
+ if (missing.length === 0) {
12112
+ return null;
12113
+ }
12114
+ return {
12115
+ status: "error",
12116
+ code: "forbidden",
12117
+ message: `This access token does not have the ${missing.join(" and ")} scope, which ${tool.name} requires. Granted: ${granted.length > 0 ? granted.join(" ") : "(none)"}.`
12118
+ };
12119
+ }
12120
+
11966
12121
  const JsFileLookupMapping = [
11967
12122
  // NOTE: first one matching will be used
11968
12123
  [".cjs.d.ts", [".esm.js", ".mjs.js"]], [".cjs.js", [".esm.js", ".mjs.js"]], [".cjs", [".mjs"]], [".d.ts", [".js", ".esm.js", ".mjs.js"]]];
@@ -14150,6 +14305,8 @@ exports.DEFAULT_LOGIN_EXPIRES_IN_SECONDS = DEFAULT_LOGIN_EXPIRES_IN_SECONDS;
14150
14305
  exports.DEFAULT_LOGIN_HOST = DEFAULT_LOGIN_HOST;
14151
14306
  exports.DEFAULT_LOGIN_POLL_INTERVAL_SECONDS = DEFAULT_LOGIN_POLL_INTERVAL_SECONDS;
14152
14307
  exports.Service = Service;
14308
+ exports.VAL_SCOPE_READ = VAL_SCOPE_READ;
14309
+ exports.VAL_SCOPE_WRITE = VAL_SCOPE_WRITE;
14153
14310
  exports.ValFSHost = ValFSHost;
14154
14311
  exports.ValLoginError = ValLoginError;
14155
14312
  exports.ValModuleLoader = ValModuleLoader;
@@ -14157,6 +14314,7 @@ exports.ValOpsFS = ValOpsFS;
14157
14314
  exports.ValOpsHttp = ValOpsHttp;
14158
14315
  exports.ValSourceFileHandler = ValSourceFileHandler;
14159
14316
  exports.analyzeValModule = analyzeValModule;
14317
+ exports.authorIdFromVerifiedSubject = authorIdFromVerifiedSubject;
14160
14318
  exports.awaitValLoginConfirmation = awaitValLoginConfirmation;
14161
14319
  exports.checkRemoteRef = checkRemoteRef;
14162
14320
  exports.classifyJsonValuesOp = classifyJsonValuesOp;
@@ -11391,6 +11391,7 @@ function describeErrors(errors) {
11391
11391
  * failing clearly.
11392
11392
  */
11393
11393
  async function savePatch(deps, moduleFilePath, patch, onInvalid = "reject") {
11394
+ var _ctx$auth;
11394
11395
  const {
11395
11396
  ops,
11396
11397
  ctx,
@@ -11426,13 +11427,24 @@ async function savePatch(deps, moduleFilePath, patch, onInvalid = "reject") {
11426
11427
  unresolved = speculative.errors;
11427
11428
  }
11428
11429
 
11429
- // Always null, and deliberately so. The app cannot resolve a PAT to a
11430
- // profile, so any id it put here would be an unverified claim dressed up as a
11431
- // checked one — and the request already carries the caller's own token, which
11432
- // is a better answer to "who did this" than anything the app could assert.
11433
- // Attributing the patch from that token is a backend concern; see
11434
- // `docs/plans/mcp.md` D.3.
11435
- const authorId = null;
11430
+ /**
11431
+ * Null on the PAT path, and the verified profile on the token path.
11432
+ *
11433
+ * The PAT case is unchanged and still deliberate: the app cannot resolve a
11434
+ * PAT, so any id it wrote here would be an unverified claim dressed up as a
11435
+ * checked one — and the request already carries the caller's own token, which
11436
+ * is a better answer to "who did this" than anything the app could assert.
11437
+ * Attributing that patch is the backend's job.
11438
+ *
11439
+ * The token case is the opposite situation, which is why it gets the opposite
11440
+ * answer. The host verified a signature over a key it does not hold, so the
11441
+ * profile is checked rather than claimed, and the backend has no token of its
11442
+ * own to attribute from — the call reaches it under the app's API key. If this
11443
+ * stayed null, every edit made through a signed-in editor's own session would
11444
+ * land with no author at all, which is worse than useless on a CMS whose
11445
+ * review screen is organised by who changed what.
11446
+ */
11447
+ const authorId = ((_ctx$auth = ctx.auth) === null || _ctx$auth === void 0 ? void 0 : _ctx$auth.type) === "verified-profile" ? ctx.auth.profileId : null;
11436
11448
  for (let attempt = 0; attempt < 2; attempt++) {
11437
11449
  const patchId = mintPatchId();
11438
11450
  // Re-derived on the retry rather than reused: reusing the ref that just
@@ -11656,6 +11668,80 @@ function rejectFileOps(patch) {
11656
11668
  };
11657
11669
  }
11658
11670
 
11671
+ /**
11672
+ * The public surface of Val's server-side tool registry.
11673
+ *
11674
+ * Types only, deliberately: this file is the contract that the MCP hosts, the
11675
+ * CLI's stdio transport and the tools themselves are all written against, and
11676
+ * keeping it free of implementation means those can be built in any order
11677
+ * without one of them owning the shape.
11678
+ *
11679
+ * The design this implements is `docs/plans/mcp.md`, Part A. Two constraints
11680
+ * from it are load-bearing and easy to break by accident:
11681
+ *
11682
+ * 1. **Nothing here may import an MCP SDK.** That is what lets hosts other
11683
+ * than the template consume these tools, and it is not hypothetical
11684
+ * hygiene — the TypeScript SDK reorganised itself at v2.0.0, and a registry
11685
+ * coupled to it would have moved with it.
11686
+ * 2. **The result type is deliberately not MCP's `CallToolResult`.** Each host
11687
+ * adapts {@link ValToolResult} at its own edge, which is also where an
11688
+ * error becomes an in-band `isError` result the model can recover from
11689
+ * rather than a transport failure.
11690
+ */
11691
+
11692
+ /** Why a tool call failed, in a form a host can map onto its own errors. */
11693
+
11694
+ /**
11695
+ * The same definition with `inputSchema` as JSON Schema, for hosts that want the
11696
+ * wire shape rather than a Standard Schema.
11697
+ *
11698
+ * Typed as whatever zod's own converter produces, so deriving it needs no cast
11699
+ * and no second hand-written description of the same input.
11700
+ */
11701
+
11702
+ /**
11703
+ * How the caller was established, and it is a union because there are two
11704
+ * genuinely different answers — with different consequences downstream.
11705
+ *
11706
+ * The distinction that matters is **who checked**. A PAT is forwarded to the
11707
+ * backend unchecked, because the app cannot resolve one; an access token is
11708
+ * verified by the app itself, against a public key it does not hold and
11709
+ * therefore cannot forge. The first is a credential being relayed. The second
11710
+ * is a signature that has already been checked.
11711
+ */
11712
+
11713
+ /**
11714
+ * Who is calling, established once per request by the host.
11715
+ *
11716
+ * `null` means local fs mode, where there is no credential to hold and patches
11717
+ * are written with no author, exactly as the Studio does locally (D.1). In
11718
+ * proxy mode `null` is refused rather than falling back to the app's own API
11719
+ * key: that key can do more than any single user, and quietly substituting it
11720
+ * would turn a missing credential into full access.
11721
+ */
11722
+
11723
+ /**
11724
+ * Brand a verified subject as an {@link AuthorId}.
11725
+ *
11726
+ * `AuthorId` is a branded string so that an id cannot be conjured from any
11727
+ * string that happens to be lying around — which is exactly the mistake this
11728
+ * type is guarding against. That makes one assertion unavoidable at the boundary
11729
+ * where a real id enters the system, so it lives here, once, with a name that
11730
+ * says what makes it legitimate: the caller has *verified* this subject, not
11731
+ * received it.
11732
+ *
11733
+ * Do not reach for this to satisfy a type. If you are holding a string you did
11734
+ * not verify, the honest value is `null`.
11735
+ */
11736
+ function authorIdFromVerifiedSubject(subject) {
11737
+ return subject;
11738
+ }
11739
+
11740
+ /** Read access. Every call needs it, the writes included. */
11741
+ const VAL_SCOPE_READ = "val:read";
11742
+ /** Write access. Needed *in addition* by any tool not marked `readOnlyHint`. */
11743
+ const VAL_SCOPE_WRITE = "val:write";
11744
+
11659
11745
  /**
11660
11746
  * How many callers' data layers to keep around in proxy mode.
11661
11747
  *
@@ -11722,6 +11808,10 @@ function createValTools(valModules, options) {
11722
11808
  message: describeZodError(parsed.error)
11723
11809
  };
11724
11810
  }
11811
+ const insufficient = refuseInsufficientScope(tool, ctx);
11812
+ if (insufficient) {
11813
+ return insufficient;
11814
+ }
11725
11815
  const resolved = resolveOps(ctx);
11726
11816
  if (resolved.status === "error") {
11727
11817
  return resolved.result;
@@ -11806,6 +11896,13 @@ function createOpsResolver(valModules, options) {
11806
11896
  // instance holds the token regardless — but it keeps credentials out of the
11807
11897
  // key set, which is the thing that ends up in a heap dump or an error dump.
11808
11898
  const byPatHash = new Map();
11899
+ /**
11900
+ * One instance for every verified caller, and unlike the PAT map that is
11901
+ * correct rather than a shortcut: this instance authenticates with the app's
11902
+ * own API key, so there is nothing per-caller in it to keep apart. Who did
11903
+ * what travels as the patch's `authorId` instead — see `writePath`.
11904
+ */
11905
+ let sharedOps = null;
11809
11906
  return ctx => {
11810
11907
  if (!ctx.auth) {
11811
11908
  return {
@@ -11813,10 +11910,32 @@ function createOpsResolver(valModules, options) {
11813
11910
  result: {
11814
11911
  status: "error",
11815
11912
  code: "forbidden",
11816
- message: "This Val project talks to the Val content backend, so every call needs the caller's own personal access token. Run `val login` to get one."
11913
+ message: "This Val project talks to the Val content backend, so every call needs a credential: an access token from the Val authorization server, or the caller's own personal access token from `val login`."
11817
11914
  }
11818
11915
  };
11819
11916
  }
11917
+ if (ctx.auth.type === "verified-profile") {
11918
+ if (!options.apiKey) {
11919
+ // Proxy mode is inferred from the api key being present, so this is
11920
+ // unreachable through `initHandlerOptions`. It stays because the
11921
+ // alternative to refusing is building ops with no credential at all.
11922
+ return {
11923
+ status: "error",
11924
+ result: {
11925
+ status: "error",
11926
+ code: "forbidden",
11927
+ message: "This Val project has no API key configured, so a verified access token cannot be exchanged for backend access."
11928
+ }
11929
+ };
11930
+ }
11931
+ if (!sharedOps) {
11932
+ sharedOps = createValOps(valModules, options);
11933
+ }
11934
+ return {
11935
+ status: "ok",
11936
+ ops: sharedOps
11937
+ };
11938
+ }
11820
11939
  const key = createHash("sha256").update(ctx.auth.pat).digest("hex");
11821
11940
  const cached = byPatHash.get(key);
11822
11941
  if (cached) {
@@ -11929,6 +12048,42 @@ function describeZodError(error) {
11929
12048
  }).join("; ");
11930
12049
  }
11931
12050
 
12051
+ /**
12052
+ * Refuse a call the token was not granted, before anything is attempted.
12053
+ *
12054
+ * Derived from `readOnlyHint` rather than from a second list of tool names,
12055
+ * because a second list is a thing that drifts. The derivation also fails in
12056
+ * the safe direction: a tool that forgets the hint is treated as a write and
12057
+ * demands the wider scope, rather than a write slipping through as a read.
12058
+ *
12059
+ * Only the verified-token path is checked. A PAT carries no scopes here by
12060
+ * design — the backend resolves it and decides — so there is nothing to
12061
+ * enforce, and inventing a default would be this app claiming an authority it
12062
+ * does not have.
12063
+ */
12064
+ function refuseInsufficientScope(tool, ctx) {
12065
+ var _ctx$auth, _tool$annotations;
12066
+ if (((_ctx$auth = ctx.auth) === null || _ctx$auth === void 0 ? void 0 : _ctx$auth.type) !== "verified-profile") {
12067
+ return null;
12068
+ }
12069
+ // Read is needed by every call, including the writes: a tool that changes
12070
+ // content reads it first, and `ValToolAuth` says as much. Checking only the
12071
+ // wider scope would let a write-but-not-read token through here — today's
12072
+ // verifier refuses such a token before this point, but `createValTools` is
12073
+ // exported and another host may not.
12074
+ const needed = (_tool$annotations = tool.annotations) !== null && _tool$annotations !== void 0 && _tool$annotations.readOnlyHint ? [VAL_SCOPE_READ] : [VAL_SCOPE_READ, VAL_SCOPE_WRITE];
12075
+ const granted = ctx.auth.scopes;
12076
+ const missing = needed.filter(scope => !granted.includes(scope));
12077
+ if (missing.length === 0) {
12078
+ return null;
12079
+ }
12080
+ return {
12081
+ status: "error",
12082
+ code: "forbidden",
12083
+ message: `This access token does not have the ${missing.join(" and ")} scope, which ${tool.name} requires. Granted: ${granted.length > 0 ? granted.join(" ") : "(none)"}.`
12084
+ };
12085
+ }
12086
+
11932
12087
  const JsFileLookupMapping = [
11933
12088
  // NOTE: first one matching will be used
11934
12089
  [".cjs.d.ts", [".esm.js", ".mjs.js"]], [".cjs.js", [".esm.js", ".mjs.js"]], [".cjs", [".mjs"]], [".d.ts", [".js", ".esm.js", ".mjs.js"]]];
@@ -14108,4 +14263,4 @@ function readCapturedReport(snapshotDir) {
14108
14263
  return JSON.parse(fs.readFileSync(reportPath, "utf-8"));
14109
14264
  }
14110
14265
 
14111
- export { DEFAULT_LOGIN_EXPIRES_IN_SECONDS, DEFAULT_LOGIN_HOST, DEFAULT_LOGIN_POLL_INTERVAL_SECONDS, Service, ValFSHost, ValLoginError, ValModuleLoader, ValOpsFS, ValOpsHttp, ValSourceFileHandler, analyzeValModule, awaitValLoginConfirmation, checkRemoteRef, classifyJsonValuesOp, compareWithCapturedReport, createDefaultValFSHost, createFixPatch, createJsonEntryPathMap, createModulePathMap, createService, createValApiRouter, createValModuleFileInspector, createValOps, createValServer, createValTools, currentFixHandlers, decodeJwtWithoutVerifying, describePatchStoreProblems, downloadFileFromRemote, encodeJwt, evalValConfigFile, extractFileMetadata, extractImageMetadata, extractJsonValuesEntry, findAndEvalValConfigFile, findJsonEntryFilePath, fixHandlers, formatPatchSourceError, formatSyntaxErrorTree, getCachedRemoteFileDir, getCachedRemoteFilePath, getCompilerOptions, getExpire, getFileExt, getModulePathRange, getPersonalAccessTokenPath, getSettings, getValidationErrorFileRef, handleCheckAllFiles, handleFileMetadata, handleJsonValuesExtractEntry, handleRemoteFileCheck, handleRemoteFileDownload, handleRemoteFileUpload, handleRemoteGalleryFileUpload, handleUniqueFolderCheck, initHandlerOptions, loadValModules, parsePersonalAccessTokenFile, patchSourceFile, persistPersonalAccessToken, readCapturedReport, readPatchStore, rebaseContentOp, replaySnapshot, safeReadGit, startValLogin, uploadRemoteFile, validateMetadata, verifyJwt };
14266
+ export { DEFAULT_LOGIN_EXPIRES_IN_SECONDS, DEFAULT_LOGIN_HOST, DEFAULT_LOGIN_POLL_INTERVAL_SECONDS, Service, VAL_SCOPE_READ, VAL_SCOPE_WRITE, ValFSHost, ValLoginError, ValModuleLoader, ValOpsFS, ValOpsHttp, ValSourceFileHandler, analyzeValModule, authorIdFromVerifiedSubject, awaitValLoginConfirmation, checkRemoteRef, classifyJsonValuesOp, compareWithCapturedReport, createDefaultValFSHost, createFixPatch, createJsonEntryPathMap, createModulePathMap, createService, createValApiRouter, createValModuleFileInspector, createValOps, createValServer, createValTools, currentFixHandlers, decodeJwtWithoutVerifying, describePatchStoreProblems, downloadFileFromRemote, encodeJwt, evalValConfigFile, extractFileMetadata, extractImageMetadata, extractJsonValuesEntry, findAndEvalValConfigFile, findJsonEntryFilePath, fixHandlers, formatPatchSourceError, formatSyntaxErrorTree, getCachedRemoteFileDir, getCachedRemoteFilePath, getCompilerOptions, getExpire, getFileExt, getModulePathRange, getPersonalAccessTokenPath, getSettings, getValidationErrorFileRef, handleCheckAllFiles, handleFileMetadata, handleJsonValuesExtractEntry, handleRemoteFileCheck, handleRemoteFileDownload, handleRemoteFileUpload, handleRemoteGalleryFileUpload, handleUniqueFolderCheck, initHandlerOptions, loadValModules, parsePersonalAccessTokenFile, patchSourceFile, persistPersonalAccessToken, readCapturedReport, readPatchStore, rebaseContentOp, replaySnapshot, safeReadGit, startValLogin, uploadRemoteFile, validateMetadata, verifyJwt };
package/package.json CHANGED
@@ -16,7 +16,7 @@
16
16
  "./package.json": "./package.json"
17
17
  },
18
18
  "types": "dist/valbuild-server.cjs.d.ts",
19
- "version": "0.116.0",
19
+ "version": "0.117.1",
20
20
  "devDependencies": {
21
21
  "@prettier/sync": "^0.6.1",
22
22
  "@types/jest": "^30.0.0"
@@ -29,14 +29,15 @@
29
29
  "typescript": "^6.0.3",
30
30
  "zod": "^4.4.3",
31
31
  "zod-validation-error": "^5.0.0",
32
- "@valbuild/core": "0.111.0",
33
- "@valbuild/shared": "0.116.0",
34
- "@valbuild/ui": "0.116.0"
32
+ "@valbuild/core": "0.117.0",
33
+ "@valbuild/shared": "0.117.0",
34
+ "@valbuild/ui": "0.117.1"
35
35
  },
36
36
  "engines": {
37
37
  "node": "^20.19.0 || >=22"
38
38
  },
39
39
  "files": [
40
+ "CHANGELOG.md",
40
41
  "dist"
41
42
  ],
42
43
  "scripts": {