grada-run 0.0.2 → 0.32.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 (231) hide show
  1. package/.github/workflows/deploy-docs.yml +37 -0
  2. package/.github/workflows/iac-validation.yml +303 -0
  3. package/.github/workflows/publish.yml +68 -0
  4. package/.github/workflows/sync-bedrock-models.yml +57 -0
  5. package/.github/workflows/test.yml +43 -0
  6. package/.muserules +31 -0
  7. package/LICENSE +21 -0
  8. package/README.md +193 -3
  9. package/apps/docs/.astro/collections/docs.schema.json +644 -0
  10. package/apps/docs/.astro/content-assets.mjs +4 -0
  11. package/apps/docs/.astro/content-modules.mjs +4 -0
  12. package/apps/docs/.astro/content.d.ts +179 -0
  13. package/apps/docs/.astro/data-store.json +1 -0
  14. package/apps/docs/.astro/dev.json +14 -0
  15. package/apps/docs/.astro/settings.json +5 -0
  16. package/apps/docs/.astro/types.d.ts +2 -0
  17. package/apps/docs/astro.config.mjs +97 -0
  18. package/apps/docs/package.json +17 -0
  19. package/apps/docs/src/content/docs/adrs/0001-s3-native-state-locking.md +37 -0
  20. package/apps/docs/src/content/docs/adrs/0002-eject-mechanism-pure-iac.md +39 -0
  21. package/apps/docs/src/content/docs/adrs/0003-sync-ai-context-strategy.md +48 -0
  22. package/apps/docs/src/content/docs/adrs/0004-iac-driven-diagnostic-context.md +37 -0
  23. package/apps/docs/src/content/docs/adrs/0005-ecs-fargate-alb-runtime-target.md +38 -0
  24. package/apps/docs/src/content/docs/adrs/0006-github-oidc-no-stored-keys.md +37 -0
  25. package/apps/docs/src/content/docs/adrs/0007-framework-detection-with-fallback.md +37 -0
  26. package/apps/docs/src/content/docs/adrs/0008-secrets-names-in-git-values-in-aws.md +37 -0
  27. package/apps/docs/src/content/docs/adrs/0009-regenerate-with-backup-on-rerun.md +37 -0
  28. package/apps/docs/src/content/docs/adrs/0010-advisory-only-security-scans.md +37 -0
  29. package/apps/docs/src/content/docs/cli/add.md +84 -0
  30. package/apps/docs/src/content/docs/cli/apply.md +32 -0
  31. package/apps/docs/src/content/docs/cli/db.md +200 -0
  32. package/apps/docs/src/content/docs/cli/destroy.md +31 -0
  33. package/apps/docs/src/content/docs/cli/diagnose.md +37 -0
  34. package/apps/docs/src/content/docs/cli/doctor.md +28 -0
  35. package/apps/docs/src/content/docs/cli/domain.md +57 -0
  36. package/apps/docs/src/content/docs/cli/drift.md +40 -0
  37. package/apps/docs/src/content/docs/cli/eject.md +29 -0
  38. package/apps/docs/src/content/docs/cli/exec.md +49 -0
  39. package/apps/docs/src/content/docs/cli/gc.md +37 -0
  40. package/apps/docs/src/content/docs/cli/init.md +72 -0
  41. package/apps/docs/src/content/docs/cli/logs.md +39 -0
  42. package/apps/docs/src/content/docs/cli/rollback.md +51 -0
  43. package/apps/docs/src/content/docs/cli/secrets.md +73 -0
  44. package/apps/docs/src/content/docs/cli/sleep.md +53 -0
  45. package/apps/docs/src/content/docs/cli/status.md +34 -0
  46. package/apps/docs/src/content/docs/cli/sync-ai.md +27 -0
  47. package/apps/docs/src/content/docs/guides/architecture.md +87 -0
  48. package/apps/docs/src/content/docs/guides/aws-credentials.md +72 -0
  49. package/apps/docs/src/content/docs/guides/background-workers.md +45 -0
  50. package/apps/docs/src/content/docs/guides/cicd-pipeline.md +64 -0
  51. package/apps/docs/src/content/docs/guides/database-connections.md +64 -0
  52. package/apps/docs/src/content/docs/guides/docker-compose.md +37 -0
  53. package/apps/docs/src/content/docs/guides/dockerfiles.md +46 -0
  54. package/apps/docs/src/content/docs/guides/ephemeral-pr-previews.md +39 -0
  55. package/apps/docs/src/content/docs/guides/examples.md +50 -0
  56. package/apps/docs/src/content/docs/guides/frameworks.md +88 -0
  57. package/apps/docs/src/content/docs/guides/headless.md +75 -0
  58. package/apps/docs/src/content/docs/guides/quickstart.md +52 -0
  59. package/apps/docs/src/content/docs/guides/rerun-init.md +43 -0
  60. package/apps/docs/src/content/docs/guides/secrets-management.md +83 -0
  61. package/apps/docs/src/content/docs/guides/understanding-your-bill.md +63 -0
  62. package/apps/docs/src/content/docs/index.mdx +103 -0
  63. package/apps/docs/src/content/docs/migrations/astro-vercel-to-aws.md +55 -0
  64. package/apps/docs/src/content/docs/migrations/heroku-procfile-to-aws.md +41 -0
  65. package/apps/docs/src/content/docs/migrations/nextjs-vercel-to-aws.md +51 -0
  66. package/apps/docs/src/content/docs/migrations/sveltekit-vercel-to-aws.md +63 -0
  67. package/apps/docs/src/content/docs/roadmap.md +97 -0
  68. package/apps/docs/src/content/docs/testing-strategy.md +32 -0
  69. package/apps/docs/src/content.config.ts +7 -0
  70. package/apps/docs/src/custom.css +14 -0
  71. package/apps/docs/tsconfig.json +6 -0
  72. package/bin/cli.js +140 -0
  73. package/package.json +105 -7
  74. package/scripts/sync-bedrock-models.js +22 -0
  75. package/scripts/test-iac.js +261 -0
  76. package/specs/add-redis-sqs-bedrock.md +128 -0
  77. package/specs/add-storage-dynamodb.md +106 -0
  78. package/specs/bedrock-model-catalog.md +131 -0
  79. package/specs/ci-pipeline.md +17 -0
  80. package/specs/cost-transparency.md +115 -0
  81. package/specs/custom-domains-and-ses.md +153 -0
  82. package/specs/database-suite-expansion.md +151 -0
  83. package/specs/db-connect.md +69 -0
  84. package/specs/db-lifecycle-migrations.md +159 -0
  85. package/specs/dependency-aware-init.md +176 -0
  86. package/specs/deploy-stack-to-grada-run-rebrand.md +45 -0
  87. package/specs/deployment-safety.md +170 -0
  88. package/specs/diagnose.md +16 -0
  89. package/specs/docs-hub.md +16 -0
  90. package/specs/dx-polish.md +46 -0
  91. package/specs/exec.md +25 -0
  92. package/specs/finops-cron-drift.md +161 -0
  93. package/specs/gc.md +26 -0
  94. package/specs/integration-suite.md +16 -0
  95. package/specs/logs.md +32 -0
  96. package/specs/rollback-live-polling.md +40 -0
  97. package/specs/secrets-pull-audit.md +51 -0
  98. package/specs/serverless-lambda-target.md +133 -0
  99. package/specs/status.md +31 -0
  100. package/specs/telemetry-and-spawn-hardening.md +69 -0
  101. package/specs/telemetry-hardening.md +35 -0
  102. package/src/commands/add.js +1111 -0
  103. package/src/commands/apply.js +214 -0
  104. package/src/commands/db/backup.js +229 -0
  105. package/src/commands/db/connect.js +304 -0
  106. package/src/commands/db/enable-vector.js +344 -0
  107. package/src/commands/db/import.js +604 -0
  108. package/src/commands/db/migrate.js +477 -0
  109. package/src/commands/db/restore.js +361 -0
  110. package/src/commands/db.js +87 -0
  111. package/src/commands/destroy.js +217 -0
  112. package/src/commands/diagnose.js +460 -0
  113. package/src/commands/doctor.js +109 -0
  114. package/src/commands/domain.js +685 -0
  115. package/src/commands/drift.js +243 -0
  116. package/src/commands/eject.js +127 -0
  117. package/src/commands/exec.js +222 -0
  118. package/src/commands/gc.js +250 -0
  119. package/src/commands/init.js +649 -0
  120. package/src/commands/logs.js +256 -0
  121. package/src/commands/rollback.js +323 -0
  122. package/src/commands/secrets.js +485 -0
  123. package/src/commands/sleep.js +347 -0
  124. package/src/commands/status.js +309 -0
  125. package/src/commands/sync-ai.js +115 -0
  126. package/src/commands/wake.js +337 -0
  127. package/src/core/parser.js +126 -0
  128. package/src/core/telemetry.js +244 -0
  129. package/src/data/bedrock-models.json +896 -0
  130. package/src/utils/addons.js +126 -0
  131. package/src/utils/ai-rules.js +59 -0
  132. package/src/utils/args.js +91 -0
  133. package/src/utils/aws.js +178 -0
  134. package/src/utils/backup.js +69 -0
  135. package/src/utils/bedrock-catalog.js +511 -0
  136. package/src/utils/capabilities.js +500 -0
  137. package/src/utils/command.js +65 -0
  138. package/src/utils/db-tunnel.js +164 -0
  139. package/src/utils/detector.js +298 -0
  140. package/src/utils/dockerCompose.js +65 -0
  141. package/src/utils/domains.js +73 -0
  142. package/src/utils/ecs-runner.js +289 -0
  143. package/src/utils/ecs.js +92 -0
  144. package/src/utils/frameworks.js +55 -0
  145. package/src/utils/generator.js +527 -0
  146. package/src/utils/hcl.js +426 -0
  147. package/src/utils/lambda-ecr.js +185 -0
  148. package/src/utils/prompts.js +278 -0
  149. package/src/utils/rds.js +131 -0
  150. package/src/utils/resolvers.js +174 -0
  151. package/src/utils/sleep-state.js +140 -0
  152. package/src/utils/sleep-targets.js +139 -0
  153. package/src/utils/system.js +42 -0
  154. package/src/utils/terraform.js +70 -0
  155. package/src/utils/visualizer.js +381 -0
  156. package/src/utils/warnings.js +49 -0
  157. package/templates/README.md +150 -0
  158. package/templates/docker/django.Dockerfile +40 -0
  159. package/templates/docker/go.Dockerfile +23 -0
  160. package/templates/docker/nestjs.Dockerfile +33 -0
  161. package/templates/docker/nextjs.Dockerfile +55 -0
  162. package/templates/docker/node.Dockerfile +24 -0
  163. package/templates/docker/nuxt.Dockerfile +47 -0
  164. package/templates/docker/python.Dockerfile +38 -0
  165. package/templates/docker/rails.Dockerfile +59 -0
  166. package/templates/docker/static.Dockerfile +32 -0
  167. package/templates/docker/svelte.Dockerfile +52 -0
  168. package/templates/github/deploy-lambda.yml +120 -0
  169. package/templates/github/deploy.yml +138 -0
  170. package/templates/github/drift.yml +112 -0
  171. package/templates/github/preview-lambda.yml +86 -0
  172. package/templates/github/preview.yml +69 -0
  173. package/templates/github/teardown.yml +43 -0
  174. package/templates/terraform/addons/bedrock.tf +34 -0
  175. package/templates/terraform/addons/cron-lambda.tf +78 -0
  176. package/templates/terraform/addons/cron.tf +101 -0
  177. package/templates/terraform/addons/dynamodb.tf +73 -0
  178. package/templates/terraform/addons/redis.tf +64 -0
  179. package/templates/terraform/addons/s3.tf +143 -0
  180. package/templates/terraform/addons/ses.tf +73 -0
  181. package/templates/terraform/addons/sqs.tf +67 -0
  182. package/templates/terraform/backend.tf +22 -0
  183. package/templates/terraform/cloudfront-lambda.tf +80 -0
  184. package/templates/terraform/cloudfront.tf +80 -0
  185. package/templates/terraform/database-aurora-postgresql.tf +92 -0
  186. package/templates/terraform/database-mysql.tf +72 -0
  187. package/templates/terraform/database.tf +71 -0
  188. package/templates/terraform/main-lambda.tf +229 -0
  189. package/templates/terraform/main.tf +296 -0
  190. package/templates/terraform/network.tf +95 -0
  191. package/templates/terraform/oidc.tf +64 -0
  192. package/templates/terraform/secrets.tf +31 -0
  193. package/templates/terraform/worker.tf +69 -0
  194. package/tests/__snapshots__/generator.test.js.snap +9633 -0
  195. package/tests/add.test.js +2037 -0
  196. package/tests/ai.test.js +94 -0
  197. package/tests/apply.test.js +488 -0
  198. package/tests/args.test.js +86 -0
  199. package/tests/aws.test.js +244 -0
  200. package/tests/capabilities.test.js +307 -0
  201. package/tests/cli.test.js +29 -0
  202. package/tests/command.test.js +100 -0
  203. package/tests/commands-import.test.js +74 -0
  204. package/tests/db.test.js +2704 -0
  205. package/tests/destroy.test.js +391 -0
  206. package/tests/detector.test.js +79 -0
  207. package/tests/diagnose.test.js +779 -0
  208. package/tests/doctor.test.js +202 -0
  209. package/tests/domain.test.js +899 -0
  210. package/tests/drift.test.js +243 -0
  211. package/tests/ecs.test.js +130 -0
  212. package/tests/eject.test.js +65 -0
  213. package/tests/exec.test.js +380 -0
  214. package/tests/gc.test.js +496 -0
  215. package/tests/generator.test.js +794 -0
  216. package/tests/headless.test.js +562 -0
  217. package/tests/lambda-ecr.test.js +185 -0
  218. package/tests/logs.test.js +447 -0
  219. package/tests/parser.test.js +160 -0
  220. package/tests/rds.test.js +244 -0
  221. package/tests/resolvers.test.js +282 -0
  222. package/tests/rollback.test.js +692 -0
  223. package/tests/secrets.test.js +752 -0
  224. package/tests/sleep-wake.test.js +1016 -0
  225. package/tests/status.test.js +370 -0
  226. package/tests/system.test.js +70 -0
  227. package/tests/telemetry.test.js +520 -0
  228. package/tests/terraform.test.js +84 -0
  229. package/tests/visualizer.test.js +496 -0
  230. package/vitest.config.js +9 -0
  231. package/index.js +0 -2
