@erdoai/cli 0.66.0 → 0.68.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 +232 -10
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -331,8 +331,30 @@ var ErdoClient = class {
|
|
|
331
331
|
listCustomDomains() {
|
|
332
332
|
return this.request("GET", "/v1/custom-domains");
|
|
333
333
|
}
|
|
334
|
-
|
|
335
|
-
|
|
334
|
+
// `managerDefault` registers the domain and points the orgs this one manages at
|
|
335
|
+
// it in one step, for a manager setting up its very first branded hostname. The
|
|
336
|
+
// key is left out of the body when false so an ordinary registration sends what
|
|
337
|
+
// it always sent; changing the flag on a domain that is already live goes
|
|
338
|
+
// through setCustomDomainManagerDefault, which does not touch the certificate.
|
|
339
|
+
createCustomDomain(domain, managerDefault) {
|
|
340
|
+
return this.request(
|
|
341
|
+
"POST",
|
|
342
|
+
"/v1/custom-domains",
|
|
343
|
+
managerDefault ? { domain, manager_default: true } : { domain }
|
|
344
|
+
);
|
|
345
|
+
}
|
|
346
|
+
// Marks a domain the org already owns as the one its managed orgs serve their
|
|
347
|
+
// pages from, or clears it. At most one domain per org carries the flag, so
|
|
348
|
+
// promoting a second demotes the first in the same write — the managed orgs
|
|
349
|
+
// never see a moment with no default and fall back to platform URLs. Only a
|
|
350
|
+
// manager account with at least one client org can set it; clearing is always
|
|
351
|
+
// allowed, so an org that has since let its last client go can still tidy up.
|
|
352
|
+
setCustomDomainManagerDefault(domain, managerDefault) {
|
|
353
|
+
return this.request(
|
|
354
|
+
"PATCH",
|
|
355
|
+
`/v1/custom-domains/${encodeURIComponent(domain)}/manager-default`,
|
|
356
|
+
{ manager_default: managerDefault }
|
|
357
|
+
);
|
|
336
358
|
}
|
|
337
359
|
deleteCustomDomain(domain) {
|
|
338
360
|
return this.request(
|
|
@@ -464,7 +486,7 @@ var ErdoClient = class {
|
|
|
464
486
|
`/v1/evals/suites/${encodeURIComponent(slug)}/cases/${encodeURIComponent(caseName)}`
|
|
465
487
|
);
|
|
466
488
|
}
|
|
467
|
-
runEvalSuite(slug, concurrency, commitSha, frontendCommitSha, backendCommitSha, source, modelOverride) {
|
|
489
|
+
runEvalSuite(slug, concurrency, commitSha, frontendCommitSha, backendCommitSha, source, modelOverride, rotateCases) {
|
|
468
490
|
return this.request(
|
|
469
491
|
"POST",
|
|
470
492
|
`/v1/evals/suites/${encodeURIComponent(slug)}/run`,
|
|
@@ -474,7 +496,8 @@ var ErdoClient = class {
|
|
|
474
496
|
frontend_commit_sha: frontendCommitSha,
|
|
475
497
|
backend_commit_sha: backendCommitSha,
|
|
476
498
|
source,
|
|
477
|
-
model_override: modelOverride
|
|
499
|
+
model_override: modelOverride,
|
|
500
|
+
rotate_cases: rotateCases
|
|
478
501
|
}
|
|
479
502
|
);
|
|
480
503
|
}
|
|
@@ -887,6 +910,69 @@ var ErdoClient = class {
|
|
|
887
910
|
listDatasetRevisions(slug) {
|
|
888
911
|
return this.request("GET", `/v1/datasets/${encodeURIComponent(slug)}/revisions`);
|
|
889
912
|
}
|
|
913
|
+
// --- bounded outreach ---
|
|
914
|
+
putOutreachBatch(batch, body) {
|
|
915
|
+
return this.request(
|
|
916
|
+
"PUT",
|
|
917
|
+
`/v1/outreach-batches/${encodeURIComponent(batch)}`,
|
|
918
|
+
body
|
|
919
|
+
);
|
|
920
|
+
}
|
|
921
|
+
listOutreachBatches(opts = {}) {
|
|
922
|
+
const q = new URLSearchParams();
|
|
923
|
+
if (opts.limit !== void 0) q.set("limit", String(opts.limit));
|
|
924
|
+
if (opts.cursor) q.set("cursor", opts.cursor);
|
|
925
|
+
if (opts.initiative_ref) q.set("initiative_ref", opts.initiative_ref);
|
|
926
|
+
const qs = q.toString();
|
|
927
|
+
return this.request(
|
|
928
|
+
"GET",
|
|
929
|
+
`/v1/outreach-batches${qs ? `?${qs}` : ""}`
|
|
930
|
+
);
|
|
931
|
+
}
|
|
932
|
+
getOutreachBatch(batch) {
|
|
933
|
+
return this.request(
|
|
934
|
+
"GET",
|
|
935
|
+
`/v1/outreach-batches/${encodeURIComponent(batch)}`
|
|
936
|
+
);
|
|
937
|
+
}
|
|
938
|
+
listOutreachBatchRecipients(batch, opts = {}) {
|
|
939
|
+
const q = new URLSearchParams();
|
|
940
|
+
if (opts.limit !== void 0) q.set("limit", String(opts.limit));
|
|
941
|
+
if (opts.cursor) q.set("cursor", opts.cursor);
|
|
942
|
+
const qs = q.toString();
|
|
943
|
+
return this.request(
|
|
944
|
+
"GET",
|
|
945
|
+
`/v1/outreach-batches/${encodeURIComponent(batch)}/recipients${qs ? `?${qs}` : ""}`
|
|
946
|
+
);
|
|
947
|
+
}
|
|
948
|
+
actOnOutreachBatch(batch, body) {
|
|
949
|
+
return this.request(
|
|
950
|
+
"POST",
|
|
951
|
+
`/v1/outreach-batches/${encodeURIComponent(batch)}/actions`,
|
|
952
|
+
body
|
|
953
|
+
);
|
|
954
|
+
}
|
|
955
|
+
putOutreachConsent(body) {
|
|
956
|
+
return this.request("PUT", "/v1/outreach-consents", body);
|
|
957
|
+
}
|
|
958
|
+
listOutreachConsents(opts = {}) {
|
|
959
|
+
const q = new URLSearchParams();
|
|
960
|
+
if (opts.limit !== void 0) q.set("limit", String(opts.limit));
|
|
961
|
+
if (opts.cursor) q.set("cursor", opts.cursor);
|
|
962
|
+
const qs = q.toString();
|
|
963
|
+
return this.request(
|
|
964
|
+
"GET",
|
|
965
|
+
`/v1/outreach-consents${qs ? `?${qs}` : ""}`
|
|
966
|
+
);
|
|
967
|
+
}
|
|
968
|
+
getOutreachConsent(opts) {
|
|
969
|
+
const q = new URLSearchParams();
|
|
970
|
+
if (opts.grant_ref) q.set("grant_ref", opts.grant_ref);
|
|
971
|
+
if (opts.recipient_ref) q.set("recipient_ref", opts.recipient_ref);
|
|
972
|
+
if (opts.phone_raw) q.set("phone_raw", opts.phone_raw);
|
|
973
|
+
if (opts.default_country) q.set("default_country", opts.default_country);
|
|
974
|
+
return this.request("GET", `/v1/outreach-consents?${q.toString()}`);
|
|
975
|
+
}
|
|
890
976
|
uploadDatasetFile(body) {
|
|
891
977
|
return this.request("POST", "/v1/datasets-upload", body);
|
|
892
978
|
}
|
|
@@ -1533,6 +1619,25 @@ function timedOutMessage(threadID) {
|
|
|
1533
1619
|
function print(value) {
|
|
1534
1620
|
console.log(JSON.stringify(value, null, 2));
|
|
1535
1621
|
}
|
|
1622
|
+
function readJSONObject(file, label) {
|
|
1623
|
+
let parsed;
|
|
1624
|
+
try {
|
|
1625
|
+
parsed = JSON.parse(readFileSync4(file, "utf8"));
|
|
1626
|
+
} catch (error) {
|
|
1627
|
+
throw new Error(`${label} must be a readable JSON file: ${error instanceof Error ? error.message : String(error)}`);
|
|
1628
|
+
}
|
|
1629
|
+
if (parsed === null || Array.isArray(parsed) || typeof parsed !== "object") {
|
|
1630
|
+
throw new Error(`${label} must contain one JSON object`);
|
|
1631
|
+
}
|
|
1632
|
+
return parsed;
|
|
1633
|
+
}
|
|
1634
|
+
function requireOutreachProject() {
|
|
1635
|
+
if (!process.env.ERDO_PROJECT?.trim()) {
|
|
1636
|
+
throw new Error(
|
|
1637
|
+
"outreach commands require a project: pass --project <uuid> (and --org when the key can reach more than one organization)"
|
|
1638
|
+
);
|
|
1639
|
+
}
|
|
1640
|
+
}
|
|
1536
1641
|
function printPageReview(review) {
|
|
1537
1642
|
if (!review) return;
|
|
1538
1643
|
if (!review.reviewed) {
|
|
@@ -2551,7 +2656,13 @@ evalCmd.command("create <name>").description("Create a suite (in the active org)
|
|
|
2551
2656
|
}
|
|
2552
2657
|
}
|
|
2553
2658
|
);
|
|
2554
|
-
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)).option("--commit-sha <sha>", "full 40-character Git commit SHA to associate with the run").option("--frontend-commit-sha <sha>", "frontend revision in the evaluated production snapshot").option("--backend-commit-sha <sha>", "backend revision in the evaluated production snapshot").option("--source <source>", "staff-only run source: post_deploy, nightly, or rollback").option("--model <model>", "pin the suite's agent to a model for this run (e.g. glm-5.3) \u2014 for model comparisons").
|
|
2659
|
+
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)).option("--commit-sha <sha>", "full 40-character Git commit SHA to associate with the run").option("--frontend-commit-sha <sha>", "frontend revision in the evaluated production snapshot").option("--backend-commit-sha <sha>", "backend revision in the evaluated production snapshot").option("--source <source>", "staff-only run source: post_deploy, nightly, or rollback").option("--model <model>", "pin the suite's agent to a model for this run (e.g. glm-5.3) \u2014 for model comparisons").option("--rotate-cases <n>", "run a rotating subset of n cases (deterministic per suite + UTC day) instead of all cases", (v) => {
|
|
2660
|
+
const parsed = parseInt(v, 10);
|
|
2661
|
+
if (isNaN(parsed) || parsed < 0) {
|
|
2662
|
+
throw new Error(`--rotate-cases must be a non-negative integer, got "${v}"`);
|
|
2663
|
+
}
|
|
2664
|
+
return parsed;
|
|
2665
|
+
}).action(async (slug, opts) => {
|
|
2555
2666
|
try {
|
|
2556
2667
|
const api = new ErdoClient();
|
|
2557
2668
|
const { suite } = await api.getEvalSuite(slug);
|
|
@@ -2574,7 +2685,8 @@ evalCmd.command("run <slug>").description("Run a suite; --watch polls until it c
|
|
|
2574
2685
|
opts.frontendCommitSha,
|
|
2575
2686
|
opts.backendCommitSha,
|
|
2576
2687
|
opts.source,
|
|
2577
|
-
opts.model
|
|
2688
|
+
opts.model,
|
|
2689
|
+
opts.rotateCases
|
|
2578
2690
|
);
|
|
2579
2691
|
console.log(`run_id: ${run_id}`);
|
|
2580
2692
|
if (!opts.watch) return;
|
|
@@ -3916,17 +4028,21 @@ domainsCmd.command("list").description("List the org's custom domains with live
|
|
|
3916
4028
|
}
|
|
3917
4029
|
for (const d of domains) {
|
|
3918
4030
|
const checked = d.last_checked_at ? `checked ${d.last_checked_at}` : "never checked";
|
|
4031
|
+
const managerDefault = d.manager_default ? " manager default" : "";
|
|
3919
4032
|
const reason = d.error_reason ? ` ${d.error_reason}` : "";
|
|
3920
|
-
console.log(`${d.domain} ${d.status} ${checked}${reason}`);
|
|
4033
|
+
console.log(`${d.domain} ${d.status} ${checked}${managerDefault}${reason}`);
|
|
3921
4034
|
}
|
|
3922
4035
|
} catch (e) {
|
|
3923
4036
|
fail(e);
|
|
3924
4037
|
}
|
|
3925
4038
|
});
|
|
3926
|
-
domainsCmd.command("add <domain>").description("Register a custom domain (a direct subdomain, e.g. pages.acme.com) and print the DNS records to create").
|
|
4039
|
+
domainsCmd.command("add <domain>").description("Register a custom domain (a direct subdomain, e.g. pages.acme.com) and print the DNS records to create").option(
|
|
4040
|
+
"--manager-default",
|
|
4041
|
+
"also point the orgs you manage at this domain, for a manager registering its first branded hostname"
|
|
4042
|
+
).action(async (domain, opts) => {
|
|
3927
4043
|
try {
|
|
3928
|
-
const d = await new ErdoClient().createCustomDomain(domain);
|
|
3929
|
-
console.log(`${d.domain}: ${d.status}`);
|
|
4044
|
+
const d = await new ErdoClient().createCustomDomain(domain, opts.managerDefault);
|
|
4045
|
+
console.log(`${d.domain}: ${d.status}${d.manager_default ? " (manager default)" : ""}`);
|
|
3930
4046
|
if (d.dns_records.length > 0) {
|
|
3931
4047
|
process.stderr.write("Create these records at the domain's DNS provider:\n");
|
|
3932
4048
|
for (const r of d.dns_records) {
|
|
@@ -3937,6 +4053,20 @@ domainsCmd.command("add <domain>").description("Register a custom domain (a dire
|
|
|
3937
4053
|
fail(e);
|
|
3938
4054
|
}
|
|
3939
4055
|
});
|
|
4056
|
+
domainsCmd.command("manager-default <domain>").description(
|
|
4057
|
+
"Make this the domain the orgs you manage serve their pages from when they have registered none of their own \u2014 a client with its own domain keeps it \u2014 or clear that with --unset"
|
|
4058
|
+
).option("--unset", "clear the flag instead, returning the managed orgs to the platform pages host").action(async (domain, opts) => {
|
|
4059
|
+
try {
|
|
4060
|
+
const d = await new ErdoClient().setCustomDomainManagerDefault(domain, !opts.unset);
|
|
4061
|
+
if (d.manager_default) {
|
|
4062
|
+
console.log(`${d.domain} is now the default domain for the orgs you manage (status: ${d.status})`);
|
|
4063
|
+
} else {
|
|
4064
|
+
console.log(`${d.domain} is no longer the default domain for the orgs you manage`);
|
|
4065
|
+
}
|
|
4066
|
+
} catch (e) {
|
|
4067
|
+
fail(e);
|
|
4068
|
+
}
|
|
4069
|
+
});
|
|
3940
4070
|
domainsCmd.command("remove <domain>").description("Remove a custom domain registration (stops it serving pages)").action(async (domain) => {
|
|
3941
4071
|
try {
|
|
3942
4072
|
const res = await new ErdoClient().deleteCustomDomain(domain);
|
|
@@ -4160,6 +4290,98 @@ sentEmailsCmd.command("list").description("List sent email, newest first, includ
|
|
|
4160
4290
|
}
|
|
4161
4291
|
}
|
|
4162
4292
|
);
|
|
4293
|
+
var outreachCmd = program.command("outreach").description("Consent-gated SMS outreach to an explicit reviewed selection of up to 700 people");
|
|
4294
|
+
var outreachBatchesCmd = outreachCmd.command("batches").description("Prepare and control outreach batches");
|
|
4295
|
+
outreachBatchesCmd.command("put <batch>").description("Create one immutable draft preview from a JSON declaration; this never starts sending").requiredOption("-f, --file <path>", "JSON object containing the batch declaration and recipients").action(async (batch, opts) => {
|
|
4296
|
+
try {
|
|
4297
|
+
requireOutreachProject();
|
|
4298
|
+
print(await new ErdoClient().putOutreachBatch(batch, readJSONObject(opts.file, "--file")));
|
|
4299
|
+
} catch (e) {
|
|
4300
|
+
fail(e);
|
|
4301
|
+
}
|
|
4302
|
+
});
|
|
4303
|
+
outreachBatchesCmd.command("list").description("List outreach batches in the required project").option("-l, --limit <n>", "max batches to return", (v) => parseInt(v, 10)).option("--cursor <cursor>", "opaque next_cursor from the preceding page").option("--initiative <ref>", "return only this outreach initiative before pagination").action(async (opts) => {
|
|
4304
|
+
try {
|
|
4305
|
+
requireOutreachProject();
|
|
4306
|
+
print(await new ErdoClient().listOutreachBatches({
|
|
4307
|
+
limit: opts.limit,
|
|
4308
|
+
cursor: opts.cursor,
|
|
4309
|
+
initiative_ref: opts.initiative
|
|
4310
|
+
}));
|
|
4311
|
+
} catch (e) {
|
|
4312
|
+
fail(e);
|
|
4313
|
+
}
|
|
4314
|
+
});
|
|
4315
|
+
outreachBatchesCmd.command("get <batch>").description("Read a batch summary, or its paged recipient receipts").option("--recipients", "return recipient eligibility, delivery, and response receipts").option("-l, --limit <n>", "max recipients to return", (v) => parseInt(v, 10)).option("--cursor <cursor>", "opaque next_cursor from the preceding recipient page").action(async (batch, opts) => {
|
|
4316
|
+
try {
|
|
4317
|
+
requireOutreachProject();
|
|
4318
|
+
const client = new ErdoClient();
|
|
4319
|
+
print(
|
|
4320
|
+
opts.recipients ? await client.listOutreachBatchRecipients(batch, opts) : await client.getOutreachBatch(batch)
|
|
4321
|
+
);
|
|
4322
|
+
} catch (e) {
|
|
4323
|
+
fail(e);
|
|
4324
|
+
}
|
|
4325
|
+
});
|
|
4326
|
+
outreachBatchesCmd.command("act <batch> <action>").description("Apply a replay-safe arm, pause, resume, or cancel action").requiredOption("--action-ref <ref>", "stable idempotency reference for this operator action").option("--preview-revision <revision>", "arm only this reviewed preview revision").option("--eligible-count <n>", "arm only when this many recipients remain eligible", (v) => parseInt(v, 10)).option("--message-hash <hash>", "arm only the reviewed message hash").option("--source-snapshot <ref>", "pause, resume, or cancel only the rendered source snapshot").option("--batch-state <state>", "pause, resume, or cancel only the rendered batch state").option("--batch-updated-at <timestamp>", "pause, resume, or cancel only the rendered batch version").action(
|
|
4327
|
+
async (batch, action, opts) => {
|
|
4328
|
+
try {
|
|
4329
|
+
requireOutreachProject();
|
|
4330
|
+
print(
|
|
4331
|
+
await new ErdoClient().actOnOutreachBatch(batch, {
|
|
4332
|
+
action,
|
|
4333
|
+
action_ref: opts.actionRef,
|
|
4334
|
+
expected_preview_revision: opts.previewRevision,
|
|
4335
|
+
expected_eligible_count: opts.eligibleCount,
|
|
4336
|
+
expected_message_hash: opts.messageHash,
|
|
4337
|
+
expected_source_snapshot_ref: opts.sourceSnapshot,
|
|
4338
|
+
expected_batch_state: opts.batchState,
|
|
4339
|
+
expected_batch_updated_at: opts.batchUpdatedAt
|
|
4340
|
+
})
|
|
4341
|
+
);
|
|
4342
|
+
} catch (e) {
|
|
4343
|
+
fail(e);
|
|
4344
|
+
}
|
|
4345
|
+
}
|
|
4346
|
+
);
|
|
4347
|
+
var outreachConsentsCmd = outreachCmd.command("consents").description("Declare and read auditable SMS consent evidence");
|
|
4348
|
+
outreachConsentsCmd.command("put").description("Declare or revoke one consent grant from a JSON object").requiredOption("-f, --file <path>", "JSON object containing the consent declaration").action(async (opts) => {
|
|
4349
|
+
try {
|
|
4350
|
+
requireOutreachProject();
|
|
4351
|
+
print(await new ErdoClient().putOutreachConsent(readJSONObject(opts.file, "--file")));
|
|
4352
|
+
} catch (e) {
|
|
4353
|
+
fail(e);
|
|
4354
|
+
}
|
|
4355
|
+
});
|
|
4356
|
+
outreachConsentsCmd.command("list").description("List consent grants in the required project").option("-l, --limit <n>", "max grants to return", (v) => parseInt(v, 10)).option("--cursor <cursor>", "opaque next_cursor from the preceding page").action(async (opts) => {
|
|
4357
|
+
try {
|
|
4358
|
+
requireOutreachProject();
|
|
4359
|
+
print(await new ErdoClient().listOutreachConsents(opts));
|
|
4360
|
+
} catch (e) {
|
|
4361
|
+
fail(e);
|
|
4362
|
+
}
|
|
4363
|
+
});
|
|
4364
|
+
outreachConsentsCmd.command("get [grant-ref]").description("Read one consent grant by reference, or by recipient and phone").option("--recipient-ref <ref>", "stable lead identity when the grant reference is not known").option("--phone <phone>", "source phone recorded for the recipient").option("--country <code>", "ISO country hint used to normalize --phone").action(async (grantRef, opts) => {
|
|
4365
|
+
try {
|
|
4366
|
+
requireOutreachProject();
|
|
4367
|
+
const byGrant = Boolean(grantRef);
|
|
4368
|
+
const byRecipient = Boolean(opts.recipientRef || opts.phone || opts.country);
|
|
4369
|
+
if (byGrant === byRecipient) {
|
|
4370
|
+
throw new Error("consents get requires either <grant-ref> or all of --recipient-ref, --phone, and --country");
|
|
4371
|
+
}
|
|
4372
|
+
if (!byGrant && !(opts.recipientRef && opts.phone && opts.country)) {
|
|
4373
|
+
throw new Error("consents get by recipient requires --recipient-ref, --phone, and --country together");
|
|
4374
|
+
}
|
|
4375
|
+
print(await new ErdoClient().getOutreachConsent({
|
|
4376
|
+
grant_ref: grantRef,
|
|
4377
|
+
recipient_ref: opts.recipientRef,
|
|
4378
|
+
phone_raw: opts.phone,
|
|
4379
|
+
default_country: opts.country
|
|
4380
|
+
}));
|
|
4381
|
+
} catch (e) {
|
|
4382
|
+
fail(e);
|
|
4383
|
+
}
|
|
4384
|
+
});
|
|
4163
4385
|
sentEmailsCmd.command("get <emailID>").description("Read one sent email, including exact bodies and delivery evidence").action(async (emailID) => {
|
|
4164
4386
|
try {
|
|
4165
4387
|
print(await new ErdoClient().getSentEmail(emailID));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@erdoai/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.68.0",
|
|
4
4
|
"description": "Erdo CLI — drive datasets, pages, and evals from the terminal or CI",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -35,7 +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
|
|
38
|
+
"test": "node --import tsx --test src/*.test.ts",
|
|
39
39
|
"dev": "tsx src/index.ts",
|
|
40
40
|
"postinstall": "node scripts/postinstall.mjs",
|
|
41
41
|
"prepublishOnly": "npm run build"
|