@velaro/cli 1.4.102 → 1.4.104

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 (50) hide show
  1. package/README.md +19 -0
  2. package/bin/velaro.js +14 -0
  3. package/lib/api.js +2 -1
  4. package/lib/apple-pay-presets.js +66 -0
  5. package/lib/apple-pay-presets.test.js +61 -0
  6. package/lib/apple-pay.js +32 -0
  7. package/lib/apple-pay.test.js +32 -0
  8. package/lib/avatar-switch.js +42 -0
  9. package/lib/avatar-switch.test.js +49 -0
  10. package/lib/build-info.js +28 -0
  11. package/lib/build-info.test.js +24 -0
  12. package/lib/commands/apple-pay-preset.js +65 -0
  13. package/lib/commands/apple-pay.js +73 -0
  14. package/lib/commands/avatar.js +60 -2
  15. package/lib/commands/billing-automation.js +184 -0
  16. package/lib/commands/copilotstudio.js +14 -1
  17. package/lib/commands/enterprise-reports.js +1 -0
  18. package/lib/commands/entitlement.js +79 -1
  19. package/lib/commands/env.js +1 -1
  20. package/lib/commands/focus.js +1 -1
  21. package/lib/commands/index.js +38 -2
  22. package/lib/commands/logs-insights.js +342 -0
  23. package/lib/commands/logs.js +310 -11
  24. package/lib/commands/magento.js +16 -0
  25. package/lib/commands/mcp-key.js +7 -1
  26. package/lib/commands/netsuite.js +67 -2
  27. package/lib/commands/notifications.js +34 -1
  28. package/lib/commands/ops.js +23 -54
  29. package/lib/commands/report-backfill.js +23 -5
  30. package/lib/commands/report.js +77 -14
  31. package/lib/commands/salesforce.js +101 -0
  32. package/lib/commands/shopify.js +84 -0
  33. package/lib/commands/signature.js +79 -0
  34. package/lib/commands/site-builder.js +30 -0
  35. package/lib/commands/support.js +22 -0
  36. package/lib/commands/teams.js +11 -1
  37. package/lib/commands/workflow.js +157 -2
  38. package/lib/commands/zoom-phone.js +15 -0
  39. package/lib/enterprise-reports.test.js +1 -1
  40. package/lib/entitlement-report-packages.test.js +47 -0
  41. package/lib/logs-insights.test.js +146 -0
  42. package/lib/mcp-key.test.js +11 -0
  43. package/lib/messaging-routes.test.js +10 -1
  44. package/lib/oauth.js +14 -2
  45. package/lib/report-range.js +21 -0
  46. package/lib/report-range.test.js +38 -0
  47. package/lib/shopify-write-policy.test.js +34 -0
  48. package/lib/signature-policy.js +70 -0
  49. package/lib/signature-policy.test.js +48 -0
  50. package/package.json +1 -1
