swagger-digest 0.0.0-stage → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.d.ts ADDED
@@ -0,0 +1 @@
1
+ #!/usr/bin/env node
package/dist/cli.js ADDED
@@ -0,0 +1,104 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ defaultFilename,
4
+ defaultOutputDirectory,
5
+ estimateTokens,
6
+ fetchOpenApiSpec,
7
+ transformOpenApiToMarkdown,
8
+ writeDigest
9
+ } from "./chunk-UZQM2SHL.js";
10
+
11
+ // src/cli.ts
12
+ import { input } from "@inquirer/prompts";
13
+ import chalk from "chalk";
14
+ import { Command } from "commander";
15
+ import ora from "ora";
16
+ function validateUrl(value) {
17
+ try {
18
+ const url = new URL(value);
19
+ if (url.protocol !== "http:" && url.protocol !== "https:") return "Use an http:// or https:// URL.";
20
+ return true;
21
+ } catch {
22
+ return "Enter a valid URL, including https://.";
23
+ }
24
+ }
25
+ function isInteractive() {
26
+ return Boolean(process.stdin.isTTY && process.stdout.isTTY);
27
+ }
28
+ async function main() {
29
+ const program = new Command();
30
+ program.name("swagger-digest").description("Convert a Swagger UI page or OpenAPI URL into compact, AI-friendly Markdown.").version("0.1.1").option("-u, --url <url>", "Swagger UI page URL or direct OpenAPI spec URL").option("-d, --out-dir <directory>", "Directory where the Markdown digest will be saved").option("-f, --filename <filename>", "Output filename (defaults to <api-title>-digest.md)").option("--no-browser", "Disable the optional Playwright headless-browser fallback").addHelpText(
31
+ "after",
32
+ "\nExamples:\n npx swagger-digest\n npx swagger-digest --url https://petstore.swagger.io/ --out-dir ./docs\n npx swagger-digest -u https://example.com/openapi.yaml -f payments-api.md"
33
+ );
34
+ program.parse(process.argv);
35
+ const options = program.opts();
36
+ if (!options.url && !isInteractive()) {
37
+ throw new Error("An interactive terminal or --url is required. Run `swagger-digest --help` for usage.");
38
+ }
39
+ const url = options.url ?? await input({
40
+ message: "Swagger UI or OpenAPI spec URL",
41
+ validate: validateUrl
42
+ });
43
+ const validUrl = validateUrl(url);
44
+ if (validUrl !== true) throw new Error(validUrl);
45
+ const outputDirectory = options.outDir ?? (isInteractive() ? await input({
46
+ message: "Destination directory",
47
+ default: defaultOutputDirectory(),
48
+ validate: (value) => value.trim() ? true : "Enter a destination directory."
49
+ }) : defaultOutputDirectory());
50
+ const spinner = ora({ text: "Fetching and parsing OpenAPI definition\u2026", color: "cyan" }).start();
51
+ let result;
52
+ try {
53
+ result = await fetchOpenApiSpec(url, { useBrowserFallback: options.browser !== false });
54
+ } catch (error) {
55
+ spinner.fail("Could not extract an OpenAPI definition");
56
+ throw error;
57
+ }
58
+ const title = typeof result.spec.info?.title === "string" ? result.spec.info.title : "API";
59
+ const markdown = transformOpenApiToMarkdown(result.spec, result.sourceUrl);
60
+ spinner.succeed(`Parsed ${title} (${result.retrievalMethod})`);
61
+ const filename = options.filename ?? (isInteractive() ? await input({
62
+ message: "Output filename",
63
+ default: defaultFilename(title),
64
+ validate: (value) => value.trim() ? true : "Enter a filename."
65
+ }) : defaultFilename(title));
66
+ const writeSpinner = ora({ text: "Writing Markdown digest\u2026", color: "cyan" }).start();
67
+ try {
68
+ const outputPath = await writeDigest({ markdown, directory: outputDirectory, filename });
69
+ writeSpinner.succeed(`Saved ${chalk.green(outputPath)}`);
70
+ const report = estimateTokens(markdown);
71
+ const endpointCount = Object.values(result.spec.paths ?? {}).reduce((count, pathItem) => {
72
+ if (!pathItem || typeof pathItem !== "object") return count;
73
+ const methods = ["get", "put", "post", "delete", "options", "head", "patch", "trace"];
74
+ return count + methods.filter((method) => pathItem[method] && typeof pathItem[method] === "object").length;
75
+ }, 0);
76
+ const modelCount = Object.keys(result.spec.components?.schemas ?? result.spec.definitions ?? {}).length;
77
+ console.log("");
78
+ console.log(chalk.bold("Digest summary:"));
79
+ console.log(` ${chalk.dim("\u2022")} Endpoints: ${chalk.cyan(endpointCount)} operations`);
80
+ if (modelCount > 0) {
81
+ console.log(` ${chalk.dim("\u2022")} Data models: ${chalk.cyan(modelCount)} schemas`);
82
+ }
83
+ console.log(` ${chalk.dim("\u2022")} Characters: ${chalk.dim(report.characters.toLocaleString())}`);
84
+ console.log("");
85
+ console.log(chalk.bold("Estimated token usage (5 Frontier Models):"));
86
+ for (const model of Object.values(report.models)) {
87
+ const windowLabel = model.contextWindow >= 1e6 ? `${model.contextWindow / 1e6}M` : `${model.contextWindow / 1e3}k`;
88
+ const paddedName = `${model.name}:`.padEnd(30, " ");
89
+ const tokenStr = `~${model.tokens.toLocaleString()} tokens`.padEnd(16, " ");
90
+ const percentStr = chalk.dim(`(${model.contextPercent}% of ${windowLabel} context)`);
91
+ console.log(` ${chalk.dim("\u2022")} ${paddedName} ${chalk.green(tokenStr)} ${percentStr}`);
92
+ }
93
+ console.log("");
94
+ } catch (error) {
95
+ writeSpinner.fail("Could not write digest");
96
+ throw error;
97
+ }
98
+ }
99
+ main().catch((error) => {
100
+ const message = error instanceof Error ? error.message : String(error);
101
+ console.error(chalk.red(`
102
+ Error: ${message}`));
103
+ process.exitCode = 1;
104
+ });
@@ -0,0 +1,55 @@
1
+ type JsonObject = Record<string, any>;
2
+ interface FetchOptions {
3
+ timeoutMs?: number;
4
+ useBrowserFallback?: boolean;
5
+ }
6
+ interface FetchResult {
7
+ spec: JsonObject;
8
+ sourceUrl: string;
9
+ retrievalMethod: 'direct-spec' | 'html-config' | 'inline-spec' | 'fallback-path' | 'headless-browser';
10
+ }
11
+
12
+ declare function isOpenApiSpec(value: unknown): value is JsonObject;
13
+ /** Parse JSON or YAML and reject arbitrary documents that are not OpenAPI/Swagger specs. */
14
+ declare function parseSpecText(text: string, source?: string): JsonObject;
15
+ /** Resolve a Swagger UI page or a direct OpenAPI JSON/YAML URL into a parsed spec. */
16
+ declare function fetchOpenApiSpec(input: string, options?: FetchOptions): Promise<FetchResult>;
17
+
18
+ declare function toTypeScriptIdentifier(value: string): string;
19
+ /** Convert an OpenAPI 2.0, 3.0, or 3.1 document to a compact Markdown digest. */
20
+ declare function transformOpenApiToMarkdown(spec: JsonObject, sourceUrl?: string): string;
21
+
22
+ interface ModelTokenEstimate {
23
+ name: string;
24
+ tokens: number;
25
+ contextWindow: number;
26
+ contextPercent: number;
27
+ }
28
+ interface DigestTokenReport {
29
+ characters: number;
30
+ models: {
31
+ claude: ModelTokenEstimate;
32
+ openai: ModelTokenEstimate;
33
+ gemini: ModelTokenEstimate;
34
+ deepseek: ModelTokenEstimate;
35
+ llama: ModelTokenEstimate;
36
+ };
37
+ }
38
+ /**
39
+ * Estimates token counts for Markdown/code across 5 frontier model tokenizers.
40
+ * Accurately models differences between cl100k/o200k, Claude BPE, SentencePiece 256k, and 128k BPE.
41
+ */
42
+ declare function estimateTokens(text: string): DigestTokenReport;
43
+
44
+ declare function defaultOutputDirectory(): string;
45
+ declare function defaultFilename(apiTitle?: string): string;
46
+ declare function resolveOutputDirectory(directory: string, cwd?: string): string;
47
+ declare function normalizeFilename(filename: string, fallback?: string): string;
48
+ declare function writeDigest(options: {
49
+ markdown: string;
50
+ directory: string;
51
+ filename: string;
52
+ }): Promise<string>;
53
+
54
+ export { defaultFilename, defaultOutputDirectory, estimateTokens, fetchOpenApiSpec, isOpenApiSpec, normalizeFilename, parseSpecText, resolveOutputDirectory, toTypeScriptIdentifier, transformOpenApiToMarkdown, writeDigest };
55
+ export type { DigestTokenReport, FetchOptions, FetchResult, JsonObject, ModelTokenEstimate };
package/dist/index.js ADDED
@@ -0,0 +1,26 @@
1
+ import {
2
+ defaultFilename,
3
+ defaultOutputDirectory,
4
+ estimateTokens,
5
+ fetchOpenApiSpec,
6
+ isOpenApiSpec,
7
+ normalizeFilename,
8
+ parseSpecText,
9
+ resolveOutputDirectory,
10
+ toTypeScriptIdentifier,
11
+ transformOpenApiToMarkdown,
12
+ writeDigest
13
+ } from "./chunk-UZQM2SHL.js";
14
+ export {
15
+ defaultFilename,
16
+ defaultOutputDirectory,
17
+ estimateTokens,
18
+ fetchOpenApiSpec,
19
+ isOpenApiSpec,
20
+ normalizeFilename,
21
+ parseSpecText,
22
+ resolveOutputDirectory,
23
+ toTypeScriptIdentifier,
24
+ transformOpenApiToMarkdown,
25
+ writeDigest
26
+ };
package/package.json CHANGED
@@ -1,6 +1,86 @@
1
1
  {
2
2
  "name": "swagger-digest",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.1.1",
4
+ "description": "Interactive CLI that converts Swagger UI pages and OpenAPI specs into compact Markdown for AI coding agents.",
5
+ "type": "module",
6
+ "main": "dist/index.js",
7
+ "types": "dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "import": "./dist/index.js"
12
+ }
13
+ },
14
+ "bin": {
15
+ "swagger-digest": "dist/cli.js"
16
+ },
17
+ "author": "VladLegkowski <VladLegkowski@users.noreply.github.com>",
18
+ "repository": {
19
+ "type": "git",
20
+ "url": "git+https://github.com/VladLegkowski/swagger-digest.git"
21
+ },
22
+ "homepage": "https://github.com/VladLegkowski/swagger-digest#readme",
23
+ "bugs": {
24
+ "url": "https://github.com/VladLegkowski/swagger-digest/issues"
25
+ },
26
+ "sideEffects": false,
27
+ "files": [
28
+ "dist",
29
+ "README.md",
30
+ "LICENSE"
31
+ ],
32
+ "scripts": {
33
+ "build": "tsup src/index.ts src/cli.ts --format esm --dts --clean",
34
+ "dev": "tsx src/cli.ts",
35
+ "start": "node dist/cli.js",
36
+ "typecheck": "tsc --noEmit",
37
+ "test": "npm run typecheck && tsx --test test/*.test.ts",
38
+ "check:exports": "publint",
39
+ "lint": "eslint .",
40
+ "prepare": "husky",
41
+ "prepack": "npm run build",
42
+ "prepublishOnly": "npm run build && npm test && npm run lint"
43
+ },
44
+ "engines": {
45
+ "node": ">=20.0.0"
46
+ },
47
+ "dependencies": {
48
+ "@inquirer/prompts": "^7.3.2",
49
+ "chalk": "^5.4.1",
50
+ "cheerio": "^1.0.0",
51
+ "commander": "^13.1.0",
52
+ "ora": "^8.2.0",
53
+ "yaml": "^2.7.0"
54
+ },
55
+ "devDependencies": {
56
+ "@types/node": "^22.15.0",
57
+ "eslint": "^9.27.0",
58
+ "husky": "^9.1.7",
59
+ "lint-staged": "^17.6.0",
60
+ "prettier": "^3.9.10",
61
+ "publint": "^0.3.25",
62
+ "tsup": "^9.0.0",
63
+ "tsx": "^4.19.4",
64
+ "typescript": "^5.8.3",
65
+ "typescript-eslint": "^8.32.0"
66
+ },
67
+ "peerDependencies": {
68
+ "playwright": "^1.50.0"
69
+ },
70
+ "peerDependenciesMeta": {
71
+ "playwright": {
72
+ "optional": true
73
+ }
74
+ },
75
+ "keywords": [
76
+ "swagger",
77
+ "openapi",
78
+ "cli",
79
+ "ai",
80
+ "llm",
81
+ "markdown",
82
+ "docs",
83
+ "ai-coding-agent"
84
+ ],
85
+ "license": "MIT"
86
+ }