@unbrained/pm-cli 2026.3.12 → 2026.5.1

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 (285) hide show
  1. package/.agents/pm/extensions/.managed-extensions.json +42 -0
  2. package/.agents/pm/extensions/beads/index.js +109 -0
  3. package/.agents/pm/extensions/beads/manifest.json +7 -0
  4. package/{dist/cli/commands/beads.js → .agents/pm/extensions/beads/runtime.js} +31 -21
  5. package/.agents/pm/extensions/beads/runtime.ts +702 -0
  6. package/.agents/pm/extensions/todos/index.js +126 -0
  7. package/.agents/pm/extensions/todos/manifest.json +7 -0
  8. package/{dist/extensions/builtins/todos/import-export.js → .agents/pm/extensions/todos/runtime.js} +39 -29
  9. package/.agents/pm/extensions/todos/runtime.ts +568 -0
  10. package/AGENTS.md +196 -92
  11. package/CHANGELOG.md +399 -0
  12. package/CODE_OF_CONDUCT.md +42 -0
  13. package/CONTRIBUTING.md +144 -0
  14. package/PRD.md +512 -164
  15. package/README.md +1053 -2
  16. package/SECURITY.md +51 -0
  17. package/dist/cli/commands/activity.d.ts +5 -0
  18. package/dist/cli/commands/activity.js +66 -3
  19. package/dist/cli/commands/activity.js.map +1 -1
  20. package/dist/cli/commands/aggregate.d.ts +54 -0
  21. package/dist/cli/commands/aggregate.js +181 -0
  22. package/dist/cli/commands/aggregate.js.map +1 -0
  23. package/dist/cli/commands/append.js +4 -1
  24. package/dist/cli/commands/append.js.map +1 -1
  25. package/dist/cli/commands/calendar.d.ts +109 -0
  26. package/dist/cli/commands/calendar.js +797 -0
  27. package/dist/cli/commands/calendar.js.map +1 -0
  28. package/dist/cli/commands/claim.d.ts +5 -1
  29. package/dist/cli/commands/claim.js +42 -21
  30. package/dist/cli/commands/claim.js.map +1 -1
  31. package/dist/cli/commands/close.d.ts +1 -0
  32. package/dist/cli/commands/close.js +54 -5
  33. package/dist/cli/commands/close.js.map +1 -1
  34. package/dist/cli/commands/comments-audit.d.ts +91 -0
  35. package/dist/cli/commands/comments-audit.js +195 -0
  36. package/dist/cli/commands/comments-audit.js.map +1 -0
  37. package/dist/cli/commands/comments.d.ts +1 -0
  38. package/dist/cli/commands/comments.js +70 -21
  39. package/dist/cli/commands/comments.js.map +1 -1
  40. package/dist/cli/commands/completion.d.ts +10 -4
  41. package/dist/cli/commands/completion.js +1184 -137
  42. package/dist/cli/commands/completion.js.map +1 -1
  43. package/dist/cli/commands/config.d.ts +35 -3
  44. package/dist/cli/commands/config.js +968 -13
  45. package/dist/cli/commands/config.js.map +1 -1
  46. package/dist/cli/commands/context.d.ts +86 -0
  47. package/dist/cli/commands/context.js +299 -0
  48. package/dist/cli/commands/context.js.map +1 -0
  49. package/dist/cli/commands/contracts.d.ts +78 -0
  50. package/dist/cli/commands/contracts.js +920 -0
  51. package/dist/cli/commands/contracts.js.map +1 -0
  52. package/dist/cli/commands/create.d.ts +48 -14
  53. package/dist/cli/commands/create.js +1331 -160
  54. package/dist/cli/commands/create.js.map +1 -1
  55. package/dist/cli/commands/dedupe-audit.d.ts +81 -0
  56. package/dist/cli/commands/dedupe-audit.js +330 -0
  57. package/dist/cli/commands/dedupe-audit.js.map +1 -0
  58. package/dist/cli/commands/deps.d.ts +52 -0
  59. package/dist/cli/commands/deps.js +204 -0
  60. package/dist/cli/commands/deps.js.map +1 -0
  61. package/dist/cli/commands/docs.d.ts +19 -0
  62. package/dist/cli/commands/docs.js +212 -13
  63. package/dist/cli/commands/docs.js.map +1 -1
  64. package/dist/cli/commands/extension.d.ts +122 -0
  65. package/dist/cli/commands/extension.js +1850 -0
  66. package/dist/cli/commands/extension.js.map +1 -0
  67. package/dist/cli/commands/files.d.ts +52 -1
  68. package/dist/cli/commands/files.js +443 -13
  69. package/dist/cli/commands/files.js.map +1 -1
  70. package/dist/cli/commands/gc.d.ts +11 -1
  71. package/dist/cli/commands/gc.js +89 -11
  72. package/dist/cli/commands/gc.js.map +1 -1
  73. package/dist/cli/commands/get.d.ts +13 -0
  74. package/dist/cli/commands/get.js +35 -3
  75. package/dist/cli/commands/get.js.map +1 -1
  76. package/dist/cli/commands/health.d.ts +10 -2
  77. package/dist/cli/commands/health.js +774 -23
  78. package/dist/cli/commands/health.js.map +1 -1
  79. package/dist/cli/commands/history.d.ts +20 -0
  80. package/dist/cli/commands/history.js +152 -6
  81. package/dist/cli/commands/history.js.map +1 -1
  82. package/dist/cli/commands/index.d.ts +16 -3
  83. package/dist/cli/commands/index.js +16 -3
  84. package/dist/cli/commands/index.js.map +1 -1
  85. package/dist/cli/commands/init.d.ts +7 -2
  86. package/dist/cli/commands/init.js +137 -5
  87. package/dist/cli/commands/init.js.map +1 -1
  88. package/dist/cli/commands/learnings.d.ts +17 -0
  89. package/dist/cli/commands/learnings.js +129 -0
  90. package/dist/cli/commands/learnings.js.map +1 -0
  91. package/dist/cli/commands/list.d.ts +29 -1
  92. package/dist/cli/commands/list.js +289 -53
  93. package/dist/cli/commands/list.js.map +1 -1
  94. package/dist/cli/commands/normalize.d.ts +51 -0
  95. package/dist/cli/commands/normalize.js +298 -0
  96. package/dist/cli/commands/normalize.js.map +1 -0
  97. package/dist/cli/commands/notes.d.ts +17 -0
  98. package/dist/cli/commands/notes.js +129 -0
  99. package/dist/cli/commands/notes.js.map +1 -0
  100. package/dist/cli/commands/reindex.d.ts +1 -0
  101. package/dist/cli/commands/reindex.js +208 -32
  102. package/dist/cli/commands/reindex.js.map +1 -1
  103. package/dist/cli/commands/restore.js +164 -30
  104. package/dist/cli/commands/restore.js.map +1 -1
  105. package/dist/cli/commands/search.d.ts +14 -1
  106. package/dist/cli/commands/search.js +475 -81
  107. package/dist/cli/commands/search.js.map +1 -1
  108. package/dist/cli/commands/stats.js +26 -10
  109. package/dist/cli/commands/stats.js.map +1 -1
  110. package/dist/cli/commands/templates.d.ts +26 -0
  111. package/dist/cli/commands/templates.js +179 -0
  112. package/dist/cli/commands/templates.js.map +1 -0
  113. package/dist/cli/commands/test-all.d.ts +19 -1
  114. package/dist/cli/commands/test-all.js +161 -13
  115. package/dist/cli/commands/test-all.js.map +1 -1
  116. package/dist/cli/commands/test-runs.d.ts +63 -0
  117. package/dist/cli/commands/test-runs.js +179 -0
  118. package/dist/cli/commands/test-runs.js.map +1 -0
  119. package/dist/cli/commands/test.d.ts +75 -1
  120. package/dist/cli/commands/test.js +1360 -41
  121. package/dist/cli/commands/test.js.map +1 -1
  122. package/dist/cli/commands/update-many.d.ts +57 -0
  123. package/dist/cli/commands/update-many.js +631 -0
  124. package/dist/cli/commands/update-many.js.map +1 -0
  125. package/dist/cli/commands/update.d.ts +30 -0
  126. package/dist/cli/commands/update.js +1393 -84
  127. package/dist/cli/commands/update.js.map +1 -1
  128. package/dist/cli/commands/validate.d.ts +30 -0
  129. package/dist/cli/commands/validate.js +1140 -0
  130. package/dist/cli/commands/validate.js.map +1 -0
  131. package/dist/cli/error-guidance.d.ts +33 -0
  132. package/dist/cli/error-guidance.js +337 -0
  133. package/dist/cli/error-guidance.js.map +1 -0
  134. package/dist/cli/extension-command-options.d.ts +1 -0
  135. package/dist/cli/extension-command-options.js +92 -0
  136. package/dist/cli/extension-command-options.js.map +1 -1
  137. package/dist/cli/help-content.d.ts +20 -0
  138. package/dist/cli/help-content.js +543 -0
  139. package/dist/cli/help-content.js.map +1 -0
  140. package/dist/cli/main.js +3625 -445
  141. package/dist/cli/main.js.map +1 -1
  142. package/dist/core/extensions/index.d.ts +13 -1
  143. package/dist/core/extensions/index.js +108 -1
  144. package/dist/core/extensions/index.js.map +1 -1
  145. package/dist/core/extensions/item-fields.d.ts +2 -0
  146. package/dist/core/extensions/item-fields.js +79 -0
  147. package/dist/core/extensions/item-fields.js.map +1 -0
  148. package/dist/core/extensions/loader.d.ts +322 -9
  149. package/dist/core/extensions/loader.js +911 -20
  150. package/dist/core/extensions/loader.js.map +1 -1
  151. package/dist/core/extensions/runtime-registrations.d.ts +5 -0
  152. package/dist/core/extensions/runtime-registrations.js +51 -0
  153. package/dist/core/extensions/runtime-registrations.js.map +1 -0
  154. package/dist/core/history/history-stream-policy.d.ts +20 -0
  155. package/dist/core/history/history-stream-policy.js +53 -0
  156. package/dist/core/history/history-stream-policy.js.map +1 -0
  157. package/dist/core/history/history.js +90 -1
  158. package/dist/core/history/history.js.map +1 -1
  159. package/dist/core/item/id.js +4 -1
  160. package/dist/core/item/id.js.map +1 -1
  161. package/dist/core/item/index.d.ts +1 -0
  162. package/dist/core/item/index.js +1 -0
  163. package/dist/core/item/index.js.map +1 -1
  164. package/dist/core/item/item-format.d.ts +11 -5
  165. package/dist/core/item/item-format.js +507 -24
  166. package/dist/core/item/item-format.js.map +1 -1
  167. package/dist/core/item/parent-reference-policy.d.ts +6 -0
  168. package/dist/core/item/parent-reference-policy.js +32 -0
  169. package/dist/core/item/parent-reference-policy.js.map +1 -0
  170. package/dist/core/item/parse.d.ts +5 -0
  171. package/dist/core/item/parse.js +216 -19
  172. package/dist/core/item/parse.js.map +1 -1
  173. package/dist/core/item/sprint-release-format.d.ts +6 -0
  174. package/dist/core/item/sprint-release-format.js +33 -0
  175. package/dist/core/item/sprint-release-format.js.map +1 -0
  176. package/dist/core/item/status.d.ts +3 -0
  177. package/dist/core/item/status.js +24 -0
  178. package/dist/core/item/status.js.map +1 -0
  179. package/dist/core/item/type-registry.d.ts +37 -0
  180. package/dist/core/item/type-registry.js +706 -0
  181. package/dist/core/item/type-registry.js.map +1 -0
  182. package/dist/core/lock/lock.d.ts +1 -1
  183. package/dist/core/lock/lock.js +101 -12
  184. package/dist/core/lock/lock.js.map +1 -1
  185. package/dist/core/output/command-aware.d.ts +1 -0
  186. package/dist/core/output/command-aware.js +394 -0
  187. package/dist/core/output/command-aware.js.map +1 -0
  188. package/dist/core/output/output.d.ts +3 -0
  189. package/dist/core/output/output.js +124 -6
  190. package/dist/core/output/output.js.map +1 -1
  191. package/dist/core/schema/runtime-field-filters.d.ts +3 -0
  192. package/dist/core/schema/runtime-field-filters.js +39 -0
  193. package/dist/core/schema/runtime-field-filters.js.map +1 -0
  194. package/dist/core/schema/runtime-field-values.d.ts +8 -0
  195. package/dist/core/schema/runtime-field-values.js +154 -0
  196. package/dist/core/schema/runtime-field-values.js.map +1 -0
  197. package/dist/core/schema/runtime-schema.d.ts +68 -0
  198. package/dist/core/schema/runtime-schema.js +554 -0
  199. package/dist/core/schema/runtime-schema.js.map +1 -0
  200. package/dist/core/search/cache.d.ts +13 -1
  201. package/dist/core/search/cache.js +123 -14
  202. package/dist/core/search/cache.js.map +1 -1
  203. package/dist/core/search/semantic-defaults.d.ts +6 -0
  204. package/dist/core/search/semantic-defaults.js +120 -0
  205. package/dist/core/search/semantic-defaults.js.map +1 -0
  206. package/dist/core/search/vector-stores.js +3 -1
  207. package/dist/core/search/vector-stores.js.map +1 -1
  208. package/dist/core/shared/command-types.d.ts +2 -0
  209. package/dist/core/shared/conflict-markers.d.ts +7 -0
  210. package/dist/core/shared/conflict-markers.js +27 -0
  211. package/dist/core/shared/conflict-markers.js.map +1 -0
  212. package/dist/core/shared/constants.d.ts +15 -4
  213. package/dist/core/shared/constants.js +141 -1
  214. package/dist/core/shared/constants.js.map +1 -1
  215. package/dist/core/shared/errors.d.ts +10 -1
  216. package/dist/core/shared/errors.js +3 -1
  217. package/dist/core/shared/errors.js.map +1 -1
  218. package/dist/core/shared/text-normalization.d.ts +4 -0
  219. package/dist/core/shared/text-normalization.js +33 -0
  220. package/dist/core/shared/text-normalization.js.map +1 -0
  221. package/dist/core/shared/time.d.ts +1 -2
  222. package/dist/core/shared/time.js +98 -11
  223. package/dist/core/shared/time.js.map +1 -1
  224. package/dist/core/store/index.d.ts +1 -0
  225. package/dist/core/store/index.js +1 -0
  226. package/dist/core/store/index.js.map +1 -1
  227. package/dist/core/store/item-format-migration.d.ts +9 -0
  228. package/dist/core/store/item-format-migration.js +87 -0
  229. package/dist/core/store/item-format-migration.js.map +1 -0
  230. package/dist/core/store/item-store.d.ts +13 -4
  231. package/dist/core/store/item-store.js +238 -51
  232. package/dist/core/store/item-store.js.map +1 -1
  233. package/dist/core/store/paths.d.ts +21 -3
  234. package/dist/core/store/paths.js +59 -4
  235. package/dist/core/store/paths.js.map +1 -1
  236. package/dist/core/store/settings.d.ts +14 -1
  237. package/dist/core/store/settings.js +463 -7
  238. package/dist/core/store/settings.js.map +1 -1
  239. package/dist/core/telemetry/consent.d.ts +2 -0
  240. package/dist/core/telemetry/consent.js +79 -0
  241. package/dist/core/telemetry/consent.js.map +1 -0
  242. package/dist/core/telemetry/runtime.d.ts +38 -0
  243. package/dist/core/telemetry/runtime.js +733 -0
  244. package/dist/core/telemetry/runtime.js.map +1 -0
  245. package/dist/core/test/background-runs.d.ts +117 -0
  246. package/dist/core/test/background-runs.js +760 -0
  247. package/dist/core/test/background-runs.js.map +1 -0
  248. package/dist/core/test/item-test-run-tracking.d.ts +9 -0
  249. package/dist/core/test/item-test-run-tracking.js +50 -0
  250. package/dist/core/test/item-test-run-tracking.js.map +1 -0
  251. package/dist/sdk/cli-contracts.d.ts +92 -0
  252. package/dist/sdk/cli-contracts.js +2357 -0
  253. package/dist/sdk/cli-contracts.js.map +1 -0
  254. package/dist/sdk/index.d.ts +34 -0
  255. package/dist/sdk/index.js +23 -0
  256. package/dist/sdk/index.js.map +1 -0
  257. package/dist/types.d.ts +197 -3
  258. package/dist/types.js +48 -1
  259. package/dist/types.js.map +1 -1
  260. package/docs/ARCHITECTURE.md +368 -39
  261. package/docs/EXTENSIONS.md +454 -49
  262. package/docs/RELEASING.md +68 -19
  263. package/docs/SDK.md +123 -0
  264. package/docs/examples/starter-extension/README.md +48 -0
  265. package/docs/examples/starter-extension/index.js +191 -0
  266. package/docs/examples/starter-extension/manifest.json +17 -0
  267. package/docs/examples/starter-extension/package.json +10 -0
  268. package/package.json +33 -6
  269. package/.pi/extensions/pm-cli/index.ts +0 -778
  270. package/dist/cli/commands/beads.d.ts +0 -16
  271. package/dist/cli/commands/beads.js.map +0 -1
  272. package/dist/cli/commands/install.d.ts +0 -18
  273. package/dist/cli/commands/install.js +0 -87
  274. package/dist/cli/commands/install.js.map +0 -1
  275. package/dist/core/extensions/builtins.d.ts +0 -3
  276. package/dist/core/extensions/builtins.js +0 -47
  277. package/dist/core/extensions/builtins.js.map +0 -1
  278. package/dist/extensions/builtins/beads/index.d.ts +0 -8
  279. package/dist/extensions/builtins/beads/index.js +0 -33
  280. package/dist/extensions/builtins/beads/index.js.map +0 -1
  281. package/dist/extensions/builtins/todos/import-export.d.ts +0 -26
  282. package/dist/extensions/builtins/todos/import-export.js.map +0 -1
  283. package/dist/extensions/builtins/todos/index.d.ts +0 -8
  284. package/dist/extensions/builtins/todos/index.js +0 -38
  285. package/dist/extensions/builtins/todos/index.js.map +0 -1
