conduyt 1.44.0 → 1.45.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 +103 -0
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -960,6 +960,109 @@ fromk
960
960
  throw new Error("Pass --secret or set FROMK_WEBHOOK_SECRET (or --clear).");
961
961
  return client.put("/api/v1/settings/fromk/webhook", { secret });
962
962
  }));
963
+ // ---- Zoom (Conduyt #72, 2026-09-15): one Zoom meeting per booking ----
964
+ const zoom = program.command("zoom").description("Zoom: the account's Server-to-Server OAuth app; booking pages set to Zoom create one meeting per booking, delete it on cancel, move it on reschedule (settings scope)");
965
+ zoom
966
+ .command("status")
967
+ .description("Connection status (GET /settings/zoom): connected | not_configured, the account id and client id masked; the client secret is never returned")
968
+ .action(run(async (client) => client.get("/api/v1/settings/zoom")));
969
+ zoom
970
+ .command("connect")
971
+ .description("Connect or replace the Server-to-Server app (scopes meeting:write/read/update/delete:meeting:admin + user:read:user:admin) — verified with Zoom before it is stored; answers the app's own user = the default meeting host")
972
+ .option("--account-id <id>", "Zoom account id; omit to read ZOOM_ACCOUNT_ID from the environment")
973
+ .option("--client-id <id>", "Zoom client id; omit to read ZOOM_CLIENT_ID")
974
+ .option("--client-secret <secret>", "Zoom client secret; omit to read ZOOM_CLIENT_SECRET and keep it out of shell history")
975
+ .option("--force", "replace the app although upcoming bookings still hold meetings the current app created (409 zoom_meetings_active otherwise; those meetings stay on the old Zoom account)")
976
+ .action(run(async (client, opts) => {
977
+ const accountId = (opts.accountId ?? process.env.ZOOM_ACCOUNT_ID ?? "").trim();
978
+ const clientId = (opts.clientId ?? process.env.ZOOM_CLIENT_ID ?? "").trim();
979
+ const clientSecret = (opts.clientSecret ?? process.env.ZOOM_CLIENT_SECRET ?? "").trim();
980
+ if (!accountId || !clientId || !clientSecret)
981
+ throw new Error("Pass --account-id, --client-id and --client-secret (or set ZOOM_ACCOUNT_ID / ZOOM_CLIENT_ID / ZOOM_CLIENT_SECRET).");
982
+ return client.put("/api/v1/settings/zoom", { accountId, clientId, clientSecret, ...(opts.force ? { force: true } : {}) });
983
+ }));
984
+ zoom
985
+ .command("disconnect")
986
+ .description("Forget the app; booking pages set to Zoom fall back to their written location. Refused (409 zoom_meetings_active) while upcoming bookings hold meetings this app created unless --force")
987
+ .option("--force", "disconnect anyway; those meetings stay on the Zoom account and cancellations record zoom_orphaned")
988
+ .action(run(async (client, opts) => client.del(`/api/v1/settings/zoom${opts.force ? "?force=1" : ""}`)));
989
+ zoom
990
+ .command("test")
991
+ .description("Prove the stored app works (a token is issued, the app's user answers); --host also proves that user belongs to the Zoom account (the value a booking page's --video-host must be). Read-only on Zoom")
992
+ .option("--host <email>", "a Zoom user of the connected account to verify as a meeting host")
993
+ .action(run(async (client, opts) => client.post("/api/v1/settings/zoom/test", opts.host ? { hostEmail: opts.host } : {})));
994
+ // ---- Booking pages (Conduyt #72, 2026-09-15): the public scheduling pages and their video option ----
995
+ const bookingPages = program.command("booking-pages").description("Booking pages: the public scheduling pages (slug, host, duration, availability, embed origins) and their video option (calendar scope)");
996
+ bookingPages
997
+ .command("list")
998
+ .description("List the account's booking pages (GET /booking-pages)")
999
+ .option("--active", "only active pages")
1000
+ .action(run(async (client, opts) => client.get(`/api/v1/booking-pages${opts.active ? "?is_active=true" : ""}`)));
1001
+ bookingPages
1002
+ .command("get <id>")
1003
+ .description("One booking page with every setting (GET /booking-pages/:id)")
1004
+ .action(run(async (client, id) => client.get(`/api/v1/booking-pages/${id}`)));
1005
+ const bookingPageBody = (opts) => {
1006
+ const body = {};
1007
+ const set = (key, value) => { if (value !== undefined)
1008
+ body[key] = value; };
1009
+ set("title", opts.title);
1010
+ set("slug", opts.slug);
1011
+ set("description", opts.description);
1012
+ set("userId", opts.host);
1013
+ set("location", opts.location);
1014
+ if (opts.videoProvider !== undefined)
1015
+ set("videoProvider", opts.videoProvider === "none" ? null : opts.videoProvider);
1016
+ if (opts.videoHost !== undefined)
1017
+ set("videoHostEmail", opts.videoHost === "none" ? null : opts.videoHost);
1018
+ if (opts.duration !== undefined)
1019
+ set("durationMinutes", Number(opts.duration));
1020
+ if (opts.minimumNotice !== undefined)
1021
+ set("minimumNotice", Number(opts.minimumNotice));
1022
+ if (opts.maxAdvanceDays !== undefined)
1023
+ set("maxAdvanceDays", Number(opts.maxAdvanceDays));
1024
+ set("leadSource", opts.leadSource);
1025
+ set("brandName", opts.brandName);
1026
+ if (typeof opts.embedOrigins === "string")
1027
+ set("embedAllowedOrigins", opts.embedOrigins.split(",").map((o) => o.trim()).filter(Boolean));
1028
+ if (typeof opts.availability === "string")
1029
+ set("availabilityRules", JSON.parse(opts.availability));
1030
+ if (opts.active !== undefined)
1031
+ set("isActive", opts.active);
1032
+ return body;
1033
+ };
1034
+ const bookingPageOptions = (cmd) => cmd
1035
+ .option("--title <title>", "page title")
1036
+ .option("--slug <slug>", "public slug (conduyt.app/book/<slug>)")
1037
+ .option("--description <text>", "description shown on the page")
1038
+ .option("--host <userId>", "host user UUID (the page owner)")
1039
+ .option("--duration <minutes>", "slot length in minutes")
1040
+ .option("--minimum-notice <minutes>", "earliest booking, minutes from now")
1041
+ .option("--max-advance-days <days>", "how far ahead visitors may book")
1042
+ .option("--location <text>", "written location (Zoom link, phone, address); with --video-provider zoom it is the fallback when Zoom is down")
1043
+ .option("--video-provider <zoom|none>", "zoom = a new Zoom meeting for every booking (needs `conduyt zoom connect`); none = the written location only")
1044
+ .option("--video-host <email|none>", "the Zoom user the meeting is created under (a user of the connected Zoom account); none = the app's own user")
1045
+ .option("--lead-source <source>", "lead source stamped on contacts the page creates")
1046
+ .option("--brand-name <name>", "white-label name shown on the page and in Zoom meeting names")
1047
+ .option("--embed-origins <origins>", "comma-separated origins allowed to embed/call the page (empty = any)")
1048
+ .option("--availability <json>", "AvailabilityWindow[] JSON override, e.g. '[{\"dayOfWeek\":1,\"startTime\":\"09:00\",\"endTime\":\"11:00\"}]'")
1049
+ .option("--active", "page accepts bookings")
1050
+ .option("--no-active", "page stops accepting bookings");
1051
+ bookingPageOptions(bookingPages.command("create").description("Create a booking page (POST /booking-pages): --title, --slug, --host and --duration at least"))
1052
+ .action(run(async (client, opts) => {
1053
+ const body = bookingPageBody(opts);
1054
+ for (const k of ["title", "slug", "userId", "durationMinutes"])
1055
+ if (body[k] === undefined)
1056
+ throw new Error(`--${k === "userId" ? "host" : k === "durationMinutes" ? "duration" : k} is required`);
1057
+ return client.post("/api/v1/booking-pages", body);
1058
+ }));
1059
+ bookingPageOptions(bookingPages.command("update <id>").description("Update a booking page (PATCH /booking-pages/:id): only the options given change"))
1060
+ .action(run(async (client, id, opts) => {
1061
+ const body = bookingPageBody(opts);
1062
+ if (Object.keys(body).length === 0)
1063
+ throw new Error("Nothing to update: pass at least one option.");
1064
+ return client.patch(`/api/v1/booking-pages/${id}`, body);
1065
+ }));
963
1066
  // ---- WhatsApp via Twilio (2026-09-11) ----
964
1067
  const whatsapp = program.command("whatsapp").description("WhatsApp via your own Twilio account: a Meta-approved sender, approved templates for outreach, free-form replies within a contact's 24-hour window");
965
1068
  whatsapp
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "conduyt",
3
- "version": "1.44.0",
3
+ "version": "1.45.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",