conduyt 1.79.0 → 1.81.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/README.md +6 -1
- package/dist/index.js +102 -6
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -42,8 +42,12 @@ conduyt contacts collaborators add <contactId> <userId> [--expected-revision <n>
|
|
|
42
42
|
conduyt contacts collaborators remove <contactId> <userId> [--expected-revision <n>] # DELETE with a body; prints { removed, revision }; 404 when not a collaborator
|
|
43
43
|
conduyt chat channels # GET /chat/channels: your channels; also CREATES your auto-channel memberships the first time
|
|
44
44
|
conduyt chat unread # GET /chat/unread: per channel { channelId, unread, soundUnread, newestSeq, latestSeq }
|
|
45
|
-
conduyt chat messages <channelId> [--limit 1-100] [--cursor <messageId>] # newest first; prints { data, hasMore, nextCursor }; every message carries `seq`; does not move the read marker; a 403 "Not a member of this channel" is retried once after `chat channels`
|
|
45
|
+
conduyt chat messages <channelId> [--limit 1-100] [--cursor <messageId> | --after-seq <n>] # newest first; --after-seq = only messages committed after that seq (catch-up read, never with --cursor); each message also carries `version`; prints { data, hasMore, nextCursor }; every message carries `seq`; does not move the read marker; a 403 "Not a member of this channel" is retried once after `chat channels`
|
|
46
46
|
conduyt chat mark-read <channelId> <messageId> # readUpTo: marker moves to that message's seq, forward only (older or foreign message = no-op, exit 0); prints the channel's newest message; same one-retry membership bootstrap
|
|
47
|
+
conduyt chat realtime-token [--channel <id>]... # GET /realtime/token: signed Ably token request (up to 10 --channel); { enabled: false, reason } when the account switch is off; prints accountId / userId
|
|
48
|
+
conduyt chat notifications <channelId> # GET .../notifications: { level, isDefault, notify, updatedAt, membershipId, membershipSince, membershipSeq }
|
|
49
|
+
conduyt chat set-notifications <channelId> --level all|mentions|mute|default --expected-updated-at <iso|none> --expected-membership-id <id> --account-id <id> --user-id <id> # compare-and-set, sent once; 409 stale prints the current membership and exits 1
|
|
50
|
+
conduyt chat settings [--realtime on|off] [--presence on|off] [--quiet-hours HH:MM-HH:MM|none] [--digest-minutes 0|15-240] # PATCH /settings teamChat, owner/admin only
|
|
47
51
|
conduyt notifications list [--page n] [--per-page 1-200] # prints { data, meta }: meta.latestByType = { type: { latest, count } } over the last 24 h, next to unreadCount / unreadByType
|
|
48
52
|
conduyt deals list --pipeline <id> # list deals
|
|
49
53
|
conduyt deals list --health-summary --pipeline <id> # Pipeline health strip for the filtered set (totalOpenDeals, atRiskValue, checks[]); add --health-check stale --health-limit 20 for one check's deals, --health-cursor <nextCursor> to page, --health-rows-only for just the page
|
|
@@ -66,6 +70,7 @@ conduyt status-metadata list --stage-id <id> # the status codes + buckets atta
|
|
|
66
70
|
conduyt status-metadata import --file rows.json --mode replace # set-based import (max 1000; an item key outside stageId, code, bucket, lifecycleStage, dripEligible, dripTrackName, isTerminal, notes is refused before any request); per-item errors print and exit 1; `--mode replace` with no items is refused unless `--confirm-empty-replace` (it deletes every row). Timeouts: archive 75 s, import and the Mailchimp write / action commands 60 s; on a timeout the server may have applied it, so read the state first
|
|
67
71
|
conduyt integrations mailchimp settings # the Mailchimp audience sync (API key scope integrations); `set | audiences | push | retry-unsubscribes | reconcile-statuses [--restart] | confirm-reconciliation`. Connecting is browser-only (OAuth): do it in the app
|
|
68
72
|
conduyt pipelines update <id> --automation-only true # only automations, API keys and owners / admins move or edit its cards (owner / admin key); also --color <palette hex> and --sort-order
|
|
73
|
+
conduyt automations dry-run <id> [--contact <id>] [--deal <id>] [--company <id>] [--execution <id>] # preview one trigger occurrence without side effects; --deal for a deal occurrence, --company for a company occurrence, --execution (admin or owner only) replays a stored run of this automation and supplies the company its payload carried; no test-node command exists in this CLI
|
|
69
74
|
conduyt search "acme corp" # search across the CRM
|
|
70
75
|
conduyt insights summary # run an AI insight query
|
|
71
76
|
conduyt insights saved_report --report "Lead Activity" --period last_7_days # run any saved report as a tool
|
package/dist/index.js
CHANGED
|
@@ -2541,18 +2541,33 @@ automations
|
|
|
2541
2541
|
}));
|
|
2542
2542
|
automations
|
|
2543
2543
|
.command("dry-run <id>")
|
|
2544
|
-
.description("Test a workflow without side effects")
|
|
2544
|
+
.description("Test a workflow without side effects. A deal occurrence needs --deal and a company occurrence needs --company. --execution (admin or owner only) replays a stored run of this automation and supplies ONE fact, the company carried by that run's payload (same account, automation, trigger event, contact and deal as the preview; runs carrying a deal are hidden from API keys without the deals scope); without it, on a non-company occurrence, the preview is a synthetic sample and company rules evaluate as unknown/absent, which the response says")
|
|
2545
2545
|
.option("--contact <id>", "run against this contact UUID")
|
|
2546
2546
|
.option("--appointment <id>", "seed with this appointment UUID")
|
|
2547
|
+
.option("--deal <id>", "the event's deal UUID: REQUIRED for a deal occurrence (400 without it), 404 for a deal you cannot see, refused (400) on any other occurrence")
|
|
2548
|
+
.option("--company <id>", "the event's company UUID: REQUIRED for a company occurrence (400 without it); on any other occurrence accepted only when it equals the company the --execution payload carries")
|
|
2549
|
+
.option("--execution <id>", "UUID of a stored run of THIS automation to replay (admin or owner only); supplies the company its payload carried, nothing else; it does not replace --company on a company occurrence")
|
|
2547
2550
|
.option("--source <source>", "draft | published (default draft)")
|
|
2548
2551
|
.option("--mode <mode>", "single | all-paths (default single)")
|
|
2549
2552
|
.action(run(async (client, id, opts) => {
|
|
2550
2553
|
assertUuid(id, "automation id");
|
|
2554
|
+
if (opts.deal !== undefined)
|
|
2555
|
+
assertUuid(opts.deal, "--deal");
|
|
2556
|
+
if (opts.company !== undefined)
|
|
2557
|
+
assertUuid(opts.company, "--company");
|
|
2558
|
+
if (opts.execution !== undefined)
|
|
2559
|
+
assertUuid(opts.execution, "--execution");
|
|
2551
2560
|
const body = {};
|
|
2552
2561
|
if (opts.contact !== undefined)
|
|
2553
2562
|
body.contactId = opts.contact;
|
|
2554
2563
|
if (opts.appointment !== undefined)
|
|
2555
2564
|
body.appointmentId = opts.appointment;
|
|
2565
|
+
if (opts.deal !== undefined)
|
|
2566
|
+
body.dealId = opts.deal.trim();
|
|
2567
|
+
if (opts.company !== undefined)
|
|
2568
|
+
body.companyId = opts.company.trim();
|
|
2569
|
+
if (opts.execution !== undefined)
|
|
2570
|
+
body.executionId = opts.execution.trim();
|
|
2556
2571
|
return client.post(`/api/v1/automations/${encodeURIComponent(id)}/dry-run${buildQuery({ source: opts.source, mode: opts.mode })}`, body);
|
|
2557
2572
|
}));
|
|
2558
2573
|
automations
|
|
@@ -3922,7 +3937,7 @@ ai
|
|
|
3922
3937
|
return client.post("/api/v1/ai/enrich-contact", body);
|
|
3923
3938
|
}));
|
|
3924
3939
|
// ---- team chat (app 10-09, live sha 3869c79ba) ----
|
|
3925
|
-
const chat = program.command("chat").description("Team chat: unread vector, messages (each carries `seq`), read acknowledgement");
|
|
3940
|
+
const chat = program.command("chat").description("Team chat: unread vector, messages (each carries `seq` and `version`), read acknowledgement, realtime token, per-channel notification level, Team Chat 2.0 settings");
|
|
3926
3941
|
/** The messages route's membership refusal (403 "Not a member of this channel"); any other 403 (a permission) is not this one. */
|
|
3927
3942
|
function isMembershipRefusal(err) {
|
|
3928
3943
|
return err instanceof Error && /\b403\b/.test(err.message) && /Not a member of this channel/.test(err.message);
|
|
@@ -3942,23 +3957,27 @@ async function withChatMembership(client, read) {
|
|
|
3942
3957
|
}
|
|
3943
3958
|
chat
|
|
3944
3959
|
.command("channels")
|
|
3945
|
-
.description("GET /chat/channels: your channels (id, name, kind, members, last message). SIDE EFFECT: also creates your memberships in the account's auto channels (needs-a-hand, new-leads, wins) the first time. `chat messages` and `chat mark-read` do this once for you on a 403 'Not a member of this channel'")
|
|
3960
|
+
.description("GET /chat/channels: your channels (id, name, kind, members, last message). SIDE EFFECT: also creates your memberships in the account's auto channels (needs-a-hand, new-leads, wins) the first time. `chat messages` and `chat mark-read` do this once for you on a 403 'Not a member of this channel'. Team Chat 2.0 rows also carry mentionCount (unread @mentions of you), notify (your stored level: all, mentions, mute, or null = default), updatedAt (when notify last changed, null = never), membershipId, membershipSince and membershipSeq")
|
|
3946
3961
|
.action(run(async (client) => client.get("/api/v1/chat/channels")));
|
|
3947
3962
|
chat
|
|
3948
3963
|
.command("unread")
|
|
3949
|
-
.description("GET /chat/unread: per channel { channelId, unread, soundUnread, newestSeq, latestSeq }; a row can be an AUTO channel you have not opened yet (no membership row: reading it first answers 403, which `chat messages` / `chat mark-read` recover from once). unread is the badge count (auto channels 0 by design); soundUnread counts every channel; newestSeq = seq of the newest message not written by you (null = none), latestSeq the same including panic cards. Read-only")
|
|
3964
|
+
.description("GET /chat/unread: per channel { channelId, unread, soundUnread, newestSeq, latestSeq }; a row can be an AUTO channel you have not opened yet (no membership row: reading it first answers 403, which `chat messages` / `chat mark-read` recover from once). unread is the badge count (auto channels 0 by design); soundUnread counts every channel; newestSeq = seq of the newest message not written by you (null = none), latestSeq the same including panic cards. Team Chat 2.0 adds per row mentions (unread @mentions of you), notify, updatedAt, membershipId (null = no membership row), membershipSince and membershipSeq, and per response teamChatRealtime (the account's realtime switch) and membershipSeqHigh. Read-only")
|
|
3950
3965
|
.action(run(async (client) => client.get("/api/v1/chat/unread")));
|
|
3951
3966
|
chat
|
|
3952
3967
|
.command("messages <channelId>")
|
|
3953
|
-
.description("List a channel's messages, newest first (GET /chat/channels/{id}/messages); every message carries `seq`, its commit-ordered position in the channel. Prints { data, hasMore, nextCursor }. Needs channel membership (else 403). Does NOT move your read marker: use `chat mark-read`. On a 403 'Not a member of this channel' it calls GET /chat/channels once and retries ONCE (a second refusal or any other 403 exits 1 as is)")
|
|
3968
|
+
.description("List a channel's messages, newest first (GET /chat/channels/{id}/messages); every message carries `seq`, its commit-ordered position in the channel. Every message also carries `version`, a server counter bumped on every update (edit, delete, reaction, pin, claim, handoff). Prints { data, hasMore, nextCursor }. Needs channel membership (else 403). Does NOT move your read marker: use `chat mark-read`. On a 403 'Not a member of this channel' it calls GET /chat/channels once and retries ONCE (a second refusal or any other 403 exits 1 as is)")
|
|
3954
3969
|
.option("--limit <n>", "messages per page, 1-100 (default 50)")
|
|
3955
3970
|
.option("--cursor <messageId>", "nextCursor from the previous page (a message UUID)")
|
|
3971
|
+
.option("--after-seq <n>", "only messages committed after this seq (the realtime catch-up read), a whole number 0-999999999; given once, never with --cursor; hasMore then means more follow this page (re-read the latest page)")
|
|
3956
3972
|
.action(run(async (client, channelId, opts) => {
|
|
3957
3973
|
assertUuid(channelId, "channel id");
|
|
3958
3974
|
const limit = opts.limit !== undefined ? intFlag(opts.limit, "--limit", 1, 100) : undefined;
|
|
3959
3975
|
const cursor = opts.cursor !== undefined ? uuidOf(opts.cursor, "--cursor") : undefined;
|
|
3976
|
+
const afterSeq = opts.afterSeq !== undefined ? intFlag(opts.afterSeq, "--after-seq", 0, 999_999_999) : undefined;
|
|
3977
|
+
if (afterSeq !== undefined && cursor !== undefined)
|
|
3978
|
+
throw new Error("--after-seq and --cursor cannot be combined.");
|
|
3960
3979
|
// the live route answers { data: { data, hasMore, nextCursor } }: print() unwraps the outer envelope, leaving { data, hasMore, nextCursor }
|
|
3961
|
-
return withChatMembership(client, () => client.get(`/api/v1/chat/channels/${encodeURIComponent(channelId)}/messages${buildQuery({ limit, cursor })}`));
|
|
3980
|
+
return withChatMembership(client, () => client.get(`/api/v1/chat/channels/${encodeURIComponent(channelId)}/messages${buildQuery({ limit, cursor, afterSeq })}`));
|
|
3962
3981
|
}));
|
|
3963
3982
|
chat
|
|
3964
3983
|
.command("mark-read <channelId> <messageId>")
|
|
@@ -3968,6 +3987,83 @@ chat
|
|
|
3968
3987
|
const readUpTo = uuidOf(messageId, "message id");
|
|
3969
3988
|
return withChatMembership(client, () => client.get(`/api/v1/chat/channels/${encodeURIComponent(channelId)}/messages${buildQuery({ limit: 1, readUpTo })}`));
|
|
3970
3989
|
}));
|
|
3990
|
+
chat
|
|
3991
|
+
.command("realtime-token")
|
|
3992
|
+
.description("GET /realtime/token: a signed Ably token request for the Team Chat 2.0 realtime pipe, scoped to your user channel, the presence channel (only with realtimePresence on) and the chat channels you belong to (the 100 most recently active, plus any --channel). Prints { enabled: true, tokenRequest, accountId, userId, channels, presence, expiresAt }, or { enabled: false, reason } when the account switch (settings.teamChat.realtime) is off or the provider is unconfigured. accountId and userId are what `chat set-notifications` needs. The token is short-lived: do not log it. Errors exit 1 with the status and message: 403 'Not an active member of this account', 400 'channel must be a channel id', 429 'Too many token requests'")
|
|
3993
|
+
.option("--channel <channelId>", "a channel to include, repeatable up to 10 times (each a UUID; sent as repeated channel=)", (v, prev = []) => [...prev, v])
|
|
3994
|
+
.action(run(async (client, opts) => {
|
|
3995
|
+
const ids = (opts.channel ?? []).map((c) => uuidOf(c, "--channel"));
|
|
3996
|
+
if (ids.length > 10)
|
|
3997
|
+
throw new Error("--channel may be given at most 10 times.");
|
|
3998
|
+
const qs = new URLSearchParams();
|
|
3999
|
+
for (const id of ids)
|
|
4000
|
+
qs.append("channel", id);
|
|
4001
|
+
return client.get(`/api/v1/realtime/token${qs.size > 0 ? `?${qs}` : ""}`);
|
|
4002
|
+
}));
|
|
4003
|
+
chat
|
|
4004
|
+
.command("notifications <channelId>")
|
|
4005
|
+
.description("GET /chat/channels/{id}/notifications: your notification level for one channel. Prints { level, isDefault, notify, updatedAt, membershipId, membershipSince, membershipSeq }: level is the effective level (all = every message pushes, mentions = only @mentions of you, mute = nothing but a direct @mention), notify the STORED value (null = the kind's default), updatedAt its version (null = never set). `chat set-notifications` needs updatedAt and membershipId. Members only: a 403 'Not a member of this channel' is retried ONCE after GET /chat/channels, like the other reads")
|
|
4006
|
+
.action(run(async (client, channelId) => {
|
|
4007
|
+
assertUuid(channelId, "channel id");
|
|
4008
|
+
return withChatMembership(client, () => client.get(`/api/v1/chat/channels/${encodeURIComponent(channelId)}/notifications`));
|
|
4009
|
+
}));
|
|
4010
|
+
chat
|
|
4011
|
+
.command("set-notifications <channelId>")
|
|
4012
|
+
.description("PATCH /chat/channels/{id}/notifications: set your notification level, compare-and-set against what you last read (run `chat notifications` first). --level all|mentions|mute|default (default = the kind's default); --expected-updated-at <iso|none> is the updatedAt you read (none = it was null); --expected-membership-id the membershipId you read; --account-id and --user-id are yours (from `chat realtime-token`; another person's are refused 403 identity_mismatch). Sent once, never retried. Every refusal exits 1 with the status, the API's message and its code: 409 stale prints the CURRENT membership ({ code: stale, current: { notify, updatedAt, membershipId, membershipSince, membershipSeq } }; a different membershipId means you were removed and re-added; nothing was changed), 404 not_a_member, 403 identity_mismatch or 'Not a member of this channel', 400 for a bad level, expectedUpdatedAt or expectedMembershipId. Takes effect only while the account's realtime switch is on")
|
|
4013
|
+
.requiredOption("--level <level>", "all, mentions, mute or default")
|
|
4014
|
+
.requiredOption("--expected-updated-at <iso|none>", "the updatedAt you last read (an ISO date), or none when it was null")
|
|
4015
|
+
.requiredOption("--expected-membership-id <id>", "the membershipId you last read (a UUID)")
|
|
4016
|
+
.requiredOption("--account-id <id>", "your account id (a UUID)")
|
|
4017
|
+
.requiredOption("--user-id <id>", "your user id (a UUID)")
|
|
4018
|
+
.action(run(async (client, channelId, opts) => {
|
|
4019
|
+
assertUuid(channelId, "channel id");
|
|
4020
|
+
const level = opts.level.trim().toLowerCase();
|
|
4021
|
+
if (!["all", "mentions", "mute", "default"].includes(level))
|
|
4022
|
+
throw new Error(`--level takes all, mentions, mute or default (got '${opts.level}').`);
|
|
4023
|
+
const raw = opts.expectedUpdatedAt.trim();
|
|
4024
|
+
if (raw.toLowerCase() !== "none" && Number.isNaN(Date.parse(raw)))
|
|
4025
|
+
throw new Error(`--expected-updated-at takes the updatedAt you read (an ISO date) or none (got '${opts.expectedUpdatedAt}').`);
|
|
4026
|
+
return client.patch(`/api/v1/chat/channels/${encodeURIComponent(channelId)}/notifications`, {
|
|
4027
|
+
level,
|
|
4028
|
+
expectedUpdatedAt: raw.toLowerCase() === "none" ? null : raw,
|
|
4029
|
+
expectedMembershipId: uuidOf(opts.expectedMembershipId, "--expected-membership-id"),
|
|
4030
|
+
accountId: uuidOf(opts.accountId, "--account-id"),
|
|
4031
|
+
userId: uuidOf(opts.userId, "--user-id"),
|
|
4032
|
+
});
|
|
4033
|
+
}));
|
|
4034
|
+
chat
|
|
4035
|
+
.command("settings")
|
|
4036
|
+
.description("Change the account's Team Chat 2.0 settings (PATCH /settings, settings.teamChat). OWNER / ADMIN ONLY (403 forbidden 'Only an owner or admin can change Team Chat realtime delivery'). Pass at least one: --realtime on|off (instant delivery over the realtime pipe; off = the chat polls; account default off), --presence on|off (online/away over the pipe; needs realtime), --quiet-hours <HH:MM-HH:MM|none> (account timezone; the end may be earlier than the start to cross midnight; needs realtime), --digest-minutes <0|15-240> (missed-message email digest window, 0 = off, default 30; needs realtime). Sends the settings revision it read; a 409 re-reads once, then exits 1 with the current settings. 422 names an invalid field")
|
|
4037
|
+
.option("--realtime <on|off>", "Team Chat realtime delivery")
|
|
4038
|
+
.option("--presence <on|off>", "realtime presence (needs realtime)")
|
|
4039
|
+
.option("--quiet-hours <window>", "HH:MM-HH:MM in the account timezone, or none")
|
|
4040
|
+
.option("--digest-minutes <n>", "0 (off) or 15-240")
|
|
4041
|
+
.action(run(async (client, opts) => {
|
|
4042
|
+
const teamChat = {};
|
|
4043
|
+
if (opts.realtime !== undefined)
|
|
4044
|
+
teamChat.realtime = onOffArg(opts.realtime, "--realtime");
|
|
4045
|
+
if (opts.presence !== undefined)
|
|
4046
|
+
teamChat.realtimePresence = onOffArg(opts.presence, "--presence");
|
|
4047
|
+
if (opts.quietHours !== undefined) {
|
|
4048
|
+
const w = opts.quietHours.trim();
|
|
4049
|
+
const m = /^(([01]\d|2[0-3]):[0-5]\d)-(([01]\d|2[0-3]):[0-5]\d)$/.exec(w);
|
|
4050
|
+
if (w.toLowerCase() === "none")
|
|
4051
|
+
teamChat.quietHours = null;
|
|
4052
|
+
else if (m)
|
|
4053
|
+
teamChat.quietHours = { start: m[1], end: m[3] };
|
|
4054
|
+
else
|
|
4055
|
+
throw new Error(`--quiet-hours takes HH:MM-HH:MM (24-hour) or none (got '${opts.quietHours}').`);
|
|
4056
|
+
}
|
|
4057
|
+
if (opts.digestMinutes !== undefined) {
|
|
4058
|
+
const n = intFlag(opts.digestMinutes, "--digest-minutes", 0, 240);
|
|
4059
|
+
if (n !== 0 && n < 15)
|
|
4060
|
+
throw new Error("--digest-minutes is 0 (off) or 15-240.");
|
|
4061
|
+
teamChat.digestMinutes = n;
|
|
4062
|
+
}
|
|
4063
|
+
if (Object.keys(teamChat).length === 0)
|
|
4064
|
+
throw new Error("nothing to change: pass at least one of --realtime, --presence, --quiet-hours, --digest-minutes");
|
|
4065
|
+
return patchGeneralSettings(client, { teamChat });
|
|
4066
|
+
}));
|
|
3971
4067
|
// ---- notifications (read side; meta.latestByType, app 10-09) ----
|
|
3972
4068
|
const notifications = program.command("notifications").description("Your in-app notifications");
|
|
3973
4069
|
notifications
|
package/package.json
CHANGED