@oxygen-agent/cli 1.987.20 → 1.1010.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 (176) hide show
  1. package/README.md +1 -1
  2. package/dist/admin-primary-providers-render.d.ts +0 -2
  3. package/dist/admin-primary-providers-render.js +1 -1
  4. package/dist/browser-login.js +1 -4
  5. package/dist/column-decision-options.d.ts +20 -0
  6. package/dist/column-decision-options.js +54 -0
  7. package/dist/command-manifest.d.ts +3 -2
  8. package/dist/command-manifest.js +25 -2
  9. package/dist/credentials.d.ts +1 -1
  10. package/dist/functions-commands.js +13 -9
  11. package/dist/help.d.ts +8 -0
  12. package/dist/help.js +46 -0
  13. package/dist/index.js +2751 -242
  14. package/dist/knowledge-mirror.d.ts +2 -2
  15. package/dist/runtime.d.ts +0 -15
  16. package/dist/runtime.js +1 -1
  17. package/dist/search-ai-filter-notice.d.ts +17 -0
  18. package/dist/search-ai-filter-notice.js +38 -0
  19. package/dist/session.d.ts +4 -3
  20. package/dist/skills.d.ts +8 -7
  21. package/dist/skills.js +58 -20
  22. package/dist/transcript.d.ts +2 -1
  23. package/dist/util.d.ts +10 -1
  24. package/dist/util.js +14 -2
  25. package/node_modules/@oxygen/cli-ugc/dist/commands.js +296 -140
  26. package/node_modules/@oxygen/cli-ugc/dist/field-parser.d.ts +9 -0
  27. package/node_modules/@oxygen/cli-ugc/dist/field-parser.js +34 -0
  28. package/node_modules/@oxygen/formula/dist/coerce.d.ts +10 -0
  29. package/node_modules/@oxygen/formula/dist/coerce.js +10 -0
  30. package/node_modules/@oxygen/formula/dist/formula-functions.js +65 -0
  31. package/node_modules/@oxygen/formula/dist/hash.d.ts +19 -0
  32. package/node_modules/@oxygen/formula/dist/hash.js +199 -0
  33. package/node_modules/@oxygen/formula/dist/value-cleaners.d.ts +6 -1
  34. package/node_modules/@oxygen/formula/dist/value-cleaners.js +10 -26
  35. package/node_modules/@oxygen/recipe-sdk/dist/index.d.ts +13 -0
  36. package/node_modules/@oxygen/shared/dist/array-utils.d.ts +5 -0
  37. package/node_modules/@oxygen/shared/dist/array-utils.js +11 -0
  38. package/node_modules/@oxygen/shared/dist/billing.d.ts +99 -22
  39. package/node_modules/@oxygen/shared/dist/billing.js +195 -40
  40. package/node_modules/@oxygen/shared/dist/byok-connect.js +5 -0
  41. package/node_modules/@oxygen/shared/dist/capability-discovery.d.ts +27 -0
  42. package/node_modules/@oxygen/shared/dist/capability-discovery.js +311 -28
  43. package/node_modules/@oxygen/shared/dist/cli-http-error.d.ts +8 -0
  44. package/node_modules/@oxygen/shared/dist/cli-http-error.js +8 -0
  45. package/node_modules/@oxygen/shared/dist/cli-result.js +1 -0
  46. package/node_modules/@oxygen/shared/dist/column-autofill.d.ts +52 -0
  47. package/node_modules/@oxygen/shared/dist/column-autofill.js +62 -0
  48. package/node_modules/@oxygen/shared/dist/column-decision.d.ts +50 -0
  49. package/node_modules/@oxygen/shared/dist/column-decision.js +228 -0
  50. package/node_modules/@oxygen/shared/dist/column-output-fields.js +2 -6
  51. package/node_modules/@oxygen/shared/dist/company-enrichment-fields.d.ts +113 -0
  52. package/node_modules/@oxygen/shared/dist/company-enrichment-fields.js +548 -0
  53. package/node_modules/@oxygen/shared/dist/copilot-skills.generated.d.ts +6 -6
  54. package/node_modules/@oxygen/shared/dist/copilot-skills.generated.js +6 -6
  55. package/node_modules/@oxygen/shared/dist/cutover-freeze.d.ts +26 -0
  56. package/node_modules/@oxygen/shared/dist/cutover-freeze.js +52 -0
  57. package/node_modules/@oxygen/shared/dist/data-suppliers.d.ts +57 -0
  58. package/node_modules/@oxygen/shared/dist/data-suppliers.js +59 -0
  59. package/node_modules/@oxygen/shared/dist/deploy-env.d.ts +74 -0
  60. package/node_modules/@oxygen/shared/dist/deploy-env.js +82 -0
  61. package/node_modules/@oxygen/shared/dist/dnc-rules.d.ts +130 -0
  62. package/node_modules/@oxygen/shared/dist/dnc-rules.js +221 -0
  63. package/node_modules/@oxygen/shared/dist/enrichment-intents.d.ts +107 -0
  64. package/node_modules/@oxygen/shared/dist/enrichment-intents.js +809 -0
  65. package/node_modules/@oxygen/shared/dist/error-message.d.ts +1 -0
  66. package/node_modules/@oxygen/shared/dist/error-message.js +3 -0
  67. package/node_modules/@oxygen/shared/dist/error-redaction.js +1 -3
  68. package/node_modules/@oxygen/shared/dist/external-write-policy.d.ts +33 -0
  69. package/node_modules/@oxygen/shared/dist/external-write-policy.js +68 -0
  70. package/node_modules/@oxygen/shared/dist/format-percent.d.ts +8 -0
  71. package/node_modules/@oxygen/shared/dist/format-percent.js +13 -0
  72. package/node_modules/@oxygen/shared/dist/freemail-domains.d.ts +81 -0
  73. package/node_modules/@oxygen/shared/dist/freemail-domains.js +157 -0
  74. package/node_modules/@oxygen/shared/dist/future-signup-lifecycle-projection.d.ts +1 -0
  75. package/node_modules/@oxygen/shared/dist/future-signup-lifecycle-projection.js +1 -1
  76. package/node_modules/@oxygen/shared/dist/hosted-ai.d.ts +60 -4
  77. package/node_modules/@oxygen/shared/dist/hosted-ai.js +125 -10
  78. package/node_modules/@oxygen/shared/dist/index.d.ts +15 -0
  79. package/node_modules/@oxygen/shared/dist/index.js +15 -0
  80. package/node_modules/@oxygen/shared/dist/json-path.js +1 -3
  81. package/node_modules/@oxygen/shared/dist/knowledge-bases.js +1 -3
  82. package/node_modules/@oxygen/shared/dist/knowledge-bootstrap.d.ts +2 -2
  83. package/node_modules/@oxygen/shared/dist/knowledge-bootstrap.js +2 -2
  84. package/node_modules/@oxygen/shared/dist/langfuse.d.ts +44 -1
  85. package/node_modules/@oxygen/shared/dist/langfuse.js +416 -14
  86. package/node_modules/@oxygen/shared/dist/linkedin-countries.d.ts +33 -0
  87. package/node_modules/@oxygen/shared/dist/linkedin-countries.js +361 -0
  88. package/node_modules/@oxygen/shared/dist/linkedin-country-timezones.d.ts +24 -0
  89. package/node_modules/@oxygen/shared/dist/linkedin-country-timezones.js +276 -0
  90. package/node_modules/@oxygen/shared/dist/linkedin-message-deletion.d.ts +2 -0
  91. package/node_modules/@oxygen/shared/dist/linkedin-message-deletion.js +5 -0
  92. package/node_modules/@oxygen/shared/dist/linkedin-post-keywords.d.ts +44 -0
  93. package/node_modules/@oxygen/shared/dist/linkedin-post-keywords.js +116 -0
  94. package/node_modules/@oxygen/shared/dist/linkedin-sequences.d.ts +96 -0
  95. package/node_modules/@oxygen/shared/dist/linkedin-sequences.js +123 -0
  96. package/node_modules/@oxygen/shared/dist/llm-durable-capture.d.ts +24 -0
  97. package/node_modules/@oxygen/shared/dist/llm-durable-capture.js +89 -0
  98. package/node_modules/@oxygen/shared/dist/llm-prompts.d.ts +75 -0
  99. package/node_modules/@oxygen/shared/dist/llm-prompts.js +161 -0
  100. package/node_modules/@oxygen/shared/dist/log-sink-selector.d.ts +39 -0
  101. package/node_modules/@oxygen/shared/dist/log-sink-selector.js +56 -0
  102. package/node_modules/@oxygen/shared/dist/log.d.ts +1 -0
  103. package/node_modules/@oxygen/shared/dist/log.js +6 -1
  104. package/node_modules/@oxygen/shared/dist/mailbox-egress-ownership.d.ts +90 -0
  105. package/node_modules/@oxygen/shared/dist/mailbox-egress-ownership.js +130 -0
  106. package/node_modules/@oxygen/shared/dist/object-storage.d.ts +17 -0
  107. package/node_modules/@oxygen/shared/dist/object-storage.js +21 -0
  108. package/node_modules/@oxygen/shared/dist/operational-telemetry.d.ts +24 -0
  109. package/node_modules/@oxygen/shared/dist/operational-telemetry.js +73 -0
  110. package/node_modules/@oxygen/shared/dist/otlp-log-sink.d.ts +79 -0
  111. package/node_modules/@oxygen/shared/dist/otlp-log-sink.js +366 -0
  112. package/node_modules/@oxygen/shared/dist/plan-capabilities.js +1 -0
  113. package/node_modules/@oxygen/shared/dist/plan-limits.d.ts +23 -22
  114. package/node_modules/@oxygen/shared/dist/plan-limits.js +45 -18
  115. package/node_modules/@oxygen/shared/dist/pricing-sheet.d.ts +48 -41
  116. package/node_modules/@oxygen/shared/dist/pricing-sheet.js +36 -25
  117. package/node_modules/@oxygen/shared/dist/pricing-snapshot.generated.d.ts +22 -22
  118. package/node_modules/@oxygen/shared/dist/pricing-snapshot.generated.js +40 -34
  119. package/node_modules/@oxygen/shared/dist/product-analytics-environment.js +9 -0
  120. package/node_modules/@oxygen/shared/dist/product-analytics-events.d.ts +36 -2
  121. package/node_modules/@oxygen/shared/dist/product-analytics-events.js +36 -1
  122. package/node_modules/@oxygen/shared/dist/rate-window.d.ts +5 -0
  123. package/node_modules/@oxygen/shared/dist/rate-window.js +8 -0
  124. package/node_modules/@oxygen/shared/dist/redaction.js +4 -1
  125. package/node_modules/@oxygen/shared/dist/research-output-contract.js +1 -3
  126. package/node_modules/@oxygen/shared/dist/scraper-lane-credential.d.ts +18 -0
  127. package/node_modules/@oxygen/shared/dist/scraper-lane-credential.js +23 -0
  128. package/node_modules/@oxygen/shared/dist/search-vocab.js +4 -5
  129. package/node_modules/@oxygen/shared/dist/select-options.js +6 -1
  130. package/node_modules/@oxygen/shared/dist/sequence-failures.js +1 -5
  131. package/node_modules/@oxygen/shared/dist/sequences.d.ts +23 -0
  132. package/node_modules/@oxygen/shared/dist/sequences.js +115 -5
  133. package/node_modules/@oxygen/shared/dist/signup-lead-payload.d.ts +80 -0
  134. package/node_modules/@oxygen/shared/dist/signup-lead-payload.js +198 -0
  135. package/node_modules/@oxygen/shared/dist/social-capabilities.d.ts +6 -0
  136. package/node_modules/@oxygen/shared/dist/social-capabilities.js +25 -16
  137. package/node_modules/@oxygen/shared/dist/social-post-metrics-core.d.ts +32 -0
  138. package/node_modules/@oxygen/shared/dist/social-post-metrics-core.js +32 -0
  139. package/node_modules/@oxygen/shared/dist/social-post-metrics-linkedin.d.ts +31 -0
  140. package/node_modules/@oxygen/shared/dist/social-post-metrics-linkedin.js +103 -0
  141. package/node_modules/@oxygen/shared/dist/social-post-metrics-series.d.ts +96 -0
  142. package/node_modules/@oxygen/shared/dist/social-post-metrics-series.js +213 -0
  143. package/node_modules/@oxygen/shared/dist/social-post-metrics-x.d.ts +13 -0
  144. package/node_modules/@oxygen/shared/dist/social-post-metrics-x.js +78 -0
  145. package/node_modules/@oxygen/shared/dist/social-post-metrics.d.ts +36 -0
  146. package/node_modules/@oxygen/shared/dist/social-post-metrics.js +51 -0
  147. package/node_modules/@oxygen/shared/dist/spend-safety.d.ts +22 -10
  148. package/node_modules/@oxygen/shared/dist/spend-safety.js +15 -21
  149. package/node_modules/@oxygen/shared/dist/sql-rows.d.ts +1 -0
  150. package/node_modules/@oxygen/shared/dist/sql-rows.js +3 -0
  151. package/node_modules/@oxygen/shared/dist/stripe-price-catalog.d.ts +36 -0
  152. package/node_modules/@oxygen/shared/dist/stripe-price-catalog.js +184 -0
  153. package/node_modules/@oxygen/shared/dist/stripe-subscription-kind.d.ts +41 -0
  154. package/node_modules/@oxygen/shared/dist/stripe-subscription-kind.js +44 -0
  155. package/node_modules/@oxygen/shared/dist/table-limits.d.ts +3 -0
  156. package/node_modules/@oxygen/shared/dist/table-limits.js +3 -0
  157. package/node_modules/@oxygen/shared/dist/telemetry-export-observer.d.ts +94 -0
  158. package/node_modules/@oxygen/shared/dist/telemetry-export-observer.js +298 -0
  159. package/node_modules/@oxygen/shared/dist/telemetry.d.ts +11 -0
  160. package/node_modules/@oxygen/shared/dist/telemetry.js +28 -2
  161. package/node_modules/@oxygen/shared/dist/type-guards.d.ts +22 -0
  162. package/node_modules/@oxygen/shared/dist/type-guards.js +35 -0
  163. package/node_modules/@oxygen/shared/dist/ugc.d.ts +22 -11
  164. package/node_modules/@oxygen/shared/dist/ugc.js +10 -0
  165. package/node_modules/@oxygen/shared/dist/value-readers.d.ts +21 -0
  166. package/node_modules/@oxygen/shared/dist/value-readers.js +59 -0
  167. package/node_modules/@oxygen/shared/dist/version.generated.d.ts +1 -0
  168. package/node_modules/@oxygen/shared/dist/version.generated.js +2 -0
  169. package/node_modules/@oxygen/shared/dist/version.js +8 -1
  170. package/node_modules/@oxygen/shared/dist/workspace-event-catalog.js +0 -23
  171. package/node_modules/@oxygen/shared/dist/workspace-file-storage.d.ts +29 -0
  172. package/node_modules/@oxygen/shared/dist/workspace-file-storage.js +31 -0
  173. package/node_modules/@oxygen/shared/package.json +59 -0
  174. package/node_modules/@oxygen/workflows/dist/graph/expression.js +2 -5
  175. package/node_modules/@oxygen/workflows/dist/graph/params.js +1 -1
  176. package/package.json +3 -2
