@orchagent/cli 0.1.0
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/README.md +18 -0
- package/dist/commands/agents.js +26 -0
- package/dist/commands/call.js +264 -0
- package/dist/commands/fork.js +42 -0
- package/dist/commands/index.js +33 -0
- package/dist/commands/info.js +88 -0
- package/dist/commands/init.js +101 -0
- package/dist/commands/keys.js +172 -0
- package/dist/commands/llm-config.js +40 -0
- package/dist/commands/login.js +94 -0
- package/dist/commands/publish.js +192 -0
- package/dist/commands/publish.test.js +475 -0
- package/dist/commands/run.js +421 -0
- package/dist/commands/run.test.js +330 -0
- package/dist/commands/search.js +46 -0
- package/dist/commands/skill.js +141 -0
- package/dist/commands/star.js +41 -0
- package/dist/commands/whoami.js +17 -0
- package/dist/index.js +55 -0
- package/dist/lib/analytics.js +27 -0
- package/dist/lib/api.js +179 -0
- package/dist/lib/api.test.js +230 -0
- package/dist/lib/browser-auth.js +278 -0
- package/dist/lib/bundle.js +213 -0
- package/dist/lib/config.js +54 -0
- package/dist/lib/config.test.js +144 -0
- package/dist/lib/errors.js +75 -0
- package/dist/lib/llm.js +252 -0
- package/dist/lib/output.js +50 -0
- package/dist/types.js +2 -0
- package/package.json +59 -0
|
@@ -0,0 +1,172 @@
|
|
|
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
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.registerKeysCommand = registerKeysCommand;
|
|
37
|
+
const readline = __importStar(require("readline"));
|
|
38
|
+
const config_1 = require("../lib/config");
|
|
39
|
+
const api_1 = require("../lib/api");
|
|
40
|
+
const errors_1 = require("../lib/errors");
|
|
41
|
+
const VALID_PROVIDERS = ['openai', 'anthropic', 'gemini'];
|
|
42
|
+
async function promptForKey(provider) {
|
|
43
|
+
// Use hidden input to avoid exposing keys in terminal history/logs
|
|
44
|
+
return new Promise((resolve, reject) => {
|
|
45
|
+
const rl = readline.createInterface({
|
|
46
|
+
input: process.stdin,
|
|
47
|
+
output: process.stdout,
|
|
48
|
+
});
|
|
49
|
+
// Mask input by not echoing characters
|
|
50
|
+
process.stdout.write(`Enter API key for ${provider}: `);
|
|
51
|
+
if (process.stdin.isTTY) {
|
|
52
|
+
// For TTY, read without echo
|
|
53
|
+
const stdin = process.stdin;
|
|
54
|
+
stdin.setRawMode(true);
|
|
55
|
+
stdin.resume();
|
|
56
|
+
stdin.setEncoding('utf8');
|
|
57
|
+
let key = '';
|
|
58
|
+
const onData = (char) => {
|
|
59
|
+
if (char === '\n' || char === '\r') {
|
|
60
|
+
stdin.setRawMode(false);
|
|
61
|
+
stdin.removeListener('data', onData);
|
|
62
|
+
rl.close();
|
|
63
|
+
process.stdout.write('\n');
|
|
64
|
+
resolve(key.trim());
|
|
65
|
+
}
|
|
66
|
+
else if (char === '\u0003') {
|
|
67
|
+
// Ctrl+C
|
|
68
|
+
stdin.setRawMode(false);
|
|
69
|
+
rl.close();
|
|
70
|
+
reject(new errors_1.CliError('Cancelled'));
|
|
71
|
+
}
|
|
72
|
+
else if (char === '\u007F' || char === '\b') {
|
|
73
|
+
// Backspace
|
|
74
|
+
if (key.length > 0) {
|
|
75
|
+
key = key.slice(0, -1);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
else {
|
|
79
|
+
key += char;
|
|
80
|
+
}
|
|
81
|
+
};
|
|
82
|
+
stdin.on('data', onData);
|
|
83
|
+
}
|
|
84
|
+
else {
|
|
85
|
+
// Non-TTY (piped input), just read normally
|
|
86
|
+
rl.question('', (answer) => {
|
|
87
|
+
rl.close();
|
|
88
|
+
resolve(answer.trim());
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
async function addKey(config, provider, options) {
|
|
94
|
+
let apiKey = options.key;
|
|
95
|
+
if (!apiKey) {
|
|
96
|
+
apiKey = await promptForKey(provider);
|
|
97
|
+
}
|
|
98
|
+
if (!apiKey) {
|
|
99
|
+
throw new errors_1.CliError('API key is required');
|
|
100
|
+
}
|
|
101
|
+
await (0, api_1.request)(config, 'POST', '/llm-keys', {
|
|
102
|
+
body: JSON.stringify({
|
|
103
|
+
provider,
|
|
104
|
+
api_key: apiKey,
|
|
105
|
+
endpoint_url: options.endpoint,
|
|
106
|
+
}),
|
|
107
|
+
headers: { 'Content-Type': 'application/json' },
|
|
108
|
+
});
|
|
109
|
+
process.stdout.write(`Saved ${provider} API key.\n`);
|
|
110
|
+
}
|
|
111
|
+
async function listKeys(config) {
|
|
112
|
+
const keys = await (0, api_1.request)(config, 'GET', '/llm-keys');
|
|
113
|
+
if (keys.length === 0) {
|
|
114
|
+
process.stdout.write('No LLM keys configured.\n');
|
|
115
|
+
process.stdout.write('\nAdd a key with: orchagent keys add <provider>\n');
|
|
116
|
+
process.stdout.write('Providers: openai, anthropic, gemini\n');
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
process.stdout.write('Configured LLM keys:\n\n');
|
|
120
|
+
for (const key of keys) {
|
|
121
|
+
const endpoint = key.has_custom_endpoint ? ' (custom endpoint)' : '';
|
|
122
|
+
process.stdout.write(` ${key.provider}${endpoint}\n`);
|
|
123
|
+
}
|
|
124
|
+
process.stdout.write('\n');
|
|
125
|
+
}
|
|
126
|
+
async function removeKey(config, provider) {
|
|
127
|
+
await (0, api_1.request)(config, 'DELETE', `/llm-keys/${provider}`);
|
|
128
|
+
process.stdout.write(`Removed ${provider} API key.\n`);
|
|
129
|
+
}
|
|
130
|
+
function registerKeysCommand(program) {
|
|
131
|
+
const keys = program
|
|
132
|
+
.command('keys')
|
|
133
|
+
.description('Manage LLM API keys for calling agents');
|
|
134
|
+
keys
|
|
135
|
+
.command('add <provider>')
|
|
136
|
+
.description('Add or update an LLM API key')
|
|
137
|
+
.option('--key <key>', 'API key (will prompt if not provided)')
|
|
138
|
+
.option('--endpoint <url>', 'Custom endpoint URL (enterprise proxy or self-hosted model)')
|
|
139
|
+
.action(async (provider, options) => {
|
|
140
|
+
const config = await (0, config_1.getResolvedConfig)();
|
|
141
|
+
if (!config.apiKey) {
|
|
142
|
+
throw new errors_1.CliError('Missing API key. Run `orchagent login` first.');
|
|
143
|
+
}
|
|
144
|
+
if (!VALID_PROVIDERS.includes(provider)) {
|
|
145
|
+
throw new errors_1.CliError(`Invalid provider: ${provider}. Valid providers: ${VALID_PROVIDERS.join(', ')}`);
|
|
146
|
+
}
|
|
147
|
+
await addKey(config, provider, options);
|
|
148
|
+
});
|
|
149
|
+
keys
|
|
150
|
+
.command('list')
|
|
151
|
+
.description('List configured LLM API keys')
|
|
152
|
+
.action(async () => {
|
|
153
|
+
const config = await (0, config_1.getResolvedConfig)();
|
|
154
|
+
if (!config.apiKey) {
|
|
155
|
+
throw new errors_1.CliError('Missing API key. Run `orchagent login` first.');
|
|
156
|
+
}
|
|
157
|
+
await listKeys(config);
|
|
158
|
+
});
|
|
159
|
+
keys
|
|
160
|
+
.command('remove <provider>')
|
|
161
|
+
.description('Remove an LLM API key')
|
|
162
|
+
.action(async (provider) => {
|
|
163
|
+
const config = await (0, config_1.getResolvedConfig)();
|
|
164
|
+
if (!config.apiKey) {
|
|
165
|
+
throw new errors_1.CliError('Missing API key. Run `orchagent login` first.');
|
|
166
|
+
}
|
|
167
|
+
if (!VALID_PROVIDERS.includes(provider)) {
|
|
168
|
+
throw new errors_1.CliError(`Invalid provider: ${provider}. Valid providers: ${VALID_PROVIDERS.join(', ')}`);
|
|
169
|
+
}
|
|
170
|
+
await removeKey(config, provider);
|
|
171
|
+
});
|
|
172
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.registerLlmConfigCommand = registerLlmConfigCommand;
|
|
4
|
+
const config_1 = require("../lib/config");
|
|
5
|
+
const api_1 = require("../lib/api");
|
|
6
|
+
const errors_1 = require("../lib/errors");
|
|
7
|
+
function registerLlmConfigCommand(program) {
|
|
8
|
+
program
|
|
9
|
+
.command('llm-config')
|
|
10
|
+
.description('Set default LLM configuration for prompt-based agents')
|
|
11
|
+
.option('--endpoint <url>', 'LLM API base URL (e.g., https://api.openai.com/v1)')
|
|
12
|
+
.option('--model <model>', 'Default model name (e.g., gpt-4.1-mini)')
|
|
13
|
+
.option('--api-key <key>', 'LLM API key')
|
|
14
|
+
.option('--clear', 'Clear the stored default LLM config')
|
|
15
|
+
.action(async (options) => {
|
|
16
|
+
const config = await (0, config_1.getResolvedConfig)();
|
|
17
|
+
if (!config.apiKey) {
|
|
18
|
+
throw new errors_1.CliError('Missing API key. Run `orchagent login` first.');
|
|
19
|
+
}
|
|
20
|
+
if (options.clear) {
|
|
21
|
+
await (0, api_1.updateOrg)(config, { default_llm_config: null });
|
|
22
|
+
process.stdout.write('Cleared default LLM config.\n');
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
const endpoint = options.endpoint?.trim();
|
|
26
|
+
const model = options.model?.trim();
|
|
27
|
+
const apiKey = options.apiKey?.trim();
|
|
28
|
+
if (!endpoint || !model || !apiKey) {
|
|
29
|
+
throw new errors_1.CliError('Missing required flags. Use --endpoint, --model, and --api-key (or --clear).');
|
|
30
|
+
}
|
|
31
|
+
await (0, api_1.updateOrg)(config, {
|
|
32
|
+
default_llm_config: {
|
|
33
|
+
endpoint,
|
|
34
|
+
model,
|
|
35
|
+
api_key: apiKey,
|
|
36
|
+
},
|
|
37
|
+
});
|
|
38
|
+
process.stdout.write('Saved default LLM config.\n');
|
|
39
|
+
});
|
|
40
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.registerLoginCommand = registerLoginCommand;
|
|
7
|
+
const promises_1 = __importDefault(require("readline/promises"));
|
|
8
|
+
const config_1 = require("../lib/config");
|
|
9
|
+
const api_1 = require("../lib/api");
|
|
10
|
+
const browser_auth_1 = require("../lib/browser-auth");
|
|
11
|
+
const errors_1 = require("../lib/errors");
|
|
12
|
+
const analytics_1 = require("../lib/analytics");
|
|
13
|
+
const DEFAULT_AUTH_PORT = 8374;
|
|
14
|
+
async function promptForKey() {
|
|
15
|
+
const rl = promises_1.default.createInterface({
|
|
16
|
+
input: process.stdin,
|
|
17
|
+
output: process.stdout,
|
|
18
|
+
});
|
|
19
|
+
const answer = await rl.question('API key: ');
|
|
20
|
+
rl.close();
|
|
21
|
+
return answer.trim();
|
|
22
|
+
}
|
|
23
|
+
function registerLoginCommand(program) {
|
|
24
|
+
program
|
|
25
|
+
.command('login')
|
|
26
|
+
.description('Authenticate with OrchAgent via browser or API key')
|
|
27
|
+
.option('--key <key>', 'API key (for CI/CD, non-interactive)')
|
|
28
|
+
.option('--port <port>', `Localhost port for browser callback (default: ${DEFAULT_AUTH_PORT})`, String(DEFAULT_AUTH_PORT))
|
|
29
|
+
.action(async (options) => {
|
|
30
|
+
const providedKey = options.key || process.env.ORCHAGENT_API_KEY;
|
|
31
|
+
// If key provided via flag or env var, use existing key-based flow
|
|
32
|
+
if (providedKey) {
|
|
33
|
+
await keyBasedLogin(providedKey);
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
// Check if running in non-interactive mode (no TTY)
|
|
37
|
+
if (!process.stdin.isTTY) {
|
|
38
|
+
throw new errors_1.CliError('Non-interactive mode requires --key flag or ORCHAGENT_API_KEY environment variable.\n' +
|
|
39
|
+
'For CI/CD, set ORCHAGENT_API_KEY or use: orchagent login --key <your-api-key>');
|
|
40
|
+
}
|
|
41
|
+
// Interactive mode: use browser auth
|
|
42
|
+
const port = parseInt(options.port || String(DEFAULT_AUTH_PORT), 10);
|
|
43
|
+
if (isNaN(port) || port < 1024 || port > 65535) {
|
|
44
|
+
throw new errors_1.CliError('Port must be a number between 1024 and 65535');
|
|
45
|
+
}
|
|
46
|
+
await browserBasedLogin(port);
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Login using an API key (for CI/CD and non-interactive environments).
|
|
51
|
+
*/
|
|
52
|
+
async function keyBasedLogin(apiKey) {
|
|
53
|
+
if (!apiKey) {
|
|
54
|
+
throw new errors_1.CliError('API key is required.');
|
|
55
|
+
}
|
|
56
|
+
const resolved = await (0, config_1.getResolvedConfig)({ api_key: apiKey });
|
|
57
|
+
const org = await (0, api_1.getOrg)(resolved);
|
|
58
|
+
const existing = await (0, config_1.loadConfig)();
|
|
59
|
+
const nextConfig = {
|
|
60
|
+
...existing,
|
|
61
|
+
api_key: apiKey,
|
|
62
|
+
api_url: resolved.apiUrl,
|
|
63
|
+
default_org: existing.default_org ?? org.slug,
|
|
64
|
+
};
|
|
65
|
+
await (0, config_1.saveConfig)(nextConfig);
|
|
66
|
+
await (0, analytics_1.track)('cli_login', { method: 'key' });
|
|
67
|
+
process.stdout.write(`✓ Logged in to ${org.slug}\n`);
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Login via browser OAuth flow.
|
|
71
|
+
*/
|
|
72
|
+
async function browserBasedLogin(port) {
|
|
73
|
+
const existing = await (0, config_1.loadConfig)();
|
|
74
|
+
const resolved = await (0, config_1.getResolvedConfig)();
|
|
75
|
+
process.stdout.write('Opening browser for authentication...\n');
|
|
76
|
+
try {
|
|
77
|
+
const result = await (0, browser_auth_1.startBrowserAuth)(resolved.apiUrl, port);
|
|
78
|
+
const nextConfig = {
|
|
79
|
+
...existing,
|
|
80
|
+
api_key: result.apiKey,
|
|
81
|
+
api_url: resolved.apiUrl,
|
|
82
|
+
default_org: existing.default_org ?? result.orgSlug,
|
|
83
|
+
};
|
|
84
|
+
await (0, config_1.saveConfig)(nextConfig);
|
|
85
|
+
await (0, analytics_1.track)('cli_login', { method: 'browser' });
|
|
86
|
+
process.stdout.write(`\n✓ Logged in to ${result.orgSlug}\n`);
|
|
87
|
+
}
|
|
88
|
+
catch (err) {
|
|
89
|
+
if (err instanceof errors_1.CliError) {
|
|
90
|
+
throw err;
|
|
91
|
+
}
|
|
92
|
+
throw new errors_1.CliError(err instanceof Error ? err.message : 'Authentication failed');
|
|
93
|
+
}
|
|
94
|
+
}
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.registerPublishCommand = registerPublishCommand;
|
|
7
|
+
const promises_1 = __importDefault(require("fs/promises"));
|
|
8
|
+
const path_1 = __importDefault(require("path"));
|
|
9
|
+
const os_1 = __importDefault(require("os"));
|
|
10
|
+
const yaml_1 = __importDefault(require("yaml"));
|
|
11
|
+
const config_1 = require("../lib/config");
|
|
12
|
+
const api_1 = require("../lib/api");
|
|
13
|
+
const errors_1 = require("../lib/errors");
|
|
14
|
+
const analytics_1 = require("../lib/analytics");
|
|
15
|
+
const bundle_1 = require("../lib/bundle");
|
|
16
|
+
async function parseSkillMd(filePath) {
|
|
17
|
+
try {
|
|
18
|
+
const content = await promises_1.default.readFile(filePath, 'utf-8');
|
|
19
|
+
const match = content.match(/^---\n([\s\S]*?)\n---\n([\s\S]*)$/);
|
|
20
|
+
if (!match)
|
|
21
|
+
return null;
|
|
22
|
+
const frontmatter = yaml_1.default.parse(match[1]);
|
|
23
|
+
const body = match[2].trim();
|
|
24
|
+
if (!frontmatter.name || !frontmatter.description)
|
|
25
|
+
return null;
|
|
26
|
+
return { frontmatter, body };
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
function registerPublishCommand(program) {
|
|
33
|
+
program
|
|
34
|
+
.command('publish')
|
|
35
|
+
.description('Publish agent from local files')
|
|
36
|
+
.option('--url <url>', 'Agent URL (for code-based agents)')
|
|
37
|
+
.option('--public', 'Make agent public (default: true)', true)
|
|
38
|
+
.option('--private', 'Make agent private')
|
|
39
|
+
.action(async (options) => {
|
|
40
|
+
const config = await (0, config_1.getResolvedConfig)();
|
|
41
|
+
const cwd = process.cwd();
|
|
42
|
+
// Check for SKILL.md first (skills take precedence)
|
|
43
|
+
const skillMdPath = path_1.default.join(cwd, 'SKILL.md');
|
|
44
|
+
const skillData = await parseSkillMd(skillMdPath);
|
|
45
|
+
if (skillData) {
|
|
46
|
+
// Publish as a skill (server auto-assigns version)
|
|
47
|
+
const org = await (0, api_1.getOrg)(config);
|
|
48
|
+
const skillResult = await (0, api_1.createAgent)(config, {
|
|
49
|
+
name: skillData.frontmatter.name,
|
|
50
|
+
type: 'skill',
|
|
51
|
+
description: skillData.frontmatter.description,
|
|
52
|
+
prompt: skillData.body,
|
|
53
|
+
is_public: options.private ? false : true,
|
|
54
|
+
supported_providers: ['any'],
|
|
55
|
+
});
|
|
56
|
+
const skillVersion = skillResult.agent?.version || 'v1';
|
|
57
|
+
await (0, analytics_1.track)('cli_publish', { agent_type: 'skill' });
|
|
58
|
+
process.stdout.write(`\nPublished skill: ${org.slug}/${skillData.frontmatter.name}@${skillVersion}\n`);
|
|
59
|
+
process.stdout.write(`Public: ${options.private ? 'no' : 'yes'}\n`);
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
// Read manifest
|
|
63
|
+
const manifestPath = path_1.default.join(cwd, 'orchagent.json');
|
|
64
|
+
let manifest;
|
|
65
|
+
try {
|
|
66
|
+
const raw = await promises_1.default.readFile(manifestPath, 'utf-8');
|
|
67
|
+
manifest = JSON.parse(raw);
|
|
68
|
+
}
|
|
69
|
+
catch (err) {
|
|
70
|
+
if (err.code === 'ENOENT') {
|
|
71
|
+
throw new errors_1.CliError('No orchagent.json found. Run `orchagent init` first.');
|
|
72
|
+
}
|
|
73
|
+
throw new errors_1.CliError(`Failed to read orchagent.json: ${err}`);
|
|
74
|
+
}
|
|
75
|
+
// Validate manifest
|
|
76
|
+
if (!manifest.name) {
|
|
77
|
+
throw new errors_1.CliError('orchagent.json must have name');
|
|
78
|
+
}
|
|
79
|
+
// Read prompt (for prompt-based agents)
|
|
80
|
+
let prompt;
|
|
81
|
+
if (manifest.type === 'prompt') {
|
|
82
|
+
const promptPath = path_1.default.join(cwd, 'prompt.md');
|
|
83
|
+
try {
|
|
84
|
+
prompt = await promises_1.default.readFile(promptPath, 'utf-8');
|
|
85
|
+
}
|
|
86
|
+
catch (err) {
|
|
87
|
+
if (err.code === 'ENOENT') {
|
|
88
|
+
throw new errors_1.CliError('No prompt.md found for prompt-based agent');
|
|
89
|
+
}
|
|
90
|
+
throw err;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
// Read schemas
|
|
94
|
+
let inputSchema;
|
|
95
|
+
let outputSchema;
|
|
96
|
+
const schemaPath = path_1.default.join(cwd, 'schema.json');
|
|
97
|
+
try {
|
|
98
|
+
const raw = await promises_1.default.readFile(schemaPath, 'utf-8');
|
|
99
|
+
const schemas = JSON.parse(raw);
|
|
100
|
+
inputSchema = schemas.input;
|
|
101
|
+
outputSchema = schemas.output;
|
|
102
|
+
}
|
|
103
|
+
catch (err) {
|
|
104
|
+
// Schema is optional
|
|
105
|
+
if (err.code !== 'ENOENT') {
|
|
106
|
+
throw new errors_1.CliError(`Failed to read schema.json: ${err}`);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
// For code-based agents, either --url is required OR we bundle the code
|
|
110
|
+
let agentUrl = options.url;
|
|
111
|
+
let shouldUploadBundle = false;
|
|
112
|
+
if (manifest.type === 'code' && !options.url) {
|
|
113
|
+
// Check if this looks like a Python or JS project that can be bundled
|
|
114
|
+
const entrypoint = manifest.entrypoint || await (0, bundle_1.detectEntrypoint)(cwd);
|
|
115
|
+
if (entrypoint) {
|
|
116
|
+
// This is a hosted code agent - we'll bundle and upload
|
|
117
|
+
shouldUploadBundle = true;
|
|
118
|
+
// Set a placeholder URL that tells the gateway to use sandbox execution
|
|
119
|
+
agentUrl = 'https://code-agent.internal';
|
|
120
|
+
process.stdout.write(`Detected code project with entrypoint: ${entrypoint}\n`);
|
|
121
|
+
}
|
|
122
|
+
else {
|
|
123
|
+
throw new errors_1.CliError('Code agent requires either --url <url> or an entry point file (main.py, app.py, index.js, etc.)');
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
// Get org info
|
|
127
|
+
const org = await (0, api_1.getOrg)(config);
|
|
128
|
+
// Default to 'any' provider if not specified
|
|
129
|
+
const supportedProviders = manifest.supported_providers || ['any'];
|
|
130
|
+
// Create the agent (server auto-assigns version)
|
|
131
|
+
const result = await (0, api_1.createAgent)(config, {
|
|
132
|
+
name: manifest.name,
|
|
133
|
+
type: manifest.type,
|
|
134
|
+
description: manifest.description,
|
|
135
|
+
prompt,
|
|
136
|
+
url: agentUrl,
|
|
137
|
+
input_schema: inputSchema,
|
|
138
|
+
output_schema: outputSchema,
|
|
139
|
+
tags: manifest.tags,
|
|
140
|
+
is_public: options.private ? false : true,
|
|
141
|
+
supported_providers: supportedProviders,
|
|
142
|
+
default_models: manifest.default_models,
|
|
143
|
+
// Local run fields for code agents
|
|
144
|
+
source_url: manifest.source_url,
|
|
145
|
+
pip_package: manifest.pip_package,
|
|
146
|
+
run_command: manifest.run_command,
|
|
147
|
+
});
|
|
148
|
+
const assignedVersion = result.agent?.version || 'v1';
|
|
149
|
+
const agentId = result.agent?.id;
|
|
150
|
+
// Upload code bundle if this is a hosted code agent
|
|
151
|
+
if (shouldUploadBundle && agentId) {
|
|
152
|
+
process.stdout.write(`\nBundling code...\n`);
|
|
153
|
+
const tempDir = await promises_1.default.mkdtemp(path_1.default.join(os_1.default.tmpdir(), 'orchagent-bundle-'));
|
|
154
|
+
const bundlePath = path_1.default.join(tempDir, 'bundle.zip');
|
|
155
|
+
try {
|
|
156
|
+
const bundleResult = await (0, bundle_1.createCodeBundle)(cwd, bundlePath, {
|
|
157
|
+
entrypoint: manifest.entrypoint,
|
|
158
|
+
exclude: manifest.bundle?.exclude,
|
|
159
|
+
include: manifest.bundle?.include,
|
|
160
|
+
});
|
|
161
|
+
process.stdout.write(` Created bundle: ${bundleResult.fileCount} files, ${(bundleResult.sizeBytes / 1024).toFixed(1)}KB\n`);
|
|
162
|
+
// Validate bundle size
|
|
163
|
+
const validation = await (0, bundle_1.validateBundle)(bundlePath);
|
|
164
|
+
if (!validation.valid) {
|
|
165
|
+
throw new errors_1.CliError(`Bundle validation failed: ${validation.error}`);
|
|
166
|
+
}
|
|
167
|
+
// Upload the bundle
|
|
168
|
+
process.stdout.write(` Uploading bundle...\n`);
|
|
169
|
+
const uploadResult = await (0, api_1.uploadCodeBundle)(config, agentId, bundlePath);
|
|
170
|
+
process.stdout.write(` Uploaded: ${uploadResult.code_hash.substring(0, 12)}...\n`);
|
|
171
|
+
}
|
|
172
|
+
finally {
|
|
173
|
+
// Clean up temp files
|
|
174
|
+
await promises_1.default.rm(tempDir, { recursive: true, force: true });
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
await (0, analytics_1.track)('cli_publish', { agent_type: manifest.type, hosted: shouldUploadBundle });
|
|
178
|
+
process.stdout.write(`\nPublished agent: ${org.slug}/${manifest.name}@${assignedVersion}\n`);
|
|
179
|
+
process.stdout.write(`Type: ${manifest.type}${shouldUploadBundle ? ' (hosted)' : ''}\n`);
|
|
180
|
+
process.stdout.write(`Providers: ${supportedProviders.join(', ')}\n`);
|
|
181
|
+
process.stdout.write(`Public: ${options.private ? 'no' : 'yes'}\n`);
|
|
182
|
+
if (result.service_key) {
|
|
183
|
+
process.stdout.write(`\nService key (save this - shown only once):\n`);
|
|
184
|
+
process.stdout.write(` ${result.service_key}\n`);
|
|
185
|
+
}
|
|
186
|
+
process.stdout.write(`\nAPI endpoint:\n`);
|
|
187
|
+
process.stdout.write(` POST ${config.apiUrl}/${org.slug}/${manifest.name}/${assignedVersion}/run\n`);
|
|
188
|
+
if (shouldUploadBundle) {
|
|
189
|
+
process.stdout.write(`\nNote: Hosted code execution is in beta. Contact support for full deployment.\n`);
|
|
190
|
+
}
|
|
191
|
+
});
|
|
192
|
+
}
|