@oxygen-agent/cli 1.638.1 → 1.677.10
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.
- package/README.md +1 -1
- package/dist/command-manifest.js +5 -2
- package/dist/index.js +1033 -135
- package/node_modules/@oxygen/shared/dist/axiom-field-budget.d.ts +91 -0
- package/node_modules/@oxygen/shared/dist/axiom-field-budget.js +262 -0
- package/node_modules/@oxygen/shared/dist/capability-discovery.js +61 -1
- package/node_modules/@oxygen/shared/dist/collab.d.ts +229 -0
- package/node_modules/@oxygen/shared/dist/collab.js +417 -0
- package/node_modules/@oxygen/shared/dist/column-types.d.ts +3 -3
- package/node_modules/@oxygen/shared/dist/column-types.js +26 -1
- package/node_modules/@oxygen/shared/dist/credit-guidance.d.ts +9 -0
- package/node_modules/@oxygen/shared/dist/credit-guidance.js +90 -1
- package/node_modules/@oxygen/shared/dist/index.d.ts +2 -0
- package/node_modules/@oxygen/shared/dist/index.js +2 -0
- package/node_modules/@oxygen/shared/dist/pricing-sheet.d.ts +30 -0
- package/node_modules/@oxygen/shared/dist/pricing-sheet.js +30 -0
- package/node_modules/@oxygen/shared/dist/social-capabilities.d.ts +7 -6
- package/node_modules/@oxygen/shared/dist/social-capabilities.js +69 -8
- package/node_modules/@oxygen/shared/dist/tags.d.ts +2 -2
- package/node_modules/@oxygen/shared/dist/tags.js +8 -5
- package/node_modules/@oxygen/shared/dist/version.d.ts +1 -1
- package/node_modules/@oxygen/shared/dist/version.js +1 -1
- package/node_modules/@oxygen/shared/package.json +5 -0
- package/package.json +1 -1
|
@@ -14,6 +14,7 @@ export const RETYPEABLE_DATA_TYPES = [
|
|
|
14
14
|
"text",
|
|
15
15
|
"numeric",
|
|
16
16
|
"boolean",
|
|
17
|
+
"jsonb",
|
|
17
18
|
"timestamptz",
|
|
18
19
|
];
|
|
19
20
|
// Excel/Sheets store dates as a serial number of days since 1899-12-30.
|
|
@@ -134,7 +135,17 @@ value, dataType) {
|
|
|
134
135
|
}
|
|
135
136
|
switch (dataType) { // skipcq: JS-0047
|
|
136
137
|
case "text":
|
|
137
|
-
|
|
138
|
+
if (typeof value === "string")
|
|
139
|
+
return { ok: true, value };
|
|
140
|
+
if (typeof value === "object") {
|
|
141
|
+
try {
|
|
142
|
+
return { ok: true, value: JSON.stringify(value) };
|
|
143
|
+
}
|
|
144
|
+
catch {
|
|
145
|
+
return { ok: false, reason: "not JSON-serializable text" };
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
return { ok: true, value: String(value) };
|
|
138
149
|
case "timestamptz": {
|
|
139
150
|
const iso = parseDateValueToIso(value, { allowExcelSerial: true });
|
|
140
151
|
return iso
|
|
@@ -157,5 +168,19 @@ value, dataType) {
|
|
|
157
168
|
}
|
|
158
169
|
return { ok: false, reason: "not a recognized boolean" };
|
|
159
170
|
}
|
|
171
|
+
case "jsonb": {
|
|
172
|
+
try {
|
|
173
|
+
if (typeof value === "string") {
|
|
174
|
+
return { ok: true, value: JSON.parse(value.trim()) };
|
|
175
|
+
}
|
|
176
|
+
const serialized = JSON.stringify(value);
|
|
177
|
+
if (serialized === undefined)
|
|
178
|
+
return { ok: false, reason: "not valid JSON" };
|
|
179
|
+
return { ok: true, value: JSON.parse(serialized) };
|
|
180
|
+
}
|
|
181
|
+
catch {
|
|
182
|
+
return { ok: false, reason: "not valid JSON" };
|
|
183
|
+
}
|
|
184
|
+
}
|
|
160
185
|
}
|
|
161
186
|
}
|
|
@@ -5,9 +5,18 @@ export type CreditGuidance = {
|
|
|
5
5
|
available_credits: number | null;
|
|
6
6
|
credit_posture: CreditPosture;
|
|
7
7
|
credit_guidance: string;
|
|
8
|
+
/** Present only when the caller supplied an expected (average) figure. */
|
|
9
|
+
expected_credits?: number;
|
|
8
10
|
};
|
|
9
11
|
export declare function buildCreditGuidance(input: {
|
|
10
12
|
estimatedCredits: number | null | undefined;
|
|
11
13
|
availableCredits: number | null | undefined;
|
|
12
14
|
headroomMultiplier?: number;
|
|
15
|
+
/** The average a run actually costs. When given, the ceiling is sized from this. */
|
|
16
|
+
expectedCredits?: number | null | undefined;
|
|
17
|
+
/** Dearest single lane a row can reach — the tail the band covers. */
|
|
18
|
+
worstCaseCreditsPerRow?: number | null | undefined;
|
|
19
|
+
rowCount?: number | null | undefined;
|
|
20
|
+
/** True when estimatedCredits is a genuine per-row bound (bill-on-hit lanes). */
|
|
21
|
+
worstCaseIsHardBound?: boolean;
|
|
13
22
|
}): CreditGuidance;
|
|
@@ -1,10 +1,66 @@
|
|
|
1
1
|
const DEFAULT_HEADROOM_MULTIPLIER = 1.25;
|
|
2
|
+
/**
|
|
3
|
+
* Size a ceiling from the EXPECTED cost plus a variance band, rather than from
|
|
4
|
+
* the worst case.
|
|
5
|
+
*
|
|
6
|
+
* A waterfall lane bills only on a hit and a row stops at the first lane that
|
|
7
|
+
* resolves it, so the worst case is the dearest lane on EVERY row — a tail event,
|
|
8
|
+
* not a price. Quoting it made a 4.8k job read as 31.6k and told workspaces with
|
|
9
|
+
* ample balance they were "tight".
|
|
10
|
+
*
|
|
11
|
+
* The band is `expected + worst_row x tailRows`, where `tailRows` scales like
|
|
12
|
+
* sqrt(n): unlucky rows are a sample of the tail, and their count grows with the
|
|
13
|
+
* square root of the run, not linearly. `+1` guarantees a run can always afford at
|
|
14
|
+
* least one row above the mean, and `min(n, ...)` collapses the whole thing to the
|
|
15
|
+
* true worst case on tiny runs, where it is cheap to just cover everything.
|
|
16
|
+
*
|
|
17
|
+
* `expected x 1.25` remains the floor, so a flat/cheap chain with no meaningful
|
|
18
|
+
* tail keeps exactly the old headroom.
|
|
19
|
+
*/
|
|
20
|
+
function expectedCreditCeiling(input) {
|
|
21
|
+
const floor = input.expectedCredits * input.headroomMultiplier;
|
|
22
|
+
let ceiling = floor;
|
|
23
|
+
if (input.worstCaseCreditsPerRow !== null && input.rowCount !== null && input.rowCount > 0) {
|
|
24
|
+
const tailRows = Math.min(input.rowCount, Math.ceil(Math.sqrt(input.rowCount)) + 1);
|
|
25
|
+
ceiling = Math.max(floor, input.expectedCredits + input.worstCaseCreditsPerRow * tailRows);
|
|
26
|
+
}
|
|
27
|
+
// Only clamp where the worst case is a REAL per-row bound — bill-on-hit lanes,
|
|
28
|
+
// where one lane is the most a row can cost. An AI/tool column's "estimate" is a
|
|
29
|
+
// token guess that real usage can exceed, so clamping there would reintroduce
|
|
30
|
+
// mid-run truncation on exactly the columns that have no ceiling of their own.
|
|
31
|
+
//
|
|
32
|
+
// Clamp to the worst case WITH its headroom, not to the bare figure: the row
|
|
33
|
+
// count is itself an estimate for a filtered selection, and the multiplier is
|
|
34
|
+
// what absorbs an undercount. Clamping to the bare total would make a small run
|
|
35
|
+
// strictly tighter than before this change, which is the opposite of the point.
|
|
36
|
+
if (input.worstCaseIsHardBound && input.worstCaseTotal !== null) {
|
|
37
|
+
ceiling = Math.min(ceiling, input.worstCaseTotal * input.headroomMultiplier);
|
|
38
|
+
}
|
|
39
|
+
return Math.max(1, Math.ceil(ceiling));
|
|
40
|
+
}
|
|
2
41
|
export function buildCreditGuidance(input) {
|
|
3
42
|
const estimatedCredits = normalizeCreditValue(input.estimatedCredits);
|
|
4
43
|
const availableCredits = normalizeCreditValue(input.availableCredits);
|
|
5
|
-
const
|
|
44
|
+
const expectedCredits = normalizeCreditValue(input.expectedCredits);
|
|
45
|
+
const headroomMultiplier = normalizeMultiplier(input.headroomMultiplier);
|
|
46
|
+
// No expected figure supplied => byte-identical to the pre-existing behaviour,
|
|
47
|
+
// so publishing / sequences / AI callers are untouched by this change.
|
|
48
|
+
const recommendedMaxCredits = expectedCredits === null
|
|
49
|
+
? recommendedCreditCeiling(estimatedCredits, headroomMultiplier)
|
|
50
|
+
: expectedCreditCeiling({
|
|
51
|
+
expectedCredits,
|
|
52
|
+
worstCaseCreditsPerRow: normalizeCreditValue(input.worstCaseCreditsPerRow),
|
|
53
|
+
worstCaseTotal: estimatedCredits,
|
|
54
|
+
rowCount: typeof input.rowCount === "number" && Number.isFinite(input.rowCount)
|
|
55
|
+
? Math.max(0, Math.floor(input.rowCount))
|
|
56
|
+
: null,
|
|
57
|
+
headroomMultiplier,
|
|
58
|
+
worstCaseIsHardBound: input.worstCaseIsHardBound === true,
|
|
59
|
+
});
|
|
60
|
+
const expectedFields = expectedCredits === null ? {} : { expected_credits: expectedCredits };
|
|
6
61
|
if (estimatedCredits === null) {
|
|
7
62
|
return {
|
|
63
|
+
...expectedFields,
|
|
8
64
|
estimated_credits: null,
|
|
9
65
|
recommended_max_credits: null,
|
|
10
66
|
available_credits: availableCredits,
|
|
@@ -14,6 +70,7 @@ export function buildCreditGuidance(input) {
|
|
|
14
70
|
}
|
|
15
71
|
if (availableCredits === null) {
|
|
16
72
|
return {
|
|
73
|
+
...expectedFields,
|
|
17
74
|
estimated_credits: estimatedCredits,
|
|
18
75
|
recommended_max_credits: recommendedMaxCredits,
|
|
19
76
|
available_credits: null,
|
|
@@ -22,7 +79,30 @@ export function buildCreditGuidance(input) {
|
|
|
22
79
|
};
|
|
23
80
|
}
|
|
24
81
|
if (recommendedMaxCredits !== null && recommendedMaxCredits <= availableCredits) {
|
|
82
|
+
// When an expected figure drove the ceiling, the copy has to describe THAT,
|
|
83
|
+
// not "the estimate plus 25% headroom" — the old sentence would misstate how
|
|
84
|
+
// the number was derived. The tail warning is added only when a full
|
|
85
|
+
// fall-through really could outrun the balance; saying it otherwise invents
|
|
86
|
+
// a risk that does not exist.
|
|
87
|
+
if (expectedCredits !== null) {
|
|
88
|
+
const tailWarning = estimatedCredits > availableCredits
|
|
89
|
+
? " Only a run where nearly every row falls through to the dearest provider could exceed your"
|
|
90
|
+
+ " balance — the run keeps going and stops when the balance runs out, keeping every row it"
|
|
91
|
+
+ " has already completed."
|
|
92
|
+
: "";
|
|
93
|
+
return {
|
|
94
|
+
...expectedFields,
|
|
95
|
+
estimated_credits: estimatedCredits,
|
|
96
|
+
recommended_max_credits: recommendedMaxCredits,
|
|
97
|
+
available_credits: availableCredits,
|
|
98
|
+
credit_posture: "sufficient",
|
|
99
|
+
credit_guidance: `Credits cover the expected cost (≈${formatCreditsForCopy(expectedCredits)} credits) with `
|
|
100
|
+
+ "headroom for the rows that fall through to a pricier provider."
|
|
101
|
+
+ `${tailWarning} Unused credits are not spent.`,
|
|
102
|
+
};
|
|
103
|
+
}
|
|
25
104
|
return {
|
|
105
|
+
...expectedFields,
|
|
26
106
|
estimated_credits: estimatedCredits,
|
|
27
107
|
recommended_max_credits: recommendedMaxCredits,
|
|
28
108
|
available_credits: availableCredits,
|
|
@@ -33,6 +113,7 @@ export function buildCreditGuidance(input) {
|
|
|
33
113
|
};
|
|
34
114
|
}
|
|
35
115
|
return {
|
|
116
|
+
...expectedFields,
|
|
36
117
|
estimated_credits: estimatedCredits,
|
|
37
118
|
recommended_max_credits: availableCredits > 0 ? roundCreditValue(availableCredits) : null,
|
|
38
119
|
available_credits: availableCredits,
|
|
@@ -51,6 +132,14 @@ function recommendedCreditCeiling(estimatedCredits, headroomMultiplier = DEFAULT
|
|
|
51
132
|
: DEFAULT_HEADROOM_MULTIPLIER;
|
|
52
133
|
return Math.max(1, Math.ceil(normalized * multiplier));
|
|
53
134
|
}
|
|
135
|
+
function normalizeMultiplier(value) {
|
|
136
|
+
return Number.isFinite(value) && (value ?? 0) > 0
|
|
137
|
+
? value
|
|
138
|
+
: DEFAULT_HEADROOM_MULTIPLIER;
|
|
139
|
+
}
|
|
140
|
+
function formatCreditsForCopy(value) {
|
|
141
|
+
return Math.round(value).toLocaleString("en-US");
|
|
142
|
+
}
|
|
54
143
|
function normalizeCreditValue(value) {
|
|
55
144
|
if (typeof value !== "number" || !Number.isFinite(value) || value < 0)
|
|
56
145
|
return null;
|
|
@@ -11,6 +11,7 @@ export * from "./cell-format.js";
|
|
|
11
11
|
export * from "./cli-envelope.js";
|
|
12
12
|
export * from "./cli-login-code.js";
|
|
13
13
|
export * from "./cli-result.js";
|
|
14
|
+
export * from "./collab.js";
|
|
14
15
|
export * from "./crm-reply-events.js";
|
|
15
16
|
export * from "./crm-activity-events.js";
|
|
16
17
|
export * from "./column-types.js";
|
|
@@ -45,6 +46,7 @@ export * from "./sequences.js";
|
|
|
45
46
|
export * from "./suppression-entries.js";
|
|
46
47
|
export * from "./table-limits.js";
|
|
47
48
|
export * from "./log.js";
|
|
49
|
+
export * from "./axiom-field-budget.js";
|
|
48
50
|
export { sanitizeLogFields } from "./redaction.js";
|
|
49
51
|
export * from "./provider-request-outcomes.js";
|
|
50
52
|
export * from "./schedule-label.js";
|
|
@@ -11,6 +11,7 @@ export * from "./cell-format.js";
|
|
|
11
11
|
export * from "./cli-envelope.js";
|
|
12
12
|
export * from "./cli-login-code.js";
|
|
13
13
|
export * from "./cli-result.js";
|
|
14
|
+
export * from "./collab.js";
|
|
14
15
|
export * from "./crm-reply-events.js";
|
|
15
16
|
export * from "./crm-activity-events.js";
|
|
16
17
|
export * from "./column-types.js";
|
|
@@ -45,6 +46,7 @@ export * from "./sequences.js";
|
|
|
45
46
|
export * from "./suppression-entries.js";
|
|
46
47
|
export * from "./table-limits.js";
|
|
47
48
|
export * from "./log.js";
|
|
49
|
+
export * from "./axiom-field-budget.js";
|
|
48
50
|
// Narrow, deliberate export (ADR 0014): lets telemetry emitters regression-test
|
|
49
51
|
// their field names against the REAL log sanitizer — the unanchored
|
|
50
52
|
// SECRET_KEY_PATTERN redacts any name containing "token", which mocked loggers
|
|
@@ -90,6 +90,36 @@ export declare const VOICE_CREDITS_PER_AMD_CALL = 38;
|
|
|
90
90
|
/** Managed domains: registrar at-cost × 1.25 (≈ a $10.44 .com → ~13,050 credits/yr). */
|
|
91
91
|
export declare const MANAGED_DOMAIN_MARKUP = 1.25;
|
|
92
92
|
export declare const MANAGED_DOMAIN_CREDITS_PER_YEAR_TYPICAL = 13050;
|
|
93
|
+
/**
|
|
94
|
+
* Inbox deliverability (EmailGuard placement seat). A flat monthly price per
|
|
95
|
+
* SUBSCRIBED inbox covering continuous deliverability monitoring (blacklist,
|
|
96
|
+
* SPF/DKIM/DMARC, domain reputation) plus an included allowance of
|
|
97
|
+
* inbox-placement (spam) tests; tests beyond the allowance bill the per-test
|
|
98
|
+
* price.
|
|
99
|
+
*
|
|
100
|
+
* All three are DISPLAY mirrors, per this file's contract. The canonical values
|
|
101
|
+
* live where they are enforced, and seed-parity.test.ts binds them so a mirror
|
|
102
|
+
* can never quietly drift from the number actually billed:
|
|
103
|
+
* seat DELIVERABILITY_UNIT_MONTHLY_CREDITS (@oxygen/control-db,
|
|
104
|
+
* also PRICING_SEED commitment.deliverability_unit) — 1,000 credits
|
|
105
|
+
* ($1.00), restored by the founder on 2026-07-30 after reviewing the
|
|
106
|
+
* Agency-plan unit economics, superseding the 2026-07-26 signature
|
|
107
|
+
* of 2,000.
|
|
108
|
+
* allowance DELIVERABILITY_INCLUDED_TESTS_PER_INBOX (@oxygen/control-db)
|
|
109
|
+
* overage EMAILGUARD_MANAGED_PLACEMENT_CREDITS (@oxygen/providers,
|
|
110
|
+
* derived from the Agency plan rather than typed in)
|
|
111
|
+
*
|
|
112
|
+
* THE ALLOWANCE IS LOAD-BEARING, not a perk. Placement COGS is per TEST
|
|
113
|
+
* ($0.1327 on EmailGuard's Agency plan), not per inbox, so an unbounded seat
|
|
114
|
+
* inverts its own margin: at $1.00/inbox-month the EIGHTH test in a cycle is
|
|
115
|
+
* already sold at a loss. Two included tests cost $0.265 against $1.00 of
|
|
116
|
+
* revenue — a ~73% margin — and everything past them bills
|
|
117
|
+
* PLACEMENT_TEST_OVERAGE_CREDITS. Orgs on their own EmailGuard key (BYOK) pay 0.
|
|
118
|
+
*/
|
|
119
|
+
export declare const DELIVERABILITY_SEAT_CREDITS_PER_MONTH = 1000;
|
|
120
|
+
export declare const DELIVERABILITY_INCLUDED_TESTS_PER_INBOX = 2;
|
|
121
|
+
/** Overage placement test: $0.1327 EmailGuard COGS × 1.25, rounded up. */
|
|
122
|
+
export declare const PLACEMENT_TEST_OVERAGE_CREDITS = 166;
|
|
93
123
|
/**
|
|
94
124
|
* LinkedIn accounts: founder decision (2026-07-25) — a PURE seat model. The
|
|
95
125
|
* flat monthly connection fee per connected account is now the WHOLE LinkedIn
|
|
@@ -82,6 +82,36 @@ export const VOICE_CREDITS_PER_AMD_CALL = 38;
|
|
|
82
82
|
/** Managed domains: registrar at-cost × 1.25 (≈ a $10.44 .com → ~13,050 credits/yr). */
|
|
83
83
|
export const MANAGED_DOMAIN_MARKUP = 1.25;
|
|
84
84
|
export const MANAGED_DOMAIN_CREDITS_PER_YEAR_TYPICAL = 13_050;
|
|
85
|
+
/**
|
|
86
|
+
* Inbox deliverability (EmailGuard placement seat). A flat monthly price per
|
|
87
|
+
* SUBSCRIBED inbox covering continuous deliverability monitoring (blacklist,
|
|
88
|
+
* SPF/DKIM/DMARC, domain reputation) plus an included allowance of
|
|
89
|
+
* inbox-placement (spam) tests; tests beyond the allowance bill the per-test
|
|
90
|
+
* price.
|
|
91
|
+
*
|
|
92
|
+
* All three are DISPLAY mirrors, per this file's contract. The canonical values
|
|
93
|
+
* live where they are enforced, and seed-parity.test.ts binds them so a mirror
|
|
94
|
+
* can never quietly drift from the number actually billed:
|
|
95
|
+
* seat DELIVERABILITY_UNIT_MONTHLY_CREDITS (@oxygen/control-db,
|
|
96
|
+
* also PRICING_SEED commitment.deliverability_unit) — 1,000 credits
|
|
97
|
+
* ($1.00), restored by the founder on 2026-07-30 after reviewing the
|
|
98
|
+
* Agency-plan unit economics, superseding the 2026-07-26 signature
|
|
99
|
+
* of 2,000.
|
|
100
|
+
* allowance DELIVERABILITY_INCLUDED_TESTS_PER_INBOX (@oxygen/control-db)
|
|
101
|
+
* overage EMAILGUARD_MANAGED_PLACEMENT_CREDITS (@oxygen/providers,
|
|
102
|
+
* derived from the Agency plan rather than typed in)
|
|
103
|
+
*
|
|
104
|
+
* THE ALLOWANCE IS LOAD-BEARING, not a perk. Placement COGS is per TEST
|
|
105
|
+
* ($0.1327 on EmailGuard's Agency plan), not per inbox, so an unbounded seat
|
|
106
|
+
* inverts its own margin: at $1.00/inbox-month the EIGHTH test in a cycle is
|
|
107
|
+
* already sold at a loss. Two included tests cost $0.265 against $1.00 of
|
|
108
|
+
* revenue — a ~73% margin — and everything past them bills
|
|
109
|
+
* PLACEMENT_TEST_OVERAGE_CREDITS. Orgs on their own EmailGuard key (BYOK) pay 0.
|
|
110
|
+
*/
|
|
111
|
+
export const DELIVERABILITY_SEAT_CREDITS_PER_MONTH = 1_000;
|
|
112
|
+
export const DELIVERABILITY_INCLUDED_TESTS_PER_INBOX = 2;
|
|
113
|
+
/** Overage placement test: $0.1327 EmailGuard COGS × 1.25, rounded up. */
|
|
114
|
+
export const PLACEMENT_TEST_OVERAGE_CREDITS = 166;
|
|
85
115
|
/**
|
|
86
116
|
* LinkedIn accounts: founder decision (2026-07-25) — a PURE seat model. The
|
|
87
117
|
* flat monthly connection fee per connected account is now the WHOLE LinkedIn
|
|
@@ -2,13 +2,12 @@
|
|
|
2
2
|
* Social channel capability registry.
|
|
3
3
|
*
|
|
4
4
|
* Channel, provider rail, and operation support are deliberately independent.
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* channel later means registering a real adapter profile, not adding placeholder UI.
|
|
5
|
+
* Callers resolve capabilities through this contract instead of branching on a
|
|
6
|
+
* channel name. A channel is registered only when a real adapter exists.
|
|
8
7
|
*/
|
|
9
|
-
export declare const SOCIAL_CHANNELS: readonly ["linkedin"];
|
|
8
|
+
export declare const SOCIAL_CHANNELS: readonly ["linkedin", "x"];
|
|
10
9
|
export type SocialChannel = (typeof SOCIAL_CHANNELS)[number];
|
|
11
|
-
export declare const SOCIAL_PROVIDER_RAILS: readonly ["unipile"];
|
|
10
|
+
export declare const SOCIAL_PROVIDER_RAILS: readonly ["unipile", "composio"];
|
|
12
11
|
export type SocialProviderRail = (typeof SOCIAL_PROVIDER_RAILS)[number];
|
|
13
12
|
export declare const SOCIAL_OPERATIONS: readonly ["list_owned_posts", "read_post_metrics", "list_comments", "reply_to_comment"];
|
|
14
13
|
export type SocialOperation = (typeof SOCIAL_OPERATIONS)[number];
|
|
@@ -24,7 +23,7 @@ export type SocialOperationCapability = {
|
|
|
24
23
|
limitation: string | null;
|
|
25
24
|
};
|
|
26
25
|
export type SocialMetricCapability = {
|
|
27
|
-
status:
|
|
26
|
+
status: SocialCapabilityStatus;
|
|
28
27
|
provider_field: string | null;
|
|
29
28
|
reason: string | null;
|
|
30
29
|
};
|
|
@@ -41,3 +40,5 @@ export type SocialCapabilityProfile = {
|
|
|
41
40
|
};
|
|
42
41
|
export declare function listSocialCapabilityProfiles(): readonly SocialCapabilityProfile[];
|
|
43
42
|
export declare function getSocialCapabilityProfile(channel: string, providerRail: string): SocialCapabilityProfile | null;
|
|
43
|
+
/** Resolve the first configured rail that can perform an operation for a channel. */
|
|
44
|
+
export declare function getSocialCapabilityProfileForOperation(channel: string, operation: SocialOperation): SocialCapabilityProfile | null;
|
|
@@ -2,12 +2,11 @@
|
|
|
2
2
|
* Social channel capability registry.
|
|
3
3
|
*
|
|
4
4
|
* Channel, provider rail, and operation support are deliberately independent.
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* channel later means registering a real adapter profile, not adding placeholder UI.
|
|
5
|
+
* Callers resolve capabilities through this contract instead of branching on a
|
|
6
|
+
* channel name. A channel is registered only when a real adapter exists.
|
|
8
7
|
*/
|
|
9
|
-
export const SOCIAL_CHANNELS = ["linkedin"];
|
|
10
|
-
export const SOCIAL_PROVIDER_RAILS = ["unipile"];
|
|
8
|
+
export const SOCIAL_CHANNELS = ["linkedin", "x"];
|
|
9
|
+
export const SOCIAL_PROVIDER_RAILS = ["unipile", "composio"];
|
|
11
10
|
export const SOCIAL_OPERATIONS = [
|
|
12
11
|
"list_owned_posts",
|
|
13
12
|
"read_post_metrics",
|
|
@@ -61,9 +60,9 @@ const LINKEDIN_UNIPILE_CAPABILITIES = {
|
|
|
61
60
|
},
|
|
62
61
|
metrics: {
|
|
63
62
|
impressions: { status: "unavailable", provider_field: null, reason: NOT_EXPOSED_BY_UNIPILE },
|
|
64
|
-
reactions: { status: "available", provider_field: "
|
|
65
|
-
comments: { status: "available", provider_field: "
|
|
66
|
-
reposts: { status: "available", provider_field: "
|
|
63
|
+
reactions: { status: "available", provider_field: "reaction_counter", reason: null },
|
|
64
|
+
comments: { status: "available", provider_field: "comment_counter", reason: null },
|
|
65
|
+
reposts: { status: "available", provider_field: "repost_counter", reason: null },
|
|
67
66
|
saves: { status: "unavailable", provider_field: null, reason: NOT_EXPOSED_BY_UNIPILE },
|
|
68
67
|
sends: { status: "unavailable", provider_field: null, reason: NOT_EXPOSED_BY_UNIPILE },
|
|
69
68
|
clicks: { status: "unavailable", provider_field: null, reason: NOT_EXPOSED_BY_UNIPILE },
|
|
@@ -74,8 +73,66 @@ const LINKEDIN_UNIPILE_CAPABILITIES = {
|
|
|
74
73
|
webhooks: false,
|
|
75
74
|
},
|
|
76
75
|
};
|
|
76
|
+
const X_COMPOSIO_CAPABILITIES = {
|
|
77
|
+
version: 1,
|
|
78
|
+
channel: "x",
|
|
79
|
+
provider_rail: "composio",
|
|
80
|
+
operations: {
|
|
81
|
+
list_owned_posts: {
|
|
82
|
+
status: "unavailable",
|
|
83
|
+
source: "provider",
|
|
84
|
+
provider_operation: null,
|
|
85
|
+
external_write: false,
|
|
86
|
+
limitation: "oxygen_published_posts_only",
|
|
87
|
+
},
|
|
88
|
+
read_post_metrics: {
|
|
89
|
+
status: "limited",
|
|
90
|
+
source: "provider",
|
|
91
|
+
provider_operation: "TWITTER_POST_LOOKUP_BY_POST_ID",
|
|
92
|
+
external_write: false,
|
|
93
|
+
limitation: "connected_user_oauth_owned_posts",
|
|
94
|
+
},
|
|
95
|
+
list_comments: {
|
|
96
|
+
status: "unavailable",
|
|
97
|
+
source: "provider",
|
|
98
|
+
provider_operation: null,
|
|
99
|
+
external_write: false,
|
|
100
|
+
limitation: "not_implemented_for_x",
|
|
101
|
+
},
|
|
102
|
+
reply_to_comment: {
|
|
103
|
+
status: "unavailable",
|
|
104
|
+
source: "provider",
|
|
105
|
+
provider_operation: null,
|
|
106
|
+
external_write: true,
|
|
107
|
+
limitation: "not_implemented_for_x",
|
|
108
|
+
},
|
|
109
|
+
},
|
|
110
|
+
metrics: {
|
|
111
|
+
impressions: { status: "available", provider_field: "public_metrics.impression_count", reason: null },
|
|
112
|
+
reactions: { status: "available", provider_field: "public_metrics.like_count", reason: null },
|
|
113
|
+
comments: { status: "available", provider_field: "public_metrics.reply_count", reason: null },
|
|
114
|
+
reposts: { status: "available", provider_field: "public_metrics.retweet_count", reason: null },
|
|
115
|
+
saves: { status: "available", provider_field: "public_metrics.bookmark_count", reason: null },
|
|
116
|
+
sends: { status: "unavailable", provider_field: null, reason: "not_exposed_by_x_post_lookup" },
|
|
117
|
+
clicks: {
|
|
118
|
+
status: "limited",
|
|
119
|
+
provider_field: "non_public_metrics.url_link_clicks",
|
|
120
|
+
reason: "owned_posts_user_context_30_day_window",
|
|
121
|
+
},
|
|
122
|
+
video_views: {
|
|
123
|
+
status: "limited",
|
|
124
|
+
provider_field: "includes.media[].public_metrics.view_count",
|
|
125
|
+
reason: "video_media_only",
|
|
126
|
+
},
|
|
127
|
+
},
|
|
128
|
+
ingestion: {
|
|
129
|
+
polling: true,
|
|
130
|
+
webhooks: false,
|
|
131
|
+
},
|
|
132
|
+
};
|
|
77
133
|
const SOCIAL_CAPABILITY_PROFILES = [
|
|
78
134
|
LINKEDIN_UNIPILE_CAPABILITIES,
|
|
135
|
+
X_COMPOSIO_CAPABILITIES,
|
|
79
136
|
];
|
|
80
137
|
export function listSocialCapabilityProfiles() {
|
|
81
138
|
return SOCIAL_CAPABILITY_PROFILES;
|
|
@@ -83,3 +140,7 @@ export function listSocialCapabilityProfiles() {
|
|
|
83
140
|
export function getSocialCapabilityProfile(channel, providerRail) {
|
|
84
141
|
return SOCIAL_CAPABILITY_PROFILES.find((profile) => profile.channel === channel && profile.provider_rail === providerRail) ?? null;
|
|
85
142
|
}
|
|
143
|
+
/** Resolve the first configured rail that can perform an operation for a channel. */
|
|
144
|
+
export function getSocialCapabilityProfileForOperation(channel, operation) {
|
|
145
|
+
return SOCIAL_CAPABILITY_PROFILES.find((profile) => profile.channel === channel && profile.operations[operation].status !== "unavailable") ?? null;
|
|
146
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { TagColor } from "./select-options.js";
|
|
2
2
|
/** Primitive kinds that participate in the workspace tag union today. */
|
|
3
|
-
export declare const TAG_KINDS: readonly ["knowledge_page", "publishing_post", "sequence", "table", "workflow", "recipe", "conversation", "mailbox", "sender", "record", "domain", "project", "
|
|
3
|
+
export declare const TAG_KINDS: readonly ["knowledge_page", "publishing_post", "sequence", "table", "workflow", "recipe", "conversation", "mailbox", "sender", "record", "domain", "project", "sender_profile", "voice_number"];
|
|
4
4
|
export type TagKind = (typeof TAG_KINDS)[number];
|
|
5
5
|
/**
|
|
6
6
|
* The taggable kinds in prose, for CLI `--help` and MCP tool descriptions.
|
|
@@ -8,7 +8,7 @@ export type TagKind = (typeof TAG_KINDS)[number];
|
|
|
8
8
|
* Lives next to {@link TAG_KINDS} because those descriptions are the *discovery*
|
|
9
9
|
* surface: a blind-user eval (2026-07-21) found the tags help still advertising
|
|
10
10
|
* the original seven kinds, so a user reading `tags --help` had no way to learn
|
|
11
|
-
* that domains
|
|
11
|
+
* that domains or projects were taggable at all — they had
|
|
12
12
|
* to stumble on each owning group's own help. One string, one import, and
|
|
13
13
|
* `TAG_KIND_PROSE_MARKERS` fails the build/test if a new kind skips it.
|
|
14
14
|
*/
|
|
@@ -24,7 +24,8 @@ export const TAG_KINDS = [
|
|
|
24
24
|
"record",
|
|
25
25
|
"domain",
|
|
26
26
|
"project",
|
|
27
|
-
"
|
|
27
|
+
"sender_profile",
|
|
28
|
+
"voice_number",
|
|
28
29
|
];
|
|
29
30
|
/**
|
|
30
31
|
* The taggable kinds in prose, for CLI `--help` and MCP tool descriptions.
|
|
@@ -32,13 +33,13 @@ export const TAG_KINDS = [
|
|
|
32
33
|
* Lives next to {@link TAG_KINDS} because those descriptions are the *discovery*
|
|
33
34
|
* surface: a blind-user eval (2026-07-21) found the tags help still advertising
|
|
34
35
|
* the original seven kinds, so a user reading `tags --help` had no way to learn
|
|
35
|
-
* that domains
|
|
36
|
+
* that domains or projects were taggable at all — they had
|
|
36
37
|
* to stumble on each owning group's own help. One string, one import, and
|
|
37
38
|
* `TAG_KIND_PROSE_MARKERS` fails the build/test if a new kind skips it.
|
|
38
39
|
*/
|
|
39
40
|
export const TAG_KINDS_PROSE = "publishing posts, knowledge pages, sequences, tables, workflows, recipes, " +
|
|
40
41
|
"inbox conversations, CRM records, mailboxes, sender accounts, sending domains, " +
|
|
41
|
-
"projects, and
|
|
42
|
+
"projects, sender profiles, and phone numbers";
|
|
42
43
|
/**
|
|
43
44
|
* The phrase each kind must contribute to {@link TAG_KINDS_PROSE}. Typed as a
|
|
44
45
|
* total map over TagKind, so adding a kind is a compile error until it is named
|
|
@@ -57,7 +58,8 @@ export const TAG_KIND_PROSE_MARKERS = {
|
|
|
57
58
|
record: "CRM record",
|
|
58
59
|
domain: "sending domain",
|
|
59
60
|
project: "project",
|
|
60
|
-
|
|
61
|
+
sender_profile: "sender profile",
|
|
62
|
+
voice_number: "phone number",
|
|
61
63
|
};
|
|
62
64
|
export function isTagKind(value) {
|
|
63
65
|
return typeof value === "string" && TAG_KINDS.includes(value);
|
|
@@ -87,7 +89,8 @@ export const TAG_KIND_LABELS = {
|
|
|
87
89
|
record: { one: "CRM record", many: "CRM records" },
|
|
88
90
|
domain: { one: "domain", many: "domains" },
|
|
89
91
|
project: { one: "project", many: "projects" },
|
|
90
|
-
|
|
92
|
+
sender_profile: { one: "sender profile", many: "sender profiles" },
|
|
93
|
+
voice_number: { one: "phone number", many: "phone numbers" },
|
|
91
94
|
};
|
|
92
95
|
/** Inline label for a count: `tagKindLabel("sequence", 1)` → "sequence". */
|
|
93
96
|
export function tagKindLabel(kind, count) {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export const OXYGEN_VERSION = "1.
|
|
1
|
+
export const OXYGEN_VERSION = "1.677.10";
|
|
2
2
|
// The GLOBAL CLI compatibility floor: the oldest CLI allowed to call any
|
|
3
3
|
// operational route. Raising it hard-rejects every older CLI from the entire
|
|
4
4
|
// product, so it obeys one law, enforced by scripts/ci/cli-min-version-gate.mjs:
|
|
@@ -111,6 +111,11 @@
|
|
|
111
111
|
"import": "./dist/tags.js",
|
|
112
112
|
"default": "./dist/tags.js"
|
|
113
113
|
},
|
|
114
|
+
"./collab": {
|
|
115
|
+
"types": "./dist/collab.d.ts",
|
|
116
|
+
"import": "./dist/collab.js",
|
|
117
|
+
"default": "./dist/collab.js"
|
|
118
|
+
},
|
|
114
119
|
"./schedule-label": {
|
|
115
120
|
"types": "./dist/schedule-label.d.ts",
|
|
116
121
|
"import": "./dist/schedule-label.js",
|