@@ -0,0 +1,213 @@
1
+ export const EARNED_SERIES_MAX_PRORATE_MS = 3 * 24 * 60 * 60 * 1000;
2
+ function pointsFor(timeline, metric) {
3
+ const points = [];
4
+ const first = timeline.observations.find((observation) => observation.metrics[metric] !== undefined);
5
+ if (!first)
6
+ return points;
7
+ if (timeline.publishedAt && timeline.publishedAt.getTime() <= first.observedAt.getTime()) {
8
+ points.push({ from: timeline.publishedAt.getTime(), to: timeline.publishedAt.getTime(), value: 0, anchor: true });
9
+ }
10
+ for (const observation of timeline.observations) {
11
+ const value = observation.metrics[metric];
12
+ if (value === undefined)
13
+ continue;
14
+ points.push({ from: observation.observedAt.getTime(), to: observation.confirmedAt.getTime(), value, anchor: false });
15
+ }
16
+ return points;
17
+ }
18
+ /**
19
+ * Earned per day for one metric, or the sum of several (engagements), across a
20
+ * set of posts. `dayStarts` are the instants each local day begins; `windowEnd`
21
+ * closes the last day.
22
+ */
23
+ export function computeEarnedWindow(input) {
24
+ const maxProrateMs = input.maxProrateMs ?? EARNED_SERIES_MAX_PRORATE_MS;
25
+ const bounds = input.dayStarts.map((day, index) => ({
26
+ date: day.date,
27
+ start: day.start.getTime(),
28
+ end: (input.dayStarts[index + 1]?.start ?? input.windowEnd).getTime(),
29
+ }));
30
+ const windowStart = bounds[0]?.start ?? input.windowEnd.getTime();
31
+ const windowEnd = input.windowEnd.getTime();
32
+ const values = bounds.map(() => 0);
33
+ const estimated = bounds.map(() => false);
34
+ const tracked = bounds.map(() => new Set());
35
+ let unattributed = 0;
36
+ let decreases = 0;
37
+ let reporting = 0;
38
+ input.timelines.forEach((timeline, postIndex) => {
39
+ let reports = false;
40
+ for (const metric of input.metrics) {
41
+ const points = pointsFor(timeline, metric);
42
+ if (points.length === 0)
43
+ continue;
44
+ reports = true;
45
+ // A post is tracked on a day only where its earning is known: a flat
46
+ // interval it was observed through, or a short gap between two reads.
47
+ const markTracked = (from, to) => {
48
+ bounds.forEach((day, index) => {
49
+ if (day.start <= to && day.end > from)
50
+ tracked[index].add(postIndex);
51
+ });
52
+ };
53
+ for (const point of points)
54
+ if (!point.anchor)
55
+ markTracked(point.from, point.to);
56
+ for (let index = 1; index < points.length; index += 1) {
57
+ const previous = points[index - 1];
58
+ const current = points[index];
59
+ const delta = current.value - previous.value;
60
+ if (delta === 0)
61
+ continue;
62
+ if (delta < 0)
63
+ decreases += 1;
64
+ const gapStart = Math.min(previous.to, current.from);
65
+ const gapEnd = current.from;
66
+ const gap = gapEnd - gapStart;
67
+ if (gap <= 0) {
68
+ const day = bounds.findIndex((bound) => gapEnd >= bound.start && gapEnd < bound.end);
69
+ if (day >= 0)
70
+ values[day] += delta;
71
+ continue;
72
+ }
73
+ const insideWindow = Math.max(0, Math.min(gapEnd, windowEnd) - Math.max(gapStart, windowStart));
74
+ if (insideWindow === 0)
75
+ continue;
76
+ if (gap > maxProrateMs) {
77
+ unattributed += delta * (insideWindow / gap);
78
+ continue;
79
+ }
80
+ markTracked(gapStart, gapEnd);
81
+ let spannedDays = 0;
82
+ bounds.forEach((bound, day) => {
83
+ const overlap = Math.max(0, Math.min(gapEnd, bound.end) - Math.max(gapStart, bound.start));
84
+ if (overlap === 0)
85
+ return;
86
+ spannedDays += 1;
87
+ values[day] += delta * (overlap / gap);
88
+ });
89
+ if (spannedDays > 1 || insideWindow < gap) {
90
+ bounds.forEach((bound, day) => {
91
+ if (Math.min(gapEnd, bound.end) > Math.max(gapStart, bound.start))
92
+ estimated[day] = true;
93
+ });
94
+ }
95
+ }
96
+ }
97
+ if (reports)
98
+ reporting += 1;
99
+ });
100
+ const days = bounds.map((bound, index) => ({
101
+ date: bound.date,
102
+ value: tracked[index].size > 0 ? Math.round(values[index]) : null,
103
+ estimated: tracked[index].size > 0 && estimated[index],
104
+ postsTracked: tracked[index].size,
105
+ }));
106
+ // Round the window once from unrounded parts: per-day rounding would let a net
107
+ // change of -2 spread over two half-days read as -1.
108
+ const anyTracked = tracked.some((posts) => posts.size > 0);
109
+ const trackedSum = values.reduce((sum, value, index) => sum + (tracked[index].size > 0 ? value : 0), 0);
110
+ return {
111
+ days,
112
+ total: anyTracked || Math.round(unattributed) !== 0 ? Math.round(trackedSum + unattributed) : null,
113
+ unattributed: Math.round(unattributed),
114
+ decreases,
115
+ postsReporting: reporting,
116
+ };
117
+ }
118
+ /**
119
+ * A post's cumulative metric (or sum of metrics) exactly `ageMs` after publishing,
120
+ * so posts can be compared at the same age ("impressions after 24 hours"). Exact
121
+ * inside a known-flat interval; interpolated linearly across a gap no longer than
122
+ * `maxProrateMs`; null when the age falls in a longer gap, before the first read,
123
+ * after the last confirmed read, or when the post has no publish time.
124
+ */
125
+ export function valueAtAge(input) {
126
+ if (!input.timeline.publishedAt)
127
+ return null;
128
+ const maxProrateMs = input.maxProrateMs ?? EARNED_SERIES_MAX_PRORATE_MS;
129
+ const target = input.timeline.publishedAt.getTime() + input.ageMs;
130
+ let total = 0;
131
+ let interpolated = false;
132
+ for (const metric of input.metrics) {
133
+ const points = pointsFor(input.timeline, metric);
134
+ if (points.length === 0)
135
+ return null;
136
+ let found = null;
137
+ for (let index = 0; index < points.length && found === null; index += 1) {
138
+ const point = points[index];
139
+ if (target >= point.from && target <= point.to) {
140
+ found = point.value;
141
+ break;
142
+ }
143
+ const next = points[index + 1];
144
+ if (next && target > point.to && target < next.from) {
145
+ const gap = next.from - point.to;
146
+ if (gap > maxProrateMs)
147
+ return null;
148
+ found = point.value + (next.value - point.value) * ((target - point.to) / gap);
149
+ interpolated = true;
150
+ }
151
+ }
152
+ if (found === null)
153
+ return null;
154
+ total += found;
155
+ }
156
+ return { value: Math.round(total), interpolated };
157
+ }
158
+ /** Median of the values, or null for none. */
159
+ export function medianOf(values) {
160
+ if (values.length === 0)
161
+ return null;
162
+ const sorted = [...values].sort((left, right) => left - right);
163
+ const middle = Math.floor(sorted.length / 2);
164
+ return sorted.length % 2 === 1 ? sorted[middle] : Math.round((sorted[middle - 1] + sorted[middle]) / 2);
165
+ }
166
+ /**
167
+ * The instant each calendar day in [fromDate, toDate] (inclusive, YYYY-MM-DD)
168
+ * starts in `timeZone`, plus the instant after the last day. DST-safe: each start
169
+ * is resolved against that day's own offset.
170
+ */
171
+ export function zonedDayStarts(fromDate, toDate, timeZone) {
172
+ const days = [];
173
+ const cursor = new Date(`${fromDate}T00:00:00.000Z`);
174
+ const last = new Date(`${toDate}T00:00:00.000Z`);
175
+ while (cursor.getTime() <= last.getTime()) {
176
+ const date = cursor.toISOString().slice(0, 10);
177
+ days.push({ date, start: startOfZonedDay(date, timeZone) });
178
+ cursor.setUTCDate(cursor.getUTCDate() + 1);
179
+ }
180
+ return { days, end: startOfZonedDay(cursor.toISOString().slice(0, 10), timeZone) };
181
+ }
182
+ function zoneOffsetMs(instant, timeZone) {
183
+ const parts = new Intl.DateTimeFormat("en-US", {
184
+ timeZone,
185
+ hourCycle: "h23",
186
+ year: "numeric",
187
+ month: "2-digit",
188
+ day: "2-digit",
189
+ hour: "2-digit",
190
+ minute: "2-digit",
191
+ second: "2-digit",
192
+ }).formatToParts(instant);
193
+ const read = (type) => Number(parts.find((part) => part.type === type)?.value ?? 0);
194
+ const asUtc = Date.UTC(read("year"), read("month") - 1, read("day"), read("hour"), read("minute"), read("second"));
195
+ return asUtc - Math.floor(instant.getTime() / 1000) * 1000;
196
+ }
197
+ function startOfZonedDay(date, timeZone) {
198
+ const utcMidnight = new Date(`${date}T00:00:00.000Z`).getTime();
199
+ const firstGuess = utcMidnight - zoneOffsetMs(new Date(utcMidnight), timeZone);
200
+ return new Date(utcMidnight - zoneOffsetMs(new Date(firstGuess), timeZone));
201
+ }
202
+ /** An IANA zone Intl accepts, or null. Validated here so Postgres never sees a bad one. */
203
+ export function validTimeZone(value) {
204
+ if (!value)
205
+ return null;
206
+ try {
207
+ new Intl.DateTimeFormat("en-US", { timeZone: value });
208
+ return value;
209
+ }
210
+ catch {
211
+ return null;
212
+ }
213
+ }
@@ -0,0 +1,13 @@
1
+ import { type ParsedSocialPostMetrics } from "./social-post-metrics-core.js";
2
+ export declare const X_COMPOSIO_POST_METRICS_PARSER_VERSION = 1;
3
+ /**
4
+ * Every field the X parse reads, and nothing else: the post's and its media's
5
+ * metric blocks, without the post text or author.
6
+ */
7
+ export declare function xComposioMetricEvidence(payload: Record<string, unknown>): Record<string, unknown>;
8
+ /**
9
+ * Map an X v2 post lookup (through Composio) into the registry vocabulary. X post
10
+ * lookup exposes no send/share-via-DM counter, and link clicks only come back in
11
+ * the author's user context.
12
+ */
13
+ export declare function parseXComposioPostMetrics(payload: Record<string, unknown>): ParsedSocialPostMetrics;
@@ -0,0 +1,78 @@
1
+ import { pickMetricCount, presentMetrics, } from "./social-post-metrics-core.js";
2
+ import { isRecord } from "./type-guards.js";
3
+ export const X_COMPOSIO_POST_METRICS_PARSER_VERSION = 1;
4
+ function xPostEnvelope(payload) {
5
+ const firstData = isRecord(payload.data) ? payload.data : payload;
6
+ const post = isRecord(firstData.data) ? firstData.data : firstData;
7
+ const includes = isRecord(payload.includes)
8
+ ? payload.includes
9
+ : isRecord(firstData.includes)
10
+ ? firstData.includes
11
+ : {};
12
+ return { post, includes };
13
+ }
14
+ function sumXMediaViews(includes) {
15
+ const media = Array.isArray(includes.media) ? includes.media : [];
16
+ let total = 0;
17
+ let found = false;
18
+ for (const item of media) {
19
+ if (!isRecord(item))
20
+ continue;
21
+ const publicMetrics = isRecord(item.public_metrics) ? item.public_metrics : {};
22
+ const nonPublicMetrics = isRecord(item.non_public_metrics) ? item.non_public_metrics : {};
23
+ const organicMetrics = isRecord(item.organic_metrics) ? item.organic_metrics : {};
24
+ const count = pickMetricCount({ ...organicMetrics, ...nonPublicMetrics, ...publicMetrics }, ["view_count", "video_view_count"]);
25
+ if (count === null)
26
+ continue;
27
+ total += count;
28
+ found = true;
29
+ }
30
+ return found ? total : null;
31
+ }
32
+ const X_METRIC_FIELDS = ["public_metrics", "non_public_metrics", "organic_metrics"];
33
+ function pickMetricFields(record, identity) {
34
+ const picked = {};
35
+ for (const key of [...identity, ...X_METRIC_FIELDS]) {
36
+ if (key in record)
37
+ picked[key] = record[key];
38
+ }
39
+ return picked;
40
+ }
41
+ /**
42
+ * Every field the X parse reads, and nothing else: the post's and its media's
43
+ * metric blocks, without the post text or author.
44
+ */
45
+ export function xComposioMetricEvidence(payload) {
46
+ const { post, includes } = xPostEnvelope(payload);
47
+ const media = Array.isArray(includes.media)
48
+ ? includes.media.filter(isRecord).map((item) => pickMetricFields(item, ["media_key", "type"]))
49
+ : [];
50
+ return { data: pickMetricFields(post, ["id"]), ...(media.length > 0 ? { includes: { media } } : {}) };
51
+ }
52
+ /**
53
+ * Map an X v2 post lookup (through Composio) into the registry vocabulary. X post
54
+ * lookup exposes no send/share-via-DM counter, and link clicks only come back in
55
+ * the author's user context.
56
+ */
57
+ export function parseXComposioPostMetrics(payload) {
58
+ const { post, includes } = xPostEnvelope(payload);
59
+ const publicMetrics = isRecord(post.public_metrics) ? post.public_metrics : {};
60
+ const nonPublicMetrics = isRecord(post.non_public_metrics) ? post.non_public_metrics : {};
61
+ const organicMetrics = isRecord(post.organic_metrics) ? post.organic_metrics : {};
62
+ const privateMetrics = { ...organicMetrics, ...nonPublicMetrics };
63
+ return {
64
+ metrics: presentMetrics({
65
+ impressions: pickMetricCount(publicMetrics, ["impression_count"]),
66
+ reactions: pickMetricCount(publicMetrics, ["like_count"]),
67
+ comments: pickMetricCount(publicMetrics, ["reply_count"]),
68
+ reposts: pickMetricCount(publicMetrics, ["retweet_count", "repost_count"]),
69
+ saves: pickMetricCount(publicMetrics, ["bookmark_count"]),
70
+ clicks: pickMetricCount(privateMetrics, ["url_link_clicks"]),
71
+ video_views: sumXMediaViews(includes),
72
+ }),
73
+ extended: {},
74
+ shape: isRecord(post.public_metrics) ? "x_v2_lookup" : "unknown",
75
+ attributable: true,
76
+ rejected: [],
77
+ };
78
+ }
@@ -0,0 +1,36 @@
1
+ import { type SocialPostMetric } from "./social-capabilities.js";
2
+ import type { ParsedSocialPostMetrics } from "./social-post-metrics-core.js";
3
+ export type { ParsedSocialPostMetrics } from "./social-post-metrics-core.js";
4
+ /** Null when no parser is registered for the pair. */
5
+ export declare function parseSocialPostMetrics(input: {
6
+ channel: string;
7
+ providerRail: string;
8
+ payload: Record<string, unknown>;
9
+ }): ParsedSocialPostMetrics | null;
10
+ export declare function socialPostMetricsParserVersion(channel: string, providerRail: string): number | null;
11
+ /**
12
+ * The metric-bearing subset of a payload, stored with each observation so a
13
+ * parser fix can re-derive it without keeping post text, authors or media.
14
+ * Unknown pairs keep nothing.
15
+ */
16
+ export declare function socialPostMetricEvidence(input: {
17
+ channel: string;
18
+ providerRail: string;
19
+ payload: Record<string, unknown>;
20
+ }): Record<string, unknown>;
21
+ /** Every registered (channel, provider rail) pair with its current parser version. */
22
+ export declare function listSocialPostMetricsParsers(): Array<{
23
+ channel: string;
24
+ providerRail: string;
25
+ version: number;
26
+ }>;
27
+ /**
28
+ * Metrics the registry declares fully `available` for the pair that a parse did
29
+ * not produce. A non-empty answer on a real provider read is parser drift — the
30
+ * failure that silently stored every LinkedIn metric as NULL twice.
31
+ */
32
+ export declare function missingDeclaredPostMetrics(input: {
33
+ channel: string;
34
+ providerRail: string;
35
+ parsed: ParsedSocialPostMetrics;
36
+ }): SocialPostMetric[];
@@ -0,0 +1,51 @@
1
+ import { getSocialCapabilityProfile, SOCIAL_POST_METRICS, } from "./social-capabilities.js";
2
+ import { LINKEDIN_UNIPILE_POST_METRICS_PARSER_VERSION, linkedInUnipileMetricEvidence, parseLinkedInUnipilePostMetrics, } from "./social-post-metrics-linkedin.js";
3
+ import { parseXComposioPostMetrics, X_COMPOSIO_POST_METRICS_PARSER_VERSION, xComposioMetricEvidence, } from "./social-post-metrics-x.js";
4
+ const POST_METRICS_PARSERS = {
5
+ "linkedin:unipile": {
6
+ version: LINKEDIN_UNIPILE_POST_METRICS_PARSER_VERSION,
7
+ parse: parseLinkedInUnipilePostMetrics,
8
+ evidence: linkedInUnipileMetricEvidence,
9
+ },
10
+ "x:composio": {
11
+ version: X_COMPOSIO_POST_METRICS_PARSER_VERSION,
12
+ parse: parseXComposioPostMetrics,
13
+ evidence: xComposioMetricEvidence,
14
+ },
15
+ };
16
+ function parserFor(channel, providerRail) {
17
+ return POST_METRICS_PARSERS[`${channel}:${providerRail}`] ?? null;
18
+ }
19
+ /** Null when no parser is registered for the pair. */
20
+ export function parseSocialPostMetrics(input) {
21
+ return parserFor(input.channel, input.providerRail)?.parse(input.payload) ?? null;
22
+ }
23
+ export function socialPostMetricsParserVersion(channel, providerRail) {
24
+ return parserFor(channel, providerRail)?.version ?? null;
25
+ }
26
+ /**
27
+ * The metric-bearing subset of a payload, stored with each observation so a
28
+ * parser fix can re-derive it without keeping post text, authors or media.
29
+ * Unknown pairs keep nothing.
30
+ */
31
+ export function socialPostMetricEvidence(input) {
32
+ return parserFor(input.channel, input.providerRail)?.evidence(input.payload) ?? {};
33
+ }
34
+ /** Every registered (channel, provider rail) pair with its current parser version. */
35
+ export function listSocialPostMetricsParsers() {
36
+ return Object.entries(POST_METRICS_PARSERS).map(([key, parser]) => {
37
+ const [channel, providerRail] = key.split(":");
38
+ return { channel, providerRail, version: parser.version };
39
+ });
40
+ }
41
+ /**
42
+ * Metrics the registry declares fully `available` for the pair that a parse did
43
+ * not produce. A non-empty answer on a real provider read is parser drift — the
44
+ * failure that silently stored every LinkedIn metric as NULL twice.
45
+ */
46
+ export function missingDeclaredPostMetrics(input) {
47
+ const profile = getSocialCapabilityProfile(input.channel, input.providerRail);
48
+ if (!profile)
49
+ return [];
50
+ return SOCIAL_POST_METRICS.filter((metric) => profile.metrics[metric].status === "available" && input.parsed.metrics[metric] === undefined);
51
+ }
@@ -20,19 +20,19 @@ export declare const AUTONOMOUS_WORKFLOW_TRIGGER_TYPES: ReadonlySet<string>;
20
20
  * Per-run managed-credit ceiling for a LIVE workflow run fired by an autonomous
