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,126 @@
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.coverageCommand = coverageCommand;
7
+ const chalk_1 = __importDefault(require("chalk"));
8
+ const config_1 = require("../config");
9
+ /**
10
+ * `trickle coverage` — Type observation health report.
11
+ *
12
+ * Shows per-function type coverage, staleness, variant counts,
13
+ * error counts, and an overall health score. Useful for CI gates.
14
+ */
15
+ async function coverageCommand(opts) {
16
+ const backendUrl = (0, config_1.getBackendUrl)();
17
+ // Fetch coverage data
18
+ const url = new URL("/api/coverage", backendUrl);
19
+ if (opts.env)
20
+ url.searchParams.set("env", opts.env);
21
+ if (opts.staleHours)
22
+ url.searchParams.set("stale_hours", opts.staleHours);
23
+ let data;
24
+ try {
25
+ const res = await fetch(url.toString());
26
+ if (!res.ok) {
27
+ const body = await res.text();
28
+ throw new Error(`HTTP ${res.status}: ${body}`);
29
+ }
30
+ data = (await res.json());
31
+ }
32
+ catch (err) {
33
+ if (err instanceof Error && err.message.startsWith("HTTP ")) {
34
+ console.error(chalk_1.default.red(`\n Error: ${err.message}\n`));
35
+ }
36
+ else {
37
+ console.error(chalk_1.default.red(`\n Cannot connect to trickle backend at ${chalk_1.default.bold(backendUrl)}.`));
38
+ console.error(chalk_1.default.red(" Is the backend running?\n"));
39
+ }
40
+ process.exit(1);
41
+ }
42
+ // JSON output mode
43
+ if (opts.json) {
44
+ console.log(JSON.stringify(data, null, 2));
45
+ checkThreshold(data.summary.health, opts.failUnder);
46
+ return;
47
+ }
48
+ const { summary, entries } = data;
49
+ // Header
50
+ console.log("");
51
+ console.log(chalk_1.default.bold(" trickle coverage"));
52
+ console.log(chalk_1.default.gray(" " + "─".repeat(60)));
53
+ if (opts.env) {
54
+ console.log(chalk_1.default.gray(` Environment: ${opts.env}`));
55
+ }
56
+ console.log(chalk_1.default.gray(` Stale threshold: ${summary.staleThresholdHours}h`));
57
+ console.log(chalk_1.default.gray(" " + "─".repeat(60)));
58
+ console.log("");
59
+ if (entries.length === 0) {
60
+ console.log(chalk_1.default.yellow(" No functions observed yet."));
61
+ console.log(chalk_1.default.gray(" Instrument your app and make some requests first.\n"));
62
+ checkThreshold(0, opts.failUnder);
63
+ return;
64
+ }
65
+ // Health score bar
66
+ const healthColor = summary.health >= 80 ? chalk_1.default.green : summary.health >= 50 ? chalk_1.default.yellow : chalk_1.default.red;
67
+ const barFilled = Math.round(summary.health / 5);
68
+ const barEmpty = 20 - barFilled;
69
+ const healthBar = healthColor("█".repeat(barFilled)) + chalk_1.default.gray("░".repeat(barEmpty));
70
+ console.log(` Health: ${healthBar} ${healthColor(chalk_1.default.bold(`${summary.health}%`))}`);
71
+ console.log("");
72
+ // Summary stats
73
+ console.log(chalk_1.default.bold(" Summary"));
74
+ console.log(` ${chalk_1.default.cyan(String(summary.total))} functions observed`);
75
+ console.log(` ${chalk_1.default.green(String(summary.withTypes))} with types ${chalk_1.default.gray(`${summary.withoutTypes} without`)}`);
76
+ console.log(` ${chalk_1.default.green(String(summary.fresh))} fresh ${summary.stale > 0 ? chalk_1.default.yellow(`${summary.stale} stale`) : chalk_1.default.gray("0 stale")}`);
77
+ if (summary.withErrors > 0) {
78
+ console.log(` ${chalk_1.default.red(String(summary.withErrors))} with errors`);
79
+ }
80
+ if (summary.withMultipleVariants > 0) {
81
+ console.log(` ${chalk_1.default.yellow(String(summary.withMultipleVariants))} with multiple type variants`);
82
+ }
83
+ console.log("");
84
+ // Per-function table
85
+ console.log(chalk_1.default.bold(" Functions"));
86
+ console.log(chalk_1.default.gray(" " + "─".repeat(60)));
87
+ for (const entry of entries) {
88
+ const statusIcon = !entry.hasTypes
89
+ ? chalk_1.default.red("✗")
90
+ : entry.isStale
91
+ ? chalk_1.default.yellow("◦")
92
+ : chalk_1.default.green("✓");
93
+ const nameStr = chalk_1.default.bold(entry.functionName);
94
+ const moduleStr = entry.module !== "api" && entry.module !== "default" ? chalk_1.default.gray(` [${entry.module}]`) : "";
95
+ const badges = [];
96
+ if (entry.snapshots > 0)
97
+ badges.push(chalk_1.default.gray(`${entry.snapshots} snap`));
98
+ if (entry.variants > 1)
99
+ badges.push(chalk_1.default.yellow(`${entry.variants} variants`));
100
+ if (entry.errors > 0)
101
+ badges.push(chalk_1.default.red(`${entry.errors} err`));
102
+ if (entry.isStale)
103
+ badges.push(chalk_1.default.yellow("stale"));
104
+ const healthStr = entry.health >= 80
105
+ ? chalk_1.default.green(`${entry.health}%`)
106
+ : entry.health >= 50
107
+ ? chalk_1.default.yellow(`${entry.health}%`)
108
+ : chalk_1.default.red(`${entry.health}%`);
109
+ console.log(` ${statusIcon} ${nameStr}${moduleStr} ${badges.join(" ")} ${healthStr}`);
110
+ }
111
+ console.log(chalk_1.default.gray(" " + "─".repeat(60)));
112
+ console.log("");
113
+ checkThreshold(summary.health, opts.failUnder);
114
+ }
115
+ function checkThreshold(health, failUnder) {
116
+ if (!failUnder)
117
+ return;
118
+ const threshold = parseInt(failUnder, 10);
119
+ if (isNaN(threshold))
120
+ return;
121
+ if (health < threshold) {
122
+ console.error(chalk_1.default.red(` Coverage health ${health}% is below threshold ${threshold}%`));
123
+ console.error("");
124
+ process.exit(1);
125
+ }
126
+ }
@@ -0,0 +1 @@
1
+ export declare function dashboardCommand(): Promise<void>;
@@ -0,0 +1,83 @@
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.dashboardCommand = dashboardCommand;
40
+ const chalk_1 = __importDefault(require("chalk"));
41
+ const config_1 = require("../config");
42
+ async function dashboardCommand() {
43
+ const backendUrl = (0, config_1.getBackendUrl)();
44
+ const dashboardUrl = `${backendUrl}/dashboard`;
45
+ // Check backend is reachable
46
+ try {
47
+ const res = await fetch(`${backendUrl}/api/health`, { signal: AbortSignal.timeout(3000) });
48
+ if (!res.ok)
49
+ throw new Error("not ok");
50
+ }
51
+ catch {
52
+ console.error(chalk_1.default.red(`\n Cannot reach trickle backend at ${chalk_1.default.bold(backendUrl)}`));
53
+ console.error(chalk_1.default.gray(" Start the backend first: cd packages/backend && npm start\n"));
54
+ process.exit(1);
55
+ }
56
+ console.log("");
57
+ console.log(chalk_1.default.bold(" trickle dashboard"));
58
+ console.log(chalk_1.default.gray(" " + "─".repeat(50)));
59
+ console.log(chalk_1.default.gray(` Opening ${chalk_1.default.bold(dashboardUrl)}`));
60
+ console.log(chalk_1.default.gray(" " + "─".repeat(50)));
61
+ console.log("");
62
+ // Open in browser
63
+ const { exec } = await Promise.resolve().then(() => __importStar(require("child_process")));
64
+ const platform = process.platform;
65
+ let cmd;
66
+ if (platform === "darwin") {
67
+ cmd = `open "${dashboardUrl}"`;
68
+ }
69
+ else if (platform === "win32") {
70
+ cmd = `start "" "${dashboardUrl}"`;
71
+ }
72
+ else {
73
+ cmd = `xdg-open "${dashboardUrl}"`;
74
+ }
75
+ exec(cmd, (err) => {
76
+ if (err) {
77
+ console.log(chalk_1.default.yellow(` Could not open browser automatically.`));
78
+ console.log(chalk_1.default.yellow(` Open this URL manually: ${dashboardUrl}\n`));
79
+ }
80
+ });
81
+ // Keep the process alive briefly so the user sees the message
82
+ await new Promise((resolve) => setTimeout(resolve, 1000));
83
+ }
@@ -0,0 +1,14 @@
1
+ export interface DevOptions {
2
+ port?: string;
3
+ out?: string;
4
+ client?: boolean;
5
+ python?: boolean;
6
+ }
7
+ /**
8
+ * `trickle dev` — All-in-one development command.
9
+ *
10
+ * Starts your app with auto-instrumentation and watches for type changes,
11
+ * regenerating type files as requests flow through. One command replaces
12
+ * the 2-terminal setup of `trickle:start` + `trickle:dev`.
13
+ */
14
+ export declare function devCommand(command: string | undefined, opts: DevOptions): Promise<void>;
@@ -0,0 +1,319 @@
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.devCommand = devCommand;
40
+ const fs = __importStar(require("fs"));
41
+ const path = __importStar(require("path"));
42
+ const child_process_1 = require("child_process");
43
+ const chalk_1 = __importDefault(require("chalk"));
44
+ const config_1 = require("../config");
45
+ const api_client_1 = require("../api-client");
46
+ /**
47
+ * `trickle dev` — All-in-one development command.
48
+ *
49
+ * Starts your app with auto-instrumentation and watches for type changes,
50
+ * regenerating type files as requests flow through. One command replaces
51
+ * the 2-terminal setup of `trickle:start` + `trickle:dev`.
52
+ */
53
+ async function devCommand(command, opts) {
54
+ const backendUrl = (0, config_1.getBackendUrl)();
55
+ // Resolve the app command to run
56
+ const appCommand = resolveAppCommand(command);
57
+ if (!appCommand) {
58
+ console.error(chalk_1.default.red("\n Could not determine app command to run."));
59
+ console.error(chalk_1.default.gray(" Provide a command: trickle dev \"node app.js\""));
60
+ console.error(chalk_1.default.gray(" Or ensure package.json has a start or dev script.\n"));
61
+ process.exit(1);
62
+ }
63
+ // Determine output paths
64
+ const isPython = opts.python === true;
65
+ const typesOut = opts.out || (isPython ? ".trickle/types.pyi" : ".trickle/types.d.ts");
66
+ const clientOut = opts.client ? ".trickle/api-client.ts" : undefined;
67
+ // Ensure .trickle directory exists
68
+ const trickleDir = path.resolve(".trickle");
69
+ if (!fs.existsSync(trickleDir)) {
70
+ fs.mkdirSync(trickleDir, { recursive: true });
71
+ }
72
+ // Check backend connectivity
73
+ const backendReachable = await checkBackend(backendUrl);
74
+ if (!backendReachable) {
75
+ console.error(chalk_1.default.red(`\n Cannot reach trickle backend at ${chalk_1.default.bold(backendUrl)}`));
76
+ console.error(chalk_1.default.gray(" Start the backend first: cd packages/backend && npm start"));
77
+ console.error(chalk_1.default.gray(" Or set TRICKLE_BACKEND_URL to point to a running backend.\n"));
78
+ process.exit(1);
79
+ }
80
+ // Print header
81
+ console.log("");
82
+ console.log(chalk_1.default.bold(" trickle dev"));
83
+ console.log(chalk_1.default.gray(" " + "─".repeat(50)));
84
+ console.log(chalk_1.default.gray(` App command: ${appCommand}`));
85
+ console.log(chalk_1.default.gray(` Backend: ${backendUrl}`));
86
+ console.log(chalk_1.default.gray(` Types output: ${typesOut}`));
87
+ if (clientOut) {
88
+ console.log(chalk_1.default.gray(` Client output: ${clientOut}`));
89
+ }
90
+ console.log(chalk_1.default.gray(" " + "─".repeat(50)));
91
+ console.log("");
92
+ // Inject -r trickle-observe/register into the command
93
+ const instrumentedCommand = injectRegister(appCommand);
94
+ // Start the app process
95
+ const appProc = startApp(instrumentedCommand, backendUrl);
96
+ // Start the codegen watcher
97
+ const stopWatcher = startCodegenWatcher(typesOut, clientOut, isPython);
98
+ // Handle cleanup
99
+ let shuttingDown = false;
100
+ function cleanup() {
101
+ if (shuttingDown)
102
+ return;
103
+ shuttingDown = true;
104
+ console.log(chalk_1.default.gray("\n Shutting down..."));
105
+ stopWatcher();
106
+ if (!appProc.killed) {
107
+ appProc.kill("SIGTERM");
108
+ }
109
+ // Give processes time to clean up
110
+ setTimeout(() => {
111
+ process.exit(0);
112
+ }, 500);
113
+ }
114
+ process.on("SIGINT", cleanup);
115
+ process.on("SIGTERM", cleanup);
116
+ appProc.on("exit", (code) => {
117
+ if (!shuttingDown) {
118
+ if (code !== null && code !== 0) {
119
+ console.log(chalk_1.default.red(`\n App exited with code ${code}`));
120
+ }
121
+ else {
122
+ console.log(chalk_1.default.gray("\n App exited."));
123
+ }
124
+ cleanup();
125
+ }
126
+ });
127
+ }
128
+ /**
129
+ * Resolve the command to run. Checks:
130
+ * 1. Explicit command argument
131
+ * 2. package.json scripts.start
132
+ * 3. package.json scripts.dev
133
+ */
134
+ function resolveAppCommand(explicitCommand) {
135
+ if (explicitCommand) {
136
+ return explicitCommand;
137
+ }
138
+ // Try reading package.json
139
+ const pkgPath = path.resolve("package.json");
140
+ if (fs.existsSync(pkgPath)) {
141
+ try {
142
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf-8"));
143
+ const scripts = pkg.scripts || {};
144
+ // Prefer trickle:start if it exists (already instrumented)
145
+ if (scripts["trickle:start"]) {
146
+ return scripts["trickle:start"];
147
+ }
148
+ if (scripts.start) {
149
+ return scripts.start;
150
+ }
151
+ if (scripts.dev) {
152
+ return scripts.dev;
153
+ }
154
+ }
155
+ catch {
156
+ // ignore
157
+ }
158
+ }
159
+ return null;
160
+ }
161
+ /**
162
+ * Inject `-r trickle-observe/register` into a node/ts-node/nodemon command.
163
+ * If the command already has it, return as-is.
164
+ */
165
+ function injectRegister(command) {
166
+ // Already instrumented
167
+ if (command.includes("trickle-observe/register") || command.includes("trickle\\register")) {
168
+ return command;
169
+ }
170
+ // Inject -r flag
171
+ if (/\bnode\s/.test(command)) {
172
+ return command.replace(/\bnode\s/, "node -r trickle-observe/register ");
173
+ }
174
+ if (/\bts-node\s/.test(command)) {
175
+ return command.replace(/\bts-node\s/, "ts-node -r trickle-observe/register ");
176
+ }
177
+ if (/\bnodemon\s/.test(command)) {
178
+ return command.replace(/\bnodemon\s/, "nodemon -r trickle-observe/register ");
179
+ }
180
+ // Can't inject — run as-is with a warning
181
+ console.log(chalk_1.default.yellow(" Warning: Could not inject -r trickle-observe/register into command."));
182
+ console.log(chalk_1.default.yellow(" Auto-instrumentation may not work. Consider using:"));
183
+ console.log(chalk_1.default.yellow(` node -r trickle-observe/register ${command}\n`));
184
+ return command;
185
+ }
186
+ /**
187
+ * Start the app as a child process with environment variables set.
188
+ */
189
+ function startApp(command, backendUrl) {
190
+ const parts = command.split(/\s+/);
191
+ const cmd = parts[0];
192
+ const args = parts.slice(1);
193
+ const prefix = chalk_1.default.cyan("[app]");
194
+ const proc = (0, child_process_1.spawn)(cmd, args, {
195
+ stdio: ["inherit", "pipe", "pipe"],
196
+ env: {
197
+ ...process.env,
198
+ TRICKLE_BACKEND_URL: backendUrl,
199
+ },
200
+ shell: true,
201
+ });
202
+ proc.stdout?.on("data", (data) => {
203
+ const lines = data.toString().split("\n");
204
+ for (const line of lines) {
205
+ if (line.trim()) {
206
+ console.log(`${prefix} ${line}`);
207
+ }
208
+ }
209
+ });
210
+ proc.stderr?.on("data", (data) => {
211
+ const lines = data.toString().split("\n");
212
+ for (const line of lines) {
213
+ if (line.trim()) {
214
+ console.error(`${prefix} ${chalk_1.default.red(line)}`);
215
+ }
216
+ }
217
+ });
218
+ return proc;
219
+ }
220
+ /**
221
+ * Start a codegen watcher that polls for type changes.
222
+ * Returns a stop function.
223
+ */
224
+ function startCodegenWatcher(typesOut, clientOut, isPython) {
225
+ const prefix = chalk_1.default.magenta("[types]");
226
+ const language = isPython ? "python" : undefined;
227
+ let lastTypesContent = "";
228
+ let lastClientContent = "";
229
+ let stopped = false;
230
+ let firstRun = true;
231
+ // Wait a bit before first poll to let the app start
232
+ let initialDelay = true;
233
+ const poll = async () => {
234
+ if (stopped)
235
+ return;
236
+ try {
237
+ // Generate types
238
+ const result = await (0, api_client_1.fetchCodegen)({ language });
239
+ const types = result.types;
240
+ if (types !== lastTypesContent) {
241
+ lastTypesContent = types;
242
+ const resolvedPath = path.resolve(typesOut);
243
+ const dir = path.dirname(resolvedPath);
244
+ if (!fs.existsSync(dir)) {
245
+ fs.mkdirSync(dir, { recursive: true });
246
+ }
247
+ fs.writeFileSync(resolvedPath, types, "utf-8");
248
+ if (!firstRun) {
249
+ const count = countTypes(types);
250
+ console.log(`${prefix} ${chalk_1.default.green("Updated")} ${chalk_1.default.bold(typesOut)} ${chalk_1.default.gray(`(${count} types)`)}`);
251
+ }
252
+ else {
253
+ firstRun = false;
254
+ }
255
+ }
256
+ // Generate client if requested
257
+ if (clientOut) {
258
+ const clientResult = await (0, api_client_1.fetchCodegen)({ format: "client" });
259
+ if (clientResult.types !== lastClientContent) {
260
+ lastClientContent = clientResult.types;
261
+ const resolvedPath = path.resolve(clientOut);
262
+ const dir = path.dirname(resolvedPath);
263
+ if (!fs.existsSync(dir)) {
264
+ fs.mkdirSync(dir, { recursive: true });
265
+ }
266
+ fs.writeFileSync(resolvedPath, clientResult.types, "utf-8");
267
+ if (!firstRun) {
268
+ console.log(`${prefix} ${chalk_1.default.green("Updated")} ${chalk_1.default.bold(clientOut)}`);
269
+ }
270
+ }
271
+ }
272
+ }
273
+ catch {
274
+ // Backend might not be ready yet or app hasn't served requests — silently retry
275
+ if (!initialDelay) {
276
+ // Only log after initial startup period
277
+ }
278
+ }
279
+ initialDelay = false;
280
+ };
281
+ // First poll after 3s (give app time to start), then every 3s
282
+ const startTimeout = setTimeout(() => {
283
+ poll();
284
+ const interval = setInterval(poll, 3000);
285
+ // Store interval for cleanup
286
+ startTimeout.__interval = interval;
287
+ }, 3000);
288
+ let intervalRef = null;
289
+ // Use a different approach: start polling immediately with setInterval
290
+ const interval = setInterval(async () => {
291
+ if (stopped)
292
+ return;
293
+ await poll();
294
+ }, 3000);
295
+ intervalRef = interval;
296
+ // Initial poll after 2s delay
297
+ const initTimer = setTimeout(poll, 2000);
298
+ return () => {
299
+ stopped = true;
300
+ clearTimeout(startTimeout);
301
+ clearTimeout(initTimer);
302
+ if (intervalRef)
303
+ clearInterval(intervalRef);
304
+ };
305
+ }
306
+ function countTypes(code) {
307
+ const tsMatches = code.match(/export (interface|type) /g);
308
+ const pyMatches = code.match(/class \w+\(TypedDict\)/g);
309
+ return (tsMatches?.length ?? 0) + (pyMatches?.length ?? 0);
310
+ }
311
+ async function checkBackend(url) {
312
+ try {
313
+ const res = await fetch(`${url}/api/health`, { signal: AbortSignal.timeout(3000) });
314
+ return res.ok;
315
+ }
316
+ catch {
317
+ return false;
318
+ }
319
+ }
@@ -0,0 +1,7 @@
1
+ export interface DiffOptions {
2
+ since?: string;
3
+ env?: string;
4
+ env1?: string;
5
+ env2?: string;
6
+ }
7
+ export declare function diffCommand(opts: DiffOptions): Promise<void>;
@@ -0,0 +1,79 @@
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.diffCommand = diffCommand;
7
+ const chalk_1 = __importDefault(require("chalk"));
8
+ const api_client_1 = require("../api-client");
9
+ const diff_formatter_1 = require("../formatters/diff-formatter");
10
+ const badges_1 = require("../ui/badges");
11
+ const helpers_1 = require("../ui/helpers");
12
+ async function diffCommand(opts) {
13
+ try {
14
+ // Parse --since into a datetime string for the backend
15
+ let sinceStr;
16
+ if (opts.since) {
17
+ sinceStr = (0, helpers_1.parseSince)(opts.since);
18
+ }
19
+ const result = await (0, api_client_1.fetchDiffReport)({
20
+ since: sinceStr,
21
+ env: opts.env,
22
+ env1: opts.env1,
23
+ env2: opts.env2,
24
+ });
25
+ console.log("");
26
+ if (result.mode === "cross-env") {
27
+ console.log(chalk_1.default.white.bold(" Type drift: ") +
28
+ (0, badges_1.envBadge)(result.env1) +
29
+ chalk_1.default.gray(" → ") +
30
+ (0, badges_1.envBadge)(result.env2));
31
+ }
32
+ else {
33
+ const label = opts.since
34
+ ? `changes in the last ${opts.since}`
35
+ : "all type changes";
36
+ console.log(chalk_1.default.white.bold(` Type drift: ${label}`));
37
+ if (opts.env) {
38
+ console.log(chalk_1.default.gray(` Environment: ${opts.env}`));
39
+ }
40
+ }
41
+ console.log(chalk_1.default.gray(" " + "─".repeat(50)));
42
+ if (result.total === 0) {
43
+ console.log("");
44
+ console.log(chalk_1.default.green(" No type drift detected."));
45
+ if (opts.since) {
46
+ console.log(chalk_1.default.gray(` No functions had type changes in the last ${opts.since}.`));
47
+ }
48
+ console.log("");
49
+ return;
50
+ }
51
+ console.log(chalk_1.default.gray(` ${result.total} function${result.total === 1 ? "" : "s"} with type changes`));
52
+ for (const entry of result.entries) {
53
+ console.log("");
54
+ console.log(chalk_1.default.white.bold(` ${entry.functionName}`) +
55
+ chalk_1.default.gray(` (${entry.module})`));
56
+ // Show from/to metadata
57
+ console.log(chalk_1.default.gray(" from: ") +
58
+ (0, badges_1.envBadge)(entry.from.env) +
59
+ chalk_1.default.gray(" " + (0, badges_1.timeBadge)(entry.from.observed_at)));
60
+ console.log(chalk_1.default.gray(" to: ") +
61
+ (0, badges_1.envBadge)(entry.to.env) +
62
+ chalk_1.default.gray(" " + (0, badges_1.timeBadge)(entry.to.observed_at)));
63
+ console.log("");
64
+ // Indent diffs by 2 extra spaces
65
+ const formattedDiffs = (0, diff_formatter_1.formatDiffs)(entry.diffs);
66
+ const indented = formattedDiffs
67
+ .split("\n")
68
+ .map((line) => " " + line)
69
+ .join("\n");
70
+ console.log(indented);
71
+ }
72
+ console.log("");
73
+ }
74
+ catch (err) {
75
+ if (err instanceof Error) {
76
+ console.error(chalk_1.default.red(`\n Error: ${err.message}\n`));
77
+ }
78
+ }
79
+ }
@@ -0,0 +1,13 @@
1
+ export interface DocsOptions {
2
+ out?: string;
3
+ html?: boolean;
4
+ env?: string;
5
+ title?: string;
6
+ }
7
+ /**
8
+ * `trickle docs` — Generate API documentation from observed runtime types.
9
+ *
10
+ * Produces clean Markdown (or self-contained HTML) documenting every
11
+ * observed API route with request/response types and example payloads.
12
+ */
13
+ export declare function docsCommand(opts: DocsOptions): Promise<void>;