@erdoai/cli 0.64.1 → 0.66.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.
Files changed (2) hide show
  1. package/dist/index.js +169 -0
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -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");
@@ -366,6 +396,22 @@ var ErdoClient = class {
366
396
  `/v1/received-emails${qs ? `?${qs}` : ""}`
367
397
  );
368
398
  }
399
+ // The audited outbound side of email. Listing omits bodies; get reads the
400
+ // exact stored message. The scalar filters use the GET mirror of the same
401
+ // service that backs MCP's erdo_list_emails.
402
+ listSentEmails(params) {
403
+ const q = new URLSearchParams();
404
+ if (params?.to) q.set("to", params.to);
405
+ if (params?.since) q.set("since", params.since);
406
+ if (params?.search) q.set("search", params.search);
407
+ if (params?.limit) q.set("limit", String(params.limit));
408
+ if (params?.offset) q.set("offset", String(params.offset));
409
+ const qs = q.toString();
410
+ return this.request("GET", `/v1/emails${qs ? `?${qs}` : ""}`);
411
+ }
412
+ getSentEmail(emailID) {
413
+ return this.request("GET", `/v1/emails/${encodeURIComponent(emailID)}`);
414
+ }
369
415
  // Run a read-only HogQL query against the org's page-analytics events. Rows are
370
416
  // positional per columns; enabled:false means page analytics is off for the org
371
417
  // (not zero traffic). A rejected query surfaces PostHog's message as the error.
@@ -484,6 +530,14 @@ var ErdoClient = class {
484
530
  `/v1/pages/restore/${encodeURIComponent(id)}`
485
531
  );
486
532
  }
533
+ // Copy a page: the deploy endpoint's clone mode. clone_from_page_id makes
534
+ // POST /v1/pages copy an existing page server-side instead of taking content.
535
+ clonePage(id, input) {
536
+ return this.request("POST", "/v1/pages", {
537
+ clone_from_page_id: id,
538
+ title: input?.title
539
+ });
540
+ }
487
541
  // --- workstreams ---
