@orchagent/cli 0.3.37 → 0.3.38

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.
@@ -86,6 +86,37 @@ async function downloadAgentWithFallback(config, org, name, version) {
86
86
  `Use: orch call ${org}/${name}@${version} --input '{...}'`);
87
87
  }
88
88
  }
89
+ // Check if download is disabled (server-only agent)
90
+ if (publicMeta && publicMeta.allow_local_download === false) {
91
+ // Check if owner (can bypass)
92
+ if (config.apiKey) {
93
+ const callerOrg = await (0, api_1.getOrg)(config);
94
+ const isOwner = (publicMeta.org_id && callerOrg.id === publicMeta.org_id) ||
95
+ (publicMeta.org_slug && callerOrg.slug === publicMeta.org_slug);
96
+ if (isOwner) {
97
+ // Owner - fetch from authenticated endpoint
98
+ const myAgents = await (0, api_1.listMyAgents)(config);
99
+ const matching = myAgents.filter(a => a.name === name);
100
+ if (matching.length > 0) {
101
+ let targetAgent;
102
+ if (version === 'latest') {
103
+ targetAgent = matching.sort((a, b) => new Date(b.created_at).getTime() - new Date(a.created_at).getTime())[0];
104
+ }
105
+ else {
106
+ const found = matching.find(a => a.version === version);
107
+ if (!found) {
108
+ throw new api_1.ApiError(`Agent '${org}/${name}@${version}' not found`, 404);
109
+ }
110
+ targetAgent = found;
111
+ }
112
+ const agentData = await (0, api_1.request)(config, 'GET', `/agents/${targetAgent.id}`);
113
+ return { ...agentData, org_slug: org };
114
+ }
115
+ }
116
+ }
117
+ throw new errors_1.CliError(`This agent is server-only and cannot be downloaded.\n\n` +
118
+ `Use: orch call ${org}/${name}@${version} --input '{...}'`);
119
+ }
89
120
  // Free agent - proceed normally with public data