@@ -0,0 +1,733 @@
1
+ import crypto from "node:crypto";
2
+ import os from "node:os";
3
+ import path from "node:path";
4
+ import { appendLineAtomic, readFileIfExists, writeFileAtomic } from "../fs/fs-utils.js";
5
+ import { resolveGlobalPmRoot } from "../store/paths.js";
6
+ import { readSettings, writeSettings } from "../store/settings.js";
7
+ const TELEMETRY_QUEUE_RELATIVE_PATH = path.join("runtime", "telemetry", "events.jsonl");
8
+ const TELEMETRY_STATE_RELATIVE_PATH = path.join("runtime", "telemetry", "state.json");
9
+ const TELEMETRY_SCHEMA_VERSION = 1;
10
+ const TELEMETRY_FLUSH_BATCH_SIZE = 100;
11
+ const TELEMETRY_MAX_RETRY_DELAY_MS = 3_600_000;
12
+ const TELEMETRY_RETRY_BASE_DELAY_MS = 30_000;
13
+ const TELEMETRY_HTTP_TIMEOUT_MS = 2_500;
14
+ const MILLISECONDS_PER_DAY = 86_400_000;
15
+ const OTEL_TRACES_ENDPOINT_ENV = "OTEL_EXPORTER_OTLP_TRACES_ENDPOINT";
16
+ const OTEL_BASE_ENDPOINT_ENV = "OTEL_EXPORTER_OTLP_ENDPOINT";
17
+ const OTEL_SERVICE_NAME_ENV = "OTEL_SERVICE_NAME";
18
+ const PM_TELEMETRY_DISABLED_ENV = "PM_TELEMETRY_DISABLED";
19
+ const PM_TELEMETRY_DISABLED_VALUES = new Set(["1", "true", "yes", "on"]);
20
+ const PM_TELEMETRY_OTEL_DISABLED_ENV = "PM_TELEMETRY_OTEL_DISABLED";
21
+ const PM_TELEMETRY_OTEL_DISABLED_VALUES = new Set(["1", "true", "yes", "on"]);
22
+ const PM_TELEMETRY_SOURCE_CONTEXT_ENV = "PM_TELEMETRY_SOURCE_CONTEXT";
23
+ const PM_TELEMETRY_SOURCE_CONTEXT_VALUES = ["user", "automation", "test", "dogfood", "audit_smoke"];
24
+ const PM_TELEMETRY_SOURCE_CONTEXT_SET = new Set(PM_TELEMETRY_SOURCE_CONTEXT_VALUES);
25
+ const BOOLEAN_TRUE_VALUES = new Set(["1", "true", "yes", "on"]);
26
+ const PROCESS_SESSION_ID = crypto.randomUUID();
27
+ const SENSITIVE_KEYWORDS = [
28
+ "token",
29
+ "secret",
30
+ "password",
31
+ "passwd",
32
+ "api_key",
33
+ "apikey",
34
+ "authorization",
35
+ "cookie",
36
+ "session",
37
+ "credentials",
38
+ "bearer",
39
+ ];
40
+ const SENSITIVE_INLINE_KEY_PATTERN = "(?:token|secret|password|passwd|api[_-]?key|apikey|authorization|cookie|session|credentials|bearer)";
41
+ const INLINE_SENSITIVE_ASSIGNMENT_PATTERN = new RegExp(`\\b(${SENSITIVE_INLINE_KEY_PATTERN})\\s*([:=])\\s*([^\\s,;]+)`, "giu");
42
+ const INLINE_SENSITIVE_FLAG_PATTERN = new RegExp(`(--${SENSITIVE_INLINE_KEY_PATTERN})(=|\\s+)([^\\s,;]+)`, "giu");
43
+ const ABSOLUTE_PATH_TOKEN_PATTERN = /(^|[\s"'`(=])\/(?:[^\s"'`),;]+)/g;
44
+ function nowIso() {
45
+ return new Date().toISOString();
46
+ }
47
+ function queuePath(globalPmRoot) {
48
+ return path.join(globalPmRoot, TELEMETRY_QUEUE_RELATIVE_PATH);
49
+ }
50
+ function runtimeStatePath(globalPmRoot) {
51
+ return path.join(globalPmRoot, TELEMETRY_STATE_RELATIVE_PATH);
52
+ }
53
+ async function readRuntimeState(globalPmRoot) {
54
+ const raw = await readFileIfExists(runtimeStatePath(globalPmRoot));
55
+ if (!raw || raw.trim().length === 0) {
56
+ return {};
57
+ }
58
+ try {
59
+ const parsed = JSON.parse(raw);
60
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
61
+ return {};
62
+ }
63
+ return parsed;
64
+ }
65
+ catch {
66
+ return {};
67
+ }
68
+ }
69
+ async function writeRuntimeState(globalPmRoot, patch) {
70
+ try {
71
+ const current = await readRuntimeState(globalPmRoot);
72
+ const next = {
73
+ ...current,
74
+ ...patch,
75
+ };
76
+ const normalized = Object.fromEntries(Object.entries(next)
77
+ .filter(([, value]) => value !== undefined)
78
+ .sort((left, right) => left[0].localeCompare(right[0])));
79
+ await writeFileAtomic(runtimeStatePath(globalPmRoot), `${JSON.stringify(normalized, null, 2)}\n`);
80
+ }
81
+ catch {
82
+ // Runtime state persistence is best effort and must not block command execution.
83
+ }
84
+ }
85
+ function isSensitiveKey(key) {
86
+ const normalized = key.trim().toLowerCase().replaceAll("-", "_").replaceAll(/[^a-z0-9_]+/g, "_");
87
+ const tokens = normalized.split("_").filter((token) => token.length > 0);
88
+ return SENSITIVE_KEYWORDS.some((keyword) => normalized === keyword || normalized.endsWith(`_${keyword}`) || tokens.includes(keyword));
89
+ }
90
+ function redactInlineSensitiveAssignments(input) {
91
+ const withoutAssignments = input.replaceAll(INLINE_SENSITIVE_ASSIGNMENT_PATTERN, (_match, key, delimiter) => `${key}${delimiter}[redacted]`);
92
+ return withoutAssignments.replaceAll(INLINE_SENSITIVE_FLAG_PATTERN, (_match, flag, delimiter) => `${flag}${delimiter}[redacted]`);
93
+ }
94
+ function redactAbsolutePathTokens(input) {
95
+ return input.replaceAll(ABSOLUTE_PATH_TOKEN_PATTERN, (_match, prefix) => `${prefix}[redacted_path]`);
96
+ }
97
+ function sanitizeStringRedacted(input) {
98
+ const withoutEmails = input.replaceAll(/[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}/giu, "[redacted_email]");
99
+ const withoutBearer = withoutEmails.replaceAll(/bearer\s+[a-z0-9._=-]+/giu, "bearer [redacted_token]");
100
+ const withoutInlineSecrets = redactInlineSensitiveAssignments(withoutBearer);
101
+ const withoutAbsolutePaths = redactAbsolutePathTokens(withoutInlineSecrets);
102
+ const trimmed = withoutAbsolutePaths.trim();
103
+ if (trimmed.startsWith("/") && trimmed.length > 1) {
104
+ return "[redacted_path]";
105
+ }
106
+ if (withoutAbsolutePaths.length > 512) {
107
+ return `${withoutAbsolutePaths.slice(0, 509)}...`;
108
+ }
109
+ return withoutAbsolutePaths;
110
+ }
111
+ function sanitizeStringMax(input) {
112
+ const withoutBearer = input.replaceAll(/bearer\s+[a-z0-9._=-]+/giu, "bearer [redacted_token]");
113
+ const withoutInlineSecrets = redactInlineSensitiveAssignments(withoutBearer);
114
+ if (withoutInlineSecrets.length > 2048) {
115
+ return `${withoutInlineSecrets.slice(0, 2045)}...`;
116
+ }
117
+ return withoutInlineSecrets;
118
+ }
119
+ function sanitizeString(input, captureLevel = "redacted") {
120
+ if (captureLevel === "max") {
121
+ return sanitizeStringMax(input);
122
+ }
123
+ return sanitizeStringRedacted(input);
124
+ }
125
+ function sanitizeValue(value, keyHint, captureLevel = "redacted") {
126
+ if (keyHint && isSensitiveKey(keyHint)) {
127
+ return "[redacted]";
128
+ }
129
+ if (value === null || value === undefined) {
130
+ return value;
131
+ }
132
+ if (typeof value === "string") {
133
+ return sanitizeString(value, captureLevel);
134
+ }
135
+ if (typeof value === "number" || typeof value === "boolean") {
136
+ return value;
137
+ }
138
+ if (Array.isArray(value)) {
139
+ return value.map((entry) => sanitizeValue(entry, undefined, captureLevel));
140
+ }
141
+ if (typeof value === "object") {
142
+ const record = value;
143
+ const sanitized = {};
144
+ for (const [key, nested] of Object.entries(record)) {
145
+ sanitized[key] = sanitizeValue(nested, key, captureLevel);
146
+ }
147
+ return sanitized;
148
+ }
149
+ return String(value);
150
+ }
151
+ function sanitizeCommandArgs(args, captureLevel = "redacted") {
152
+ const sanitized = [];
153
+ let nextIsSensitiveValue = false;
154
+ for (const rawArg of args) {
155
+ const arg = sanitizeString(rawArg, captureLevel);
156
+ if (nextIsSensitiveValue) {
157
+ sanitized.push("[redacted]");
158
+ nextIsSensitiveValue = false;
159
+ continue;
160
+ }
161
+ if (arg.startsWith("--")) {
162
+ const withoutPrefix = arg.slice(2);
163
+ const delimiterIndex = withoutPrefix.indexOf("=");
164
+ if (delimiterIndex >= 0) {
165
+ const key = withoutPrefix.slice(0, delimiterIndex);
166
+ const value = withoutPrefix.slice(delimiterIndex + 1);
167
+ if (isSensitiveKey(key)) {
168
+ sanitized.push(`--${key}=[redacted]`);
169
+ }
170
+ else {
171
+ sanitized.push(`--${key}=${sanitizeString(value, captureLevel)}`);
172
+ }
173
+ continue;
174
+ }
175
+ if (isSensitiveKey(withoutPrefix)) {
176
+ sanitized.push(`--${withoutPrefix}`);
177
+ nextIsSensitiveValue = true;
178
+ continue;
179
+ }
180
+ }
181
+ sanitized.push(arg);
182
+ }
183
+ return sanitized;
184
+ }
185
+ function normalizeCaptureLevel(value) {
186
+ const normalized = value?.trim().toLowerCase();
187
+ if (normalized === "minimal" || normalized === "redacted" || normalized === "max") {
188
+ return normalized;
189
+ }
190
+ return "redacted";
191
+ }
192
+ function parseBooleanTrueLike(value) {
193
+ return BOOLEAN_TRUE_VALUES.has((value ?? "").trim().toLowerCase());
194
+ }
195
+ function normalizePmVersion(value) {
196
+ const trimmed = value?.trim() ?? "";
197
+ return trimmed.length > 0 ? trimmed : "0.0.0";
198
+ }
199
+ function resolveTelemetrySourceContext(globalOptions) {
200
+ const override = (process.env[PM_TELEMETRY_SOURCE_CONTEXT_ENV] ?? "").trim().toLowerCase();
201
+ if (PM_TELEMETRY_SOURCE_CONTEXT_SET.has(override)) {
202
+ return {
203
+ source_context: override,
204
+ source_context_source: "env_override",
205
+ };
206
+ }
207
+ const nodeEnv = (process.env.NODE_ENV ?? "").trim().toLowerCase();
208
+ if (typeof process.env.VITEST === "string" || typeof process.env.VITEST_WORKER_ID === "string" || nodeEnv === "test") {
209
+ return {
210
+ source_context: "test",
211
+ source_context_source: "inferred",
212
+ };
213
+ }
214
+ const nonTty = process.stdin.isTTY !== true || process.stdout.isTTY !== true;
215
+ const ci = parseBooleanTrueLike(process.env.CI);
216
+ const scriptLikeMode = globalOptions.json === true || globalOptions.quiet === true;
217
+ if (nonTty || ci || scriptLikeMode) {
218
+ return {
219
+ source_context: "automation",
220
+ source_context_source: "inferred",
221
+ };
222
+ }
223
+ return {
224
+ source_context: "user",
225
+ source_context_source: "inferred",
226
+ };
227
+ }
228
+ function hashWithInstallationId(installationId, value) {
229
+ return crypto.createHash("sha256").update(`${installationId}:${value}`).digest("hex");
230
+ }
231
+ function telemetryDisabledByEnvironment() {
232
+ return PM_TELEMETRY_DISABLED_VALUES.has((process.env[PM_TELEMETRY_DISABLED_ENV] ?? "").trim().toLowerCase());
233
+ }
234
+ function resolveOtelTracesEndpoint() {
235
+ if (PM_TELEMETRY_OTEL_DISABLED_VALUES.has((process.env[PM_TELEMETRY_OTEL_DISABLED_ENV] ?? "").trim().toLowerCase())) {
236
+ return null;
237
+ }
238
+ const directEndpoint = (process.env[OTEL_TRACES_ENDPOINT_ENV] ?? "").trim();
239
+ if (directEndpoint.length > 0) {
240
+ try {
241
+ return new URL(directEndpoint).toString();
242
+ }
243
+ catch {
244
+ return null;
245
+ }
246
+ }
247
+ const baseEndpoint = (process.env[OTEL_BASE_ENDPOINT_ENV] ?? "").trim();
248
+ if (baseEndpoint.length === 0) {
249
+ return null;
250
+ }
251
+ try {
252
+ const url = new URL(baseEndpoint);
253
+ const normalizedPath = url.pathname.replace(/\/+$/, "");
254
+ if (!normalizedPath.endsWith("/v1/traces")) {
255
+ url.pathname = `${normalizedPath.length === 0 ? "" : normalizedPath}/v1/traces`;
256
+ }
257
+ return url.toString();
258
+ }
259
+ catch {
260
+ return null;
261
+ }
262
+ }
263
+ function isoToUnixNano(iso) {
264
+ const parsedMs = Date.parse(iso);
265
+ const epochMs = Number.isNaN(parsedMs) ? Date.now() : parsedMs;
266
+ return `${BigInt(epochMs) * 1000000n}`;
267
+ }
268
+ function otlpStringAttribute(key, value) {
269
+ return {
270
+ key,
271
+ value: { stringValue: value },
272
+ };
273
+ }
274
+ function otlpBoolAttribute(key, value) {
275
+ return {
276
+ key,
277
+ value: { boolValue: value },
278
+ };
279
+ }
280
+ function otlpIntAttribute(key, value) {
281
+ return {
282
+ key,
283
+ value: { intValue: String(Math.max(0, Math.trunc(value))) },
284
+ };
285
+ }
286
+ async function exportLocalOtelSpan(activeCommand, outcome, finishedAtIso, durationMs) {
287
+ if (typeof activeCommand.otel_traces_endpoint !== "string" ||
288
+ activeCommand.otel_traces_endpoint.trim().length === 0 ||
289
+ typeof activeCommand.otel_trace_id !== "string" ||
290
+ activeCommand.otel_trace_id.length === 0 ||
291
+ typeof activeCommand.otel_span_id !== "string" ||
292
+ activeCommand.otel_span_id.length === 0) {
293
+ return;
294
+ }
295
+ const serviceNameCandidate = sanitizeString((process.env[OTEL_SERVICE_NAME_ENV] ?? "").trim());
296
+ const serviceName = serviceNameCandidate.length > 0 ? serviceNameCandidate : "pm-cli";
297
+ const attributes = [
298
+ otlpStringAttribute("pm.command", sanitizeString(activeCommand.command)),
299
+ otlpStringAttribute("pm.version", activeCommand.pm_version),
300
+ otlpStringAttribute("pm.source_context", activeCommand.source_context),
301
+ otlpStringAttribute("pm.source_context_source", activeCommand.source_context_source),
302
+ otlpStringAttribute("pm.installation_id", activeCommand.installation_id),
303
+ otlpStringAttribute("pm.session_id", PROCESS_SESSION_ID),
304
+ otlpStringAttribute("pm.pm_root_hash", activeCommand.pm_root_hash),
305
+ otlpStringAttribute("pm.cwd_hash", activeCommand.cwd_hash),
306
+ otlpBoolAttribute("pm.ok", outcome.ok),
307
+ otlpIntAttribute("pm.duration_ms", durationMs),
308
+ ];
309
+ if (typeof outcome.error === "string" && outcome.error.trim().length > 0) {
310
+ attributes.push(otlpStringAttribute("pm.error", sanitizeString(outcome.error)));
311
+ }
312
+ const payload = {
313
+ resourceSpans: [
314
+ {
315
+ resource: {
316
+ attributes: [otlpStringAttribute("service.name", serviceName)],
317
+ },
318
+ scopeSpans: [
319
+ {
320
+ scope: {
321
+ name: "pm-cli.telemetry",
322
+ version: "1",
323
+ },
324
+ spans: [
325
+ {
326
+ traceId: activeCommand.otel_trace_id,
327
+ spanId: activeCommand.otel_span_id,
328
+ name: `pm.command.${sanitizeString(activeCommand.command)}`,
329
+ kind: 1,
330
+ startTimeUnixNano: isoToUnixNano(activeCommand.started_at),
331
+ endTimeUnixNano: isoToUnixNano(finishedAtIso),
332
+ attributes,
333
+ status: {
334
+ code: outcome.ok ? 1 : 2,
335
+ message: outcome.ok ? "" : sanitizeString(outcome.error ?? "command_failed"),
336
+ },
337
+ },
338
+ ],
339
+ },
340
+ ],
341
+ },
342
+ ],
343
+ };
344
+ const response = await fetch(activeCommand.otel_traces_endpoint, {
345
+ method: "POST",
346
+ headers: {
347
+ "content-type": "application/json",
348
+ },
349
+ body: JSON.stringify(payload),
350
+ signal: AbortSignal.timeout(TELEMETRY_HTTP_TIMEOUT_MS),
351
+ });
352
+ if (!response.ok) {
353
+ throw new Error(`local_otel_export_http_${response.status}`);
354
+ }
355
+ }
356
+ function summarizeResult(result, captureLevel = "redacted") {
357
+ if (result === null || result === undefined) {
358
+ return { type: "nullish" };
359
+ }
360
+ if (typeof result === "string") {
361
+ return { type: "string", value: sanitizeString(result, captureLevel) };
362
+ }
363
+ if (typeof result === "number" || typeof result === "boolean") {
364
+ return { type: typeof result, value: result };
365
+ }
366
+ if (Array.isArray(result)) {
367
+ return {
368
+ type: "array",
369
+ length: result.length,
370
+ sample: result.slice(0, 5).map((entry) => sanitizeValue(entry, undefined, captureLevel)),
371
+ };
372
+ }
373
+ if (typeof result === "object") {
374
+ const record = result;
375
+ const keys = Object.keys(record).sort((left, right) => left.localeCompare(right));
376
+ const sanitized = {};
377
+ for (const key of keys.slice(0, 25)) {
378
+ sanitized[key] = sanitizeValue(record[key], key, captureLevel);
379
+ }
380
+ return {
381
+ type: "object",
382
+ key_count: keys.length,
383
+ keys_preview: keys.slice(0, 50),
384
+ preview: sanitized,
385
+ };
386
+ }
387
+ return { type: typeof result, value: String(result) };
388
+ }
389
+ function buildCommandStartPayload(params) {
390
+ const { captureLevel, context, pmVersion, sourceContext, pmRootHash, cwdHash, installationId } = params;
391
+ if (captureLevel === "minimal") {
392
+ return {
393
+ capture_level: captureLevel,
394
+ pm_version: pmVersion,
395
+ source_context: sourceContext.source_context,
396
+ source_context_source: sourceContext.source_context_source,
397
+ };
398
+ }
399
+ return {
400
+ pm_version: pmVersion,
401
+ source_context: sourceContext.source_context,
402
+ source_context_source: sourceContext.source_context_source,
403
+ command_args: sanitizeCommandArgs(context.args, captureLevel),
404
+ command_options: sanitizeValue(context.options, undefined, captureLevel),
405
+ global_options: sanitizeValue(context.global, undefined, captureLevel),
406
+ pm_root_hash: pmRootHash,
407
+ cwd_hash: cwdHash,
408
+ capture_level: captureLevel,
409
+ runtime: {
410
+ node: process.version,
411
+ platform: process.platform,
412
+ arch: process.arch,
413
+ hostname_hash: hashWithInstallationId(installationId, os.hostname()),
414
+ stdin_tty: process.stdin.isTTY === true,
415
+ stdout_tty: process.stdout.isTTY === true,
416
+ },
417
+ };
418
+ }
419
+ function buildCommandFinishPayload(params) {
420
+ const { captureLevel, pmVersion, sourceContext, outcome, durationMs, startedAt } = params;
421
+ if (captureLevel === "minimal") {
422
+ return {
423
+ capture_level: captureLevel,
424
+ pm_version: pmVersion,
425
+ source_context: sourceContext.source_context,
426
+ source_context_source: sourceContext.source_context_source,
427
+ ok: outcome.ok,
428
+ error: outcome.error ? sanitizeString(outcome.error, "redacted") : undefined,
429
+ duration_ms: durationMs,
430
+ };
431
+ }
432
+ return {
433
+ capture_level: captureLevel,
434
+ pm_version: pmVersion,
435
+ source_context: sourceContext.source_context,
436
+ source_context_source: sourceContext.source_context_source,
437
+ ok: outcome.ok,
438
+ error: outcome.error ? sanitizeString(outcome.error, captureLevel) : undefined,
439
+ duration_ms: durationMs,
440
+ started_at: startedAt,
441
+ result_summary: summarizeResult(outcome.result, captureLevel),
442
+ };
443
+ }
444
+ async function ensureInstallationId(globalPmRoot) {
445
+ const settings = await readSettings(globalPmRoot);
446
+ let changed = false;
447
+ if (settings.telemetry.installation_id.trim().length === 0) {
448
+ settings.telemetry.installation_id = crypto.randomUUID();
449
+ changed = true;
450
+ }
451
+ if (changed) {
452
+ await writeSettings(globalPmRoot, settings, "telemetry:install_id");
453
+ }
454
+ return {
455
+ installationId: settings.telemetry.installation_id,
456
+ endpoint: settings.telemetry.endpoint,
457
+ retentionDays: Math.max(1, Math.trunc(settings.telemetry.retention_days)),
458
+ };
459
+ }
460
+ async function enqueueTelemetryEvent(globalPmRoot, event) {
461
+ const queued = {
462
+ event,
463
+ attempts: 0,
464
+ };
465
+ await appendLineAtomic(queuePath(globalPmRoot), JSON.stringify(queued));
466
+ }
467
+ function parseQueueLines(raw) {
468
+ const entries = [];
469
+ for (const line of raw.split("\n")) {
470
+ const trimmed = line.trim();
471
+ if (trimmed.length === 0) {
472
+ continue;
473
+ }
474
+ try {
475
+ const parsed = JSON.parse(trimmed);
476
+ if (parsed &&
477
+ typeof parsed === "object" &&
478
+ parsed.event &&
479
+ typeof parsed.event === "object" &&
480
+ typeof parsed.attempts === "number") {
481
+ entries.push(parsed);
482
+ }
483
+ }
484
+ catch {
485
+ // Drop malformed lines to preserve queue forward progress.
486
+ }
487
+ }
488
+ return entries;
489
+ }
490
+ function nextRetryIso(attempts) {
491
+ const delay = Math.min(TELEMETRY_RETRY_BASE_DELAY_MS * 2 ** Math.max(attempts - 1, 0), TELEMETRY_MAX_RETRY_DELAY_MS);
492
+ return new Date(Date.now() + delay).toISOString();
493
+ }
494
+ function isDueForRetry(entry) {
495
+ if (typeof entry.next_attempt_after !== "string" || entry.next_attempt_after.trim().length === 0) {
496
+ return true;
497
+ }
498
+ const dueAtMs = Date.parse(entry.next_attempt_after);
499
+ if (Number.isNaN(dueAtMs)) {
500
+ return true;
501
+ }
502
+ return dueAtMs <= Date.now();
503
+ }
504
+ function retentionCutoffMs(retentionDays) {
505
+ const normalizedDays = Number.isFinite(retentionDays) ? Math.max(1, Math.trunc(retentionDays)) : 1;
506
+ return Date.now() - normalizedDays * MILLISECONDS_PER_DAY;
507
+ }
508
+ function isExpiredQueueEntry(entry, cutoffMs) {
509
+ const occurredAt = entry.event?.occurred_at;
510
+ if (typeof occurredAt !== "string" || occurredAt.trim().length === 0) {
511
+ return false;
512
+ }
513
+ const occurredAtMs = Date.parse(occurredAt);
514
+ if (Number.isNaN(occurredAtMs)) {
515
+ return false;
516
+ }
517
+ return occurredAtMs < cutoffMs;
518
+ }
519
+ function pruneExpiredQueueEntries(entries, retentionDays) {
520
+ const cutoffMs = retentionCutoffMs(retentionDays);
521
+ const retained = [];
522
+ let prunedCount = 0;
523
+ for (const entry of entries) {
524
+ if (isExpiredQueueEntry(entry, cutoffMs)) {
525
+ prunedCount += 1;
526
+ continue;
527
+ }
528
+ retained.push(entry);
529
+ }
530
+ return { entries: retained, prunedCount };
531
+ }
532
+ async function rewriteQueue(globalPmRoot, entries) {
533
+ const serialized = entries.map((entry) => JSON.stringify(entry)).join("\n");
534
+ await writeFileAtomic(queuePath(globalPmRoot), serialized.length > 0 ? `${serialized}\n` : "");
535
+ }
536
+ async function flushQueue(globalPmRoot, endpoint, retentionDays) {
537
+ const normalizedEndpoint = endpoint.trim();
538
+ if (normalizedEndpoint.length === 0) {
539
+ return;
540
+ }
541
+ const raw = await readFileIfExists(queuePath(globalPmRoot));
542
+ if (raw === null || raw.trim().length === 0) {
543
+ await writeRuntimeState(globalPmRoot, {
544
+ endpoint: normalizedEndpoint,
545
+ queue_entries: 0,
546
+ });
547
+ return;
548
+ }
549
+ const queueEntries = parseQueueLines(raw);
550
+ if (queueEntries.length === 0) {
551
+ await writeRuntimeState(globalPmRoot, {
552
+ endpoint: normalizedEndpoint,
553
+ queue_entries: 0,
554
+ });
555
+ return;
556
+ }
557
+ const { entries: retainedEntries, prunedCount } = pruneExpiredQueueEntries(queueEntries, retentionDays);
558
+ if (retainedEntries.length === 0) {
559
+ if (prunedCount > 0) {
560
+ await rewriteQueue(globalPmRoot, []);
561
+ }
562
+ await writeRuntimeState(globalPmRoot, {
563
+ endpoint: normalizedEndpoint,
564
+ queue_entries: 0,
565
+ });
566
+ return;
567
+ }
568
+ const dueEntries = retainedEntries.filter((entry) => isDueForRetry(entry)).slice(0, TELEMETRY_FLUSH_BATCH_SIZE);
569
+ if (dueEntries.length === 0) {
570
+ if (prunedCount > 0) {
571
+ await rewriteQueue(globalPmRoot, retainedEntries);
572
+ }
573
+ await writeRuntimeState(globalPmRoot, {
574
+ endpoint: normalizedEndpoint,
575
+ queue_entries: retainedEntries.length,
576
+ });
577
+ return;
578
+ }
579
+ const dueIds = new Set(dueEntries.map((entry) => entry.event.event_id));
580
+ const attemptTime = nowIso();
581
+ try {
582
+ const response = await fetch(normalizedEndpoint, {
583
+ method: "POST",
584
+ headers: {
585
+ "content-type": "application/json",
586
+ },
587
+ body: JSON.stringify({
588
+ schema_version: TELEMETRY_SCHEMA_VERSION,
589
+ events: dueEntries.map((entry) => entry.event),
590
+ }),
591
+ signal: AbortSignal.timeout(TELEMETRY_HTTP_TIMEOUT_MS),
592
+ });
593
+ if (!response.ok) {
594
+ throw new Error(`telemetry_flush_http_${response.status}`);
595
+ }
596
+ const remaining = retainedEntries.filter((entry) => !dueIds.has(entry.event.event_id));
597
+ await rewriteQueue(globalPmRoot, remaining);
598
+ await writeRuntimeState(globalPmRoot, {
599
+ endpoint: normalizedEndpoint,
600
+ queue_entries: remaining.length,
601
+ last_attempted_flush_at: attemptTime,
602
+ last_successful_flush_at: attemptTime,
603
+ last_failed_flush_at: undefined,
604
+ last_failed_flush_error: undefined,
605
+ });
606
+ }
607
+ catch (error) {
608
+ const retried = retainedEntries.map((entry) => {
609
+ if (!dueIds.has(entry.event.event_id)) {
610
+ return entry;
611
+ }
612
+ const attempts = entry.attempts + 1;
613
+ return {
614
+ ...entry,
615
+ attempts,
616
+ last_attempt_at: attemptTime,
617
+ next_attempt_after: nextRetryIso(attempts),
618
+ };
619
+ });
620
+ await rewriteQueue(globalPmRoot, retried);
621
+ const errorMessage = (() => {
622
+ if (error instanceof Error) {
623
+ return sanitizeString(error.message, "redacted");
624
+ }
625
+ return "telemetry_flush_failed";
626
+ })();
627
+ await writeRuntimeState(globalPmRoot, {
628
+ endpoint: normalizedEndpoint,
629
+ queue_entries: retried.length,
630
+ last_attempted_flush_at: attemptTime,
631
+ last_failed_flush_at: attemptTime,
632
+ last_failed_flush_error: errorMessage,
633
+ });
634
+ }
635
+ }
636
+ export async function startTelemetryCommand(context) {
637
+ if (telemetryDisabledByEnvironment()) {
638
+ return null;
639
+ }
640
+ try {
641
+ const globalPmRoot = resolveGlobalPmRoot(process.cwd());
642
+ const settings = await readSettings(globalPmRoot);
643
+ if (!settings.telemetry.enabled) {
644
+ return null;
645
+ }
646
+ const captureLevel = normalizeCaptureLevel(settings.telemetry.capture_level);
647
+ const { installationId, endpoint, retentionDays } = await ensureInstallationId(globalPmRoot);
648
+ const pmVersion = normalizePmVersion(context.pm_version);
649
+ const sourceContext = resolveTelemetrySourceContext(context.global);
650
+ const pmRootHash = hashWithInstallationId(installationId, context.pm_root);
651
+ const cwdHash = hashWithInstallationId(installationId, process.cwd());
652
+ const otelTracesEndpoint = resolveOtelTracesEndpoint();
653
+ const occurredAt = nowIso();
654
+ const event = {
655
+ schema_version: TELEMETRY_SCHEMA_VERSION,
656
+ event_id: crypto.randomUUID(),
657
+ event_type: "command_start",
658
+ occurred_at: occurredAt,
659
+ installation_id: installationId,
660
+ session_id: PROCESS_SESSION_ID,
661
+ command: context.command,
662
+ payload: buildCommandStartPayload({
663
+ captureLevel,
664
+ context,
665
+ pmVersion,
666
+ sourceContext,
667
+ pmRootHash,
668
+ cwdHash,
669
+ installationId,
670
+ }),
671
+ };
672
+ await enqueueTelemetryEvent(globalPmRoot, event);
673
+ await flushQueue(globalPmRoot, endpoint, retentionDays);
674
+ return {
675
+ started_at: occurredAt,
676
+ started_at_ms: Date.now(),
677
+ command: context.command,
678
+ pm_version: pmVersion,
679
+ source_context: sourceContext.source_context,
680
+ source_context_source: sourceContext.source_context_source,
681
+ installation_id: installationId,
682
+ pm_root_hash: pmRootHash,
683
+ cwd_hash: cwdHash,
684
+ endpoint,
685
+ retention_days: retentionDays,
686
+ global_pm_root: globalPmRoot,
687
+ capture_level: captureLevel,
688
+ otel_traces_endpoint: otelTracesEndpoint ?? undefined,
689
+ otel_trace_id: otelTracesEndpoint ? crypto.randomBytes(16).toString("hex") : undefined,
690
+ otel_span_id: otelTracesEndpoint ? crypto.randomBytes(8).toString("hex") : undefined,
691
+ };
692
+ }
693
+ catch {
694
+ // Telemetry must never block command execution.
695
+ return null;
696
+ }
697
+ }
698
+ export async function finishTelemetryCommand(activeCommand, outcome) {
699
+ if (!activeCommand) {
700
+ return;
701
+ }
702
+ try {
703
+ const finishedAt = nowIso();
704
+ const durationMs = Math.max(0, Date.now() - activeCommand.started_at_ms);
705
+ const event = {
706
+ schema_version: TELEMETRY_SCHEMA_VERSION,
707
+ event_id: crypto.randomUUID(),
708
+ event_type: "command_finish",
709
+ occurred_at: finishedAt,
710
+ installation_id: activeCommand.installation_id,
711
+ session_id: PROCESS_SESSION_ID,
712
+ command: activeCommand.command,
713
+ payload: buildCommandFinishPayload({
714
+ captureLevel: activeCommand.capture_level,
715
+ pmVersion: activeCommand.pm_version,
716
+ sourceContext: {
717
+ source_context: activeCommand.source_context,
718
+ source_context_source: activeCommand.source_context_source,
719
+ },
720
+ outcome,
721
+ durationMs,
722
+ startedAt: activeCommand.started_at,
723
+ }),
724
+ };
725
+ await enqueueTelemetryEvent(activeCommand.global_pm_root, event);
726
+ await flushQueue(activeCommand.global_pm_root, activeCommand.endpoint, activeCommand.retention_days);
727
+ await exportLocalOtelSpan(activeCommand, outcome, finishedAt, durationMs);
728
+ }
729
+ catch {
730
+ // Telemetry must never block command execution.
731
+ }
732
+ }
733
+ //# sourceMappingURL=runtime.js.map