@orchagent/cli 0.3.58 → 0.3.60

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.
@@ -37,7 +37,6 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
37
37
  };
38
38
  Object.defineProperty(exports, "__esModule", { value: true });
39
39
  exports.registerGitHubCommand = registerGitHubCommand;
40
- const http_1 = __importDefault(require("http"));
41
40
  const open_1 = __importDefault(require("open"));
42
41
  const cli_table3_1 = __importDefault(require("cli-table3"));
43
42
  const chalk_1 = __importDefault(require("chalk"));
@@ -47,195 +46,10 @@ const api_1 = require("../lib/api");
47
46
  const errors_1 = require("../lib/errors");
48
47
  const analytics_1 = require("../lib/analytics");
49
48
  const output_1 = require("../lib/output");
50
- const DEFAULT_AUTH_PORT = 8375;
51
- const AUTH_TIMEOUT_MS = 5 * 60 * 1000; // 5 minutes
49
+ const POLL_INTERVAL_MS = 1500;
50
+ const POLL_TIMEOUT_MS = 5 * 60 * 1000; // 5 minutes
51
+ const SETTINGS_REDIRECT_BASE = 'https://orchagent.io/settings';
52
52
  // Helper functions
53
- function successHtml() {
54
- return `<!DOCTYPE html>
55
- <html>
56
- <head>
57
- <meta charset="utf-8">
58
- <title>orchagent CLI - GitHub Connected</title>
59
- <style>
60
- body {
61
- font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
62
- display: flex;
63
- align-items: center;
64
- justify-content: center;
65
- min-height: 100vh;
66
- margin: 0;
67
- background: #0a0a0a;
68
- color: #fafafa;
69
- }
70
- .container {
71
- text-align: center;
72
- padding: 2rem;
73
- }
74
- .icon {
75
- width: 64px;
76
- height: 64px;
77
- background: rgba(34, 197, 94, 0.1);
78
- border-radius: 50%;
79
- display: flex;
80
- align-items: center;
81
- justify-content: center;
82
- margin: 0 auto 1.5rem;
83
- }
84
- .icon svg {
85
- width: 32px;
86
- height: 32px;
87
- color: #22c55e;
88
- }
89
- h1 {
90
- font-size: 1.5rem;
91
- margin: 0 0 0.5rem;
92
- }
93
- p {
94
- color: #a1a1aa;
95
- margin: 0;
96
- }
97
- </style>
98
- </head>
99
- <body>
100
- <div class="container">
101
- <div class="icon">
102
- <svg fill="none" stroke="currentColor" viewBox="0 0 24 24">
103
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"/>
104
- </svg>
105
- </div>
106
- <h1>GitHub Connected</h1>
107
- <p>You can close this tab and return to your terminal.</p>
108
- </div>
109
- </body>
110
- </html>`;
111
- }
112
- function errorHtml(message) {
113
- return `<!DOCTYPE html>
114
- <html>
115
- <head>
116
- <meta charset="utf-8">
117
- <title>orchagent CLI - GitHub Connection Error</title>
118
- <style>
119
- body {
120
- font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
121
- display: flex;
122
- align-items: center;
123
- justify-content: center;
124
- min-height: 100vh;
125
- margin: 0;
126
- background: #0a0a0a;
127
- color: #fafafa;
128
- }
129
- .container {
130
- text-align: center;
131
- padding: 2rem;
132
- }
133
- .icon {
134
- width: 64px;
135
- height: 64px;
136
- background: rgba(239, 68, 68, 0.1);
137
- border-radius: 50%;
138
- display: flex;
139
- align-items: center;
140
- justify-content: center;
141
- margin: 0 auto 1.5rem;
142
- }
143
- .icon svg {
144
- width: 32px;
145
- height: 32px;
146
- color: #ef4444;
147
- }
148
- h1 {
149
- font-size: 1.5rem;
150
- margin: 0 0 0.5rem;
151
- }
152
- p {
153
- color: #a1a1aa;
154
- margin: 0;
155
- }
156
- </style>
157
- </head>
158
- <body>
159
- <div class="container">
160
- <div class="icon">
161
- <svg fill="none" stroke="currentColor" viewBox="0 0 24 24">
162
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"/>
163
- </svg>
164
- </div>
165
- <h1>Connection Error</h1>
166
- <p>${message}</p>
167
- </div>
168
- </body>
169
- </html>`;
170
- }
171
- async function waitForGitHubCallback(port, timeoutMs) {
172
- return new Promise((resolve, reject) => {
173
- let resolved = false;
174
- let server = null;
175
- const cleanup = () => {
176
- if (server) {
177
- server.close();
178
- server = null;
179
- }
180
- };
181
- const timeout = setTimeout(() => {
182
- if (!resolved) {
183
- resolved = true;
184
- cleanup();
185
- reject(new errors_1.CliError('GitHub authentication timed out. Please try again.'));
186
- }
187
- }, timeoutMs);
188
- server = http_1.default.createServer((req, res) => {
189
- const url = new URL(req.url || '/', `http://127.0.0.1:${port}`);
190
- if (url.pathname !== '/callback') {
191
- res.writeHead(404);
192
- res.end('Not Found');
193
- return;
194
- }
195
- const code = url.searchParams.get('code');
196
- const state = url.searchParams.get('state');
197
- const error = url.searchParams.get('error');
198
- if (error) {
199
- res.writeHead(400, { 'Content-Type': 'text/html' });
200
- res.end(errorHtml(url.searchParams.get('error_description') || error));
201
- if (!resolved) {
202
- resolved = true;
203
- clearTimeout(timeout);
204
- cleanup();
205
- reject(new errors_1.CliError(`GitHub authorization failed: ${error}`));
206
- }
207
- return;
208
- }
209
- if (!code || !state) {
210
- res.writeHead(400, { 'Content-Type': 'text/html' });
211
- res.end(errorHtml('Missing code or state parameter'));
212
- return;
213
- }
214
- res.writeHead(200, { 'Content-Type': 'text/html' });
215
- res.end(successHtml());
216
- if (!resolved) {
217
- resolved = true;
218
- clearTimeout(timeout);
219
- cleanup();
220
- resolve({ code, state });
221
- }
222
- });
223
- server.on('error', (err) => {
224
- if (!resolved) {
225
- resolved = true;
226
- clearTimeout(timeout);
227
- cleanup();
228
- if (err.code === 'EADDRINUSE') {
229
- reject(new errors_1.CliError(`Port ${port} is already in use. Try a different port with --port.`));
230
- }
231
- else {
232
- reject(new errors_1.CliError(`Failed to start auth server: ${err.message}`));
233
- }
234
- }
235
- });
236
- server.listen(port, '127.0.0.1');
237
- });
238
- }
239
53
  async function promptForSelection(items) {
240
54
  if (!process.stdin.isTTY) {
241
55
  return null;
@@ -271,38 +85,54 @@ async function promptForSelection(items) {
271
85
  });
272
86
  });
