@orchagent/cli 0.3.118 → 0.3.120
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/AGENT_GUIDE.md +34 -0
- package/dist/commands/agents.js +15 -2
- package/dist/commands/context.js +76 -0
- package/dist/commands/describe.js +131 -0
- package/dist/commands/index.js +6 -0
- package/dist/commands/info.js +8 -2
- package/dist/commands/login.js +18 -6
- package/dist/commands/logs.js +35 -1
- package/dist/commands/publish.js +107 -12
- package/dist/commands/run.js +39 -9
- package/dist/commands/schedule.js +37 -5
- package/dist/commands/schema.js +78 -0
- package/dist/commands/secrets.js +17 -2
- package/dist/commands/service.js +20 -3
- package/dist/commands/storage.js +61 -24
- package/dist/index.js +21 -2
- package/dist/lib/agent-ref.js +3 -0
- package/dist/lib/command-introspection.js +250 -0
- package/dist/lib/errors.js +125 -18
- package/dist/lib/list-options.js +69 -0
- package/dist/lib/llm.js +35 -0
- package/dist/lib/output.js +29 -0
- package/dist/lib/sanitize.js +99 -0
- package/dist/lib/validate.js +17 -0
- package/package.json +3 -2
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.tokenizeCommandQuery = tokenizeCommandQuery;
|
|
4
|
+
exports.buildCliCommandMetadata = buildCliCommandMetadata;
|
|
5
|
+
exports.findCommandMetadata = findCommandMetadata;
|
|
6
|
+
exports.collectFlagNames = collectFlagNames;
|
|
7
|
+
const MUTATING_TOKENS = new Set([
|
|
8
|
+
'add',
|
|
9
|
+
'clear',
|
|
10
|
+
'connect',
|
|
11
|
+
'create',
|
|
12
|
+
'delete',
|
|
13
|
+
'deploy',
|
|
14
|
+
'disconnect',
|
|
15
|
+
'fork',
|
|
16
|
+
'install',
|
|
17
|
+
'invite',
|
|
18
|
+
'login',
|
|
19
|
+
'logout',
|
|
20
|
+
'publish',
|
|
21
|
+
'remove',
|
|
22
|
+
'revoke',
|
|
23
|
+
'rotate',
|
|
24
|
+
'set',
|
|
25
|
+
'trigger',
|
|
26
|
+
'transfer',
|
|
27
|
+
'unlink',
|
|
28
|
+
'unset',
|
|
29
|
+
'update',
|
|
30
|
+
'use',
|
|
31
|
+
]);
|
|
32
|
+
function normalizeToken(value) {
|
|
33
|
+
return value.trim().toLowerCase();
|
|
34
|
+
}
|
|
35
|
+
function parseAliasTarget(description) {
|
|
36
|
+
const match = description.match(/^\s*alias for\s+(--[a-z0-9][a-z0-9-]*)(?:\.)?\s*$/i);
|
|
37
|
+
if (!match)
|
|
38
|
+
return null;
|
|
39
|
+
return match[1].toLowerCase();
|
|
40
|
+
}
|
|
41
|
+
function extractOptionAliases(options) {
|
|
42
|
+
const aliasOptions = new Set();
|
|
43
|
+
const aliasesByPrimary = new Map();
|
|
44
|
+
for (const option of options) {
|
|
45
|
+
if (!option.long)
|
|
46
|
+
continue;
|
|
47
|
+
const primary = parseAliasTarget(option.description || '');
|
|
48
|
+
if (!primary)
|
|
49
|
+
continue;
|
|
50
|
+
const aliasName = option.long.toLowerCase();
|
|
51
|
+
if (aliasName === primary)
|
|
52
|
+
continue;
|
|
53
|
+
aliasOptions.add(aliasName);
|
|
54
|
+
const existing = aliasesByPrimary.get(primary) || [];
|
|
55
|
+
existing.push(option.long);
|
|
56
|
+
aliasesByPrimary.set(primary, existing);
|
|
57
|
+
}
|
|
58
|
+
return { aliasOptions, aliasesByPrimary };
|
|
59
|
+
}
|
|
60
|
+
function getFlagType(option) {
|
|
61
|
+
if (option.isBoolean())
|
|
62
|
+
return 'boolean';
|
|
63
|
+
if (option.variadic)
|
|
64
|
+
return 'string[]';
|
|
65
|
+
return 'string';
|
|
66
|
+
}
|
|
67
|
+
function inferArgumentFormat(pathSegments, argument) {
|
|
68
|
+
const name = argument.name().toLowerCase();
|
|
69
|
+
const path = pathSegments.join(' ').toLowerCase();
|
|
70
|
+
if (name === 'agent' || name.endsWith('agent')) {
|
|
71
|
+
return 'org/name[@version]';
|
|
72
|
+
}
|
|
73
|
+
if (name === 'command') {
|
|
74
|
+
return 'command [subcommand...]';
|
|
75
|
+
}
|
|
76
|
+
if (name === 'shell') {
|
|
77
|
+
return 'bash | zsh | fish';
|
|
78
|
+
}
|
|
79
|
+
if (name === 'json' || name === 'data' || name === 'input' || name.includes('json')) {
|
|
80
|
+
return 'inline-json | @file | @-';
|
|
81
|
+
}
|
|
82
|
+
if (name.includes('file') || name.includes('path') || name.includes('dir')) {
|
|
83
|
+
return 'path';
|
|
84
|
+
}
|
|
85
|
+
if (path === 'run' && name === 'agent') {
|
|
86
|
+
return 'org/name[@version]';
|
|
87
|
+
}
|
|
88
|
+
return argument.variadic ? `${argument.name()}...` : argument.name();
|
|
89
|
+
}
|
|
90
|
+
function extractArguments(pathSegments, args) {
|
|
91
|
+
return args.map((arg) => ({
|
|
92
|
+
name: arg.name(),
|
|
93
|
+
required: arg.required,
|
|
94
|
+
variadic: arg.variadic,
|
|
95
|
+
description: arg.description || '',
|
|
96
|
+
format: inferArgumentFormat(pathSegments, arg),
|
|
97
|
+
}));
|
|
98
|
+
}
|
|
99
|
+
function extractFlags(options) {
|
|
100
|
+
const { aliasOptions, aliasesByPrimary } = extractOptionAliases(options);
|
|
101
|
+
const flags = [];
|
|
102
|
+
for (const option of options) {
|
|
103
|
+
if (option.hidden)
|
|
104
|
+
continue;
|
|
105
|
+
if (!option.long && !option.short)
|
|
106
|
+
continue;
|
|
107
|
+
if (option.long && aliasOptions.has(option.long.toLowerCase()))
|
|
108
|
+
continue;
|
|
109
|
+
if (option.long === '--help')
|
|
110
|
+
continue;
|
|
111
|
+
const aliases = option.long
|
|
112
|
+
? aliasesByPrimary.get(option.long.toLowerCase()) || []
|
|
113
|
+
: [];
|
|
114
|
+
flags.push({
|
|
115
|
+
name: option.long || option.short,
|
|
116
|
+
short: option.short || null,
|
|
117
|
+
type: getFlagType(option),
|
|
118
|
+
required: option.mandatory,
|
|
119
|
+
valueRequired: option.required,
|
|
120
|
+
description: option.description || '',
|
|
121
|
+
...(option.defaultValue !== undefined ? { defaultValue: option.defaultValue } : {}),
|
|
122
|
+
...(option.argChoices ? { choices: option.argChoices } : {}),
|
|
123
|
+
...(aliases.length > 0 ? { alias: aliases[0] } : {}),
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
return flags;
|
|
127
|
+
}
|
|
128
|
+
function renderHelpText(command) {
|
|
129
|
+
const internal = command;
|
|
130
|
+
const original = internal._outputConfiguration;
|
|
131
|
+
let output = '';
|
|
132
|
+
internal._outputConfiguration = {
|
|
133
|
+
...original,
|
|
134
|
+
writeOut: (str) => {
|
|
135
|
+
output += str;
|
|
136
|
+
},
|
|
137
|
+
writeErr: (str) => {
|
|
138
|
+
output += str;
|
|
139
|
+
},
|
|
140
|
+
};
|
|
141
|
+
try {
|
|
142
|
+
command.outputHelp();
|
|
143
|
+
}
|
|
144
|
+
finally {
|
|
145
|
+
internal._outputConfiguration = original;
|
|
146
|
+
}
|
|
147
|
+
return output;
|
|
148
|
+
}
|
|
149
|
+
function extractExamples(command) {
|
|
150
|
+
const helpText = renderHelpText(command);
|
|
151
|
+
const examples = helpText
|
|
152
|
+
.split('\n')
|
|
153
|
+
.map((line) => line.trim())
|
|
154
|
+
.filter((line) => /^(orch|orchagent)\s+\S+/.test(line));
|
|
155
|
+
return [...new Set(examples)];
|
|
156
|
+
}
|
|
157
|
+
function isMutatingSelf(pathSegments, description, flags) {
|
|
158
|
+
if (flags.some((flag) => flag.name === '--dry-run')) {
|
|
159
|
+
return true;
|
|
160
|
+
}
|
|
161
|
+
const tokens = pathSegments
|
|
162
|
+
.map((segment) => normalizeToken(segment))
|
|
163
|
+
.flatMap((segment) => segment.split(/[^a-z0-9]+/g))
|
|
164
|
+
.filter(Boolean);
|
|
165
|
+
if (tokens.some((token) => MUTATING_TOKENS.has(token))) {
|
|
166
|
+
return true;
|
|
167
|
+
}
|
|
168
|
+
const desc = description.toLowerCase();
|
|
169
|
+
return /\b(add|create|delete|deploy|fork|install|invite|login|logout|publish|remove|revoke|set|transfer|trigger|unset|update|use)\b/.test(desc);
|
|
170
|
+
}
|
|
171
|
+
function buildUsage(pathSegments, command) {
|
|
172
|
+
const base = pathSegments.join(' ');
|
|
173
|
+
const usage = command.usage();
|
|
174
|
+
if (!usage)
|
|
175
|
+
return base;
|
|
176
|
+
return `${base} ${usage}`.trim();
|
|
177
|
+
}
|
|
178
|
+
function buildCommandMetadata(command, parentPath) {
|
|
179
|
+
const pathSegments = [...parentPath, command.name()];
|
|
180
|
+
const subcommands = command.commands
|
|
181
|
+
.filter((subcommand) => subcommand.name() !== 'help')
|
|
182
|
+
.map((subcommand) => buildCommandMetadata(subcommand, pathSegments));
|
|
183
|
+
const flags = extractFlags(command.options);
|
|
184
|
+
const selfMutating = isMutatingSelf(pathSegments, command.description(), flags);
|
|
185
|
+
const childMutating = subcommands.some((subcommand) => subcommand.mutations);
|
|
186
|
+
const hasDryRun = flags.some((flag) => flag.name === '--dry-run') || subcommands.some((subcommand) => subcommand.dryRun);
|
|
187
|
+
return {
|
|
188
|
+
name: command.name(),
|
|
189
|
+
path: pathSegments.join(' '),
|
|
190
|
+
description: command.description() || '',
|
|
191
|
+
usage: buildUsage(pathSegments, command),
|
|
192
|
+
aliases: command.aliases(),
|
|
193
|
+
arguments: extractArguments(pathSegments, command.registeredArguments),
|
|
194
|
+
flags,
|
|
195
|
+
subcommands,
|
|
196
|
+
mutations: selfMutating || childMutating,
|
|
197
|
+
dryRun: hasDryRun,
|
|
198
|
+
examples: extractExamples(command),
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
function isCommandMatch(command, token) {
|
|
202
|
+
const lower = normalizeToken(token);
|
|
203
|
+
if (command.name.toLowerCase() === lower)
|
|
204
|
+
return true;
|
|
205
|
+
return command.aliases.some((alias) => alias.toLowerCase() === lower);
|
|
206
|
+
}
|
|
207
|
+
function tokenizeCommandQuery(parts) {
|
|
208
|
+
return parts
|
|
209
|
+
.flatMap((part) => part.split(/[/:.]/g))
|
|
210
|
+
.map((part) => normalizeToken(part))
|
|
211
|
+
.filter(Boolean);
|
|
212
|
+
}
|
|
213
|
+
function buildCliCommandMetadata(program, options) {
|
|
214
|
+
const excluded = new Set((options?.excludeTopLevelCommands || []).map((name) => name.toLowerCase()));
|
|
215
|
+
return program.commands
|
|
216
|
+
.filter((command) => command.name() !== 'help')
|
|
217
|
+
.filter((command) => !excluded.has(command.name().toLowerCase()))
|
|
218
|
+
.map((command) => buildCommandMetadata(command, []));
|
|
219
|
+
}
|
|
220
|
+
function findCommandMetadata(commands, queryParts) {
|
|
221
|
+
const tokens = tokenizeCommandQuery(queryParts);
|
|
222
|
+
if (tokens.length === 0)
|
|
223
|
+
return null;
|
|
224
|
+
let currentCommands = commands;
|
|
225
|
+
let matched = null;
|
|
226
|
+
for (const token of tokens) {
|
|
227
|
+
matched = currentCommands.find((command) => isCommandMatch(command, token)) || null;
|
|
228
|
+
if (!matched) {
|
|
229
|
+
return null;
|
|
230
|
+
}
|
|
231
|
+
currentCommands = matched.subcommands;
|
|
232
|
+
}
|
|
233
|
+
return matched;
|
|
234
|
+
}
|
|
235
|
+
function collectFlagNames(command) {
|
|
236
|
+
const names = new Set();
|
|
237
|
+
const visit = (node) => {
|
|
238
|
+
for (const flag of node.flags) {
|
|
239
|
+
names.add(flag.name);
|
|
240
|
+
if (flag.alias) {
|
|
241
|
+
names.add(flag.alias);
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
for (const child of node.subcommands) {
|
|
245
|
+
visit(child);
|
|
246
|
+
}
|
|
247
|
+
};
|
|
248
|
+
visit(command);
|
|
249
|
+
return [...names].sort();
|
|
250
|
+
}
|
package/dist/lib/errors.js
CHANGED
|
@@ -33,16 +33,32 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
33
33
|
};
|
|
34
34
|
})();
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
-
exports.NetworkError = exports.ExitCodes = exports.CliError = void 0;
|
|
36
|
+
exports.NetworkError = exports.ExitCodes = exports.CliError = exports.ErrorCodes = void 0;
|
|
37
37
|
exports.formatError = formatError;
|
|
38
|
+
exports.formatJsonError = formatJsonError;
|
|
38
39
|
exports.exitWithError = exitWithError;
|
|
39
40
|
exports.mapHttpStatusToExitCode = mapHttpStatusToExitCode;
|
|
40
41
|
exports.jsonInputError = jsonInputError;
|
|
41
42
|
const Sentry = __importStar(require("@sentry/node"));
|
|
42
43
|
const analytics_1 = require("./analytics");
|
|
43
44
|
const api_1 = require("./api");
|
|
45
|
+
const output_1 = require("./output");
|
|
46
|
+
exports.ErrorCodes = {
|
|
47
|
+
GENERAL_ERROR: 'GENERAL_ERROR',
|
|
48
|
+
AUTH_REQUIRED: 'AUTH_REQUIRED',
|
|
49
|
+
PERMISSION_DENIED: 'PERMISSION_DENIED',
|
|
50
|
+
NOT_FOUND: 'NOT_FOUND',
|
|
51
|
+
INVALID_INPUT: 'INVALID_INPUT',
|
|
52
|
+
RATE_LIMITED: 'RATE_LIMITED',
|
|
53
|
+
TIMEOUT: 'TIMEOUT',
|
|
54
|
+
SERVER_ERROR: 'SERVER_ERROR',
|
|
55
|
+
NETWORK_ERROR: 'NETWORK_ERROR',
|
|
56
|
+
MISSING_SECRETS: 'MISSING_SECRETS',
|
|
57
|
+
};
|
|
44
58
|
class CliError extends Error {
|
|
45
59
|
exitCode;
|
|
60
|
+
code;
|
|
61
|
+
hint;
|
|
46
62
|
cause;
|
|
47
63
|
responseBody;
|
|
48
64
|
/** When true, exitWithError skips printing — the message was already shown (e.g. via spinner.fail). */
|
|
@@ -96,28 +112,119 @@ function formatError(err) {
|
|
|
96
112
|
}
|
|
97
113
|
return String(err);
|
|
98
114
|
}
|
|
115
|
+
/** Map an exit code to a default error code string. */
|
|
116
|
+
function exitCodeToErrorCode(exitCode) {
|
|
117
|
+
switch (exitCode) {
|
|
118
|
+
case exports.ExitCodes.AUTH_ERROR: return exports.ErrorCodes.AUTH_REQUIRED;
|
|
119
|
+
case exports.ExitCodes.PERMISSION_DENIED: return exports.ErrorCodes.PERMISSION_DENIED;
|
|
120
|
+
case exports.ExitCodes.NOT_FOUND: return exports.ErrorCodes.NOT_FOUND;
|
|
121
|
+
case exports.ExitCodes.INVALID_INPUT: return exports.ErrorCodes.INVALID_INPUT;
|
|
122
|
+
case exports.ExitCodes.RATE_LIMITED: return exports.ErrorCodes.RATE_LIMITED;
|
|
123
|
+
case exports.ExitCodes.TIMEOUT: return exports.ErrorCodes.TIMEOUT;
|
|
124
|
+
case exports.ExitCodes.SERVER_ERROR: return exports.ErrorCodes.SERVER_ERROR;
|
|
125
|
+
case exports.ExitCodes.NETWORK_ERROR: return exports.ErrorCodes.NETWORK_ERROR;
|
|
126
|
+
default: return exports.ErrorCodes.GENERAL_ERROR;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
/** Map an HTTP status code to an error code string. */
|
|
130
|
+
function httpStatusToErrorCode(status) {
|
|
131
|
+
if (status === 401)
|
|
132
|
+
return exports.ErrorCodes.AUTH_REQUIRED;
|
|
133
|
+
if (status === 402 || status === 403)
|
|
134
|
+
return exports.ErrorCodes.PERMISSION_DENIED;
|
|
135
|
+
if (status === 404)
|
|
136
|
+
return exports.ErrorCodes.NOT_FOUND;
|
|
137
|
+
if (status === 422)
|
|
138
|
+
return exports.ErrorCodes.INVALID_INPUT;
|
|
139
|
+
if (status === 429)
|
|
140
|
+
return exports.ErrorCodes.RATE_LIMITED;
|
|
141
|
+
if (status >= 500 && status <= 599)
|
|
142
|
+
return exports.ErrorCodes.SERVER_ERROR;
|
|
143
|
+
return exports.ErrorCodes.GENERAL_ERROR;
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Build a structured JSON error object from any error.
|
|
147
|
+
* Output format: { error: true, code: string, message: string, hint?: string, exit_code: number }
|
|
148
|
+
*/
|
|
149
|
+
function formatJsonError(err) {
|
|
150
|
+
if (err instanceof CliError) {
|
|
151
|
+
const code = err.code || exitCodeToErrorCode(err.exitCode);
|
|
152
|
+
return {
|
|
153
|
+
error: true,
|
|
154
|
+
code,
|
|
155
|
+
message: err.message,
|
|
156
|
+
...(err.hint ? { hint: err.hint } : {}),
|
|
157
|
+
exit_code: err.exitCode,
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
if (err instanceof api_1.ApiError) {
|
|
161
|
+
const p = err.payload;
|
|
162
|
+
const gatewayCode = p?.error?.code;
|
|
163
|
+
const detail = p?.error?.detail || p?.detail;
|
|
164
|
+
const code = gatewayCode || httpStatusToErrorCode(err.status);
|
|
165
|
+
const exitCode = mapHttpStatusToExitCode(err.status);
|
|
166
|
+
return {
|
|
167
|
+
error: true,
|
|
168
|
+
code,
|
|
169
|
+
message: err.message,
|
|
170
|
+
...(typeof detail === 'string' && detail !== err.message ? { hint: detail } : {}),
|
|
171
|
+
exit_code: exitCode,
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
if (err instanceof Error) {
|
|
175
|
+
const anyErr = err;
|
|
176
|
+
if (anyErr.status) {
|
|
177
|
+
const exitCode = mapHttpStatusToExitCode(anyErr.status);
|
|
178
|
+
return {
|
|
179
|
+
error: true,
|
|
180
|
+
code: httpStatusToErrorCode(anyErr.status),
|
|
181
|
+
message: anyErr.message,
|
|
182
|
+
exit_code: exitCode,
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
return {
|
|
186
|
+
error: true,
|
|
187
|
+
code: exports.ErrorCodes.GENERAL_ERROR,
|
|
188
|
+
message: anyErr.message,
|
|
189
|
+
exit_code: 1,
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
return {
|
|
193
|
+
error: true,
|
|
194
|
+
code: exports.ErrorCodes.GENERAL_ERROR,
|
|
195
|
+
message: String(err),
|
|
196
|
+
exit_code: 1,
|
|
197
|
+
};
|
|
198
|
+
}
|
|
99
199
|
async function exitWithError(err) {
|
|
100
|
-
const message = formatError(err);
|
|
101
200
|
// Report to Sentry if it's a real error (not a CliError which is expected)
|
|
102
201
|
if (!(err instanceof CliError) && process.env.SENTRY_DSN) {
|
|
103
202
|
Sentry.captureException(err);
|
|
104
203
|
}
|
|
105
204
|
// Flush PostHog before exiting
|
|
106
205
|
await (0, analytics_1.shutdownPostHog)();
|
|
206
|
+
// Determine exit code
|
|
207
|
+
let exitCode = 1;
|
|
208
|
+
if (err instanceof CliError) {
|
|
209
|
+
exitCode = err.exitCode;
|
|
210
|
+
}
|
|
211
|
+
else if (err instanceof api_1.ApiError) {
|
|
212
|
+
exitCode = mapHttpStatusToExitCode(err.status);
|
|
213
|
+
}
|
|
107
214
|
// Skip printing if the error was already shown (e.g. by spinner.fail)
|
|
108
215
|
const alreadyDisplayed = (err instanceof CliError && err.displayed) ||
|
|
109
216
|
(err instanceof Error && err._displayed);
|
|
110
217
|
if (!alreadyDisplayed) {
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
218
|
+
if ((0, output_1.isJsonMode)()) {
|
|
219
|
+
const jsonErr = formatJsonError(err);
|
|
220
|
+
process.stdout.write(`${JSON.stringify(jsonErr, null, 2)}\n`);
|
|
221
|
+
}
|
|
222
|
+
else {
|
|
223
|
+
const message = formatError(err);
|
|
224
|
+
process.stderr.write(`${message}\n`);
|
|
225
|
+
}
|
|
119
226
|
}
|
|
120
|
-
process.exit(
|
|
227
|
+
process.exit(exitCode);
|
|
121
228
|
}
|
|
122
229
|
exports.ExitCodes = {
|
|
123
230
|
SUCCESS: 0,
|
|
@@ -152,18 +259,15 @@ function mapHttpStatusToExitCode(status) {
|
|
|
152
259
|
class NetworkError extends CliError {
|
|
153
260
|
constructor(url, cause) {
|
|
154
261
|
const host = new URL(url).host;
|
|
155
|
-
super(`Unable to connect to ${host}
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
' - Service temporarily unavailable\n' +
|
|
159
|
-
' - Firewall or proxy blocking the request\n\n' +
|
|
160
|
-
'Check status at: https://status.orchagent.io', exports.ExitCodes.NETWORK_ERROR);
|
|
262
|
+
super(`Unable to connect to ${host}`, exports.ExitCodes.NETWORK_ERROR);
|
|
263
|
+
this.code = exports.ErrorCodes.NETWORK_ERROR;
|
|
264
|
+
this.hint = 'Check network connectivity or status at https://status.orchagent.io';
|
|
161
265
|
this.cause = cause;
|
|
162
266
|
}
|
|
163
267
|
}
|
|
164
268
|
exports.NetworkError = NetworkError;
|
|
165
269
|
function jsonInputError(flag) {
|
|
166
|
-
|
|
270
|
+
const err = new CliError(`Invalid JSON in --${flag} option.\n\n` +
|
|
167
271
|
'Common causes:\n' +
|
|
168
272
|
' - Shell special characters (!, $, `) need escaping\n' +
|
|
169
273
|
' - Missing or mismatched quotes\n\n' +
|
|
@@ -174,4 +278,7 @@ function jsonInputError(flag) {
|
|
|
174
278
|
'Alternatives:\n' +
|
|
175
279
|
` - Use a file: --${flag} @input.json\n` +
|
|
176
280
|
` - Use stdin: echo '{"key":"value"}' | orch run agent --${flag} @-`, exports.ExitCodes.INVALID_INPUT);
|
|
281
|
+
err.code = exports.ErrorCodes.INVALID_INPUT;
|
|
282
|
+
err.hint = `Use a file: --${flag} @input.json`;
|
|
283
|
+
return err;
|
|
177
284
|
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Shared utilities for --fields, --limit, and --offset on list commands.
|
|
4
|
+
*
|
|
5
|
+
* --fields: client-side JSON key filtering (implies --json output)
|
|
6
|
+
* --limit / --offset: pagination (passed to API where supported, applied client-side as fallback)
|
|
7
|
+
*/
|
|
8
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
+
exports.parseFields = parseFields;
|
|
10
|
+
exports.filterFields = filterFields;
|
|
11
|
+
exports.applyLimitOffset = applyLimitOffset;
|
|
12
|
+
exports.parseIntOption = parseIntOption;
|
|
13
|
+
/**
|
|
14
|
+
* Parse a comma-separated fields string into an array of field names.
|
|
15
|
+
* Trims whitespace and removes empty entries.
|
|
16
|
+
*/
|
|
17
|
+
function parseFields(fieldsStr) {
|
|
18
|
+
return fieldsStr.split(',').map(f => f.trim()).filter(Boolean);
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Filter an object or array of objects to include only specified fields.
|
|
22
|
+
*
|
|
23
|
+
* - Array of objects: filter each object's keys
|
|
24
|
+
* - Single object: filter top-level keys
|
|
25
|
+
* - Primitives: return as-is
|
|
26
|
+
*/
|
|
27
|
+
function filterFields(data, fields) {
|
|
28
|
+
if (Array.isArray(data)) {
|
|
29
|
+
return data.map(item => item !== null && typeof item === 'object'
|
|
30
|
+
? pickKeys(item, fields)
|
|
31
|
+
: item);
|
|
32
|
+
}
|
|
33
|
+
if (data !== null && typeof data === 'object') {
|
|
34
|
+
return pickKeys(data, fields);
|
|
35
|
+
}
|
|
36
|
+
return data;
|
|
37
|
+
}
|
|
38
|
+
function pickKeys(obj, fields) {
|
|
39
|
+
const result = {};
|
|
40
|
+
for (const field of fields) {
|
|
41
|
+
if (field in obj) {
|
|
42
|
+
result[field] = obj[field];
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
return result;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Apply client-side limit and offset to an array.
|
|
49
|
+
* Returns a new array (never mutates the input).
|
|
50
|
+
*/
|
|
51
|
+
function applyLimitOffset(items, limit, offset) {
|
|
52
|
+
const start = offset ?? 0;
|
|
53
|
+
if (limit != null) {
|
|
54
|
+
return items.slice(start, start + limit);
|
|
55
|
+
}
|
|
56
|
+
if (start > 0) {
|
|
57
|
+
return items.slice(start);
|
|
58
|
+
}
|
|
59
|
+
return items;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Parse a string number option, returning undefined if invalid or absent.
|
|
63
|
+
*/
|
|
64
|
+
function parseIntOption(value) {
|
|
65
|
+
if (value == null)
|
|
66
|
+
return undefined;
|
|
67
|
+
const n = parseInt(value, 10);
|
|
68
|
+
return isNaN(n) ? undefined : n;
|
|
69
|
+
}
|
package/dist/lib/llm.js
CHANGED
|
@@ -15,6 +15,7 @@ exports.buildPrompt = buildPrompt;
|
|
|
15
15
|
exports.callLlm = callLlm;
|
|
16
16
|
exports.callLlmWithFallback = callLlmWithFallback;
|
|
17
17
|
exports.validateProvider = validateProvider;
|
|
18
|
+
exports.validateModelIds = validateModelIds;
|
|
18
19
|
exports.detectProviderFromModel = detectProviderFromModel;
|
|
19
20
|
exports.warnProviderModelMismatch = warnProviderModelMismatch;
|
|
20
21
|
const errors_1 = require("./errors");
|
|
@@ -267,6 +268,40 @@ function validateProvider(provider) {
|
|
|
267
268
|
throw new errors_1.CliError(`Invalid provider: ${provider}. Valid: ${validProviders.join(', ')}`);
|
|
268
269
|
}
|
|
269
270
|
}
|
|
271
|
+
/**
|
|
272
|
+
* Validate model IDs in default_models against known provider patterns.
|
|
273
|
+
* Returns warnings for unrecognized model IDs (does not error — could be new models).
|
|
274
|
+
*/
|
|
275
|
+
function validateModelIds(defaultModels) {
|
|
276
|
+
const validProviders = ['openai', 'anthropic', 'gemini', 'ollama'];
|
|
277
|
+
const warnings = [];
|
|
278
|
+
for (const [provider, model] of Object.entries(defaultModels)) {
|
|
279
|
+
if (!validProviders.includes(provider)) {
|
|
280
|
+
warnings.push({
|
|
281
|
+
provider,
|
|
282
|
+
model,
|
|
283
|
+
message: `Unknown provider "${provider}" in default_models. Valid providers: ${validProviders.join(', ')}`,
|
|
284
|
+
});
|
|
285
|
+
continue;
|
|
286
|
+
}
|
|
287
|
+
const detectedProvider = detectProviderFromModel(model);
|
|
288
|
+
if (!detectedProvider) {
|
|
289
|
+
warnings.push({
|
|
290
|
+
provider,
|
|
291
|
+
model,
|
|
292
|
+
message: `Unrecognized model ID "${model}" for provider "${provider}". This may cause a 404 at runtime.`,
|
|
293
|
+
});
|
|
294
|
+
}
|
|
295
|
+
else if (detectedProvider !== provider) {
|
|
296
|
+
warnings.push({
|
|
297
|
+
provider,
|
|
298
|
+
model,
|
|
299
|
+
message: `Model "${model}" looks like a ${detectedProvider} model but is set under "${provider}".`,
|
|
300
|
+
});
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
return warnings;
|
|
304
|
+
}
|
|
270
305
|
/**
|
|
271
306
|
* Model-name patterns for auto-detecting the LLM provider.
|
|
272
307
|
* Tested against the lowercased model string.
|
package/dist/lib/output.js
CHANGED
|
@@ -1,6 +1,35 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.printJson = printJson;
|
|
4
|
+
exports.setJsonMode = setJsonMode;
|
|
5
|
+
exports.isJsonMode = isJsonMode;
|
|
6
|
+
exports.shouldAutoJson = shouldAutoJson;
|
|
4
7
|
function printJson(value) {
|
|
5
8
|
process.stdout.write(`${JSON.stringify(value, null, 2)}\n`);
|
|
6
9
|
}
|
|
10
|
+
/** Global JSON-mode flag — set by the preAction hook when --json is active. */
|
|
11
|
+
let _jsonMode = false;
|
|
12
|
+
function setJsonMode(enabled) {
|
|
13
|
+
_jsonMode = enabled;
|
|
14
|
+
}
|
|
15
|
+
function isJsonMode() {
|
|
16
|
+
return _jsonMode;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Determine if the CLI should auto-switch to JSON output.
|
|
20
|
+
*
|
|
21
|
+
* Priority (highest wins):
|
|
22
|
+
* 1. Explicit --json flag on the command → true (handled by Commander, not here)
|
|
23
|
+
* 2. ORCHAGENT_OUTPUT=json env var → true
|
|
24
|
+
* 3. ORCHAGENT_OUTPUT=text env var → false (override non-TTY auto-detection)
|
|
25
|
+
* 4. stdout is not a TTY (piped, redirected, or run by an agent) → true
|
|
26
|
+
* 5. stdout is a TTY (human terminal) → false
|
|
27
|
+
*/
|
|
28
|
+
function shouldAutoJson() {
|
|
29
|
+
const outputEnv = process.env.ORCHAGENT_OUTPUT?.toLowerCase();
|
|
30
|
+
if (outputEnv === 'json')
|
|
31
|
+
return true;
|
|
32
|
+
if (outputEnv === 'text')
|
|
33
|
+
return false;
|
|
34
|
+
return !process.stdout.isTTY;
|
|
35
|
+
}
|