conduyt 1.55.0 → 1.57.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 +100 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1096,10 +1096,22 @@ const bookingPageBody = (opts) => {
|
|
|
1096
1096
|
set("brandName", opts.brandName);
|
|
1097
1097
|
if (typeof opts.embedOrigins === "string")
|
|
1098
1098
|
set("embedAllowedOrigins", opts.embedOrigins.split(",").map((o) => o.trim()).filter(Boolean));
|
|
1099
|
+
if (opts.documents !== undefined)
|
|
1100
|
+
set("documentsEnabled", opts.documents === "on" || opts.documents === true);
|
|
1101
|
+
if (typeof opts.documentsPageUrl === "string")
|
|
1102
|
+
set("documentsPageUrl", opts.documentsPageUrl === "none" ? null : opts.documentsPageUrl);
|
|
1099
1103
|
if (typeof opts.availability === "string")
|
|
1100
1104
|
set("availabilityRules", JSON.parse(opts.availability));
|
|
1101
1105
|
if (opts.active !== undefined)
|
|
1102
1106
|
set("isActive", opts.active);
|
|
1107
|
+
// (Codex rb-r9 m3) every other field the route accepts (the OpenAPI BookingPagePatch schema) through --data; the typed
|
|
1108
|
+
// options above win over the same key in it
|
|
1109
|
+
if (typeof opts.data === "string") {
|
|
1110
|
+
const extra = JSON.parse(opts.data);
|
|
1111
|
+
if (!extra || typeof extra !== "object" || Array.isArray(extra))
|
|
1112
|
+
throw new Error("--data must be a JSON object");
|
|
1113
|
+
return { ...extra, ...body };
|
|
1114
|
+
}
|
|
1103
1115
|
return body;
|
|
1104
1116
|
};
|
|
1105
1117
|
const bookingPageOptions = (cmd) => cmd
|
|
@@ -1107,6 +1119,8 @@ const bookingPageOptions = (cmd) => cmd
|
|
|
1107
1119
|
.option("--slug <slug>", "public slug (conduyt.app/book/<slug>)")
|
|
1108
1120
|
.option("--description <text>", "description shown on the page")
|
|
1109
1121
|
.option("--host <userId>", "host user UUID (the page owner)")
|
|
1122
|
+
.option("--documents <on|off>", "the documents step after the booking (bank statements and funding agreements attached to the lead)")
|
|
1123
|
+
.option("--documents-page-url <url>", "the site page the confirmation email links back to for the upload (https; reads ?appointment=<id>); 'none' clears it")
|
|
1110
1124
|
.option("--duration <minutes>", "slot length in minutes")
|
|
1111
1125
|
.option("--minimum-notice <minutes>", "earliest booking, minutes from now")
|
|
1112
1126
|
.option("--max-advance-days <days>", "how far ahead visitors may book")
|
|
@@ -1118,7 +1132,8 @@ const bookingPageOptions = (cmd) => cmd
|
|
|
1118
1132
|
.option("--embed-origins <origins>", "comma-separated origins allowed to embed/call the page (empty = any)")
|
|
1119
1133
|
.option("--availability <json>", "AvailabilityWindow[] JSON override, e.g. '[{\"dayOfWeek\":1,\"startTime\":\"09:00\",\"endTime\":\"11:00\"}]'")
|
|
1120
1134
|
.option("--active", "page accepts bookings")
|
|
1121
|
-
.option("--no-active", "page stops accepting bookings")
|
|
1135
|
+
.option("--no-active", "page stops accepting bookings")
|
|
1136
|
+
.option("--data <json>", "any other page field as a JSON object (the OpenAPI BookingPagePatch schema: eventType, hostUserIds, reminders, formFields, bufferBefore, color, redirectUrl, gdprEnabled, waitlistEnabled, template, …)");
|
|
1122
1137
|
bookingPageOptions(bookingPages.command("create").description("Create a booking page (POST /booking-pages): --title, --slug, --host and --duration at least"))
|
|
1123
1138
|
.action(run(async (client, opts) => {
|
|
1124
1139
|
const body = bookingPageBody(opts);
|
|
@@ -2398,6 +2413,90 @@ function onOffArg(raw, flag) {
|
|
|
2398
2413
|
return false;
|
|
2399
2414
|
throw new Error(`${flag} takes on or off (got '${raw}').`);
|
|
2400
2415
|
}
|
|
2416
|
+
/** the two lines a workspace records calls on: its Twilio lines (settings.recording) and its iPhone line (settings.projectBlueRecording) */
|
|
2417
|
+
const RECORDING_KEY = { twilio: "recording", iphone: "projectBlueRecording" };
|
|
2418
|
+
const isDisclosureOn = (v) => v === "inbound" || v === "outbound" || v === "both";
|
|
2419
|
+
/** what a stored policy MEANS, read the way the server reads it: the old shape { enabled, consentGreeting } is outbound
|
|
2420
|
+
* recording with the disclosure on whenever a wording is set; enabled stays beside outbound as the old name of the switch */
|
|
2421
|
+
export function effectiveRecordingPolicy(raw) {
|
|
2422
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw))
|
|
2423
|
+
return null;
|
|
2424
|
+
const r = raw;
|
|
2425
|
+
const greeting = typeof r.consentGreeting === "string" ? r.consentGreeting.trim() : "";
|
|
2426
|
+
const newShape = typeof r.inbound === "boolean" || typeof r.outbound === "boolean" || (r.disclosure !== undefined && r.disclosure !== null);
|
|
2427
|
+
if (newShape) {
|
|
2428
|
+
const d = (r.disclosure && typeof r.disclosure === "object" ? r.disclosure : {});
|
|
2429
|
+
const outbound = typeof r.outbound === "boolean" ? r.outbound : r.enabled === true;
|
|
2430
|
+
return { inbound: r.inbound === true, outbound, disclosure: { enabled: d.enabled === true, on: isDisclosureOn(d.on) ? d.on : "outbound" }, consentGreeting: greeting, enabled: outbound };
|
|
2431
|
+
}
|
|
2432
|
+
const outbound = r.enabled === true;
|
|
2433
|
+
return { inbound: false, outbound, disclosure: { enabled: outbound && greeting.length > 0, on: "outbound" }, consentGreeting: greeting, enabled: outbound };
|
|
2434
|
+
}
|
|
2435
|
+
/** the iPhone line has no inbound calls: what its policy does there, the way the server applies it */
|
|
2436
|
+
export function iphoneRecordingDomain(policy) {
|
|
2437
|
+
return { ...policy, inbound: false, disclosure: policy.disclosure.on === "inbound" ? { enabled: false, on: "outbound" } : { enabled: policy.disclosure.enabled, on: "outbound" } };
|
|
2438
|
+
}
|
|
2439
|
+
dialer
|
|
2440
|
+
.command("recording")
|
|
2441
|
+
.description("Show the workspace's call recording policy for both kinds of line (GET /settings). twilio = its Twilio lines: always an object, with everything off when the workspace never set it. Needs a key whose creator may view settings; without it the command fails instead of showing a policy. iphone = the iPhone line's OWN policy (outbound only); null = none was written, and the iPhone line then follows the Twilio lines' policy without its inbound part. Each object is the policy as it is APPLIED: inbound, outbound, disclosure { enabled, on: inbound | outbound | both }, consentGreeting (what is said; empty = the default sentence) and enabled (the OLD name of the outbound switch: the same value as outbound). A workspace that never saved the new shape is read the way the server reads its old one: outbound recording with the disclosure on whenever a wording is set")
|
|
2442
|
+
.action(run(async (client) => {
|
|
2443
|
+
const result = (await client.get("/api/v1/settings"));
|
|
2444
|
+
const raw = result?.data ?? {};
|
|
2445
|
+
const twilio = effectiveRecordingPolicy(raw[RECORDING_KEY.twilio]);
|
|
2446
|
+
// a key whose creator may not view settings gets a short answer WITHOUT the policy: that is "not visible",
|
|
2447
|
+
// never "not configured" (the full answer always carries the Twilio policy)
|
|
2448
|
+
if (!twilio)
|
|
2449
|
+
throw new Error("The call recording policy is not visible to this API key: its creator needs permission to view settings. Nothing can be said about whether calls are recorded.");
|
|
2450
|
+
const iphone = effectiveRecordingPolicy(raw[RECORDING_KEY.iphone]);
|
|
2451
|
+
return { data: { twilio, iphone: iphone ? iphoneRecordingDomain(iphone) : null } };
|
|
2452
|
+
}));
|
|
2453
|
+
dialer
|
|
2454
|
+
.command("set-recording")
|
|
2455
|
+
.description("Change the workspace's call recording policy (admin: settings scope). Send only what changes; the rest stays. The workspace is responsible for consent where it operates. Example: conduyt dialer set-recording --inbound on --outbound on --disclosure on --disclosure-on inbound")
|
|
2456
|
+
.option("--line <line>", "which line's policy: twilio (default) or iphone")
|
|
2457
|
+
.option("--inbound <on|off>", "record calls the team receives (Twilio lines only)")
|
|
2458
|
+
.option("--outbound <on|off>", "record calls the team makes, including Smart Dialing")
|
|
2459
|
+
.option("--enabled <on|off>", "the OLD name of --outbound, accepted for older callers and sent as --outbound (when both are sent, --outbound decides); nothing else changes with it")
|
|
2460
|
+
.option("--disclosure <on|off>", "play a recording disclosure (send together with --disclosure-on)")
|
|
2461
|
+
.option("--disclosure-on <calls>", "which calls the disclosure plays on: inbound | outbound | both (send together with --disclosure). On the iPhone line: inbound is refused, both is saved as outbound")
|
|
2462
|
+
.option("--greeting <text>", "what is said (500 characters at most; an empty string = the default sentence)")
|
|
2463
|
+
.action(run(async (client, opts) => {
|
|
2464
|
+
const line = opts.line ?? "twilio";
|
|
2465
|
+
if (line !== "twilio" && line !== "iphone")
|
|
2466
|
+
throw new Error("--line must be twilio or iphone.");
|
|
2467
|
+
if ([opts.inbound, opts.outbound, opts.enabled, opts.disclosure, opts.disclosureOn, opts.greeting].every((v) => v === undefined)) {
|
|
2468
|
+
throw new Error("Nothing to change: pass at least one of --inbound, --outbound, --disclosure with --disclosure-on, --greeting.");
|
|
2469
|
+
}
|
|
2470
|
+
if ((opts.disclosure === undefined) !== (opts.disclosureOn === undefined)) {
|
|
2471
|
+
throw new Error("--disclosure and --disclosure-on are sent together: the disclosure is one setting (whether it plays, and on which calls).");
|
|
2472
|
+
}
|
|
2473
|
+
if (line === "iphone" && opts.inbound !== undefined)
|
|
2474
|
+
throw new Error("The iPhone line records outbound calls only: --inbound is not a setting of that line.");
|
|
2475
|
+
const policy = {};
|
|
2476
|
+
if (opts.inbound !== undefined)
|
|
2477
|
+
policy.inbound = onOffArg(opts.inbound, "--inbound");
|
|
2478
|
+
// (Codex r4) the old name is the outbound switch and nothing else: sent AS outbound, never as enabled (the server
|
|
2479
|
+
// gives an enabled-only write over a policy saved in the old shape its old whole meaning, disclosure included)
|
|
2480
|
+
if (opts.outbound !== undefined)
|
|
2481
|
+
policy.outbound = onOffArg(opts.outbound, "--outbound");
|
|
2482
|
+
else if (opts.enabled !== undefined)
|
|
2483
|
+
policy.outbound = onOffArg(opts.enabled, "--enabled");
|
|
2484
|
+
if (opts.disclosure !== undefined && opts.disclosureOn !== undefined) {
|
|
2485
|
+
if (!["inbound", "outbound", "both"].includes(opts.disclosureOn))
|
|
2486
|
+
throw new Error("--disclosure-on must be one of: inbound, outbound, both.");
|
|
2487
|
+
// the iPhone line has no inbound calls: a disclosure on inbound only would never play, so it is refused here
|
|
2488
|
+
// instead of saved as if it were on; both is what the line does on its outbound calls
|
|
2489
|
+
if (line === "iphone" && opts.disclosureOn === "inbound")
|
|
2490
|
+
throw new Error("The iPhone line has outbound calls only: a disclosure on inbound calls would never play. Use --disclosure-on outbound.");
|
|
2491
|
+
policy.disclosure = { enabled: onOffArg(opts.disclosure, "--disclosure"), on: line === "iphone" && opts.disclosureOn === "both" ? "outbound" : opts.disclosureOn };
|
|
2492
|
+
}
|
|
2493
|
+
if (opts.greeting !== undefined) {
|
|
2494
|
+
if (opts.greeting.trim().length > 500)
|
|
2495
|
+
throw new Error("--greeting may be at most 500 characters.");
|
|
2496
|
+
policy.consentGreeting = opts.greeting.trim();
|
|
2497
|
+
}
|
|
2498
|
+
return patchGeneralSettings(client, { [RECORDING_KEY[line]]: policy });
|
|
2499
|
+
}));
|
|
2401
2500
|
dialer
|
|
2402
2501
|
.command("settings")
|
|
2403
2502
|
.description("Show the account's Smart Dialing policy (GET /dialer/settings, dialer scope): autoAdvanceEnabled, pauseSeconds, advanceOn (disposition | timer), openLeadOnDial (the lead's record opens as each Smart Dialing call goes out), smartDialCallerId (local_presence = an area-code match from the number pool, the rep's own line when no pool exists; did = always the rep's own line), plus localPresenceConfigured.")
|
package/package.json
CHANGED