@ascendkit/cli 0.1.10

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.
Files changed (48) hide show
  1. package/LICENSE +21 -0
  2. package/dist/api/client.d.ts +34 -0
  3. package/dist/api/client.js +155 -0
  4. package/dist/cli.d.ts +2 -0
  5. package/dist/cli.js +1153 -0
  6. package/dist/commands/auth.d.ts +17 -0
  7. package/dist/commands/auth.js +29 -0
  8. package/dist/commands/content.d.ts +25 -0
  9. package/dist/commands/content.js +28 -0
  10. package/dist/commands/email.d.ts +37 -0
  11. package/dist/commands/email.js +39 -0
  12. package/dist/commands/journeys.d.ts +86 -0
  13. package/dist/commands/journeys.js +69 -0
  14. package/dist/commands/platform.d.ts +35 -0
  15. package/dist/commands/platform.js +517 -0
  16. package/dist/commands/surveys.d.ts +51 -0
  17. package/dist/commands/surveys.js +41 -0
  18. package/dist/commands/webhooks.d.ts +16 -0
  19. package/dist/commands/webhooks.js +28 -0
  20. package/dist/index.d.ts +2 -0
  21. package/dist/index.js +29 -0
  22. package/dist/mcp.d.ts +2 -0
  23. package/dist/mcp.js +40 -0
  24. package/dist/tools/auth.d.ts +3 -0
  25. package/dist/tools/auth.js +75 -0
  26. package/dist/tools/content.d.ts +3 -0
  27. package/dist/tools/content.js +64 -0
  28. package/dist/tools/email.d.ts +3 -0
  29. package/dist/tools/email.js +57 -0
  30. package/dist/tools/journeys.d.ts +3 -0
  31. package/dist/tools/journeys.js +302 -0
  32. package/dist/tools/platform.d.ts +3 -0
  33. package/dist/tools/platform.js +63 -0
  34. package/dist/tools/surveys.d.ts +3 -0
  35. package/dist/tools/surveys.js +212 -0
  36. package/dist/tools/webhooks.d.ts +3 -0
  37. package/dist/tools/webhooks.js +56 -0
  38. package/dist/types.d.ts +96 -0
  39. package/dist/types.js +4 -0
  40. package/dist/utils/credentials.d.ts +27 -0
  41. package/dist/utils/credentials.js +90 -0
  42. package/dist/utils/duration.d.ts +16 -0
  43. package/dist/utils/duration.js +47 -0
  44. package/dist/utils/journey-format.d.ts +112 -0
  45. package/dist/utils/journey-format.js +200 -0
  46. package/dist/utils/survey-format.d.ts +60 -0
  47. package/dist/utils/survey-format.js +164 -0
  48. package/package.json +37 -0
