@commissionsight/cli 0.1.0

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 (92) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +248 -0
  3. package/bin/cs.mjs +2 -0
  4. package/dist/commands/admin.d.ts +7 -0
  5. package/dist/commands/admin.js +409 -0
  6. package/dist/commands/auth.d.ts +7 -0
  7. package/dist/commands/auth.js +107 -0
  8. package/dist/commands/batch.d.ts +2 -0
  9. package/dist/commands/batch.js +68 -0
  10. package/dist/commands/billing.d.ts +6 -0
  11. package/dist/commands/billing.js +75 -0
  12. package/dist/commands/carrier.d.ts +6 -0
  13. package/dist/commands/carrier.js +111 -0
  14. package/dist/commands/completion.d.ts +6 -0
  15. package/dist/commands/completion.js +56 -0
  16. package/dist/commands/context.d.ts +6 -0
  17. package/dist/commands/context.js +73 -0
  18. package/dist/commands/file.d.ts +6 -0
  19. package/dist/commands/file.js +97 -0
  20. package/dist/commands/job.d.ts +2 -0
  21. package/dist/commands/job.js +186 -0
  22. package/dist/commands/member.d.ts +5 -0
  23. package/dist/commands/member.js +91 -0
  24. package/dist/commands/meta.d.ts +7 -0
  25. package/dist/commands/meta.js +36 -0
  26. package/dist/commands/rate.d.ts +5 -0
  27. package/dist/commands/rate.js +69 -0
  28. package/dist/commands/registry.d.ts +14 -0
  29. package/dist/commands/registry.js +56 -0
  30. package/dist/commands/report.d.ts +2 -0
  31. package/dist/commands/report.js +168 -0
  32. package/dist/commands/session.d.ts +5 -0
  33. package/dist/commands/session.js +21 -0
  34. package/dist/commands/team.d.ts +5 -0
  35. package/dist/commands/team.js +61 -0
  36. package/dist/commands/upload.d.ts +85 -0
  37. package/dist/commands/upload.js +111 -0
  38. package/dist/commands/webhook.d.ts +5 -0
  39. package/dist/commands/webhook.js +56 -0
  40. package/dist/commands/workspace.d.ts +8 -0
  41. package/dist/commands/workspace.js +65 -0
  42. package/dist/config/schema.d.ts +21 -0
  43. package/dist/config/schema.js +33 -0
  44. package/dist/config/store.d.ts +17 -0
  45. package/dist/config/store.js +74 -0
  46. package/dist/context.d.ts +22 -0
  47. package/dist/context.js +100 -0
  48. package/dist/errors.d.ts +37 -0
  49. package/dist/errors.js +70 -0
  50. package/dist/globals.d.ts +10 -0
  51. package/dist/globals.js +38 -0
  52. package/dist/io.d.ts +28 -0
  53. package/dist/io.js +28 -0
  54. package/dist/lib/batch.d.ts +52 -0
  55. package/dist/lib/batch.js +0 -0
  56. package/dist/lib/confirm.d.ts +2 -0
  57. package/dist/lib/confirm.js +23 -0
  58. package/dist/lib/file.d.ts +6 -0
  59. package/dist/lib/file.js +43 -0
  60. package/dist/lib/input.d.ts +2 -0
  61. package/dist/lib/input.js +35 -0
  62. package/dist/lib/paginate.d.ts +33 -0
  63. package/dist/lib/paginate.js +47 -0
  64. package/dist/lib/period.d.ts +15 -0
  65. package/dist/lib/period.js +43 -0
  66. package/dist/lib/poll.d.ts +14 -0
  67. package/dist/lib/poll.js +17 -0
  68. package/dist/lib/resolve.d.ts +30 -0
  69. package/dist/lib/resolve.js +81 -0
  70. package/dist/main.d.ts +1 -0
  71. package/dist/main.js +17 -0
  72. package/dist/output/color.d.ts +26 -0
  73. package/dist/output/color.js +37 -0
  74. package/dist/output/csv.d.ts +25 -0
  75. package/dist/output/csv.js +119 -0
  76. package/dist/output/envelope.d.ts +29 -0
  77. package/dist/output/envelope.js +66 -0
  78. package/dist/output/help.d.ts +7 -0
  79. package/dist/output/help.js +57 -0
  80. package/dist/output/print.d.ts +14 -0
  81. package/dist/output/print.js +70 -0
  82. package/dist/output/schema-tree.d.ts +32 -0
  83. package/dist/output/schema-tree.js +33 -0
  84. package/dist/router.d.ts +6 -0
  85. package/dist/router.js +267 -0
  86. package/dist/types.d.ts +66 -0
  87. package/dist/types.js +1 -0
  88. package/dist/util.d.ts +11 -0
  89. package/dist/util.js +39 -0
  90. package/dist/version.d.ts +2 -0
  91. package/dist/version.js +41 -0
  92. package/package.json +53 -0