21
21
  * trigger when neither run metadata nor the manifest declares `max_credits`.
22
22
  */
23
- export declare const DEFAULT_TRIGGER_RUN_CREDIT_CEILING: Record<PlanTier, number | null>;
23
+ export declare const DEFAULT_TRIGGER_RUN_CREDIT_CEILING: Record<SpendSafetyPlanTier, number | null>;
24
24
  /**
25
25
  * Per-delivery credit ceiling stamped onto a standing/webhook table auto-run
26
26
  * batch when the armed configuration carries no explicit `max_credits`.
27
27
  */
28
- export declare const DEFAULT_AUTO_RUN_BATCH_CREDIT_CEILING: Record<PlanTier, number | null>;
28
+ export declare const DEFAULT_AUTO_RUN_BATCH_CREDIT_CEILING: Record<SpendSafetyPlanTier, number | null>;
29
29
  /**
30
30
  * Row ceiling for a BYOK AI-column run whose caller gave no explicit row bound
31
31
  * (no limit, no row_ids, selection "all"). BYOK bills the customer's own
32
32
  * provider account, so a credit ceiling is meaningless — row count is the
33
33
  * enforceable unit. An explicit limit (up to the 500k platform row cap) wins.
34
34
  */
35
- export declare const DEFAULT_BYOK_COLUMN_RUN_MAX_ROWS: Record<PlanTier, number | null>;
35
+ export declare const DEFAULT_BYOK_COLUMN_RUN_MAX_ROWS: Record<SpendSafetyPlanTier, number | null>;
36
36
  /**
37
37
  * Default per-provider DAILY call cap for BYOK provider traffic (the customer's
38
38
  * own key). Short-window pacing defaults already exist in provider-fetch; this
@@ -40,7 +40,7 @@ export declare const DEFAULT_BYOK_COLUMN_RUN_MAX_ROWS: Record<PlanTier, number |
40
40
  * with the self-serve override surface live. A workspace
41
41
  * provider_rate_limit_policies row with windowSeconds=86400 overrides it.
42
42
  */
