@orchagent/cli 0.2.11 → 0.2.13

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.
@@ -90,7 +90,7 @@ async function resolveJsonBody(input) {
90
90
  return JSON.stringify(JSON.parse(raw));
91
91
  }
92
92
  catch {
93
- throw new errors_1.CliError('Invalid JSON input. Provide a valid JSON string or @file.');
93
+ throw (0, errors_1.jsonInputError)('data');
94
94
  }
95
95
  }
96
96
  function registerCallCommand(program) {
@@ -156,12 +156,11 @@ Note: Use 'call' for server-side execution (requires login), 'run' for local exe
156
156
  llmProvider = detected.provider;
157
157
  }
158
158
  }
159
- // Add LLM key to headers
160
- if (llmKey) {
161
- headers['X-LLM-API-Key'] = llmKey;
162
- if (llmProvider) {
163
- headers['X-LLM-Provider'] = llmProvider;
164
- }
159
+ // LLM credentials will be added to request body (not headers) for security
160
+ // Headers can be logged by proxies/load balancers, body is not logged by default
161
+ let llmCredentials;
162
+ if (llmKey && llmProvider) {
163
+ llmCredentials = { api_key: llmKey, provider: llmProvider };
165
164
  }
166
165
  else if (agentMeta.type === 'prompt') {
167
166
  // Warn if no key found for prompt-based agent
@@ -189,16 +188,44 @@ Note: Use 'call' for server-side execution (requires login), 'run' for local exe
189
188
  if (filePaths.length > 0 || options.metadata) {
190
189
  throw new errors_1.CliError('Cannot use --data with file uploads or --metadata.');
191
190
  }
192
- body = await resolveJsonBody(options.data);
191
+ // Parse JSON and inject llm_credentials if available
192
+ const resolvedBody = await resolveJsonBody(options.data);
193
+ if (llmCredentials) {
194
+ const bodyObj = JSON.parse(resolvedBody);
195
+ bodyObj.llm_credentials = llmCredentials;
196
+ body = JSON.stringify(bodyObj);
197
+ }
198
+ else {
199
+ body = resolvedBody;
200
+ }
201
+ headers['Content-Type'] = 'application/json';
202
+ }
203
+ else if (filePaths.length > 0 || options.metadata) {
204
+ // Handle multipart file uploads
205
+ // Inject llm_credentials into metadata if available
206
+ let metadata = options.metadata;
207
+ if (llmCredentials) {
208
+ const metaObj = metadata ? JSON.parse(metadata) : {};
209
+ metaObj.llm_credentials = llmCredentials;
210
+ metadata = JSON.stringify(metaObj);
211
+ }
212
+ const multipart = await buildMultipartBody(filePaths, metadata);
213
+ body = multipart.body;
214
+ sourceLabel = multipart.sourceLabel;
215
+ }
216
+ else if (llmCredentials) {
217
+ // No data or files, but we have LLM credentials - send as JSON body
218
+ body = JSON.stringify({ llm_credentials: llmCredentials });
193
219
  headers['Content-Type'] = 'application/json';
194
220
  }
195
221
  else {
196
- const multipart = await buildMultipartBody(filePaths.length ? filePaths : undefined, options.metadata);
222
+ // No data, files, or credentials - check for stdin
223
+ const multipart = await buildMultipartBody(undefined, options.metadata);
197
224
  body = multipart.body;
198
225
  sourceLabel = multipart.sourceLabel;
199
226
  }
200
227
  const url = `${resolved.apiUrl.replace(/\/$/, '')}/${org}/${parsed.agent}/${parsed.version}/${endpoint}`;
201
- const response = await fetch(url, {
228
+ const response = await (0, api_1.safeFetch)(url, {
202
229
  method: 'POST',
203
230
  headers,
204
231
  body,
@@ -29,6 +29,7 @@ function registerDeleteCommand(program) {
29
29
  .description('Delete an agent')
30
30
  .option('--version <version>', 'Specific version to delete (default: latest)')
31
31
  .option('-y, --yes', 'Skip confirmation prompt')
32
+ .option('--dry-run', 'Show what would be deleted without making changes')
32
33
  .action(async (agentName, options) => {
33
34
  const config = await (0, config_1.getResolvedConfig)();
34
35
  if (!config.apiKey) {
@@ -61,6 +62,17 @@ function registerDeleteCommand(program) {
61
62
  process.stdout.write(`${chalk_1.default.bold('Stars:')} ${deleteCheck.stars_count} ${chalk_1.default.bold('Forks:')} ${deleteCheck.fork_count}\n`);
62
63
  }
63
64
  process.stdout.write('\n');
65
+ // Handle dry-run
66
+ if (options.dryRun) {
67
+ process.stdout.write('\nDRY RUN - No changes will be made\n\n');
68
+ process.stdout.write(`Would delete: ${agent.name}@${agent.version}\n`);
69
+ if (deleteCheck.stars_count > 0 || deleteCheck.fork_count > 0) {
70
+ process.stdout.write(chalk_1.default.yellow('Warning: This agent has stars or forks\n'));
71
+ }
72
+ process.stdout.write(chalk_1.default.gray('\nData would be retained for 30 days before permanent deletion.\n'));
73
+ process.stdout.write('\nNo changes made (dry run)\n');
74
+ return;
75
+ }
64
76
  // Handle confirmation
65
77
  if (!options.yes) {
66
78
  if (deleteCheck.requires_confirmation) {
@@ -0,0 +1,27 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.registerDoctorCommand = registerDoctorCommand;
4
+ const doctor_1 = require("../lib/doctor");
5
+ const output_1 = require("../lib/output");
6
+ function registerDoctorCommand(program) {
7
+ program
8
+ .command('doctor')
9
+ .description('Diagnose setup issues with OrchAgent CLI')
10
+ .option('-v, --verbose', 'Show detailed output for each check')
11
+ .option('--json', 'Output results as JSON')
12
+ .action(async (options) => {
13
+ const results = await (0, doctor_1.runAllChecks)();
14
+ const summary = (0, doctor_1.calculateSummary)(results);
15
+ if (options.json) {
16
+ (0, output_1.printJson)((0, doctor_1.formatJsonOutput)(results, summary));
17
+ }
18
+ else {
19
+ (0, doctor_1.printHumanOutput)(results, summary, options.verbose ?? false);
20
+ }
21
+ // Exit with code 1 if any errors or warnings (enables scripting)
22
+ const hasIssues = summary.errors > 0 || summary.warnings > 0;
23
+ if (hasIssues) {
24
+ process.exit(1);
25
+ }
26
+ });
27
+ }
@@ -0,0 +1,543 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ var __importDefault = (this && this.__importDefault) || function (mod) {
36
+ return (mod && mod.__esModule) ? mod : { "default": mod };
37
+ };
38
+ Object.defineProperty(exports, "__esModule", { value: true });
39
+ exports.registerGitHubCommand = registerGitHubCommand;
40
+ const http_1 = __importDefault(require("http"));
41
+ const open_1 = __importDefault(require("open"));
42
+ const cli_table3_1 = __importDefault(require("cli-table3"));
43
+ const chalk_1 = __importDefault(require("chalk"));
44
+ const readline = __importStar(require("readline"));
45
+ const config_1 = require("../lib/config");
46
+ const api_1 = require("../lib/api");
47
+ const errors_1 = require("../lib/errors");
48
+ const analytics_1 = require("../lib/analytics");
49
+ const output_1 = require("../lib/output");
50
+ const DEFAULT_AUTH_PORT = 8375;
51
+ const AUTH_TIMEOUT_MS = 5 * 60 * 1000; // 5 minutes
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
+ async function promptForSelection(items) {
240
+ if (!process.stdin.isTTY) {
241
+ return null;
242
+ }
243
+ process.stdout.write('\nSelect an item to import (enter number, or "q" to quit):\n\n');
244
+ items.forEach((item, idx) => {
245
+ process.stdout.write(` ${idx + 1}. [${item.type}] ${item.path}`);
246
+ if (item.name) {
247
+ process.stdout.write(` (${item.name})`);
248
+ }
249
+ process.stdout.write('\n');
250
+ });
251
+ process.stdout.write('\n');
252
+ const rl = readline.createInterface({
253
+ input: process.stdin,
254
+ output: process.stdout,
255
+ });
256
+ return new Promise((resolve) => {
257
+ rl.question('Choice: ', (answer) => {
258
+ rl.close();
259
+ const trimmed = answer.trim().toLowerCase();
260
+ if (trimmed === 'q' || trimmed === '') {
261
+ resolve(null);
262
+ return;
263
+ }
264
+ const num = parseInt(trimmed, 10);
265
+ if (isNaN(num) || num < 1 || num > items.length) {
266
+ process.stdout.write('Invalid selection.\n');
267
+ resolve(null);
268
+ return;
269
+ }
270
+ resolve(items[num - 1]);
271
+ });
272
+ });
273
+ }
274
+ // 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 }),
280
+ headers: { 'Content-Type': 'application/json' },
281
+ });
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');
286
+ try {
287
+ await (0, open_1.default)(initResponse.auth_url);
288
+ }
289
+ catch {
290
+ process.stdout.write(`\nPlease open this URL in your browser:\n${initResponse.auth_url}\n\n`);
291
+ }
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`);
301
+ }
302
+ async function disconnectGitHub(config) {
303
+ await (0, api_1.request)(config, 'DELETE', '/github/disconnect');
304
+ await (0, analytics_1.track)('cli_github_disconnect');
305
+ process.stdout.write('Disconnected from GitHub.\n');
306
+ }
307
+ async function getGitHubStatus(config, json) {
308
+ const connection = await (0, api_1.request)(config, 'GET', '/github/connection');
309
+ if (json) {
310
+ (0, output_1.printJson)(connection);
311
+ return;
312
+ }
313
+ if (!connection.connected) {
314
+ process.stdout.write('Not connected to GitHub.\n');
315
+ process.stdout.write('\nConnect with: orchagent github connect\n');
316
+ return;
317
+ }
318
+ process.stdout.write(`GitHub Status:\n\n`);
319
+ 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();
323
+ process.stdout.write(` Since: ${date}\n`);
324
+ }
325
+ if (connection.scopes?.length) {
326
+ process.stdout.write(` Scopes: ${connection.scopes.join(', ')}\n`);
327
+ }
328
+ process.stdout.write('\n');
329
+ }
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
+ async function scanGitHubRepo(config, repo, json) {
367
+ // Parse owner/repo format
368
+ const parts = repo.split('/');
369
+ if (parts.length !== 2) {
370
+ throw new errors_1.CliError(`Invalid repository format: ${repo}\n\n` +
371
+ `Use owner/repo format, e.g.: orchagent github scan myorg/myrepo`);
372
+ }
373
+ const [owner, repoName] = parts;
374
+ const results = await (0, api_1.request)(config, 'GET', `/github/repos/${owner}/${repoName}/scan`);
375
+ await (0, analytics_1.track)('cli_github_scan', { repo, found: results.length });
376
+ if (json) {
377
+ (0, output_1.printJson)(results);
378
+ return results;
379
+ }
380
+ if (results.length === 0) {
381
+ process.stdout.write(`No agents or skills detected in ${repo}.\n`);
382
+ process.stdout.write('\nMake sure your repository contains:\n');
383
+ process.stdout.write(' - An orchagent.yaml or orchagent.json manifest file\n');
384
+ process.stdout.write(' - Or a directory with agent/skill configuration\n');
385
+ return results;
386
+ }
387
+ const table = new cli_table3_1.default({
388
+ head: [
389
+ chalk_1.default.bold('Type'),
390
+ chalk_1.default.bold('Path'),
391
+ chalk_1.default.bold('Name'),
392
+ ],
393
+ });
394
+ results.forEach((item) => {
395
+ const typeLabel = item.type === 'skill' ? chalk_1.default.cyan('skill') : chalk_1.default.magenta('agent');
396
+ table.push([typeLabel, item.path, item.name || '-']);
397
+ });
398
+ process.stdout.write(`${table.toString()}\n`);
399
+ process.stdout.write(`\nFound ${results.length} item${results.length === 1 ? '' : 's'} in ${repo}.\n`);
400
+ process.stdout.write(`\nImport with: orchagent github import ${repo} --path <path>\n`);
401
+ return results;
402
+ }
403
+ async function importFromGitHub(config, repo, options) {
404
+ // Parse owner/repo format
405
+ const parts = repo.split('/');
406
+ if (parts.length !== 2) {
407
+ throw new errors_1.CliError(`Invalid repository format: ${repo}\n\n` +
408
+ `Use owner/repo format, e.g.: orchagent github import myorg/myrepo`);
409
+ }
410
+ const [owner, repoName] = parts;
411
+ let selectedPath = options.path;
412
+ // If no path specified, scan first and let user choose
413
+ if (!selectedPath) {
414
+ const results = await scanGitHubRepo(config, repo, false);
415
+ if (results.length === 0) {
416
+ throw new errors_1.CliError(`No importable items found in ${repo}.\n\n` +
417
+ `Make sure your repository contains an orchagent.yaml manifest.`);
418
+ }
419
+ if (results.length === 1) {
420
+ selectedPath = results[0].path;
421
+ process.stdout.write(`\nImporting: ${results[0].path}\n`);
422
+ }
423
+ else {
424
+ const selection = await promptForSelection(results);
425
+ if (!selection) {
426
+ process.stdout.write('Import cancelled.\n');
427
+ return;
428
+ }
429
+ selectedPath = selection.path;
430
+ }
431
+ }
432
+ // Determine visibility (default to public)
433
+ const isPublic = options.private ? false : true;
434
+ const importResult = await (0, api_1.request)(config, 'POST', '/github/import', {
435
+ body: JSON.stringify({
436
+ owner,
437
+ repo: repoName,
438
+ path: selectedPath,
439
+ is_public: isPublic,
440
+ name: options.name,
441
+ }),
442
+ headers: { 'Content-Type': 'application/json' },
443
+ });
444
+ await (0, analytics_1.track)('cli_github_import', {
445
+ repo,
446
+ path: selectedPath,
447
+ is_public: isPublic,
448
+ type: importResult.type,
449
+ });
450
+ if (options.json) {
451
+ (0, output_1.printJson)(importResult);
452
+ return;
453
+ }
454
+ process.stdout.write('\n');
455
+ process.stdout.write(`Imported ${chalk_1.default.bold(importResult.name)} from ${repo}\n`);
456
+ 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`);
460
+ process.stdout.write(` Public: ${isPublic ? chalk_1.default.green('Yes') : chalk_1.default.yellow('No')}\n`);
461
+ process.stdout.write('\n');
462
+ process.stdout.write(`View at: https://orchagent.io/${importResult.org_slug}/${importResult.name}\n`);
463
+ }
464
+ // Command registration
465
+ function registerGitHubCommand(program) {
466
+ const github = program
467
+ .command('github')
468
+ .description('Connect to GitHub and import agents');
469
+ github
470
+ .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) => {
474
+ const config = await (0, config_1.getResolvedConfig)();
475
+ if (!config.apiKey) {
476
+ throw new errors_1.CliError('Missing API key. Run `orchagent login` first.');
477
+ }
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);
483
+ });
484
+ github
485
+ .command('disconnect')
486
+ .description('Remove GitHub connection')
487
+ .action(async () => {
488
+ const config = await (0, config_1.getResolvedConfig)();
489
+ if (!config.apiKey) {
490
+ throw new errors_1.CliError('Missing API key. Run `orchagent login` first.');
491
+ }
492
+ await disconnectGitHub(config);
493
+ });
494
+ github
495
+ .command('status')
496
+ .description('Show GitHub connection status')
497
+ .option('--json', 'Output raw JSON')
498
+ .action(async (options) => {
499
+ const config = await (0, config_1.getResolvedConfig)();
500
+ if (!config.apiKey) {
501
+ throw new errors_1.CliError('Missing API key. Run `orchagent login` first.');
502
+ }
503
+ await getGitHubStatus(config, options.json || false);
504
+ });
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
+ github
518
+ .command('scan <repo>')
519
+ .description('Scan a repository for agents and skills')
520
+ .option('--json', 'Output raw JSON')
521
+ .action(async (repo, options) => {
522
+ const config = await (0, config_1.getResolvedConfig)();
523
+ if (!config.apiKey) {
524
+ throw new errors_1.CliError('Missing API key. Run `orchagent login` first.');
525
+ }
526
+ await scanGitHubRepo(config, repo, options.json || false);
527
+ });
528
+ github
529
+ .command('import <repo>')
530
+ .description('Import an agent or skill from GitHub')
531
+ .option('--path <path>', 'Path to manifest within repo (scans if not specified)')
532
+ .option('--public', 'Make the agent public (default)')
533
+ .option('--private', 'Make the agent private')
534
+ .option('--name <name>', 'Override agent name')
535
+ .option('--json', 'Output raw JSON')
536
+ .action(async (repo, options) => {
537
+ const config = await (0, config_1.getResolvedConfig)();
538
+ if (!config.apiKey) {
539
+ throw new errors_1.CliError('Missing API key. Run `orchagent login` first.');
540
+ }
541
+ await importFromGitHub(config, repo, options);
542
+ });
543
+ }