@@ -0,0 +1,75 @@
1
+ import { cover } from './registry.js';
2
+ import { makePrinter } from '../output/print.js';
3
+ import { optStr } from '../util.js';
4
+ const usd = (cents) => `$${(cents / 100).toFixed(2)}`;
5
+ export function billingCommands() {
6
+ cover('getBilling', 'cs billing get');
7
+ cover('updateBilling', 'cs billing update');
8
+ cover('billingPreview', 'cs billing preview');
9
+ return [
10
+ {
11
+ path: ['billing', 'get'],
12
+ summary: 'Get the billing profile',
13
+ async run(ctx) {
14
+ return ctx.client().getBilling();
15
+ },
16
+ },
17
+ {
18
+ path: ['billing', 'preview'],
19
+ summary: 'Preview the upcoming invoice (amounts in USD)',
20
+ async run(ctx) {
21
+ return ctx.client().billingPreview();
22
+ },
23
+ render(data, ctx) {
24
+ const b = data;
25
+ const p = makePrinter(ctx.io, ctx.globals.color);
26
+ p.kv({
27
+ period: b.period ?? '—',
28
+ members: b.members,
29
+ pricePerMember: usd(b.pricePerMemberCents),
30
+ amount: usd(b.amountCents),
31
+ method: b.method,
32
+ fee: usd(b.feeCents),
33
+ total: usd(b.totalCents),
34
+ achSavings: usd(b.achSavingsCents),
35
+ dueDate: b.dueDate ?? '—',
36
+ });
37
+ },
38
+ },
39
+ {
40
+ path: ['billing', 'update'],
41
+ summary: 'Update billing contact/address details',
42
+ options: {
43
+ 'contact-name': { type: 'string', desc: 'Contact name', placeholder: '<name>' },
44
+ company: { type: 'string', desc: 'Company name', placeholder: '<name>' },
45
+ email: { type: 'string', desc: 'Billing email', placeholder: '<email>' },
46
+ phone: { type: 'string', desc: 'Phone', placeholder: '<phone>' },
47
+ 'address-line1': { type: 'string', desc: 'Address line 1', placeholder: '<addr>' },
48
+ 'address-line2': { type: 'string', desc: 'Address line 2', placeholder: '<addr>' },
49
+ city: { type: 'string', desc: 'City', placeholder: '<city>' },
50
+ state: { type: 'string', desc: 'State', placeholder: '<state>' },
51
+ 'postal-code': { type: 'string', desc: 'Postal code', placeholder: '<zip>' },
52
+ country: { type: 'string', desc: 'Country', placeholder: '<country>' },
53
+ },
54
+ async run(ctx, parsed) {
55
+ const o = parsed.options;
56
+ const body = {
57
+ ...set('contactName', optStr(o['contact-name'])),
58
+ ...set('companyName', optStr(o['company'])),
59
+ ...set('email', optStr(o['email'])),
60
+ ...set('phone', optStr(o['phone'])),
61
+ ...set('addressLine1', optStr(o['address-line1'])),
62
+ ...set('addressLine2', optStr(o['address-line2'])),
63
+ ...set('city', optStr(o['city'])),
64
+ ...set('state', optStr(o['state'])),
65
+ ...set('postalCode', optStr(o['postal-code'])),
66
+ ...set('country', optStr(o['country'])),
67
+ };
68
+ return ctx.client().updateBilling(body);
69
+ },
70
+ },
71
+ ];
72
+ }
73
+ function set(key, value) {
74
+ return value === undefined ? {} : { [key]: value };
75
+ }
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Carrier + carrier-config commands (plan §6.2). Carrier refs accept
3
+ * id|slug|name via the resolver.
4
+ */
5
+ import type { Cmd } from '../types.js';
6
+ export declare function carrierCommands(): Cmd[];
@@ -0,0 +1,111 @@
1
+ import { cover } from './registry.js';
2
+ import { resolveCarrier } from '../lib/resolve.js';
3
+ import { loadFile } from '../lib/file.js';
4
+ import { readJsonInput } from '../lib/input.js';
5
+ import { UsageError } from '../errors.js';
6
+ import { optStr, optBool } from '../util.js';
7
+ export function carrierCommands() {
8
+ cover('listCarriers', 'cs carrier list');
9
+ cover('getCarrier', 'cs carrier get');
10
+ cover('listConfigs', 'cs carrier config list');
11
+ cover('getConfigVersion', 'cs carrier config get');
12
+ cover('createConfig', 'cs carrier config create');
13
+ cover('testConfig', 'cs carrier config test');
14
+ cover('inferConfig', 'cs carrier config infer');
15
+ return [
16
+ {
17
+ path: ['carrier', 'list'],
18
+ summary: 'List carriers',
19
+ options: {
20
+ 'with-config': { type: 'boolean', desc: 'Include each carrier’s active config' },
21
+ },
22
+ async run(ctx, parsed) {
23
+ const page = await ctx.client().listCarriers({ withConfig: optBool(parsed.options['with-config']) });
24
+ return page.data;
25
+ },
26
+ },
27
+ {
28
+ path: ['carrier', 'get'],
29
+ summary: 'Get a carrier by id, slug, or name',
30
+ args: [{ name: 'carrier', required: true }],
31
+ async run(ctx, parsed) {
32
+ const id = await resolveCarrier(ctx, parsed.args[0]);
33
+ return ctx.client().getCarrier(id);
34
+ },
35
+ },
36
+ {
37
+ path: ['carrier', 'config', 'list'],
38
+ summary: 'List a carrier’s config versions',
39
+ args: [{ name: 'carrier', required: true }],
40
+ async run(ctx, parsed) {
41
+ const id = await resolveCarrier(ctx, parsed.args[0]);
42
+ const page = await ctx.client().listConfigs(id);
43
+ return page.data;
44
+ },
45
+ },
46
+ {
47
+ path: ['carrier', 'config', 'get'],
48
+ summary: 'Get a specific carrier config version',
49
+ args: [
50
+ { name: 'carrier', required: true },
51
+ { name: 'version', required: true },
52
+ ],
53
+ async run(ctx, parsed) {
54
+ const id = await resolveCarrier(ctx, parsed.args[0]);
55
+ const version = Number(parsed.args[1]);
56
+ if (!Number.isInteger(version))
57
+ throw new UsageError(`invalid version: ${parsed.args[1]}`);
58
+ return ctx.client().getConfigVersion(id, version);
59
+ },
60
+ },
61
+ {
62
+ path: ['carrier', 'config', 'create'],
63
+ summary: 'Create an account-scoped carrier config (-f file.json or stdin)',
64
+ args: [{ name: 'carrier', required: true }],
65
+ options: {
66
+ file: { type: 'string', short: 'f', desc: 'Config JSON file (else stdin)', placeholder: '<config.json>' },
67
+ },
68
+ async run(ctx, parsed) {
69
+ const id = await resolveCarrier(ctx, parsed.args[0]);
70
+ const config = readJsonInput(optStr(parsed.options['file']));
71
+ return ctx.client().createConfig(id, config);
72
+ },
73
+ },
74
+ {
75
+ path: ['carrier', 'config', 'test'],
76
+ summary: 'Dry-run a config against a sample file (maps + previews)',
77
+ args: [{ name: 'carrier', required: true }],
78
+ options: {
79
+ config: { type: 'string', short: 'f', desc: 'Config JSON file (else stdin)', placeholder: '<config.json>' },
80
+ file: { type: 'string', desc: 'Sample statement file', placeholder: '<sample>' },
81
+ },
82
+ async run(ctx, parsed) {
83
+ const id = await resolveCarrier(ctx, parsed.args[0]);
84
+ const samplePath = optStr(parsed.options['file']);
85
+ if (!samplePath)
86
+ throw new UsageError('--file <sample> is required');
87
+ const config = readJsonInput(optStr(parsed.options['config']));
88
+ const file = loadFile(samplePath);
89
+ return ctx.client().testConfig(id, config, file);
90
+ },
91
+ },
92
+ {
93
+ path: ['carrier', 'config', 'infer'],
94
+ summary: 'Infer a draft config from a sample file',
95
+ args: [{ name: 'carrier', required: true }],
96
+ options: {
97
+ file: { type: 'string', desc: 'Sample statement file', placeholder: '<sample>' },
98
+ sheet: { type: 'string', desc: 'Sheet name (xlsx)', placeholder: '<name>' },
99
+ },
100
+ async run(ctx, parsed) {
101
+ const id = await resolveCarrier(ctx, parsed.args[0]);
102
+ const samplePath = optStr(parsed.options['file']);
103
+ if (!samplePath)
104
+ throw new UsageError('--file <sample> is required');
105
+ const file = loadFile(samplePath);
106
+ const sheetName = optStr(parsed.options['sheet']);
107
+ return ctx.client().inferConfig(id, file, sheetName ? { sheetName } : undefined);
108
+ },
109
+ },
110
+ ];
111
+ }
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Shell completion (plan §6.13) generated from the command table. Nice-to-have:
3
+ * completes top-level groups + subcommands.
4
+ */
5
+ import type { Cmd } from '../types.js';
6
+ export declare function completionCommands(getTable: () => Cmd[]): Cmd[];
@@ -0,0 +1,56 @@
1
+ import { UsageError } from '../errors.js';
2
+ export function completionCommands(getTable) {
3
+ return [
4
+ {
5
+ path: ['completion'],
6
+ summary: 'Print a shell completion script (bash|zsh|fish)',
7
+ args: [{ name: 'shell', required: true }],
8
+ async run(ctx, parsed) {
9
+ const shell = parsed.args[0];
10
+ const commands = [...new Set(getTable().map((c) => c.path.join(' ')))].sort();
11
+ const words = [...new Set(getTable().flatMap((c) => c.path))].sort();
12
+ let script;
13
+ switch (shell) {
14
+ case 'bash':
15
+ script = bash(words);
16
+ break;
17
+ case 'zsh':
18
+ script = zsh(commands);
19
+ break;
20
+ case 'fish':
21
+ script = fish(commands);
22
+ break;
23
+ default:
24
+ throw new UsageError(`unsupported shell: ${shell} (expected bash|zsh|fish)`);
25
+ }
26
+ ctx.io.stdout(script.endsWith('\n') ? script : script + '\n');
27
+ return undefined;
28
+ },
29
+ },
30
+ ];
31
+ }
32
+ function bash(words) {
33
+ return `# cs bash completion — source this file or add to ~/.bashrc
34
+ _cs_complete() {
35
+ local cur="\${COMP_WORDS[COMP_CWORD]}"
36
+ COMPREPLY=( $(compgen -W "${words.join(' ')} --help --json --version" -- "$cur") )
37
+ }
38
+ complete -F _cs_complete cs commissionsight`;
39
+ }
40
+ function zsh(commands) {
41
+ const lines = commands.map((c) => ` '${c.replace(/ /g, ':')}'`).join('\n');
42
+ return `#compdef cs commissionsight
43
+ # cs zsh completion — add to a directory on your $fpath
44
+ _cs() {
45
+ local -a cmds
46
+ cmds=(
47
+ ${lines}
48
+ )
49
+ _describe 'command' cmds
50
+ }
51
+ _cs "$@"`;
52
+ }
53
+ function fish(commands) {
54
+ return ('# cs fish completion — save to ~/.config/fish/completions/cs.fish\n' +
55
+ commands.map((c) => `complete -c cs -a '${c}'`).join('\n'));
56
+ }
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Context (multi-environment) commands (plan §4.4). A context bundles a base
3
+ * URL + optional token + default workspace/carrier. `current` selects one.
4
+ */
5
+ import type { Cmd } from '../types.js';
6
+ export declare function contextCommands(): Cmd[];
@@ -0,0 +1,73 @@
1
+ import { loadConfig, saveConfig, upsertContext } from '../config/store.js';
2
+ import { DEFAULT_BASE_URL, resolveExplicitToken } from '../context.js';
3
+ import { UsageError } from '../errors.js';
4
+ import { maskToken } from '../util.js';
5
+ export function contextCommands() {
6
+ return [
7
+ {
8
+ path: ['context', 'list'],
9
+ summary: 'List configured contexts',
10
+ async run(ctx) {
11
+ const config = loadConfig(ctx.io);
12
+ return Object.entries(config.contexts).map(([name, c]) => ({
13
+ current: name === config.current,
14
+ name,
15
+ baseUrl: c.baseUrl || DEFAULT_BASE_URL,
16
+ token: maskToken(c.token),
17
+ workspace: c.defaultWorkspaceName ?? c.defaultWorkspaceId ?? null,
18
+ }));
19
+ },
20
+ },
21
+ {
22
+ path: ['context', 'current'],
23
+ summary: 'Print the active context name',
24
+ async run(ctx) {
25
+ return { current: loadConfig(ctx.io).current };
26
+ },
27
+ },
28
+ {
29
+ path: ['context', 'use'],
30
+ summary: 'Switch the active context',
31
+ args: [{ name: 'name', required: true }],
32
+ async run(ctx, parsed) {
33
+ const name = parsed.args[0];
34
+ const config = loadConfig(ctx.io);
35
+ if (!config.contexts[name]) {
36
+ throw new UsageError(`unknown context: ${name}`, Object.keys(config.contexts).map((n) => `cs context use ${n}`));
37
+ }
38
+ config.current = name;
39
+ saveConfig(ctx.io, config);
40
+ return { current: name };
41
+ },
42
+ },
43
+ {
44
+ path: ['context', 'add'],
45
+ summary: 'Add a context (--base-url, optional token via --token-stdin)',
46
+ args: [{ name: 'name', required: true }],
47
+ async run(ctx, parsed) {
48
+ const name = parsed.args[0];
49
+ const baseUrl = ctx.globals.baseUrl ?? DEFAULT_BASE_URL;
50
+ const token = resolveExplicitToken(ctx.globals, ctx.io);
51
+ upsertContext(ctx.io, name, { baseUrl, ...(token ? { token } : {}) });
52
+ return { name, baseUrl, token: maskToken(token) };
53
+ },
54
+ },
55
+ {
56
+ path: ['context', 'remove'],
57
+ summary: 'Remove a context',
58
+ args: [{ name: 'name', required: true }],
59
+ async run(ctx, parsed) {
60
+ const name = parsed.args[0];
61
+ const config = loadConfig(ctx.io);
62
+ if (!config.contexts[name])
63
+ throw new UsageError(`unknown context: ${name}`);
64
+ delete config.contexts[name];
65
+ if (config.current === name) {
66
+ config.current = Object.keys(config.contexts)[0] ?? 'default';
67
+ }
68
+ saveConfig(ctx.io, config);
69
+ return { removed: name, current: config.current };
70
+ },
71
+ },
72
+ ];
73
+ }
@@ -0,0 +1,6 @@
1
+ /**
2
+ * File commands (plan §6.4). listFiles uses cursor pagination; retract/purge are
3
+ * destructive and gated by --yes. rescore-stale is a convenience sweep.
4
+ */
5
+ import type { Cmd } from '../types.js';
6
+ export declare function fileCommands(): Cmd[];
@@ -0,0 +1,97 @@
1
+ import { cover } from './registry.js';
2
+ import { resolveCarrier } from '../lib/resolve.js';
3
+ import { collectCursor } from '../lib/paginate.js';
4
+ import { confirmDestructive } from '../lib/confirm.js';
5
+ import { optStr, optBool, optNum } from '../util.js';
6
+ export function fileCommands() {
7
+ cover('listFiles', 'cs file list');
8
+ cover('getFile', 'cs file get');
9
+ cover('rescoreFile', 'cs file rescore / cs file rescore-stale');
10
+ cover('retractFile', 'cs file retract');
11
+ cover('purgeFile', 'cs file purge');
12
+ return [
13
+ {
14
+ path: ['file', 'list'],
15
+ summary: 'List uploaded files (cursor-paginated)',
16
+ options: {
17
+ carrier: { type: 'string', desc: 'Filter by carrier id|slug|name', placeholder: '<c>' },
18
+ limit: { type: 'string', desc: 'Page size', placeholder: '<n>' },
19
+ cursor: { type: 'string', desc: 'Start cursor', placeholder: '<n>' },
20
+ all: { type: 'boolean', desc: 'Auto-paginate all pages' },
21
+ },
22
+ async run(ctx, parsed) {
23
+ const carrierId = optStr(parsed.options['carrier'])
24
+ ? await resolveCarrier(ctx, optStr(parsed.options['carrier']))
25
+ : undefined;
26
+ const limit = optNum(parsed.options['limit']);
27
+ if (optBool(parsed.options['all'])) {
28
+ const { items, truncated } = await collectCursor((p) => ctx.client().listFiles({ ...(carrierId ? { carrierId } : {}), ...p }), limit !== undefined ? { limit } : {});
29
+ if (truncated)
30
+ ctx.log('warning: page cap reached; results truncated');
31
+ return items;
32
+ }
33
+ const page = await ctx.client().listFiles({
34
+ ...(carrierId ? { carrierId } : {}),
35
+ ...(limit !== undefined ? { limit } : {}),
36
+ ...(optNum(parsed.options['cursor']) !== undefined ? { cursor: optNum(parsed.options['cursor']) } : {}),
37
+ });
38
+ return page.data;
39
+ },
40
+ },
41
+ {
42
+ path: ['file', 'get'],
43
+ summary: 'Get a file by id',
44
+ args: [{ name: 'fileId', required: true }],
45
+ async run(ctx, parsed) {
46
+ return ctx.client().getFile(parsed.args[0]);
47
+ },
48
+ },
49
+ {
50
+ path: ['file', 'rescore'],
51
+ summary: 'Re-score a file’s period without re-uploading',
52
+ args: [{ name: 'fileId', required: true }],
53
+ async run(ctx, parsed) {
54
+ return ctx.client().rescoreFile(parsed.args[0]);
55
+ },
56
+ },
57
+ {
58
+ path: ['file', 'retract'],
59
+ summary: 'Retract (unapply) a file’s carrier+period (destructive)',
60
+ args: [{ name: 'fileId', required: true }],
61
+ async run(ctx, parsed) {
62
+ await confirmDestructive(ctx, `retract file ${parsed.args[0]}`);
63
+ return ctx.client().retractFile(parsed.args[0]);
64
+ },
65
+ },
66
+ {
67
+ path: ['file', 'purge'],
68
+ summary: 'Purge a file’s raw bytes from storage (destructive)',
69
+ args: [{ name: 'fileId', required: true }],
70
+ async run(ctx, parsed) {
71
+ await confirmDestructive(ctx, `purge file ${parsed.args[0]}`);
72
+ return ctx.client().purgeFile(parsed.args[0]);
73
+ },
74
+ },
75
+ {
76
+ path: ['file', 'rescore-stale'],
77
+ summary: 'Rescore every file flagged rescoreSuggested',
78
+ options: {
79
+ carrier: { type: 'string', desc: 'Limit to a carrier', placeholder: '<c>' },
80
+ },
81
+ async run(ctx, parsed) {
82
+ const carrierId = optStr(parsed.options['carrier'])
83
+ ? await resolveCarrier(ctx, optStr(parsed.options['carrier']))
84
+ : undefined;
85
+ const { items } = await collectCursor((p) => ctx.client().listFiles({ ...(carrierId ? { carrierId } : {}), ...p }));
86
+ const stale = items.filter((f) => f.rescoreSuggested);
87
+ const rescored = [];
88
+ for (const f of stale) {
89
+ const res = await ctx.client().rescoreFile(f.id);
90
+ rescored.push({ fileId: f.id, jobId: res.jobId });
91
+ ctx.log(` rescored ${f.id} → job ${res.jobId}`);
92
+ }
93
+ return { staleCount: stale.length, rescored };
94
+ },
95
+ },
96
+ ];
97
+ }
@@ -0,0 +1,2 @@
1
+ import type { Cmd } from '../types.js';
2
+ export declare function jobCommands(): Cmd[];
@@ -0,0 +1,186 @@
1
+ /**
2
+ * Job commands (plan §6.5): list/get/wait/results/deltas/exceptions/retry.
3
+ */
4
+ import { writeFileSync } from 'node:fs';
5
+ import { cover } from './registry.js';
6
+ import { resolveCarrier } from '../lib/resolve.js';
7
+ import { parsePeriod } from '../lib/period.js';
8
+ import { waitForJob } from '../lib/poll.js';
9
+ import { collectOffset } from '../lib/paginate.js';
10
+ import { emitCSV, RESULT_ROW_COLUMNS } from '../output/csv.js';
11
+ import { ApiError, CliError, EXIT } from '../errors.js';
12
+ import { optStr, optBool, optNum } from '../util.js';
13
+ export function jobCommands() {
14
+ cover('listJobs', 'cs job list');
15
+ cover('getJob', 'cs job get / cs job wait');
16
+ cover('getJobDeltas', 'cs job deltas');
17
+ cover('downloadExceptions', 'cs job exceptions');
18
+ cover('retryJob', 'cs job retry');
19
+ return [
20
+ {
21
+ path: ['job', 'list'],
22
+ summary: 'List jobs (filter by status/carrier/period)',
23
+ options: {
24
+ status: { type: 'string', desc: 'Filter by status', placeholder: '<s>' },
25
+ carrier: { type: 'string', desc: 'Filter by carrier', placeholder: '<c>' },
26
+ period: { type: 'string', desc: 'Filter by period YYYY-MM', placeholder: '<YYYY-MM>' },
27
+ limit: { type: 'string', desc: 'Page size', placeholder: '<n>' },
28
+ offset: { type: 'string', desc: 'Offset', placeholder: '<n>' },
29
+ all: { type: 'boolean', desc: 'Auto-paginate all pages' },
30
+ },
31
+ async run(ctx, parsed) {
32
+ const params = await jobListParams(ctx, parsed);
33
+ if (optBool(parsed.options['all'])) {
34
+ const { items } = await collectOffset((p) => ctx.client().listJobs({ ...params, ...p }));
35
+ return items;
36
+ }
37
+ const page = await ctx.client().listJobs({
38
+ ...params,
39
+ ...(optNum(parsed.options['limit']) !== undefined ? { limit: optNum(parsed.options['limit']) } : {}),
40
+ ...(optNum(parsed.options['offset']) !== undefined ? { offset: optNum(parsed.options['offset']) } : {}),
41
+ });
42
+ return page.data;
43
+ },
44
+ },
45
+ {
46
+ path: ['job', 'get'],
47
+ summary: 'Get a job by id',
48
+ args: [{ name: 'jobId', required: true }],
49
+ async run(ctx, parsed) {
50
+ return ctx.client().getJob(parsed.args[0]);
51
+ },
52
+ },
53
+ {
54
+ path: ['job', 'wait'],
55
+ summary: 'Poll a job to completed/failed (124 on timeout, 1 on failed)',
56
+ args: [{ name: 'jobId', required: true }],
57
+ options: {
58
+ timeout: { type: 'string', desc: 'Timeout seconds (default 300)', placeholder: '<s>' },
59
+ interval: { type: 'string', desc: 'Poll interval seconds (default 2)', placeholder: '<s>' },
60
+ },
61
+ async run(ctx, parsed) {
62
+ const job = await waitForJob(ctx.client(), parsed.args[0], {
63
+ timeoutMs: (optNum(parsed.options['timeout']) ?? 300) * 1000,
64
+ intervalMs: (optNum(parsed.options['interval']) ?? 2) * 1000,
65
+ onTick: (j) => ctx.log(` ${parsed.args[0]}: ${j.status}`),
66
+ });
67
+ if (job.status === 'failed') {
68
+ throw new CliError(`job failed: ${job.error ?? 'unknown error'}`, EXIT.ERROR);
69
+ }
70
+ return job;
71
+ },
72
+ },
73
+ {
74
+ path: ['job', 'results'],
75
+ summary: 'Scored rows for a job',
76
+ args: [{ name: 'jobId', required: true }],
77
+ options: {
78
+ status: { type: 'string', desc: 'Filter by status', choices: ['green', 'yellow', 'red'], placeholder: '<s>' },
79
+ 'owed-only': { type: 'boolean', desc: 'Only rows with commission owed' },
80
+ chargeback: { type: 'boolean', desc: 'Only chargeback rows' },
81
+ limit: { type: 'string', desc: 'Page size', placeholder: '<n>' },
82
+ offset: { type: 'string', desc: 'Offset', placeholder: '<n>' },
83
+ all: { type: 'boolean', desc: 'Auto-paginate all pages' },
84
+ csv: { type: 'boolean', desc: 'Emit CSV' },
85
+ output: { type: 'string', short: 'o', desc: 'Write CSV to a file', placeholder: '<file>' },
86
+ },
87
+ async run(ctx, parsed) {
88
+ const jobId = parsed.args[0];
89
+ const o = parsed.options;
90
+ const filters = {
91
+ ...(optStr(o['status']) ? { status: optStr(o['status']) } : {}),
92
+ ...(optBool(o['owed-only']) ? { owedOnly: true } : {}),
93
+ ...(optBool(o['chargeback']) ? { chargeback: true } : {}),
94
+ };
95
+ let rows;
96
+ if (optBool(o['all'])) {
97
+ const { items } = await collectOffset((p) => ctx.client().getJobResults(jobId, { ...filters, ...p }));
98
+ rows = items;
99
+ }
100
+ else {
101
+ const page = await ctx.client().getJobResults(jobId, {
102
+ ...filters,
103
+ ...(optNum(o['limit']) !== undefined ? { limit: optNum(o['limit']) } : {}),
104
+ ...(optNum(o['offset']) !== undefined ? { offset: optNum(o['offset']) } : {}),
105
+ });
106
+ rows = page.data;
107
+ }
108
+ if (optBool(o['csv']) && !ctx.globals.json) {
109
+ return emitCSV(ctx.io, rows, RESULT_ROW_COLUMNS, optStr(o['output']), (p, d) => writeFileSync(p, d));
110
+ }
111
+ return rows;
112
+ },
113
+ },
114
+ {
115
+ path: ['job', 'deltas'],
116
+ summary: 'Field-level deltas for a job',
117
+ args: [{ name: 'jobId', required: true }],
118
+ options: {
119
+ member: { type: 'string', desc: 'Filter by member ref id', placeholder: '<id>' },
120
+ 'change-type': { type: 'string', desc: 'Filter by change type', placeholder: '<t>' },
121
+ all: { type: 'boolean', desc: '(accepted; endpoint returns a single page)' },
122
+ },
123
+ async run(ctx, parsed) {
124
+ const page = await ctx.client().getJobDeltas(parsed.args[0], {
125
+ ...(optStr(parsed.options['member']) ? { memberRefId: optStr(parsed.options['member']) } : {}),
126
+ ...(optStr(parsed.options['change-type']) ? { changeType: optStr(parsed.options['change-type']) } : {}),
127
+ });
128
+ return page.data;
129
+ },
130
+ },
131
+ {
132
+ path: ['job', 'exceptions'],
133
+ summary: 'Download a job’s rejected-rows CSV (or 404 if purged/none)',
134
+ args: [{ name: 'jobId', required: true }],
135
+ options: {
136
+ output: { type: 'string', short: 'o', desc: 'Write CSV to a file', placeholder: '<file>' },
137
+ },
138
+ async run(ctx, parsed) {
139
+ const jobId = parsed.args[0];
140
+ let csv;
141
+ try {
142
+ csv = await ctx.client().downloadExceptions(jobId);
143
+ }
144
+ catch (e) {
145
+ if (e instanceof ApiError && e.status === 404) {
146
+ throw new CliError(`no exception file for job ${jobId} (purged or none)`, EXIT.NOTFOUND);
147
+ }
148
+ throw e;
149
+ }
150
+ const out = optStr(parsed.options['output']);
151
+ if (ctx.globals.json)
152
+ return { jobId, csv };
153
+ if (out) {
154
+ writeFileSync(out, csv);
155
+ return { jobId, out, bytes: csv.length };
156
+ }
157
+ ctx.io.stdout(csv.endsWith('\n') ? csv : csv + '\n');
158
+ return undefined;
159
+ },
160
+ },
161
+ {
162
+ path: ['job', 'retry'],
163
+ summary: 'Retry a job',
164
+ args: [{ name: 'jobId', required: true }],
165
+ async run(ctx, parsed) {
166
+ return ctx.client().retryJob(parsed.args[0]);
167
+ },
168
+ },
169
+ ];
170
+ }
171
+ async function jobListParams(ctx, parsed) {
172
+ const params = {};
173
+ const status = optStr(parsed.options['status']);
174
+ if (status)
175
+ params.status = status;
176
+ const carrier = optStr(parsed.options['carrier']);
177
+ if (carrier)
178
+ params.carrierId = await resolveCarrier(ctx, carrier);
179
+ const period = optStr(parsed.options['period']);
180
+ if (period) {
181
+ const p = parsePeriod(period);
182
+ params.periodYear = p.periodYear;
183
+ params.periodMonth = p.periodMonth;
184
+ }
185
+ return params;
186
+ }
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Member & policy commands (plan §6.6).
3
+ */
4
+ import type { Cmd } from '../types.js';
5
+ export declare function memberCommands(): Cmd[];