@ascendkit/cli 0.2.6 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/api/client.d.ts +1 -0
- package/dist/api/client.js +31 -8
- package/dist/cli.js +713 -384
- package/dist/commands/content.js +4 -4
- package/dist/commands/email.d.ts +62 -6
- package/dist/commands/email.js +26 -17
- package/dist/commands/journeys.d.ts +1 -0
- package/dist/commands/journeys.js +9 -6
- package/dist/commands/platform.d.ts +22 -2
- package/dist/commands/platform.js +203 -101
- package/dist/commands/surveys.js +5 -5
- package/dist/mcp.js +31 -3
- package/dist/tools/auth.js +32 -11
- package/dist/tools/content.js +24 -12
- package/dist/tools/email.js +47 -17
- package/dist/tools/import.js +9 -9
- package/dist/tools/journeys.js +21 -14
- package/dist/tools/platform.js +125 -10
- package/dist/tools/surveys.js +9 -9
- package/dist/utils/journey-format.d.ts +6 -0
- package/dist/utils/journey-format.js +6 -4
- package/dist/utils/survey-format.js +2 -2
- package/package.json +5 -5
package/dist/commands/content.js
CHANGED
|
@@ -9,10 +9,10 @@ export async function listTemplates(client, params) {
|
|
|
9
9
|
searchParams.set("isSystem", String(params.isSystem));
|
|
10
10
|
const qs = searchParams.toString();
|
|
11
11
|
const path = qs ? `/api/content/templates?${qs}` : "/api/content/templates";
|
|
12
|
-
return client.
|
|
12
|
+
return client.managedGet(path);
|
|
13
13
|
}
|
|
14
14
|
export async function getTemplate(client, templateId) {
|
|
15
|
-
return client.
|
|
15
|
+
return client.managedGet(`/api/content/templates/${templateId}`);
|
|
16
16
|
}
|
|
17
17
|
export async function updateTemplate(client, templateId, params) {
|
|
18
18
|
return client.managedPut(`/api/content/templates/${templateId}`, params);
|
|
@@ -21,8 +21,8 @@ export async function deleteTemplate(client, templateId) {
|
|
|
21
21
|
return client.managedDelete(`/api/content/templates/${templateId}`);
|
|
22
22
|
}
|
|
23
23
|
export async function listVersions(client, templateId) {
|
|
24
|
-
return client.
|
|
24
|
+
return client.managedGet(`/api/content/templates/${templateId}/versions`);
|
|
25
25
|
}
|
|
26
26
|
export async function getVersion(client, templateId, versionNumber) {
|
|
27
|
-
return client.
|
|
27
|
+
return client.managedGet(`/api/content/templates/${templateId}/versions/${versionNumber}`);
|
|
28
28
|
}
|
package/dist/commands/email.d.ts
CHANGED
|
@@ -23,15 +23,71 @@ export interface EmailSettings {
|
|
|
23
23
|
value: string;
|
|
24
24
|
}>;
|
|
25
25
|
dnsProvider?: DnsProviderInfo;
|
|
26
|
+
identities?: EmailIdentity[];
|
|
27
|
+
}
|
|
28
|
+
export interface EmailIdentity {
|
|
29
|
+
email: string;
|
|
30
|
+
displayName: string;
|
|
31
|
+
verificationStatus: string;
|
|
32
|
+
isDefault: boolean;
|
|
33
|
+
}
|
|
34
|
+
export interface EmailIdentitiesResponse {
|
|
35
|
+
identities: EmailIdentity[];
|
|
36
|
+
active?: {
|
|
37
|
+
fromEmail?: string;
|
|
38
|
+
fromName?: string;
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
export interface DnsCheckRecord {
|
|
42
|
+
name: string;
|
|
43
|
+
type: string;
|
|
44
|
+
expectedValue: string;
|
|
45
|
+
found: boolean;
|
|
46
|
+
observedValue: string | null;
|
|
47
|
+
mismatch: boolean;
|
|
48
|
+
error: string | null;
|
|
49
|
+
checkedAt: string;
|
|
50
|
+
}
|
|
51
|
+
export interface DnsCheckResponse {
|
|
52
|
+
summary: {
|
|
53
|
+
total: number;
|
|
54
|
+
found: number;
|
|
55
|
+
notFound: number;
|
|
56
|
+
mismatch: number;
|
|
57
|
+
errored: number;
|
|
58
|
+
};
|
|
59
|
+
records: DnsCheckRecord[];
|
|
26
60
|
}
|
|
27
61
|
export declare function getSettings(client: AscendKitClient): Promise<EmailSettings>;
|
|
28
62
|
export declare function updateSettings(client: AscendKitClient, params: UpdateEmailSettingsParams): Promise<EmailSettings>;
|
|
29
63
|
export declare function setupDomain(client: AscendKitClient, domain: string): Promise<EmailSettings>;
|
|
30
|
-
export declare function checkDomainStatus(client: AscendKitClient): Promise<
|
|
64
|
+
export declare function checkDomainStatus(client: AscendKitClient): Promise<{
|
|
65
|
+
domain?: string;
|
|
66
|
+
status?: string;
|
|
67
|
+
dnsRecords?: Array<{
|
|
68
|
+
type: string;
|
|
69
|
+
name: string;
|
|
70
|
+
value: string;
|
|
71
|
+
}>;
|
|
72
|
+
dnsProvider?: DnsProviderInfo;
|
|
73
|
+
}>;
|
|
74
|
+
export declare function checkDnsRecords(client: AscendKitClient): Promise<DnsCheckResponse>;
|
|
31
75
|
export declare function getDnsProvider(client: AscendKitClient, domain?: string): Promise<DnsProviderInfo>;
|
|
32
76
|
export declare function removeDomain(client: AscendKitClient): Promise<EmailSettings>;
|
|
33
|
-
export declare function
|
|
34
|
-
export declare function
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
}): Promise<
|
|
77
|
+
export declare function listIdentities(client: AscendKitClient): Promise<EmailIdentitiesResponse>;
|
|
78
|
+
export declare function createIdentity(client: AscendKitClient, identity: {
|
|
79
|
+
email: string;
|
|
80
|
+
displayName?: string;
|
|
81
|
+
}): Promise<EmailIdentitiesResponse>;
|
|
82
|
+
export declare function resendIdentityVerification(client: AscendKitClient, email: string): Promise<EmailIdentitiesResponse>;
|
|
83
|
+
export declare function setDefaultIdentity(client: AscendKitClient, identity: {
|
|
84
|
+
email: string;
|
|
85
|
+
displayName?: string;
|
|
86
|
+
}): Promise<EmailIdentitiesResponse>;
|
|
87
|
+
export declare function removeIdentity(client: AscendKitClient, email: string): Promise<EmailIdentitiesResponse>;
|
|
88
|
+
export declare function sendTestEmail(client: AscendKitClient, request: {
|
|
89
|
+
to: string;
|
|
90
|
+
fromIdentityEmail?: string;
|
|
91
|
+
}): Promise<{
|
|
92
|
+
message: string;
|
|
93
|
+
}>;
|
package/dist/commands/email.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export async function getSettings(client) {
|
|
2
|
-
return client.
|
|
2
|
+
return client.managedGet("/api/email/settings");
|
|
3
3
|
}
|
|
4
4
|
export async function updateSettings(client, params) {
|
|
5
5
|
const body = {};
|
|
@@ -13,27 +13,36 @@ export async function setupDomain(client, domain) {
|
|
|
13
13
|
return client.managedPost("/api/email/settings/setup-domain", { domain });
|
|
14
14
|
}
|
|
15
15
|
export async function checkDomainStatus(client) {
|
|
16
|
-
return client.
|
|
16
|
+
return client.managedGet("/api/email/settings/domain-status");
|
|
17
|
+
}
|
|
18
|
+
export async function checkDnsRecords(client) {
|
|
19
|
+
return client.managedGet("/api/email/settings/check-dns-records");
|
|
17
20
|
}
|
|
18
21
|
export async function getDnsProvider(client, domain) {
|
|
19
22
|
const suffix = domain ? `?domain=${encodeURIComponent(domain)}` : "";
|
|
20
|
-
return client.
|
|
23
|
+
return client.managedGet(`/api/email/settings/dns-provider${suffix}`);
|
|
21
24
|
}
|
|
22
25
|
export async function removeDomain(client) {
|
|
23
26
|
return client.managedDelete("/api/email/settings/domain");
|
|
24
27
|
}
|
|
25
|
-
export async function
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
return
|
|
28
|
+
export async function listIdentities(client) {
|
|
29
|
+
return client.managedGet("/api/email/settings/identities");
|
|
30
|
+
}
|
|
31
|
+
export async function createIdentity(client, identity) {
|
|
32
|
+
return client.managedPost("/api/email/settings/identities", {
|
|
33
|
+
email: identity.email,
|
|
34
|
+
displayName: identity.displayName ?? "",
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
export async function resendIdentityVerification(client, email) {
|
|
38
|
+
return client.managedPost(`/api/email/settings/identities/${encodeURIComponent(email)}/resend`, {});
|
|
39
|
+
}
|
|
40
|
+
export async function setDefaultIdentity(client, identity) {
|
|
41
|
+
return client.managedPost(`/api/email/settings/identities/${encodeURIComponent(identity.email)}/default`, { displayName: identity.displayName ?? "" });
|
|
42
|
+
}
|
|
43
|
+
export async function removeIdentity(client, email) {
|
|
44
|
+
return client.managedDelete(`/api/email/settings/identities/${encodeURIComponent(email)}`);
|
|
45
|
+
}
|
|
46
|
+
export async function sendTestEmail(client, request) {
|
|
47
|
+
return client.managedPost("/api/email/settings/send-test", request);
|
|
39
48
|
}
|
|
@@ -30,6 +30,7 @@ export declare function updateJourney(client: AscendKitClient, journeyId: string
|
|
|
30
30
|
export declare function deleteJourney(client: AscendKitClient, journeyId: string): Promise<unknown>;
|
|
31
31
|
export declare function activateJourney(client: AscendKitClient, journeyId: string): Promise<unknown>;
|
|
32
32
|
export declare function pauseJourney(client: AscendKitClient, journeyId: string): Promise<unknown>;
|
|
33
|
+
export declare function resumeJourney(client: AscendKitClient, journeyId: string): Promise<unknown>;
|
|
33
34
|
export declare function archiveJourney(client: AscendKitClient, journeyId: string): Promise<unknown>;
|
|
34
35
|
export declare function getJourneyAnalytics(client: AscendKitClient, journeyId: string): Promise<unknown>;
|
|
35
36
|
export declare function getJourneyVisualization(client: AscendKitClient, journeyId: string): Promise<unknown>;
|
|
@@ -11,10 +11,10 @@ export async function listJourneys(client, opts) {
|
|
|
11
11
|
if (opts?.offset != null)
|
|
12
12
|
qs.set("offset", String(opts.offset));
|
|
13
13
|
const query = qs.toString();
|
|
14
|
-
return client.
|
|
14
|
+
return client.managedGet(`${BASE}${query ? `?${query}` : ""}`);
|
|
15
15
|
}
|
|
16
16
|
export async function getJourney(client, journeyId) {
|
|
17
|
-
return client.
|
|
17
|
+
return client.managedGet(`${BASE}/${journeyId}`);
|
|
18
18
|
}
|
|
19
19
|
export async function updateJourney(client, journeyId, params) {
|
|
20
20
|
return client.managedRequest("PATCH", `${BASE}/${journeyId}`, params);
|
|
@@ -28,17 +28,20 @@ export async function activateJourney(client, journeyId) {
|
|
|
28
28
|
export async function pauseJourney(client, journeyId) {
|
|
29
29
|
return client.managedPost(`${BASE}/${journeyId}/pause`);
|
|
30
30
|
}
|
|
31
|
+
export async function resumeJourney(client, journeyId) {
|
|
32
|
+
return client.managedPost(`${BASE}/${journeyId}/resume`);
|
|
33
|
+
}
|
|
31
34
|
export async function archiveJourney(client, journeyId) {
|
|
32
35
|
return client.managedPost(`${BASE}/${journeyId}/archive`);
|
|
33
36
|
}
|
|
34
37
|
export async function getJourneyAnalytics(client, journeyId) {
|
|
35
|
-
return client.
|
|
38
|
+
return client.managedGet(`${BASE}/${journeyId}/analytics`);
|
|
36
39
|
}
|
|
37
40
|
export async function getJourneyVisualization(client, journeyId) {
|
|
38
|
-
return client.
|
|
41
|
+
return client.managedGet(`${BASE}/${journeyId}/visualization`);
|
|
39
42
|
}
|
|
40
43
|
export async function listNodes(client, journeyId) {
|
|
41
|
-
return client.
|
|
44
|
+
return client.managedGet(`${BASE}/${journeyId}/nodes`);
|
|
42
45
|
}
|
|
43
46
|
export async function addNode(client, journeyId, params) {
|
|
44
47
|
return client.managedPost(`${BASE}/${journeyId}/nodes`, params);
|
|
@@ -56,7 +59,7 @@ export async function listTransitions(client, journeyId, opts) {
|
|
|
56
59
|
if (opts?.to_node)
|
|
57
60
|
qs.set("to_node", opts.to_node);
|
|
58
61
|
const query = qs.toString();
|
|
59
|
-
return client.
|
|
62
|
+
return client.managedGet(`${BASE}/${journeyId}/transitions${query ? `?${query}` : ""}`);
|
|
60
63
|
}
|
|
61
64
|
export async function addTransition(client, journeyId, params) {
|
|
62
65
|
return client.managedPost(`${BASE}/${journeyId}/transitions`, params);
|
|
@@ -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 showProject(projectId: string): Promise<Record<string, unknown>>;
|
|
4
5
|
export declare function createProject(name: string, description?: string, enabledServices?: string[]): Promise<unknown>;
|
|
5
6
|
export declare function listEnvironments(projectId: string): Promise<unknown>;
|
|
6
7
|
export declare function useEnvironment(tier: string, projectId: string): Promise<void>;
|
|
@@ -30,6 +31,21 @@ export declare function mcpLogin(client: AscendKitClient, params: McpLoginParams
|
|
|
30
31
|
};
|
|
31
32
|
accountId: string;
|
|
32
33
|
}>;
|
|
34
|
+
export declare function mcpSetEnvironmentByPublicKey(client: AscendKitClient, publicKey: string): Promise<{
|
|
35
|
+
updatedFiles: string[];
|
|
36
|
+
environment: {
|
|
37
|
+
id: string;
|
|
38
|
+
name: string;
|
|
39
|
+
tier: string;
|
|
40
|
+
publicKey: string;
|
|
41
|
+
secretKey?: string | null;
|
|
42
|
+
};
|
|
43
|
+
project: {
|
|
44
|
+
id: string;
|
|
45
|
+
name: string;
|
|
46
|
+
};
|
|
47
|
+
role: string;
|
|
48
|
+
}>;
|
|
33
49
|
export declare function mcpListProjects(client: AscendKitClient): Promise<unknown>;
|
|
34
50
|
export declare function mcpCreateProject(client: AscendKitClient, params: McpCreateProjectParams): Promise<unknown>;
|
|
35
51
|
export declare function mcpListEnvironments(client: AscendKitClient, projectId: string): Promise<unknown>;
|
|
@@ -48,10 +64,14 @@ export declare function mcpPromoteEnvironment(client: AscendKitClient, params: {
|
|
|
48
64
|
targetTier: string;
|
|
49
65
|
}): Promise<unknown>;
|
|
50
66
|
export interface McpUpdateEnvironmentVariablesParams {
|
|
51
|
-
projectId
|
|
52
|
-
envId
|
|
67
|
+
projectId?: string;
|
|
68
|
+
envId?: string;
|
|
53
69
|
variables: Record<string, string>;
|
|
54
70
|
}
|
|
71
|
+
export declare function mcpGetEnvironment(client: AscendKitClient, params: {
|
|
72
|
+
projectId?: string;
|
|
73
|
+
envId?: string;
|
|
74
|
+
}): Promise<Record<string, unknown>>;
|
|
55
75
|
export declare function mcpUpdateEnvironmentVariables(client: AscendKitClient, params: McpUpdateEnvironmentVariablesParams): Promise<unknown>;
|
|
56
76
|
export declare function getEnvironment(projectId: string, envId: string): Promise<Record<string, unknown>>;
|
|
57
77
|
export declare function updateEnvironmentVariables(projectId: string, envId: string, variables: Record<string, string>): Promise<unknown>;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
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
|
-
import { loadAuth, saveAuth, deleteAuth, saveEnvContext, ensureGitignore } from "../utils/credentials.js";
|
|
4
|
+
import { loadAuth, loadEnvContext, saveAuth, deleteAuth, saveEnvContext, ensureGitignore, } from "../utils/credentials.js";
|
|
5
5
|
import { DEFAULT_API_URL, DEFAULT_PORTAL_URL } from "../constants.js";
|
|
6
6
|
const POLL_INTERVAL_MS = 2000;
|
|
7
7
|
const DEVICE_CODE_EXPIRY_MS = 300_000; // 5 minutes
|
|
@@ -16,6 +16,14 @@ const ASCENDKIT_ENV_KEYS = [
|
|
|
16
16
|
];
|
|
17
17
|
const ASCENDKIT_BLOCK_START = "# --- AscendKit Managed Environment Variables ---";
|
|
18
18
|
const ASCENDKIT_BLOCK_END = "# --- End AscendKit Managed Environment Variables ---";
|
|
19
|
+
function normalizePromotionTier(targetTier) {
|
|
20
|
+
const normalized = targetTier.trim().toLowerCase();
|
|
21
|
+
if (normalized === "production")
|
|
22
|
+
return "prod";
|
|
23
|
+
if (normalized === "staging")
|
|
24
|
+
return "beta";
|
|
25
|
+
return normalized;
|
|
26
|
+
}
|
|
19
27
|
async function promptYesNo(question, defaultYes) {
|
|
20
28
|
const suffix = defaultYes ? "[Y/n]" : "[y/N]";
|
|
21
29
|
if (!process.stdin.isTTY || !process.stdout.isTTY) {
|
|
@@ -184,13 +192,14 @@ async function updateRuntimeEnvFile(filePath, apiUrl, publicKey, secretKey, opti
|
|
|
184
192
|
const existingPublicKey = readEnvValue(original, "ASCENDKIT_ENV_KEY");
|
|
185
193
|
const existingSecretKey = readEnvValue(original, "ASCENDKIT_SECRET_KEY");
|
|
186
194
|
const existingWebhookSecret = readEnvValue(original, "ASCENDKIT_WEBHOOK_SECRET") ?? "";
|
|
195
|
+
const incomingSecretKey = (secretKey ?? "").trim();
|
|
187
196
|
const resolvedApiUrl = apiUrl;
|
|
188
197
|
const resolvedPublicKey = preserveExistingKeys
|
|
189
198
|
? (existingPublicKey && existingPublicKey.trim() ? existingPublicKey : publicKey)
|
|
190
199
|
: publicKey;
|
|
191
200
|
const resolvedSecretKey = preserveExistingKeys
|
|
192
|
-
? (existingSecretKey && existingSecretKey.trim() ? existingSecretKey :
|
|
193
|
-
:
|
|
201
|
+
? (existingSecretKey && existingSecretKey.trim() ? existingSecretKey : incomingSecretKey)
|
|
202
|
+
: (incomingSecretKey || existingSecretKey || "");
|
|
194
203
|
const resolvedWebhookSecret = options?.webhookSecret ?? existingWebhookSecret;
|
|
195
204
|
return rewriteAscendKitEnvBlock(filePath, {
|
|
196
205
|
NEXT_PUBLIC_ASCENDKIT_API_URL: resolvedApiUrl,
|
|
@@ -201,6 +210,124 @@ async function updateRuntimeEnvFile(filePath, apiUrl, publicKey, secretKey, opti
|
|
|
201
210
|
ASCENDKIT_WEBHOOK_SECRET: resolvedWebhookSecret,
|
|
202
211
|
});
|
|
203
212
|
}
|
|
213
|
+
async function syncLocalEnvFiles(apiUrl, publicKey, secretKey) {
|
|
214
|
+
const cwd = process.cwd();
|
|
215
|
+
const folders = await findEnvFolders(cwd);
|
|
216
|
+
const updatedFiles = [];
|
|
217
|
+
for (const folder of folders) {
|
|
218
|
+
for (const filePath of folder.examples) {
|
|
219
|
+
const changed = await updateEnvExampleFile(filePath);
|
|
220
|
+
if (changed)
|
|
221
|
+
updatedFiles.push(path.relative(cwd, filePath));
|
|
222
|
+
}
|
|
223
|
+
for (const filePath of folder.runtime) {
|
|
224
|
+
const changed = await updateRuntimeEnvFile(filePath, apiUrl, publicKey, secretKey);
|
|
225
|
+
if (changed)
|
|
226
|
+
updatedFiles.push(path.relative(cwd, filePath));
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
return updatedFiles;
|
|
230
|
+
}
|
|
231
|
+
function saveActiveEnvironmentContext(result) {
|
|
232
|
+
saveEnvContext({
|
|
233
|
+
publicKey: result.environment.publicKey,
|
|
234
|
+
projectId: result.project.id,
|
|
235
|
+
projectName: result.project.name,
|
|
236
|
+
environmentId: result.environment.id,
|
|
237
|
+
environmentName: result.environment.name,
|
|
238
|
+
tier: result.environment.tier,
|
|
239
|
+
});
|
|
240
|
+
}
|
|
241
|
+
async function activateEnvironment(auth, result, options) {
|
|
242
|
+
const promptForFileUpdates = options?.promptForFileUpdates ?? true;
|
|
243
|
+
const shouldSetupWebhook = options?.setupWebhook ?? true;
|
|
244
|
+
const logOutput = options?.logOutput ?? true;
|
|
245
|
+
saveActiveEnvironmentContext(result);
|
|
246
|
+
if (logOutput) {
|
|
247
|
+
console.log(` → Project: ${result.project.name}`);
|
|
248
|
+
console.log(` → Environment: ${result.environment.name}`);
|
|
249
|
+
console.log(` → Role: ${result.role}`);
|
|
250
|
+
}
|
|
251
|
+
const cwd = process.cwd();
|
|
252
|
+
const folders = await findEnvFolders(cwd);
|
|
253
|
+
const updatedFiles = [];
|
|
254
|
+
const consentedRuntimeFolders = [];
|
|
255
|
+
if (folders.length > 0) {
|
|
256
|
+
if (logOutput) {
|
|
257
|
+
console.log(`\nFound env files in ${folders.length} folder(s).`);
|
|
258
|
+
}
|
|
259
|
+
for (const folder of folders) {
|
|
260
|
+
let update = true;
|
|
261
|
+
if (promptForFileUpdates) {
|
|
262
|
+
const fileNames = [...folder.examples, ...folder.runtime]
|
|
263
|
+
.map(f => path.basename(f)).join(", ");
|
|
264
|
+
update = await promptYesNo(` ${folder.label} (${fileNames}) — update?`, true);
|
|
265
|
+
}
|
|
266
|
+
if (!update)
|
|
267
|
+
continue;
|
|
268
|
+
for (const filePath of folder.examples) {
|
|
269
|
+
const changed = await updateEnvExampleFile(filePath);
|
|
270
|
+
if (changed)
|
|
271
|
+
updatedFiles.push(path.relative(cwd, filePath));
|
|
272
|
+
}
|
|
273
|
+
if (folder.runtime.length > 0) {
|
|
274
|
+
consentedRuntimeFolders.push(folder);
|
|
275
|
+
for (const filePath of folder.runtime) {
|
|
276
|
+
const changed = await updateRuntimeEnvFile(filePath, auth.apiUrl, result.environment.publicKey, result.environment.secretKey);
|
|
277
|
+
if (changed)
|
|
278
|
+
updatedFiles.push(path.relative(cwd, filePath));
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
if (logOutput) {
|
|
284
|
+
console.log("\nUpdated files:");
|
|
285
|
+
if (updatedFiles.length === 0) {
|
|
286
|
+
console.log(" (none)");
|
|
287
|
+
}
|
|
288
|
+
else {
|
|
289
|
+
for (const filePath of updatedFiles)
|
|
290
|
+
console.log(` - ${filePath}`);
|
|
291
|
+
}
|
|
292
|
+
if (!result.environment.secretKey) {
|
|
293
|
+
console.log("\nNote: your role does not have access to the environment secret key.");
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
if (!shouldSetupWebhook) {
|
|
297
|
+
return { updatedFiles, webhookSecret: null };
|
|
298
|
+
}
|
|
299
|
+
const webhookSecret = await setupWebhook(auth, result.environment.publicKey);
|
|
300
|
+
if (webhookSecret) {
|
|
301
|
+
if (consentedRuntimeFolders.length > 0) {
|
|
302
|
+
for (const folder of consentedRuntimeFolders) {
|
|
303
|
+
for (const filePath of folder.runtime) {
|
|
304
|
+
await updateRuntimeEnvFile(filePath, auth.apiUrl, result.environment.publicKey, result.environment.secretKey, { preserveExistingKeys: true, webhookSecret });
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
if (logOutput) {
|
|
308
|
+
console.log("\nWebhook secret written to .env file(s).");
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
else if (logOutput) {
|
|
312
|
+
console.log(`\nAdd this to your environment:`);
|
|
313
|
+
console.log(` ASCENDKIT_WEBHOOK_SECRET=${webhookSecret}`);
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
if (logOutput) {
|
|
317
|
+
const remainingSteps = [];
|
|
318
|
+
if (!webhookSecret) {
|
|
319
|
+
remainingSteps.push("Set up a webhook endpoint (run ascendkit set-env again, or use the portal).");
|
|
320
|
+
}
|
|
321
|
+
remainingSteps.push("Ensure frontend and backend are using keys from the same AscendKit environment.");
|
|
322
|
+
if (remainingSteps.length > 0) {
|
|
323
|
+
console.log("\nWhat you still need to do:");
|
|
324
|
+
for (let i = 0; i < remainingSteps.length; i++) {
|
|
325
|
+
console.log(` ${i + 1}. ${remainingSteps[i]}`);
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
return { updatedFiles, webhookSecret };
|
|
330
|
+
}
|
|
204
331
|
function generateDeviceCode() {
|
|
205
332
|
const chars = "ABCDEFGHJKLMNPQRSTUVWXYZ23456789"; // no I, O, 0, 1 for readability
|
|
206
333
|
let code = "";
|
|
@@ -223,6 +350,28 @@ async function apiRequest(apiUrl, method, path, body, token, publicKey) {
|
|
|
223
350
|
const response = await fetch(`${apiUrl}${path}`, init);
|
|
224
351
|
if (!response.ok) {
|
|
225
352
|
const text = await response.text();
|
|
353
|
+
if (!text) {
|
|
354
|
+
throw new Error(`API error ${response.status}: ${response.statusText}`);
|
|
355
|
+
}
|
|
356
|
+
try {
|
|
357
|
+
const parsed = JSON.parse(text);
|
|
358
|
+
if (typeof parsed.error === "string") {
|
|
359
|
+
throw new Error(parsed.error);
|
|
360
|
+
}
|
|
361
|
+
if (typeof parsed.detail === "string") {
|
|
362
|
+
throw new Error(parsed.detail);
|
|
363
|
+
}
|
|
364
|
+
if (parsed.detail
|
|
365
|
+
&& typeof parsed.detail === "object"
|
|
366
|
+
&& typeof parsed.detail.message === "string") {
|
|
367
|
+
throw new Error(parsed.detail.message);
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
catch (error) {
|
|
371
|
+
if (error instanceof Error && error.message !== text) {
|
|
372
|
+
throw error;
|
|
373
|
+
}
|
|
374
|
+
}
|
|
226
375
|
throw new Error(`API error ${response.status}: ${text}`);
|
|
227
376
|
}
|
|
228
377
|
const json = await response.json();
|
|
@@ -330,6 +479,14 @@ export async function listProjects() {
|
|
|
330
479
|
const auth = requireAuth();
|
|
331
480
|
return apiRequest(auth.apiUrl, "GET", "/api/platform/projects", undefined, auth.token);
|
|
332
481
|
}
|
|
482
|
+
export async function showProject(projectId) {
|
|
483
|
+
const projects = await listProjects();
|
|
484
|
+
const project = projects.find((item) => item.id === projectId);
|
|
485
|
+
if (!project) {
|
|
486
|
+
throw new Error(`Project ${projectId} not found.`);
|
|
487
|
+
}
|
|
488
|
+
return project;
|
|
489
|
+
}
|
|
333
490
|
export async function createProject(name, description, enabledServices) {
|
|
334
491
|
const auth = requireAuth();
|
|
335
492
|
return apiRequest(auth.apiUrl, "POST", "/api/platform/projects", {
|
|
@@ -349,26 +506,7 @@ export async function useEnvironment(tier, projectId) {
|
|
|
349
506
|
if (!env) {
|
|
350
507
|
throw new Error(`No ${tier} environment found for project ${projectId}. Available: ${envs.map(e => e.tier).join(", ")}`);
|
|
351
508
|
}
|
|
352
|
-
|
|
353
|
-
let projectName = projectId;
|
|
354
|
-
try {
|
|
355
|
-
const projects = await apiRequest(auth.apiUrl, "GET", "/api/platform/projects", undefined, auth.token);
|
|
356
|
-
const project = projects.find(p => p.id === projectId);
|
|
357
|
-
if (project)
|
|
358
|
-
projectName = project.name;
|
|
359
|
-
}
|
|
360
|
-
catch { /* non-critical */ }
|
|
361
|
-
saveEnvContext({
|
|
362
|
-
publicKey: env.publicKey,
|
|
363
|
-
projectId,
|
|
364
|
-
projectName,
|
|
365
|
-
environmentId: env.id,
|
|
366
|
-
environmentName: env.name,
|
|
367
|
-
tier: env.tier,
|
|
368
|
-
});
|
|
369
|
-
console.log(`Active environment: ${env.name} (${env.tier})`);
|
|
370
|
-
console.log(`Project: ${projectName}`);
|
|
371
|
-
console.log(`Public key: ${env.publicKey}`);
|
|
509
|
+
await setEnv(env.publicKey);
|
|
372
510
|
}
|
|
373
511
|
async function setupWebhook(auth, publicKey) {
|
|
374
512
|
// Never run webhook setup in non-interactive mode — selecting a webhook
|
|
@@ -434,82 +572,11 @@ async function setupWebhook(auth, publicKey) {
|
|
|
434
572
|
export async function setEnv(publicKey) {
|
|
435
573
|
const auth = requireAuth();
|
|
436
574
|
const result = await apiRequest(auth.apiUrl, "GET", `/api/platform/resolve-key?pk=${encodeURIComponent(publicKey)}`, undefined, auth.token);
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
environmentId: result.environment.id,
|
|
442
|
-
environmentName: result.environment.name,
|
|
443
|
-
tier: result.environment.tier,
|
|
575
|
+
await activateEnvironment(auth, result, {
|
|
576
|
+
promptForFileUpdates: true,
|
|
577
|
+
setupWebhook: true,
|
|
578
|
+
logOutput: true,
|
|
444
579
|
});
|
|
445
|
-
console.log(` → Project: ${result.project.name}`);
|
|
446
|
-
console.log(` → Environment: ${result.environment.name}`);
|
|
447
|
-
console.log(` → Role: ${result.role}`);
|
|
448
|
-
const cwd = process.cwd();
|
|
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) {
|
|
461
|
-
const changed = await updateEnvExampleFile(filePath);
|
|
462
|
-
if (changed)
|
|
463
|
-
updatedFiles.push(path.relative(cwd, filePath));
|
|
464
|
-
}
|
|
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
|
-
}
|
|
472
|
-
}
|
|
473
|
-
}
|
|
474
|
-
}
|
|
475
|
-
console.log("\nUpdated files:");
|
|
476
|
-
if (updatedFiles.length === 0) {
|
|
477
|
-
console.log(" (none)");
|
|
478
|
-
}
|
|
479
|
-
else {
|
|
480
|
-
for (const filePath of updatedFiles)
|
|
481
|
-
console.log(` - ${filePath}`);
|
|
482
|
-
}
|
|
483
|
-
if (!result.environment.secretKey) {
|
|
484
|
-
console.log("\nNote: your role does not have access to the environment secret key.");
|
|
485
|
-
}
|
|
486
|
-
// --- Webhook setup ---
|
|
487
|
-
const webhookSecret = await setupWebhook(auth, result.environment.publicKey);
|
|
488
|
-
if (webhookSecret) {
|
|
489
|
-
if (consentedRuntimeFolders.length > 0) {
|
|
490
|
-
for (const folder of consentedRuntimeFolders) {
|
|
491
|
-
for (const filePath of folder.runtime) {
|
|
492
|
-
await updateRuntimeEnvFile(filePath, auth.apiUrl, result.environment.publicKey, result.environment.secretKey ?? "", { preserveExistingKeys: true, webhookSecret });
|
|
493
|
-
}
|
|
494
|
-
}
|
|
495
|
-
console.log("\nWebhook secret written to .env file(s).");
|
|
496
|
-
}
|
|
497
|
-
else {
|
|
498
|
-
console.log(`\nAdd this to your environment:`);
|
|
499
|
-
console.log(` ASCENDKIT_WEBHOOK_SECRET=${webhookSecret}`);
|
|
500
|
-
}
|
|
501
|
-
}
|
|
502
|
-
const remainingSteps = [];
|
|
503
|
-
if (!webhookSecret) {
|
|
504
|
-
remainingSteps.push("Set up a webhook endpoint (run ascendkit set-env again, or use the portal).");
|
|
505
|
-
}
|
|
506
|
-
remainingSteps.push("Ensure frontend and backend are using keys from the same AscendKit environment.");
|
|
507
|
-
if (remainingSteps.length > 0) {
|
|
508
|
-
console.log("\nWhat you still need to do:");
|
|
509
|
-
for (let i = 0; i < remainingSteps.length; i++) {
|
|
510
|
-
console.log(` ${i + 1}. ${remainingSteps[i]}`);
|
|
511
|
-
}
|
|
512
|
-
}
|
|
513
580
|
}
|
|
514
581
|
export async function mcpLogin(client, params) {
|
|
515
582
|
const data = await client.publicRequest("POST", "/api/platform/auth/token", {
|
|
@@ -517,8 +584,20 @@ export async function mcpLogin(client, params) {
|
|
|
517
584
|
password: params.password,
|
|
518
585
|
});
|
|
519
586
|
client.configurePlatform(data.token);
|
|
587
|
+
saveAuth({ token: data.token, apiUrl: client.currentApiUrl });
|
|
588
|
+
ensureGitignore();
|
|
520
589
|
return data;
|
|
521
590
|
}
|
|
591
|
+
export async function mcpSetEnvironmentByPublicKey(client, publicKey) {
|
|
592
|
+
const result = await client.platformRequest("GET", `/api/platform/resolve-key?pk=${encodeURIComponent(publicKey)}`);
|
|
593
|
+
client.configure(result.environment.publicKey);
|
|
594
|
+
const { updatedFiles } = await activateEnvironment({ token: "__mcp__", apiUrl: client.currentApiUrl }, result, {
|
|
595
|
+
promptForFileUpdates: false,
|
|
596
|
+
setupWebhook: false,
|
|
597
|
+
logOutput: false,
|
|
598
|
+
});
|
|
599
|
+
return { ...result, updatedFiles };
|
|
600
|
+
}
|
|
522
601
|
export async function mcpListProjects(client) {
|
|
523
602
|
return client.platformRequest("GET", "/api/platform/projects");
|
|
524
603
|
}
|
|
@@ -546,7 +625,7 @@ export async function updateEnvironment(projectId, envId, name, description) {
|
|
|
546
625
|
}
|
|
547
626
|
export async function promoteEnvironment(environmentId, targetTier) {
|
|
548
627
|
const auth = requireAuth();
|
|
549
|
-
return apiRequest(auth.apiUrl, "POST", `/api/platform/environments/${environmentId}/promote`, { targetTier, dryRun: false }, auth.token);
|
|
628
|
+
return apiRequest(auth.apiUrl, "POST", `/api/platform/environments/${environmentId}/promote`, { targetTier: normalizePromotionTier(targetTier), dryRun: false }, auth.token);
|
|
550
629
|
}
|
|
551
630
|
export async function mcpUpdateEnvironment(client, params) {
|
|
552
631
|
const body = {};
|
|
@@ -557,10 +636,33 @@ export async function mcpUpdateEnvironment(client, params) {
|
|
|
557
636
|
return client.platformRequest("PATCH", `/api/platform/projects/${params.projectId}/environments/${params.environmentId}`, body);
|
|
558
637
|
}
|
|
559
638
|
export async function mcpPromoteEnvironment(client, params) {
|
|
560
|
-
return client.platformRequest("POST", `/api/platform/environments/${params.environmentId}/promote`, { targetTier: params.targetTier, dryRun: false });
|
|
639
|
+
return client.platformRequest("POST", `/api/platform/environments/${params.environmentId}/promote`, { targetTier: normalizePromotionTier(params.targetTier), dryRun: false });
|
|
640
|
+
}
|
|
641
|
+
function resolveActiveEnvironmentIds(params) {
|
|
642
|
+
if (params?.projectId && params?.envId) {
|
|
643
|
+
return { projectId: params.projectId, envId: params.envId };
|
|
644
|
+
}
|
|
645
|
+
const ctx = loadEnvContext();
|
|
646
|
+
if (!ctx?.projectId || !ctx.environmentId) {
|
|
647
|
+
throw new Error("No active environment found. Use ascendkit_set_env or ascendkit set-env first.");
|
|
648
|
+
}
|
|
649
|
+
return {
|
|
650
|
+
projectId: params?.projectId ?? ctx.projectId,
|
|
651
|
+
envId: params?.envId ?? ctx.environmentId,
|
|
652
|
+
};
|
|
653
|
+
}
|
|
654
|
+
export async function mcpGetEnvironment(client, params) {
|
|
655
|
+
const target = resolveActiveEnvironmentIds(params);
|
|
656
|
+
const envs = await client.platformRequest("GET", `/api/platform/projects/${target.projectId}/environments`);
|
|
657
|
+
const env = envs.find((item) => item.id === target.envId);
|
|
658
|
+
if (!env) {
|
|
659
|
+
throw new Error(`Environment ${target.envId} not found in project ${target.projectId}.`);
|
|
660
|
+
}
|
|
661
|
+
return env;
|
|
561
662
|
}
|
|
562
663
|
export async function mcpUpdateEnvironmentVariables(client, params) {
|
|
563
|
-
|
|
664
|
+
const target = resolveActiveEnvironmentIds(params);
|
|
665
|
+
return client.platformRequest("PATCH", `/api/platform/projects/${target.projectId}/environments/${target.envId}`, { variables: params.variables });
|
|
564
666
|
}
|
|
565
667
|
export async function getEnvironment(projectId, envId) {
|
|
566
668
|
const auth = requireAuth();
|