conduyt 1.15.0 → 1.17.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 +63 -1
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { readFileSync } from "node:fs";
2
+ import { readFileSync, writeFileSync } from "node:fs";
3
3
  import { fileURLToPath } from "node:url";
4
4
  import { dirname, join } from "node:path";
5
5
  import { Command } from "commander";
@@ -1639,6 +1639,68 @@ ai
1639
1639
  .option("--user <id>", "user UUID (admins/owners only; defaults to you)")
1640
1640
  .option("--no-narrative", "skip the AI-composed brief (returns the deterministic one; spends no AI budget)")
1641
1641
  .action(run(async (client, opts) => client.get(`/api/v1/ai/agenda${buildQuery({ userId: opts.user, narrative: opts.narrative === false ? "0" : undefined })}`)));
1642
+ ai
1643
+ .command("act <message>")
1644
+ .description("Copilot actions: plan what the copilot would do for a plain-language request (tasks, callbacks, notes, appointments, tags, deal moves, SMS/email drafts). Prints the resolved preview + confirmation token; --apply executes the ready ones through the same routes the UI uses. Appointments are never created (customer-facing) — the plan carries a hand-off link. Save the plan with --plan-out to apply, replay or resume it later with `ai act-apply`.")
1645
+ .option("--contact <id>", "contact UUID the request refers to ('this contact')")
1646
+ .option("--deal <id>", "deal UUID the request refers to ('this deal')")
1647
+ .option("--apply", "execute the ready actions immediately (drafts are never sent)")
1648
+ .option("--select <ids>", "with --apply: comma-separated action ids to apply (default: every ready action)")
1649
+ .option("--plan-out <file>", "write the plan (actions + confirmationToken) to a JSON file for `ai act-apply`")
1650
+ .action(run(async (client, message, opts) => {
1651
+ if (message.trim() === "")
1652
+ throw new Error("message must not be empty.");
1653
+ const section = opts.contact ? { type: "contact", id: opts.contact } : opts.deal ? { type: "deal", id: opts.deal } : undefined;
1654
+ const plan = (await client.post("/api/v1/ai/actions", { message, ...(section ? { section } : {}) }));
1655
+ const data = (plan.data ?? plan);
1656
+ if (opts.planOut)
1657
+ writeFileSync(opts.planOut, JSON.stringify({ actions: data.actions ?? [], confirmationToken: data.confirmationToken ?? null }, null, 2) + "\n");
1658
+ if (!opts.apply || !data.confirmationToken || !Array.isArray(data.actions) || !data.readyCount)
1659
+ return plan;
1660
+ const selectedIds = opts.select ? opts.select.split(",").map((v) => v.trim()).filter(Boolean) : undefined;
1661
+ const applied = await client.post("/api/v1/ai/actions/execute", { actions: data.actions, confirmationToken: data.confirmationToken, ...(selectedIds ? { selectedIds } : {}) });
1662
+ return { plan: data, applied };
1663
+ }));
1664
+ ai
1665
+ .command("act-apply <planFile>")
1666
+ .description("Apply a saved plan (from `ai act --plan-out`). Sending the same plan again replays the run — nothing runs twice. Results with inFlight:true were started but never confirmed (e.g. the connection dropped mid-apply): check the record, then re-run them explicitly with --resume.")
1667
+ .option("--select <ids>", "first apply only: comma-separated action ids to apply (default: every ready action); a replay must resend the same selection")
1668
+ .option("--resume <ids>", "comma-separated ids of inFlight actions to run again — only after checking the record and after the result's resumableAt (the earlier attempt can no longer be running); later actions wait behind an in-flight one")
1669
+ .action(run(async (client, planFile, opts) => {
1670
+ const raw = JSON.parse(readFileSync(planFile, "utf8"));
1671
+ if (!Array.isArray(raw.actions) || raw.actions.length === 0)
1672
+ throw new Error(`${planFile} has no actions.`);
1673
+ if (!raw.confirmationToken)
1674
+ throw new Error(`${planFile} has no confirmationToken (the plan had nothing ready to apply).`);
1675
+ const list = (v) => (v ? v.split(",").map((x) => x.trim()).filter(Boolean) : undefined);
1676
+ const selectedIds = list(opts.select);
1677
+ const resumeIds = list(opts.resume);
1678
+ return client.post("/api/v1/ai/actions/execute", { actions: raw.actions, confirmationToken: raw.confirmationToken, ...(selectedIds ? { selectedIds } : {}), ...(resumeIds ? { resumeIds } : {}) });
1679
+ }));
1680
+ ai
1681
+ .command("suggest-reply <contactId>")
1682
+ .description("Copilot Tier 2: the suggested reply for the contact's LATEST inbound text or email — what they said, what they want, sentiment, and a draft. Advice only; nothing is sent.")
1683
+ .action(run(async (client, contactId) => {
1684
+ assertUuid(contactId, "contact id");
1685
+ return client.get(`/api/v1/conversations/${contactId}/suggestion`);
1686
+ }));
1687
+ ai
1688
+ .command("reply-decision <contactId>")
1689
+ .description("Record what was done with a suggested reply: accepted, sent (with --sent <outbound message id>), or dismissed")
1690
+ .requiredOption("--message <id>", "the inbound message the suggestion answers (suggestion.messageId)")
1691
+ .requiredOption("--decision <decision>", "accepted | sent | dismissed")
1692
+ .option("--sent <id>", "required with --decision sent: the outbound message id that was sent (must be provider-confirmed)")
1693
+ .action(run(async (client, contactId, opts) => {
1694
+ assertUuid(contactId, "contact id");
1695
+ assertUuid(opts.message, "message id");
1696
+ if (!["accepted", "sent", "dismissed"].includes(opts.decision))
1697
+ throw new Error("--decision must be accepted, sent or dismissed.");
1698
+ if (opts.decision === "sent" && !opts.sent)
1699
+ throw new Error("--decision sent requires --sent <outbound message id> (the confirmed message that was sent)");
1700
+ if (opts.sent)
1701
+ assertUuid(opts.sent, "sent message id");
1702
+ return client.post(`/api/v1/conversations/${contactId}/suggestion`, { messageId: opts.message, decision: opts.decision, ...(opts.sent ? { sentMessageId: opts.sent } : {}) });
1703
+ }));
1642
1704
  ai
1643
1705
  .command("daily-brief")
1644
1706
  .description("Your AI daily brief (task-focused summary)")
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "conduyt",
3
- "version": "1.15.0",
3
+ "version": "1.17.0",
4
4
  "description": "Command-line interface for Conduyt CRM — manage contacts, deals, pipelines, and run insight queries from your terminal.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",