273
87
  }
88
+ function sleep(ms) {
89
+ return new Promise((resolve) => setTimeout(resolve, ms));
90
+ }
274
91
  // Command implementations
275
- async function connectGitHub(config, port) {
276
- const redirectUri = `http://127.0.0.1:${port}/callback`;
277
- // Step 1: Initialize the GitHub OAuth flow
278
- const initResponse = await (0, api_1.request)(config, 'POST', '/github/connect/init', {
279
- body: JSON.stringify({ redirect_uri: redirectUri }),
92
+ async function connectGitHub(config) {
93
+ // Step 1: Initialize the GitHub App install flow
94
+ const initResponse = await (0, api_1.request)(config, 'POST', '/github/install/init', {
95
+ body: JSON.stringify({ redirect_uri: SETTINGS_REDIRECT_BASE }),
280
96
  headers: { 'Content-Type': 'application/json' },
281
97
  });
282
- // Step 2: Start local server to receive callback
283
- const callbackPromise = waitForGitHubCallback(port, AUTH_TIMEOUT_MS);
284
- // Step 3: Open browser
285
- process.stdout.write('Opening browser for GitHub authentication...\n');
98
+ // Step 2: Open browser for GitHub App installation
99
+ process.stdout.write('Opening browser to install the GitHub App...\n');
286
100
  try {
287
- await (0, open_1.default)(initResponse.auth_url);
101
+ await (0, open_1.default)(initResponse.install_url);
288
102
  }
289
103
  catch {
290
- process.stdout.write(`\nPlease open this URL in your browser:\n${initResponse.auth_url}\n\n`);
104
+ // Headless or browser unavailable - print URL for manual copy/paste
105
+ process.stdout.write(`\nCould not open browser automatically.\n`);
106
+ process.stdout.write(`Please open this URL in your browser:\n\n`);
107
+ process.stdout.write(` ${initResponse.install_url}\n\n`);
291
108
  }
292
- // Step 4: Wait for callback
293
- const { code, state } = await callbackPromise;
294
- // Step 5: Exchange code for connection
295
- const callbackResponse = await (0, api_1.request)(config, 'POST', '/github/connect/callback', {
296
- body: JSON.stringify({ code, state }),
297
- headers: { 'Content-Type': 'application/json' },
298
- });
299
- await (0, analytics_1.track)('cli_github_connect', { success: true });
300
- process.stdout.write(`\nConnected to GitHub as ${chalk_1.default.bold(callbackResponse.username)}\n`);
109
+ // Step 3: Poll for completion
110
+ process.stdout.write('Waiting for GitHub App installation...\n');
111
+ const startTime = Date.now();
112
+ while (Date.now() - startTime < POLL_TIMEOUT_MS) {
113
+ await sleep(POLL_INTERVAL_MS);
114
+ const status = await (0, api_1.request)(config, 'GET', `/github/install/status?state=${encodeURIComponent(initResponse.state)}`);
115
+ if (status.status === 'completed') {
116
+ await (0, analytics_1.track)('cli_github_connect', { success: true });
117
+ process.stdout.write('\n');
118
+ process.stdout.write(`Connected to GitHub as ${chalk_1.default.bold(status.github_account_login)}\n`);
119
+ if (status.github_account_type) {
120
+ process.stdout.write(` Type: ${status.github_account_type}\n`);
121
+ }
122
+ return;
123
+ }
124
+ if (status.status === 'failed') {
125
+ await (0, analytics_1.track)('cli_github_connect', { success: false });
126
+ throw new errors_1.CliError(`GitHub App installation failed: ${status.error_message || 'Unknown error'}`);
127
+ }
128
+ // Still pending - continue polling
129
+ }
130
+ throw new errors_1.CliError('GitHub App installation timed out after 5 minutes. Please try again.');
301
131
  }
302
132
  async function disconnectGitHub(config) {
303
- await (0, api_1.request)(config, 'DELETE', '/github/disconnect');
133
+ await (0, api_1.request)(config, 'DELETE', '/github/uninstall');
304
134
  await (0, analytics_1.track)('cli_github_disconnect');
305
- process.stdout.write('Disconnected from GitHub.\n');
135
+ process.stdout.write('GitHub App uninstalled.\n');
306
136
  }
307
137
  async function getGitHubStatus(config, json) {
308
138
  const connection = await (0, api_1.request)(config, 'GET', '/github/connection');
@@ -317,52 +147,19 @@ async function getGitHubStatus(config, json) {
317
147
  }
318
148
  process.stdout.write(`GitHub Status:\n\n`);
319
149
  process.stdout.write(` Connected: ${chalk_1.default.green('Yes')}\n`);
320
- process.stdout.write(` Username: ${chalk_1.default.bold(connection.username)}\n`);
321
- if (connection.connected_at) {
322
- const date = new Date(connection.connected_at).toLocaleDateString();
150
+ process.stdout.write(` Account: ${chalk_1.default.bold(connection.github_account_login)}\n`);
151
+ if (connection.github_account_type) {
152
+ process.stdout.write(` Type: ${connection.github_account_type === 'User' ? 'User' : 'Organization'}\n`);
153
+ }
154
+ if (connection.installed_at) {
155
+ const date = new Date(connection.installed_at).toLocaleDateString();
323
156
  process.stdout.write(` Since: ${date}\n`);
324
157
  }
325
- if (connection.scopes?.length) {
326
- process.stdout.write(` Scopes: ${connection.scopes.join(', ')}\n`);
158
+ if (connection.suspended_at) {
159
+ process.stdout.write(` Status: ${chalk_1.default.yellow('Suspended')} (since ${new Date(connection.suspended_at).toLocaleDateString()})\n`);
327
160
  }
328
161
  process.stdout.write('\n');
329
162
  }
330
- async function listGitHubRepos(config, search, json) {
331
- const params = new URLSearchParams();
332
- if (search) {
333
- params.append('search', search);
334
- }
335
- const queryStr = params.toString();
336
- const repos = await (0, api_1.request)(config, 'GET', `/github/repos${queryStr ? `?${queryStr}` : ''}`);
337
- await (0, analytics_1.track)('cli_github_list', { search: !!search, count: repos.length });
338
- if (json) {
339
- (0, output_1.printJson)(repos);
340
- return;
341
- }
342
- if (repos.length === 0) {
343
- process.stdout.write('No repositories found.\n');
344
- if (search) {
345
- process.stdout.write(`\nTry a different search term or run without --search to see all repos.\n`);
346
- }
347
- return;
348
- }
349
- const table = new cli_table3_1.default({
350
- head: [
351
- chalk_1.default.bold('Repository'),
352
- chalk_1.default.bold('Private'),
353
- chalk_1.default.bold('Last Pushed'),
354
- ],
355
- });
356
- repos.forEach((repo) => {
357
- const visibility = repo.private ? chalk_1.default.yellow('Yes') : chalk_1.default.green('No');
358
- const pushed = repo.pushed_at
359
- ? new Date(repo.pushed_at).toLocaleDateString()
360
- : '-';
361
- table.push([repo.full_name, visibility, pushed]);
362
- });
363
- process.stdout.write(`${table.toString()}\n`);
364
- process.stdout.write(`\nFound ${repos.length} repositor${repos.length === 1 ? 'y' : 'ies'}.\n`);
365
- }
366
163
  async function scanGitHubRepo(config, repo, json) {
367
164
  // Parse owner/repo format
368
165
  const parts = repo.split('/');
@@ -371,7 +168,8 @@ async function scanGitHubRepo(config, repo, json) {
371
168
  `Use owner/repo format, e.g.: orchagent github scan myorg/myrepo`);
372
169
  }
373
170
  const [owner, repoName] = parts;
374
- const results = await (0, api_1.request)(config, 'GET', `/github/repos/${owner}/${repoName}/scan`);
171
+ const response = await (0, api_1.request)(config, 'GET', `/github/repos/${owner}/${repoName}/scan`);
172
+ const results = response.items;
375
173
  await (0, analytics_1.track)('cli_github_scan', { repo, found: results.length });
376
174
  if (json) {
377
175
  (0, output_1.printJson)(results);
@@ -392,7 +190,7 @@ async function scanGitHubRepo(config, repo, json) {
392
190
  ],
393
191
  });
394
192
  results.forEach((item) => {
395
- const typeLabel = item.type === 'skill' ? chalk_1.default.cyan('skill') : chalk_1.default.magenta('agent');
193
+ const typeLabel = item.type === 'skill' ? chalk_1.default.cyan('skill') : chalk_1.default.magenta(item.type);
396
194
  table.push([typeLabel, item.path, item.name || '-']);
397
195
  });
398
196
  process.stdout.write(`${table.toString()}\n`);
@@ -401,13 +199,12 @@ async function scanGitHubRepo(config, repo, json) {
401
199
  return results;
402
200
  }
403
201
  async function importFromGitHub(config, repo, options) {
404
- // Parse owner/repo format
202
+ // Validate owner/repo format
405
203
  const parts = repo.split('/');
406
204
  if (parts.length !== 2) {
407
205
  throw new errors_1.CliError(`Invalid repository format: ${repo}\n\n` +
408
206
  `Use owner/repo format, e.g.: orchagent github import myorg/myrepo`);
409
207
  }
410
- const [owner, repoName] = parts;
411
208
  let selectedPath = options.path;
412
209
  // If no path specified, scan first and let user choose
413
210
  if (!selectedPath) {
@@ -433,8 +230,7 @@ async function importFromGitHub(config, repo, options) {
433
230
  const isPublic = options.private ? false : true;
434
231
  const importResult = await (0, api_1.request)(config, 'POST', '/github/import', {
435
232
  body: JSON.stringify({
436
- owner,
437
- repo: repoName,
233
+ repo,
438
234
  path: selectedPath,
439
235
  is_public: isPublic,
440
236
  name: options.name,
@@ -445,21 +241,48 @@ async function importFromGitHub(config, repo, options) {
445
241
  repo,
446
242
  path: selectedPath,
447
243
  is_public: isPublic,
448
- type: importResult.type,
244
+ type: importResult.agent.type,
449
245
  });
450
246
  if (options.json) {
451
247
  (0, output_1.printJson)(importResult);
452
248
  return;
453
249
  }
454
250
  process.stdout.write('\n');
455
- process.stdout.write(`Imported ${chalk_1.default.bold(importResult.name)} from ${repo}\n`);
251
+ process.stdout.write(`Imported ${chalk_1.default.bold(importResult.agent.name)} from ${repo}\n`);
456
252
  process.stdout.write('\n');
457
- process.stdout.write(` Agent: ${importResult.org_slug}/${importResult.name}\n`);
458
- process.stdout.write(` Version: ${importResult.version}\n`);
459
- process.stdout.write(` Type: ${importResult.type}\n`);
253
+ process.stdout.write(` Agent: ${importResult.agent.name}\n`);
254
+ process.stdout.write(` Version: ${importResult.agent.version}\n`);
255
+ process.stdout.write(` Type: ${importResult.agent.type}\n`);
460
256
  process.stdout.write(` Public: ${isPublic ? chalk_1.default.green('Yes') : chalk_1.default.yellow('No')}\n`);
461
257
  process.stdout.write('\n');
462
- process.stdout.write(`View at: https://orchagent.io/${importResult.org_slug}/${importResult.name}\n`);
258
+ }
259
+ async function getSyncConfig(config, agentId, options) {
260
+ // If --set-auto-publish is specified, update config first
261
+ if (options.setAutoPublish !== undefined) {
262
+ const autoPublish = options.setAutoPublish === 'true';
263
+ await (0, api_1.request)(config, 'PATCH', `/github/agents/${agentId}/sync-config`, {
264
+ body: JSON.stringify({ auto_publish: autoPublish }),
265
+ headers: { 'Content-Type': 'application/json' },
266
+ });
267
+ process.stdout.write(`Updated auto_publish to ${chalk_1.default.bold(String(autoPublish))} for agent ${agentId}\n`);
268
+ return;
269
+ }
270
+ const syncConfig = await (0, api_1.request)(config, 'GET', `/github/agents/${agentId}/sync-config`);
271
+ if (options.json) {
272
+ (0, output_1.printJson)(syncConfig);
273
+ return;
274
+ }
275
+ process.stdout.write(`Sync Config for ${chalk_1.default.bold(agentId)}:\n\n`);
276
+ process.stdout.write(` Auto-publish: ${syncConfig.auto_publish ? chalk_1.default.green('enabled') : chalk_1.default.yellow('disabled')}\n`);
277
+ if (syncConfig.sync_status) {
278
+ process.stdout.write(` Sync status: ${syncConfig.sync_status}\n`);
279
+ }
280
+ process.stdout.write('\n');
281
+ }
282
+ async function approveSync(config, agentId) {
283
+ await (0, api_1.request)(config, 'POST', `/github/agents/${agentId}/approve`);
284
+ await (0, analytics_1.track)('cli_github_approve', { agent_id: agentId });
285
+ process.stdout.write(`Approved pending sync for agent ${chalk_1.default.bold(agentId)}.\n`);
463
286
  }
464
287
  // Command registration
465
288
  function registerGitHubCommand(program) {
@@ -468,22 +291,17 @@ function registerGitHubCommand(program) {
468
291
  .description('Connect to GitHub and import agents');
469
292
  github
470
293
  .command('connect')
471
- .description('Connect your GitHub account via browser OAuth')
472
- .option('--port <port>', `Localhost port for callback (default: ${DEFAULT_AUTH_PORT})`, String(DEFAULT_AUTH_PORT))
473
- .action(async (options) => {
294
+ .description('Install the GitHub App to connect your account')
295
+ .action(async () => {
474
296
  const config = await (0, config_1.getResolvedConfig)();
475
297
  if (!config.apiKey) {
476
298
  throw new errors_1.CliError('Missing API key. Run `orchagent login` first.');
477
299
  }
478
- const port = parseInt(options.port || String(DEFAULT_AUTH_PORT), 10);
479
- if (isNaN(port) || port < 1024 || port > 65535) {
480
- throw new errors_1.CliError('Port must be a number between 1024 and 65535');
481
- }
482
- await connectGitHub(config, port);
300
+ await connectGitHub(config);
483
301
  });
484
302
  github
485
303
  .command('disconnect')
486
- .description('Remove GitHub connection')
304
+ .description('Remove GitHub App installation')
487
305
  .action(async () => {
488
306
  const config = await (0, config_1.getResolvedConfig)();
489
307
  if (!config.apiKey) {
@@ -502,18 +320,6 @@ function registerGitHubCommand(program) {
502
320
  }
503
321
  await getGitHubStatus(config, options.json || false);
504
322
  });
505
- github
506
- .command('list')
507
- .description('List accessible GitHub repositories')
508
- .option('--search <query>', 'Filter repositories by name')
509
- .option('--json', 'Output raw JSON')
510
- .action(async (options) => {
511
- const config = await (0, config_1.getResolvedConfig)();
512
- if (!config.apiKey) {
513
- throw new errors_1.CliError('Missing API key. Run `orchagent login` first.');
514
- }
515
- await listGitHubRepos(config, options.search, options.json || false);
516
- });
517
323
  github
518
324
  .command('scan <repo>')
519
325
  .description('Scan a repository for agents and skills')
@@ -540,4 +346,29 @@ function registerGitHubCommand(program) {
540
346
  }
541
347
  await importFromGitHub(config, repo, options);
542
348
  });
349
+ github
350
+ .command('sync-config <agent>')
351
+ .description('View or update sync configuration for a GitHub-linked agent')
352
+ .option('--set-auto-publish <value>', 'Set auto_publish (true or false)')
353
+ .option('--json', 'Output raw JSON')
354
+ .action(async (agent, options) => {
355
+ const config = await (0, config_1.getResolvedConfig)();
356
+ if (!config.apiKey) {
357
+ throw new errors_1.CliError('Missing API key. Run `orchagent login` first.');
358
+ }
359
+ if (options.setAutoPublish !== undefined && options.setAutoPublish !== 'true' && options.setAutoPublish !== 'false') {
360
+ throw new errors_1.CliError('--set-auto-publish must be "true" or "false"');
361
+ }
362
+ await getSyncConfig(config, agent, options);
363
+ });
364
+ github
365
+ .command('approve <agent_id>')
366
+ .description('Approve a pending GitHub sync for an agent')
367
+ .action(async (agentId) => {
368
+ const config = await (0, config_1.getResolvedConfig)();
369
+ if (!config.apiKey) {
370
+ throw new errors_1.CliError('Missing API key. Run `orchagent login` first.');
371
+ }
372
+ await approveSync(config, agentId);
373
+ });
543
374
  }
@@ -248,7 +248,7 @@ function registerInitCommand(program) {
248
248
  }
249
249
  }
250
250
  if (initMode.flavor === 'direct_llm' && runMode === 'always_on') {
251
- throw new errors_1.CliError("run_mode=always_on requires a non-direct runtime. Use legacy '--type tool' or add runtime.command in orchagent.json.");
251
+ throw new errors_1.CliError("run_mode=always_on requires runtime.command in orchagent.json (e.g. \"runtime\": { \"command\": \"python main.py\" }).");
252
252
  }
253
253
  // Create manifest and type-specific files
254
254
  const manifest = JSON.parse(MANIFEST_TEMPLATE);
@@ -36,6 +36,11 @@ function resolveEngine(data) {
36
36
  if (ee === 'direct_llm' || ee === 'managed_loop' || ee === 'code_runtime') {
37
37
  return ee;
38
38
  }
39
+ const runtimeCommand = data.runtime?.command;
40
+ if (typeof runtimeCommand === 'string' && runtimeCommand.trim())
41
+ return 'code_runtime';
42
+ if (data.loop && Object.keys(data.loop).length > 0)
43
+ return 'managed_loop';
39
44
  const normalized = (data.type || '').toLowerCase();
40
45
  if (normalized === 'tool' || normalized === 'code')
41
46
  return 'code_runtime';
@@ -43,6 +48,15 @@ function resolveEngine(data) {
43
48
  return 'managed_loop';
44
49
  return 'direct_llm';
45
50
  }
51
+ function commandForEntrypoint(entrypoint) {
52
+ if (entrypoint.endsWith('.js')
53
+ || entrypoint.endsWith('.mjs')
54
+ || entrypoint.endsWith('.cjs')
55
+ || entrypoint.endsWith('.ts')) {
56
+ return `node ${entrypoint}`;
57
+ }
58
+ return `python ${entrypoint}`;
59
+ }
46
60
  // ─── Agent Resolution ───────────────────────────────────────────────────────
47
61
  async function resolveAgent(config, org, agent, version) {
48
62
  // 1. Try public download endpoint
@@ -55,10 +69,13 @@ async function resolveAgent(config, org, agent, version) {
55
69
  type: data.type || 'agent',
56
70
  run_mode: data.run_mode,
57
71
  execution_engine: data.execution_engine,
72
+ runtime: data.runtime,
73
+ loop: data.loop,
58
74
  callable: data.callable,
59
75
  prompt: data.prompt,
60
76
  input_schema: data.input_schema,
61
77
  output_schema: data.output_schema,
78
+ dependencies: data.dependencies,
62
79
  supported_providers: data.supported_providers,
63
80
  default_models: data.default_models,
64
81
  default_skills: data.default_skills,
@@ -133,7 +150,7 @@ async function tryOwnerFallback(config, org, agent, version) {
133
150
  }
134
151
  async function resolveFromMyAgents(config, agent, version, org) {
135
152
  const agents = await (0, api_1.listMyAgents)(config);
136
- const matching = agents.filter(a => a.name === agent);
153
+ const matching = agents.filter(a => a.name === agent && a.org_slug === org);
137
154
  if (matching.length === 0)
138
155
  return null;
139
156
  let target;
@@ -157,10 +174,13 @@ function mapAgentToPullData(agent) {
157
174
  type: agent.type,
158
175
  run_mode: agent.run_mode ?? null,
159
176
  execution_engine: agent.execution_engine ?? null,
177
+ runtime: agent.runtime ?? null,
178
+ loop: agent.loop ?? null,
160
179
  callable: agent.callable,
161
180
  prompt: agent.prompt,
162
181
  input_schema: agent.input_schema,
163
182
  output_schema: agent.output_schema,
183
+ dependencies: agent.manifest?.dependencies,
164
184
  supported_providers: agent.supported_providers,
165
185
  default_models: agent.default_models,
166
186
  tags: agent.tags,
@@ -207,6 +227,16 @@ function buildManifest(data) {
207
227
  // Engine-specific fields
208
228
  const engine = resolveEngine(data);
209
229
  if (engine === 'code_runtime') {
230
+ const runtime = (data.runtime && typeof data.runtime === 'object' && Object.keys(data.runtime).length > 0)
231
+ ? { ...data.runtime }
232
+ : undefined;
233
+ const runtimeCommand = (typeof runtime?.command === 'string' && runtime.command.trim())
234
+ ? runtime.command
235
+ : (data.run_command?.trim()
236
+ || (data.entrypoint ? commandForEntrypoint(data.entrypoint) : undefined));
237
+ if (runtimeCommand) {
238
+ manifest.runtime = { ...(runtime || {}), command: runtimeCommand };
239
+ }
210
240
  if (data.entrypoint && data.entrypoint !== 'sandbox_main.py') {
211
241
  manifest.entrypoint = data.entrypoint;
212
242
  }
@@ -217,9 +247,32 @@ function buildManifest(data) {
217
247
  if (data.run_command)
218
248
  manifest.run_command = data.run_command;
219
249
  }
250
+ if (engine === 'managed_loop') {
251
+ const loop = (data.loop && typeof data.loop === 'object' && Object.keys(data.loop).length > 0)
252
+ ? { ...data.loop }
253
+ : undefined;
254
+ if (loop) {
255
+ manifest.loop = loop;
256
+ const loopCustomTools = loop.custom_tools;
257
+ if (Array.isArray(loopCustomTools) && loopCustomTools.length > 0) {
258
+ manifest.custom_tools = loopCustomTools;
259
+ }
260
+ const loopMaxTurns = loop.max_turns;
261
+ if (typeof loopMaxTurns === 'number') {
262
+ manifest.max_turns = loopMaxTurns;
263
+ }
264
+ }
265
+ }
220
266
  // Include orchestration manifest if present (for dependencies, etc.)
221
267
  if (data.manifest && typeof data.manifest === 'object') {
222
268
  const m = { ...data.manifest };
269
+ if (data.dependencies
270
+ && data.dependencies.length > 0
271
+ && (!Array.isArray(m.dependencies)
272
+ || m.dependencies?.length === 0)) {
273
+ ;
274
+ m.dependencies = data.dependencies;
275
+ }
223
276
  // Clean up fields that are already top-level
224
277
  delete m.runtime;
225
278
  delete m.loop;
@@ -227,6 +280,9 @@ function buildManifest(data) {
227
280
  manifest.manifest = m;
228
281
  }
229
282
  }
283
+ else if (data.dependencies && data.dependencies.length > 0) {
284
+ manifest.manifest = { dependencies: data.dependencies };
285
+ }
230
286
  return manifest;
231
287
  }
232
288
  // ─── Bundle Download + Extraction ───────────────────────────────────────────
@@ -235,8 +291,11 @@ async function downloadBundle(config, org, agent, version, agentId) {
235
291
  return await (0, api_1.downloadCodeBundle)(config, org, agent, version);
236
292
  }
237
293
  catch (err) {
238
- if (!(err instanceof api_1.ApiError) || err.status !== 404)
294
+ if (!(err instanceof api_1.ApiError))
295
+ throw err;
296
+ if (err.status !== 404 && !(err.status === 403 && config.apiKey && agentId)) {
239
297
  throw err;
298
+ }
240
299
  }
241
300
  if (config.apiKey && agentId) {
242
301
  try {
@@ -174,7 +174,7 @@ function registerServiceCommand(program) {
174
174
  process.stdout.write(` ${chalk_1.default.bold('Name:')} ${svc.service_name}\n`);
175
175
  process.stdout.write(` ${chalk_1.default.bold('Agent:')} ${svc.agent_name}@${svc.agent_version}\n`);
176
176
  process.stdout.write(` ${chalk_1.default.bold('State:')} ${stateColor(svc.current_state)}\n`);
177
- process.stdout.write(` ${chalk_1.default.bold('URL:')} ${svc.cloud_run_url || '-'}\n`);
177
+ process.stdout.write(` ${chalk_1.default.bold('URL:')} ${svc.provider_url || svc.cloud_run_url || '-'}\n`);
178
178
  process.stdout.write(`\n`);
179
179
  process.stdout.write(chalk_1.default.gray(`View logs: orch service logs ${svc.id}\n`));
180
180
  }
@@ -334,8 +334,8 @@ function registerServiceCommand(program) {
334
334
  process.stdout.write(` Fail Streak: ${chalk_1.default.red(String(svc.consecutive_restart_failures))} / ${svc.max_restart_failures}\n`);
335
335
  }
336
336
  process.stdout.write(` Instances: ${svc.min_instances}-${svc.max_instances}\n`);
337
- process.stdout.write(` Cloud Run: ${svc.cloud_run_service || '-'}\n`);
338
- process.stdout.write(` URL: ${svc.cloud_run_url || '-'}\n`);
337
+ process.stdout.write(` Service ID: ${svc.provider_service_id || svc.cloud_run_service || '-'}\n`);
338
+ process.stdout.write(` URL: ${svc.provider_url || svc.cloud_run_url || '-'}\n`);
339
339
  process.stdout.write(` Deployed: ${formatDate(svc.last_deployed_at)}\n`);
340
340
  process.stdout.write(` Last Restart: ${formatDate(svc.last_restart_at)}\n`);
341
341
  if (svc.last_error) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@orchagent/cli",
3
- "version": "0.3.58",
3
+ "version": "0.3.60",
4
4
  "description": "Command-line interface for orchagent — deploy and run AI agents for your team",
5
5
  "license": "MIT",
6
6
  "author": "orchagent <hello@orchagent.io>",