@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.
@@ -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
+ }