488
542
  listWorkstreams(status) {
489
543
  const q = new URLSearchParams();
@@ -1751,6 +1805,57 @@ id: ${o.id}`);
1751
1805
  fail(e);
1752
1806
  }
1753
1807
  });
1808
+ managed.command("adopt <token>").description("Take over an existing org by redeeming a one-time consent token").action(async (token) => {
1809
+ try {
1810
+ const o = await new ErdoClient().adoptManagedOrganization(token);
1811
+ console.log(`Adopted managed org: ${o.name}`);
1812
+ console.log(`slug: ${o.slug}
1813
+ id: ${o.id}`);
1814
+ process.stderr.write(
1815
+ `Operate it with the manager key by passing --org ${o.slug} on any command (e.g. erdo --org ${o.slug} datasets list).
1816
+ `
1817
+ );
1818
+ } catch (e) {
1819
+ fail(e);
1820
+ }
1821
+ });
1822
+ 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) => {
1823
+ try {
1824
+ if (opts.role && opts.role !== "member" && opts.role !== "admin") {
1825
+ fail(new Error("--role must be member or admin (ownership of a managed org stays with the client)"));
1826
+ }
1827
+ const m = await new ErdoClient().addManagedOrganizationMember(orgSlug, {
1828
+ email,
1829
+ role: opts.role
1830
+ });
1831
+ if (m.invited) {
1832
+ console.log(`Invited ${m.email} to ${m.org_slug} as ${m.role} \u2014 pending until they accept`);
1833
+ } else {
1834
+ console.log(`Added ${m.email} to ${m.org_slug} as ${m.role}`);
1835
+ }
1836
+ } catch (e) {
1837
+ fail(e);
1838
+ }
1839
+ });
1840
+ 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) => {
1841
+ try {
1842
+ const res = await new ErdoClient().createManagedOrganizationHandoffToken(orgSlug, opts.successor);
1843
+ process.stderr.write(
1844
+ "Give this handoff token to the successor manager now \u2014 it is shown only once and cannot be retrieved again.\n"
1845
+ );
1846
+ console.log(res.token);
1847
+ process.stderr.write(
1848
+ `
1849
+ org: ${res.org_slug}
1850
+ successor: ${res.successor_manager_slug}
1851
+ expires: ${res.expires_at}
1852
+ Only ${res.successor_manager_slug} can redeem it, with: erdo org managed adopt <token>, and only until it expires at the time above.
1853
+ `
1854
+ );
1855
+ } catch (e) {
1856
+ fail(e);
1857
+ }
1858
+ });
1754
1859
  managed.command("revoke <slug>").description("Stop managing a client org (removes access; keeps an audit record)").action(async (slug) => {
1755
1860
  try {
1756
1861
  await new ErdoClient().revokeManagedOrganization(slug);
@@ -3726,6 +3831,24 @@ pagesCmd.command("restore <id>").description("Restore a previously deleted page
3726
3831
  fail(e);
3727
3832
  }
3728
3833
  });
3834
+ pagesCmd.command("clone <id>").description(
3835
+ "Copy a page. The copy is byte-identical and starts private (publish state is never inherited); the source's lead-form pipelines are duplicated onto it and the ids in its content rewritten, so it captures its own leads"
3836
+ ).option("--title <title>", 'title for the copy (default: "Copy of <source title>")').option("--json", "print the full JSON, including the cloned pipelines and id rewrites").action(async (id, opts) => {
3837
+ try {
3838
+ const res = await new ErdoClient().clonePage(id, { title: opts.title });
3839
+ if (opts.json) {
3840
+ print(res);
3841
+ return;
3842
+ }
3843
+ console.log(`${res.id} ${res.url}`);
3844
+ for (const p of res.cloned_pipelines || []) {
3845
+ console.error(`cloned pipeline: ${p.source_id} -> ${p.id}${p.slug ? ` (${p.slug})` : ""}`);
3846
+ }
3847
+ for (const note of res.notes || []) console.error(`note: ${note}`);
3848
+ } catch (e) {
3849
+ fail(e);
3850
+ }
3851
+ });
3729
3852
  pagesCmd.command("get <id>").description("Show an artifact").action(async (id) => {
3730
3853
  try {
3731
3854
  print(await new ErdoClient().getArtifact(id));
@@ -3998,6 +4121,52 @@ emailCmd.command("received").description(
3998
4121
  fail(e);
3999
4122
  }
4000
4123
  });
4124
+ var sentEmailsCmd = emailCmd.command("sent").description("Read the audited outbound email log and provider delivery evidence");
4125
+ sentEmailsCmd.command("list").description("List sent email, newest first, including immutable delivered_at evidence").option("--to <email>", "only mail sent to this address").option("--since <timestamp>", "only mail created at or after this RFC 3339 timestamp").option("--search <text>", "case-insensitive subject substring").option("--limit <n>", "page size (default 25, max 200)").option("--offset <n>", "rows to skip").option("--json", "print the raw JSON result instead of a table").action(
4126
+ async (opts) => {
4127
+ try {
4128
+ const res = await new ErdoClient().listSentEmails({
4129
+ to: opts.to,
4130
+ since: opts.since,
4131
+ search: opts.search,
4132
+ limit: opts.limit ? Number(opts.limit) : void 0,
4133
+ offset: opts.offset ? Number(opts.offset) : void 0
4134
+ });
4135
+ if (opts.json) {
4136
+ print(res);
4137
+ return;
4138
+ }
4139
+ const emails = res.emails ?? [];
4140
+ if (emails.length === 0) {
4141
+ console.log("No sent email matches those filters.");
4142
+ return;
4143
+ }
4144
+ printAlignedTable(
4145
+ ["id", "to", "subject", "status", "delivered at", "latest event", "sent at"],
4146
+ emails.map((email) => [
4147
+ email.id,
4148
+ email.to,
4149
+ email.subject,
4150
+ email.status_reason ? `${email.status}: ${email.status_reason}` : email.status,
4151
+ email.delivered_at ?? "",
4152
+ email.last_delivery_event_at ?? "",
4153
+ email.sent_at ?? ""
4154
+ ])
4155
+ );
4156
+ process.stderr.write(`showing ${emails.length} of ${res.total} matching message(s)
4157
+ `);
4158
+ } catch (e) {
4159
+ fail(e);
4160
+ }
4161
+ }
4162
+ );
4163
+ sentEmailsCmd.command("get <emailID>").description("Read one sent email, including exact bodies and delivery evidence").action(async (emailID) => {
4164
+ try {
4165
+ print(await new ErdoClient().getSentEmail(emailID));
4166
+ } catch (e) {
4167
+ fail(e);
4168
+ }
4169
+ });
4001
4170
  var datasetsCmd = program.command("datasets").description("Datasets");
4002
4171
  datasetsCmd.command("list").description("List datasets").option(
4003
4172
  "--class <class>",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@erdoai/cli",
3
- "version": "0.64.1",
3
+ "version": "0.66.0",
4
4
  "description": "Erdo CLI — drive datasets, pages, and evals from the terminal or CI",
5
5
  "type": "module",
6
6
  "bin": {