90
121
  if (publicMeta) {
91
122
  // Cast PublicAgent to Agent for use as CanonicalAgent
@@ -12,7 +12,9 @@ function registerPricingCommand(program) {
12
12
  program
13
13
  .command('pricing <agent> <mode>')
14
14
  .description('Set pricing for your agent (free or per-call in USD)')
15
- .action(async (agentRef, mode) => {
15
+ .option('--local-download', 'Allow users to download and run locally')
16
+ .option('--no-local-download', 'Restrict to server-only (orch call)')
17
+ .action(async (agentRef, mode, options) => {
16
18
  const resolved = await (0, config_1.getResolvedConfig)();
17
19
  // Parse agent reference
18
20
  const [orgOrAgent, maybeName] = agentRef.split('/');
@@ -56,17 +58,36 @@ function registerPricingCommand(program) {
56
58
  pricingMode = 'per_call';
57
59
  pricePerCallCents = Math.round(priceFloat * 100);
58
60
  }
61
+ // Determine allow_local_download value
62
+ let allowLocalDownload;
63
+ if (pricingMode === 'per_call') {
64
+ // Paid agents are always server-only
65
+ allowLocalDownload = false;
66
+ if (options.localDownload) {
67
+ process.stderr.write(chalk_1.default.yellow('Note: Paid agents are always server-only. --local-download ignored.\n'));
68
+ }
69
+ }
70
+ else if (options.localDownload !== undefined) {
71
+ allowLocalDownload = options.localDownload;
72
+ }
59
73
  // Set pricing
60
- await (0, api_1.setAgentPricing)(resolved, agent.id, pricingMode, pricePerCallCents);
74
+ await (0, api_1.setAgentPricing)(resolved, agent.id, pricingMode, pricePerCallCents, allowLocalDownload);
61
75
  // Show confirmation
62
76
  process.stdout.write(chalk_1.default.green('✓ Pricing updated\n'));
63
77
  process.stdout.write(`Agent: ${org}/${agentName}\n`);
64
78
  if (pricingMode === 'free') {
65
79
  process.stdout.write(`Mode: FREE\n`);
80
+ if (allowLocalDownload === true) {
81
+ process.stdout.write(`Local download: enabled\n`);
82
+ }
83
+ else if (allowLocalDownload === false) {
84
+ process.stdout.write(`Local download: disabled (server-only)\n`);
85
+ }
66
86
  }
67
87
  else {
68
88
  process.stdout.write(`Mode: Pay per call\n`);
69
89
  process.stdout.write(`Price: $${(pricePerCallCents / 100).toFixed(2)} USD per call\n`);
90
+ process.stdout.write(`Local download: disabled (paid agents are server-only)\n`);
70
91
  }
71
92
  });
72
93
  }
@@ -54,36 +54,6 @@ function deriveInputSchema(variables) {
54
54
  required: [...variables],
55
55
  };
56
56
  }
57
- /**
58
- * Handle security flagged error response (422 with error: 'content_flagged')
59
- * Returns true if the error was handled, false otherwise
60
- */
61
- function handleSecurityFlaggedError(err) {
62
- if (!(err instanceof api_1.ApiError) || err.status !== 422) {
63
- return false;
64
- }
65
- const payload = err.payload;
66
- if (payload?.error !== 'content_flagged') {
67
- return false;
68
- }
69
- process.stderr.write('\n');
70
- process.stderr.write('Error: Skill flagged for security review\n\n');
71
- if (payload.concerns && payload.concerns.length > 0) {
72
- process.stderr.write('Concerns found:\n');
73
- for (const concern of payload.concerns) {
74
- const severityLabel = concern.severity.toUpperCase();
75
- const fileInfo = concern.file_path ? ` in ${concern.file_path}` : '';
76
- process.stderr.write(` [${severityLabel}] ${concern.category}${fileInfo}\n`);
77
- process.stderr.write(` "${concern.description}"\n\n`);
78
- }
79
- }
80
- if (payload.summary) {
81
- process.stderr.write(`Summary: ${payload.summary}\n\n`);
82
- }
83
- process.stderr.write('Please review and remove suspicious patterns before publishing.\n');
84
- process.stderr.write('If you believe this is a false positive, contact support@orchagent.com\n');
85
- return true;
86
- }
87
57
  /**
88
58
  * Check if orchagent-sdk is listed in requirements.txt or pyproject.toml
89
59
  */
@@ -204,6 +174,7 @@ function registerPublishCommand(program) {
204
174
  .option('--docker', 'Include Dockerfile for custom environment (builds E2B template)')
205
175
  .option('--price <amount>', 'Set price per call in USD (e.g., 0.50 for $0.50/call)')
206
176
  .option('--pricing-mode <mode>', 'Pricing mode: free or per_call (default: free)')
177
+ .option('--local-download', 'Allow users to download and run locally (default: server-only)')
207
178
  .action(async (options) => {
208
179
  if (options.private) {
209
180
  process.stderr.write('Warning: --private is deprecated (private is now the default). You can safely remove it.\n');
@@ -270,6 +241,7 @@ function registerPublishCommand(program) {
270
241
  skills_locked: options.skillsLocked || undefined,
271
242
  // SC-05: Include all skill files for UI preview
272
243
  skill_files: hasMultipleFiles ? skillFiles : undefined,
244
+ allow_local_download: options.localDownload || false,
273
245
  });
274
246
  const skillVersion = skillResult.agent?.version || 'v1';
275
247
  const skillAgentId = skillResult.agent?.id;
@@ -316,9 +288,6 @@ function registerPublishCommand(program) {
316
288
  process.stdout.write(`\nView analytics and usage: https://orchagent.io/dashboard\n`);
317
289
  }
318
290
  catch (err) {
319
- if (handleSecurityFlaggedError(err)) {
320
- process.exit(1);
321
- }
322
291
  throw err;
323
292
  }
324
293
  return;
@@ -545,12 +514,10 @@ function registerPublishCommand(program) {
545
514
  manifest: manifest.manifest,
546
515
  default_skills: skillsFromFlag || manifest.default_skills,
547
516
  skills_locked: manifest.skills_locked || options.skillsLocked || undefined,
517
+ allow_local_download: options.localDownload || false,
548
518
  });
549
519
  }
550
520
  catch (err) {
551
- if (handleSecurityFlaggedError(err)) {
552
- process.exit(1);
553
- }
554
521
  throw err;
555
522
  }
556
523
  const assignedVersion = result.agent?.version || 'v1';
@@ -74,11 +74,12 @@ async function downloadAgent(config, org, agent, version) {
74
74
  return await (0, api_1.publicRequest)(config, `/public/agents/${org}/${agent}/${version}/download`);
75
75
  }
76
76
  catch (err) {
77
- // Check for paid-agent error
77
+ // Check for paid-agent or download-disabled error
78
78
  if (err instanceof api_1.ApiError && err.status === 403) {
79
79
  const payload = err.payload;
80
- if (payload?.error?.code === 'PAID_AGENT_SERVER_ONLY') {
81
- // Paid agent - try owner path if authenticated
80
+ const errorCode = payload?.error?.code;
81
+ if (errorCode === 'PAID_AGENT_SERVER_ONLY' || errorCode === 'DOWNLOAD_DISABLED') {
82
+ // Try owner path if authenticated
82
83
  if (config.apiKey) {
83
84
  try {
84
85
  const myAgents = await (0, api_1.listMyAgents)(config);
@@ -112,10 +113,16 @@ async function downloadAgent(config, org, agent, version) {
112
113
  }
113
114
  }
114
115
  // Non-owner - block with helpful message
115
- const price = payload.error.price_per_call_cents || 0;
116
- const priceStr = price ? `$${(price / 100).toFixed(2)}/call` : 'PAID';
117
- throw new errors_1.CliError(`This agent is paid (${priceStr}) and runs on server only.\n\n` +
118
- `Use: orch call ${org}/${agent}@${version} --input '{...}'`);
116
+ if (errorCode === 'PAID_AGENT_SERVER_ONLY') {
117
+ const price = payload.error.price_per_call_cents || 0;
118
+ const priceStr = price ? `$${(price / 100).toFixed(2)}/call` : 'PAID';
119
+ throw new errors_1.CliError(`This agent is paid (${priceStr}) and runs on server only.\n\n` +
120
+ `Use: orch call ${org}/${agent}@${version} --input '{...}'`);
121
+ }
122
+ else {
123
+ throw new errors_1.CliError(`This agent is server-only and cannot be downloaded.\n\n` +
124
+ `Use: orch call ${org}/${agent}@${version} --input '{...}'`);
125
+ }
119
126
  }
120
127
  }
121
128
  if (!(err instanceof api_1.ApiError) || err.status !== 404)
@@ -155,6 +155,42 @@ async function downloadSkillWithFallback(config, org, skill, version) {
155
155
  `Paid skills are loaded automatically during server execution.`);
156
156
  }
157
157
  }
158
+ // Check if download is disabled (server-only skill)
159
+ if (skillMeta && skillMeta.allow_local_download === false) {
160
+ if (config.apiKey) {
161
+ const callerOrg = await (0, api_1.getOrg)(config);
162
+ const isOwner = (skillMeta.org_id && callerOrg.id === skillMeta.org_id) ||
163
+ (skillMeta.org_slug && callerOrg.slug === skillMeta.org_slug);
164
+ if (isOwner) {
165
+ // Owner - fetch from authenticated endpoint
166
+ const myAgents = await (0, api_1.listMyAgents)(config);
167
+ const matching = myAgents.filter(a => a.name === skill && a.type === 'skill');
168
+ if (matching.length > 0) {
169
+ let targetAgent;
170
+ if (version === 'latest') {
171
+ targetAgent = matching.sort((a, b) => new Date(b.created_at).getTime() - new Date(a.created_at).getTime())[0];
172
+ }
173
+ else {
174
+ const found = matching.find(a => a.version === version);
175
+ if (!found) {
176
+ throw new api_1.ApiError(`Skill '${org}/${skill}@${version}' not found`, 404);
177
+ }
178
+ targetAgent = found;
179
+ }
180
+ const skillData = await (0, api_1.request)(config, 'GET', `/agents/${targetAgent.id}`);
181
+ return {
182
+ type: skillData.type,
183
+ name: skillData.name,
184
+ version: skillData.version,
185
+ description: skillData.description,
186
+ prompt: skillData.prompt,
187
+ };
188
+ }
189
+ }
190
+ }
191
+ throw new errors_1.CliError(`This skill is server-only and cannot be downloaded.\n\n` +
192
+ `Skills are loaded automatically during server execution via 'orch call'.`);
193
+ }
158
194
  // Free skill or public metadata available - proceed with normal download
159
195
  if (skillMeta) {
160
196
  try {
@@ -165,11 +201,14 @@ async function downloadSkillWithFallback(config, org, skill, version) {
165
201
  if (err instanceof api_1.ApiError && err.status === 403) {
166
202
  const payload = err.payload;
167
203
  if (payload?.error?.code === 'PAID_AGENT_SERVER_ONLY') {
168
- // Legacy error handling (shouldn't reach here with new logic)
169
204
  const price = payload.error.price_per_call_cents || 0;
170
205
  throw new errors_1.CliError(`This skill costs $${(price / 100).toFixed(2)}/call and runs on server only.\n\n` +
171
206
  `Use: orch call ${org}/${skill}@${version} --input '{...}'`);
172
207
  }
208
+ if (payload?.error?.code === 'DOWNLOAD_DISABLED') {
209
+ throw new errors_1.CliError(`This skill is server-only and cannot be downloaded.\n\n` +
210
+ `Skills are loaded automatically during server execution via 'orch call'.`);
211
+ }
173
212
  }
174
213
  throw err;
175
214
  }
package/dist/lib/api.js CHANGED
@@ -547,11 +547,12 @@ async function getSellerDashboardLink(config) {
547
547
  async function getSellerEarnings(config) {
548
548
  return request(config, 'GET', '/billing/earnings');
549
549
  }
550
- async function setAgentPricing(config, agentId, pricingMode, pricePerCallCents) {
550
+ async function setAgentPricing(config, agentId, pricingMode, pricePerCallCents, allowLocalDownload) {
551
551
  return request(config, 'PUT', `/agents/${agentId}/pricing`, {
552
552
  body: JSON.stringify({
553
553
  pricing_mode: pricingMode,
554
554
  price_per_call_cents: pricePerCallCents,
555
+ allow_local_download: allowLocalDownload,
555
556
  }),
556
557
  headers: { 'Content-Type': 'application/json' },
557
558
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@orchagent/cli",
3
- "version": "0.3.37",
3
+ "version": "0.3.38",
4
4
  "description": "Command-line interface for the orchagent AI agent marketplace",
5
5
  "license": "MIT",
6
6
  "author": "orchagent <hello@orchagent.io>",