@erdoai/cli 0.132.0 → 0.134.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 +89 -3
  2. package/package.json +2 -2
package/dist/index.js CHANGED
@@ -1819,6 +1819,16 @@ var ErdoClient = class {
1819
1819
  input
1820
1820
  );
1821
1821
  }
1822
+ listApprovalPolicies(action_key) {
1823
+ const qs = action_key ? `?action_key=${encodeURIComponent(action_key)}` : "";
1824
+ return this.request("GET", `/v1/approval-policies${qs}`);
1825
+ }
1826
+ createApprovalPolicy(input) {
1827
+ return this.request("POST", "/v1/approval-policies", input);
1828
+ }
1829
+ deleteApprovalPolicy(id) {
1830
+ return this.request("DELETE", `/v1/approval-policies/${encodeURIComponent(id)}`);
1831
+ }
1822
1832
  // --- decisions ---
1823
1833
  // The decision record: what the organization committed to, what executed, and
1824
1834
  // what the evidence said afterwards. Referenced by slug, never UUID.
@@ -2329,6 +2339,24 @@ function timedOutMessage(threadID) {
2329
2339
  function print(value) {
2330
2340
  console.log(JSON.stringify(value, null, 2));
2331
2341
  }
2342
+ function policyValue(value) {
2343
+ if (Array.isArray(value)) return value.map(policyValue);
2344
+ if (value && typeof value === "object") {
2345
+ return Object.fromEntries(Object.entries(value).sort(([a], [b]) => a.localeCompare(b)).map(([key, item]) => [key, policyValue(item)]));
2346
+ }
2347
+ return value;
2348
+ }
2349
+ function effectivePolicyMismatch(requested, effective) {
2350
+ const differences = [];
2351
+ if (effective.action_key !== requested.action_key) differences.push("action_key");
2352
+ if (effective.scope_type !== requested.scope_type || (effective.scope_id ?? null) !== (requested.scope_id ?? null)) differences.push("scope");
2353
+ if (effective.decision !== requested.decision) differences.push("decision");
2354
+ if (JSON.stringify(policyValue(effective.parameter_constraints ?? null)) !== JSON.stringify(policyValue(requested.parameter_constraints ?? null))) differences.push("parameter_constraints");
2355
+ const requestedExpiry = requested.expires_at ? Date.parse(requested.expires_at) : null;
2356
+ const effectiveExpiry = effective.expires_at ? Date.parse(effective.expires_at) : null;
2357
+ if (requestedExpiry !== effectiveExpiry) differences.push("expires_at");
2358
+ return differences.length ? differences.join(", ") : void 0;
2359
+ }
2332
2360
  function formatNumber(n) {
2333
2361
  if (!Number.isFinite(n)) return String(n);
2334
2362
  if (Number.isInteger(n)) return n.toLocaleString("en-US");
@@ -4777,6 +4805,61 @@ approvalsCmd.command("list").description("List approval requests, optionally fil
4777
4805
  }
4778
4806
  }
4779
4807
  );
