@ascendkit/cli 0.2.6 → 0.3.1
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 +1 -0
- package/dist/api/client.js +36 -9
- package/dist/cli.js +869 -474
- 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/journeys.d.ts +1 -0
- package/dist/commands/journeys.js +9 -6
- package/dist/commands/platform.d.ts +22 -2
- package/dist/commands/platform.js +211 -103
- package/dist/commands/surveys.js +5 -5
- package/dist/mcp.js +31 -3
- package/dist/tools/auth.js +32 -11
- package/dist/tools/content.js +24 -12
- package/dist/tools/email.js +47 -17
- package/dist/tools/import.js +9 -9
- package/dist/tools/journeys.js +21 -14
- package/dist/tools/platform.js +125 -10
- package/dist/tools/surveys.js +9 -9
- package/dist/utils/correlation.d.ts +11 -0
- package/dist/utils/correlation.js +67 -0
- package/dist/utils/exit.d.ts +23 -0
- package/dist/utils/exit.js +64 -0
- package/dist/utils/journey-format.d.ts +6 -0
- package/dist/utils/journey-format.js +6 -4
- package/dist/utils/redaction.d.ts +16 -0
- package/dist/utils/redaction.js +114 -0
- package/dist/utils/survey-format.js +2 -2
- package/dist/utils/telemetry.d.ts +32 -0
- package/dist/utils/telemetry.js +47 -0
- package/package.json +5 -5
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
|
}
|
package/dist/tools/import.js
CHANGED
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import * as importCmd from "../commands/import.js";
|
|
3
3
|
export function registerImportTools(server, client) {
|
|
4
|
-
server.tool("
|
|
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"),
|
|
5
9
|
clerkApiKey: z
|
|
6
10
|
.string()
|
|
7
11
|
.describe("Clerk secret API key (sk_live_... or sk_test_...)"),
|
|
@@ -9,10 +13,6 @@ export function registerImportTools(server, client) {
|
|
|
9
13
|
.string()
|
|
10
14
|
.optional()
|
|
11
15
|
.describe("Custom Clerk API URL (default: https://api.clerk.com)"),
|
|
12
|
-
execute: z
|
|
13
|
-
.boolean()
|
|
14
|
-
.optional()
|
|
15
|
-
.describe("Set to true to apply changes. Default is dry-run (preview only)."),
|
|
16
16
|
importUsers: z
|
|
17
17
|
.boolean()
|
|
18
18
|
.optional()
|
|
@@ -23,7 +23,7 @@ export function registerImportTools(server, client) {
|
|
|
23
23
|
.describe("Import auth settings — OAuth provider config (default: true)"),
|
|
24
24
|
}, async (params) => {
|
|
25
25
|
try {
|
|
26
|
-
const dryRun =
|
|
26
|
+
const dryRun = params.mode !== "run";
|
|
27
27
|
const shouldImportUsers = params.importUsers ?? true;
|
|
28
28
|
const shouldImportSettings = params.importSettings ?? true;
|
|
29
29
|
const rawUsers = await importCmd.fetchClerkUsers(params.clerkApiKey, params.clerkInstanceUrl);
|
|
@@ -87,10 +87,10 @@ export function registerImportTools(server, client) {
|
|
|
87
87
|
summary.warnings = allWarnings;
|
|
88
88
|
let text = JSON.stringify(summary, null, 2);
|
|
89
89
|
if (dryRun) {
|
|
90
|
-
text += "\n\nThis was a dry run.
|
|
90
|
+
text += "\n\nThis was a dry run. Use mode='run' to apply changes.";
|
|
91
91
|
}
|
|
92
92
|
else if (totalImported > 0) {
|
|
93
|
-
text += "\n\nTo set up migration emails, use the
|
|
93
|
+
text += "\n\nTo set up migration emails, use the import_migration_journey_create tool.";
|
|
94
94
|
}
|
|
95
95
|
return { content: [{ type: "text", text }] };
|
|
96
96
|
}
|
|
@@ -104,7 +104,7 @@ export function registerImportTools(server, client) {
|
|
|
104
104
|
};
|
|
105
105
|
}
|
|
106
106
|
});
|
|
107
|
-
server.tool("
|
|
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
108
|
fromIdentityEmail: z
|
|
109
109
|
.string()
|
|
110
110
|
.optional()
|
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?, variables?}) 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()
|
|
@@ -153,7 +160,7 @@ export function registerJourneyTools(server, client) {
|
|
|
153
160
|
const formatted = formatSingleNode(data, "Added", params.name);
|
|
154
161
|
return { content: [{ type: "text", text: formatted }] };
|
|
155
162
|
});
|
|
156
|
-
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).", {
|
|
157
164
|
journeyId: z.string().describe("Journey ID (jrn_ prefixed)"),
|
|
158
165
|
nodeName: z.string().describe("Name of the node to edit"),
|
|
159
166
|
action: z
|
|
@@ -178,7 +185,7 @@ export function registerJourneyTools(server, client) {
|
|
|
178
185
|
const formatted = formatSingleNode(data, "Updated", nodeName);
|
|
179
186
|
return { content: [{ type: "text", text: formatted }] };
|
|
180
187
|
});
|
|
181
|
-
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.", {
|
|
182
189
|
journeyId: z.string().describe("Journey ID (jrn_ prefixed)"),
|
|
183
190
|
nodeName: z.string().describe("Name of the node to remove"),
|
|
184
191
|
}, async (params) => {
|
|
@@ -187,7 +194,7 @@ export function registerJourneyTools(server, client) {
|
|
|
187
194
|
return { content: [{ type: "text", text: formatted }] };
|
|
188
195
|
});
|
|
189
196
|
// --- Granular transition tools ---
|
|
190
|
-
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.", {
|
|
191
198
|
journeyId: z.string().describe("Journey ID (jrn_ prefixed)"),
|
|
192
199
|
from_node: z.string().optional().describe("Filter by source node name"),
|
|
193
200
|
to_node: z.string().optional().describe("Filter by target node name"),
|
|
@@ -197,7 +204,7 @@ export function registerJourneyTools(server, client) {
|
|
|
197
204
|
const formatted = formatTransitionList(data);
|
|
198
205
|
return { content: [{ type: "text", text: formatted }] };
|
|
199
206
|
});
|
|
200
|
-
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.", {
|
|
201
208
|
journeyId: z.string().describe("Journey ID (jrn_ prefixed)"),
|
|
202
209
|
from: z.string().describe("Source node name"),
|
|
203
210
|
to: z.string().describe("Target node name"),
|
|
@@ -252,7 +259,7 @@ export function registerJourneyTools(server, client) {
|
|
|
252
259
|
const formatted = formatSingleTransition(data, "Added", transitionName);
|
|
253
260
|
return { content: [{ type: "text", text: formatted }] };
|
|
254
261
|
});
|
|
255
|
-
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.", {
|
|
256
263
|
journeyId: z.string().describe("Journey ID (jrn_ prefixed)"),
|
|
257
264
|
transitionName: z.string().describe("Name of the transition to edit"),
|
|
258
265
|
on: z
|
|
@@ -295,7 +302,7 @@ export function registerJourneyTools(server, client) {
|
|
|
295
302
|
const formatted = formatSingleTransition(data, "Updated", transitionName);
|
|
296
303
|
return { content: [{ type: "text", text: formatted }] };
|
|
297
304
|
});
|
|
298
|
-
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.", {
|
|
299
306
|
journeyId: z.string().describe("Journey ID (jrn_ prefixed)"),
|
|
300
307
|
transitionName: z.string().describe("Name of the transition to remove"),
|
|
301
308
|
}, async (params) => {
|
package/dist/tools/platform.js
CHANGED
|
@@ -15,13 +15,47 @@ export function registerPlatformTools(server, client) {
|
|
|
15
15
|
],
|
|
16
16
|
};
|
|
17
17
|
});
|
|
18
|
-
server.tool("
|
|
18
|
+
server.tool("ascendkit_set_env", "Set the active environment by public key and persist the shared .ascendkit environment context used by both MCP and CLI.", {
|
|
19
|
+
publicKey: z.string().describe("Environment public key (pk_dev_..., pk_beta_..., or pk_prod_...)"),
|
|
20
|
+
}, async (params) => {
|
|
21
|
+
try {
|
|
22
|
+
const data = await platform.mcpSetEnvironmentByPublicKey(client, params.publicKey);
|
|
23
|
+
return {
|
|
24
|
+
content: [{
|
|
25
|
+
type: "text",
|
|
26
|
+
text: `Active environment: ${data.environment.name} (${data.environment.tier})\n` +
|
|
27
|
+
`Project: ${data.project.name}\n` +
|
|
28
|
+
`Public key: ${data.environment.publicKey}\n` +
|
|
29
|
+
`Updated env files: ${Array.isArray(data.updatedFiles) && data.updatedFiles.length > 0 ? data.updatedFiles.join(", ") : "(none)"}`,
|
|
30
|
+
}],
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
catch (err) {
|
|
34
|
+
let message = err instanceof Error ? err.message : String(err);
|
|
35
|
+
const jsonMatch = message.match(/\{.*\}/s);
|
|
36
|
+
if (jsonMatch) {
|
|
37
|
+
try {
|
|
38
|
+
const parsed = JSON.parse(jsonMatch[0]);
|
|
39
|
+
if (parsed.error)
|
|
40
|
+
message = parsed.error;
|
|
41
|
+
else if (parsed.detail)
|
|
42
|
+
message = parsed.detail;
|
|
43
|
+
}
|
|
44
|
+
catch { /* use raw message */ }
|
|
45
|
+
}
|
|
46
|
+
return {
|
|
47
|
+
content: [{ type: "text", text: message }],
|
|
48
|
+
isError: true,
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
server.tool("project_list", "List all projects in your organization", {}, async () => {
|
|
19
53
|
const data = await platform.mcpListProjects(client);
|
|
20
54
|
return {
|
|
21
55
|
content: [{ type: "text", text: JSON.stringify(data, null, 2) }],
|
|
22
56
|
};
|
|
23
57
|
});
|
|
24
|
-
server.tool("
|
|
58
|
+
server.tool("project_create", "Create a new project in your organization", {
|
|
25
59
|
name: z.string().describe("Project name"),
|
|
26
60
|
description: z.string().optional().describe("Project description"),
|
|
27
61
|
enabledServices: z
|
|
@@ -54,7 +88,7 @@ export function registerPlatformTools(server, client) {
|
|
|
54
88
|
};
|
|
55
89
|
}
|
|
56
90
|
});
|
|
57
|
-
server.tool("
|
|
91
|
+
server.tool("env_list", "List environments for a project", {
|
|
58
92
|
projectId: z.string().describe("Project ID (prj_ prefixed)"),
|
|
59
93
|
}, async (params) => {
|
|
60
94
|
const data = await platform.mcpListEnvironments(client, params.projectId);
|
|
@@ -62,7 +96,7 @@ export function registerPlatformTools(server, client) {
|
|
|
62
96
|
content: [{ type: "text", text: JSON.stringify(data, null, 2) }],
|
|
63
97
|
};
|
|
64
98
|
});
|
|
65
|
-
server.tool("
|
|
99
|
+
server.tool("env_create", "Create a new environment for a project. Returns the public key needed for SDK/CLI configuration.", {
|
|
66
100
|
projectId: z.string().describe("Project ID (prj_ prefixed)"),
|
|
67
101
|
name: z
|
|
68
102
|
.string()
|
|
@@ -101,7 +135,7 @@ export function registerPlatformTools(server, client) {
|
|
|
101
135
|
};
|
|
102
136
|
}
|
|
103
137
|
});
|
|
104
|
-
server.tool("
|
|
138
|
+
server.tool("env_update", "Update an environment's name or description.", {
|
|
105
139
|
projectId: z.string().describe("Project ID (prj_ prefixed)"),
|
|
106
140
|
environmentId: z.string().describe("Environment ID (env_ prefixed)"),
|
|
107
141
|
name: z.string().optional().describe("New environment name"),
|
|
@@ -138,7 +172,7 @@ export function registerPlatformTools(server, client) {
|
|
|
138
172
|
};
|
|
139
173
|
}
|
|
140
174
|
});
|
|
141
|
-
server.tool("
|
|
175
|
+
server.tool("env_promote", "Promote an environment's configuration to a higher tier (dev → beta → prod).", {
|
|
142
176
|
environmentId: z.string().describe("Environment ID to promote"),
|
|
143
177
|
targetTier: z
|
|
144
178
|
.string()
|
|
@@ -172,10 +206,39 @@ export function registerPlatformTools(server, client) {
|
|
|
172
206
|
};
|
|
173
207
|
}
|
|
174
208
|
});
|
|
175
|
-
server.tool("
|
|
176
|
-
projectId: z.string().describe("
|
|
177
|
-
envId: z.string().describe("
|
|
178
|
-
|
|
209
|
+
server.tool("keystore_list", "List keystore values for the active environment. Uses the shared .ascendkit environment context by default.", {
|
|
210
|
+
projectId: z.string().optional().describe("Optional project ID override (defaults to active environment project)"),
|
|
211
|
+
envId: z.string().optional().describe("Optional environment ID override (defaults to active environment)"),
|
|
212
|
+
}, async (params) => {
|
|
213
|
+
try {
|
|
214
|
+
const env = await platform.mcpGetEnvironment(client, params);
|
|
215
|
+
const variables = (env.variables ?? {});
|
|
216
|
+
const entries = Object.entries(variables);
|
|
217
|
+
const systemVariables = Array.isArray(env.systemVariables)
|
|
218
|
+
? env.systemVariables
|
|
219
|
+
: [];
|
|
220
|
+
const sections = [];
|
|
221
|
+
sections.push(entries.length === 0
|
|
222
|
+
? "Custom variables:\n(none)"
|
|
223
|
+
: `Custom variables:\n${entries.map(([key, value]) => `${key}=${value}`).join("\n")}`);
|
|
224
|
+
if (systemVariables.length > 0) {
|
|
225
|
+
sections.push(`System variables:\n${systemVariables
|
|
226
|
+
.map((item) => `${String(item.key)} = ${String(item.valuePreview)} (${String(item.availability)})`)
|
|
227
|
+
.join("\n")}`);
|
|
228
|
+
}
|
|
229
|
+
return {
|
|
230
|
+
content: [{ type: "text", text: sections.join("\n\n") }],
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
catch (err) {
|
|
234
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
235
|
+
return { content: [{ type: "text", text: message }], isError: true };
|
|
236
|
+
}
|
|
237
|
+
});
|
|
238
|
+
server.tool("keystore_replace", "Replace the full keystore for the active environment. Pass the full key-value map.", {
|
|
239
|
+
projectId: z.string().optional().describe("Optional project ID override (defaults to active environment project)"),
|
|
240
|
+
envId: z.string().optional().describe("Optional environment ID override (defaults to active environment)"),
|
|
241
|
+
variables: z.record(z.string()).describe("Key-value map of keystore values"),
|
|
179
242
|
}, async (params) => {
|
|
180
243
|
try {
|
|
181
244
|
const data = await platform.mcpUpdateEnvironmentVariables(client, params);
|
|
@@ -202,4 +265,56 @@ export function registerPlatformTools(server, client) {
|
|
|
202
265
|
};
|
|
203
266
|
}
|
|
204
267
|
});
|
|
268
|
+
server.tool("keystore_set", "Set a single keystore key for the active environment.", {
|
|
269
|
+
projectId: z.string().optional().describe("Optional project ID override (defaults to active environment project)"),
|
|
270
|
+
envId: z.string().optional().describe("Optional environment ID override (defaults to active environment)"),
|
|
271
|
+
key: z.string().describe("Keystore key"),
|
|
272
|
+
value: z.string().describe("Keystore value"),
|
|
273
|
+
}, async (params) => {
|
|
274
|
+
try {
|
|
275
|
+
const env = await platform.mcpGetEnvironment(client, {
|
|
276
|
+
projectId: params.projectId,
|
|
277
|
+
envId: params.envId,
|
|
278
|
+
});
|
|
279
|
+
const variables = { ...(env.variables ?? {}), [params.key]: params.value };
|
|
280
|
+
const data = await platform.mcpUpdateEnvironmentVariables(client, {
|
|
281
|
+
projectId: params.projectId,
|
|
282
|
+
envId: params.envId,
|
|
283
|
+
variables,
|
|
284
|
+
});
|
|
285
|
+
return {
|
|
286
|
+
content: [{ type: "text", text: `Saved ${params.key}=${params.value}\n\n${JSON.stringify(data, null, 2)}` }],
|
|
287
|
+
};
|
|
288
|
+
}
|
|
289
|
+
catch (err) {
|
|
290
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
291
|
+
return { content: [{ type: "text", text: message }], isError: true };
|
|
292
|
+
}
|
|
293
|
+
});
|
|
294
|
+
server.tool("keystore_remove", "Remove a single keystore key from the active environment.", {
|
|
295
|
+
projectId: z.string().optional().describe("Optional project ID override (defaults to active environment project)"),
|
|
296
|
+
envId: z.string().optional().describe("Optional environment ID override (defaults to active environment)"),
|
|
297
|
+
key: z.string().describe("Keystore key to remove"),
|
|
298
|
+
}, async (params) => {
|
|
299
|
+
try {
|
|
300
|
+
const env = await platform.mcpGetEnvironment(client, {
|
|
301
|
+
projectId: params.projectId,
|
|
302
|
+
envId: params.envId,
|
|
303
|
+
});
|
|
304
|
+
const variables = { ...(env.variables ?? {}) };
|
|
305
|
+
delete variables[params.key];
|
|
306
|
+
const data = await platform.mcpUpdateEnvironmentVariables(client, {
|
|
307
|
+
projectId: params.projectId,
|
|
308
|
+
envId: params.envId,
|
|
309
|
+
variables,
|
|
310
|
+
});
|
|
311
|
+
return {
|
|
312
|
+
content: [{ type: "text", text: `Removed ${params.key}\n\n${JSON.stringify(data, null, 2)}` }],
|
|
313
|
+
};
|
|
314
|
+
}
|
|
315
|
+
catch (err) {
|
|
316
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
317
|
+
return { content: [{ type: "text", text: message }], isError: true };
|
|
318
|
+
}
|
|
319
|
+
});
|
|
205
320
|
}
|
package/dist/tools/surveys.js
CHANGED
|
@@ -2,7 +2,7 @@ import { z } from "zod";
|
|
|
2
2
|
import * as surveys from "../commands/surveys.js";
|
|
3
3
|
import { formatDistributionResult, formatQuestionList, formatSingleQuestion, formatSurveyList, formatSurveyWithGuidance, } from "../utils/survey-format.js";
|
|
4
4
|
export function registerSurveyTools(server, client) {
|
|
5
|
-
server.tool("survey_create", "Create a new survey. Use type 'nps' or 'csat' for ready-to-use presets with standard questions, or 'custom' to build from scratch with
|
|
5
|
+
server.tool("survey_create", "Create a new survey. Use type 'nps' or 'csat' for ready-to-use presets with standard questions, or 'custom' to build from scratch with survey_question_add. Survey lifecycle: create → add questions → activate → distribute → collect → analyze.", {
|
|
6
6
|
name: z.string().describe("Survey name, e.g. 'Q1 NPS Survey'"),
|
|
7
7
|
type: z
|
|
8
8
|
.enum(["nps", "csat", "custom"])
|
|
@@ -22,7 +22,7 @@ export function registerSurveyTools(server, client) {
|
|
|
22
22
|
const formatted = formatSurveyList(data);
|
|
23
23
|
return { content: [{ type: "text", text: formatted }] };
|
|
24
24
|
});
|
|
25
|
-
server.tool("
|
|
25
|
+
server.tool("survey_show", "Get a survey by ID with status, question count, response count, and next-step guidance.", {
|
|
26
26
|
surveyId: z.string().describe("Survey ID (srv_ prefixed)"),
|
|
27
27
|
}, async (params) => {
|
|
28
28
|
const data = await surveys.getSurvey(client, params.surveyId);
|
|
@@ -46,7 +46,7 @@ export function registerSurveyTools(server, client) {
|
|
|
46
46
|
const formatted = formatSurveyWithGuidance(data);
|
|
47
47
|
return { content: [{ type: "text", text: formatted }] };
|
|
48
48
|
});
|
|
49
|
-
server.tool("
|
|
49
|
+
server.tool("survey_remove", "Delete a survey and all its invitations and responses", {
|
|
50
50
|
surveyId: z.string().describe("Survey ID (srv_ prefixed)"),
|
|
51
51
|
}, async (params) => {
|
|
52
52
|
const data = await surveys.deleteSurvey(client, params.surveyId);
|
|
@@ -62,7 +62,7 @@ export function registerSurveyTools(server, client) {
|
|
|
62
62
|
const formatted = formatDistributionResult(data);
|
|
63
63
|
return { content: [{ type: "text", text: formatted }] };
|
|
64
64
|
});
|
|
65
|
-
server.tool("
|
|
65
|
+
server.tool("survey_invitation_list", "List all invitations for a survey with their status (sent/opened/submitted)", {
|
|
66
66
|
surveyId: z.string().describe("Survey ID (srv_ prefixed)"),
|
|
67
67
|
}, async (params) => {
|
|
68
68
|
const data = await surveys.listInvitations(client, params.surveyId);
|
|
@@ -75,14 +75,14 @@ export function registerSurveyTools(server, client) {
|
|
|
75
75
|
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
|
|
76
76
|
});
|
|
77
77
|
// --- Question-level management tools ---
|
|
78
|
-
server.tool("
|
|
78
|
+
server.tool("survey_question_list", "List all questions in a survey in human-readable format. Shows question number, type, title, required status, choices, and any conditional logic.", {
|
|
79
79
|
surveyId: z.string().describe("Survey ID (srv_ prefixed)"),
|
|
80
80
|
}, async (params) => {
|
|
81
81
|
const data = await surveys.listQuestions(client, params.surveyId);
|
|
82
82
|
const formatted = formatQuestionList(data);
|
|
83
83
|
return { content: [{ type: "text", text: formatted }] };
|
|
84
84
|
});
|
|
85
|
-
server.tool("
|
|
85
|
+
server.tool("survey_question_add", "Add a question to a survey. Supported types: text (short text), comment (long text/multi-line), radiogroup (single choice), checkbox (multiple choice), rating (scale — use rateMin:0 rateMax:10 for NPS), boolean (yes/no), dropdown, ranking. For date input, use type 'text' with inputType 'date'. Question name is auto-generated from title if not provided.", {
|
|
86
86
|
surveyId: z.string().describe("Survey ID (srv_ prefixed)"),
|
|
87
87
|
type: z
|
|
88
88
|
.enum([
|
|
@@ -147,7 +147,7 @@ export function registerSurveyTools(server, client) {
|
|
|
147
147
|
const formatted = formatSingleQuestion(data, "Added");
|
|
148
148
|
return { content: [{ type: "text", text: formatted }] };
|
|
149
149
|
});
|
|
150
|
-
server.tool("
|
|
150
|
+
server.tool("survey_question_update", "Edit an existing question in a survey by its name. Only provided fields are updated; others remain unchanged. Pass empty string for visibleIf/requiredIf to clear conditional logic.", {
|
|
151
151
|
surveyId: z.string().describe("Survey ID (srv_ prefixed)"),
|
|
152
152
|
questionName: z.string().describe("Name of the question to edit"),
|
|
153
153
|
title: z.string().optional().describe("New question text"),
|
|
@@ -181,7 +181,7 @@ export function registerSurveyTools(server, client) {
|
|
|
181
181
|
const formatted = formatSingleQuestion(data, "Updated");
|
|
182
182
|
return { content: [{ type: "text", text: formatted }] };
|
|
183
183
|
});
|
|
184
|
-
server.tool("
|
|
184
|
+
server.tool("survey_question_remove", "Remove a question from a survey by its name. This is permanent — the question and its configuration are deleted from the survey definition.", {
|
|
185
185
|
surveyId: z.string().describe("Survey ID (srv_ prefixed)"),
|
|
186
186
|
questionName: z.string().describe("Name of the question to remove"),
|
|
187
187
|
}, async (params) => {
|
|
@@ -195,7 +195,7 @@ export function registerSurveyTools(server, client) {
|
|
|
195
195
|
],
|
|
196
196
|
};
|
|
197
197
|
});
|
|
198
|
-
server.tool("
|
|
198
|
+
server.tool("survey_question_reorder", "Reorder questions in a survey by providing all question names in the desired order. All existing question names must be included.", {
|
|
199
199
|
surveyId: z.string().describe("Survey ID (srv_ prefixed)"),
|
|
200
200
|
order: z
|
|
201
201
|
.array(z.string())
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Correlation IDs for request tracing.
|
|
3
|
+
*
|
|
4
|
+
* - `invocationId` — one UUID per CLI process (lazy-initialized)
|
|
5
|
+
* - `clientRequestId` — fresh UUID per outbound HTTP request
|
|
6
|
+
* - `machineId` — persisted at ~/.ascendkit/machine-id, generated once
|
|
7
|
+
*/
|
|
8
|
+
export declare function getInvocationId(): string;
|
|
9
|
+
export declare function generateClientRequestId(): string;
|
|
10
|
+
export declare function correlationHeaders(): Record<string, string>;
|
|
11
|
+
export declare function getMachineId(): string;
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Correlation IDs for request tracing.
|
|
3
|
+
*
|
|
4
|
+
* - `invocationId` — one UUID per CLI process (lazy-initialized)
|
|
5
|
+
* - `clientRequestId` — fresh UUID per outbound HTTP request
|
|
6
|
+
* - `machineId` — persisted at ~/.ascendkit/machine-id, generated once
|
|
7
|
+
*/
|
|
8
|
+
import { randomUUID } from "node:crypto";
|
|
9
|
+
import { readFileSync, writeFileSync, mkdirSync, existsSync } from "node:fs";
|
|
10
|
+
import { join } from "node:path";
|
|
11
|
+
import { homedir } from "node:os";
|
|
12
|
+
// ---------------------------------------------------------------------------
|
|
13
|
+
// Invocation ID — one per process
|
|
14
|
+
// ---------------------------------------------------------------------------
|
|
15
|
+
let _invocationId;
|
|
16
|
+
export function getInvocationId() {
|
|
17
|
+
if (!_invocationId) {
|
|
18
|
+
_invocationId = randomUUID();
|
|
19
|
+
}
|
|
20
|
+
return _invocationId;
|
|
21
|
+
}
|
|
22
|
+
// ---------------------------------------------------------------------------
|
|
23
|
+
// Client Request ID — one per outbound request
|
|
24
|
+
// ---------------------------------------------------------------------------
|
|
25
|
+
export function generateClientRequestId() {
|
|
26
|
+
return randomUUID();
|
|
27
|
+
}
|
|
28
|
+
// ---------------------------------------------------------------------------
|
|
29
|
+
// Correlation headers (added to every AscendKit API call)
|
|
30
|
+
// ---------------------------------------------------------------------------
|
|
31
|
+
export function correlationHeaders() {
|
|
32
|
+
return {
|
|
33
|
+
"X-AscendKit-Invocation-Id": getInvocationId(),
|
|
34
|
+
"X-AscendKit-Client-Request-Id": generateClientRequestId(),
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
// ---------------------------------------------------------------------------
|
|
38
|
+
// Machine ID — persisted in ~/.ascendkit/machine-id
|
|
39
|
+
// ---------------------------------------------------------------------------
|
|
40
|
+
const MACHINE_ID_DIR = join(homedir(), ".ascendkit");
|
|
41
|
+
const MACHINE_ID_PATH = join(MACHINE_ID_DIR, "machine-id");
|
|
42
|
+
let _machineId;
|
|
43
|
+
export function getMachineId() {
|
|
44
|
+
if (_machineId)
|
|
45
|
+
return _machineId;
|
|
46
|
+
try {
|
|
47
|
+
const stored = readFileSync(MACHINE_ID_PATH, "utf-8").trim();
|
|
48
|
+
if (stored) {
|
|
49
|
+
_machineId = stored;
|
|
50
|
+
return _machineId;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
// File doesn't exist yet — will create below.
|
|
55
|
+
}
|
|
56
|
+
_machineId = randomUUID();
|
|
57
|
+
try {
|
|
58
|
+
if (!existsSync(MACHINE_ID_DIR)) {
|
|
59
|
+
mkdirSync(MACHINE_ID_DIR, { recursive: true });
|
|
60
|
+
}
|
|
61
|
+
writeFileSync(MACHINE_ID_PATH, _machineId, "utf-8");
|
|
62
|
+
}
|
|
63
|
+
catch {
|
|
64
|
+
// Non-fatal — we still have the in-memory value for this session.
|
|
65
|
+
}
|
|
66
|
+
return _machineId;
|
|
67
|
+
}
|