@orchagent/cli 0.3.83 → 0.3.84
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/dist/commands/billing.js +21 -107
- package/dist/commands/github.js +1 -1
- package/dist/commands/info.js +1 -15
- package/dist/commands/install.js +2 -48
- package/dist/commands/publish.js +9 -4
- package/dist/commands/run.js +13 -94
- package/dist/commands/security.js +2 -2
- package/dist/commands/service.js +8 -8
- package/dist/commands/skill.js +0 -55
- package/dist/lib/api.js +0 -12
- package/dist/lib/errors.js +8 -1
- package/dist/lib/spinner.js +5 -0
- package/package.json +1 -1
- package/dist/lib/pricing.js +0 -22
package/dist/commands/billing.js
CHANGED
|
@@ -4,119 +4,33 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.registerBillingCommand = registerBillingCommand;
|
|
7
|
-
const cli_table3_1 = __importDefault(require("cli-table3"));
|
|
8
|
-
const chalk_1 = __importDefault(require("chalk"));
|
|
9
7
|
const open_1 = __importDefault(require("open"));
|
|
10
8
|
const config_1 = require("../lib/config");
|
|
11
9
|
const api_1 = require("../lib/api");
|
|
12
|
-
const errors_1 = require("../lib/errors");
|
|
13
|
-
const output_1 = require("../lib/output");
|
|
14
10
|
function registerBillingCommand(program) {
|
|
15
|
-
|
|
11
|
+
program
|
|
16
12
|
.command('billing')
|
|
17
|
-
.description('
|
|
18
|
-
|
|
19
|
-
billing
|
|
20
|
-
.command('balance')
|
|
21
|
-
.description('Show your credit balance and recent transactions')
|
|
22
|
-
.option('--json', 'Output as JSON')
|
|
23
|
-
.action(async (options) => {
|
|
13
|
+
.description('Open billing portal in your browser')
|
|
14
|
+
.action(async () => {
|
|
24
15
|
const resolved = await (0, config_1.getResolvedConfig)();
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
16
|
+
if (!resolved.apiKey) {
|
|
17
|
+
process.stderr.write('Not logged in. Run: orch login\n');
|
|
18
|
+
process.exit(1);
|
|
19
|
+
}
|
|
20
|
+
// Get the billing portal URL from the gateway
|
|
21
|
+
try {
|
|
22
|
+
const data = await (0, api_1.request)(resolved, 'GET', '/billing/portal');
|
|
23
|
+
process.stdout.write('Opening billing portal...\n');
|
|
24
|
+
await (0, open_1.default)(data.url);
|
|
25
|
+
process.stdout.write(`If browser doesn't open, visit:\n${data.url}\n`);
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
// Fallback to direct web URL
|
|
29
|
+
const webUrl = resolved.apiUrl.replace('api.', '').replace('/v1', '');
|
|
30
|
+
const url = `${webUrl}/settings/billing`;
|
|
31
|
+
process.stdout.write('Opening billing page...\n');
|
|
32
|
+
await (0, open_1.default)(url);
|
|
33
|
+
process.stdout.write(`If browser doesn't open, visit:\n${url}\n`);
|
|
29
34
|
}
|
|
30
|
-
// Show balance
|
|
31
|
-
const balance = data.balance_cents / 100;
|
|
32
|
-
process.stdout.write(chalk_1.default.bold(`\nBalance: ${chalk_1.default.green(`$${balance.toFixed(2)} USD`)}\n\n`));
|
|
33
|
-
// Show recent transactions
|
|
34
|
-
if (data.recent_transactions && data.recent_transactions.length > 0) {
|
|
35
|
-
process.stdout.write(chalk_1.default.bold('Recent Transactions:\n'));
|
|
36
|
-
const table = new cli_table3_1.default({
|
|
37
|
-
head: [
|
|
38
|
-
chalk_1.default.bold('Date'),
|
|
39
|
-
chalk_1.default.bold('Type'),
|
|
40
|
-
chalk_1.default.bold('Amount'),
|
|
41
|
-
chalk_1.default.bold('Balance'),
|
|
42
|
-
],
|
|
43
|
-
});
|
|
44
|
-
data.recent_transactions.forEach((tx) => {
|
|
45
|
-
const date = new Date(tx.created_at).toLocaleDateString();
|
|
46
|
-
const amount = (tx.amount_cents / 100).toFixed(2);
|
|
47
|
-
const balance = (tx.balance_after_cents / 100).toFixed(2);
|
|
48
|
-
const amountColor = tx.amount_cents >= 0 ? chalk_1.default.green : chalk_1.default.red;
|
|
49
|
-
table.push([date, tx.transaction_type, amountColor(`$${amount}`), `$${balance}`]);
|
|
50
|
-
});
|
|
51
|
-
process.stdout.write(`${table.toString()}\n\n`);
|
|
52
|
-
}
|
|
53
|
-
else {
|
|
54
|
-
process.stdout.write('No recent transactions\n\n');
|
|
55
|
-
}
|
|
56
|
-
process.stdout.write(chalk_1.default.gray('Add credits: orch billing add 5\n'));
|
|
57
|
-
});
|
|
58
|
-
// orch billing add <amount>
|
|
59
|
-
billing
|
|
60
|
-
.command('add [amount]')
|
|
61
|
-
.description('Add credits via Stripe checkout (minimum $5.00 USD)')
|
|
62
|
-
.action(async (amount) => {
|
|
63
|
-
const resolved = await (0, config_1.getResolvedConfig)();
|
|
64
|
-
// Parse and validate amount
|
|
65
|
-
let amountNum;
|
|
66
|
-
if (!amount) {
|
|
67
|
-
amountNum = 5.00; // Default to $5
|
|
68
|
-
}
|
|
69
|
-
else {
|
|
70
|
-
amountNum = parseFloat(amount);
|
|
71
|
-
if (isNaN(amountNum) || amountNum < 5.00) {
|
|
72
|
-
throw new errors_1.CliError('Amount must be at least $5.00 USD', errors_1.ExitCodes.INVALID_INPUT);
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
const amountCents = Math.round(amountNum * 100);
|
|
76
|
-
// Create checkout session
|
|
77
|
-
const checkout = await (0, api_1.createCreditCheckout)(resolved, amountCents);
|
|
78
|
-
// Open in browser
|
|
79
|
-
process.stdout.write(`\nOpening checkout page...\n`);
|
|
80
|
-
process.stdout.write(`Amount: $${amountNum.toFixed(2)} USD\n\n`);
|
|
81
|
-
await (0, open_1.default)(checkout.checkout_url);
|
|
82
|
-
process.stdout.write(chalk_1.default.gray(`If browser doesn't open, visit:\n${checkout.checkout_url}\n`));
|
|
83
|
-
});
|
|
84
|
-
// orch billing history (alias)
|
|
85
|
-
billing
|
|
86
|
-
.command('history')
|
|
87
|
-
.description('Show transaction history (alias for balance)')
|
|
88
|
-
.option('--json', 'Output as JSON')
|
|
89
|
-
.action(async (options) => {
|
|
90
|
-
// Just call balance command
|
|
91
|
-
const resolved = await (0, config_1.getResolvedConfig)();
|
|
92
|
-
const data = await (0, api_1.getCreditsBalance)(resolved);
|
|
93
|
-
if (options.json) {
|
|
94
|
-
(0, output_1.printJson)(data);
|
|
95
|
-
return;
|
|
96
|
-
}
|
|
97
|
-
// Simplified view - just show transactions
|
|
98
|
-
if (data.recent_transactions && data.recent_transactions.length > 0) {
|
|
99
|
-
const table = new cli_table3_1.default({
|
|
100
|
-
head: [
|
|
101
|
-
chalk_1.default.bold('Date'),
|
|
102
|
-
chalk_1.default.bold('Type'),
|
|
103
|
-
chalk_1.default.bold('Amount'),
|
|
104
|
-
chalk_1.default.bold('Balance'),
|
|
105
|
-
],
|
|
106
|
-
});
|
|
107
|
-
data.recent_transactions.forEach((tx) => {
|
|
108
|
-
const date = new Date(tx.created_at).toLocaleDateString();
|
|
109
|
-
const amount = (tx.amount_cents / 100).toFixed(2);
|
|
110
|
-
const balance = (tx.balance_after_cents / 100).toFixed(2);
|
|
111
|
-
const amountColor = tx.amount_cents >= 0 ? chalk_1.default.green : chalk_1.default.red;
|
|
112
|
-
table.push([date, tx.transaction_type, amountColor(`$${amount}`), `$${balance}`]);
|
|
113
|
-
});
|
|
114
|
-
process.stdout.write(`\n${table.toString()}\n\n`);
|
|
115
|
-
}
|
|
116
|
-
else {
|
|
117
|
-
process.stdout.write('\nNo transactions found\n\n');
|
|
118
|
-
}
|
|
119
|
-
const balance = data.balance_cents / 100;
|
|
120
|
-
process.stdout.write(chalk_1.default.gray(`Current balance: $${balance.toFixed(2)} USD\n`));
|
|
121
35
|
});
|
|
122
36
|
}
|
package/dist/commands/github.js
CHANGED
|
@@ -147,7 +147,7 @@ async function getGitHubStatus(config, json) {
|
|
|
147
147
|
}
|
|
148
148
|
process.stdout.write(`GitHub Status:\n\n`);
|
|
149
149
|
process.stdout.write(` Connected: ${chalk_1.default.green('Yes')}\n`);
|
|
150
|
-
process.stdout.write(` Account: ${chalk_1.default.bold(connection.github_account_login)}\n`);
|
|
150
|
+
process.stdout.write(` Account: ${chalk_1.default.bold(connection.github_account_login || 'Unknown')}\n`);
|
|
151
151
|
if (connection.github_account_type) {
|
|
152
152
|
process.stdout.write(` Type: ${connection.github_account_type === 'User' ? 'User' : 'Organization'}\n`);
|
|
153
153
|
}
|
package/dist/commands/info.js
CHANGED
|
@@ -8,7 +8,6 @@ const chalk_1 = __importDefault(require("chalk"));
|
|
|
8
8
|
const config_1 = require("../lib/config");
|
|
9
9
|
const api_1 = require("../lib/api");
|
|
10
10
|
const agent_ref_1 = require("../lib/agent-ref");
|
|
11
|
-
const pricing_1 = require("../lib/pricing");
|
|
12
11
|
function formatSchema(schema, indent = ' ') {
|
|
13
12
|
const lines = [];
|
|
14
13
|
const props = schema.properties || {};
|
|
@@ -66,8 +65,6 @@ async function getAgentInfo(config, org, agent, version, workspaceId) {
|
|
|
66
65
|
source_url: meta.source_url,
|
|
67
66
|
run_command: meta.run_command,
|
|
68
67
|
url: meta.url,
|
|
69
|
-
pricing_mode: publicMeta.pricing_mode,
|
|
70
|
-
price_per_call_cents: publicMeta.price_per_call_cents,
|
|
71
68
|
};
|
|
72
69
|
}
|
|
73
70
|
catch (err) {
|
|
@@ -111,14 +108,12 @@ async function getAgentInfo(config, org, agent, version, workspaceId) {
|
|
|
111
108
|
source_url: targetAgent.source_url,
|
|
112
109
|
run_command: targetAgent.run_command,
|
|
113
110
|
url: targetAgent.url,
|
|
114
|
-
pricing_mode: targetAgent.pricing_mode,
|
|
115
|
-
price_per_call_cents: targetAgent.price_per_call_cents,
|
|
116
111
|
};
|
|
117
112
|
}
|
|
118
113
|
function registerInfoCommand(program) {
|
|
119
114
|
program
|
|
120
115
|
.command('info <agent>')
|
|
121
|
-
.description('Show agent details including
|
|
116
|
+
.description('Show agent details including inputs and outputs')
|
|
122
117
|
.option('--json', 'Output as JSON')
|
|
123
118
|
.action(async (agentArg, options) => {
|
|
124
119
|
const config = await (0, config_1.getResolvedConfig)();
|
|
@@ -148,15 +143,6 @@ function registerInfoCommand(program) {
|
|
|
148
143
|
process.stdout.write(`Callable: ${chalk_1.default.green('yes')} — other agents can invoke this via the orchagent SDK\n`);
|
|
149
144
|
}
|
|
150
145
|
process.stdout.write(`Providers: ${agentData.supported_providers.join(', ')}\n`);
|
|
151
|
-
// Display pricing information
|
|
152
|
-
const priceStr = (0, pricing_1.formatPrice)(agentData);
|
|
153
|
-
const color = (0, pricing_1.isPaidAgent)(agentData) ? chalk_1.default.yellow : chalk_1.default.green;
|
|
154
|
-
process.stdout.write(`Price: ${color(priceStr)}\n`);
|
|
155
|
-
// If paid, show server-only message for non-owners
|
|
156
|
-
if ((0, pricing_1.isPaidAgent)(agentData)) {
|
|
157
|
-
process.stdout.write(chalk_1.default.gray('Note: Paid agents run on server only (use orch run)\n'));
|
|
158
|
-
process.stdout.write(chalk_1.default.gray(' Owners can still download for development/testing\n'));
|
|
159
|
-
}
|
|
160
146
|
if (agentData.type === 'tool') {
|
|
161
147
|
// Don't show internal routing URLs - they confuse users
|
|
162
148
|
if (agentData.url && !agentData.url.includes('.internal')) {
|
package/dist/commands/install.js
CHANGED
|
@@ -15,7 +15,6 @@ const adapters_1 = require("../adapters");
|
|
|
15
15
|
const skill_resolve_1 = require("../lib/skill-resolve");
|
|
16
16
|
const installed_1 = require("../lib/installed");
|
|
17
17
|
const agents_md_utils_1 = require("../lib/agents-md-utils");
|
|
18
|
-
const pricing_1 = require("../lib/pricing");
|
|
19
18
|
const DEFAULT_VERSION = 'latest';
|
|
20
19
|
function parseAgentRef(value) {
|
|
21
20
|
const [ref, versionPart] = value.split('@');
|
|
@@ -44,48 +43,6 @@ async function downloadAgentWithFallback(config, org, name, version, workspaceId
|
|
|
44
43
|
throw err;
|
|
45
44
|
}
|
|
46
45
|
}
|
|
47
|
-
// Check if paid agent
|
|
48
|
-
if (publicMeta && (0, pricing_1.isPaidAgent)(publicMeta)) {
|
|
49
|
-
// Paid agent - check if owner
|
|
50
|
-
if (config.apiKey) {
|
|
51
|
-
const callerOrg = await (0, api_1.getOrg)(config, workspaceId);
|
|
52
|
-
const isOwner = (publicMeta.org_id && callerOrg.id === publicMeta.org_id) ||
|
|
53
|
-
(publicMeta.org_slug && callerOrg.slug === publicMeta.org_slug);
|
|
54
|
-
if (isOwner) {
|
|
55
|
-
// Owner - fetch from authenticated endpoint with full prompt
|
|
56
|
-
const myAgents = await (0, api_1.listMyAgents)(config, workspaceId);
|
|
57
|
-
const matching = myAgents.filter(a => a.name === name);
|
|
58
|
-
if (matching.length > 0) {
|
|
59
|
-
let targetAgent;
|
|
60
|
-
if (version === 'latest') {
|
|
61
|
-
targetAgent = matching.sort((a, b) => new Date(b.created_at).getTime() - new Date(a.created_at).getTime())[0];
|
|
62
|
-
}
|
|
63
|
-
else {
|
|
64
|
-
const found = matching.find(a => a.version === version);
|
|
65
|
-
if (!found) {
|
|
66
|
-
throw new api_1.ApiError(`Agent '${org}/${name}@${version}' not found`, 404);
|
|
67
|
-
}
|
|
68
|
-
targetAgent = found;
|
|
69
|
-
}
|
|
70
|
-
// Fetch full agent data with prompt from authenticated endpoint
|
|
71
|
-
const agentData = await (0, api_1.request)(config, 'GET', `/agents/${targetAgent.id}`);
|
|
72
|
-
return { ...agentData, org_slug: org };
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
else {
|
|
76
|
-
// Non-owner - block with helpful message
|
|
77
|
-
const price = (0, pricing_1.formatPrice)(publicMeta);
|
|
78
|
-
throw new errors_1.CliError(`This agent is paid (${price}) and runs on server only.\n\n` +
|
|
79
|
-
`Use: orch run ${org}/${name}@${version} --data '{...}'`);
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
else {
|
|
83
|
-
// Not authenticated - block
|
|
84
|
-
const price = (0, pricing_1.formatPrice)(publicMeta);
|
|
85
|
-
throw new errors_1.CliError(`This agent is paid (${price}) and runs on server only.\n\n` +
|
|
86
|
-
`Use: orch run ${org}/${name}@${version} --data '{...}'`);
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
46
|
// Check if download is disabled (server-only agent)
|
|
90
47
|
if (publicMeta && publicMeta.allow_local_download === false) {
|
|
91
48
|
// Check if owner (can bypass)
|
|
@@ -114,7 +71,8 @@ async function downloadAgentWithFallback(config, org, name, version, workspaceId
|
|
|
114
71
|
}
|
|
115
72
|
}
|
|
116
73
|
}
|
|
117
|
-
|
|
74
|
+
const typeLabel = publicMeta.type || 'agent';
|
|
75
|
+
throw new errors_1.CliError(`This ${typeLabel} is server-only and cannot be downloaded.\n\n` +
|
|
118
76
|
`Use: orch run ${org}/${name}@${version} --data '{...}'`);
|
|
119
77
|
}
|
|
120
78
|
// Free agent - proceed normally with public data
|
|
@@ -159,10 +117,6 @@ function registerInstallCommand(program) {
|
|
|
159
117
|
.option('--global', 'Install to home directory (alias for --scope user)')
|
|
160
118
|
.option('--dry-run', 'Show what would be installed without making changes')
|
|
161
119
|
.option('--json', 'Output result as JSON (for automation/tooling)')
|
|
162
|
-
.addHelpText('after', `
|
|
163
|
-
Note: Paid agents cannot be installed locally - they run on server only.
|
|
164
|
-
Use 'orchagent run' to execute paid agents.
|
|
165
|
-
`)
|
|
166
120
|
.action(async (agentArg, options) => {
|
|
167
121
|
const jsonMode = options.json === true;
|
|
168
122
|
const log = (msg) => { if (!jsonMode)
|
package/dist/commands/publish.js
CHANGED
|
@@ -495,7 +495,7 @@ function registerPublishCommand(program) {
|
|
|
495
495
|
const skillVersion = skillResult.agent?.version || 'v1';
|
|
496
496
|
const skillAgentId = skillResult.agent?.id;
|
|
497
497
|
await (0, analytics_1.track)('cli_publish', { agent_type: 'skill', multi_file: hasMultipleFiles });
|
|
498
|
-
process.stdout.write(`\
|
|
498
|
+
process.stdout.write(`\n${chalk_1.default.green('✔')} Published ${org.slug}/${skillData.frontmatter.name}@${skillVersion} successfully!\n\n`);
|
|
499
499
|
if (hasMultipleFiles) {
|
|
500
500
|
process.stdout.write(`Files: ${skillFiles.length} files included\n`);
|
|
501
501
|
}
|
|
@@ -706,6 +706,7 @@ function registerPublishCommand(program) {
|
|
|
706
706
|
}
|
|
707
707
|
let agentUrl = options.url;
|
|
708
708
|
let shouldUploadBundle = false;
|
|
709
|
+
let servicesUpdated = 0;
|
|
709
710
|
let runtimeConfig;
|
|
710
711
|
let bundleEntrypoint = manifest.entrypoint;
|
|
711
712
|
if (executionEngine === 'code_runtime') {
|
|
@@ -1045,9 +1046,9 @@ function registerPublishCommand(program) {
|
|
|
1045
1046
|
process.stdout.write(` ${chalk_1.default.cyan('Using workspace default environment')}\n`);
|
|
1046
1047
|
}
|
|
1047
1048
|
}
|
|
1048
|
-
//
|
|
1049
|
+
// Store service update count for success message
|
|
1049
1050
|
if (uploadResult.services_updated && uploadResult.services_updated > 0) {
|
|
1050
|
-
|
|
1051
|
+
servicesUpdated = uploadResult.services_updated;
|
|
1051
1052
|
}
|
|
1052
1053
|
}
|
|
1053
1054
|
finally {
|
|
@@ -1062,7 +1063,11 @@ function registerPublishCommand(program) {
|
|
|
1062
1063
|
callable,
|
|
1063
1064
|
hosted: shouldUploadBundle,
|
|
1064
1065
|
});
|
|
1065
|
-
process.stdout.write(`\
|
|
1066
|
+
process.stdout.write(`\n${chalk_1.default.green('✔')} Published ${org.slug}/${manifest.name}@${assignedVersion} successfully!\n`);
|
|
1067
|
+
if (servicesUpdated > 0) {
|
|
1068
|
+
process.stdout.write(`${chalk_1.default.green('✔')} Updated ${servicesUpdated} running service(s) to ${assignedVersion} successfully!\n`);
|
|
1069
|
+
}
|
|
1070
|
+
process.stdout.write(`\n`);
|
|
1066
1071
|
process.stdout.write(`Type: ${canonicalType}\n`);
|
|
1067
1072
|
process.stdout.write(`Run mode: ${runMode}\n`);
|
|
1068
1073
|
process.stdout.write(`Execution engine: ${executionEngine}${shouldUploadBundle ? ' (hosted)' : ''}\n`);
|
package/dist/commands/run.js
CHANGED
|
@@ -55,7 +55,6 @@ const output_1 = require("../lib/output");
|
|
|
55
55
|
const spinner_1 = require("../lib/spinner");
|
|
56
56
|
const llm_1 = require("../lib/llm");
|
|
57
57
|
const analytics_1 = require("../lib/analytics");
|
|
58
|
-
const pricing_1 = require("../lib/pricing");
|
|
59
58
|
const package_json_1 = __importDefault(require("../../package.json"));
|
|
60
59
|
const DEFAULT_VERSION = 'latest';
|
|
61
60
|
const AGENTS_DIR = path_1.default.join(os_1.default.homedir(), '.orchagent', 'agents');
|
|
@@ -462,16 +461,10 @@ async function downloadAgent(config, org, agent, version, workspaceId) {
|
|
|
462
461
|
}
|
|
463
462
|
}
|
|
464
463
|
// Non-owner - block with helpful message
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
`Run without --local: orch run ${org}/${agent}@${version} --data '{...}'`);
|
|
470
|
-
}
|
|
471
|
-
else {
|
|
472
|
-
throw new errors_1.CliError(`This agent is server-only and cannot be downloaded.\n\n` +
|
|
473
|
-
`Run without --local: orch run ${org}/${agent}@${version} --data '{...}'`);
|
|
474
|
-
}
|
|
464
|
+
// Use the gateway message which has the correct type label (agent/tool/skill/prompt)
|
|
465
|
+
const serverMsg = payload?.error?.message || 'This agent is server-only.';
|
|
466
|
+
throw new errors_1.CliError(`${serverMsg}\n\n` +
|
|
467
|
+
`Run without --local: orch run ${org}/${agent}@${version} --data '{...}'`);
|
|
475
468
|
}
|
|
476
469
|
}
|
|
477
470
|
if (!(err instanceof api_1.ApiError) || err.status !== 404)
|
|
@@ -1703,58 +1696,6 @@ async function executeCloud(agentRef, file, options) {
|
|
|
1703
1696
|
}
|
|
1704
1697
|
}
|
|
1705
1698
|
}
|
|
1706
|
-
// Pre-call balance check for paid agents
|
|
1707
|
-
let pricingInfo;
|
|
1708
|
-
if ((0, pricing_1.isPaidAgent)(agentMeta)) {
|
|
1709
|
-
let isOwner = false;
|
|
1710
|
-
try {
|
|
1711
|
-
const callerOrg = await (0, api_1.getOrg)(resolved, workspaceId);
|
|
1712
|
-
const agentOrgId = agentMeta.org_id;
|
|
1713
|
-
const agentOrgSlug = agentMeta.org_slug;
|
|
1714
|
-
if (agentOrgId && callerOrg.id === agentOrgId) {
|
|
1715
|
-
isOwner = true;
|
|
1716
|
-
}
|
|
1717
|
-
else if (agentOrgSlug && callerOrg.slug === agentOrgSlug) {
|
|
1718
|
-
isOwner = true;
|
|
1719
|
-
}
|
|
1720
|
-
}
|
|
1721
|
-
catch {
|
|
1722
|
-
isOwner = false;
|
|
1723
|
-
}
|
|
1724
|
-
if (isOwner) {
|
|
1725
|
-
if (!options.json)
|
|
1726
|
-
process.stderr.write(`Cost: FREE (author)\n\n`);
|
|
1727
|
-
}
|
|
1728
|
-
else {
|
|
1729
|
-
const price = agentMeta.price_per_call_cents;
|
|
1730
|
-
pricingInfo = { price_cents: price ?? null };
|
|
1731
|
-
if (!price || price <= 0) {
|
|
1732
|
-
if (!options.json)
|
|
1733
|
-
process.stderr.write(`Warning: Pricing data unavailable. The server will verify payment.\n\n`);
|
|
1734
|
-
}
|
|
1735
|
-
else {
|
|
1736
|
-
try {
|
|
1737
|
-
const balanceData = await (0, api_1.getCreditsBalance)(resolved);
|
|
1738
|
-
const balance = balanceData.balance_cents;
|
|
1739
|
-
if (balance < price) {
|
|
1740
|
-
process.stderr.write(`Insufficient credits:\n` +
|
|
1741
|
-
` Balance: $${(balance / 100).toFixed(2)}\n` +
|
|
1742
|
-
` Required: $${(price / 100).toFixed(2)}\n\n` +
|
|
1743
|
-
`Add credits:\n` +
|
|
1744
|
-
` orch billing add 5\n` +
|
|
1745
|
-
` orch billing balance # check current balance\n`);
|
|
1746
|
-
process.exit(errors_1.ExitCodes.PERMISSION_DENIED);
|
|
1747
|
-
}
|
|
1748
|
-
if (!options.json)
|
|
1749
|
-
process.stderr.write(`Cost: $${(price / 100).toFixed(2)}/call\n\n`);
|
|
1750
|
-
}
|
|
1751
|
-
catch (err) {
|
|
1752
|
-
if (!options.json)
|
|
1753
|
-
process.stderr.write(`Warning: Could not verify balance. The server will check payment.\n\n`);
|
|
1754
|
-
}
|
|
1755
|
-
}
|
|
1756
|
-
}
|
|
1757
|
-
}
|
|
1758
1699
|
const endpoint = options.endpoint?.trim() || agentMeta.default_endpoint || 'analyze';
|
|
1759
1700
|
const headers = {
|
|
1760
1701
|
Authorization: `Bearer ${resolved.apiKey}`,
|
|
@@ -2033,7 +1974,7 @@ async function executeCloud(agentRef, file, options) {
|
|
|
2033
1974
|
});
|
|
2034
1975
|
}
|
|
2035
1976
|
catch (err) {
|
|
2036
|
-
spinner?.
|
|
1977
|
+
spinner?.stop();
|
|
2037
1978
|
throw err;
|
|
2038
1979
|
}
|
|
2039
1980
|
if (!response.ok) {
|
|
@@ -2048,20 +1989,8 @@ async function executeCloud(agentRef, file, options) {
|
|
|
2048
1989
|
const errorCode = typeof payload === 'object' && payload
|
|
2049
1990
|
? payload.error?.code
|
|
2050
1991
|
: undefined;
|
|
2051
|
-
if (response.status === 402 || errorCode === 'INSUFFICIENT_CREDITS') {
|
|
2052
|
-
spinner?.fail('Insufficient credits');
|
|
2053
|
-
let errorMessage = 'Insufficient credits to run this agent.\n\n';
|
|
2054
|
-
if (pricingInfo?.price_cents) {
|
|
2055
|
-
errorMessage += `This agent costs $${(pricingInfo.price_cents / 100).toFixed(2)} per call.\n\n`;
|
|
2056
|
-
}
|
|
2057
|
-
errorMessage +=
|
|
2058
|
-
'Add credits:\n' +
|
|
2059
|
-
' orch billing add 5\n' +
|
|
2060
|
-
' orch billing balance # check current balance\n';
|
|
2061
|
-
throw new errors_1.CliError(errorMessage, errors_1.ExitCodes.PERMISSION_DENIED);
|
|
2062
|
-
}
|
|
2063
1992
|
if (errorCode === 'CLI_VERSION_TOO_OLD') {
|
|
2064
|
-
spinner?.
|
|
1993
|
+
spinner?.stop();
|
|
2065
1994
|
const minVersion = typeof payload === 'object' && payload
|
|
2066
1995
|
? payload.error?.min_version
|
|
2067
1996
|
: undefined;
|
|
@@ -2070,7 +1999,7 @@ async function executeCloud(agentRef, file, options) {
|
|
|
2070
1999
|
'Update with: npm update -g @orchagent/cli');
|
|
2071
2000
|
}
|
|
2072
2001
|
if (errorCode === 'LLM_KEY_REQUIRED') {
|
|
2073
|
-
spinner?.
|
|
2002
|
+
spinner?.stop();
|
|
2074
2003
|
throw new errors_1.CliError('This public agent requires you to provide an LLM key.\n' +
|
|
2075
2004
|
'Use --key <key> --provider <provider> or set OPENAI_API_KEY/ANTHROPIC_API_KEY env var.');
|
|
2076
2005
|
}
|
|
@@ -2078,7 +2007,7 @@ async function executeCloud(agentRef, file, options) {
|
|
|
2078
2007
|
const rateLimitMsg = typeof payload === 'object' && payload
|
|
2079
2008
|
? payload.error?.message || 'Rate limit exceeded'
|
|
2080
2009
|
: 'Rate limit exceeded';
|
|
2081
|
-
spinner?.
|
|
2010
|
+
spinner?.stop();
|
|
2082
2011
|
throw new errors_1.CliError(rateLimitMsg + '\n\n' +
|
|
2083
2012
|
'This is the LLM provider\'s rate limit on your API key, not an OrchAgent limit.\n' +
|
|
2084
2013
|
'To switch providers: orch run <agent> --provider <gemini|anthropic|openai>', errors_1.ExitCodes.RATE_LIMITED);
|
|
@@ -2094,7 +2023,7 @@ async function executeCloud(agentRef, file, options) {
|
|
|
2094
2023
|
: undefined;
|
|
2095
2024
|
const refSuffix = requestId ? `\n\nref: ${requestId}` : '';
|
|
2096
2025
|
if (errorCode === 'SANDBOX_ERROR') {
|
|
2097
|
-
spinner?.
|
|
2026
|
+
spinner?.stop();
|
|
2098
2027
|
const hint = typeof payload === 'object' && payload
|
|
2099
2028
|
? payload.error?.hint
|
|
2100
2029
|
: undefined;
|
|
@@ -2105,7 +2034,7 @@ async function executeCloud(agentRef, file, options) {
|
|
|
2105
2034
|
refSuffix);
|
|
2106
2035
|
}
|
|
2107
2036
|
if (errorCode === 'SANDBOX_TIMEOUT') {
|
|
2108
|
-
spinner?.
|
|
2037
|
+
spinner?.stop();
|
|
2109
2038
|
throw new errors_1.CliError(`${message}\n\n` +
|
|
2110
2039
|
`The agent did not complete in time. Try:\n` +
|
|
2111
2040
|
` - Simplifying the input\n` +
|
|
@@ -2114,7 +2043,7 @@ async function executeCloud(agentRef, file, options) {
|
|
|
2114
2043
|
refSuffix);
|
|
2115
2044
|
}
|
|
2116
2045
|
if (errorCode === 'MISSING_SECRETS') {
|
|
2117
|
-
spinner?.
|
|
2046
|
+
spinner?.stop();
|
|
2118
2047
|
// Extract secret names from gateway message:
|
|
2119
2048
|
// "Agent requires secret(s) not found in workspace: NAME1, NAME2. Add them in Settings > Secrets."
|
|
2120
2049
|
const secretNames = [];
|
|
@@ -2145,13 +2074,13 @@ async function executeCloud(agentRef, file, options) {
|
|
|
2145
2074
|
throw new errors_1.CliError(hint + refSuffix);
|
|
2146
2075
|
}
|
|
2147
2076
|
if (response.status >= 500) {
|
|
2148
|
-
spinner?.
|
|
2077
|
+
spinner?.stop();
|
|
2149
2078
|
throw new errors_1.CliError(`${message}\n\n` +
|
|
2150
2079
|
`This is a platform error — try again in a moment.\n` +
|
|
2151
2080
|
`If it persists, contact support.` +
|
|
2152
2081
|
refSuffix);
|
|
2153
2082
|
}
|
|
2154
|
-
spinner?.
|
|
2083
|
+
spinner?.stop();
|
|
2155
2084
|
throw new errors_1.CliError(message + refSuffix);
|
|
2156
2085
|
}
|
|
2157
2086
|
// Handle SSE streaming response
|
|
@@ -2235,9 +2164,6 @@ async function executeCloud(agentRef, file, options) {
|
|
|
2235
2164
|
return;
|
|
2236
2165
|
}
|
|
2237
2166
|
spinner?.succeed(`Ran ${org}/${parsed.agent}@${parsed.version}`);
|
|
2238
|
-
if (!options.json && (0, pricing_1.isPaidAgent)(agentMeta) && pricingInfo?.price_cents && pricingInfo.price_cents > 0) {
|
|
2239
|
-
process.stderr.write(`\nCost: $${(pricingInfo.price_cents / 100).toFixed(2)} USD\n`);
|
|
2240
|
-
}
|
|
2241
2167
|
const inputType = hasInjection
|
|
2242
2168
|
? 'file_injection'
|
|
2243
2169
|
: unkeyedFileArgs.length > 0
|
|
@@ -2606,13 +2532,6 @@ Examples:
|
|
|
2606
2532
|
orch run joe/summarizer --local --data '{"text": "Hello world"}'
|
|
2607
2533
|
orch run orchagent/leak-finder --local --download-only
|
|
2608
2534
|
|
|
2609
|
-
Paid Agents:
|
|
2610
|
-
Paid agents charge per call and deduct from your prepaid credits.
|
|
2611
|
-
Check your balance: orch billing balance
|
|
2612
|
-
Add credits: orch billing add 5
|
|
2613
|
-
|
|
2614
|
-
Same-author calls are FREE - you won't be charged for calling your own agents.
|
|
2615
|
-
|
|
2616
2535
|
File handling (cloud):
|
|
2617
2536
|
For prompt agents, file content is read and sent as JSON mapped to the agent's
|
|
2618
2537
|
input schema. Use --file-field to specify the field name (auto-detected by default).
|
|
@@ -222,7 +222,7 @@ Examples:
|
|
|
222
222
|
});
|
|
223
223
|
}
|
|
224
224
|
catch (err) {
|
|
225
|
-
spinner.
|
|
225
|
+
spinner.stop();
|
|
226
226
|
throw err;
|
|
227
227
|
}
|
|
228
228
|
if (!response.ok) {
|
|
@@ -239,7 +239,7 @@ Examples:
|
|
|
239
239
|
payload.message ||
|
|
240
240
|
response.statusText
|
|
241
241
|
: response.statusText;
|
|
242
|
-
spinner.
|
|
242
|
+
spinner.stop();
|
|
243
243
|
throw new errors_1.CliError(message);
|
|
244
244
|
}
|
|
245
245
|
spinner.succeed(`Scan completed for ${agentId}`);
|
package/dist/commands/service.js
CHANGED
|
@@ -135,7 +135,7 @@ function registerServiceCommand(program) {
|
|
|
135
135
|
match = agentsList.find(a => a.name === agentName && a.version === agentVersion);
|
|
136
136
|
}
|
|
137
137
|
if (!match) {
|
|
138
|
-
spinner.
|
|
138
|
+
spinner.stop();
|
|
139
139
|
throw new errors_1.CliError(`Agent '${agentName}' (version ${agentVersion}) not found in workspace`);
|
|
140
140
|
}
|
|
141
141
|
agentId = match.id;
|
|
@@ -144,7 +144,7 @@ function registerServiceCommand(program) {
|
|
|
144
144
|
catch (e) {
|
|
145
145
|
if (e instanceof errors_1.CliError)
|
|
146
146
|
throw e;
|
|
147
|
-
spinner.
|
|
147
|
+
spinner.stop();
|
|
148
148
|
throw e;
|
|
149
149
|
}
|
|
150
150
|
// C-2: Show deprecation notice when --secret is used
|
|
@@ -311,7 +311,7 @@ function registerServiceCommand(program) {
|
|
|
311
311
|
process.stdout.write(`${chalk_1.default.green('\u2713')} Service '${result.service.service_name}' restarted (restarts: ${result.service.restart_count})\n`);
|
|
312
312
|
}
|
|
313
313
|
catch (e) {
|
|
314
|
-
spinner.
|
|
314
|
+
spinner.stop();
|
|
315
315
|
throw e;
|
|
316
316
|
}
|
|
317
317
|
});
|
|
@@ -414,7 +414,7 @@ function registerServiceCommand(program) {
|
|
|
414
414
|
process.stdout.write(`${chalk_1.default.green('\u2713')} Service '${result.service.service_name}' deleted\n`);
|
|
415
415
|
}
|
|
416
416
|
catch (e) {
|
|
417
|
-
spinner.
|
|
417
|
+
spinner.stop();
|
|
418
418
|
throw e;
|
|
419
419
|
}
|
|
420
420
|
});
|
|
@@ -468,7 +468,7 @@ function registerServiceCommand(program) {
|
|
|
468
468
|
}
|
|
469
469
|
}
|
|
470
470
|
catch (e) {
|
|
471
|
-
spinner.
|
|
471
|
+
spinner.stop();
|
|
472
472
|
throw e;
|
|
473
473
|
}
|
|
474
474
|
});
|
|
@@ -517,7 +517,7 @@ function registerServiceCommand(program) {
|
|
|
517
517
|
}
|
|
518
518
|
}
|
|
519
519
|
catch (e) {
|
|
520
|
-
spinner.
|
|
520
|
+
spinner.stop();
|
|
521
521
|
throw e;
|
|
522
522
|
}
|
|
523
523
|
});
|
|
@@ -594,7 +594,7 @@ function registerServiceCommand(program) {
|
|
|
594
594
|
}
|
|
595
595
|
}
|
|
596
596
|
catch (e) {
|
|
597
|
-
spinner.
|
|
597
|
+
spinner.stop();
|
|
598
598
|
throw e;
|
|
599
599
|
}
|
|
600
600
|
});
|
|
@@ -638,7 +638,7 @@ function registerServiceCommand(program) {
|
|
|
638
638
|
}
|
|
639
639
|
}
|
|
640
640
|
catch (e) {
|
|
641
|
-
spinner.
|
|
641
|
+
spinner.stop();
|
|
642
642
|
throw e;
|
|
643
643
|
}
|
|
644
644
|
});
|
package/dist/commands/skill.js
CHANGED
|
@@ -45,7 +45,6 @@ const api_1 = require("../lib/api");
|
|
|
45
45
|
const errors_1 = require("../lib/errors");
|
|
46
46
|
const analytics_1 = require("../lib/analytics");
|
|
47
47
|
const installed_1 = require("../lib/installed");
|
|
48
|
-
const pricing_1 = require("../lib/pricing");
|
|
49
48
|
const package_json_1 = __importDefault(require("../../package.json"));
|
|
50
49
|
const DEFAULT_VERSION = 'latest';
|
|
51
50
|
function stripFrontmatter(content) {
|
|
@@ -110,55 +109,6 @@ async function downloadSkillWithFallback(config, org, skill, version, workspaceI
|
|
|
110
109
|
throw new errors_1.CliError(`${org}/${skill} is not a skill (type: ${skillType || 'prompt'})`);
|
|
111
110
|
}
|
|
112
111
|
}
|
|
113
|
-
// Check if paid skill BEFORE attempting download
|
|
114
|
-
if (skillMeta && (0, pricing_1.isPaidAgent)(skillMeta)) {
|
|
115
|
-
// Paid skill - check ownership
|
|
116
|
-
if (config.apiKey) {
|
|
117
|
-
const callerOrg = await (0, api_1.getOrg)(config, workspaceId);
|
|
118
|
-
const isOwner = (skillMeta.org_id && callerOrg.id === skillMeta.org_id) ||
|
|
119
|
-
(skillMeta.org_slug && callerOrg.slug === skillMeta.org_slug);
|
|
120
|
-
if (isOwner) {
|
|
121
|
-
// Owner - fetch from authenticated endpoint with full content
|
|
122
|
-
const myAgents = await (0, api_1.listMyAgents)(config, workspaceId);
|
|
123
|
-
const matching = myAgents.filter(a => a.name === skill && a.type === 'skill');
|
|
124
|
-
if (matching.length > 0) {
|
|
125
|
-
let targetAgent;
|
|
126
|
-
if (version === 'latest') {
|
|
127
|
-
targetAgent = matching.sort((a, b) => new Date(b.created_at).getTime() - new Date(a.created_at).getTime())[0];
|
|
128
|
-
}
|
|
129
|
-
else {
|
|
130
|
-
const found = matching.find(a => a.version === version);
|
|
131
|
-
if (!found) {
|
|
132
|
-
throw new api_1.ApiError(`Skill '${org}/${skill}@${version}' not found`, 404);
|
|
133
|
-
}
|
|
134
|
-
targetAgent = found;
|
|
135
|
-
}
|
|
136
|
-
// Fetch full skill data with prompt from authenticated endpoint
|
|
137
|
-
const skillData = await (0, api_1.request)(config, 'GET', `/agents/${targetAgent.id}`);
|
|
138
|
-
// Convert Agent to SkillDownload format
|
|
139
|
-
return {
|
|
140
|
-
type: skillData.type,
|
|
141
|
-
name: skillData.name,
|
|
142
|
-
version: skillData.version,
|
|
143
|
-
description: skillData.description,
|
|
144
|
-
prompt: skillData.prompt,
|
|
145
|
-
};
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
else {
|
|
149
|
-
// Non-owner - block with helpful message
|
|
150
|
-
const price = (0, pricing_1.formatPrice)(skillMeta);
|
|
151
|
-
throw new errors_1.CliError(`This skill is paid (${price}) and can only be used on the server.\n\n` +
|
|
152
|
-
`Paid skills are loaded automatically during server execution.`);
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
else {
|
|
156
|
-
// Not authenticated - block
|
|
157
|
-
const price = (0, pricing_1.formatPrice)(skillMeta);
|
|
158
|
-
throw new errors_1.CliError(`This skill is paid (${price}) and can only be used on the server.\n\n` +
|
|
159
|
-
`Paid skills are loaded automatically during server execution.`);
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
112
|
// Check if download is disabled (server-only skill)
|
|
163
113
|
if (skillMeta && skillMeta.allow_local_download === false) {
|
|
164
114
|
if (config.apiKey) {
|
|
@@ -204,11 +154,6 @@ async function downloadSkillWithFallback(config, org, skill, version, workspaceI
|
|
|
204
154
|
// If download fails but metadata exists, it might be a 403 for other reasons
|
|
205
155
|
if (err instanceof api_1.ApiError && err.status === 403) {
|
|
206
156
|
const payload = err.payload;
|
|
207
|
-
if (payload?.error?.code === 'PAID_AGENT_SERVER_ONLY') {
|
|
208
|
-
const price = payload.error.price_per_call_cents || 0;
|
|
209
|
-
throw new errors_1.CliError(`This skill costs $${(price / 100).toFixed(2)}/call and runs on server only.\n\n` +
|
|
210
|
-
`Use: orchagent run ${org}/${skill}@${version} --data '{...}'`);
|
|
211
|
-
}
|
|
212
157
|
if (payload?.error?.code === 'DOWNLOAD_DISABLED') {
|
|
213
158
|
throw new errors_1.CliError(`This skill is server-only and cannot be downloaded.\n\n` +
|
|
214
159
|
`Skills are loaded automatically during server execution via 'orchagent run'.`);
|
package/dist/lib/api.js
CHANGED
|
@@ -65,8 +65,6 @@ exports.getEnvironment = getEnvironment;
|
|
|
65
65
|
exports.createEnvironment = createEnvironment;
|
|
66
66
|
exports.deleteEnvironment = deleteEnvironment;
|
|
67
67
|
exports.setWorkspaceDefaultEnvironment = setWorkspaceDefaultEnvironment;
|
|
68
|
-
exports.getCreditsBalance = getCreditsBalance;
|
|
69
|
-
exports.createCreditCheckout = createCreditCheckout;
|
|
70
68
|
exports.listAgentKeys = listAgentKeys;
|
|
71
69
|
exports.createAgentKey = createAgentKey;
|
|
72
70
|
exports.deleteAgentKey = deleteAgentKey;
|
|
@@ -531,16 +529,6 @@ async function setWorkspaceDefaultEnvironment(config, workspaceId, environmentId
|
|
|
531
529
|
headers: { 'Content-Type': 'application/json' },
|
|
532
530
|
});
|
|
533
531
|
}
|
|
534
|
-
// Billing API functions
|
|
535
|
-
async function getCreditsBalance(config) {
|
|
536
|
-
return request(config, 'GET', '/billing/credits');
|
|
537
|
-
}
|
|
538
|
-
async function createCreditCheckout(config, amountCents) {
|
|
539
|
-
return request(config, 'POST', '/billing/add-credits', {
|
|
540
|
-
body: JSON.stringify({ amount_cents: amountCents }),
|
|
541
|
-
headers: { 'Content-Type': 'application/json' },
|
|
542
|
-
});
|
|
543
|
-
}
|
|
544
532
|
async function listAgentKeys(config, agentId) {
|
|
545
533
|
return request(config, 'GET', `/agents/${agentId}/keys`);
|
|
546
534
|
}
|
package/dist/lib/errors.js
CHANGED
|
@@ -45,6 +45,8 @@ class CliError extends Error {
|
|
|
45
45
|
exitCode;
|
|
46
46
|
cause;
|
|
47
47
|
responseBody;
|
|
48
|
+
/** When true, exitWithError skips printing — the message was already shown (e.g. via spinner.fail). */
|
|
49
|
+
displayed;
|
|
48
50
|
constructor(message, exitCode = 1) {
|
|
49
51
|
super(message);
|
|
50
52
|
this.exitCode = exitCode;
|
|
@@ -78,7 +80,12 @@ async function exitWithError(err) {
|
|
|
78
80
|
}
|
|
79
81
|
// Flush PostHog before exiting
|
|
80
82
|
await (0, analytics_1.shutdownPostHog)();
|
|
81
|
-
|
|
83
|
+
// Skip printing if the error was already shown (e.g. by spinner.fail)
|
|
84
|
+
const alreadyDisplayed = (err instanceof CliError && err.displayed) ||
|
|
85
|
+
(err instanceof Error && err._displayed);
|
|
86
|
+
if (!alreadyDisplayed) {
|
|
87
|
+
process.stderr.write(`${message}\n`);
|
|
88
|
+
}
|
|
82
89
|
if (err instanceof CliError) {
|
|
83
90
|
process.exit(err.exitCode);
|
|
84
91
|
}
|
package/dist/lib/spinner.js
CHANGED
|
@@ -96,6 +96,11 @@ async function withSpinner(text, fn, options) {
|
|
|
96
96
|
? options.failText(err)
|
|
97
97
|
: options?.failText || (err instanceof Error ? err.message : 'Failed');
|
|
98
98
|
spinner.fail(failMsg);
|
|
99
|
+
// Mark as already displayed so exitWithError doesn't print again
|
|
100
|
+
if (err instanceof Error) {
|
|
101
|
+
;
|
|
102
|
+
err._displayed = true;
|
|
103
|
+
}
|
|
99
104
|
throw err;
|
|
100
105
|
}
|
|
101
106
|
}
|
package/package.json
CHANGED
package/dist/lib/pricing.js
DELETED
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.isPaidAgent = isPaidAgent;
|
|
4
|
-
exports.formatPrice = formatPrice;
|
|
5
|
-
function isPaidAgent(agent) {
|
|
6
|
-
// Fail-closed: per_call mode with missing or >0 price is paid
|
|
7
|
-
if (agent.pricing_mode === 'per_call') {
|
|
8
|
-
const price = agent.price_per_call_cents;
|
|
9
|
-
return price === null || price === undefined || price > 0;
|
|
10
|
-
}
|
|
11
|
-
return false;
|
|
12
|
-
}
|
|
13
|
-
function formatPrice(agent) {
|
|
14
|
-
if (!isPaidAgent(agent)) {
|
|
15
|
-
return 'FREE';
|
|
16
|
-
}
|
|
17
|
-
const price = agent.price_per_call_cents;
|
|
18
|
-
if (!price) {
|
|
19
|
-
return 'PAID (server-only)'; // Fail-closed message
|
|
20
|
-
}
|
|
21
|
-
return `$${(price / 100).toFixed(2)}/call`;
|
|
22
|
-
}
|