@ascendkit/cli 0.1.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/dist/api/client.d.ts +34 -0
- package/dist/api/client.js +155 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +1153 -0
- package/dist/commands/auth.d.ts +17 -0
- package/dist/commands/auth.js +29 -0
- package/dist/commands/content.d.ts +25 -0
- package/dist/commands/content.js +28 -0
- package/dist/commands/email.d.ts +37 -0
- package/dist/commands/email.js +39 -0
- package/dist/commands/journeys.d.ts +86 -0
- package/dist/commands/journeys.js +69 -0
- package/dist/commands/platform.d.ts +35 -0
- package/dist/commands/platform.js +517 -0
- package/dist/commands/surveys.d.ts +51 -0
- package/dist/commands/surveys.js +41 -0
- package/dist/commands/webhooks.d.ts +16 -0
- package/dist/commands/webhooks.js +28 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +29 -0
- package/dist/mcp.d.ts +2 -0
- package/dist/mcp.js +40 -0
- package/dist/tools/auth.d.ts +3 -0
- package/dist/tools/auth.js +75 -0
- package/dist/tools/content.d.ts +3 -0
- package/dist/tools/content.js +64 -0
- package/dist/tools/email.d.ts +3 -0
- package/dist/tools/email.js +57 -0
- package/dist/tools/journeys.d.ts +3 -0
- package/dist/tools/journeys.js +302 -0
- package/dist/tools/platform.d.ts +3 -0
- package/dist/tools/platform.js +63 -0
- package/dist/tools/surveys.d.ts +3 -0
- package/dist/tools/surveys.js +212 -0
- package/dist/tools/webhooks.d.ts +3 -0
- package/dist/tools/webhooks.js +56 -0
- package/dist/types.d.ts +96 -0
- package/dist/types.js +4 -0
- package/dist/utils/credentials.d.ts +27 -0
- package/dist/utils/credentials.js +90 -0
- package/dist/utils/duration.d.ts +16 -0
- package/dist/utils/duration.js +47 -0
- package/dist/utils/journey-format.d.ts +112 -0
- package/dist/utils/journey-format.js +200 -0
- package/dist/utils/survey-format.d.ts +60 -0
- package/dist/utils/survey-format.js +164 -0
- package/package.json +37 -0
package/dist/cli.js
ADDED
|
@@ -0,0 +1,1153 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { readFileSync, writeFileSync } from "fs";
|
|
3
|
+
import { AscendKitClient } from "./api/client.js";
|
|
4
|
+
import { loadAuth, loadEnvContext } from "./utils/credentials.js";
|
|
5
|
+
import * as auth from "./commands/auth.js";
|
|
6
|
+
import * as content from "./commands/content.js";
|
|
7
|
+
import * as surveys from "./commands/surveys.js";
|
|
8
|
+
import * as platform from "./commands/platform.js";
|
|
9
|
+
import * as journeys from "./commands/journeys.js";
|
|
10
|
+
import * as email from "./commands/email.js";
|
|
11
|
+
import { parseDelay } from "./utils/duration.js";
|
|
12
|
+
const HELP = `ascendkit - AscendKit CLI
|
|
13
|
+
|
|
14
|
+
Usage: ascendkit <command> [options]
|
|
15
|
+
ascendkit help [section]
|
|
16
|
+
|
|
17
|
+
Platform Commands:
|
|
18
|
+
init Initialize AscendKit in this directory (login via browser)
|
|
19
|
+
--backend <url> API URL (default: https://api.ascendkit.com)
|
|
20
|
+
--portal <url> Portal URL (default: http://localhost:3000)
|
|
21
|
+
logout Clear local credentials
|
|
22
|
+
set-env <public-key> Set active environment by public key
|
|
23
|
+
projects list List your projects
|
|
24
|
+
env list --project <project-id> List environments for a project
|
|
25
|
+
env use <tier> --project <project-id> Set active environment by tier
|
|
26
|
+
|
|
27
|
+
Service Commands (require 'ascendkit init' AND 'ascendkit set-env'):
|
|
28
|
+
auth settings Get auth settings
|
|
29
|
+
auth settings update Update auth settings
|
|
30
|
+
--providers <p1,p2,...> Comma-separated provider list
|
|
31
|
+
--email-verification <true|false> Toggle email verification
|
|
32
|
+
--waitlist <true|false> Toggle waitlist
|
|
33
|
+
--password-reset <true|false> Toggle password reset
|
|
34
|
+
--session-duration <duration> e.g. "7d", "24h"
|
|
35
|
+
auth providers <p1,p2,...> Set enabled providers
|
|
36
|
+
auth oauth <provider> Open browser to configure OAuth credentials
|
|
37
|
+
auth oauth set <provider> Set OAuth credentials from CLI
|
|
38
|
+
--client-id <id>
|
|
39
|
+
--client-secret <secret> (not recommended; appears in shell history)
|
|
40
|
+
--client-secret-stdin Read secret from stdin
|
|
41
|
+
--callback-url <url> Auth callback URL for this provider
|
|
42
|
+
auth users List project users
|
|
43
|
+
|
|
44
|
+
content create Create a content template
|
|
45
|
+
--name <name>
|
|
46
|
+
--subject <subject>
|
|
47
|
+
--body-html <html>
|
|
48
|
+
--body-text <text>
|
|
49
|
+
--slug <slug> URL-safe identifier
|
|
50
|
+
--description <description> Template description
|
|
51
|
+
content list List all templates
|
|
52
|
+
--query <search> Search by name/slug/description
|
|
53
|
+
--system true Show only system templates
|
|
54
|
+
--custom true Show only custom templates
|
|
55
|
+
content get <template-id> Get a template
|
|
56
|
+
content update <template-id> Update a template (creates new version)
|
|
57
|
+
--subject <subject>
|
|
58
|
+
--body-html <html>
|
|
59
|
+
--body-text <text>
|
|
60
|
+
--change-note <note>
|
|
61
|
+
content delete <template-id> Delete a template
|
|
62
|
+
content versions <template-id> List template versions
|
|
63
|
+
content version <template-id> <n> Get specific version
|
|
64
|
+
|
|
65
|
+
survey create Create a survey
|
|
66
|
+
--name <name>
|
|
67
|
+
--type <nps|csat|custom>
|
|
68
|
+
--definition <json>
|
|
69
|
+
survey list List all surveys
|
|
70
|
+
survey get <survey-id> Get a survey
|
|
71
|
+
survey update <survey-id> Update a survey
|
|
72
|
+
--name <name>
|
|
73
|
+
--status <draft|active|paused>
|
|
74
|
+
--definition <json>
|
|
75
|
+
survey delete <survey-id> Delete a survey
|
|
76
|
+
survey distribute <survey-id> Distribute to users
|
|
77
|
+
--users <usr_id1,usr_id2,...>
|
|
78
|
+
survey invitations <survey-id> List invitations
|
|
79
|
+
survey analytics <survey-id> Get analytics
|
|
80
|
+
survey export-definition <survey-id> Export survey definition JSON
|
|
81
|
+
--out <file> Optional output file path (prints to stdout if omitted)
|
|
82
|
+
survey import-definition <survey-id> Import survey definition JSON (definition only; slug unchanged)
|
|
83
|
+
--in <file> Input JSON file (accepts raw definition or {definition: ...})
|
|
84
|
+
|
|
85
|
+
journey create Create a journey
|
|
86
|
+
--name <name>
|
|
87
|
+
--entry-event <event> e.g. "user.created"
|
|
88
|
+
--entry-node <node> First node name
|
|
89
|
+
--nodes <json> Node definitions (JSON, optional)
|
|
90
|
+
--transitions <json> Transition definitions (JSON)
|
|
91
|
+
--description <description>
|
|
92
|
+
--entry-conditions <json> Filter on entry event payload
|
|
93
|
+
--re-entry-policy <skip|restart> Default: skip
|
|
94
|
+
journey list List all journeys
|
|
95
|
+
--status <draft|active|paused|archived>
|
|
96
|
+
journey get <journey-id> Get a journey
|
|
97
|
+
journey update <journey-id> Update a journey
|
|
98
|
+
--name <name>
|
|
99
|
+
--nodes <json>
|
|
100
|
+
--transitions <json>
|
|
101
|
+
--description <description>
|
|
102
|
+
--entry-event <event>
|
|
103
|
+
--entry-node <node>
|
|
104
|
+
--entry-conditions <json>
|
|
105
|
+
--re-entry-policy <skip|restart>
|
|
106
|
+
journey delete <journey-id> Delete a journey
|
|
107
|
+
journey activate <journey-id> Activate a draft journey
|
|
108
|
+
journey pause <journey-id> Pause an active journey
|
|
109
|
+
journey archive <journey-id> Archive a journey (permanent)
|
|
110
|
+
journey analytics <journey-id> Get journey analytics
|
|
111
|
+
journey list-nodes <journey-id> List nodes with action and transition counts
|
|
112
|
+
journey add-node <journey-id> Add a node
|
|
113
|
+
--name <node-name>
|
|
114
|
+
--action <json> Optional action JSON (default: {"type":"none"})
|
|
115
|
+
--terminal <true|false> Optional terminal flag (default: false)
|
|
116
|
+
journey edit-node <journey-id> <node-name>
|
|
117
|
+
--action <json> Optional action JSON
|
|
118
|
+
--terminal <true|false> Optional terminal flag
|
|
119
|
+
journey remove-node <journey-id> <node-name>
|
|
120
|
+
journey list-transitions <journey-id>
|
|
121
|
+
--from <node-name> Optional source node filter
|
|
122
|
+
--to <node-name> Optional target node filter
|
|
123
|
+
journey add-transition <journey-id>
|
|
124
|
+
--from <node-name>
|
|
125
|
+
--to <node-name>
|
|
126
|
+
--trigger <json> Trigger JSON, e.g. {"type":"event","event":"user.login"}
|
|
127
|
+
--priority <n> Optional numeric priority
|
|
128
|
+
--name <transition-name> Optional transition name
|
|
129
|
+
journey edit-transition <journey-id> <transition-name>
|
|
130
|
+
--trigger <json> Optional trigger JSON
|
|
131
|
+
--priority <n> Optional numeric priority
|
|
132
|
+
journey remove-transition <journey-id> <transition-name>
|
|
133
|
+
|
|
134
|
+
email settings Get email settings
|
|
135
|
+
email settings update Update email settings
|
|
136
|
+
--from-email <email> Sender email address
|
|
137
|
+
--from-name <name> Sender display name
|
|
138
|
+
email identity Show current identity mode and next steps
|
|
139
|
+
email use-default Switch to AscendKit default sender
|
|
140
|
+
email use-custom <domain> Switch to customer-owned identity (starts DNS setup)
|
|
141
|
+
--from-email <email> Optional sender after domain setup
|
|
142
|
+
--from-name <name> Optional display name after domain setup
|
|
143
|
+
email setup-domain <domain> Start domain verification
|
|
144
|
+
email domain-status Check domain verification status
|
|
145
|
+
--watch Poll until verified/failed
|
|
146
|
+
--interval <seconds> Poll interval for --watch (default: 15)
|
|
147
|
+
email open-dns Show DNS provider URL for current domain
|
|
148
|
+
--domain <domain> Optional override domain
|
|
149
|
+
--open Open URL in browser
|
|
150
|
+
email remove-domain Remove domain and reset to default
|
|
151
|
+
|
|
152
|
+
status Show current login and environment state
|
|
153
|
+
verify Check all services in the active environment
|
|
154
|
+
|
|
155
|
+
Environment:
|
|
156
|
+
ASCENDKIT_PUBLIC_KEY Environment public key (overrides stored credentials)
|
|
157
|
+
ASCENDKIT_API_URL API URL (default: https://api.ascendkit.com)
|
|
158
|
+
`;
|
|
159
|
+
const HELP_SECTION = {
|
|
160
|
+
auth: `Usage: ascendkit auth <command>
|
|
161
|
+
|
|
162
|
+
Commands:
|
|
163
|
+
auth settings
|
|
164
|
+
auth settings update --providers <p1,p2,...> [--email-verification <true|false>] [--waitlist <true|false>] [--password-reset <true|false>] [--session-duration <duration>]
|
|
165
|
+
auth providers <p1,p2,...>
|
|
166
|
+
auth oauth <provider>
|
|
167
|
+
auth oauth set <provider> --client-id <id> [--client-secret <secret> | --client-secret-stdin] [--callback-url <url>]
|
|
168
|
+
auth users`,
|
|
169
|
+
content: `Usage: ascendkit content <command>
|
|
170
|
+
|
|
171
|
+
Commands:
|
|
172
|
+
content create --name <name> --subject <subject> --body-html <html> --body-text <text> [--slug <slug>] [--description <description>]
|
|
173
|
+
content list [--query <search>] [--system true|--custom true]
|
|
174
|
+
content get <template-id>
|
|
175
|
+
content update <template-id> [--subject <subject>] [--body-html <html>] [--body-text <text>] [--change-note <note>]
|
|
176
|
+
content delete <template-id>
|
|
177
|
+
content versions <template-id>
|
|
178
|
+
content version <template-id> <n>`,
|
|
179
|
+
survey: `Usage: ascendkit survey <command>
|
|
180
|
+
|
|
181
|
+
Commands:
|
|
182
|
+
survey create --name <name> [--type <nps|csat|custom>] [--definition <json>]
|
|
183
|
+
survey list
|
|
184
|
+
survey get <survey-id>
|
|
185
|
+
survey update <survey-id> [--name <name>] [--status <draft|active|paused>] [--definition <json>]
|
|
186
|
+
survey delete <survey-id>
|
|
187
|
+
survey distribute <survey-id> --users <usr_id1,usr_id2,...>
|
|
188
|
+
survey invitations <survey-id>
|
|
189
|
+
survey analytics <survey-id>
|
|
190
|
+
survey export-definition <survey-id> [--out <file>]
|
|
191
|
+
survey import-definition <survey-id> --in <file>
|
|
192
|
+
|
|
193
|
+
Notes:
|
|
194
|
+
- Question-level editing commands are available via MCP tools, not this terminal CLI.
|
|
195
|
+
- import-definition only updates the survey definition. It does not mutate slug/name/status.`,
|
|
196
|
+
journey: `Usage: ascendkit journey <command>
|
|
197
|
+
|
|
198
|
+
Commands:
|
|
199
|
+
journey create --name <name> --entry-event <event> --entry-node <node> [--nodes <json>] [--transitions <json>] [--description <description>] [--entry-conditions <json>] [--re-entry-policy <skip|restart>]
|
|
200
|
+
journey list [--status <draft|active|paused|archived>]
|
|
201
|
+
journey get <journey-id>
|
|
202
|
+
journey update <journey-id> [--name <name>] [--nodes <json>] [--transitions <json>] [--description <description>] [--entry-event <event>] [--entry-node <node>] [--entry-conditions <json>] [--re-entry-policy <skip|restart>]
|
|
203
|
+
journey delete <journey-id>
|
|
204
|
+
journey activate <journey-id>
|
|
205
|
+
journey pause <journey-id>
|
|
206
|
+
journey archive <journey-id>
|
|
207
|
+
journey analytics <journey-id>
|
|
208
|
+
journey list-nodes <journey-id>
|
|
209
|
+
journey add-node <journey-id> --name <node-name> [--action <json>] [--terminal <true|false>]
|
|
210
|
+
journey edit-node <journey-id> <node-name> [--action <json>] [--terminal <true|false>]
|
|
211
|
+
journey remove-node <journey-id> <node-name>
|
|
212
|
+
journey list-transitions <journey-id> [--from <node-name>] [--to <node-name>]
|
|
213
|
+
journey add-transition <journey-id> --from <node-name> --to <node-name> --trigger <json> [--priority <n>] [--name <transition-name>]
|
|
214
|
+
journey edit-transition <journey-id> <transition-name> [--trigger <json>] [--priority <n>]
|
|
215
|
+
journey remove-transition <journey-id> <transition-name>`,
|
|
216
|
+
email: `Usage: ascendkit email <command>
|
|
217
|
+
|
|
218
|
+
Commands:
|
|
219
|
+
email settings
|
|
220
|
+
email settings update [--from-email <email>] [--from-name <name>]
|
|
221
|
+
email identity
|
|
222
|
+
email use-default
|
|
223
|
+
email use-custom <domain> [--from-email <email>] [--from-name <name>]
|
|
224
|
+
email setup-domain <domain>
|
|
225
|
+
email domain-status [--watch] [--interval <seconds>]
|
|
226
|
+
email open-dns [--domain <domain>] [--open]
|
|
227
|
+
email remove-domain`,
|
|
228
|
+
env: `Usage: ascendkit env <command>
|
|
229
|
+
|
|
230
|
+
Commands:
|
|
231
|
+
env list --project <project-id>
|
|
232
|
+
env use <tier> --project <project-id>`,
|
|
233
|
+
projects: `Usage: ascendkit projects list`,
|
|
234
|
+
};
|
|
235
|
+
function printSectionHelp(section) {
|
|
236
|
+
if (!section)
|
|
237
|
+
return false;
|
|
238
|
+
const key = section.toLowerCase();
|
|
239
|
+
const text = HELP_SECTION[key];
|
|
240
|
+
if (!text)
|
|
241
|
+
return false;
|
|
242
|
+
console.log(text);
|
|
243
|
+
return true;
|
|
244
|
+
}
|
|
245
|
+
function getClient() {
|
|
246
|
+
let publicKey = process.env.ASCENDKIT_PUBLIC_KEY;
|
|
247
|
+
let apiUrl = process.env.ASCENDKIT_API_URL;
|
|
248
|
+
const auth = loadAuth();
|
|
249
|
+
const env = loadEnvContext();
|
|
250
|
+
// Require auth unless env var provides the public key (CI/CD escape hatch)
|
|
251
|
+
if (!auth?.token && !publicKey) {
|
|
252
|
+
console.error("Not initialized. Run: ascendkit init");
|
|
253
|
+
process.exit(1);
|
|
254
|
+
}
|
|
255
|
+
if (!publicKey && env?.publicKey) {
|
|
256
|
+
publicKey = env.publicKey;
|
|
257
|
+
}
|
|
258
|
+
if (!apiUrl && auth?.apiUrl) {
|
|
259
|
+
apiUrl = auth.apiUrl;
|
|
260
|
+
}
|
|
261
|
+
if (!publicKey) {
|
|
262
|
+
console.error("No environment set. Run: ascendkit set-env <public-key>");
|
|
263
|
+
process.exit(1);
|
|
264
|
+
}
|
|
265
|
+
const client = new AscendKitClient({
|
|
266
|
+
apiUrl: apiUrl ?? "https://api.ascendkit.com",
|
|
267
|
+
publicKey,
|
|
268
|
+
});
|
|
269
|
+
if (auth?.token) {
|
|
270
|
+
client.configurePlatform(auth.token);
|
|
271
|
+
}
|
|
272
|
+
return client;
|
|
273
|
+
}
|
|
274
|
+
function parseFlags(args) {
|
|
275
|
+
const flags = {};
|
|
276
|
+
for (let i = 0; i < args.length; i++) {
|
|
277
|
+
if (args[i].startsWith("--")) {
|
|
278
|
+
const key = args[i].slice(2);
|
|
279
|
+
if (i + 1 < args.length && !args[i + 1].startsWith("--")) {
|
|
280
|
+
flags[key] = args[++i];
|
|
281
|
+
}
|
|
282
|
+
else {
|
|
283
|
+
flags[key] = "true";
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
return flags;
|
|
288
|
+
}
|
|
289
|
+
async function readSecretFromStdin() {
|
|
290
|
+
const chunks = [];
|
|
291
|
+
for await (const chunk of process.stdin) {
|
|
292
|
+
chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
|
|
293
|
+
}
|
|
294
|
+
return Buffer.concat(chunks).toString("utf8").trim();
|
|
295
|
+
}
|
|
296
|
+
function output(data) {
|
|
297
|
+
console.log(JSON.stringify(data, null, 2));
|
|
298
|
+
}
|
|
299
|
+
function normalizeJourneyRows(data) {
|
|
300
|
+
if (Array.isArray(data)) {
|
|
301
|
+
return data;
|
|
302
|
+
}
|
|
303
|
+
if (data && typeof data === "object") {
|
|
304
|
+
const rows = data.journeys;
|
|
305
|
+
if (Array.isArray(rows)) {
|
|
306
|
+
return rows;
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
return [];
|
|
310
|
+
}
|
|
311
|
+
function table(rows, columns) {
|
|
312
|
+
if (rows.length === 0) {
|
|
313
|
+
console.log("No results.");
|
|
314
|
+
return;
|
|
315
|
+
}
|
|
316
|
+
// Compute column widths
|
|
317
|
+
const widths = columns.map(col => {
|
|
318
|
+
const max = Math.max(col.label.length, ...rows.map(r => String(r[col.key] ?? "").length));
|
|
319
|
+
return col.width ? Math.min(max, col.width) : max;
|
|
320
|
+
});
|
|
321
|
+
// Header
|
|
322
|
+
const header = columns.map((col, i) => col.label.padEnd(widths[i])).join(" ");
|
|
323
|
+
console.log(header);
|
|
324
|
+
console.log(columns.map((_, i) => "─".repeat(widths[i])).join(" "));
|
|
325
|
+
// Rows
|
|
326
|
+
for (const row of rows) {
|
|
327
|
+
const line = columns.map((col, i) => {
|
|
328
|
+
const val = String(row[col.key] ?? "");
|
|
329
|
+
return val.length > widths[i] ? val.slice(0, widths[i] - 1) + "…" : val.padEnd(widths[i]);
|
|
330
|
+
}).join(" ");
|
|
331
|
+
console.log(line);
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
async function run() {
|
|
335
|
+
const args = process.argv.slice(2);
|
|
336
|
+
if (args.length === 0 || args[0] === "--help" || args[0] === "-h") {
|
|
337
|
+
console.log(HELP);
|
|
338
|
+
return;
|
|
339
|
+
}
|
|
340
|
+
if (args[0] === "help") {
|
|
341
|
+
if (args[1]) {
|
|
342
|
+
if (!printSectionHelp(args[1])) {
|
|
343
|
+
console.error(`Unknown help section: ${args[1]}`);
|
|
344
|
+
console.error('Run "ascendkit --help" for available sections.');
|
|
345
|
+
process.exit(1);
|
|
346
|
+
}
|
|
347
|
+
return;
|
|
348
|
+
}
|
|
349
|
+
console.log(HELP);
|
|
350
|
+
return;
|
|
351
|
+
}
|
|
352
|
+
const domain = args[0];
|
|
353
|
+
const action = args[1];
|
|
354
|
+
if (!action && printSectionHelp(domain)) {
|
|
355
|
+
return;
|
|
356
|
+
}
|
|
357
|
+
if (action === "--help" || action === "-h" || action === "help") {
|
|
358
|
+
if (!printSectionHelp(domain)) {
|
|
359
|
+
console.error(`Unknown command section: ${domain}`);
|
|
360
|
+
console.error('Run "ascendkit --help" for usage');
|
|
361
|
+
process.exit(1);
|
|
362
|
+
}
|
|
363
|
+
return;
|
|
364
|
+
}
|
|
365
|
+
// Platform commands (don't need environment key)
|
|
366
|
+
switch (domain) {
|
|
367
|
+
case "init": {
|
|
368
|
+
const flags = parseFlags(args.slice(1));
|
|
369
|
+
await platform.init(flags["backend"], flags["portal"]);
|
|
370
|
+
return;
|
|
371
|
+
}
|
|
372
|
+
case "logout":
|
|
373
|
+
platform.logout();
|
|
374
|
+
return;
|
|
375
|
+
case "projects":
|
|
376
|
+
if (action === "list") {
|
|
377
|
+
const projects = await platform.listProjects();
|
|
378
|
+
table(projects, [
|
|
379
|
+
{ key: "id", label: "ID" },
|
|
380
|
+
{ key: "name", label: "Name", width: 30 },
|
|
381
|
+
{ key: "enabledServices", label: "Services", width: 30 },
|
|
382
|
+
]);
|
|
383
|
+
}
|
|
384
|
+
else {
|
|
385
|
+
console.error('Usage: ascendkit projects list');
|
|
386
|
+
process.exit(1);
|
|
387
|
+
}
|
|
388
|
+
return;
|
|
389
|
+
case "set-env":
|
|
390
|
+
if (!action) {
|
|
391
|
+
console.error("Usage: ascendkit set-env <public-key>");
|
|
392
|
+
process.exit(1);
|
|
393
|
+
}
|
|
394
|
+
await platform.setEnv(action);
|
|
395
|
+
return;
|
|
396
|
+
case "status":
|
|
397
|
+
runStatus();
|
|
398
|
+
return;
|
|
399
|
+
case "verify":
|
|
400
|
+
await runVerify();
|
|
401
|
+
return;
|
|
402
|
+
case "env":
|
|
403
|
+
await runEnv(action, args.slice(2));
|
|
404
|
+
return;
|
|
405
|
+
}
|
|
406
|
+
// Service commands (need environment key)
|
|
407
|
+
const client = getClient();
|
|
408
|
+
switch (domain) {
|
|
409
|
+
case "auth":
|
|
410
|
+
await runAuth(client, action, args.slice(2));
|
|
411
|
+
break;
|
|
412
|
+
case "content":
|
|
413
|
+
await runContent(client, action, args.slice(2));
|
|
414
|
+
break;
|
|
415
|
+
case "survey":
|
|
416
|
+
await runSurvey(client, action, args.slice(2));
|
|
417
|
+
break;
|
|
418
|
+
case "journey":
|
|
419
|
+
await runJourney(client, action, args.slice(2));
|
|
420
|
+
break;
|
|
421
|
+
case "email":
|
|
422
|
+
await runEmail(client, action, args.slice(2));
|
|
423
|
+
break;
|
|
424
|
+
default:
|
|
425
|
+
console.error(`Unknown command: ${domain}`);
|
|
426
|
+
console.error('Run "ascendkit --help" for usage');
|
|
427
|
+
process.exit(1);
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
async function runEnv(action, rest) {
|
|
431
|
+
const flags = parseFlags(rest);
|
|
432
|
+
switch (action) {
|
|
433
|
+
case "list":
|
|
434
|
+
if (!flags.project) {
|
|
435
|
+
console.error("Usage: ascendkit env list --project <project-id>");
|
|
436
|
+
process.exit(1);
|
|
437
|
+
}
|
|
438
|
+
table(await platform.listEnvironments(flags.project), [
|
|
439
|
+
{ key: "name", label: "Name", width: 20 },
|
|
440
|
+
{ key: "tier", label: "Tier" },
|
|
441
|
+
{ key: "publicKey", label: "Public Key" },
|
|
442
|
+
]);
|
|
443
|
+
break;
|
|
444
|
+
case "use":
|
|
445
|
+
if (!rest[0] || !flags.project) {
|
|
446
|
+
console.error("Usage: ascendkit env use <tier> --project <project-id>");
|
|
447
|
+
process.exit(1);
|
|
448
|
+
}
|
|
449
|
+
await platform.useEnvironment(rest[0], flags.project);
|
|
450
|
+
break;
|
|
451
|
+
default:
|
|
452
|
+
console.error(`Unknown env command: ${action}`);
|
|
453
|
+
console.error("Usage: ascendkit env list|use");
|
|
454
|
+
process.exit(1);
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
async function runAuth(client, action, rest) {
|
|
458
|
+
const flags = parseFlags(rest);
|
|
459
|
+
switch (action) {
|
|
460
|
+
case "settings":
|
|
461
|
+
if (rest[0] === "update") {
|
|
462
|
+
const params = {};
|
|
463
|
+
if (flags.providers)
|
|
464
|
+
params.providers = flags.providers.split(",");
|
|
465
|
+
if (flags["email-verification"] || flags.waitlist || flags["password-reset"]) {
|
|
466
|
+
params.features = {};
|
|
467
|
+
if (flags["email-verification"])
|
|
468
|
+
params.features.emailVerification = flags["email-verification"] === "true";
|
|
469
|
+
if (flags.waitlist)
|
|
470
|
+
params.features.waitlist = flags.waitlist === "true";
|
|
471
|
+
if (flags["password-reset"])
|
|
472
|
+
params.features.passwordReset = flags["password-reset"] === "true";
|
|
473
|
+
}
|
|
474
|
+
if (flags["session-duration"])
|
|
475
|
+
params.sessionDuration = flags["session-duration"];
|
|
476
|
+
output(await auth.updateSettings(client, params));
|
|
477
|
+
}
|
|
478
|
+
else {
|
|
479
|
+
output(await auth.getSettings(client));
|
|
480
|
+
}
|
|
481
|
+
break;
|
|
482
|
+
case "providers":
|
|
483
|
+
if (!rest[0]) {
|
|
484
|
+
console.error("Usage: ascendkit auth providers <p1,p2,...>");
|
|
485
|
+
process.exit(1);
|
|
486
|
+
}
|
|
487
|
+
output(await auth.updateProviders(client, rest[0].split(",")));
|
|
488
|
+
break;
|
|
489
|
+
case "oauth": {
|
|
490
|
+
if (rest[0] === "set") {
|
|
491
|
+
const provider = rest[1];
|
|
492
|
+
if (!provider || !flags["client-id"]) {
|
|
493
|
+
console.error("Usage: ascendkit auth oauth set <provider> --client-id <id> [--client-secret <secret> | --client-secret-stdin] [--callback-url <url>]");
|
|
494
|
+
process.exit(1);
|
|
495
|
+
}
|
|
496
|
+
const secretFromArg = flags["client-secret"];
|
|
497
|
+
const secretFromStdin = flags["client-secret-stdin"] === "true";
|
|
498
|
+
if (!secretFromArg && !secretFromStdin) {
|
|
499
|
+
console.error("Missing client secret. Use --client-secret-stdin (recommended) or --client-secret.");
|
|
500
|
+
process.exit(1);
|
|
501
|
+
}
|
|
502
|
+
if (secretFromArg && secretFromStdin) {
|
|
503
|
+
console.error("Use only one of --client-secret or --client-secret-stdin.");
|
|
504
|
+
process.exit(1);
|
|
505
|
+
}
|
|
506
|
+
const clientSecret = secretFromArg ?? await readSecretFromStdin();
|
|
507
|
+
if (!clientSecret) {
|
|
508
|
+
console.error("Client secret cannot be empty.");
|
|
509
|
+
process.exit(1);
|
|
510
|
+
}
|
|
511
|
+
output(await auth.updateOAuthCredentials(client, provider, flags["client-id"], clientSecret, flags["callback-url"]));
|
|
512
|
+
}
|
|
513
|
+
else {
|
|
514
|
+
if (!rest[0]) {
|
|
515
|
+
console.error("Usage: ascendkit auth oauth <provider>");
|
|
516
|
+
process.exit(1);
|
|
517
|
+
}
|
|
518
|
+
const portalUrl = process.env.ASCENDKIT_PORTAL_URL ?? "http://localhost:3000";
|
|
519
|
+
const url = auth.getOAuthSetupUrl(portalUrl, rest[0], client.currentPublicKey ?? undefined);
|
|
520
|
+
console.log(`Opening browser to configure ${rest[0]} OAuth credentials...`);
|
|
521
|
+
console.log(url);
|
|
522
|
+
openBrowser(url);
|
|
523
|
+
}
|
|
524
|
+
break;
|
|
525
|
+
}
|
|
526
|
+
case "users":
|
|
527
|
+
table(await auth.listUsers(client), [
|
|
528
|
+
{ key: "id", label: "ID" },
|
|
529
|
+
{ key: "email", label: "Email", width: 35 },
|
|
530
|
+
{ key: "name", label: "Name", width: 25 },
|
|
531
|
+
{ key: "status", label: "Status" },
|
|
532
|
+
]);
|
|
533
|
+
break;
|
|
534
|
+
default:
|
|
535
|
+
console.error(`Unknown auth command: ${action}`);
|
|
536
|
+
process.exit(1);
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
async function runContent(client, action, rest) {
|
|
540
|
+
const flags = parseFlags(rest);
|
|
541
|
+
switch (action) {
|
|
542
|
+
case "create":
|
|
543
|
+
if (!flags.name || !flags.subject || !flags["body-html"] || !flags["body-text"]) {
|
|
544
|
+
console.error("Usage: ascendkit content create --name <n> --subject <s> --body-html <h> --body-text <t> [--slug <slug>] [--description <desc>]");
|
|
545
|
+
process.exit(1);
|
|
546
|
+
}
|
|
547
|
+
output(await content.createTemplate(client, {
|
|
548
|
+
name: flags.name, subject: flags.subject,
|
|
549
|
+
bodyHtml: flags["body-html"], bodyText: flags["body-text"],
|
|
550
|
+
slug: flags.slug, description: flags.description,
|
|
551
|
+
}));
|
|
552
|
+
break;
|
|
553
|
+
case "list": {
|
|
554
|
+
const listParams = {};
|
|
555
|
+
if (flags.query)
|
|
556
|
+
listParams.query = flags.query;
|
|
557
|
+
if (flags.system === "true")
|
|
558
|
+
listParams.isSystem = true;
|
|
559
|
+
else if (flags.custom === "true")
|
|
560
|
+
listParams.isSystem = false;
|
|
561
|
+
const templates = await content.listTemplates(client, Object.keys(listParams).length > 0 ? listParams : undefined);
|
|
562
|
+
table(templates, [
|
|
563
|
+
{ key: "id", label: "ID" },
|
|
564
|
+
{ key: "name", label: "Name", width: 30 },
|
|
565
|
+
{ key: "slug", label: "Slug", width: 25 },
|
|
566
|
+
{ key: "subject", label: "Subject", width: 30 },
|
|
567
|
+
]);
|
|
568
|
+
break;
|
|
569
|
+
}
|
|
570
|
+
case "get":
|
|
571
|
+
if (!rest[0]) {
|
|
572
|
+
console.error("Usage: ascendkit content get <template-id>");
|
|
573
|
+
process.exit(1);
|
|
574
|
+
}
|
|
575
|
+
output(await content.getTemplate(client, rest[0]));
|
|
576
|
+
break;
|
|
577
|
+
case "update":
|
|
578
|
+
if (!rest[0]) {
|
|
579
|
+
console.error("Usage: ascendkit content update <template-id> [--flags]");
|
|
580
|
+
process.exit(1);
|
|
581
|
+
}
|
|
582
|
+
output(await content.updateTemplate(client, rest[0], {
|
|
583
|
+
subject: flags.subject,
|
|
584
|
+
bodyHtml: flags["body-html"],
|
|
585
|
+
bodyText: flags["body-text"],
|
|
586
|
+
changeNote: flags["change-note"],
|
|
587
|
+
}));
|
|
588
|
+
break;
|
|
589
|
+
case "delete":
|
|
590
|
+
if (!rest[0]) {
|
|
591
|
+
console.error("Usage: ascendkit content delete <template-id>");
|
|
592
|
+
process.exit(1);
|
|
593
|
+
}
|
|
594
|
+
output(await content.deleteTemplate(client, rest[0]));
|
|
595
|
+
break;
|
|
596
|
+
case "versions":
|
|
597
|
+
if (!rest[0]) {
|
|
598
|
+
console.error("Usage: ascendkit content versions <template-id>");
|
|
599
|
+
process.exit(1);
|
|
600
|
+
}
|
|
601
|
+
output(await content.listVersions(client, rest[0]));
|
|
602
|
+
break;
|
|
603
|
+
case "version":
|
|
604
|
+
if (!rest[0] || !rest[1]) {
|
|
605
|
+
console.error("Usage: ascendkit content version <template-id> <n>");
|
|
606
|
+
process.exit(1);
|
|
607
|
+
}
|
|
608
|
+
output(await content.getVersion(client, rest[0], parseInt(rest[1], 10)));
|
|
609
|
+
break;
|
|
610
|
+
default:
|
|
611
|
+
console.error(`Unknown content command: ${action}`);
|
|
612
|
+
process.exit(1);
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
async function runSurvey(client, action, rest) {
|
|
616
|
+
const flags = parseFlags(rest);
|
|
617
|
+
if (!action) {
|
|
618
|
+
console.log(HELP_SECTION.survey);
|
|
619
|
+
return;
|
|
620
|
+
}
|
|
621
|
+
switch (action) {
|
|
622
|
+
case "create":
|
|
623
|
+
if (!flags.name) {
|
|
624
|
+
console.error("Usage: ascendkit survey create --name <n> [--type nps|csat|custom]");
|
|
625
|
+
process.exit(1);
|
|
626
|
+
}
|
|
627
|
+
output(await surveys.createSurvey(client, {
|
|
628
|
+
name: flags.name,
|
|
629
|
+
type: flags.type ?? "custom",
|
|
630
|
+
definition: flags.definition ? JSON.parse(flags.definition) : undefined,
|
|
631
|
+
}));
|
|
632
|
+
break;
|
|
633
|
+
case "list":
|
|
634
|
+
table(await surveys.listSurveys(client), [
|
|
635
|
+
{ key: "id", label: "ID" },
|
|
636
|
+
{ key: "name", label: "Name", width: 30 },
|
|
637
|
+
{ key: "type", label: "Type" },
|
|
638
|
+
{ key: "status", label: "Status" },
|
|
639
|
+
]);
|
|
640
|
+
break;
|
|
641
|
+
case "get":
|
|
642
|
+
if (!rest[0]) {
|
|
643
|
+
console.error("Usage: ascendkit survey get <survey-id>");
|
|
644
|
+
process.exit(1);
|
|
645
|
+
}
|
|
646
|
+
output(await surveys.getSurvey(client, rest[0]));
|
|
647
|
+
break;
|
|
648
|
+
case "update":
|
|
649
|
+
if (!rest[0]) {
|
|
650
|
+
console.error("Usage: ascendkit survey update <survey-id> [--flags]");
|
|
651
|
+
process.exit(1);
|
|
652
|
+
}
|
|
653
|
+
output(await surveys.updateSurvey(client, rest[0], {
|
|
654
|
+
name: flags.name,
|
|
655
|
+
status: flags.status,
|
|
656
|
+
definition: flags.definition ? JSON.parse(flags.definition) : undefined,
|
|
657
|
+
}));
|
|
658
|
+
break;
|
|
659
|
+
case "delete":
|
|
660
|
+
if (!rest[0]) {
|
|
661
|
+
console.error("Usage: ascendkit survey delete <survey-id>");
|
|
662
|
+
process.exit(1);
|
|
663
|
+
}
|
|
664
|
+
output(await surveys.deleteSurvey(client, rest[0]));
|
|
665
|
+
break;
|
|
666
|
+
case "distribute":
|
|
667
|
+
if (!rest[0] || !flags.users) {
|
|
668
|
+
console.error("Usage: ascendkit survey distribute <survey-id> --users <usr_id1,usr_id2,...>");
|
|
669
|
+
process.exit(1);
|
|
670
|
+
}
|
|
671
|
+
output(await surveys.distributeSurvey(client, rest[0], flags.users.split(",")));
|
|
672
|
+
break;
|
|
673
|
+
case "invitations":
|
|
674
|
+
if (!rest[0]) {
|
|
675
|
+
console.error("Usage: ascendkit survey invitations <survey-id>");
|
|
676
|
+
process.exit(1);
|
|
677
|
+
}
|
|
678
|
+
output(await surveys.listInvitations(client, rest[0]));
|
|
679
|
+
break;
|
|
680
|
+
case "analytics":
|
|
681
|
+
if (!rest[0]) {
|
|
682
|
+
console.error("Usage: ascendkit survey analytics <survey-id>");
|
|
683
|
+
process.exit(1);
|
|
684
|
+
}
|
|
685
|
+
output(await surveys.getAnalytics(client, rest[0]));
|
|
686
|
+
break;
|
|
687
|
+
case "export-definition": {
|
|
688
|
+
if (!rest[0]) {
|
|
689
|
+
console.error("Usage: ascendkit survey export-definition <survey-id> [--out <file>]");
|
|
690
|
+
process.exit(1);
|
|
691
|
+
}
|
|
692
|
+
const survey = await surveys.getSurvey(client, rest[0]);
|
|
693
|
+
const text = `${JSON.stringify(survey.definition ?? {}, null, 2)}\n`;
|
|
694
|
+
const outPath = flags.out || flags.file;
|
|
695
|
+
if (outPath) {
|
|
696
|
+
writeFileSync(outPath, text, "utf8");
|
|
697
|
+
console.log(`Wrote survey definition to ${outPath}`);
|
|
698
|
+
}
|
|
699
|
+
else {
|
|
700
|
+
process.stdout.write(text);
|
|
701
|
+
}
|
|
702
|
+
break;
|
|
703
|
+
}
|
|
704
|
+
case "import-definition": {
|
|
705
|
+
if (!rest[0] || !(flags.in || flags.file)) {
|
|
706
|
+
console.error("Usage: ascendkit survey import-definition <survey-id> --in <file>");
|
|
707
|
+
process.exit(1);
|
|
708
|
+
}
|
|
709
|
+
const inPath = flags.in || flags.file;
|
|
710
|
+
const raw = readFileSync(inPath, "utf8");
|
|
711
|
+
const parsed = JSON.parse(raw);
|
|
712
|
+
const candidate = parsed && typeof parsed === "object" && "definition" in parsed
|
|
713
|
+
? parsed.definition
|
|
714
|
+
: parsed;
|
|
715
|
+
if (!candidate || typeof candidate !== "object" || Array.isArray(candidate)) {
|
|
716
|
+
console.error("Invalid definition JSON: expected an object or { definition: object }");
|
|
717
|
+
process.exit(1);
|
|
718
|
+
}
|
|
719
|
+
output(await surveys.updateSurvey(client, rest[0], {
|
|
720
|
+
definition: candidate,
|
|
721
|
+
}));
|
|
722
|
+
break;
|
|
723
|
+
}
|
|
724
|
+
default:
|
|
725
|
+
console.error(`Unknown survey command: ${action}`);
|
|
726
|
+
console.error('Run "ascendkit survey --help" for usage');
|
|
727
|
+
process.exit(1);
|
|
728
|
+
}
|
|
729
|
+
}
|
|
730
|
+
function runStatus() {
|
|
731
|
+
const authData = loadAuth();
|
|
732
|
+
const env = loadEnvContext();
|
|
733
|
+
console.log("\nLogin:");
|
|
734
|
+
if (authData?.token) {
|
|
735
|
+
console.log(` Logged in (API: ${authData.apiUrl})`);
|
|
736
|
+
}
|
|
737
|
+
else {
|
|
738
|
+
console.log(" Not initialized. Run: ascendkit init");
|
|
739
|
+
}
|
|
740
|
+
console.log("\nEnvironment:");
|
|
741
|
+
if (env?.publicKey) {
|
|
742
|
+
console.log(` Project: ${env.projectName || env.projectId}`);
|
|
743
|
+
console.log(` Environment: ${env.environmentName || env.environmentId} (${env.tier})`);
|
|
744
|
+
console.log(` Public key: ${env.publicKey}`);
|
|
745
|
+
}
|
|
746
|
+
else {
|
|
747
|
+
console.log(" No environment set. Run: ascendkit set-env <public-key>");
|
|
748
|
+
console.log(" List environments: ascendkit env list --project <project-id>");
|
|
749
|
+
}
|
|
750
|
+
console.log();
|
|
751
|
+
}
|
|
752
|
+
async function runVerify() {
|
|
753
|
+
const client = getClient();
|
|
754
|
+
const checks = [
|
|
755
|
+
{
|
|
756
|
+
service: "Auth",
|
|
757
|
+
check: async () => {
|
|
758
|
+
const s = await auth.getSettings(client);
|
|
759
|
+
const providers = s?.providers ?? [];
|
|
760
|
+
return providers.length > 0 ? `configured (${providers.join(", ")})` : "no providers configured";
|
|
761
|
+
},
|
|
762
|
+
},
|
|
763
|
+
{
|
|
764
|
+
service: "Email",
|
|
765
|
+
check: async () => {
|
|
766
|
+
const s = await email.getSettings(client);
|
|
767
|
+
if (s?.domain)
|
|
768
|
+
return `domain ${s.verificationStatus === "verified" ? "verified" : "pending DNS verification"} (${s.domain})`;
|
|
769
|
+
return "no domain configured (using AscendKit default sender)";
|
|
770
|
+
},
|
|
771
|
+
},
|
|
772
|
+
{
|
|
773
|
+
service: "Content",
|
|
774
|
+
check: async () => {
|
|
775
|
+
const items = await content.listTemplates(client);
|
|
776
|
+
return `${items.length} template${items.length !== 1 ? "s" : ""}`;
|
|
777
|
+
},
|
|
778
|
+
},
|
|
779
|
+
{
|
|
780
|
+
service: "Surveys",
|
|
781
|
+
check: async () => {
|
|
782
|
+
const items = await surveys.listSurveys(client);
|
|
783
|
+
const active = items.filter(s => s.status === "active").length;
|
|
784
|
+
return `${items.length} survey${items.length !== 1 ? "s" : ""}${active > 0 ? ` (${active} active)` : ""}`;
|
|
785
|
+
},
|
|
786
|
+
},
|
|
787
|
+
{
|
|
788
|
+
service: "Journeys",
|
|
789
|
+
check: async () => {
|
|
790
|
+
const raw = await journeys.listJourneys(client);
|
|
791
|
+
const items = normalizeJourneyRows(raw);
|
|
792
|
+
const active = items.filter(j => j.status === "active").length;
|
|
793
|
+
return `${items.length} journey${items.length !== 1 ? "s" : ""}${active > 0 ? ` (${active} active)` : ""}`;
|
|
794
|
+
},
|
|
795
|
+
},
|
|
796
|
+
];
|
|
797
|
+
console.log();
|
|
798
|
+
const results = await Promise.allSettled(checks.map(c => c.check()));
|
|
799
|
+
for (let i = 0; i < checks.length; i++) {
|
|
800
|
+
const r = results[i];
|
|
801
|
+
const label = checks[i].service.padEnd(12);
|
|
802
|
+
if (r.status === "fulfilled") {
|
|
803
|
+
console.log(` ${label} ✓ ${r.value}`);
|
|
804
|
+
}
|
|
805
|
+
else {
|
|
806
|
+
console.log(` ${label} ✗ ${r.reason?.message ?? "unavailable"}`);
|
|
807
|
+
}
|
|
808
|
+
}
|
|
809
|
+
console.log();
|
|
810
|
+
}
|
|
811
|
+
async function runJourney(client, action, rest) {
|
|
812
|
+
const flags = parseFlags(rest);
|
|
813
|
+
switch (action) {
|
|
814
|
+
case "create":
|
|
815
|
+
if (!flags.name || !flags["entry-event"] || !flags["entry-node"]) {
|
|
816
|
+
console.error("Usage: ascendkit journey create --name <n> --entry-event <e> --entry-node <n> [--nodes <json>] [--transitions <json>] [--description <d>] [--entry-conditions <json>] [--re-entry-policy <skip|restart>]");
|
|
817
|
+
process.exit(1);
|
|
818
|
+
}
|
|
819
|
+
output(await journeys.createJourney(client, {
|
|
820
|
+
name: flags.name,
|
|
821
|
+
entryEvent: flags["entry-event"],
|
|
822
|
+
entryNode: flags["entry-node"],
|
|
823
|
+
nodes: flags.nodes ? JSON.parse(flags.nodes) : undefined,
|
|
824
|
+
transitions: flags.transitions ? JSON.parse(flags.transitions) : undefined,
|
|
825
|
+
description: flags.description,
|
|
826
|
+
entryConditions: flags["entry-conditions"] ? JSON.parse(flags["entry-conditions"]) : undefined,
|
|
827
|
+
reEntryPolicy: flags["re-entry-policy"],
|
|
828
|
+
}));
|
|
829
|
+
break;
|
|
830
|
+
case "list": {
|
|
831
|
+
const opts = {};
|
|
832
|
+
if (flags.status)
|
|
833
|
+
opts.status = flags.status;
|
|
834
|
+
const raw = await journeys.listJourneys(client, Object.keys(opts).length > 0 ? opts : undefined);
|
|
835
|
+
const items = normalizeJourneyRows(raw);
|
|
836
|
+
table(items, [
|
|
837
|
+
{ key: "id", label: "ID" },
|
|
838
|
+
{ key: "name", label: "Name", width: 30 },
|
|
839
|
+
{ key: "status", label: "Status" },
|
|
840
|
+
{ key: "entryEvent", label: "Entry Event", width: 20 },
|
|
841
|
+
]);
|
|
842
|
+
break;
|
|
843
|
+
}
|
|
844
|
+
case "get":
|
|
845
|
+
if (!rest[0]) {
|
|
846
|
+
console.error("Usage: ascendkit journey get <journey-id>");
|
|
847
|
+
process.exit(1);
|
|
848
|
+
}
|
|
849
|
+
output(await journeys.getJourney(client, rest[0]));
|
|
850
|
+
break;
|
|
851
|
+
case "update":
|
|
852
|
+
if (!rest[0]) {
|
|
853
|
+
console.error("Usage: ascendkit journey update <journey-id> [--flags]");
|
|
854
|
+
process.exit(1);
|
|
855
|
+
}
|
|
856
|
+
output(await journeys.updateJourney(client, rest[0], {
|
|
857
|
+
name: flags.name,
|
|
858
|
+
description: flags.description,
|
|
859
|
+
entryEvent: flags["entry-event"],
|
|
860
|
+
entryNode: flags["entry-node"],
|
|
861
|
+
entryConditions: flags["entry-conditions"] ? JSON.parse(flags["entry-conditions"]) : undefined,
|
|
862
|
+
reEntryPolicy: flags["re-entry-policy"],
|
|
863
|
+
nodes: flags.nodes ? JSON.parse(flags.nodes) : undefined,
|
|
864
|
+
transitions: flags.transitions ? JSON.parse(flags.transitions) : undefined,
|
|
865
|
+
}));
|
|
866
|
+
break;
|
|
867
|
+
case "delete":
|
|
868
|
+
if (!rest[0]) {
|
|
869
|
+
console.error("Usage: ascendkit journey delete <journey-id>");
|
|
870
|
+
process.exit(1);
|
|
871
|
+
}
|
|
872
|
+
output(await journeys.deleteJourney(client, rest[0]));
|
|
873
|
+
break;
|
|
874
|
+
case "activate":
|
|
875
|
+
if (!rest[0]) {
|
|
876
|
+
console.error("Usage: ascendkit journey activate <journey-id>");
|
|
877
|
+
process.exit(1);
|
|
878
|
+
}
|
|
879
|
+
output(await journeys.activateJourney(client, rest[0]));
|
|
880
|
+
break;
|
|
881
|
+
case "pause":
|
|
882
|
+
if (!rest[0]) {
|
|
883
|
+
console.error("Usage: ascendkit journey pause <journey-id>");
|
|
884
|
+
process.exit(1);
|
|
885
|
+
}
|
|
886
|
+
output(await journeys.pauseJourney(client, rest[0]));
|
|
887
|
+
break;
|
|
888
|
+
case "archive":
|
|
889
|
+
if (!rest[0]) {
|
|
890
|
+
console.error("Usage: ascendkit journey archive <journey-id>");
|
|
891
|
+
process.exit(1);
|
|
892
|
+
}
|
|
893
|
+
output(await journeys.archiveJourney(client, rest[0]));
|
|
894
|
+
break;
|
|
895
|
+
case "analytics":
|
|
896
|
+
if (!rest[0]) {
|
|
897
|
+
console.error("Usage: ascendkit journey analytics <journey-id>");
|
|
898
|
+
process.exit(1);
|
|
899
|
+
}
|
|
900
|
+
output(await journeys.getJourneyAnalytics(client, rest[0]));
|
|
901
|
+
break;
|
|
902
|
+
case "list-nodes":
|
|
903
|
+
if (!rest[0]) {
|
|
904
|
+
console.error("Usage: ascendkit journey list-nodes <journey-id>");
|
|
905
|
+
process.exit(1);
|
|
906
|
+
}
|
|
907
|
+
output(await journeys.listNodes(client, rest[0]));
|
|
908
|
+
break;
|
|
909
|
+
case "add-node": {
|
|
910
|
+
if (!rest[0] || !flags.name) {
|
|
911
|
+
console.error("Usage: ascendkit journey add-node <journey-id> --name <node-name> [--action <json>] [--terminal <true|false>]");
|
|
912
|
+
process.exit(1);
|
|
913
|
+
}
|
|
914
|
+
const params = { name: flags.name };
|
|
915
|
+
if (flags.action)
|
|
916
|
+
params.action = JSON.parse(flags.action);
|
|
917
|
+
if (flags.terminal)
|
|
918
|
+
params.terminal = flags.terminal === "true";
|
|
919
|
+
output(await journeys.addNode(client, rest[0], params));
|
|
920
|
+
break;
|
|
921
|
+
}
|
|
922
|
+
case "edit-node": {
|
|
923
|
+
if (!rest[0] || !rest[1]) {
|
|
924
|
+
console.error("Usage: ascendkit journey edit-node <journey-id> <node-name> [--action <json>] [--terminal <true|false>]");
|
|
925
|
+
process.exit(1);
|
|
926
|
+
}
|
|
927
|
+
const params = {};
|
|
928
|
+
if (flags.action)
|
|
929
|
+
params.action = JSON.parse(flags.action);
|
|
930
|
+
if (flags.terminal)
|
|
931
|
+
params.terminal = flags.terminal === "true";
|
|
932
|
+
output(await journeys.editNode(client, rest[0], rest[1], params));
|
|
933
|
+
break;
|
|
934
|
+
}
|
|
935
|
+
case "remove-node":
|
|
936
|
+
if (!rest[0] || !rest[1]) {
|
|
937
|
+
console.error("Usage: ascendkit journey remove-node <journey-id> <node-name>");
|
|
938
|
+
process.exit(1);
|
|
939
|
+
}
|
|
940
|
+
output(await journeys.removeNode(client, rest[0], rest[1]));
|
|
941
|
+
break;
|
|
942
|
+
case "list-transitions": {
|
|
943
|
+
if (!rest[0]) {
|
|
944
|
+
console.error("Usage: ascendkit journey list-transitions <journey-id> [--from <node-name>] [--to <node-name>]");
|
|
945
|
+
process.exit(1);
|
|
946
|
+
}
|
|
947
|
+
output(await journeys.listTransitions(client, rest[0], {
|
|
948
|
+
from_node: flags.from,
|
|
949
|
+
to_node: flags.to,
|
|
950
|
+
}));
|
|
951
|
+
break;
|
|
952
|
+
}
|
|
953
|
+
case "add-transition": {
|
|
954
|
+
if (!rest[0] || !flags.from || !flags.to || !(flags.on || flags.after || flags.trigger)) {
|
|
955
|
+
console.error("Usage: ascendkit journey add-transition <journey-id> --from <node-name> --to <node-name> --on <event> | --after <delay> | --trigger <json> [--priority <n>] [--name <transition-name>]");
|
|
956
|
+
process.exit(1);
|
|
957
|
+
}
|
|
958
|
+
let trigger;
|
|
959
|
+
if (flags.on) {
|
|
960
|
+
trigger = { type: "event", event: flags.on };
|
|
961
|
+
}
|
|
962
|
+
else if (flags.after) {
|
|
963
|
+
trigger = { type: "timer", delay: parseDelay(flags.after) };
|
|
964
|
+
}
|
|
965
|
+
else {
|
|
966
|
+
trigger = JSON.parse(flags.trigger);
|
|
967
|
+
}
|
|
968
|
+
const params = {
|
|
969
|
+
from: flags.from,
|
|
970
|
+
to: flags.to,
|
|
971
|
+
trigger,
|
|
972
|
+
};
|
|
973
|
+
if (flags.priority != null)
|
|
974
|
+
params.priority = Number(flags.priority);
|
|
975
|
+
if (flags.name)
|
|
976
|
+
params.name = flags.name;
|
|
977
|
+
output(await journeys.addTransition(client, rest[0], params));
|
|
978
|
+
break;
|
|
979
|
+
}
|
|
980
|
+
case "edit-transition": {
|
|
981
|
+
if (!rest[0] || !rest[1]) {
|
|
982
|
+
console.error("Usage: ascendkit journey edit-transition <journey-id> <transition-name> [--on <event>] [--after <delay>] [--trigger <json>] [--priority <n>]");
|
|
983
|
+
process.exit(1);
|
|
984
|
+
}
|
|
985
|
+
const params = {};
|
|
986
|
+
if (flags.on) {
|
|
987
|
+
params.trigger = { type: "event", event: flags.on };
|
|
988
|
+
}
|
|
989
|
+
else if (flags.after) {
|
|
990
|
+
params.trigger = { type: "timer", delay: parseDelay(flags.after) };
|
|
991
|
+
}
|
|
992
|
+
else if (flags.trigger) {
|
|
993
|
+
params.trigger = JSON.parse(flags.trigger);
|
|
994
|
+
}
|
|
995
|
+
if (flags.priority != null)
|
|
996
|
+
params.priority = Number(flags.priority);
|
|
997
|
+
output(await journeys.editTransition(client, rest[0], rest[1], params));
|
|
998
|
+
break;
|
|
999
|
+
}
|
|
1000
|
+
case "remove-transition":
|
|
1001
|
+
if (!rest[0] || !rest[1]) {
|
|
1002
|
+
console.error("Usage: ascendkit journey remove-transition <journey-id> <transition-name>");
|
|
1003
|
+
process.exit(1);
|
|
1004
|
+
}
|
|
1005
|
+
output(await journeys.removeTransition(client, rest[0], rest[1]));
|
|
1006
|
+
break;
|
|
1007
|
+
default:
|
|
1008
|
+
console.error(`Unknown journey command: ${action}`);
|
|
1009
|
+
process.exit(1);
|
|
1010
|
+
}
|
|
1011
|
+
}
|
|
1012
|
+
async function runEmail(client, action, rest) {
|
|
1013
|
+
const flags = parseFlags(rest);
|
|
1014
|
+
switch (action) {
|
|
1015
|
+
case "identity": {
|
|
1016
|
+
const s = await email.getSettings(client);
|
|
1017
|
+
printIdentityStatus(s);
|
|
1018
|
+
break;
|
|
1019
|
+
}
|
|
1020
|
+
case "use-default": {
|
|
1021
|
+
const s = await email.useDefaultIdentity(client);
|
|
1022
|
+
printIdentityStatus(s);
|
|
1023
|
+
break;
|
|
1024
|
+
}
|
|
1025
|
+
case "use-custom": {
|
|
1026
|
+
if (!rest[0]) {
|
|
1027
|
+
console.error("Usage: ascendkit email use-custom <domain> [--from-email <email>] [--from-name <name>]");
|
|
1028
|
+
process.exit(1);
|
|
1029
|
+
}
|
|
1030
|
+
const s = await email.useCustomIdentity(client, rest[0], {
|
|
1031
|
+
fromEmail: flags["from-email"],
|
|
1032
|
+
fromName: flags["from-name"],
|
|
1033
|
+
});
|
|
1034
|
+
await printEmailSetup(s);
|
|
1035
|
+
break;
|
|
1036
|
+
}
|
|
1037
|
+
case "settings":
|
|
1038
|
+
if (rest[0] === "update") {
|
|
1039
|
+
const params = {};
|
|
1040
|
+
if (flags["from-email"])
|
|
1041
|
+
params.fromEmail = flags["from-email"];
|
|
1042
|
+
if (flags["from-name"])
|
|
1043
|
+
params.fromName = flags["from-name"];
|
|
1044
|
+
output(await email.updateSettings(client, params));
|
|
1045
|
+
}
|
|
1046
|
+
else {
|
|
1047
|
+
output(await email.getSettings(client));
|
|
1048
|
+
}
|
|
1049
|
+
break;
|
|
1050
|
+
case "setup-domain":
|
|
1051
|
+
if (!rest[0]) {
|
|
1052
|
+
console.error("Usage: ascendkit email setup-domain <domain>");
|
|
1053
|
+
process.exit(1);
|
|
1054
|
+
}
|
|
1055
|
+
await printEmailSetup(await email.setupDomain(client, rest[0]));
|
|
1056
|
+
break;
|
|
1057
|
+
case "domain-status": {
|
|
1058
|
+
const interval = Math.max(5, Number.parseInt(flags.interval || "15", 10) || 15);
|
|
1059
|
+
if (flags.watch === "true") {
|
|
1060
|
+
await watchDomainStatus(client, interval);
|
|
1061
|
+
}
|
|
1062
|
+
else {
|
|
1063
|
+
output(await email.checkDomainStatus(client));
|
|
1064
|
+
}
|
|
1065
|
+
break;
|
|
1066
|
+
}
|
|
1067
|
+
case "open-dns": {
|
|
1068
|
+
const provider = await email.getDnsProvider(client, flags.domain);
|
|
1069
|
+
const url = provider?.portalUrl;
|
|
1070
|
+
if (!url) {
|
|
1071
|
+
console.error("Could not determine DNS provider URL for this domain.");
|
|
1072
|
+
process.exit(1);
|
|
1073
|
+
}
|
|
1074
|
+
console.log(url);
|
|
1075
|
+
if (flags.open === "true") {
|
|
1076
|
+
openBrowser(url);
|
|
1077
|
+
}
|
|
1078
|
+
break;
|
|
1079
|
+
}
|
|
1080
|
+
case "remove-domain":
|
|
1081
|
+
output(await email.removeDomain(client));
|
|
1082
|
+
break;
|
|
1083
|
+
default:
|
|
1084
|
+
console.error(`Unknown email command: ${action}`);
|
|
1085
|
+
process.exit(1);
|
|
1086
|
+
}
|
|
1087
|
+
}
|
|
1088
|
+
run().catch((err) => {
|
|
1089
|
+
console.error(err.message);
|
|
1090
|
+
process.exit(1);
|
|
1091
|
+
});
|
|
1092
|
+
async function printEmailSetup(settings) {
|
|
1093
|
+
output(settings);
|
|
1094
|
+
if (!settings.domain)
|
|
1095
|
+
return;
|
|
1096
|
+
const provider = settings.dnsProvider;
|
|
1097
|
+
if (provider?.name) {
|
|
1098
|
+
console.log(`\nDetected DNS provider: ${provider.name} (${provider.confidence ?? "unknown"})`);
|
|
1099
|
+
}
|
|
1100
|
+
if (provider?.portalUrl) {
|
|
1101
|
+
console.log(`Provider console: ${provider.portalUrl}`);
|
|
1102
|
+
}
|
|
1103
|
+
if (provider?.assistantSetupUrl) {
|
|
1104
|
+
console.log(`Guided setup: ${provider.assistantSetupUrl}`);
|
|
1105
|
+
}
|
|
1106
|
+
if (Array.isArray(settings.dnsRecords) && settings.dnsRecords.length > 0) {
|
|
1107
|
+
console.log("\nAdd these DNS records:");
|
|
1108
|
+
for (const rec of settings.dnsRecords) {
|
|
1109
|
+
console.log(` ${rec.type}\t${rec.name}\t${rec.value}`);
|
|
1110
|
+
}
|
|
1111
|
+
}
|
|
1112
|
+
}
|
|
1113
|
+
function printIdentityStatus(settings) {
|
|
1114
|
+
const mode = settings.domain ? "customer-owned" : "ascendkit-default";
|
|
1115
|
+
const from = settings.fromEmail || "noreply@ascendkit.dev";
|
|
1116
|
+
const status = settings.verificationStatus || "none";
|
|
1117
|
+
console.log(`Identity mode: ${mode}`);
|
|
1118
|
+
console.log(`From email: ${from}`);
|
|
1119
|
+
console.log(`From name: ${settings.fromName || "not set"}`);
|
|
1120
|
+
if (settings.domain) {
|
|
1121
|
+
console.log(`Domain: ${settings.domain} (${status})`);
|
|
1122
|
+
if (status !== "verified") {
|
|
1123
|
+
console.log("Next step: add DNS records and run `ascendkit email domain-status --watch`");
|
|
1124
|
+
}
|
|
1125
|
+
}
|
|
1126
|
+
else {
|
|
1127
|
+
console.log("Next step: run `ascendkit email use-custom <domain>` to configure customer-owned identity.");
|
|
1128
|
+
}
|
|
1129
|
+
}
|
|
1130
|
+
async function watchDomainStatus(client, intervalSeconds) {
|
|
1131
|
+
while (true) {
|
|
1132
|
+
const data = await email.checkDomainStatus(client);
|
|
1133
|
+
output(data);
|
|
1134
|
+
if (data?.status === "verified" || data?.status === "failed" || data?.status === "none") {
|
|
1135
|
+
return;
|
|
1136
|
+
}
|
|
1137
|
+
await new Promise((resolve) => setTimeout(resolve, intervalSeconds * 1000));
|
|
1138
|
+
}
|
|
1139
|
+
}
|
|
1140
|
+
function openBrowser(url) {
|
|
1141
|
+
import("child_process").then(({ execFile }) => {
|
|
1142
|
+
if (process.platform === "darwin") {
|
|
1143
|
+
execFile("open", [url]);
|
|
1144
|
+
}
|
|
1145
|
+
else if (process.platform === "win32") {
|
|
1146
|
+
const safe = url.replace(/[&|<>^"]/g, "^$&");
|
|
1147
|
+
execFile("cmd.exe", ["/c", "start", "", safe]);
|
|
1148
|
+
}
|
|
1149
|
+
else {
|
|
1150
|
+
execFile("xdg-open", [url]);
|
|
1151
|
+
}
|
|
1152
|
+
});
|
|
1153
|
+
}
|