@orchagent/cli 0.3.27 → 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.
@@ -0,0 +1,142 @@
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.registerSellerCommand = registerSellerCommand;
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 api_2 = require("../lib/api");
13
+ const errors_1 = require("../lib/errors");
14
+ const output_1 = require("../lib/output");
15
+ function registerSellerCommand(program) {
16
+ const seller = program
17
+ .command('seller')
18
+ .description('Manage seller account for monetizing your agents');
19
+ // orch seller onboard
20
+ seller
21
+ .command('onboard')
22
+ .description('Start Stripe seller onboarding process')
23
+ .option('--country <code>', 'Country code (default: GB)', 'GB')
24
+ .action(async (options) => {
25
+ const resolved = await (0, config_1.getResolvedConfig)();
26
+ const country = options.country || 'GB';
27
+ // Create onboarding session
28
+ const response = await (0, api_1.createSellerOnboarding)(resolved, country);
29
+ // Open in browser
30
+ process.stdout.write(`\nOpening Stripe onboarding...\n`);
31
+ process.stdout.write(`Country: ${country}\n\n`);
32
+ await (0, open_1.default)(response.onboarding_url);
33
+ process.stdout.write(chalk_1.default.gray(`If browser doesn't open, visit:\n${response.onboarding_url}\n`));
34
+ });
35
+ // orch seller status
36
+ seller
37
+ .command('status')
38
+ .description('Check seller account status')
39
+ .option('--json', 'Output as JSON')
40
+ .action(async (options) => {
41
+ const resolved = await (0, config_1.getResolvedConfig)();
42
+ const status = await (0, api_1.getSellerStatus)(resolved);
43
+ if (options.json) {
44
+ (0, output_1.printJson)(status);
45
+ return;
46
+ }
47
+ // Display status
48
+ if (!status.onboarded) {
49
+ process.stdout.write(chalk_1.default.yellow('\nNot onboarded\n\n'));
50
+ process.stdout.write('Start selling: orch seller onboard\n');
51
+ return;
52
+ }
53
+ process.stdout.write(chalk_1.default.green('\n✓ Onboarded\n\n'));
54
+ if (status.charges_enabled !== undefined) {
55
+ const chargesStatus = status.charges_enabled ? chalk_1.default.green('✓ Enabled') : chalk_1.default.yellow('⚠ Disabled');
56
+ process.stdout.write(`Charges: ${chargesStatus}\n`);
57
+ }
58
+ if (status.payouts_enabled !== undefined) {
59
+ const payoutsStatus = status.payouts_enabled ? chalk_1.default.green('✓ Enabled') : chalk_1.default.yellow('⚠ Disabled');
60
+ process.stdout.write(`Payouts: ${payoutsStatus}\n`);
61
+ }
62
+ if (!status.charges_enabled || !status.payouts_enabled) {
63
+ process.stdout.write(chalk_1.default.gray('\nComplete setup: orch seller dashboard\n'));
64
+ }
65
+ });
66
+ // orch seller dashboard
67
+ seller
68
+ .command('dashboard')
69
+ .description('Open Stripe Express dashboard')
70
+ .action(async () => {
71
+ const resolved = await (0, config_1.getResolvedConfig)();
72
+ try {
73
+ const response = await (0, api_1.getSellerDashboardLink)(resolved);
74
+ // Open in browser
75
+ process.stdout.write('\nOpening Stripe dashboard...\n\n');
76
+ await (0, open_1.default)(response.dashboard_url);
77
+ process.stdout.write(chalk_1.default.gray(`If browser doesn't open, visit:\n${response.dashboard_url}\n`));
78
+ }
79
+ catch (err) {
80
+ if (err instanceof api_2.ApiError && (err.status === 404 || err.status === 403)) {
81
+ process.stdout.write(chalk_1.default.yellow('\nNo seller account found\n\n'));
82
+ process.stdout.write('Complete onboarding: orch seller onboard\n');
83
+ process.exit(errors_1.ExitCodes.NOT_FOUND);
84
+ }
85
+ throw err;
86
+ }
87
+ });
88
+ // orch seller earnings
89
+ seller
90
+ .command('earnings')
91
+ .description('Show earnings from your agents')
92
+ .option('--json', 'Output as JSON')
93
+ .action(async (options) => {
94
+ const resolved = await (0, config_1.getResolvedConfig)();
95
+ const earnings = await (0, api_1.getSellerEarnings)(resolved);
96
+ if (options.json) {
97
+ (0, output_1.printJson)(earnings);
98
+ return;
99
+ }
100
+ // Show total earnings
101
+ const total = earnings.total_earnings_cents / 100;
102
+ process.stdout.write(chalk_1.default.bold(`\nTotal Earnings: ${chalk_1.default.green(`$${total.toFixed(2)} USD`)}\n\n`));
103
+ // Show earnings by agent
104
+ if (earnings.by_agent && earnings.by_agent.length > 0) {
105
+ process.stdout.write(chalk_1.default.bold('Earnings by Agent:\n'));
106
+ const table = new cli_table3_1.default({
107
+ head: [
108
+ chalk_1.default.bold('Agent'),
109
+ chalk_1.default.bold('Calls'),
110
+ chalk_1.default.bold('Earnings'),
111
+ ],
112
+ });
113
+ earnings.by_agent.forEach((item) => {
114
+ const earningsStr = `$${(item.earnings_cents / 100).toFixed(2)}`;
115
+ table.push([item.agent_name, item.calls.toString(), earningsStr]);
116
+ });
117
+ process.stdout.write(`${table.toString()}\n\n`);
118
+ }
119
+ // Show recent transactions
120
+ if (earnings.recent_transactions && earnings.recent_transactions.length > 0) {
121
+ process.stdout.write(chalk_1.default.bold('Recent Transactions:\n'));
122
+ const table = new cli_table3_1.default({
123
+ head: [
124
+ chalk_1.default.bold('Date'),
125
+ chalk_1.default.bold('Agent'),
126
+ chalk_1.default.bold('Sale'),
127
+ chalk_1.default.bold('Your Cut'),
128
+ chalk_1.default.bold('Fee'),
129
+ ],
130
+ });
131
+ earnings.recent_transactions.forEach((tx) => {
132
+ const date = new Date(tx.created_at).toLocaleDateString();
133
+ const sale = `$${(tx.sale_amount_cents / 100).toFixed(2)}`;
134
+ const cut = `$${(tx.earnings_cents / 100).toFixed(2)}`;
135
+ const fee = `$${(tx.fee_cents / 100).toFixed(2)}`;
136
+ table.push([date, tx.agent_name, sale, cut, fee]);
137
+ });
138
+ process.stdout.write(`${table.toString()}\n\n`);
139
+ }
140
+ process.stdout.write(chalk_1.default.gray('Manage payouts: orch seller dashboard\n'));
141
+ });
142
+ }
@@ -45,6 +45,7 @@ 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");
48
49
  const package_json_1 = __importDefault(require("../../package.json"));
