toolcraft 0.0.100 → 0.0.102

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 (36) hide show
  1. package/composition.json +6 -1
  2. package/dist/cli.d.ts +2 -0
  3. package/dist/cli.js +85 -18
  4. package/dist/composition.json +6 -1
  5. package/node_modules/toolcraft-design/dist/explorer/jobs.d.ts +1 -0
  6. package/node_modules/toolcraft-design/dist/explorer/jobs.js +28 -0
  7. package/node_modules/toolcraft-design/dist/explorer/render/detail.js +1 -1
  8. package/node_modules/toolcraft-design/dist/explorer/render/list.js +1 -1
  9. package/node_modules/toolcraft-schema/LICENSE +21 -0
  10. package/node_modules/toolcraft-schema/README.md +89 -0
  11. package/node_modules/toolcraft-schema/dist/index.compile-check.d.ts +1 -0
  12. package/node_modules/toolcraft-schema/dist/index.compile-check.js +17 -0
  13. package/node_modules/toolcraft-schema/dist/index.d.ts +182 -0
  14. package/node_modules/toolcraft-schema/dist/index.js +294 -0
  15. package/node_modules/toolcraft-schema/dist/json-schema-document.d.ts +14 -0
  16. package/node_modules/toolcraft-schema/dist/json-schema-document.js +17 -0
  17. package/node_modules/toolcraft-schema/dist/json.compile-check.d.ts +1 -0
  18. package/node_modules/toolcraft-schema/dist/json.compile-check.js +2 -0
  19. package/node_modules/toolcraft-schema/dist/json.d.ts +10 -0
  20. package/node_modules/toolcraft-schema/dist/json.js +5 -0
  21. package/node_modules/toolcraft-schema/dist/oneof.compile-check.d.ts +1 -0
  22. package/node_modules/toolcraft-schema/dist/oneof.compile-check.js +12 -0
  23. package/node_modules/toolcraft-schema/dist/oneof.d.ts +15 -0
  24. package/node_modules/toolcraft-schema/dist/oneof.js +18 -0
  25. package/node_modules/toolcraft-schema/dist/record.compile-check.d.ts +1 -0
  26. package/node_modules/toolcraft-schema/dist/record.compile-check.js +2 -0
  27. package/node_modules/toolcraft-schema/dist/record.d.ts +5 -0
  28. package/node_modules/toolcraft-schema/dist/record.js +6 -0
  29. package/node_modules/toolcraft-schema/dist/union.compile-check.d.ts +1 -0
  30. package/node_modules/toolcraft-schema/dist/union.compile-check.js +9 -0
  31. package/node_modules/toolcraft-schema/dist/union.d.ts +8 -0
  32. package/node_modules/toolcraft-schema/dist/union.js +45 -0
  33. package/node_modules/toolcraft-schema/dist/validate.d.ts +16 -0
  34. package/node_modules/toolcraft-schema/dist/validate.js +379 -0
  35. package/node_modules/toolcraft-schema/package.json +32 -0
  36. package/package.json +5 -4
package/composition.json CHANGED
@@ -48,13 +48,18 @@
48
48
  },
49
49
  {
50
50
  "name": "toolcraft",
51
- "version": "0.0.100",
51
+ "version": "0.0.102",
52
52
  "license": "MIT"
53
53
  },
54
54
  {
55
55
  "name": "toolcraft-design",
56
56
  "version": "0.0.2",
57
57
  "license": "MIT"
58
+ },
59
+ {
60
+ "name": "toolcraft-schema",
61
+ "version": "0.0.102",
62
+ "license": "MIT"
58
63
  }
59
64
  ]
60
65
  }
package/dist/cli.d.ts CHANGED
@@ -27,6 +27,8 @@ export interface RunCLIOptions<TServices extends object = Record<string, unknown
27
27
  logLevel?: LogLevel;
28
28
  logger?: RuntimeLoggerInput;
29
29
  outputEmitter?: (entry: string) => void;
30
+ promptInput?: NodeJS.ReadableStream;
31
+ promptOutput?: NodeJS.WritableStream;
30
32
  projectRoot?: string;
31
33
  rootDisplayName?: string;
32
34
  rootUsageName?: string;
package/dist/cli.js CHANGED
@@ -2,6 +2,7 @@ import "./node-require-shim.js";
2
2
  import { readFile } from "node:fs/promises";
3
3
  import path from "node:path";
