@velaro/cli 1.2.0 → 1.4.9

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.
Files changed (98) hide show
  1. package/README.md +161 -138
  2. package/bin/velaro.js +177 -62
  3. package/lib/api.js +91 -52
  4. package/lib/api.test.js +46 -0
  5. package/lib/banner.js +76 -0
  6. package/lib/commands/activity.js +133 -0
  7. package/lib/commands/acuity.js +66 -0
  8. package/lib/commands/agent.js +204 -50
  9. package/lib/commands/ai-config.js +193 -0
  10. package/lib/commands/ai-models.js +159 -0
  11. package/lib/commands/appointments.js +198 -0
  12. package/lib/commands/article.js +668 -388
  13. package/lib/commands/automation-draft.js +134 -0
  14. package/lib/commands/avatar.js +75 -0
  15. package/lib/commands/bigcommerce.js +50 -0
  16. package/lib/commands/billing-contacts.js +62 -0
  17. package/lib/commands/billing-email-preference.js +64 -0
  18. package/lib/commands/billing-subscription.js +265 -0
  19. package/lib/commands/billing.js +138 -0
  20. package/lib/commands/bot.js +141 -137
  21. package/lib/commands/bundle.js +168 -0
  22. package/lib/commands/calendly.js +62 -0
  23. package/lib/commands/callback.js +125 -0
  24. package/lib/commands/callrail.js +88 -0
  25. package/lib/commands/campaigns.js +44 -0
  26. package/lib/commands/case.js +102 -0
  27. package/lib/commands/check.js +163 -163
  28. package/lib/commands/compliance.js +229 -0
  29. package/lib/commands/conversation-efficiency.js +178 -0
  30. package/lib/commands/copilotstudio.js +114 -0
  31. package/lib/commands/coupon-grant.js +192 -0
  32. package/lib/commands/db.js +101 -0
  33. package/lib/commands/deployment.js +107 -107
  34. package/lib/commands/diagnostics.js +298 -0
  35. package/lib/commands/email-campaign.js +47 -0
  36. package/lib/commands/email-inbox.js +88 -0
  37. package/lib/commands/entitlement.js +176 -0
  38. package/lib/commands/env.js +45 -45
  39. package/lib/commands/feature-discovery.js +40 -0
  40. package/lib/commands/focus.js +278 -0
  41. package/lib/commands/index.js +38 -5
  42. package/lib/commands/ingest.js +31 -31
  43. package/lib/commands/inline-widget-config.js +126 -0
  44. package/lib/commands/integration.js +93 -0
  45. package/lib/commands/kb.js +450 -309
  46. package/lib/commands/login.js +86 -86
  47. package/lib/commands/logs.js +680 -0
  48. package/lib/commands/magento.js +210 -0
  49. package/lib/commands/mcp-key.js +188 -159
  50. package/lib/commands/migrate.js +134 -0
  51. package/lib/commands/migration-status.js +66 -0
  52. package/lib/commands/monday.js +137 -0
  53. package/lib/commands/netsuite.js +87 -0
  54. package/lib/commands/notifications.js +63 -0
  55. package/lib/commands/notion.js +70 -0
  56. package/lib/commands/ops.js +267 -173
  57. package/lib/commands/payment-recovery.js +170 -0
  58. package/lib/commands/pickup.js +172 -0
  59. package/lib/commands/pricing.js +132 -0
  60. package/lib/commands/product.js +55 -0
  61. package/lib/commands/recruiting.js +374 -0
  62. package/lib/commands/report.js +462 -0
  63. package/lib/commands/routing.js +304 -0
  64. package/lib/commands/rule.js +85 -85
  65. package/lib/commands/sharepoint.js +167 -0
  66. package/lib/commands/site-provision.js +68 -0
  67. package/lib/commands/site.js +62 -62
  68. package/lib/commands/sitesync.js +158 -0
  69. package/lib/commands/slack.js +64 -0
  70. package/lib/commands/squarespace.js +108 -0
  71. package/lib/commands/status.js +24 -24
  72. package/lib/commands/subscription.js +43 -0
  73. package/lib/commands/support.js +128 -0
  74. package/lib/commands/survey.js +216 -0
  75. package/lib/commands/team.js +144 -144
  76. package/lib/commands/teams-phone.js +131 -0
  77. package/lib/commands/teams.js +106 -0
  78. package/lib/commands/telephony.js +99 -0
  79. package/lib/commands/update.js +47 -47
  80. package/lib/commands/webflow.js +128 -0
  81. package/lib/commands/whoami.js +25 -22
  82. package/lib/commands/widget-container.js +152 -0
  83. package/lib/commands/woocommerce.js +240 -0
  84. package/lib/commands/workflow.js +233 -98
  85. package/lib/config.js +85 -83
  86. package/lib/kb-screenshot.js +320 -0
  87. package/lib/migrations/amscro.json +72 -0
  88. package/lib/migrations/azenta.json +68 -0
  89. package/lib/migrations/bluefire.json +49 -0
  90. package/lib/migrations/donaldson.json +75 -0
  91. package/lib/oauth.js +149 -135
  92. package/lib/run.js +21 -16
  93. package/lib/sharepoint-auth.js +138 -0
  94. package/lib/subscription.js +41 -39
  95. package/lib/track.js +35 -35
  96. package/lib/update-check.js +64 -64
  97. package/package.json +34 -19
  98. package/scripts/postinstall.js +12 -0
