trickle-cli 0.1.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.
Files changed (125) hide show
  1. package/dist/api-client.d.ts +208 -0
  2. package/dist/api-client.js +237 -0
  3. package/dist/commands/annotate.d.ts +6 -0
  4. package/dist/commands/annotate.js +433 -0
  5. package/dist/commands/audit.d.ts +7 -0
  6. package/dist/commands/audit.js +82 -0
  7. package/dist/commands/auto.d.ts +8 -0
  8. package/dist/commands/auto.js +268 -0
  9. package/dist/commands/capture.d.ts +14 -0
  10. package/dist/commands/capture.js +271 -0
  11. package/dist/commands/check.d.ts +6 -0
  12. package/dist/commands/check.js +408 -0
  13. package/dist/commands/codegen.d.ts +21 -0
  14. package/dist/commands/codegen.js +129 -0
  15. package/dist/commands/coverage.d.ts +13 -0
  16. package/dist/commands/coverage.js +126 -0
  17. package/dist/commands/dashboard.d.ts +1 -0
  18. package/dist/commands/dashboard.js +83 -0
  19. package/dist/commands/dev.d.ts +14 -0
  20. package/dist/commands/dev.js +319 -0
  21. package/dist/commands/diff.d.ts +7 -0
  22. package/dist/commands/diff.js +79 -0
  23. package/dist/commands/docs.d.ts +13 -0
  24. package/dist/commands/docs.js +383 -0
  25. package/dist/commands/errors.d.ts +7 -0
  26. package/dist/commands/errors.js +180 -0
  27. package/dist/commands/export.d.ts +18 -0
  28. package/dist/commands/export.js +238 -0
  29. package/dist/commands/functions.d.ts +6 -0
  30. package/dist/commands/functions.js +71 -0
  31. package/dist/commands/infer.d.ts +14 -0
  32. package/dist/commands/infer.js +275 -0
  33. package/dist/commands/init.d.ts +5 -0
  34. package/dist/commands/init.js +395 -0
  35. package/dist/commands/mock.d.ts +5 -0
  36. package/dist/commands/mock.js +232 -0
  37. package/dist/commands/openapi.d.ts +8 -0
  38. package/dist/commands/openapi.js +82 -0
  39. package/dist/commands/overview.d.ts +11 -0
  40. package/dist/commands/overview.js +266 -0
  41. package/dist/commands/pack.d.ts +11 -0
  42. package/dist/commands/pack.js +133 -0
  43. package/dist/commands/proxy.d.ts +13 -0
  44. package/dist/commands/proxy.js +312 -0
  45. package/dist/commands/replay.d.ts +14 -0
  46. package/dist/commands/replay.js +289 -0
  47. package/dist/commands/run.d.ts +17 -0
  48. package/dist/commands/run.js +997 -0
  49. package/dist/commands/sample.d.ts +13 -0
  50. package/dist/commands/sample.js +260 -0
  51. package/dist/commands/search.d.ts +5 -0
  52. package/dist/commands/search.js +80 -0
  53. package/dist/commands/stubs.d.ts +6 -0
  54. package/dist/commands/stubs.js +187 -0
  55. package/dist/commands/tail.d.ts +4 -0
  56. package/dist/commands/tail.js +76 -0
  57. package/dist/commands/test-gen.d.ts +13 -0
  58. package/dist/commands/test-gen.js +237 -0
  59. package/dist/commands/trace.d.ts +14 -0
  60. package/dist/commands/trace.js +417 -0
  61. package/dist/commands/types.d.ts +7 -0
  62. package/dist/commands/types.js +128 -0
  63. package/dist/commands/unpack.d.ts +11 -0
  64. package/dist/commands/unpack.js +166 -0
  65. package/dist/commands/validate.d.ts +13 -0
  66. package/dist/commands/validate.js +310 -0
  67. package/dist/commands/watch.d.ts +9 -0
  68. package/dist/commands/watch.js +267 -0
  69. package/dist/config.d.ts +1 -0
  70. package/dist/config.js +66 -0
  71. package/dist/formatters/diff-formatter.d.ts +5 -0
  72. package/dist/formatters/diff-formatter.js +43 -0
  73. package/dist/formatters/type-formatter.d.ts +22 -0
  74. package/dist/formatters/type-formatter.js +135 -0
  75. package/dist/index.d.ts +2 -0
  76. package/dist/index.js +419 -0
  77. package/dist/local-codegen.d.ts +22 -0
  78. package/dist/local-codegen.js +762 -0
  79. package/dist/ui/badges.d.ts +16 -0
  80. package/dist/ui/badges.js +71 -0
  81. package/dist/ui/helpers.d.ts +13 -0
  82. package/dist/ui/helpers.js +85 -0
  83. package/package.json +23 -0
  84. package/src/api-client.ts +407 -0
  85. package/src/commands/annotate.ts +450 -0
  86. package/src/commands/audit.ts +103 -0
  87. package/src/commands/auto.ts +268 -0
  88. package/src/commands/capture.ts +257 -0
  89. package/src/commands/check.ts +437 -0
  90. package/src/commands/codegen.ts +128 -0
  91. package/src/commands/coverage.ts +170 -0
  92. package/src/commands/dashboard.ts +46 -0
  93. package/src/commands/dev.ts +323 -0
  94. package/src/commands/diff.ts +99 -0
  95. package/src/commands/docs.ts +392 -0
  96. package/src/commands/errors.ts +205 -0
  97. package/src/commands/export.ts +287 -0
  98. package/src/commands/functions.ts +81 -0
  99. package/src/commands/infer.ts +260 -0
  100. package/src/commands/init.ts +419 -0
  101. package/src/commands/mock.ts +220 -0
  102. package/src/commands/openapi.ts +53 -0
  103. package/src/commands/overview.ts +310 -0
  104. package/src/commands/pack.ts +139 -0
  105. package/src/commands/proxy.ts +314 -0
  106. package/src/commands/replay.ts +356 -0
  107. package/src/commands/run.ts +1190 -0
  108. package/src/commands/sample.ts +259 -0
  109. package/src/commands/search.ts +107 -0
  110. package/src/commands/stubs.ts +211 -0
  111. package/src/commands/tail.ts +94 -0
  112. package/src/commands/test-gen.ts +236 -0
  113. package/src/commands/trace.ts +440 -0
  114. package/src/commands/types.ts +161 -0
  115. package/src/commands/unpack.ts +179 -0
  116. package/src/commands/validate.ts +368 -0
  117. package/src/commands/watch.ts +277 -0
  118. package/src/config.ts +38 -0
  119. package/src/formatters/diff-formatter.ts +51 -0
  120. package/src/formatters/type-formatter.ts +161 -0
  121. package/src/index.ts +454 -0
  122. package/src/local-codegen.ts +859 -0
  123. package/src/ui/badges.ts +66 -0
  124. package/src/ui/helpers.ts +80 -0
  125. package/tsconfig.json +8 -0
