@erdoai/cli 0.120.1 → 0.122.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.
- package/dist/index.js +67 -9
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -186,7 +186,7 @@ async function rawRequest(baseURL, token, orgId, projectId, method, path, body,
|
|
|
186
186
|
continue;
|
|
187
187
|
}
|
|
188
188
|
if (!retryNetworkFailures) {
|
|
189
|
-
throw new ErdoNetworkError(`${netErr.message}. The
|
|
189
|
+
throw new ErdoNetworkError(`${netErr.message}. The request may have succeeded; inspect the affected resource before retrying.`, netErr);
|
|
190
190
|
}
|
|
191
191
|
throw netErr;
|
|
192
192
|
}
|
|
@@ -1200,6 +1200,12 @@ var ErdoClient = class {
|
|
|
1200
1200
|
updateDatasetSchema(slug, input) {
|
|
1201
1201
|
return this.request("POST", `/v1/datasets/${encodeURIComponent(slug)}/schema`, input);
|
|
1202
1202
|
}
|
|
1203
|
+
// Dataset row writes are deliberately never replayed after a network
|
|
1204
|
+
// failure: the server may have committed the rows before the response was
|
|
1205
|
+
// lost, and repeating an append or upsert can change the result.
|
|
1206
|
+
writeDatasetRows(slug, input) {
|
|
1207
|
+
return this.request("POST", `/v1/datasets/${encodeURIComponent(slug)}/rows`, input, false);
|
|
1208
|
+
}
|
|
1203
1209
|
// The endpoint's field is `question` (QueryDataNaturalLanguageInput). Sending // The endpoint's field is `question` (QueryDataNaturalLanguageInput). Sending
|
|
1204
1210
|
// `query` made every `erdo datasets query` fail with "question is required".
|
|
1205
1211
|
//
|
|
@@ -3012,14 +3018,15 @@ ${res.from} to ${res.to} (${res.timezone} days)
|
|
|
3012
3018
|
}
|
|
3013
3019
|
);
|
|
3014
3020
|
var tokenCmd = program.command("token").description("Manage your API tokens (account-level credentials)");
|
|
3015
|
-
tokenCmd.command("create").description("Mint a new API token; the secret is printed ONCE").requiredOption("--name <name>", "a label for the token").option("--expires-days <n>", "days until expiry (default: 30)", (v) => parseInt(v, 10)).addOption(
|
|
3021
|
+
tokenCmd.command("create").description("Mint a new API token; the secret is printed ONCE").requiredOption("--name <name>", "a label for the token").option("--expires-days <n>", "days until expiry (default: 30)", (v) => parseInt(v, 10)).option("--read-only", "mint a read-only token: it can read through the API in any org you belong to, and never write").addOption(
|
|
3016
3022
|
orgOption("the token's default org (defaults to your active org; must be one you belong to)")
|
|
3017
3023
|
).action(async (opts) => {
|
|
3018
3024
|
try {
|
|
3019
3025
|
const res = await new ErdoClient().createToken({
|
|
3020
3026
|
name: opts.name,
|
|
3021
3027
|
expires_in_days: opts.expiresDays ?? 30,
|
|
3022
|
-
organization_id: opts.org
|
|
3028
|
+
organization_id: opts.org,
|
|
3029
|
+
...opts.readOnly ? { access: "read" } : {}
|
|
3023
3030
|
});
|
|
3024
3031
|
process.stderr.write(
|
|
3025
3032
|
"Store this token now \u2014 it is shown only once and cannot be retrieved again.\n"
|
|
@@ -3031,8 +3038,8 @@ id: ${res.id}
|
|
|
3031
3038
|
hint: ${res.hint}
|
|
3032
3039
|
expires: ${res.expires_at}
|
|
3033
3040
|
default org: ${res.default_org ? `${res.default_org.name} (${res.default_org.id})` : "(none)"}
|
|
3034
|
-
|
|
3035
|
-
`
|
|
3041
|
+
access: ${res.access ?? "full"}
|
|
3042
|
+
` + (res.access === "read" ? "This token can read as you in any org you belong to and cannot change anything; pass --org on a command to target another.\n" : "This token acts as you in any org you belong to; pass --org on a command to target another.\n")
|
|
3036
3043
|
);
|
|
3037
3044
|
} catch (e) {
|
|
3038
3045
|
fail(e);
|
|
@@ -3048,7 +3055,7 @@ tokenCmd.command("list").description("List your API tokens (never shows the secr
|
|
|
3048
3055
|
for (const t of tokens) {
|
|
3049
3056
|
const org2 = t.default_org ? t.default_org.name || t.default_org.id : "-";
|
|
3050
3057
|
const used = t.last_used_at ?? "never";
|
|
3051
|
-
console.log(`${t.id} ${t.name || "-"} ${t.hint} default-org:${org2} expires:${t.expires_at} used:${used}`);
|
|
3058
|
+
console.log(`${t.id} ${t.name || "-"} ${t.hint} access:${t.access ?? "full"} default-org:${org2} expires:${t.expires_at} used:${used}`);
|
|
3052
3059
|
}
|
|
3053
3060
|
} catch (e) {
|
|
3054
3061
|
fail(e);
|
|
@@ -4191,13 +4198,13 @@ runsCmd.command("list").description("List agent runs (filter by agent OR thread
|
|
|
4191
4198
|
fail(e);
|
|
4192
4199
|
}
|
|
4193
4200
|
});
|
|
4194
|
-
runsCmd.command("get <id>").description("Show an agent run (status, output, trace metadata)").option("--resources", "also show the skills, knowledge and entities the run used").option("--steps", "also show the run's iterations, tool calls and sub-agent invocations").action(async (id, opts) => {
|
|
4201
|
+
runsCmd.command("get <id>").description("Show an agent run (status, output, trace metadata)").option("--resources", "also show the skills, knowledge and entities the run used").option("--steps", "also show the run's iterations, tool calls and sub-agent invocations").option("--json", "print the full JSON response, including requested resources and steps").action(async (id, opts) => {
|
|
4195
4202
|
try {
|
|
4196
4203
|
const include = [];
|
|
4197
4204
|
if (opts.resources) include.push("resources");
|
|
4198
4205
|
if (opts.steps) include.push("steps");
|
|
4199
4206
|
const detail = await new ErdoClient().getAgentRun(id, include);
|
|
4200
|
-
if (include.length === 0) {
|
|
4207
|
+
if (opts.json || include.length === 0) {
|
|
4201
4208
|
print(detail);
|
|
4202
4209
|
return;
|
|
4203
4210
|
}
|
|
@@ -6657,6 +6664,53 @@ datasetsCmd.command("purposes").description(
|
|
|
6657
6664
|
fail(e);
|
|
6658
6665
|
}
|
|
6659
6666
|
});
|
|
6667
|
+
datasetsCmd.command("write <slug>").description(
|
|
6668
|
+
"Write rows to a dataset. Without --key-column rows append. File and writable PostgreSQL datasets support keyed replace or merge. ClickHouse-backed writes always append, even with a key, and refuse merge."
|
|
6669
|
+
).requiredOption("--rows <json-or-@file>", "JSON array of row objects, or @path to a JSON file").option("--key-column <columns>", "non-blank key column, or comma-separated columns for a composite key").option("--write-mode <replace|merge>", "replace matching fields (files clear omitted fields; PostgreSQL updates batch columns), or merge; either explicit mode requires --key-column").option("--overwrite-columns <csv>", "comma-separated columns merge mode may overwrite").action(
|
|
6670
|
+
async (slug, opts) => {
|
|
6671
|
+
try {
|
|
6672
|
+
const rawRows = readMaybeFile(opts.rows);
|
|
6673
|
+
let parsed;
|
|
6674
|
+
try {
|
|
6675
|
+
parsed = JSON.parse(rawRows ?? "");
|
|
6676
|
+
} catch (error) {
|
|
6677
|
+
throw new Error(`--rows must contain valid JSON: ${error instanceof Error ? error.message : String(error)}`);
|
|
6678
|
+
}
|
|
6679
|
+
if (!Array.isArray(parsed) || parsed.some((row) => row === null || typeof row !== "object" || Array.isArray(row))) {
|
|
6680
|
+
throw new Error("--rows must contain a JSON array of non-null objects");
|
|
6681
|
+
}
|
|
6682
|
+
const writeMode = opts.writeMode;
|
|
6683
|
+
if (writeMode !== void 0 && writeMode !== "replace" && writeMode !== "merge") {
|
|
6684
|
+
throw new Error('--write-mode must be "replace" or "merge"');
|
|
6685
|
+
}
|
|
6686
|
+
if (writeMode !== void 0 && !opts.keyColumn?.trim()) {
|
|
6687
|
+
throw new Error(`--write-mode "${writeMode}" requires a non-blank --key-column`);
|
|
6688
|
+
}
|
|
6689
|
+
if (opts.keyColumn !== void 0 && !opts.keyColumn.split(",").some((column) => column.trim())) {
|
|
6690
|
+
throw new Error("--key-column must name at least one non-blank column");
|
|
6691
|
+
}
|
|
6692
|
+
let overwriteColumns;
|
|
6693
|
+
if (opts.overwriteColumns !== void 0) {
|
|
6694
|
+
if (writeMode !== "merge") {
|
|
6695
|
+
throw new Error("--overwrite-columns requires --write-mode merge");
|
|
6696
|
+
}
|
|
6697
|
+
overwriteColumns = opts.overwriteColumns.split(",").map((column) => column.trim());
|
|
6698
|
+
if (overwriteColumns.some((column) => !column)) {
|
|
6699
|
+
throw new Error("--overwrite-columns must not contain empty column names");
|
|
6700
|
+
}
|
|
6701
|
+
}
|
|
6702
|
+
const result = await new ErdoClient().writeDatasetRows(slug, {
|
|
6703
|
+
rows: parsed,
|
|
6704
|
+
...opts.keyColumn !== void 0 ? { key_column: opts.keyColumn } : {},
|
|
6705
|
+
...writeMode !== void 0 ? { write_mode: writeMode } : {},
|
|
6706
|
+
...overwriteColumns !== void 0 ? { overwrite_columns: overwriteColumns } : {}
|
|
6707
|
+
});
|
|
6708
|
+
print(result);
|
|
6709
|
+
} catch (e) {
|
|
6710
|
+
fail(e);
|
|
6711
|
+
}
|
|
6712
|
+
}
|
|
6713
|
+
);
|
|
6660
6714
|
function readSchemaFile(path) {
|
|
6661
6715
|
const parsed = JSON.parse(readFileSync4(path, "utf8"));
|
|
6662
6716
|
const schema = Array.isArray(parsed) ? { columns: parsed } : parsed;
|
|
@@ -7095,7 +7149,7 @@ refreshCmd.command("set <slug>").description(
|
|
|
7095
7149
|
).option(
|
|
7096
7150
|
"--mode <replace|upsert>",
|
|
7097
7151
|
"replace overwrites every row with what the recipe returned; upsert merges by --key-column and keeps rows outside the returned window. Omit it and you get upsert when you passed --key-column and replace when you did not, since a key column is only meaningful to a merge"
|
|
7098
|
-
).option("--key-column <c>", "
|
|
7152
|
+
).option("--key-column <c>", "non-blank column, or comma-separated columns, an upsert merges on; required with --mode upsert").option(
|
|
7099
7153
|
"--schedule <cron>",
|
|
7100
7154
|
"a cron expression to run the refresh on a fixed cadence. Omit it and the refresh runs on view instead \u2014 cheaper, because it never runs while nobody is looking"
|
|
7101
7155
|
).option("--timezone <tz>", "IANA timezone the cron expression is read in (default UTC)").option(
|
|
@@ -7145,6 +7199,10 @@ refreshCmd.command("set <slug>").description(
|
|
|
7145
7199
|
fail(new Error("--mode must be replace or upsert"));
|
|
7146
7200
|
return;
|
|
7147
7201
|
}
|
|
7202
|
+
if (opts.keyColumn !== void 0 && !opts.keyColumn.split(",").some((column) => column.trim())) {
|
|
7203
|
+
fail(new Error("--key-column must name at least one non-blank column"));
|
|
7204
|
+
return;
|
|
7205
|
+
}
|
|
7148
7206
|
if (opts.mode === "upsert" && !opts.keyColumn) {
|
|
7149
7207
|
fail(new Error("--mode upsert needs --key-column <c>: the column the merge keys on"));
|
|
7150
7208
|
return;
|