49
50
  const DEFAULT_VERSION = 'latest';
50
51
  /**
@@ -84,20 +85,94 @@ function parseSkillRef(value) {
84
85
  throw new errors_1.CliError('Invalid skill reference. Use org/skill or skill format.');
85
86
  }
86
87
  async function downloadSkillWithFallback(config, org, skill, version) {
87
- // Try public endpoint first
88
+ // Fetch metadata first to check if paid
89
+ let skillMeta;
88
90
  try {
89
- const meta = await (0, api_1.publicRequest)(config, `/public/agents/${org}/${skill}/${version}`);
90
- // Verify it's a skill type before downloading
91
- const skillType = meta.type;
91
+ skillMeta = await (0, api_1.getPublicAgent)(config, org, skill, version);
92
+ }
93
+ catch (err) {
94
+ // If 404, might be private skill - will handle below
95
+ if (err instanceof api_1.ApiError && err.status === 404) {
96
+ skillMeta = null;
97
+ }
98
+ else {
99
+ throw err;
100
+ }
101
+ }
102
+ // Verify it's a skill type before proceeding
103
+ if (skillMeta) {
104
+ const skillType = skillMeta.type;
92
105
  if (skillType !== 'skill') {
93
106
  throw new errors_1.CliError(`${org}/${skill} is not a skill (type: ${skillType || 'prompt'})`);
94
107
  }
95
- // Download content from public endpoint
96
- return await (0, api_1.publicRequest)(config, `/public/agents/${org}/${skill}/${version}/download`);
97
108
  }
98
- catch (err) {
99
- if (!(err instanceof api_1.ApiError) || err.status !== 404)
109
+ // Check if paid skill BEFORE attempting download
110
+ if (skillMeta && (0, pricing_1.isPaidAgent)(skillMeta)) {
111
+ // Paid skill - check ownership
112
+ if (config.apiKey) {
113
+ const callerOrg = await (0, api_1.getOrg)(config);
114
+ const isOwner = (skillMeta.org_id && callerOrg.id === skillMeta.org_id) ||
115
+ (skillMeta.org_slug && callerOrg.slug === skillMeta.org_slug);
116
+ if (isOwner) {
117
+ // Owner - fetch from authenticated endpoint with full content
118
+ const myAgents = await (0, api_1.listMyAgents)(config);
119
+ const matching = myAgents.filter(a => a.name === skill && a.type === 'skill');
120
+ if (matching.length > 0) {
121
+ let targetAgent;
122
+ if (version === 'latest') {
123
+ targetAgent = matching.sort((a, b) => new Date(b.created_at).getTime() - new Date(a.created_at).getTime())[0];
124
+ }
125
+ else {
126
+ const found = matching.find(a => a.version === version);
127
+ if (!found) {
128
+ throw new api_1.ApiError(`Skill '${org}/${skill}@${version}' not found`, 404);
129
+ }
130
+ targetAgent = found;
131
+ }
132
+ // Fetch full skill data with prompt from authenticated endpoint
133
+ const skillData = await (0, api_1.request)(config, 'GET', `/agents/${targetAgent.id}`);
134
+ // Convert Agent to SkillDownload format
135
+ return {
136
+ type: skillData.type,
137
+ name: skillData.name,
138
+ version: skillData.version,
139
+ description: skillData.description,
140
+ prompt: skillData.prompt,
141
+ };
142
+ }
143
+ }
144
+ else {
145
+ // Non-owner - block with helpful message
146
+ const price = (0, pricing_1.formatPrice)(skillMeta);
147
+ throw new errors_1.CliError(`This skill is paid (${price}) and can only be used on the server.\n\n` +
148
+ `Paid skills are loaded automatically during server execution.`);
149
+ }
150
+ }
151
+ else {
152
+ // Not authenticated - block
153
+ const price = (0, pricing_1.formatPrice)(skillMeta);
154
+ throw new errors_1.CliError(`This skill is paid (${price}) and can only be used on the server.\n\n` +
155
+ `Paid skills are loaded automatically during server execution.`);
156
+ }
157
+ }
158
+ // Free skill or public metadata available - proceed with normal download
159
+ if (skillMeta) {
160
+ try {
161
+ return await (0, api_1.publicRequest)(config, `/public/agents/${org}/${skill}/${version}/download`);
162
+ }
163
+ catch (err) {
164
+ // If download fails but metadata exists, it might be a 403 for other reasons
165
+ if (err instanceof api_1.ApiError && err.status === 403) {
166
+ const payload = err.payload;
167
+ if (payload?.error?.code === 'PAID_AGENT_SERVER_ONLY') {
168
+ // Legacy error handling (shouldn't reach here with new logic)
169
+ const price = payload.error.price_per_call_cents || 0;
170
+ throw new errors_1.CliError(`This skill costs $${(price / 100).toFixed(2)}/call and runs on server only.\n\n` +
171
+ `Use: orch call ${org}/${skill}@${version} --input '{...}'`);
172
+ }
173
+ }
100
174
  throw err;
175
+ }
101
176
  }
102
177
  // Fallback to authenticated endpoint for private skills
103
178
  if (!config.apiKey) {
@@ -13,5 +13,13 @@ function registerWhoamiCommand(program) {
13
13
  process.stdout.write(`Logged in as: ${org.name}\n`);
14
14
  process.stdout.write(`Org slug: ${org.slug}\n`);
15
15
  process.stdout.write(`API URL: ${config.apiUrl}\n`);
16
+ // Show balance after org info
17
+ try {
18
+ const balance = await (0, api_1.getCreditsBalance)(config);
19
+ process.stdout.write(`Credits: $${(balance.balance_cents / 100).toFixed(2)} USD\n`);
20
+ }
21
+ catch {
22
+ // Ignore errors - don't let balance check break whoami
23
+ }
16
24
  });
17
25
  }
@@ -85,7 +85,6 @@ async function useWorkspace(slug) {
85
85
  // Save to config
86
86
  const configFile = await (0, config_1.loadConfig)();
87
87
  configFile.workspace = slug;
88
- delete configFile.workspace_confirmed;
89
88
  await (0, config_1.saveConfig)(configFile);
90
89
  await (0, analytics_1.track)('cli_workspace_use', { slug });
91
90
  process.stdout.write(chalk_1.default.green('\u2713') + ` Now using workspace: ${workspace.name} (${workspace.slug})\n`);
package/dist/lib/api.js CHANGED
@@ -64,6 +64,13 @@ exports.getEnvironment = getEnvironment;
64
64
  exports.createEnvironment = createEnvironment;
65
65
  exports.deleteEnvironment = deleteEnvironment;
66
66
  exports.setWorkspaceDefaultEnvironment = setWorkspaceDefaultEnvironment;
67
+ exports.getCreditsBalance = getCreditsBalance;
68
+ exports.createCreditCheckout = createCreditCheckout;
69
+ exports.getSellerStatus = getSellerStatus;
70
+ exports.createSellerOnboarding = createSellerOnboarding;
71
+ exports.getSellerDashboardLink = getSellerDashboardLink;
72
+ exports.getSellerEarnings = getSellerEarnings;
73
+ exports.setAgentPricing = setAgentPricing;
67
74
  const errors_1 = require("./errors");
68
75
  const DEFAULT_TIMEOUT_MS = 15000;
69
76
  const CALL_TIMEOUT_MS = 120000; // 2 minutes for agent calls (can take time)
@@ -447,3 +454,37 @@ async function setWorkspaceDefaultEnvironment(config, workspaceId, environmentId
447
454
  headers: { 'Content-Type': 'application/json' },
448
455
  });
449
456
  }
457
+ // Billing API functions
458
+ async function getCreditsBalance(config) {
459
+ return request(config, 'GET', '/billing/credits');
460
+ }
461
+ async function createCreditCheckout(config, amountCents) {
462
+ return request(config, 'POST', '/billing/add-credits', {
463
+ body: JSON.stringify({ amount_cents: amountCents }),
464
+ headers: { 'Content-Type': 'application/json' },
465
+ });
466
+ }
467
+ async function getSellerStatus(config) {
468
+ return request(config, 'GET', '/sellers/status');
469
+ }
470
+ async function createSellerOnboarding(config, country) {
471
+ return request(config, 'POST', '/sellers/onboard', {
472
+ body: JSON.stringify({ country }),
473
+ headers: { 'Content-Type': 'application/json' },
474
+ });
475
+ }
476
+ async function getSellerDashboardLink(config) {
477
+ return request(config, 'POST', '/sellers/dashboard-link');
478
+ }
479
+ async function getSellerEarnings(config) {
480
+ return request(config, 'GET', '/billing/earnings');
481
+ }
482
+ async function setAgentPricing(config, agentId, pricingMode, pricePerCallCents) {
483
+ return request(config, 'PUT', `/agents/${agentId}/pricing`, {
484
+ body: JSON.stringify({
485
+ pricing_mode: pricingMode,
486
+ price_per_call_cents: pricePerCallCents,
487
+ }),
488
+ headers: { 'Content-Type': 'application/json' },
489
+ });
490
+ }
@@ -99,6 +99,8 @@ exports.ExitCodes = {
99
99
  function mapHttpStatusToExitCode(status) {
100
100
  if (status === 401)
101
101
  return exports.ExitCodes.AUTH_ERROR;
102
+ if (status === 402)
103
+ return exports.ExitCodes.PERMISSION_DENIED; // Payment required
102
104
  if (status === 403)
103
105
  return exports.ExitCodes.PERMISSION_DENIED;
104
106
  if (status === 404)
@@ -7,6 +7,7 @@ exports.printJson = printJson;
7
7
  exports.printAgentsTable = printAgentsTable;
8
8
  const cli_table3_1 = __importDefault(require("cli-table3"));
9
9
  const chalk_1 = __importDefault(require("chalk"));
10
+ const pricing_1 = require("./pricing");
10
11
  function printJson(value) {
11
12
  process.stdout.write(`${JSON.stringify(value, null, 2)}\n`);
12
13
  }
@@ -17,6 +18,7 @@ function printAgentsTable(agents) {
17
18
  chalk_1.default.bold('Type'),
18
19
  chalk_1.default.bold('Providers'),
19
20
  chalk_1.default.bold('Stars'),
21
+ chalk_1.default.bold('Price'),
20
22
  chalk_1.default.bold('Description'),
21
23
  ],
22
24
  });
@@ -25,12 +27,14 @@ function printAgentsTable(agents) {
25
27
  const type = agent.type || 'code';
26
28
  const providers = formatProviders(agent.supported_providers);
27
29
  const stars = agent.stars_count ?? 0;
30
+ const price = (0, pricing_1.formatPrice)(agent);
31
+ const priceColored = (0, pricing_1.isPaidAgent)(agent) ? chalk_1.default.yellow(price) : chalk_1.default.green(price);
28
32
  const desc = agent.description
29
33
  ? agent.description.length > 30
30
34
  ? agent.description.slice(0, 27) + '...'
31
35
  : agent.description
32
36
  : '-';
33
- table.push([fullName, type, providers, stars.toString(), desc]);
37
+ table.push([fullName, type, providers, stars.toString(), priceColored, desc]);
34
38
  });
35
39
  process.stdout.write(`${table.toString()}\n`);
36
40
  }
@@ -0,0 +1,22 @@
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
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@orchagent/cli",
3
- "version": "0.3.27",
3
+ "version": "0.3.29",
4
4
  "description": "Command-line interface for the orchagent AI agent marketplace",
5
5
  "license": "MIT",
6
6
  "author": "orchagent <hello@orchagent.io>",