@ascendkit/cli 0.2.0 → 0.3.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/api/client.d.ts +2 -0
- package/dist/api/client.js +34 -8
- package/dist/cli.js +1047 -314
- package/dist/commands/auth.d.ts +4 -0
- package/dist/commands/auth.js +12 -0
- package/dist/commands/campaigns.d.ts +22 -0
- package/dist/commands/campaigns.js +25 -0
- package/dist/commands/content.js +4 -4
- package/dist/commands/email.d.ts +62 -6
- package/dist/commands/email.js +26 -17
- package/dist/commands/import.d.ts +75 -0
- package/dist/commands/import.js +97 -0
- package/dist/commands/journeys.d.ts +3 -0
- package/dist/commands/journeys.js +9 -6
- package/dist/commands/platform.d.ts +28 -0
- package/dist/commands/platform.js +219 -101
- package/dist/commands/surveys.js +5 -5
- package/dist/mcp.js +35 -3
- package/dist/tools/auth.js +66 -8
- package/dist/tools/campaigns.d.ts +3 -0
- package/dist/tools/campaigns.js +78 -0
- package/dist/tools/content.js +24 -12
- package/dist/tools/email.js +47 -17
- package/dist/tools/import.d.ts +3 -0
- package/dist/tools/import.js +116 -0
- package/dist/tools/journeys.js +25 -14
- package/dist/tools/platform.js +151 -6
- package/dist/tools/surveys.js +9 -9
- package/dist/utils/journey-format.d.ts +6 -0
- package/dist/utils/journey-format.js +6 -4
- package/dist/utils/survey-format.js +2 -2
- package/package.json +5 -5
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import * as campaigns from "../commands/campaigns.js";
|
|
3
|
+
export function registerCampaignTools(server, client) {
|
|
4
|
+
server.tool("campaign_create", "Create a new email campaign targeting users that match an audience filter. Campaigns start as drafts; set scheduledAt to schedule for future delivery. Campaign lifecycle: create → preview audience → schedule → sending → sent.", {
|
|
5
|
+
name: z.string().describe("Campaign name, e.g. 'March Newsletter'"),
|
|
6
|
+
templateId: z.string().describe("Email template ID (tpl_ prefixed) to use for the campaign"),
|
|
7
|
+
audienceFilter: z
|
|
8
|
+
.record(z.unknown())
|
|
9
|
+
.describe("Filter object to select target users (e.g. { tags: { $in: ['premium'] } }). Allowed fields: createdAt, emailVerified, lastLoginAt, tags, stage, name, email, waitlistStatus, metadata.*"),
|
|
10
|
+
scheduledAt: z
|
|
11
|
+
.string()
|
|
12
|
+
.optional()
|
|
13
|
+
.describe("ISO 8601 datetime to schedule sending (omit to keep as draft)"),
|
|
14
|
+
fromIdentityEmail: z
|
|
15
|
+
.string()
|
|
16
|
+
.optional()
|
|
17
|
+
.describe("Verified email identity to use as sender. Falls back to environment default."),
|
|
18
|
+
}, async (params) => {
|
|
19
|
+
const data = await campaigns.createCampaign(client, params);
|
|
20
|
+
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
|
|
21
|
+
});
|
|
22
|
+
server.tool("campaign_list", "List campaigns for the current project. Optionally filter by status: draft, scheduled, sending, sent, failed, or cancelled.", {
|
|
23
|
+
status: z
|
|
24
|
+
.enum(["draft", "scheduled", "sending", "sent", "failed", "cancelled"])
|
|
25
|
+
.optional()
|
|
26
|
+
.describe("Filter by campaign status"),
|
|
27
|
+
}, async (params) => {
|
|
28
|
+
const data = await campaigns.listCampaigns(client, params.status);
|
|
29
|
+
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
|
|
30
|
+
});
|
|
31
|
+
server.tool("campaign_get", "Get full details of a campaign including its status, template, audience filter, and schedule.", {
|
|
32
|
+
campaignId: z.string().describe("Campaign ID (cmp_ prefixed)"),
|
|
33
|
+
}, async (params) => {
|
|
34
|
+
const data = await campaigns.getCampaign(client, params.campaignId);
|
|
35
|
+
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
|
|
36
|
+
});
|
|
37
|
+
server.tool("campaign_update", "Update a draft, scheduled, or failed campaign. You can change the name, template, audience filter, or schedule. Only provided fields are updated.", {
|
|
38
|
+
campaignId: z.string().describe("Campaign ID (cmp_ prefixed)"),
|
|
39
|
+
name: z.string().optional().describe("New campaign name"),
|
|
40
|
+
templateId: z.string().optional().describe("New email template ID (tpl_ prefixed)"),
|
|
41
|
+
audienceFilter: z
|
|
42
|
+
.record(z.unknown())
|
|
43
|
+
.optional()
|
|
44
|
+
.describe("New audience filter object"),
|
|
45
|
+
scheduledAt: z
|
|
46
|
+
.string()
|
|
47
|
+
.optional()
|
|
48
|
+
.describe("New scheduled send time (ISO 8601 datetime)"),
|
|
49
|
+
fromIdentityEmail: z
|
|
50
|
+
.string()
|
|
51
|
+
.optional()
|
|
52
|
+
.describe("Verified email identity to use as sender. Falls back to environment default."),
|
|
53
|
+
}, async (params) => {
|
|
54
|
+
const { campaignId, ...rest } = params;
|
|
55
|
+
const data = await campaigns.updateCampaign(client, campaignId, rest);
|
|
56
|
+
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
|
|
57
|
+
});
|
|
58
|
+
server.tool("campaign_delete", "Delete a draft or failed campaign, or cancel a scheduled/sending campaign. Sent campaigns cannot be deleted.", {
|
|
59
|
+
campaignId: z.string().describe("Campaign ID (cmp_ prefixed)"),
|
|
60
|
+
}, async (params) => {
|
|
61
|
+
const data = await campaigns.deleteCampaign(client, params.campaignId);
|
|
62
|
+
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
|
|
63
|
+
});
|
|
64
|
+
server.tool("campaign_preview_audience", "Preview how many users match an audience filter before creating or updating a campaign. Returns the count and a sample of matching users.", {
|
|
65
|
+
audienceFilter: z
|
|
66
|
+
.record(z.unknown())
|
|
67
|
+
.describe("Filter object to preview (e.g. { tags: { $in: ['premium'] } }). Allowed fields: createdAt, emailVerified, lastLoginAt, tags, stage, name, email, waitlistStatus, metadata.*"),
|
|
68
|
+
}, async (params) => {
|
|
69
|
+
const data = await campaigns.previewAudience(client, params.audienceFilter);
|
|
70
|
+
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
|
|
71
|
+
});
|
|
72
|
+
server.tool("campaign_analytics", "Get campaign performance analytics: delivery stats (sent, failed, bounced), engagement metrics (opened, clicked), and calculated rates.", {
|
|
73
|
+
campaignId: z.string().describe("Campaign ID (cmp_ prefixed)"),
|
|
74
|
+
}, async (params) => {
|
|
75
|
+
const data = await campaigns.getCampaignAnalytics(client, params.campaignId);
|
|
76
|
+
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
|
|
77
|
+
});
|
|
78
|
+
}
|
package/dist/tools/content.js
CHANGED
|
@@ -1,7 +1,19 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import * as content from "../commands/content.js";
|
|
3
|
+
function formatTemplateSummary(data) {
|
|
4
|
+
const lines = [`Template: ${data.name} (${data.id})`];
|
|
5
|
+
if (data.slug)
|
|
6
|
+
lines.push(`Slug: ${data.slug}`);
|
|
7
|
+
lines.push(`Subject: ${data.subject ?? "-"}`);
|
|
8
|
+
if (data.currentVersion != null)
|
|
9
|
+
lines.push(`Version: ${data.currentVersion}`);
|
|
10
|
+
if (Array.isArray(data.variables) && data.variables.length > 0) {
|
|
11
|
+
lines.push(`Variables: ${data.variables.join(", ")}`);
|
|
12
|
+
}
|
|
13
|
+
return lines.join("\n");
|
|
14
|
+
}
|
|
3
15
|
export function registerContentTools(server, client) {
|
|
4
|
-
server.tool("
|
|
16
|
+
server.tool("template_create", "Create a new email content template with HTML and plain-text versions. Supports {{variable}} placeholders.", {
|
|
5
17
|
name: z.string().describe("Template name, e.g. 'welcome-email'"),
|
|
6
18
|
subject: z.string().describe("Email subject line"),
|
|
7
19
|
bodyHtml: z.string().describe("HTML email body. Use {{variable}} for placeholders"),
|
|
@@ -10,9 +22,9 @@ export function registerContentTools(server, client) {
|
|
|
10
22
|
description: z.string().optional().describe("Template description"),
|
|
11
23
|
}, async (params) => {
|
|
12
24
|
const data = await content.createTemplate(client, params);
|
|
13
|
-
return { content: [{ type: "text", text:
|
|
25
|
+
return { content: [{ type: "text", text: formatTemplateSummary(data) }] };
|
|
14
26
|
});
|
|
15
|
-
server.tool("
|
|
27
|
+
server.tool("template_list", "List all content templates for the current project", {
|
|
16
28
|
query: z.string().optional().describe("Search templates by name, slug, or description"),
|
|
17
29
|
isSystem: z.boolean().optional().describe("Filter: true for system templates, false for custom"),
|
|
18
30
|
}, async (params) => {
|
|
@@ -22,13 +34,13 @@ export function registerContentTools(server, client) {
|
|
|
22
34
|
const data = await content.listTemplates(client, listParams);
|
|
23
35
|
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
|
|
24
36
|
});
|
|
25
|
-
server.tool("
|
|
37
|
+
server.tool("template_show", "Get a content template by ID, including current version body and variables", {
|
|
26
38
|
templateId: z.string().describe("Template ID (tpl_ prefixed)"),
|
|
27
39
|
}, async (params) => {
|
|
28
40
|
const data = await content.getTemplate(client, params.templateId);
|
|
29
|
-
return { content: [{ type: "text", text:
|
|
41
|
+
return { content: [{ type: "text", text: formatTemplateSummary(data) }] };
|
|
30
42
|
});
|
|
31
|
-
server.tool("
|
|
43
|
+
server.tool("template_update", "Update a content template. Creates a new immutable version — previous versions are preserved for result tracking.", {
|
|
32
44
|
templateId: z.string().describe("Template ID (tpl_ prefixed)"),
|
|
33
45
|
subject: z.string().optional().describe("New email subject line"),
|
|
34
46
|
bodyHtml: z.string().optional().describe("New HTML email body"),
|
|
@@ -40,21 +52,21 @@ export function registerContentTools(server, client) {
|
|
|
40
52
|
}, async (params) => {
|
|
41
53
|
const { templateId, ...rest } = params;
|
|
42
54
|
const data = await content.updateTemplate(client, templateId, rest);
|
|
43
|
-
return { content: [{ type: "text", text:
|
|
55
|
+
return { content: [{ type: "text", text: formatTemplateSummary(data) }] };
|
|
44
56
|
});
|
|
45
|
-
server.tool("
|
|
57
|
+
server.tool("template_remove", "Delete a content template and all its versions", {
|
|
46
58
|
templateId: z.string().describe("Template ID (tpl_ prefixed)"),
|
|
47
59
|
}, async (params) => {
|
|
48
|
-
|
|
49
|
-
return { content: [{ type: "text", text:
|
|
60
|
+
await content.deleteTemplate(client, params.templateId);
|
|
61
|
+
return { content: [{ type: "text", text: `Removed template ${params.templateId}.` }] };
|
|
50
62
|
});
|
|
51
|
-
server.tool("
|
|
63
|
+
server.tool("template_version_list", "List all versions of a content template (newest first)", {
|
|
52
64
|
templateId: z.string().describe("Template ID (tpl_ prefixed)"),
|
|
53
65
|
}, async (params) => {
|
|
54
66
|
const data = await content.listVersions(client, params.templateId);
|
|
55
67
|
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
|
|
56
68
|
});
|
|
57
|
-
server.tool("
|
|
69
|
+
server.tool("template_version_show", "Get a specific version of a content template", {
|
|
58
70
|
templateId: z.string().describe("Template ID (tpl_ prefixed)"),
|
|
59
71
|
versionNumber: z.number().describe("Version number to retrieve"),
|
|
60
72
|
}, async (params) => {
|
package/dist/tools/email.js
CHANGED
|
@@ -1,23 +1,12 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import * as email from "../commands/email.js";
|
|
3
3
|
export function registerEmailTools(server, client) {
|
|
4
|
-
server.tool("
|
|
4
|
+
server.tool("email_identity_show", "Get email domain and sender identity settings for the current environment", {}, async () => {
|
|
5
5
|
const data = await email.getSettings(client);
|
|
6
6
|
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
|
|
7
7
|
});
|
|
8
|
-
server.tool("
|
|
9
|
-
fromEmail: z.string().optional().describe("Sender email address"),
|
|
10
|
-
fromName: z.string().optional().describe("Sender display name"),
|
|
11
|
-
}, async (params) => {
|
|
12
|
-
const data = await email.updateSettings(client, params);
|
|
13
|
-
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
|
|
14
|
-
});
|
|
15
|
-
server.tool("email_setup_domain", "Initiate domain verification for email sending. Returns DNS records, detected DNS provider details, and guided setup links.", {
|
|
8
|
+
server.tool("email_identity_setup_domain", "Initiate domain verification for email sending. Returns DNS records, detected DNS provider details, and guided setup links.", {
|
|
16
9
|
domain: z.string().describe("Domain to verify (e.g. 'myapp.com')"),
|
|
17
|
-
dnsProvider: z
|
|
18
|
-
.string()
|
|
19
|
-
.optional()
|
|
20
|
-
.describe("Optional preferred DNS provider hint, e.g. 'cloudflare'."),
|
|
21
10
|
}, async (params) => {
|
|
22
11
|
const data = await email.setupDomain(client, params.domain);
|
|
23
12
|
// Manual DNS setup — show records for copy-paste
|
|
@@ -36,22 +25,63 @@ export function registerEmailTools(server, client) {
|
|
|
36
25
|
return {
|
|
37
26
|
content: [{
|
|
38
27
|
type: "text",
|
|
39
|
-
text: `Domain ${params.domain} identity created in SES.\n\n${providerLine}${portalLine}${guidedLine}\nAdd these DNS records to your domain:\n${formatted}\n\nAfter adding the records, use
|
|
28
|
+
text: `Domain ${params.domain} identity created in SES.\n\n${providerLine}${portalLine}${guidedLine}\nAdd these DNS records to your domain:\n${formatted}\n\nAfter adding the records, use email_identity_status to poll for verification.`,
|
|
40
29
|
}],
|
|
41
30
|
};
|
|
42
31
|
});
|
|
43
|
-
server.tool("
|
|
32
|
+
server.tool("email_identity_check_dns", "Check the configured email DNS records against public DNS", {}, async () => {
|
|
33
|
+
const data = await email.checkDnsRecords(client);
|
|
34
|
+
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
|
|
35
|
+
});
|
|
36
|
+
server.tool("email_identity_dns_provider", "Detect DNS provider for a domain and return provider dashboard links", {
|
|
44
37
|
domain: z.string().optional().describe("Domain to detect; defaults to configured email domain"),
|
|
45
38
|
}, async (params) => {
|
|
46
39
|
const data = await email.getDnsProvider(client, params.domain);
|
|
47
40
|
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
|
|
48
41
|
});
|
|
49
|
-
server.tool("
|
|
42
|
+
server.tool("email_identity_status", "Poll SES verification status for your configured domain", {}, async () => {
|
|
50
43
|
const data = await email.checkDomainStatus(client);
|
|
51
44
|
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
|
|
52
45
|
});
|
|
53
|
-
server.tool("
|
|
46
|
+
server.tool("email_identity_remove_domain", "Remove domain and reset to default AscendKit sender", {}, async () => {
|
|
54
47
|
const data = await email.removeDomain(client);
|
|
55
48
|
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
|
|
56
49
|
});
|
|
50
|
+
server.tool("email_identity_list", "List sender identities for the current environment and refresh their SES verification status", {}, async () => {
|
|
51
|
+
const data = await email.listIdentities(client);
|
|
52
|
+
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
|
|
53
|
+
});
|
|
54
|
+
server.tool("email_identity_add", "Add a sender email identity and request SES inbox verification if needed", {
|
|
55
|
+
email: z.string().describe("Email identity to add, e.g. 'hello@myapp.com'"),
|
|
56
|
+
displayName: z.string().optional().describe("Optional display name for this sender"),
|
|
57
|
+
}, async (params) => {
|
|
58
|
+
const data = await email.createIdentity(client, params);
|
|
59
|
+
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
|
|
60
|
+
});
|
|
61
|
+
server.tool("email_identity_resend", "Request SES to resend inbox verification for a pending sender identity", {
|
|
62
|
+
email: z.string().describe("Sender identity email"),
|
|
63
|
+
}, async (params) => {
|
|
64
|
+
const data = await email.resendIdentityVerification(client, params.email);
|
|
65
|
+
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
|
|
66
|
+
});
|
|
67
|
+
server.tool("email_identity_set_default", "Set the default sender identity for this environment", {
|
|
68
|
+
email: z.string().describe("Sender identity email"),
|
|
69
|
+
displayName: z.string().optional().describe("Optional updated display name"),
|
|
70
|
+
}, async (params) => {
|
|
71
|
+
const data = await email.setDefaultIdentity(client, params);
|
|
72
|
+
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
|
|
73
|
+
});
|
|
74
|
+
server.tool("email_identity_remove", "Remove a sender identity from this environment", {
|
|
75
|
+
email: z.string().describe("Sender identity email"),
|
|
76
|
+
}, async (params) => {
|
|
77
|
+
const data = await email.removeIdentity(client, params.email);
|
|
78
|
+
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
|
|
79
|
+
});
|
|
80
|
+
server.tool("email_identity_test", "Send a test email from the selected verified identity", {
|
|
81
|
+
to: z.string().describe("Recipient address for the test email"),
|
|
82
|
+
fromIdentityEmail: z.string().optional().describe("Verified sender identity to use"),
|
|
83
|
+
}, async (params) => {
|
|
84
|
+
const data = await email.sendTestEmail(client, params);
|
|
85
|
+
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
|
|
86
|
+
});
|
|
57
87
|
}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import * as importCmd from "../commands/import.js";
|
|
3
|
+
export function registerImportTools(server, client) {
|
|
4
|
+
server.tool("import_clerk", "Import users from Clerk into the current AscendKit environment. Fetches all users from the Clerk API, transforms them to AscendKit format, and pushes them to the import endpoint. Tags users by auth type: import:needs-password-reset or import:social-only. Use mode='preview' for dry-run or mode='run' to apply changes.", {
|
|
5
|
+
mode: z
|
|
6
|
+
.enum(["preview", "run"])
|
|
7
|
+
.default("preview")
|
|
8
|
+
.describe("Use 'preview' for dry-run or 'run' to apply changes"),
|
|
9
|
+
clerkApiKey: z
|
|
10
|
+
.string()
|
|
11
|
+
.describe("Clerk secret API key (sk_live_... or sk_test_...)"),
|
|
12
|
+
clerkInstanceUrl: z
|
|
13
|
+
.string()
|
|
14
|
+
.optional()
|
|
15
|
+
.describe("Custom Clerk API URL (default: https://api.clerk.com)"),
|
|
16
|
+
importUsers: z
|
|
17
|
+
.boolean()
|
|
18
|
+
.optional()
|
|
19
|
+
.describe("Import users (default: true)"),
|
|
20
|
+
importSettings: z
|
|
21
|
+
.boolean()
|
|
22
|
+
.optional()
|
|
23
|
+
.describe("Import auth settings — OAuth provider config (default: true)"),
|
|
24
|
+
}, async (params) => {
|
|
25
|
+
try {
|
|
26
|
+
const dryRun = params.mode !== "run";
|
|
27
|
+
const shouldImportUsers = params.importUsers ?? true;
|
|
28
|
+
const shouldImportSettings = params.importSettings ?? true;
|
|
29
|
+
const rawUsers = await importCmd.fetchClerkUsers(params.clerkApiKey, params.clerkInstanceUrl);
|
|
30
|
+
const users = rawUsers.map(importCmd.transformClerkUser);
|
|
31
|
+
if (users.length === 0) {
|
|
32
|
+
return {
|
|
33
|
+
content: [{ type: "text", text: "No users found in Clerk." }],
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
let totalImported = 0;
|
|
37
|
+
let totalDuplicates = 0;
|
|
38
|
+
const allErrors = [];
|
|
39
|
+
const allWarnings = [];
|
|
40
|
+
// Import users in batches of 500
|
|
41
|
+
if (shouldImportUsers) {
|
|
42
|
+
const batchSize = 500;
|
|
43
|
+
for (let i = 0; i < users.length; i += batchSize) {
|
|
44
|
+
const batch = users.slice(i, i + batchSize);
|
|
45
|
+
const result = await importCmd.importUsers(client, {
|
|
46
|
+
source: "clerk",
|
|
47
|
+
users: batch,
|
|
48
|
+
dryRun,
|
|
49
|
+
});
|
|
50
|
+
totalImported += result.imported;
|
|
51
|
+
totalDuplicates += result.duplicates.length;
|
|
52
|
+
allErrors.push(...result.errors);
|
|
53
|
+
allWarnings.push(...result.warnings);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
// Import auth settings (OAuth provider config)
|
|
57
|
+
if (shouldImportSettings) {
|
|
58
|
+
const providers = new Set();
|
|
59
|
+
for (const u of users) {
|
|
60
|
+
for (const p of u.oauthProviders ?? []) {
|
|
61
|
+
providers.add(p.providerId);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
if (providers.size > 0) {
|
|
65
|
+
const settingsResult = await importCmd.importUsers(client, {
|
|
66
|
+
source: "clerk",
|
|
67
|
+
users: [],
|
|
68
|
+
authSettings: { enabledProviders: [...providers] },
|
|
69
|
+
dryRun,
|
|
70
|
+
});
|
|
71
|
+
allWarnings.push(...settingsResult.warnings);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
const summary = {
|
|
75
|
+
fetched: users.length,
|
|
76
|
+
dryRun,
|
|
77
|
+
importUsers: shouldImportUsers,
|
|
78
|
+
importSettings: shouldImportSettings,
|
|
79
|
+
};
|
|
80
|
+
if (shouldImportUsers) {
|
|
81
|
+
summary.imported = totalImported;
|
|
82
|
+
summary.duplicates = totalDuplicates;
|
|
83
|
+
}
|
|
84
|
+
if (allErrors.length > 0)
|
|
85
|
+
summary.errors = allErrors;
|
|
86
|
+
if (allWarnings.length > 0)
|
|
87
|
+
summary.warnings = allWarnings;
|
|
88
|
+
let text = JSON.stringify(summary, null, 2);
|
|
89
|
+
if (dryRun) {
|
|
90
|
+
text += "\n\nThis was a dry run. Use mode='run' to apply changes.";
|
|
91
|
+
}
|
|
92
|
+
else if (totalImported > 0) {
|
|
93
|
+
text += "\n\nTo set up migration emails, use the import_migration_journey_create tool.";
|
|
94
|
+
}
|
|
95
|
+
return { content: [{ type: "text", text }] };
|
|
96
|
+
}
|
|
97
|
+
catch (err) {
|
|
98
|
+
return {
|
|
99
|
+
content: [{
|
|
100
|
+
type: "text",
|
|
101
|
+
text: `Import failed: ${err instanceof Error ? err.message : String(err)}`,
|
|
102
|
+
}],
|
|
103
|
+
isError: true,
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
});
|
|
107
|
+
server.tool("import_migration_journey_create", "Create pre-built migration email templates and draft journeys for notifying imported users. Creates 5 email templates (announcement, go-live, reminder, password reset, password reset reminder) and 2 journeys: announcement cadence (all users, Day 0/3/7) and password reset cadence (credential users, Day 1/4). Idempotent — skips already-existing templates/journeys.", {
|
|
108
|
+
fromIdentityEmail: z
|
|
109
|
+
.string()
|
|
110
|
+
.optional()
|
|
111
|
+
.describe("Verified email identity to use as sender for migration emails"),
|
|
112
|
+
}, async (params) => {
|
|
113
|
+
const data = await importCmd.instantiateMigrationJourney(client, params.fromIdentityEmail);
|
|
114
|
+
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
|
|
115
|
+
});
|
|
116
|
+
}
|
package/dist/tools/journeys.js
CHANGED
|
@@ -3,7 +3,7 @@ import * as journeys from "../commands/journeys.js";
|
|
|
3
3
|
import { parseDelay } from "../utils/duration.js";
|
|
4
4
|
import { formatJourneyAnalytics, formatJourneyList, formatJourneyWithGuidance, formatNodeList, formatSingleNode, formatTransitionList, formatSingleTransition, } from "../utils/journey-format.js";
|
|
5
5
|
export function registerJourneyTools(server, client) {
|
|
6
|
-
server.tool("journey_create", "Create a new user lifecycle journey. Journeys start in 'draft' status. Minimal create: just name + entryEvent + entryNode (entry node auto-created as a 'none' action placeholder). Add nodes and transitions incrementally with
|
|
6
|
+
server.tool("journey_create", "Create a new user lifecycle journey. Journeys start in 'draft' status. Minimal create: just name + entryEvent + entryNode (entry node auto-created as a 'none' action placeholder). Add nodes and transitions incrementally with journey_node_add and journey_transition_add. Lifecycle: create → add nodes/transitions → activate → users enroll on entryEvent.", {
|
|
7
7
|
name: z.string().describe("Journey name, e.g. 'Onboarding Flow'"),
|
|
8
8
|
entryEvent: z
|
|
9
9
|
.string()
|
|
@@ -14,7 +14,7 @@ export function registerJourneyTools(server, client) {
|
|
|
14
14
|
nodes: z
|
|
15
15
|
.record(z.record(z.unknown()))
|
|
16
16
|
.optional()
|
|
17
|
-
.describe("Map of node names to definitions. Each node has 'action' ({type, templateSlug?, surveySlug?, tagName?, stageName?}) and optional 'terminal' (boolean). If omitted, the entry node is auto-created as a 'none' action placeholder."),
|
|
17
|
+
.describe("Map of node names to definitions. Each node has 'action' ({type, templateSlug?, surveySlug?, tagName?, stageName?, fromIdentityEmail?, variables?}) and optional 'terminal' (boolean). If omitted, the entry node is auto-created as a 'none' action placeholder."),
|
|
18
18
|
transitions: z
|
|
19
19
|
.array(z.record(z.unknown()))
|
|
20
20
|
.optional()
|
|
@@ -45,7 +45,7 @@ export function registerJourneyTools(server, client) {
|
|
|
45
45
|
const formatted = formatJourneyList(data);
|
|
46
46
|
return { content: [{ type: "text", text: formatted }] };
|
|
47
47
|
});
|
|
48
|
-
server.tool("
|
|
48
|
+
server.tool("journey_show", "Get a journey by ID with full definition (nodes, transitions) and current stats (enrolled, active, completed).", {
|
|
49
49
|
journeyId: z.string().describe("Journey ID (jrn_ prefixed)"),
|
|
50
50
|
}, async (params) => {
|
|
51
51
|
const data = await journeys.getJourney(client, params.journeyId);
|
|
@@ -69,7 +69,7 @@ export function registerJourneyTools(server, client) {
|
|
|
69
69
|
nodes: z
|
|
70
70
|
.record(z.record(z.unknown()))
|
|
71
71
|
.optional()
|
|
72
|
-
.describe("Updated node definitions"),
|
|
72
|
+
.describe("Updated node definitions. send_email actions may include fromIdentityEmail to override the environment default sender for that node."),
|
|
73
73
|
transitions: z
|
|
74
74
|
.array(z.record(z.unknown()))
|
|
75
75
|
.optional()
|
|
@@ -80,7 +80,7 @@ export function registerJourneyTools(server, client) {
|
|
|
80
80
|
const formatted = formatJourneyWithGuidance(data);
|
|
81
81
|
return { content: [{ type: "text", text: formatted }] };
|
|
82
82
|
});
|
|
83
|
-
server.tool("
|
|
83
|
+
server.tool("journey_remove", "Delete a journey and all associated user states. This is permanent.", {
|
|
84
84
|
journeyId: z.string().describe("Journey ID (jrn_ prefixed)"),
|
|
85
85
|
}, async (params) => {
|
|
86
86
|
await journeys.deleteJourney(client, params.journeyId);
|
|
@@ -89,13 +89,20 @@ export function registerJourneyTools(server, client) {
|
|
|
89
89
|
};
|
|
90
90
|
});
|
|
91
91
|
// --- Lifecycle tools ---
|
|
92
|
-
server.tool("journey_activate", "Activate a journey (draft → active). Once active, users are automatically enrolled when the entry event fires.", {
|
|
92
|
+
server.tool("journey_activate", "Activate a draft journey (draft → active). Once active, users are automatically enrolled when the entry event fires.", {
|
|
93
93
|
journeyId: z.string().describe("Journey ID (jrn_ prefixed)"),
|
|
94
94
|
}, async (params) => {
|
|
95
95
|
const data = await journeys.activateJourney(client, params.journeyId);
|
|
96
96
|
const formatted = formatJourneyWithGuidance(data);
|
|
97
97
|
return { content: [{ type: "text", text: formatted }] };
|
|
98
98
|
});
|
|
99
|
+
server.tool("journey_resume", "Resume a paused journey (paused → active). Transitions continue while paused, and queued actions are drained on resume.", {
|
|
100
|
+
journeyId: z.string().describe("Journey ID (jrn_ prefixed)"),
|
|
101
|
+
}, async (params) => {
|
|
102
|
+
const data = await journeys.resumeJourney(client, params.journeyId);
|
|
103
|
+
const formatted = formatJourneyWithGuidance(data);
|
|
104
|
+
return { content: [{ type: "text", text: formatted }] };
|
|
105
|
+
});
|
|
99
106
|
server.tool("journey_pause", "Pause an active journey. Users stay at their current nodes but actions are queued instead of executed. Transitions still occur.", {
|
|
100
107
|
journeyId: z.string().describe("Journey ID (jrn_ prefixed)"),
|
|
101
108
|
}, async (params) => {
|
|
@@ -119,14 +126,14 @@ export function registerJourneyTools(server, client) {
|
|
|
119
126
|
return { content: [{ type: "text", text: formatted }] };
|
|
120
127
|
});
|
|
121
128
|
// --- Granular node tools ---
|
|
122
|
-
server.tool("
|
|
129
|
+
server.tool("journey_node_list", "List all nodes in a journey with their actions, terminal status, and transition counts. Use this to inspect the journey graph before making changes.", {
|
|
123
130
|
journeyId: z.string().describe("Journey ID (jrn_ prefixed)"),
|
|
124
131
|
}, async (params) => {
|
|
125
132
|
const data = await journeys.listNodes(client, params.journeyId);
|
|
126
133
|
const formatted = formatNodeList(data);
|
|
127
134
|
return { content: [{ type: "text", text: formatted }] };
|
|
128
135
|
});
|
|
129
|
-
server.tool("
|
|
136
|
+
server.tool("journey_node_add", "Add a node to a journey. Nodes define what happens at each step: send_email (with optional surveySlug), tag_user, advance_stage, or none (wait/branching). Node name is the stable identifier used in transitions.", {
|
|
130
137
|
journeyId: z.string().describe("Journey ID (jrn_ prefixed)"),
|
|
131
138
|
name: z
|
|
132
139
|
.string()
|
|
@@ -138,6 +145,8 @@ export function registerJourneyTools(server, client) {
|
|
|
138
145
|
surveySlug: z.string().optional().describe("Survey slug to include in email (for send_email)"),
|
|
139
146
|
tagName: z.string().optional().describe("Tag to add (for tag_user)"),
|
|
140
147
|
stageName: z.string().optional().describe("Stage to set (for advance_stage)"),
|
|
148
|
+
fromIdentityEmail: z.string().optional().describe("Verified email identity to use when this node sends email"),
|
|
149
|
+
variables: z.record(z.string()).optional().describe("Custom template variables for this node's email (e.g. {loginUrl: 'https://...', resetUrl: 'https://...'})"),
|
|
141
150
|
})
|
|
142
151
|
.optional()
|
|
143
152
|
.describe("Action to execute when a user enters this node. Defaults to {type: 'none'}."),
|
|
@@ -151,7 +160,7 @@ export function registerJourneyTools(server, client) {
|
|
|
151
160
|
const formatted = formatSingleNode(data, "Added", params.name);
|
|
152
161
|
return { content: [{ type: "text", text: formatted }] };
|
|
153
162
|
});
|
|
154
|
-
server.tool("
|
|
163
|
+
server.tool("journey_node_update", "Update a node's action or terminal flag by name. Only provided fields are changed. To clear an action, set type to 'none'. Removing templateSlug also removes surveySlug (survey rides on email).", {
|
|
155
164
|
journeyId: z.string().describe("Journey ID (jrn_ prefixed)"),
|
|
156
165
|
nodeName: z.string().describe("Name of the node to edit"),
|
|
157
166
|
action: z
|
|
@@ -161,6 +170,8 @@ export function registerJourneyTools(server, client) {
|
|
|
161
170
|
surveySlug: z.string().optional().describe("Survey slug to include in email"),
|
|
162
171
|
tagName: z.string().optional().describe("Tag to add"),
|
|
163
172
|
stageName: z.string().optional().describe("Stage to set"),
|
|
173
|
+
fromIdentityEmail: z.string().optional().describe("Verified email identity to use when this node sends email"),
|
|
174
|
+
variables: z.record(z.string()).optional().describe("Custom template variables for this node's email (e.g. {loginUrl: 'https://...', resetUrl: 'https://...'})"),
|
|
164
175
|
})
|
|
165
176
|
.optional()
|
|
166
177
|
.describe("New action definition (replaces the entire action)"),
|
|
@@ -174,7 +185,7 @@ export function registerJourneyTools(server, client) {
|
|
|
174
185
|
const formatted = formatSingleNode(data, "Updated", nodeName);
|
|
175
186
|
return { content: [{ type: "text", text: formatted }] };
|
|
176
187
|
});
|
|
177
|
-
server.tool("
|
|
188
|
+
server.tool("journey_node_remove", "Remove a node from a journey by name. All transitions to/from this node are automatically deleted (cascade). Users currently at this node are exited. This is permanent.", {
|
|
178
189
|
journeyId: z.string().describe("Journey ID (jrn_ prefixed)"),
|
|
179
190
|
nodeName: z.string().describe("Name of the node to remove"),
|
|
180
191
|
}, async (params) => {
|
|
@@ -183,7 +194,7 @@ export function registerJourneyTools(server, client) {
|
|
|
183
194
|
return { content: [{ type: "text", text: formatted }] };
|
|
184
195
|
});
|
|
185
196
|
// --- Granular transition tools ---
|
|
186
|
-
server.tool("
|
|
197
|
+
server.tool("journey_transition_list", "List all transitions in a journey. Shows 'from → to' with trigger type (on event / after delay). Filterable by source or target node.", {
|
|
187
198
|
journeyId: z.string().describe("Journey ID (jrn_ prefixed)"),
|
|
188
199
|
from_node: z.string().optional().describe("Filter by source node name"),
|
|
189
200
|
to_node: z.string().optional().describe("Filter by target node name"),
|
|
@@ -193,7 +204,7 @@ export function registerJourneyTools(server, client) {
|
|
|
193
204
|
const formatted = formatTransitionList(data);
|
|
194
205
|
return { content: [{ type: "text", text: formatted }] };
|
|
195
206
|
});
|
|
196
|
-
server.tool("
|
|
207
|
+
server.tool("journey_transition_add", "Add a transition between two nodes. Use 'on' for event triggers (e.g. on: 'user.login') or 'after' for delays (e.g. after: '3 day', '12 hr', '30 m', '2 week'). Bare numbers default to minutes. Also accepts the structured 'trigger' object. Transition name is auto-generated from '{from}-to-{to}' if not provided.", {
|
|
197
208
|
journeyId: z.string().describe("Journey ID (jrn_ prefixed)"),
|
|
198
209
|
from: z.string().describe("Source node name"),
|
|
199
210
|
to: z.string().describe("Target node name"),
|
|
@@ -248,7 +259,7 @@ export function registerJourneyTools(server, client) {
|
|
|
248
259
|
const formatted = formatSingleTransition(data, "Added", transitionName);
|
|
249
260
|
return { content: [{ type: "text", text: formatted }] };
|
|
250
261
|
});
|
|
251
|
-
server.tool("
|
|
262
|
+
server.tool("journey_transition_update", "Update a transition's trigger or priority by name. Use 'on' for event triggers or 'after' for delays (e.g. '3 day', '12 hr', '30 m'). Also accepts the structured 'trigger' object. Use journey_transition_list to find transition names.", {
|
|
252
263
|
journeyId: z.string().describe("Journey ID (jrn_ prefixed)"),
|
|
253
264
|
transitionName: z.string().describe("Name of the transition to edit"),
|
|
254
265
|
on: z
|
|
@@ -291,7 +302,7 @@ export function registerJourneyTools(server, client) {
|
|
|
291
302
|
const formatted = formatSingleTransition(data, "Updated", transitionName);
|
|
292
303
|
return { content: [{ type: "text", text: formatted }] };
|
|
293
304
|
});
|
|
294
|
-
server.tool("
|
|
305
|
+
server.tool("journey_transition_remove", "Remove a transition from a journey by name. This is permanent. Use journey_transition_list to find transition names.", {
|
|
295
306
|
journeyId: z.string().describe("Journey ID (jrn_ prefixed)"),
|
|
296
307
|
transitionName: z.string().describe("Name of the transition to remove"),
|
|
297
308
|
}, async (params) => {
|