@orchagent/cli 0.3.61 → 0.3.63
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/github.js +0 -7
- package/dist/commands/index.js +2 -0
- package/dist/commands/info.js +5 -0
- package/dist/commands/init.js +5 -0
- package/dist/commands/logs.js +182 -0
- package/dist/commands/publish.js +84 -0
- package/dist/commands/run.js +63 -4
- package/dist/commands/schedule.js +17 -1
- package/dist/commands/service.js +251 -0
- package/dist/commands/test.js +685 -153
- package/dist/index.js +2 -0
- package/dist/lib/api.js +52 -2
- package/dist/lib/dotenv.js +64 -0
- package/dist/lib/errors.js +7 -1
- package/dist/lib/suggest.js +146 -0
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -44,6 +44,7 @@ if (process.env.SENTRY_DSN) {
|
|
|
44
44
|
const commander_1 = require("commander");
|
|
45
45
|
const commands_1 = require("./commands");
|
|
46
46
|
const errors_1 = require("./lib/errors");
|
|
47
|
+
const suggest_1 = require("./lib/suggest");
|
|
47
48
|
const analytics_1 = require("./lib/analytics");
|
|
48
49
|
const config_1 = require("./lib/config");
|
|
49
50
|
const spinner_1 = require("./lib/spinner");
|
|
@@ -73,6 +74,7 @@ Documentation: https://docs.orchagent.io
|
|
|
73
74
|
orchagent docs agents Building agents guide
|
|
74
75
|
`);
|
|
75
76
|
(0, commands_1.registerCommands)(program);
|
|
77
|
+
(0, suggest_1.enhanceUnknownOptionSuggestions)(program);
|
|
76
78
|
// Initialize progress setting before parsing
|
|
77
79
|
async function main() {
|
|
78
80
|
// Check config for no_progress setting
|
package/dist/lib/api.js
CHANGED
|
@@ -109,13 +109,38 @@ async function safeFetchWithRetryForCalls(url, options) {
|
|
|
109
109
|
}
|
|
110
110
|
// Retry on 5xx or 429
|
|
111
111
|
if (response.status >= 500 || response.status === 429) {
|
|
112
|
+
// Read body to check if error is retryable
|
|
113
|
+
const bodyText = await response.text().catch(() => '');
|
|
114
|
+
let parsed = null;
|
|
115
|
+
try {
|
|
116
|
+
parsed = JSON.parse(bodyText);
|
|
117
|
+
}
|
|
118
|
+
catch { /* ignore */ }
|
|
119
|
+
const detail = parsed?.error?.message ||
|
|
120
|
+
parsed?.message || '';
|
|
121
|
+
const isRetryable = parsed?.error?.is_retryable;
|
|
122
|
+
// Don't retry if server explicitly says error is not retryable
|
|
123
|
+
if (isRetryable === false) {
|
|
124
|
+
return new Response(bodyText, {
|
|
125
|
+
status: response.status,
|
|
126
|
+
statusText: response.statusText,
|
|
127
|
+
headers: response.headers,
|
|
128
|
+
});
|
|
129
|
+
}
|
|
112
130
|
if (attempt < MAX_RETRIES) {
|
|
113
131
|
const delay = BASE_DELAY_MS * Math.pow(2, attempt - 1);
|
|
114
132
|
const jitter = Math.random() * 500;
|
|
115
|
-
|
|
133
|
+
const detailSuffix = detail ? `: ${detail}` : '';
|
|
134
|
+
process.stderr.write(`Request failed (${response.status}${detailSuffix}), retrying in ${Math.round((delay + jitter) / 1000)}s...\n`);
|
|
116
135
|
await new Promise(r => setTimeout(r, delay + jitter));
|
|
117
136
|
continue;
|
|
118
137
|
}
|
|
138
|
+
// Last attempt — return reconstructed response (body was consumed)
|
|
139
|
+
return new Response(bodyText, {
|
|
140
|
+
status: response.status,
|
|
141
|
+
statusText: response.statusText,
|
|
142
|
+
headers: response.headers,
|
|
143
|
+
});
|
|
119
144
|
}
|
|
120
145
|
return response;
|
|
121
146
|
}
|
|
@@ -142,13 +167,38 @@ async function safeFetchWithRetry(url, options) {
|
|
|
142
167
|
}
|
|
143
168
|
// Retry on 5xx or 429
|
|
144
169
|
if (response.status >= 500 || response.status === 429) {
|
|
170
|
+
// Read body to check if error is retryable
|
|
171
|
+
const bodyText = await response.text().catch(() => '');
|
|
172
|
+
let parsed = null;
|
|
173
|
+
try {
|
|
174
|
+
parsed = JSON.parse(bodyText);
|
|
175
|
+
}
|
|
176
|
+
catch { /* ignore */ }
|
|
177
|
+
const detail = parsed?.error?.message ||
|
|
178
|
+
parsed?.message || '';
|
|
179
|
+
const isRetryable = parsed?.error?.is_retryable;
|
|
180
|
+
// Don't retry if server explicitly says error is not retryable
|
|
181
|
+
if (isRetryable === false) {
|
|
182
|
+
return new Response(bodyText, {
|
|
183
|
+
status: response.status,
|
|
184
|
+
statusText: response.statusText,
|
|
185
|
+
headers: response.headers,
|
|
186
|
+
});
|
|
187
|
+
}
|
|
145
188
|
if (attempt < MAX_RETRIES) {
|
|
146
189
|
const delay = BASE_DELAY_MS * Math.pow(2, attempt - 1);
|
|
147
190
|
const jitter = Math.random() * 500;
|
|
148
|
-
|
|
191
|
+
const detailSuffix = detail ? `: ${detail}` : '';
|
|
192
|
+
process.stderr.write(`Request failed (${response.status}${detailSuffix}), retrying in ${Math.round((delay + jitter) / 1000)}s...\n`);
|
|
149
193
|
await new Promise(r => setTimeout(r, delay + jitter));
|
|
150
194
|
continue;
|
|
151
195
|
}
|
|
196
|
+
// Last attempt — return reconstructed response (body was consumed)
|
|
197
|
+
return new Response(bodyText, {
|
|
198
|
+
status: response.status,
|
|
199
|
+
statusText: response.statusText,
|
|
200
|
+
headers: response.headers,
|
|
201
|
+
});
|
|
152
202
|
}
|
|
153
203
|
return response;
|
|
154
204
|
}
|
|
@@ -0,0 +1,64 @@
|
|
|
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.parseDotEnv = parseDotEnv;
|
|
7
|
+
exports.loadDotEnv = loadDotEnv;
|
|
8
|
+
exports.mergeEnv = mergeEnv;
|
|
9
|
+
const promises_1 = __importDefault(require("fs/promises"));
|
|
10
|
+
const path_1 = __importDefault(require("path"));
|
|
11
|
+
/**
|
|
12
|
+
* Parse a .env file into key-value pairs.
|
|
13
|
+
* Handles: comments (#), blank lines, KEY=VALUE, single/double quoted values.
|
|
14
|
+
* Does NOT support multiline values or variable expansion.
|
|
15
|
+
*/
|
|
16
|
+
function parseDotEnv(content) {
|
|
17
|
+
const vars = {};
|
|
18
|
+
for (const rawLine of content.split('\n')) {
|
|
19
|
+
const line = rawLine.trim();
|
|
20
|
+
if (!line || line.startsWith('#'))
|
|
21
|
+
continue;
|
|
22
|
+
const eqIndex = line.indexOf('=');
|
|
23
|
+
if (eqIndex === -1)
|
|
24
|
+
continue;
|
|
25
|
+
const key = line.slice(0, eqIndex).trim();
|
|
26
|
+
if (!key)
|
|
27
|
+
continue;
|
|
28
|
+
let value = line.slice(eqIndex + 1).trim();
|
|
29
|
+
// Strip matching quotes
|
|
30
|
+
if ((value.startsWith('"') && value.endsWith('"')) ||
|
|
31
|
+
(value.startsWith("'") && value.endsWith("'"))) {
|
|
32
|
+
value = value.slice(1, -1);
|
|
33
|
+
}
|
|
34
|
+
vars[key] = value;
|
|
35
|
+
}
|
|
36
|
+
return vars;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Load .env file from a directory. Returns the parsed vars (empty object if no file).
|
|
40
|
+
* Does NOT modify process.env — caller decides how to merge.
|
|
41
|
+
*/
|
|
42
|
+
async function loadDotEnv(dir) {
|
|
43
|
+
const envPath = path_1.default.join(dir, '.env');
|
|
44
|
+
try {
|
|
45
|
+
const content = await promises_1.default.readFile(envPath, 'utf-8');
|
|
46
|
+
return parseDotEnv(content);
|
|
47
|
+
}
|
|
48
|
+
catch {
|
|
49
|
+
return {};
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Merge .env vars into an env object. Existing keys take precedence
|
|
54
|
+
* (process.env wins over .env file, matching standard dotenv behaviour).
|
|
55
|
+
*/
|
|
56
|
+
function mergeEnv(base, dotEnvVars) {
|
|
57
|
+
const merged = { ...base };
|
|
58
|
+
for (const [key, value] of Object.entries(dotEnvVars)) {
|
|
59
|
+
if (!(key in merged) || merged[key] === undefined) {
|
|
60
|
+
merged[key] = value;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
return merged;
|
|
64
|
+
}
|
package/dist/lib/errors.js
CHANGED
|
@@ -58,7 +58,13 @@ function formatError(err) {
|
|
|
58
58
|
if (err instanceof Error) {
|
|
59
59
|
const anyErr = err;
|
|
60
60
|
if (anyErr.status && anyErr.payload) {
|
|
61
|
-
|
|
61
|
+
const p = anyErr.payload;
|
|
62
|
+
const code = p.error?.code;
|
|
63
|
+
const detail = p.error?.detail || p.detail;
|
|
64
|
+
let msg = `${anyErr.message} (status ${anyErr.status}${code ? `, ${code}` : ''})`;
|
|
65
|
+
if (detail)
|
|
66
|
+
msg += `\n${detail}`;
|
|
67
|
+
return msg;
|
|
62
68
|
}
|
|
63
69
|
return anyErr.message;
|
|
64
70
|
}
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.editDistance = editDistance;
|
|
4
|
+
exports.findBestMatch = findBestMatch;
|
|
5
|
+
exports.enhanceUnknownOptionSuggestions = enhanceUnknownOptionSuggestions;
|
|
6
|
+
// ---------------------------------------------------------------------------
|
|
7
|
+
// Hints for flags that aren't typos but semantic misunderstandings.
|
|
8
|
+
// Keyed by command name → flag → helpful message.
|
|
9
|
+
// ---------------------------------------------------------------------------
|
|
10
|
+
const COMMAND_HINTS = {
|
|
11
|
+
run: {
|
|
12
|
+
'--cloud': 'Cloud execution is the default. Use --local for local execution.',
|
|
13
|
+
},
|
|
14
|
+
};
|
|
15
|
+
// ---------------------------------------------------------------------------
|
|
16
|
+
// Damerau-Levenshtein distance (same algorithm commander uses internally)
|
|
17
|
+
// ---------------------------------------------------------------------------
|
|
18
|
+
function editDistance(a, b) {
|
|
19
|
+
const MAX = Math.max(a.length, b.length);
|
|
20
|
+
if (Math.abs(a.length - b.length) > MAX)
|
|
21
|
+
return MAX;
|
|
22
|
+
const d = [];
|
|
23
|
+
for (let i = 0; i <= a.length; i++)
|
|
24
|
+
d[i] = [i];
|
|
25
|
+
for (let j = 0; j <= b.length; j++)
|
|
26
|
+
d[0][j] = j;
|
|
27
|
+
for (let j = 1; j <= b.length; j++) {
|
|
28
|
+
for (let i = 1; i <= a.length; i++) {
|
|
29
|
+
const cost = a[i - 1] === b[j - 1] ? 0 : 1;
|
|
30
|
+
d[i][j] = Math.min(d[i - 1][j] + 1, // deletion
|
|
31
|
+
d[i][j - 1] + 1, // insertion
|
|
32
|
+
d[i - 1][j - 1] + cost // substitution
|
|
33
|
+
);
|
|
34
|
+
// transposition
|
|
35
|
+
if (i > 1 && j > 1 && a[i - 1] === b[j - 2] && a[i - 2] === b[j - 1]) {
|
|
36
|
+
d[i][j] = Math.min(d[i][j], d[i - 2][j - 2] + 1);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
return d[a.length][b.length];
|
|
41
|
+
}
|
|
42
|
+
// ---------------------------------------------------------------------------
|
|
43
|
+
// Find the best matching option flag for an unknown flag.
|
|
44
|
+
//
|
|
45
|
+
// Enhancements over commander's built-in:
|
|
46
|
+
// - For --no-X options, also compares the unknown against the bare X name.
|
|
47
|
+
// This lets --strem match --no-stream (comparing "strem" vs "stream").
|
|
48
|
+
// ---------------------------------------------------------------------------
|
|
49
|
+
const MIN_SIMILARITY = 0.4;
|
|
50
|
+
const MAX_DISTANCE = 3;
|
|
51
|
+
function isSimilarEnough(dist, wordLen, candidateLen) {
|
|
52
|
+
if (dist > MAX_DISTANCE)
|
|
53
|
+
return false;
|
|
54
|
+
const len = Math.max(wordLen, candidateLen);
|
|
55
|
+
return (len - dist) / len > MIN_SIMILARITY;
|
|
56
|
+
}
|
|
57
|
+
function findBestMatch(unknownFlag, candidateFlags) {
|
|
58
|
+
if (!candidateFlags.length)
|
|
59
|
+
return null;
|
|
60
|
+
const unknown = unknownFlag.replace(/^--?/, '');
|
|
61
|
+
let bestFlag = null;
|
|
62
|
+
let bestDist = MAX_DISTANCE + 1;
|
|
63
|
+
for (const candidate of candidateFlags) {
|
|
64
|
+
const name = candidate.replace(/^--?/, '');
|
|
65
|
+
if (name.length <= 1)
|
|
66
|
+
continue;
|
|
67
|
+
// Standard comparison
|
|
68
|
+
const dist = editDistance(unknown, name);
|
|
69
|
+
if (dist < bestDist && isSimilarEnough(dist, unknown.length, name.length)) {
|
|
70
|
+
bestDist = dist;
|
|
71
|
+
bestFlag = candidate;
|
|
72
|
+
}
|
|
73
|
+
// Negation-aware: for --no-X, also compare unknown against X
|
|
74
|
+
if (name.startsWith('no-')) {
|
|
75
|
+
const baseName = name.slice(3);
|
|
76
|
+
if (baseName.length <= 1)
|
|
77
|
+
continue;
|
|
78
|
+
const baseDist = editDistance(unknown, baseName);
|
|
79
|
+
if (baseDist < bestDist && isSimilarEnough(baseDist, unknown.length, baseName.length)) {
|
|
80
|
+
bestDist = baseDist;
|
|
81
|
+
bestFlag = candidate;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
return bestFlag;
|
|
86
|
+
}
|
|
87
|
+
// ---------------------------------------------------------------------------
|
|
88
|
+
// Override unknownOption on a command tree to provide enhanced suggestions.
|
|
89
|
+
// ---------------------------------------------------------------------------
|
|
90
|
+
function getHint(commandName, flag) {
|
|
91
|
+
return COMMAND_HINTS[commandName]?.[flag] ?? null;
|
|
92
|
+
}
|
|
93
|
+
function gatherCandidateFlags(cmd) {
|
|
94
|
+
const flags = [];
|
|
95
|
+
let current = cmd;
|
|
96
|
+
do {
|
|
97
|
+
const moreFlags = current
|
|
98
|
+
.createHelp()
|
|
99
|
+
.visibleOptions(current)
|
|
100
|
+
.filter((opt) => opt.long)
|
|
101
|
+
.map((opt) => opt.long);
|
|
102
|
+
flags.push(...moreFlags);
|
|
103
|
+
current = current.parent;
|
|
104
|
+
} while (current && !current._enablePositionalOptions);
|
|
105
|
+
return [...new Set(flags)];
|
|
106
|
+
}
|
|
107
|
+
function overrideUnknownOption(cmd) {
|
|
108
|
+
;
|
|
109
|
+
cmd.unknownOption = function (flag) {
|
|
110
|
+
if (this._allowUnknownOption)
|
|
111
|
+
return;
|
|
112
|
+
// 1. Check for a context-aware hint
|
|
113
|
+
const hint = getHint(this.name(), flag);
|
|
114
|
+
if (hint) {
|
|
115
|
+
this.error(`error: unknown option '${flag}'\n${hint}`, {
|
|
116
|
+
code: 'commander.unknownOption',
|
|
117
|
+
});
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
// 2. Gather candidate flags and find best match (enhanced)
|
|
121
|
+
let suggestion = '';
|
|
122
|
+
if (flag.startsWith('--')) {
|
|
123
|
+
const candidates = gatherCandidateFlags(this);
|
|
124
|
+
const match = findBestMatch(flag, candidates);
|
|
125
|
+
if (match) {
|
|
126
|
+
suggestion = `\n(Did you mean ${match}?)`;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
this.error(`error: unknown option '${flag}'${suggestion}`, {
|
|
130
|
+
code: 'commander.unknownOption',
|
|
131
|
+
});
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Walk the full command tree and override unknownOption on every command.
|
|
136
|
+
* Call this after all commands have been registered.
|
|
137
|
+
*/
|
|
138
|
+
function enhanceUnknownOptionSuggestions(program) {
|
|
139
|
+
function walk(cmd) {
|
|
140
|
+
overrideUnknownOption(cmd);
|
|
141
|
+
for (const sub of cmd.commands) {
|
|
142
|
+
walk(sub);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
walk(program);
|
|
146
|
+
}
|
package/package.json
CHANGED