@orchagent/cli 0.3.6 → 0.3.8
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 +1 -1
- package/dist/commands/env.js +245 -0
- package/dist/commands/index.js +2 -0
- package/dist/commands/install.js +88 -17
- package/dist/commands/publish.js +15 -0
- package/dist/commands/skill.js +87 -15
- package/dist/lib/api.js +86 -2
- package/package.json +1 -1
package/dist/commands/call.js
CHANGED
|
@@ -229,7 +229,7 @@ Note: Use 'call' for server-side execution (requires login), 'run' for local exe
|
|
|
229
229
|
sourceLabel = multipart.sourceLabel;
|
|
230
230
|
}
|
|
231
231
|
const url = `${resolved.apiUrl.replace(/\/$/, '')}/${org}/${parsed.agent}/${parsed.version}/${endpoint}`;
|
|
232
|
-
const response = await (0, api_1.
|
|
232
|
+
const response = await (0, api_1.safeFetchWithRetryForCalls)(url, {
|
|
233
233
|
method: 'POST',
|
|
234
234
|
headers,
|
|
235
235
|
body,
|
|
@@ -0,0 +1,245 @@
|
|
|
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.registerEnvCommand = registerEnvCommand;
|
|
40
|
+
const cli_table3_1 = __importDefault(require("cli-table3"));
|
|
41
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
42
|
+
const fs = __importStar(require("fs/promises"));
|
|
43
|
+
const config_1 = require("../lib/config");
|
|
44
|
+
const api_1 = require("../lib/api");
|
|
45
|
+
const errors_1 = require("../lib/errors");
|
|
46
|
+
const analytics_1 = require("../lib/analytics");
|
|
47
|
+
async function resolveWorkspaceId(config, slug) {
|
|
48
|
+
const configFile = await (0, config_1.loadConfig)();
|
|
49
|
+
const targetSlug = slug ?? configFile.workspace;
|
|
50
|
+
if (!targetSlug) {
|
|
51
|
+
// Use user's personal org
|
|
52
|
+
const org = await (0, api_1.getOrg)(config);
|
|
53
|
+
return org.id;
|
|
54
|
+
}
|
|
55
|
+
const response = await (0, api_1.request)(config, 'GET', '/workspaces');
|
|
56
|
+
const workspace = response.workspaces.find((w) => w.slug === targetSlug);
|
|
57
|
+
if (!workspace) {
|
|
58
|
+
throw new errors_1.CliError(`Workspace '${targetSlug}' not found`);
|
|
59
|
+
}
|
|
60
|
+
return workspace.id;
|
|
61
|
+
}
|
|
62
|
+
function statusColor(status) {
|
|
63
|
+
switch (status) {
|
|
64
|
+
case 'ready':
|
|
65
|
+
return chalk_1.default.green(status);
|
|
66
|
+
case 'building':
|
|
67
|
+
return chalk_1.default.yellow(status);
|
|
68
|
+
case 'failed':
|
|
69
|
+
return chalk_1.default.red(status);
|
|
70
|
+
case 'pending':
|
|
71
|
+
return chalk_1.default.gray(status);
|
|
72
|
+
default:
|
|
73
|
+
return chalk_1.default.gray(status ?? 'unknown');
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
async function listEnvs(config, options) {
|
|
77
|
+
const workspaceId = await resolveWorkspaceId(config, options.workspace);
|
|
78
|
+
const result = await (0, api_1.listEnvironments)(config, workspaceId);
|
|
79
|
+
if (result.environments.length === 0) {
|
|
80
|
+
console.log(chalk_1.default.gray('No environments found.'));
|
|
81
|
+
console.log(chalk_1.default.gray('Use `orch env create` to create one, or include a Dockerfile in your agent bundle.'));
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
const table = new cli_table3_1.default({
|
|
85
|
+
head: [
|
|
86
|
+
chalk_1.default.cyan('Name'),
|
|
87
|
+
chalk_1.default.cyan('Status'),
|
|
88
|
+
chalk_1.default.cyan('Agents'),
|
|
89
|
+
chalk_1.default.cyan('Type'),
|
|
90
|
+
chalk_1.default.cyan('ID'),
|
|
91
|
+
],
|
|
92
|
+
style: { head: [], border: [] },
|
|
93
|
+
});
|
|
94
|
+
for (const env of result.environments) {
|
|
95
|
+
const isDefault = env.environment.id === result.default_environment_id;
|
|
96
|
+
const name = isDefault
|
|
97
|
+
? `${env.environment.name} ${chalk_1.default.yellow('(default)')}`
|
|
98
|
+
: env.environment.name;
|
|
99
|
+
table.push([
|
|
100
|
+
name,
|
|
101
|
+
statusColor(env.build?.status),
|
|
102
|
+
env.agent_count.toString(),
|
|
103
|
+
env.environment.is_predefined ? chalk_1.default.blue('predefined') : chalk_1.default.gray('custom'),
|
|
104
|
+
env.environment.id.slice(0, 8),
|
|
105
|
+
]);
|
|
106
|
+
}
|
|
107
|
+
console.log();
|
|
108
|
+
console.log(chalk_1.default.bold('Environments:'));
|
|
109
|
+
console.log(table.toString());
|
|
110
|
+
console.log();
|
|
111
|
+
if (result.default_environment_id) {
|
|
112
|
+
const defaultEnv = result.environments.find((e) => e.environment.id === result.default_environment_id);
|
|
113
|
+
if (defaultEnv) {
|
|
114
|
+
console.log(chalk_1.default.gray(`Workspace default: ${chalk_1.default.white(defaultEnv.environment.name)}`));
|
|
115
|
+
console.log(chalk_1.default.gray('All new agents will use this environment unless they include their own Dockerfile.'));
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
async function getEnvStatus(config, environmentId) {
|
|
120
|
+
const result = await (0, api_1.getEnvironment)(config, environmentId);
|
|
121
|
+
console.log();
|
|
122
|
+
console.log(chalk_1.default.bold(`Environment: ${result.environment.name}`));
|
|
123
|
+
console.log();
|
|
124
|
+
console.log(` ID: ${result.environment.id}`);
|
|
125
|
+
console.log(` Status: ${statusColor(result.build?.status)}`);
|
|
126
|
+
console.log(` Agents: ${result.agent_count}`);
|
|
127
|
+
console.log(` Type: ${result.environment.is_predefined ? 'predefined' : 'custom'}`);
|
|
128
|
+
console.log(` Created: ${new Date(result.environment.created_at).toLocaleString()}`);
|
|
129
|
+
if (result.build?.status === 'failed') {
|
|
130
|
+
console.log();
|
|
131
|
+
console.log(chalk_1.default.red('Build Error:'));
|
|
132
|
+
console.log(chalk_1.default.red(` ${result.build.error_message || 'Unknown error'}`));
|
|
133
|
+
}
|
|
134
|
+
if (result.build?.build_logs) {
|
|
135
|
+
console.log();
|
|
136
|
+
console.log(chalk_1.default.gray('Build Logs:'));
|
|
137
|
+
console.log(chalk_1.default.gray(result.build.build_logs));
|
|
138
|
+
}
|
|
139
|
+
console.log();
|
|
140
|
+
console.log(chalk_1.default.gray('Dockerfile:'));
|
|
141
|
+
console.log(chalk_1.default.gray('---'));
|
|
142
|
+
console.log(result.environment.dockerfile_content);
|
|
143
|
+
console.log(chalk_1.default.gray('---'));
|
|
144
|
+
}
|
|
145
|
+
async function createEnv(config, options) {
|
|
146
|
+
let dockerfileContent;
|
|
147
|
+
try {
|
|
148
|
+
dockerfileContent = await fs.readFile(options.file, 'utf-8');
|
|
149
|
+
}
|
|
150
|
+
catch (err) {
|
|
151
|
+
throw new errors_1.CliError(`Failed to read Dockerfile: ${options.file}`);
|
|
152
|
+
}
|
|
153
|
+
console.log(chalk_1.default.gray(`Creating environment '${options.name}'...`));
|
|
154
|
+
const result = await (0, api_1.createEnvironment)(config, options.name, dockerfileContent);
|
|
155
|
+
if (result.reused) {
|
|
156
|
+
console.log(chalk_1.default.cyan('Existing environment with same Dockerfile found, reusing.'));
|
|
157
|
+
console.log(`Environment ID: ${result.environment.id}`);
|
|
158
|
+
}
|
|
159
|
+
else {
|
|
160
|
+
console.log(chalk_1.default.green('Environment created, build started.'));
|
|
161
|
+
console.log(`Environment ID: ${result.environment.id}`);
|
|
162
|
+
console.log();
|
|
163
|
+
console.log(chalk_1.default.gray(`Check build status: orch env status ${result.environment.id}`));
|
|
164
|
+
}
|
|
165
|
+
await (0, analytics_1.track)('env_create', {
|
|
166
|
+
environment_id: result.environment.id,
|
|
167
|
+
reused: result.reused,
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
async function deleteEnv(config, environmentId) {
|
|
171
|
+
console.log(chalk_1.default.gray(`Deleting environment ${environmentId}...`));
|
|
172
|
+
await (0, api_1.deleteEnvironment)(config, environmentId);
|
|
173
|
+
console.log(chalk_1.default.green('Environment deleted.'));
|
|
174
|
+
await (0, analytics_1.track)('env_delete', { environment_id: environmentId });
|
|
175
|
+
}
|
|
176
|
+
async function setDefault(config, environmentId, options) {
|
|
177
|
+
const workspaceId = await resolveWorkspaceId(config, options.workspace);
|
|
178
|
+
console.log(chalk_1.default.gray(`Setting default environment for workspace...`));
|
|
179
|
+
await (0, api_1.setWorkspaceDefaultEnvironment)(config, workspaceId, environmentId);
|
|
180
|
+
console.log(chalk_1.default.green('Default environment set for workspace.'));
|
|
181
|
+
console.log(chalk_1.default.gray('All new agents will use this environment unless they include their own Dockerfile.'));
|
|
182
|
+
await (0, analytics_1.track)('env_set_default', {
|
|
183
|
+
environment_id: environmentId,
|
|
184
|
+
workspace_id: workspaceId,
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
async function clearDefault(config, options) {
|
|
188
|
+
const workspaceId = await resolveWorkspaceId(config, options.workspace);
|
|
189
|
+
console.log(chalk_1.default.gray(`Clearing default environment for workspace...`));
|
|
190
|
+
await (0, api_1.setWorkspaceDefaultEnvironment)(config, workspaceId, null);
|
|
191
|
+
console.log(chalk_1.default.green('Default environment cleared. Agents will use base image.'));
|
|
192
|
+
await (0, analytics_1.track)('env_clear_default', { workspace_id: workspaceId });
|
|
193
|
+
}
|
|
194
|
+
function registerEnvCommand(program) {
|
|
195
|
+
const env = program
|
|
196
|
+
.command('env')
|
|
197
|
+
.description('Manage custom Docker environments for code agents');
|
|
198
|
+
env
|
|
199
|
+
.command('list')
|
|
200
|
+
.description('List environments in workspace')
|
|
201
|
+
.option('-w, --workspace <slug>', 'Workspace slug')
|
|
202
|
+
.action(async (options) => {
|
|
203
|
+
const config = await (0, config_1.getResolvedConfig)();
|
|
204
|
+
await listEnvs(config, options);
|
|
205
|
+
});
|
|
206
|
+
env
|
|
207
|
+
.command('status <environment-id>')
|
|
208
|
+
.description('Check environment build status')
|
|
209
|
+
.action(async (environmentId) => {
|
|
210
|
+
const config = await (0, config_1.getResolvedConfig)();
|
|
211
|
+
await getEnvStatus(config, environmentId);
|
|
212
|
+
});
|
|
213
|
+
env
|
|
214
|
+
.command('create')
|
|
215
|
+
.description('Create environment from Dockerfile')
|
|
216
|
+
.requiredOption('-f, --file <path>', 'Path to Dockerfile')
|
|
217
|
+
.requiredOption('-n, --name <name>', 'Environment name')
|
|
218
|
+
.action(async (options) => {
|
|
219
|
+
const config = await (0, config_1.getResolvedConfig)();
|
|
220
|
+
await createEnv(config, options);
|
|
221
|
+
});
|
|
222
|
+
env
|
|
223
|
+
.command('delete <environment-id>')
|
|
224
|
+
.description('Delete an environment')
|
|
225
|
+
.action(async (environmentId) => {
|
|
226
|
+
const config = await (0, config_1.getResolvedConfig)();
|
|
227
|
+
await deleteEnv(config, environmentId);
|
|
228
|
+
});
|
|
229
|
+
env
|
|
230
|
+
.command('set-default <environment-id>')
|
|
231
|
+
.description('Set workspace default environment (all agents use this)')
|
|
232
|
+
.option('-w, --workspace <slug>', 'Workspace slug (defaults to current)')
|
|
233
|
+
.action(async (environmentId, options) => {
|
|
234
|
+
const config = await (0, config_1.getResolvedConfig)();
|
|
235
|
+
await setDefault(config, environmentId, options);
|
|
236
|
+
});
|
|
237
|
+
env
|
|
238
|
+
.command('clear-default')
|
|
239
|
+
.description('Clear workspace default environment (agents use base image)')
|
|
240
|
+
.option('-w, --workspace <slug>', 'Workspace slug (defaults to current)')
|
|
241
|
+
.action(async (options) => {
|
|
242
|
+
const config = await (0, config_1.getResolvedConfig)();
|
|
243
|
+
await clearDefault(config, options);
|
|
244
|
+
});
|
|
245
|
+
}
|
package/dist/commands/index.js
CHANGED
|
@@ -25,6 +25,7 @@ const config_1 = require("./config");
|
|
|
25
25
|
const install_1 = require("./install");
|
|
26
26
|
const formats_1 = require("./formats");
|
|
27
27
|
const update_1 = require("./update");
|
|
28
|
+
const env_1 = require("./env");
|
|
28
29
|
function registerCommands(program) {
|
|
29
30
|
(0, login_1.registerLoginCommand)(program);
|
|
30
31
|
(0, whoami_1.registerWhoamiCommand)(program);
|
|
@@ -50,4 +51,5 @@ function registerCommands(program) {
|
|
|
50
51
|
(0, install_1.registerInstallCommand)(program);
|
|
51
52
|
(0, formats_1.registerFormatsCommand)(program);
|
|
52
53
|
(0, update_1.registerUpdateCommand)(program);
|
|
54
|
+
(0, env_1.registerEnvCommand)(program);
|
|
53
55
|
}
|
package/dist/commands/install.js
CHANGED
|
@@ -70,20 +70,50 @@ function registerInstallCommand(program) {
|
|
|
70
70
|
.option('--format <formats>', 'Comma-separated format IDs (e.g., claude-code,cursor)')
|
|
71
71
|
.option('--scope <scope>', 'Install scope: user (home dir) or project (current dir)', 'user')
|
|
72
72
|
.option('--dry-run', 'Show what would be installed without making changes')
|
|
73
|
+
.option('--json', 'Output result as JSON (for automation/tooling)')
|
|
73
74
|
.action(async (agentArg, options) => {
|
|
75
|
+
const jsonMode = options.json === true;
|
|
76
|
+
const log = (msg) => { if (!jsonMode)
|
|
77
|
+
process.stdout.write(msg); };
|
|
78
|
+
const logErr = (msg) => { if (!jsonMode)
|
|
79
|
+
process.stderr.write(msg); };
|
|
80
|
+
// Result tracking for JSON output
|
|
81
|
+
const result = {
|
|
82
|
+
success: false,
|
|
83
|
+
agent: '',
|
|
84
|
+
version: '',
|
|
85
|
+
scope: '',
|
|
86
|
+
formats: [],
|
|
87
|
+
files: [],
|
|
88
|
+
warnings: [],
|
|
89
|
+
errors: [],
|
|
90
|
+
};
|
|
74
91
|
const resolved = await (0, config_1.getResolvedConfig)();
|
|
75
92
|
const parsed = parseAgentRef(agentArg);
|
|
76
93
|
const org = parsed.org ?? resolved.defaultOrg;
|
|
77
94
|
if (!org) {
|
|
95
|
+
if (jsonMode) {
|
|
96
|
+
result.errors.push('Missing org. Use org/agent format or set default org.');
|
|
97
|
+
process.stdout.write(JSON.stringify(result, null, 2) + '\n');
|
|
98
|
+
process.exit(1);
|
|
99
|
+
}
|
|
78
100
|
throw new errors_1.CliError('Missing org. Use org/agent format or set default org.');
|
|
79
101
|
}
|
|
102
|
+
result.agent = `${org}/${parsed.name}`;
|
|
103
|
+
result.version = parsed.version;
|
|
80
104
|
// Determine target formats
|
|
81
105
|
let targetFormats = [];
|
|
82
106
|
if (options.format) {
|
|
83
107
|
targetFormats = options.format.split(',').map(f => f.trim());
|
|
84
108
|
const invalid = targetFormats.filter(f => !adapters_1.adapterRegistry.has(f));
|
|
85
109
|
if (invalid.length > 0) {
|
|
86
|
-
|
|
110
|
+
const errMsg = `Unknown format(s): ${invalid.join(', ')}. Available: ${adapters_1.adapterRegistry.getIds().join(', ')}`;
|
|
111
|
+
if (jsonMode) {
|
|
112
|
+
result.errors.push(errMsg);
|
|
113
|
+
process.stdout.write(JSON.stringify(result, null, 2) + '\n');
|
|
114
|
+
process.exit(1);
|
|
115
|
+
}
|
|
116
|
+
throw new errors_1.CliError(errMsg);
|
|
87
117
|
}
|
|
88
118
|
}
|
|
89
119
|
else {
|
|
@@ -96,41 +126,65 @@ function registerInstallCommand(program) {
|
|
|
96
126
|
targetFormats = ['claude-code'];
|
|
97
127
|
}
|
|
98
128
|
}
|
|
129
|
+
result.formats = targetFormats;
|
|
99
130
|
// Validate scope
|
|
100
131
|
let scope = options.scope;
|
|
101
132
|
if (scope !== 'user' && scope !== 'project') {
|
|
102
|
-
|
|
133
|
+
const errMsg = 'Scope must be "user" or "project"';
|
|
134
|
+
if (jsonMode) {
|
|
135
|
+
result.errors.push(errMsg);
|
|
136
|
+
process.stdout.write(JSON.stringify(result, null, 2) + '\n');
|
|
137
|
+
process.exit(1);
|
|
138
|
+
}
|
|
139
|
+
throw new errors_1.CliError(errMsg);
|
|
103
140
|
}
|
|
141
|
+
result.scope = scope;
|
|
104
142
|
// Download agent
|
|
105
|
-
|
|
106
|
-
|
|
143
|
+
log(`Fetching ${org}/${parsed.name}@${parsed.version}...\n`);
|
|
144
|
+
let agent;
|
|
145
|
+
try {
|
|
146
|
+
agent = await downloadAgentWithFallback(resolved, org, parsed.name, parsed.version);
|
|
147
|
+
}
|
|
148
|
+
catch (err) {
|
|
149
|
+
if (jsonMode) {
|
|
150
|
+
result.errors.push(err instanceof Error ? err.message : String(err));
|
|
151
|
+
process.stdout.write(JSON.stringify(result, null, 2) + '\n');
|
|
152
|
+
process.exit(1);
|
|
153
|
+
}
|
|
154
|
+
throw err;
|
|
155
|
+
}
|
|
107
156
|
// Install for each format
|
|
108
157
|
let filesWritten = 0;
|
|
109
158
|
for (const formatId of targetFormats) {
|
|
110
159
|
const adapter = adapters_1.adapterRegistry.get(formatId);
|
|
111
160
|
if (!adapter) {
|
|
112
|
-
|
|
161
|
+
const warn = `Unknown format '${formatId}', skipping`;
|
|
162
|
+
result.warnings.push(warn);
|
|
163
|
+
logErr(`Warning: ${warn}\n`);
|
|
113
164
|
continue;
|
|
114
165
|
}
|
|
115
166
|
// Check if can convert
|
|
116
167
|
const checkResult = adapter.canConvert(agent);
|
|
117
168
|
if (!checkResult.canConvert) {
|
|
118
|
-
|
|
169
|
+
logErr(`Cannot convert to ${adapter.name}:\n`);
|
|
119
170
|
for (const err of checkResult.errors) {
|
|
120
|
-
|
|
171
|
+
result.errors.push(`${adapter.name}: ${err}`);
|
|
172
|
+
logErr(` - ${err}\n`);
|
|
121
173
|
}
|
|
122
174
|
continue;
|
|
123
175
|
}
|
|
124
176
|
// Show warnings
|
|
125
177
|
for (const warn of checkResult.warnings) {
|
|
126
|
-
|
|
178
|
+
result.warnings.push(`${formatId}: ${warn}`);
|
|
179
|
+
log(`Warning (${formatId}): ${warn}\n`);
|
|
127
180
|
}
|
|
128
181
|
// Determine scope for this adapter (use local variable to not affect other formats)
|
|
129
182
|
let effectiveScope = scope;
|
|
130
183
|
const supportedScopes = adapter.installPaths.map(p => p.scope);
|
|
131
184
|
if (!supportedScopes.includes(effectiveScope)) {
|
|
132
|
-
|
|
133
|
-
|
|
185
|
+
const warn = `${adapter.name} doesn't support '${scope}' scope. Using '${supportedScopes[0]}' instead.`;
|
|
186
|
+
result.warnings.push(warn);
|
|
187
|
+
logErr(`Warning: ${warn}\n`);
|
|
134
188
|
effectiveScope = supportedScopes[0];
|
|
135
189
|
}
|
|
136
190
|
// Convert
|
|
@@ -142,8 +196,9 @@ function registerInstallCommand(program) {
|
|
|
142
196
|
const fullDir = path_1.default.join(baseDir, file.installPath);
|
|
143
197
|
const fullPath = path_1.default.join(fullDir, file.filename);
|
|
144
198
|
if (options.dryRun) {
|
|
145
|
-
|
|
146
|
-
|
|
199
|
+
log(`Would install: ${fullPath}\n`);
|
|
200
|
+
log(`Content preview:\n${file.content.slice(0, 500)}...\n\n`);
|
|
201
|
+
result.files.push({ path: fullPath, format: formatId });
|
|
147
202
|
continue;
|
|
148
203
|
}
|
|
149
204
|
// Create directory and write file
|
|
@@ -174,7 +229,8 @@ function registerInstallCommand(program) {
|
|
|
174
229
|
contentHash: (0, installed_1.computeHash)(file.content),
|
|
175
230
|
};
|
|
176
231
|
await (0, installed_1.trackInstall)(installedAgent);
|
|
177
|
-
|
|
232
|
+
result.files.push({ path: fullPath, format: formatId });
|
|
233
|
+
log(`Installed: ${fullPath}\n`);
|
|
178
234
|
}
|
|
179
235
|
}
|
|
180
236
|
if (!options.dryRun) {
|
|
@@ -184,18 +240,33 @@ function registerInstallCommand(program) {
|
|
|
184
240
|
formats: targetFormats,
|
|
185
241
|
scope,
|
|
186
242
|
});
|
|
187
|
-
|
|
243
|
+
result.success = true;
|
|
244
|
+
log(`\nAgent installed successfully!\n`);
|
|
188
245
|
if (scope === 'user') {
|
|
189
|
-
|
|
246
|
+
log(`Available in all your projects.\n`);
|
|
190
247
|
}
|
|
191
248
|
else {
|
|
192
|
-
|
|
249
|
+
log(`Available in this project only.\n`);
|
|
193
250
|
}
|
|
194
251
|
}
|
|
195
252
|
else {
|
|
196
|
-
|
|
253
|
+
result.errors.push('No files were installed. Check warnings.');
|
|
254
|
+
logErr(`\nNo files were installed. Check warnings above.\n`);
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
else {
|
|
258
|
+
// Dry run is considered success if we got file list
|
|
259
|
+
result.success = result.files.length > 0;
|
|
260
|
+
}
|
|
261
|
+
// Output JSON result
|
|
262
|
+
if (jsonMode) {
|
|
263
|
+
process.stdout.write(JSON.stringify(result, null, 2) + '\n');
|
|
264
|
+
if (!result.success) {
|
|
197
265
|
process.exit(1);
|
|
198
266
|
}
|
|
199
267
|
}
|
|
268
|
+
else if (!result.success && !options.dryRun) {
|
|
269
|
+
process.exit(1);
|
|
270
|
+
}
|
|
200
271
|
});
|
|
201
272
|
}
|
package/dist/commands/publish.js
CHANGED
|
@@ -8,6 +8,7 @@ const promises_1 = __importDefault(require("fs/promises"));
|
|
|
8
8
|
const path_1 = __importDefault(require("path"));
|
|
9
9
|
const os_1 = __importDefault(require("os"));
|
|
10
10
|
const yaml_1 = __importDefault(require("yaml"));
|
|
11
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
11
12
|
const config_1 = require("../lib/config");
|
|
12
13
|
const api_1 = require("../lib/api");
|
|
13
14
|
const errors_1 = require("../lib/errors");
|
|
@@ -463,6 +464,20 @@ function registerPublishCommand(program) {
|
|
|
463
464
|
process.stdout.write(` Uploading bundle...\n`);
|
|
464
465
|
const uploadResult = await (0, api_1.uploadCodeBundle)(config, agentId, bundlePath, manifest.entrypoint);
|
|
465
466
|
process.stdout.write(` Uploaded: ${uploadResult.code_hash.substring(0, 12)}...\n`);
|
|
467
|
+
// Show environment info if applicable
|
|
468
|
+
if (uploadResult.environment_id) {
|
|
469
|
+
if (uploadResult.environment_source === 'dockerfile_new') {
|
|
470
|
+
process.stdout.write(` ${chalk_1.default.cyan('Custom environment detected (Dockerfile)')}\n`);
|
|
471
|
+
process.stdout.write(` ${chalk_1.default.yellow('Environment building...')} Agent will be ready when build completes.\n`);
|
|
472
|
+
process.stdout.write(` ${chalk_1.default.gray(`Check status: orch env status ${uploadResult.environment_id}`)}\n`);
|
|
473
|
+
}
|
|
474
|
+
else if (uploadResult.environment_source === 'dockerfile_reused') {
|
|
475
|
+
process.stdout.write(` ${chalk_1.default.green('Custom environment (reusing existing build)')}\n`);
|
|
476
|
+
}
|
|
477
|
+
else if (uploadResult.environment_source === 'workspace_default') {
|
|
478
|
+
process.stdout.write(` ${chalk_1.default.cyan('Using workspace default environment')}\n`);
|
|
479
|
+
}
|
|
480
|
+
}
|
|
466
481
|
}
|
|
467
482
|
finally {
|
|
468
483
|
// Clean up temp files
|
package/dist/commands/skill.js
CHANGED
|
@@ -164,7 +164,24 @@ Instructions and guidance for AI agents...
|
|
|
164
164
|
.option('--scope <scope>', 'Install scope: user or project', 'project')
|
|
165
165
|
.option('--dry-run', 'Show what would be installed without making changes')
|
|
166
166
|
.option('--format <formats>', 'Comma-separated format IDs (e.g., claude-code,cursor)')
|
|
167
|
+
.option('--json', 'Output result as JSON (for automation/tooling)')
|
|
167
168
|
.action(async (skillRef, options) => {
|
|
169
|
+
const jsonMode = options.json === true;
|
|
170
|
+
const log = (msg) => { if (!jsonMode)
|
|
171
|
+
process.stdout.write(msg); };
|
|
172
|
+
const logErr = (msg) => { if (!jsonMode)
|
|
173
|
+
process.stderr.write(msg); };
|
|
174
|
+
// Result tracking for JSON output
|
|
175
|
+
const result = {
|
|
176
|
+
success: false,
|
|
177
|
+
skill: '',
|
|
178
|
+
version: '',
|
|
179
|
+
scope: '',
|
|
180
|
+
tools: [],
|
|
181
|
+
files: [],
|
|
182
|
+
warnings: [],
|
|
183
|
+
errors: [],
|
|
184
|
+
};
|
|
168
185
|
const resolved = await (0, config_1.getResolvedConfig)();
|
|
169
186
|
// Determine target formats
|
|
170
187
|
let targetFormats = [];
|
|
@@ -173,7 +190,13 @@ Instructions and guidance for AI agents...
|
|
|
173
190
|
// Validate format IDs
|
|
174
191
|
const invalid = targetFormats.filter(f => !config_1.VALID_FORMAT_IDS.includes(f));
|
|
175
192
|
if (invalid.length > 0) {
|
|
176
|
-
|
|
193
|
+
const errMsg = `Invalid format ID(s): ${invalid.join(', ')}. Valid: ${config_1.VALID_FORMAT_IDS.join(', ')}`;
|
|
194
|
+
if (jsonMode) {
|
|
195
|
+
result.errors.push(errMsg);
|
|
196
|
+
process.stdout.write(JSON.stringify(result, null, 2) + '\n');
|
|
197
|
+
process.exit(1);
|
|
198
|
+
}
|
|
199
|
+
throw new errors_1.CliError(errMsg);
|
|
177
200
|
}
|
|
178
201
|
}
|
|
179
202
|
else {
|
|
@@ -183,7 +206,9 @@ Instructions and guidance for AI agents...
|
|
|
183
206
|
targetFormats = defaults.filter(f => config_1.VALID_FORMAT_IDS.includes(f));
|
|
184
207
|
const skipped = defaults.filter(f => !config_1.VALID_FORMAT_IDS.includes(f));
|
|
185
208
|
if (skipped.length > 0) {
|
|
186
|
-
|
|
209
|
+
const warn = `Skipping ${skipped.join(', ')} (no skill directory)`;
|
|
210
|
+
result.warnings.push(warn);
|
|
211
|
+
logErr(`Note: ${warn}\n`);
|
|
187
212
|
}
|
|
188
213
|
}
|
|
189
214
|
}
|
|
@@ -194,17 +219,43 @@ Instructions and guidance for AI agents...
|
|
|
194
219
|
const parsed = parseSkillRef(skillRef);
|
|
195
220
|
const org = parsed.org ?? resolved.defaultOrg;
|
|
196
221
|
if (!org) {
|
|
197
|
-
|
|
222
|
+
const errMsg = 'Missing org. Use org/skill or set default org.';
|
|
223
|
+
if (jsonMode) {
|
|
224
|
+
result.errors.push(errMsg);
|
|
225
|
+
process.stdout.write(JSON.stringify(result, null, 2) + '\n');
|
|
226
|
+
process.exit(1);
|
|
227
|
+
}
|
|
228
|
+
throw new errors_1.CliError(errMsg);
|
|
198
229
|
}
|
|
230
|
+
result.skill = `${org}/${parsed.skill}`;
|
|
231
|
+
result.version = parsed.version;
|
|
199
232
|
// Download skill (tries public first, falls back to authenticated for private)
|
|
200
|
-
|
|
233
|
+
let skillData;
|
|
234
|
+
try {
|
|
235
|
+
skillData = await downloadSkillWithFallback(resolved, org, parsed.skill, parsed.version);
|
|
236
|
+
}
|
|
237
|
+
catch (err) {
|
|
238
|
+
if (jsonMode) {
|
|
239
|
+
result.errors.push(err instanceof Error ? err.message : String(err));
|
|
240
|
+
process.stdout.write(JSON.stringify(result, null, 2) + '\n');
|
|
241
|
+
process.exit(1);
|
|
242
|
+
}
|
|
243
|
+
throw err;
|
|
244
|
+
}
|
|
201
245
|
if (!skillData.prompt) {
|
|
246
|
+
const errMsg = 'Skill has no content. The skill exists but has an empty prompt.';
|
|
247
|
+
if (jsonMode) {
|
|
248
|
+
result.errors.push(errMsg);
|
|
249
|
+
process.stdout.write(JSON.stringify(result, null, 2) + '\n');
|
|
250
|
+
process.exit(1);
|
|
251
|
+
}
|
|
202
252
|
throw new errors_1.CliError('Skill has no content.\n\n' +
|
|
203
253
|
'The skill exists but has an empty prompt. This may be a publishing issue.\n' +
|
|
204
254
|
'Try re-publishing the skill or contact the skill author.');
|
|
205
255
|
}
|
|
206
256
|
// Determine scope (--global is legacy alias for --scope user)
|
|
207
257
|
const scope = options.global ? 'user' : (options.scope || 'project');
|
|
258
|
+
result.scope = scope;
|
|
208
259
|
// Build skill content with header
|
|
209
260
|
const skillContent = `# ${skillData.name}
|
|
210
261
|
|
|
@@ -216,16 +267,21 @@ ${skillData.prompt}
|
|
|
216
267
|
`;
|
|
217
268
|
// Dry run - show what would be installed
|
|
218
269
|
if (options.dryRun) {
|
|
219
|
-
|
|
220
|
-
|
|
270
|
+
log(`Would install ${org}/${parsed.skill}@${parsed.version}\n\n`);
|
|
271
|
+
log(`Target directories (scope: ${scope}):\n`);
|
|
221
272
|
for (const tool of toolDirs) {
|
|
222
273
|
const baseDir = scope === 'user' ? os_1.default.homedir() : process.cwd();
|
|
223
274
|
const toolPath = scope === 'user' ? tool.userPath : tool.projectPath;
|
|
224
275
|
const skillDir = path_1.default.join(baseDir, toolPath);
|
|
225
276
|
const skillFile = path_1.default.join(skillDir, `${parsed.skill}.md`);
|
|
226
|
-
|
|
277
|
+
result.files.push({ path: skillFile, tool: tool.name });
|
|
278
|
+
log(` - ${tool.name}: ${skillFile}\n`);
|
|
279
|
+
}
|
|
280
|
+
log(`\nNo changes made (dry run)\n`);
|
|
281
|
+
result.success = true;
|
|
282
|
+
if (jsonMode) {
|
|
283
|
+
process.stdout.write(JSON.stringify(result, null, 2) + '\n');
|
|
227
284
|
}
|
|
228
|
-
process.stdout.write(`\nNo changes made (dry run)\n`);
|
|
229
285
|
return;
|
|
230
286
|
}
|
|
231
287
|
// Install to target AI tool directories
|
|
@@ -239,15 +295,26 @@ ${skillData.prompt}
|
|
|
239
295
|
await promises_1.default.mkdir(skillDir, { recursive: true });
|
|
240
296
|
await promises_1.default.writeFile(skillFile, skillContent);
|
|
241
297
|
installed.push(tool.name);
|
|
298
|
+
result.files.push({ path: skillFile, tool: tool.name });
|
|
242
299
|
}
|
|
243
300
|
catch (err) {
|
|
244
301
|
// Skip if we can't write (e.g., permission issues)
|
|
245
|
-
|
|
302
|
+
const warn = `Could not install to ${toolPath}: ${err.message}`;
|
|
303
|
+
result.warnings.push(warn);
|
|
304
|
+
logErr(`Warning: ${warn}\n`);
|
|
246
305
|
}
|
|
247
306
|
}
|
|
248
307
|
if (installed.length === 0) {
|
|
249
|
-
|
|
308
|
+
const errMsg = 'Failed to install skill to any directory';
|
|
309
|
+
if (jsonMode) {
|
|
310
|
+
result.errors.push(errMsg);
|
|
311
|
+
process.stdout.write(JSON.stringify(result, null, 2) + '\n');
|
|
312
|
+
process.exit(1);
|
|
313
|
+
}
|
|
314
|
+
throw new errors_1.CliError(errMsg);
|
|
250
315
|
}
|
|
316
|
+
result.tools = installed;
|
|
317
|
+
result.success = true;
|
|
251
318
|
await (0, analytics_1.track)('cli_skill_install', {
|
|
252
319
|
skill: `${org}/${parsed.skill}`,
|
|
253
320
|
scope,
|
|
@@ -257,11 +324,16 @@ ${skillData.prompt}
|
|
|
257
324
|
if (resolved.apiKey) {
|
|
258
325
|
(0, api_1.reportInstall)(resolved, org, parsed.skill, parsed.version, package_json_1.default.version).catch(() => { });
|
|
259
326
|
}
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
327
|
+
if (jsonMode) {
|
|
328
|
+
process.stdout.write(JSON.stringify(result, null, 2) + '\n');
|
|
329
|
+
}
|
|
330
|
+
else {
|
|
331
|
+
log(`Installed ${org}/${parsed.skill}@${parsed.version}\n`);
|
|
332
|
+
log(`\nAvailable for:\n`);
|
|
333
|
+
for (const tool of installed) {
|
|
334
|
+
log(` - ${tool}\n`);
|
|
335
|
+
}
|
|
336
|
+
log(`\nLocation: ${scope === 'user' ? '~/' : './'}\n`);
|
|
264
337
|
}
|
|
265
|
-
process.stdout.write(`\nLocation: ${scope === 'user' ? '~/' : './'}\n`);
|
|
266
338
|
});
|
|
267
339
|
}
|
package/dist/lib/api.js
CHANGED
|
@@ -35,6 +35,7 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
36
|
exports.ApiError = void 0;
|
|
37
37
|
exports.safeFetch = safeFetch;
|
|
38
|
+
exports.safeFetchWithRetryForCalls = safeFetchWithRetryForCalls;
|
|
38
39
|
exports.request = request;
|
|
39
40
|
exports.publicRequest = publicRequest;
|
|
40
41
|
exports.getOrg = getOrg;
|
|
@@ -58,15 +59,23 @@ exports.deleteAgent = deleteAgent;
|
|
|
58
59
|
exports.previewAgentVersion = previewAgentVersion;
|
|
59
60
|
exports.reportInstall = reportInstall;
|
|
60
61
|
exports.fetchUserProfile = fetchUserProfile;
|
|
62
|
+
exports.listEnvironments = listEnvironments;
|
|
63
|
+
exports.getEnvironment = getEnvironment;
|
|
64
|
+
exports.createEnvironment = createEnvironment;
|
|
65
|
+
exports.deleteEnvironment = deleteEnvironment;
|
|
66
|
+
exports.setWorkspaceDefaultEnvironment = setWorkspaceDefaultEnvironment;
|
|
61
67
|
const errors_1 = require("./errors");
|
|
62
68
|
const DEFAULT_TIMEOUT_MS = 15000;
|
|
69
|
+
const CALL_TIMEOUT_MS = 120000; // 2 minutes for agent calls (can take time)
|
|
63
70
|
const MAX_RETRIES = 3;
|
|
64
71
|
const BASE_DELAY_MS = 1000;
|
|
65
72
|
async function safeFetch(url, options) {
|
|
73
|
+
const timeoutMs = options?.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
74
|
+
const { timeoutMs: _, ...fetchOptions } = options ?? {};
|
|
66
75
|
try {
|
|
67
76
|
return await fetch(url, {
|
|
68
|
-
...
|
|
69
|
-
signal: AbortSignal.timeout(
|
|
77
|
+
...fetchOptions,
|
|
78
|
+
signal: AbortSignal.timeout(timeoutMs),
|
|
70
79
|
});
|
|
71
80
|
}
|
|
72
81
|
catch (err) {
|
|
@@ -77,6 +86,44 @@ async function safeFetch(url, options) {
|
|
|
77
86
|
throw new errors_1.NetworkError(url, err instanceof Error ? err : undefined);
|
|
78
87
|
}
|
|
79
88
|
}
|
|
89
|
+
/**
|
|
90
|
+
* safeFetch with retry logic for connection failures.
|
|
91
|
+
* Use for important operations that should retry on transient errors.
|
|
92
|
+
*/
|
|
93
|
+
async function safeFetchWithRetryForCalls(url, options) {
|
|
94
|
+
let lastError;
|
|
95
|
+
const timeoutMs = options?.timeoutMs ?? CALL_TIMEOUT_MS;
|
|
96
|
+
for (let attempt = 1; attempt <= MAX_RETRIES; attempt++) {
|
|
97
|
+
try {
|
|
98
|
+
const response = await safeFetch(url, { ...options, timeoutMs });
|
|
99
|
+
// Don't retry client errors (except 429)
|
|
100
|
+
if (response.status >= 400 && response.status < 500 && response.status !== 429) {
|
|
101
|
+
return response;
|
|
102
|
+
}
|
|
103
|
+
// Retry on 5xx or 429
|
|
104
|
+
if (response.status >= 500 || response.status === 429) {
|
|
105
|
+
if (attempt < MAX_RETRIES) {
|
|
106
|
+
const delay = BASE_DELAY_MS * Math.pow(2, attempt - 1);
|
|
107
|
+
const jitter = Math.random() * 500;
|
|
108
|
+
process.stderr.write(`Request failed (${response.status}), retrying in ${Math.round((delay + jitter) / 1000)}s...\n`);
|
|
109
|
+
await new Promise(r => setTimeout(r, delay + jitter));
|
|
110
|
+
continue;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
return response;
|
|
114
|
+
}
|
|
115
|
+
catch (error) {
|
|
116
|
+
lastError = error;
|
|
117
|
+
if (attempt < MAX_RETRIES) {
|
|
118
|
+
const delay = BASE_DELAY_MS * Math.pow(2, attempt - 1);
|
|
119
|
+
const jitter = Math.random() * 500;
|
|
120
|
+
process.stderr.write(`Connection error, retrying in ${Math.round((delay + jitter) / 1000)}s...\n`);
|
|
121
|
+
await new Promise(r => setTimeout(r, delay + jitter));
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
throw lastError ?? new errors_1.NetworkError(url);
|
|
126
|
+
}
|
|
80
127
|
async function safeFetchWithRetry(url, options) {
|
|
81
128
|
let lastError;
|
|
82
129
|
for (let attempt = 1; attempt <= MAX_RETRIES; attempt++) {
|
|
@@ -357,3 +404,40 @@ async function fetchUserProfile(config) {
|
|
|
357
404
|
const result = await request(config, 'GET', '/users/me');
|
|
358
405
|
return result.user;
|
|
359
406
|
}
|
|
407
|
+
/**
|
|
408
|
+
* List environments in a workspace (plus predefined).
|
|
409
|
+
*/
|
|
410
|
+
async function listEnvironments(config, workspaceId) {
|
|
411
|
+
const params = workspaceId ? `?workspace_id=${encodeURIComponent(workspaceId)}` : '';
|
|
412
|
+
return request(config, 'GET', `/environments${params}`);
|
|
413
|
+
}
|
|
414
|
+
/**
|
|
415
|
+
* Get environment details including build status.
|
|
416
|
+
*/
|
|
417
|
+
async function getEnvironment(config, environmentId) {
|
|
418
|
+
return request(config, 'GET', `/environments/${environmentId}`);
|
|
419
|
+
}
|
|
420
|
+
/**
|
|
421
|
+
* Create a new environment from Dockerfile.
|
|
422
|
+
*/
|
|
423
|
+
async function createEnvironment(config, name, dockerfileContent) {
|
|
424
|
+
return request(config, 'POST', '/environments', {
|
|
425
|
+
body: JSON.stringify({ name, dockerfile_content: dockerfileContent }),
|
|
426
|
+
headers: { 'Content-Type': 'application/json' },
|
|
427
|
+
});
|
|
428
|
+
}
|
|
429
|
+
/**
|
|
430
|
+
* Delete an environment (must have no agents using it).
|
|
431
|
+
*/
|
|
432
|
+
async function deleteEnvironment(config, environmentId) {
|
|
433
|
+
return request(config, 'DELETE', `/environments/${environmentId}`);
|
|
434
|
+
}
|
|
435
|
+
/**
|
|
436
|
+
* Set workspace default environment.
|
|
437
|
+
*/
|
|
438
|
+
async function setWorkspaceDefaultEnvironment(config, workspaceId, environmentId) {
|
|
439
|
+
return request(config, 'POST', `/environments/workspaces/${workspaceId}/default-environment`, {
|
|
440
|
+
body: JSON.stringify({ environment_id: environmentId }),
|
|
441
|
+
headers: { 'Content-Type': 'application/json' },
|
|
442
|
+
});
|
|
443
|
+
}
|