@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.
- package/dist/api/client.d.ts +4 -0
- package/dist/api/client.js +38 -2
- package/dist/cli.js +274 -163
- package/dist/commands/platform.d.ts +15 -1
- package/dist/commands/platform.js +99 -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 +121 -9
- package/package.json +10 -4
- package/dist/index.d.ts +0 -2
- package/dist/index.js +0 -29
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, update, 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,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
|
-
|
|
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
|
-
|
|
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 ??
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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,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
|
+
}
|
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)"),
|
|
@@ -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
|
-
.
|
|
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
|
-
|
|
59
|
-
|
|
60
|
-
|
|
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.
|
|
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": [
|
|
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
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);
|