@erdoai/cli 0.118.0 → 0.119.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/index.js +106 -6
- package/package.json +8 -8
package/README.md
CHANGED
|
@@ -11,7 +11,7 @@ npx @erdoai/cli --help # or run without installing
|
|
|
11
11
|
erdo update # self-update to the latest published version
|
|
12
12
|
```
|
|
13
13
|
|
|
14
|
-
Requires Node.js
|
|
14
|
+
Requires Node.js 22.13+. User-facing install docs: https://docs.erdo.ai/cli
|
|
15
15
|
|
|
16
16
|
When a newer version is published, every command prints one line on **stderr**
|
|
17
17
|
naming the installed version and `erdo update`. It is on stderr so stdout stays
|
package/dist/index.js
CHANGED
|
@@ -135,7 +135,7 @@ function formatOrg(name, slug) {
|
|
|
135
135
|
if (name && slug) return `${name} (${slug})`;
|
|
136
136
|
return name || slug || "";
|
|
137
137
|
}
|
|
138
|
-
var TERMINAL_RUN_STATUSES = /* @__PURE__ */ new Set(["completed", "failed", "cancelled", "error"]);
|
|
138
|
+
var TERMINAL_RUN_STATUSES = /* @__PURE__ */ new Set(["completed", "failed", "cancelled", "declined", "error"]);
|
|
139
139
|
var POLL_STOP_STATUSES = /* @__PURE__ */ new Set([...TERMINAL_RUN_STATUSES, "awaiting_approval"]);
|
|
140
140
|
var ErdoApiError = class extends Error {
|
|
141
141
|
constructor(status, message) {
|
|
@@ -162,13 +162,13 @@ function networkErrorFor(method, path, err) {
|
|
|
162
162
|
return new ErdoNetworkError(`${method} ${path} failed: ${detail}`, cause);
|
|
163
163
|
}
|
|
164
164
|
var sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
165
|
-
async function rawRequest(baseURL, token, orgId, projectId, method, path, body) {
|
|
165
|
+
async function rawRequest(baseURL, token, orgId, projectId, method, path, body, retryNetworkFailures = true) {
|
|
166
166
|
const headers = { Authorization: `Bearer ${token}` };
|
|
167
167
|
if (orgId) headers["X-Organization-ID"] = orgId;
|
|
168
168
|
if (projectId) headers["X-Project-ID"] = projectId;
|
|
169
169
|
if (body !== void 0) headers["Content-Type"] = "application/json";
|
|
170
170
|
const upper = method.toUpperCase();
|
|
171
|
-
const maxRetries = upper === "GET" || upper === "HEAD" ? 2 : 1;
|
|
171
|
+
const maxRetries = retryNetworkFailures ? upper === "GET" || upper === "HEAD" ? 2 : 1 : 0;
|
|
172
172
|
let attempt = 0;
|
|
173
173
|
while (true) {
|
|
174
174
|
let res;
|
|
@@ -185,6 +185,9 @@ async function rawRequest(baseURL, token, orgId, projectId, method, path, body)
|
|
|
185
185
|
attempt++;
|
|
186
186
|
continue;
|
|
187
187
|
}
|
|
188
|
+
if (!retryNetworkFailures) {
|
|
189
|
+
throw new ErdoNetworkError(`${netErr.message}. The launch may have started; inspect agent runs before retrying.`, netErr);
|
|
190
|
+
}
|
|
188
191
|
throw netErr;
|
|
189
192
|
}
|
|
190
193
|
const text = await res.text();
|
|
@@ -260,7 +263,7 @@ var ErdoClient = class {
|
|
|
260
263
|
process.stderr.write("org: token default (no org resolved \u2014 pass --org to pin)\n");
|
|
261
264
|
}
|
|
262
265
|
}
|
|
263
|
-
request(method, path, body) {
|
|
266
|
+
request(method, path, body, retryNetworkFailures = true) {
|
|
264
267
|
this.announceOrgOnce(method);
|
|
265
268
|
return rawRequest(
|
|
266
269
|
this.baseURL,
|
|
@@ -269,7 +272,8 @@ var ErdoClient = class {
|
|
|
269
272
|
this.projectId,
|
|
270
273
|
method,
|
|
271
274
|
path,
|
|
272
|
-
body
|
|
275
|
+
body,
|
|
276
|
+
retryNetworkFailures
|
|
273
277
|
);
|
|
274
278
|
}
|
|
275
279
|
me() {
|
|
@@ -1058,7 +1062,7 @@ var ErdoClient = class {
|
|
|
1058
1062
|
}
|
|
1059
1063
|
// --- agents ---
|
|
1060
1064
|
ask(input) {
|
|
1061
|
-
return this.request("POST", "/v1/ask", input);
|
|
1065
|
+
return this.request("POST", "/v1/ask", input, !input.saved_ask_id);
|
|
1062
1066
|
}
|
|
1063
1067
|
createThread(input) {
|
|
1064
1068
|
return this.request(
|
|
@@ -1075,6 +1079,25 @@ var ErdoClient = class {
|
|
|
1075
1079
|
throw err;
|
|
1076
1080
|
}
|
|
1077
1081
|
}
|
|
1082
|
+
// The thread owns the current invocation identity. Approval resumes create a
|
|
1083
|
+
// new invocation while deliberately reusing the original agent run, so the
|
|
1084
|
+
// run list is not an authoritative source for cancellation.
|
|
1085
|
+
getThreadCompletionStatus(threadID) {
|
|
1086
|
+
return this.request(
|
|
1087
|
+
"GET",
|
|
1088
|
+
`/v1/threads/${encodeURIComponent(threadID)}/completion-status`
|
|
1089
|
+
);
|
|
1090
|
+
}
|
|
1091
|
+
// cancelThreadInvocation names the invocation being stopped. The thread
|
|
1092
|
+
// service refuses (HTTP 400, "the run already changed") when the thread has
|
|
1093
|
+
// moved on to another invocation, so a replacement run is never cancelled.
|
|
1094
|
+
cancelThreadInvocation(threadID, invocationID) {
|
|
1095
|
+
return this.request(
|
|
1096
|
+
"POST",
|
|
1097
|
+
`/v1/threads/${encodeURIComponent(threadID)}/cancel-invocation`,
|
|
1098
|
+
{ invocation_id: invocationID }
|
|
1099
|
+
);
|
|
1100
|
+
}
|
|
1078
1101
|
sendMessage(threadID, input) {
|
|
1079
1102
|
return this.request(
|
|
1080
1103
|
"POST",
|
|
@@ -3991,6 +4014,24 @@ agentCmd.command("send <threadId> <message>").description("Send a message to a t
|
|
|
3991
4014
|
fail(e);
|
|
3992
4015
|
}
|
|
3993
4016
|
});
|
|
4017
|
+
agentCmd.command("cancel <threadId>").description("Request cancellation of a thread's active invocation; read the run to confirm it has stopped").option("--invocation <id>", "invocation to cancel (default: the thread's current invocation)").action(async (threadId, opts) => {
|
|
4018
|
+
try {
|
|
4019
|
+
const client = new ErdoClient();
|
|
4020
|
+
let invocationID = opts.invocation;
|
|
4021
|
+
if (!invocationID) {
|
|
4022
|
+
const current = await client.getThreadCompletionStatus(threadId);
|
|
4023
|
+
invocationID = current.invocation_id;
|
|
4024
|
+
if (!invocationID) throw new Error(`thread ${threadId} has no invocation to cancel`);
|
|
4025
|
+
}
|
|
4026
|
+
const result = await client.cancelThreadInvocation(threadId, invocationID);
|
|
4027
|
+
if (!result.success) {
|
|
4028
|
+
throw new Error("cancellation could not be confirmed; inspect the thread's latest run and retry");
|
|
4029
|
+
}
|
|
4030
|
+
console.log(JSON.stringify(result, null, 2));
|
|
4031
|
+
} catch (e) {
|
|
4032
|
+
fail(e);
|
|
4033
|
+
}
|
|
4034
|
+
});
|
|
3994
4035
|
agentCmd.command("wait <threadId>").description("Re-attach to a thread's latest run and wait for it to finish; prints the answer").action(async (threadId) => {
|
|
3995
4036
|
try {
|
|
3996
4037
|
const client = new ErdoClient();
|
|
@@ -4007,6 +4048,65 @@ agentCmd.command("wait <threadId>").description("Re-attach to a thread's latest
|
|
|
4007
4048
|
fail(e);
|
|
4008
4049
|
}
|
|
4009
4050
|
});
|
|
4051
|
+
var shortcutCmd = program.command("shortcut").description("Inspect and run saved Shortcuts with their stored agent and context");
|
|
4052
|
+
shortcutCmd.command("get <id>").description("Read the complete saved Ask, including its config and linked Knowledge").action(async (id) => {
|
|
4053
|
+
try {
|
|
4054
|
+
const response = await new ErdoClient().getKnowledge(id);
|
|
4055
|
+
if (response.object.type !== "ask" || ["archived", "deleted"].includes(response.object.status)) {
|
|
4056
|
+
throw new Error("Shortcut not found or inactive");
|
|
4057
|
+
}
|
|
4058
|
+
print(response);
|
|
4059
|
+
} catch (e) {
|
|
4060
|
+
fail(e);
|
|
4061
|
+
}
|
|
4062
|
+
});
|
|
4063
|
+
shortcutCmd.command("run <id>").description("Run the current saved prompt, agent, datasets and linked skills").option("--parameters <json>", "flat invocation data as JSON or @path; does not override saved configuration").option("--sync", "hold one HTTP request until completion; long runs can exceed proxy limits").option("--no-wait", "print the startup acknowledgement without waiting for the terminal run").action(async (id, opts) => {
|
|
4064
|
+
try {
|
|
4065
|
+
if (opts.sync && opts.wait === false) throw new Error("--sync cannot be combined with --no-wait");
|
|
4066
|
+
let parameters;
|
|
4067
|
+
if (opts.parameters !== void 0) {
|
|
4068
|
+
const value = JSON.parse(readMaybeFile(opts.parameters) ?? "");
|
|
4069
|
+
if (value === null || typeof value !== "object" || Array.isArray(value)) {
|
|
4070
|
+
throw new Error("Invocation parameters must be a plain object");
|
|
4071
|
+
}
|
|
4072
|
+
for (const [key, field] of Object.entries(value)) {
|
|
4073
|
+
if (!/^[A-Za-z_][A-Za-z0-9_]{0,63}$/.test(key)) throw new Error(`Invalid invocation parameter key: ${key}`);
|
|
4074
|
+
if (field !== null && typeof field !== "string" && typeof field !== "boolean" && (typeof field !== "number" || !Number.isFinite(field))) {
|
|
4075
|
+
throw new Error(`Invalid invocation parameter value for "${key}"`);
|
|
4076
|
+
}
|
|
4077
|
+
}
|
|
4078
|
+
if (JSON.stringify(value).length > 4096) throw new Error("Invocation parameters exceed the 4096-character limit");
|
|
4079
|
+
parameters = value;
|
|
4080
|
+
}
|
|
4081
|
+
const client = new ErdoClient();
|
|
4082
|
+
const response = await client.ask({ saved_ask_id: id, parameters, async: !opts.sync });
|
|
4083
|
+
if (typeof response.thread_id !== "string" || !response.thread_id.trim() || !opts.sync && response.status !== "running") throw new Error("Saved Shortcut did not start");
|
|
4084
|
+
if (opts.wait === false) {
|
|
4085
|
+
print({ thread_id: response.thread_id, message_id: response.message_id, status: response.status });
|
|
4086
|
+
return;
|
|
4087
|
+
}
|
|
4088
|
+
if (opts.sync) {
|
|
4089
|
+
if (response.status !== "completed" && response.status !== "success") throw new Error(`Saved Shortcut did not complete (status: ${response.status}; thread: ${response.thread_id})`);
|
|
4090
|
+
console.log(response.answer || `(status: ${response.status})`);
|
|
4091
|
+
console.log(`
|
|
4092
|
+
thread: ${response.thread_id}`);
|
|
4093
|
+
return;
|
|
4094
|
+
}
|
|
4095
|
+
process.stderr.write(`thread: ${response.thread_id}
|
|
4096
|
+
running\u2026`);
|
|
4097
|
+
const run = await client.waitForThreadRun(response.thread_id, { onTick: () => process.stderr.write(".") });
|
|
4098
|
+
process.stderr.write("\n");
|
|
4099
|
+
if (!run) throw new Error(timedOutMessage(response.thread_id));
|
|
4100
|
+
if (run.status === "awaiting_approval") throw new Error(awaitingApprovalMessage(response.thread_id));
|
|
4101
|
+
if (run.error) throw new Error(run.error);
|
|
4102
|
+
if (run.status !== "completed") throw new Error(`Saved Shortcut did not complete (status: ${run.status})`);
|
|
4103
|
+
console.log(run.output || `(status: ${run.status})`);
|
|
4104
|
+
console.log(`
|
|
4105
|
+
thread: ${response.thread_id}`);
|
|
4106
|
+
} catch (e) {
|
|
4107
|
+
fail(e);
|
|
4108
|
+
}
|
|
4109
|
+
});
|
|
4010
4110
|
agentCmd.command("thread").description("Create a thread; prints its id").option("-n, --name <name>", "thread name").option("-d, --datasets <csv>", "dataset UUIDs to attach").option("--hidden", "keep the thread out of the organization's thread list (for diagnostic or scripted threads)").action(async (opts) => {
|
|
4011
4111
|
try {
|
|
4012
4112
|
const res = await new ErdoClient().createThread({
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@erdoai/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.119.1",
|
|
4
4
|
"description": "Erdo CLI — drive datasets, pages, and evals from the terminal or CI",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
"scripts/postinstall.mjs"
|
|
12
12
|
],
|
|
13
13
|
"engines": {
|
|
14
|
-
"node": ">=
|
|
14
|
+
"node": "^22.13.0 || >=23.5.0"
|
|
15
15
|
},
|
|
16
16
|
"publishConfig": {
|
|
17
17
|
"access": "public",
|
|
@@ -41,14 +41,14 @@
|
|
|
41
41
|
"prepublishOnly": "npm run build"
|
|
42
42
|
},
|
|
43
43
|
"dependencies": {
|
|
44
|
-
"@inquirer/prompts": "^7.
|
|
45
|
-
"commander": "^
|
|
44
|
+
"@inquirer/prompts": "^8.7.3",
|
|
45
|
+
"commander": "^15.0.0"
|
|
46
46
|
},
|
|
47
47
|
"devDependencies": {
|
|
48
|
-
"@types/node": "^
|
|
49
|
-
"tsup": "^8.
|
|
50
|
-
"tsx": "^4.
|
|
51
|
-
"typescript": "^
|
|
48
|
+
"@types/node": "^26.6.4",
|
|
49
|
+
"tsup": "^8.5.1",
|
|
50
|
+
"tsx": "^4.23.15",
|
|
51
|
+
"typescript": "^7.0.2"
|
|
52
52
|
},
|
|
53
53
|
"overrides": {
|
|
54
54
|
"esbuild": "^0.28.1"
|