@erdoai/cli 0.64.0 → 0.65.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 +102 -17
- package/package.json +2 -1
package/dist/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/index.ts
|
|
4
|
-
import { readFileSync as
|
|
4
|
+
import { readFileSync as readFileSync4 } from "fs";
|
|
5
5
|
import { basename } from "path";
|
|
6
6
|
import { select } from "@inquirer/prompts";
|
|
7
7
|
import { Command } from "commander";
|
|
@@ -297,6 +297,36 @@ var ErdoClient = class {
|
|
|
297
297
|
createManagerKey() {
|
|
298
298
|
return this.request("POST", "/v1/manager-key");
|
|
299
299
|
}
|
|
300
|
+
// Brings an EXISTING org under your manager org by redeeming a one-time consent
|
|
301
|
+
// token — a manager credential on its own can never adopt an arbitrary org, it
|
|
302
|
+
// has to present a secret only the target org's owner (or, for an ownerless
|
|
303
|
+
// managed org, its current manager) could have minted. The path is a sibling of
|
|
304
|
+
// /v1/managed-organizations rather than a nested `adopt` because Encore rejects
|
|
305
|
+
// a static segment alongside the parameterized /:orgSlug routes.
|
|
306
|
+
adoptManagedOrganization(token) {
|
|
307
|
+
return this.request("POST", "/v1/managed-organization-adoptions", { token });
|
|
308
|
+
}
|
|
309
|
+
// Seats a person in a managed org. The role is capped at admin — ownership of a
|
|
310
|
+
// client org stays with the client — and an omitted role means the backend's
|
|
311
|
+
// default (member).
|
|
312
|
+
addManagedOrganizationMember(slug, input) {
|
|
313
|
+
return this.request(
|
|
314
|
+
"POST",
|
|
315
|
+
`/v1/managed-organizations/${encodeURIComponent(slug)}/members`,
|
|
316
|
+
input
|
|
317
|
+
);
|
|
318
|
+
}
|
|
319
|
+
// Offers an ownerless managed org to another manager. Such an org has no owner
|
|
320
|
+
// to consent, so without this it stays pinned to whoever created it; the mint is
|
|
321
|
+
// refused the moment the org has a real owner, since re-parenting is then the
|
|
322
|
+
// owner's call through an adoption token.
|
|
323
|
+
createManagedOrganizationHandoffToken(slug, successorManagerSlug) {
|
|
324
|
+
return this.request(
|
|
325
|
+
"POST",
|
|
326
|
+
`/v1/managed-organizations/${encodeURIComponent(slug)}/handoff-tokens`,
|
|
327
|
+
{ successor_manager_slug: successorManagerSlug }
|
|
328
|
+
);
|
|
329
|
+
}
|
|
300
330
|
// --- Custom page domains: the branded hostnames the org's pages serve from ---
|
|
301
331
|
listCustomDomains() {
|
|
302
332
|
return this.request("GET", "/v1/custom-domains");
|
|
@@ -1333,9 +1363,17 @@ async function browserLogin() {
|
|
|
1333
1363
|
return { token: data.access_token, me };
|
|
1334
1364
|
}
|
|
1335
1365
|
|
|
1366
|
+
// src/input.ts
|
|
1367
|
+
import { readFileSync as readFileSync2 } from "fs";
|
|
1368
|
+
function readMaybeFile(value) {
|
|
1369
|
+
if (!value) return void 0;
|
|
1370
|
+
if (value.startsWith("@@")) return value.slice(1);
|
|
1371
|
+
return value.startsWith("@") ? readFileSync2(value.slice(1), "utf8") : value;
|
|
1372
|
+
}
|
|
1373
|
+
|
|
1336
1374
|
// src/update.ts
|
|
1337
1375
|
import { spawn as spawn2, spawnSync } from "child_process";
|
|
1338
|
-
import { existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync as
|
|
1376
|
+
import { existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from "fs";
|
|
1339
1377
|
import { dirname as dirname2 } from "path";
|
|
1340
1378
|
var PKG = "@erdoai/cli";
|
|
1341
1379
|
var MANUAL_HINT = `npm install -g ${PKG}@latest`;
|
|
@@ -1373,7 +1411,7 @@ async function fetchLatestVersion(timeoutMs) {
|
|
|
1373
1411
|
function readVersionCache() {
|
|
1374
1412
|
try {
|
|
1375
1413
|
if (!existsSync2(CACHE_PATH)) return {};
|
|
1376
|
-
const raw = JSON.parse(
|
|
1414
|
+
const raw = JSON.parse(readFileSync3(CACHE_PATH, "utf8"));
|
|
1377
1415
|
return raw && typeof raw === "object" ? raw : {};
|
|
1378
1416
|
} catch {
|
|
1379
1417
|
return {};
|
|
@@ -1441,7 +1479,7 @@ async function update(current) {
|
|
|
1441
1479
|
}
|
|
1442
1480
|
|
|
1443
1481
|
// src/index.ts
|
|
1444
|
-
var pkg = JSON.parse(
|
|
1482
|
+
var pkg = JSON.parse(readFileSync4(new URL("../package.json", import.meta.url), "utf8"));
|
|
1445
1483
|
function fail(err) {
|
|
1446
1484
|
if (err instanceof ErdoApiError) {
|
|
1447
1485
|
console.error(`Error ${err.status}: ${err.message}`);
|
|
@@ -1743,6 +1781,57 @@ id: ${o.id}`);
|
|
|
1743
1781
|
fail(e);
|
|
1744
1782
|
}
|
|
1745
1783
|
});
|
|
1784
|
+
managed.command("adopt <token>").description("Take over an existing org by redeeming a one-time consent token").action(async (token) => {
|
|
1785
|
+
try {
|
|
1786
|
+
const o = await new ErdoClient().adoptManagedOrganization(token);
|
|
1787
|
+
console.log(`Adopted managed org: ${o.name}`);
|
|
1788
|
+
console.log(`slug: ${o.slug}
|
|
1789
|
+
id: ${o.id}`);
|
|
1790
|
+
process.stderr.write(
|
|
1791
|
+
`Operate it with the manager key by passing --org ${o.slug} on any command (e.g. erdo --org ${o.slug} datasets list).
|
|
1792
|
+
`
|
|
1793
|
+
);
|
|
1794
|
+
} catch (e) {
|
|
1795
|
+
fail(e);
|
|
1796
|
+
}
|
|
1797
|
+
});
|
|
1798
|
+
managed.command("add-member <orgSlug> <email>").description("Seat a person in a managed org (role member or admin)").option("--role <role>", "member (default) or admin").action(async (orgSlug, email, opts) => {
|
|
1799
|
+
try {
|
|
1800
|
+
if (opts.role && opts.role !== "member" && opts.role !== "admin") {
|
|
1801
|
+
fail(new Error("--role must be member or admin (ownership of a managed org stays with the client)"));
|
|
1802
|
+
}
|
|
1803
|
+
const m = await new ErdoClient().addManagedOrganizationMember(orgSlug, {
|
|
1804
|
+
email,
|
|
1805
|
+
role: opts.role
|
|
1806
|
+
});
|
|
1807
|
+
if (m.invited) {
|
|
1808
|
+
console.log(`Invited ${m.email} to ${m.org_slug} as ${m.role} \u2014 pending until they accept`);
|
|
1809
|
+
} else {
|
|
1810
|
+
console.log(`Added ${m.email} to ${m.org_slug} as ${m.role}`);
|
|
1811
|
+
}
|
|
1812
|
+
} catch (e) {
|
|
1813
|
+
fail(e);
|
|
1814
|
+
}
|
|
1815
|
+
});
|
|
1816
|
+
managed.command("handoff-token <orgSlug>").description("Mint a one-time token offering an ownerless managed org to another manager; shown ONCE").requiredOption("--successor <managerOrgSlug>", "the manager org allowed to redeem the token").action(async (orgSlug, opts) => {
|
|
1817
|
+
try {
|
|
1818
|
+
const res = await new ErdoClient().createManagedOrganizationHandoffToken(orgSlug, opts.successor);
|
|
1819
|
+
process.stderr.write(
|
|
1820
|
+
"Give this handoff token to the successor manager now \u2014 it is shown only once and cannot be retrieved again.\n"
|
|
1821
|
+
);
|
|
1822
|
+
console.log(res.token);
|
|
1823
|
+
process.stderr.write(
|
|
1824
|
+
`
|
|
1825
|
+
org: ${res.org_slug}
|
|
1826
|
+
successor: ${res.successor_manager_slug}
|
|
1827
|
+
expires: ${res.expires_at}
|
|
1828
|
+
Only ${res.successor_manager_slug} can redeem it, with: erdo org managed adopt <token>, and only until it expires at the time above.
|
|
1829
|
+
`
|
|
1830
|
+
);
|
|
1831
|
+
} catch (e) {
|
|
1832
|
+
fail(e);
|
|
1833
|
+
}
|
|
1834
|
+
});
|
|
1746
1835
|
managed.command("revoke <slug>").description("Stop managing a client org (removes access; keeps an audit record)").action(async (slug) => {
|
|
1747
1836
|
try {
|
|
1748
1837
|
await new ErdoClient().revokeManagedOrganization(slug);
|
|
@@ -2643,14 +2732,14 @@ caseCmd.command("rm <slug> <caseName>").description("Archive a case from a suite
|
|
|
2643
2732
|
}
|
|
2644
2733
|
});
|
|
2645
2734
|
var agentCmd = program.command("agent").description("Run agents");
|
|
2646
|
-
agentCmd.command("ask <question>").description("Ask the data-question agent (one-shot);
|
|
2735
|
+
agentCmd.command("ask <question>").description("Ask the data-question agent (one-shot); question accepts @path to a UTF-8 file").option("-d, --datasets <csv>", "dataset slugs to scope to").option(
|
|
2647
2736
|
"--sync",
|
|
2648
2737
|
"hold one long HTTP request open until the run finishes, instead of polling. Faster for quick questions; a proxy will time it out on any run over ~100s, losing the thread id while the run continues server-side"
|
|
2649
2738
|
).action(async (question, opts) => {
|
|
2650
2739
|
try {
|
|
2651
2740
|
const client = new ErdoClient();
|
|
2652
2741
|
const res = await client.ask({
|
|
2653
|
-
question,
|
|
2742
|
+
question: readMaybeFile(question) ?? "",
|
|
2654
2743
|
dataset_slugs: opts.datasets ? opts.datasets.split(",").map((s) => s.trim()) : void 0,
|
|
2655
2744
|
async: !opts.sync
|
|
2656
2745
|
});
|
|
@@ -2676,9 +2765,9 @@ thread: ${res.thread_id}`);
|
|
|
2676
2765
|
fail(e);
|
|
2677
2766
|
}
|
|
2678
2767
|
});
|
|
2679
|
-
agentCmd.command("send <threadId> <message>").description("Send a message to a thread and print the agent's reply").option("-a, --agent <key>", "agent to invoke (e.g. erdo.artifact-builder)").option(
|
|
2768
|
+
agentCmd.command("send <threadId> <message>").description("Send a message to a thread and print the agent's reply; message accepts @path to a UTF-8 file").option("-a, --agent <key>", "agent to invoke (e.g. erdo.artifact-builder)").option(
|
|
2680
2769
|
"--context <text>",
|
|
2681
|
-
"application context for this turn
|
|
2770
|
+
"application context for this turn, or @path to a UTF-8 file; omitted from the visible user message"
|
|
2682
2771
|
).option(
|
|
2683
2772
|
"--sync",
|
|
2684
2773
|
"hold one long HTTP request open until the run finishes, instead of polling. Faster for quick questions; a proxy will time it out on any run over ~100s while the run continues server-side"
|
|
@@ -2687,8 +2776,8 @@ agentCmd.command("send <threadId> <message>").description("Send a message to a t
|
|
|
2687
2776
|
const client = new ErdoClient();
|
|
2688
2777
|
const baselineRunID = opts.sync ? void 0 : await client.latestRunID(threadId);
|
|
2689
2778
|
const res = await client.sendMessage(threadId, {
|
|
2690
|
-
message,
|
|
2691
|
-
context: opts.context,
|
|
2779
|
+
message: readMaybeFile(message) ?? "",
|
|
2780
|
+
context: readMaybeFile(opts.context),
|
|
2692
2781
|
agent_key: opts.agent,
|
|
2693
2782
|
async: !opts.sync
|
|
2694
2783
|
});
|
|
@@ -3596,10 +3685,6 @@ reviewsCmd.command("decide <id>").description(
|
|
|
3596
3685
|
}
|
|
3597
3686
|
);
|
|
3598
3687
|
var pagesCmd = program.command("pages").description("Create and manage pages/artifacts");
|
|
3599
|
-
function readMaybeFile(v) {
|
|
3600
|
-
if (!v) return void 0;
|
|
3601
|
-
return v.startsWith("@") ? readFileSync3(v.slice(1), "utf8") : v;
|
|
3602
|
-
}
|
|
3603
3688
|
function grantList(v) {
|
|
3604
3689
|
if (v === void 0) return void 0;
|
|
3605
3690
|
if (v.trim() === "[]") return [];
|
|
@@ -4139,7 +4224,7 @@ datasetsCmd.command("schema <slug>").alias("describe").description(
|
|
|
4139
4224
|
var MAX_UPLOAD_BYTES = 20 * 1024 * 1024;
|
|
4140
4225
|
datasetsCmd.command("upload <file>").description("Upload a file (CSV, Excel, JSON, ...) and create a dataset from it").option("-n, --name <name>", "display name for the dataset (defaults to the filename)").option("-d, --description <text>", "description shown to agents analyzing the dataset").action(async (file, opts) => {
|
|
4141
4226
|
try {
|
|
4142
|
-
const content =
|
|
4227
|
+
const content = readFileSync4(file);
|
|
4143
4228
|
if (content.byteLength > MAX_UPLOAD_BYTES) {
|
|
4144
4229
|
fail(
|
|
4145
4230
|
new Error(
|
|
@@ -4568,7 +4653,7 @@ knowledgeCmd.command("attach <objectId> <file>").description(
|
|
|
4568
4653
|
try {
|
|
4569
4654
|
const body = opts.url ? { filename: basename(file), source_url: opts.url } : {
|
|
4570
4655
|
filename: basename(file),
|
|
4571
|
-
content_base64:
|
|
4656
|
+
content_base64: readFileSync4(file).toString("base64")
|
|
4572
4657
|
};
|
|
4573
4658
|
print(
|
|
4574
4659
|
await new ErdoClient().attachKnowledgeAsset(objectId, {
|
|
@@ -4608,7 +4693,7 @@ autoCmd.command("update <id>").description("Edit an automation \u2014 rename, re
|
|
|
4608
4693
|
fail(new Error("pass either --script-js or --script-file, not both"));
|
|
4609
4694
|
return;
|
|
4610
4695
|
}
|
|
4611
|
-
const scriptJs = opts.scriptFile ?
|
|
4696
|
+
const scriptJs = opts.scriptFile ? readFileSync4(opts.scriptFile, "utf8") : opts.scriptJs;
|
|
4612
4697
|
const body = {
|
|
4613
4698
|
name: opts.name,
|
|
4614
4699
|
description: opts.description,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@erdoai/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.65.0",
|
|
4
4
|
"description": "Erdo CLI — drive datasets, pages, and evals from the terminal or CI",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -35,6 +35,7 @@
|
|
|
35
35
|
"scripts": {
|
|
36
36
|
"build": "tsup src/index.ts --format esm --clean",
|
|
37
37
|
"build:check": "tsc --noEmit",
|
|
38
|
+
"test": "node --import tsx --test src/input.test.ts",
|
|
38
39
|
"dev": "tsx src/index.ts",
|
|
39
40
|
"postinstall": "node scripts/postinstall.mjs",
|
|
40
41
|
"prepublishOnly": "npm run build"
|