@@ -0,0 +1,34 @@
1
+ import test from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import fs from 'node:fs';
4
+ import path from 'node:path';
5
+ import { fileURLToPath } from 'node:url';
6
+ import { buildWritePolicyBody, parseCap } from './commands/shopify.js';
7
+
8
+ const root = path.join(path.dirname(fileURLToPath(import.meta.url)), '..', '..');
9
+
10
+ test('parseCap handles numbers, default and invalid', () => {
11
+ assert.equal(parseCap(undefined, 'x'), undefined);
12
+ assert.equal(parseCap('default', 'x'), null);
13
+ assert.equal(parseCap('12.5', 'x'), 12.5);
14
+ assert.throws(() => parseCap('abc', 'x'));
15
+ assert.throws(() => parseCap('-1', 'x'));
16
+ });
17
+
18
+ test('buildWritePolicyBody is partial and maps default to null', () => {
19
+ assert.deepEqual(buildWritePolicyBody({ enableShopifyWrite: false }), { enableShopifyWrite: false });
20
+ assert.deepEqual(buildWritePolicyBody({ maxRefundAmount: '100', maxDiscountPercent: 'default' }), { maxRefundAmount: 100, maxDiscountPercent: null });
21
+ assert.throws(() => buildWritePolicyBody({}));
22
+ });
23
+
24
+ test('CLI and MCP expose the write-policy surface and it is registered', () => {
25
+ const cli = fs.readFileSync(path.join(root, 'cli/bin/velaro.js'), 'utf8');
26
+ assert.match(cli, /\.command\(shopifyCommand\)/);
27
+ const mcp = fs.readFileSync(path.join(root, 'mcp/server.js'), 'utf8');
28
+ for (const t of ['shopify_get_write_policy', 'shopify_set_write_policy']) {
29
+ assert.match(mcp, new RegExp(`name: '${t}'`));
30
+ assert.match(mcp, new RegExp(`case '${t}'`));
31
+ }
32
+ assert.match(mcp, /messagingPut\('\/Shopify\/WritePolicy'/);
33
+ assert.match(mcp, /messagingGet\('\/Shopify\/WritePolicy'/);
34
+ });
@@ -0,0 +1,70 @@
1
+ // Pure helpers for `velaro signature policy` (and mirrored in mcp/server.js signature_policy_set).
2
+ // The messaging endpoint PUT Signatures/policy REPLACES the row for a team, and silently turns an unknown mode
3
+ // into "follow the company-wide value", so we validate here and merge onto the current row before sending.
4
+
5
+ const MODES = {
6
+ 'locked': 'admin_locked',
7
+ 'admin-locked': 'admin_locked',
8
+ 'admin_locked': 'admin_locked',
9
+ 'company-default': 'company_default',
10
+ 'company_default': 'company_default',
11
+ 'personal-only': 'personal_only',
12
+ 'personal_only': 'personal_only',
13
+ };
14
+
15
+ // Returns undefined when not supplied, null for "follow the company-wide setting", else the API value.
16
+ export function parseMode(value) {
17
+ if (value === undefined) return undefined;
18
+ const v = String(value).trim().toLowerCase();
19
+ if (v === 'default' || v === 'inherit' || v === '') return null;
20
+ const mode = MODES[v];
21
+ if (!mode) throw new Error('mode must be locked, company-default, personal-only or default');
22
+ return mode;
23
+ }
24
+
25
+ export function parseTeamId(value) {
26
+ if (value === undefined || value === null || value === '') return 0;
27
+ const n = Number(value);
28
+ if (!Number.isInteger(n) || n < 0) throw new Error('teamId must be 0 (company-wide) or a team id');
29
+ return n;
30
+ }
31
+
32
+ // current: the matching row from GET Signatures/policy (or undefined). opts: only the fields being changed.
33
+ // A field left out keeps its current value; null clears it back to "follow the company-wide setting".
34
+ export function buildPolicyBody(current, opts = {}) {
35
+ const cur = current ?? {};
36
+ const body = {
37
+ teamId: parseTeamId(opts.teamId ?? cur.teamId),
38
+ mode: cur.mode ?? null,
39
+ corporateSignature: cur.corporateSignature || null,
40
+ disclaimerEnabled: cur.disclaimerEnabled ?? null,
41
+ disclaimerText: cur.disclaimerText || null,
42
+ };
43
+ const mode = parseMode(opts.mode);
44
+ if (mode !== undefined) body.mode = mode;
45
+ if (opts.corporateSignature !== undefined) body.corporateSignature = opts.corporateSignature || null;
46
+ if (opts.disclaimerEnabled !== undefined) {
47
+ if (opts.disclaimerEnabled !== null && typeof opts.disclaimerEnabled !== 'boolean') {
48
+ throw new Error('disclaimerEnabled must be true, false or null');
49
+ }
50
+ body.disclaimerEnabled = opts.disclaimerEnabled;
51
+ }
52
+ if (opts.disclaimerText !== undefined) body.disclaimerText = opts.disclaimerText || null;
53
+ if (body.corporateSignature && body.corporateSignature.length > 4000) throw new Error('corporateSignature is limited to 4000 characters');
54
+ if (body.disclaimerText && body.disclaimerText.length > 2000) throw new Error('disclaimerText is limited to 2000 characters');
55
+ return body;
56
+ }
57
+
58
+ export function findPolicy(policies, teamId) {
59
+ const id = parseTeamId(teamId);
60
+ return (Array.isArray(policies) ? policies : []).find((p) => p.teamId === id);
61
+ }
62
+
63
+ export function describeMode(mode) {
64
+ switch (mode) {
65
+ case 'admin_locked': return 'locked (company signature only)';
66
+ case 'company_default': return 'company default (personal signatures allowed)';
67
+ case 'personal_only': return 'personal only';
68
+ default: return 'follows company-wide setting (personal only if none set)';
69
+ }
70
+ }
@@ -0,0 +1,48 @@
1
+ import test from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import { parseMode, parseTeamId, buildPolicyBody, findPolicy, describeMode } from './signature-policy.js';
4
+
5
+ test('parseMode maps friendly names to API values and rejects unknown ones', () => {
6
+ assert.equal(parseMode('locked'), 'admin_locked');
7
+ assert.equal(parseMode('Company-Default'), 'company_default');
8
+ assert.equal(parseMode('personal_only'), 'personal_only');
9
+ assert.equal(parseMode('default'), null);
10
+ assert.equal(parseMode(undefined), undefined);
11
+ assert.throws(() => parseMode('strict'), /mode must be/);
12
+ });
13
+
14
+ test('parseTeamId defaults to company-wide and rejects junk', () => {
15
+ assert.equal(parseTeamId(undefined), 0);
16
+ assert.equal(parseTeamId('7'), 7);
17
+ assert.throws(() => parseTeamId('abc'), /teamId/);
18
+ assert.throws(() => parseTeamId(-1), /teamId/);
19
+ });
20
+
21
+ test('buildPolicyBody keeps current values for fields that are not passed', () => {
22
+ const cur = { teamId: 0, mode: 'company_default', corporateSignature: '<b>Acme</b>', disclaimerEnabled: true, disclaimerText: 'Confidential' };
23
+ const body = buildPolicyBody(cur, { mode: 'locked' });
24
+ assert.deepEqual(body, { teamId: 0, mode: 'admin_locked', corporateSignature: '<b>Acme</b>', disclaimerEnabled: true, disclaimerText: 'Confidential' });
25
+ });
26
+
27
+ test('buildPolicyBody with no existing row starts from all-null (follow company-wide)', () => {
28
+ const body = buildPolicyBody(undefined, { teamId: 5, disclaimerEnabled: true });
29
+ assert.deepEqual(body, { teamId: 5, mode: null, corporateSignature: null, disclaimerEnabled: true, disclaimerText: null });
30
+ });
31
+
32
+ test('null clears a field and over-long text is rejected before sending', () => {
33
+ const cur = { teamId: 0, mode: 'admin_locked', disclaimerEnabled: true, disclaimerText: 'x' };
34
+ assert.equal(buildPolicyBody(cur, { disclaimerEnabled: null }).disclaimerEnabled, null);
35
+ assert.equal(buildPolicyBody(cur, { mode: 'default' }).mode, null);
36
+ assert.throws(() => buildPolicyBody(cur, { disclaimerText: 'a'.repeat(2001) }), /2000/);
37
+ assert.throws(() => buildPolicyBody(cur, { corporateSignature: 'a'.repeat(4001) }), /4000/);
38
+ assert.throws(() => buildPolicyBody(cur, { disclaimerEnabled: 'yes' }), /true, false or null/);
39
+ });
40
+
41
+ test('findPolicy picks the row for a team and describeMode is readable', () => {
42
+ const rows = [{ teamId: 0, mode: 'personal_only' }, { teamId: 3, mode: 'admin_locked' }];
43
+ assert.equal(findPolicy(rows, 3).mode, 'admin_locked');
44
+ assert.equal(findPolicy(rows, undefined).teamId, 0);
45
+ assert.equal(findPolicy(null, 1), undefined);
46
+ assert.match(describeMode('admin_locked'), /locked/);
47
+ assert.match(describeMode(null), /follows/);
48
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@velaro/cli",
3
- "version": "1.4.102",
3
+ "version": "1.4.104",
4
4
  "description": "Velaro Workspace v20 — command-line interface for managing bots, knowledge base ingestion, MCP API keys, search indexes, and ops health.",
5
5
  "type": "module",
6
6
  "bin": {