@ascendkit/cli 0.2.6 → 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.
@@ -15,13 +15,47 @@ export function registerPlatformTools(server, client) {
15
15
  ],
16
16
  };
17
17
  });
18
- server.tool("platform_list_projects", "List all projects in your organization", {}, async () => {
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("platform_create_project", "Create a new project in your organization", {
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("platform_list_environments", "List environments for a project", {
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("platform_create_environment", "Create a new environment for a project. Returns the public key needed for SDK/CLI configuration.", {
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("platform_update_environment", "Update an environment's name or description.", {
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("platform_promote_environment", "Promote an environment's configuration to a higher tier (dev → beta → prod).", {
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("platform_update_environment_variables", "Set environment variables for a project environment. Pass the full variables dict it replaces all existing variables.", {
176
- projectId: z.string().describe("Project ID (prj_ prefixed)"),
177
- envId: z.string().describe("Environment ID (env_ prefixed)"),
178
- variables: z.record(z.string()).describe("Key-value map of environment variables"),
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
  }
@@ -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 survey_add_question. Survey lifecycle: create → add questions → activate → distribute → collect → analyze.", {
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("survey_get", "Get a survey by ID with status, question count, response count, and next-step guidance.", {
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("survey_delete", "Delete a survey and all its invitations and responses", {
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("survey_list_invitations", "List all invitations for a survey with their status (sent/opened/submitted)", {
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("survey_list_questions", "List all questions in a survey in human-readable format. Shows question number, type, title, required status, choices, and any conditional logic.", {
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("survey_add_question", "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.", {
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("survey_edit_question", "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.", {
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("survey_remove_question", "Remove a question from a survey by its name. This is permanent — the question and its configuration are deleted from the survey definition.", {
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("survey_reorder_questions", "Reorder questions in a survey by providing all question names in the desired order. All existing question names must be included.", {
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())
@@ -13,6 +13,11 @@ interface JourneyData {
13
13
  nodes: Record<string, {
14
14
  action?: {
15
15
  type?: string;
16
+ templateSlug?: string;
17
+ surveySlug?: string;
18
+ tagName?: string;
19
+ stageName?: string;
20
+ fromIdentityEmail?: string;
16
21
  };
17
22
  terminal?: boolean;
18
23
  }>;
@@ -83,6 +88,7 @@ interface NodeListItem {
83
88
  surveySlug?: string;
84
89
  tagName?: string;
85
90
  stageName?: string;
91
+ fromIdentityEmail?: string;
86
92
  };
87
93
  terminal: boolean;
88
94
  isEntryNode: boolean;
@@ -32,7 +32,7 @@ export function formatJourneyWithGuidance(journey) {
32
32
  lines.push("");
33
33
  lines.push("Nodes:");
34
34
  for (const [name, node] of Object.entries(journey.nodes || {})) {
35
- const action = node.action?.type || "none";
35
+ const action = formatActionLabel(node.action || {});
36
36
  const terminal = node.terminal ? " (terminal)" : "";
37
37
  const isEntry = name === journey.entryNode ? " [entry]" : "";
38
38
  lines.push(` ${name}: ${action}${terminal}${isEntry}`);
@@ -70,7 +70,7 @@ export function formatJourneyWithGuidance(journey) {
70
70
  hints.push("Journey is active and enrolling users. Use journey_analytics to see user flow.");
71
71
  }
72
72
  else if (journey.status === "paused") {
73
- hints.push("Journey is paused. Actions are queued. Use journey_activate to resume.");
73
+ hints.push("Journey is paused. Actions are queued. Use journey_resume to continue delivery.");
74
74
  }
75
75
  else if (journey.status === "archived") {
76
76
  hints.push("Journey is archived. No further enrollment or transitions.");
@@ -113,6 +113,8 @@ function formatActionLabel(action) {
113
113
  const type = action?.type || "none";
114
114
  if (type === "send_email") {
115
115
  const parts = [`send_email (${action.templateSlug || "?"})`];
116
+ if (action.fromIdentityEmail)
117
+ parts.push(`from: ${action.fromIdentityEmail}`);
116
118
  if (action.surveySlug)
117
119
  parts.push(`+ survey: ${action.surveySlug}`);
118
120
  return parts.join(" ");
@@ -131,7 +133,7 @@ function formatTriggerLabel(trigger) {
131
133
  export function formatNodeList(data) {
132
134
  const nodes = data.nodes || [];
133
135
  if (nodes.length === 0) {
134
- return "No nodes. Use journey_add_node to add the first node.";
136
+ return "No nodes. Use journey_node_add to add the first node.";
135
137
  }
136
138
  const lines = [`${nodes.length} node(s):\n`];
137
139
  for (const n of nodes) {
@@ -150,7 +152,7 @@ export function formatSingleNode(data, action, nodeName) {
150
152
  export function formatTransitionList(data) {
151
153
  const transitions = data.transitions || [];
152
154
  if (transitions.length === 0) {
153
- return "No transitions. Use journey_add_transition to connect nodes.";
155
+ return "No transitions. Use journey_transition_add to connect nodes.";
154
156
  }
155
157
  const lines = [`${transitions.length} transition(s):\n`];
156
158
  for (const t of transitions) {
@@ -84,11 +84,11 @@ export function formatSurveyWithGuidance(survey) {
84
84
  // Next steps based on current state
85
85
  const hints = [];
86
86
  if (questionCount === 0) {
87
- hints.push("This survey has no questions yet. Use survey_add_question to add questions, or survey_list_questions to see supported question types.");
87
+ hints.push("This survey has no questions yet. Use survey_question_add to add questions, or survey_question_list to see supported question types.");
88
88
  }
89
89
  else if (survey.status === "draft") {
90
90
  hints.push(`Survey has ${questionCount} question(s) and is in draft. Use survey_update with status 'active' to activate it for distribution.`);
91
- hints.push("Use survey_list_questions to review the questions before activating.");
91
+ hints.push("Use survey_question_list to review the questions before activating.");
92
92
  }
93
93
  else if (survey.status === "active" && responseCount === 0) {
94
94
  hints.push("Survey is active and ready to distribute. Use survey_distribute with a list of user IDs to create personalized tracking links.");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ascendkit/cli",
3
- "version": "0.2.6",
3
+ "version": "0.3.0",
4
4
  "description": "AscendKit CLI and MCP server",
5
5
  "author": "ascendkit.dev",
6
6
  "license": "MIT",
@@ -33,11 +33,11 @@
33
33
  "start": "node dist/cli.js"
34
34
  },
35
35
  "dependencies": {
36
- "@modelcontextprotocol/sdk": "^1.0.0",
37
- "zod": "^3.23.0"
36
+ "@modelcontextprotocol/sdk": "^1.27.1",
37
+ "zod": "^3.25.76"
38
38
  },
39
39
  "devDependencies": {
40
- "typescript": "^5.7.0",
41
- "@types/node": "^22.0.0"
40
+ "@types/node": "^25.5.0",
41
+ "typescript": "^5.7.0"
42
42
  }
43
43
  }