@orchagent/cli 0.3.44 → 0.3.46

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.
@@ -49,49 +49,27 @@ async function fetchReadme(url) {
49
49
  return null;
50
50
  }
51
51
  }
52
- async function downloadAgentWithFallback(config, org, agent, version) {
53
- // First fetch public metadata to get pricing fields
54
- let publicMeta = null;
52
+ async function getAgentInfo(config, org, agent, version) {
53
+ // Use public metadata endpoint as primary source — never blocked by download restrictions
55
54
  try {
56
- publicMeta = await (0, api_1.getPublicAgent)(config, org, agent, version);
55
+ const publicMeta = await (0, api_1.getPublicAgent)(config, org, agent, version);
56
+ const meta = publicMeta;
57
+ return {
58
+ type: (publicMeta.type || 'tool'),
59
+ name: publicMeta.name,
60
+ version: publicMeta.version,
61
+ description: (publicMeta.description ?? undefined),
62
+ supported_providers: publicMeta.supported_providers || ['any'],
63
+ input_schema: publicMeta.input_schema,
64
+ output_schema: publicMeta.output_schema,
65
+ source_url: meta.source_url,
66
+ run_command: meta.run_command,
67
+ url: meta.url,
68
+ pricing_mode: publicMeta.pricing_mode,
69
+ price_per_call_cents: publicMeta.price_per_call_cents,
70
+ };
57
71
  }
58
72
  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
- }
95
73
  if (!(err instanceof api_1.ApiError) || err.status !== 404)
96
74
  throw err;
97
75
  }