@@ -0,0 +1,244 @@
1
+ import crypto from 'crypto';
2
+ import fs from 'fs';
3
+ import os from 'os';
4
+ import path from 'path';
5
+
6
+ const TELEMETRY_ENDPOINT = 'https://eu.i.posthog.com/capture/';
7
+ const POSTHOG_API_KEY = 'phc_o2wgA3jVT9rVDiGSDzFAR42zZeiVGhhCY53HXVHUcYGT';
8
+ const pendingRequests = [];
9
+
10
+ const CLI_ENTRY_BASENAMES = ['cli.js', 'grada', 'grada-run', 'deploy-stack'];
11
+
12
+ let cachedDistinctId = null;
13
+
14
+ export function resetTelemetryIdentityCache() {
15
+ cachedDistinctId = null;
16
+ }
17
+
18
+ function sha16(raw) {
19
+ return crypto.createHash('sha256').update(String(raw)).digest('hex').substring(0, 16);
20
+ }
21
+
22
+ // An env var counts as active only when defined with a meaningful value.
23
+ // This keeps CI='false', CI='0', and CI='' from masquerading as CI.
24
+ export function isActiveEnvValue(value) {
25
+ if (value === undefined || value === null) return false;
26
+ const normalized = String(value).trim().toLowerCase();
27
+ return normalized !== '' && normalized !== '0' && normalized !== 'false';
28
+ }
29
+
30
+ // Distinguishes real CI pipelines from local shells/agents that set CI=true.
31
+ // Precedence: specific providers first, then generic CI, then none.
32
+ export function detectCiProvider(env = process.env) {
33
+ if (isActiveEnvValue(env.GITHUB_ACTIONS)) return 'github_actions';
34
+ if (isActiveEnvValue(env.GITLAB_CI)) return 'gitlab_ci';
35
+ if (isActiveEnvValue(env.CIRCLECI)) return 'circleci';
36
+ if (isActiveEnvValue(env.JENKINS_URL)) return 'jenkins';
37
+ if (isActiveEnvValue(env.CI) || isActiveEnvValue(env.CONTINUOUS_INTEGRATION)) return 'generic_ci';
38
+ return 'none';
39
+ }
40
+
41
+ export function isTestEnv(env = process.env) {
42
+ return Boolean(env.VITEST || env.NODE_ENV === 'test');
43
+ }
44
+
45
+ export function defaultTelemetryIdPath() {
46
+ return path.join(os.homedir(), '.grada', 'telemetry-id');
47
+ }
48
+
49
+ export function legacyTelemetryIdPath() {
50
+ return path.join(os.homedir(), '.deploy-stack', 'telemetry-id');
51
+ }
52
+
53
+ // Migrates a legacy telemetry identity forward: when the new path is
54
+ // missing but the legacy one holds an identity, its contents seed the
55
+ // new file so the stable distinct ID survives the rebrand. Returns true
56
+ // when a migration happened. Never throws.
57
+ export function migrateLegacyTelemetryId(newPath, legacyPath) {
58
+ try {
59
+ if (fs.existsSync(newPath)) return false;
60
+ const raw = fs.readFileSync(legacyPath, 'utf-8').trim();
61
+ if (!raw) return false;
62
+ fs.mkdirSync(path.dirname(newPath), { recursive: true });
63
+ fs.writeFileSync(newPath, `${raw}\n`, 'utf-8');
64
+ return true;
65
+ } catch {
66
+ return false;
67
+ }
68
+ }
69
+
70
+ // Branch A: deterministic machine/CI fingerprint for ephemeral runners.
71
+ // Cwd is deliberately excluded so commands from different directories on
72
+ // the same runner share one Person within a run.
73
+ function fingerprintDistinctId() {
74
+ return sha16([
75
+ os.hostname(),
76
+ os.platform(),
77
+ os.arch(),
78
+ process.env.GITHUB_REPOSITORY || '',
79
+ process.env.GITHUB_RUN_ID || '',
80
+ process.env.GITLAB_PROJECT_PATH || '',
81
+ ].join(':'));
82
+ }
83
+
84
+ // Branch B: persistent random UUID, created on first use. Any filesystem
85
+ // failure falls back to the Branch A fingerprint so telemetry never throws.
86
+ function persistentDistinctId(idPath) {
87
+ try {
88
+ let raw = '';
89
+ try {
90
+ raw = fs.readFileSync(idPath, 'utf-8').trim();
91
+ } catch {
92
+ raw = '';
93
+ }
94
+ if (!raw) {
95
+ raw = crypto.randomUUID();
96
+ fs.mkdirSync(path.dirname(idPath), { recursive: true });
97
+ fs.writeFileSync(idPath, `${raw}\n`, 'utf-8');
98
+ }
99
+ return sha16(raw);
100
+ } catch {
101
+ return fingerprintDistinctId();
102
+ }
103
+ }
104
+
105
+ export function resolveDistinctId({ ciProvider = detectCiProvider(), testEnv = isTestEnv() } = {}) {
106
+ if (cachedDistinctId) return cachedDistinctId;
107
+ const overridePath = process.env.GRADA_TELEMETRY_ID_PATH || process.env.DEPLOY_STACK_TELEMETRY_ID_PATH;
108
+ let resolved;
109
+ if (typeof overridePath === 'string' && overridePath.trim() !== '') {
110
+ resolved = persistentDistinctId(overridePath);
111
+ } else if (ciProvider !== 'none' || testEnv) {
112
+ resolved = fingerprintDistinctId();
113
+ } else {
114
+ const idPath = defaultTelemetryIdPath();
115
+ migrateLegacyTelemetryId(idPath, legacyTelemetryIdPath());
116
+ resolved = persistentDistinctId(idPath);
117
+ }
118
+ cachedDistinctId = resolved;
119
+ return resolved;
120
+ }
121
+
122
+ export function trackEvent(eventName, properties) {
123
+ // 1. Respect privacy standards
124
+ if (process.env.DO_NOT_TRACK === '1' || process.env.DO_NOT_TRACK === 'true') {
125
+ return;
126
+ }
127
+
128
+ // 2. Keep all events: drop only missing, blank, or non-serializable
129
+ // (object/function) names; normalize the rest so strings like
130
+ // 'cli-error' plus booleans and numbers serialize safely.
131
+ if (
132
+ eventName === undefined ||
133
+ eventName === null ||
134
+ typeof eventName === 'object' ||
135
+ typeof eventName === 'function' ||
136
+ String(eventName).trim() === ''
137
+ ) {
138
+ return;
139
+ }
140
+ const normalizedEvent = String(eventName);
141
+
142
+ // 3. Protect the PostHog column schema: only spread plain objects.
143
+ // Primitives and arrays are wrapped so e.g. a string never spreads its
144
+ // character indices (0, 1, 2, ...) as top-level columns.
145
+ let eventProps;
146
+ if (properties === undefined || properties === null) {
147
+ eventProps = {};
148
+ } else if (typeof properties === 'object' && !Array.isArray(properties)) {
149
+ eventProps = { ...properties };
150
+ } else {
151
+ eventProps = { raw_properties: properties };
152
+ }
153
+
154
+ // 4. Resolve context first: provider before is_ci, entry before command.
155
+ const ciProvider = detectCiProvider();
156
+ const testEnv = isTestEnv();
157
+ const isCi = isActiveEnvValue(process.env.CI) || ciProvider !== 'none';
158
+ const isCliEntry = CLI_ENTRY_BASENAMES.includes(path.basename(process.argv?.[1] || ''));
159
+
160
+ // 5. Machine-scoped Person identity, stable across commands in this env.
161
+ const distinctId = resolveDistinctId({ ciProvider, testEnv });
162
+
163
+ // 6. Project-scoped grouping stays in the payload as a hash. Coerce
164
+ // first so non-string projectName values can never throw inside
165
+ // createHash; fall back to the working directory name when omitted.
166
+ let rawProjectName = eventProps.projectName;
167
+ if (rawProjectName === undefined || rawProjectName === null || rawProjectName === '') {
168
+ rawProjectName = path.basename(process.cwd()) || 'unknown';
169
+ }
170
+ const projectId = sha16(rawProjectName);
171
+
172
+ // 7. Strip the raw name out of the payload
173
+ delete eventProps.projectName;
174
+
175
+ const payload = {
176
+ api_key: POSTHOG_API_KEY,
177
+ event: normalizedEvent,
178
+ distinct_id: distinctId,
179
+ properties: {
180
+ os: process.platform,
181
+ node_version: process.version,
182
+ is_ci: isCi,
183
+ ci_provider: ciProvider,
184
+ is_test_env: testEnv,
185
+ is_tty: Boolean(process.stdout && process.stdout.isTTY),
186
+ is_cli_entry: isCliEntry,
187
+ cli_command: isCliEntry
188
+ ? (process.env.CLI_COMMAND || process.argv.slice(2).join(' ') || 'unknown')
189
+ : 'module_import',
190
+ project_id: projectId,
191
+ framework: process.env.GRADA_FRAMEWORK || process.env.DEPLOY_STACK_FRAMEWORK || eventProps.framework || undefined,
192
+ ...eventProps
193
+ }
194
+ };
195
+
196
+ // 6. Fire and forget (No 'await' so we don't block the user's terminal)
197
+ const request = fetch(TELEMETRY_ENDPOINT, {
198
+ method: 'POST',
199
+ headers: { 'Content-Type': 'application/json' },
200
+ body: JSON.stringify(payload),
201
+ }).catch(() => {
202
+ // Silently swallow network errors (e.g., user is offline)
203
+ });
204
+
205
+ pendingRequests.push(request);
206
+ }
207
+
208
+ export async function flushTelemetry() {
209
+ if (pendingRequests.length > 0) {
210
+ await Promise.all(pendingRequests);
211
+ }
212
+ }
213
+
214
+ // Stamps the success flag onto wrapper properties using trackEvent's own
215
+ // normalization: plain objects merge, missing values send the flag alone,
216
+ // and anything else (strings, numbers, arrays) wraps as raw_properties so
217
+ // a primitive can never spread its indices as top-level columns.
218
+ function withSuccessFlag(properties, success) {
219
+ if (properties === undefined || properties === null) {
220
+ return { success };
221
+ }
222
+ if (typeof properties === 'object' && !Array.isArray(properties)) {
223
+ return { ...properties, success };
224
+ }
225
+ return { raw_properties: properties, success };
226
+ }
227
+
228
+ // Reports a successful command outcome: tracks the event stamped
229
+ // `success: true` and flushes immediately, so a subsequent exit or
230
+ // long-lived process never loses it. Single definition for the
231
+ // track+flush pair every command repeats on its happy path.
232
+ export async function trackSuccess(eventName, properties = {}) {
233
+ trackEvent(eventName, withSuccessFlag(properties, true));
234
+ await flushTelemetry();
235
+ }
236
+
237
+ // Reports a failed command outcome without terminating: tracks the event
238
+ // stamped `success: false` and flushes immediately. Companion to
239
+ // trackSuccess for catch blocks that must keep branching (auth recovery,
240
+ // not-found guidance) after reporting — failCommand covers terminal failures.
241
+ export async function trackFailure(eventName, properties = {}) {
242
+ trackEvent(eventName, withSuccessFlag(properties, false));
243
+ await flushTelemetry();
244
+ }