@ascendkit/cli 0.1.10 → 0.2.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.
@@ -1,10 +1,12 @@
1
1
  /**
2
2
  * REST client for AscendKit API.
3
3
  */
4
+ export declare const CLI_VERSION: string;
4
5
  export declare class AscendKitClient {
5
6
  private baseUrl;
6
7
  private publicKey;
7
8
  private platformToken;
9
+ private upgradeWarned;
8
10
  constructor(options?: {
9
11
  apiUrl?: string;
10
12
  publicKey?: string;
@@ -14,10 +16,12 @@ export declare class AscendKitClient {
14
16
  get platformConfigured(): boolean;
15
17
  configure(publicKey: string, apiUrl?: string): void;
16
18
  configurePlatform(token: string, apiUrl?: string): void;
19
+ private get versionHeader();
17
20
  private get headers();
18
21
  private get platformHeaders();
19
22
  /** Headers with both public key and Bearer token for management write operations. */
20
23
  private get managedHeaders();
24
+ private checkUpgradeHeader;
21
25
  request<T = unknown>(method: string, path: string, body?: unknown): Promise<T>;
22
26
  platformRequest<T = unknown>(method: string, path: string, body?: unknown): Promise<T>;
23
27
  /** Unauthenticated request (for login). */
@@ -1,12 +1,29 @@
1
1
  /**
2
2
  * REST client for AscendKit API.
3
3
  */
4
+ import { readFileSync } from "fs";
5
+ import { fileURLToPath } from "url";
6
+ import { dirname, resolve } from "path";
7
+ import { DEFAULT_API_URL } from "../constants.js";
8
+ const __filename = fileURLToPath(import.meta.url);
9
+ const __dirname = dirname(__filename);
10
+ function readPackageVersion() {
11
+ try {
12
+ const pkg = JSON.parse(readFileSync(resolve(__dirname, "../../package.json"), "utf-8"));
13
+ return pkg.version ?? "0.0.0";
14
+ }
15
+ catch {
16
+ return "0.0.0";
17
+ }
18
+ }
19
+ export const CLI_VERSION = readPackageVersion();
4
20
  export class AscendKitClient {
5
21
  baseUrl;
6
22
  publicKey;
7
23
  platformToken;
24
+ upgradeWarned = false;
8
25
  constructor(options) {
9
- this.baseUrl = options?.apiUrl ?? "https://api.ascendkit.com";
26
+ this.baseUrl = options?.apiUrl ?? DEFAULT_API_URL;
10
27
  this.publicKey = options?.publicKey ?? null;
11
28
  this.platformToken = null;
12
29
  }
@@ -29,6 +46,9 @@ export class AscendKitClient {
29
46
  if (apiUrl)
30
47
  this.baseUrl = apiUrl;
31
48
  }
49
+ get versionHeader() {
50
+ return { "X-AscendKit-Client-Version": `cli/${CLI_VERSION}` };
51
+ }
32
52
  get headers() {
33
53
  if (!this.publicKey) {
34
54
  throw new Error("Not configured. Call ascendkit_configure with your public key first.");
@@ -36,6 +56,7 @@ export class AscendKitClient {
36
56
  return {
37
57
  "Content-Type": "application/json",
38
58
  "X-AscendKit-Public-Key": this.publicKey,
59
+ ...this.versionHeader,
39
60
  };
40
61
  }
41
62
  get platformHeaders() {
@@ -45,6 +66,7 @@ export class AscendKitClient {
45
66
  return {
46
67
  "Content-Type": "application/json",
47
68
  "Authorization": `Bearer ${this.platformToken}`,
69
+ ...this.versionHeader,
48
70
  };
49
71
  }
50
72
  /** Headers with both public key and Bearer token for management write operations. */
@@ -59,8 +81,18 @@ export class AscendKitClient {
59
81
  "Content-Type": "application/json",
60
82
  "X-AscendKit-Public-Key": this.publicKey,
61
83
  "Authorization": `Bearer ${this.platformToken}`,
84
+ ...this.versionHeader,
62
85
  };
63
86
  }
87
+ checkUpgradeHeader(response) {
88
+ const upgrade = response.headers.get("X-AscendKit-Upgrade");
89
+ if (upgrade === "recommended" && !this.upgradeWarned) {
90
+ const latest = response.headers.get("X-AscendKit-Latest-Version") ?? "latest";
91
+ console.error(`[ascendkit] A newer CLI version (v${latest}) is available. ` +
92
+ `You are running v${CLI_VERSION}. Run "npm update -g @ascendkit/cli" to upgrade.`);
93
+ this.upgradeWarned = true;
94
+ }
95
+ }
64
96
  async request(method, path, body) {
65
97
  const url = `${this.baseUrl}${path}`;
66
98
  const init = {
@@ -71,6 +103,7 @@ export class AscendKitClient {
71
103
  init.body = JSON.stringify(body);
72
104
  }
73
105
  const response = await fetch(url, init);
106
+ this.checkUpgradeHeader(response);
74
107
  if (!response.ok) {
75
108
  const text = await response.text();
76
109
  throw new Error(`AscendKit API error ${response.status}: ${text}`);
@@ -88,6 +121,7 @@ export class AscendKitClient {
88
121
  init.body = JSON.stringify(body);
89
122
  }
90
123
  const response = await fetch(url, init);
124
+ this.checkUpgradeHeader(response);
91
125
  if (!response.ok) {
92
126
  const text = await response.text();
93
127
  throw new Error(`AscendKit API error ${response.status}: ${text}`);
@@ -100,12 +134,13 @@ export class AscendKitClient {
100
134
  const url = `${this.baseUrl}${path}`;
101
135
  const init = {
102
136
  method,
103
- headers: { "Content-Type": "application/json" },
137
+ headers: { "Content-Type": "application/json", ...this.versionHeader },
104
138
  };
105
139
  if (body !== undefined) {
106
140
  init.body = JSON.stringify(body);
107
141
  }
108
142
  const response = await fetch(url, init);
143
+ this.checkUpgradeHeader(response);
109
144
  if (!response.ok) {
110
145
  const text = await response.text();
111
146
  throw new Error(`AscendKit API error ${response.status}: ${text}`);
@@ -136,6 +171,7 @@ export class AscendKitClient {
136
171
  init.body = JSON.stringify(body);
137
172
  }
138
173
  const response = await fetch(url, init);
174
+ this.checkUpgradeHeader(response);
139
175
  if (!response.ok) {
140
176
  const text = await response.text();
141
177
  throw new Error(`AscendKit API error ${response.status}: ${text}`);
package/dist/cli.js CHANGED
@@ -1,6 +1,8 @@
1
1
  #!/usr/bin/env node
2
2
  import { readFileSync, writeFileSync } from "fs";
3
+ import { createRequire } from "module";
3
4
  import { AscendKitClient } from "./api/client.js";
5
+ import { DEFAULT_API_URL } from "./constants.js";
4
6
  import { loadAuth, loadEnvContext } from "./utils/credentials.js";
5
7
  import * as auth from "./commands/auth.js";
6
8
  import * as content from "./commands/content.js";
@@ -8,154 +10,36 @@ import * as surveys from "./commands/surveys.js";
8
10
  import * as platform from "./commands/platform.js";
9
11
  import * as journeys from "./commands/journeys.js";
10
12
  import * as email from "./commands/email.js";
13
+ import * as webhooks from "./commands/webhooks.js";
11
14
  import { parseDelay } from "./utils/duration.js";
12
- const HELP = `ascendkit - AscendKit CLI
15
+ const require = createRequire(import.meta.url);
16
+ const { version: CLI_VERSION } = require("../package.json");
17
+ const HELP = `ascendkit v${CLI_VERSION} - AscendKit CLI
13
18
 
14
19
  Usage: ascendkit <command> [options]
15
- ascendkit help [section]
20
+ ascendkit help <section>
16
21
 
17
- Platform Commands:
18
- init Initialize AscendKit in this directory (login via browser)
19
- --backend <url> API URL (default: https://api.ascendkit.com)
20
- --portal <url> Portal URL (default: http://localhost:3000)
21
- logout Clear local credentials
22
- set-env <public-key> Set active environment by public key
23
- projects list List your projects
24
- env list --project <project-id> List environments for a project
25
- env use <tier> --project <project-id> Set active environment by tier
22
+ Getting Started:
23
+ init Initialize AscendKit in this directory (login via browser)
24
+ set-env <key> Set active environment by public key
25
+ status Show current login and environment state
26
+ logout Clear local credentials
26
27
 
27
- Service Commands (require 'ascendkit init' AND 'ascendkit set-env'):
28
- auth settings Get auth settings
29
- auth settings update Update auth settings
30
- --providers <p1,p2,...> Comma-separated provider list
31
- --email-verification <true|false> Toggle email verification
32
- --waitlist <true|false> Toggle waitlist
33
- --password-reset <true|false> Toggle password reset
34
- --session-duration <duration> e.g. "7d", "24h"
35
- auth providers <p1,p2,...> Set enabled providers
36
- auth oauth <provider> Open browser to configure OAuth credentials
37
- auth oauth set <provider> Set OAuth credentials from CLI
38
- --client-id <id>
39
- --client-secret <secret> (not recommended; appears in shell history)
40
- --client-secret-stdin Read secret from stdin
41
- --callback-url <url> Auth callback URL for this provider
42
- auth users List project users
28
+ Services:
29
+ auth Authentication, providers, OAuth, users
30
+ templates Email templates and versioning
31
+ survey Surveys, questions, distribution, analytics
32
+ journey Lifecycle journeys, nodes, transitions
33
+ email Email settings, domain verification, DNS
34
+ webhook Webhook endpoints and testing
43
35
 
44
- content create Create a content template
45
- --name <name>
46
- --subject <subject>
47
- --body-html <html>
48
- --body-text <text>
49
- --slug <slug> URL-safe identifier
50
- --description <description> Template description
51
- content list List all templates
52
- --query <search> Search by name/slug/description
53
- --system true Show only system templates
54
- --custom true Show only custom templates
55
- content get <template-id> Get a template
56
- content update <template-id> Update a template (creates new version)
57
- --subject <subject>
58
- --body-html <html>
59
- --body-text <text>
60
- --change-note <note>
61
- content delete <template-id> Delete a template
62
- content versions <template-id> List template versions
63
- content version <template-id> <n> Get specific version
36
+ Project Management:
37
+ projects List and create projects
38
+ env List, switch, update, and promote environments
39
+ verify Check all services in the active environment
64
40
 
65
- survey create Create a survey
66
- --name <name>
67
- --type <nps|csat|custom>
68
- --definition <json>
69
- survey list List all surveys
70
- survey get <survey-id> Get a survey
71
- survey update <survey-id> Update a survey
72
- --name <name>
73
- --status <draft|active|paused>
74
- --definition <json>
75
- survey delete <survey-id> Delete a survey
76
- survey distribute <survey-id> Distribute to users
77
- --users <usr_id1,usr_id2,...>
78
- survey invitations <survey-id> List invitations
79
- survey analytics <survey-id> Get analytics
80
- survey export-definition <survey-id> Export survey definition JSON
81
- --out <file> Optional output file path (prints to stdout if omitted)
82
- survey import-definition <survey-id> Import survey definition JSON (definition only; slug unchanged)
83
- --in <file> Input JSON file (accepts raw definition or {definition: ...})
84
-
85
- journey create Create a journey
86
- --name <name>
87
- --entry-event <event> e.g. "user.created"
88
- --entry-node <node> First node name
89
- --nodes <json> Node definitions (JSON, optional)
90
- --transitions <json> Transition definitions (JSON)
91
- --description <description>
92
- --entry-conditions <json> Filter on entry event payload
93
- --re-entry-policy <skip|restart> Default: skip
94
- journey list List all journeys
95
- --status <draft|active|paused|archived>
96
- journey get <journey-id> Get a journey
97
- journey update <journey-id> Update a journey
98
- --name <name>
99
- --nodes <json>
100
- --transitions <json>
101
- --description <description>
102
- --entry-event <event>
103
- --entry-node <node>
104
- --entry-conditions <json>
105
- --re-entry-policy <skip|restart>
106
- journey delete <journey-id> Delete a journey
107
- journey activate <journey-id> Activate a draft journey
108
- journey pause <journey-id> Pause an active journey
109
- journey archive <journey-id> Archive a journey (permanent)
110
- journey analytics <journey-id> Get journey analytics
111
- journey list-nodes <journey-id> List nodes with action and transition counts
112
- journey add-node <journey-id> Add a node
113
- --name <node-name>
114
- --action <json> Optional action JSON (default: {"type":"none"})
115
- --terminal <true|false> Optional terminal flag (default: false)
116
- journey edit-node <journey-id> <node-name>
117
- --action <json> Optional action JSON
118
- --terminal <true|false> Optional terminal flag
119
- journey remove-node <journey-id> <node-name>
120
- journey list-transitions <journey-id>
121
- --from <node-name> Optional source node filter
122
- --to <node-name> Optional target node filter
123
- journey add-transition <journey-id>
124
- --from <node-name>
125
- --to <node-name>
126
- --trigger <json> Trigger JSON, e.g. {"type":"event","event":"user.login"}
127
- --priority <n> Optional numeric priority
128
- --name <transition-name> Optional transition name
129
- journey edit-transition <journey-id> <transition-name>
130
- --trigger <json> Optional trigger JSON
131
- --priority <n> Optional numeric priority
132
- journey remove-transition <journey-id> <transition-name>
133
-
134
- email settings Get email settings
135
- email settings update Update email settings
136
- --from-email <email> Sender email address
137
- --from-name <name> Sender display name
138
- email identity Show current identity mode and next steps
139
- email use-default Switch to AscendKit default sender
140
- email use-custom <domain> Switch to customer-owned identity (starts DNS setup)
141
- --from-email <email> Optional sender after domain setup
142
- --from-name <name> Optional display name after domain setup
143
- email setup-domain <domain> Start domain verification
144
- email domain-status Check domain verification status
145
- --watch Poll until verified/failed
146
- --interval <seconds> Poll interval for --watch (default: 15)
147
- email open-dns Show DNS provider URL for current domain
148
- --domain <domain> Optional override domain
149
- --open Open URL in browser
150
- email remove-domain Remove domain and reset to default
151
-
152
- status Show current login and environment state
153
- verify Check all services in the active environment
154
-
155
- Environment:
156
- ASCENDKIT_PUBLIC_KEY Environment public key (overrides stored credentials)
157
- ASCENDKIT_API_URL API URL (default: https://api.ascendkit.com)
158
- `;
41
+ Run "ascendkit help <section>" for detailed command usage.
42
+ e.g. ascendkit help auth, ascendkit help journey`;
159
43
  const HELP_SECTION = {
160
44
  auth: `Usage: ascendkit auth <command>
161
45
 
@@ -166,16 +50,16 @@ Commands:
166
50
  auth oauth <provider>
167
51
  auth oauth set <provider> --client-id <id> [--client-secret <secret> | --client-secret-stdin] [--callback-url <url>]
168
52
  auth users`,
169
- content: `Usage: ascendkit content <command>
53
+ templates: `Usage: ascendkit templates <command>
170
54
 
171
55
  Commands:
172
- content create --name <name> --subject <subject> --body-html <html> --body-text <text> [--slug <slug>] [--description <description>]
173
- content list [--query <search>] [--system true|--custom true]
174
- content get <template-id>
175
- content update <template-id> [--subject <subject>] [--body-html <html>] [--body-text <text>] [--change-note <note>]
176
- content delete <template-id>
177
- content versions <template-id>
178
- content version <template-id> <n>`,
56
+ templates create --name <name> --subject <subject> --body-html <html> --body-text <text> [--slug <slug>] [--description <description>]
57
+ templates list [--query <search>] [--system true|--custom true]
58
+ templates get <template-id>
59
+ templates update <template-id> [--subject <subject>] [--body-html <html>] [--body-text <text>] [--change-note <note>]
60
+ templates delete <template-id>
61
+ templates versions <template-id>
62
+ templates version <template-id> <n>`,
179
63
  survey: `Usage: ascendkit survey <command>
180
64
 
181
65
  Commands:
@@ -191,7 +75,6 @@ Commands:
191
75
  survey import-definition <survey-id> --in <file>
192
76
 
193
77
  Notes:
194
- - Question-level editing commands are available via MCP tools, not this terminal CLI.
195
78
  - import-definition only updates the survey definition. It does not mutate slug/name/status.`,
196
79
  journey: `Usage: ascendkit journey <command>
197
80
 
@@ -225,17 +108,34 @@ Commands:
225
108
  email domain-status [--watch] [--interval <seconds>]
226
109
  email open-dns [--domain <domain>] [--open]
227
110
  email remove-domain`,
111
+ webhook: `Usage: ascendkit webhook <command>
112
+
113
+ Commands:
114
+ webhook create --url <url> [--events <e1,e2,...>]
115
+ webhook list
116
+ webhook get <webhook-id>
117
+ webhook update <webhook-id> [--url <url>] [--events <e1,e2,...>] [--status <active|inactive>]
118
+ webhook delete <webhook-id>
119
+ webhook test <webhook-id> [--event <event-type>]`,
228
120
  env: `Usage: ascendkit env <command>
229
121
 
230
122
  Commands:
231
123
  env list --project <project-id>
232
- env use <tier> --project <project-id>`,
233
- projects: `Usage: ascendkit projects list`,
124
+ env use <tier> --project <project-id>
125
+ env update <env-id> --project <project-id> [--name <name>] [--description <desc>]
126
+ env promote <env-id> --target <tier>`,
127
+ projects: `Usage: ascendkit projects <command>
128
+
129
+ Commands:
130
+ projects list
131
+ projects create --name <name> [--description <description>] [--services <s1,s2,...>]`,
234
132
  };
235
133
  function printSectionHelp(section) {
236
134
  if (!section)
237
135
  return false;
238
- const key = section.toLowerCase();
136
+ let key = section.toLowerCase();
137
+ if (key === "content")
138
+ key = "templates";
239
139
  const text = HELP_SECTION[key];
240
140
  if (!text)
241
141
  return false;
@@ -263,7 +163,7 @@ function getClient() {
263
163
  process.exit(1);
264
164
  }
265
165
  const client = new AscendKitClient({
266
- apiUrl: apiUrl ?? "https://api.ascendkit.com",
166
+ apiUrl: apiUrl ?? DEFAULT_API_URL,
267
167
  publicKey,
268
168
  });
269
169
  if (auth?.token) {
@@ -333,6 +233,10 @@ function table(rows, columns) {
333
233
  }
334
234
  async function run() {
335
235
  const args = process.argv.slice(2);
236
+ if (args[0] === "--version" || args[0] === "-v" || args[0] === "-V") {
237
+ console.log(CLI_VERSION);
238
+ return;
239
+ }
336
240
  if (args.length === 0 || args[0] === "--help" || args[0] === "-h") {
337
241
  console.log(HELP);
338
242
  return;
@@ -381,8 +285,34 @@ async function run() {
381
285
  { key: "enabledServices", label: "Services", width: 30 },
382
286
  ]);
383
287
  }
288
+ else if (action === "create") {
289
+ const flags = parseFlags(args.slice(2));
290
+ if (!flags.name) {
291
+ console.error("Usage: ascendkit projects create --name <name> [--description <description>] [--services <s1,s2,...>]");
292
+ process.exit(1);
293
+ }
294
+ try {
295
+ output(await platform.createProject(flags.name, flags.description, flags.services?.split(",")));
296
+ }
297
+ catch (err) {
298
+ let message = err instanceof Error ? err.message : String(err);
299
+ const jsonMatch = message.match(/\{.*\}/s);
300
+ if (jsonMatch) {
301
+ try {
302
+ const parsed = JSON.parse(jsonMatch[0]);
303
+ if (parsed.error)
304
+ message = parsed.error;
305
+ else if (parsed.detail)
306
+ message = parsed.detail;
307
+ }
308
+ catch { /* use raw message */ }
309
+ }
310
+ console.error(message);
311
+ process.exit(1);
312
+ }
313
+ }
384
314
  else {
385
- console.error('Usage: ascendkit projects list');
315
+ console.error('Usage: ascendkit projects list|create');
386
316
  process.exit(1);
387
317
  }
388
318
  return;
@@ -409,6 +339,7 @@ async function run() {
409
339
  case "auth":
410
340
  await runAuth(client, action, args.slice(2));
411
341
  break;
342
+ case "templates":
412
343
  case "content":
413
344
  await runContent(client, action, args.slice(2));
414
345
  break;
@@ -421,6 +352,9 @@ async function run() {
421
352
  case "email":
422
353
  await runEmail(client, action, args.slice(2));
423
354
  break;
355
+ case "webhook":
356
+ await runWebhook(client, action, args.slice(2));
357
+ break;
424
358
  default:
425
359
  console.error(`Unknown command: ${domain}`);
426
360
  console.error('Run "ascendkit --help" for usage');
@@ -448,9 +382,74 @@ async function runEnv(action, rest) {
448
382
  }
449
383
  await platform.useEnvironment(rest[0], flags.project);
450
384
  break;
385
+ case "promote": {
386
+ const envId = rest[0];
387
+ const target = flags.target;
388
+ if (!envId || !target) {
389
+ console.error("Usage: ascendkit env promote <env-id> --target <tier>");
390
+ process.exit(1);
391
+ }
392
+ try {
393
+ const result = await platform.promoteEnvironment(envId, target);
394
+ console.log("Promotion successful:");
395
+ console.log(JSON.stringify(result, null, 2));
396
+ }
397
+ catch (err) {
398
+ let message = err instanceof Error ? err.message : String(err);
399
+ const jsonMatch = message.match(/\{.*\}/s);
400
+ if (jsonMatch) {
401
+ try {
402
+ const parsed = JSON.parse(jsonMatch[0]);
403
+ if (parsed.error)
404
+ message = parsed.error;
405
+ else if (parsed.detail)
406
+ message = parsed.detail;
407
+ }
408
+ catch { /* use raw message */ }
409
+ }
410
+ console.error(message);
411
+ process.exit(1);
412
+ }
413
+ break;
414
+ }
415
+ case "update": {
416
+ const envId = rest[0];
417
+ if (!envId || !flags.project) {
418
+ console.error("Usage: ascendkit env update <env-id> --project <project-id> [--name <name>] [--description <desc>]");
419
+ process.exit(1);
420
+ }
421
+ const name = flags.name;
422
+ const description = flags.description;
423
+ if (!name && description === undefined) {
424
+ console.error("Provide at least --name or --description to update.");
425
+ process.exit(1);
426
+ }
427
+ try {
428
+ const result = await platform.updateEnvironment(flags.project, envId, name, description);
429
+ console.log("Environment updated:");
430
+ console.log(JSON.stringify(result, null, 2));
431
+ }
432
+ catch (err) {
433
+ let message = err instanceof Error ? err.message : String(err);
434
+ const jsonMatch = message.match(/\{.*\}/s);
435
+ if (jsonMatch) {
436
+ try {
437
+ const parsed = JSON.parse(jsonMatch[0]);
438
+ if (parsed.error)
439
+ message = parsed.error;
440
+ else if (parsed.detail)
441
+ message = parsed.detail;
442
+ }
443
+ catch { /* use raw message */ }
444
+ }
445
+ console.error(message);
446
+ process.exit(1);
447
+ }
448
+ break;
449
+ }
451
450
  default:
452
451
  console.error(`Unknown env command: ${action}`);
453
- console.error("Usage: ascendkit env list|use");
452
+ console.error("Usage: ascendkit env list|use|update|promote");
454
453
  process.exit(1);
455
454
  }
456
455
  }
@@ -541,7 +540,7 @@ async function runContent(client, action, rest) {
541
540
  switch (action) {
542
541
  case "create":
543
542
  if (!flags.name || !flags.subject || !flags["body-html"] || !flags["body-text"]) {
544
- console.error("Usage: ascendkit content create --name <n> --subject <s> --body-html <h> --body-text <t> [--slug <slug>] [--description <desc>]");
543
+ console.error("Usage: ascendkit templates create --name <n> --subject <s> --body-html <h> --body-text <t> [--slug <slug>] [--description <desc>]");
545
544
  process.exit(1);
546
545
  }
547
546
  output(await content.createTemplate(client, {
@@ -569,14 +568,14 @@ async function runContent(client, action, rest) {
569
568
  }
570
569
  case "get":
571
570
  if (!rest[0]) {
572
- console.error("Usage: ascendkit content get <template-id>");
571
+ console.error("Usage: ascendkit templates get <template-id>");
573
572
  process.exit(1);
574
573
  }
575
574
  output(await content.getTemplate(client, rest[0]));
576
575
  break;
577
576
  case "update":
578
577
  if (!rest[0]) {
579
- console.error("Usage: ascendkit content update <template-id> [--flags]");
578
+ console.error("Usage: ascendkit templates update <template-id> [--flags]");
580
579
  process.exit(1);
581
580
  }
582
581
  output(await content.updateTemplate(client, rest[0], {
@@ -588,27 +587,27 @@ async function runContent(client, action, rest) {
588
587
  break;
589
588
  case "delete":
590
589
  if (!rest[0]) {
591
- console.error("Usage: ascendkit content delete <template-id>");
590
+ console.error("Usage: ascendkit templates delete <template-id>");
592
591
  process.exit(1);
593
592
  }
594
593
  output(await content.deleteTemplate(client, rest[0]));
595
594
  break;
596
595
  case "versions":
597
596
  if (!rest[0]) {
598
- console.error("Usage: ascendkit content versions <template-id>");
597
+ console.error("Usage: ascendkit templates versions <template-id>");
599
598
  process.exit(1);
600
599
  }
601
600
  output(await content.listVersions(client, rest[0]));
602
601
  break;
603
602
  case "version":
604
603
  if (!rest[0] || !rest[1]) {
605
- console.error("Usage: ascendkit content version <template-id> <n>");
604
+ console.error("Usage: ascendkit templates version <template-id> <n>");
606
605
  process.exit(1);
607
606
  }
608
607
  output(await content.getVersion(client, rest[0], parseInt(rest[1], 10)));
609
608
  break;
610
609
  default:
611
- console.error(`Unknown content command: ${action}`);
610
+ console.error(`Unknown templates command: ${action}`);
612
611
  process.exit(1);
613
612
  }
614
613
  }
@@ -721,6 +720,59 @@ async function runSurvey(client, action, rest) {
721
720
  }));
722
721
  break;
723
722
  }
723
+ case "list-questions":
724
+ if (!rest[0]) {
725
+ console.error("Usage: ascendkit survey list-questions <survey-id>");
726
+ process.exit(1);
727
+ }
728
+ output(await surveys.listQuestions(client, rest[0]));
729
+ break;
730
+ case "add-question": {
731
+ if (!rest[0] || !flags.type || !flags.title) {
732
+ console.error("Usage: ascendkit survey add-question <survey-id> --type <type> --title <title> [--name <name>] [--required <true|false>] [--choices <c1,c2,...>] [--position <n>]");
733
+ process.exit(1);
734
+ }
735
+ const params = { type: flags.type, title: flags.title };
736
+ if (flags.name)
737
+ params.name = flags.name;
738
+ if (flags.required)
739
+ params.isRequired = flags.required === "true";
740
+ if (flags.choices)
741
+ params.choices = flags.choices.split(",");
742
+ if (flags.position != null)
743
+ params.position = Number(flags.position);
744
+ output(await surveys.addQuestion(client, rest[0], params));
745
+ break;
746
+ }
747
+ case "edit-question": {
748
+ if (!rest[0] || !rest[1]) {
749
+ console.error("Usage: ascendkit survey edit-question <survey-id> <question-name> [--title <title>] [--required <true|false>] [--choices <c1,c2,...>]");
750
+ process.exit(1);
751
+ }
752
+ const params = {};
753
+ if (flags.title)
754
+ params.title = flags.title;
755
+ if (flags.required)
756
+ params.isRequired = flags.required === "true";
757
+ if (flags.choices)
758
+ params.choices = flags.choices.split(",");
759
+ output(await surveys.editQuestion(client, rest[0], rest[1], params));
760
+ break;
761
+ }
762
+ case "remove-question":
763
+ if (!rest[0] || !rest[1]) {
764
+ console.error("Usage: ascendkit survey remove-question <survey-id> <question-name>");
765
+ process.exit(1);
766
+ }
767
+ output(await surveys.removeQuestion(client, rest[0], rest[1]));
768
+ break;
769
+ case "reorder-questions":
770
+ if (!rest[0] || !flags.order) {
771
+ console.error("Usage: ascendkit survey reorder-questions <survey-id> --order <name1,name2,...>");
772
+ process.exit(1);
773
+ }
774
+ output(await surveys.reorderQuestions(client, rest[0], flags.order.split(",")));
775
+ break;
724
776
  default:
725
777
  console.error(`Unknown survey command: ${action}`);
726
778
  console.error('Run "ascendkit survey --help" for usage');
@@ -1009,6 +1061,65 @@ async function runJourney(client, action, rest) {
1009
1061
  process.exit(1);
1010
1062
  }
1011
1063
  }
1064
+ async function runWebhook(client, action, rest) {
1065
+ const flags = parseFlags(rest);
1066
+ switch (action) {
1067
+ case "create":
1068
+ if (!flags.url) {
1069
+ console.error("Usage: ascendkit webhook create --url <url> [--events <e1,e2,...>]");
1070
+ process.exit(1);
1071
+ }
1072
+ output(await webhooks.createWebhook(client, {
1073
+ url: flags.url,
1074
+ events: flags.events ? flags.events.split(",") : undefined,
1075
+ }));
1076
+ break;
1077
+ case "list":
1078
+ table(await webhooks.listWebhooks(client), [
1079
+ { key: "id", label: "ID" },
1080
+ { key: "url", label: "URL", width: 40 },
1081
+ { key: "status", label: "Status" },
1082
+ { key: "events", label: "Events", width: 30 },
1083
+ ]);
1084
+ break;
1085
+ case "get":
1086
+ if (!rest[0]) {
1087
+ console.error("Usage: ascendkit webhook get <webhook-id>");
1088
+ process.exit(1);
1089
+ }
1090
+ output(await webhooks.getWebhook(client, rest[0]));
1091
+ break;
1092
+ case "update":
1093
+ if (!rest[0]) {
1094
+ console.error("Usage: ascendkit webhook update <webhook-id> [--flags]");
1095
+ process.exit(1);
1096
+ }
1097
+ output(await webhooks.updateWebhook(client, rest[0], {
1098
+ url: flags.url,
1099
+ events: flags.events ? flags.events.split(",") : undefined,
1100
+ status: flags.status,
1101
+ }));
1102
+ break;
1103
+ case "delete":
1104
+ if (!rest[0]) {
1105
+ console.error("Usage: ascendkit webhook delete <webhook-id>");
1106
+ process.exit(1);
1107
+ }
1108
+ output(await webhooks.deleteWebhook(client, rest[0]));
1109
+ break;
1110
+ case "test":
1111
+ if (!rest[0]) {
1112
+ console.error("Usage: ascendkit webhook test <webhook-id> [--event <event-type>]");
1113
+ process.exit(1);
1114
+ }
1115
+ output(await webhooks.testWebhook(client, rest[0], flags.event));
1116
+ break;
1117
+ default:
1118
+ console.error(`Unknown webhook command: ${action}`);
1119
+ console.error('Run "ascendkit webhook --help" for usage');
1120
+ process.exit(1);
1121
+ }
1122
+ }
1012
1123
  async function runEmail(client, action, rest) {
1013
1124
  const flags = parseFlags(rest);
1014
1125
  switch (action) {
@@ -1,6 +1,7 @@
1
1
  export declare function init(apiUrl?: string, portalUrl?: string): Promise<void>;
2
2
  export declare function logout(): void;
3
3
  export declare function listProjects(): Promise<unknown>;
4
+ export declare function createProject(name: string, description?: string, enabledServices?: string[]): Promise<unknown>;
4
5
  export declare function listEnvironments(projectId: string): Promise<unknown>;
5
6
  export declare function useEnvironment(tier: string, projectId: string): Promise<void>;
6
7
  export declare function setEnv(publicKey: string): Promise<void>;
@@ -16,7 +17,7 @@ export interface McpCreateProjectParams {
16
17
  }
17
18
  export interface McpCreateEnvironmentParams {
18
19
  projectId: string;
19
- name: string;
20
+ name?: string;
20
21
  description?: string;
21
22
  tier: string;
22
23
  }
@@ -33,3 +34,16 @@ export declare function mcpListProjects(client: AscendKitClient): Promise<unknow
33
34
  export declare function mcpCreateProject(client: AscendKitClient, params: McpCreateProjectParams): Promise<unknown>;
34
35
  export declare function mcpListEnvironments(client: AscendKitClient, projectId: string): Promise<unknown>;
35
36
  export declare function mcpCreateEnvironment(client: AscendKitClient, params: McpCreateEnvironmentParams): Promise<unknown>;
37
+ export declare function updateEnvironment(projectId: string, envId: string, name?: string, description?: string): Promise<unknown>;
38
+ export declare function promoteEnvironment(environmentId: string, targetTier: string): Promise<unknown>;
39
+ export interface McpUpdateEnvironmentParams {
40
+ projectId: string;
41
+ environmentId: string;
42
+ name?: string;
43
+ description?: string;
44
+ }
45
+ export declare function mcpUpdateEnvironment(client: AscendKitClient, params: McpUpdateEnvironmentParams): Promise<unknown>;
46
+ export declare function mcpPromoteEnvironment(client: AscendKitClient, params: {
47
+ environmentId: string;
48
+ targetTier: string;
49
+ }): Promise<unknown>;
@@ -2,8 +2,7 @@ import { hostname, platform as osPlatform, release } from "node:os";
2
2
  import { readdir, readFile, writeFile } from "node:fs/promises";
3
3
  import path from "node:path";
4
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";
5
+ import { DEFAULT_API_URL, DEFAULT_PORTAL_URL } from "../constants.js";
7
6
  const POLL_INTERVAL_MS = 2000;
8
7
  const DEVICE_CODE_EXPIRY_MS = 300_000; // 5 minutes
9
8
  const IGNORE_DIRS = new Set([".git", "node_modules", ".next", "dist", "build"]);
@@ -66,9 +65,8 @@ async function promptChoice(question, options) {
66
65
  rl.close();
67
66
  }
68
67
  }
69
- async function findEnvFiles(root) {
70
- const examples = [];
71
- const runtime = [];
68
+ async function findEnvFolders(root) {
69
+ const byDir = new Map();
72
70
  async function walk(dir) {
73
71
  const entries = await readdir(dir, { withFileTypes: true });
74
72
  for (const entry of entries) {
@@ -81,18 +79,34 @@ async function findEnvFiles(root) {
81
79
  }
82
80
  if (!entry.isFile())
83
81
  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);
82
+ const isExample = entry.name === ".env.example";
83
+ const isRuntime = entry.name === ".env" || entry.name === ".env.local";
84
+ if (!isExample && !isRuntime)
85
+ continue;
86
+ let bucket = byDir.get(dir);
87
+ if (!bucket) {
88
+ bucket = { examples: [], runtime: [] };
89
+ byDir.set(dir, bucket);
89
90
  }
91
+ if (isExample)
92
+ bucket.examples.push(fullPath);
93
+ else
94
+ bucket.runtime.push(fullPath);
90
95
  }
91
96
  }
92
97
  await walk(root);
93
- examples.sort();
94
- runtime.sort();
95
- return { examples, runtime };
98
+ const folders = [];
99
+ for (const [dir, bucket] of byDir) {
100
+ const rel = path.relative(root, dir) || ".";
101
+ folders.push({
102
+ dir,
103
+ label: rel === "." ? "(project root)" : `${rel}/`,
104
+ examples: bucket.examples.sort(),
105
+ runtime: bucket.runtime.sort(),
106
+ });
107
+ }
108
+ folders.sort((a, b) => a.dir.localeCompare(b.dir));
109
+ return folders;
96
110
  }
97
111
  function readEnvValue(content, key) {
98
112
  const match = content.match(new RegExp(`^\\s*${key}=(.*)$`, "m"));
@@ -263,28 +277,26 @@ export async function init(apiUrl, portalUrl) {
263
277
  console.log(`\nInitialized as ${result.email ?? "unknown"}`);
264
278
  // Seed .env files with API URL and empty key placeholders
265
279
  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) {
280
+ const folders = await findEnvFolders(cwd);
281
+ if (folders.length > 0) {
282
+ console.log(`\nFound env files in ${folders.length} folder(s).`);
283
+ for (const folder of folders) {
284
+ const fileNames = [...folder.examples, ...folder.runtime]
285
+ .map(f => path.basename(f)).join(", ");
286
+ const update = await promptYesNo(` ${folder.label} (${fileNames}) — update?`, true);
287
+ if (!update)
288
+ continue;
289
+ for (const filePath of folder.examples) {
272
290
  const changed = await updateEnvExampleFile(filePath);
273
291
  if (changed)
274
- console.log(` Updated ${path.relative(cwd, filePath)}`);
292
+ console.log(` Updated ${path.relative(cwd, filePath)}`);
275
293
  }
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) {
294
+ for (const filePath of folder.runtime) {
283
295
  const changed = await updateRuntimeEnvFile(filePath, api, "", "", {
284
296
  preserveExistingKeys: true,
285
297
  });
286
298
  if (changed)
287
- console.log(` Updated ${path.relative(cwd, filePath)}`);
299
+ console.log(` Updated ${path.relative(cwd, filePath)}`);
288
300
  }
289
301
  }
290
302
  }
@@ -318,6 +330,14 @@ export async function listProjects() {
318
330
  const auth = requireAuth();
319
331
  return apiRequest(auth.apiUrl, "GET", "/api/platform/projects", undefined, auth.token);
320
332
  }
333
+ export async function createProject(name, description, enabledServices) {
334
+ const auth = requireAuth();
335
+ return apiRequest(auth.apiUrl, "POST", "/api/platform/projects", {
336
+ name,
337
+ description: description ?? "",
338
+ enabledServices: enabledServices ?? [],
339
+ }, auth.token);
340
+ }
321
341
  export async function listEnvironments(projectId) {
322
342
  const auth = requireAuth();
323
343
  return apiRequest(auth.apiUrl, "GET", `/api/platform/projects/${projectId}/environments`, undefined, auth.token);
@@ -426,40 +446,38 @@ export async function setEnv(publicKey) {
426
446
  console.log(` → Environment: ${result.environment.name}`);
427
447
  console.log(` → Role: ${result.role}`);
428
448
  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) {
449
+ const folders = await findEnvFolders(cwd);
450
+ const updatedFiles = [];
451
+ const consentedRuntimeFolders = [];
452
+ if (folders.length > 0) {
453
+ console.log(`\nFound env files in ${folders.length} folder(s).`);
454
+ for (const folder of folders) {
455
+ const fileNames = [...folder.examples, ...folder.runtime]
456
+ .map(f => path.basename(f)).join(", ");
457
+ const update = await promptYesNo(` ${folder.label} (${fileNames}) — update?`, true);
458
+ if (!update)
459
+ continue;
460
+ for (const filePath of folder.examples) {
437
461
  const changed = await updateEnvExampleFile(filePath);
438
462
  if (changed)
439
- updatedExamples.push(path.relative(cwd, filePath));
463
+ updatedFiles.push(path.relative(cwd, filePath));
440
464
  }
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));
465
+ if (folder.runtime.length > 0) {
466
+ consentedRuntimeFolders.push(folder);
467
+ for (const filePath of folder.runtime) {
468
+ const changed = await updateRuntimeEnvFile(filePath, auth.apiUrl, result.environment.publicKey, result.environment.secretKey ?? "");
469
+ if (changed)
470
+ updatedFiles.push(path.relative(cwd, filePath));
471
+ }
452
472
  }
453
473
  }
454
474
  }
455
475
  console.log("\nUpdated files:");
456
- if (updatedExamples.length === 0 && updatedRuntime.length === 0) {
476
+ if (updatedFiles.length === 0) {
457
477
  console.log(" (none)");
458
478
  }
459
479
  else {
460
- for (const filePath of updatedExamples)
461
- console.log(` - ${filePath}`);
462
- for (const filePath of updatedRuntime)
480
+ for (const filePath of updatedFiles)
463
481
  console.log(` - ${filePath}`);
464
482
  }
465
483
  if (!result.environment.secretKey) {
@@ -468,9 +486,11 @@ export async function setEnv(publicKey) {
468
486
  // --- Webhook setup ---
469
487
  const webhookSecret = await setupWebhook(auth, result.environment.publicKey);
470
488
  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 });
489
+ if (consentedRuntimeFolders.length > 0) {
490
+ for (const folder of consentedRuntimeFolders) {
491
+ for (const filePath of folder.runtime) {
492
+ await updateRuntimeEnvFile(filePath, auth.apiUrl, result.environment.publicKey, result.environment.secretKey ?? "", { preserveExistingKeys: true, webhookSecret });
493
+ }
474
494
  }
475
495
  console.log("\nWebhook secret written to .env file(s).");
476
496
  }
@@ -515,3 +535,27 @@ export async function mcpListEnvironments(client, projectId) {
515
535
  export async function mcpCreateEnvironment(client, params) {
516
536
  return client.platformRequest("POST", `/api/platform/projects/${params.projectId}/environments`, { name: params.name, description: params.description ?? "", tier: params.tier });
517
537
  }
538
+ export async function updateEnvironment(projectId, envId, name, description) {
539
+ const auth = requireAuth();
540
+ const body = {};
541
+ if (name)
542
+ body.name = name;
543
+ if (description !== undefined)
544
+ body.description = description;
545
+ return apiRequest(auth.apiUrl, "PATCH", `/api/platform/projects/${projectId}/environments/${envId}`, body, auth.token);
546
+ }
547
+ export async function promoteEnvironment(environmentId, targetTier) {
548
+ const auth = requireAuth();
549
+ return apiRequest(auth.apiUrl, "POST", `/api/platform/environments/${environmentId}/promote`, { targetTier, dryRun: false }, auth.token);
550
+ }
551
+ export async function mcpUpdateEnvironment(client, params) {
552
+ const body = {};
553
+ if (params.name)
554
+ body.name = params.name;
555
+ if (params.description !== undefined)
556
+ body.description = params.description;
557
+ return client.platformRequest("PATCH", `/api/platform/projects/${params.projectId}/environments/${params.environmentId}`, body);
558
+ }
559
+ export async function mcpPromoteEnvironment(client, params) {
560
+ return client.platformRequest("POST", `/api/platform/environments/${params.environmentId}/promote`, { targetTier: params.targetTier, dryRun: false });
561
+ }
@@ -0,0 +1,2 @@
1
+ export declare const DEFAULT_API_URL = "https://api.ascendkit.dev";
2
+ export declare const DEFAULT_PORTAL_URL = "https://ascendkit.dev";
@@ -0,0 +1,2 @@
1
+ export const DEFAULT_API_URL = "https://api.ascendkit.dev";
2
+ export const DEFAULT_PORTAL_URL = "https://ascendkit.dev";
package/dist/mcp.js CHANGED
@@ -10,8 +10,9 @@ import { registerPlatformTools } from "./tools/platform.js";
10
10
  import { registerEmailTools } from "./tools/email.js";
11
11
  import { registerJourneyTools } from "./tools/journeys.js";
12
12
  import { registerWebhookTools } from "./tools/webhooks.js";
13
+ import { DEFAULT_API_URL } from "./constants.js";
13
14
  const client = new AscendKitClient({
14
- apiUrl: process.env.ASCENDKIT_API_URL ?? "https://api.ascendkit.com",
15
+ apiUrl: process.env.ASCENDKIT_API_URL ?? DEFAULT_API_URL,
15
16
  });
16
17
  const server = new McpServer({
17
18
  name: "ascendkit",
@@ -19,7 +20,7 @@ const server = new McpServer({
19
20
  });
20
21
  server.tool("ascendkit_configure", "Configure AscendKit with your project's public key. Must be called before using any other AscendKit tools.", {
21
22
  publicKey: z.string().describe("Your project's public key (pk_dev_..., pk_beta_..., or pk_prod_...)"),
22
- apiUrl: z.string().optional().describe("AscendKit API URL (default: https://api.ascendkit.com)"),
23
+ apiUrl: z.string().optional().describe("AscendKit API URL (default: https://api.ascendkit.dev)"),
23
24
  }, async (params) => {
24
25
  client.configure(params.publicKey, params.apiUrl);
25
26
  return {
@@ -29,10 +29,30 @@ export function registerPlatformTools(server, client) {
29
29
  .optional()
30
30
  .describe('Services to enable, e.g. ["auth", "content", "surveys"]'),
31
31
  }, async (params) => {
32
- const data = await platform.mcpCreateProject(client, params);
33
- return {
34
- content: [{ type: "text", text: JSON.stringify(data, null, 2) }],
35
- };
32
+ try {
33
+ const data = await platform.mcpCreateProject(client, params);
34
+ return {
35
+ content: [{ type: "text", text: JSON.stringify(data, null, 2) }],
36
+ };
37
+ }
38
+ catch (err) {
39
+ let message = err instanceof Error ? err.message : String(err);
40
+ const jsonMatch = message.match(/\{.*\}/s);
41
+ if (jsonMatch) {
42
+ try {
43
+ const parsed = JSON.parse(jsonMatch[0]);
44
+ if (parsed.error)
45
+ message = parsed.error;
46
+ else if (parsed.detail)
47
+ message = parsed.detail;
48
+ }
49
+ catch { /* use raw message */ }
50
+ }
51
+ return {
52
+ content: [{ type: "text", text: message }],
53
+ isError: true,
54
+ };
55
+ }
36
56
  });
37
57
  server.tool("platform_list_environments", "List environments for a project", {
38
58
  projectId: z.string().describe("Project ID (prj_ prefixed)"),
@@ -46,7 +66,8 @@ export function registerPlatformTools(server, client) {
46
66
  projectId: z.string().describe("Project ID (prj_ prefixed)"),
47
67
  name: z
48
68
  .string()
49
- .describe('Environment name, e.g. "Development", "Production"'),
69
+ .optional()
70
+ .describe("Environment name. If omitted, derived from project name + tier."),
50
71
  description: z
51
72
  .string()
52
73
  .optional()
@@ -55,9 +76,100 @@ export function registerPlatformTools(server, client) {
55
76
  .string()
56
77
  .describe('Environment tier: "dev", "beta", or "prod"'),
57
78
  }, async (params) => {
58
- const data = await platform.mcpCreateEnvironment(client, params);
59
- return {
60
- content: [{ type: "text", text: JSON.stringify(data, null, 2) }],
61
- };
79
+ try {
80
+ const data = await platform.mcpCreateEnvironment(client, params);
81
+ return {
82
+ content: [{ type: "text", text: JSON.stringify(data, null, 2) }],
83
+ };
84
+ }
85
+ catch (err) {
86
+ let message = err instanceof Error ? err.message : String(err);
87
+ const jsonMatch = message.match(/\{.*\}/s);
88
+ if (jsonMatch) {
89
+ try {
90
+ const parsed = JSON.parse(jsonMatch[0]);
91
+ if (parsed.error)
92
+ message = parsed.error;
93
+ else if (parsed.detail)
94
+ message = parsed.detail;
95
+ }
96
+ catch { /* use raw message */ }
97
+ }
98
+ return {
99
+ content: [{ type: "text", text: message }],
100
+ isError: true,
101
+ };
102
+ }
103
+ });
104
+ server.tool("platform_update_environment", "Update an environment's name or description.", {
105
+ projectId: z.string().describe("Project ID (prj_ prefixed)"),
106
+ environmentId: z.string().describe("Environment ID (env_ prefixed)"),
107
+ name: z.string().optional().describe("New environment name"),
108
+ description: z.string().optional().describe("New environment description"),
109
+ }, async (params) => {
110
+ if (!params.name && !params.description) {
111
+ return {
112
+ content: [{ type: "text", text: "At least one of name or description is required." }],
113
+ isError: true,
114
+ };
115
+ }
116
+ try {
117
+ const data = await platform.mcpUpdateEnvironment(client, params);
118
+ return {
119
+ content: [{ type: "text", text: JSON.stringify(data, null, 2) }],
120
+ };
121
+ }
122
+ catch (err) {
123
+ let message = err instanceof Error ? err.message : String(err);
124
+ const jsonMatch = message.match(/\{.*\}/s);
125
+ if (jsonMatch) {
126
+ try {
127
+ const parsed = JSON.parse(jsonMatch[0]);
128
+ if (parsed.error)
129
+ message = parsed.error;
130
+ else if (parsed.detail)
131
+ message = parsed.detail;
132
+ }
133
+ catch { /* use raw message */ }
134
+ }
135
+ return {
136
+ content: [{ type: "text", text: message }],
137
+ isError: true,
138
+ };
139
+ }
140
+ });
141
+ server.tool("platform_promote_environment", "Promote an environment's configuration to a higher tier (dev → beta → prod).", {
142
+ environmentId: z.string().describe("Environment ID to promote"),
143
+ targetTier: z
144
+ .string()
145
+ .describe('Target tier: "beta" or "prod"'),
146
+ }, async (params) => {
147
+ try {
148
+ const data = await platform.mcpPromoteEnvironment(client, {
149
+ environmentId: params.environmentId,
150
+ targetTier: params.targetTier,
151
+ });
152
+ return {
153
+ content: [{ type: "text", text: JSON.stringify(data, null, 2) }],
154
+ };
155
+ }
156
+ catch (err) {
157
+ let message = err instanceof Error ? err.message : String(err);
158
+ const jsonMatch = message.match(/\{.*\}/s);
159
+ if (jsonMatch) {
160
+ try {
161
+ const parsed = JSON.parse(jsonMatch[0]);
162
+ if (parsed.error)
163
+ message = parsed.error;
164
+ else if (parsed.detail)
165
+ message = parsed.detail;
166
+ }
167
+ catch { /* use raw message */ }
168
+ }
169
+ return {
170
+ content: [{ type: "text", text: message }],
171
+ isError: true,
172
+ };
173
+ }
62
174
  });
63
175
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ascendkit/cli",
3
- "version": "0.1.10",
3
+ "version": "0.2.0",
4
4
  "description": "AscendKit CLI and MCP server",
5
5
  "author": "ascendkit.dev",
6
6
  "license": "MIT",
@@ -8,7 +8,14 @@
8
8
  "publishConfig": {
9
9
  "access": "public"
10
10
  },
11
- "keywords": ["ascendkit", "cli", "mcp", "model-context-protocol", "b2b", "saas"],
11
+ "keywords": [
12
+ "ascendkit",
13
+ "cli",
14
+ "mcp",
15
+ "model-context-protocol",
16
+ "b2b",
17
+ "saas"
18
+ ],
12
19
  "type": "module",
13
20
  "main": "dist/cli.js",
14
21
  "files": [
@@ -23,8 +30,7 @@
23
30
  "scripts": {
24
31
  "build": "tsc",
25
32
  "dev": "tsc --watch",
26
- "start": "node dist/cli.js",
27
- "release:local": "./scripts/release-local.sh"
33
+ "start": "node dist/cli.js"
28
34
  },
29
35
  "dependencies": {
30
36
  "@modelcontextprotocol/sdk": "^1.0.0",
package/dist/index.d.ts DELETED
@@ -1,2 +0,0 @@
1
- #!/usr/bin/env node
2
- export {};
package/dist/index.js DELETED
@@ -1,29 +0,0 @@
1
- #!/usr/bin/env node
2
- import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
3
- import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
4
- import { AscendKitClient } from "./api/client.js";
5
- import { registerAuthTools } from "./tools/auth.js";
6
- import { registerContentTools } from "./tools/content.js";
7
- import { registerSurveyTools } from "./tools/surveys.js";
8
- const args = process.argv.slice(2);
9
- const publicKey = args[0];
10
- const apiUrl = args[1] ?? process.env.ASCENDKIT_API_URL ?? "http://localhost:8000";
11
- if (!publicKey) {
12
- console.error("Usage: ascendkit-mcp <public-key> [api-url]");
13
- console.error(" public-key Your project's public key (pk_live_...)");
14
- console.error(" api-url AscendKit API URL (default: http://localhost:8000)");
15
- process.exit(1);
16
- }
17
- const client = new AscendKitClient({ apiUrl, publicKey });
18
- const server = new McpServer({
19
- name: "ascendkit",
20
- version: "0.1.0",
21
- });
22
- registerAuthTools(server, client);
23
- registerContentTools(server, client);
24
- registerSurveyTools(server, client);
25
- async function main() {
26
- const transport = new StdioServerTransport();
27
- await server.connect(transport);
28
- }
29
- main().catch(console.error);