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,433 @@
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.annotateCommand = annotateCommand;
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
+ * Detect file kind from extension.
46
+ * JavaScript files get JSDoc comments; TypeScript files get inline type annotations.
47
+ */
48
+ function detectFileKind(filePath) {
49
+ const ext = path.extname(filePath).toLowerCase();
50
+ if ([".ts", ".tsx"].includes(ext))
51
+ return "typescript";
52
+ if ([".js", ".jsx", ".mjs", ".cjs"].includes(ext))
53
+ return "javascript";
54
+ if ([".py", ".pyi"].includes(ext))
55
+ return "python";
56
+ return null;
57
+ }
58
+ // ── Shared regex for JS/TS function detection ──
59
+ const funcDeclRe = /^(\s*(?:export\s+)?(?:async\s+)?function\s+)(\w+)\s*\(([^)]*)\)/;
60
+ const arrowRe = /^(\s*(?:export\s+)?(?:const|let|var)\s+)(\w+)\s*=\s*(?:async\s+)?\(([^)]*)\)/;
61
+ const methodRe = /^(\s+)(\w+)\s*\(([^)]*)\)\s*\{/;
62
+ /**
63
+ * Extract function name and raw params from a line, if it matches a function pattern.
64
+ */
65
+ function matchFunction(line) {
66
+ let match = funcDeclRe.exec(line);
67
+ if (match)
68
+ return { fnName: match[2], rawParams: match[3].trim(), kind: "function", match };
69
+ match = arrowRe.exec(line);
70
+ if (match)
71
+ return { fnName: match[2], rawParams: match[3].trim(), kind: "arrow", match };
72
+ match = methodRe.exec(line);
73
+ if (match)
74
+ return { fnName: match[2], rawParams: match[3].trim(), kind: "method", match };
75
+ return null;
76
+ }
77
+ /**
78
+ * Get param names from raw param string, filtering out complex patterns.
79
+ */
80
+ function getParamNames(rawParams) {
81
+ if (!rawParams)
82
+ return [];
83
+ return rawParams.split(",").map((p) => p.trim());
84
+ }
85
+ // ── JSDoc annotation for JavaScript files ──
86
+ /**
87
+ * Annotate a JavaScript file with JSDoc comments above functions.
88
+ * Produces valid JavaScript that IDEs understand for type hints.
89
+ */
90
+ function annotateJSDoc(source, annotations) {
91
+ const lines = source.split("\n");
92
+ const result = [];
93
+ for (let i = 0; i < lines.length; i++) {
94
+ const line = lines[i];
95
+ const info = matchFunction(line);
96
+ if (!info) {
97
+ result.push(line);
98
+ continue;
99
+ }
100
+ const annotation = annotations[info.fnName];
101
+ if (!annotation) {
102
+ result.push(line);
103
+ continue;
104
+ }
105
+ // Skip if there's already a JSDoc comment right above
106
+ const prevLineIdx = result.length - 1;
107
+ if (prevLineIdx >= 0) {
108
+ const prevLine = result[prevLineIdx].trim();
109
+ if (prevLine === "*/" || prevLine.endsWith("*/")) {
110
+ // Check if this is a JSDoc block that already has @param or @returns
111
+ let j = prevLineIdx;
112
+ while (j >= 0 && !result[j].trim().startsWith("/**"))
113
+ j--;
114
+ if (j >= 0) {
115
+ const block = result.slice(j, prevLineIdx + 1).join("\n");
116
+ if (block.includes("@param") || block.includes("@returns")) {
117
+ result.push(line);
118
+ continue;
119
+ }
120
+ }
121
+ }
122
+ }
123
+ // Build JSDoc comment
124
+ const paramNames = getParamNames(info.rawParams);
125
+ const indent = line.match(/^(\s*)/)?.[1] || "";
126
+ const jsdocLines = [];
127
+ jsdocLines.push(`${indent}/**`);
128
+ // Add @param tags
129
+ for (const pName of paramNames) {
130
+ if (pName.startsWith("...") || pName.startsWith("{") || pName.includes("=")) {
131
+ // Handle rest, destructured, default params
132
+ const baseName = pName.startsWith("...")
133
+ ? pName.slice(3)
134
+ : pName.includes("=")
135
+ ? pName.slice(0, pName.indexOf("=")).trim()
136
+ : pName;
137
+ const paramType = annotation.params.find((a) => a.name === baseName);
138
+ if (paramType) {
139
+ jsdocLines.push(`${indent} * @param {${paramType.type}} ${baseName}`);
140
+ }
141
+ continue;
142
+ }
143
+ // Try name match first, then positional
144
+ let paramType = annotation.params.find((a) => a.name === pName);
145
+ if (!paramType) {
146
+ const idx = paramNames.indexOf(pName);
147
+ if (idx >= 0 && idx < annotation.params.length) {
148
+ paramType = annotation.params[idx];
149
+ }
150
+ }
151
+ if (paramType) {
152
+ jsdocLines.push(`${indent} * @param {${paramType.type}} ${pName}`);
153
+ }
154
+ }
155
+ // Add @returns tag
156
+ if (annotation.returnType && annotation.returnType !== "void" && annotation.returnType !== "undefined") {
157
+ jsdocLines.push(`${indent} * @returns {${annotation.returnType}}`);
158
+ }
159
+ jsdocLines.push(`${indent} */`);
160
+ // Only add JSDoc if we have useful content
161
+ if (jsdocLines.length > 2) {
162
+ // Insert JSDoc before the function line
163
+ result.push(...jsdocLines);
164
+ }
165
+ result.push(line);
166
+ }
167
+ return result.join("\n");
168
+ }
169
+ // ── TypeScript annotation (inline types) ──
170
+ /**
171
+ * Annotate a TypeScript file with inline type annotations on function signatures.
172
+ */
173
+ function annotateTS(source, annotations) {
174
+ const lines = source.split("\n");
175
+ const result = [];
176
+ for (let i = 0; i < lines.length; i++) {
177
+ const line = lines[i];
178
+ const info = matchFunction(line);
179
+ if (!info) {
180
+ result.push(line);
181
+ continue;
182
+ }
183
+ const annotation = annotations[info.fnName];
184
+ if (!annotation) {
185
+ result.push(line);
186
+ continue;
187
+ }
188
+ // Skip if already typed (has a colon in param list or return annotation)
189
+ if (info.rawParams.includes(":") || line.includes("): ")) {
190
+ result.push(line);
191
+ continue;
192
+ }
193
+ // Build typed param list
194
+ const paramNames = getParamNames(info.rawParams);
195
+ const typedParams = paramNames.map((pName) => {
196
+ if (pName.startsWith("...") || pName.startsWith("{") || pName.includes("=")) {
197
+ return pName;
198
+ }
199
+ const paramType = annotation.params.find((a) => a.name === pName);
200
+ if (paramType) {
201
+ return `${pName}: ${paramType.type}`;
202
+ }
203
+ const idx = paramNames.indexOf(pName);
204
+ if (idx >= 0 && idx < annotation.params.length) {
205
+ return `${pName}: ${annotation.params[idx].type}`;
206
+ }
207
+ return pName;
208
+ });
209
+ const typedParamStr = typedParams.join(", ");
210
+ const retType = annotation.returnType;
211
+ if (info.kind === "function") {
212
+ const prefix = info.match[1];
213
+ const rest = line.slice(info.match[0].length);
214
+ const hasReturnType = rest.match(/^\s*:/);
215
+ if (hasReturnType) {
216
+ result.push(`${prefix}${info.fnName}(${typedParamStr})${rest}`);
217
+ }
218
+ else {
219
+ result.push(`${prefix}${info.fnName}(${typedParamStr}): ${retType}${rest}`);
220
+ }
221
+ }
222
+ else if (info.kind === "arrow") {
223
+ const prefix = info.match[1];
224
+ const afterMatch = line.slice(info.match[0].length);
225
+ const asyncMatch = line.match(new RegExp(`${info.fnName}\\s*=\\s*(async\\s+)?\\(`));
226
+ const asyncPrefix = asyncMatch?.[1] || "";
227
+ result.push(`${prefix}${info.fnName} = ${asyncPrefix}(${typedParamStr}): ${retType}${afterMatch}`);
228
+ }
229
+ else {
230
+ const indent = info.match[1];
231
+ const rest = line.slice(info.match[0].length);
232
+ result.push(`${indent}${info.fnName}(${typedParamStr}): ${retType} {${rest}`);
233
+ }
234
+ }
235
+ return result.join("\n");
236
+ }
237
+ // ── Python annotation ──
238
+ /**
239
+ * Annotate a Python file with inline type annotations.
240
+ */
241
+ function annotatePython(source, annotations) {
242
+ const lines = source.split("\n");
243
+ const result = [];
244
+ const defRe = /^(\s*(?:async\s+)?def\s+)(\w+)\s*\(([^)]*)\)\s*(->\s*\S+\s*)?:/;
245
+ for (let i = 0; i < lines.length; i++) {
246
+ const line = lines[i];
247
+ const match = defRe.exec(line);
248
+ if (!match) {
249
+ result.push(line);
250
+ continue;
251
+ }
252
+ const prefix = match[1];
253
+ const fnName = match[2];
254
+ const rawParams = match[3].trim();
255
+ const existingReturn = match[4];
256
+ const annotation = annotations[fnName];
257
+ if (!annotation) {
258
+ result.push(line);
259
+ continue;
260
+ }
261
+ const paramsHaveTypes = rawParams.split(",").some((p) => {
262
+ const trimmed = p.trim();
263
+ if (trimmed === "self" || trimmed === "cls")
264
+ return false;
265
+ return trimmed.includes(":") && !trimmed.startsWith("*");
266
+ });
267
+ if (paramsHaveTypes && existingReturn) {
268
+ result.push(line);
269
+ continue;
270
+ }
271
+ const paramNames = rawParams ? rawParams.split(",").map((p) => p.trim()) : [];
272
+ const typedParams = paramNames.map((pName) => {
273
+ if (pName === "self" || pName === "cls" || pName.startsWith("*") || pName.includes(":")) {
274
+ return pName;
275
+ }
276
+ const eqIdx = pName.indexOf("=");
277
+ const baseName = eqIdx >= 0 ? pName.slice(0, eqIdx).trim() : pName;
278
+ const defaultPart = eqIdx >= 0 ? ` = ${pName.slice(eqIdx + 1).trim()}` : "";
279
+ const paramType = annotation.params.find((a) => a.name === baseName);
280
+ if (paramType) {
281
+ return `${baseName}: ${paramType.type}${defaultPart}`;
282
+ }
283
+ const nonSelfParams = paramNames.filter((p) => p !== "self" && p !== "cls" && !p.startsWith("*"));
284
+ const idx = nonSelfParams.indexOf(pName);
285
+ if (idx >= 0 && idx < annotation.params.length) {
286
+ return `${baseName}: ${annotation.params[idx].type}${defaultPart}`;
287
+ }
288
+ return pName;
289
+ });
290
+ const typedParamStr = typedParams.join(", ");
291
+ const retAnnotation = existingReturn || ` -> ${annotation.returnType}`;
292
+ result.push(`${prefix}${fnName}(${typedParamStr})${retAnnotation}:`);
293
+ }
294
+ return result.join("\n");
295
+ }
296
+ // ── Main command ──
297
+ async function annotateCommand(file, opts) {
298
+ const filePath = path.resolve(file);
299
+ if (!fs.existsSync(filePath)) {
300
+ console.error(chalk_1.default.red(`\n File not found: ${filePath}\n`));
301
+ process.exit(1);
302
+ }
303
+ const fileKind = detectFileKind(filePath);
304
+ if (!fileKind) {
305
+ console.error(chalk_1.default.red(`\n Unsupported file type: ${path.extname(filePath)}\n`));
306
+ console.error(chalk_1.default.gray(" Supported: .ts, .tsx, .js, .jsx, .mjs, .cjs, .py, .pyi\n"));
307
+ process.exit(1);
308
+ }
309
+ // Determine annotation language for the API
310
+ const apiLanguage = fileKind === "python" ? "python" : undefined;
311
+ // Determine annotation mode
312
+ // --jsdoc flag forces JSDoc mode; otherwise JS files get JSDoc automatically
313
+ const useJSDoc = opts.jsdoc || fileKind === "javascript";
314
+ const { annotations } = await (0, api_client_1.fetchAnnotations)({
315
+ env: opts.env,
316
+ language: apiLanguage,
317
+ });
318
+ if (!annotations || Object.keys(annotations).length === 0) {
319
+ console.log(chalk_1.default.yellow("\n No observed types found. Run your code with trickle first.\n"));
320
+ return;
321
+ }
322
+ const source = fs.readFileSync(filePath, "utf-8");
323
+ // Apply annotations based on file type
324
+ let annotated;
325
+ let mode;
326
+ if (fileKind === "python") {
327
+ annotated = annotatePython(source, annotations);
328
+ mode = "Python type annotations";
329
+ }
330
+ else if (useJSDoc) {
331
+ annotated = annotateJSDoc(source, annotations);
332
+ mode = "JSDoc comments";
333
+ }
334
+ else {
335
+ annotated = annotateTS(source, annotations);
336
+ mode = "TypeScript annotations";
337
+ }
338
+ // Count changes
339
+ const originalLines = source.split("\n");
340
+ const annotatedLines = annotated.split("\n");
341
+ // For JSDoc, count functions annotated (not lines changed, since JSDoc adds lines)
342
+ let changeCount = 0;
343
+ if (useJSDoc && fileKind !== "python") {
344
+ // Count new JSDoc blocks added
345
+ for (let i = 0; i < annotatedLines.length; i++) {
346
+ if (annotatedLines[i].trimEnd().endsWith("/**") && (i >= originalLines.length || originalLines[i] !== annotatedLines[i])) {
347
+ changeCount++;
348
+ }
349
+ }
350
+ }
351
+ else {
352
+ for (let i = 0; i < Math.max(originalLines.length, annotatedLines.length); i++) {
353
+ if ((originalLines[i] || "") !== (annotatedLines[i] || ""))
354
+ changeCount++;
355
+ }
356
+ }
357
+ if (changeCount === 0) {
358
+ console.log(chalk_1.default.gray("\n No annotations to add — functions already typed or not observed.\n"));
359
+ return;
360
+ }
361
+ if (opts.dryRun) {
362
+ console.log(chalk_1.default.cyan(`\n Dry run — ${mode} that would be added:\n`));
363
+ if (useJSDoc && fileKind !== "python") {
364
+ // Show JSDoc blocks that would be inserted
365
+ let inNew = false;
366
+ for (let i = 0; i < annotatedLines.length; i++) {
367
+ const isNewLine = i >= originalLines.length || originalLines[i] !== annotatedLines[i];
368
+ if (isNewLine && !inNew) {
369
+ // Find the function line that follows this JSDoc
370
+ inNew = true;
371
+ }
372
+ if (isNewLine) {
373
+ console.log(chalk_1.default.green(` + ${annotatedLines[i]}`));
374
+ if (annotatedLines[i].trimEnd().endsWith("*/")) {
375
+ inNew = false;
376
+ }
377
+ }
378
+ }
379
+ }
380
+ else {
381
+ for (let i = 0; i < Math.max(originalLines.length, annotatedLines.length); i++) {
382
+ if ((originalLines[i] || "") !== (annotatedLines[i] || "")) {
383
+ console.log(chalk_1.default.red(` - ${(originalLines[i] || "").trim()}`));
384
+ console.log(chalk_1.default.green(` + ${(annotatedLines[i] || "").trim()}`));
385
+ console.log();
386
+ }
387
+ }
388
+ }
389
+ console.log(chalk_1.default.gray(`\n ${changeCount} function(s) would be annotated with ${mode}.\n`));
390
+ return;
391
+ }
392
+ // Write annotated file
393
+ fs.writeFileSync(filePath, annotated, "utf-8");
394
+ console.log(chalk_1.default.green(`\n Annotated ${changeCount} function(s) in ${path.relative(process.cwd(), filePath)} (${mode})\n`));
395
+ // Show what changed
396
+ if (useJSDoc && fileKind !== "python") {
397
+ // Show JSDoc blocks that were added
398
+ let inBlock = false;
399
+ let blockLines = [];
400
+ for (let i = 0; i < annotatedLines.length; i++) {
401
+ const isNew = i >= originalLines.length || originalLines[i] !== annotatedLines[i];
402
+ if (isNew && annotatedLines[i].trimEnd().endsWith("/**")) {
403
+ inBlock = true;
404
+ blockLines = [annotatedLines[i]];
405
+ }
406
+ else if (inBlock) {
407
+ blockLines.push(annotatedLines[i]);
408
+ if (annotatedLines[i].trimEnd().endsWith("*/")) {
409
+ inBlock = false;
410
+ // Next line should be the function
411
+ const fnLine = annotatedLines[i + 1] || "";
412
+ const fnMatch = fnLine.match(/(?:function|const|let|var)\s+(\w+)/);
413
+ const fnName = fnMatch?.[1] || "function";
414
+ console.log(chalk_1.default.gray(` ${fnName}:`));
415
+ for (const bl of blockLines) {
416
+ console.log(chalk_1.default.gray(` ${bl.trim()}`));
417
+ }
418
+ }
419
+ }
420
+ }
421
+ }
422
+ else {
423
+ for (let i = 0; i < annotatedLines.length; i++) {
424
+ if (i < originalLines.length && originalLines[i] !== annotatedLines[i]) {
425
+ const fnMatch = annotatedLines[i].match(/(?:function|def|const|let|var)\s+(\w+)/);
426
+ const fnName = fnMatch?.[1] || "unknown";
427
+ console.log(chalk_1.default.gray(` ${fnName}:`));
428
+ console.log(chalk_1.default.gray(` ${annotatedLines[i].trim()}`));
429
+ }
430
+ }
431
+ }
432
+ console.log();
433
+ }
@@ -0,0 +1,7 @@
1
+ export interface AuditOptions {
2
+ env?: string;
3
+ json?: boolean;
4
+ failOnError?: boolean;
5
+ failOnWarning?: boolean;
6
+ }
7
+ export declare function auditCommand(opts: AuditOptions): Promise<void>;
@@ -0,0 +1,82 @@
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.auditCommand = auditCommand;
7
+ const chalk_1 = __importDefault(require("chalk"));
8
+ const config_1 = require("../config");
9
+ async function auditCommand(opts) {
10
+ const base = (0, config_1.getBackendUrl)();
11
+ const url = new URL("/api/audit", base);
12
+ if (opts.env)
13
+ url.searchParams.set("env", opts.env);
14
+ let data;
15
+ try {
16
+ const res = await fetch(url.toString());
17
+ if (!res.ok)
18
+ throw new Error(`HTTP ${res.status}`);
19
+ data = (await res.json());
20
+ }
21
+ catch {
22
+ console.error(chalk_1.default.red(`\n Cannot connect to trickle backend at ${chalk_1.default.bold(base)}.`));
23
+ console.error(chalk_1.default.red(" Is the backend running?\n"));
24
+ process.exit(1);
25
+ }
26
+ if (opts.json) {
27
+ console.log(JSON.stringify(data, null, 2));
28
+ if (opts.failOnError && data.summary.errors > 0)
29
+ process.exit(1);
30
+ if (opts.failOnWarning && (data.summary.errors + data.summary.warnings) > 0)
31
+ process.exit(1);
32
+ return;
33
+ }
34
+ console.log("");
35
+ console.log(chalk_1.default.bold(" API Audit Report"));
36
+ console.log(chalk_1.default.gray(` ${data.summary.routesAnalyzed} routes analyzed\n`));
37
+ if (data.issues.length === 0) {
38
+ console.log(chalk_1.default.green(" ✓ No issues found — your API looks clean!\n"));
39
+ return;
40
+ }
41
+ // Group by severity
42
+ const errors = data.issues.filter((i) => i.severity === "error");
43
+ const warnings = data.issues.filter((i) => i.severity === "warning");
44
+ const infos = data.issues.filter((i) => i.severity === "info");
45
+ if (errors.length > 0) {
46
+ console.log(chalk_1.default.red.bold(` ✗ ${errors.length} error${errors.length > 1 ? "s" : ""}`));
47
+ for (const issue of errors) {
48
+ const route = issue.route ? chalk_1.default.gray(` [${issue.route}]`) : "";
49
+ console.log(chalk_1.default.red(` • ${issue.message}${route}`));
50
+ }
51
+ console.log("");
52
+ }
53
+ if (warnings.length > 0) {
54
+ console.log(chalk_1.default.yellow.bold(` ⚠ ${warnings.length} warning${warnings.length > 1 ? "s" : ""}`));
55
+ for (const issue of warnings) {
56
+ const route = issue.route ? chalk_1.default.gray(` [${issue.route}]`) : "";
57
+ console.log(chalk_1.default.yellow(` • ${issue.message}${route}`));
58
+ }
59
+ console.log("");
60
+ }
61
+ if (infos.length > 0) {
62
+ console.log(chalk_1.default.blue.bold(` ℹ ${infos.length} info`));
63
+ for (const issue of infos) {
64
+ const route = issue.route ? chalk_1.default.gray(` [${issue.route}]`) : "";
65
+ console.log(chalk_1.default.blue(` • ${issue.message}${route}`));
66
+ }
67
+ console.log("");
68
+ }
69
+ // Summary line
70
+ const parts = [];
71
+ if (errors.length > 0)
72
+ parts.push(chalk_1.default.red(`${errors.length} errors`));
73
+ if (warnings.length > 0)
74
+ parts.push(chalk_1.default.yellow(`${warnings.length} warnings`));
75
+ if (infos.length > 0)
76
+ parts.push(chalk_1.default.blue(`${infos.length} info`));
77
+ console.log(chalk_1.default.gray(` Total: ${parts.join(", ")}\n`));
78
+ if (opts.failOnError && data.summary.errors > 0)
79
+ process.exit(1);
80
+ if (opts.failOnWarning && (data.summary.errors + data.summary.warnings) > 0)
81
+ process.exit(1);
82
+ }
@@ -0,0 +1,8 @@
1
+ export interface AutoOptions {
2
+ dir?: string;
3
+ env?: string;
4
+ }
5
+ /**
6
+ * `trickle auto` — Auto-detect project deps and generate only relevant type files.
7
+ */
8
+ export declare function autoCommand(opts: AutoOptions): Promise<void>;