@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.
- package/dist/commands/call.js +37 -10
- package/dist/commands/delete.js +12 -0
- package/dist/commands/doctor.js +27 -0
- package/dist/commands/github.js +543 -0
- package/dist/commands/index.js +6 -2
- package/dist/commands/publish.js +86 -2
- package/dist/commands/run.js +7 -2
- package/dist/commands/skill.js +10 -7
- package/dist/commands/status.js +97 -0
- package/dist/commands/workspace.js +133 -0
- package/dist/lib/api.js +30 -5
- package/dist/lib/auth-errors.js +27 -0
- package/dist/lib/browser-auth.js +5 -8
- package/dist/lib/bundle.js +48 -0
- package/dist/lib/doctor/checks/auth.js +115 -0
- package/dist/lib/doctor/checks/config.js +119 -0
- package/dist/lib/doctor/checks/connectivity.js +109 -0
- package/dist/lib/doctor/checks/environment.js +151 -0
- package/dist/lib/doctor/checks/llm.js +108 -0
- package/dist/lib/doctor/index.js +19 -0
- package/dist/lib/doctor/output.js +105 -0
- package/dist/lib/doctor/runner.js +68 -0
- package/dist/lib/doctor/types.js +2 -0
- package/dist/lib/errors.js +41 -1
- package/dist/lib/llm-errors.js +79 -0
- package/dist/lib/llm.js +4 -3
- package/package.json +1 -1
package/dist/commands/call.js
CHANGED
|
@@ -90,7 +90,7 @@ async function resolveJsonBody(input) {
|
|
|
90
90
|
return JSON.stringify(JSON.parse(raw));
|
|
91
91
|
}
|
|
92
92
|
catch {
|
|
93
|
-
throw
|
|
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
|
-
//
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
228
|
+
const response = await (0, api_1.safeFetch)(url, {
|
|
202
229
|
method: 'POST',
|
|
203
230
|
headers,
|
|
204
231
|
body,
|
package/dist/commands/delete.js
CHANGED
|
@@ -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
|
+
}
|