@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.
- package/README.md +161 -138
- package/bin/velaro.js +177 -62
- package/lib/api.js +91 -52
- package/lib/api.test.js +46 -0
- package/lib/banner.js +76 -0
- package/lib/commands/activity.js +133 -0
- package/lib/commands/acuity.js +66 -0
- package/lib/commands/agent.js +204 -50
- package/lib/commands/ai-config.js +193 -0
- package/lib/commands/ai-models.js +159 -0
- package/lib/commands/appointments.js +198 -0
- package/lib/commands/article.js +668 -388
- package/lib/commands/automation-draft.js +134 -0
- package/lib/commands/avatar.js +75 -0
- package/lib/commands/bigcommerce.js +50 -0
- package/lib/commands/billing-contacts.js +62 -0
- package/lib/commands/billing-email-preference.js +64 -0
- package/lib/commands/billing-subscription.js +265 -0
- package/lib/commands/billing.js +138 -0
- package/lib/commands/bot.js +141 -137
- package/lib/commands/bundle.js +168 -0
- package/lib/commands/calendly.js +62 -0
- package/lib/commands/callback.js +125 -0
- package/lib/commands/callrail.js +88 -0
- package/lib/commands/campaigns.js +44 -0
- package/lib/commands/case.js +102 -0
- package/lib/commands/check.js +163 -163
- package/lib/commands/compliance.js +229 -0
- package/lib/commands/conversation-efficiency.js +178 -0
- package/lib/commands/copilotstudio.js +114 -0
- package/lib/commands/coupon-grant.js +192 -0
- package/lib/commands/db.js +101 -0
- package/lib/commands/deployment.js +107 -107
- package/lib/commands/diagnostics.js +298 -0
- package/lib/commands/email-campaign.js +47 -0
- package/lib/commands/email-inbox.js +88 -0
- package/lib/commands/entitlement.js +176 -0
- package/lib/commands/env.js +45 -45
- package/lib/commands/feature-discovery.js +40 -0
- package/lib/commands/focus.js +278 -0
- package/lib/commands/index.js +38 -5
- package/lib/commands/ingest.js +31 -31
- package/lib/commands/inline-widget-config.js +126 -0
- package/lib/commands/integration.js +93 -0
- package/lib/commands/kb.js +450 -309
- package/lib/commands/login.js +86 -86
- package/lib/commands/logs.js +680 -0
- package/lib/commands/magento.js +210 -0
- package/lib/commands/mcp-key.js +188 -159
- package/lib/commands/migrate.js +134 -0
- package/lib/commands/migration-status.js +66 -0
- package/lib/commands/monday.js +137 -0
- package/lib/commands/netsuite.js +87 -0
- package/lib/commands/notifications.js +63 -0
- package/lib/commands/notion.js +70 -0
- package/lib/commands/ops.js +267 -173
- package/lib/commands/payment-recovery.js +170 -0
- package/lib/commands/pickup.js +172 -0
- package/lib/commands/pricing.js +132 -0
- package/lib/commands/product.js +55 -0
- package/lib/commands/recruiting.js +374 -0
- package/lib/commands/report.js +462 -0
- package/lib/commands/routing.js +304 -0
- package/lib/commands/rule.js +85 -85
- package/lib/commands/sharepoint.js +167 -0
- package/lib/commands/site-provision.js +68 -0
- package/lib/commands/site.js +62 -62
- package/lib/commands/sitesync.js +158 -0
- package/lib/commands/slack.js +64 -0
- package/lib/commands/squarespace.js +108 -0
- package/lib/commands/status.js +24 -24
- package/lib/commands/subscription.js +43 -0
- package/lib/commands/support.js +128 -0
- package/lib/commands/survey.js +216 -0
- package/lib/commands/team.js +144 -144
- package/lib/commands/teams-phone.js +131 -0
- package/lib/commands/teams.js +106 -0
- package/lib/commands/telephony.js +99 -0
- package/lib/commands/update.js +47 -47
- package/lib/commands/webflow.js +128 -0
- package/lib/commands/whoami.js +25 -22
- package/lib/commands/widget-container.js +152 -0
- package/lib/commands/woocommerce.js +240 -0
- package/lib/commands/workflow.js +233 -98
- package/lib/config.js +85 -83
- package/lib/kb-screenshot.js +320 -0
- package/lib/migrations/amscro.json +72 -0
- package/lib/migrations/azenta.json +68 -0
- package/lib/migrations/bluefire.json +49 -0
- package/lib/migrations/donaldson.json +75 -0
- package/lib/oauth.js +149 -135
- package/lib/run.js +21 -16
- package/lib/sharepoint-auth.js +138 -0
- package/lib/subscription.js +41 -39
- package/lib/track.js +35 -35
- package/lib/update-check.js +64 -64
- package/package.json +34 -19
- package/scripts/postinstall.js +12 -0
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import { get, post, request } from '../api.js';
|
|
2
|
+
import { runCommand } from '../run.js';
|
|
3
|
+
|
|
4
|
+
export const migrateCommand = {
|
|
5
|
+
command: 'migrate <subcommand>',
|
|
6
|
+
describe: 'Migrate customer setups between Velaro platforms',
|
|
7
|
+
builder: (yargs) =>
|
|
8
|
+
yargs
|
|
9
|
+
.command(migrateExportCommand)
|
|
10
|
+
.command(migrateLivefluenceCommand)
|
|
11
|
+
.demandCommand(1, 'Specify a subcommand: export, livefluence'),
|
|
12
|
+
handler: () => {},
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
// ── export V10 routing rules for a site ───────────────────────────────────────
|
|
16
|
+
|
|
17
|
+
const migrateExportCommand = {
|
|
18
|
+
command: 'export <v10SiteId>',
|
|
19
|
+
describe: 'Export V10 (Livefluence) routing rules for a site and preview V20 migration',
|
|
20
|
+
builder: (y) =>
|
|
21
|
+
y
|
|
22
|
+
.positional('v10SiteId', { type: 'number', describe: 'V10 site ID to export from' })
|
|
23
|
+
.option('v20-site-id', { type: 'number', alias: 's', describe: 'V20 site ID for auto-matching teams/AI configs' }),
|
|
24
|
+
handler: runCommand(async (argv) => {
|
|
25
|
+
const qs = argv['v20-site-id'] ? `?v20SiteId=${argv['v20-site-id']}` : '';
|
|
26
|
+
const data = await get(`/DatabaseTool/V10Export/${argv.v10SiteId}${qs}`);
|
|
27
|
+
|
|
28
|
+
console.log(`\n${'─'.repeat(60)}`);
|
|
29
|
+
console.log(` V10 Site ${data.v10SiteId} — Livefluence Routing Rules`);
|
|
30
|
+
console.log(` ${data.ruleCount} rule(s) across ${data.groupCount} group(s)`);
|
|
31
|
+
console.log(`${'─'.repeat(60)}\n`);
|
|
32
|
+
|
|
33
|
+
for (const r of data.rules) {
|
|
34
|
+
const resolved = r.canAutoResolve ? '✅' : '⚠';
|
|
35
|
+
console.log(` ${resolved} [#${r.priority}] ${r.name}`);
|
|
36
|
+
console.log(` Group: ${r.groupName} → ${r.action}`);
|
|
37
|
+
console.log(` V10 expression: ${r.v10Expression}`);
|
|
38
|
+
if (r.canAutoResolve) {
|
|
39
|
+
if (r.suggestedAiConfigName)
|
|
40
|
+
console.log(` → AI Config: "${r.suggestedAiConfigName}" (id=${r.suggestedAiConfigId})`);
|
|
41
|
+
if (r.suggestedTeamName)
|
|
42
|
+
console.log(` → Team: "${r.suggestedTeamName}" (id=${r.suggestedTeamId})`);
|
|
43
|
+
} else {
|
|
44
|
+
if (r.hasBot)
|
|
45
|
+
console.log(` ⚠ No V20 AI config named "${r.groupName}" — create it first`);
|
|
46
|
+
else
|
|
47
|
+
console.log(` ⚠ No V20 team named "${r.groupName}" — create it first`);
|
|
48
|
+
}
|
|
49
|
+
console.log('');
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
console.log(` Auto-resolvable: ${data.autoResolvable}/${data.ruleCount}`);
|
|
53
|
+
if (data.needsManualMapping > 0) {
|
|
54
|
+
console.log(` Needs manual mapping: ${data.needsManualMapping} rule(s)`);
|
|
55
|
+
console.log(` Create matching V20 teams/AI configs by name, then re-run.`);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if (data.v20Teams?.length) {
|
|
59
|
+
console.log(`\n V20 Teams on site ${data.v20SiteId}: ${data.v20Teams.map(t => t.name).join(', ')}`);
|
|
60
|
+
}
|
|
61
|
+
if (data.v20AiConfigs?.length) {
|
|
62
|
+
console.log(` V20 AI Configs: ${data.v20AiConfigs.map(a => a.name).join(', ')}`);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
console.log(`\n Run: velaro migrate livefluence --v10-site-id ${argv.v10SiteId} --v20-site-id <id> --dry-run`);
|
|
66
|
+
console.log(`${'─'.repeat(60)}\n`);
|
|
67
|
+
}),
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
// ── migrate Livefluence → V20 canvas workflows ────────────────────────────────
|
|
71
|
+
|
|
72
|
+
const migrateLivefluenceCommand = {
|
|
73
|
+
command: 'livefluence',
|
|
74
|
+
describe: 'Migrate a Livefluence site\'s routing rules to V20 canvas workflows',
|
|
75
|
+
builder: (y) =>
|
|
76
|
+
y
|
|
77
|
+
.option('v10-site-id', { type: 'number', alias: 'v', describe: 'V10 (Livefluence) site ID', demandOption: true })
|
|
78
|
+
.option('v20-site-id', { type: 'number', alias: 's', describe: 'V20 destination site ID', demandOption: true })
|
|
79
|
+
.option('dry-run', { type: 'boolean', default: false, describe: 'Preview without creating' })
|
|
80
|
+
.option('enable', { type: 'boolean', default: false, describe: 'Enable created workflows immediately (default: disabled)' })
|
|
81
|
+
.option('team-map', { type: 'string', describe: 'JSON: [{"v10GroupId":1,"v20TeamId":5},...] override auto-resolution for teams' })
|
|
82
|
+
.option('ai-config-map',{ type: 'string', describe: 'JSON: [{"v10GroupId":2,"v20AiConfigId":3},...] override auto-resolution for AI configs' }),
|
|
83
|
+
handler: runCommand(async (argv) => {
|
|
84
|
+
const v10SiteId = argv['v10-site-id'];
|
|
85
|
+
const v20SiteId = argv['v20-site-id'];
|
|
86
|
+
|
|
87
|
+
const body = {
|
|
88
|
+
v10SiteId,
|
|
89
|
+
v20SiteId,
|
|
90
|
+
dryRun: argv['dry-run'],
|
|
91
|
+
enable: argv.enable,
|
|
92
|
+
teamMappings: argv['team-map'] ? JSON.parse(argv['team-map']) : [],
|
|
93
|
+
aiConfigMappings: argv['ai-config-map'] ? JSON.parse(argv['ai-config-map']) : [],
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
console.log(`\n${'─'.repeat(60)}`);
|
|
97
|
+
console.log(` Livefluence → V20 Canvas Workflow Migration`);
|
|
98
|
+
console.log(` V10 site: ${v10SiteId} → V20 site: ${v20SiteId}`);
|
|
99
|
+
console.log(` Mode: ${argv['dry-run'] ? 'DRY RUN (no changes)' : 'LIVE'}`);
|
|
100
|
+
console.log(`${'─'.repeat(60)}\n`);
|
|
101
|
+
|
|
102
|
+
const result = await post('/DatabaseTool/V10Migrate', body);
|
|
103
|
+
|
|
104
|
+
if (result.createdItems?.length) {
|
|
105
|
+
console.log(` Workflows ${argv['dry-run'] ? '(would create)' : 'created'}:\n`);
|
|
106
|
+
for (const item of result.createdItems) {
|
|
107
|
+
const id = item.workflowId ? ` [id=${item.workflowId}]` : '';
|
|
108
|
+
console.log(` + [#${item.priority}] ${item.ruleName}${id}`);
|
|
109
|
+
console.log(` Action: ${item.action}`);
|
|
110
|
+
if (item.v2AiConfigId) console.log(` → AI Config id=${item.v2AiConfigId}`);
|
|
111
|
+
if (item.v2TeamId) console.log(` → Team id=${item.v2TeamId}`);
|
|
112
|
+
if (item.v10Expression) console.log(` V10 expression: ${item.v10Expression}`);
|
|
113
|
+
console.log('');
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
if (result.skippedItems?.length) {
|
|
118
|
+
console.log(` Skipped (unresolvable):\n`);
|
|
119
|
+
for (const item of result.skippedItems) {
|
|
120
|
+
console.log(` ⚠ ${item.name}: ${item.reason}`);
|
|
121
|
+
}
|
|
122
|
+
console.log('');
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
console.log(`${'─'.repeat(60)}`);
|
|
126
|
+
console.log(` ${result.message}`);
|
|
127
|
+
if (!argv['dry-run'] && result.created > 0) {
|
|
128
|
+
console.log(`\n ⚠ All workflows created DISABLED — review conditions in the canvas then enable.`);
|
|
129
|
+
console.log(` Note: V10 routing expressions were NOT auto-converted.`);
|
|
130
|
+
console.log(` Add conditions manually in the Workflows canvas (Phase 3 trigger node).`);
|
|
131
|
+
}
|
|
132
|
+
console.log(`${'─'.repeat(60)}\n`);
|
|
133
|
+
}),
|
|
134
|
+
};
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { get } from '../api.js';
|
|
2
|
+
import { runCommand } from '../run.js';
|
|
3
|
+
|
|
4
|
+
// Reads AdminStartupDiagnostics (SuperAdmin/StartupDiagnostics) — the dependency-free canary
|
|
5
|
+
// table StartupMigrationRunner.WriteCanary writes on every startup, one row per DbContext per
|
|
6
|
+
// restart. Built 2026-08-23: no CLI/MCP/REST surface previously existed to read this table, so
|
|
7
|
+
// every prior round of the MessagingContext production migration investigation
|
|
8
|
+
// (docs/architecture/postmortems.md, 2026-07-14 through 2026-08-19) had to guess at the real
|
|
9
|
+
// Database.Migrate()/SchemaDriftChecker outcome instead of just reading it directly.
|
|
10
|
+
export const migrationStatusCommand = {
|
|
11
|
+
command: 'migration-status [contextLabel]',
|
|
12
|
+
describe: 'Show recent StartupMigrationRunner/SchemaDriftChecker outcomes (AdminStartupDiagnostics canary table)',
|
|
13
|
+
builder: (y) =>
|
|
14
|
+
y
|
|
15
|
+
.positional('contextLabel', { type: 'string', describe: 'Filter to one DbContext label, e.g. Messaging, Marketing, Billing' })
|
|
16
|
+
.option('count', { type: 'number', alias: 'n', default: 20, describe: 'Max rows to show (1-200)' }),
|
|
17
|
+
handler: runCommand(async (argv) => {
|
|
18
|
+
const qs = new URLSearchParams();
|
|
19
|
+
if (argv.contextLabel) qs.set('contextLabel', argv.contextLabel);
|
|
20
|
+
if (argv.count) qs.set('count', String(argv.count));
|
|
21
|
+
|
|
22
|
+
const data = await get(`/SuperAdmin/StartupDiagnostics?${qs.toString()}`);
|
|
23
|
+
|
|
24
|
+
if (data.note) {
|
|
25
|
+
console.log(`\n ${data.note}\n`);
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
if (!data.rows?.length) {
|
|
30
|
+
console.log(`\n No AdminStartupDiagnostics rows${argv.contextLabel ? ` for '${argv.contextLabel}'` : ''}.\n`);
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
console.log(`\n${'─'.repeat(70)}`);
|
|
35
|
+
console.log(` Startup Migration Diagnostics${argv.contextLabel ? ` — ${argv.contextLabel}` : ''}`);
|
|
36
|
+
console.log(`${'─'.repeat(70)}\n`);
|
|
37
|
+
|
|
38
|
+
for (const row of data.rows) {
|
|
39
|
+
const outcomeIcon =
|
|
40
|
+
row.outcome === 'Success' ? '✅' :
|
|
41
|
+
row.outcome === 'PendingAfterMigrate_HistoryOnly' ? '🟡' :
|
|
42
|
+
row.outcome?.startsWith('PendingAfterMigrate_RealDrift') ? '🔴' :
|
|
43
|
+
row.outcome === 'PendingAfterMigrate_Unchecked' ? '⚠️' :
|
|
44
|
+
row.outcome === 'MigrateThrew' || row.outcome === 'DriftCheckThrew' ? '❌' :
|
|
45
|
+
'·';
|
|
46
|
+
|
|
47
|
+
console.log(` ${outcomeIcon} [${row.contextLabel}] ${row.outcome} — ${new Date(row.createdAtUtc).toISOString()}`);
|
|
48
|
+
if (row.pendingCount > 0) {
|
|
49
|
+
console.log(` Pending (${row.pendingCount}): ${row.pendingMigrationIds}`);
|
|
50
|
+
}
|
|
51
|
+
if (row.exceptionType) {
|
|
52
|
+
console.log(` Exception: ${row.exceptionType}`);
|
|
53
|
+
console.log(` Detail: ${(row.exceptionDetail || '').slice(0, 400)}${(row.exceptionDetail || '').length > 400 ? '…' : ''}`);
|
|
54
|
+
}
|
|
55
|
+
console.log('');
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
console.log(`${'─'.repeat(70)}`);
|
|
59
|
+
console.log(' Outcomes: Success | PendingAfterMigrate_HistoryOnly (safe, Degraded) |');
|
|
60
|
+
console.log(' PendingAfterMigrate_RealDrift* (real missing schema, Critical) |');
|
|
61
|
+
console.log(' PendingAfterMigrate_Unchecked (drift check never ran, fail-safe Critical) |');
|
|
62
|
+
console.log(' MigrateThrew (Database.Migrate() itself threw) |');
|
|
63
|
+
console.log(' DriftCheckThrew (SchemaDriftChecker itself threw after a successful Migrate())');
|
|
64
|
+
console.log(`${'─'.repeat(70)}\n`);
|
|
65
|
+
}),
|
|
66
|
+
};
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* velaro monday - Monday.com board/column field mapping + CRM search
|
|
3
|
+
*
|
|
4
|
+
* velaro monday columns List a board's real columns (status/people/dropdown/date/numbers/text)
|
|
5
|
+
* velaro monday mapping Show the site's saved field mapping
|
|
6
|
+
* velaro monday mapping-set Set a single field's mapping to a column id
|
|
7
|
+
* velaro monday search Freeform CRM search across a board's items
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { request } from '../api.js';
|
|
11
|
+
|
|
12
|
+
const GRN = '\x1b[32m';
|
|
13
|
+
const RED = '\x1b[31m';
|
|
14
|
+
const CYN = '\x1b[36m';
|
|
15
|
+
const DIM = '\x1b[2m';
|
|
16
|
+
const BOLD = '\x1b[1m';
|
|
17
|
+
const RST = '\x1b[0m';
|
|
18
|
+
|
|
19
|
+
async function resolveBoardId(argv) {
|
|
20
|
+
if (argv.board) return argv.board;
|
|
21
|
+
const cfg = await request('GET', '/Monday');
|
|
22
|
+
return cfg?.defaultBoardId ?? null;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// -- columns ---------------------------------------------------------------
|
|
26
|
+
|
|
27
|
+
async function columnsHandler(argv) {
|
|
28
|
+
const boardId = await resolveBoardId(argv);
|
|
29
|
+
if (!boardId) {
|
|
30
|
+
console.error(`${RED}No board ID given and no default board configured. Use --board or set a default board first.${RST}`);
|
|
31
|
+
process.exitCode = 1;
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
const columns = await request('GET', `/Monday/boards/${encodeURIComponent(boardId)}/columns`);
|
|
35
|
+
if (!Array.isArray(columns) || columns.length === 0) {
|
|
36
|
+
console.log(`\nNo columns found for board ${boardId}.\n`);
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
console.log(`\n${BOLD}Columns on board ${boardId} (${columns.length})${RST}\n`);
|
|
40
|
+
for (const c of columns) {
|
|
41
|
+
console.log(` ${CYN}${(c.id ?? '').padEnd(20)}${RST} ${BOLD}${c.title}${RST} ${DIM}(${c.type})${RST}`);
|
|
42
|
+
if (Array.isArray(c.options) && c.options.length) {
|
|
43
|
+
console.log(` ${DIM}options: ${c.options.map(o => o.label).join(', ')}${RST}`);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
console.log('');
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// -- mapping -----------------------------------------------------------------
|
|
50
|
+
|
|
51
|
+
async function mappingHandler() {
|
|
52
|
+
const result = await request('GET', '/Monday/FieldMapping');
|
|
53
|
+
const entries = Object.entries(result?.mapping ?? {});
|
|
54
|
+
if (entries.length === 0) {
|
|
55
|
+
console.log(`\nNo field mapping saved yet${result?.boardId ? ` for board ${result.boardId}` : ''}.\n`);
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
console.log(`\n${BOLD}Field mapping${result?.boardId ? ` (board ${result.boardId})` : ''}${RST}\n`);
|
|
59
|
+
for (const [k, v] of entries) console.log(` ${k.padEnd(16)} -> ${GRN}${v}${RST}`);
|
|
60
|
+
console.log('');
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// -- mapping-set ---------------------------------------------------------------
|
|
64
|
+
|
|
65
|
+
async function mappingSetHandler(argv) {
|
|
66
|
+
if (!argv.field || !argv.column) {
|
|
67
|
+
console.error(`${RED}--field and --column are required (e.g. --field contactEmail --column email_mkjy)${RST}`);
|
|
68
|
+
process.exitCode = 1;
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
const current = await request('GET', '/Monday/FieldMapping');
|
|
72
|
+
const mapping = { ...(current?.mapping ?? {}), [argv.field]: argv.column };
|
|
73
|
+
const result = await request('PUT', '/Monday/FieldMapping', { mapping });
|
|
74
|
+
console.log(`\n${GRN}Mapped${RST} ${argv.field} -> ${result?.mapping?.[argv.field]}\n`);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// -- search --------------------------------------------------------------------
|
|
78
|
+
|
|
79
|
+
async function searchHandler(argv) {
|
|
80
|
+
if (!argv.q) {
|
|
81
|
+
console.error(`${RED}--q (search text) is required${RST}`);
|
|
82
|
+
process.exitCode = 1;
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
const qs = new URLSearchParams({ q: argv.q });
|
|
86
|
+
if (argv.board) qs.set('boardId', argv.board);
|
|
87
|
+
const results = await request('GET', `/Monday/Data/Contacts/Search?${qs.toString()}`);
|
|
88
|
+
if (!Array.isArray(results) || results.length === 0) {
|
|
89
|
+
console.log(`\nNo Monday.com items matched "${argv.q}".\n`);
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
console.log(`\n${BOLD}${results.length} match(es) for "${argv.q}"${RST}\n`);
|
|
93
|
+
for (const r of results) {
|
|
94
|
+
console.log(` [${r.id}] ${BOLD}${r.name}${RST}`);
|
|
95
|
+
const bits = [r.email, r.phone, r.company].filter(Boolean);
|
|
96
|
+
if (bits.length) console.log(` ${DIM}${bits.join(' | ')}${RST}`);
|
|
97
|
+
}
|
|
98
|
+
console.log('');
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// -- command export --------------------------------------------------------------
|
|
102
|
+
|
|
103
|
+
export const mondayCommand = {
|
|
104
|
+
command: 'monday <subcommand>',
|
|
105
|
+
describe: 'Monday.com board/column field mapping + CRM search',
|
|
106
|
+
builder: y => y
|
|
107
|
+
.command({
|
|
108
|
+
command: 'columns',
|
|
109
|
+
describe: "List a board's real columns (status/people/dropdown/date/numbers/text)",
|
|
110
|
+
builder: yy => yy
|
|
111
|
+
.option('board', { describe: "Board ID (default: site's configured default board)", type: 'string' }),
|
|
112
|
+
handler: columnsHandler,
|
|
113
|
+
})
|
|
114
|
+
.command({
|
|
115
|
+
command: 'mapping',
|
|
116
|
+
describe: "Show the site's saved Velaro field to Monday column mapping",
|
|
117
|
+
handler: mappingHandler,
|
|
118
|
+
})
|
|
119
|
+
.command({
|
|
120
|
+
command: 'mapping-set',
|
|
121
|
+
describe: 'Map a single Velaro field to a Monday.com column id',
|
|
122
|
+
builder: yy => yy
|
|
123
|
+
.option('field', { describe: 'Velaro field key (contactName, contactEmail, contactPhone, company, leadSource, status)', type: 'string' })
|
|
124
|
+
.option('column', { describe: 'Monday.com column id (from velaro monday columns)', type: 'string' }),
|
|
125
|
+
handler: mappingSetHandler,
|
|
126
|
+
})
|
|
127
|
+
.command({
|
|
128
|
+
command: 'search',
|
|
129
|
+
describe: 'Freeform CRM search (name/email/phone/company) across a board',
|
|
130
|
+
builder: yy => yy
|
|
131
|
+
.option('q', { describe: 'Search text', type: 'string' })
|
|
132
|
+
.option('board', { describe: "Board ID (default: site's configured default board)", type: 'string' }),
|
|
133
|
+
handler: searchHandler,
|
|
134
|
+
})
|
|
135
|
+
.demandCommand(1, 'Specify a subcommand. Try: velaro monday --help'),
|
|
136
|
+
handler: () => {},
|
|
137
|
+
};
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* velaro netsuite - NetSuite click-to-dial (CTI) setup
|
|
3
|
+
*
|
|
4
|
+
* velaro netsuite cti list List NetSuite config IDs for the site
|
|
5
|
+
* velaro netsuite cti urls <configId> Show the current CTI setup URLs (or null if never generated)
|
|
6
|
+
* velaro netsuite cti generate <configId> Generate/rotate the CTI URL token
|
|
7
|
+
*
|
|
8
|
+
* Backend contract: server/Velaro.Messaging/Controllers/CRMs/NetsuiteIntegrationsController.cs
|
|
9
|
+
* (velaro-messaging). Site is resolved server-side from the auth context — never caller-supplied.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { messagingGet, messagingPost } from '../api.js';
|
|
13
|
+
|
|
14
|
+
const GRN = '\x1b[32m';
|
|
15
|
+
const DIM = '\x1b[2m';
|
|
16
|
+
const BOLD = '\x1b[1m';
|
|
17
|
+
const RST = '\x1b[0m';
|
|
18
|
+
|
|
19
|
+
function printConfigs(configs) {
|
|
20
|
+
if (!Array.isArray(configs) || !configs.length) {
|
|
21
|
+
console.log(`\n ${DIM}No NetSuite configurations found for this site.${RST}\n`);
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
console.log(`\n${BOLD}NetSuite configurations${RST}`);
|
|
25
|
+
for (const c of configs) console.log(` [${c.id}] ${c.displayName} (${c.company})`);
|
|
26
|
+
console.log('');
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function printUrls(urls) {
|
|
30
|
+
console.log(`\n${BOLD}NetSuite CTI setup — config ${urls.id}${RST}`);
|
|
31
|
+
if (!urls.ctiUrlToken) {
|
|
32
|
+
console.log(` ${DIM}No CTI URL generated yet. Run "velaro netsuite cti generate ${urls.id}".${RST}\n`);
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
console.log(` ${GRN}Dial URL${RST} (paste into NetSuite Home > Set Preferences > Telephony > CTI URL):`);
|
|
36
|
+
console.log(` ${urls.dialUrlTemplate}`);
|
|
37
|
+
console.log(` ${GRN}Screen-pop URL${RST} (optional, unconfirmed automatic by NetSuite):`);
|
|
38
|
+
console.log(` ${urls.screenPopUrl}\n`);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
async function listHandler() {
|
|
42
|
+
const configs = await messagingGet('/NetSuite/Integrations/List');
|
|
43
|
+
printConfigs(configs);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
async function urlsHandler(argv) {
|
|
47
|
+
const urls = await messagingGet(`/NetSuite/Integrations/${argv.configId}/CtiUrls`);
|
|
48
|
+
printUrls(urls);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
async function generateHandler(argv) {
|
|
52
|
+
const urls = await messagingPost(`/NetSuite/Integrations/${argv.configId}/GenerateCtiToken`, {});
|
|
53
|
+
console.log(`\n ${GRN}✓${RST} CTI token generated/rotated.`);
|
|
54
|
+
printUrls(urls);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export const netsuiteCommand = {
|
|
58
|
+
command: 'netsuite <subcommand>',
|
|
59
|
+
describe: 'NetSuite integration setup (click-to-dial / CTI)',
|
|
60
|
+
builder: y => y
|
|
61
|
+
.command({
|
|
62
|
+
command: 'cti <action>',
|
|
63
|
+
describe: 'NetSuite click-to-dial (CTI URL) setup',
|
|
64
|
+
builder: yy => yy
|
|
65
|
+
.command({
|
|
66
|
+
command: 'list',
|
|
67
|
+
describe: 'List NetSuite config IDs for the site',
|
|
68
|
+
handler: listHandler,
|
|
69
|
+
})
|
|
70
|
+
.command({
|
|
71
|
+
command: 'urls <configId>',
|
|
72
|
+
describe: 'Show the current CTI setup URLs for a config',
|
|
73
|
+
handler: argv => urlsHandler({ configId: argv.configId }),
|
|
74
|
+
})
|
|
75
|
+
.command({
|
|
76
|
+
command: 'generate <configId>',
|
|
77
|
+
describe: 'Generate (or rotate) the CTI URL token for a config',
|
|
78
|
+
handler: argv => generateHandler({ configId: argv.configId }),
|
|
79
|
+
})
|
|
80
|
+
.demandCommand(1, 'Specify a cti action: list, urls, or generate.')
|
|
81
|
+
.strict(),
|
|
82
|
+
handler: () => {},
|
|
83
|
+
})
|
|
84
|
+
.demandCommand(1, 'Specify a netsuite subcommand.')
|
|
85
|
+
.strict(),
|
|
86
|
+
handler: () => {},
|
|
87
|
+
};
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { messagingGet, messagingPost } from '../api.js';
|
|
2
|
+
|
|
3
|
+
const FLAGS = [
|
|
4
|
+
['new-conversations', 'newConversations', 'NewConversations'],
|
|
5
|
+
['conversation-assigned','conversationAssigned','ConversationAssigned'],
|
|
6
|
+
['new-messages', 'newMessages', 'NewMessages'],
|
|
7
|
+
['unassign-warning', 'unassignWarning', 'UnassignWarning'],
|
|
8
|
+
['text-alerts', 'textAlerts', 'TextAlerts'],
|
|
9
|
+
['sticky-alerts', 'stickyAlerts', 'StickyAlerts'],
|
|
10
|
+
['sms-on-new-conversation', 'smsOnNewConversation', 'SmsOnNewConversation'],
|
|
11
|
+
['sms-on-missed-chat', 'smsOnMissedChat', 'SmsOnMissedChat'],
|
|
12
|
+
['sms-on-unassign-warning', 'smsOnUnassignWarning', 'SmsOnUnassignWarning'],
|
|
13
|
+
['use-profile-phone', 'useProfilePhone', 'UseProfilePhone'],
|
|
14
|
+
];
|
|
15
|
+
|
|
16
|
+
const getCommand = {
|
|
17
|
+
command: 'get',
|
|
18
|
+
describe: 'Show your current notification/alert settings (new conversations, SMS alerts, etc.)',
|
|
19
|
+
handler: async () => {
|
|
20
|
+
const s = await messagingGet('/NotificationSettings');
|
|
21
|
+
console.log('\n\x1b[1mNotification settings\x1b[0m');
|
|
22
|
+
for (const [flag, jsonKey] of FLAGS) {
|
|
23
|
+
console.log(` ${flag.padEnd(28)} ${s[jsonKey] ? '\x1b[32mon\x1b[0m' : '\x1b[90moff\x1b[0m'}`);
|
|
24
|
+
}
|
|
25
|
+
console.log(` ${'sticky-duration'.padEnd(28)} ${s.stickyDuration}ms`);
|
|
26
|
+
console.log(` ${'sms-alert-phone'.padEnd(28)} ${s.smsAlertPhone ?? '(none)'}`);
|
|
27
|
+
console.log('');
|
|
28
|
+
},
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
const setCommand = {
|
|
32
|
+
command: 'set',
|
|
33
|
+
describe: 'Update your notification/alert settings (only the flags you pass are changed)',
|
|
34
|
+
builder: (y) => {
|
|
35
|
+
for (const [flag] of FLAGS) {
|
|
36
|
+
y.option(flag, { type: 'boolean', describe: `Turn ${flag.replace(/-/g, ' ')} on/off` });
|
|
37
|
+
}
|
|
38
|
+
y.option('sms-alert-phone', { type: 'string', describe: 'Phone number for SMS alerts (E.164, e.g. +14155551234)' });
|
|
39
|
+
return y;
|
|
40
|
+
},
|
|
41
|
+
handler: async (argv) => {
|
|
42
|
+
const current = await messagingGet('/NotificationSettings');
|
|
43
|
+
|
|
44
|
+
for (const [flag, jsonKey] of FLAGS) {
|
|
45
|
+
if (argv[flag] !== undefined) current[jsonKey] = argv[flag];
|
|
46
|
+
}
|
|
47
|
+
if (argv['sms-alert-phone'] !== undefined) current.smsAlertPhone = argv['sms-alert-phone'];
|
|
48
|
+
|
|
49
|
+
await messagingPost('/NotificationSettings', current);
|
|
50
|
+
console.log('\x1b[32m✓ Notification settings updated.\x1b[0m Run `velaro notifications get` to review.');
|
|
51
|
+
},
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
export const notificationsCommand = {
|
|
55
|
+
command: 'notifications <subcommand>',
|
|
56
|
+
describe: 'View and update your own notification/SMS alert settings',
|
|
57
|
+
builder: (yargs) =>
|
|
58
|
+
yargs
|
|
59
|
+
.command(getCommand)
|
|
60
|
+
.command(setCommand)
|
|
61
|
+
.demandCommand(1, 'Specify a subcommand: get | set'),
|
|
62
|
+
handler: () => {},
|
|
63
|
+
};
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* velaro notion - Notion workspace connection status + connectivity test
|
|
3
|
+
*
|
|
4
|
+
* velaro notion status Show the site's connected Notion workspace + config
|
|
5
|
+
* velaro notion test Test a Notion integration token without saving it
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { request } from '../api.js';
|
|
9
|
+
|
|
10
|
+
const GRN = '\x1b[32m';
|
|
11
|
+
const RED = '\x1b[31m';
|
|
12
|
+
const DIM = '\x1b[2m';
|
|
13
|
+
const BOLD = '\x1b[1m';
|
|
14
|
+
const RST = '\x1b[0m';
|
|
15
|
+
|
|
16
|
+
// -- status ------------------------------------------------------------------
|
|
17
|
+
|
|
18
|
+
async function statusHandler() {
|
|
19
|
+
const configs = await request('GET', '/Notion/Configs');
|
|
20
|
+
if (!Array.isArray(configs) || configs.length === 0) {
|
|
21
|
+
console.log(`\n${RED}Notion is not connected.${RST} Visit Integration Settings -> Notion to connect a workspace.\n`);
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
const cfg = configs[0];
|
|
25
|
+
console.log(`\n${BOLD}Notion${RST} ${cfg.isEnabled ? `${GRN}connected${RST}` : `${RED}disabled${RST}`}`);
|
|
26
|
+
console.log(` Workspace: ${cfg.workspaceName || DIM + '(unknown)' + RST}`);
|
|
27
|
+
console.log(` Workspace ID: ${cfg.workspaceId || DIM + '(none)' + RST}`);
|
|
28
|
+
console.log(` Knowledge base pages: ${cfg.knowledgeBasePageIds || DIM + 'entire workspace' + RST}`);
|
|
29
|
+
console.log(` Leads database: ${cfg.leadsDatabaseId || DIM + '(not configured)' + RST}`);
|
|
30
|
+
console.log('');
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// -- test ----------------------------------------------------------------------
|
|
34
|
+
|
|
35
|
+
async function testHandler(argv) {
|
|
36
|
+
if (!argv.token) {
|
|
37
|
+
console.error(`${RED}--token (Notion internal integration token) is required${RST}`);
|
|
38
|
+
process.exitCode = 1;
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
const result = await request('POST', '/Notion/Configs/Test', { accessToken: argv.token });
|
|
42
|
+
if (!result?.valid) {
|
|
43
|
+
console.log(`\n${RED}Invalid token${RST}${result?.error ? `: ${result.error}` : '.'}\n`);
|
|
44
|
+
process.exitCode = 1;
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
console.log(`\n${GRN}Valid${RST} — workspace: ${BOLD}${result.workspaceName}${RST} (${result.workspaceId})\n`);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// -- command export --------------------------------------------------------------
|
|
51
|
+
|
|
52
|
+
export const notionCommand = {
|
|
53
|
+
command: 'notion <subcommand>',
|
|
54
|
+
describe: 'Notion workspace connection status + connectivity test',
|
|
55
|
+
builder: y => y
|
|
56
|
+
.command({
|
|
57
|
+
command: 'status',
|
|
58
|
+
describe: "Show the site's connected Notion workspace and configuration",
|
|
59
|
+
handler: statusHandler,
|
|
60
|
+
})
|
|
61
|
+
.command({
|
|
62
|
+
command: 'test',
|
|
63
|
+
describe: 'Test a Notion integration token without saving it',
|
|
64
|
+
builder: yy => yy
|
|
65
|
+
.option('token', { describe: 'Notion internal integration token (secret_...)', type: 'string' }),
|
|
66
|
+
handler: testHandler,
|
|
67
|
+
})
|
|
68
|
+
.demandCommand(1, 'Specify a subcommand. Try: velaro notion --help'),
|
|
69
|
+
handler: () => {},
|
|
70
|
+
};
|