@oxygen-agent/cli 1.310.2 → 1.334.3
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 +26 -10
- package/dist/help.js +7 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +2077 -134
- package/dist/runtime.js +13 -3
- package/dist/skills.js +192 -32
- package/node_modules/@oxygen/recipe-sdk/dist/index.d.ts +2 -0
- package/node_modules/@oxygen/shared/dist/billing.d.ts +52 -29
- package/node_modules/@oxygen/shared/dist/billing.js +77 -55
- package/node_modules/@oxygen/shared/dist/index.d.ts +4 -0
- package/node_modules/@oxygen/shared/dist/index.js +4 -0
- package/node_modules/@oxygen/shared/dist/pricing-sheet.d.ts +84 -0
- package/node_modules/@oxygen/shared/dist/pricing-sheet.js +82 -0
- package/node_modules/@oxygen/shared/dist/recipes.d.ts +19 -0
- package/node_modules/@oxygen/shared/dist/recipes.js +90 -0
- package/node_modules/@oxygen/shared/dist/sequences.d.ts +61 -15
- package/node_modules/@oxygen/shared/dist/sequences.js +134 -23
- package/node_modules/@oxygen/shared/dist/sql-error.d.ts +25 -0
- package/node_modules/@oxygen/shared/dist/sql-error.js +46 -0
- package/node_modules/@oxygen/shared/dist/version.d.ts +2 -2
- package/node_modules/@oxygen/shared/dist/version.js +13 -11
- package/node_modules/@oxygen/shared/dist/workflow-mcp-tools.d.ts +4 -0
- package/node_modules/@oxygen/shared/dist/workflow-mcp-tools.js +18 -0
- package/node_modules/@oxygen/shared/dist/workflow-status-change.d.ts +61 -0
- package/node_modules/@oxygen/shared/dist/workflow-status-change.js +124 -0
- package/node_modules/@oxygen/shared/dist/workspace-agents.d.ts +65 -0
- package/node_modules/@oxygen/shared/dist/workspace-agents.js +67 -0
- package/node_modules/@oxygen/workflows/dist/index.js +86 -2
- package/package.json +1 -1
package/dist/runtime.js
CHANGED
|
@@ -22,6 +22,16 @@ export function resolveCliBinaryName(env = process.env, argv = process.argv) {
|
|
|
22
22
|
const explicit = normalizeBinaryName(env.OXYGEN_CLI_BINARY ?? env.OXYGEN_CLI_NAME);
|
|
23
23
|
if (explicit)
|
|
24
24
|
return explicit;
|
|
25
|
+
// The dev install (`npm run install-dev-cli`) also ships a legacy shim literally
|
|
26
|
+
// named `oxygen` under ~/.oxygen-dev/. A basename check alone misreads it as the
|
|
27
|
+
// npm production binary, and every command then scolds the user about "pointing
|
|
28
|
+
// a production binary at a non-production API" — for a setup that is exactly as
|
|
29
|
+
// intended (live blind-user finding, 2026-07-13). The install PATH is the
|
|
30
|
+
// truthful channel signal, so check it before the basename.
|
|
31
|
+
const invokedPath = argv[1] ?? "";
|
|
32
|
+
if (invokedPath.includes("/.oxygen-dev/") || invokedPath.includes("\\.oxygen-dev\\")) {
|
|
33
|
+
return DEV_CLI_BINARY;
|
|
34
|
+
}
|
|
25
35
|
const invoked = normalizeBinaryName(argv[1]);
|
|
26
36
|
if (invoked)
|
|
27
37
|
return invoked;
|
|
@@ -33,10 +43,10 @@ export function resolveCliUpdateGuidance(endpoint, env = process.env, argv = pro
|
|
|
33
43
|
return {
|
|
34
44
|
binaryName,
|
|
35
45
|
channel: "dev",
|
|
36
|
-
warningInstruction: "
|
|
37
|
-
failureInstruction: "
|
|
46
|
+
warningInstruction: "Rebuild `oxygen-dev` from the latest dev branch (`npm run install-dev-cli`) before using operational commands.",
|
|
47
|
+
failureInstruction: "Rebuild `oxygen-dev` from the latest dev branch (`npm run install-dev-cli`) before using this command.",
|
|
38
48
|
details: {
|
|
39
|
-
cli_update_instruction: "
|
|
49
|
+
cli_update_instruction: "Rebuild `oxygen-dev` from the latest dev branch (`npm run install-dev-cli`).",
|
|
40
50
|
},
|
|
41
51
|
};
|
|
42
52
|
}
|
package/dist/skills.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { spawnSync } from "node:child_process";
|
|
2
|
-
import { existsSync, readdirSync } from "node:fs";
|
|
3
|
-
import { homedir } from "node:os";
|
|
4
|
-
import { join, resolve } from "node:path";
|
|
2
|
+
import { existsSync, mkdirSync, mkdtempSync, readdirSync, rmSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { homedir, tmpdir } from "node:os";
|
|
4
|
+
import { dirname, join, resolve } from "node:path";
|
|
5
5
|
import { OxygenError, toFailure } from "@oxygen/shared";
|
|
6
6
|
import { defaultApiUrl, loadCredentials, normalizeApiUrl } from "./credentials.js";
|
|
7
7
|
import { requestOxygen } from "./http-client.js";
|
|
@@ -118,35 +118,71 @@ options, runtime = {}) {
|
|
|
118
118
|
const skill = readOption(options.skill) ?? "*";
|
|
119
119
|
const env = runtime.env ?? process.env;
|
|
120
120
|
const platform = runtime.platform ?? process.platform;
|
|
121
|
-
const args = installerAddArgs(source.install_url, agents, skill);
|
|
122
|
-
if (!options.project)
|
|
123
|
-
args.push("--global");
|
|
124
|
-
if (options.copy)
|
|
125
|
-
args.push("--copy");
|
|
126
|
-
const invocation = resolveCmdShimInvocation("npx", args, platform, env);
|
|
127
121
|
const spawn = runtime.spawnSync ?? wrapExecFileSync(runtime.execFileSync) ?? spawnSync;
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
const
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
122
|
+
// A specific shortcut skill can declare prerequisite skills that its SKILL.md
|
|
123
|
+
// invokes (e.g. oxygen-clay-migration → oxygen-gtm, whose recipes it lives in).
|
|
124
|
+
// Installing it alone would leave those missing, so pull them in too. Best-
|
|
125
|
+
// effort (an unreachable/unparseable index just installs the requested skill),
|
|
126
|
+
// and skipped for "*", which already installs everything.
|
|
127
|
+
const dependencySkills = skill === "*"
|
|
128
|
+
? []
|
|
129
|
+
: await resolveSkillInstallDependencies(skill, source.index_url, runtime.fetch);
|
|
130
|
+
const targets = [skill, ...dependencySkills];
|
|
131
|
+
// The authenticated access URL is org-bound and secret, so it must never
|
|
132
|
+
// reach the installer's argv — any local process can read a child's
|
|
133
|
+
// arguments. Stage the requested skill content to a private temp dir (the
|
|
134
|
+
// tokenized URLs stay inside this process on the fetches) and hand the
|
|
135
|
+
// installer that local path instead. The public, non-secret index URL is
|
|
136
|
+
// passed straight through as before.
|
|
137
|
+
let stagingDir = null;
|
|
138
|
+
const outputs = [];
|
|
139
|
+
try {
|
|
140
|
+
let installSource = source.install_url;
|
|
141
|
+
if (source.authenticated) {
|
|
142
|
+
stagingDir = mkdtempSync(join(tmpdir(), "oxygen-skills-"));
|
|
143
|
+
await stageAuthenticatedSkills(stagingDir, source, targets, runtime.fetch ?? fetch);
|
|
144
|
+
installSource = stagingDir;
|
|
145
|
+
}
|
|
146
|
+
for (const target of targets) {
|
|
147
|
+
const args = installerAddArgs(installSource, agents, target);
|
|
148
|
+
if (!options.project)
|
|
149
|
+
args.push("--global");
|
|
150
|
+
if (options.copy)
|
|
151
|
+
args.push("--copy");
|
|
152
|
+
const invocation = resolveCmdShimInvocation("npx", args, platform, env);
|
|
153
|
+
const result = spawn(invocation.executable, invocation.args, {
|
|
154
|
+
encoding: "utf8",
|
|
155
|
+
env,
|
|
156
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
157
|
+
windowsHide: true,
|
|
158
|
+
...(platform === "win32" ? { windowsVerbatimArguments: true } : {}),
|
|
159
|
+
});
|
|
160
|
+
const stdout = typeof result.stdout === "string" ? result.stdout : "";
|
|
161
|
+
const stderr = typeof result.stderr === "string" ? result.stderr : "";
|
|
162
|
+
if (result.error || (typeof result.status === "number" && result.status !== 0)) {
|
|
163
|
+
const message = result.error instanceof Error ? result.error.message : null;
|
|
164
|
+
throw new OxygenError("skills_install_failed", "Unable to install Oxygen agent skills.", {
|
|
165
|
+
details: {
|
|
166
|
+
index_url: source.index_url,
|
|
167
|
+
authenticated: source.authenticated,
|
|
168
|
+
skill: target,
|
|
169
|
+
executable: invocation.label,
|
|
170
|
+
exit_code: typeof result.status === "number" ? result.status : null,
|
|
171
|
+
stdout: stdout.slice(0, 2000),
|
|
172
|
+
stderr: (stderr || message || "unknown").slice(0, 2000),
|
|
173
|
+
},
|
|
174
|
+
exitCode: 1,
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
outputs.push(stdout);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
finally {
|
|
181
|
+
if (stagingDir)
|
|
182
|
+
rmSync(stagingDir, { recursive: true, force: true });
|
|
183
|
+
}
|
|
184
|
+
if (dependencySkills.length > 0) {
|
|
185
|
+
process.stderr.write(`note: '${skill}' also installs its prerequisite skill(s): ${dependencySkills.join(", ")}.\n`);
|
|
150
186
|
}
|
|
151
187
|
return {
|
|
152
188
|
indexUrl: source.index_url,
|
|
@@ -156,10 +192,129 @@ options, runtime = {}) {
|
|
|
156
192
|
...(source.fallback_reason ? { access_fallback: source.fallback_reason } : {}),
|
|
157
193
|
agents,
|
|
158
194
|
skill,
|
|
195
|
+
...(dependencySkills.length > 0 ? { dependency_skills: dependencySkills } : {}),
|
|
159
196
|
scope: options.project ? "project" : "global",
|
|
160
|
-
output:
|
|
197
|
+
output: outputs.join(""),
|
|
161
198
|
};
|
|
162
199
|
}
|
|
200
|
+
/**
|
|
201
|
+
* Stages the requested skill(s) into `dir` so the installer receives a local
|
|
202
|
+
* filesystem path instead of the org-bound tokenized access URL. Keeping the URL
|
|
203
|
+
* out of the child's argv closes a local-process argv-inspection exposure; the
|
|
204
|
+
* token still travels over the network on these in-process fetches, which is the
|
|
205
|
+
* already-accepted part of the design. Layout matches what the installer walks
|
|
206
|
+
* for a local source: `<dir>/<skill>/<file>` (nested paths preserved).
|
|
207
|
+
*
|
|
208
|
+
* Any fetch/staging failure throws the same `skills_install_failed` error the
|
|
209
|
+
* installer path raises — we never fall back to passing the tokenized URL as a
|
|
210
|
+
* process argument.
|
|
211
|
+
*/
|
|
212
|
+
async function stageAuthenticatedSkills(dir, source, targets, fetchImpl) {
|
|
213
|
+
const entries = await fetchStagedSkillEntries(source.index_url, fetchImpl);
|
|
214
|
+
const wanted = targets.includes("*")
|
|
215
|
+
? entries
|
|
216
|
+
: entries.filter((entry) => targets.includes(entry.name));
|
|
217
|
+
// `<base>/.well-known/skills/index.json` → `<base>/.well-known/skills`; the
|
|
218
|
+
// installer reads a skill's files from `<root>/<name>/<file>`.
|
|
219
|
+
const skillsRoot = source.index_url.replace(/\/index\.json$/i, "");
|
|
220
|
+
for (const entry of wanted) {
|
|
221
|
+
// allSettled, not all: on a failed fetch, Promise.all would reject while
|
|
222
|
+
// sibling fetches are still writing — the caller's finally rmSyncs the temp
|
|
223
|
+
// dir and a late mkdirSync would silently recreate it as orphan litter.
|
|
224
|
+
// Waiting for every settle guarantees no write is in flight when we throw.
|
|
225
|
+
const staged = await Promise.allSettled(entry.files.map(async (file) => {
|
|
226
|
+
const content = await fetchStagedSkillFile(`${skillsRoot}/${entry.name}/${file}`, fetchImpl);
|
|
227
|
+
const destination = join(dir, entry.name, ...file.split("/"));
|
|
228
|
+
mkdirSync(dirname(destination), { recursive: true });
|
|
229
|
+
writeFileSync(destination, content);
|
|
230
|
+
}));
|
|
231
|
+
const failed = staged.find((result) => result.status === "rejected");
|
|
232
|
+
if (failed)
|
|
233
|
+
throw failed.reason;
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
async function fetchStagedSkillEntries(indexUrl, fetchImpl) {
|
|
237
|
+
let response;
|
|
238
|
+
try {
|
|
239
|
+
response = await fetchImpl(indexUrl, { headers: { Accept: "application/json" } });
|
|
240
|
+
}
|
|
241
|
+
catch (error) {
|
|
242
|
+
throw stagingFailure("fetch_index", { index_url: indexUrl, message: errorMessage(error) });
|
|
243
|
+
}
|
|
244
|
+
if (!response.ok) {
|
|
245
|
+
throw stagingFailure("fetch_index", { index_url: indexUrl, status: response.status });
|
|
246
|
+
}
|
|
247
|
+
let parsed;
|
|
248
|
+
try {
|
|
249
|
+
parsed = JSON.parse(await response.text());
|
|
250
|
+
}
|
|
251
|
+
catch (error) {
|
|
252
|
+
throw stagingFailure("parse_index", { index_url: indexUrl, message: errorMessage(error) });
|
|
253
|
+
}
|
|
254
|
+
if (!isRecord(parsed) || !Array.isArray(parsed.skills)) {
|
|
255
|
+
throw stagingFailure("parse_index", { index_url: indexUrl, message: "skills array missing" });
|
|
256
|
+
}
|
|
257
|
+
const entries = [];
|
|
258
|
+
for (const entry of parsed.skills) {
|
|
259
|
+
if (!isRecord(entry) || typeof entry.name !== "string" || !entry.name.trim())
|
|
260
|
+
continue;
|
|
261
|
+
const files = Array.isArray(entry.files)
|
|
262
|
+
? entry.files.filter((file) => typeof file === "string" && file.trim().length > 0)
|
|
263
|
+
: [];
|
|
264
|
+
entries.push({ name: entry.name.trim(), files });
|
|
265
|
+
}
|
|
266
|
+
return entries;
|
|
267
|
+
}
|
|
268
|
+
async function fetchStagedSkillFile(url, fetchImpl) {
|
|
269
|
+
let response;
|
|
270
|
+
try {
|
|
271
|
+
response = await fetchImpl(url);
|
|
272
|
+
}
|
|
273
|
+
catch (error) {
|
|
274
|
+
throw stagingFailure("fetch_file", { url, message: errorMessage(error) });
|
|
275
|
+
}
|
|
276
|
+
if (!response.ok) {
|
|
277
|
+
throw stagingFailure("fetch_file", { url, status: response.status });
|
|
278
|
+
}
|
|
279
|
+
return response.text();
|
|
280
|
+
}
|
|
281
|
+
// Mirror the installer-failure error so a staging failure surfaces identically
|
|
282
|
+
// (code, message, exitCode) to the caller — never as a silent URL fallback.
|
|
283
|
+
function stagingFailure(stage, details) {
|
|
284
|
+
return new OxygenError("skills_install_failed", "Unable to install Oxygen agent skills.", {
|
|
285
|
+
details: { stage, ...details },
|
|
286
|
+
exitCode: 1,
|
|
287
|
+
});
|
|
288
|
+
}
|
|
289
|
+
function errorMessage(error) {
|
|
290
|
+
return error instanceof Error ? error.message : String(error);
|
|
291
|
+
}
|
|
292
|
+
// Best-effort resolution of a skill's declared prerequisite skills from the
|
|
293
|
+
// index. Any failure (unreachable index, parse error, unknown skill) yields no
|
|
294
|
+
// dependencies so a dependency lookup can never block or fail an install.
|
|
295
|
+
async function resolveSkillInstallDependencies(skill, indexUrl, fetchImpl) {
|
|
296
|
+
try {
|
|
297
|
+
const index = await inspectSkillIndex(indexUrl, fetchImpl ?? fetch);
|
|
298
|
+
if (!index.reachable)
|
|
299
|
+
return [];
|
|
300
|
+
const entry = index.skills.find((candidate) => candidate.name === skill);
|
|
301
|
+
if (!entry)
|
|
302
|
+
return [];
|
|
303
|
+
// Drop self-references and duplicates; preserve declared order.
|
|
304
|
+
const seen = new Set([skill]);
|
|
305
|
+
const resolved = [];
|
|
306
|
+
for (const dep of entry.dependencies) {
|
|
307
|
+
if (seen.has(dep))
|
|
308
|
+
continue;
|
|
309
|
+
seen.add(dep);
|
|
310
|
+
resolved.push(dep);
|
|
311
|
+
}
|
|
312
|
+
return resolved;
|
|
313
|
+
}
|
|
314
|
+
catch {
|
|
315
|
+
return [];
|
|
316
|
+
}
|
|
317
|
+
}
|
|
163
318
|
// Adapt the legacy `execFileSync` runtime hook into a spawnSync-shaped return so
|
|
164
319
|
// existing test fixtures (update.test.ts) keep working without a coordinated
|
|
165
320
|
// rewrite. New tests should inject `spawnSync` directly.
|
|
@@ -347,6 +502,11 @@ function parseSkillIndex(value) {
|
|
|
347
502
|
title: typeof entry.title === "string" ? entry.title : null,
|
|
348
503
|
description: typeof entry.description === "string" ? entry.description : null,
|
|
349
504
|
entrypoint: typeof entry.entrypoint === "string" ? entry.entrypoint : null,
|
|
505
|
+
dependencies: Array.isArray(entry.dependencies)
|
|
506
|
+
? entry.dependencies
|
|
507
|
+
.filter((dep) => typeof dep === "string" && dep.trim().length > 0)
|
|
508
|
+
.map((dep) => dep.trim())
|
|
509
|
+
: [],
|
|
350
510
|
};
|
|
351
511
|
})
|
|
352
512
|
.filter((entry) => entry !== null);
|
|
@@ -4,6 +4,7 @@ export type RecipeLogLevel = "debug" | "info" | "warn" | "error";
|
|
|
4
4
|
export type RecipeRuntime = "durable";
|
|
5
5
|
export type RecipeToolRunOptions = {
|
|
6
6
|
key?: string;
|
|
7
|
+
optional?: boolean;
|
|
7
8
|
};
|
|
8
9
|
export type RecipeToolApi = {
|
|
9
10
|
run: <T = unknown>(toolId: string, payload?: Record<string, unknown>, options?: RecipeToolRunOptions) => Promise<T>;
|
|
@@ -46,6 +47,7 @@ export type RecipeTablesApi = {
|
|
|
46
47
|
includeProfile?: boolean;
|
|
47
48
|
includeDataQuality?: boolean;
|
|
48
49
|
key?: string;
|
|
50
|
+
optional?: boolean;
|
|
49
51
|
}) => Promise<T>;
|
|
50
52
|
};
|
|
51
53
|
export type RecipeRowsApi = {
|
|
@@ -26,86 +26,109 @@ export type PricingPlanDefinition = {
|
|
|
26
26
|
};
|
|
27
27
|
export type PricingPlanMetadata = Record<string, unknown> | null | undefined;
|
|
28
28
|
export declare const CONTACT_SALES_URL = "https://cal.com/tim-scheuer-mxbib9/45";
|
|
29
|
+
export declare const CREDITS_PER_USD = 1000;
|
|
30
|
+
export declare const TOPUP_USD_CENTS_PER_1K_CREDITS = 125;
|
|
31
|
+
export declare const AUTOMATION_ACTION_CREDITS = 0.01;
|
|
32
|
+
export declare const CREDIT_TOPUP_PACKS: readonly [{
|
|
33
|
+
readonly id: "10";
|
|
34
|
+
readonly usdCents: 1000;
|
|
35
|
+
readonly credits: 8000;
|
|
36
|
+
}, {
|
|
37
|
+
readonly id: "25";
|
|
38
|
+
readonly usdCents: 2500;
|
|
39
|
+
readonly credits: 20000;
|
|
40
|
+
}, {
|
|
41
|
+
readonly id: "100";
|
|
42
|
+
readonly usdCents: 10000;
|
|
43
|
+
readonly credits: 80000;
|
|
44
|
+
}, {
|
|
45
|
+
readonly id: "250";
|
|
46
|
+
readonly usdCents: 25000;
|
|
47
|
+
readonly credits: 200000;
|
|
48
|
+
}];
|
|
49
|
+
export type CreditTopupPack = (typeof CREDIT_TOPUP_PACKS)[number];
|
|
50
|
+
export type CreditTopupPackId = CreditTopupPack["id"];
|
|
51
|
+
export declare function resolveCreditTopupPack(id: string | null | undefined): CreditTopupPack | null;
|
|
29
52
|
export declare const TRIAL_PERIOD_DAYS = 7;
|
|
30
|
-
export declare const TRIAL_CREDIT_GRANT =
|
|
53
|
+
export declare const TRIAL_CREDIT_GRANT = 20000;
|
|
31
54
|
export declare const BASE_PRICING_PLANS: {
|
|
32
55
|
readonly free: {
|
|
33
56
|
readonly tier: "free";
|
|
34
57
|
readonly name: "Free";
|
|
35
58
|
readonly monthlyPriceCents: 0;
|
|
36
|
-
readonly monthlyCredits:
|
|
37
|
-
readonly weeklyCreditsLimit:
|
|
38
|
-
readonly rolloverCap:
|
|
39
|
-
readonly monthlyAutomationActions:
|
|
59
|
+
readonly monthlyCredits: 10000;
|
|
60
|
+
readonly weeklyCreditsLimit: 10000;
|
|
61
|
+
readonly rolloverCap: 10000;
|
|
62
|
+
readonly monthlyAutomationActions: null;
|
|
40
63
|
readonly automationOverageCentsPerMillion: null;
|
|
41
64
|
readonly automationOverageEnabledDefault: false;
|
|
42
65
|
readonly byokEnabled: false;
|
|
43
66
|
readonly description: "Get $10 in credits on us every month — try OXYGEN with managed email, phone enrichment, and AI credits.";
|
|
44
67
|
readonly ctaLabel: "Start free";
|
|
45
|
-
readonly features: readonly ["$10 in credits every month", "
|
|
68
|
+
readonly features: readonly ["$10 in credits every month", "Automation actions at 0.01 credits each", "All integrations", "Workflows", "No card required"];
|
|
46
69
|
};
|
|
47
70
|
readonly starter: {
|
|
48
71
|
readonly tier: "starter";
|
|
49
72
|
readonly name: "Starter";
|
|
50
73
|
readonly monthlyPriceCents: 9900;
|
|
51
|
-
readonly monthlyCredits:
|
|
52
|
-
readonly weeklyCreditsLimit:
|
|
53
|
-
readonly rolloverCap:
|
|
54
|
-
readonly monthlyAutomationActions:
|
|
55
|
-
readonly automationOverageCentsPerMillion:
|
|
74
|
+
readonly monthlyCredits: 99000;
|
|
75
|
+
readonly weeklyCreditsLimit: 40000;
|
|
76
|
+
readonly rolloverCap: 99000;
|
|
77
|
+
readonly monthlyAutomationActions: null;
|
|
78
|
+
readonly automationOverageCentsPerMillion: null;
|
|
56
79
|
readonly automationOverageEnabledDefault: false;
|
|
57
80
|
readonly byokEnabled: true;
|
|
58
81
|
readonly description: "For founders running focused managed enrichment and AI columns.";
|
|
59
82
|
readonly ctaLabel: "Choose Starter";
|
|
60
|
-
readonly features: readonly ["All integrations", "
|
|
83
|
+
readonly features: readonly ["99,000 credits every month", "All integrations", "Workflows — automation actions at 0.01 credits each", "Bring your own keys", "Standard support"];
|
|
61
84
|
};
|
|
62
85
|
readonly pro: {
|
|
63
86
|
readonly tier: "pro";
|
|
64
87
|
readonly name: "Pro";
|
|
65
88
|
readonly monthlyPriceCents: 24900;
|
|
66
|
-
readonly monthlyCredits:
|
|
67
|
-
readonly weeklyCreditsLimit:
|
|
68
|
-
readonly rolloverCap:
|
|
69
|
-
readonly monthlyAutomationActions:
|
|
70
|
-
readonly automationOverageCentsPerMillion:
|
|
89
|
+
readonly monthlyCredits: 249000;
|
|
90
|
+
readonly weeklyCreditsLimit: 100000;
|
|
91
|
+
readonly rolloverCap: 249000;
|
|
92
|
+
readonly monthlyAutomationActions: null;
|
|
93
|
+
readonly automationOverageCentsPerMillion: null;
|
|
71
94
|
readonly automationOverageEnabledDefault: false;
|
|
72
95
|
readonly byokEnabled: true;
|
|
73
96
|
readonly description: "For recurring GTM enrichment and AI-column workflows on managed credits.";
|
|
74
97
|
readonly badge: "Most popular";
|
|
75
98
|
readonly highlighted: true;
|
|
76
99
|
readonly ctaLabel: "Choose Pro";
|
|
77
|
-
readonly features: readonly ["All integrations", "
|
|
100
|
+
readonly features: readonly ["249,000 credits every month", "All integrations", "Workflows — automation actions at 0.01 credits each", "Bring your own keys", "Standard support"];
|
|
78
101
|
};
|
|
79
102
|
readonly team: {
|
|
80
103
|
readonly tier: "team";
|
|
81
104
|
readonly name: "Team";
|
|
82
105
|
readonly monthlyPriceCents: 74900;
|
|
83
|
-
readonly monthlyCredits:
|
|
84
|
-
readonly weeklyCreditsLimit:
|
|
85
|
-
readonly rolloverCap:
|
|
86
|
-
readonly monthlyAutomationActions:
|
|
87
|
-
readonly automationOverageCentsPerMillion:
|
|
106
|
+
readonly monthlyCredits: 749000;
|
|
107
|
+
readonly weeklyCreditsLimit: 300000;
|
|
108
|
+
readonly rolloverCap: 749000;
|
|
109
|
+
readonly monthlyAutomationActions: null;
|
|
110
|
+
readonly automationOverageCentsPerMillion: null;
|
|
88
111
|
readonly automationOverageEnabledDefault: false;
|
|
89
112
|
readonly byokEnabled: true;
|
|
90
113
|
readonly description: "For small GTM teams and agencies running shared workflows with their own AI keys.";
|
|
91
114
|
readonly ctaLabel: "Choose Team";
|
|
92
|
-
readonly features: readonly ["All integrations", "
|
|
115
|
+
readonly features: readonly ["749,000 credits every month", "All integrations", "Workflows — automation actions at 0.01 credits each", "Bring your own keys", "Premium support"];
|
|
93
116
|
};
|
|
94
117
|
readonly scale: {
|
|
95
118
|
readonly tier: "scale";
|
|
96
119
|
readonly name: "Scale";
|
|
97
120
|
readonly monthlyPriceCents: null;
|
|
98
|
-
readonly monthlyCredits:
|
|
99
|
-
readonly weeklyCreditsLimit:
|
|
100
|
-
readonly rolloverCap:
|
|
101
|
-
readonly monthlyAutomationActions:
|
|
121
|
+
readonly monthlyCredits: 10000000;
|
|
122
|
+
readonly weeklyCreditsLimit: 10000000;
|
|
123
|
+
readonly rolloverCap: 10000000;
|
|
124
|
+
readonly monthlyAutomationActions: null;
|
|
102
125
|
readonly automationOverageCentsPerMillion: null;
|
|
103
126
|
readonly automationOverageEnabledDefault: false;
|
|
104
127
|
readonly byokEnabled: true;
|
|
105
128
|
readonly description: "For high-volume GTM teams that need custom limits and rollout support.";
|
|
106
129
|
readonly ctaLabel: "Contact sales";
|
|
107
130
|
readonly contactHref: "https://cal.com/tim-scheuer-mxbib9/45";
|
|
108
|
-
readonly features: readonly ["
|
|
131
|
+
readonly features: readonly ["10,000,000+ managed credits / month", "Automation actions at 0.01 credits each", "Phone number enrichment on managed credits", "Custom provider routing and BYOK policies", "Dedicated onboarding"];
|
|
109
132
|
};
|
|
110
133
|
readonly enterprise: {
|
|
111
134
|
readonly tier: "enterprise";
|