4
4
  import { Command as CommanderCommand, CommanderError, InvalidArgumentError, Option } from "commander";
5
+ import { validate as validateSchema } from "toolcraft-schema";
5
6
  import { cancel, configureTheme, confirm, createLogger, formatCommandList, formatOptionList, getTheme, helpFormatterPlain, isCancel, note, promptText, renderTable, resetOutputFormatCache, select, text } from "toolcraft-design";
6
7
  import { ApprovalDeclinedError, UserError, assertCommandRequirements, getCommandSourcePath, hasMcpProxyConfig, resolveCommandSecrets } from "./index.js";
7
8
  import { hasOwnErrorCode } from "./error-codes.js";
@@ -1761,7 +1762,18 @@ function enumOptionLabel(schema, value) {
1761
1762
  }
1762
1763
  return schema.labels[key] ?? key;
1763
1764
  }
1764
- async function promptForField(field) {
1765
+ function withPromptStreams(options, streams) {
1766
+ return {
1767
+ ...options,
1768
+ ...(streams.input === undefined ? {} : { input: streams.input }),
1769
+ ...(streams.output === undefined ? {} : { output: streams.output })
1770
+ };
1771
+ }
1772
+ function throwPromptCancellation() {
1773
+ cancel("Operation cancelled.");
1774
+ throw new UserError("Operation cancelled.");
1775
+ }
1776
+ async function promptForField(field, streams = {}) {
1765
1777
  const schema = field.schema;
1766
1778
  if (schema.kind === "enum") {
1767
1779
  const options = schema.loadOptions
@@ -1770,37 +1782,34 @@ async function promptForField(field) {
1770
1782
  label: enumOptionLabel(schema, value),
1771
1783
  value
1772
1784
  }));
1773
- const selected = await select({
1785
+ const selected = await select(withPromptStreams({
1774
1786
  message: field.description ?? fieldPromptLabel(field),
1775
1787
  options,
1776
1788
  initialValue: field.hasDefault ? field.defaultValue : undefined
1777
- });
1789
+ }, streams));
1778
1790
  if (isCancel(selected)) {
1779
- cancel("Operation cancelled.");
1780
- throw new UserError("Operation cancelled.");
1791
+ throwPromptCancellation();
1781
1792
  }
1782
1793
  return selected;
1783
1794
  }
1784
1795
  if (field.schema.kind === "boolean") {
1785
- const selected = await confirm({
1796
+ const selected = await confirm(withPromptStreams({
1786
1797
  message: fieldPromptLabel(field),
1787
1798
  initialValue: field.hasDefault ? Boolean(field.defaultValue) : undefined
1788
- });
1799
+ }, streams));
1789
1800
  if (isCancel(selected)) {
1790
- cancel("Operation cancelled.");
1791
- throw new UserError("Operation cancelled.");
1801
+ throwPromptCancellation();
1792
1802
  }
1793
1803
  return selected;
1794
1804
  }
1795
- const entered = await promptText({
1805
+ const entered = await promptText(withPromptStreams({
1796
1806
  message: fieldPromptLabel(field),
1797
1807
  initialValue: field.hasDefault && field.defaultValue !== undefined
1798
1808
  ? formatResolvedValue(field.defaultValue)
1799
1809
  : undefined
1800
- });
1810
+ }, streams));
1801
1811
  if (isCancel(entered)) {
1802
- cancel("Operation cancelled.");
1803
- throw new UserError("Operation cancelled.");
1812
+ throwPromptCancellation();
1804
1813
  }
1805
1814
  if (typeof entered !== "string") {
1806
1815
  throw new UserError(`Missing required parameter "${field.displayPath}".`);
@@ -2825,7 +2834,7 @@ async function enforceVariantConstraints(params, fields, dynamicFields, variants
2825
2834
  }
2826
2835
  }
2827
2836
  }
