@orchagent/cli 0.3.100 → 0.3.102

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.
@@ -9,18 +9,14 @@ const config_1 = require("../lib/config");
9
9
  const api_1 = require("../lib/api");
10
10
  const errors_1 = require("../lib/errors");
11
11
  const key_store_1 = require("../lib/key-store");
12
+ const resolve_agent_1 = require("../lib/resolve-agent");
12
13
  /**
13
14
  * Resolve an agent reference ("org/agent" or just "agent") to an agent ID.
14
- * Uses the authenticated list-agents endpoint and finds the latest version.
15
+ * Uses the shared resolveAgentContext() for parsing and org/workspace resolution,
16
+ * then finds the latest version via the authenticated list-agents endpoint.
15
17
  */
16
18
  async function resolveAgentId(config, ref) {
17
- const parts = ref.split('/');
18
- const agentName = parts.length >= 2 ? parts[1] : parts[0];
19
- const orgSlug = parts.length >= 2 ? parts[0] : undefined;
20
- // Resolve workspace context from org slug or config
21
- const configFile = await (0, config_1.loadConfig)();
22
- const resolvedOrg = orgSlug ?? configFile.workspace ?? config.defaultOrg;
23
- const workspaceId = resolvedOrg ? await (0, api_1.resolveWorkspaceIdForOrg)(config, resolvedOrg) : undefined;
19
+ const { org, agent: agentName, workspaceId } = await (0, resolve_agent_1.resolveAgentContext)(ref, config);
24
20
  const agents = await (0, api_1.listMyAgents)(config, workspaceId);
25
21
  const matching = agents.filter(a => a.name === agentName);
26
22
  if (matching.length === 0) {
@@ -28,7 +24,7 @@ async function resolveAgentId(config, ref) {
28
24
  }
29
25
  // Use the latest version
30
26
  const latest = matching.sort((a, b) => new Date(b.created_at).getTime() - new Date(a.created_at).getTime())[0];
31
- return { agent: latest, agentId: latest.id, orgSlug: latest.org_slug ?? resolvedOrg ?? '', workspaceId };
27
+ return { agent: latest, agentId: latest.id, orgSlug: latest.org_slug ?? org, workspaceId };
32
28
  }
33
29
  function registerAgentKeysCommand(program) {
34
30
  const agentKeys = program
@@ -246,8 +246,10 @@ function parseSecondRef(value, firstOrg, firstName) {
246
246
  const parsed = (0, agent_ref_1.parseAgentRef)(value);
247
247
  return { org: parsed.org ?? firstOrg, agent: parsed.agent, version: parsed.version };
248
248
  }
249
- // Otherwise treat as a version shorthand for the same agent
250
- return { org: firstOrg, agent: firstName, version: value };
249
+ // Otherwise treat as a version shorthand for the same agent.
250
+ // Strip leading '@' users naturally type `@v2` since the full form is `org/agent@v2`.
251
+ const version = value.startsWith('@') ? value.slice(1) : value;
252
+ return { org: firstOrg, agent: firstName, version };
251
253
  }
252
254
  // ── Command ────────────────────────────────────────────────────
253
255
  function registerDiffCommand(program) {
@@ -10,16 +10,21 @@ const DOCS_ROUTES = {
10
10
  '': '/',
11
11
  'cli': '/using-agents/cli-commands',
12
12
  'agents': '/building-agents/agent-types',
13
+ 'orchestration': '/building-agents/orchestration',
13
14
  'skills': '/building-agents/orchestration',
14
15
  'sdk': '/building-agents/sdk',
15
16
  'api': '/api-reference/overview',
16
17
  'quickstart': '/quickstart',
18
+ 'scheduling': '/using-agents/scheduling',
19
+ 'services': '/using-agents/services',
20
+ 'security': '/concepts/security',
21
+ 'billing': '/concepts/billing',
17
22
  };
18
23
  function registerDocsCommand(program) {
19
24
  program
20
25
  .command('docs')
21
26
  .description('Open documentation in browser')
22
- .argument('[topic]', 'Topic: cli, agents, skills, sdk, api, quickstart')
27
+ .argument('[topic]', 'Topic: cli, agents, orchestration, skills, sdk, api, quickstart, scheduling, services, security, billing')
23
28
  .action(async (topic) => {
24
29
  if (topic && !(topic in DOCS_ROUTES)) {
25
30
  const validTopics = Object.keys(DOCS_ROUTES).filter(k => k).join(', ');
@@ -81,8 +81,9 @@ Examples:
81
81
  throw new errors_1.CliError('Missing org. Use org/agent format or set default org.');
82
82
  }
83
83
  const { agent, version } = parsed;
84
+ const workspaceId = await (0, api_1.resolveWorkspaceIdForOrg)(config, org);
84
85
  write('Resolving source agent...\n');
85
- const source = await (0, api_1.getAgentWithFallback)(config, org, agent, version);
86
+ const source = await (0, api_1.getAgentWithFallback)(config, org, agent, version, workspaceId);
86
87
  if (!source.id) {
87
88
  throw new errors_1.CliError(`Could not resolve source agent ID for '${org}/${agent}@${version}'.`);
88
89
  }
@@ -58,7 +58,7 @@ Reads JSON input from stdin, processes it, and writes JSON output to stdout.
58
58
  This is the standard orchagent tool protocol.
59
59
 
60
60
  Usage:
61
- echo '{"input": "hello"}' | python main.py
61
+ echo '{"input": "hello"}' | python3 main.py
62
62
  """
63
63
 
64
64
  import json
@@ -136,7 +136,7 @@ IMPORTANT: Port 8080 is reserved by the platform health server.
136
136
  Use a different port (default: 3000).
137
137
 
138
138
  Local development:
139
- python main.py
139
+ python3 main.py
140
140
  """
141
141
 
142
142
  import json
@@ -405,7 +405,7 @@ orchagent fan-out orchestrator.
405
405
  Calls multiple agents in parallel, combines their results.
406
406
 
407
407
  Usage:
408
- echo '{"task": "analyze this"}' | python main.py
408
+ echo '{"task": "analyze this"}' | python3 main.py
409
409
  """
410
410
 
411
411
  import asyncio
@@ -497,7 +497,7 @@ orchagent pipeline orchestrator.
497
497
  Calls agents sequentially — each step's output feeds into the next.
498
498
 
499
499
  Usage:
500
- echo '{"task": "process this data"}' | python main.py
500
+ echo '{"task": "process this data"}' | python3 main.py
501
501
  """
502
502
 
503
503
  import asyncio
@@ -584,7 +584,7 @@ orchagent map-reduce orchestrator.
584
584
  Splits input into chunks, processes each in parallel (map), then aggregates (reduce).
585
585
 
586
586
  Usage:
587
- echo '{"items": ["item1", "item2", "item3"]}' | python main.py
587
+ echo '{"items": ["item1", "item2", "item3"]}' | python3 main.py
588
588
  """
589
589
 
590
590
  import asyncio
@@ -803,7 +803,7 @@ At least one platform token is required.
803
803
 
804
804
  \`\`\`sh
805
805
  pip install -r requirements.txt
806
- python main.py
806
+ python3 main.py
807
807
  \`\`\`
808
808
 
809
809
  ### 5. Deploy
@@ -848,7 +848,7 @@ cp .env.example .env
848
848
  # Fill in DISCORD_BOT_TOKEN, ANTHROPIC_API_KEY, DISCORD_CHANNEL_IDS
849
849
 
850
850
  pip install -r requirements.txt
851
- python main.py
851
+ python3 main.py
852
852
  \`\`\`
853
853
 
854
854
  ### 4. Deploy
@@ -1068,7 +1068,7 @@ Reads JSON input from stdin, processes the task, and writes JSON output to stdou
1068
1068
  This is a code-runtime agent — you control the logic and can call any LLM provider.
1069
1069
 
1070
1070
  Usage:
1071
- echo '{"task": "summarize this text"}' | python main.py
1071
+ echo '{"task": "summarize this text"}' | python3 main.py
1072
1072
  """
1073
1073
 
1074
1074
  import json
@@ -1203,7 +1203,7 @@ Reads JSON input from stdin, calls dependency agents via the orchagent SDK,
1203
1203
  and writes JSON output to stdout.
1204
1204
 
1205
1205
  Usage:
1206
- echo '{"task": "do something"}' | python main.py
1206
+ echo '{"task": "do something"}' | python3 main.py
1207
1207
  """
1208
1208
 
1209
1209
  import asyncio
@@ -1328,7 +1328,7 @@ Listens for messages in configured channels and responds using the Anthropic API
1328
1328
  Local development:
1329
1329
  1. Copy .env.example to .env and fill in your tokens
1330
1330
  2. pip install -r requirements.txt
1331
- 3. python main.py
1331
+ 3. python3 main.py
1332
1332
  """
1333
1333
 
1334
1334
  import asyncio
@@ -1494,7 +1494,10 @@ function resolveInitFlavor(typeOption) {
1494
1494
  if (normalized === 'prompt') {
1495
1495
  return { type: 'prompt', flavor: 'direct_llm' };
1496
1496
  }
1497
- if (normalized === 'agent' || normalized === 'agentic') {
1497
+ if (normalized === 'agent') {
1498
+ return { type: 'agent', flavor: 'managed_loop' };
1499
+ }
1500
+ if (normalized === 'agentic') {
1498
1501
  return { type: 'agent', flavor: 'code_runtime' };
1499
1502
  }
1500
1503
  if (normalized === 'tool' || normalized === 'code') {
@@ -1511,7 +1514,7 @@ function registerInitCommand(program) {
1511
1514
  .option('--orchestrator', 'Create an orchestrator agent with dependency scaffolding and SDK boilerplate')
1512
1515
  .option('--run-mode <mode>', 'Run mode for agents: on_demand or always_on', 'on_demand')
1513
1516
  .option('--language <lang>', 'Language: python or javascript (default: python)', 'python')
1514
- .option('--loop', 'Use platform-managed LLM loop instead of code runtime (requires --type agent)')
1517
+ .option('--loop', 'Use platform-managed LLM loop execution (explicit for --type agentic; default for --type agent)')
1515
1518
  .option('--template <name>', 'Start from a template (use --list-templates to see options)')
1516
1519
  .option('--list-templates', 'Show available templates with descriptions')
1517
1520
  .action(async (name, options) => {
@@ -1617,7 +1620,7 @@ function registerInitCommand(program) {
1617
1620
  const isJavaScript = ['javascript', 'js', 'typescript', 'ts'].includes(language);
1618
1621
  // Block unsupported JS flavors
1619
1622
  if (isJavaScript && initMode.flavor === 'managed_loop') {
1620
- throw new errors_1.CliError('JavaScript is not supported for --loop (managed loop). Use --type agent without --loop for a code-runtime agent.');
1623
+ throw new errors_1.CliError('JavaScript is not supported for managed-loop agents. Use --type agentic for a code-runtime agent scaffold.');
1621
1624
  }
1622
1625
  // JS orchestrators are now supported via the orchagent-sdk npm package
1623
1626
  // Block --language for types that don't create runtime files
@@ -1683,7 +1686,7 @@ function registerInitCommand(program) {
1683
1686
  type: 'agent',
1684
1687
  description: 'Multi-platform support agent powered by Claude. Connects to Discord, Telegram, and/or Slack.',
1685
1688
  run_mode: 'always_on',
1686
- runtime: { command: 'python main.py' },
1689
+ runtime: { command: 'python3 main.py' },
1687
1690
  entrypoint: 'main.py',
1688
1691
  supported_providers: ['anthropic'],
1689
1692
  default_models: { anthropic: 'claude-sonnet-4-5-20250929' },
@@ -1731,7 +1734,7 @@ function registerInitCommand(program) {
1731
1734
  process.stdout.write(` ${s}. Edit config.py with your product name and description\n`);
1732
1735
  process.stdout.write(` ${s + 1}. Replace knowledge/ files with your own docs\n`);
1733
1736
  process.stdout.write(` ${s + 2}. Copy .env.example to .env and add platform tokens\n`);
1734
- process.stdout.write(` ${s + 3}. Test locally: pip install -r requirements.txt && python main.py\n`);
1737
+ process.stdout.write(` ${s + 3}. Test locally: pip install -r requirements.txt && python3 main.py\n`);
1735
1738
  process.stdout.write(` ${s + 4}. Deploy: orch publish && orch service deploy\n`);
1736
1739
  process.stdout.write(AGENT_BUILDER_HINT);
1737
1740
  return;
@@ -1901,7 +1904,7 @@ function registerInitCommand(program) {
1901
1904
  type: 'agent',
1902
1905
  description: `A ${templateLabel} orchestrator agent`,
1903
1906
  run_mode: runMode,
1904
- runtime: { command: isJavaScript ? 'node main.js' : 'python main.py' },
1907
+ runtime: { command: isJavaScript ? 'node main.js' : 'python3 main.py' },
1905
1908
  manifest: {
1906
1909
  manifest_version: 1,
1907
1910
  dependencies,
@@ -1974,7 +1977,7 @@ function registerInitCommand(program) {
1974
1977
  type: 'tool',
1975
1978
  description: 'A scheduled job that runs on a cron schedule',
1976
1979
  run_mode: 'on_demand',
1977
- runtime: { command: isJavaScript ? 'node main.js' : 'python main.py' },
1980
+ runtime: { command: isJavaScript ? 'node main.js' : 'python3 main.py' },
1978
1981
  required_secrets: [],
1979
1982
  tags: ['scheduled', 'cron'],
1980
1983
  };
@@ -2015,7 +2018,7 @@ function registerInitCommand(program) {
2015
2018
  process.stdout.write(` 1. cd ${name}\n`);
2016
2019
  }
2017
2020
  const mainFile = isJavaScript ? 'main.js' : 'main.py';
2018
- const testCmd = isJavaScript ? 'node main.js' : 'python main.py';
2021
+ const testCmd = isJavaScript ? 'node main.js' : 'python3 main.py';
2019
2022
  process.stdout.write(` ${stepNum}. Edit ${mainFile} with your job logic\n`);
2020
2023
  process.stdout.write(` ${stepNum + 1}. Test: echo '{}' | ${testCmd}\n`);
2021
2024
  process.stdout.write(` ${stepNum + 2}. Publish: orch publish\n`);
@@ -2037,7 +2040,7 @@ function registerInitCommand(program) {
2037
2040
  }
2038
2041
  }
2039
2042
  if (initMode.flavor !== 'code_runtime' && initMode.flavor !== 'orchestrator' && initMode.flavor !== 'discord' && runMode === 'always_on') {
2040
- throw new errors_1.CliError("run_mode=always_on requires runtime.command in orchagent.json (e.g. \"runtime\": { \"command\": \"python main.py\" }). Use --type tool for code-runtime agents.");
2043
+ throw new errors_1.CliError("run_mode=always_on requires runtime.command in orchagent.json (e.g. \"runtime\": { \"command\": \"python3 main.py\" }). Use --type tool or --type agentic for code-runtime agents.");
2041
2044
  }
2042
2045
  // Create manifest and type-specific files
2043
2046
  const manifest = JSON.parse(MANIFEST_TEMPLATE);
@@ -2051,7 +2054,7 @@ function registerInitCommand(program) {
2051
2054
  manifest.entrypoint = 'main.js';
2052
2055
  }
2053
2056
  else {
2054
- manifest.runtime = { command: 'python main.py' };
2057
+ manifest.runtime = { command: 'python3 main.py' };
2055
2058
  }
2056
2059
  manifest.manifest = {
2057
2060
  manifest_version: 1,
@@ -2070,7 +2073,7 @@ function registerInitCommand(program) {
2070
2073
  }
2071
2074
  else if (initMode.flavor === 'discord') {
2072
2075
  manifest.description = 'An always-on Discord bot powered by Claude';
2073
- manifest.runtime = { command: 'python main.py' };
2076
+ manifest.runtime = { command: 'python3 main.py' };
2074
2077
  manifest.supported_providers = ['anthropic'];
2075
2078
  manifest.required_secrets = ['ANTHROPIC_API_KEY', 'DISCORD_BOT_TOKEN', 'DISCORD_CHANNEL_IDS'];
2076
2079
  manifest.tags = ['discord', 'always-on'];
@@ -2082,7 +2085,7 @@ function registerInitCommand(program) {
2082
2085
  manifest.entrypoint = 'main.js';
2083
2086
  }
2084
2087
  else {
2085
- manifest.runtime = { command: 'python main.py' };
2088
+ manifest.runtime = { command: 'python3 main.py' };
2086
2089
  }
2087
2090
  manifest.required_secrets = [];
2088
2091
  }
@@ -2199,7 +2202,7 @@ function registerInitCommand(program) {
2199
2202
  process.stdout.write(` ${stepNum}. Create a Discord bot at https://discord.com/developers/applications\n`);
2200
2203
  process.stdout.write(` ${stepNum + 1}. Enable Message Content Intent in bot settings\n`);
2201
2204
  process.stdout.write(` ${stepNum + 2}. Copy .env.example to .env and fill in your tokens\n`);
2202
- process.stdout.write(` ${stepNum + 3}. Test locally: pip install -r requirements.txt && python main.py\n`);
2205
+ process.stdout.write(` ${stepNum + 3}. Test locally: pip install -r requirements.txt && python3 main.py\n`);
2203
2206
  process.stdout.write(` ${stepNum + 4}. Deploy: orch publish\n`);
2204
2207
  }
2205
2208
  else if (initMode.flavor === 'code_runtime') {
@@ -2209,7 +2212,7 @@ function registerInitCommand(program) {
2209
2212
  }
2210
2213
  if (runMode === 'always_on') {
2211
2214
  const mainFile = isJavaScript ? 'main.js' : 'main.py';
2212
- const testCmd = isJavaScript ? 'node main.js' : 'python main.py';
2215
+ const testCmd = isJavaScript ? 'node main.js' : 'python3 main.py';
2213
2216
  process.stdout.write(` ${stepNum}. Edit ${mainFile} with your service logic\n`);
2214
2217
  process.stdout.write(` ${stepNum + 1}. Test locally: ${testCmd}\n`);
2215
2218
  process.stdout.write(` ${stepNum + 2}. Publish: orch publish\n`);
@@ -2226,7 +2229,7 @@ function registerInitCommand(program) {
2226
2229
  const inputField = initMode.type === 'agent' ? 'task' : 'input';
2227
2230
  process.stdout.write(` ${stepNum}. Edit main.py with your agent logic\n`);
2228
2231
  process.stdout.write(` ${stepNum + 1}. Edit schema.json with your input/output schemas\n`);
2229
- process.stdout.write(` ${stepNum + 2}. Test: echo '{"${inputField}": "test"}' | python main.py\n`);
2232
+ process.stdout.write(` ${stepNum + 2}. Test: echo '{"${inputField}": "test"}' | python3 main.py\n`);
2230
2233
  process.stdout.write(` ${stepNum + 3}. Run: orchagent publish\n`);
2231
2234
  }
2232
2235
  }
@@ -10,24 +10,12 @@ const os_1 = __importDefault(require("os"));
10
10
  const config_1 = require("../lib/config");
11
11
  const api_1 = require("../lib/api");
12
12
  const errors_1 = require("../lib/errors");
13
+ const resolve_agent_1 = require("../lib/resolve-agent");
13
14
  const analytics_1 = require("../lib/analytics");
14
15
  const adapters_1 = require("../adapters");
15
16
  const skill_resolve_1 = require("../lib/skill-resolve");
16
17
  const installed_1 = require("../lib/installed");
17
18
  const agents_md_utils_1 = require("../lib/agents-md-utils");
18
- const DEFAULT_VERSION = 'latest';
19
- function parseAgentRef(value) {
20
- const [ref, versionPart] = value.split('@');
21
- const version = versionPart?.trim() || DEFAULT_VERSION;
22
- const segments = ref.split('/');
23
- if (segments.length === 1) {
24
- return { name: segments[0], version };
25
- }
26
- if (segments.length === 2) {
27
- return { org: segments[0], name: segments[1], version };
28
- }
29
- throw new errors_1.CliError('Invalid agent reference. Use org/agent or agent format.');
30
- }
31
19
  async function downloadAgentWithFallback(config, org, name, version, workspaceId) {
32
20
  // Fetch public metadata first to check if paid
33
21
  let publicMeta;
@@ -135,19 +123,21 @@ function registerInstallCommand(program) {
135
123
  errors: [],
136
124
  };
137
125
  const resolved = await (0, config_1.getResolvedConfig)();
138
- const parsed = parseAgentRef(agentArg);
139
- const configFile = await (0, config_1.loadConfig)();
140
- const org = parsed.org ?? configFile.workspace ?? resolved.defaultOrg;
141
- if (!org) {
142
- if (jsonMode) {
143
- result.errors.push('Missing org. Use org/agent format or set default org.');
126
+ let agentCtx;
127
+ try {
128
+ agentCtx = await (0, resolve_agent_1.resolveAgentContext)(agentArg, resolved);
129
+ }
130
+ catch (err) {
131
+ if (jsonMode && err instanceof errors_1.CliError) {
132
+ result.errors.push(err.message);
144
133
  process.stdout.write(JSON.stringify(result, null, 2) + '\n');
145
134
  process.exit(errors_1.ExitCodes.INVALID_INPUT);
146
135
  }
147
- throw new errors_1.CliError('Missing org. Use org/agent format or set default org.');
136
+ throw err;
148
137
  }
149
- result.agent = `${org}/${parsed.name}`;
150
- result.version = parsed.version;
138
+ const { org, agent: agentName, version, workspaceId } = agentCtx;
139
+ result.agent = `${org}/${agentName}`;
140
+ result.version = version;
151
141
  // Determine target formats
152
142
  let targetFormats = [];
153
143
  if (options.format) {
@@ -186,13 +176,11 @@ function registerInstallCommand(program) {
186
176
  throw new errors_1.CliError(errMsg);
187
177
  }
188
178
  result.scope = scope;
189
- // Resolve workspace context for the target org
190
- const workspaceId = await (0, api_1.resolveWorkspaceIdForOrg)(resolved, org);
191
179
  // Download agent
192
- log(`Fetching ${org}/${parsed.name}@${parsed.version}...\n`);
180
+ log(`Fetching ${org}/${agentName}@${version}...\n`);
193
181
  let agent;
194
182
  try {
195
- agent = await downloadAgentWithFallback(resolved, org, parsed.name, parsed.version, workspaceId);
183
+ agent = await downloadAgentWithFallback(resolved, org, agentName, version, workspaceId);
196
184
  }
197
185
  catch (err) {
198
186
  if (jsonMode) {
@@ -273,15 +261,15 @@ function registerInstallCommand(program) {
273
261
  catch {
274
262
  // File doesn't exist, will create new
275
263
  }
276
- const agentRef = `${org}/${parsed.name}`;
264
+ const agentRef = `${org}/${agentName}`;
277
265
  file.content = (0, agents_md_utils_1.mergeAgentsMdContent)(existingContent, file.content, agentRef);
278
266
  }
279
267
  await promises_1.default.writeFile(fullPath, file.content);
280
268
  filesWritten++;
281
269
  // Track installation
282
270
  const installedAgent = {
283
- agent: `${org}/${parsed.name}`,
284
- version: parsed.version,
271
+ agent: `${org}/${agentName}`,
272
+ version: version,
285
273
  format: formatId,
286
274
  scope: effectiveScope,
287
275
  path: fullPath,
@@ -297,7 +285,7 @@ function registerInstallCommand(program) {
297
285
  if (!options.dryRun) {
298
286
  if (filesWritten > 0) {
299
287
  await (0, analytics_1.track)('cli_agent_install', {
300
- agent: `${org}/${parsed.name}`,
288
+ agent: `${org}/${agentName}`,
301
289
  formats: targetFormats,
302
290
  scope,
303
291
  });
@@ -182,7 +182,7 @@ async function listRuns(config, workspaceId, agentName, options) {
182
182
  if (result.total > result.runs.length) {
183
183
  process.stdout.write(chalk_1.default.gray(`\nShowing ${result.runs.length} of ${result.total} runs. Use --limit to see more.\n`));
184
184
  }
185
- process.stdout.write(chalk_1.default.gray('\nView detailed logs for a run: orch logs <run-id>\n'));
185
+ process.stdout.write(chalk_1.default.gray('\nView detailed logs for a run: orch logs <run-id> · Replay a run: orch replay <run-id>\n'));
186
186
  }
187
187
  // ============================================
188
188
  // SHOW RUN LOGS
@@ -229,5 +229,7 @@ async function showRunLogs(config, workspaceId, runId, json) {
229
229
  process.stdout.write(chalk_1.default.gray('\nNo sandbox output available for this run. ' +
230
230
  'Execution logs are captured for agents with a code runtime (tool/agent types with runtime.command).\n'));
231
231
  }
232
+ // Footer hints
233
+ process.stdout.write(chalk_1.default.gray(`\nReplay: orch replay ${runId.slice(0, 8)} · Trace: orch trace ${runId.slice(0, 8)}\n`));
232
234
  process.stdout.write('\n');
233
235
  }
@@ -42,6 +42,7 @@ exports.scanUndeclaredEnvVars = scanUndeclaredEnvVars;
42
42
  exports.scanReservedPort = scanReservedPort;
43
43
  exports.detectSdkCompatible = detectSdkCompatible;
44
44
  exports.checkDependencies = checkDependencies;
45
+ exports.checkWorkspaceLlmKeys = checkWorkspaceLlmKeys;
45
46
  exports.batchPublish = batchPublish;
46
47
  exports.registerPublishCommand = registerPublishCommand;
47
48
  const promises_1 = __importDefault(require("fs/promises"));
@@ -394,7 +395,7 @@ function commandForEntrypoint(entrypoint) {
394
395
  if (entrypoint.endsWith('.js') || entrypoint.endsWith('.mjs') || entrypoint.endsWith('.cjs') || entrypoint.endsWith('.ts')) {
395
396
  return `node ${entrypoint}`;
396
397
  }
397
- return `python ${entrypoint}`;
398
+ return `python3 ${entrypoint}`;
398
399
  }
399
400
  /**
400
401
  * Check if manifest dependencies are published and callable.
@@ -439,13 +440,71 @@ async function checkDependencies(config, dependencies, publishingOrgSlug, worksp
439
440
  }
440
441
  catch (err) {
441
442
  if (err?.status === 404) {
442
- return { ref, status: 'not_found' };
443
+ // Could be unpublished OR published-but-private — we can't tell from a 404
444
+ return { ref, status: 'not_found_cross_org' };
443
445
  }
444
446
  // Network/unexpected error — don't false alarm
445
447
  return { ref, status: 'found_callable' };
446
448
  }
447
449
  }));
448
450
  }
451
+ /**
452
+ * Provider names the platform supports for LLM vault keys.
453
+ * Must stay in sync with gateway's _PROVIDER_TO_SECRET_NAME (db.py).
454
+ */
455
+ const PROVIDER_TO_SECRET_NAME = {
456
+ anthropic: 'ANTHROPIC_API_KEY',
457
+ openai: 'OPENAI_API_KEY',
458
+ gemini: 'GEMINI_API_KEY',
459
+ };
460
+ /**
461
+ * After a successful publish, check whether the target workspace has LLM vault
462
+ * keys that match the agent's supported_providers. If not, print a warning so
463
+ * the user knows cloud runs will fail.
464
+ *
465
+ * Best-effort: API errors are silently swallowed (the agent is already published).
466
+ */
467
+ async function checkWorkspaceLlmKeys(config, workspaceId, workspaceSlug, executionEngine, supportedProviders) {
468
+ // Only direct_llm and managed_loop engines need LLM keys
469
+ if (executionEngine !== 'direct_llm' && executionEngine !== 'managed_loop') {
470
+ return;
471
+ }
472
+ let secrets;
473
+ try {
474
+ const result = await (0, api_1.request)(config, 'GET', `/workspaces/${workspaceId}/secrets`);
475
+ secrets = result.secrets;
476
+ if (!Array.isArray(secrets))
477
+ return;
478
+ }
479
+ catch {
480
+ return; // Can't reach API or unexpected response — skip warning silently
481
+ }
482
+ const llmKeys = secrets.filter(s => s.secret_type === 'llm_key' && s.llm_provider);
483
+ const availableProviders = new Set(llmKeys.map(s => s.llm_provider));
484
+ // Determine which providers the agent needs
485
+ const needsAny = supportedProviders.includes('any');
486
+ if (needsAny) {
487
+ // 'any' means any single provider works
488
+ if (availableProviders.size > 0)
489
+ return;
490
+ }
491
+ else {
492
+ // Check if at least one of the supported providers has a key
493
+ const hasMatch = supportedProviders.some(p => availableProviders.has(p));
494
+ if (hasMatch)
495
+ return;
496
+ }
497
+ // Build the warning message
498
+ const providerList = needsAny
499
+ ? Object.keys(PROVIDER_TO_SECRET_NAME).join(', ')
500
+ : supportedProviders.join(', ');
501
+ const exampleSecretName = needsAny
502
+ ? 'ANTHROPIC_API_KEY'
503
+ : (PROVIDER_TO_SECRET_NAME[supportedProviders[0]] || `${supportedProviders[0].toUpperCase()}_API_KEY`);
504
+ process.stderr.write(chalk_1.default.yellow(`\n⚠ No LLM vault keys found in workspace '${workspaceSlug}' for providers: ${providerList}\n`) +
505
+ ` Cloud runs will fail until you add keys.\n` +
506
+ ` Add a key: ${chalk_1.default.cyan(`orch secrets set ${exampleSecretName} <key>`)}\n`);
507
+ }
449
508
  /**
450
509
  * Batch publish all agents found in subdirectories, in dependency order.
451
510
  * Discovers orchagent.json/SKILL.md in immediate subdirectories,
@@ -510,6 +569,8 @@ async function batchPublish(rootDir, options) {
510
569
  forwardArgs.push('--no-local-download');
511
570
  if (options.requiredSecrets === false)
512
571
  forwardArgs.push('--no-required-secrets');
572
+ if (options.verbose)
573
+ forwardArgs.push('--verbose');
513
574
  const results = [];
514
575
  for (let i = 0; i < sorted.length; i++) {
515
576
  const agent = sorted[i];
@@ -586,6 +647,7 @@ function registerPublishCommand(program) {
586
647
  .option('--no-local-download', 'Prevent users from downloading and running locally (default: allowed)')
587
648
  .option('--no-required-secrets', '(deprecated) No longer needed — required_secrets defaults to []')
588
649
  .option('--all', 'Publish all agents in subdirectories (dependency order)')
650
+ .option('--verbose', 'Show detailed bundle contents (file list)')
589
651
  .action(async (options) => {
590
652
  const cwd = process.cwd();
591
653
  // --all: batch publish all agents in subdirectories
@@ -696,6 +758,11 @@ function registerPublishCommand(program) {
696
758
  process.stdout.write(`\n${chalk_1.default.green('✔')} Published ${org.slug}/${skillData.frontmatter.name}@${skillVersion} successfully!\n\n`);
697
759
  if (hasMultipleFiles) {
698
760
  process.stdout.write(`Files: ${skillFiles.length} files included\n`);
761
+ if (options.verbose) {
762
+ for (const f of skillFiles) {
763
+ process.stdout.write(` ${f.path}\n`);
764
+ }
765
+ }
699
766
  }
700
767
  process.stdout.write(`Visibility: private\n`);
701
768
  process.stdout.write(`\nView analytics and usage: https://orchagent.io/dashboard\n`);
@@ -971,6 +1038,7 @@ function registerPublishCommand(program) {
971
1038
  if (manifestDeps?.length) {
972
1039
  const depResults = await checkDependencies(config, manifestDeps, org.slug, workspaceId);
973
1040
  const notFound = depResults.filter(r => r.status === 'not_found');
1041
+ const notFoundCrossOrg = depResults.filter(r => r.status === 'not_found_cross_org');
974
1042
  const notCallable = depResults.filter(r => r.status === 'found_not_callable');
975
1043
  if (notFound.length > 0) {
976
1044
  process.stderr.write(chalk_1.default.yellow(`\n⚠ Unpublished dependencies:\n`));
@@ -980,6 +1048,14 @@ function registerPublishCommand(program) {
980
1048
  process.stderr.write(`\n These agents must be published before this orchestrator can call them.\n` +
981
1049
  ` Publish each dependency first, then re-run this publish.\n\n`);
982
1050
  }
1051
+ if (notFoundCrossOrg.length > 0) {
1052
+ process.stderr.write(chalk_1.default.yellow(`\n⚠ Dependencies not found (unpublished or not accessible from this workspace):\n`));
1053
+ for (const dep of notFoundCrossOrg) {
1054
+ process.stderr.write(chalk_1.default.yellow(` - ${dep.ref}\n`));
1055
+ }
1056
+ process.stderr.write(`\n If the dependency is published in another workspace, ensure it's in the same\n` +
1057
+ ` workspace as this orchestrator, or use agent access grants.\n\n`);
1058
+ }
983
1059
  if (notCallable.length > 0) {
984
1060
  process.stderr.write(chalk_1.default.yellow(`\n⚠ Dependencies have callable: false:\n`));
985
1061
  for (const dep of notCallable) {
@@ -990,6 +1066,23 @@ function registerPublishCommand(program) {
990
1066
  ` the field to use the default) and republish each dependency.\n\n`);
991
1067
  }
992
1068
  }
1069
+ // UX-13-02: Warn when managed-loop orchestrator has dependencies but no custom_tools.
1070
+ // Without custom_tools, the LLM has no way to call its declared dependencies.
1071
+ if (executionEngine === 'managed_loop' && manifestDeps?.length) {
1072
+ const mergedTools = Array.isArray(loopConfig?.custom_tools) ? loopConfig.custom_tools : [];
1073
+ if (mergedTools.length === 0) {
1074
+ process.stderr.write(chalk_1.default.yellow(`\n⚠ This managed-loop agent declares dependencies but no custom_tools.\n` +
1075
+ ` Without custom_tools, the LLM cannot call dependencies at runtime —\n` +
1076
+ ` it will waste turns exploring the filesystem instead.\n\n` +
1077
+ ` Use 'orch scaffold orchestration' to auto-generate the correct\n` +
1078
+ ` custom_tools configuration, or add them manually to your loop config:\n\n` +
1079
+ ` "loop": {\n` +
1080
+ ` "custom_tools": [\n` +
1081
+ ` { "name": "call_worker", "description": "...", "command": "python3 /home/user/helpers/orch_call.py org/worker@v1" }\n` +
1082
+ ` ]\n` +
1083
+ ` }\n\n`));
1084
+ }
1085
+ }
993
1086
  // UX-2: Default required_secrets to [] when omitted for tool/agent types.
994
1087
  // Prompt and skill types are exempt (prompt agents get LLM keys from platform,
995
1088
  // skills don't run standalone).
@@ -1061,6 +1154,12 @@ function registerPublishCommand(program) {
1061
1154
  process.stderr.write(` Files: ${bundlePreview.fileCount} files\n`);
1062
1155
  process.stderr.write(` Size: ${(bundlePreview.totalSizeBytes / 1024).toFixed(1)} KB\n`);
1063
1156
  process.stderr.write(` Entrypoint: ${bundlePreview.entrypoint}\n`);
1157
+ if (options.verbose && bundlePreview.files?.length) {
1158
+ process.stderr.write(` Bundled files:\n`);
1159
+ for (const f of bundlePreview.files) {
1160
+ process.stderr.write(` ${f}\n`);
1161
+ }
1162
+ }
1064
1163
  }
1065
1164
  process.stderr.write('\nAgent Preview:\n');
1066
1165
  process.stderr.write(` Name: ${manifest.name}\n`);
@@ -1324,6 +1423,12 @@ function registerPublishCommand(program) {
1324
1423
  skipEntrypointCheck: false,
1325
1424
  });
1326
1425
  process.stdout.write(` Created bundle: ${bundleResult.fileCount} files, ${(bundleResult.sizeBytes / 1024).toFixed(1)}KB\n`);
1426
+ if (options.verbose && bundleResult.files?.length) {
1427
+ process.stdout.write(` Bundled files:\n`);
1428
+ for (const f of bundleResult.files) {
1429
+ process.stdout.write(` ${f}\n`);
1430
+ }
1431
+ }
1327
1432
  // Validate bundle size
1328
1433
  const validation = await (0, bundle_1.validateBundle)(bundlePath);
1329
1434
  if (!validation.valid) {
@@ -1389,6 +1494,8 @@ function registerPublishCommand(program) {
1389
1494
  process.stderr.write(chalk_1.default.yellow(`⚠ ${warning}\n`));
1390
1495
  }
1391
1496
  }
1497
+ // Warn if workspace has no LLM vault keys for this agent's providers (UX-13-01)
1498
+ await checkWorkspaceLlmKeys(config, workspaceId || org.id, org.slug, executionEngine, supportedProviders);
1392
1499
  // Show required secrets with setup instructions (F-18)
1393
1500
  if (manifest.required_secrets?.length) {
1394
1501
  process.stdout.write(`\nRequired secrets:\n`);
@@ -1404,14 +1511,20 @@ function registerPublishCommand(program) {
1404
1511
  // Show security review result if available
1405
1512
  const secReview = result.security_review;
1406
1513
  if (secReview?.verdict) {
1407
- if (secReview.verdict === 'passed') {
1514
+ if (secReview.verdict === 'approved') {
1408
1515
  process.stdout.write(`Security: ${chalk_1.default.green('passed')}\n`);
1409
1516
  }
1410
1517
  else if (secReview.verdict === 'flagged') {
1411
1518
  process.stdout.write(`Security: ${chalk_1.default.yellow('flagged')} — ${secReview.summary || 'review recommended'}\n`);
1412
1519
  }
1520
+ else if (secReview.verdict === 'error') {
1521
+ process.stdout.write(`Security: ${chalk_1.default.gray('review unavailable')} — publish succeeded, review will be retried\n`);
1522
+ }
1523
+ else if (secReview.verdict === 'skipped') {
1524
+ process.stdout.write(`Security: ${chalk_1.default.gray('review skipped')} — ${secReview.summary || 'content not eligible for review'}\n`);
1525
+ }
1413
1526
  else {
1414
- process.stdout.write(`Security: ${secReview.verdict}\n`);
1527
+ process.stdout.write(`Security: ${chalk_1.default.gray(secReview.verdict)} — ${secReview.summary || ''}\n`);
1415
1528
  }
1416
1529
  }
1417
1530
  if (result.service_key) {