@sellable/mcp 0.1.548 → 0.1.550
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/api.js +6 -3
- package/dist/auth.d.ts +6 -0
- package/dist/auth.js +44 -2
- package/dist/index-dev.js +0 -0
- package/dist/index.js +0 -0
- package/dist/refill-date-window.d.ts +34 -0
- package/dist/refill-date-window.js +210 -0
- package/dist/server.js +1 -1
- package/dist/tools/auth.d.ts +5 -0
- package/dist/tools/auth.js +49 -12
- package/dist/tools/campaigns.d.ts +8 -57
- package/dist/tools/campaigns.js +5 -30
- package/dist/tools/csv-dnc.js +2 -2
- package/dist/tools/leads.d.ts +0 -22
- package/dist/tools/leads.js +1 -6
- package/dist/tools/refill-sends-evergreen.d.ts +28 -0
- package/dist/tools/refill-sends-evergreen.js +47 -0
- package/dist/tools/registry.d.ts +7 -83
- package/dist/tools/sequencer.d.ts +1 -9
- package/dist/tools/sequencer.js +36 -65
- package/dist/tools/setup-evergreen-campaigns.d.ts +0 -21
- package/dist/tools/setup-evergreen-campaigns.js +3 -22
- package/dist/tools/workspace-context.d.ts +1 -1
- package/dist/tools/workspace-context.js +8 -3
- package/dist/tools/workspace-export.js +2 -2
- package/dist/tools/workspaces.d.ts +48 -2
- package/dist/tools/workspaces.js +48 -5
- package/package.json +1 -1
- package/skills/create-campaign-v2/references/tier-routing-matrix.md +0 -5
- package/skills/create-evergreen-campaigns/SKILL.md +8 -56
- package/skills/refill-sends/SKILL.md +0 -5
- package/skills/refill-sends-v2/SKILL.md +0 -5
- package/skills/refill-sends-v2-workflow/SKILL.md +0 -7
- package/skills/refill-sends-v2-workflow/core/flow.v1.json +0 -1
package/dist/api.js
CHANGED
|
@@ -3,7 +3,7 @@ import { mkdir, rename, rm, stat } from "node:fs/promises";
|
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { Readable } from "node:stream";
|
|
5
5
|
import { pipeline } from "node:stream/promises";
|
|
6
|
-
import { getConfig, getConfigPath } from "./auth.js";
|
|
6
|
+
import { getConfig, getConfigPath, getEffectiveConfiguredWorkspaceId, resolveWorkspaceIdForRequest, } from "./auth.js";
|
|
7
7
|
export class SellableApiError extends Error {
|
|
8
8
|
status;
|
|
9
9
|
body;
|
|
@@ -42,9 +42,12 @@ export class SellableApi {
|
|
|
42
42
|
// Re-read config on every request so workspace switches take effect immediately
|
|
43
43
|
const config = getConfig();
|
|
44
44
|
const url = `${config.apiUrl}${path}`;
|
|
45
|
-
const
|
|
45
|
+
const requestedWorkspaceId = options && Object.prototype.hasOwnProperty.call(options, "workspaceId")
|
|
46
46
|
? options.workspaceId
|
|
47
|
-
: config
|
|
47
|
+
: getEffectiveConfiguredWorkspaceId(config);
|
|
48
|
+
const workspaceId = options && Object.prototype.hasOwnProperty.call(options, "workspaceId")
|
|
49
|
+
? resolveWorkspaceIdForRequest(requestedWorkspaceId, `${method} ${path}`)
|
|
50
|
+
: requestedWorkspaceId;
|
|
48
51
|
const response = await fetch(url, {
|
|
49
52
|
method,
|
|
50
53
|
headers: {
|
package/dist/auth.d.ts
CHANGED
|
@@ -40,6 +40,12 @@ export type SkillState = {
|
|
|
40
40
|
export declare function setConfigFile(fileName: string): void;
|
|
41
41
|
export declare function getResolvedConfigsDir(): string | null;
|
|
42
42
|
export declare function getConfigPath(): string;
|
|
43
|
+
export declare function getLockedWorkspaceId(): string | null;
|
|
44
|
+
export declare function isWorkspaceLockRequired(): boolean;
|
|
45
|
+
export declare function workspaceLockRequiredMessage(action?: string): string;
|
|
46
|
+
export declare function assertWorkspaceLockSatisfied(action?: string): void;
|
|
47
|
+
export declare function getEffectiveConfiguredWorkspaceId(config: Pick<SellableConfig, "activeWorkspaceId" | "workspaceId">): string | null;
|
|
48
|
+
export declare function resolveWorkspaceIdForRequest(requestedWorkspaceId: string | null | undefined, action?: string): string | null;
|
|
43
49
|
export declare function getConfig(): SellableConfig;
|
|
44
50
|
export declare function updateActiveWorkspace(params: {
|
|
45
51
|
workspaceId: string;
|
package/dist/auth.js
CHANGED
|
@@ -2,6 +2,8 @@ import * as fs from "fs";
|
|
|
2
2
|
import * as os from "os";
|
|
3
3
|
import * as path from "path";
|
|
4
4
|
const DEFAULT_API_URL = "https://app.sellable.dev";
|
|
5
|
+
const WORKSPACE_LOCK_ENV = "SELLABLE_LOCK_WORKSPACE_ID";
|
|
6
|
+
const REQUIRE_WORKSPACE_LOCK_ENV = "SELLABLE_REQUIRE_WORKSPACE_LOCK";
|
|
5
7
|
// No caching — re-read config on every call so token/workspace changes
|
|
6
8
|
// made by the LLM (editing sellable.json) take effect immediately
|
|
7
9
|
// without requiring an MCP server restart.
|
|
@@ -85,6 +87,44 @@ function normalizeConfig(raw) {
|
|
|
85
87
|
apiUrl: raw.apiUrl || DEFAULT_API_URL,
|
|
86
88
|
};
|
|
87
89
|
}
|
|
90
|
+
export function getLockedWorkspaceId() {
|
|
91
|
+
const value = process.env[WORKSPACE_LOCK_ENV]?.trim();
|
|
92
|
+
return value || null;
|
|
93
|
+
}
|
|
94
|
+
export function isWorkspaceLockRequired() {
|
|
95
|
+
const value = process.env[REQUIRE_WORKSPACE_LOCK_ENV]?.trim().toLowerCase();
|
|
96
|
+
return value === "1" || value === "true" || value === "yes";
|
|
97
|
+
}
|
|
98
|
+
export function workspaceLockRequiredMessage(action = "Sellable request") {
|
|
99
|
+
return (`Workspace lock required: ${action} cannot run because ` +
|
|
100
|
+
`${REQUIRE_WORKSPACE_LOCK_ENV}=1 but ${WORKSPACE_LOCK_ENV} is missing. ` +
|
|
101
|
+
"Ask the sellable-admin profile to provision this customer profile with an approved customer workspace.");
|
|
102
|
+
}
|
|
103
|
+
export function assertWorkspaceLockSatisfied(action = "Sellable request") {
|
|
104
|
+
if (isWorkspaceLockRequired() && !getLockedWorkspaceId()) {
|
|
105
|
+
throw new Error(workspaceLockRequiredMessage(action));
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
function normalizeWorkspaceId(value) {
|
|
109
|
+
return typeof value === "string" && value.trim() ? value.trim() : null;
|
|
110
|
+
}
|
|
111
|
+
export function getEffectiveConfiguredWorkspaceId(config) {
|
|
112
|
+
assertWorkspaceLockSatisfied("workspace resolution");
|
|
113
|
+
return (getLockedWorkspaceId() ||
|
|
114
|
+
normalizeWorkspaceId(config.activeWorkspaceId) ||
|
|
115
|
+
normalizeWorkspaceId(config.workspaceId));
|
|
116
|
+
}
|
|
117
|
+
export function resolveWorkspaceIdForRequest(requestedWorkspaceId, action = "Sellable request") {
|
|
118
|
+
assertWorkspaceLockSatisfied(action);
|
|
119
|
+
const lockedWorkspaceId = getLockedWorkspaceId();
|
|
120
|
+
const requested = normalizeWorkspaceId(requestedWorkspaceId);
|
|
121
|
+
if (!lockedWorkspaceId)
|
|
122
|
+
return requested;
|
|
123
|
+
if (requested && requested !== lockedWorkspaceId) {
|
|
124
|
+
throw new Error(`Workspace lock violation: ${action} requested workspace ${requested}, but this profile is locked to ${lockedWorkspaceId}.`);
|
|
125
|
+
}
|
|
126
|
+
return lockedWorkspaceId;
|
|
127
|
+
}
|
|
88
128
|
export function getConfig() {
|
|
89
129
|
// Config cache removed 2026-05-04: we always re-read from disk so
|
|
90
130
|
// workspace switches (set_active_workspace) take effect immediately
|
|
@@ -139,6 +179,7 @@ export function getConfig() {
|
|
|
139
179
|
}
|
|
140
180
|
}
|
|
141
181
|
export function updateActiveWorkspace(params) {
|
|
182
|
+
resolveWorkspaceIdForRequest(params.workspaceId, "set_active_workspace");
|
|
142
183
|
const configPath = getConfigPath();
|
|
143
184
|
const configDir = path.dirname(configPath);
|
|
144
185
|
if (!fs.existsSync(configDir)) {
|
|
@@ -226,6 +267,7 @@ function writeRawConfigFile(configPath, raw) {
|
|
|
226
267
|
* new config without an MCP server restart.
|
|
227
268
|
*/
|
|
228
269
|
export function writeNewConfig(opts) {
|
|
270
|
+
resolveWorkspaceIdForRequest(opts.activeWorkspaceId, "write Sellable auth config");
|
|
229
271
|
const configPath = getConfigWritePath();
|
|
230
272
|
let raw = {};
|
|
231
273
|
if (fs.existsSync(configPath)) {
|
|
@@ -283,7 +325,7 @@ function getActiveEnvConfigRef(raw) {
|
|
|
283
325
|
}
|
|
284
326
|
export function getEngageState() {
|
|
285
327
|
const config = getConfig();
|
|
286
|
-
const workspaceId = config
|
|
328
|
+
const workspaceId = getEffectiveConfiguredWorkspaceId(config);
|
|
287
329
|
if (!workspaceId) {
|
|
288
330
|
return { activeWorkspaceId: null, state: null };
|
|
289
331
|
}
|
|
@@ -294,7 +336,7 @@ export function updateEngageState(patch) {
|
|
|
294
336
|
const { configPath, raw } = readRawConfigFile();
|
|
295
337
|
const { envConfig, set } = getActiveEnvConfigRef(raw);
|
|
296
338
|
const normalized = normalizeConfig(envConfig);
|
|
297
|
-
const workspaceId = normalized
|
|
339
|
+
const workspaceId = getEffectiveConfiguredWorkspaceId(normalized);
|
|
298
340
|
if (!workspaceId) {
|
|
299
341
|
throw new Error("No active workspace selected. Run list_workspaces then set_active_workspace.");
|
|
300
342
|
}
|
package/dist/index-dev.js
CHANGED
|
File without changes
|
package/dist/index.js
CHANGED
|
File without changes
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
export type RefillDateWindowSource = "default_scheduler_forward" | "horizon_send_days" | "until_date" | "target_date";
|
|
2
|
+
export type NormalizedRefillDateSelector = {
|
|
3
|
+
source: RefillDateWindowSource;
|
|
4
|
+
targetDate: string | null;
|
|
5
|
+
untilDate: string | null;
|
|
6
|
+
horizonSendDays: number | null;
|
|
7
|
+
};
|
|
8
|
+
export type RefillRunDateWindow = NormalizedRefillDateSelector & {
|
|
9
|
+
version: 1;
|
|
10
|
+
selectedDates: string[];
|
|
11
|
+
senderWindows?: Array<{
|
|
12
|
+
senderId: string | null;
|
|
13
|
+
timeZone: string | null;
|
|
14
|
+
selectedDates: string[];
|
|
15
|
+
}>;
|
|
16
|
+
};
|
|
17
|
+
type DateSelectorInput = {
|
|
18
|
+
targetDate?: unknown;
|
|
19
|
+
untilDate?: unknown;
|
|
20
|
+
horizonSendDays?: unknown;
|
|
21
|
+
};
|
|
22
|
+
export declare function normalizeRefillDateSelector(input: DateSelectorInput): NormalizedRefillDateSelector;
|
|
23
|
+
export declare function refillDateSelectorBody(selector: NormalizedRefillDateSelector): {
|
|
24
|
+
targetDate?: string;
|
|
25
|
+
untilDate?: string;
|
|
26
|
+
horizonSendDays?: number;
|
|
27
|
+
};
|
|
28
|
+
export declare function dateWindowFromSelector(selector: NormalizedRefillDateSelector): RefillRunDateWindow;
|
|
29
|
+
export declare function dateWindowFromPlan(plan: Record<string, unknown>, selector: NormalizedRefillDateSelector): RefillRunDateWindow;
|
|
30
|
+
export declare function dateWindowFromRunState(runState: unknown): RefillRunDateWindow | null;
|
|
31
|
+
export declare function selectorFromDateWindow(window: RefillRunDateWindow): NormalizedRefillDateSelector;
|
|
32
|
+
export declare function dateSelectorMatchesWindow(selector: NormalizedRefillDateSelector, window: RefillRunDateWindow): boolean;
|
|
33
|
+
export declare function schedulerSweepTargetDates(window: RefillRunDateWindow): string[];
|
|
34
|
+
export {};
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
function isRecord(value) {
|
|
2
|
+
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
3
|
+
}
|
|
4
|
+
function stringValue(value) {
|
|
5
|
+
return typeof value === "string" && value.trim() ? value.trim() : null;
|
|
6
|
+
}
|
|
7
|
+
function numberValue(value) {
|
|
8
|
+
return typeof value === "number" && Number.isFinite(value) ? value : null;
|
|
9
|
+
}
|
|
10
|
+
function arrayValue(value) {
|
|
11
|
+
return Array.isArray(value) ? value : [];
|
|
12
|
+
}
|
|
13
|
+
function isValidDateKey(value) {
|
|
14
|
+
if (!/^\d{4}-\d{2}-\d{2}$/.test(value))
|
|
15
|
+
return false;
|
|
16
|
+
const [year, month, day] = value.split("-").map(Number);
|
|
17
|
+
const date = new Date(Date.UTC(year, month - 1, day));
|
|
18
|
+
return (date.getUTCFullYear() === year &&
|
|
19
|
+
date.getUTCMonth() === month - 1 &&
|
|
20
|
+
date.getUTCDate() === day);
|
|
21
|
+
}
|
|
22
|
+
function normalizeDateKey(value, field) {
|
|
23
|
+
if (value === undefined || value === null || value === "")
|
|
24
|
+
return null;
|
|
25
|
+
if (typeof value !== "string") {
|
|
26
|
+
throw new Error(`${field} must be a string in YYYY-MM-DD format.`);
|
|
27
|
+
}
|
|
28
|
+
const trimmed = value.trim();
|
|
29
|
+
if (!isValidDateKey(trimmed)) {
|
|
30
|
+
throw new Error(`${field} must be a valid YYYY-MM-DD calendar date.`);
|
|
31
|
+
}
|
|
32
|
+
return trimmed;
|
|
33
|
+
}
|
|
34
|
+
function normalizeHorizonSendDays(value) {
|
|
35
|
+
if (value === undefined)
|
|
36
|
+
return null;
|
|
37
|
+
if (typeof value !== "number" ||
|
|
38
|
+
!Number.isInteger(value) ||
|
|
39
|
+
value < 1 ||
|
|
40
|
+
value > 7) {
|
|
41
|
+
throw new Error("horizonSendDays must be an integer between 1 and 7.");
|
|
42
|
+
}
|
|
43
|
+
return value;
|
|
44
|
+
}
|
|
45
|
+
export function normalizeRefillDateSelector(input) {
|
|
46
|
+
const targetDate = normalizeDateKey(input.targetDate, "targetDate");
|
|
47
|
+
const untilDate = normalizeDateKey(input.untilDate, "untilDate");
|
|
48
|
+
const horizonSendDays = normalizeHorizonSendDays(input.horizonSendDays);
|
|
49
|
+
if (targetDate) {
|
|
50
|
+
return {
|
|
51
|
+
source: "target_date",
|
|
52
|
+
targetDate,
|
|
53
|
+
untilDate: null,
|
|
54
|
+
horizonSendDays: null,
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
if (untilDate) {
|
|
58
|
+
return {
|
|
59
|
+
source: "until_date",
|
|
60
|
+
targetDate: null,
|
|
61
|
+
untilDate,
|
|
62
|
+
horizonSendDays: null,
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
if (horizonSendDays !== null) {
|
|
66
|
+
return {
|
|
67
|
+
source: "horizon_send_days",
|
|
68
|
+
targetDate: null,
|
|
69
|
+
untilDate: null,
|
|
70
|
+
horizonSendDays,
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
return {
|
|
74
|
+
source: "default_scheduler_forward",
|
|
75
|
+
targetDate: null,
|
|
76
|
+
untilDate: null,
|
|
77
|
+
horizonSendDays: null,
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
export function refillDateSelectorBody(selector) {
|
|
81
|
+
if (selector.source === "target_date" && selector.targetDate) {
|
|
82
|
+
return { targetDate: selector.targetDate };
|
|
83
|
+
}
|
|
84
|
+
if (selector.source === "until_date" && selector.untilDate) {
|
|
85
|
+
return { untilDate: selector.untilDate };
|
|
86
|
+
}
|
|
87
|
+
if (selector.source === "horizon_send_days" &&
|
|
88
|
+
selector.horizonSendDays !== null) {
|
|
89
|
+
return { horizonSendDays: selector.horizonSendDays };
|
|
90
|
+
}
|
|
91
|
+
return {};
|
|
92
|
+
}
|
|
93
|
+
export function dateWindowFromSelector(selector) {
|
|
94
|
+
return {
|
|
95
|
+
version: 1,
|
|
96
|
+
...selector,
|
|
97
|
+
selectedDates: selector.targetDate ? [selector.targetDate] : [],
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
function selectedDatesFromPlan(plan) {
|
|
101
|
+
const packet = isRecord(plan.packet) ? plan.packet : {};
|
|
102
|
+
const target = isRecord(packet.target) ? packet.target : {};
|
|
103
|
+
const window = isRecord(target.window) ? target.window : {};
|
|
104
|
+
const senderWindows = arrayValue(window.senderWindows).filter(isRecord);
|
|
105
|
+
const fromWindows = senderWindows.flatMap((senderWindow) => arrayValue(senderWindow.selectedDates)
|
|
106
|
+
.map((entry) => stringValue(entry))
|
|
107
|
+
.filter((entry) => Boolean(entry)));
|
|
108
|
+
if (fromWindows.length > 0)
|
|
109
|
+
return Array.from(new Set(fromWindows)).sort();
|
|
110
|
+
const senderRefillPlans = arrayValue(target.senderRefillPlans).filter(isRecord);
|
|
111
|
+
return Array.from(new Set(senderRefillPlans.flatMap((senderPlan) => {
|
|
112
|
+
const horizon = isRecord(senderPlan.horizon) ? senderPlan.horizon : {};
|
|
113
|
+
return arrayValue(horizon.selectedDays)
|
|
114
|
+
.filter(isRecord)
|
|
115
|
+
.map((day) => stringValue(day.date))
|
|
116
|
+
.filter((entry) => Boolean(entry));
|
|
117
|
+
}))).sort();
|
|
118
|
+
}
|
|
119
|
+
function senderWindowsFromPlan(plan) {
|
|
120
|
+
const packet = isRecord(plan.packet) ? plan.packet : {};
|
|
121
|
+
const target = isRecord(packet.target) ? packet.target : {};
|
|
122
|
+
const window = isRecord(target.window) ? target.window : {};
|
|
123
|
+
return arrayValue(window.senderWindows)
|
|
124
|
+
.filter(isRecord)
|
|
125
|
+
.map((senderWindow) => ({
|
|
126
|
+
senderId: stringValue(senderWindow.senderId),
|
|
127
|
+
timeZone: stringValue(senderWindow.timeZone),
|
|
128
|
+
selectedDates: arrayValue(senderWindow.selectedDates)
|
|
129
|
+
.map((entry) => stringValue(entry))
|
|
130
|
+
.filter((entry) => Boolean(entry)),
|
|
131
|
+
}));
|
|
132
|
+
}
|
|
133
|
+
export function dateWindowFromPlan(plan, selector) {
|
|
134
|
+
const packet = isRecord(plan.packet) ? plan.packet : {};
|
|
135
|
+
const request = isRecord(packet.request) ? packet.request : {};
|
|
136
|
+
const targetDate = stringValue(request.targetDate) ?? selector.targetDate ?? null;
|
|
137
|
+
const untilDate = stringValue(request.untilDate) ?? selector.untilDate ?? null;
|
|
138
|
+
const horizonSendDays = numberValue(request.horizonSendDays) ?? selector.horizonSendDays ?? null;
|
|
139
|
+
const source = targetDate
|
|
140
|
+
? "target_date"
|
|
141
|
+
: untilDate
|
|
142
|
+
? "until_date"
|
|
143
|
+
: horizonSendDays !== null
|
|
144
|
+
? "horizon_send_days"
|
|
145
|
+
: selector.source;
|
|
146
|
+
const selectedDates = selectedDatesFromPlan(plan);
|
|
147
|
+
return {
|
|
148
|
+
version: 1,
|
|
149
|
+
source,
|
|
150
|
+
targetDate: source === "target_date" ? targetDate : null,
|
|
151
|
+
untilDate: source === "until_date" ? untilDate : null,
|
|
152
|
+
horizonSendDays: source === "horizon_send_days" ? horizonSendDays : null,
|
|
153
|
+
selectedDates: selectedDates.length > 0
|
|
154
|
+
? selectedDates
|
|
155
|
+
: targetDate
|
|
156
|
+
? [targetDate]
|
|
157
|
+
: [],
|
|
158
|
+
senderWindows: senderWindowsFromPlan(plan),
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
export function dateWindowFromRunState(runState) {
|
|
162
|
+
const state = isRecord(runState) ? runState : {};
|
|
163
|
+
const raw = isRecord(state.dateWindow) ? state.dateWindow : null;
|
|
164
|
+
if (!raw || raw.version !== 1)
|
|
165
|
+
return null;
|
|
166
|
+
const source = raw.source === "target_date" ||
|
|
167
|
+
raw.source === "until_date" ||
|
|
168
|
+
raw.source === "horizon_send_days" ||
|
|
169
|
+
raw.source === "default_scheduler_forward"
|
|
170
|
+
? raw.source
|
|
171
|
+
: null;
|
|
172
|
+
if (!source)
|
|
173
|
+
return null;
|
|
174
|
+
return {
|
|
175
|
+
version: 1,
|
|
176
|
+
source,
|
|
177
|
+
targetDate: source === "target_date" ? stringValue(raw.targetDate) : null,
|
|
178
|
+
untilDate: source === "until_date" ? stringValue(raw.untilDate) : null,
|
|
179
|
+
horizonSendDays: source === "horizon_send_days"
|
|
180
|
+
? numberValue(raw.horizonSendDays)
|
|
181
|
+
: null,
|
|
182
|
+
selectedDates: arrayValue(raw.selectedDates)
|
|
183
|
+
.map((entry) => stringValue(entry))
|
|
184
|
+
.filter((entry) => Boolean(entry)),
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
export function selectorFromDateWindow(window) {
|
|
188
|
+
return {
|
|
189
|
+
source: window.source,
|
|
190
|
+
targetDate: window.targetDate,
|
|
191
|
+
untilDate: window.untilDate,
|
|
192
|
+
horizonSendDays: window.horizonSendDays,
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
export function dateSelectorMatchesWindow(selector, window) {
|
|
196
|
+
return (selector.source === window.source &&
|
|
197
|
+
selector.targetDate === window.targetDate &&
|
|
198
|
+
selector.untilDate === window.untilDate &&
|
|
199
|
+
selector.horizonSendDays === window.horizonSendDays);
|
|
200
|
+
}
|
|
201
|
+
export function schedulerSweepTargetDates(window) {
|
|
202
|
+
if (window.source === "target_date" && window.targetDate) {
|
|
203
|
+
return [window.targetDate];
|
|
204
|
+
}
|
|
205
|
+
if (window.source === "until_date" ||
|
|
206
|
+
window.source === "horizon_send_days") {
|
|
207
|
+
return window.selectedDates;
|
|
208
|
+
}
|
|
209
|
+
return [];
|
|
210
|
+
}
|
package/dist/server.js
CHANGED
|
@@ -338,7 +338,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
338
338
|
}
|
|
339
339
|
break;
|
|
340
340
|
case "duplicate_campaign":
|
|
341
|
-
result = await duplicateCampaign(args?.campaignId
|
|
341
|
+
result = await duplicateCampaign(args?.campaignId);
|
|
342
342
|
if (result?.campaignOfferId) {
|
|
343
343
|
markCampaignContextDirty(result.campaignOfferId, "duplicate_campaign");
|
|
344
344
|
}
|
package/dist/tools/auth.d.ts
CHANGED
|
@@ -11,6 +11,11 @@ export type AuthStatus = {
|
|
|
11
11
|
tokenPresent: boolean;
|
|
12
12
|
tokenPrefix: string | null;
|
|
13
13
|
workspacesCount: number | null;
|
|
14
|
+
workspaceLock: {
|
|
15
|
+
enabled: boolean;
|
|
16
|
+
workspaceId: string | null;
|
|
17
|
+
required: boolean;
|
|
18
|
+
};
|
|
14
19
|
checkedAt: string;
|
|
15
20
|
/** Short message the LLM MUST show the user after auth succeeds. */
|
|
16
21
|
_userNotice: string | null;
|
package/dist/tools/auth.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as fs from "fs";
|
|
2
2
|
import { getApi, SellableApiError } from "../api.js";
|
|
3
|
-
import { getConfig, getConfigPath, getResolvedConfigsDir } from "../auth.js";
|
|
3
|
+
import { getConfig, getConfigPath, getEffectiveConfiguredWorkspaceId, getLockedWorkspaceId, getResolvedConfigsDir, isWorkspaceLockRequired, workspaceLockRequiredMessage, } from "../auth.js";
|
|
4
4
|
import { checkForUpdates } from "../update-check.js";
|
|
5
5
|
function maskToken(token) {
|
|
6
6
|
if (token.length <= 10)
|
|
@@ -38,6 +38,8 @@ export async function getAuthStatus() {
|
|
|
38
38
|
const checkedAt = new Date().toISOString();
|
|
39
39
|
const update = await getUpdateStatus();
|
|
40
40
|
let tokenPresent = false;
|
|
41
|
+
const workspaceLockRequired = isWorkspaceLockRequired();
|
|
42
|
+
const lockedWorkspaceId = getLockedWorkspaceId();
|
|
41
43
|
const base = {
|
|
42
44
|
ok: false,
|
|
43
45
|
configPath,
|
|
@@ -50,27 +52,47 @@ export async function getAuthStatus() {
|
|
|
50
52
|
tokenPresent: false,
|
|
51
53
|
tokenPrefix: null,
|
|
52
54
|
workspacesCount: null,
|
|
55
|
+
workspaceLock: {
|
|
56
|
+
enabled: Boolean(lockedWorkspaceId),
|
|
57
|
+
workspaceId: lockedWorkspaceId,
|
|
58
|
+
required: workspaceLockRequired,
|
|
59
|
+
},
|
|
53
60
|
_userNotice: appendUpdateNotice(null, update),
|
|
54
61
|
update,
|
|
55
62
|
checkedAt,
|
|
56
63
|
};
|
|
64
|
+
if (workspaceLockRequired && !lockedWorkspaceId) {
|
|
65
|
+
return {
|
|
66
|
+
...base,
|
|
67
|
+
error: {
|
|
68
|
+
type: "workspace",
|
|
69
|
+
message: "Customer profile workspace lock is missing.",
|
|
70
|
+
guidance: workspaceLockRequiredMessage("get_auth_status"),
|
|
71
|
+
},
|
|
72
|
+
};
|
|
73
|
+
}
|
|
57
74
|
try {
|
|
58
75
|
const config = getConfig();
|
|
59
76
|
tokenPresent = Boolean(config.token);
|
|
60
77
|
base.activeEnvName = config.activeEnvName || null;
|
|
61
78
|
const api = getApi();
|
|
62
79
|
const { workspaces } = await api.get("/api/v3/workspaces");
|
|
63
|
-
const activeWorkspaceId = config
|
|
80
|
+
const activeWorkspaceId = getEffectiveConfiguredWorkspaceId(config);
|
|
64
81
|
const activeWorkspace = activeWorkspaceId
|
|
65
82
|
? workspaces.find((ws) => ws.id === activeWorkspaceId)
|
|
66
83
|
: null;
|
|
84
|
+
const visibleWorkspacesCount = lockedWorkspaceId
|
|
85
|
+
? activeWorkspace
|
|
86
|
+
? 1
|
|
87
|
+
: 0
|
|
88
|
+
: workspaces.length;
|
|
67
89
|
if (!activeWorkspaceId) {
|
|
68
90
|
return {
|
|
69
91
|
...base,
|
|
70
92
|
apiUrl: config.apiUrl || null,
|
|
71
93
|
tokenPresent,
|
|
72
94
|
tokenPrefix: maskToken(config.token),
|
|
73
|
-
workspacesCount:
|
|
95
|
+
workspacesCount: visibleWorkspacesCount,
|
|
74
96
|
error: {
|
|
75
97
|
type: "workspace",
|
|
76
98
|
message: "No active workspace selected.",
|
|
@@ -87,22 +109,26 @@ export async function getAuthStatus() {
|
|
|
87
109
|
apiUrl: config.apiUrl || null,
|
|
88
110
|
tokenPresent,
|
|
89
111
|
tokenPrefix: maskToken(config.token),
|
|
90
|
-
workspacesCount:
|
|
112
|
+
workspacesCount: visibleWorkspacesCount,
|
|
91
113
|
error: {
|
|
92
114
|
type: "workspace",
|
|
93
115
|
message: `Active workspace ${activeWorkspaceId} not found in access list.`,
|
|
94
|
-
guidance:
|
|
95
|
-
?
|
|
96
|
-
:
|
|
116
|
+
guidance: lockedWorkspaceId
|
|
117
|
+
? "This profile is locked to a workspace that the configured Sellable token cannot access. Re-provision the customer profile from sellable-admin with the correct workspace token."
|
|
118
|
+
: available
|
|
119
|
+
? `Available: ${available}. Run set_active_workspace with a valid ID.`
|
|
120
|
+
: "Run list_workspaces to view available workspaces, then set_active_workspace.",
|
|
97
121
|
},
|
|
98
122
|
};
|
|
99
123
|
}
|
|
100
124
|
const workspaceName = activeWorkspace.name || config.activeWorkspaceName || null;
|
|
101
125
|
const envLabel = config.activeEnvName || null;
|
|
102
126
|
const wsLabel = workspaceName || activeWorkspaceId;
|
|
103
|
-
const
|
|
104
|
-
? `You're
|
|
105
|
-
:
|
|
127
|
+
const noticePrefix = lockedWorkspaceId
|
|
128
|
+
? `You're locked to ${wsLabel}.`
|
|
129
|
+
: envLabel
|
|
130
|
+
? `You're in ${wsLabel} (${envLabel}).`
|
|
131
|
+
: `You're in ${wsLabel}.`;
|
|
106
132
|
return {
|
|
107
133
|
ok: true,
|
|
108
134
|
configPath,
|
|
@@ -114,8 +140,19 @@ export async function getAuthStatus() {
|
|
|
114
140
|
activeWorkspaceName: workspaceName,
|
|
115
141
|
tokenPresent,
|
|
116
142
|
tokenPrefix: maskToken(config.token),
|
|
117
|
-
workspacesCount:
|
|
118
|
-
|
|
143
|
+
workspacesCount: visibleWorkspacesCount,
|
|
144
|
+
workspaceLock: lockedWorkspaceId
|
|
145
|
+
? {
|
|
146
|
+
enabled: true,
|
|
147
|
+
workspaceId: lockedWorkspaceId,
|
|
148
|
+
required: workspaceLockRequired,
|
|
149
|
+
}
|
|
150
|
+
: {
|
|
151
|
+
enabled: false,
|
|
152
|
+
workspaceId: null,
|
|
153
|
+
required: workspaceLockRequired,
|
|
154
|
+
},
|
|
155
|
+
_userNotice: appendUpdateNotice(noticePrefix, update),
|
|
119
156
|
update,
|
|
120
157
|
checkedAt,
|
|
121
158
|
error: null,
|
|
@@ -136,7 +136,6 @@ export interface CreateCampaignInput {
|
|
|
136
136
|
}
|
|
137
137
|
export interface UpdateCampaignInput {
|
|
138
138
|
flowVersion?: "v1" | "v2";
|
|
139
|
-
name?: string;
|
|
140
139
|
offerPositioning?: unknown;
|
|
141
140
|
campaignBrief?: unknown;
|
|
142
141
|
leadSourceType?: string | null;
|
|
@@ -162,55 +161,12 @@ export declare const campaignToolDefinitions: ({
|
|
|
162
161
|
type: string;
|
|
163
162
|
description: string;
|
|
164
163
|
};
|
|
165
|
-
name?: undefined;
|
|
166
|
-
limit?: undefined;
|
|
167
|
-
tableId?: undefined;
|
|
168
|
-
leadLimit?: undefined;
|
|
169
|
-
page?: undefined;
|
|
170
|
-
filters?: undefined;
|
|
171
|
-
clientProspectId?: undefined;
|
|
172
|
-
senderLinkedinUrl?: undefined;
|
|
173
|
-
offerPositioning?: undefined;
|
|
174
|
-
campaignBrief?: undefined;
|
|
175
|
-
messageGenerationMode?: undefined;
|
|
176
|
-
currentStep?: undefined;
|
|
177
|
-
watchNarration?: undefined;
|
|
178
|
-
leadSourceType?: undefined;
|
|
179
|
-
leadSourceProvider?: undefined;
|
|
180
|
-
selectedLeadListId?: undefined;
|
|
181
|
-
senderIds?: undefined;
|
|
182
|
-
currentStepTransition?: undefined;
|
|
183
|
-
clearCurrentStepIfMatches?: undefined;
|
|
184
|
-
interactionMode?: undefined;
|
|
185
|
-
enableICPFilters?: undefined;
|
|
186
|
-
useMessagingTemplate?: undefined;
|
|
187
|
-
rubric?: undefined;
|
|
188
|
-
flowVersion?: undefined;
|
|
189
|
-
workspaceId?: undefined;
|
|
190
|
-
};
|
|
191
|
-
required: string[];
|
|
192
|
-
additionalProperties: boolean;
|
|
193
|
-
};
|
|
194
|
-
} | {
|
|
195
|
-
name: string;
|
|
196
|
-
description: string;
|
|
197
|
-
inputSchema: {
|
|
198
|
-
type: string;
|
|
199
|
-
properties: {
|
|
200
|
-
campaignId: {
|
|
201
|
-
type: string;
|
|
202
|
-
description: string;
|
|
203
|
-
};
|
|
204
|
-
name: {
|
|
205
|
-
type: string;
|
|
206
|
-
maxLength: number;
|
|
207
|
-
description: string;
|
|
208
|
-
};
|
|
209
164
|
limit?: undefined;
|
|
210
165
|
tableId?: undefined;
|
|
211
166
|
leadLimit?: undefined;
|
|
212
167
|
page?: undefined;
|
|
213
168
|
filters?: undefined;
|
|
169
|
+
name?: undefined;
|
|
214
170
|
clientProspectId?: undefined;
|
|
215
171
|
senderLinkedinUrl?: undefined;
|
|
216
172
|
offerPositioning?: undefined;
|
|
@@ -245,11 +201,11 @@ export declare const campaignToolDefinitions: ({
|
|
|
245
201
|
description: string;
|
|
246
202
|
};
|
|
247
203
|
campaignId?: undefined;
|
|
248
|
-
name?: undefined;
|
|
249
204
|
tableId?: undefined;
|
|
250
205
|
leadLimit?: undefined;
|
|
251
206
|
page?: undefined;
|
|
252
207
|
filters?: undefined;
|
|
208
|
+
name?: undefined;
|
|
253
209
|
clientProspectId?: undefined;
|
|
254
210
|
senderLinkedinUrl?: undefined;
|
|
255
211
|
offerPositioning?: undefined;
|
|
@@ -283,12 +239,12 @@ export declare const campaignToolDefinitions: ({
|
|
|
283
239
|
type: string;
|
|
284
240
|
description: string;
|
|
285
241
|
};
|
|
286
|
-
name?: undefined;
|
|
287
242
|
limit?: undefined;
|
|
288
243
|
tableId?: undefined;
|
|
289
244
|
leadLimit?: undefined;
|
|
290
245
|
page?: undefined;
|
|
291
246
|
filters?: undefined;
|
|
247
|
+
name?: undefined;
|
|
292
248
|
clientProspectId?: undefined;
|
|
293
249
|
senderLinkedinUrl?: undefined;
|
|
294
250
|
offerPositioning?: undefined;
|
|
@@ -369,8 +325,8 @@ export declare const campaignToolDefinitions: ({
|
|
|
369
325
|
additionalProperties: boolean;
|
|
370
326
|
};
|
|
371
327
|
};
|
|
372
|
-
name?: undefined;
|
|
373
328
|
limit?: undefined;
|
|
329
|
+
name?: undefined;
|
|
374
330
|
clientProspectId?: undefined;
|
|
375
331
|
senderLinkedinUrl?: undefined;
|
|
376
332
|
offerPositioning?: undefined;
|
|
@@ -407,7 +363,6 @@ export declare const campaignToolDefinitions: ({
|
|
|
407
363
|
name: {
|
|
408
364
|
type: string;
|
|
409
365
|
description: string;
|
|
410
|
-
maxLength?: undefined;
|
|
411
366
|
};
|
|
412
367
|
clientProspectId: {
|
|
413
368
|
type: string;
|
|
@@ -600,11 +555,6 @@ export declare const campaignToolDefinitions: ({
|
|
|
600
555
|
type: string;
|
|
601
556
|
description: string;
|
|
602
557
|
};
|
|
603
|
-
name: {
|
|
604
|
-
type: string;
|
|
605
|
-
maxLength: number;
|
|
606
|
-
description: string;
|
|
607
|
-
};
|
|
608
558
|
offerPositioning: {
|
|
609
559
|
type: string;
|
|
610
560
|
description: string;
|
|
@@ -792,6 +742,7 @@ export declare const campaignToolDefinitions: ({
|
|
|
792
742
|
leadLimit?: undefined;
|
|
793
743
|
page?: undefined;
|
|
794
744
|
filters?: undefined;
|
|
745
|
+
name?: undefined;
|
|
795
746
|
clientProspectId?: undefined;
|
|
796
747
|
senderLinkedinUrl?: undefined;
|
|
797
748
|
messageGenerationMode?: undefined;
|
|
@@ -814,12 +765,12 @@ export declare const campaignToolDefinitions: ({
|
|
|
814
765
|
type: string;
|
|
815
766
|
description: string;
|
|
816
767
|
};
|
|
817
|
-
name?: undefined;
|
|
818
768
|
limit?: undefined;
|
|
819
769
|
tableId?: undefined;
|
|
820
770
|
leadLimit?: undefined;
|
|
821
771
|
page?: undefined;
|
|
822
772
|
filters?: undefined;
|
|
773
|
+
name?: undefined;
|
|
823
774
|
clientProspectId?: undefined;
|
|
824
775
|
senderLinkedinUrl?: undefined;
|
|
825
776
|
offerPositioning?: undefined;
|
|
@@ -856,12 +807,12 @@ export declare const campaignToolDefinitions: ({
|
|
|
856
807
|
type: string;
|
|
857
808
|
description: string;
|
|
858
809
|
};
|
|
859
|
-
name?: undefined;
|
|
860
810
|
limit?: undefined;
|
|
861
811
|
tableId?: undefined;
|
|
862
812
|
leadLimit?: undefined;
|
|
863
813
|
page?: undefined;
|
|
864
814
|
filters?: undefined;
|
|
815
|
+
name?: undefined;
|
|
865
816
|
clientProspectId?: undefined;
|
|
866
817
|
senderLinkedinUrl?: undefined;
|
|
867
818
|
offerPositioning?: undefined;
|
|
@@ -932,7 +883,7 @@ export declare function archiveCampaign(campaignId: string): Promise<{
|
|
|
932
883
|
cellsReset?: number;
|
|
933
884
|
workflowTableId?: string;
|
|
934
885
|
}>;
|
|
935
|
-
export declare function duplicateCampaign(campaignId: string
|
|
886
|
+
export declare function duplicateCampaign(campaignId: string): Promise<{
|
|
936
887
|
campaignOfferId: string;
|
|
937
888
|
campaignName: string;
|
|
938
889
|
workflowTableId: string | null;
|