@orchagent/cli 0.3.28 → 0.3.29

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.
@@ -1,20 +1,97 @@
1
1
  "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ var __importDefault = (this && this.__importDefault) || function (mod) {
36
+ return (mod && mod.__esModule) ? mod : { "default": mod };
37
+ };
2
38
  Object.defineProperty(exports, "__esModule", { value: true });
3
39
  exports.registerAgentsCommand = registerAgentsCommand;
40
+ const chalk_1 = __importDefault(require("chalk"));
41
+ const config_1 = require("../lib/config");
42
+ const api_1 = require("../lib/api");
43
+ const output_1 = require("../lib/output");
44
+ const pricing_1 = require("../lib/pricing");
4
45
  function registerAgentsCommand(program) {
5
46
  program
6
47
  .command('agents')
7
- .description('(Deprecated) Use "orchagent search" instead')
48
+ .description('List your published agents')
8
49
  .option('--filter <text>', 'Filter by name')
9
50
  .option('--json', 'Output raw JSON')
10
- .action(async () => {
11
- process.stdout.write('The "agents" command has been replaced by "search".\n\n' +
12
- 'Usage:\n' +
13
- ' orchagent search <query> Search agents by keyword\n' +
14
- ' orchagent search --popular Top agents by stars\n' +
15
- ' orchagent search --recent Most recently published\n' +
16
- ' orchagent search --type agents Filter to agents only\n\n' +
17
- 'Browse all agents at: https://orchagent.io/explore\n');
18
- process.exit(0);
51
+ .action(async (options) => {
52
+ const config = await (0, config_1.getResolvedConfig)();
53
+ const agents = await (0, api_1.listMyAgents)(config);
54
+ // Apply filter if provided
55
+ const filteredAgents = options.filter
56
+ ? agents.filter(a => a.name.toLowerCase().includes(options.filter.toLowerCase()))
57
+ : agents;
58
+ if (options.json) {
59
+ (0, output_1.printJson)(filteredAgents);
60
+ return;
61
+ }
62
+ if (filteredAgents.length === 0) {
63
+ process.stdout.write(options.filter
64
+ ? `No agents found matching "${options.filter}"\n`
65
+ : 'No agents published yet.\n\nPublish an agent: orch publish\n');
66
+ return;
67
+ }
68
+ // Print table with pricing
69
+ const Table = (await Promise.resolve().then(() => __importStar(require('cli-table3')))).default;
70
+ const table = new Table({
71
+ head: [
72
+ chalk_1.default.bold('Agent'),
73
+ chalk_1.default.bold('Version'),
74
+ chalk_1.default.bold('Type'),
75
+ chalk_1.default.bold('Stars'),
76
+ chalk_1.default.bold('Price'),
77
+ chalk_1.default.bold('Description'),
78
+ ],
79
+ });
80
+ filteredAgents.forEach((agent) => {
81
+ const name = agent.name;
82
+ const version = agent.version;
83
+ const type = agent.type || 'code';
84
+ const stars = agent.stars_count ?? 0;
85
+ const price = (0, pricing_1.formatPrice)(agent);
86
+ const coloredPrice = (0, pricing_1.isPaidAgent)(agent) ? chalk_1.default.yellow(price) : chalk_1.default.green(price);
87
+ const desc = agent.description
88
+ ? agent.description.length > 30
89
+ ? agent.description.slice(0, 27) + '...'
90
+ : agent.description
91
+ : '-';
92
+ table.push([name, version, type, stars.toString(), coloredPrice, desc]);
93
+ });
94
+ process.stdout.write(`${table.toString()}\n`);
95
+ process.stdout.write(`\nTotal: ${filteredAgents.length} agent${filteredAgents.length === 1 ? '' : 's'}\n`);
19
96
  });
20
97
  }
@@ -0,0 +1,122 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.registerBillingCommand = registerBillingCommand;
7
+ const cli_table3_1 = __importDefault(require("cli-table3"));
8
+ const chalk_1 = __importDefault(require("chalk"));
9
+ const open_1 = __importDefault(require("open"));
10
+ const config_1 = require("../lib/config");
11
+ const api_1 = require("../lib/api");
12
+ const errors_1 = require("../lib/errors");
13
+ const output_1 = require("../lib/output");
14
+ function registerBillingCommand(program) {
15
+ const billing = program
16
+ .command('billing')
17
+ .description('Manage prepaid credits for calling paid agents');
18
+ // orch billing balance
19
+ billing
20
+ .command('balance')
21
+ .description('Show your credit balance and recent transactions')
22
+ .option('--json', 'Output as JSON')
23
+ .action(async (options) => {
24
+ const resolved = await (0, config_1.getResolvedConfig)();
25
+ const data = await (0, api_1.getCreditsBalance)(resolved);
26
+ if (options.json) {
27
+ (0, output_1.printJson)(data);
28
+ return;
29
+ }
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
+ });
122
+ }
@@ -13,6 +13,7 @@ const output_1 = require("../lib/output");
13
13
  const spinner_1 = require("../lib/spinner");
14
14
  const llm_1 = require("../lib/llm");
15
15
  const analytics_1 = require("../lib/analytics");
16
+ const pricing_1 = require("../lib/pricing");
16
17
  const DEFAULT_VERSION = 'latest';
17
18
  // Keys that might indicate local file path references in JSON payloads
18
19
  const LOCAL_PATH_KEYS = ['path', 'directory', 'file', 'filepath', 'dir', 'folder', 'local'];
@@ -132,7 +133,7 @@ async function resolveJsonBody(input) {
132
133
  function registerCallCommand(program) {
133
134
  program
134
135
  .command('call <agent> [file]')
135
- .description('Call an agent endpoint')
136
+ .description('Call an agent on the server (may incur charges for paid agents)')
136
137
  .option('--endpoint <endpoint>', 'Override agent endpoint')
137
138
  .option('--tenant <tenant>', 'Tenant identifier for multi-tenant callers')
138
139
  .option('--data <json>', 'JSON payload (string or @file, @- for stdin)')
@@ -160,6 +161,13 @@ Important: Remote agents cannot access your local filesystem. If your --data pay
160
161
  contains keys like 'path', 'directory', 'file', etc., those values will be interpreted
161
162
  by the server, not your local machine. To send local files, use the positional file
162
163
  argument or --file option instead.
164
+
165
+ Paid Agents:
166
+ Paid agents charge per call and deduct from your prepaid credits.
167
+ Check your balance: orch billing balance
168
+ Add credits: orch billing add 5
169
+
170
+ Same-author calls are FREE - you won't be charged for calling your own agents.
163
171
  `)
164
172
  .action(async (agentRef, file, options) => {
165
173
  // Merge --input alias into --data
@@ -176,6 +184,64 @@ argument or --file option instead.
176
184
  throw new errors_1.CliError('Missing org. Use org/agent or set default org.');
177
185
  }
178
186
  const agentMeta = await (0, api_1.getAgentWithFallback)(resolved, org, parsed.agent, parsed.version);
187
+ // Part 1: Pre-call balance check for paid agents
188
+ let pricingInfo;
189
+ if ((0, pricing_1.isPaidAgent)(agentMeta)) {
190
+ // Detect ownership: compare agent's org with caller's org
191
+ let isOwner = false;
192
+ try {
193
+ const callerOrg = await (0, api_1.getOrg)(resolved);
194
+ // Use org_id when available (preferred), org_slug as fallback
195
+ const agentOrgId = agentMeta.org_id;
196
+ const agentOrgSlug = agentMeta.org_slug;
197
+ if (agentOrgId && callerOrg.id === agentOrgId) {
198
+ isOwner = true;
199
+ }
200
+ else if (agentOrgSlug && callerOrg.slug === agentOrgSlug) {
201
+ isOwner = true;
202
+ }
203
+ }
204
+ catch {
205
+ // If we can't determine ownership, treat as non-owner (fail-safe)
206
+ isOwner = false;
207
+ }
208
+ if (isOwner) {
209
+ // Owner: show free message, no balance check needed
210
+ process.stderr.write(`Cost: FREE (author)\n\n`);
211
+ }
212
+ else {
213
+ // Non-owner: check balance
214
+ const price = agentMeta.price_per_call_cents;
215
+ pricingInfo = { price_cents: price ?? null };
216
+ if (!price || price <= 0) {
217
+ // Price missing or invalid - warn but proceed (server will enforce)
218
+ process.stderr.write(`Warning: Pricing data unavailable. The server will verify payment.\n\n`);
219
+ }
220
+ else {
221
+ // Valid price - check balance
222
+ try {
223
+ const balanceData = await (0, api_1.getCreditsBalance)(resolved);
224
+ const balance = balanceData.balance_cents;
225
+ if (balance < price) {
226
+ // Insufficient balance
227
+ process.stderr.write(`Insufficient credits:\n` +
228
+ ` Balance: $${(balance / 100).toFixed(2)}\n` +
229
+ ` Required: $${(price / 100).toFixed(2)}\n\n` +
230
+ `Add credits:\n` +
231
+ ` orch billing add 5\n` +
232
+ ` orch billing balance # check current balance\n`);
233
+ process.exit(errors_1.ExitCodes.PERMISSION_DENIED);
234
+ }
235
+ // Sufficient balance - show cost preview
236
+ process.stderr.write(`Cost: $${(price / 100).toFixed(2)}/call\n\n`);
237
+ }
238
+ catch (err) {
239
+ // Balance check failed - warn but proceed (server will enforce)
240
+ process.stderr.write(`Warning: Could not verify balance. The server will check payment.\n\n`);
241
+ }
242
+ }
243
+ }
244
+ }
179
245
  const endpoint = options.endpoint?.trim() || agentMeta.default_endpoint || 'analyze';