@@ -0,0 +1,238 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ var __importDefault = (this && this.__importDefault) || function (mod) {
36
+ return (mod && mod.__esModule) ? mod : { "default": mod };
37
+ };
38
+ Object.defineProperty(exports, "__esModule", { value: true });
39
+ exports.exportCommand = exportCommand;
40
+ const fs = __importStar(require("fs"));
41
+ const path = __importStar(require("path"));
42
+ const chalk_1 = __importDefault(require("chalk"));
43
+ const api_client_1 = require("../api-client");
44
+ /**
45
+ * `trickle export` — Generate all output formats into a directory at once.
46
+ *
47
+ * Creates a complete `.trickle/` directory with:
48
+ * - types.d.ts — TypeScript type declarations
49
+ * - api-client.ts — Typed fetch-based API client
50
+ * - handlers.d.ts — Express handler type aliases
51
+ * - schemas.ts — Zod validation schemas
52
+ * - hooks.ts — TanStack React Query hooks
53
+ * - guards.ts — Runtime type guard functions
54
+ * - openapi.json — OpenAPI 3.0 specification
55
+ * - api.test.ts — Generated API test scaffolds
56
+ */
57
+ async function exportCommand(opts) {
58
+ const outDir = path.resolve(opts.dir || ".trickle");
59
+ // Ensure directory exists
60
+ if (!fs.existsSync(outDir)) {
61
+ fs.mkdirSync(outDir, { recursive: true });
62
+ }
63
+ console.log("");
64
+ console.log(chalk_1.default.bold(" trickle export"));
65
+ console.log(chalk_1.default.gray(" " + "─".repeat(50)));
66
+ console.log(chalk_1.default.gray(` Output directory: ${outDir}`));
67
+ if (opts.env) {
68
+ console.log(chalk_1.default.gray(` Environment: ${opts.env}`));
69
+ }
70
+ console.log(chalk_1.default.gray(" " + "─".repeat(50)));
71
+ console.log("");
72
+ const results = [];
73
+ const queryOpts = { env: opts.env };
74
+ // 1. TypeScript types
75
+ results.push(await generateFile(path.join(outDir, "types.d.ts"), "TypeScript types", () => (0, api_client_1.fetchCodegen)({ ...queryOpts }).then((r) => r.types), countInterfaces));
76
+ // 2. API client
77
+ results.push(await generateFile(path.join(outDir, "api-client.ts"), "Typed API client", () => (0, api_client_1.fetchCodegen)({ ...queryOpts, format: "client" }).then((r) => r.types), countFunctions));
78
+ // 3. Express handler types
79
+ results.push(await generateFile(path.join(outDir, "handlers.d.ts"), "Express handler types", () => (0, api_client_1.fetchCodegen)({ ...queryOpts, format: "handlers" }).then((r) => r.types), countHandlers));
80
+ // 4. Zod schemas
81
+ results.push(await generateFile(path.join(outDir, "schemas.ts"), "Zod schemas", () => (0, api_client_1.fetchCodegen)({ ...queryOpts, format: "zod" }).then((r) => r.types), countSchemas));
82
+ // 5. React Query hooks
83
+ results.push(await generateFile(path.join(outDir, "hooks.ts"), "React Query hooks", () => (0, api_client_1.fetchCodegen)({ ...queryOpts, format: "react-query" }).then((r) => r.types), countHooks));
84
+ // 6. Type guards
85
+ results.push(await generateFile(path.join(outDir, "guards.ts"), "Type guards", () => (0, api_client_1.fetchCodegen)({ ...queryOpts, format: "guards" }).then((r) => r.types), countGuards));
86
+ // OpenAPI spec
87
+ results.push(await generateFile(path.join(outDir, "openapi.json"), "OpenAPI 3.0 spec", async () => {
88
+ const spec = await (0, api_client_1.fetchOpenApiSpec)({ env: opts.env });
89
+ return JSON.stringify(spec, null, 2);
90
+ }, countPaths));
91
+ // API tests
92
+ results.push(await generateFile(path.join(outDir, "api.test.ts"), "API test scaffolds", async () => {
93
+ const { routes } = await (0, api_client_1.fetchMockConfig)();
94
+ if (routes.length === 0)
95
+ return null;
96
+ // Use the testGenCommand's internal logic by fetching via codegen format?
97
+ // Actually, let's generate a simple version directly
98
+ return generateTestContent(routes);
99
+ }, countTests));
100
+ // Summary
101
+ console.log("");
102
+ const successCount = results.filter((r) => r.ok).length;
103
+ const skipCount = results.filter((r) => !r.ok).length;
104
+ for (const r of results) {
105
+ if (r.ok) {
106
+ const countStr = r.count ? chalk_1.default.gray(` (${r.count})`) : "";
107
+ console.log(chalk_1.default.green(" ✓ ") + chalk_1.default.bold(r.file) + countStr);
108
+ }
109
+ else {
110
+ console.log(chalk_1.default.yellow(" ─ ") + chalk_1.default.gray(r.file) + chalk_1.default.gray(" (skipped — no data)"));
111
+ }
112
+ }
113
+ console.log("");
114
+ if (successCount > 0) {
115
+ console.log(chalk_1.default.green(` ${successCount} files generated`) + (skipCount > 0 ? chalk_1.default.gray(`, ${skipCount} skipped`) : ""));
116
+ }
117
+ else {
118
+ console.log(chalk_1.default.yellow(" No files generated — instrument your app and make some requests first."));
119
+ }
120
+ console.log("");
121
+ }
122
+ async function generateFile(filePath, label, generator, counter) {
123
+ const fileName = path.basename(filePath);
124
+ try {
125
+ const content = await generator();
126
+ if (!content || content.includes("No functions found") || content.includes("No API routes found") || content.includes("No observations")) {
127
+ return { file: fileName, label, ok: false };
128
+ }
129
+ fs.writeFileSync(filePath, content, "utf-8");
130
+ const count = counter(content);
131
+ return { file: fileName, label, ok: true, count };
132
+ }
133
+ catch {
134
+ return { file: fileName, label, ok: false };
135
+ }
136
+ }
137
+ function countInterfaces(content) {
138
+ const count = (content.match(/export (interface|type) /g) || []).length;
139
+ return count > 0 ? `${count} types` : undefined;
140
+ }
141
+ function countFunctions(content) {
142
+ if (content.includes("createTrickleClient"))
143
+ return "client factory";
144
+ return undefined;
145
+ }
146
+ function countHandlers(content) {
147
+ const count = (content.match(/export type \w+Handler/g) || []).length;
148
+ return count > 0 ? `${count} handlers` : undefined;
149
+ }
150
+ function countSchemas(content) {
151
+ const count = (content.match(/Schema = /g) || []).length;
152
+ return count > 0 ? `${count} schemas` : undefined;
153
+ }
154
+ function countHooks(content) {
155
+ const count = (content.match(/export function use\w+/g) || []).length;
156
+ return count > 0 ? `${count} hooks` : undefined;
157
+ }
158
+ function countPaths(content) {
159
+ try {
160
+ const spec = JSON.parse(content);
161
+ const paths = Object.keys(spec.paths || {}).length;
162
+ return paths > 0 ? `${paths} paths` : undefined;
163
+ }
164
+ catch {
165
+ return undefined;
166
+ }
167
+ }
168
+ function countTests(content) {
169
+ const count = (content.match(/it\("/g) || []).length;
170
+ return count > 0 ? `${count} tests` : undefined;
171
+ }
172
+ function countGuards(content) {
173
+ const count = (content.match(/export function is\w+/g) || []).length;
174
+ return count > 0 ? `${count} guards` : undefined;
175
+ }
176
+ function generateTestContent(routes) {
177
+ const lines = [];
178
+ lines.push("// Auto-generated API tests by trickle");
179
+ lines.push(`// Generated at ${new Date().toISOString()}`);
180
+ lines.push("// Do not edit manually — re-run `trickle export` to update");
181
+ lines.push("");
182
+ lines.push('import { describe, it, expect } from "vitest";');
183
+ lines.push("");
184
+ lines.push('const BASE_URL = process.env.TEST_API_URL || "http://localhost:3000";');
185
+ lines.push("");
186
+ // Group by resource
187
+ const groups = {};
188
+ for (const r of routes) {
189
+ const parts = r.path.split("/").filter(Boolean);
190
+ const resource = parts[0] === "api" && parts.length >= 2 ? `/api/${parts[1]}` : `/${parts[0] || "root"}`;
191
+ if (!groups[resource])
192
+ groups[resource] = [];
193
+ groups[resource].push(r);
194
+ }
195
+ for (const [resource, resourceRoutes] of Object.entries(groups)) {
196
+ lines.push(`describe("${resource}", () => {`);
197
+ for (const route of resourceRoutes) {
198
+ const hasBody = ["POST", "PUT", "PATCH"].includes(route.method);
199
+ const fetchPath = route.path.replace(/:(\w+)/g, "test-$1");
200
+ lines.push(` it("${route.method} ${route.path} — returns expected shape", async () => {`);
201
+ lines.push(` const res = await fetch(\`\${BASE_URL}${fetchPath}\`, {`);
202
+ lines.push(` method: "${route.method}",`);
203
+ if (hasBody && route.sampleInput) {
204
+ const body = typeof route.sampleInput === "object" && route.sampleInput !== null
205
+ ? route.sampleInput.body || route.sampleInput
206
+ : route.sampleInput;
207
+ if (body && typeof body === "object" && Object.keys(body).length > 0) {
208
+ lines.push(` headers: { "Content-Type": "application/json" },`);
209
+ lines.push(` body: JSON.stringify(${JSON.stringify(body)}),`);
210
+ }
211
+ }
212
+ lines.push(" });");
213
+ lines.push(" expect(res.ok).toBe(true);");
214
+ if (route.sampleOutput && typeof route.sampleOutput === "object") {
215
+ lines.push(" const body = await res.json();");
216
+ for (const [key, value] of Object.entries(route.sampleOutput)) {
217
+ if (Array.isArray(value)) {
218
+ lines.push(` expect(Array.isArray(body.${key})).toBe(true);`);
219
+ }
220
+ else if (typeof value === "string") {
221
+ lines.push(` expect(typeof body.${key}).toBe("string");`);
222
+ }
223
+ else if (typeof value === "number") {
224
+ lines.push(` expect(typeof body.${key}).toBe("number");`);
225
+ }
226
+ else if (typeof value === "boolean") {
227
+ lines.push(` expect(typeof body.${key}).toBe("boolean");`);
228
+ }
229
+ }
230
+ }
231
+ lines.push(" });");
232
+ lines.push("");
233
+ }
234
+ lines.push("});");
235
+ lines.push("");
236
+ }
237
+ return lines.join("\n").trimEnd() + "\n";
238
+ }
@@ -0,0 +1,6 @@
1
+ export interface FunctionsOptions {
2
+ env?: string;
3
+ lang?: string;
4
+ search?: string;
5
+ }
6
+ export declare function functionsCommand(opts: FunctionsOptions): Promise<void>;
@@ -0,0 +1,71 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.functionsCommand = functionsCommand;
7
+ const chalk_1 = __importDefault(require("chalk"));
8
+ const cli_table3_1 = __importDefault(require("cli-table3"));
9
+ const api_client_1 = require("../api-client");
10
+ const badges_1 = require("../ui/badges");
11
+ async function functionsCommand(opts) {
12
+ const result = await (0, api_client_1.listFunctions)({
13
+ env: opts.env,
14
+ language: opts.lang,
15
+ search: opts.search,
16
+ });
17
+ const { functions } = result;
18
+ if (functions.length === 0) {
19
+ console.log(chalk_1.default.yellow("\n No functions found.\n"));
20
+ if (opts.env || opts.lang || opts.search) {
21
+ console.log(chalk_1.default.gray(" Try adjusting your filters.\n"));
22
+ }
23
+ return;
24
+ }
25
+ console.log("");
26
+ const table = new cli_table3_1.default({
27
+ head: [
28
+ chalk_1.default.cyan.bold("Name"),
29
+ chalk_1.default.cyan.bold("Module"),
30
+ chalk_1.default.cyan.bold("Language"),
31
+ chalk_1.default.cyan.bold("Environment"),
32
+ chalk_1.default.cyan.bold("Last Seen"),
33
+ ],
34
+ style: {
35
+ head: [],
36
+ border: ["gray"],
37
+ },
38
+ chars: {
39
+ top: "─",
40
+ "top-mid": "┬",
41
+ "top-left": "┌",
42
+ "top-right": "┐",
43
+ bottom: "─",
44
+ "bottom-mid": "┴",
45
+ "bottom-left": "└",
46
+ "bottom-right": "┘",
47
+ left: "│",
48
+ "left-mid": "├",
49
+ mid: "─",
50
+ "mid-mid": "┼",
51
+ right: "│",
52
+ "right-mid": "┤",
53
+ middle: "│",
54
+ },
55
+ });
56
+ for (const fn of functions) {
57
+ table.push([
58
+ chalk_1.default.white.bold(fn.function_name),
59
+ chalk_1.default.gray(fn.module),
60
+ (0, badges_1.langBadge)(fn.language),
61
+ (0, badges_1.envBadge)(fn.environment),
62
+ (0, badges_1.timeBadge)(fn.last_seen_at),
63
+ ]);
64
+ }
65
+ console.log(table.toString());
66
+ console.log(chalk_1.default.gray(`\n Showing ${chalk_1.default.white.bold(String(functions.length))} functions`) +
67
+ (result.total > functions.length
68
+ ? chalk_1.default.gray(` of ${result.total} total`)
69
+ : "") +
70
+ "\n");
71
+ }
@@ -0,0 +1,14 @@
1
+ export interface InferOptions {
2
+ name: string;
3
+ env?: string;
4
+ module?: string;
5
+ requestBody?: string;
6
+ }
7
+ /**
8
+ * `trickle infer <file>` — Infer types from a JSON file or stdin and store them.
9
+ *
10
+ * Reads JSON from a file (or stdin if file is "-" or omitted with piped input),
11
+ * infers TypeNode from the data, and sends the observation to the trickle backend.
12
+ * Works offline with saved API responses, test fixtures, or piped command output.
13
+ */
14
+ export declare function inferCommand(file: string | undefined, opts: InferOptions): Promise<void>;
@@ -0,0 +1,275 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ var __importDefault = (this && this.__importDefault) || function (mod) {
36
+ return (mod && mod.__esModule) ? mod : { "default": mod };
37
+ };
38
+ Object.defineProperty(exports, "__esModule", { value: true });
39
+ exports.inferCommand = inferCommand;
40
+ const fs = __importStar(require("fs"));
41
+ const crypto = __importStar(require("crypto"));
42
+ const chalk_1 = __importDefault(require("chalk"));
43
+ const config_1 = require("../config");
44
+ /**
45
+ * `trickle infer <file>` — Infer types from a JSON file or stdin and store them.
46
+ *
47
+ * Reads JSON from a file (or stdin if file is "-" or omitted with piped input),
48
+ * infers TypeNode from the data, and sends the observation to the trickle backend.
49
+ * Works offline with saved API responses, test fixtures, or piped command output.
50
+ */
51
+ async function inferCommand(file, opts) {
52
+ const backendUrl = (0, config_1.getBackendUrl)();
53
+ // Check backend connectivity
54
+ try {
55
+ const res = await fetch(`${backendUrl}/api/health`, { signal: AbortSignal.timeout(3000) });
56
+ if (!res.ok)
57
+ throw new Error("not ok");
58
+ }
59
+ catch {
60
+ console.error(chalk_1.default.red(`\n Cannot reach trickle backend at ${chalk_1.default.bold(backendUrl)}\n`));
61
+ process.exit(1);
62
+ }
63
+ // Read JSON input
64
+ let jsonText;
65
+ let sourceName;
66
+ if (!file || file === "-") {
67
+ // Read from stdin
68
+ if (process.stdin.isTTY) {
69
+ console.error(chalk_1.default.red("\n No input provided."));
70
+ console.error(chalk_1.default.gray(" Pipe JSON via stdin or provide a file path:\n"));
71
+ console.error(chalk_1.default.gray(' echo \'{"key":"value"}\' | trickle infer --name "GET /api/data"'));
72
+ console.error(chalk_1.default.gray(' trickle infer response.json --name "GET /api/data"\n'));
73
+ process.exit(1);
74
+ }
75
+ jsonText = await readStdin();
76
+ sourceName = "stdin";
77
+ }
78
+ else {
79
+ // Read from file
80
+ if (!fs.existsSync(file)) {
81
+ console.error(chalk_1.default.red(`\n File not found: ${file}\n`));
82
+ process.exit(1);
83
+ }
84
+ jsonText = fs.readFileSync(file, "utf-8");
85
+ sourceName = file;
86
+ }
87
+ // Parse JSON
88
+ let jsonData;
89
+ try {
90
+ jsonData = JSON.parse(jsonText.trim());
91
+ }
92
+ catch {
93
+ console.error(chalk_1.default.red("\n Input is not valid JSON.\n"));
94
+ process.exit(1);
95
+ }
96
+ // Build type observations
97
+ const functionName = opts.name;
98
+ const returnType = jsonToTypeNode(jsonData);
99
+ const argsProperties = {};
100
+ // Parse request body example if provided
101
+ if (opts.requestBody) {
102
+ try {
103
+ const reqJson = JSON.parse(opts.requestBody);
104
+ argsProperties.body = jsonToTypeNode(reqJson);
105
+ }
106
+ catch {
107
+ console.error(chalk_1.default.red("\n --request-body is not valid JSON.\n"));
108
+ process.exit(1);
109
+ }
110
+ }
111
+ const argsType = Object.keys(argsProperties).length > 0
112
+ ? { kind: "object", properties: argsProperties }
113
+ : { kind: "object", properties: {} };
114
+ const typeHash = computeTypeHash(argsType, returnType);
115
+ const payload = {
116
+ functionName,
117
+ module: opts.module || "infer",
118
+ language: "js",
119
+ environment: opts.env || "development",
120
+ typeHash,
121
+ argsType,
122
+ returnType,
123
+ sampleInput: Object.keys(argsProperties).length > 0 ? argsProperties : undefined,
124
+ sampleOutput: jsonData,
125
+ };
126
+ // Send to backend
127
+ try {
128
+ const res = await fetch(`${backendUrl}/api/ingest`, {
129
+ method: "POST",
130
+ headers: { "Content-Type": "application/json" },
131
+ body: JSON.stringify(payload),
132
+ signal: AbortSignal.timeout(5000),
133
+ });
134
+ if (!res.ok)
135
+ throw new Error(`HTTP ${res.status}`);
136
+ }
137
+ catch (err) {
138
+ const msg = err instanceof Error ? err.message : "Unknown error";
139
+ console.error(chalk_1.default.red(`\n Failed to send types to backend: ${msg}\n`));
140
+ process.exit(1);
141
+ }
142
+ console.log("");
143
+ console.log(chalk_1.default.bold(" trickle infer"));
144
+ console.log(chalk_1.default.gray(" " + "─".repeat(50)));
145
+ console.log(chalk_1.default.gray(` Source: `) + chalk_1.default.white(sourceName));
146
+ console.log(chalk_1.default.gray(` Name: `) + chalk_1.default.white(functionName));
147
+ console.log(chalk_1.default.gray(` Backend: `) + chalk_1.default.white(backendUrl));
148
+ console.log(chalk_1.default.gray(" " + "─".repeat(50)));
149
+ console.log(chalk_1.default.green(" Types inferred and stored successfully!"));
150
+ // Show a preview of the inferred shape
151
+ const shape = describeShape(returnType, 1);
152
+ console.log("");
153
+ console.log(chalk_1.default.gray(" Inferred shape:"));
154
+ for (const line of shape) {
155
+ console.log(chalk_1.default.gray(" ") + line);
156
+ }
157
+ if (opts.requestBody) {
158
+ const reqShape = describeShape(argsProperties.body, 1);
159
+ console.log("");
160
+ console.log(chalk_1.default.gray(" Request body shape:"));
161
+ for (const line of reqShape) {
162
+ console.log(chalk_1.default.gray(" ") + line);
163
+ }
164
+ }
165
+ console.log("");
166
+ console.log(chalk_1.default.gray(" Run ") + chalk_1.default.white("trickle codegen") + chalk_1.default.gray(" to generate type definitions."));
167
+ console.log("");
168
+ }
169
+ function readStdin() {
170
+ return new Promise((resolve, reject) => {
171
+ let data = "";
172
+ process.stdin.setEncoding("utf-8");
173
+ process.stdin.on("data", (chunk) => { data += chunk; });
174
+ process.stdin.on("end", () => resolve(data));
175
+ process.stdin.on("error", reject);
176
+ // Timeout after 10 seconds
177
+ setTimeout(() => {
178
+ if (data.length === 0) {
179
+ reject(new Error("No input received from stdin"));
180
+ }
181
+ else {
182
+ resolve(data);
183
+ }
184
+ }, 10000);
185
+ });
186
+ }
187
+ function jsonToTypeNode(value) {
188
+ if (value === null)
189
+ return { kind: "primitive", name: "null" };
190
+ if (value === undefined)
191
+ return { kind: "primitive", name: "undefined" };
192
+ switch (typeof value) {
193
+ case "string": return { kind: "primitive", name: "string" };
194
+ case "number": return { kind: "primitive", name: "number" };
195
+ case "boolean": return { kind: "primitive", name: "boolean" };
196
+ }
197
+ if (Array.isArray(value)) {
198
+ if (value.length === 0)
199
+ return { kind: "array", element: { kind: "unknown" } };
200
+ return { kind: "array", element: jsonToTypeNode(value[0]) };
201
+ }
202
+ const obj = value;
203
+ const properties = {};
204
+ for (const [key, val] of Object.entries(obj)) {
205
+ properties[key] = jsonToTypeNode(val);
206
+ }
207
+ return { kind: "object", properties };
208
+ }
209
+ function computeTypeHash(argsType, returnType) {
210
+ const data = JSON.stringify({ a: argsType, r: returnType });
211
+ return crypto.createHash("sha256").update(data).digest("hex").slice(0, 16);
212
+ }
213
+ /**
214
+ * Describe a TypeNode shape as human-readable lines.
215
+ */
216
+ function describeShape(node, maxDepth, depth = 0) {
217
+ const indent = " ".repeat(depth);
218
+ const lines = [];
219
+ if (node.kind === "primitive") {
220
+ lines.push(indent + chalk_1.default.cyan(node.name));
221
+ }
222
+ else if (node.kind === "array") {
223
+ const element = node.element;
224
+ if (element.kind === "object" && depth < maxDepth) {
225
+ lines.push(indent + chalk_1.default.yellow("Array<{"));
226
+ const subLines = describeShape(element, maxDepth, depth + 1);
227
+ lines.push(...subLines);
228
+ lines.push(indent + chalk_1.default.yellow("}>"));
229
+ }
230
+ else {
231
+ lines.push(indent + chalk_1.default.yellow(`${describeTypeCompact(element)}[]`));
232
+ }
233
+ }
234
+ else if (node.kind === "object") {
235
+ const props = node.properties;
236
+ const keys = Object.keys(props);
237
+ if (keys.length === 0) {
238
+ lines.push(indent + chalk_1.default.gray("{}"));
239
+ }
240
+ else {
241
+ for (const key of keys) {
242
+ const propType = props[key];
243
+ const typeStr = describeTypeCompact(propType);
244
+ lines.push(indent + chalk_1.default.white(key) + chalk_1.default.gray(": ") + chalk_1.default.cyan(typeStr));
245
+ }
246
+ }
247
+ }
248
+ else {
249
+ lines.push(indent + chalk_1.default.gray(node.kind));
250
+ }
251
+ return lines;
252
+ }
253
+ function describeTypeCompact(node) {
254
+ switch (node.kind) {
255
+ case "primitive":
256
+ return node.name;
257
+ case "array": {
258
+ const element = node.element;
259
+ return `${describeTypeCompact(element)}[]`;
260
+ }
261
+ case "object": {
262
+ const props = node.properties;
263
+ const keys = Object.keys(props);
264
+ if (keys.length === 0)
265
+ return "{}";
266
+ if (keys.length <= 3) {
267
+ const inner = keys.map((k) => `${k}: ${describeTypeCompact(props[k])}`).join(", ");
268
+ return `{ ${inner} }`;
269
+ }
270
+ return `{ ${keys.slice(0, 2).map((k) => `${k}: ${describeTypeCompact(props[k])}`).join(", ")}, ... }`;
271
+ }
272
+ default:
273
+ return node.kind;
274
+ }
275
+ }
@@ -0,0 +1,5 @@
1
+ export interface InitOptions {
2
+ dir?: string;
3
+ python?: boolean;
4
+ }
5
+ export declare function initCommand(opts: InitOptions): Promise<void>;