spaps 0.7.6 → 0.7.8
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/AI_TOOLS.json +5566 -38
- package/README.md +67 -13
- package/assets/local-runtime/Dockerfile +13 -0
- package/assets/local-runtime/docker-compose.yml +2 -1
- package/assets/local-runtime/manifest.json +3 -1
- package/bin/spaps.js +34 -8
- package/package.json +3 -4
- package/src/ai-helper.js +44 -10
- package/src/ai-tool-spec.js +19 -4
- package/src/auth/env.js +5 -0
- package/src/cli-dispatcher.js +365 -91
- package/src/docs-quick.js +37 -0
- package/src/docs-system.js +1 -31
- package/src/doctor.js +58 -1
- package/src/domain-cli.js +79 -0
- package/src/domains.js +193 -0
- package/src/fixture-kernel.js +898 -29
- package/src/handlers.js +535 -29
- package/src/help-quick.js +42 -0
- package/src/help-system.js +1 -36
- package/src/home-view.js +200 -0
- package/src/local-runtime.js +19 -4
- package/src/local-server.js +30 -1
package/src/handlers.js
CHANGED
|
@@ -2,24 +2,30 @@ const chalk = require('chalk');
|
|
|
2
2
|
const fs = require('fs');
|
|
3
3
|
const { DEFAULT_PORT } = require('./config');
|
|
4
4
|
const { handleError } = require('./error-handler');
|
|
5
|
-
const {
|
|
6
|
-
const {
|
|
7
|
-
const {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
5
|
+
const { hasInteractiveTerminal } = require('./auth/env');
|
|
6
|
+
const { showQuickHelp } = require('./help-quick');
|
|
7
|
+
const { showQuickReference } = require('./docs-quick');
|
|
8
|
+
|
|
9
|
+
function lazy(factory) {
|
|
10
|
+
let value;
|
|
11
|
+
return () => {
|
|
12
|
+
if (value === undefined) {
|
|
13
|
+
value = factory();
|
|
14
|
+
}
|
|
15
|
+
return value;
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const loadHelpSystem = lazy(() => require('./help-system'));
|
|
20
|
+
const loadDocsSystem = lazy(() => require('./docs-system'));
|
|
21
|
+
const loadAiHelper = lazy(() => require('./ai-helper'));
|
|
22
|
+
const loadAiToolSpec = lazy(() => require('./ai-tool-spec'));
|
|
23
|
+
const loadDoctor = lazy(() => require('./doctor'));
|
|
24
|
+
const loadHomeView = lazy(() => require('./home-view'));
|
|
25
|
+
const loadFixtureKernel = lazy(() => require('./fixture-kernel'));
|
|
26
|
+
const loadProjectScaffolder = lazy(() => require('./project-scaffolder'));
|
|
27
|
+
const loadAuthCommandHandlers = lazy(() => require('./auth/handlers'));
|
|
28
|
+
const loadDomainCli = lazy(() => require('./domain-cli'));
|
|
23
29
|
|
|
24
30
|
function createHandlers(version, logo) {
|
|
25
31
|
function invalidArgument(message) {
|
|
@@ -28,7 +34,62 @@ function createHandlers(version, logo) {
|
|
|
28
34
|
return error;
|
|
29
35
|
}
|
|
30
36
|
|
|
37
|
+
function emitInteractiveFallback(message) {
|
|
38
|
+
console.log(
|
|
39
|
+
chalk.yellow(`\n⚠️ ${message}. Showing the quick reference instead.\n`)
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const verifyHandler = async ({ options }) => {
|
|
44
|
+
const { runQuickTest } = loadAiHelper();
|
|
45
|
+
const result = await runQuickTest({
|
|
46
|
+
port: options.port,
|
|
47
|
+
serverUrl: options.serverUrl,
|
|
48
|
+
cwd: process.cwd(),
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
if (options.json) {
|
|
52
|
+
console.log(JSON.stringify(result, null, 2));
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
console.log(chalk.yellow('\n🍠 SPAPS Verify\n'));
|
|
57
|
+
console.log(result.success ? chalk.green(result.summary) : chalk.yellow(result.summary));
|
|
58
|
+
console.log();
|
|
59
|
+
(result.results || []).forEach((entry) => {
|
|
60
|
+
const mark = entry.success ? chalk.green('✓') : chalk.red('✗');
|
|
61
|
+
const message = entry.message ? `: ${entry.message}` : '';
|
|
62
|
+
console.log(` ${mark} ${entry.test}${message}`);
|
|
63
|
+
if (!entry.success && entry.fix) {
|
|
64
|
+
console.log(chalk.gray(` fix: ${entry.fix}`));
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
if (Array.isArray(result.next_steps) && result.next_steps.length > 0) {
|
|
68
|
+
console.log();
|
|
69
|
+
console.log(chalk.bold('Next'));
|
|
70
|
+
result.next_steps.forEach((step) => {
|
|
71
|
+
console.log(chalk.cyan(` ${step}`));
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
console.log();
|
|
75
|
+
};
|
|
76
|
+
|
|
31
77
|
return {
|
|
78
|
+
home: async ({ options }) => {
|
|
79
|
+
const { buildHomeView, renderHomeView } = loadHomeView();
|
|
80
|
+
const view = await buildHomeView({
|
|
81
|
+
port: options.port,
|
|
82
|
+
serverUrl: options.serverUrl,
|
|
83
|
+
cwd: process.cwd(),
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
if (options.json) {
|
|
87
|
+
console.log(JSON.stringify(view, null, 2));
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
renderHomeView(view, { logo });
|
|
92
|
+
},
|
|
32
93
|
local: async ({ options }) => {
|
|
33
94
|
const isJson = options.json;
|
|
34
95
|
if (!isJson) console.log(logo);
|
|
@@ -86,6 +147,7 @@ function createHandlers(version, logo) {
|
|
|
86
147
|
}
|
|
87
148
|
},
|
|
88
149
|
quickstart: async ({ options }) => {
|
|
150
|
+
const { getQuickStartInstructions } = loadAiHelper();
|
|
89
151
|
const instructions = await getQuickStartInstructions(options.port);
|
|
90
152
|
if (options.json) {
|
|
91
153
|
console.log(JSON.stringify(instructions, null, 2));
|
|
@@ -104,6 +166,7 @@ function createHandlers(version, logo) {
|
|
|
104
166
|
}
|
|
105
167
|
},
|
|
106
168
|
status: async ({ options }) => {
|
|
169
|
+
const { getServerStatus } = loadAiHelper();
|
|
107
170
|
const status = await getServerStatus(options.port);
|
|
108
171
|
if (options.json) {
|
|
109
172
|
console.log(JSON.stringify(status));
|
|
@@ -127,10 +190,7 @@ function createHandlers(version, logo) {
|
|
|
127
190
|
}
|
|
128
191
|
}
|
|
129
192
|
},
|
|
130
|
-
|
|
131
|
-
const result = await runQuickTest(options.port);
|
|
132
|
-
console.log(JSON.stringify(result, null, 2));
|
|
133
|
-
},
|
|
193
|
+
verify: verifyHandler,
|
|
134
194
|
init: async ({ options }) => {
|
|
135
195
|
const isJson = options.json;
|
|
136
196
|
const envContent = `# SPAPS Local Development\nSPAPS_API_URL=http://localhost:${DEFAULT_PORT}\n# SPAPS_API_KEY=your-api-key-here\n`;
|
|
@@ -158,6 +218,7 @@ function createHandlers(version, logo) {
|
|
|
158
218
|
},
|
|
159
219
|
create: async ({ options }) => {
|
|
160
220
|
const isJson = options.json;
|
|
221
|
+
const { createProjectStarter } = loadProjectScaffolder();
|
|
161
222
|
|
|
162
223
|
try {
|
|
163
224
|
const result = await createProjectStarter({
|
|
@@ -223,7 +284,12 @@ function createHandlers(version, logo) {
|
|
|
223
284
|
},
|
|
224
285
|
help: async ({ options }) => {
|
|
225
286
|
if (options.interactive) {
|
|
226
|
-
|
|
287
|
+
if (!hasInteractiveTerminal()) {
|
|
288
|
+
emitInteractiveFallback('Interactive help requires a TTY');
|
|
289
|
+
showQuickHelp();
|
|
290
|
+
return;
|
|
291
|
+
}
|
|
292
|
+
await loadHelpSystem().showInteractiveHelp();
|
|
227
293
|
} else if (options.quick) {
|
|
228
294
|
showQuickHelp();
|
|
229
295
|
} else {
|
|
@@ -232,7 +298,7 @@ function createHandlers(version, logo) {
|
|
|
232
298
|
},
|
|
233
299
|
docs: async ({ options }) => {
|
|
234
300
|
if (options.search) {
|
|
235
|
-
const results = searchDocs(options.search);
|
|
301
|
+
const results = loadDocsSystem().searchDocs(options.search);
|
|
236
302
|
if (options.json) {
|
|
237
303
|
console.log(JSON.stringify({ results }, null, 2));
|
|
238
304
|
} else {
|
|
@@ -250,12 +316,18 @@ function createHandlers(version, logo) {
|
|
|
250
316
|
console.log(chalk.blue(' to browse full documentation\n'));
|
|
251
317
|
}
|
|
252
318
|
} else if (options.interactive) {
|
|
253
|
-
|
|
319
|
+
if (!hasInteractiveTerminal()) {
|
|
320
|
+
emitInteractiveFallback('Interactive docs require a TTY');
|
|
321
|
+
showQuickReference();
|
|
322
|
+
return;
|
|
323
|
+
}
|
|
324
|
+
await loadDocsSystem().showInteractiveDocs();
|
|
254
325
|
} else {
|
|
255
326
|
showQuickReference();
|
|
256
327
|
}
|
|
257
328
|
},
|
|
258
329
|
tools: async ({ options }) => {
|
|
330
|
+
const { buildToolSpec } = loadAiToolSpec();
|
|
259
331
|
const spec = await buildToolSpec({
|
|
260
332
|
format: options.format || 'openai',
|
|
261
333
|
port: options.port,
|
|
@@ -282,6 +354,12 @@ function createHandlers(version, logo) {
|
|
|
282
354
|
},
|
|
283
355
|
fixtures: async ({ options }) => {
|
|
284
356
|
const isJson = options.json;
|
|
357
|
+
const {
|
|
358
|
+
applyFixtures,
|
|
359
|
+
exportStorageState,
|
|
360
|
+
initFixtureKernel,
|
|
361
|
+
resetFixtures,
|
|
362
|
+
} = loadFixtureKernel();
|
|
285
363
|
|
|
286
364
|
try {
|
|
287
365
|
if (options.format && options.format !== 'playwright') {
|
|
@@ -305,6 +383,8 @@ function createHandlers(version, logo) {
|
|
|
305
383
|
port: options.port,
|
|
306
384
|
baseUrl: options.baseUrl,
|
|
307
385
|
version,
|
|
386
|
+
persona: options.persona,
|
|
387
|
+
seed: options.seed,
|
|
308
388
|
});
|
|
309
389
|
break;
|
|
310
390
|
case 'reset':
|
|
@@ -313,6 +393,7 @@ function createHandlers(version, logo) {
|
|
|
313
393
|
port: options.port,
|
|
314
394
|
baseUrl: options.baseUrl,
|
|
315
395
|
version,
|
|
396
|
+
seed: options.seed,
|
|
316
397
|
});
|
|
317
398
|
break;
|
|
318
399
|
case 'storage-state':
|
|
@@ -325,6 +406,7 @@ function createHandlers(version, logo) {
|
|
|
325
406
|
baseUrl: options.baseUrl,
|
|
326
407
|
version,
|
|
327
408
|
persona: options.persona,
|
|
409
|
+
seed: options.seed,
|
|
328
410
|
});
|
|
329
411
|
break;
|
|
330
412
|
default:
|
|
@@ -362,6 +444,13 @@ function createHandlers(version, logo) {
|
|
|
362
444
|
});
|
|
363
445
|
}
|
|
364
446
|
|
|
447
|
+
if (Array.isArray(result.seeding?.personas) && result.seeding.personas.length > 0) {
|
|
448
|
+
console.log(chalk.green('\nSeeded personas:'));
|
|
449
|
+
result.seeding.personas.forEach((entry) => {
|
|
450
|
+
console.log(chalk.gray(` • ${entry.persona} (${entry.request_count} requests)`));
|
|
451
|
+
});
|
|
452
|
+
}
|
|
453
|
+
|
|
365
454
|
if (result.generated?.personas?.length) {
|
|
366
455
|
console.log(chalk.green('\nGenerated personas:'));
|
|
367
456
|
result.generated.personas.forEach((entry) => {
|
|
@@ -408,13 +497,430 @@ function createHandlers(version, logo) {
|
|
|
408
497
|
}
|
|
409
498
|
},
|
|
410
499
|
doctor: async ({ options }) => {
|
|
500
|
+
const { runDoctor } = loadDoctor();
|
|
411
501
|
await runDoctor({ port: options.port || DEFAULT_PORT, stripe: options.stripe || null, json: options.json });
|
|
412
502
|
},
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
503
|
+
test: verifyHandler,
|
|
504
|
+
login: async (...args) => loadAuthCommandHandlers().loginHandler(...args),
|
|
505
|
+
logout: async (...args) => loadAuthCommandHandlers().logoutHandler(...args),
|
|
506
|
+
whoami: async (...args) => loadAuthCommandHandlers().whoamiHandler(...args),
|
|
507
|
+
token: async (...args) => loadAuthCommandHandlers().tokenHandler(...args),
|
|
508
|
+
dayrate: dayrateHandler,
|
|
509
|
+
email: emailHandler,
|
|
510
|
+
policy: policyHandler,
|
|
511
|
+
webhook: webhookHandler,
|
|
512
|
+
'issue-reports': issueReportsHandler,
|
|
417
513
|
};
|
|
418
514
|
}
|
|
419
515
|
|
|
516
|
+
function emitText({ intent, result, isJson, successMessage = null }) {
|
|
517
|
+
const { emit } = loadDomainCli();
|
|
518
|
+
if (isJson) {
|
|
519
|
+
emit({ intent, result, isJson, successMessage });
|
|
520
|
+
return;
|
|
521
|
+
}
|
|
522
|
+
if (!result.ok) {
|
|
523
|
+
emit({ intent, result, isJson, successMessage });
|
|
524
|
+
return;
|
|
525
|
+
}
|
|
526
|
+
if (successMessage) {
|
|
527
|
+
console.log(successMessage);
|
|
528
|
+
}
|
|
529
|
+
const output = typeof result.data === 'string' ? result.data : JSON.stringify(result.data, null, 2);
|
|
530
|
+
process.stdout.write(output);
|
|
531
|
+
if (!output.endsWith('\n')) process.stdout.write('\n');
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
function parseJsonFlag(raw, label) {
|
|
535
|
+
if (raw === null || raw === undefined) {
|
|
536
|
+
return { ok: true, value: null };
|
|
537
|
+
}
|
|
538
|
+
try {
|
|
539
|
+
return { ok: true, value: JSON.parse(raw) };
|
|
540
|
+
} catch {
|
|
541
|
+
console.error(`${label}: must be valid JSON`);
|
|
542
|
+
process.exitCode = 2;
|
|
543
|
+
return { ok: false, value: null };
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
function parseBooleanFlag(raw, label) {
|
|
548
|
+
if (raw === null || raw === undefined) {
|
|
549
|
+
return { ok: true, value: null };
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
if (typeof raw === 'boolean') {
|
|
553
|
+
return { ok: true, value: raw };
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
const normalized = String(raw).trim().toLowerCase();
|
|
557
|
+
if (['true', '1', 'yes', 'y'].includes(normalized)) {
|
|
558
|
+
return { ok: true, value: true };
|
|
559
|
+
}
|
|
560
|
+
if (['false', '0', 'no', 'n'].includes(normalized)) {
|
|
561
|
+
return { ok: true, value: false };
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
console.error(`${label}: must be one of true|false|1|0|yes|no`);
|
|
565
|
+
process.exitCode = 2;
|
|
566
|
+
return { ok: false, value: null };
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
function requireOption(value, message) {
|
|
570
|
+
if (value !== null && value !== undefined && value !== '') {
|
|
571
|
+
return true;
|
|
572
|
+
}
|
|
573
|
+
console.error(message);
|
|
574
|
+
process.exitCode = 2;
|
|
575
|
+
return false;
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
function assignIfDefined(target, key, value) {
|
|
579
|
+
if (value !== null && value !== undefined) {
|
|
580
|
+
target[key] = value;
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
async function dayrateHandler({ options }) {
|
|
585
|
+
const { callEndpoint, emit, emitAuthError } = loadDomainCli();
|
|
586
|
+
const isJson = Boolean(options.json);
|
|
587
|
+
const sub = options.subcommand;
|
|
588
|
+
if (sub !== 'config') {
|
|
589
|
+
console.error('dayrate: unknown subcommand. Supported: config');
|
|
590
|
+
process.exitCode = 2;
|
|
591
|
+
return;
|
|
592
|
+
}
|
|
593
|
+
try {
|
|
594
|
+
const result = await callEndpoint({ options, method: 'GET', path: '/api/dayrate/admin/config' });
|
|
595
|
+
emit({ intent: 'dayrate.config', result, isJson });
|
|
596
|
+
if (!result.ok) process.exitCode = 1;
|
|
597
|
+
} catch (err) {
|
|
598
|
+
emitAuthError('dayrate.config', err, isJson);
|
|
599
|
+
process.exitCode = 1;
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
async function emailHandler({ options }) {
|
|
604
|
+
const { callEndpoint, emit, emitAuthError } = loadDomainCli();
|
|
605
|
+
const isJson = Boolean(options.json);
|
|
606
|
+
const sub = options.subcommand;
|
|
607
|
+
|
|
608
|
+
try {
|
|
609
|
+
if (sub === 'send') {
|
|
610
|
+
if (!requireOption(options.templateKey, 'email send: --template-key is required')) return;
|
|
611
|
+
if (!requireOption(options.to, 'email send: --to is required')) return;
|
|
612
|
+
|
|
613
|
+
const context = parseJsonFlag(options.context, 'email send: --context');
|
|
614
|
+
if (!context.ok) return;
|
|
615
|
+
|
|
616
|
+
const body = {
|
|
617
|
+
template_key: options.templateKey,
|
|
618
|
+
to: options.to,
|
|
619
|
+
};
|
|
620
|
+
assignIfDefined(body, 'context', context.value);
|
|
621
|
+
assignIfDefined(body, 'user_id', options.userId);
|
|
622
|
+
assignIfDefined(body, 'owner_id', options.ownerId);
|
|
623
|
+
assignIfDefined(body, 'subject_override', options.subjectOverride);
|
|
624
|
+
assignIfDefined(body, 'body_override', options.bodyOverride);
|
|
625
|
+
|
|
626
|
+
const result = await callEndpoint({ options, method: 'POST', path: '/api/email/send', body });
|
|
627
|
+
emit({ intent: 'email.send', result, isJson });
|
|
628
|
+
if (!result.ok) process.exitCode = 1;
|
|
629
|
+
return;
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
if (sub === 'get-template') {
|
|
633
|
+
if (!requireOption(options.templateKey, 'email get-template: --template-key is required')) return;
|
|
634
|
+
const result = await callEndpoint({
|
|
635
|
+
options,
|
|
636
|
+
method: 'GET',
|
|
637
|
+
path: `/api/email/templates/${encodeURIComponent(options.templateKey)}`,
|
|
638
|
+
});
|
|
639
|
+
emit({ intent: 'email.get-template', result, isJson });
|
|
640
|
+
if (!result.ok) process.exitCode = 1;
|
|
641
|
+
return;
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
if (sub === 'preview') {
|
|
645
|
+
if (!requireOption(options.templateKey, 'email preview: --template-key is required')) return;
|
|
646
|
+
|
|
647
|
+
const context = parseJsonFlag(options.context, 'email preview: --context');
|
|
648
|
+
if (!context.ok) return;
|
|
649
|
+
|
|
650
|
+
const path = `/api/email/templates/${encodeURIComponent(options.templateKey)}/preview`;
|
|
651
|
+
const result = context.value === null
|
|
652
|
+
? await callEndpoint({ options, method: 'GET', path })
|
|
653
|
+
: await callEndpoint({
|
|
654
|
+
options,
|
|
655
|
+
method: 'POST',
|
|
656
|
+
path,
|
|
657
|
+
body: { context: context.value },
|
|
658
|
+
});
|
|
659
|
+
|
|
660
|
+
emitText({ intent: 'email.preview', result, isJson });
|
|
661
|
+
if (!result.ok) process.exitCode = 1;
|
|
662
|
+
return;
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
if (sub === 'logs') {
|
|
666
|
+
const query = {};
|
|
667
|
+
if (options.ownerId) query.owner_id = options.ownerId;
|
|
668
|
+
if (options.userId) query.user_id = options.userId;
|
|
669
|
+
if (options.limit) query.limit = options.limit;
|
|
670
|
+
if (options.offset) query.offset = options.offset;
|
|
671
|
+
const result = await callEndpoint({ options, method: 'GET', path: '/api/email/logs', query });
|
|
672
|
+
emit({ intent: 'email.logs', result, isJson });
|
|
673
|
+
if (!result.ok) process.exitCode = 1;
|
|
674
|
+
return;
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
if (sub === 'list-templates') {
|
|
678
|
+
const result = await callEndpoint({ options, method: 'GET', path: '/api/email/templates' });
|
|
679
|
+
emit({ intent: 'email.list-templates', result, isJson });
|
|
680
|
+
if (!result.ok) process.exitCode = 1;
|
|
681
|
+
return;
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
if (sub === 'create-template') {
|
|
685
|
+
if (!requireOption(options.templateKey, 'email create-template: --template-key is required')) return;
|
|
686
|
+
if (!requireOption(options.name, 'email create-template: --name is required')) return;
|
|
687
|
+
if (!requireOption(options.subject, 'email create-template: --subject is required')) return;
|
|
688
|
+
if (!requireOption(options.htmlBody, 'email create-template: --html-body is required')) return;
|
|
689
|
+
|
|
690
|
+
const variables = parseJsonFlag(options.variables, 'email create-template: --variables');
|
|
691
|
+
if (!variables.ok) return;
|
|
692
|
+
const sampleContext = parseJsonFlag(options.sampleContext, 'email create-template: --sample-context');
|
|
693
|
+
if (!sampleContext.ok) return;
|
|
694
|
+
const isActive = parseBooleanFlag(options.isActive, 'email create-template: --is-active');
|
|
695
|
+
if (!isActive.ok) return;
|
|
696
|
+
|
|
697
|
+
const body = {
|
|
698
|
+
template_key: options.templateKey,
|
|
699
|
+
name: options.name,
|
|
700
|
+
subject: options.subject,
|
|
701
|
+
html_body: options.htmlBody,
|
|
702
|
+
};
|
|
703
|
+
assignIfDefined(body, 'description', options.description);
|
|
704
|
+
assignIfDefined(body, 'text_body', options.textBody);
|
|
705
|
+
assignIfDefined(body, 'from_email', options.fromEmail);
|
|
706
|
+
assignIfDefined(body, 'from_name', options.fromName);
|
|
707
|
+
assignIfDefined(body, 'reply_to', options.replyTo);
|
|
708
|
+
assignIfDefined(body, 'variables', variables.value);
|
|
709
|
+
assignIfDefined(body, 'sample_context', sampleContext.value);
|
|
710
|
+
assignIfDefined(body, 'is_active', isActive.value);
|
|
711
|
+
assignIfDefined(body, 'category', options.category);
|
|
712
|
+
|
|
713
|
+
const result = await callEndpoint({ options, method: 'POST', path: '/api/email/templates', body });
|
|
714
|
+
emit({ intent: 'email.create-template', result, isJson });
|
|
715
|
+
if (!result.ok) process.exitCode = 1;
|
|
716
|
+
return;
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
if (sub === 'update-template') {
|
|
720
|
+
if (!requireOption(options.templateKey, 'email update-template: --template-key is required')) return;
|
|
721
|
+
|
|
722
|
+
const variables = parseJsonFlag(options.variables, 'email update-template: --variables');
|
|
723
|
+
if (!variables.ok) return;
|
|
724
|
+
const sampleContext = parseJsonFlag(options.sampleContext, 'email update-template: --sample-context');
|
|
725
|
+
if (!sampleContext.ok) return;
|
|
726
|
+
const isActive = parseBooleanFlag(options.isActive, 'email update-template: --is-active');
|
|
727
|
+
if (!isActive.ok) return;
|
|
728
|
+
|
|
729
|
+
const body = {};
|
|
730
|
+
assignIfDefined(body, 'name', options.name);
|
|
731
|
+
assignIfDefined(body, 'description', options.description);
|
|
732
|
+
assignIfDefined(body, 'subject', options.subject);
|
|
733
|
+
assignIfDefined(body, 'html_body', options.htmlBody);
|
|
734
|
+
assignIfDefined(body, 'text_body', options.textBody);
|
|
735
|
+
assignIfDefined(body, 'from_email', options.fromEmail);
|
|
736
|
+
assignIfDefined(body, 'from_name', options.fromName);
|
|
737
|
+
assignIfDefined(body, 'reply_to', options.replyTo);
|
|
738
|
+
assignIfDefined(body, 'variables', variables.value);
|
|
739
|
+
assignIfDefined(body, 'sample_context', sampleContext.value);
|
|
740
|
+
assignIfDefined(body, 'is_active', isActive.value);
|
|
741
|
+
assignIfDefined(body, 'category', options.category);
|
|
742
|
+
|
|
743
|
+
if (Object.keys(body).length === 0) {
|
|
744
|
+
console.error('email update-template: provide at least one field to update');
|
|
745
|
+
process.exitCode = 2;
|
|
746
|
+
return;
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
const result = await callEndpoint({
|
|
750
|
+
options,
|
|
751
|
+
method: 'PUT',
|
|
752
|
+
path: `/api/email/templates/${encodeURIComponent(options.templateKey)}`,
|
|
753
|
+
body,
|
|
754
|
+
});
|
|
755
|
+
emit({ intent: 'email.update-template', result, isJson });
|
|
756
|
+
if (!result.ok) process.exitCode = 1;
|
|
757
|
+
return;
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
if (sub === 'get-override') {
|
|
761
|
+
if (!requireOption(options.templateKey, 'email get-override: --template-key is required')) return;
|
|
762
|
+
const result = await callEndpoint({
|
|
763
|
+
options,
|
|
764
|
+
method: 'GET',
|
|
765
|
+
path: `/api/email/templates/${encodeURIComponent(options.templateKey)}/override`,
|
|
766
|
+
});
|
|
767
|
+
emit({ intent: 'email.get-override', result, isJson });
|
|
768
|
+
if (!result.ok) process.exitCode = 1;
|
|
769
|
+
return;
|
|
770
|
+
}
|
|
771
|
+
|
|
772
|
+
if (sub === 'set-override') {
|
|
773
|
+
if (!requireOption(options.templateKey, 'email set-override: --template-key is required')) return;
|
|
774
|
+
const body = {};
|
|
775
|
+
assignIfDefined(body, 'subject_override', options.subjectOverride);
|
|
776
|
+
assignIfDefined(body, 'body_override', options.bodyOverride);
|
|
777
|
+
if (Object.keys(body).length === 0) {
|
|
778
|
+
console.error('email set-override: provide --subject-override and/or --body-override');
|
|
779
|
+
process.exitCode = 2;
|
|
780
|
+
return;
|
|
781
|
+
}
|
|
782
|
+
const result = await callEndpoint({
|
|
783
|
+
options,
|
|
784
|
+
method: 'PUT',
|
|
785
|
+
path: `/api/email/templates/${encodeURIComponent(options.templateKey)}/override`,
|
|
786
|
+
body,
|
|
787
|
+
});
|
|
788
|
+
emit({ intent: 'email.set-override', result, isJson });
|
|
789
|
+
if (!result.ok) process.exitCode = 1;
|
|
790
|
+
return;
|
|
791
|
+
}
|
|
792
|
+
|
|
793
|
+
if (sub === 'clear-override') {
|
|
794
|
+
if (!requireOption(options.templateKey, 'email clear-override: --template-key is required')) return;
|
|
795
|
+
const result = await callEndpoint({
|
|
796
|
+
options,
|
|
797
|
+
method: 'DELETE',
|
|
798
|
+
path: `/api/email/templates/${encodeURIComponent(options.templateKey)}/override`,
|
|
799
|
+
});
|
|
800
|
+
emit({ intent: 'email.clear-override', result, isJson });
|
|
801
|
+
if (!result.ok) process.exitCode = 1;
|
|
802
|
+
return;
|
|
803
|
+
}
|
|
804
|
+
|
|
805
|
+
console.error(
|
|
806
|
+
'email: unknown subcommand. Supported: send, get-template, preview, logs, list-templates, create-template, update-template, get-override, set-override, clear-override'
|
|
807
|
+
);
|
|
808
|
+
process.exitCode = 2;
|
|
809
|
+
} catch (err) {
|
|
810
|
+
emitAuthError(`email.${sub || 'unknown'}`, err, isJson);
|
|
811
|
+
process.exitCode = 1;
|
|
812
|
+
}
|
|
813
|
+
}
|
|
814
|
+
|
|
815
|
+
async function policyHandler({ options }) {
|
|
816
|
+
const { callEndpoint, emit, emitAuthError } = loadDomainCli();
|
|
817
|
+
const isJson = Boolean(options.json);
|
|
818
|
+
const sub = options.subcommand;
|
|
819
|
+
try {
|
|
820
|
+
if (sub === 'list') {
|
|
821
|
+
const query = {};
|
|
822
|
+
if (options.isActive !== null && options.isActive !== undefined) query.is_active = options.isActive;
|
|
823
|
+
if (options.limit) query.limit = options.limit;
|
|
824
|
+
const result = await callEndpoint({ options, method: 'GET', path: '/api/policies', query });
|
|
825
|
+
emit({ intent: 'policy.list', result, isJson });
|
|
826
|
+
if (!result.ok) process.exitCode = 1;
|
|
827
|
+
return;
|
|
828
|
+
}
|
|
829
|
+
if (sub === 'create') {
|
|
830
|
+
if (!options.name || !options.effect) {
|
|
831
|
+
console.error('policy create: --name and --effect are required');
|
|
832
|
+
process.exitCode = 2;
|
|
833
|
+
return;
|
|
834
|
+
}
|
|
835
|
+
let conditions = {};
|
|
836
|
+
if (options.conditions) {
|
|
837
|
+
try { conditions = JSON.parse(options.conditions); }
|
|
838
|
+
catch { console.error('policy create: --conditions must be valid JSON'); process.exitCode = 2; return; }
|
|
839
|
+
}
|
|
840
|
+
const body = {
|
|
841
|
+
name: options.name,
|
|
842
|
+
effect: options.effect,
|
|
843
|
+
conditions,
|
|
844
|
+
description: options.description || null,
|
|
845
|
+
priority: options.priority || 0,
|
|
846
|
+
};
|
|
847
|
+
const result = await callEndpoint({ options, method: 'POST', path: '/api/policies', body });
|
|
848
|
+
emit({ intent: 'policy.create', result, isJson });
|
|
849
|
+
if (!result.ok) process.exitCode = 1;
|
|
850
|
+
return;
|
|
851
|
+
}
|
|
852
|
+
if (sub === 'delete') {
|
|
853
|
+
if (!options.id) {
|
|
854
|
+
console.error('policy delete: --id is required');
|
|
855
|
+
process.exitCode = 2;
|
|
856
|
+
return;
|
|
857
|
+
}
|
|
858
|
+
const result = await callEndpoint({ options, method: 'DELETE', path: `/api/policies/${encodeURIComponent(options.id)}` });
|
|
859
|
+
emit({ intent: 'policy.delete', result, isJson });
|
|
860
|
+
if (!result.ok) process.exitCode = 1;
|
|
861
|
+
return;
|
|
862
|
+
}
|
|
863
|
+
console.error('policy: unknown subcommand. Supported: list, create, delete');
|
|
864
|
+
process.exitCode = 2;
|
|
865
|
+
} catch (err) {
|
|
866
|
+
emitAuthError(`policy.${sub || 'unknown'}`, err, isJson);
|
|
867
|
+
process.exitCode = 1;
|
|
868
|
+
}
|
|
869
|
+
}
|
|
870
|
+
|
|
871
|
+
async function webhookHandler({ options }) {
|
|
872
|
+
const { callEndpoint, emit, emitAuthError } = loadDomainCli();
|
|
873
|
+
const isJson = Boolean(options.json);
|
|
874
|
+
const sub = options.subcommand;
|
|
875
|
+
try {
|
|
876
|
+
if (sub === 'list') {
|
|
877
|
+
const result = await callEndpoint({ options, method: 'GET', path: '/api/webhooks' });
|
|
878
|
+
emit({ intent: 'webhook.list', result, isJson });
|
|
879
|
+
if (!result.ok) process.exitCode = 1;
|
|
880
|
+
return;
|
|
881
|
+
}
|
|
882
|
+
if (sub === 'register') {
|
|
883
|
+
if (!options.url || !options.events) {
|
|
884
|
+
console.error('webhook register: --url and --events (comma-separated) are required');
|
|
885
|
+
process.exitCode = 2;
|
|
886
|
+
return;
|
|
887
|
+
}
|
|
888
|
+
const events = String(options.events).split(',').map((e) => e.trim()).filter(Boolean);
|
|
889
|
+
const body = { url: options.url, events };
|
|
890
|
+
const result = await callEndpoint({ options, method: 'POST', path: '/api/webhooks', body });
|
|
891
|
+
emit({ intent: 'webhook.register', result, isJson });
|
|
892
|
+
if (!result.ok) process.exitCode = 1;
|
|
893
|
+
return;
|
|
894
|
+
}
|
|
895
|
+
console.error('webhook: unknown subcommand. Supported: list, register');
|
|
896
|
+
process.exitCode = 2;
|
|
897
|
+
} catch (err) {
|
|
898
|
+
emitAuthError(`webhook.${sub || 'unknown'}`, err, isJson);
|
|
899
|
+
process.exitCode = 1;
|
|
900
|
+
}
|
|
901
|
+
}
|
|
902
|
+
|
|
903
|
+
async function issueReportsHandler({ options }) {
|
|
904
|
+
const { callEndpoint, emit, emitAuthError } = loadDomainCli();
|
|
905
|
+
const isJson = Boolean(options.json);
|
|
906
|
+
const sub = options.subcommand;
|
|
907
|
+
if (sub !== 'list-mine') {
|
|
908
|
+
console.error('issue-reports: unknown subcommand. Supported: list-mine');
|
|
909
|
+
process.exitCode = 2;
|
|
910
|
+
return;
|
|
911
|
+
}
|
|
912
|
+
try {
|
|
913
|
+
const query = {};
|
|
914
|
+
if (options.status) query.status = options.status;
|
|
915
|
+
if (options.limit) query.limit = options.limit;
|
|
916
|
+
if (options.offset) query.offset = options.offset;
|
|
917
|
+
const result = await callEndpoint({ options, method: 'GET', path: '/api/v1/issue-reports', query });
|
|
918
|
+
emit({ intent: 'issue-reports.list-mine', result, isJson });
|
|
919
|
+
if (!result.ok) process.exitCode = 1;
|
|
920
|
+
} catch (err) {
|
|
921
|
+
emitAuthError('issue-reports.list-mine', err, isJson);
|
|
922
|
+
process.exitCode = 1;
|
|
923
|
+
}
|
|
924
|
+
}
|
|
925
|
+
|
|
420
926
|
module.exports = { createHandlers };
|