180
246
  const headers = {
181
247
  Authorization: `Bearer ${resolved.apiKey}`,
@@ -341,6 +407,20 @@ argument or --file option instead.
341
407
  const errorCode = typeof payload === 'object' && payload
342
408
  ? payload.error?.code
343
409
  : undefined;
410
+ // Part 2: Handle 402 Payment Required
411
+ if (response.status === 402 || errorCode === 'INSUFFICIENT_CREDITS') {
412
+ spinner.fail('Insufficient credits');
413
+ let errorMessage = 'Insufficient credits to call this agent.\n\n';
414
+ // Use pricing info from pre-call check if available
415
+ if (pricingInfo?.price_cents) {
416
+ errorMessage += `This agent costs $${(pricingInfo.price_cents / 100).toFixed(2)} per call.\n\n`;
417
+ }
418
+ errorMessage +=
419
+ 'Add credits:\n' +
420
+ ' orch billing add 5\n' +
421
+ ' orch billing balance # check current balance\n';
422
+ throw new errors_1.CliError(errorMessage, errors_1.ExitCodes.PERMISSION_DENIED);
423
+ }
344
424
  if (errorCode === 'LLM_KEY_REQUIRED') {
345
425
  spinner.fail('LLM key required');
346
426
  throw new errors_1.CliError('This public agent requires you to provide an LLM key.\n' +
@@ -356,6 +436,10 @@ argument or --file option instead.
356
436
  throw new errors_1.CliError(message);
357
437
  }
358
438
  spinner.succeed(`Called ${org}/${parsed.agent}@${parsed.version}`);
439
+ // After successful call, if it was a paid agent, show cost
440
+ if ((0, pricing_1.isPaidAgent)(agentMeta) && pricingInfo?.price_cents && pricingInfo.price_cents > 0) {
441
+ process.stderr.write(`\nCost: $${(pricingInfo.price_cents / 100).toFixed(2)} USD\n`);
442
+ }
359
443
  // Track successful call
360
444
  const inputType = filePaths.length > 0
361
445
  ? 'file'
@@ -28,6 +28,10 @@ const update_1 = require("./update");
28
28
  const env_1 = require("./env");
29
29
  const list_1 = require("./list");
30
30
  const test_1 = require("./test");
31
+ const security_1 = require("./security");
32
+ const billing_1 = require("./billing");
33
+ const seller_1 = require("./seller");
34
+ const pricing_1 = require("./pricing");
31
35
  function registerCommands(program) {
32
36
  (0, login_1.registerLoginCommand)(program);
33
37
  (0, whoami_1.registerWhoamiCommand)(program);
@@ -56,4 +60,8 @@ function registerCommands(program) {
56
60
  (0, env_1.registerEnvCommand)(program);
57
61
  (0, list_1.registerListCommand)(program);
58
62
  (0, test_1.registerTestCommand)(program);
63
+ (0, security_1.registerSecurityCommand)(program);
64
+ (0, billing_1.registerBillingCommand)(program);
65
+ (0, seller_1.registerSellerCommand)(program);
66
+ (0, pricing_1.registerPricingCommand)(program);
59
67
  }
@@ -1,9 +1,14 @@
1
1
  "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
2
5
  Object.defineProperty(exports, "__esModule", { value: true });
3
6
  exports.registerInfoCommand = registerInfoCommand;
7
+ const chalk_1 = __importDefault(require("chalk"));
4
8
  const config_1 = require("../lib/config");
5
9
  const api_1 = require("../lib/api");
6
10
  const agent_ref_1 = require("../lib/agent-ref");
11
+ const pricing_1 = require("../lib/pricing");
7
12
  function formatSchema(schema, indent = ' ') {
8
13
  const lines = [];
9
14
  const props = schema.properties || {};
@@ -45,11 +50,48 @@ async function fetchReadme(url) {
45
50
  }
46
51
  }
47
52
  async function downloadAgentWithFallback(config, org, agent, version) {
48
- // Try public endpoint first
53
+ // First fetch public metadata to get pricing fields
54
+ let publicMeta = null;
49
55
  try {
50
- return await (0, api_1.publicRequest)(config, `/public/agents/${org}/${agent}/${version}/download`);
56
+ publicMeta = await (0, api_1.getPublicAgent)(config, org, agent, version);
51
57
  }
52
58
  catch (err) {
59
+ // If public metadata not found, continue to try download
60
+ if (!(err instanceof api_1.ApiError) || err.status !== 404) {
61
+ // Some other error, rethrow
62
+ throw err;
63
+ }
64
+ }
65
+ // Try public download endpoint
66
+ try {
67
+ const downloadData = await (0, api_1.publicRequest)(config, `/public/agents/${org}/${agent}/${version}/download`);
68
+ // Merge pricing fields from publicMeta if not present
69
+ if (publicMeta && !downloadData.pricing_mode) {
70
+ downloadData.pricing_mode = publicMeta.pricing_mode;
71
+ downloadData.price_per_call_cents = publicMeta.price_per_call_cents;
72
+ }
73
+ return downloadData;
74
+ }
75
+ catch (err) {
76
+ // Handle 403 PAID_AGENT_SERVER_ONLY error
77
+ if (err instanceof api_1.ApiError && err.status === 403) {
78
+ const payload = err.payload;
79
+ if (payload?.error?.code === 'PAID_AGENT_SERVER_ONLY') {
80
+ // For non-owners, use public metadata
81
+ if (publicMeta) {
82
+ return {
83
+ type: publicMeta.type,
84
+ name: publicMeta.name,
85
+ version: publicMeta.version,
86
+ description: publicMeta.description,
87
+ supported_providers: publicMeta.supported_providers || ['any'],
88
+ source_url: publicMeta.source_url,
89
+ pricing_mode: publicMeta.pricing_mode,
90
+ price_per_call_cents: publicMeta.price_per_call_cents,
91
+ };
92
+ }
93
+ }
94
+ }
53
95
  if (!(err instanceof api_1.ApiError) || err.status !== 404)
54
96
  throw err;
55
97
  }
@@ -93,12 +135,14 @@ async function downloadAgentWithFallback(config, org, agent, version) {
93
135
  run_command: targetAgent.run_command,
94
136
  url: targetAgent.url,
95
137
  sdk_compatible: false,
138
+ pricing_mode: targetAgent.pricing_mode,
139
+ price_per_call_cents: targetAgent.price_per_call_cents,
96
140
  };
97
141
  }
98
142
  function registerInfoCommand(program) {
99
143
  program
100
144
  .command('info <agent>')
101
- .description('Show agent information and README')
145
+ .description('Show agent details including pricing, inputs, and outputs')
102
146
  .option('--json', 'Output as JSON')
103
147
  .action(async (agentArg, options) => {
104
148
  const config = await (0, config_1.getResolvedConfig)();
@@ -123,6 +167,15 @@ function registerInfoCommand(program) {
123
167
  }
124
168
  process.stdout.write(`Type: ${agentData.type}\n`);
125
169
  process.stdout.write(`Providers: ${agentData.supported_providers.join(', ')}\n`);
170
+ // Display pricing information
171
+ const priceStr = (0, pricing_1.formatPrice)(agentData);
172
+ const color = (0, pricing_1.isPaidAgent)(agentData) ? chalk_1.default.yellow : chalk_1.default.green;
173
+ process.stdout.write(`Price: ${color(priceStr)}\n`);
174
+ // If paid, show server-only message for non-owners
175
+ if ((0, pricing_1.isPaidAgent)(agentData)) {
176
+ process.stdout.write(chalk_1.default.gray('Note: Paid agents run on server only (use orch call)\n'));
177
+ process.stdout.write(chalk_1.default.gray(' Owners can still download for development/testing\n'));
178
+ }
126
179
  if (agentData.type === 'code') {
127
180
  // Don't show internal routing URLs - they confuse users
128
181
  if (agentData.url && !agentData.url.includes('.internal')) {
@@ -14,6 +14,7 @@ const analytics_1 = require("../lib/analytics");
14
14
  const adapters_1 = require("../adapters");
15
15
  const installed_1 = require("../lib/installed");
16
16
  const agents_md_utils_1 = require("../lib/agents-md-utils");
17
+ const pricing_1 = require("../lib/pricing");
17
18
  const DEFAULT_VERSION = 'latest';
18
19
  function parseAgentRef(value) {
19
20
  const [ref, versionPart] = value.split('@');
@@ -28,16 +29,70 @@ function parseAgentRef(value) {
28
29
  throw new errors_1.CliError('Invalid agent reference. Use org/agent or agent format.');
29
30
  }
30
31
  async function downloadAgentWithFallback(config, org, name, version) {
31
- // Try public endpoint first
32
+ // Fetch public metadata first to check if paid
33
+ let publicMeta;
32
34
  try {
33
- const agent = await (0, api_1.publicRequest)(config, `/public/agents/${org}/${name}/${version}`);
34
- return { ...agent, org_slug: org };
35
+ publicMeta = await (0, api_1.getPublicAgent)(config, org, name, version);
35
36
  }
36
37
  catch (err) {
37
- if (!(err instanceof api_1.ApiError) || err.status !== 404)
38
+ // If 404, might be private agent - will handle below
39
+ if (err instanceof api_1.ApiError && err.status === 404) {
40
+ publicMeta = null;
41
+ }
42
+ else {
38
43
  throw err;
44
+ }
45
+ }
46
+ // Check if paid agent
47
+ if (publicMeta && (0, pricing_1.isPaidAgent)(publicMeta)) {
48
+ // Paid agent - check if owner
49
+ if (config.apiKey) {
50
+ const callerOrg = await (0, api_1.getOrg)(config);
51
+ const isOwner = (publicMeta.org_id && callerOrg.id === publicMeta.org_id) ||
52
+ (publicMeta.org_slug && callerOrg.slug === publicMeta.org_slug);
53
+ if (isOwner) {
54
+ // Owner - fetch from authenticated endpoint with full prompt
55
+ const myAgents = await (0, api_1.listMyAgents)(config);
56
+ const matching = myAgents.filter(a => a.name === name);
57
+ if (matching.length > 0) {
58
+ let targetAgent;
59
+ if (version === 'latest') {
60
+ targetAgent = matching.sort((a, b) => new Date(b.created_at).getTime() - new Date(a.created_at).getTime())[0];
61
+ }
62
+ else {
63
+ const found = matching.find(a => a.version === version);
64
+ if (!found) {
65
+ throw new api_1.ApiError(`Agent '${org}/${name}@${version}' not found`, 404);
66
+ }
67
+ targetAgent = found;
68
+ }
69
+ // Fetch full agent data with prompt from authenticated endpoint
70
+ const agentData = await (0, api_1.request)(config, 'GET', `/agents/${targetAgent.id}`);
71
+ return { ...agentData, org_slug: org };
72
+ }
73
+ }
74
+ else {
75
+ // Non-owner - block with helpful message
76
+ const price = (0, pricing_1.formatPrice)(publicMeta);
77
+ throw new errors_1.CliError(`This agent is paid (${price}) and runs on server only.\n\n` +
78
+ `Use: orch call ${org}/${name}@${version} --input '{...}'`);
79
+ }
80
+ }
81
+ else {
82
+ // Not authenticated - block
83
+ const price = (0, pricing_1.formatPrice)(publicMeta);
84
+ throw new errors_1.CliError(`This agent is paid (${price}) and runs on server only.\n\n` +
85
+ `Use: orch call ${org}/${name}@${version} --input '{...}'`);
86
+ }
87
+ }
88
+ // Free agent - proceed normally with public data
89
+ if (publicMeta) {
90
+ // Cast PublicAgent to Agent for use as CanonicalAgent
91
+ // publicRequest already returns full agent data for free agents
92
+ const agent = await (0, api_1.publicRequest)(config, `/public/agents/${org}/${name}/${version}`);
93
+ return { ...agent, org_slug: org };
39
94
  }
40
- // Fallback to authenticated endpoint for private agents
95
+ // No public metadata found - fallback to authenticated endpoint for private agents
41
96
  if (!config.apiKey) {
42
97
  throw new api_1.ApiError(`Agent '${org}/${name}@${version}' not found`, 404);
43
98
  }
@@ -71,6 +126,10 @@ function registerInstallCommand(program) {
71
126
  .option('--scope <scope>', 'Install scope: user (home dir) or project (current dir)', 'user')
72
127
  .option('--dry-run', 'Show what would be installed without making changes')
73
128
  .option('--json', 'Output result as JSON (for automation/tooling)')
129
+ .addHelpText('after', `
130
+ Note: Paid agents cannot be installed locally - they run on server only.
131
+ Use 'orch call' to execute paid agents.
132
+ `)
74
133
  .action(async (agentArg, options) => {
75
134
  const jsonMode = options.json === true;
76
135
  const log = (msg) => { if (!jsonMode)
@@ -0,0 +1,72 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.registerPricingCommand = registerPricingCommand;
7
+ const chalk_1 = __importDefault(require("chalk"));
8
+ const config_1 = require("../lib/config");
9
+ const api_1 = require("../lib/api");
10
+ const errors_1 = require("../lib/errors");
11
+ function registerPricingCommand(program) {
12
+ program
13
+ .command('pricing <agent> <mode>')
14
+ .description('Set pricing for your agent (free or per-call in USD)')
15
+ .action(async (agentRef, mode) => {
16
+ const resolved = await (0, config_1.getResolvedConfig)();
17
+ // Parse agent reference
18
+ const [orgOrAgent, maybeName] = agentRef.split('/');
19
+ let org;
20
+ let agentName;
21
+ if (maybeName) {
22
+ // Format: org/agent
23
+ org = orgOrAgent;
24
+ agentName = maybeName;
25
+ }
26
+ else {
27
+ // Format: agent (use default org)
28
+ const userOrg = await (0, api_1.getOrg)(resolved);
29
+ org = userOrg.slug;
30
+ agentName = orgOrAgent;
31
+ }
32
+ // Look up agent ID
33
+ const myAgents = await (0, api_1.listMyAgents)(resolved);
34
+ const agent = myAgents.find(a => a.org_slug === org && a.name === agentName);
35
+ if (!agent) {
36
+ throw new errors_1.CliError(`Agent '${org}/${agentName}' not found in your agents.\n\n` +
37
+ `List your agents: orch agents`, errors_1.ExitCodes.NOT_FOUND);
38
+ }
39
+ // Parse pricing mode
40
+ let pricingMode;
41
+ let pricePerCallCents;
42
+ if (mode === 'free' || mode === '0') {
43
+ pricingMode = 'free';
44
+ pricePerCallCents = undefined;
45
+ }
46
+ else {
47
+ // Parse as dollar amount
48
+ const priceFloat = parseFloat(mode);
49
+ if (isNaN(priceFloat)) {
50
+ throw new errors_1.CliError(`Invalid pricing mode: ${mode}\n\n` +
51
+ `Use "free" or a dollar amount (e.g., "0.50" for $0.50 USD)`, errors_1.ExitCodes.INVALID_INPUT);
52
+ }
53
+ if (priceFloat < 0.01) {
54
+ throw new errors_1.CliError('Price must be at least $0.01 USD', errors_1.ExitCodes.INVALID_INPUT);
55
+ }
56
+ pricingMode = 'per_call';
57
+ pricePerCallCents = Math.round(priceFloat * 100);
58
+ }
59
+ // Set pricing
60
+ await (0, api_1.setAgentPricing)(resolved, agent.id, pricingMode, pricePerCallCents);
61
+ // Show confirmation
62
+ process.stdout.write(chalk_1.default.green('✓ Pricing updated\n'));
63
+ process.stdout.write(`Agent: ${org}/${agentName}\n`);
64
+ if (pricingMode === 'free') {
65
+ process.stdout.write(`Mode: FREE\n`);
66
+ }
67
+ else {
68
+ process.stdout.write(`Mode: Pay per call\n`);
69
+ process.stdout.write(`Price: $${(pricePerCallCents / 100).toFixed(2)} USD per call\n`);
70
+ }
71
+ });
72
+ }