2828
- async function resolveParams(fields, dynamicFields, variants, positionalValues, optionValues, rawArgv, casing, presetPath, shouldPrompt) {
2837
+ async function resolveParams(fields, dynamicFields, variants, positionalValues, optionValues, rawArgv, casing, presetPath, shouldPrompt, missingParameterContext, promptStreams) {
2829
2838
  const params = {};
2830
2839
  const presetValues = typeof presetPath === "string" && presetPath.length > 0
2831
2840
  ? await loadPresetValues(fields, presetPath)
@@ -2835,6 +2844,7 @@ async function resolveParams(fields, dynamicFields, variants, positionalValues,
2835
2844
  const errors = [];
2836
2845
  for (const field of fields) {
2837
2846
  let value;
2847
+ let resolvedMissing = false;
2838
2848
  let source;
2839
2849
  if (field.positionalIndex !== undefined) {
2840
2850
  const positionalValue = positionalValues[field.positionalIndex];
@@ -2885,8 +2895,49 @@ async function resolveParams(fields, dynamicFields, variants, positionalValues,
2885
2895
  }
2886
2896
  value = parsed.value;
2887
2897
  }
2898
+ if (value === undefined &&
2899
+ field.optional &&
2900
+ missingParameterContext !== undefined &&
2901
+ field.schema.cli?.resolveMissing !== undefined) {
2902
+ const resolution = await field.schema.cli.resolveMissing({
2903
+ ...missingParameterContext,
2904
+ params: { ...params }
2905
+ });
2906
+ const choices = resolution?.choices ?? [];
2907
+ if (choices.length === 1) {
2908
+ value = choices[0]?.value;
2909
+ resolvedMissing = true;
2910
+ source = "prompt";
2911
+ }
2912
+ else if (choices.length > 1) {
2913
+ const selected = await select(withPromptStreams({
2914
+ message: resolution?.message ?? field.description ?? fieldPromptLabel(field),
2915
+ options: choices.map((choice) => ({
2916
+ label: choice.label,
2917
+ value: choice.value
2918
+ }))
2919
+ }, promptStreams));
2920
+ if (isCancel(selected)) {
2921
+ throwPromptCancellation();
2922
+ }
2923
+ value = selected;
2924
+ resolvedMissing = true;
2925
+ source = "prompt";
2926
+ }
2927
+ }
2928
+ if (resolvedMissing) {
2929
+ const validation = validateSchema(field.schema, value);
2930
+ if (!validation.ok) {
2931
+ errors.push(...validation.issues.map((issue) => ({
2932
+ path: field.displayPath,
2933
+ message: issue.message
2934
+ })));
2935
+ continue;
2936
+ }
2937
+ value = validation.value;
2938
+ }
2888
2939
  if (value === undefined && shouldPrompt && !field.optional) {
2889
- value = await promptForField(field);
2940
+ value = await promptForField(field, promptStreams);
2890
2941
  source = "prompt";
2891
2942
  }
2892
2943
  if (value === undefined && field.hasDefault) {
@@ -2942,7 +2993,7 @@ function getResolvedFlags(command) {
2942
2993
  const flags = command.optsWithGlobals();
2943
2994
  return flags;
2944
2995
  }
2945
- async function executeCommand(state, services, requirementOptions, runtimeFetch, runtimeOptions, runtimeEnv, runtimeFs, outputEmitter, diagnosticsOptions, onErrorReportContext) {
2996
+ async function executeCommand(state, services, requirementOptions, runtimeFetch, runtimeOptions, runtimeEnv, runtimeFs, outputEmitter, promptStreams, diagnosticsOptions, onErrorReportContext) {
2946
2997
  const logger = createLogger(outputEmitter);
2947
2998
  const primitives = {
2948
2999
  logger,
@@ -2960,7 +3011,20 @@ async function executeCommand(state, services, requirementOptions, runtimeFetch,
2960
3011
  : diagnosticsOptions.logLevel),
2961
3012
  logger: diagnosticsOptions.logger ?? writeCLIDiagnosticEvent
2962
3013
  });
2963
- const shouldPrompt = !resolvedFlags.yes && Boolean(process.stdin.isTTY);
3014
+ const promptInput = promptStreams.input ?? process.stdin;
3015
+ const promptOutput = promptStreams.output ?? process.stdout;
3016
+ const stdinTTY = Boolean(promptInput.isTTY);
3017
+ const stdoutTTY = Boolean(promptOutput.isTTY);
3018
+ const shouldPrompt = !resolvedFlags.yes && stdinTTY;
3019
+ const missingParameterContext = !resolvedFlags.yes && output === "rich" && stdinTTY && stdoutTTY
3020
+ ? {
3021
+ commandPath: state.commandPath,
3022
+ params: {},
3023
+ output,
3024
+ stdinTTY,
3025
+ stdoutTTY
3026
+ }
3027
+ : undefined;
2964
3028
  const runtime = await resolveFixtureRuntime(state.command, services, requirementOptions, runtimeFetch, runtimeEnv, runtimeFs);
2965
3029
  const preflightContext = {
2966
3030
  ...runtime.services,
@@ -2979,7 +3043,7 @@ async function executeCommand(state, services, requirementOptions, runtimeFetch,
2979
3043
  try {
2980
3044
  await withOutputFormat(output, async () => {
2981
3045
  await assertCommandRequirements(state.command, preflightContext, runtime.requirementOptions);
2982
- const params = await resolveParams(state.fields, state.dynamicFields, state.variants, state.positionalValues, optionValues, state.rawArgv, state.casing, state.presetsEnabled ? resolvedFlags.preset : undefined, shouldPrompt);
3046
+ const params = await resolveParams(state.fields, state.dynamicFields, state.variants, state.positionalValues, optionValues, state.rawArgv, state.casing, state.presetsEnabled ? resolvedFlags.preset : undefined, shouldPrompt, missingParameterContext, promptStreams);
2983
3047
  resolvedParams = params;
2984
3048
  runtimeSecrets = runtime.secrets;
2985
3049
  const context = {
@@ -3607,6 +3671,9 @@ export async function runCLI(roots, options = {}) {
3607
3671
  lastActionCommand = state.actionCommand;
3608
3672
  resolvedCommandPath = formatCliCommandPath(state.commandPath);
3609
3673
  await executeCommand(state, servicesWithBuiltIns, requirementOptions, runtimeFetch, runtimeOptions, options.env, options.fs, options.outputEmitter, {
3674
+ input: options.promptInput,
3675
+ output: options.promptOutput
3676
+ }, {
3610
3677
  logLevel: options.logLevel,
3611
3678
  logger: options.logger,
3612
3679
  verboseControlEnabled: controls.verbose
@@ -48,13 +48,18 @@
48
48
  },
49
49
  {
50
50
  "name": "toolcraft",
51
- "version": "0.0.100",
51
+ "version": "0.0.102",
52
52
  "license": "MIT"
53
53
  },
54
54
  {
55
55
  "name": "toolcraft-design",
56
56
  "version": "0.0.2",
57
57
  "license": "MIT"
58
+ },
59
+ {
60
+ "name": "toolcraft-schema",
61
+ "version": "0.0.102",
62
+ "license": "MIT"
58
63
  }
59
64
  ]
60
65
  }
@@ -1,6 +1,7 @@
1
1
  import type { ExplorerEvent } from "./events.js";
2
2
  import type { DetailCtx, DetailItem } from "./state.js";
3
3
  export declare const LOADING_INDICATOR_MS = 150;
4
+ export declare const DETAIL_DEBOUNCE_MS = 100;
4
5
  export declare function createDetailJobs(emit: (event: ExplorerEvent) => void): {
5
6
  schedule: (rowId: string, items: (ctx: DetailCtx) => Promise<DetailItem[]>, ctx: DetailCtx) => Promise<void>;
6
7
  abort: () => void;
@@ -1,6 +1,8 @@
1
1
  export const LOADING_INDICATOR_MS = 150;
2
+ export const DETAIL_DEBOUNCE_MS = 100;
2
3
  export function createDetailJobs(emit) {
3
4
  let token = 0;
5
+ let lastScheduleAt = 0;
4
6
  let current = null;
5
7
  const abortedTokens = new Set();
6
8
  return {
@@ -9,6 +11,9 @@ export function createDetailJobs(emit) {
9
11
  current.controller.abort();
10
12
  clearTimeout(current.loadingTimer);
11
13
  }
14
+ const scheduledAt = Date.now();
15
+ const debounce = scheduledAt - lastScheduleAt < DETAIL_DEBOUNCE_MS;
16
+ lastScheduleAt = scheduledAt;
12
17
  const nextToken = ++token;
13
18
  const controller = new AbortController();
14
19
  let finished = false;
@@ -19,6 +24,12 @@ export function createDetailJobs(emit) {
19
24
  }, LOADING_INDICATOR_MS);
20
25
  current = { controller, loadingTimer, token: nextToken };
21
26
  try {
27
+ if (debounce) {
28
+ await waitUnlessAborted(DETAIL_DEBOUNCE_MS, controller.signal);
29
+ if (controller.signal.aborted || nextToken !== token) {
30
+ return;
31
+ }
32
+ }
22
33
  const loadedItems = await items({ ...ctx, signal: controller.signal });
23
34
  finished = true;
24
35
  if (!abortedTokens.has(nextToken)) {
@@ -51,6 +62,23 @@ export function createDetailJobs(emit) {
51
62
  }
52
63
  };
53
64
  }
65
+ function waitUnlessAborted(ms, signal) {
66
+ return new Promise((resolve) => {
67
+ if (signal.aborted) {
68
+ resolve();
69
+ return;
70
+ }
71
+ const onAbort = () => {
72
+ clearTimeout(timer);
73
+ resolve();
74
+ };
75
+ const timer = setTimeout(() => {
76
+ signal.removeEventListener("abort", onAbort);
77
+ resolve();
78
+ }, ms);
79
+ signal.addEventListener("abort", onAbort, { once: true });
80
+ });
81
+ }
54
82
  function toError(error) {
55
83
  if (error instanceof Error) {
56
84
  return error;
@@ -26,7 +26,7 @@ function renderDetailBody(state, screen, rect, row) {
26
26
  return;
27
27
  }
28
28
  if (items === null) {
29
- writeLine(screen, rect, 0, state.detail.loading ? "Loading detail..." : state.emptyHint, styles.muted);
29
+ writeLine(screen, rect, 0, row === null ? state.emptyHint : "Loading detail...", styles.muted);
30
30
  return;
31
31
  }
32
32
  if (items.length === 0) {
@@ -13,7 +13,7 @@ export function renderList(state, screen, layout) {
13
13
  writeLine(screen, rect, 0, "Terminal too narrow", styles.muted);
14
14
  return;
15
15
  }
16
- drawPaneFrame(screen, rect, "Plans", state.focused === "list" ? styles.borderFocused : styles.border);
16
+ drawPaneFrame(screen, rect, state.title, state.focused === "list" ? styles.borderFocused : styles.border);
17
17
  const bodyRect = paneBodyRect(rect);
18
18
  if (bodyRect.width <= 0 || bodyRect.height <= 0) {
19
19
  return;
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Poe Platform
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,89 @@
1
+ # toolcraft-schema
2
+
3
+ Zero-dependency schema builder for typed command inputs, runtime validation,
4
+ and JSON Schema generation.
5
+
6
+ ## Features
7
+
8
+ - Zero runtime dependencies
9
+ - Typed schema descriptors
10
+ - `Static<typeof schema>` type inference
11
+ - Runtime validation with `validateValue()`
12
+ - JSON Schema serialization via `toJsonSchema()`
13
+ - JSON Schema document serialization via `toJsonSchemaDocument()`
14
+
15
+ ## Usage
16
+
17
+ ```ts
18
+ import { S, toJsonSchema, toJsonSchemaDocument, validateValue } from "toolcraft-schema";
19
+ import type { Static } from "toolcraft-schema";
20
+
21
+ const schema = S.Object({
22
+ name: S.String({ description: "User name" }),
23
+ retries: S.Optional(S.Number({ default: 3 })),
24
+ mode: S.Enum(["fast", "safe"] as const, { default: "safe" }),
25
+ tags: S.Array(S.String(), { default: [] })
26
+ });
27
+
28
+ type Input = Static<typeof schema>;
29
+ // {
30
+ // name: string;
31
+ // retries?: number;
32
+ // mode: "fast" | "safe";
33
+ // tags: string[];
34
+ // }
35
+
36
+ const jsonSchema = toJsonSchema(schema);
37
+ const document = toJsonSchemaDocument(schema, {
38
+ id: "https://example.test/schema.json",
39
+ title: "Example schema"
40
+ });
41
+ const validation = validateValue(schema, {
42
+ name: "Ada",
43
+ mode: "safe",
44
+ tags: []
45
+ });
46
+ ```
47
+
48
+ ## API
49
+
50
+ ### Builders
51
+
52
+ - `S.String({ description?, default?, short?, cliAliases? })`
53
+ - `S.Number({ description?, default?, short?, cliAliases? })`
54
+ - `S.Boolean({ description?, default?, short?, cliAliases? })`
55
+ - `S.Enum(values, { description?, default?, short?, cliAliases? })`
56
+ - `S.Array(itemSchema, { description?, default?, short?, cliAliases? })`
57
+ - `S.Record(valueSchema, { description?, default? })`
58
+ - `S.Union([schemaA, schemaB], { description?, default? })`
59
+ - `S.OneOf([schemaA, schemaB], { description?, default? })`
60
+ - `S.Object({ [key]: schema })`
61
+ - `S.Optional(schema)`
62
+
63
+ ### Type helpers
64
+
65
+ - `Static<typeof schema>` infers the runtime TypeScript shape for a schema descriptor.
66
+ - Object properties wrapped in `S.Optional(...)` become optional properties in `Static`.
67
+
68
+ ### JSON Schema generation
69
+
70
+ - `toJsonSchema(schema)` converts any schema descriptor to standard JSON Schema.
71
+ - `toJsonSchemaDocument(schema, options)` wraps `toJsonSchema(schema)` in a full JSON Schema document with `$schema`, optional `$id`, `title`, and `description`.
72
+ - Object properties not wrapped in `S.Optional(...)` are emitted in `required`.
73
+ - Defaults provided to schema builders are emitted as JSON Schema `default`.
74
+ - Nested `S.Object(...)` schemas produce nested JSON Schema objects.
75
+ - `S.Enum(...)` rejects empty or duplicate values at runtime for JavaScript callers.
76
+
77
+ ### Runtime validation
78
+
79
+ - `validateValue(schema, value)` returns `{ ok: true, value }` for valid input.
80
+ - Invalid input returns `{ ok: false, issues }` with path-aware diagnostics.
81
+ - Validation applies defaults from schema descriptors.
82
+
83
+ ## Environment Variables
84
+
85
+ This package exposes no environment variables.
86
+
87
+ ## Configuration
88
+
89
+ This package currently exposes no package-level configuration options.
@@ -0,0 +1,17 @@
1
+ import { S, toJsonSchemaDocument } from "./index.js";
2
+ const ignoredStringSchema = S.String({ description: "Name", default: "guest" });
3
+ const ignoredNumberSchema = S.Number({ description: "Count", default: 1 });
4
+ const ignoredBooleanSchema = S.Boolean({ description: "Enabled", default: false });
5
+ const ignoredEnumSchema = S.Enum(["admin", "user"], { default: "admin" });
6
+ const ignoredIntegerEnumSchema = S.Enum([1, 2], { jsonType: "integer" });
7
+ const ignoredArraySchema = S.Array(S.String(), { default: ["a"] });
8
+ const ignoredObjectSchema = S.Object({
9
+ name: S.String(),
10
+ retries: S.Optional(S.Number())
11
+ });
12
+ const ignoredOptionalSchema = S.Optional(S.Boolean());
13
+ const ignoredJsonSchemaDocument = toJsonSchemaDocument(ignoredObjectSchema, {
14
+ id: "https://example.test/schema.json",
15
+ title: "Example schema",
16
+ description: "Example schema document"
17
+ });
@@ -0,0 +1,182 @@
1
+ import { Json } from "./json.js";
2
+ import { OneOf } from "./oneof.js";
3
+ import { Record as RecordBuilder } from "./record.js";
4
+ import { Union } from "./union.js";
5
+ import { validate } from "./validate.js";
6
+ import type { JsonValue, JsonValueSchema } from "./json.js";
7
+ import type { JsonSchemaDocument, JsonSchemaDocumentOptions } from "./json-schema-document.js";
8
+ import type { OneOfSchema } from "./oneof.js";
9
+ import type { RecordSchema } from "./record.js";
10
+ import type { UnionSchema } from "./union.js";
11
+ import type { ValidationIssue, ValidationResult } from "./validate.js";
12
+ type JsonSchemaType = "string" | "number" | "integer" | "boolean" | "array" | "object";
13
+ type SchemaKind = "string" | "number" | "boolean" | "enum" | "array" | "object" | "optional" | "oneOf" | "union" | "record" | "json";
14
+ type EnumValue = string | number | boolean;
15
+ type JsonSchemaEnumValue = EnumValue | null;
16
+ type NumberJsonType = "number" | "integer";
17
+ type NonEmptyReadonlyArray<T> = readonly [T, ...T[]];
18
+ type ObjectShape = Record<string, AnySchema>;
19
+ type EmptyOptions = Record<never, never>;
20
+ type SchemaScope = "cli" | "mcp" | "sdk";
21
+ export type CliOutputMode = "rich" | "md" | "json";
22
+ export interface CliMissingParameterChoice<TValue> {
23
+ label: string;
24
+ value: TValue;
25
+ }
26
+ export interface CliMissingParameterContext {
27
+ commandPath: string;
28
+ params: Readonly<Record<string, unknown>>;
29
+ output: CliOutputMode;
30
+ stdinTTY: boolean;
31
+ stdoutTTY: boolean;
32
+ }
33
+ export interface CliMissingParameterResolution<TValue> {
34
+ choices: readonly CliMissingParameterChoice<TValue>[];
35
+ message?: string;
36
+ }
37
+ export interface CliSchemaOptions<TValue> {
38
+ resolveMissing?: (context: CliMissingParameterContext) => CliMissingParameterResolution<TValue> | undefined | Promise<CliMissingParameterResolution<TValue> | undefined>;
39
+ }
40
+ type StringMetadata = {
41
+ format?: string;
42
+ maxLength?: number;
43
+ minLength?: number;
44
+ pattern?: string;
45
+ secret?: boolean;
46
+ };
47
+ type NumberMetadata = {
48
+ maximum?: number;
49
+ minimum?: number;
50
+ secret?: boolean;
51
+ };
52
+ type ArrayMetadata = {
53
+ maxItems?: number;
54
+ minItems?: number;
55
+ };
56
+ type ObjectMetadata = {
57
+ additionalProperties?: boolean;
58
+ };
59
+ type OptionalKeys<TShape extends ObjectShape> = {
60
+ [TKey in keyof TShape]: TShape[TKey] extends OptionalSchema<any> ? TKey : never;
61
+ }[keyof TShape];
62
+ type RequiredKeys<TShape extends ObjectShape> = Exclude<keyof TShape, OptionalKeys<TShape>>;
63
+ type PropertyStatic<TSchema extends AnySchema> = TSchema extends OptionalSchema<infer TInner> ? Static<TInner> : Static<TSchema>;
64
+ type InferObject<TShape extends ObjectShape> = {
65
+ [TKey in RequiredKeys<TShape>]: PropertyStatic<TShape[TKey]>;
66
+ } & {
67
+ [TKey in OptionalKeys<TShape>]?: PropertyStatic<TShape[TKey]>;
68
+ };
69
+ type SchemaOptions<TDefault> = {
70
+ cli?: CliSchemaOptions<TDefault>;
71
+ description?: string;
72
+ cliDescription?: string;
73
+ cliAliases?: readonly string[];
74
+ default?: TDefault;
75
+ nullable?: boolean;
76
+ requiredScopes?: readonly SchemaScope[];
77
+ short?: string;
78
+ scope?: readonly SchemaScope[];
79
+ global?: boolean;
80
+ };
81
+ type WithNullable<TSchema extends AnySchema, TOptions extends {
82
+ nullable?: boolean;
83
+ }> = TOptions extends {
84
+ readonly nullable: true;
85
+ } ? TSchema & {
86
+ readonly nullable: true;
87
+ } : TSchema;
88
+ export interface SchemaBase<TKind extends SchemaKind, TStatic> {
89
+ readonly kind: TKind;
90
+ readonly cli?: CliSchemaOptions<TStatic>;
91
+ readonly description?: string;
92
+ readonly cliDescription?: string;
93
+ readonly cliAliases?: readonly string[];
94
+ readonly default?: TStatic;
95
+ readonly nullable?: boolean;
96
+ readonly requiredScopes?: readonly SchemaScope[];
97
+ readonly short?: string;
98
+ readonly scope?: readonly SchemaScope[];
99
+ readonly global?: boolean;
100
+ readonly __static?: TStatic;
101
+ }
102
+ export interface JsonSchema {
103
+ additionalProperties?: boolean | JsonSchema;
104
+ type?: JsonSchemaType;
105
+ description?: string;
106
+ default?: unknown;
107
+ enum?: ReadonlyArray<JsonSchemaEnumValue>;
108
+ format?: string;
109
+ items?: JsonSchema;
110
+ maxItems?: number;
111
+ maximum?: number;
112
+ maxLength?: number;
113
+ minItems?: number;
114
+ minimum?: number;
115
+ minLength?: number;
116
+ nullable?: boolean;
117
+ oneOf?: JsonSchema[];
118
+ pattern?: string;
119
+ properties?: Record<string, JsonSchema>;
120
+ required?: string[];
121
+ }
122
+ export interface StringSchema extends SchemaBase<"string", string>, StringMetadata {
123
+ }
124
+ export interface NumberSchema extends SchemaBase<"number", number>, NumberMetadata {
125
+ readonly jsonType?: NumberJsonType;
126
+ }
127
+ export type BooleanSchema = SchemaBase<"boolean", boolean>;
128
+ export interface EnumSchema<TValues extends NonEmptyReadonlyArray<EnumValue>> extends SchemaBase<"enum", TValues[number]> {
129
+ readonly values: TValues;
130
+ readonly jsonType?: "integer";
131
+ readonly labels?: Partial<Record<string, string>>;
132
+ readonly loadOptions?: (() => Array<{
133
+ label: string;
134
+ value: string;
135
+ }>) | (() => Promise<Array<{
136
+ label: string;
137
+ value: string;
138
+ }>>);
139
+ }
140
+ export interface ArraySchema<TItem extends AnySchema> extends SchemaBase<"array", Array<Static<TItem>>>, ArrayMetadata {
141
+ readonly item: TItem;
142
+ }
143
+ export interface ObjectSchema<TShape extends ObjectShape> extends SchemaBase<"object", InferObject<TShape>>, ObjectMetadata {
144
+ readonly shape: TShape;
145
+ }
146
+ export interface OptionalSchema<TInner extends AnySchema> extends SchemaBase<"optional", Static<TInner> | undefined> {
147
+ readonly inner: TInner;
148
+ }
149
+ export type AnySchema = StringSchema | NumberSchema | BooleanSchema | EnumSchema<NonEmptyReadonlyArray<EnumValue>> | ArraySchema<AnySchema> | ObjectSchema<ObjectShape> | OptionalSchema<AnySchema> | OneOfSchema<Record<string, ObjectSchema<any>>, string> | UnionSchema<readonly ObjectSchema<any>[]> | RecordSchema<AnySchema> | JsonValueSchema;
150
+ export type Static<TSchema extends AnySchema> = TSchema extends {
151
+ readonly nullable: true;
152
+ } ? TSchema extends SchemaBase<any, infer TStatic> ? TStatic | null : never : TSchema extends SchemaBase<any, infer TStatic> ? TStatic : never;
153
+ export declare const S: {
154
+ readonly String: <const TOptions extends SchemaOptions<string> & StringMetadata = EmptyOptions>(options?: TOptions) => WithNullable<StringSchema, TOptions>;
155
+ readonly Number: <const TOptions extends SchemaOptions<number> & NumberMetadata & {
156
+ jsonType?: NumberJsonType;
157
+ } = EmptyOptions>(options?: TOptions) => WithNullable<NumberSchema, TOptions>;
158
+ readonly Boolean: <const TOptions extends SchemaOptions<boolean> = EmptyOptions>(options?: TOptions) => WithNullable<BooleanSchema, TOptions>;
159
+ readonly Enum: <const TValues extends NonEmptyReadonlyArray<EnumValue>, const TOptions extends SchemaOptions<TValues[number]> & {
160
+ jsonType?: "integer";
161
+ labels?: Partial<Record<string, string>>;
162
+ loadOptions?: (() => Array<{
163
+ label: string;
164
+ value: string;
165
+ }>) | (() => Promise<Array<{
166
+ label: string;
167
+ value: string;
168
+ }>>);
169
+ } = EmptyOptions>(values: TValues, options?: TOptions) => WithNullable<EnumSchema<TValues>, TOptions>;
170
+ readonly Array: <TItem extends AnySchema, const TOptions extends SchemaOptions<Array<Static<TItem>>> & ArrayMetadata = EmptyOptions>(item: TItem, options?: TOptions) => WithNullable<ArraySchema<TItem>, TOptions>;
171
+ readonly Object: <const TShape extends ObjectShape, const TOptions extends SchemaOptions<InferObject<TShape>> & ObjectMetadata = EmptyOptions>(shape: TShape, options?: TOptions) => WithNullable<ObjectSchema<TShape>, TOptions>;
172
+ readonly Optional: <TInner extends AnySchema>(inner: TInner) => OptionalSchema<TInner>;
173
+ readonly OneOf: typeof OneOf;
174
+ readonly Union: typeof Union;
175
+ readonly Record: typeof RecordBuilder;
176
+ readonly Json: typeof Json;
177
+ };
178
+ export declare function toJsonSchema(schema: AnySchema): JsonSchema;
179
+ export declare function toJsonSchemaDocument(schema: AnySchema, options?: JsonSchemaDocumentOptions): JsonSchemaDocument;
180
+ export { Json, OneOf, RecordBuilder as Record, Union, validate };
181
+ export type { JsonSchemaDocument, JsonSchemaDocumentOptions } from "./json-schema-document.js";
182
+ export type { JsonValue, JsonValueSchema, OneOfSchema, RecordSchema, UnionSchema, ValidationIssue, ValidationResult };