4808
+ var policiesCmd = approvalsCmd.command("policies").description("List and manage standing approval policies");
4809
+ policiesCmd.command("list").description("List standing approval policies").option("--action-key <key>", "only policies for this action").option("--json", "output raw JSON").action(async (opts) => {
4810
+ try {
4811
+ const res = await new ErdoClient().listApprovalPolicies(opts.actionKey);
4812
+ if (opts.json) return print(res);
4813
+ for (const p of res.policies) {
4814
+ console.log(`${p.id} ${p.decision} ${p.action_key} ${p.scope_type}${p.scope_id ? `/${p.scope_id}` : ""}${p.expires_at ? ` expires ${p.expires_at}` : ""}`);
4815
+ }
4816
+ } catch (e) {
4817
+ fail(e);
4818
+ }
4819
+ });
4820
+ policiesCmd.command("create").description("Create a standing approval policy; this does not execute the action").requiredOption("--action-key <key>", "exact action key").requiredOption("--scope-type <type>", "organization | job | workstream | user").option("--scope-id <id>", "job, workstream, or user UUID").requiredOption("--decision <decision>", "always_approve | always_require").option("--constraints <json>", "parameter constraints JSON object").option("--expires-at <timestamp>", "future RFC3339 expiry").option("--json", "output raw JSON").action(async (opts) => {
4821
+ try {
4822
+ if (!["organization", "job", "workstream", "user"].includes(opts.scopeType)) throw new Error("--scope-type must be organization, job, workstream, or user");
4823
+ if (!["always_approve", "always_require"].includes(opts.decision)) throw new Error("--decision must be always_approve or always_require");
4824
+ let constraints;
4825
+ if (opts.constraints !== void 0) {
4826
+ try {
4827
+ constraints = JSON.parse(opts.constraints);
4828
+ } catch {
4829
+ throw new Error("--constraints must be a JSON object");
4830
+ }
4831
+ if (constraints === null || Array.isArray(constraints) || typeof constraints !== "object") throw new Error("--constraints must be a JSON object");
4832
+ }
4833
+ const requested = {
4834
+ action_key: opts.actionKey,
4835
+ scope_type: opts.scopeType,
4836
+ ...opts.scopeId ? { scope_id: opts.scopeId } : {},
4837
+ decision: opts.decision,
4838
+ ...constraints !== void 0 ? { parameter_constraints: constraints } : {},
4839
+ ...opts.expiresAt ? { expires_at: opts.expiresAt } : {}
4840
+ };
4841
+ const res = await new ErdoClient().createApprovalPolicy(requested);
4842
+ const mismatch = effectivePolicyMismatch(requested, res.policy);
4843
+ if (opts.json) print(res);
4844
+ if (mismatch) {
4845
+ console.error(`Effective approval policy ${res.policy.id} does not grant the requested authority (${mismatch}). The existing policy was kept; inspect it with: erdo approvals policies list --action-key ${opts.actionKey}`);
4846
+ process.exitCode = 1;
4847
+ } else if (!opts.json) {
4848
+ console.log(`Effective ${res.policy.decision} policy ${res.policy.id}`);
4849
+ }
4850
+ } catch (e) {
4851
+ fail(e);
4852
+ }
4853
+ });
4854
+ policiesCmd.command("delete <id>").description("Delete a standing approval policy").action(async (id) => {
4855
+ try {
4856
+ const result = await new ErdoClient().deleteApprovalPolicy(id);
4857
+ if (!result.success) throw new Error(`Policy ${id} was not deleted`);
4858
+ console.log(`Deleted policy ${id}`);
4859
+ } catch (e) {
4860
+ fail(e);
4861
+ }
4862
+ });
4780
4863
  function executionVerdict(execution) {
4781
4864
  switch (execution.state) {
4782
4865
  case "succeeded":
@@ -8697,7 +8780,7 @@ var autoCmd = program.command("automations").description("Scheduled automations
8697
8780
  autoCmd.command("list").description("List automations").action(async () => {
8698
8781
  try {
8699
8782
  const { heartbeats } = await new ErdoClient().listHeartbeats();
8700
- for (const h of heartbeats) console.log(`${h.id} ${h.state} ${h.name}`);
8783
+ for (const h of heartbeats) console.log(`${h.id} ${h.state} ${h.name} ${h.kind === "script" ? "script" : h.thread_id ? `thread:${h.thread_id}` : h.conversation ?? "new"}${h.persona_id ? ` persona:${h.persona_id}` : ""}`);
8701
8784
  } catch (e) {
8702
8785
  fail(e);
8703
8786
  }
@@ -8723,7 +8806,7 @@ autoCmd.command("run <id>").description("Trigger an automation now").action(asyn
8723
8806
  autoCmd.command("update <id>").description("Edit an automation \u2014 rename, reschedule, or replace its script/instructions").option("--name <name>", "New name").option("--description <text>", "New description").option("--instructions <text>", "Replacement instructions (agent automation)").addOption(new Option2("--agent-key <key>", "Executor for an agent automation; omit to keep current").choices(["erdo.heartbeat-executor", "erdo.workflow-executor"])).option("--script-js <js>", "Replacement script body (scripted automation)").option(
8724
8807
  "--script-file <path>",
8725
8808
  "Read the replacement script body from a file (scripted automation)"
8726
- ).option("--interval <minutes>", "New interval in minutes (minimum 5)", (v) => parseInt(v, 10)).option("--timezone <tz>", "New scheduling timezone, e.g. America/New_York").action(
8809
+ ).addOption(new Option2("--conversation <target>", "Target 'new' (fresh thread) or 'direct' conversation").choices(["new", "direct"])).option("--thread-id <id>", "Post each run into an existing conversation UUID").option("--persona-id <id>", "Org-enabled persona UUID to speak as").option("--interval <minutes>", "New interval in minutes (minimum 5)", (v) => parseInt(v, 10)).option("--timezone <tz>", "New scheduling timezone, e.g. America/New_York").action(
8727
8810
  async (id, opts) => {
8728
8811
  try {
8729
8812
  if (opts.scriptJs && opts.scriptFile) {
@@ -8738,7 +8821,10 @@ autoCmd.command("update <id>").description("Edit an automation \u2014 rename, re
8738
8821
  agent_key: opts.agentKey,
8739
8822
  script_js: scriptJs,
8740
8823
  interval_minutes: opts.interval,
8741
- timezone: opts.timezone
8824
+ timezone: opts.timezone,
8825
+ conversation: opts.conversation,
8826
+ thread_id: opts.threadId,
8827
+ persona_id: opts.personaId
8742
8828
  };
8743
8829
  if (Object.values(body).every((v) => v === void 0)) {
8744
8830
  fail(new Error("nothing to update \u2014 pass at least one field to change"));
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@erdoai/cli",
3
- "version": "0.132.0",
4
- "description": "Erdo CLI — drive datasets, pages, and evals from the terminal or CI",
3
+ "version": "0.134.0",
4
+ "description": "Erdo CLI \u2014 drive datasets, pages, and evals from the terminal or CI",
5
5
  "type": "module",
6
6
  "bin": {
7
7
  "erdo": "dist/index.js"