@@ -103,7 +81,6 @@ async function downloadAgentWithFallback(config, org, agent, version) {
103
81
  if (userOrg.slug !== org) {
104
82
  throw new api_1.ApiError(`Agent '${org}/${agent}@${version}' not found`, 404);
105
83
  }
106
- // Find agent in user's list and construct download data
107
84
  const agents = await (0, api_1.listMyAgents)(config);
108
85
  const matching = agents.filter(a => a.name === agent);
109
86
  if (matching.length === 0) {
@@ -118,10 +95,8 @@ async function downloadAgentWithFallback(config, org, agent, version) {
118
95
  targetAgent = found;
119
96
  }
120
97
  else {
121
- // Get most recent
122
98
  targetAgent = matching.sort((a, b) => new Date(b.created_at).getTime() - new Date(a.created_at).getTime())[0];
123
99
  }
124
- // Convert Agent to AgentDownload format
125
100
  return {
126
101
  type: targetAgent.type,
127
102
  name: targetAgent.name,
@@ -134,7 +109,6 @@ async function downloadAgentWithFallback(config, org, agent, version) {
134
109
  source_url: targetAgent.source_url,
135
110
  run_command: targetAgent.run_command,
136
111
  url: targetAgent.url,
137
- sdk_compatible: false,
138
112
  pricing_mode: targetAgent.pricing_mode,
139
113
  price_per_call_cents: targetAgent.price_per_call_cents,
140
114
  };
@@ -148,7 +122,7 @@ function registerInfoCommand(program) {
148
122
  const config = await (0, config_1.getResolvedConfig)();
149
123
  const { org, agent, version } = (0, agent_ref_1.parseAgentRef)(agentArg);
150
124
  // Fetch agent metadata
151
- const agentData = await downloadAgentWithFallback(config, org, agent, version);
125
+ const agentData = await getAgentInfo(config, org, agent, version);
152
126
  if (options.json) {
153
127
  // Don't expose internal routing URLs in JSON output
154
128
  const output = { ...agentData };
@@ -124,27 +124,13 @@ const AGENT_MANIFEST_TEMPLATE = `{
124
124
  "description": "An AI agent with tool use",
125
125
  "type": "agent",
126
126
  "supported_providers": ["anthropic"],
127
- "max_turns": 25,
128
- "custom_tools": [
129
- {
130
- "name": "run_tests",
131
- "description": "Run the test suite",
132
- "command": "pytest"
133
- }
134
- ]
127
+ "max_turns": 25
135
128
  }
136
129
  `;
137
- const AGENT_PROMPT_TEMPLATE = `You are a helpful AI agent with access to a sandboxed environment.
130
+ const AGENT_PROMPT_TEMPLATE = `You are a helpful AI agent.
138
131
 
139
- Given the input, complete the task using the available tools:
140
- - Use bash to run commands
141
- - Use read_file and write_file to work with files
142
- - Use custom tools defined by the agent author
143
- - Call submit_result when you're done
144
-
145
- Input: The caller's input will be provided as JSON.
146
-
147
- Work step by step, verify your results, and submit the final output.
132
+ Given the input, complete the task step by step.
133
+ Verify your results before submitting.
148
134
  `;
149
135
  const AGENT_SCHEMA_TEMPLATE = `{
150
136
  "input": {
@@ -287,9 +273,8 @@ function registerInitCommand(program) {
287
273
  process.stdout.write(` 1. cd ${name}\n`);
288
274
  }
289
275
  process.stdout.write(` ${stepNum}. Edit prompt.md with your agent instructions\n`);
290
- process.stdout.write(` ${stepNum + 1}. Edit custom_tools in orchagent.json for your environment\n`);
291
- process.stdout.write(` ${stepNum + 2}. Edit schema.json with your input/output schemas\n`);
292
- process.stdout.write(` ${stepNum + 3}. Run: orchagent publish\n`);
276
+ process.stdout.write(` ${stepNum + 1}. Edit schema.json with your input/output schemas\n`);
277
+ process.stdout.write(` ${stepNum + 2}. Run: orchagent publish\n`);
293
278
  }
294
279
  else if (options.type !== 'tool') {
295
280
  const stepNum = name ? 2 : 1;
@@ -309,6 +309,16 @@ function registerPublishCommand(program) {
309
309
  if (!manifest.name) {
310
310
  throw new errors_1.CliError('orchagent.json must have name');
311
311
  }
312
+ // Warn about deprecated fields that are ignored
313
+ if (manifest.prompt) {
314
+ process.stderr.write(chalk_1.default.yellow('Warning: "prompt" field in orchagent.json is ignored. Use prompt.md file instead.\n'));
315
+ }
316
+ if (manifest.input_schema) {
317
+ process.stderr.write(chalk_1.default.yellow('Warning: "input_schema" field in orchagent.json is ignored. Use schema.json file instead.\n'));
318
+ }
319
+ if (manifest.output_schema) {
320
+ process.stderr.write(chalk_1.default.yellow('Warning: "output_schema" field in orchagent.json is ignored. Use schema.json file instead.\n'));
321
+ }
312
322
  // Check for misplaced manifest fields at top level (common user error)
313
323
  const manifestFields = ['manifest_version', 'dependencies', 'max_hops', 'timeout_ms', 'per_call_downstream_cap'];
314
324
  const misplacedFields = manifestFields.filter(f => f in manifest && !manifest.manifest);
@@ -74,6 +74,18 @@ function parseAgentRef(value) {
74
74
  }
75
75
  throw new errors_1.CliError('Invalid agent reference. Use org/agent or agent format.');
76
76
  }
77
+ // ─── Validation helpers ─────────────────────────────────────────────────────
78
+ async function validateFilePath(filePath) {
79
+ const stat = await promises_1.default.stat(filePath);
80
+ if (stat.isDirectory()) {
81
+ throw new errors_1.CliError(`Cannot upload a directory for cloud execution: ${filePath}\n\n` +
82
+ `Options:\n` +
83
+ ` Use --local to run locally with filesystem access:\n` +
84
+ ` orch run <agent> --local --path ${filePath}\n` +
85
+ ` Or specify individual files:\n` +
86
+ ` orch run <agent> --file ${filePath}/specific-file.ts`);
87
+ }
88
+ }
77
89
  // ─── Cloud execution helpers (from call.ts) ─────────────────────────────────
78
90
  function findLocalPathKey(obj) {
79
91
  if (typeof obj !== 'object' || obj === null) {
@@ -124,6 +136,19 @@ function inferFileField(inputSchema) {
124
136
  return requiredStrings[0];
125
137
  return 'content';
126
138
  }
139
+ function applySchemaDefaults(body, schema) {
140
+ if (!schema)
141
+ return body;
142
+ const props = schema.properties;
143
+ if (!props || typeof props !== 'object')
144
+ return body;
145
+ for (const [key, def] of Object.entries(props)) {
146
+ if (body[key] === undefined && def.default !== undefined) {
147
+ body[key] = def.default;
148
+ }
149
+ }
150
+ return body;
151
+ }
127
152
  async function readStdin() {
128
153
  if (process.stdin.isTTY)
129
154
  return null;
@@ -155,6 +180,7 @@ async function buildMultipartBody(filePaths, metadata) {
155
180
  }
156
181
  const form = new FormData();
157
182
  for (const filePath of filePaths) {
183
+ await validateFilePath(filePath);
158
184
  const buffer = await promises_1.default.readFile(filePath);
159
185
  const filename = path_1.default.basename(filePath);
160
186
  form.append('files[]', new Blob([new Uint8Array(buffer)]), filename);
@@ -182,6 +208,7 @@ async function resolveJsonBody(input) {
182
208
  raw = stdinData.toString('utf8');
183
209
  }
184
210
  else {
211
+ await validateFilePath(source);
185
212
  raw = await promises_1.default.readFile(source, 'utf8');
186
213
  }
187
214
  }
@@ -857,6 +884,224 @@ async function saveBundleLocally(config, org, agent, version, agentId) {
857
884
  }
858
885
  return bundleDir;
859
886
  }
887
+ // ─── Local directory execution ───────────────────────────────────────────────
888
+ function isLocalPath(ref) {
889
+ return ref.startsWith('.') || ref.startsWith('/') || ref.startsWith('~');
890
+ }
891
+ async function executeLocalFromDir(dirPath, args, options) {
892
+ // Merge --data alias into --input
893
+ if (options.data && !options.input) {
894
+ options.input = options.data;
895
+ }
896
+ if (options.here) {
897
+ options.input = JSON.stringify({ path: process.cwd() });
898
+ }
899
+ else if (options.path) {
900
+ options.input = JSON.stringify({ path: options.path });
901
+ }
902
+ const resolved = path_1.default.resolve(dirPath);
903
+ // Verify directory exists
904
+ let stat;
905
+ try {
906
+ stat = await promises_1.default.stat(resolved);
907
+ }
908
+ catch {
909
+ throw new errors_1.CliError(`Directory not found: ${resolved}`);
910
+ }
911
+ if (!stat.isDirectory()) {
912
+ throw new errors_1.CliError(`Not a directory: ${resolved}`);
913
+ }
914
+ // Read orchagent.json manifest
915
+ const manifestPath = path_1.default.join(resolved, 'orchagent.json');
916
+ let manifest;
917
+ try {
918
+ const raw = await promises_1.default.readFile(manifestPath, 'utf-8');
919
+ manifest = JSON.parse(raw);
920
+ }
921
+ catch {
922
+ throw new errors_1.CliError(`No orchagent.json found in ${resolved}\n\n` +
923
+ `To run a local agent, the directory must contain orchagent.json.\n` +
924
+ `Create one with: orch init`);
925
+ }
926
+ const agentType = manifest.type || 'tool';
927
+ if (agentType === 'skill') {
928
+ throw new errors_1.CliError('Skills cannot be run directly.\n\n' +
929
+ 'Skills are instructions meant to be injected into AI agent contexts.\n' +
930
+ `Install with: orchagent skill install <org>/<skill>`);
931
+ }
932
+ if (agentType === 'agent') {
933
+ throw new errors_1.CliError('Agent type cannot be run locally.\n\n' +
934
+ 'Agent type requires a sandbox environment with tool use capabilities.\n' +
935
+ 'Publish first, then run in the cloud:\n' +
936
+ ' orch publish && orch run <org>/<agent> --data \'{"task": "..."}\'');
937
+ }
938
+ if (agentType === 'prompt') {
939
+ // Read prompt.md
940
+ const promptPath = path_1.default.join(resolved, 'prompt.md');
941
+ let prompt;
942
+ try {
943
+ prompt = await promises_1.default.readFile(promptPath, 'utf-8');
944
+ }
945
+ catch {
946
+ throw new errors_1.CliError(`No prompt.md found in ${resolved}`);
947
+ }
948
+ // Read schema.json for schemas
949
+ let inputSchema;
950
+ let outputSchema;
951
+ try {
952
+ const schemaRaw = await promises_1.default.readFile(path_1.default.join(resolved, 'schema.json'), 'utf-8');
953
+ const schemas = JSON.parse(schemaRaw);
954
+ inputSchema = schemas.input;
955
+ outputSchema = schemas.output;
956
+ }
957
+ catch {
958
+ // Schema is optional
959
+ }
960
+ // Build AgentDownload from local files
961
+ const agentData = {
962
+ type: 'prompt',
963
+ name: manifest.name || path_1.default.basename(resolved),
964
+ version: manifest.version || 'local',
965
+ description: manifest.description,
966
+ prompt,
967
+ input_schema: inputSchema,
968
+ output_schema: outputSchema,
969
+ supported_providers: manifest.supported_providers || ['any'],
970
+ default_models: manifest.default_models,
971
+ };
972
+ if (!options.input) {
973
+ process.stderr.write(`Loaded local agent: ${agentData.name}\n\n`);
974
+ process.stderr.write(`Run with input:\n`);
975
+ process.stderr.write(` orch run ${dirPath} --local --data '{...}'\n`);
976
+ return;
977
+ }
978
+ let inputData;
979
+ try {
980
+ inputData = JSON.parse(options.input);
981
+ }
982
+ catch {
983
+ throw new errors_1.CliError('Invalid JSON input');
984
+ }
985
+ const config = await (0, config_1.getResolvedConfig)();
986
+ const result = await executePromptLocally(agentData, inputData, [], config, options.provider, options.model);
987
+ (0, output_1.printJson)(result);
988
+ return;
989
+ }
990
+ // Tool agents with bundle
991
+ const entrypoint = manifest.entrypoint || 'sandbox_main.py';
992
+ const entrypointPath = path_1.default.join(resolved, entrypoint);
993
+ try {
994
+ await promises_1.default.access(entrypointPath);
995
+ }
996
+ catch {
997
+ // No local entrypoint — try run_command
998
+ if (manifest.run_command) {
999
+ const agentData = {
1000
+ type: 'tool',
1001
+ name: manifest.name || path_1.default.basename(resolved),
1002
+ version: manifest.version || 'local',
1003
+ supported_providers: manifest.supported_providers || ['any'],
1004
+ run_command: manifest.run_command,
1005
+ source_url: manifest.source_url,
1006
+ pip_package: manifest.pip_package,
1007
+ };
1008
+ await executeTool(agentData, args);
1009
+ return;
1010
+ }
1011
+ throw new errors_1.CliError(`No entrypoint found in ${resolved}\n\n` +
1012
+ `Expected: ${entrypoint}\n` +
1013
+ `For tool agents, ensure the directory contains the entrypoint file\n` +
1014
+ `or has run_command set in orchagent.json.`);
1015
+ }
1016
+ // Execute bundle-style from local directory
1017
+ const config = await (0, config_1.getResolvedConfig)();
1018
+ const agentData = {
1019
+ type: 'tool',
1020
+ name: manifest.name || path_1.default.basename(resolved),
1021
+ version: manifest.version || 'local',
1022
+ supported_providers: manifest.supported_providers || ['any'],
1023
+ entrypoint,
1024
+ };
1025
+ // Install requirements if present
1026
+ const requirementsPath = path_1.default.join(resolved, 'requirements.txt');
1027
+ try {
1028
+ await promises_1.default.access(requirementsPath);
1029
+ const { code } = await runCommand('python3', ['-m', 'pip', 'install', '-q', '--disable-pip-version-check', '-r', requirementsPath]);
1030
+ if (code !== 0) {
1031
+ process.stderr.write('Warning: Failed to install requirements.txt\n');
1032
+ }
1033
+ }
1034
+ catch {
1035
+ // No requirements.txt
1036
+ }
1037
+ let inputJson = '{}';
1038
+ if (options.input) {
1039
+ try {
1040
+ JSON.parse(options.input);
1041
+ inputJson = options.input;
1042
+ }
1043
+ catch {
1044
+ throw new errors_1.CliError('Invalid JSON input');
1045
+ }
1046
+ }
1047
+ else if (args.length > 0) {
1048
+ inputJson = JSON.stringify({ input: args[0] });
1049
+ }
1050
+ process.stderr.write(`\nRunning: python3 ${entrypoint}\n\n`);
1051
+ const subprocessEnv = { ...process.env };
1052
+ if (config.apiKey) {
1053
+ subprocessEnv.ORCHAGENT_SERVICE_KEY = config.apiKey;
1054
+ subprocessEnv.ORCHAGENT_API_URL = config.apiUrl;
1055
+ }
1056
+ const proc = (0, child_process_1.spawn)('python3', [entrypointPath], {
1057
+ cwd: resolved,
1058
+ stdio: ['pipe', 'pipe', 'pipe'],
1059
+ env: subprocessEnv,
1060
+ });
1061
+ proc.stdin.write(inputJson);
1062
+ proc.stdin.end();
1063
+ let stdout = '';
1064
+ let stderr = '';
1065
+ proc.stdout?.on('data', (data) => {
1066
+ stdout += data.toString();
1067
+ });
1068
+ proc.stderr?.on('data', (data) => {
1069
+ const text = data.toString();
1070
+ stderr += text;
1071
+ process.stderr.write(text);
1072
+ });
1073
+ const exitCode = await new Promise((resolve) => {
1074
+ proc.on('close', (code) => resolve(code ?? 1));
1075
+ proc.on('error', (err) => {
1076
+ process.stderr.write(`Error running agent: ${err.message}\n`);
1077
+ resolve(1);
1078
+ });
1079
+ });
1080
+ if (stdout.trim()) {
1081
+ try {
1082
+ const result = JSON.parse(stdout.trim());
1083
+ if (exitCode !== 0 && typeof result === 'object' && result !== null && 'error' in result) {
1084
+ throw new errors_1.CliError(`Agent error: ${result.error}`);
1085
+ }
1086
+ if (exitCode !== 0) {
1087
+ (0, output_1.printJson)(result);
1088
+ throw new errors_1.CliError(`Agent exited with code ${exitCode}`);
1089
+ }
1090
+ (0, output_1.printJson)(result);
1091
+ }
1092
+ catch (err) {
1093
+ if (err instanceof errors_1.CliError)
1094
+ throw err;
1095
+ process.stdout.write(stdout);
1096
+ if (exitCode !== 0) {
1097
+ throw new errors_1.CliError(`Agent exited with code ${exitCode}`);
1098
+ }
1099
+ }
1100
+ }
1101
+ else if (exitCode !== 0) {
1102
+ throw new errors_1.CliError(`Agent exited with code ${exitCode}`);
1103
+ }
1104
+ }
860
1105
  // ─── Cloud execution path ───────────────────────────────────────────────────
861
1106
  async function executeCloud(agentRef, file, options) {
862
1107
  // Merge --input alias into --data
@@ -1012,10 +1257,58 @@ async function executeCloud(agentRef, file, options) {
1012
1257
  ...(options.file ?? []),
1013
1258
  ...(file ? [file] : []),
1014
1259
  ];
1015
- if (options.data) {
1016
- if (filePaths.length > 0 || options.metadata) {
1017
- throw new errors_1.CliError('Cannot use --data with file uploads or --metadata.');
1260
+ if (options.data && options.metadata) {
1261
+ throw new errors_1.CliError('Cannot use --data with --metadata. Use one or the other.');
1262
+ }
1263
+ if (options.data && filePaths.length > 0) {
1264
+ // Merge file content into --data
1265
+ const resolvedBody = await resolveJsonBody(options.data);
1266
+ const bodyObj = JSON.parse(resolvedBody);
1267
+ if (agentMeta.type === 'prompt') {
1268
+ const fieldName = options.fileField || inferFileField(agentMeta.input_schema);
1269
+ if (filePaths.length === 1) {
1270
+ await validateFilePath(filePaths[0]);
1271
+ bodyObj[fieldName] = await promises_1.default.readFile(filePaths[0], 'utf-8');
1272
+ sourceLabel = filePaths[0];
1273
+ }
1274
+ else {
1275
+ const allContents = {};
1276
+ for (const fp of filePaths) {
1277
+ await validateFilePath(fp);
1278
+ allContents[path_1.default.basename(fp)] = await promises_1.default.readFile(fp, 'utf-8');
1279
+ }
1280
+ bodyObj[fieldName] = await promises_1.default.readFile(filePaths[0], 'utf-8');
1281
+ bodyObj.files = allContents;
1282
+ sourceLabel = `${filePaths.length} files`;
1283
+ }
1284
+ // Auto-populate filename if schema has it and user didn't provide it
1285
+ if (filePaths.length >= 1 && bodyObj.filename === undefined) {
1286
+ const schema = agentMeta.input_schema;
1287
+ const schemaProps = schema?.properties;
1288
+ if (schemaProps?.filename) {
1289
+ bodyObj.filename = path_1.default.basename(filePaths[0]);
1290
+ }
1291
+ }
1292
+ applySchemaDefaults(bodyObj, agentMeta.input_schema);
1293
+ if (llmCredentials)
1294
+ bodyObj.llm_credentials = llmCredentials;
1295
+ body = JSON.stringify(bodyObj);
1296
+ headers['Content-Type'] = 'application/json';
1297
+ }
1298
+ else {
1299
+ // Tool agents: send files as multipart, --data as metadata
1300
+ let metadata = resolvedBody;
1301
+ if (llmCredentials) {
1302
+ const metaObj = JSON.parse(metadata);
1303
+ metaObj.llm_credentials = llmCredentials;
1304
+ metadata = JSON.stringify(metaObj);
1305
+ }
1306
+ const multipart = await buildMultipartBody(filePaths, metadata);
1307
+ body = multipart.body;
1308
+ sourceLabel = multipart.sourceLabel;
1018
1309
  }
1310
+ }
1311
+ else if (options.data) {
1019
1312
  const resolvedBody = await resolveJsonBody(options.data);
1020
1313
  warnIfLocalPathReference(resolvedBody);
1021
1314
  if (llmCredentials) {
@@ -1040,6 +1333,7 @@ async function executeCloud(agentRef, file, options) {
1040
1333
  }
1041
1334
  }
1042
1335
  if (filePaths.length === 1) {
1336
+ await validateFilePath(filePaths[0]);
1043
1337
  const fileContent = await promises_1.default.readFile(filePaths[0], 'utf-8');
1044
1338
  bodyObj[fieldName] = fileContent;
1045
1339
  sourceLabel = filePaths[0];
@@ -1047,6 +1341,7 @@ async function executeCloud(agentRef, file, options) {
1047
1341
  else if (filePaths.length > 1) {
1048
1342
  const allContents = {};
1049
1343
  for (const fp of filePaths) {
1344
+ await validateFilePath(fp);
1050
1345
  allContents[path_1.default.basename(fp)] = await promises_1.default.readFile(fp, 'utf-8');
1051
1346
  }
1052
1347
  const firstContent = await promises_1.default.readFile(filePaths[0], 'utf-8');
@@ -1054,6 +1349,15 @@ async function executeCloud(agentRef, file, options) {
1054
1349
  bodyObj.files = allContents;
1055
1350
  sourceLabel = `${filePaths.length} files`;
1056
1351
  }
1352
+ // Auto-populate filename if schema has it and user didn't provide it
1353
+ if (filePaths.length >= 1 && bodyObj.filename === undefined) {
1354
+ const schema = agentMeta.input_schema;
1355
+ const schemaProps = schema?.properties;
1356
+ if (schemaProps?.filename) {
1357
+ bodyObj.filename = path_1.default.basename(filePaths[0]);
1358
+ }
1359
+ }
1360
+ applySchemaDefaults(bodyObj, agentMeta.input_schema);
1057
1361
  if (llmCredentials) {
1058
1362
  bodyObj.llm_credentials = llmCredentials;
1059
1363
  }
@@ -1139,6 +1443,12 @@ async function executeCloud(agentRef, file, options) {
1139
1443
  payload.message ||
1140
1444
  response.statusText
1141
1445
  : response.statusText;
1446
+ if (response.status >= 500) {
1447
+ spinner?.fail(`Server error (${response.status})`);
1448
+ throw new errors_1.CliError(`${message}\n\n` +
1449
+ `This is a server-side error. Try again in a moment.\n` +
1450
+ `If it persists, check the dashboard for run logs or try a different provider.`);
1451
+ }
1142
1452
  spinner?.fail(`Run failed: ${message}`);
1143
1453
  throw new errors_1.CliError(message);
1144
1454
  }
@@ -1413,7 +1723,12 @@ contains keys like 'path', 'directory', 'file', etc., those values will be inter
1413
1723
  by the server, not your local machine. To use local files, use --local or --file.
1414
1724
  `)
1415
1725
  .action(async (agentRef, file, options) => {
1416
- if (options.local) {
1726
+ if (options.local && isLocalPath(agentRef)) {
1727
+ // Local directory execution (e.g., orch run . --local)
1728
+ const args = file ? [file] : [];
1729
+ await executeLocalFromDir(agentRef, args, options);
1730
+ }
1731
+ else if (options.local) {
1417
1732
  // Local execution: file arg becomes first positional arg
1418
1733
  const args = file ? [file] : [];
1419
1734
  await executeLocal(agentRef, args, options);
@@ -48,6 +48,10 @@ const installed_1 = require("../lib/installed");
48
48
  const pricing_1 = require("../lib/pricing");
49
49
  const package_json_1 = __importDefault(require("../../package.json"));
50
50
  const DEFAULT_VERSION = 'latest';
51
+ function stripFrontmatter(content) {
52
+ const match = content.match(/^---\s*\n[\s\S]*?\n---\s*\n([\s\S]*)$/);
53
+ return match ? match[1].trimStart() : content;
54
+ }
51
55
  /**
52
56
  * AI tool skill directories.
53
57
  *
@@ -443,6 +447,8 @@ Instructions and guidance for AI agents...
443
447
  // Determine scope (--global is legacy alias for --scope user; then config default; then 'project')
444
448
  const scope = options.global ? 'user' : (options.scope || await (0, config_1.getDefaultScope)() || 'project');
445
449
  result.scope = scope;
450
+ // Strip existing YAML frontmatter from the prompt to avoid duplication
451
+ const cleanPrompt = stripFrontmatter(skillData.prompt);
446
452
  // Build skill content with header
447
453
  const skillContent = `# ${skillData.name}
448
454
 
@@ -450,7 +456,7 @@ ${skillData.description || ''}
450
456
 
451
457
  ---
452
458
 
453
- ${skillData.prompt}
459
+ ${cleanPrompt}
454
460
  `;
455
461
  // Dry run - show what would be installed
456
462
  if (options.dryRun) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@orchagent/cli",
3
- "version": "0.3.44",
3
+ "version": "0.3.46",
4
4
  "description": "Command-line interface for the orchagent AI agent marketplace",
5
5
  "license": "MIT",
6
6
  "author": "orchagent <hello@orchagent.io>",