@erdoai/cli 0.30.0 → 0.32.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 +99 -4
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -180,10 +180,13 @@ function looksLikeHTML(text) {
|
|
|
180
180
|
function fetchMe(token, orgId) {
|
|
181
181
|
return rawRequest(resolveApiUrl(), token, orgId, "GET", "/v1/me");
|
|
182
182
|
}
|
|
183
|
+
var announcedOrg = false;
|
|
183
184
|
var ErdoClient = class {
|
|
184
185
|
baseURL;
|
|
185
186
|
token;
|
|
186
187
|
orgId;
|
|
188
|
+
orgPinned;
|
|
189
|
+
orgName;
|
|
187
190
|
constructor(orgOverride) {
|
|
188
191
|
this.baseURL = resolveApiUrl();
|
|
189
192
|
const acct = activeAccount();
|
|
@@ -193,8 +196,27 @@ var ErdoClient = class {
|
|
|
193
196
|
}
|
|
194
197
|
this.token = token;
|
|
195
198
|
this.orgId = resolveOrgId(acct, orgOverride);
|
|
199
|
+
this.orgPinned = Boolean(orgOverride || process.env.ERDO_ORG);
|
|
200
|
+
this.orgName = acct?.organizationName;
|
|
201
|
+
}
|
|
202
|
+
announceOrgOnce(method) {
|
|
203
|
+
if (announcedOrg) return;
|
|
204
|
+
if (method.toUpperCase() === "GET") return;
|
|
205
|
+
announcedOrg = true;
|
|
206
|
+
if (this.orgPinned && this.orgId) {
|
|
207
|
+
process.stderr.write(`org: ${this.orgId} (pinned)
|
|
208
|
+
`);
|
|
209
|
+
} else if (this.orgId) {
|
|
210
|
+
process.stderr.write(
|
|
211
|
+
`org: ${this.orgName ?? this.orgId} (active org \u2014 pass --org to pin)
|
|
212
|
+
`
|
|
213
|
+
);
|
|
214
|
+
} else {
|
|
215
|
+
process.stderr.write("org: token default (no org resolved \u2014 pass --org to pin)\n");
|
|
216
|
+
}
|
|
196
217
|
}
|
|
197
218
|
request(method, path, body) {
|
|
219
|
+
this.announceOrgOnce(method);
|
|
198
220
|
return rawRequest(this.baseURL, this.token, this.orgId, method, path, body);
|
|
199
221
|
}
|
|
200
222
|
me() {
|
|
@@ -203,6 +225,16 @@ var ErdoClient = class {
|
|
|
203
225
|
listOrganizations() {
|
|
204
226
|
return this.request("GET", "/v1/organizations");
|
|
205
227
|
}
|
|
228
|
+
// --- API tokens (user credentials; default_org is only the header-absent fallback) ---
|
|
229
|
+
createToken(input) {
|
|
230
|
+
return this.request("POST", "/v1/tokens", input);
|
|
231
|
+
}
|
|
232
|
+
listTokens() {
|
|
233
|
+
return this.request("GET", "/v1/tokens");
|
|
234
|
+
}
|
|
235
|
+
revokeToken(id) {
|
|
236
|
+
return this.request("DELETE", `/v1/tokens/${encodeURIComponent(id)}`);
|
|
237
|
+
}
|
|
206
238
|
getAutonomyMode() {
|
|
207
239
|
return this.request("GET", "/v1/autonomy-mode");
|
|
208
240
|
}
|
|
@@ -1166,6 +1198,55 @@ org.command("autonomy [mode]").description("Show or set the org's engine-autonom
|
|
|
1166
1198
|
fail(e);
|
|
1167
1199
|
}
|
|
1168
1200
|
});
|
|
1201
|
+
var tokenCmd = program.command("token").description("Manage your API tokens (account-level credentials)");
|
|
1202
|
+
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("--org <idOrSlug>", "the token's default org (defaults to your active org; must be one you belong to)").action(async (opts) => {
|
|
1203
|
+
try {
|
|
1204
|
+
const res = await new ErdoClient().createToken({
|
|
1205
|
+
name: opts.name,
|
|
1206
|
+
expires_in_days: opts.expiresDays ?? 30,
|
|
1207
|
+
organization_id: opts.org
|
|
1208
|
+
});
|
|
1209
|
+
process.stderr.write(
|
|
1210
|
+
"Store this token now \u2014 it is shown only once and cannot be retrieved again.\n"
|
|
1211
|
+
);
|
|
1212
|
+
console.log(res.token);
|
|
1213
|
+
process.stderr.write(
|
|
1214
|
+
`
|
|
1215
|
+
id: ${res.id}
|
|
1216
|
+
hint: ${res.hint}
|
|
1217
|
+
expires: ${res.expires_at}
|
|
1218
|
+
default org: ${res.default_org ? `${res.default_org.name} (${res.default_org.id})` : "(none)"}
|
|
1219
|
+
This token acts as you in any org you belong to; pass --org on a command to target another.
|
|
1220
|
+
`
|
|
1221
|
+
);
|
|
1222
|
+
} catch (e) {
|
|
1223
|
+
fail(e);
|
|
1224
|
+
}
|
|
1225
|
+
});
|
|
1226
|
+
tokenCmd.command("list").description("List your API tokens (never shows the secret)").action(async () => {
|
|
1227
|
+
try {
|
|
1228
|
+
const { tokens } = await new ErdoClient().listTokens();
|
|
1229
|
+
if (tokens.length === 0) {
|
|
1230
|
+
console.log("No API tokens. Create one with: erdo token create --name <name>");
|
|
1231
|
+
return;
|
|
1232
|
+
}
|
|
1233
|
+
for (const t of tokens) {
|
|
1234
|
+
const org2 = t.default_org ? t.default_org.name || t.default_org.id : "-";
|
|
1235
|
+
const used = t.last_used_at ?? "never";
|
|
1236
|
+
console.log(`${t.id} ${t.name || "-"} ${t.hint} default-org:${org2} expires:${t.expires_at} used:${used}`);
|
|
1237
|
+
}
|
|
1238
|
+
} catch (e) {
|
|
1239
|
+
fail(e);
|
|
1240
|
+
}
|
|
1241
|
+
});
|
|
1242
|
+
tokenCmd.command("revoke <id>").description("Revoke an API token by id").action(async (id) => {
|
|
1243
|
+
try {
|
|
1244
|
+
await new ErdoClient().revokeToken(id);
|
|
1245
|
+
console.log(`revoked: ${id}`);
|
|
1246
|
+
} catch (e) {
|
|
1247
|
+
fail(e);
|
|
1248
|
+
}
|
|
1249
|
+
});
|
|
1169
1250
|
var evalCmd = program.command("eval").description("Run and manage evals");
|
|
1170
1251
|
evalCmd.command("suites").description("List eval suites in the active org (+ global)").action(async () => {
|
|
1171
1252
|
try {
|
|
@@ -1766,6 +1847,19 @@ evalCmd.command("create <name>").description("Create a suite (in the active org)
|
|
|
1766
1847
|
evalCmd.command("run <slug>").description("Run a suite; --watch polls until it completes").option("-w, --watch", "poll until the run finishes and print results").option("-c, --concurrency <n>", "parallel cases", (v) => parseInt(v, 10)).action(async (slug, opts) => {
|
|
1767
1848
|
try {
|
|
1768
1849
|
const api = new ErdoClient();
|
|
1850
|
+
const { suite } = await api.getEvalSuite(slug);
|
|
1851
|
+
if (suite.evaluate_artifact && !process.env.ERDO_ORG) {
|
|
1852
|
+
const resolved = resolveOrgId(activeAccount()) ?? "<org>";
|
|
1853
|
+
console.error(
|
|
1854
|
+
[
|
|
1855
|
+
`Refusing to run "${slug}": it builds real artifacts in the resolved org.`,
|
|
1856
|
+
"The active org is machine-global \u2014 a concurrent session can switch it, so this",
|
|
1857
|
+
"run could land in the wrong org. Re-run with the org pinned:",
|
|
1858
|
+
` erdo --org ${resolved} eval run ${slug}`
|
|
1859
|
+
].join("\n")
|
|
1860
|
+
);
|
|
1861
|
+
process.exit(1);
|
|
1862
|
+
}
|
|
1769
1863
|
const { run_id } = await api.runEvalSuite(slug, opts.concurrency);
|
|
1770
1864
|
console.log(`run_id: ${run_id}`);
|
|
1771
1865
|
if (!opts.watch) return;
|
|
@@ -2007,8 +2101,9 @@ approvalsCmd.command("list").description("List approval requests, optionally fil
|
|
|
2007
2101
|
return;
|
|
2008
2102
|
}
|
|
2009
2103
|
for (const r of res.requests) {
|
|
2104
|
+
const occ = r.occurrence_count && r.occurrence_count > 1 ? ` \xD7${r.occurrence_count}` : "";
|
|
2010
2105
|
console.log(
|
|
2011
|
-
`${r.id} ${r.status} ${r.action_key} ${r.action_display} ${r.created_at}`
|
|
2106
|
+
`${r.id} ${r.status} ${r.action_key} ${r.action_display} ${r.created_at}${occ}`
|
|
2012
2107
|
);
|
|
2013
2108
|
}
|
|
2014
2109
|
} catch (e) {
|
|
@@ -2387,11 +2482,11 @@ analytics.command("query <hogql>").description("Run a read-only HogQL query agai
|
|
|
2387
2482
|
fail(e);
|
|
2388
2483
|
}
|
|
2389
2484
|
});
|
|
2390
|
-
var OPERATORS = ["equals", "not_equals", "greater_than", "less_than", "contains", "between"];
|
|
2485
|
+
var OPERATORS = ["equals", "not_equals", "greater_than", "less_than", "contains", "not_contains", "between"];
|
|
2391
2486
|
function parseCondition(s) {
|
|
2392
2487
|
const m = s.trim().match(/^(\S+)\s+(\S+)\s+([\s\S]+)$/);
|
|
2393
2488
|
if (!m) {
|
|
2394
|
-
fail(new Error(`invalid --where ${JSON.stringify(s)}; expected '<column> <operator> <value>', e.g. 'email
|
|
2489
|
+
fail(new Error(`invalid --where ${JSON.stringify(s)}; expected '<column> <operator> <value>', e.g. 'email not_contains @example.com'`));
|
|
2395
2490
|
}
|
|
2396
2491
|
const [, column, operator, value] = m;
|
|
2397
2492
|
if (!OPERATORS.includes(operator)) {
|
|
@@ -2486,7 +2581,7 @@ pipelinesCmd.command("get <slug>").description("Show one event pipeline \u2014 s
|
|
|
2486
2581
|
});
|
|
2487
2582
|
filterCmd.command("add <slug>").description("Add a filter to a dataset (default-on unless --no-default)").requiredOption(
|
|
2488
2583
|
"--where <condition>",
|
|
2489
|
-
"a '<column> <operator> <value>' condition (repeatable, AND-combined),
|
|
2584
|
+
"a '<column> <operator> <value>' condition (repeatable, AND-combined). A filter KEEPS matching rows, so exclude by matching what you want to keep: --where 'email not_contains @example.com' (label 'Exclude test submissions') hides the test rows; --where 'email contains @acme.com' would keep ONLY @acme.com rows. Operators: " + OPERATORS.join(", ") + " (for 'between' use 'col between 10,100')",
|
|
2490
2585
|
collect,
|
|
2491
2586
|
[]
|
|
2492
2587
|
).option("--name <name>", "optional human-readable label, e.g. 'Exclude test submissions'").option(
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@erdoai/cli",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Erdo CLI
|
|
3
|
+
"version": "0.32.0",
|
|
4
|
+
"description": "Erdo CLI — drive datasets, pages, and evals from the terminal or CI",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
7
|
"erdo": "dist/index.js"
|