@ascendkit/cli 0.1.10 → 0.1.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/api/client.d.ts +4 -0
- package/dist/api/client.js +38 -2
- package/dist/cli.js +238 -163
- package/dist/commands/platform.d.ts +6 -0
- package/dist/commands/platform.js +82 -55
- package/dist/constants.d.ts +2 -0
- package/dist/constants.js +2 -0
- package/dist/mcp.js +3 -2
- package/dist/tools/platform.js +82 -8
- package/package.json +10 -4
package/dist/api/client.d.ts
CHANGED
|
@@ -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). */
|
package/dist/api/client.js
CHANGED
|
@@ -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 ??
|
|
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
|
|
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
|
|
20
|
+
ascendkit help <section>
|
|
16
21
|
|
|
17
|
-
|
|
18
|
-
init
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
logout
|
|
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
|
-
|
|
28
|
-
auth
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
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
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
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, and promote environments
|
|
39
|
+
verify Check all services in the active environment
|
|
64
40
|
|
|
65
|
-
|
|
66
|
-
|
|
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
|
-
|
|
53
|
+
templates: `Usage: ascendkit templates <command>
|
|
170
54
|
|
|
171
55
|
Commands:
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
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,33 @@ 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
|
-
|
|
124
|
+
env use <tier> --project <project-id>
|
|
125
|
+
env promote <env-id> --target <tier>`,
|
|
126
|
+
projects: `Usage: ascendkit projects <command>
|
|
127
|
+
|
|
128
|
+
Commands:
|
|
129
|
+
projects list
|
|
130
|
+
projects create --name <name> [--description <description>] [--services <s1,s2,...>]`,
|
|
234
131
|
};
|
|
235
132
|
function printSectionHelp(section) {
|
|
236
133
|
if (!section)
|
|
237
134
|
return false;
|
|
238
|
-
|
|
135
|
+
let key = section.toLowerCase();
|
|
136
|
+
if (key === "content")
|
|
137
|
+
key = "templates";
|
|
239
138
|
const text = HELP_SECTION[key];
|
|
240
139
|
if (!text)
|
|
241
140
|
return false;
|
|
@@ -263,7 +162,7 @@ function getClient() {
|
|
|
263
162
|
process.exit(1);
|
|
264
163
|
}
|
|
265
164
|
const client = new AscendKitClient({
|
|
266
|
-
apiUrl: apiUrl ??
|
|
165
|
+
apiUrl: apiUrl ?? DEFAULT_API_URL,
|
|
267
166
|
publicKey,
|
|
268
167
|
});
|
|
269
168
|
if (auth?.token) {
|
|
@@ -333,6 +232,10 @@ function table(rows, columns) {
|
|
|
333
232
|
}
|
|
334
233
|
async function run() {
|
|
335
234
|
const args = process.argv.slice(2);
|
|
235
|
+
if (args[0] === "--version" || args[0] === "-v" || args[0] === "-V") {
|
|
236
|
+
console.log(CLI_VERSION);
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
336
239
|
if (args.length === 0 || args[0] === "--help" || args[0] === "-h") {
|
|
337
240
|
console.log(HELP);
|
|
338
241
|
return;
|
|
@@ -381,8 +284,34 @@ async function run() {
|
|
|
381
284
|
{ key: "enabledServices", label: "Services", width: 30 },
|
|
382
285
|
]);
|
|
383
286
|
}
|
|
287
|
+
else if (action === "create") {
|
|
288
|
+
const flags = parseFlags(args.slice(2));
|
|
289
|
+
if (!flags.name) {
|
|
290
|
+
console.error("Usage: ascendkit projects create --name <name> [--description <description>] [--services <s1,s2,...>]");
|
|
291
|
+
process.exit(1);
|
|
292
|
+
}
|
|
293
|
+
try {
|
|
294
|
+
output(await platform.createProject(flags.name, flags.description, flags.services?.split(",")));
|
|
295
|
+
}
|
|
296
|
+
catch (err) {
|
|
297
|
+
let message = err instanceof Error ? err.message : String(err);
|
|
298
|
+
const jsonMatch = message.match(/\{.*\}/s);
|
|
299
|
+
if (jsonMatch) {
|
|
300
|
+
try {
|
|
301
|
+
const parsed = JSON.parse(jsonMatch[0]);
|
|
302
|
+
if (parsed.error)
|
|
303
|
+
message = parsed.error;
|
|
304
|
+
else if (parsed.detail)
|
|
305
|
+
message = parsed.detail;
|
|
306
|
+
}
|
|
307
|
+
catch { /* use raw message */ }
|
|
308
|
+
}
|
|
309
|
+
console.error(message);
|
|
310
|
+
process.exit(1);
|
|
311
|
+
}
|
|
312
|
+
}
|
|
384
313
|
else {
|
|
385
|
-
console.error('Usage: ascendkit projects list');
|
|
314
|
+
console.error('Usage: ascendkit projects list|create');
|
|
386
315
|
process.exit(1);
|
|
387
316
|
}
|
|
388
317
|
return;
|
|
@@ -409,6 +338,7 @@ async function run() {
|
|
|
409
338
|
case "auth":
|
|
410
339
|
await runAuth(client, action, args.slice(2));
|
|
411
340
|
break;
|
|
341
|
+
case "templates":
|
|
412
342
|
case "content":
|
|
413
343
|
await runContent(client, action, args.slice(2));
|
|
414
344
|
break;
|
|
@@ -421,6 +351,9 @@ async function run() {
|
|
|
421
351
|
case "email":
|
|
422
352
|
await runEmail(client, action, args.slice(2));
|
|
423
353
|
break;
|
|
354
|
+
case "webhook":
|
|
355
|
+
await runWebhook(client, action, args.slice(2));
|
|
356
|
+
break;
|
|
424
357
|
default:
|
|
425
358
|
console.error(`Unknown command: ${domain}`);
|
|
426
359
|
console.error('Run "ascendkit --help" for usage');
|
|
@@ -448,9 +381,39 @@ async function runEnv(action, rest) {
|
|
|
448
381
|
}
|
|
449
382
|
await platform.useEnvironment(rest[0], flags.project);
|
|
450
383
|
break;
|
|
384
|
+
case "promote": {
|
|
385
|
+
const envId = rest[0];
|
|
386
|
+
const target = flags.target;
|
|
387
|
+
if (!envId || !target) {
|
|
388
|
+
console.error("Usage: ascendkit env promote <env-id> --target <tier>");
|
|
389
|
+
process.exit(1);
|
|
390
|
+
}
|
|
391
|
+
try {
|
|
392
|
+
const result = await platform.promoteEnvironment(envId, target);
|
|
393
|
+
console.log("Promotion successful:");
|
|
394
|
+
console.log(JSON.stringify(result, null, 2));
|
|
395
|
+
}
|
|
396
|
+
catch (err) {
|
|
397
|
+
let message = err instanceof Error ? err.message : String(err);
|
|
398
|
+
const jsonMatch = message.match(/\{.*\}/s);
|
|
399
|
+
if (jsonMatch) {
|
|
400
|
+
try {
|
|
401
|
+
const parsed = JSON.parse(jsonMatch[0]);
|
|
402
|
+
if (parsed.error)
|
|
403
|
+
message = parsed.error;
|
|
404
|
+
else if (parsed.detail)
|
|
405
|
+
message = parsed.detail;
|
|
406
|
+
}
|
|
407
|
+
catch { /* use raw message */ }
|
|
408
|
+
}
|
|
409
|
+
console.error(message);
|
|
410
|
+
process.exit(1);
|
|
411
|
+
}
|
|
412
|
+
break;
|
|
413
|
+
}
|
|
451
414
|
default:
|
|
452
415
|
console.error(`Unknown env command: ${action}`);
|
|
453
|
-
console.error("Usage: ascendkit env list|use");
|
|
416
|
+
console.error("Usage: ascendkit env list|use|promote");
|
|
454
417
|
process.exit(1);
|
|
455
418
|
}
|
|
456
419
|
}
|
|
@@ -541,7 +504,7 @@ async function runContent(client, action, rest) {
|
|
|
541
504
|
switch (action) {
|
|
542
505
|
case "create":
|
|
543
506
|
if (!flags.name || !flags.subject || !flags["body-html"] || !flags["body-text"]) {
|
|
544
|
-
console.error("Usage: ascendkit
|
|
507
|
+
console.error("Usage: ascendkit templates create --name <n> --subject <s> --body-html <h> --body-text <t> [--slug <slug>] [--description <desc>]");
|
|
545
508
|
process.exit(1);
|
|
546
509
|
}
|
|
547
510
|
output(await content.createTemplate(client, {
|
|
@@ -569,14 +532,14 @@ async function runContent(client, action, rest) {
|
|
|
569
532
|
}
|
|
570
533
|
case "get":
|
|
571
534
|
if (!rest[0]) {
|
|
572
|
-
console.error("Usage: ascendkit
|
|
535
|
+
console.error("Usage: ascendkit templates get <template-id>");
|
|
573
536
|
process.exit(1);
|
|
574
537
|
}
|
|
575
538
|
output(await content.getTemplate(client, rest[0]));
|
|
576
539
|
break;
|
|
577
540
|
case "update":
|
|
578
541
|
if (!rest[0]) {
|
|
579
|
-
console.error("Usage: ascendkit
|
|
542
|
+
console.error("Usage: ascendkit templates update <template-id> [--flags]");
|
|
580
543
|
process.exit(1);
|
|
581
544
|
}
|
|
582
545
|
output(await content.updateTemplate(client, rest[0], {
|
|
@@ -588,27 +551,27 @@ async function runContent(client, action, rest) {
|
|
|
588
551
|
break;
|
|
589
552
|
case "delete":
|
|
590
553
|
if (!rest[0]) {
|
|
591
|
-
console.error("Usage: ascendkit
|
|
554
|
+
console.error("Usage: ascendkit templates delete <template-id>");
|
|
592
555
|
process.exit(1);
|
|
593
556
|
}
|
|
594
557
|
output(await content.deleteTemplate(client, rest[0]));
|
|
595
558
|
break;
|
|
596
559
|
case "versions":
|
|
597
560
|
if (!rest[0]) {
|
|
598
|
-
console.error("Usage: ascendkit
|
|
561
|
+
console.error("Usage: ascendkit templates versions <template-id>");
|
|
599
562
|
process.exit(1);
|
|
600
563
|
}
|
|
601
564
|
output(await content.listVersions(client, rest[0]));
|
|
602
565
|
break;
|
|
603
566
|
case "version":
|
|
604
567
|
if (!rest[0] || !rest[1]) {
|
|
605
|
-
console.error("Usage: ascendkit
|
|
568
|
+
console.error("Usage: ascendkit templates version <template-id> <n>");
|
|
606
569
|
process.exit(1);
|
|
607
570
|
}
|
|
608
571
|
output(await content.getVersion(client, rest[0], parseInt(rest[1], 10)));
|
|
609
572
|
break;
|
|
610
573
|
default:
|
|
611
|
-
console.error(`Unknown
|
|
574
|
+
console.error(`Unknown templates command: ${action}`);
|
|
612
575
|
process.exit(1);
|
|
613
576
|
}
|
|
614
577
|
}
|
|
@@ -721,6 +684,59 @@ async function runSurvey(client, action, rest) {
|
|
|
721
684
|
}));
|
|
722
685
|
break;
|
|
723
686
|
}
|
|
687
|
+
case "list-questions":
|
|
688
|
+
if (!rest[0]) {
|
|
689
|
+
console.error("Usage: ascendkit survey list-questions <survey-id>");
|
|
690
|
+
process.exit(1);
|
|
691
|
+
}
|
|
692
|
+
output(await surveys.listQuestions(client, rest[0]));
|
|
693
|
+
break;
|
|
694
|
+
case "add-question": {
|
|
695
|
+
if (!rest[0] || !flags.type || !flags.title) {
|
|
696
|
+
console.error("Usage: ascendkit survey add-question <survey-id> --type <type> --title <title> [--name <name>] [--required <true|false>] [--choices <c1,c2,...>] [--position <n>]");
|
|
697
|
+
process.exit(1);
|
|
698
|
+
}
|
|
699
|
+
const params = { type: flags.type, title: flags.title };
|
|
700
|
+
if (flags.name)
|
|
701
|
+
params.name = flags.name;
|
|
702
|
+
if (flags.required)
|
|
703
|
+
params.isRequired = flags.required === "true";
|
|
704
|
+
if (flags.choices)
|
|
705
|
+
params.choices = flags.choices.split(",");
|
|
706
|
+
if (flags.position != null)
|
|
707
|
+
params.position = Number(flags.position);
|
|
708
|
+
output(await surveys.addQuestion(client, rest[0], params));
|
|
709
|
+
break;
|
|
710
|
+
}
|
|
711
|
+
case "edit-question": {
|
|
712
|
+
if (!rest[0] || !rest[1]) {
|
|
713
|
+
console.error("Usage: ascendkit survey edit-question <survey-id> <question-name> [--title <title>] [--required <true|false>] [--choices <c1,c2,...>]");
|
|
714
|
+
process.exit(1);
|
|
715
|
+
}
|
|
716
|
+
const params = {};
|
|
717
|
+
if (flags.title)
|
|
718
|
+
params.title = flags.title;
|
|
719
|
+
if (flags.required)
|
|
720
|
+
params.isRequired = flags.required === "true";
|
|
721
|
+
if (flags.choices)
|
|
722
|
+
params.choices = flags.choices.split(",");
|
|
723
|
+
output(await surveys.editQuestion(client, rest[0], rest[1], params));
|
|
724
|
+
break;
|
|
725
|
+
}
|
|
726
|
+
case "remove-question":
|
|
727
|
+
if (!rest[0] || !rest[1]) {
|
|
728
|
+
console.error("Usage: ascendkit survey remove-question <survey-id> <question-name>");
|
|
729
|
+
process.exit(1);
|
|
730
|
+
}
|
|
731
|
+
output(await surveys.removeQuestion(client, rest[0], rest[1]));
|
|
732
|
+
break;
|
|
733
|
+
case "reorder-questions":
|
|
734
|
+
if (!rest[0] || !flags.order) {
|
|
735
|
+
console.error("Usage: ascendkit survey reorder-questions <survey-id> --order <name1,name2,...>");
|
|
736
|
+
process.exit(1);
|
|
737
|
+
}
|
|
738
|
+
output(await surveys.reorderQuestions(client, rest[0], flags.order.split(",")));
|
|
739
|
+
break;
|
|
724
740
|
default:
|
|
725
741
|
console.error(`Unknown survey command: ${action}`);
|
|
726
742
|
console.error('Run "ascendkit survey --help" for usage');
|
|
@@ -1009,6 +1025,65 @@ async function runJourney(client, action, rest) {
|
|
|
1009
1025
|
process.exit(1);
|
|
1010
1026
|
}
|
|
1011
1027
|
}
|
|
1028
|
+
async function runWebhook(client, action, rest) {
|
|
1029
|
+
const flags = parseFlags(rest);
|
|
1030
|
+
switch (action) {
|
|
1031
|
+
case "create":
|
|
1032
|
+
if (!flags.url) {
|
|
1033
|
+
console.error("Usage: ascendkit webhook create --url <url> [--events <e1,e2,...>]");
|
|
1034
|
+
process.exit(1);
|
|
1035
|
+
}
|
|
1036
|
+
output(await webhooks.createWebhook(client, {
|
|
1037
|
+
url: flags.url,
|
|
1038
|
+
events: flags.events ? flags.events.split(",") : undefined,
|
|
1039
|
+
}));
|
|
1040
|
+
break;
|
|
1041
|
+
case "list":
|
|
1042
|
+
table(await webhooks.listWebhooks(client), [
|
|
1043
|
+
{ key: "id", label: "ID" },
|
|
1044
|
+
{ key: "url", label: "URL", width: 40 },
|
|
1045
|
+
{ key: "status", label: "Status" },
|
|
1046
|
+
{ key: "events", label: "Events", width: 30 },
|
|
1047
|
+
]);
|
|
1048
|
+
break;
|
|
1049
|
+
case "get":
|
|
1050
|
+
if (!rest[0]) {
|
|
1051
|
+
console.error("Usage: ascendkit webhook get <webhook-id>");
|
|
1052
|
+
process.exit(1);
|
|
1053
|
+
}
|
|
1054
|
+
output(await webhooks.getWebhook(client, rest[0]));
|
|
1055
|
+
break;
|
|
1056
|
+
case "update":
|
|
1057
|
+
if (!rest[0]) {
|
|
1058
|
+
console.error("Usage: ascendkit webhook update <webhook-id> [--flags]");
|
|
1059
|
+
process.exit(1);
|
|
1060
|
+
}
|
|
1061
|
+
output(await webhooks.updateWebhook(client, rest[0], {
|
|
1062
|
+
url: flags.url,
|
|
1063
|
+
events: flags.events ? flags.events.split(",") : undefined,
|
|
1064
|
+
status: flags.status,
|
|
1065
|
+
}));
|
|
1066
|
+
break;
|
|
1067
|
+
case "delete":
|
|
1068
|
+
if (!rest[0]) {
|
|
1069
|
+
console.error("Usage: ascendkit webhook delete <webhook-id>");
|
|
1070
|
+
process.exit(1);
|
|
1071
|
+
}
|
|
1072
|
+
output(await webhooks.deleteWebhook(client, rest[0]));
|
|
1073
|
+
break;
|
|
1074
|
+
case "test":
|
|
1075
|
+
if (!rest[0]) {
|
|
1076
|
+
console.error("Usage: ascendkit webhook test <webhook-id> [--event <event-type>]");
|
|
1077
|
+
process.exit(1);
|
|
1078
|
+
}
|
|
1079
|
+
output(await webhooks.testWebhook(client, rest[0], flags.event));
|
|
1080
|
+
break;
|
|
1081
|
+
default:
|
|
1082
|
+
console.error(`Unknown webhook command: ${action}`);
|
|
1083
|
+
console.error('Run "ascendkit webhook --help" for usage');
|
|
1084
|
+
process.exit(1);
|
|
1085
|
+
}
|
|
1086
|
+
}
|
|
1012
1087
|
async function runEmail(client, action, rest) {
|
|
1013
1088
|
const flags = parseFlags(rest);
|
|
1014
1089
|
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>;
|
|
@@ -33,3 +34,8 @@ 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 promoteEnvironment(environmentId: string, targetTier: string): Promise<unknown>;
|
|
38
|
+
export declare function mcpPromoteEnvironment(client: AscendKitClient, params: {
|
|
39
|
+
environmentId: string;
|
|
40
|
+
targetTier: string;
|
|
41
|
+
}): 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
|
-
|
|
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
|
|
70
|
-
const
|
|
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
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
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
|
-
|
|
94
|
-
|
|
95
|
-
|
|
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
|
|
267
|
-
if (
|
|
268
|
-
console.log(`\nFound ${
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
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(`
|
|
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(`
|
|
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
|
|
430
|
-
const
|
|
431
|
-
const
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
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
|
-
|
|
463
|
+
updatedFiles.push(path.relative(cwd, filePath));
|
|
440
464
|
}
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
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 (
|
|
476
|
+
if (updatedFiles.length === 0) {
|
|
457
477
|
console.log(" (none)");
|
|
458
478
|
}
|
|
459
479
|
else {
|
|
460
|
-
for (const filePath of
|
|
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 (
|
|
472
|
-
for (const
|
|
473
|
-
|
|
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,10 @@ 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 promoteEnvironment(environmentId, targetTier) {
|
|
539
|
+
const auth = requireAuth();
|
|
540
|
+
return apiRequest(auth.apiUrl, "POST", `/api/platform/environments/${environmentId}/promote`, { targetTier, dryRun: false }, auth.token);
|
|
541
|
+
}
|
|
542
|
+
export async function mcpPromoteEnvironment(client, params) {
|
|
543
|
+
return client.platformRequest("POST", `/api/platform/environments/${params.environmentId}/promote`, { targetTier: params.targetTier, dryRun: false });
|
|
544
|
+
}
|
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 ??
|
|
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.
|
|
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 {
|
package/dist/tools/platform.js
CHANGED
|
@@ -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
|
-
|
|
33
|
-
|
|
34
|
-
|
|
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)"),
|
|
@@ -55,9 +75,63 @@ export function registerPlatformTools(server, client) {
|
|
|
55
75
|
.string()
|
|
56
76
|
.describe('Environment tier: "dev", "beta", or "prod"'),
|
|
57
77
|
}, async (params) => {
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
78
|
+
try {
|
|
79
|
+
const data = await platform.mcpCreateEnvironment(client, params);
|
|
80
|
+
return {
|
|
81
|
+
content: [{ type: "text", text: JSON.stringify(data, null, 2) }],
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
catch (err) {
|
|
85
|
+
let message = err instanceof Error ? err.message : String(err);
|
|
86
|
+
const jsonMatch = message.match(/\{.*\}/s);
|
|
87
|
+
if (jsonMatch) {
|
|
88
|
+
try {
|
|
89
|
+
const parsed = JSON.parse(jsonMatch[0]);
|
|
90
|
+
if (parsed.error)
|
|
91
|
+
message = parsed.error;
|
|
92
|
+
else if (parsed.detail)
|
|
93
|
+
message = parsed.detail;
|
|
94
|
+
}
|
|
95
|
+
catch { /* use raw message */ }
|
|
96
|
+
}
|
|
97
|
+
return {
|
|
98
|
+
content: [{ type: "text", text: message }],
|
|
99
|
+
isError: true,
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
});
|
|
103
|
+
server.tool("platform_promote_environment", "Promote an environment's configuration to a higher tier (dev → beta → prod).", {
|
|
104
|
+
environmentId: z.string().describe("Environment ID to promote"),
|
|
105
|
+
targetTier: z
|
|
106
|
+
.string()
|
|
107
|
+
.describe('Target tier: "beta" or "prod"'),
|
|
108
|
+
}, async (params) => {
|
|
109
|
+
try {
|
|
110
|
+
const data = await platform.mcpPromoteEnvironment(client, {
|
|
111
|
+
environmentId: params.environmentId,
|
|
112
|
+
targetTier: params.targetTier,
|
|
113
|
+
});
|
|
114
|
+
return {
|
|
115
|
+
content: [{ type: "text", text: JSON.stringify(data, null, 2) }],
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
catch (err) {
|
|
119
|
+
let message = err instanceof Error ? err.message : String(err);
|
|
120
|
+
const jsonMatch = message.match(/\{.*\}/s);
|
|
121
|
+
if (jsonMatch) {
|
|
122
|
+
try {
|
|
123
|
+
const parsed = JSON.parse(jsonMatch[0]);
|
|
124
|
+
if (parsed.error)
|
|
125
|
+
message = parsed.error;
|
|
126
|
+
else if (parsed.detail)
|
|
127
|
+
message = parsed.detail;
|
|
128
|
+
}
|
|
129
|
+
catch { /* use raw message */ }
|
|
130
|
+
}
|
|
131
|
+
return {
|
|
132
|
+
content: [{ type: "text", text: message }],
|
|
133
|
+
isError: true,
|
|
134
|
+
};
|
|
135
|
+
}
|
|
62
136
|
});
|
|
63
137
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ascendkit/cli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.11",
|
|
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": [
|
|
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",
|