43
- export declare const DEFAULT_BYOK_PROVIDER_DAILY_CALL_CAP: Record<PlanTier, number | null>;
43
+ export declare const DEFAULT_BYOK_PROVIDER_DAILY_CALL_CAP: Record<SpendSafetyPlanTier, number | null>;
44
44
  export declare const BYOK_PROVIDER_DAILY_WINDOW_SECONDS: number;
45
45
  export declare const BYOK_PROVIDER_DAILY_CAP_ENFORCEMENT_ENV = "OXYGEN_BYOK_DAILY_CAPS";
46
46
  export type ByokProviderDailyCapEnforcementMode = "observe_only" | "enforced";
@@ -105,10 +105,14 @@ export type OrgDailySpendGuard = {
105
105
  * decision is made, so enforcement and the read surfaces cannot drift apart.
106
106
  */
107
107
  export declare function resolveOrgDailySpendGuard(monthlyCredits: number | null | undefined): OrgDailySpendGuard | null;
108
- export declare function resolveDefaultTriggerRunCreditCeiling(tier: PlanTier): number | null;
109
- export declare function resolveDefaultAutoRunBatchCreditCeiling(tier: PlanTier): number | null;
110
- export declare function resolveDefaultByokColumnRunMaxRows(tier: PlanTier): number | null;
111
- export declare function resolveDefaultByokProviderDailyCallCap(tier: PlanTier): number | null;
108
+ /** Credits carry 3 decimals everywhere (control-DB numeric(18,3)). */
109
+ export declare function roundCredits(value: number): number;
110
+ /** Credit estimates that present to 2 decimals (cent-grained customer copy). */
111
+ export declare function roundCreditsToCents(value: number): number;
112
+ export declare function resolveDefaultTriggerRunCreditCeiling(tier: SpendSafetyPlanTier): number | null;
113
+ export declare function resolveDefaultAutoRunBatchCreditCeiling(tier: SpendSafetyPlanTier): number | null;
114
+ export declare function resolveDefaultByokColumnRunMaxRows(tier: SpendSafetyPlanTier): number | null;
115
+ export declare function resolveDefaultByokProviderDailyCallCap(tier: SpendSafetyPlanTier): number | null;
112
116
  /**
113
117
  * The plan tiers the spend-safety default tables above are keyed by. `limitsTier`
114
118
  * collapses "enterprise" onto the "scale" enforcement rung, but these tables carry
@@ -119,5 +123,13 @@ export declare function resolveDefaultByokProviderDailyCallCap(tier: PlanTier):
119
123
  * `oxygen limits` report, the org-facing provider-limits API, and the BYOK
120
124
  * daily-cap enforcement resolver — they must agree on which column an org reads.
121
125
  */
122
- export declare const SPEND_SAFETY_PLAN_TIERS: readonly PlanTier[];
123
- export declare function resolveSpendSafetyPlanTier(planTier: string | null, limitsTier: LimitsTier): PlanTier;
126
+ /**
127
+ * The tiers these tables are keyed by. `oxygen` is deliberately ABSENT: its six
128
+ * rungs span four enforcement rungs, so an Oxygen plan must fall through to its
129
+ * limitsTier (the price band) rather than read one shared column. Adding an
130
+ * "oxygen" column here would silently give a $1,999 workspace the same ceiling
131
+ * as a $49 one.
132
+ */
133
+ export type SpendSafetyPlanTier = Exclude<PlanTier, "oxygen">;
134
+ export declare const SPEND_SAFETY_PLAN_TIERS: readonly SpendSafetyPlanTier[];
135
+ export declare function resolveSpendSafetyPlanTier(planTier: string | null, limitsTier: LimitsTier): SpendSafetyPlanTier;
@@ -9,11 +9,11 @@ export const AUTONOMOUS_WORKFLOW_TRIGGER_TYPES = new Set([
9
9
  * trigger when neither run metadata nor the manifest declares `max_credits`.
10
10
  */
11
11
  export const DEFAULT_TRIGGER_RUN_CREDIT_CEILING = {
12
- free: 500,
13
- starter: 5_000,
14
- pro: 10_000,
15
- team: 25_000,
16
- scale: 250_000,
12
+ free: 50,
13
+ starter: 500,
14
+ pro: 1_000,
15
+ team: 2_500,
16
+ scale: 25_000,
17
17
  enterprise: null,
18
18
  };
19
19
  /**
@@ -21,11 +21,11 @@ export const DEFAULT_TRIGGER_RUN_CREDIT_CEILING = {
21
21
  * batch when the armed configuration carries no explicit `max_credits`.
22
22
  */
23
23
  export const DEFAULT_AUTO_RUN_BATCH_CREDIT_CEILING = {
24
- free: 500,
25
- starter: 5_000,
26
- pro: 10_000,
27
- team: 25_000,
28
- scale: 250_000,
24
+ free: 50,
25
+ starter: 500,
26
+ pro: 1_000,
27
+ team: 2_500,
28
+ scale: 25_000,
29
29
  enterprise: null,
30
30
  };
31
31
  /**
@@ -129,9 +129,13 @@ export function resolveOrgDailySpendGuard(monthlyCredits) {
129
129
  };
130
130
  }
131
131
  /** Credits carry 3 decimals everywhere (control-DB numeric(18,3)). */
132
- function roundCredits(value) {
132
+ export function roundCredits(value) {
133
133
  return Math.round(value * 1000) / 1000;
134
134
  }
135
+ /** Credit estimates that present to 2 decimals (cent-grained customer copy). */
136
+ export function roundCreditsToCents(value) {
137
+ return Math.round((value + Number.EPSILON) * 100) / 100;
138
+ }
135
139
  export function resolveDefaultTriggerRunCreditCeiling(tier) {
136
140
  return DEFAULT_TRIGGER_RUN_CREDIT_CEILING[tier];
137
141
  }
@@ -144,16 +148,6 @@ export function resolveDefaultByokColumnRunMaxRows(tier) {
144
148
  export function resolveDefaultByokProviderDailyCallCap(tier) {
145
149
  return DEFAULT_BYOK_PROVIDER_DAILY_CALL_CAP[tier];
146
150
  }
147
- /**
148
- * The plan tiers the spend-safety default tables above are keyed by. `limitsTier`
149
- * collapses "enterprise" onto the "scale" enforcement rung, but these tables carry
150
- * a distinct "enterprise" column (null = runs on custom limits) — so prefer the
151
- * raw plan_tier whenever it is itself a table key, and fall back to the limits rung
152
- * (always a valid PlanTier key) when plan_tier is absent or an unknown/legacy
153
- * string (e.g. a Stripe price nickname). Single source of truth for the
154
- * `oxygen limits` report, the org-facing provider-limits API, and the BYOK
155
- * daily-cap enforcement resolver — they must agree on which column an org reads.
156
- */
157
151
  export const SPEND_SAFETY_PLAN_TIERS = [
158
152
  "free",
159
153
  "starter",
@@ -0,0 +1 @@
1
+ export declare function executeRows<T>(value: unknown): T[];
@@ -0,0 +1,3 @@
1
+ export function executeRows(value) {
2
+ return (value.rows ?? value);
3
+ }
@@ -0,0 +1,36 @@
1
+ import { CONTACT_SALES_URL, PURCHASABLE_PLAN_KEYS, SELF_SERVE_PLAN_TIERS, normalizeBillingCurrency, type BillingCurrency, type OxygenPlanKey, type PricingPlanDefinition, type SelfServePlanTier } from "./billing.js";
2
+ export { CONTACT_SALES_URL, PURCHASABLE_PLAN_KEYS, SELF_SERVE_PLAN_TIERS, normalizeBillingCurrency, };
3
+ export type { BillingCurrency, OxygenPlanKey, SelfServePlanTier };
4
+ export type PricingPlan = PricingPlanDefinition & {
5
+ priceIds: Record<BillingCurrency, string>;
6
+ priceId: string;
7
+ };
8
+ export declare const PRICING_PLANS: {
9
+ readonly free: PricingPlan;
10
+ readonly oxygen: PricingPlan;
11
+ readonly starter: PricingPlan;
12
+ readonly pro: PricingPlan;
13
+ readonly team: PricingPlan;
14
+ readonly scale: PricingPlan;
15
+ readonly enterprise: PricingPlan;
16
+ };
17
+ /** The six purchasable rungs with their Stripe price ids, cheapest first. */
18
+ export declare const PURCHASABLE_PLANS: Record<OxygenPlanKey, PricingPlan>;
19
+ export declare function getPlanPriceId(plan: PricingPlan, currency: BillingCurrency): string;
20
+ /**
21
+ * Resolve a Stripe price id to the plan key stored in `subscriptions.tier`.
22
+ *
23
+ * Returns a plan KEY ("oxygen_499") for the six purchasable rungs and a TIER
24
+ * ("starter", "growth_250") for grandfathered plans. Both are resolvable by
25
+ * resolveBasePricingPlan, which is what the credit grant reads — so the rung a
26
+ * customer paid for is the allowance they receive.
27
+ */
28
+ export declare function tierFromPriceId(priceId: string): string | null;
29
+ /**
30
+ * Complete runtime-recognized Price set grouped by the current self-serve
31
+ * family. Catalog audit/migration code consumes this instead of maintaining a
32
+ * second env map that can silently omit grandfathered subscriptions.
33
+ */
34
+ export declare function recognizedStripePriceIdsBySelfServeTier(): Record<SelfServePlanTier, readonly string[]>;
35
+ export declare function formatMonthlyPrice(priceCents: number | null, currency?: BillingCurrency): string;
36
+ export declare function getMonthlyAllowanceLabel(plan: PricingPlanDefinition): string;