@orchagent/cli 0.3.58 → 0.3.59

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
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@orchagent/cli",
3
- "version": "0.3.58",
3
+ "version": "0.3.59",
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>",