@@ -0,0 +1,517 @@
1
+ import { hostname, platform as osPlatform, release } from "node:os";
2
+ import { readdir, readFile, writeFile } from "node:fs/promises";
3
+ import path from "node:path";
4
+ import { loadAuth, saveAuth, deleteAuth, saveEnvContext, ensureGitignore } from "../utils/credentials.js";
5
+ const DEFAULT_API_URL = "https://api.ascendkit.com";
6
+ const DEFAULT_PORTAL_URL = "http://localhost:3000";
7
+ const POLL_INTERVAL_MS = 2000;
8
+ const DEVICE_CODE_EXPIRY_MS = 300_000; // 5 minutes
9
+ const IGNORE_DIRS = new Set([".git", "node_modules", ".next", "dist", "build"]);
10
+ const ASCENDKIT_ENV_KEYS = [
11
+ "NEXT_PUBLIC_ASCENDKIT_API_URL",
12
+ "ASCENDKIT_API_URL",
13
+ "NEXT_PUBLIC_ASCENDKIT_ENV_KEY",
14
+ "ASCENDKIT_ENV_KEY",
15
+ "ASCENDKIT_SECRET_KEY",
16
+ "ASCENDKIT_WEBHOOK_SECRET",
17
+ ];
18
+ const ASCENDKIT_BLOCK_START = "# --- AscendKit Managed Environment Variables ---";
19
+ const ASCENDKIT_BLOCK_END = "# --- End AscendKit Managed Environment Variables ---";
20
+ async function promptYesNo(question, defaultYes) {
21
+ const suffix = defaultYes ? "[Y/n]" : "[y/N]";
22
+ if (!process.stdin.isTTY || !process.stdout.isTTY) {
23
+ console.log(`${question} ${suffix} (auto-selected: ${defaultYes ? "yes" : "no"})`);
24
+ return defaultYes;
25
+ }
26
+ const { createInterface } = await import("node:readline/promises");
27
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
28
+ try {
29
+ const answer = (await rl.question(`${question} ${suffix} `)).trim().toLowerCase();
30
+ if (!answer)
31
+ return defaultYes;
32
+ return answer === "y" || answer === "yes";
33
+ }
34
+ finally {
35
+ rl.close();
36
+ }
37
+ }
38
+ async function promptLine(question) {
39
+ if (!process.stdin.isTTY || !process.stdout.isTTY)
40
+ return "";
41
+ const { createInterface } = await import("node:readline/promises");
42
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
43
+ try {
44
+ return (await rl.question(question)).trim();
45
+ }
46
+ finally {
47
+ rl.close();
48
+ }
49
+ }
50
+ async function promptChoice(question, options) {
51
+ if (!process.stdin.isTTY || !process.stdout.isTTY)
52
+ return 0;
53
+ const { createInterface } = await import("node:readline/promises");
54
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
55
+ try {
56
+ for (let i = 0; i < options.length; i++) {
57
+ console.log(` ${i + 1}. ${options[i]}`);
58
+ }
59
+ const answer = (await rl.question(question)).trim();
60
+ const idx = parseInt(answer, 10) - 1;
61
+ if (isNaN(idx) || idx < 0 || idx >= options.length)
62
+ return 0;
63
+ return idx;
64
+ }
65
+ finally {
66
+ rl.close();
67
+ }
68
+ }
69
+ async function findEnvFiles(root) {
70
+ const examples = [];
71
+ const runtime = [];
72
+ async function walk(dir) {
73
+ const entries = await readdir(dir, { withFileTypes: true });
74
+ for (const entry of entries) {
75
+ const fullPath = path.join(dir, entry.name);
76
+ if (entry.isDirectory()) {
77
+ if (IGNORE_DIRS.has(entry.name))
78
+ continue;
79
+ await walk(fullPath);
80
+ continue;
81
+ }
82
+ if (!entry.isFile())
83
+ continue;
84
+ if (entry.name === ".env.example") {
85
+ examples.push(fullPath);
86
+ }
87
+ else if (entry.name === ".env" || entry.name === ".env.local") {
88
+ runtime.push(fullPath);
89
+ }
90
+ }
91
+ }
92
+ await walk(root);
93
+ examples.sort();
94
+ runtime.sort();
95
+ return { examples, runtime };
96
+ }
97
+ function readEnvValue(content, key) {
98
+ const match = content.match(new RegExp(`^\\s*${key}=(.*)$`, "m"));
99
+ if (!match)
100
+ return null;
101
+ return match[1] ?? "";
102
+ }
103
+ function buildAscendKitEnvBlock(values) {
104
+ return [
105
+ ASCENDKIT_BLOCK_START,
106
+ "# AscendKit API URL (used by SDK to reach the backend)",
107
+ `NEXT_PUBLIC_ASCENDKIT_API_URL=${values.NEXT_PUBLIC_ASCENDKIT_API_URL}`,
108
+ `ASCENDKIT_API_URL=${values.ASCENDKIT_API_URL}`,
109
+ "",
110
+ "# AscendKit public env key (safe for browser and server use)",
111
+ `NEXT_PUBLIC_ASCENDKIT_ENV_KEY=${values.NEXT_PUBLIC_ASCENDKIT_ENV_KEY}`,
112
+ `ASCENDKIT_ENV_KEY=${values.ASCENDKIT_ENV_KEY}`,
113
+ "",
114
+ "# AscendKit secret key (server-only; never expose to client/browser)",
115
+ `ASCENDKIT_SECRET_KEY=${values.ASCENDKIT_SECRET_KEY}`,
116
+ "",
117
+ "# Webhook signing secret (server-only)",
118
+ `ASCENDKIT_WEBHOOK_SECRET=${values.ASCENDKIT_WEBHOOK_SECRET}`,
119
+ ASCENDKIT_BLOCK_END,
120
+ ];
121
+ }
122
+ async function rewriteAscendKitEnvBlock(filePath, values) {
123
+ const original = await readFile(filePath, "utf8");
124
+ const lines = original.split(/\r?\n/);
125
+ const filtered = [];
126
+ let inManagedBlock = false;
127
+ for (const line of lines) {
128
+ const trimmed = line.trim();
129
+ if (trimmed === ASCENDKIT_BLOCK_START) {
130
+ inManagedBlock = true;
131
+ continue;
132
+ }
133
+ if (trimmed === ASCENDKIT_BLOCK_END) {
134
+ inManagedBlock = false;
135
+ continue;
136
+ }
137
+ if (inManagedBlock)
138
+ continue;
139
+ const key = line.split("=")[0]?.trim();
140
+ if (ASCENDKIT_ENV_KEYS.includes(key))
141
+ continue;
142
+ filtered.push(line);
143
+ }
144
+ while (filtered.length > 0 && filtered[filtered.length - 1].trim() === "") {
145
+ filtered.pop();
146
+ }
147
+ if (filtered.length > 0)
148
+ filtered.push("");
149
+ filtered.push(...buildAscendKitEnvBlock(values));
150
+ const updated = `${filtered.join("\n").replace(/\n+$/, "")}\n`;
151
+ if (updated === original)
152
+ return false;
153
+ await writeFile(filePath, updated, "utf8");
154
+ return true;
155
+ }
156
+ async function updateEnvExampleFile(filePath) {
157
+ return rewriteAscendKitEnvBlock(filePath, {
158
+ NEXT_PUBLIC_ASCENDKIT_API_URL: "",
159
+ ASCENDKIT_API_URL: "",
160
+ NEXT_PUBLIC_ASCENDKIT_ENV_KEY: "",
161
+ ASCENDKIT_ENV_KEY: "",
162
+ ASCENDKIT_SECRET_KEY: "",
163
+ ASCENDKIT_WEBHOOK_SECRET: "",
164
+ });
165
+ }
166
+ async function updateRuntimeEnvFile(filePath, apiUrl, publicKey, secretKey, options) {
167
+ const original = await readFile(filePath, "utf8");
168
+ const preserveExistingKeys = options?.preserveExistingKeys ?? false;
169
+ const existingApiUrl = readEnvValue(original, "ASCENDKIT_API_URL");
170
+ const existingPublicKey = readEnvValue(original, "ASCENDKIT_ENV_KEY");
171
+ const existingSecretKey = readEnvValue(original, "ASCENDKIT_SECRET_KEY");
172
+ const existingWebhookSecret = readEnvValue(original, "ASCENDKIT_WEBHOOK_SECRET") ?? "";
173
+ const resolvedApiUrl = existingApiUrl ?? apiUrl;
174
+ const resolvedPublicKey = preserveExistingKeys
175
+ ? (existingPublicKey && existingPublicKey.trim() ? existingPublicKey : publicKey)
176
+ : publicKey;
177
+ const resolvedSecretKey = preserveExistingKeys
178
+ ? (existingSecretKey && existingSecretKey.trim() ? existingSecretKey : secretKey)
179
+ : secretKey;
180
+ const resolvedWebhookSecret = options?.webhookSecret ?? existingWebhookSecret;
181
+ return rewriteAscendKitEnvBlock(filePath, {
182
+ NEXT_PUBLIC_ASCENDKIT_API_URL: resolvedApiUrl,
183
+ ASCENDKIT_API_URL: resolvedApiUrl,
184
+ NEXT_PUBLIC_ASCENDKIT_ENV_KEY: resolvedPublicKey,
185
+ ASCENDKIT_ENV_KEY: resolvedPublicKey,
186
+ ASCENDKIT_SECRET_KEY: resolvedSecretKey,
187
+ ASCENDKIT_WEBHOOK_SECRET: resolvedWebhookSecret,
188
+ });
189
+ }
190
+ function generateDeviceCode() {
191
+ const chars = "ABCDEFGHJKLMNPQRSTUVWXYZ23456789"; // no I, O, 0, 1 for readability
192
+ let code = "";
193
+ for (let i = 0; i < 8; i++) {
194
+ if (i === 4)
195
+ code += "-";
196
+ code += chars[Math.floor(Math.random() * chars.length)];
197
+ }
198
+ return code;
199
+ }
200
+ async function apiRequest(apiUrl, method, path, body, token, publicKey) {
201
+ const headers = { "Content-Type": "application/json" };
202
+ if (token)
203
+ headers["Authorization"] = `Bearer ${token}`;
204
+ if (publicKey)
205
+ headers["X-AscendKit-Public-Key"] = publicKey;
206
+ const init = { method, headers };
207
+ if (body !== undefined)
208
+ init.body = JSON.stringify(body);
209
+ const response = await fetch(`${apiUrl}${path}`, init);
210
+ if (!response.ok) {
211
+ const text = await response.text();
212
+ throw new Error(`API error ${response.status}: ${text}`);
213
+ }
214
+ const json = await response.json();
215
+ return json.data;
216
+ }
217
+ async function openBrowser(url) {
218
+ const { execFile } = await import("node:child_process");
219
+ if (process.platform === "darwin") {
220
+ execFile("open", [url]);
221
+ }
222
+ else if (process.platform === "win32") {
223
+ // "start" is a cmd.exe built-in — invoke via cmd.exe with execFile.
224
+ // Escape cmd.exe metacharacters with ^ to prevent injection.
225
+ const safe = url.replace(/[&|<>^"]/g, "^$&");
226
+ execFile("cmd.exe", ["/c", "start", "", safe]);
227
+ }
228
+ else {
229
+ execFile("xdg-open", [url]);
230
+ }
231
+ }
232
+ export async function init(apiUrl, portalUrl) {
233
+ const api = apiUrl ?? DEFAULT_API_URL;
234
+ const portal = portalUrl ?? DEFAULT_PORTAL_URL;
235
+ const deviceCode = generateDeviceCode();
236
+ console.log(`\nDevice code: ${deviceCode}\n`);
237
+ // Register the device code with the backend
238
+ try {
239
+ await apiRequest(api, "POST", "/api/platform/cli/auth-request", {
240
+ deviceCode,
241
+ machineHostname: hostname(),
242
+ machinePlatform: `${osPlatform()} ${release()}`,
243
+ });
244
+ }
245
+ catch (err) {
246
+ throw new Error(`Failed to start login flow: ${err.message}`);
247
+ }
248
+ // Open browser
249
+ const authUrl = `${portal}/cli-auth?code=${deviceCode}`;
250
+ console.log(`Opening browser to: ${authUrl}`);
251
+ console.log("If the browser doesn't open, navigate to the URL above.\n");
252
+ openBrowser(authUrl);
253
+ console.log("Waiting for authorization...");
254
+ // Poll for approval
255
+ const deadline = Date.now() + DEVICE_CODE_EXPIRY_MS;
256
+ while (Date.now() < deadline) {
257
+ await new Promise(resolve => setTimeout(resolve, POLL_INTERVAL_MS));
258
+ try {
259
+ const result = await apiRequest(api, "GET", `/api/platform/cli/auth-status?code=${deviceCode}`);
260
+ if (result.status === "approved" && result.token) {
261
+ saveAuth({ token: result.token, apiUrl: api });
262
+ ensureGitignore();
263
+ console.log(`\nInitialized as ${result.email ?? "unknown"}`);
264
+ // Seed .env files with API URL and empty key placeholders
265
+ const cwd = process.cwd();
266
+ const discovered = await findEnvFiles(cwd);
267
+ if (discovered.examples.length > 0) {
268
+ console.log(`\nFound ${discovered.examples.length} .env.example file(s).`);
269
+ const updateExamples = await promptYesNo("Update with AscendKit env placeholders?", true);
270
+ if (updateExamples) {
271
+ for (const filePath of discovered.examples) {
272
+ const changed = await updateEnvExampleFile(filePath);
273
+ if (changed)
274
+ console.log(` Updated ${path.relative(cwd, filePath)}`);
275
+ }
276
+ }
277
+ }
278
+ if (discovered.runtime.length > 0) {
279
+ console.log(`\nFound ${discovered.runtime.length} .env/.env.local file(s).`);
280
+ const updateRuntime = await promptYesNo("Seed with AscendKit API URL?", true);
281
+ if (updateRuntime) {
282
+ for (const filePath of discovered.runtime) {
283
+ const changed = await updateRuntimeEnvFile(filePath, api, "", "", {
284
+ preserveExistingKeys: true,
285
+ });
286
+ if (changed)
287
+ console.log(` Updated ${path.relative(cwd, filePath)}`);
288
+ }
289
+ }
290
+ }
291
+ return;
292
+ }
293
+ if (result.status === "expired") {
294
+ throw new Error("Login request expired. Please try again.");
295
+ }
296
+ }
297
+ catch (err) {
298
+ if (err.message.includes("expired"))
299
+ throw err;
300
+ // Transient error, keep polling
301
+ }
302
+ }
303
+ throw new Error("Login request timed out. Please try again.");
304
+ }
305
+ export function logout() {
306
+ deleteAuth();
307
+ console.log("Logged out.");
308
+ }
309
+ function requireAuth() {
310
+ const auth = loadAuth();
311
+ if (!auth?.token) {
312
+ console.error("Not initialized. Run: ascendkit init");
313
+ process.exit(1);
314
+ }
315
+ return auth;
316
+ }
317
+ export async function listProjects() {
318
+ const auth = requireAuth();
319
+ return apiRequest(auth.apiUrl, "GET", "/api/platform/projects", undefined, auth.token);
320
+ }
321
+ export async function listEnvironments(projectId) {
322
+ const auth = requireAuth();
323
+ return apiRequest(auth.apiUrl, "GET", `/api/platform/projects/${projectId}/environments`, undefined, auth.token);
324
+ }
325
+ export async function useEnvironment(tier, projectId) {
326
+ const auth = requireAuth();
327
+ const envs = await apiRequest(auth.apiUrl, "GET", `/api/platform/projects/${projectId}/environments`, undefined, auth.token);
328
+ const env = envs.find(e => e.tier === tier);
329
+ if (!env) {
330
+ throw new Error(`No ${tier} environment found for project ${projectId}. Available: ${envs.map(e => e.tier).join(", ")}`);
331
+ }
332
+ // Fetch project name for echo-back
333
+ let projectName = projectId;
334
+ try {
335
+ const projects = await apiRequest(auth.apiUrl, "GET", "/api/platform/projects", undefined, auth.token);
336
+ const project = projects.find(p => p.id === projectId);
337
+ if (project)
338
+ projectName = project.name;
339
+ }
340
+ catch { /* non-critical */ }
341
+ saveEnvContext({
342
+ publicKey: env.publicKey,
343
+ projectId,
344
+ projectName,
345
+ environmentId: env.id,
346
+ environmentName: env.name,
347
+ tier: env.tier,
348
+ });
349
+ console.log(`Active environment: ${env.name} (${env.tier})`);
350
+ console.log(`Project: ${projectName}`);
351
+ console.log(`Public key: ${env.publicKey}`);
352
+ }
353
+ async function setupWebhook(auth, publicKey) {
354
+ // Never run webhook setup in non-interactive mode — selecting a webhook
355
+ // returns its secret, which would be printed to stdout/CI logs.
356
+ if (!process.stdin.isTTY || !process.stdout.isTTY) {
357
+ console.log("\nSkipping webhook setup (non-interactive mode). Run ascendkit set-env interactively or use the portal.");
358
+ return null;
359
+ }
360
+ console.log("\n--- Webhook Setup ---");
361
+ console.log("Webhooks are required for login/signup writebacks (user.created, user.login, etc.).");
362
+ let existingWebhooks = [];
363
+ try {
364
+ existingWebhooks = await apiRequest(auth.apiUrl, "GET", "/api/v1/webhooks", undefined, auth.token, publicKey);
365
+ }
366
+ catch {
367
+ console.error("Could not fetch existing webhooks. You can set up webhooks later in the portal.");
368
+ return null;
369
+ }
370
+ if (existingWebhooks.length > 0) {
371
+ console.log(`\nFound ${existingWebhooks.length} existing webhook(s):`);
372
+ const options = existingWebhooks.map(w => `${w.url} (${w.events.join(", ")})`);
373
+ options.push("Create a new webhook");
374
+ options.push("Skip webhook setup");
375
+ const choice = await promptChoice("\nWhich webhook to use? ", options);
376
+ if (choice === options.length - 1) {
377
+ // Skip
378
+ return null;
379
+ }
380
+ if (choice < existingWebhooks.length) {
381
+ // Reuse existing
382
+ const selected = existingWebhooks[choice];
383
+ console.log(`\nUsing webhook: ${selected.url}`);
384
+ return selected.secret;
385
+ }
386
+ // Fall through to create new
387
+ }
388
+ else {
389
+ const create = await promptYesNo("\nNo webhooks configured. Create one now?", true);
390
+ if (!create)
391
+ return null;
392
+ }
393
+ // Create new webhook
394
+ console.log("\nThe webhook URL is where AscendKit sends event notifications.");
395
+ console.log("Typically this is your backend or frontend /api/webhook/ascendkit endpoint.");
396
+ console.log("Example: http://localhost:3000/api/webhook/ascendkit");
397
+ const url = await promptLine("\nWebhook URL: ");
398
+ if (!url) {
399
+ console.log("No URL provided. Skipping webhook setup.");
400
+ return null;
401
+ }
402
+ try {
403
+ const created = await apiRequest(auth.apiUrl, "POST", "/api/v1/webhooks", { url, events: ["user.created"] }, auth.token, publicKey);
404
+ console.log(`\nWebhook created: ${created.url}`);
405
+ console.log(` Events: ${created.events.join(", ")}`);
406
+ console.log(` ID: ${created.id}`);
407
+ return created.secret;
408
+ }
409
+ catch (err) {
410
+ console.error(`Failed to create webhook: ${err.message}`);
411
+ return null;
412
+ }
413
+ }
414
+ export async function setEnv(publicKey) {
415
+ const auth = requireAuth();
416
+ const result = await apiRequest(auth.apiUrl, "GET", `/api/platform/resolve-key?pk=${encodeURIComponent(publicKey)}`, undefined, auth.token);
417
+ saveEnvContext({
418
+ publicKey: result.environment.publicKey,
419
+ projectId: result.project.id,
420
+ projectName: result.project.name,
421
+ environmentId: result.environment.id,
422
+ environmentName: result.environment.name,
423
+ tier: result.environment.tier,
424
+ });
425
+ console.log(` → Project: ${result.project.name}`);
426
+ console.log(` → Environment: ${result.environment.name}`);
427
+ console.log(` → Role: ${result.role}`);
428
+ const cwd = process.cwd();
429
+ const discovered = await findEnvFiles(cwd);
430
+ const updatedExamples = [];
431
+ const updatedRuntime = [];
432
+ console.log(`\nFound ${discovered.examples.length} .env.example file(s).`);
433
+ if (discovered.examples.length > 0) {
434
+ const updateExamples = await promptYesNo("Update with env placeholders?", true);
435
+ if (updateExamples) {
436
+ for (const filePath of discovered.examples) {
437
+ const changed = await updateEnvExampleFile(filePath);
438
+ if (changed)
439
+ updatedExamples.push(path.relative(cwd, filePath));
440
+ }
441
+ }
442
+ }
443
+ console.log(`\nFound ${discovered.runtime.length} .env/.env.local file(s).`);
444
+ let runtimeConsent = false;
445
+ if (discovered.runtime.length > 0) {
446
+ runtimeConsent = await promptYesNo("Update with actual env and secret values?", false);
447
+ if (runtimeConsent) {
448
+ for (const filePath of discovered.runtime) {
449
+ const changed = await updateRuntimeEnvFile(filePath, auth.apiUrl, result.environment.publicKey, result.environment.secretKey ?? "");
450
+ if (changed)
451
+ updatedRuntime.push(path.relative(cwd, filePath));
452
+ }
453
+ }
454
+ }
455
+ console.log("\nUpdated files:");
456
+ if (updatedExamples.length === 0 && updatedRuntime.length === 0) {
457
+ console.log(" (none)");
458
+ }
459
+ else {
460
+ for (const filePath of updatedExamples)
461
+ console.log(` - ${filePath}`);
462
+ for (const filePath of updatedRuntime)
463
+ console.log(` - ${filePath}`);
464
+ }
465
+ if (!result.environment.secretKey) {
466
+ console.log("\nNote: your role does not have access to the environment secret key.");
467
+ }
468
+ // --- Webhook setup ---
469
+ const webhookSecret = await setupWebhook(auth, result.environment.publicKey);
470
+ if (webhookSecret) {
471
+ if (runtimeConsent && discovered.runtime.length > 0) {
472
+ for (const filePath of discovered.runtime) {
473
+ await updateRuntimeEnvFile(filePath, auth.apiUrl, result.environment.publicKey, result.environment.secretKey ?? "", { preserveExistingKeys: true, webhookSecret });
474
+ }
475
+ console.log("\nWebhook secret written to .env file(s).");
476
+ }
477
+ else {
478
+ console.log(`\nAdd this to your environment:`);
479
+ console.log(` ASCENDKIT_WEBHOOK_SECRET=${webhookSecret}`);
480
+ }
481
+ }
482
+ const remainingSteps = [];
483
+ if (!webhookSecret) {
484
+ remainingSteps.push("Set up a webhook endpoint (run ascendkit set-env again, or use the portal).");
485
+ }
486
+ remainingSteps.push("Ensure frontend and backend are using keys from the same AscendKit environment.");
487
+ if (remainingSteps.length > 0) {
488
+ console.log("\nWhat you still need to do:");
489
+ for (let i = 0; i < remainingSteps.length; i++) {
490
+ console.log(` ${i + 1}. ${remainingSteps[i]}`);
491
+ }
492
+ }
493
+ }
494
+ export async function mcpLogin(client, params) {
495
+ const data = await client.publicRequest("POST", "/api/platform/auth/token", {
496
+ email: params.email,
497
+ password: params.password,
498
+ });
499
+ client.configurePlatform(data.token);
500
+ return data;
501
+ }
502
+ export async function mcpListProjects(client) {
503
+ return client.platformRequest("GET", "/api/platform/projects");
504
+ }
505
+ export async function mcpCreateProject(client, params) {
506
+ return client.platformRequest("POST", "/api/platform/projects", {
507
+ name: params.name,
508
+ description: params.description ?? "",
509
+ enabledServices: params.enabledServices ?? [],
510
+ });
511
+ }
512
+ export async function mcpListEnvironments(client, projectId) {
513
+ return client.platformRequest("GET", `/api/platform/projects/${projectId}/environments`);
514
+ }
515
+ export async function mcpCreateEnvironment(client, params) {
516
+ return client.platformRequest("POST", `/api/platform/projects/${params.projectId}/environments`, { name: params.name, description: params.description ?? "", tier: params.tier });
517
+ }
@@ -0,0 +1,51 @@
1
+ import { AscendKitClient } from "../api/client.js";
2
+ export interface CreateSurveyParams {
3
+ name: string;
4
+ type?: "nps" | "csat" | "custom";
5
+ definition?: Record<string, unknown>;
6
+ }
7
+ export interface UpdateSurveyParams {
8
+ name?: string;
9
+ definition?: Record<string, unknown>;
10
+ status?: "draft" | "active" | "paused";
11
+ }
12
+ export declare function createSurvey(client: AscendKitClient, params: CreateSurveyParams): Promise<unknown>;
13
+ export declare function listSurveys(client: AscendKitClient): Promise<unknown>;
14
+ export declare function getSurvey(client: AscendKitClient, surveyId: string): Promise<unknown>;
15
+ export declare function updateSurvey(client: AscendKitClient, surveyId: string, params: UpdateSurveyParams): Promise<unknown>;
16
+ export declare function deleteSurvey(client: AscendKitClient, surveyId: string): Promise<unknown>;
17
+ export declare function distributeSurvey(client: AscendKitClient, surveyId: string, userIds: string[]): Promise<unknown>;
18
+ export declare function listInvitations(client: AscendKitClient, surveyId: string): Promise<unknown>;
19
+ export declare function getAnalytics(client: AscendKitClient, surveyId: string): Promise<unknown>;
20
+ export interface AddQuestionParams {
21
+ type: string;
22
+ title: string;
23
+ name?: string;
24
+ isRequired?: boolean;
25
+ choices?: string[];
26
+ rateMin?: number;
27
+ rateMax?: number;
28
+ minRateDescription?: string;
29
+ maxRateDescription?: string;
30
+ inputType?: string;
31
+ visibleIf?: string;
32
+ requiredIf?: string;
33
+ position?: number;
34
+ }
35
+ export interface EditQuestionParams {
36
+ title?: string;
37
+ isRequired?: boolean;
38
+ choices?: string[];
39
+ rateMin?: number;
40
+ rateMax?: number;
41
+ minRateDescription?: string;
42
+ maxRateDescription?: string;
43
+ inputType?: string;
44
+ visibleIf?: string;
45
+ requiredIf?: string;
46
+ }
47
+ export declare function listQuestions(client: AscendKitClient, surveyId: string): Promise<unknown>;
48
+ export declare function addQuestion(client: AscendKitClient, surveyId: string, params: AddQuestionParams): Promise<unknown>;
49
+ export declare function editQuestion(client: AscendKitClient, surveyId: string, questionName: string, params: EditQuestionParams): Promise<unknown>;
50
+ export declare function removeQuestion(client: AscendKitClient, surveyId: string, questionName: string): Promise<unknown>;
51
+ export declare function reorderQuestions(client: AscendKitClient, surveyId: string, order: string[]): Promise<unknown>;
@@ -0,0 +1,41 @@
1
+ export async function createSurvey(client, params) {
2
+ return client.managedPost("/api/surveys", params);
3
+ }
4
+ export async function listSurveys(client) {
5
+ return client.get("/api/surveys");
6
+ }
7
+ export async function getSurvey(client, surveyId) {
8
+ return client.get(`/api/surveys/${surveyId}`);
9
+ }
10
+ export async function updateSurvey(client, surveyId, params) {
11
+ return client.managedPut(`/api/surveys/${surveyId}`, params);
12
+ }
13
+ export async function deleteSurvey(client, surveyId) {
14
+ return client.managedDelete(`/api/surveys/${surveyId}`);
15
+ }
16
+ export async function distributeSurvey(client, surveyId, userIds) {
17
+ return client.managedPost(`/api/surveys/${surveyId}/distribute`, { userIds });
18
+ }
19
+ export async function listInvitations(client, surveyId) {
20
+ return client.get(`/api/surveys/${surveyId}/invitations`);
21
+ }
22
+ export async function getAnalytics(client, surveyId) {
23
+ return client.get(`/api/surveys/${surveyId}/analytics`);
24
+ }
25
+ export async function listQuestions(client, surveyId) {
26
+ return client.get(`/api/surveys/${surveyId}/questions`);
27
+ }
28
+ export async function addQuestion(client, surveyId, params) {
29
+ return client.managedPost(`/api/surveys/${surveyId}/questions`, params);
30
+ }
31
+ export async function editQuestion(client, surveyId, questionName, params) {
32
+ return client.managedPut(`/api/surveys/${surveyId}/questions/${encodeURIComponent(questionName)}`, params);
33
+ }
34
+ export async function removeQuestion(client, surveyId, questionName) {
35
+ return client.managedDelete(`/api/surveys/${surveyId}/questions/${encodeURIComponent(questionName)}`);
36
+ }
37
+ export async function reorderQuestions(client, surveyId, order) {
38
+ return client.managedPut(`/api/surveys/${surveyId}/questions/reorder`, {
39
+ order,
40
+ });
41
+ }
@@ -0,0 +1,16 @@
1
+ import { AscendKitClient } from "../api/client.js";
2
+ export interface CreateWebhookParams {
3
+ url: string;
4
+ events?: string[];
5
+ }
6
+ export interface UpdateWebhookParams {
7
+ url?: string;
8
+ events?: string[];
9
+ status?: "active" | "inactive";
10
+ }
11
+ export declare function createWebhook(client: AscendKitClient, params: CreateWebhookParams): Promise<unknown>;
12
+ export declare function listWebhooks(client: AscendKitClient): Promise<unknown>;
13
+ export declare function getWebhook(client: AscendKitClient, configId: string): Promise<unknown>;
14
+ export declare function updateWebhook(client: AscendKitClient, configId: string, params: UpdateWebhookParams): Promise<unknown>;
15
+ export declare function deleteWebhook(client: AscendKitClient, configId: string): Promise<unknown>;
16
+ export declare function testWebhook(client: AscendKitClient, configId: string, eventType?: string): Promise<unknown>;
@@ -0,0 +1,28 @@
1
+ export async function createWebhook(client, params) {
2
+ return client.managedPost("/api/v1/webhooks", params);
3
+ }
4
+ export async function listWebhooks(client) {
5
+ return client.managedRequest("GET", "/api/v1/webhooks");
6
+ }
7
+ export async function getWebhook(client, configId) {
8
+ return client.managedRequest("GET", `/api/v1/webhooks/${configId}`);
9
+ }
10
+ export async function updateWebhook(client, configId, params) {
11
+ const body = {};
12
+ if (params.url !== undefined)
13
+ body.url = params.url;
14
+ if (params.events !== undefined)
15
+ body.events = params.events;
16
+ if (params.status !== undefined)
17
+ body.status = params.status;
18
+ return client.managedRequest("PATCH", `/api/v1/webhooks/${configId}`, body);
19
+ }
20
+ export async function deleteWebhook(client, configId) {
21
+ return client.managedDelete(`/api/v1/webhooks/${configId}`);
22
+ }
23
+ export async function testWebhook(client, configId, eventType) {
24
+ const body = {};
25
+ if (eventType !== undefined)
26
+ body.eventType = eventType;
27
+ return client.managedPost(`/api/v1/webhooks/${configId}/test`, body);
28
+ }
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};