@@ -0,0 +1,68 @@
1
+ // velaro site-provision: staff-only site provisioning, including cross-environment
2
+ // parity provisioning (forcing a specific SiteId to match another environment).
3
+ import { messagingPost, getCredentials } from '../api.js';
4
+ import { runCommand } from '../run.js';
5
+
6
+ function isVelaroAdmin(creds) {
7
+ return creds.siteId === 1032;
8
+ }
9
+
10
+ export const siteProvisionCommand = {
11
+ command: 'site-provision',
12
+ describe: 'Provision a new Velaro site (Velaro staff only)',
13
+ builder: (y) =>
14
+ y
15
+ .option('company', { type: 'string', demandOption: true, describe: 'Company name' })
16
+ .option('admin-email', { type: 'string', demandOption: true, describe: 'Admin user email' })
17
+ .option('admin-first-name', { type: 'string', describe: 'Admin first name' })
18
+ .option('admin-last-name', { type: 'string', describe: 'Admin last name' })
19
+ .option('tier', { type: 'string', default: 'Starter', describe: 'Starter, Professional, Business, Enterprise, or Trial' })
20
+ .option('site-id', { type: 'number', describe: 'Force a specific SiteId (>= 100000), used for cross-environment parity' })
21
+ .option('package-id', { type: 'number', describe: 'SubscriptionPackage Id to provision from' })
22
+ .option('package-version-id', { type: 'number', describe: 'Specific PackageVersion Id (defaults to latest published for package-id)' })
23
+ .option('all-features-on', { type: 'boolean', default: false, describe: 'Everything ON subscription, internal/demo use only' })
24
+ .option('trial', { type: 'boolean', default: false, describe: 'Provision as a trial' })
25
+ .option('trial-days', { type: 'number', default: 7 })
26
+ .option('is-velaro-internal-site', { type: 'boolean', describe: 'Mark as a Velaro-internal site' })
27
+ .option('has-demo-data', { type: 'boolean', describe: 'Mark as having demo data' })
28
+ .option('billing-source', { type: 'string', describe: 'e.g. "direct"' }),
29
+ handler: runCommand(async (argv) => {
30
+ const creds = await getCredentials();
31
+ if (!isVelaroAdmin(creds)) {
32
+ console.error('This command is only available to Velaro staff.');
33
+ process.exit(1);
34
+ }
35
+
36
+ const body = {
37
+ companyName: argv.company,
38
+ adminEmail: argv['admin-email'],
39
+ adminFirstName: argv['admin-first-name'],
40
+ adminLastName: argv['admin-last-name'],
41
+ subscriptionTier: argv.tier,
42
+ explicitSiteId: argv['site-id'],
43
+ packageId: argv['package-id'],
44
+ packageVersionId: argv['package-version-id'],
45
+ allFeaturesOn: argv['all-features-on'],
46
+ isTrial: argv.trial,
47
+ trialDurationDays: argv['trial-days'],
48
+ isVelaroInternalSite: argv['is-velaro-internal-site'],
49
+ hasDemoData: argv['has-demo-data'],
50
+ billingSource: argv['billing-source'],
51
+ };
52
+
53
+ console.log(`\nProvisioning site${argv['site-id'] ? ` ${argv['site-id']}` : ''} for "${argv.company}" on ${creds.env}...`);
54
+ const result = await messagingPost('/SuperAdmin/ProvisionSite', body);
55
+
56
+ if (!result.success) {
57
+ console.error(`\x1b[31mFAILED: ${result.error}\x1b[0m`);
58
+ if (result.existingSiteId) console.error(` Existing site: ${result.existingSiteId}`);
59
+ process.exit(1);
60
+ }
61
+
62
+ console.log(`\x1b[32mOK: site ${result.siteId} provisioned\x1b[0m`);
63
+ console.log(` Company: ${result.companyName}`);
64
+ console.log(` Admin: ${result.adminEmail}`);
65
+ console.log(` Team: ${result.defaultTeamId}`);
66
+ if (result.temporaryPassword) console.log(` Temp password: ${result.temporaryPassword}`);
67
+ }),
68
+ };
@@ -1,62 +1,62 @@
1
- import { get } from '../api.js';
2
- import { runCommand } from '../run.js';
3
-
4
- const FEATURE_MAP = {
5
- enableWeb: 'Web Chat',
6
- enableSms: 'SMS',
7
- enableEmail: 'Email',
8
- enableWhatsapp: 'WhatsApp',
9
- enableIvr: 'IVR / Voice',
10
- enableFacebook: 'Facebook Messenger',
11
- enableInstagram: 'Instagram',
12
- enableAI: 'AI Bots',
13
- enableKnowledgeBase: 'Knowledge Base',
14
- enableAutomation: 'Workflows',
15
- enableWorkflowRules: 'Routing Rules',
16
- enableChatTranslations: 'Chat Translation',
17
- enableVideoChat: 'Video Chat',
18
- enableTicketing: 'Ticketing',
19
- enableAgentDashboard: 'Agent Dashboard',
20
- enableMcpApi: 'MCP API Gateway',
21
- enableOutboundCampaigns: 'Email Campaigns',
22
- enableRcs: 'RCS Messaging',
23
- enablePageWidgets: 'Page Widgets',
24
- enableVisitorTracking: 'Visitor Tracking',
25
- };
26
-
27
- const STATUS_LABEL = { Available: 'online', Away: 'away', Offline: 'offline' };
28
-
29
- export const siteCommand = {
30
- command: 'site',
31
- describe: 'Show site overview: active features and agent availability',
32
- handler: runCommand(async () => {
33
- const [sub, agents] = await Promise.all([
34
- get('/Subscription'),
35
- get('/Users/List'),
36
- ]);
37
-
38
- // Active features — friendly names only, no internal flag names
39
- const active = Object.entries(FEATURE_MAP)
40
- .filter(([key]) => sub[key] === true)
41
- .map(([, label]) => label);
42
-
43
- console.log('\nActive features:');
44
- if (active.length) {
45
- console.log(' ' + active.join(' · '));
46
- } else {
47
- console.log(' (none)');
48
- }
49
-
50
- if (sub.maxCreatedUsers) {
51
- console.log(`\nAgent seats: ${sub.maxCreatedUsers} licensed`);
52
- }
53
-
54
- // Agent availability summary
55
- const counts = { online: 0, away: 0, offline: 0 };
56
- for (const a of agents) {
57
- const key = STATUS_LABEL[a.status] ?? 'offline';
58
- counts[key]++;
59
- }
60
- console.log(`Agent status: ${counts.online} online · ${counts.away} away · ${counts.offline} offline\n`);
61
- }),
62
- };
1
+ import { get, messagingGet } from '../api.js';
2
+ import { runCommand } from '../run.js';
3
+
4
+ const FEATURE_MAP = {
5
+ enableWeb: 'Web Chat',
6
+ enableSms: 'SMS',
7
+ enableEmail: 'Email',
8
+ enableWhatsapp: 'WhatsApp',
9
+ enableIvr: 'IVR / Voice',
10
+ enableFacebook: 'Facebook Messenger',
11
+ enableInstagram: 'Instagram',
12
+ enableAI: 'AI Bots',
13
+ enableKnowledgeBase: 'Knowledge Base',
14
+ enableAutomation: 'Workflows',
15
+ enableWorkflowRules: 'Routing Rules',
16
+ enableChatTranslations: 'Chat Translation',
17
+ enableVideoChat: 'Video Chat',
18
+ enableTicketing: 'Ticketing',
19
+ enableAgentDashboard: 'Agent Dashboard',
20
+ enableMcpApi: 'MCP API Gateway',
21
+ enableOutboundCampaigns: 'Email Campaigns',
22
+ enableRcs: 'RCS Messaging',
23
+ enablePageWidgets: 'Page Widgets',
24
+ enableVisitorTracking: 'Visitor Tracking',
25
+ };
26
+
27
+ const STATUS_LABEL = { Available: 'online', Away: 'away', Offline: 'offline' };
28
+
29
+ export const siteCommand = {
30
+ command: 'site',
31
+ describe: 'Show site overview: active features and agent availability',
32
+ handler: runCommand(async () => {
33
+ const [sub, agents] = await Promise.all([
34
+ messagingGet('/Subscription'),
35
+ get('/Users/List'),
36
+ ]);
37
+
38
+ // Active features — friendly names only, no internal flag names
39
+ const active = Object.entries(FEATURE_MAP)
40
+ .filter(([key]) => sub[key] === true)
41
+ .map(([, label]) => label);
42
+
43
+ console.log('\nActive features:');
44
+ if (active.length) {
45
+ console.log(' ' + active.join(' · '));
46
+ } else {
47
+ console.log(' (none)');
48
+ }
49
+
50
+ if (sub.maxCreatedUsers) {
51
+ console.log(`\nAgent seats: ${sub.maxCreatedUsers} licensed`);
52
+ }
53
+
54
+ // Agent availability summary
55
+ const counts = { online: 0, away: 0, offline: 0 };
56
+ for (const a of agents) {
57
+ const key = STATUS_LABEL[a.status] ?? 'offline';
58
+ counts[key]++;
59
+ }
60
+ console.log(`Agent status: ${counts.online} online · ${counts.away} away · ${counts.offline} offline\n`);
61
+ }),
62
+ };
@@ -0,0 +1,158 @@
1
+ // velaro sitesync: copies AI configs, bot profiles, workflows, deployments, and site
2
+ // settings from a source site to a target site (Velaro staff only). The blob-push/blob-import
3
+ // pair is the cross-environment path (shared blob storage between staging and prod), so a
4
+ // bundle exported on one environment can be applied on another without direct cross-DB access.
5
+ import { get, post, getCredentials } from '../api.js';
6
+ import { runCommand } from '../run.js';
7
+
8
+ function isVelaroAdmin(creds) {
9
+ return creds.siteId === 1032;
10
+ }
11
+
12
+ function requireStaff(creds) {
13
+ if (!isVelaroAdmin(creds)) {
14
+ console.error('This command is only available to Velaro staff.');
15
+ process.exit(1);
16
+ }
17
+ }
18
+
19
+ function printResult(result) {
20
+ if (result.errors?.length) {
21
+ console.log('\x1b[31mErrors:\x1b[0m');
22
+ for (const e of result.errors) console.log(` ${e}`);
23
+ }
24
+ if (result.warnings?.length) {
25
+ console.log('\x1b[33mWarnings:\x1b[0m');
26
+ for (const w of result.warnings) console.log(` ${w}`);
27
+ }
28
+ if (result.plan?.length) {
29
+ console.log(`\nPlan (${result.isDryRun ? 'dry run' : 'applied'}):`);
30
+ for (const item of result.plan) {
31
+ console.log(` [${item.action}] ${item.resource}: ${item.name}${item.note ? ` (${item.note})` : ''}`);
32
+ }
33
+ }
34
+ console.log(`\nApplied: ${result.applied?.length ?? 0} Skipped: ${result.skipped?.length ?? 0} Errors: ${result.errors?.length ?? 0}`);
35
+ console.log(result.success ? '\x1b[32mSuccess\x1b[0m' : '\x1b[31mCompleted with errors\x1b[0m');
36
+ }
37
+
38
+ const previewCommand = {
39
+ command: 'preview',
40
+ describe: 'Dry-run preview of a site sync, no writes',
41
+ builder: (y) =>
42
+ y
43
+ .option('source', { type: 'number', demandOption: true, describe: 'Source site ID' })
44
+ .option('target', { type: 'number', demandOption: true, describe: 'Target site ID' })
45
+ .option('conflict-mode', { type: 'string', default: 'skip', choices: ['skip', 'overwrite', 'rename'] }),
46
+ handler: runCommand(async (argv) => {
47
+ const creds = await getCredentials();
48
+ requireStaff(creds);
49
+ const result = await get(`/SiteSync/preview?sourceSiteId=${argv.source}&targetSiteId=${argv.target}&conflictMode=${argv['conflict-mode']}`);
50
+ printResult(result);
51
+ }),
52
+ };
53
+
54
+ const blobPushCommand = {
55
+ command: 'blob-push',
56
+ describe: 'Export a site config bundle and push it to shared blob storage (readable from any environment)',
57
+ builder: (y) =>
58
+ y
59
+ .option('source', { type: 'number', demandOption: true, describe: 'Source site ID' })
60
+ .option('label', { type: 'string', describe: 'Label for this bundle' }),
61
+ handler: runCommand(async (argv) => {
62
+ const creds = await getCredentials();
63
+ requireStaff(creds);
64
+ const result = await post('/SiteSync/blob/push', { sourceSiteId: argv.source, label: argv.label });
65
+ if (!result.success) {
66
+ console.error(`\x1b[31mFAILED: ${result.error}\x1b[0m`);
67
+ process.exit(1);
68
+ }
69
+ console.log(`\x1b[32mOK: pushed\x1b[0m`);
70
+ console.log(` Blob key: ${result.blobKey}`);
71
+ console.log(`\nOn the target environment, run:`);
72
+ console.log(` velaro env <target-env>`);
73
+ console.log(` velaro sitesync blob-import --blob-key ${result.blobKey} --target <targetSiteId> --dry-run`);
74
+ }),
75
+ };
76
+
77
+ const blobListCommand = {
78
+ command: 'blob-list',
79
+ describe: 'List bundles currently in shared blob storage, newest first',
80
+ handler: runCommand(async () => {
81
+ const creds = await getCredentials();
82
+ requireStaff(creds);
83
+ const entries = await get('/SiteSync/blob/list');
84
+ if (!entries?.length) {
85
+ console.log('No bundles in blob storage.');
86
+ return;
87
+ }
88
+ for (const e of entries) {
89
+ console.log(`${e.blobKey}`);
90
+ console.log(` label: ${e.label ?? '(none)'} source site: ${e.sourceSiteId ?? '?'} pushed by: ${e.pushedBy ?? 'unknown'} at: ${e.exportedAt ?? '?'}`);
91
+ }
92
+ }),
93
+ };
94
+
95
+ const blobImportCommand = {
96
+ command: 'blob-import',
97
+ describe: 'Pull a bundle from blob by key and apply (or preview) it to a target site',
98
+ builder: (y) =>
99
+ y
100
+ .option('blob-key', { type: 'string', demandOption: true })
101
+ .option('target', { type: 'number', demandOption: true, describe: 'Target site ID' })
102
+ .option('conflict-mode', { type: 'string', default: 'skip', choices: ['skip', 'overwrite', 'rename'] })
103
+ .option('dry-run', { type: 'boolean', default: false }),
104
+ handler: runCommand(async (argv) => {
105
+ const creds = await getCredentials();
106
+ requireStaff(creds);
107
+ const result = await post('/SiteSync/blob/import', {
108
+ blobKey: argv['blob-key'],
109
+ targetSiteId: argv.target,
110
+ conflictMode: argv['conflict-mode'],
111
+ dryRun: argv['dry-run'],
112
+ });
113
+ printResult(result);
114
+ if (argv['dry-run'] && result.success) {
115
+ console.log(`\nRe-run without --dry-run to apply.`);
116
+ }
117
+ }),
118
+ };
119
+
120
+ const applyCommand = {
121
+ command: 'apply',
122
+ describe: 'Export a bundle from a source site (same database) and apply it directly to a target site',
123
+ builder: (y) =>
124
+ y
125
+ .option('source', { type: 'number', demandOption: true, describe: 'Source site ID' })
126
+ .option('target', { type: 'number', demandOption: true, describe: 'Target site ID' })
127
+ .option('conflict-mode', { type: 'string', default: 'skip', choices: ['skip', 'overwrite', 'rename'] })
128
+ .option('dry-run', { type: 'boolean', default: false }),
129
+ handler: runCommand(async (argv) => {
130
+ const creds = await getCredentials();
131
+ requireStaff(creds);
132
+ const bundle = await get(`/SiteSync/export?sourceSiteId=${argv.source}`);
133
+ const result = await post('/SiteSync/apply', {
134
+ targetSiteId: argv.target,
135
+ conflictMode: argv['conflict-mode'],
136
+ dryRun: argv['dry-run'],
137
+ bundle,
138
+ });
139
+ printResult(result);
140
+ if (argv['dry-run'] && result.success) {
141
+ console.log(`\nRe-run without --dry-run to apply.`);
142
+ }
143
+ }),
144
+ };
145
+
146
+ export const siteSyncCommand = {
147
+ command: 'sitesync <subcommand>',
148
+ describe: 'Copy AI configs, bot profiles, workflows, deployments, and site settings between sites (Velaro staff only)',
149
+ builder: (yargs) =>
150
+ yargs
151
+ .command(previewCommand)
152
+ .command(applyCommand)
153
+ .command(blobPushCommand)
154
+ .command(blobListCommand)
155
+ .command(blobImportCommand)
156
+ .demandCommand(1, 'Specify a subcommand: preview, apply, blob-push, blob-list, blob-import'),
157
+ handler: () => {},
158
+ };
@@ -0,0 +1,64 @@
1
+ import { request } from '../api.js';
2
+
3
+ export const slackCommand = {
4
+ command: 'slack <subcommand>',
5
+ describe: 'Send messages to Slack channels',
6
+ builder: (yargs) =>
7
+ yargs
8
+ .command(
9
+ 'notify [message]',
10
+ 'Send a message to a Slack channel webhook',
11
+ (y) =>
12
+ y
13
+ .positional('message', { type: 'string', describe: 'Message to send' })
14
+ .option('title', { type: 'string', describe: 'Message title', alias: 't' })
15
+ .option('id', { type: 'number', describe: 'Webhook config ID (omit to list)', alias: 'i' }),
16
+ async (argv) => {
17
+ const webhooks = await request('GET', '/SlackNotification');
18
+
19
+ if (!Array.isArray(webhooks) || !webhooks.length) {
20
+ console.log('No Slack webhooks configured. Go to Integrations → Slack to add one.');
21
+ return;
22
+ }
23
+
24
+ if (!argv.id) {
25
+ console.log('Available Slack webhooks:');
26
+ for (const w of webhooks) {
27
+ console.log(` [${w.id}] ${w.name}${w.isEnabled ? '' : ' (disabled)'}`);
28
+ }
29
+ console.log('\nRun with --id <number> to send a message.');
30
+ return;
31
+ }
32
+
33
+ const webhook = webhooks.find((w) => w.id === argv.id);
34
+ if (!webhook) { console.error(`No webhook with id ${argv.id}`); process.exit(1); }
35
+ if (!webhook.isEnabled) { console.error(`Webhook "${webhook.name}" is disabled.`); process.exit(1); }
36
+
37
+ const message = argv.message;
38
+ if (!message) { console.error('Message is required. Pass it as an argument: velaro slack notify "hello"'); process.exit(1); }
39
+
40
+ await request('POST', `/SlackNotification/Send/${argv.id}`, {
41
+ message,
42
+ title: argv.title || null,
43
+ });
44
+ console.log(`✓ Sent to Slack webhook "${webhook.name}"`);
45
+ }
46
+ )
47
+ .command(
48
+ 'list',
49
+ 'List configured Slack webhooks',
50
+ () => {},
51
+ async () => {
52
+ const webhooks = await request('GET', '/SlackNotification');
53
+ if (!Array.isArray(webhooks) || !webhooks.length) {
54
+ console.log('No Slack webhooks configured.');
55
+ return;
56
+ }
57
+ for (const w of webhooks) {
58
+ console.log(`[${w.id}] ${w.name} — ${w.isEnabled ? 'enabled' : 'disabled'}`);
59
+ }
60
+ }
61
+ )
62
+ .demandCommand(1, 'Specify a subcommand: notify, list'),
63
+ handler: () => {},
64
+ };
@@ -0,0 +1,108 @@
1
+ /**
2
+ * velaro squarespace, Squarespace Commerce integration management
3
+ *
4
+ * velaro squarespace status Show connection status for the current site
5
+ * velaro squarespace connect <apiKey> Connect a Squarespace store with a Commerce API Key
6
+ * velaro squarespace disconnect Disconnect the Squarespace store
7
+ * velaro squarespace test Test the current connection
8
+ *
9
+ * Read-only integration: Squarespace has no cart or checkout API, so there is no
10
+ * "order create" or "checkout" subcommand here by design.
11
+ */
12
+
13
+ import { request } from '../api.js';
14
+
15
+ const GRN = '\x1b[32m';
16
+ const RED = '\x1b[31m';
17
+ const DIM = '\x1b[2m';
18
+ const BOLD = '\x1b[1m';
19
+ const RST = '\x1b[0m';
20
+
21
+ async function statusHandler(_argv) {
22
+ const r = await request('GET', `/Squarespace/Config`);
23
+ if (!r || !r.connected) {
24
+ console.log('\nNo Squarespace connection found for this site.\n');
25
+ return;
26
+ }
27
+ console.log(`\n${BOLD}Squarespace Status${RST}\n`);
28
+ console.log(` Status: ${r.isEnabled ? `${GRN}connected${RST}` : `${RED}disabled${RST}`}`);
29
+ if (r.storeUrl) console.log(` Store URL: ${r.storeUrl}`);
30
+ console.log(` API Key: ${r.hasApiKey ? 'on file' : 'missing'}`);
31
+ if (r.updatedAt) console.log(` Updated: ${DIM}${new Date(r.updatedAt).toLocaleString()}${RST}`);
32
+ console.log('');
33
+ }
34
+
35
+ async function connectHandler(argv) {
36
+ if (!argv.apiKey) {
37
+ console.error(`${RED}x <apiKey> is required (velaro squarespace connect --apiKey <key> [--storeUrl <url>])${RST}`);
38
+ process.exitCode = 1;
39
+ return;
40
+ }
41
+ const r = await request('POST', `/Squarespace/Config`, {
42
+ apiKey: argv.apiKey,
43
+ storeUrl: argv.storeUrl || '',
44
+ });
45
+ if (r?.success) {
46
+ console.log(`${GRN}Squarespace connected.${RST}`);
47
+ } else {
48
+ console.error(`${RED}x ${r?.message || 'Failed to connect Squarespace.'}${RST}`);
49
+ process.exitCode = 1;
50
+ }
51
+ }
52
+
53
+ async function disconnectHandler(_argv) {
54
+ const cfg = await request('GET', `/Squarespace/Config`);
55
+ if (!cfg?.connected) {
56
+ console.log('Squarespace is not connected.');
57
+ return;
58
+ }
59
+ const r = await request('DELETE', `/Squarespace/Config/${cfg.id}`);
60
+ if (r?.success) {
61
+ console.log(`${GRN}Squarespace disconnected.${RST}`);
62
+ } else {
63
+ console.error(`${RED}x ${r?.message || 'Failed to disconnect.'}${RST}`);
64
+ process.exitCode = 1;
65
+ }
66
+ }
67
+
68
+ async function testHandler(_argv) {
69
+ const r = await request('POST', `/Squarespace/TestConnection`, {});
70
+ if (r?.success) {
71
+ console.log(`${GRN}${r.message}${RST}`);
72
+ } else {
73
+ console.error(`${RED}x ${r?.message || 'Connection test failed.'}${RST}`);
74
+ process.exitCode = 1;
75
+ }
76
+ }
77
+
78
+ export const squarespaceCommand = {
79
+ command: 'squarespace <subcommand>',
80
+ describe: 'Manage the Squarespace Commerce integration',
81
+ builder: (yy) =>
82
+ yy
83
+ .command({
84
+ command: 'status',
85
+ describe: 'Show Squarespace connection status',
86
+ handler: statusHandler,
87
+ })
88
+ .command({
89
+ command: 'connect',
90
+ describe: 'Connect a Squarespace store with a Commerce API Key',
91
+ builder: (y) => y
92
+ .option('apiKey', { describe: 'Squarespace Commerce API Key', type: 'string' })
93
+ .option('storeUrl', { describe: 'Squarespace store URL (optional, reference only)', type: 'string' }),
94
+ handler: connectHandler,
95
+ })
96
+ .command({
97
+ command: 'disconnect',
98
+ describe: 'Disconnect the Squarespace store',
99
+ handler: disconnectHandler,
100
+ })
101
+ .command({
102
+ command: 'test',
103
+ describe: 'Test the current Squarespace connection',
104
+ handler: testHandler,
105
+ })
106
+ .demandCommand(1, 'Specify a subcommand. Run "velaro squarespace --help" for options.'),
107
+ handler: () => {},
108
+ };
@@ -1,24 +1,24 @@
1
- import { readConfig, getActiveEnv, ENVS } from '../config.js';
2
-
3
- export const statusCommand = {
4
- command: 'status',
5
- describe: 'Check Velaro API health',
6
- handler: async () => {
7
- const cfg = readConfig();
8
- const env = getActiveEnv();
9
- const apiBase = cfg.envs?.[env]?.adminApiBase ?? ENVS[env].adminApiBase;
10
- process.stdout.write(`Checking ${apiBase}/Status ... `);
11
- try {
12
- const res = await fetch(`${apiBase}/Status`);
13
- if (res.ok) {
14
- console.log('OK');
15
- } else {
16
- console.log(`DEGRADED (HTTP ${res.status})`);
17
- process.exit(1);
18
- }
19
- } catch (err) {
20
- console.log(`UNREACHABLE: ${err.message}`);
21
- process.exit(1);
22
- }
23
- },
24
- };
1
+ import { readConfig, getActiveEnv, ENVS } from '../config.js';
2
+
3
+ export const statusCommand = {
4
+ command: 'status',
5
+ describe: 'Check Velaro API health',
6
+ handler: async () => {
7
+ const cfg = readConfig();
8
+ const env = getActiveEnv();
9
+ const apiBase = cfg.envs?.[env]?.adminApiBase ?? ENVS[env].adminApiBase;
10
+ process.stdout.write(`Checking ${apiBase}/Status ... `);
11
+ try {
12
+ const res = await fetch(`${apiBase}/Status`);
13
+ if (res.ok) {
14
+ console.log('OK');
15
+ } else {
16
+ console.log(`DEGRADED (HTTP ${res.status})`);
17
+ process.exit(1);
18
+ }
19
+ } catch (err) {
20
+ console.log(`UNREACHABLE: ${err.message}`);
21
+ process.exit(1);
22
+ }
23
+ },
24
+ };
@@ -0,0 +1,43 @@
1
+ // velaro subscription — apply/diff SubscriptionPackage packages on a site (superadmin only)
2
+ // Wraps SuperAdminController.ApplyPackageToSiteAsync / GetPackageDiffAsync in velaro-messaging,
3
+ // the same reflection-copy service the SuperAdmin browser UI uses -- never hand-toggles flags.
4
+ import { messagingPost, messagingGet } from '../api.js';
5
+
6
+ async function applyPackage(args) {
7
+ if (!args.site || !args.package) {
8
+ console.error('Usage: velaro subscription apply-package --site 100150 --package 3 [--version 3] [--autoUpgrade] [--resetOverrides]');
9
+ process.exit(1);
10
+ }
11
+ const body = {
12
+ PackageId: args.package,
13
+ VersionId: args.version ?? null,
14
+ AutoUpgrade: !!args.autoUpgrade,
15
+ ResetOverrides: !!args.resetOverrides,
16
+ };
17
+ const result = await messagingPost(`SuperAdmin/subscription/${args.site}/apply-package`, body);
18
+ console.log(JSON.stringify(result, null, 2));
19
+ }
20
+
21
+ async function packageDiff(args) {
22
+ if (!args.site) {
23
+ console.error('Usage: velaro subscription package-diff --site 100150');
24
+ process.exit(1);
25
+ }
26
+ const result = await messagingGet(`SuperAdmin/subscription/${args.site}/package-diff`);
27
+ console.log(JSON.stringify(result, null, 2));
28
+ }
29
+
30
+ export function subscriptionCommand(yargs) {
31
+ return yargs
32
+ .command('apply-package', 'Apply a SubscriptionPackage to a site (superadmin only) -- correct reflection-copy path, never hand-toggles flags', y => y
33
+ .option('site', { type: 'number', demandOption: true, desc: 'Target site ID' })
34
+ .option('package', { type: 'number', demandOption: true, desc: 'SubscriptionPackage.Id (e.g. 3 = Enterprise) -- verify by name in SuperAdmin first, IDs can drift between staging/prod' })
35
+ .option('version', { type: 'number', desc: 'Specific PackageVersion.Id (omit to use the package\'s latest published version)' })
36
+ .option('autoUpgrade', { type: 'boolean', desc: 'Auto-upgrade this site to future published versions of the package' })
37
+ .option('resetOverrides', { type: 'boolean', desc: 'Clear any existing per-site Subscription overrides before applying' })
38
+ , a => applyPackage(a))
39
+ .command('package-diff', 'Show what would change if the site\'s current package were re-applied (read-only)', y => y
40
+ .option('site', { type: 'number', demandOption: true, desc: 'Target site ID' })
41
+ , a => packageDiff(a))
42
+ .demandCommand(1, 'Specify a subcommand: apply-package | package-diff');
43
+ }