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
package/src/index.ts ADDED
@@ -0,0 +1,454 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { Command } from "commander";
4
+ import chalk from "chalk";
5
+ import { functionsCommand } from "./commands/functions";
6
+ import { typesCommand } from "./commands/types";
7
+ import { errorsCommand } from "./commands/errors";
8
+ import { tailCommand } from "./commands/tail";
9
+ import { codegenCommand } from "./commands/codegen";
10
+ import { mockCommand } from "./commands/mock";
11
+ import { initCommand } from "./commands/init";
12
+ import { diffCommand } from "./commands/diff";
13
+ import { openapiCommand } from "./commands/openapi";
14
+ import { checkCommand } from "./commands/check";
15
+ import { devCommand } from "./commands/dev";
16
+ import { testGenCommand } from "./commands/test-gen";
17
+ import { dashboardCommand } from "./commands/dashboard";
18
+ import { proxyCommand } from "./commands/proxy";
19
+ import { exportCommand } from "./commands/export";
20
+ import { coverageCommand } from "./commands/coverage";
21
+ import { replayCommand } from "./commands/replay";
22
+ import { docsCommand } from "./commands/docs";
23
+ import { sampleCommand } from "./commands/sample";
24
+ import { auditCommand } from "./commands/audit";
25
+ import { captureCommand } from "./commands/capture";
26
+ import { searchCommand } from "./commands/search";
27
+ import { autoCommand } from "./commands/auto";
28
+ import { validateCommand } from "./commands/validate";
29
+ import { watchCommand } from "./commands/watch";
30
+ import { inferCommand } from "./commands/infer";
31
+ import { overviewCommand } from "./commands/overview";
32
+ import { traceCommand } from "./commands/trace";
33
+ import { packCommand } from "./commands/pack";
34
+ import { unpackCommand } from "./commands/unpack";
35
+ import { runCommand } from "./commands/run";
36
+ import { annotateCommand } from "./commands/annotate";
37
+ import { stubsCommand } from "./commands/stubs";
38
+
39
+ const program = new Command();
40
+
41
+ program
42
+ .name("trickle")
43
+ .description("CLI for trickle runtime type observability")
44
+ .version("0.1.0");
45
+
46
+ // trickle init
47
+ program
48
+ .command("init")
49
+ .description("Set up trickle in your project — configures types, tsconfig, and npm scripts")
50
+ .option("--dir <path>", "Project directory (defaults to current directory)")
51
+ .option("--python", "Set up for a Python project")
52
+ .action(async (opts) => {
53
+ await initCommand(opts);
54
+ });
55
+
56
+ // trickle dev [command]
57
+ program
58
+ .command("dev [command]")
59
+ .description("Start your app with auto-instrumentation and live type generation")
60
+ .option("-o, --out <path>", "Types output path (default: .trickle/types.d.ts)")
61
+ .option("--client", "Also generate typed API client (.trickle/api-client.ts)")
62
+ .option("--python", "Generate Python type stubs instead of TypeScript")
63
+ .action(async (command: string | undefined, opts) => {
64
+ await devCommand(command, opts);
65
+ });
66
+
67
+ // trickle run <command>
68
+ program
69
+ .command("run [command]")
70
+ .description("Run any command or file with universal type observation — zero code changes needed")
71
+ .option("--module <name>", "Module name for captured functions")
72
+ .option("--include <patterns>", "Comma-separated substrings — only observe matching modules")
73
+ .option("--exclude <patterns>", "Comma-separated substrings — skip matching modules")
74
+ .option("--stubs <dir>", "Auto-generate .d.ts/.pyi type stubs in this directory after the run")
75
+ .option("--annotate <path>", "Auto-annotate this file or directory with types after the run")
76
+ .option("-w, --watch", "Watch source files and re-run on changes")
77
+ .action(async (command: string | undefined, opts) => {
78
+ await runCommand(command, opts);
79
+ });
80
+
81
+ // trickle functions
82
+ program
83
+ .command("functions")
84
+ .description("List observed functions")
85
+ .option("--env <env>", "Filter by environment")
86
+ .option("--lang <lang>", "Filter by language")
87
+ .option("--search <query>", "Search by function name")
88
+ .action(async (opts) => {
89
+ await functionsCommand(opts);
90
+ });
91
+
92
+ // trickle types <function-name>
93
+ program
94
+ .command("types <function-name>")
95
+ .description("Show type snapshots for a function")
96
+ .option("--env <env>", "Filter by environment")
97
+ .option("--diff", "Show diff between latest two snapshots")
98
+ .option("--env1 <env>", "First environment for cross-env diff")
99
+ .option("--env2 <env>", "Second environment for cross-env diff")
100
+ .action(async (functionName: string, opts) => {
101
+ await typesCommand(functionName, opts);
102
+ });
103
+
104
+ // trickle errors [id]
105
+ program
106
+ .command("errors [id]")
107
+ .description("List errors or show error detail")
108
+ .option("--env <env>", "Filter by environment")
109
+ .option("--since <timeframe>", "Show errors since (e.g., 2d, 5m, 1h)")
110
+ .option("--function <name>", "Filter by function name")
111
+ .option("--limit <n>", "Limit number of results")
112
+ .action(async (id: string | undefined, opts) => {
113
+ await errorsCommand(id, opts);
114
+ });
115
+
116
+ // trickle tail
117
+ program
118
+ .command("tail")
119
+ .description("Stream live events from the backend")
120
+ .option("--filter <pattern>", "Filter by function name pattern")
121
+ .action(async (opts) => {
122
+ await tailCommand(opts);
123
+ });
124
+
125
+ // trickle codegen [function-name]
126
+ program
127
+ .command("codegen [function-name]")
128
+ .description("Generate TypeScript (or Python) type definitions from observed runtime types")
129
+ .option("-o, --out <path>", "Write output to a file instead of stdout")
130
+ .option("--env <env>", "Filter by environment")
131
+ .option("--python", "Generate Python type stubs (.pyi) instead of TypeScript")
132
+ .option("--client", "Generate a typed fetch-based API client from observed routes")
133
+ .option("--handlers", "Generate typed Express handler types for route handlers")
134
+ .option("--zod", "Generate Zod validation schemas with inferred types")
135
+ .option("--react-query", "Generate typed TanStack React Query hooks")
136
+ .option("--guards", "Generate runtime type guard functions")
137
+ .option("--middleware", "Generate Express request validation middleware")
138
+ .option("--msw", "Generate Mock Service Worker (MSW) request handlers")
139
+ .option("--json-schema", "Generate JSON Schema definitions from observed types")
140
+ .option("--swr", "Generate typed SWR data-fetching hooks")
141
+ .option("--pydantic", "Generate Pydantic BaseModel classes (Python)")
142
+ .option("--class-validator", "Generate class-validator DTOs for NestJS")
143
+ .option("--graphql", "Generate GraphQL SDL schema from observed routes")
144
+ .option("--trpc", "Generate typed tRPC router from observed routes")
145
+ .option("--axios", "Generate typed Axios client from observed routes")
146
+ .option("--watch", "Watch mode: re-generate when new types are observed")
147
+ .action(async (functionName: string | undefined, opts) => {
148
+ await codegenCommand(functionName, opts);
149
+ });
150
+
151
+ // trickle diff
152
+ program
153
+ .command("diff")
154
+ .description("Show type drift across all functions — what changed and where")
155
+ .option("--since <timeframe>", "Show changes since (e.g., 1h, 2d, 1w)")
156
+ .option("--env <env>", "Filter by environment")
157
+ .option("--env1 <env>", "First environment for cross-env comparison")
158
+ .option("--env2 <env>", "Second environment for cross-env comparison")
159
+ .action(async (opts) => {
160
+ await diffCommand(opts);
161
+ });
162
+
163
+ // trickle openapi
164
+ program
165
+ .command("openapi")
166
+ .description("Generate an OpenAPI 3.0 spec from runtime-observed API routes")
167
+ .option("-o, --out <path>", "Write spec to a file (JSON)")
168
+ .option("--env <env>", "Filter by environment")
169
+ .option("--title <title>", "API title in the spec", "API")
170
+ .option("--api-version <version>", "API version in the spec", "1.0.0")
171
+ .option("--server <url>", "Server URL to include in the spec")
172
+ .action(async (opts) => {
173
+ await openapiCommand(opts);
174
+ });
175
+
176
+ // trickle check
177
+ program
178
+ .command("check")
179
+ .description("Detect breaking API changes by comparing against a saved baseline")
180
+ .option("--save <file>", "Save current types as a baseline snapshot")
181
+ .option("--against <file>", "Check current types against a baseline (exit 1 on breaking changes)")
182
+ .option("--env <env>", "Filter by environment")
183
+ .action(async (opts) => {
184
+ await checkCommand(opts);
185
+ });
186
+
187
+ // trickle mock
188
+ program
189
+ .command("mock")
190
+ .description("Start a mock API server from runtime-observed routes and sample data")
191
+ .option("-p, --port <port>", "Port to listen on", "3000")
192
+ .option("--no-cors", "Disable CORS headers")
193
+ .action(async (opts) => {
194
+ await mockCommand(opts);
195
+ });
196
+
197
+ // trickle test --generate
198
+ program
199
+ .command("test")
200
+ .description("Generate API test files from runtime-observed routes and sample data")
201
+ .option("--generate", "Generate test file from observed routes")
202
+ .option("-o, --out <path>", "Write tests to a file")
203
+ .option("--framework <name>", "Test framework: vitest or jest (default: vitest)")
204
+ .option("--base-url <url>", "Base URL for API requests (default: http://localhost:3000)")
205
+ .action(async (opts) => {
206
+ if (!opts.generate) {
207
+ console.log(chalk.gray("\n Usage: trickle test --generate [--out tests.ts] [--framework vitest|jest]\n"));
208
+ return;
209
+ }
210
+ await testGenCommand(opts);
211
+ });
212
+
213
+ // trickle dashboard
214
+ program
215
+ .command("dashboard")
216
+ .description("Open the trickle web dashboard to explore observed types visually")
217
+ .action(async () => {
218
+ await dashboardCommand();
219
+ });
220
+
221
+ // trickle proxy
222
+ program
223
+ .command("proxy")
224
+ .description("Transparent reverse proxy that captures API types without any backend code changes")
225
+ .requiredOption("-t, --target <url>", "Target server URL to proxy to (e.g. http://localhost:3000)")
226
+ .option("-p, --port <port>", "Port for the proxy server", "4000")
227
+ .action(async (opts) => {
228
+ await proxyCommand(opts);
229
+ });
230
+
231
+ // trickle export
232
+ program
233
+ .command("export")
234
+ .description("Generate all output formats into a .trickle/ directory at once")
235
+ .option("-d, --dir <path>", "Output directory (default: .trickle)")
236
+ .option("--env <env>", "Filter by environment")
237
+ .action(async (opts) => {
238
+ await exportCommand(opts);
239
+ });
240
+
241
+ // trickle coverage
242
+ program
243
+ .command("coverage")
244
+ .description("Type observation health report — coverage, staleness, variants, and overall score")
245
+ .option("--env <env>", "Filter by environment")
246
+ .option("--json", "Output raw JSON (for CI integration)")
247
+ .option("--fail-under <score>", "Exit 1 if health score is below this threshold (0-100)")
248
+ .option("--stale-hours <hours>", "Hours before a function is considered stale (default: 24)")
249
+ .action(async (opts) => {
250
+ await coverageCommand(opts);
251
+ });
252
+
253
+ // trickle replay
254
+ program
255
+ .command("replay")
256
+ .description("Replay captured API requests as regression tests — verify response shapes match")
257
+ .option("-t, --target <url>", "Target server URL (default: http://localhost:3000)")
258
+ .option("--strict", "Compare exact values instead of just shapes")
259
+ .option("--json", "Output JSON results (for CI)")
260
+ .option("--fail-fast", "Stop on first failure")
261
+ .action(async (opts) => {
262
+ await replayCommand(opts);
263
+ });
264
+
265
+ // trickle docs
266
+ program
267
+ .command("docs")
268
+ .description("Generate API documentation from observed runtime types and sample data")
269
+ .option("-o, --out <path>", "Write docs to a file instead of stdout")
270
+ .option("--html", "Generate self-contained HTML instead of Markdown")
271
+ .option("--env <env>", "Filter by environment")
272
+ .option("--title <title>", "Documentation title", "API Documentation")
273
+ .action(async (opts) => {
274
+ await docsCommand(opts);
275
+ });
276
+
277
+ // trickle sample [route]
278
+ program
279
+ .command("sample [route]")
280
+ .description("Generate test fixtures and factory functions from observed runtime data")
281
+ .option("-f, --format <format>", "Output format: json, ts, or factory (default: json)")
282
+ .option("-o, --out <path>", "Write fixtures to a file")
283
+ .action(async (route: string | undefined, opts) => {
284
+ await sampleCommand(route, opts);
285
+ });
286
+
287
+ // trickle audit
288
+ program
289
+ .command("audit")
290
+ .description("Analyze observed API types for quality issues — sensitive data, naming, complexity")
291
+ .option("--env <env>", "Filter by environment")
292
+ .option("--json", "Output raw JSON (for CI integration)")
293
+ .option("--fail-on-error", "Exit 1 if any errors are found")
294
+ .option("--fail-on-warning", "Exit 1 if any errors or warnings are found")
295
+ .action(async (opts) => {
296
+ await auditCommand(opts);
297
+ });
298
+
299
+ // trickle capture <method> <url>
300
+ program
301
+ .command("capture <method> <url>")
302
+ .description("Capture types from a live API endpoint — no instrumentation needed")
303
+ .option("-H, --header <header...>", "HTTP headers (e.g. -H 'Authorization: Bearer token')")
304
+ .option("-d, --body <body>", "Request body (JSON string)")
305
+ .option("--env <env>", "Environment label (default: development)")
306
+ .option("--module <module>", "Module label (default: capture)")
307
+ .action(async (method: string, url: string, opts) => {
308
+ await captureCommand(method, url, opts);
309
+ });
310
+
311
+ // trickle search <query>
312
+ program
313
+ .command("search <query>")
314
+ .description("Search across all observed types — find functions by field names, types, or patterns")
315
+ .option("--env <env>", "Filter by environment")
316
+ .option("--json", "Output raw JSON")
317
+ .action(async (query: string, opts) => {
318
+ await searchCommand(query, opts);
319
+ });
320
+
321
+ // trickle auto
322
+ program
323
+ .command("auto")
324
+ .description("Auto-detect project dependencies and generate only the relevant type files")
325
+ .option("-d, --dir <path>", "Output directory (default: .trickle)")
326
+ .option("--env <env>", "Filter by environment")
327
+ .action(async (opts) => {
328
+ await autoCommand(opts);
329
+ });
330
+
331
+ // trickle validate <method> <url>
332
+ program
333
+ .command("validate <method> <url>")
334
+ .description("Validate a live API response against previously observed types")
335
+ .option("-H, --header <header...>", "HTTP headers")
336
+ .option("-d, --body <body>", "Request body (JSON string)")
337
+ .option("--env <env>", "Filter by environment")
338
+ .option("--strict", "Treat extra fields as errors (not just warnings)")
339
+ .action(async (method: string, url: string, opts) => {
340
+ await validateCommand(method, url, opts);
341
+ });
342
+
343
+ // trickle watch
344
+ program
345
+ .command("watch")
346
+ .description("Watch for new type observations and auto-regenerate type files")
347
+ .option("-d, --dir <path>", "Output directory (default: .trickle)")
348
+ .option("--env <env>", "Filter by environment")
349
+ .option("--interval <interval>", "Poll interval (e.g., 3s, 500ms, 1m)", "3s")
350
+ .action(async (opts) => {
351
+ await watchCommand(opts);
352
+ });
353
+
354
+ // trickle infer [file]
355
+ program
356
+ .command("infer [file]")
357
+ .description("Infer types from a JSON file or stdin — no live API needed")
358
+ .requiredOption("-n, --name <name>", "Function/route name (e.g., 'GET /api/users')")
359
+ .option("--env <env>", "Environment label (default: development)")
360
+ .option("--module <module>", "Module label (default: infer)")
361
+ .option("--request-body <json>", "Example request body JSON (for documenting input types)")
362
+ .action(async (file: string | undefined, opts) => {
363
+ await inferCommand(file, opts);
364
+ });
365
+
366
+ // trickle overview
367
+ program
368
+ .command("overview")
369
+ .description("Compact API overview — all routes with inline type signatures")
370
+ .option("--env <env>", "Filter by environment")
371
+ .option("--json", "Output raw JSON")
372
+ .action(async (opts) => {
373
+ await overviewCommand(opts);
374
+ });
375
+
376
+ // trickle trace <method> <url>
377
+ program
378
+ .command("trace <method> <url>")
379
+ .description("Make an HTTP request and show the response with inline type annotations")
380
+ .option("-H, --header <header...>", "HTTP headers")
381
+ .option("-d, --body <body>", "Request body (JSON string)")
382
+ .option("--save", "Save inferred types to the backend")
383
+ .option("--env <env>", "Environment label (default: development)")
384
+ .option("--module <module>", "Module label (default: trace)")
385
+ .action(async (method: string, url: string, opts) => {
386
+ await traceCommand(method, url, opts);
387
+ });
388
+
389
+ // trickle pack
390
+ program
391
+ .command("pack")
392
+ .description("Export all observed types as a portable bundle")
393
+ .option("-o, --out <file>", "Write bundle to a file (otherwise stdout)")
394
+ .option("--env <env>", "Filter by environment")
395
+ .action(async (opts) => {
396
+ await packCommand(opts);
397
+ });
398
+
399
+ // trickle unpack <file>
400
+ program
401
+ .command("unpack <file>")
402
+ .description("Import types from a packed bundle into the backend")
403
+ .option("--env <env>", "Override environment for all imported types")
404
+ .option("--dry-run", "List contents without importing")
405
+ .action(async (file: string, opts) => {
406
+ await unpackCommand(file, opts);
407
+ });
408
+
409
+ // trickle stubs <dir>
410
+ program
411
+ .command("stubs <dir>")
412
+ .description("Generate .d.ts and .pyi sidecar type stubs next to source files — IDEs pick them up automatically")
413
+ .option("--env <env>", "Filter by environment")
414
+ .option("--dry-run", "Preview which files would be created without writing them")
415
+ .action(async (dir: string, opts) => {
416
+ await stubsCommand(dir, opts);
417
+ });
418
+
419
+ // trickle annotate <file>
420
+ program
421
+ .command("annotate <file>")
422
+ .description("Add runtime-observed type annotations directly into source files")
423
+ .option("--env <env>", "Filter by environment")
424
+ .option("--dry-run", "Preview changes without modifying the file")
425
+ .option("--jsdoc", "Force JSDoc comments (default for .js files)")
426
+ .action(async (file: string, opts) => {
427
+ await annotateCommand(file, opts);
428
+ });
429
+
430
+ // Handle unhandled rejections
431
+ process.on("unhandledRejection", (err) => {
432
+ if (err instanceof Error) {
433
+ console.error(chalk.red(`\n Error: ${err.message}\n`));
434
+ } else {
435
+ console.error(chalk.red("\n An unexpected error occurred.\n"));
436
+ }
437
+ process.exit(1);
438
+ });
439
+
440
+ // ── Direct file execution shorthand ──
441
+ // `trickle app.js` → `trickle run app.js`
442
+ // `trickle script.py --watch` → `trickle run script.py --watch`
443
+ const CODE_EXTENSIONS = /\.(js|ts|tsx|jsx|mjs|cjs|mts|py)$/i;
444
+ const firstArg = process.argv[2];
445
+ if (
446
+ firstArg &&
447
+ !firstArg.startsWith("-") &&
448
+ CODE_EXTENSIONS.test(firstArg)
449
+ ) {
450
+ // Inject "run" before the file argument
451
+ process.argv.splice(2, 0, "run");
452
+ }
453
+
454
+ program.parse();