@erdoai/cli 0.67.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 +220 -6
- 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(
|
|
@@ -888,6 +910,69 @@ var ErdoClient = class {
|
|
|
888
910
|
listDatasetRevisions(slug) {
|
|
889
911
|
return this.request("GET", `/v1/datasets/${encodeURIComponent(slug)}/revisions`);
|
|
890
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
|
+
}
|
|
891
976
|
uploadDatasetFile(body) {
|
|
892
977
|
return this.request("POST", "/v1/datasets-upload", body);
|
|
893
978
|
}
|
|
@@ -1534,6 +1619,25 @@ function timedOutMessage(threadID) {
|
|
|
1534
1619
|
function print(value) {
|
|
1535
1620
|
console.log(JSON.stringify(value, null, 2));
|
|
1536
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
|
+
}
|
|
1537
1641
|
function printPageReview(review) {
|
|
1538
1642
|
if (!review) return;
|
|
1539
1643
|
if (!review.reviewed) {
|
|
@@ -3924,17 +4028,21 @@ domainsCmd.command("list").description("List the org's custom domains with live
|
|
|
3924
4028
|
}
|
|
3925
4029
|
for (const d of domains) {
|
|
3926
4030
|
const checked = d.last_checked_at ? `checked ${d.last_checked_at}` : "never checked";
|
|
4031
|
+
const managerDefault = d.manager_default ? " manager default" : "";
|
|
3927
4032
|
const reason = d.error_reason ? ` ${d.error_reason}` : "";
|
|
3928
|
-
console.log(`${d.domain} ${d.status} ${checked}${reason}`);
|
|
4033
|
+
console.log(`${d.domain} ${d.status} ${checked}${managerDefault}${reason}`);
|
|
3929
4034
|
}
|
|
3930
4035
|
} catch (e) {
|
|
3931
4036
|
fail(e);
|
|
3932
4037
|
}
|
|
3933
4038
|
});
|
|
3934
|
-
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) => {
|
|
3935
4043
|
try {
|
|
3936
|
-
const d = await new ErdoClient().createCustomDomain(domain);
|
|
3937
|
-
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)" : ""}`);
|
|
3938
4046
|
if (d.dns_records.length > 0) {
|
|
3939
4047
|
process.stderr.write("Create these records at the domain's DNS provider:\n");
|
|
3940
4048
|
for (const r of d.dns_records) {
|
|
@@ -3945,6 +4053,20 @@ domainsCmd.command("add <domain>").description("Register a custom domain (a dire
|
|
|
3945
4053
|
fail(e);
|
|
3946
4054
|
}
|
|
3947
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
|
+
});
|
|
3948
4070
|
domainsCmd.command("remove <domain>").description("Remove a custom domain registration (stops it serving pages)").action(async (domain) => {
|
|
3949
4071
|
try {
|
|
3950
4072
|
const res = await new ErdoClient().deleteCustomDomain(domain);
|
|
@@ -4168,6 +4290,98 @@ sentEmailsCmd.command("list").description("List sent email, newest first, includ
|
|
|
4168
4290
|
}
|
|
4169
4291
|
}
|
|
4170
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
|
+
});
|
|
4171
4385
|
sentEmailsCmd.command("get <emailID>").description("Read one sent email, including exact bodies and delivery evidence").action(async (emailID) => {
|
|
4172
4386
|
try {
|
|
4173
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"
|