stitchkit 0.90.3 → 0.90.5

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.
Files changed (61) hide show
  1. package/CHANGELOG.md +142 -0
  2. package/dist/agent-runtime-coding-tools.js +4 -4
  3. package/dist/agent-runtime-harness.js +6 -6
  4. package/dist/agent-runtime-sandbox.js +4 -4
  5. package/dist/agent-runtime.js +9 -9
  6. package/dist/cli.d.ts +14 -1
  7. package/dist/cli.d.ts.map +1 -1
  8. package/dist/cli.js +402 -11
  9. package/dist/{index-2hrfpw2c.js → index-1bnkzq95.js} +2 -2
  10. package/dist/{index-j7q0xj6d.js → index-3yqgj323.js} +3 -3
  11. package/dist/{index-gbqjt8jz.js → index-83fafqw8.js} +3 -3
  12. package/dist/{index-44ht2790.js → index-8pjqv3zh.js} +6 -6
  13. package/dist/{index-kc6h6hg0.js → index-8z9we758.js} +1 -1
  14. package/dist/{index-r159gjwy.js → index-fenaekmk.js} +94 -8
  15. package/dist/{index-1923shw9.js → index-fqg5mfk7.js} +339 -19
  16. package/dist/{index-f475yj00.js → index-s208wab5.js} +1 -1
  17. package/dist/{index-79hb1wyh.js → index-tgh3ksfh.js} +5 -5
  18. package/dist/{index-wb15909q.js → index-zsdgd1tz.js} +3 -3
  19. package/dist/index.js +7 -7
  20. package/dist/node.js +2 -2
  21. package/dist/observability/index.js +1 -1
  22. package/dist/remote.js +1 -1
  23. package/dist/server/index.js +6 -6
  24. package/dist/testing.js +3 -3
  25. package/dist/tool-invoker.js +6 -6
  26. package/dist/tools/cli-args.d.ts +30 -6
  27. package/dist/tools/cli-args.d.ts.map +1 -1
  28. package/dist/tools/cli-installer.d.ts +34 -0
  29. package/dist/tools/cli-installer.d.ts.map +1 -0
  30. package/dist/tools/cli-manifest.d.ts +83 -0
  31. package/dist/tools/cli-manifest.d.ts.map +1 -0
  32. package/dist/tools/cli-profile.d.ts +40 -0
  33. package/dist/tools/cli-profile.d.ts.map +1 -0
  34. package/dist/tools/cli-update.d.ts +64 -0
  35. package/dist/tools/cli-update.d.ts.map +1 -0
  36. package/dist/tools/cli-view.d.ts +12 -0
  37. package/dist/tools/cli-view.d.ts.map +1 -0
  38. package/dist/tools/cli.d.ts.map +1 -1
  39. package/dist/tools/connections/index.d.ts +2 -1
  40. package/dist/tools/connections/index.d.ts.map +1 -1
  41. package/dist/tools/connections/index.js +74 -14
  42. package/dist/tools/connections/mcp-envelope.d.ts +44 -0
  43. package/dist/tools/connections/mcp-envelope.d.ts.map +1 -0
  44. package/dist/tools/connections/mcp.d.ts +2 -1
  45. package/dist/tools/connections/mcp.d.ts.map +1 -1
  46. package/dist/tools/connections/mount.d.ts.map +1 -1
  47. package/dist/tools/connections/openapi.d.ts +2 -1
  48. package/dist/tools/connections/openapi.d.ts.map +1 -1
  49. package/dist/tools/connections/runtime.d.ts +17 -0
  50. package/dist/tools/connections/runtime.d.ts.map +1 -1
  51. package/dist/tools/connections/types.d.ts +18 -1
  52. package/dist/tools/connections/types.d.ts.map +1 -1
  53. package/dist/tools/json-schema-dialect.d.ts +17 -0
  54. package/dist/tools/json-schema-dialect.d.ts.map +1 -0
  55. package/dist/tools/presentation.d.ts +8 -1
  56. package/dist/tools/presentation.d.ts.map +1 -1
  57. package/dist/tools.js +13 -13
  58. package/dist/tracking.js +1 -1
  59. package/llms-full.txt +273 -3
  60. package/package.json +1 -1
  61. package/dist/{index-db51n3xr.js → index-ra6txecz.js} +3 -3
@@ -0,0 +1,44 @@
1
+ /**
2
+ * What a discovered MCP tool's answer is, per transport.
3
+ *
4
+ * `tools/call` returns an envelope — `{ content: [...], structuredContent? }` —
5
+ * and an agent mount needs exactly that: the parts are what a model is shown.
6
+ * The CLI transport is different in kind, because the handler's value is what
7
+ * gets printed, piped and aggregated. Handed the envelope, `--count-by status`
8
+ * groups the *parts* and answers `no record carries the field "status" —
9
+ * available: text, type`, and `--json` emits the answer as a JSON string nested
10
+ * inside `content[0].text`, so every consumer unwraps before anything works.
11
+ *
12
+ * So the CLI unwraps, and only the CLI. The order is the server's own order of
13
+ * preference: `structuredContent` is the answer when the server sent one; a lone
14
+ * text part is the answer when it parses as JSON, and its text when it does not.
15
+ * Anything else — several parts, an image, audio — is passed through whole,
16
+ * because picking one part out of many would be inventing an answer.
17
+ */
18
+ import { AppError } from '../../contract/errors.js';
19
+ export declare function unwrapMcpResult(result: unknown): unknown;
20
+ /**
21
+ * The class a relayed failure carries when the server sent no structured body.
22
+ *
23
+ * Not `INTERNAL_SERVER_ERROR`: nothing of ours broke. The honest statement is
24
+ * that the call failed upstream and we cannot say more than the server did.
25
+ */
26
+ export declare const UPSTREAM_TOOL_ERROR = "UPSTREAM_TOOL_ERROR";
27
+ /**
28
+ * Turn a failed `CallToolResult` into the error the caller can act on.
29
+ *
30
+ * The line this replaces threw a one-sentence `Error` and discarded the result,
31
+ * which cost three things at once: the code (so `exitCodes` had nothing to map
32
+ * and every remote failure exited `1`), the message the operator needed, and the
33
+ * error's own class — a plain `Error` is an *unexpected* error to the runner, so
34
+ * it printed a code frame of the framework bundle before the JSON failure and
35
+ * was then scrubbed to a bare `INTERNAL_SERVER_ERROR`.
36
+ *
37
+ * A structured `{ error, details }` body is the remote contract's own refusal
38
+ * and is relayed as one. Anything else keeps the framework's sentence and
39
+ * carries what the server did send — reporting less than we have is not caution.
40
+ * The status is 502 either way: whatever the code says, the failure happened
41
+ * upstream, and a consumer re-serving it over HTTP should say so.
42
+ */
43
+ export declare function mcpToolFailure(toolName: string, result: unknown): AppError;
44
+ //# sourceMappingURL=mcp-envelope.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mcp-envelope.d.ts","sourceRoot":"","sources":["../../../src/tools/connections/mcp-envelope.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AAIjD,wBAAgB,eAAe,CAAC,MAAM,EAAE,OAAO,GAAG,OAAO,CAaxD;AAED;;;;;GAKG;AACH,eAAO,MAAM,mBAAmB,wBAAwB,CAAC;AAEzD;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,cAAc,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,GAAG,QAAQ,CAa1E"}
@@ -1,5 +1,6 @@
1
1
  import { type RuntimeToolDefinition } from '../runtime-tool.js';
