@outfitter/schema 0.2.0

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/README.md ADDED
@@ -0,0 +1,126 @@
1
+ # @outfitter/schema
2
+
3
+ Schema introspection, surface map generation, and drift detection for Outfitter action registries.
4
+
5
+ **Stability: Active** -- APIs evolving based on usage.
6
+
7
+ ## Installation
8
+
9
+ ```bash
10
+ bun add @outfitter/schema zod
11
+ ```
12
+
13
+ `zod` is a peer dependency because schema generation converts Zod input/output contracts into JSON Schema.
14
+
15
+ ## Quick Start
16
+
17
+ ```typescript
18
+ import { createActionRegistry, defineAction, Result } from "@outfitter/contracts";
19
+ import { generateSurfaceMap } from "@outfitter/schema/surface";
20
+ import { z } from "zod";
21
+
22
+ const registry = createActionRegistry().add(
23
+ defineAction({
24
+ id: "doctor",
25
+ description: "Validate environment",
26
+ surfaces: ["cli", "mcp"],
27
+ input: z.object({ verbose: z.boolean().optional() }),
28
+ handler: async () => Result.ok({ ok: true }),
29
+ })
30
+ );
31
+
32
+ const surface = generateSurfaceMap(registry, {
33
+ version: "1.0.0",
34
+ generator: "runtime",
35
+ });
36
+
37
+ console.log(surface.actions.map((a) => a.id));
38
+ ```
39
+
40
+ ## Subpath Exports
41
+
42
+ | Subpath | What's In It |
43
+ |---------|---------------|
44
+ | `@outfitter/schema` | Full public API from all modules |
45
+ | `@outfitter/schema/manifest` | `generateManifest`, `ActionManifest*` types |
46
+ | `@outfitter/schema/surface` | `generateSurfaceMap`, snapshot path + read/write helpers |
47
+ | `@outfitter/schema/diff` | `diffSurfaceMaps`, diff result types |
48
+ | `@outfitter/schema/markdown` | `formatManifestMarkdown`, markdown formatting options |
49
+
50
+ ## Core Workflows
51
+
52
+ ### 1) Generate a manifest from an action registry
53
+
54
+ ```typescript
55
+ import { generateManifest } from "@outfitter/schema/manifest";
56
+
57
+ const manifest = generateManifest(registry, {
58
+ version: "1.0.0",
59
+ surface: "mcp", // optional: cli | mcp | api
60
+ });
61
+
62
+ console.log(manifest.errors.validation.exit); // 1
63
+ console.log(manifest.outputModes); // ["human", "json", "jsonl", "tree", "table"]
64
+ ```
65
+
66
+ ### 2) Generate and persist a surface map snapshot
67
+
68
+ ```typescript
69
+ import {
70
+ generateSurfaceMap,
71
+ resolveSnapshotPath,
72
+ writeSurfaceMap,
73
+ } from "@outfitter/schema/surface";
74
+
75
+ const surface = generateSurfaceMap(registry, {
76
+ version: "1.0.0",
77
+ generator: "build",
78
+ });
79
+
80
+ const path = resolveSnapshotPath(process.cwd(), ".outfitter", "v1.0.0");
81
+ await writeSurfaceMap(surface, path);
82
+ ```
83
+
84
+ ### 3) Detect schema drift in CI
85
+
86
+ ```typescript
87
+ import { generateSurfaceMap, readSurfaceMap } from "@outfitter/schema/surface";
88
+ import { diffSurfaceMaps } from "@outfitter/schema/diff";
89
+
90
+ const committed = await readSurfaceMap(".outfitter/snapshots/v1.0.0.json");
91
+ const current = generateSurfaceMap(registry, { version: "1.0.0" });
92
+
93
+ const diff = diffSurfaceMaps(committed, current);
94
+ if (diff.hasChanges) {
95
+ console.error("Surface drift detected", diff);
96
+ process.exit(1);
97
+ }
98
+ ```
99
+
100
+ ### 4) Generate markdown reference docs
101
+
102
+ ```typescript
103
+ import { formatManifestMarkdown } from "@outfitter/schema/markdown";
104
+
105
+ const manifest = generateManifest(registry, { surface: "mcp", version: "1.0.0" });
106
+
107
+ const markdown = formatManifestMarkdown(manifest, {
108
+ surface: "mcp",
109
+ title: "MCP Tool Reference",
110
+ toc: true,
111
+ timestamp: false,
112
+ });
113
+
114
+ await Bun.write("docs/reference/tools.md", markdown);
115
+ ```
116
+
117
+ ## API Notes
118
+
119
+ - `generateManifest()` accepts either an `ActionRegistry` or a plain array of action specs.
120
+ - `generateSurfaceMap()` wraps manifest output with envelope metadata (`$schema`, `generator`).
121
+ - `diffSurfaceMaps()` ignores volatile timestamps and reports granular change categories (`input`, `output`, `surfaces`, `cli`, `mcp`, `api`, metadata changes).
122
+ - `formatManifestMarkdown()` renders either MCP-tool docs or CLI-command docs from the same manifest source.
123
+
124
+ ## License
125
+
126
+ MIT
package/dist/diff.d.ts ADDED
@@ -0,0 +1,4 @@
1
+ import { DiffEntry, ModifiedEntry, SurfaceMapDiff, diffSurfaceMaps } from "./shared/@outfitter/schema-qm3b0skz";
2
+ import "./shared/@outfitter/schema-wgnvy5zh";
3
+ import "./shared/@outfitter/schema-vzq7nzs3";
4
+ export { diffSurfaceMaps, SurfaceMapDiff, ModifiedEntry, DiffEntry };
package/dist/diff.js ADDED
@@ -0,0 +1,7 @@
1
+ // @bun
2
+ import {
3
+ diffSurfaceMaps
4
+ } from "./shared/@outfitter/schema-d4xhpz76.js";
5
+ export {
6
+ diffSurfaceMaps
7
+ };
@@ -0,0 +1,5 @@
1
+ import { DiffEntry, ModifiedEntry, SurfaceMapDiff, diffSurfaceMaps } from "./shared/@outfitter/schema-qm3b0skz";
2
+ import { GenerateSurfaceMapOptions, SurfaceMap, generateSurfaceMap, readSurfaceMap, resolveSnapshotPath, writeSurfaceMap } from "./shared/@outfitter/schema-wgnvy5zh";
3
+ import { MarkdownFormatOptions, formatManifestMarkdown } from "./shared/@outfitter/schema-xqcdyq2s";
4
+ import { ActionManifest, ActionManifestEntry, ActionSource, GenerateManifestOptions, ManifestApiSpec, ManifestCliOption, ManifestCliSpec, ManifestMcpSpec, generateManifest } from "./shared/@outfitter/schema-vzq7nzs3";
5
+ export { writeSurfaceMap, resolveSnapshotPath, readSurfaceMap, generateSurfaceMap, generateManifest, formatManifestMarkdown, diffSurfaceMaps, SurfaceMapDiff, SurfaceMap, ModifiedEntry, MarkdownFormatOptions, ManifestMcpSpec, ManifestCliSpec, ManifestCliOption, ManifestApiSpec, GenerateSurfaceMapOptions, GenerateManifestOptions, DiffEntry, ActionSource, ActionManifestEntry, ActionManifest };
package/dist/index.js ADDED
@@ -0,0 +1,25 @@
1
+ // @bun
2
+ import {
3
+ generateSurfaceMap,
4
+ readSurfaceMap,
5
+ resolveSnapshotPath,
6
+ writeSurfaceMap
7
+ } from "./shared/@outfitter/schema-cq7f2r5z.js";
8
+ import {
9
+ diffSurfaceMaps
10
+ } from "./shared/@outfitter/schema-d4xhpz76.js";
11
+ import {
12
+ formatManifestMarkdown
13
+ } from "./shared/@outfitter/schema-y9k7tad6.js";
14
+ import {
15
+ generateManifest
16
+ } from "./shared/@outfitter/schema-tnrxpckr.js";
17
+ export {
18
+ writeSurfaceMap,
19
+ resolveSnapshotPath,
20
+ readSurfaceMap,
21
+ generateSurfaceMap,
22
+ generateManifest,
23
+ formatManifestMarkdown,
24
+ diffSurfaceMaps
25
+ };
@@ -0,0 +1,2 @@
1
+ import { ActionManifest, ActionManifestEntry, ActionSource, GenerateManifestOptions, ManifestApiSpec, ManifestCliOption, ManifestCliSpec, ManifestMcpSpec, generateManifest } from "./shared/@outfitter/schema-vzq7nzs3";
2
+ export { generateManifest, ManifestMcpSpec, ManifestCliSpec, ManifestCliOption, ManifestApiSpec, GenerateManifestOptions, ActionSource, ActionManifestEntry, ActionManifest };
@@ -0,0 +1,7 @@
1
+ // @bun
2
+ import {
3
+ generateManifest
4
+ } from "./shared/@outfitter/schema-tnrxpckr.js";
5
+ export {
6
+ generateManifest
7
+ };
@@ -0,0 +1,3 @@
1
+ import { MarkdownFormatOptions, formatManifestMarkdown } from "./shared/@outfitter/schema-xqcdyq2s";
2
+ import "./shared/@outfitter/schema-vzq7nzs3";
3
+ export { formatManifestMarkdown, MarkdownFormatOptions };
@@ -0,0 +1,7 @@
1
+ // @bun
2
+ import {
3
+ formatManifestMarkdown
4
+ } from "./shared/@outfitter/schema-y9k7tad6.js";
5
+ export {
6
+ formatManifestMarkdown
7
+ };
@@ -0,0 +1,32 @@
1
+ // @bun
2
+ import {
3
+ generateManifest
4
+ } from "./schema-tnrxpckr.js";
5
+
6
+ // packages/schema/src/surface.ts
7
+ import { mkdir, readFile, writeFile } from "fs/promises";
8
+ import { dirname, join } from "path";
9
+ var SURFACE_MAP_SCHEMA = "https://outfitter.dev/surface/v1";
10
+ function generateSurfaceMap(source, options) {
11
+ const manifest = generateManifest(source, options);
12
+ return {
13
+ ...manifest,
14
+ $schema: SURFACE_MAP_SCHEMA,
15
+ generator: options?.generator ?? "runtime"
16
+ };
17
+ }
18
+ async function writeSurfaceMap(surfaceMap, outputPath) {
19
+ await mkdir(dirname(outputPath), { recursive: true });
20
+ const content = `${JSON.stringify(surfaceMap, null, 2)}
21
+ `;
22
+ await writeFile(outputPath, content, "utf-8");
23
+ }
24
+ async function readSurfaceMap(inputPath) {
25
+ const content = await readFile(inputPath, "utf-8");
26
+ return JSON.parse(content);
27
+ }
28
+ function resolveSnapshotPath(cwd, outputDir, version) {
29
+ return join(cwd, outputDir, "snapshots", `${version}.json`);
30
+ }
31
+
32
+ export { generateSurfaceMap, writeSurfaceMap, readSurfaceMap, resolveSnapshotPath };
@@ -0,0 +1,91 @@
1
+ // @bun
2
+ // packages/schema/src/diff.ts
3
+ function stableJson(value) {
4
+ return JSON.stringify(value, (_key, val) => {
5
+ if (val !== null && typeof val === "object" && !Array.isArray(val)) {
6
+ const sorted = {};
7
+ for (const k of Object.keys(val).sort()) {
8
+ sorted[k] = val[k];
9
+ }
10
+ return sorted;
11
+ }
12
+ return val;
13
+ });
14
+ }
15
+ function compareEntries(committed, current) {
16
+ const changes = [];
17
+ if (stableJson(committed.input) !== stableJson(current.input)) {
18
+ changes.push("input");
19
+ }
20
+ if (stableJson(committed.output) !== stableJson(current.output)) {
21
+ changes.push("output");
22
+ }
23
+ if (JSON.stringify([...committed.surfaces].sort()) !== JSON.stringify([...current.surfaces].sort())) {
24
+ changes.push("surfaces");
25
+ }
26
+ if (committed.description !== current.description) {
27
+ changes.push("description");
28
+ }
29
+ if (stableJson(committed.cli) !== stableJson(current.cli)) {
30
+ changes.push("cli");
31
+ }
32
+ if (stableJson(committed.mcp) !== stableJson(current.mcp)) {
33
+ changes.push("mcp");
34
+ }
35
+ if (stableJson(committed.api) !== stableJson(current.api)) {
36
+ changes.push("api");
37
+ }
38
+ return changes;
39
+ }
40
+ function diffSurfaceMaps(committed, current) {
41
+ const metadataChanges = [];
42
+ if (committed.version !== current.version) {
43
+ metadataChanges.push("version");
44
+ }
45
+ if (JSON.stringify([...committed.surfaces].sort()) !== JSON.stringify([...current.surfaces].sort())) {
46
+ metadataChanges.push("surfaces");
47
+ }
48
+ if (stableJson(committed.errors) !== stableJson(current.errors)) {
49
+ metadataChanges.push("errors");
50
+ }
51
+ if (stableJson(committed.outputModes) !== stableJson(current.outputModes)) {
52
+ metadataChanges.push("outputModes");
53
+ }
54
+ if ("$schema" in committed && "$schema" in current && committed.$schema !== current.$schema) {
55
+ metadataChanges.push("$schema");
56
+ }
57
+ const committedMap = new Map(committed.actions.map((a) => [a.id, a]));
58
+ const currentMap = new Map(current.actions.map((a) => [a.id, a]));
59
+ const added = [];
60
+ const removed = [];
61
+ const modified = [];
62
+ for (const [id] of currentMap) {
63
+ if (!committedMap.has(id)) {
64
+ added.push({ id });
65
+ }
66
+ }
67
+ for (const [id] of committedMap) {
68
+ if (!currentMap.has(id)) {
69
+ removed.push({ id });
70
+ }
71
+ }
72
+ for (const [id, committedEntry] of committedMap) {
73
+ const currentEntry = currentMap.get(id);
74
+ if (!currentEntry) {
75
+ continue;
76
+ }
77
+ const changes = compareEntries(committedEntry, currentEntry);
78
+ if (changes.length > 0) {
79
+ modified.push({ id, changes });
80
+ }
81
+ }
82
+ return {
83
+ hasChanges: added.length > 0 || removed.length > 0 || modified.length > 0 || metadataChanges.length > 0,
84
+ added,
85
+ removed,
86
+ modified,
87
+ metadataChanges
88
+ };
89
+ }
90
+
91
+ export { diffSurfaceMaps };
@@ -0,0 +1,26 @@
1
+ import { SurfaceMap } from "./schema-wgnvy5zh";
2
+ interface SurfaceMapDiff {
3
+ readonly hasChanges: boolean;
4
+ readonly added: readonly DiffEntry[];
5
+ readonly removed: readonly DiffEntry[];
6
+ readonly modified: readonly ModifiedEntry[];
7
+ readonly metadataChanges: readonly string[];
8
+ }
9
+ interface DiffEntry {
10
+ readonly id: string;
11
+ }
12
+ interface ModifiedEntry extends DiffEntry {
13
+ readonly changes: readonly string[];
14
+ }
15
+ /**
16
+ * Compare two surface maps and return a structured diff.
17
+ *
18
+ * Ignores volatile fields like `generatedAt`. Reports added, removed,
19
+ * and modified actions with specific change categories.
20
+ *
21
+ * @param committed - The previously committed surface map
22
+ * @param current - The current runtime surface map
23
+ * @returns Structured diff result
24
+ */
25
+ declare function diffSurfaceMaps(committed: SurfaceMap, current: SurfaceMap): SurfaceMapDiff;
26
+ export { SurfaceMapDiff, DiffEntry, ModifiedEntry, diffSurfaceMaps };
@@ -0,0 +1,85 @@
1
+ // @bun
2
+ // packages/schema/src/manifest.ts
3
+ import {
4
+ ACTION_SURFACES,
5
+ DEFAULT_REGISTRY_SURFACES,
6
+ exitCodeMap,
7
+ statusCodeMap,
8
+ zodToJsonSchema
9
+ } from "@outfitter/contracts";
10
+ function isActionRegistry(source) {
11
+ return "list" in source && typeof source.list === "function";
12
+ }
13
+ var OUTPUT_MODES = ["human", "json", "jsonl", "tree", "table"];
14
+ function buildErrorTaxonomy() {
15
+ const taxonomy = {};
16
+ for (const category of Object.keys(exitCodeMap)) {
17
+ taxonomy[category] = {
18
+ exit: exitCodeMap[category],
19
+ http: statusCodeMap[category]
20
+ };
21
+ }
22
+ return taxonomy;
23
+ }
24
+ function actionToManifestEntry(action) {
25
+ const surfaces = [
26
+ ...action.surfaces ?? DEFAULT_REGISTRY_SURFACES
27
+ ];
28
+ return {
29
+ id: action.id,
30
+ description: action.description,
31
+ surfaces,
32
+ input: zodToJsonSchema(action.input),
33
+ output: action.output ? zodToJsonSchema(action.output) : undefined,
34
+ cli: action.cli ? {
35
+ group: action.cli.group,
36
+ command: action.cli.command,
37
+ description: action.cli.description,
38
+ aliases: action.cli.aliases && action.cli.aliases.length > 0 ? action.cli.aliases : undefined,
39
+ options: action.cli.options && action.cli.options.length > 0 ? action.cli.options.map((o) => ({
40
+ flags: o.flags,
41
+ description: o.description,
42
+ defaultValue: o.defaultValue,
43
+ required: o.required
44
+ })) : undefined
45
+ } : undefined,
46
+ mcp: action.mcp ? {
47
+ tool: action.mcp.tool,
48
+ description: action.mcp.description,
49
+ deferLoading: action.mcp.deferLoading
50
+ } : undefined,
51
+ api: action.api ? {
52
+ method: action.api.method,
53
+ path: action.api.path,
54
+ tags: action.api.tags
55
+ } : undefined
56
+ };
57
+ }
58
+ function generateManifest(source, options) {
59
+ let actions = isActionRegistry(source) ? source.list() : [...source];
60
+ if (options?.surface) {
61
+ const surface = options.surface;
62
+ actions = actions.filter((action) => {
63
+ const surfaces = action.surfaces ?? DEFAULT_REGISTRY_SURFACES;
64
+ return surfaces.includes(surface);
65
+ });
66
+ }
67
+ const surfaceSet = new Set;
68
+ for (const action of actions) {
69
+ for (const s of action.surfaces ?? DEFAULT_REGISTRY_SURFACES) {
70
+ if (ACTION_SURFACES.includes(s)) {
71
+ surfaceSet.add(s);
72
+ }
73
+ }
74
+ }
75
+ return {
76
+ version: options?.version ?? "1.0.0",
77
+ generatedAt: new Date().toISOString(),
78
+ surfaces: [...surfaceSet].sort(),
79
+ actions: actions.map(actionToManifestEntry),
80
+ errors: buildErrorTaxonomy(),
81
+ outputModes: [...OUTPUT_MODES]
82
+ };
83
+ }
84
+
85
+ export { generateManifest };
@@ -0,0 +1,59 @@
1
+ import { ActionRegistry, ActionSurface, AnyActionSpec, ErrorCategory, JsonSchema } from "@outfitter/contracts";
2
+ interface ActionManifest {
3
+ readonly version: string;
4
+ readonly generatedAt: string;
5
+ readonly surfaces: ActionSurface[];
6
+ readonly actions: ActionManifestEntry[];
7
+ readonly errors: Record<ErrorCategory, {
8
+ exit: number;
9
+ http: number;
10
+ }>;
11
+ readonly outputModes: string[];
12
+ }
13
+ interface ActionManifestEntry {
14
+ readonly id: string;
15
+ readonly description?: string | undefined;
16
+ readonly surfaces: ActionSurface[];
17
+ readonly input: JsonSchema;
18
+ readonly output?: JsonSchema | undefined;
19
+ readonly cli?: ManifestCliSpec | undefined;
20
+ readonly mcp?: ManifestMcpSpec | undefined;
21
+ readonly api?: ManifestApiSpec | undefined;
22
+ }
23
+ interface ManifestCliSpec {
24
+ readonly group?: string | undefined;
25
+ readonly command?: string | undefined;
26
+ readonly description?: string | undefined;
27
+ readonly aliases?: readonly string[] | undefined;
28
+ readonly options?: readonly ManifestCliOption[] | undefined;
29
+ }
30
+ interface ManifestCliOption {
31
+ readonly flags: string;
32
+ readonly description: string;
33
+ readonly defaultValue?: string | boolean | string[] | undefined;
34
+ readonly required?: boolean | undefined;
35
+ }
36
+ interface ManifestMcpSpec {
37
+ readonly tool?: string | undefined;
38
+ readonly description?: string | undefined;
39
+ readonly deferLoading?: boolean | undefined;
40
+ }
41
+ interface ManifestApiSpec {
42
+ readonly method?: string | undefined;
43
+ readonly path?: string | undefined;
44
+ readonly tags?: readonly string[] | undefined;
45
+ }
46
+ interface GenerateManifestOptions {
47
+ readonly surface?: ActionSurface;
48
+ readonly version?: string;
49
+ }
50
+ type ActionSource = ActionRegistry | readonly AnyActionSpec[];
51
+ /**
52
+ * Generate a manifest from an action registry or action array.
53
+ *
54
+ * @param source - ActionRegistry or array of ActionSpec
55
+ * @param options - Filtering and version options
56
+ * @returns The manifest object
57
+ */
58
+ declare function generateManifest(source: ActionSource, options?: GenerateManifestOptions): ActionManifest;
59
+ export { ActionManifest, ActionManifestEntry, ManifestCliSpec, ManifestCliOption, ManifestMcpSpec, ManifestApiSpec, GenerateManifestOptions, ActionSource, generateManifest };
@@ -0,0 +1,44 @@
1
+ import { ActionManifest, ActionSource, GenerateManifestOptions } from "./schema-vzq7nzs3";
2
+ interface SurfaceMap extends ActionManifest {
3
+ readonly $schema: string;
4
+ readonly generator: "runtime" | "build";
5
+ }
6
+ interface GenerateSurfaceMapOptions extends GenerateManifestOptions {
7
+ readonly generator?: "runtime" | "build";
8
+ }
9
+ /**
10
+ * Generate a surface map from an action registry or action array.
11
+ *
12
+ * Wraps `generateManifest()` with envelope metadata (`$schema`, `generator`).
13
+ *
14
+ * @param source - ActionRegistry or array of ActionSpec
15
+ * @param options - Filtering, version, and generator options
16
+ * @returns The surface map object
17
+ */
18
+ declare function generateSurfaceMap(source: ActionSource, options?: GenerateSurfaceMapOptions): SurfaceMap;
19
+ /**
20
+ * Write a surface map to disk as pretty-printed JSON.
21
+ *
22
+ * Creates parent directories if they don't exist.
23
+ *
24
+ * @param surfaceMap - The surface map to write
25
+ * @param outputPath - Absolute path for the output file
26
+ */
27
+ declare function writeSurfaceMap(surfaceMap: SurfaceMap, outputPath: string): Promise<void>;
28
+ /**
29
+ * Read a surface map from disk.
30
+ *
31
+ * @param inputPath - Absolute path to the surface map file
32
+ * @returns The parsed surface map
33
+ */
34
+ declare function readSurfaceMap(inputPath: string): Promise<SurfaceMap>;
35
+ /**
36
+ * Resolve the file path for a named snapshot.
37
+ *
38
+ * @param cwd - Project root directory
39
+ * @param outputDir - Output directory name (e.g., ".outfitter")
40
+ * @param version - Snapshot version label
41
+ * @returns Absolute path to the snapshot file
42
+ */
43
+ declare function resolveSnapshotPath(cwd: string, outputDir: string, version: string): string;
44
+ export { SurfaceMap, GenerateSurfaceMapOptions, generateSurfaceMap, writeSurfaceMap, readSurfaceMap, resolveSnapshotPath };
@@ -0,0 +1,21 @@
1
+ import { ActionManifest } from "./schema-vzq7nzs3";
2
+ type DocSurface = "mcp" | "cli";
3
+ interface MarkdownFormatOptions {
4
+ /** Which surface to document. Default: "mcp" */
5
+ readonly surface?: DocSurface;
6
+ /** Document title. Default: surface-specific (e.g., "MCP Tools Reference") */
7
+ readonly title?: string;
8
+ /** Include table of contents. Default: true when 2+ entries */
9
+ readonly toc?: boolean;
10
+ /** Include generated timestamp. Default: true */
11
+ readonly timestamp?: boolean;
12
+ }
13
+ /**
14
+ * Format an action manifest as a markdown reference document.
15
+ *
16
+ * @param manifest - The manifest to format
17
+ * @param options - Formatting options
18
+ * @returns Formatted markdown string
19
+ */
20
+ declare function formatManifestMarkdown(manifest: ActionManifest, options?: MarkdownFormatOptions): string;
21
+ export { MarkdownFormatOptions, formatManifestMarkdown };
@@ -0,0 +1,180 @@
1
+ // @bun
2
+ // packages/schema/src/markdown.ts
3
+ var DEFAULT_TITLES = {
4
+ mcp: "MCP Tools Reference",
5
+ cli: "CLI Reference"
6
+ };
7
+ function formatManifestMarkdown(manifest, options) {
8
+ const surface = options?.surface ?? "mcp";
9
+ const title = options?.title ?? DEFAULT_TITLES[surface];
10
+ const showTimestamp = options?.timestamp ?? true;
11
+ const sections = [];
12
+ sections.push(renderHeader(title, manifest.version, showTimestamp ? manifest.generatedAt : undefined));
13
+ const sorted = manifest.actions.filter((action) => action.surfaces.includes(surface)).sort((a, b) => {
14
+ const nameA = displayName(a, surface);
15
+ const nameB = displayName(b, surface);
16
+ return nameA.localeCompare(nameB);
17
+ });
18
+ if (sorted.length === 0) {
19
+ sections.push("_No tools registered._");
20
+ return sections.join(`
21
+
22
+ `);
23
+ }
24
+ const showToc = options?.toc ?? sorted.length >= 2;
25
+ if (showToc) {
26
+ sections.push(renderToc(sorted, surface));
27
+ }
28
+ for (const action of sorted) {
29
+ sections.push(surface === "cli" ? renderCliCommand(action) : renderMcpTool(action));
30
+ }
31
+ return `${sections.join(`
32
+
33
+ ---
34
+
35
+ `)}
36
+ `;
37
+ }
38
+ function renderHeader(title, version, generatedAt) {
39
+ const lines = [];
40
+ lines.push(`# ${title}`);
41
+ const meta = [];
42
+ meta.push(`schema v${version}`);
43
+ if (generatedAt) {
44
+ meta.push(generatedAt);
45
+ }
46
+ lines.push("");
47
+ lines.push(`> Generated from ${meta.join(" | ")}`);
48
+ return lines.join(`
49
+ `);
50
+ }
51
+ function renderToc(actions, surface) {
52
+ const lines = [];
53
+ lines.push("## Table of Contents");
54
+ lines.push("");
55
+ for (const action of actions) {
56
+ const name = displayName(action, surface);
57
+ lines.push(`- [${name}](#${slugify(name)})`);
58
+ }
59
+ return lines.join(`
60
+ `);
61
+ }
62
+ function renderMcpTool(action) {
63
+ const name = displayName(action, "mcp");
64
+ const desc = action.mcp?.description ?? action.description;
65
+ const lines = [];
66
+ lines.push(`## ${name}`);
67
+ lines.push("");
68
+ if (desc) {
69
+ lines.push(desc);
70
+ }
71
+ if (action.mcp?.deferLoading) {
72
+ lines.push("");
73
+ lines.push("> Deferred loading: must be explicitly loaded before use.");
74
+ }
75
+ lines.push("");
76
+ lines.push("### Parameters");
77
+ lines.push("");
78
+ lines.push(renderSchemaTable(action.input));
79
+ return lines.join(`
80
+ `);
81
+ }
82
+ function renderCliCommand(action) {
83
+ const name = displayName(action, "cli");
84
+ const desc = action.cli?.description ?? action.description;
85
+ const lines = [];
86
+ lines.push(`## ${name}`);
87
+ lines.push("");
88
+ if (desc) {
89
+ lines.push(desc);
90
+ }
91
+ if (action.cli?.aliases && action.cli.aliases.length > 0) {
92
+ lines.push("");
93
+ const aliasStr = action.cli.aliases.map((a) => `\`${a}\``).join(", ");
94
+ lines.push(`**Aliases:** ${aliasStr}`);
95
+ }
96
+ lines.push("");
97
+ lines.push("### Options");
98
+ lines.push("");
99
+ lines.push(renderCliOptionsTable(action.cli));
100
+ return lines.join(`
101
+ `);
102
+ }
103
+ function renderCliOptionsTable(cli) {
104
+ const options = cli?.options;
105
+ if (!options || options.length === 0) {
106
+ return "_No options._";
107
+ }
108
+ const lines = [];
109
+ lines.push("| Flag | Description | Default |");
110
+ lines.push("|------|-------------|---------|");
111
+ for (const opt of options) {
112
+ const defaultStr = opt.defaultValue !== undefined ? `\`${JSON.stringify(opt.defaultValue)}\`` : "\u2014";
113
+ lines.push(`| \`${escapeMarkdown(opt.flags)}\` | ${escapeMarkdown(opt.description)} | ${defaultStr} |`);
114
+ }
115
+ return lines.join(`
116
+ `);
117
+ }
118
+ function renderSchemaTable(schema) {
119
+ const properties = schema.properties;
120
+ if (!properties || Object.keys(properties).length === 0) {
121
+ return "_No parameters._";
122
+ }
123
+ const required = new Set(schema.required ?? []);
124
+ const lines = [];
125
+ lines.push("| Property | Type | Required | Description |");
126
+ lines.push("|----------|------|----------|-------------|");
127
+ for (const [key, prop] of Object.entries(properties)) {
128
+ const typeStr = formatType(prop);
129
+ const isRequired = required.has(key) ? "Yes" : "No";
130
+ const descParts = [];
131
+ if (prop.description) {
132
+ descParts.push(escapeMarkdown(prop.description));
133
+ }
134
+ if (prop.default !== undefined) {
135
+ descParts.push(`(default: \`${JSON.stringify(prop.default)}\`)`);
136
+ }
137
+ const desc = descParts.join(" ");
138
+ lines.push(`| \`${key}\` | ${typeStr} | ${isRequired} | ${desc} |`);
139
+ }
140
+ return lines.join(`
141
+ `);
142
+ }
143
+ function formatType(schema) {
144
+ if (schema.enum) {
145
+ return schema.enum.map((v) => `\`${JSON.stringify(v)}\``).join(" \\| ");
146
+ }
147
+ if (schema.anyOf) {
148
+ return schema.anyOf.map(formatType).join(" \\| ");
149
+ }
150
+ if (schema.oneOf) {
151
+ return schema.oneOf.map(formatType).join(" \\| ");
152
+ }
153
+ if (schema.type === "array") {
154
+ const items = schema.items;
155
+ if (items && !Array.isArray(items) && items.type) {
156
+ return `array of ${items.type}`;
157
+ }
158
+ return "array";
159
+ }
160
+ return schema.type ?? "unknown";
161
+ }
162
+ function escapeMarkdown(text) {
163
+ return text.replace(/\|/g, "\\|");
164
+ }
165
+ function slugify(text) {
166
+ return text.toLowerCase().replace(/[^\w\s-]/g, "").replace(/\s+/g, "-");
167
+ }
168
+ function displayName(action, surface) {
169
+ if (surface === "cli") {
170
+ const cli = action.cli;
171
+ if (!cli)
172
+ return action.id;
173
+ const group = cli.group;
174
+ const command = cli.command ?? action.id;
175
+ return group ? `${group} ${command}` : command;
176
+ }
177
+ return action.mcp?.tool ?? action.id;
178
+ }
179
+
180
+ export { formatManifestMarkdown };
@@ -0,0 +1,3 @@
1
+ import { GenerateSurfaceMapOptions, SurfaceMap, generateSurfaceMap, readSurfaceMap, resolveSnapshotPath, writeSurfaceMap } from "./shared/@outfitter/schema-wgnvy5zh";
2
+ import "./shared/@outfitter/schema-vzq7nzs3";
3
+ export { writeSurfaceMap, resolveSnapshotPath, readSurfaceMap, generateSurfaceMap, SurfaceMap, GenerateSurfaceMapOptions };
@@ -0,0 +1,14 @@
1
+ // @bun
2
+ import {
3
+ generateSurfaceMap,
4
+ readSurfaceMap,
5
+ resolveSnapshotPath,
6
+ writeSurfaceMap
7
+ } from "./shared/@outfitter/schema-cq7f2r5z.js";
8
+ import"./shared/@outfitter/schema-tnrxpckr.js";
9
+ export {
10
+ writeSurfaceMap,
11
+ resolveSnapshotPath,
12
+ readSurfaceMap,
13
+ generateSurfaceMap
14
+ };
package/package.json ADDED
@@ -0,0 +1,85 @@
1
+ {
2
+ "name": "@outfitter/schema",
3
+ "description": "Schema introspection, surface map generation, and drift detection for Outfitter",
4
+ "version": "0.2.0",
5
+ "type": "module",
6
+ "files": [
7
+ "dist"
8
+ ],
9
+ "module": "./dist/index.js",
10
+ "types": "./dist/index.d.ts",
11
+ "exports": {
12
+ ".": {
13
+ "import": {
14
+ "types": "./dist/index.d.ts",
15
+ "default": "./dist/index.js"
16
+ }
17
+ },
18
+ "./diff": {
19
+ "import": {
20
+ "types": "./dist/diff.d.ts",
21
+ "default": "./dist/diff.js"
22
+ }
23
+ },
24
+ "./manifest": {
25
+ "import": {
26
+ "types": "./dist/manifest.d.ts",
27
+ "default": "./dist/manifest.js"
28
+ }
29
+ },
30
+ "./markdown": {
31
+ "import": {
32
+ "types": "./dist/markdown.d.ts",
33
+ "default": "./dist/markdown.js"
34
+ }
35
+ },
36
+ "./package.json": "./package.json",
37
+ "./surface": {
38
+ "import": {
39
+ "types": "./dist/surface.d.ts",
40
+ "default": "./dist/surface.js"
41
+ }
42
+ }
43
+ },
44
+ "sideEffects": false,
45
+ "scripts": {
46
+ "build": "cd ../.. && bunup --filter @outfitter/schema",
47
+ "test": "bun test",
48
+ "test:watch": "bun test --watch",
49
+ "lint": "biome lint ./src",
50
+ "lint:fix": "biome lint --write ./src",
51
+ "typecheck": "tsc --noEmit",
52
+ "clean": "rm -rf dist"
53
+ },
54
+ "dependencies": {
55
+ "@outfitter/contracts": "workspace:*",
56
+ "@outfitter/types": "workspace:*"
57
+ },
58
+ "peerDependencies": {
59
+ "zod": "^4.3.5"
60
+ },
61
+ "devDependencies": {
62
+ "@types/bun": "^1.3.7",
63
+ "typescript": "^5.9.3"
64
+ },
65
+ "engines": {
66
+ "bun": ">=1.3.7"
67
+ },
68
+ "keywords": [
69
+ "outfitter",
70
+ "schema",
71
+ "manifest",
72
+ "surface-map",
73
+ "introspection",
74
+ "typescript"
75
+ ],
76
+ "license": "MIT",
77
+ "repository": {
78
+ "type": "git",
79
+ "url": "https://github.com/outfitter-dev/outfitter.git",
80
+ "directory": "packages/schema"
81
+ },
82
+ "publishConfig": {
83
+ "access": "public"
84
+ }
85
+ }