2
+ import { type ConnectionToolSkipReporter } from './runtime.js';
2
3
  import type { McpClientConnection } from './types.js';
3
4
  /** Discover and mount every tool one MCP connection exposes. */
4
- export declare function mountMcpConnection(connection: McpClientConnection, instanceId: string): Promise<RuntimeToolDefinition[]>;
5
+ export declare function mountMcpConnection(connection: McpClientConnection, instanceId: string, onSkipped: ConnectionToolSkipReporter): Promise<RuntimeToolDefinition[]>;
5
6
  //# sourceMappingURL=mcp.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"mcp.d.ts","sourceRoot":"","sources":["../../../src/tools/connections/mcp.ts"],"names":[],"mappings":"AAEA,OAAO,EAAqB,KAAK,qBAAqB,EAAE,MAAM,iBAAiB,CAAC;AAMhF,OAAO,KAAK,EAAE,mBAAmB,EAAiB,MAAM,SAAS,CAAC;AAQlE,gEAAgE;AAChE,wBAAsB,kBAAkB,CACtC,UAAU,EAAE,mBAAmB,EAC/B,UAAU,EAAE,MAAM,GACjB,OAAO,CAAC,qBAAqB,EAAE,CAAC,CAyDlC"}
1
+ {"version":3,"file":"mcp.d.ts","sourceRoot":"","sources":["../../../src/tools/connections/mcp.ts"],"names":[],"mappings":"AAEA,OAAO,EAAqB,KAAK,qBAAqB,EAAE,MAAM,iBAAiB,CAAC;AAIhF,OAAO,EACL,KAAK,0BAA0B,EAIhC,MAAM,WAAW,CAAC;AAGnB,OAAO,KAAK,EAAE,mBAAmB,EAAiB,MAAM,SAAS,CAAC;AAQlE,gEAAgE;AAChE,wBAAsB,kBAAkB,CACtC,UAAU,EAAE,mBAAmB,EAC/B,UAAU,EAAE,MAAM,EAClB,SAAS,EAAE,0BAA0B,GACpC,OAAO,CAAC,qBAAqB,EAAE,CAAC,CAqElC"}
@@ -1 +1 @@
1
- {"version":3,"file":"mount.d.ts","sourceRoot":"","sources":["../../../src/tools/connections/mount.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,iBAAiB,CAAC;AAM7D,OAAO,KAAK,EAAE,oBAAoB,EAAE,sBAAsB,EAAE,MAAM,SAAS,CAAC;AAE5E;;;;;;;GAOG;AACH,wBAAsB,gBAAgB,CACpC,WAAW,EAAE,SAAS,oBAAoB,EAAE,EAC5C,OAAO,GAAE,sBAA2B,GACnC,OAAO,CAAC,SAAS,qBAAqB,EAAE,CAAC,CAsB3C"}
1
+ {"version":3,"file":"mount.d.ts","sourceRoot":"","sources":["../../../src/tools/connections/mount.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,iBAAiB,CAAC;AAO7D,OAAO,KAAK,EAAE,oBAAoB,EAAE,sBAAsB,EAAE,MAAM,SAAS,CAAC;AAE5E;;;;;;;GAOG;AACH,wBAAsB,gBAAgB,CACpC,WAAW,EAAE,SAAS,oBAAoB,EAAE,EAC5C,OAAO,GAAE,sBAA2B,GACnC,OAAO,CAAC,SAAS,qBAAqB,EAAE,CAAC,CAuB3C"}
@@ -1,5 +1,6 @@
1
1
  import { type RuntimeToolDefinition } from '../runtime-tool.js';
2
+ import { type ConnectionToolSkipReporter } from './runtime.js';
2
3
  import type { OpenApiConnection } from './types.js';
3
4
  /** Load and mount every operation of one OpenAPI document. */
4
- export declare function mountOpenApiConnection(connection: OpenApiConnection, instanceId: string): Promise<RuntimeToolDefinition[]>;
5
+ export declare function mountOpenApiConnection(connection: OpenApiConnection, instanceId: string, onSkipped: ConnectionToolSkipReporter): Promise<RuntimeToolDefinition[]>;
5
6
  //# sourceMappingURL=openapi.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"openapi.d.ts","sourceRoot":"","sources":["../../../src/tools/connections/openapi.ts"],"names":[],"mappings":"AAGA,OAAO,EAAqB,KAAK,qBAAqB,EAAE,MAAM,iBAAiB,CAAC;AAchF,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,SAAS,CAAC;AAgBjD,8DAA8D;AAC9D,wBAAsB,sBAAsB,CAC1C,UAAU,EAAE,iBAAiB,EAC7B,UAAU,EAAE,MAAM,GACjB,OAAO,CAAC,qBAAqB,EAAE,CAAC,CAkGlC"}
1
+ {"version":3,"file":"openapi.d.ts","sourceRoot":"","sources":["../../../src/tools/connections/openapi.ts"],"names":[],"mappings":"AAGA,OAAO,EAAqB,KAAK,qBAAqB,EAAE,MAAM,iBAAiB,CAAC;AAWhF,OAAO,EACL,KAAK,0BAA0B,EAKhC,MAAM,WAAW,CAAC;AAGnB,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,SAAS,CAAC;AAgBjD,8DAA8D;AAC9D,wBAAsB,sBAAsB,CAC1C,UAAU,EAAE,iBAAiB,EAC7B,UAAU,EAAE,MAAM,EAClB,SAAS,EAAE,0BAA0B,GACpC,OAAO,CAAC,qBAAqB,EAAE,CAAC,CA4GlC"}
@@ -1,5 +1,6 @@
1
1
  import { type ZodObject } from 'zod';
2
2
  import type { RuntimeContext } from '../../contract/index.js';
3
+ import type { RuntimeToolDefinition } from '../runtime-tool.js';
3
4
  /** A connection's optional principal-scoped credential resolver. */
4
5
  export type ConnectionTokenProvider = (context?: RuntimeContext) => string | undefined | Promise<string | undefined>;
5
6
  interface TeardownTarget {
@@ -25,5 +26,21 @@ export declare function withConnectionToken<T>(params: {
25
26
  export declare function zodObjectFromJsonSchema(schema: Record<string, unknown> | undefined): ZodObject;
26
27
  /** Normalise a tool name into the characters every provider accepts. */
27
28
  export declare function sanitizeToolName(value: string): string;
29
+ /** One discovered tool the mount could not turn into a definition. */
30
+ export interface SkippedConnectionTool {
31
+ connection: string;
32
+ tool: string;
33
+ reason: string;
34
+ }
35
+ export type ConnectionToolSkipReporter = (skipped: SkippedConnectionTool) => void;
36
+ /** Default report: name the tool and the reason, and keep the connection. */
37
+ export declare function reportSkippedConnectionTool(skipped: SkippedConnectionTool): void;
38
+ /**
39
+ * Build one definition per discovered entry, surviving the ones that cannot be
40
+ * built. A mount is all-or-nothing only if it is written that way, and a foreign
41
+ * surface of two hundred tools should not be lost to one unconvertible schema —
42
+ * the refusal is reported and named, which is what a caller can act on.
43
+ */
44
+ export declare function mountToolsTolerantly<TEntry>(entries: readonly TEntry[], connection: string, nameOf: (entry: TEntry) => string, build: (entry: TEntry) => RuntimeToolDefinition, onSkipped: ConnectionToolSkipReporter): RuntimeToolDefinition[];
28
45
  export {};
29
46
  //# sourceMappingURL=runtime.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"runtime.d.ts","sourceRoot":"","sources":["../../../src/tools/connections/runtime.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,SAAS,EAAK,MAAM,KAAK,CAAC;AACxC,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAGrD,oEAAoE;AACpE,MAAM,MAAM,uBAAuB,GAAG,CACpC,OAAO,CAAC,EAAE,cAAc,KACrB,MAAM,GAAG,SAAS,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAAC;AAEtD,UAAU,cAAc;IACtB,QAAQ,IAAI,IAAI,CAAC;CAClB;AAED;;;;GAIG;AACH,wBAAsB,mBAAmB,CAAC,CAAC,EACzC,MAAM,EAAE;IACN,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,uBAAuB,CAAC;IACnC,MAAM,CAAC,EAAE,cAAc,CAAC;IACxB,OAAO,CAAC,EAAE,cAAc,CAAC;CAC1B,EACD,IAAI,EAAE,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,KAAK,OAAO,CAAC,CAAC,CAAC,GAC9C,OAAO,CAAC,CAAC,CAAC,CAUZ;AAED;;;;;GAKG;AACH,wBAAgB,uBAAuB,CACrC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,GAC1C,SAAS,CAKX;AAED,wEAAwE;AACxE,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAEtD"}
1
+ {"version":3,"file":"runtime.d.ts","sourceRoot":"","sources":["../../../src/tools/connections/runtime.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,SAAS,EAAK,MAAM,KAAK,CAAC;AACxC,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAErD,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,iBAAiB,CAAC;AAG7D,oEAAoE;AACpE,MAAM,MAAM,uBAAuB,GAAG,CACpC,OAAO,CAAC,EAAE,cAAc,KACrB,MAAM,GAAG,SAAS,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAAC;AAEtD,UAAU,cAAc;IACtB,QAAQ,IAAI,IAAI,CAAC;CAClB;AAED;;;;GAIG;AACH,wBAAsB,mBAAmB,CAAC,CAAC,EACzC,MAAM,EAAE;IACN,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,uBAAuB,CAAC;IACnC,MAAM,CAAC,EAAE,cAAc,CAAC;IACxB,OAAO,CAAC,EAAE,cAAc,CAAC;CAC1B,EACD,IAAI,EAAE,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,KAAK,OAAO,CAAC,CAAC,CAAC,GAC9C,OAAO,CAAC,CAAC,CAAC,CAUZ;AAED;;;;;GAKG;AACH,wBAAgB,uBAAuB,CACrC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,GAC1C,SAAS,CAKX;AAcD,wEAAwE;AACxE,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAEtD;AAED,sEAAsE;AACtE,MAAM,WAAW,qBAAqB;IACpC,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,MAAM,0BAA0B,GAAG,CAAC,OAAO,EAAE,qBAAqB,KAAK,IAAI,CAAC;AAElF,6EAA6E;AAC7E,wBAAgB,2BAA2B,CAAC,OAAO,EAAE,qBAAqB,GAAG,IAAI,CAIhF;AAED;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAAC,MAAM,EACzC,OAAO,EAAE,SAAS,MAAM,EAAE,EAC1B,UAAU,EAAE,MAAM,EAClB,MAAM,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,EACjC,KAAK,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,qBAAqB,EAC/C,SAAS,EAAE,0BAA0B,GACpC,qBAAqB,EAAE,CAczB"}
@@ -1,4 +1,5 @@
1
- import type { ConnectionTokenProvider } from './runtime.js';
1
+ import type { RuntimeToolTransport } from '../runtime-tool.js';
2
+ import type { ConnectionTokenProvider, ConnectionToolSkipReporter } from './runtime.js';
2
3
  /** Where one external MCP server lives, plus static request headers. */
3
4
  export interface McpConnectionTransport {
4
5
  url: string;
@@ -23,6 +24,14 @@ export interface McpClientConnectionConfig {
23
24
  timeoutMs?: number;
24
25
  /** Response body ceiling in bytes; defaults to 1 MiB. */
25
26
  maxResponseBytes?: number;
27
+ /**
28
+ * Which surfaces this server's discovered tools appear on; default MCP and
29
+ * AGENT. Naming `['CLI']` is how a whole server becomes a set of commands,
30
+ * without the consumer rebuilding each discovered definition — and a
31
+ * connection without it contributes nothing to the CLI, because CLI exposure
32
+ * is explicit everywhere else in the framework too.
33
+ */
34
+ transports?: readonly RuntimeToolTransport[];
26
35
  }
27
36
  /** A defined MCP connection. */
28
37
  export interface McpClientConnection extends McpClientConnectionConfig {
@@ -40,6 +49,8 @@ export interface OpenApiConnectionConfig {
40
49
  timeoutMs?: number;
41
50
  /** Response body ceiling in bytes; defaults to 1 MiB. */
42
51
  maxResponseBytes?: number;
52
+ /** Which surfaces every mounted operation appears on; default MCP and AGENT. */
53
+ transports?: readonly RuntimeToolTransport[];
43
54
  }
44
55
  /** A defined OpenAPI connection. */
45
56
  export interface OpenApiConnection extends OpenApiConnectionConfig {
@@ -56,5 +67,11 @@ export interface ConnectionBudget {
56
67
  /** Shared mount policy for every connection in one call. */
57
68
  export interface ConnectionMountOptions {
58
69
  budget?: ConnectionBudget;
70
+ /**
71
+ * Called for each discovered tool that could not be mounted. Defaults to a
72
+ * stderr line naming the connection, the tool and the reason; the rest of the
73
+ * surface is mounted either way.
74
+ */
75
+ onSkippedTool?: ConnectionToolSkipReporter;
59
76
  }
60
77
  //# sourceMappingURL=types.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/tools/connections/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,WAAW,CAAC;AAEzD,wEAAwE;AACxE,MAAM,WAAW,sBAAsB;IACrC,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAClC;AAED,+EAA+E;AAC/E,MAAM,WAAW,aAAa;IAC5B,KAAK,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAC1B,KAAK,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CAC3B;AAED,+DAA+D;AAC/D,MAAM,WAAW,yBAAyB;IACxC,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,sBAAsB,CAAC;IAClC,KAAK,CAAC,EAAE,aAAa,CAAC;IACtB,KAAK,CAAC,EAAE,uBAAuB,CAAC;IAChC,mFAAmF;IACnF,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,4EAA4E;IAC5E,UAAU,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAC/B,gEAAgE;IAChE,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,yDAAyD;IACzD,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B;AAED,gCAAgC;AAChC,MAAM,WAAW,mBAAoB,SAAQ,yBAAyB;IACpE,IAAI,EAAE,KAAK,CAAC;CACb;AAED,kFAAkF;AAClF,MAAM,WAAW,uBAAuB;IACtC,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC;IACtB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,uBAAuB,CAAC;IAChC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,UAAU,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAC/B,gEAAgE;IAChE,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,yDAAyD;IACzD,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B;AAED,oCAAoC;AACpC,MAAM,WAAW,iBAAkB,SAAQ,uBAAuB;IAChE,IAAI,EAAE,SAAS,CAAC;CACjB;AAED,mEAAmE;AACnE,MAAM,MAAM,oBAAoB,GAAG,mBAAmB,GAAG,iBAAiB,CAAC;AAE3E,wEAAwE;AACxE,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,6EAA6E;IAC7E,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED,4DAA4D;AAC5D,MAAM,WAAW,sBAAsB;IACrC,MAAM,CAAC,EAAE,gBAAgB,CAAC;CAC3B"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/tools/connections/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,iBAAiB,CAAC;AAC5D,OAAO,KAAK,EAAE,uBAAuB,EAAE,0BAA0B,EAAE,MAAM,WAAW,CAAC;AAErF,wEAAwE;AACxE,MAAM,WAAW,sBAAsB;IACrC,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAClC;AAED,+EAA+E;AAC/E,MAAM,WAAW,aAAa;IAC5B,KAAK,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAC1B,KAAK,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CAC3B;AAED,+DAA+D;AAC/D,MAAM,WAAW,yBAAyB;IACxC,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,sBAAsB,CAAC;IAClC,KAAK,CAAC,EAAE,aAAa,CAAC;IACtB,KAAK,CAAC,EAAE,uBAAuB,CAAC;IAChC,mFAAmF;IACnF,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,4EAA4E;IAC5E,UAAU,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAC/B,gEAAgE;IAChE,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,yDAAyD;IACzD,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B;;;;;;OAMG;IACH,UAAU,CAAC,EAAE,SAAS,oBAAoB,EAAE,CAAC;CAC9C;AAED,gCAAgC;AAChC,MAAM,WAAW,mBAAoB,SAAQ,yBAAyB;IACpE,IAAI,EAAE,KAAK,CAAC;CACb;AAED,kFAAkF;AAClF,MAAM,WAAW,uBAAuB;IACtC,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC;IACtB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,uBAAuB,CAAC;IAChC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,UAAU,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAC/B,gEAAgE;IAChE,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,yDAAyD;IACzD,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,gFAAgF;IAChF,UAAU,CAAC,EAAE,SAAS,oBAAoB,EAAE,CAAC;CAC9C;AAED,oCAAoC;AACpC,MAAM,WAAW,iBAAkB,SAAQ,uBAAuB;IAChE,IAAI,EAAE,SAAS,CAAC;CACjB;AAED,mEAAmE;AACnE,MAAM,MAAM,oBAAoB,GAAG,mBAAmB,GAAG,iBAAiB,CAAC;AAE3E,wEAAwE;AACxE,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,6EAA6E;IAC7E,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED,4DAA4D;AAC5D,MAAM,WAAW,sBAAsB;IACrC,MAAM,CAAC,EAAE,gBAAgB,CAAC;IAC1B;;;;OAIG;IACH,aAAa,CAAC,EAAE,0BAA0B,CAAC;CAC5C"}
@@ -0,0 +1,17 @@
1
+ /** True when a document says, in its own `$schema`, that it is draft-07. */
2
+ export declare function declaresDraft07(dialect: unknown): boolean;
3
+ /**
4
+ * Move a document's top-level `definitions` to `$defs`, carrying its pointers.
5
+ *
6
+ * A JSON Schema document that names one dialect and uses another's keyword is
7
+ * not a style question: a reader registers reusable subschemas from the keyword
8
+ * the dialect declares and then cannot resolve `#/definitions/x` at all. Both
9
+ * halves of this framework met that document — the MCP SDK stamps 2020-12 onto
10
+ * the metadata it is handed, and our own MCP client obeyed the stamp.
11
+ *
12
+ * Only the top level moves. A `definitions` deeper in the document may be a
13
+ * property literally called "definitions", and guessing which is worse than
14
+ * leaving it where the author put it.
15
+ */
16
+ export declare function withDefsDialect(schema: Record<string, unknown>): Record<string, unknown>;
17
+ //# sourceMappingURL=json-schema-dialect.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"json-schema-dialect.d.ts","sourceRoot":"","sources":["../../src/tools/json-schema-dialect.ts"],"names":[],"mappings":"AAQA,4EAA4E;AAC5E,wBAAgB,eAAe,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAEzD;AAeD;;;;;;;;;;;;GAYG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAUxF"}
@@ -10,6 +10,13 @@ export interface ToolPresentationConfig {
10
10
  /** Build the one model-facing document shared by MCP, agents and manifests. */
11
11
  export declare function buildToolPresentationSchema(config: ToolPresentationConfig): ToolPresentationSchema;
12
12
  export declare function isObjectPresentationSchema(schema: ToolPresentationSchema): boolean;
13
- /** Metadata passed to the Zod identity carrier; the SDK supplies its own dialect. */
13
+ /**
14
+ * Metadata passed to the Zod identity carrier.
15
+ *
16
+ * The SDK supplies its own dialect and it is 2020-12, so the document has to
17
+ * speak 2020-12: a draft-07 `definitions` block under a 2020-12 `$schema` is a
18
+ * document that says one thing and does another, and a client that believes the
19
+ * stamp cannot resolve a single `#/definitions/...` pointer in it.
20
+ */
14
21
  export declare function presentationMetadata(schema: ToolPresentationSchema): ToolPresentationSchema;
15
22
  //# sourceMappingURL=presentation.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"presentation.d.ts","sourceRoot":"","sources":["../../src/tools/presentation.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,EAGL,KAAK,sBAAsB,EAC5B,MAAM,WAAW,CAAC;AAGnB,MAAM,WAAW,sBAAsB;IACrC,YAAY,CAAC,EAAE,CAAC,CAAC,OAAO,CAAC;IACzB,WAAW,CAAC,EAAE,CAAC,CAAC,OAAO,CAAC;IACxB,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC,OAAO,CAAC,CAAC;IACzC,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B,eAAe,CAAC,EAAE,OAAO,GAAG,KAAK,CAAC;CACnC;AA+FD,+EAA+E;AAC/E,wBAAgB,2BAA2B,CACzC,MAAM,EAAE,sBAAsB,GAC7B,sBAAsB,CAgDxB;AAED,wBAAgB,0BAA0B,CAAC,MAAM,EAAE,sBAAsB,GAAG,OAAO,CAElF;AAED,qFAAqF;AACrF,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,sBAAsB,GAAG,sBAAsB,CAE3F"}
1
+ {"version":3,"file":"presentation.d.ts","sourceRoot":"","sources":["../../src/tools/presentation.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,EAGL,KAAK,sBAAsB,EAC5B,MAAM,WAAW,CAAC;AAInB,MAAM,WAAW,sBAAsB;IACrC,YAAY,CAAC,EAAE,CAAC,CAAC,OAAO,CAAC;IACzB,WAAW,CAAC,EAAE,CAAC,CAAC,OAAO,CAAC;IACxB,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC,OAAO,CAAC,CAAC;IACzC,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B,eAAe,CAAC,EAAE,OAAO,GAAG,KAAK,CAAC;CACnC;AA+KD,+EAA+E;AAC/E,wBAAgB,2BAA2B,CACzC,MAAM,EAAE,sBAAsB,GAC7B,sBAAsB,CAgDxB;AAED,wBAAgB,0BAA0B,CAAC,MAAM,EAAE,sBAAsB,GAAG,OAAO,CAElF;AAED;;;;;;;GAOG;AACH,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,sBAAsB,GAAG,sBAAsB,CAE3F"}
package/dist/tools.js CHANGED
@@ -21,7 +21,7 @@ import {
21
21
  buildToolManifest,
22
22
  describeToolCatalog,
23
23
  mountAgent
24
- } from "./index-gbqjt8jz.js";
24
+ } from "./index-83fafqw8.js";
25
25
  import"./index-7rkhw9ec.js";
26
26
  import {
27
27
  argumentsDigest
@@ -37,7 +37,7 @@ import {
37
37
  } from "./index-4fpa9dy2.js";
38
38
  import {
39
39
  createToolInvoker
40
- } from "./index-79hb1wyh.js";
40
+ } from "./index-tgh3ksfh.js";
41
41
  import {
42
42
  WaitTimeoutError,
43
43
  createCli,
@@ -46,22 +46,22 @@ import {
46
46
  fetchPinnedDocument,
47
47
  readCapped,
48
48
  runWaitOperation
49
- } from "./index-1923shw9.js";
49
+ } from "./index-fqg5mfk7.js";
50
50
  import"./index-sbdmyz75.js";
51
51
  import {
52
52
  collectToolSurface
53
- } from "./index-wb15909q.js";
53
+ } from "./index-zsdgd1tz.js";
54
54
  import {
55
55
  collectTools,
56
56
  createToolRunner,
57
57
  formatToolError
58
- } from "./index-2hrfpw2c.js";
58
+ } from "./index-1bnkzq95.js";
59
59
  import {
60
60
  ToolExecutionControlError,
61
61
  executeToolMethod,
62
62
  isToolExecutionControlError,
63
63
  toolResultFromError
64
- } from "./index-44ht2790.js";
64
+ } from "./index-8pjqv3zh.js";
65
65
  import {
66
66
  getRequestContext,
67
67
  getTraceId,
@@ -78,21 +78,16 @@ import {
78
78
  ManagedFilePathSchema,
79
79
  ManagedFileRefSchema
80
80
  } from "./index-6k1937bx.js";
81
- import {
82
- AppError,
83
- STITCH_ERROR_STATUS
84
- } from "./index-scs3f1eg.js";
85
81
  import {
86
82
  resolvePropagationContext
87
83
  } from "./index-zcgf3gqf.js";
88
84
  import {
89
85
  coerceJsonArgs
90
86
  } from "./index-7zbps32p.js";
91
- import"./index-kzfs85xp.js";
92
87
  import {
93
88
  createRuntimeToolFactory,
94
89
  defineRuntimeTool
95
- } from "./index-kc6h6hg0.js";
90
+ } from "./index-8z9we758.js";
96
91
  import {
97
92
  PORTABLE_JSON_SCHEMA_FORMATS,
98
93
  assertToolName,
@@ -103,8 +98,13 @@ import {
103
98
  prepareProjectedMcpTools,
104
99
  presentationMetadata,
105
100
  validateMcpRoundPolicy
106
- } from "./index-r159gjwy.js";
101
+ } from "./index-fenaekmk.js";
107
102
  import"./index-cby4ar3v.js";
103
+ import {
104
+ AppError,
105
+ STITCH_ERROR_STATUS
106
+ } from "./index-scs3f1eg.js";
107
+ import"./index-kzfs85xp.js";
108
108
  import {
109
109
  isRecord
110
110
  } from "./index-77fekveh.js";
package/dist/tracking.js CHANGED
@@ -5,8 +5,8 @@ import {
5
5
  defineContract
6
6
  } from "./index-kazec06k.js";
7
7
  import"./index-6k1937bx.js";
8
- import"./index-scs3f1eg.js";
9
8
  import"./index-zcgf3gqf.js";
9
+ import"./index-scs3f1eg.js";
10
10
  import"./index-kzfs85xp.js";
11
11
  import"./index-77fekveh.js";
12
12
 
package/llms-full.txt CHANGED
@@ -7489,6 +7489,13 @@ same Zod schema an HTTP or MCP call does.
7489
7489
  | `--quiet` | Suppress non-essential stderr output |
7490
7490
  | `--dry-run` | Print the resolved call without executing |
7491
7491
  | `--help`, `-h` | Usage — top-level or per-command flag table |
7492
+ | `--help <text>` | List only the commands matching a substring |
7493
+ | `--count-by <field>` | Count records per distinct value — see [Aggregate views](#aggregate-views) |
7494
+ | `--sum <f> [--by <g>]`| Total a numeric field, optionally grouped |
7495
+ | `--sort <field>` | Order records by a field, largest first |
7496
+ | `--ascending` | Flip `--sort` to smallest first |
7497
+ | `--top <n>` | Keep the n leading entries of the view asked for |
7498
+ | `--table <a,b>` | Render named fields as an aligned table |
7492
7499
 
7493
7500
  stdout carries the result; structured errors and progress go to stderr. With
7494
7501
  `--json`, a success or structured failure is exactly one compact,
@@ -7498,6 +7505,34 @@ diagnostics remain ordinary stderr text. This keeps stdout pipeable and
7498
7505
  `VALIDATION_ERROR → 1`, `UNAUTHORIZED → 2`, `FORBIDDEN → 3`, `NOT_FOUND → 4`,
7499
7506
  …) — override per app with `exitCodes`.
7500
7507
 
7508
+ ### A narrower question than "all of them"
7509
+
7510
+ On a discovered surface `--help` is the only way to learn what exists, and that
7511
+ can be two hundred commands. At that size the list stops being an answer: it
7512
+ scrolls past a person and costs an agent the same context an unfiltered result
7513
+ would. So there is a question between "one command" and "all of them":
7514
+
7515
+ ```bash
7516
+ myapp --help broadcast # also: -h broadcast · help broadcast · --help=broadcast
7517
+ ```
7518
+
7519
+ ```
7520
+ Commands matching "broadcast" (3 of 205):
7521
+ broadcast_send Send a broadcast to every subscriber
7522
+ broadcast_cancel Stop a running broadcast
7523
+ announce_publish Publish an announcement as a broadcast
7524
+ ```
7525
+
7526
+ The description is searched as well as the name, because the word someone knows
7527
+ is often in the sentence rather than the name — `announce_publish` above is
7528
+ matched that way. The count says what was left out.
7529
+
7530
+ **No match is an exit code**, not an empty success: `0` over an empty list reads
7531
+ as "there are none", which is a different statement from "none of these". It
7532
+ exits with whatever `NOT_FOUND` maps to (`4` by default). Bare `--help` is
7533
+ unchanged, and `--help=false` still means what it always did — the reserved
7534
+ boolean's negation — so one value never carries two meanings.
7535
+
7501
7536
  ## Application global options
7502
7537
 
7503
7538
  `--json` and friends above are the framework's. An application usually has
@@ -7603,6 +7638,211 @@ myapp generate "a fox" --wait --output-dir ./out
7603
7638
  myapp generate "a fox" --wait --json > result.json &
7604
7639
  ```
7605
7640
 
7641
+ ## Aggregate views
7642
+
7643
+ The CLI's audience is agents, scripts and `jq`, so output is JSON. That settles
7644
+ the *encoding*; it does not settle whether the answer to "how many items per
7645
+ status" should be every item. Measured on a live server, one ordinary question:
7646
+
7647
+ | call | characters returned |
7648
+ |---|---|
7649
+ | the listing (98 records) | 34 750 |
7650
+ | `--count-by status` | ~90 |
7651
+
7652
+ An agent pays for every one of those characters in its context window, and `|
7653
+ jq` does not help: the bytes have been read into the conversation by the time
7654
+ `jq` sees them. So the aggregate is computed on the result, before anything is
7655
+ written.
7656
+
7657
+ ```bash
7658
+ myapp item_list --count-by status # { "active": 33, "idle": 33, "stopped": 32 }
7659
+ myapp item_list --count-by status --top 2 # the two largest groups
7660
+ myapp item_list --sum messages # 4753
7661
+ myapp item_list --sum messages --by status # one total per status
7662
+ myapp item_list --top 5 --by status # same view, written the other way round
7663
+ myapp item_list --table id,status # the one human-facing shape
7664
+ ```
7665
+
7666
+ Two words, one each: **`--by` groups, `--sort` orders.** That leaves `--top` a
7667
+ single meaning everywhere — *the n leading entries of the view you asked for* —
7668
+ so the question a CLI actually gets asked composes out of the parts:
7669
+
7670
+ ```bash
7671
+ myapp item_list --top 5 --sort messages --table id,messages # the five biggest, as a table
7672
+ myapp item_list --sort messages --top 5 --json # the same five, as records
7673
+ myapp item_list --sort name --ascending # ordered the other way
7674
+ ```
7675
+
7676
+ Groups come back largest first for the same reason. A record that carries no
7677
+ value for the sort field sorts **last in both directions**: it is not the
7678
+ smallest, it is not on the scale at all, and letting it lead an ascending list
7679
+ would answer a question nobody asked.
7680
+
7681
+ Three rules worth knowing before you rely on them:
7682
+
7683
+ - **A field the result does not carry is an argument error.** A group of zero
7684
+ over a misspelled field is indistinguishable from a true empty answer, and the
7685
+ caller reads it as data. The message names the fields that *are* there.
7686
+ - **An aggregate needs a collection** — the result itself when it is an array,
7687
+ or the single array field of a result object. An aggregate over a scalar, or
7688
+ over an object with two array fields, is refused rather than guessed.
7689
+ - **Without a view flag the output is byte-for-byte what it was.** The flags are
7690
+ reserved CLI behaviour like `--json`; they never reach a tool argument.
7691
+ - **Ordering and grouping do not mix.** `--sort` with `--by`, `--count-by` or
7692
+ `--sum` is refused rather than given a second meaning.
7693
+
7694
+ A failed call still reports its own error and exit code. An aggregate over an
7695
+ error is not an answer to the question that was asked.
7696
+
7697
+ ## Named profiles
7698
+
7699
+ A CLI that talks to a deployed server needs an address and a credential per
7700
+ environment, and the way a person picks one is a name: `--profile prod`.
7701
+ `globalOptions` gives the flag a home and `resolveAuth(globals)` gives it a
7702
+ resolution point. The rule that makes the mechanism safe is easy to write the
7703
+ wrong way round, because the unsafe version reads as kindness:
7704
+
7705
+ > the named profile does not exist, but exactly one profile is configured — use it.
7706
+
7707
+ That is correct exactly while a single profile exists. The day a second appears
7708
+ it is a command run against the wrong deployment, with nothing in the output to
7709
+ say so. **A profile named explicitly and not found is a refusal, never a
7710
+ substitution.** Substitution survives only where it cannot be wrong: no name was
7711
+ given at all and exactly one profile exists — and even then it is announced on
7712
+ stderr. The distinction has to be drawn at resolution; one step later, "prod" and
7713
+ "prod by default" are the same string.
7714
+
7715
+ `createCliProfileStore` is that rule, plus the twenty lines every consumer of
7716
+ this shape writes:
7717
+
7718
+ ```ts
7719
+ import { createCliProfileStore } from 'stitchkit/cli'
7720
+ import { homedir } from 'node:os'
7721
+ import { join } from 'node:path'
7722
+ import { z } from 'zod'
7723
+
7724
+ const profiles = createCliProfileStore({
7725
+ directory: join(homedir(), '.config/myapp/profiles'),
7726
+ schema: z.object({ url: z.url(), token: z.string().min(1) }),
7727
+ createHint: (name, path) => `write ${path} with {"url","token"} for "${name}"`,
7728
+ })
7729
+
7730
+ await createCli({
7731
+ name: 'myapp',
7732
+ version,
7733
+ globalOptions: z.object({ profile: z.string().optional() }),
7734
+ resolveAuth: (globals) => profiles.resolve(globals.profile).value,
7735
+ services,
7736
+ })
7737
+ ```
7738
+
7739
+ Files are written `0600` in a `0700` directory — and a profile file other users
7740
+ can read is refused with the `chmod` that fixes it, because it holds a
7741
+ credential.
7742
+
7743
+ ## Distribution and self-update
7744
+
7745
+ `createCli` ships no executable, and that is right — but the step after the
7746
+ executable is not application logic either. It is the same problem for every
7747
+ consumer, with the same three traps:
7748
+
7749
+ 1. **The installer cannot parse the manifest.** A `curl … | sh` runs on a
7750
+ machine where nothing is installed yet, including `jq`. So the installer is
7751
+ generated *from* the manifest, server-side, with the URL and digest already
7752
+ substituted — it parses no JSON at all.
7753
+ 2. **Replacing a running binary is a rename, not a write.** Anything else can
7754
+ leave a half-written executable on someone's PATH when the connection drops.
7755
+ 3. **The digest covers the decompressed bytes** — the file that will actually be
7756
+ executed, not the archive that was transferred.
7757
+
7758
+ The framework owns the manifest shape, the installer generation and the update
7759
+ primitive. The application owns where the assets live, which platforms it
7760
+ publishes and who may download them.
7761
+
7762
+ ```ts
7763
+ import {
7764
+ CliBuildManifestSchema, assertCliPublishable, renderCliInstaller,
7765
+ selectCliBuildAsset, checkCliUpdate, applyCliUpdate,
7766
+ } from 'stitchkit/cli'
7767
+
7768
+ // Publishing: refuse to republish one version from a different commit —
7769
+ // otherwise everyone who already installed it never receives the fix.
7770
+ assertCliPublishable(previous, next)
7771
+
7772
+ // Serving: omit `asset` and one script covers every published target, selecting
7773
+ // by uname at run time — otherwise that dispatch is the last hand-written piece
7774
+ // of the install path, and every publisher writes the same x86_64 → x64 table.
7775
+ renderCliInstaller({ manifest, binaryName: 'myapp' })
7776
+ // Or pin one target explicitly:
7777
+ renderCliInstaller({ manifest, asset: selectCliBuildAsset(manifest, target), binaryName: 'myapp' })
7778
+
7779
+ // Checking: bounded, at most once per interval, silent on any failure.
7780
+ const check = await checkCliUpdate({ manifestUrl, currentVersion, lastCheckedAt })
7781
+ if (check.status === 'outdated' && check.asset) {
7782
+ // Replacing is always an explicit command, never a side effect of a check.
7783
+ await applyCliUpdate({ asset: check.asset })
7784
+ }
7785
+ ```
7786
+
7787
+ `checkCliUpdate` has **four** answers, not three: `skipped`, `current`,
7788
+ `outdated` and `unknown`. "Could not ask" is not "up to date" — collapsing them
7789
+ is how a tool goes quiet about its own staleness for months. It never throws,
7790
+ and a command still exits with the code it earned.
7791
+
7792
+ Carry the build stamp inside the binary (`CliBuildStampSchema`,
7793
+ `formatCliBuildStamp`) so the tool can say what it is rather than leaving the
7794
+ reader to infer it from behaviour.
7795
+
7796
+ ## Commands discovered from a running server
7797
+
7798
+ A CLI compiled from contracts carries the surface of the build it was compiled
7799
+ from. One built from discovery carries the surface the server has *right now* —
7800
+ which matters, because a long-lived MCP client freezes schemas at connect time
7801
+ and then refuses the server's own newer fields:
7802
+
7803
+ ```ts
7804
+ const discovered = await mountConnections([
7805
+ defineMcpClientConnection({
7806
+ name: 'api',
7807
+ transport: { url },
7808
+ token: () => key,
7809
+ transports: ['CLI'], // the opt-in: this server's tools are commands
7810
+ }),
7811
+ ])
7812
+ await createCli({ name: 'myapp', version, runtimeTools: discovered, commands: [...] })
7813
+ ```
7814
+
7815
+ `transports` is where the opt-in belongs: a whole server becomes a set of
7816
+ commands, and a connection without it still contributes nothing to the CLI —
7817
+ exposure stays explicit, as it is everywhere else in the framework.
7818
+
7819
+ One unconvertible schema no longer takes the connection down with it. The tool
7820
+ is skipped and **named** (`onSkippedTool`, or a stderr line by default), so a
7821
+ surface of two hundred tools is not lost to one.
7822
+
7823
+ **A remote refusal keeps its code.** A failed `tools/call` used to become a
7824
+ one-sentence `Error` with the result discarded, which cost three things at once:
7825
+ the code (so `exitCodes` had nothing to map and every remote failure exited `1`),
7826
+ the message the operator needed, and the error's own class — a plain `Error` is
7827
+ an *unexpected* error to the runner, so it printed a code frame of the framework
7828
+ bundle before the JSON failure and was then scrubbed to `INTERNAL_SERVER_ERROR`.
7829
+ A structured `{ error, details }` body is now relayed as the contract error it
7830
+ is, on every transport; anything else fails as `UPSTREAM_TOOL_ERROR` carrying
7831
+ what the server did send. Not `INTERNAL_SERVER_ERROR`, because nothing of ours
7832
+ broke.
7833
+
7834
+ **On the CLI a discovered command prints the answer, not the envelope.**
7835
+ `tools/call` returns `{ content: [...], structuredContent? }`, and an agent mount
7836
+ needs exactly that — the parts are what a model is shown. The CLI is different in
7837
+ kind, because the handler's value is what gets printed, piped and aggregated:
7838
+ handed the envelope, `--count-by status` groups the *content parts* and answers
7839
+ `no record carries the field "status" — available: text, type`. So the CLI
7840
+ transport unwraps, and only it: `structuredContent` when the server sent one, a
7841
+ lone text part when it parses as JSON, its text when it does not. Several parts,
7842
+ an image or audio pass through whole — picking one of many would be inventing an
7843
+ answer.
7844
+
7845
+
7606
7846
  ## Auth parity
7607
7847
 
7608
7848
  A scoped command is guarded by the same `createAuthHook` your HTTP server uses —
@@ -16710,13 +16950,16 @@ and approval path.
16710
16950
  | `ConnectionTokenProvider` | _type_ | `() => string \| undefined \| Promise<string \| undefined>` — the lazily resolved credential |
16711
16951
  | `ConnectionBudget` | _type_ | `{ maxTools? }` ceiling on one `mountConnections` call |
16712
16952
  | `ConnectionDefinition` | _type_ | either a defined MCP client or OpenAPI connection |
16713
- | `ConnectionMountOptions` | _type_ | shared `{ lifecycle?, budget? }` mount policy |
16953
+ | `ConnectionMountOptions` | _type_ | shared `{ lifecycle?, budget?, onSkippedTool? }` mount policy |
16954
+ | `ConnectionToolSkipReporter` | _type_ | `(skipped) => void` — called per discovered tool the mount could not build; defaults to a stderr line, and the rest of the surface still mounts |
16955
+ | `SkippedConnectionTool` | _type_ | `{ connection, tool, reason }` — which tool was not mounted, and why |
16956
+ | `RuntimeToolTransport` | _type_ | `'MCP' \| 'AGENT' \| 'CLI'` — the surfaces a connection's `transports` may name |
16714
16957
  | `McpClientConnection` | _type_ | a defined MCP client connection |
16715
- | `McpClientConnectionConfig` | _type_ | name, transport, tool filter, token provider, instance key and allowed hosts |
16958
+ | `McpClientConnectionConfig` | _type_ | name, transport, tool filter, token provider, instance key, allowed hosts and `transports` — naming `['CLI']` makes the whole server's discovered tools commands, with no per-definition rewriting |
16716
16959
  | `McpConnectionTransport` | _type_ | `{ url, headers? }` for one MCP endpoint |
16717
16960
  | `McpToolFilter` | _type_ | `{ allow?, block? }` discovered-tool filter |
16718
16961
  | `OpenApiConnection` | _type_ | a defined OpenAPI connection |
16719
- | `OpenApiConnectionConfig` | _type_ | name, spec, base URL, token provider, instance key and allowed hosts |
16962
+ | `OpenApiConnectionConfig` | _type_ | name, spec, base URL, token provider, instance key, allowed hosts and `transports` |
16720
16963
 
16721
16964
  ---
16722
16965
 
@@ -17032,6 +17275,33 @@ SDK nor the `ai` peer.
17032
17275
  | `createCli` | function | build and run a CLI from contracts — [guide](../guide/cli.md) |
17033
17276
  | `defineCliCommand` | function | define one Zod-typed CLI-only executable command with optional validated-result presentation/exit policy |
17034
17277
  | `parseCliArgs` | function | argv → typed tool args against a schema (advanced) |
17278
+ | `routeCliArgv` | function | select the command out of argv without duplicating the global-option grammar (advanced) |
17279
+ | `extractCliGlobalOptions` | function | lift the application's own global options out of argv before routing (advanced) |
17280
+ | `coerceJsonArgs` | function | the second half of `parseCliArgs` — parse array/object values a consumer sends itself, without the `stitchkit/tools` barrel |
17281
+ | `CliArgumentError` | class | the refusal `parseCliArgs` and the view flags raise; a CLI reports it and exits `2` |
17282
+ | `renderCliView` | function | compute an aggregate over a result (`--count-by`, `--sum`, `--top`, `--table`) — [guide](../guide/cli.md#aggregate-views) |
17283
+ | `createCliProfileStore` | function | named `0600` credential profiles resolved by name, with the never-substitute rule built in — [guide](../guide/cli.md#named-profiles) |
17284
+ | `CliProfileError` | class | an `AppError` refusing a named profile that is missing, unreadable or shared between users |
17285
+ | `renderCliInstaller` | function | generate the one-line installer from a build manifest, with the URL and digest substituted — [guide](../guide/cli.md#distribution-and-self-update) |
17286
+ | `checkCliUpdate` | function | bounded, interval-limited, never-throwing check for a newer published build |
17287
+ | `applyCliUpdate` | function | download, verify the decompressed digest and replace the binary by rename |
17288
+ | `assertCliPublishable` | function | refuse republishing one version from a different commit |
17289
+ | `selectCliBuildAsset` | function | the asset for one target, or `undefined` |
17290
+ | `currentCliBuildTarget` | function | `{ platform, arch }` of the running process |
17291
+ | `formatCliBuildStamp` | function | one line saying what the running build is |
17292
+ | `compareCliVersions` | function | compare two versions, or `undefined` when they are not comparable |
17293
+ | `CliBuildManifestSchema` / `CliBuildManifest` | schema / _type_ | name, version, commit, build time and assets |
17294
+ | `CliBuildAssetSchema` / `CliBuildAsset` | schema / _type_ | one download; `size` and `sha256` describe the **decompressed** bytes |
17295
+ | `CliBuildTargetSchema` / `CliBuildTarget` | schema / _type_ | `{ platform, arch }` |
17296
+ | `CliBuildStampSchema` / `CliBuildStamp` | schema / _type_ | the version/commit/build time carried inside a binary |
17297
+ | `CliInstallerConfig` | _type_ | manifest, binary name, default install directory, and an optional `asset` — omit it for one script that selects the target by `uname` |
17298
+ | `CliUpdateCheckConfig` / `CliUpdateCheck` | _type_ | check inputs, and its four answers — `skipped`, `current`, `outdated`, `unknown` |
17299
+ | `CliUpdateApplyConfig` / `AppliedCliUpdate` | _type_ | apply inputs and the replaced path, byte count and digest |
17300
+ | `CliProfileStore` / `CliProfileStoreConfig` / `ResolvedCliProfile` | _type_ | the profile store, its directory/schema/hint config, and one resolution |
17301
+ | `CliResultView` | _type_ | the requested view — `count`, `sum`, or `records` (ordered and/or table-rendered) |
17302
+ | `CliViewOutput` | _type_ | a JSON value, or the one human-facing text shape |
17303
+ | `CliGlobalOptionsParse` | _type_ | `{ argv, globals }` returned by `extractCliGlobalOptions` |
17304
+ | `CliArgvRoute` | _type_ | `{ command, commandArgv, topLevelHelp, version, error? }` returned by `routeCliArgv` |
17035
17305
  | `pollUntilDone` | function | the generic `--wait` poller (advanced) |
17036
17306
  | `emitResult` | function | write a pretty or compact `ToolResult` record to stdout/stderr + exit code (advanced) |
17037
17307
  | `DEFAULT_EXIT_CODES` | const | the default `ToolResult.code` → exit-code map |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "stitchkit",
3
- "version": "0.90.3",
3
+ "version": "0.90.5",
4
4
  "description": "Contract-first backend framework — one defineContract() into an HTTP API, MCP tools, AI-agent tools and a typed client. Bun and Node.",
5
5
  "keywords": [
6
6
  "bun",