@ciphrix/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.
- package/LICENSE +201 -0
- package/README.md +201 -0
- package/dist/actions.d.ts +19 -0
- package/dist/actions.js +30 -0
- package/dist/api.d.ts +27 -0
- package/dist/api.js +180 -0
- package/dist/banner.d.ts +2 -0
- package/dist/banner.js +12 -0
- package/dist/browser.d.ts +2 -0
- package/dist/browser.js +44 -0
- package/dist/cli.d.ts +14 -0
- package/dist/cli.js +1579 -0
- package/dist/commands/asset.d.ts +59 -0
- package/dist/commands/asset.js +237 -0
- package/dist/commands/check.d.ts +30 -0
- package/dist/commands/check.js +182 -0
- package/dist/commands/context.d.ts +23 -0
- package/dist/commands/context.js +102 -0
- package/dist/commands/control.d.ts +44 -0
- package/dist/commands/control.js +189 -0
- package/dist/commands/framework.d.ts +40 -0
- package/dist/commands/framework.js +181 -0
- package/dist/commands/job.d.ts +16 -0
- package/dist/commands/job.js +30 -0
- package/dist/commands/login.d.ts +12 -0
- package/dist/commands/login.js +54 -0
- package/dist/commands/logout.d.ts +9 -0
- package/dist/commands/logout.js +56 -0
- package/dist/commands/policy.d.ts +23 -0
- package/dist/commands/policy.js +112 -0
- package/dist/commands/policyActions.d.ts +37 -0
- package/dist/commands/policyActions.js +177 -0
- package/dist/commands/risk.d.ts +51 -0
- package/dist/commands/risk.js +289 -0
- package/dist/commands/test.d.ts +54 -0
- package/dist/commands/test.js +331 -0
- package/dist/commands/vendor.d.ts +45 -0
- package/dist/commands/vendor.js +218 -0
- package/dist/config.d.ts +12 -0
- package/dist/config.js +56 -0
- package/dist/constants.d.ts +7 -0
- package/dist/constants.js +7 -0
- package/dist/credentials.d.ts +19 -0
- package/dist/credentials.js +182 -0
- package/dist/deviceFlow.d.ts +32 -0
- package/dist/deviceFlow.js +115 -0
- package/dist/deviceInfo.d.ts +11 -0
- package/dist/deviceInfo.js +25 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +4 -0
- package/dist/io.d.ts +46 -0
- package/dist/io.js +99 -0
- package/dist/output.d.ts +14 -0
- package/dist/output.js +37 -0
- package/dist/theme.d.ts +25 -0
- package/dist/theme.js +39 -0
- package/dist/toolSurface.d.ts +20 -0
- package/dist/toolSurface.js +49 -0
- package/dist/upload.d.ts +18 -0
- package/dist/upload.js +115 -0
- package/package.json +71 -0
- package/skills/ciphrix/SKILL.md +169 -0
- package/skills/ciphrix/references/commands.md +1592 -0
package/dist/cli.js
ADDED
|
@@ -0,0 +1,1579 @@
|
|
|
1
|
+
import { Command, CommanderError } from 'commander';
|
|
2
|
+
import { formatBanner } from './banner.js';
|
|
3
|
+
import { loginCommand } from './commands/login.js';
|
|
4
|
+
import { logoutCommand } from './commands/logout.js';
|
|
5
|
+
import { clauseAvailableItemsCommand, clauseGetCommand, clauseItemsCommand, clauseLinkCommand, clauseUpdateCommand, frameworkClausesCommand, frameworkListCommand, } from './commands/framework.js';
|
|
6
|
+
import { vendorAttachCommand, vendorCreateCommand, vendorDeleteCommand, vendorDetachCommand, vendorFilesCommand, vendorGetCommand, vendorListCommand, vendorUpdateCommand, } from './commands/vendor.js';
|
|
7
|
+
import { jobStatusCommand } from './commands/job.js';
|
|
8
|
+
import { assetCreateCommand, assetDeleteCommand, assetGetCommand, assetLinksCommand, assetListCommand, assetTagCommand, assetTagsCommand, assetUpdateCommand, } from './commands/asset.js';
|
|
9
|
+
import { controlAvailableItemsCommand, controlGetCommand, controlItemsCommand, controlLinkCommand, controlListCommand, controlUpdateCommand, } from './commands/control.js';
|
|
10
|
+
import { testAttachCommand, testAvailableItemsCommand, testDeleteCommand, testGetCommand, testItemsCommand, testLinksCommand, testListCommand, testRunCreateCommand, testRunDeleteCommand, testRunResultCommand, testRunsCommand, testUpdateCommand, } from './commands/test.js';
|
|
11
|
+
import { contextGetCommand, contextResetCommand, contextSetCommand, contextStatusCommand, } from './commands/context.js';
|
|
12
|
+
import { policyGetCommand, policyListCommand } from './commands/policy.js';
|
|
13
|
+
import { policyApproveCommand, policyCreateCommand, policyDeleteCommand, policyEditCommand, policySubmitCommand, policyUpdateMetaCommand, policyVersionCommand, policyVersionsCommand, } from './commands/policyActions.js';
|
|
14
|
+
import { riskAttachCommand, riskControlsCommand, riskCreateCommand, riskDeleteCommand, riskDetachCommand, riskFilesCommand, riskGetCommand, riskLinkControlCommand, riskListCommand, riskScoresCommand, riskTreatmentCommand, riskUpdateCommand, } from './commands/risk.js';
|
|
15
|
+
import { checkFindingsCommand, checkIntegrationsCommand, checkListCommand, checkResourcesCommand, checkRunCommand, checkRunsCommand, checkSetEnabledCommand, } from './commands/check.js';
|
|
16
|
+
import { CLI_NAME, CLI_TAGLINE, CLI_VERSION } from './constants.js';
|
|
17
|
+
import { setCliInsecureHttpOptIn } from './config.js';
|
|
18
|
+
import { createDefaultIO, writeBlock, writeLine } from './io.js';
|
|
19
|
+
const readStringOption = (options, key) => {
|
|
20
|
+
if (!options || typeof options !== 'object')
|
|
21
|
+
return undefined;
|
|
22
|
+
const value = options[key];
|
|
23
|
+
return typeof value === 'string' ? value : undefined;
|
|
24
|
+
};
|
|
25
|
+
const readBooleanOption = (options, key) => {
|
|
26
|
+
if (!options || typeof options !== 'object')
|
|
27
|
+
return false;
|
|
28
|
+
return options[key] === true;
|
|
29
|
+
};
|
|
30
|
+
const readNumberOption = (options, key) => {
|
|
31
|
+
if (!options || typeof options !== 'object')
|
|
32
|
+
return undefined;
|
|
33
|
+
const value = options[key];
|
|
34
|
+
if (typeof value === 'number')
|
|
35
|
+
return value;
|
|
36
|
+
if (typeof value === 'string' && value.trim() !== '' && Number.isFinite(Number(value)))
|
|
37
|
+
return Number(value);
|
|
38
|
+
return undefined;
|
|
39
|
+
};
|
|
40
|
+
const apiUrlOf = (url, options) => url ?? readStringOption(options, 'apiUrl');
|
|
41
|
+
export const CONFIGURATION_HELP = `
|
|
42
|
+
Configuration:
|
|
43
|
+
--api-url <url> override the API base URL for one command
|
|
44
|
+
CIPHRIX_API_URL default API base URL for this process
|
|
45
|
+
CIPHRIX_CREDENTIAL_STORE=file use plaintext file credentials for local/headless testing
|
|
46
|
+
CIPHRIX_ALLOW_INSECURE_HTTP=true
|
|
47
|
+
permit HTTP for intentional local testing
|
|
48
|
+
|
|
49
|
+
Production and the OS keychain are used by default. No general config file is read.
|
|
50
|
+
See the README Configuration section for precedence and security details.
|
|
51
|
+
`;
|
|
52
|
+
/**
|
|
53
|
+
* Builds the command tree. Kept separate from `main` so tests can construct a
|
|
54
|
+
* program with captured output and no process exits.
|
|
55
|
+
*/
|
|
56
|
+
export const createProgram = (io = createDefaultIO()) => {
|
|
57
|
+
const program = new Command();
|
|
58
|
+
program
|
|
59
|
+
.name(CLI_NAME)
|
|
60
|
+
.description(CLI_TAGLINE)
|
|
61
|
+
.version(CLI_VERSION, '-v, --version', 'output the version number')
|
|
62
|
+
.option('--allow-insecure-http', 'allow plaintext HTTP for intentional local or non-production testing only')
|
|
63
|
+
.addHelpText('after', CONFIGURATION_HELP)
|
|
64
|
+
.showHelpAfterError()
|
|
65
|
+
.showSuggestionAfterError();
|
|
66
|
+
// This is a root option, so it works before or after the subcommand. Scope its
|
|
67
|
+
// effect to the action; URL validation still happens before command side effects.
|
|
68
|
+
program.hook('preAction', (_thisCommand, actionCommand) => {
|
|
69
|
+
setCliInsecureHttpOptIn(actionCommand.optsWithGlobals().allowInsecureHttp === true);
|
|
70
|
+
});
|
|
71
|
+
program.hook('postAction', () => setCliInsecureHttpOptIn(false));
|
|
72
|
+
program
|
|
73
|
+
.command('login [url]')
|
|
74
|
+
.description('Sign in to Ciphrix with device authorization')
|
|
75
|
+
.option('--api-url <url>', 'override the API base URL for this command')
|
|
76
|
+
.option('--device-name <name>', 'a name for this device (defaults to the machine name)')
|
|
77
|
+
.option('--no-open', 'do not open the verification link in a browser')
|
|
78
|
+
.action(async (url, options) => {
|
|
79
|
+
await loginCommand({
|
|
80
|
+
io,
|
|
81
|
+
apiUrl: apiUrlOf(url, options),
|
|
82
|
+
deviceName: readStringOption(options, 'deviceName'),
|
|
83
|
+
noOpen: readBooleanOption(options, 'noOpen') ||
|
|
84
|
+
(options !== null &&
|
|
85
|
+
typeof options === 'object' &&
|
|
86
|
+
options.open === false),
|
|
87
|
+
});
|
|
88
|
+
});
|
|
89
|
+
program
|
|
90
|
+
.command('logout')
|
|
91
|
+
.description('Revoke the remote session and remove the stored credential')
|
|
92
|
+
.option('--api-url <url>', 'override the API base URL for this command')
|
|
93
|
+
.option('--local-only', 'remove the local credential without revoking the remote session')
|
|
94
|
+
.action(async (options) => {
|
|
95
|
+
await logoutCommand({
|
|
96
|
+
io,
|
|
97
|
+
apiUrl: readStringOption(options, 'apiUrl'),
|
|
98
|
+
localOnly: readBooleanOption(options, 'localOnly'),
|
|
99
|
+
});
|
|
100
|
+
});
|
|
101
|
+
const context = program
|
|
102
|
+
.command('context')
|
|
103
|
+
.description('Read and update Business / Operating Context');
|
|
104
|
+
context
|
|
105
|
+
.command('get <store>')
|
|
106
|
+
.description('Print a context digest (business | operating)')
|
|
107
|
+
.option('--api-url <url>', 'override the API base URL for this command')
|
|
108
|
+
.option('--json', 'output raw JSON')
|
|
109
|
+
.action(async (store, options) => {
|
|
110
|
+
await contextGetCommand({
|
|
111
|
+
io,
|
|
112
|
+
store,
|
|
113
|
+
apiUrl: readStringOption(options, 'apiUrl'),
|
|
114
|
+
json: readBooleanOption(options, 'json'),
|
|
115
|
+
});
|
|
116
|
+
});
|
|
117
|
+
context
|
|
118
|
+
.command('status <store>')
|
|
119
|
+
.description('Show context completion and unanswered questions')
|
|
120
|
+
.option('--api-url <url>', 'override the API base URL for this command')
|
|
121
|
+
.option('--json', 'output raw JSON')
|
|
122
|
+
.action(async (store, options) => {
|
|
123
|
+
await contextStatusCommand({
|
|
124
|
+
io,
|
|
125
|
+
store,
|
|
126
|
+
apiUrl: readStringOption(options, 'apiUrl'),
|
|
127
|
+
json: readBooleanOption(options, 'json'),
|
|
128
|
+
});
|
|
129
|
+
});
|
|
130
|
+
context
|
|
131
|
+
.command('set <target> <value>')
|
|
132
|
+
.description('Set one answer, for example business.company_size "50-200"')
|
|
133
|
+
.option('--api-url <url>', 'override the API base URL for this command')
|
|
134
|
+
.option('--json', 'output raw JSON')
|
|
135
|
+
.action(async (target, value, options) => {
|
|
136
|
+
await contextSetCommand({
|
|
137
|
+
io,
|
|
138
|
+
target,
|
|
139
|
+
value,
|
|
140
|
+
apiUrl: readStringOption(options, 'apiUrl'),
|
|
141
|
+
json: readBooleanOption(options, 'json'),
|
|
142
|
+
});
|
|
143
|
+
});
|
|
144
|
+
context
|
|
145
|
+
.command('reset')
|
|
146
|
+
.description('Reset Business Context (asks to confirm)')
|
|
147
|
+
.option('--yes', 'apply without prompting')
|
|
148
|
+
.option('--api-url <url>', 'override the API base URL for this command')
|
|
149
|
+
.option('--json', 'output raw JSON')
|
|
150
|
+
.action(async (options) => {
|
|
151
|
+
await contextResetCommand({
|
|
152
|
+
io,
|
|
153
|
+
yes: readBooleanOption(options, 'yes'),
|
|
154
|
+
apiUrl: readStringOption(options, 'apiUrl'),
|
|
155
|
+
json: readBooleanOption(options, 'json'),
|
|
156
|
+
});
|
|
157
|
+
});
|
|
158
|
+
const test = program.command('test').description('Tests, their runs, items and ownership');
|
|
159
|
+
test
|
|
160
|
+
.command('list')
|
|
161
|
+
.description('List tests with their latest run status')
|
|
162
|
+
.option('--search <text>', 'filter by name')
|
|
163
|
+
.option('--status <csv>', 'filter by latest run status')
|
|
164
|
+
.option('--page <n>', 'page number')
|
|
165
|
+
.option('--limit <n>', 'page size')
|
|
166
|
+
.option('--api-url <url>', 'override the API base URL for this command')
|
|
167
|
+
.option('--json', 'output raw JSON')
|
|
168
|
+
.action(async (options) => {
|
|
169
|
+
await testListCommand({
|
|
170
|
+
io,
|
|
171
|
+
search: readStringOption(options, 'search'),
|
|
172
|
+
status: readStringOption(options, 'status'),
|
|
173
|
+
page: readNumberOption(options, 'page'),
|
|
174
|
+
limit: readNumberOption(options, 'limit'),
|
|
175
|
+
apiUrl: readStringOption(options, 'apiUrl'),
|
|
176
|
+
json: readBooleanOption(options, 'json'),
|
|
177
|
+
});
|
|
178
|
+
});
|
|
179
|
+
test
|
|
180
|
+
.command('get <name>')
|
|
181
|
+
.description('Show a test, its runs and AI assurance')
|
|
182
|
+
.option('--api-url <url>', 'override the API base URL for this command')
|
|
183
|
+
.option('--json', 'output raw JSON')
|
|
184
|
+
.action(async (name, options) => {
|
|
185
|
+
await testGetCommand({
|
|
186
|
+
io,
|
|
187
|
+
name,
|
|
188
|
+
apiUrl: readStringOption(options, 'apiUrl'),
|
|
189
|
+
json: readBooleanOption(options, 'json'),
|
|
190
|
+
});
|
|
191
|
+
});
|
|
192
|
+
test
|
|
193
|
+
.command('runs <name>')
|
|
194
|
+
.description('List a test\u2019s runs with AI assurance')
|
|
195
|
+
.option('--api-url <url>', 'override the API base URL for this command')
|
|
196
|
+
.option('--json', 'output raw JSON')
|
|
197
|
+
.action(async (name, options) => {
|
|
198
|
+
await testRunsCommand({
|
|
199
|
+
io,
|
|
200
|
+
name,
|
|
201
|
+
apiUrl: readStringOption(options, 'apiUrl'),
|
|
202
|
+
json: readBooleanOption(options, 'json'),
|
|
203
|
+
});
|
|
204
|
+
});
|
|
205
|
+
test
|
|
206
|
+
.command('items <name>')
|
|
207
|
+
.description('List a run\u2019s evidence with AI relevance (defaults to the current run)')
|
|
208
|
+
.option('--run <id>', 'a specific run')
|
|
209
|
+
.option('--api-url <url>', 'override the API base URL for this command')
|
|
210
|
+
.option('--json', 'output raw JSON')
|
|
211
|
+
.action(async (name, options) => {
|
|
212
|
+
await testItemsCommand({
|
|
213
|
+
io,
|
|
214
|
+
name,
|
|
215
|
+
runId: readStringOption(options, 'run'),
|
|
216
|
+
apiUrl: readStringOption(options, 'apiUrl'),
|
|
217
|
+
json: readBooleanOption(options, 'json'),
|
|
218
|
+
});
|
|
219
|
+
});
|
|
220
|
+
test
|
|
221
|
+
.command('attach <name>')
|
|
222
|
+
.description('Attach evidence to a run (defaults to the current run)')
|
|
223
|
+
.option('--check <id>', 'link a check')
|
|
224
|
+
.option('--native-doc <id>', 'link a native document')
|
|
225
|
+
.option('--upload <id>', 'link a staged upload id')
|
|
226
|
+
.option('--file <path>', 'upload a local file and attach it')
|
|
227
|
+
.option('--run <id>', 'a specific run')
|
|
228
|
+
.option('--unlink', 'unlink instead of link')
|
|
229
|
+
.option('--api-url <url>', 'override the API base URL for this command')
|
|
230
|
+
.option('--json', 'output raw JSON')
|
|
231
|
+
.action(async (name, options) => {
|
|
232
|
+
const check = readStringOption(options, 'check');
|
|
233
|
+
const nativeDoc = readStringOption(options, 'nativeDoc');
|
|
234
|
+
const upload = readStringOption(options, 'upload');
|
|
235
|
+
const itemId = check ?? nativeDoc ?? upload;
|
|
236
|
+
const itemType = check ? 'check' : nativeDoc ? 'native_doc' : 'upload';
|
|
237
|
+
await testAttachCommand({
|
|
238
|
+
io,
|
|
239
|
+
name,
|
|
240
|
+
itemType,
|
|
241
|
+
itemId: check || nativeDoc ? itemId : undefined,
|
|
242
|
+
uploadIds: upload ? [upload] : undefined,
|
|
243
|
+
file: readStringOption(options, 'file'),
|
|
244
|
+
runId: readStringOption(options, 'run'),
|
|
245
|
+
unlink: readBooleanOption(options, 'unlink'),
|
|
246
|
+
apiUrl: readStringOption(options, 'apiUrl'),
|
|
247
|
+
json: readBooleanOption(options, 'json'),
|
|
248
|
+
});
|
|
249
|
+
});
|
|
250
|
+
const testRun = test.command('run').description('Create, result and delete runs');
|
|
251
|
+
testRun
|
|
252
|
+
.command('create <name>')
|
|
253
|
+
.description('Create a new pending run')
|
|
254
|
+
.option('--run-at <date>', 'run date (ISO); defaults to now')
|
|
255
|
+
.option('--api-url <url>', 'override the API base URL for this command')
|
|
256
|
+
.option('--json', 'output raw JSON')
|
|
257
|
+
.action(async (name, options) => {
|
|
258
|
+
await testRunCreateCommand({
|
|
259
|
+
io,
|
|
260
|
+
name,
|
|
261
|
+
runAt: readStringOption(options, 'runAt'),
|
|
262
|
+
apiUrl: readStringOption(options, 'apiUrl'),
|
|
263
|
+
json: readBooleanOption(options, 'json'),
|
|
264
|
+
});
|
|
265
|
+
});
|
|
266
|
+
testRun
|
|
267
|
+
.command('result <name>')
|
|
268
|
+
.description('Record a run result: passing | failing | skipped | pending (reopen)')
|
|
269
|
+
.requiredOption('--status <status>', 'passing | failing | skipped | pending')
|
|
270
|
+
.option('--note <text>', 'note for the run')
|
|
271
|
+
.option('--run <id>', 'a specific run')
|
|
272
|
+
.option('--api-url <url>', 'override the API base URL for this command')
|
|
273
|
+
.option('--json', 'output raw JSON')
|
|
274
|
+
.action(async (name, options) => {
|
|
275
|
+
await testRunResultCommand({
|
|
276
|
+
io,
|
|
277
|
+
name,
|
|
278
|
+
status: readStringOption(options, 'status') ?? '',
|
|
279
|
+
note: readStringOption(options, 'note'),
|
|
280
|
+
runId: readStringOption(options, 'run'),
|
|
281
|
+
apiUrl: readStringOption(options, 'apiUrl'),
|
|
282
|
+
json: readBooleanOption(options, 'json'),
|
|
283
|
+
});
|
|
284
|
+
});
|
|
285
|
+
testRun
|
|
286
|
+
.command('delete <name>')
|
|
287
|
+
.description('Delete a run')
|
|
288
|
+
.requiredOption('--run <id>', 'the run to delete')
|
|
289
|
+
.option('--yes', 'apply without prompting')
|
|
290
|
+
.option('--api-url <url>', 'override the API base URL for this command')
|
|
291
|
+
.option('--json', 'output raw JSON')
|
|
292
|
+
.action(async (name, options) => {
|
|
293
|
+
await testRunDeleteCommand({
|
|
294
|
+
io,
|
|
295
|
+
name,
|
|
296
|
+
runId: readStringOption(options, 'run'),
|
|
297
|
+
yes: readBooleanOption(options, 'yes'),
|
|
298
|
+
apiUrl: readStringOption(options, 'apiUrl'),
|
|
299
|
+
json: readBooleanOption(options, 'json'),
|
|
300
|
+
});
|
|
301
|
+
});
|
|
302
|
+
test
|
|
303
|
+
.command('available <name>')
|
|
304
|
+
.description('List items available to attach to a run (defaults to the current run)')
|
|
305
|
+
.requiredOption('--type <type>', 'item type: native_doc or check')
|
|
306
|
+
.option('--run <id>', 'a specific run')
|
|
307
|
+
.option('--search <text>', 'filter by item name')
|
|
308
|
+
.option('--api-url <url>', 'override the API base URL for this command')
|
|
309
|
+
.option('--json', 'output raw JSON')
|
|
310
|
+
.action(async (name, options) => {
|
|
311
|
+
await testAvailableItemsCommand({
|
|
312
|
+
io,
|
|
313
|
+
name,
|
|
314
|
+
runId: readStringOption(options, 'run'),
|
|
315
|
+
itemType: readStringOption(options, 'type') ?? 'native_doc',
|
|
316
|
+
search: readStringOption(options, 'search'),
|
|
317
|
+
apiUrl: readStringOption(options, 'apiUrl'),
|
|
318
|
+
json: readBooleanOption(options, 'json'),
|
|
319
|
+
});
|
|
320
|
+
});
|
|
321
|
+
test
|
|
322
|
+
.command('links <name>')
|
|
323
|
+
.description('Show the controls and clauses a test covers')
|
|
324
|
+
.option('--api-url <url>', 'override the API base URL for this command')
|
|
325
|
+
.option('--json', 'output raw JSON')
|
|
326
|
+
.action(async (name, options) => {
|
|
327
|
+
await testLinksCommand({
|
|
328
|
+
io,
|
|
329
|
+
name,
|
|
330
|
+
apiUrl: readStringOption(options, 'apiUrl'),
|
|
331
|
+
json: readBooleanOption(options, 'json'),
|
|
332
|
+
});
|
|
333
|
+
});
|
|
334
|
+
test
|
|
335
|
+
.command('update <name>')
|
|
336
|
+
.description('Change applicability, responsibility, assignee, frequency or auto-pass')
|
|
337
|
+
.option('--applicability <value>', 'in_scope or out_of_scope')
|
|
338
|
+
.option('--responsibility <value>', 'internal, third_party or shared')
|
|
339
|
+
.option('--assigned-to <id>', 'owner user id')
|
|
340
|
+
.option('--frequency <value>', 'none, monthly, quarterly, half-yearly or yearly')
|
|
341
|
+
.option('--next-run-at <date>', 'ISO date for the next run')
|
|
342
|
+
.option('--auto-pass <on|off>', 'auto-pass from checks')
|
|
343
|
+
.option('--rename <text>', 'new name (custom tests only)')
|
|
344
|
+
.option('--guidance <text>', 'new guidance (custom tests only)')
|
|
345
|
+
.option('--yes', 'apply without prompting')
|
|
346
|
+
.option('--api-url <url>', 'override the API base URL for this command')
|
|
347
|
+
.option('--json', 'output raw JSON')
|
|
348
|
+
.action(async (name, options) => {
|
|
349
|
+
const changes = {};
|
|
350
|
+
for (const [flag, field] of [
|
|
351
|
+
['applicability', 'applicability'],
|
|
352
|
+
['responsibility', 'responsibility'],
|
|
353
|
+
['assignedTo', 'assignedTo'],
|
|
354
|
+
['frequency', 'frequency'],
|
|
355
|
+
['nextRunAt', 'nextRunAt'],
|
|
356
|
+
['rename', 'name'],
|
|
357
|
+
['guidance', 'guidance'],
|
|
358
|
+
]) {
|
|
359
|
+
const value = readStringOption(options, flag);
|
|
360
|
+
if (value !== undefined)
|
|
361
|
+
changes[field] = value;
|
|
362
|
+
}
|
|
363
|
+
const autoPass = readStringOption(options, 'autoPass');
|
|
364
|
+
if (autoPass !== undefined)
|
|
365
|
+
changes.autoPassFromChecks = autoPass === 'on';
|
|
366
|
+
await testUpdateCommand({
|
|
367
|
+
io,
|
|
368
|
+
name,
|
|
369
|
+
changes,
|
|
370
|
+
yes: readBooleanOption(options, 'yes'),
|
|
371
|
+
apiUrl: readStringOption(options, 'apiUrl'),
|
|
372
|
+
json: readBooleanOption(options, 'json'),
|
|
373
|
+
});
|
|
374
|
+
});
|
|
375
|
+
test
|
|
376
|
+
.command('delete <name>')
|
|
377
|
+
.description('Delete a custom test')
|
|
378
|
+
.option('--yes', 'apply without prompting')
|
|
379
|
+
.option('--api-url <url>', 'override the API base URL for this command')
|
|
380
|
+
.option('--json', 'output raw JSON')
|
|
381
|
+
.action(async (name, options) => {
|
|
382
|
+
await testDeleteCommand({
|
|
383
|
+
io,
|
|
384
|
+
name,
|
|
385
|
+
yes: readBooleanOption(options, 'yes'),
|
|
386
|
+
apiUrl: readStringOption(options, 'apiUrl'),
|
|
387
|
+
json: readBooleanOption(options, 'json'),
|
|
388
|
+
});
|
|
389
|
+
});
|
|
390
|
+
const framework = program.command('framework').description('Applied frameworks');
|
|
391
|
+
framework
|
|
392
|
+
.command('list')
|
|
393
|
+
.description('List applied frameworks with status and available frameworks')
|
|
394
|
+
.option('--api-url <url>', 'override the API base URL for this command')
|
|
395
|
+
.option('--json', 'output raw JSON')
|
|
396
|
+
.action(async (options) => {
|
|
397
|
+
await frameworkListCommand({
|
|
398
|
+
io,
|
|
399
|
+
apiUrl: readStringOption(options, 'apiUrl'),
|
|
400
|
+
json: readBooleanOption(options, 'json'),
|
|
401
|
+
});
|
|
402
|
+
});
|
|
403
|
+
framework
|
|
404
|
+
.command('clauses <framework>')
|
|
405
|
+
.description('List a framework’s clauses')
|
|
406
|
+
.option('--search <text>', 'filter by clause code or name')
|
|
407
|
+
.option('--applicability <value>', 'in_scope or out_of_scope')
|
|
408
|
+
.option('--page <n>', 'page number')
|
|
409
|
+
.option('--limit <n>', 'page size')
|
|
410
|
+
.option('--api-url <url>', 'override the API base URL for this command')
|
|
411
|
+
.option('--json', 'output raw JSON')
|
|
412
|
+
.action(async (name, options) => {
|
|
413
|
+
await frameworkClausesCommand({
|
|
414
|
+
io,
|
|
415
|
+
framework: name,
|
|
416
|
+
search: readStringOption(options, 'search'),
|
|
417
|
+
applicability: readStringOption(options, 'applicability'),
|
|
418
|
+
page: readNumberOption(options, 'page'),
|
|
419
|
+
limit: readNumberOption(options, 'limit'),
|
|
420
|
+
apiUrl: readStringOption(options, 'apiUrl'),
|
|
421
|
+
json: readBooleanOption(options, 'json'),
|
|
422
|
+
});
|
|
423
|
+
});
|
|
424
|
+
framework
|
|
425
|
+
.command('clause <framework> <clause>')
|
|
426
|
+
.description('Show one clause by code or name')
|
|
427
|
+
.option('--api-url <url>', 'override the API base URL for this command')
|
|
428
|
+
.option('--json', 'output raw JSON')
|
|
429
|
+
.action(async (frameworkName, clause, options) => {
|
|
430
|
+
await clauseGetCommand({
|
|
431
|
+
io,
|
|
432
|
+
framework: frameworkName,
|
|
433
|
+
clause,
|
|
434
|
+
apiUrl: readStringOption(options, 'apiUrl'),
|
|
435
|
+
json: readBooleanOption(options, 'json'),
|
|
436
|
+
});
|
|
437
|
+
});
|
|
438
|
+
framework
|
|
439
|
+
.command('items <framework> <clause>')
|
|
440
|
+
.description('List the items mapped to a clause')
|
|
441
|
+
.option('--api-url <url>', 'override the API base URL for this command')
|
|
442
|
+
.option('--json', 'output raw JSON')
|
|
443
|
+
.action(async (frameworkName, clause, options) => {
|
|
444
|
+
await clauseItemsCommand({
|
|
445
|
+
io,
|
|
446
|
+
framework: frameworkName,
|
|
447
|
+
clause,
|
|
448
|
+
apiUrl: readStringOption(options, 'apiUrl'),
|
|
449
|
+
json: readBooleanOption(options, 'json'),
|
|
450
|
+
});
|
|
451
|
+
});
|
|
452
|
+
framework
|
|
453
|
+
.command('available <framework> <clause>')
|
|
454
|
+
.description('List items that can be mapped to a clause')
|
|
455
|
+
.requiredOption('--type <type>', 'item type: test or document')
|
|
456
|
+
.option('--search <text>', 'filter by item name')
|
|
457
|
+
.option('--api-url <url>', 'override the API base URL for this command')
|
|
458
|
+
.option('--json', 'output raw JSON')
|
|
459
|
+
.action(async (frameworkName, clause, options) => {
|
|
460
|
+
await clauseAvailableItemsCommand({
|
|
461
|
+
io,
|
|
462
|
+
framework: frameworkName,
|
|
463
|
+
clause,
|
|
464
|
+
itemType: readStringOption(options, 'type') ?? 'test',
|
|
465
|
+
search: readStringOption(options, 'search'),
|
|
466
|
+
apiUrl: readStringOption(options, 'apiUrl'),
|
|
467
|
+
json: readBooleanOption(options, 'json'),
|
|
468
|
+
});
|
|
469
|
+
});
|
|
470
|
+
framework
|
|
471
|
+
.command('update <framework> <clause>')
|
|
472
|
+
.description('Change a clause’s applicability or design-requirement assessment')
|
|
473
|
+
.option('--applicability <value>', 'in_scope or out_of_scope')
|
|
474
|
+
.option('--justification <text>', 'why the applicability changed')
|
|
475
|
+
.option('--design-requirements <state>', 'not_assessed | not_needed | not_met | partial | met')
|
|
476
|
+
.option('--yes', 'apply without prompting')
|
|
477
|
+
.option('--api-url <url>', 'override the API base URL for this command')
|
|
478
|
+
.option('--json', 'output raw JSON')
|
|
479
|
+
.action(async (frameworkName, clause, options) => {
|
|
480
|
+
const changes = {};
|
|
481
|
+
for (const [flag, field] of [
|
|
482
|
+
['applicability', 'applicability'],
|
|
483
|
+
['justification', 'justification'],
|
|
484
|
+
['designRequirements', 'designRequirements'],
|
|
485
|
+
]) {
|
|
486
|
+
const value = readStringOption(options, flag);
|
|
487
|
+
if (value !== undefined)
|
|
488
|
+
changes[field] = value;
|
|
489
|
+
}
|
|
490
|
+
await clauseUpdateCommand({
|
|
491
|
+
io,
|
|
492
|
+
framework: frameworkName,
|
|
493
|
+
clause,
|
|
494
|
+
changes,
|
|
495
|
+
yes: readBooleanOption(options, 'yes'),
|
|
496
|
+
apiUrl: readStringOption(options, 'apiUrl'),
|
|
497
|
+
json: readBooleanOption(options, 'json'),
|
|
498
|
+
});
|
|
499
|
+
});
|
|
500
|
+
framework
|
|
501
|
+
.command('link <framework> <clause> <itemId>')
|
|
502
|
+
.description('Map a test or document to a clause')
|
|
503
|
+
.requiredOption('--type <type>', 'item type: test or document')
|
|
504
|
+
.option('--unlink', 'unlink instead of link')
|
|
505
|
+
.option('--yes', 'apply without prompting')
|
|
506
|
+
.option('--api-url <url>', 'override the API base URL for this command')
|
|
507
|
+
.option('--json', 'output raw JSON')
|
|
508
|
+
.action(async (frameworkName, clause, itemId, options) => {
|
|
509
|
+
await clauseLinkCommand({
|
|
510
|
+
io,
|
|
511
|
+
framework: frameworkName,
|
|
512
|
+
clause,
|
|
513
|
+
itemId,
|
|
514
|
+
itemType: readStringOption(options, 'type') ?? 'test',
|
|
515
|
+
action: readBooleanOption(options, 'unlink') ? 'unlink' : 'link',
|
|
516
|
+
yes: readBooleanOption(options, 'yes'),
|
|
517
|
+
apiUrl: readStringOption(options, 'apiUrl'),
|
|
518
|
+
json: readBooleanOption(options, 'json'),
|
|
519
|
+
});
|
|
520
|
+
});
|
|
521
|
+
const asset = program.command('asset').description('The asset register');
|
|
522
|
+
asset
|
|
523
|
+
.command('list')
|
|
524
|
+
.description('List assets with their code, status and classification')
|
|
525
|
+
.option('--name <text>', 'filter by name')
|
|
526
|
+
.option('--category <value>', 'filter by category')
|
|
527
|
+
.option('--status <csv>', 'filter by status')
|
|
528
|
+
.option('--business-impact <value>', 'filter by business impact')
|
|
529
|
+
.option('--data-classification <value>', 'filter by data classification')
|
|
530
|
+
.option('--page <n>', 'page number')
|
|
531
|
+
.option('--limit <n>', 'page size')
|
|
532
|
+
.option('--api-url <url>', 'override the API base URL for this command')
|
|
533
|
+
.option('--json', 'output raw JSON')
|
|
534
|
+
.action(async (options) => {
|
|
535
|
+
await assetListCommand({
|
|
536
|
+
io,
|
|
537
|
+
name: readStringOption(options, 'name'),
|
|
538
|
+
category: readStringOption(options, 'category'),
|
|
539
|
+
status: readStringOption(options, 'status'),
|
|
540
|
+
businessImpact: readStringOption(options, 'businessImpact'),
|
|
541
|
+
dataClassification: readStringOption(options, 'dataClassification'),
|
|
542
|
+
page: readNumberOption(options, 'page'),
|
|
543
|
+
limit: readNumberOption(options, 'limit'),
|
|
544
|
+
apiUrl: readStringOption(options, 'apiUrl'),
|
|
545
|
+
json: readBooleanOption(options, 'json'),
|
|
546
|
+
});
|
|
547
|
+
});
|
|
548
|
+
asset
|
|
549
|
+
.command('get <asset>')
|
|
550
|
+
.description('Show one asset by code, customer key or name')
|
|
551
|
+
.option('--api-url <url>', 'override the API base URL for this command')
|
|
552
|
+
.option('--json', 'output raw JSON')
|
|
553
|
+
.action(async (name, options) => {
|
|
554
|
+
await assetGetCommand({
|
|
555
|
+
io,
|
|
556
|
+
name,
|
|
557
|
+
apiUrl: readStringOption(options, 'apiUrl'),
|
|
558
|
+
json: readBooleanOption(options, 'json'),
|
|
559
|
+
});
|
|
560
|
+
});
|
|
561
|
+
asset
|
|
562
|
+
.command('links <asset>')
|
|
563
|
+
.description('List the controls, risks, tests or checks mapped to an asset')
|
|
564
|
+
.requiredOption('--type <type>', 'controls, risks, tests or checks')
|
|
565
|
+
.option('--page <n>', 'page number')
|
|
566
|
+
.option('--limit <n>', 'page size')
|
|
567
|
+
.option('--api-url <url>', 'override the API base URL for this command')
|
|
568
|
+
.option('--json', 'output raw JSON')
|
|
569
|
+
.action(async (name, options) => {
|
|
570
|
+
await assetLinksCommand({
|
|
571
|
+
io,
|
|
572
|
+
name,
|
|
573
|
+
type: readStringOption(options, 'type') ?? 'controls',
|
|
574
|
+
page: readNumberOption(options, 'page'),
|
|
575
|
+
limit: readNumberOption(options, 'limit'),
|
|
576
|
+
apiUrl: readStringOption(options, 'apiUrl'),
|
|
577
|
+
json: readBooleanOption(options, 'json'),
|
|
578
|
+
});
|
|
579
|
+
});
|
|
580
|
+
asset
|
|
581
|
+
.command('update <asset>')
|
|
582
|
+
.description('Change asset fields')
|
|
583
|
+
.option('--name <text>', 'rename')
|
|
584
|
+
.option('--description <text>', 'description')
|
|
585
|
+
.option('--category <value>', 'category')
|
|
586
|
+
.option('--sub-category <value>', 'sub-category')
|
|
587
|
+
.option('--status <value>', 'status')
|
|
588
|
+
.option('--data-classification <value>', 'data classification')
|
|
589
|
+
.option('--business-impact <value>', 'business impact')
|
|
590
|
+
.option('--technical-owner <id>', 'technical owner user id')
|
|
591
|
+
.option('--business-owner <value>', 'business owner')
|
|
592
|
+
.option('--customer-asset-key <value>', 'the tenant\u2019s own key for this asset')
|
|
593
|
+
.option('--external-ref <value>', 'external reference id')
|
|
594
|
+
.option('--location <value>', 'location')
|
|
595
|
+
.option('--yes', 'apply without prompting')
|
|
596
|
+
.option('--api-url <url>', 'override the API base URL for this command')
|
|
597
|
+
.option('--json', 'output raw JSON')
|
|
598
|
+
.action(async (name, options) => {
|
|
599
|
+
const changes = {};
|
|
600
|
+
for (const [flag, field] of [
|
|
601
|
+
['name', 'name'],
|
|
602
|
+
['description', 'description'],
|
|
603
|
+
['category', 'category'],
|
|
604
|
+
['subCategory', 'subCategory'],
|
|
605
|
+
['status', 'status'],
|
|
606
|
+
['dataClassification', 'dataClassification'],
|
|
607
|
+
['businessImpact', 'businessImpact'],
|
|
608
|
+
['technicalOwner', 'technicalOwner'],
|
|
609
|
+
['businessOwner', 'businessOwner'],
|
|
610
|
+
['customerAssetKey', 'customerAssetKey'],
|
|
611
|
+
['externalRef', 'externalRefId'],
|
|
612
|
+
['location', 'location'],
|
|
613
|
+
]) {
|
|
614
|
+
const value = readStringOption(options, flag);
|
|
615
|
+
if (value !== undefined)
|
|
616
|
+
changes[field] = value;
|
|
617
|
+
}
|
|
618
|
+
await assetUpdateCommand({
|
|
619
|
+
io,
|
|
620
|
+
name,
|
|
621
|
+
changes,
|
|
622
|
+
yes: readBooleanOption(options, 'yes'),
|
|
623
|
+
apiUrl: readStringOption(options, 'apiUrl'),
|
|
624
|
+
json: readBooleanOption(options, 'json'),
|
|
625
|
+
});
|
|
626
|
+
});
|
|
627
|
+
asset
|
|
628
|
+
.command('create')
|
|
629
|
+
.description('Create an asset')
|
|
630
|
+
.requiredOption('--name <name>', 'asset name')
|
|
631
|
+
.requiredOption('--category <value>', 'category')
|
|
632
|
+
.requiredOption('--sub-category <value>', 'sub-category')
|
|
633
|
+
.option('--customer-key <value>', 'the tenant\u2019s own key for this asset')
|
|
634
|
+
.option('--external-ref <value>', 'external reference id')
|
|
635
|
+
.option('--description <text>', 'description')
|
|
636
|
+
.option('--status <value>', 'status')
|
|
637
|
+
.option('--business-impact <value>', 'business impact')
|
|
638
|
+
.option('--data-classification <value>', 'data classification')
|
|
639
|
+
.option('--technical-owner <id>', 'technical owner user id')
|
|
640
|
+
.option('--business-owner <value>', 'business owner')
|
|
641
|
+
.option('--location <value>', 'location')
|
|
642
|
+
.option('--api-url <url>', 'override the API base URL for this command')
|
|
643
|
+
.option('--json', 'output raw JSON')
|
|
644
|
+
.action(async (options) => {
|
|
645
|
+
await assetCreateCommand({
|
|
646
|
+
io,
|
|
647
|
+
name: readStringOption(options, 'name') ?? '',
|
|
648
|
+
category: readStringOption(options, 'category') ?? '',
|
|
649
|
+
subCategory: readStringOption(options, 'subCategory') ?? '',
|
|
650
|
+
customerAssetKey: readStringOption(options, 'customerKey'),
|
|
651
|
+
externalRefId: readStringOption(options, 'externalRef'),
|
|
652
|
+
description: readStringOption(options, 'description'),
|
|
653
|
+
status: readStringOption(options, 'status'),
|
|
654
|
+
businessImpact: readStringOption(options, 'businessImpact'),
|
|
655
|
+
dataClassification: readStringOption(options, 'dataClassification'),
|
|
656
|
+
technicalOwner: readStringOption(options, 'technicalOwner'),
|
|
657
|
+
businessOwner: readStringOption(options, 'businessOwner'),
|
|
658
|
+
location: readStringOption(options, 'location'),
|
|
659
|
+
apiUrl: readStringOption(options, 'apiUrl'),
|
|
660
|
+
json: readBooleanOption(options, 'json'),
|
|
661
|
+
});
|
|
662
|
+
});
|
|
663
|
+
asset
|
|
664
|
+
.command('delete <asset>')
|
|
665
|
+
.description('Delete an asset')
|
|
666
|
+
.option('--yes', 'apply without prompting')
|
|
667
|
+
.option('--api-url <url>', 'override the API base URL for this command')
|
|
668
|
+
.option('--json', 'output raw JSON')
|
|
669
|
+
.action(async (name, options) => {
|
|
670
|
+
await assetDeleteCommand({
|
|
671
|
+
io,
|
|
672
|
+
name,
|
|
673
|
+
yes: readBooleanOption(options, 'yes'),
|
|
674
|
+
apiUrl: readStringOption(options, 'apiUrl'),
|
|
675
|
+
json: readBooleanOption(options, 'json'),
|
|
676
|
+
});
|
|
677
|
+
});
|
|
678
|
+
asset
|
|
679
|
+
.command('tags <asset>')
|
|
680
|
+
.description('List an asset\u2019s tags')
|
|
681
|
+
.option('--api-url <url>', 'override the API base URL for this command')
|
|
682
|
+
.option('--json', 'output raw JSON')
|
|
683
|
+
.action(async (name, options) => {
|
|
684
|
+
await assetTagsCommand({
|
|
685
|
+
io,
|
|
686
|
+
name,
|
|
687
|
+
apiUrl: readStringOption(options, 'apiUrl'),
|
|
688
|
+
json: readBooleanOption(options, 'json'),
|
|
689
|
+
});
|
|
690
|
+
});
|
|
691
|
+
asset
|
|
692
|
+
.command('tag <asset>')
|
|
693
|
+
.description('Assign a key/value tag to an asset, or remove one')
|
|
694
|
+
.option('--key <key>', 'tag key')
|
|
695
|
+
.option('--value <value>', 'tag value')
|
|
696
|
+
.option('--unlink <mappingId>', 'tag mapping id to remove')
|
|
697
|
+
.option('--yes', 'apply without prompting')
|
|
698
|
+
.option('--api-url <url>', 'override the API base URL for this command')
|
|
699
|
+
.option('--json', 'output raw JSON')
|
|
700
|
+
.action(async (name, options) => {
|
|
701
|
+
const mappingId = readStringOption(options, 'unlink');
|
|
702
|
+
const key = readStringOption(options, 'key');
|
|
703
|
+
const value = readStringOption(options, 'value');
|
|
704
|
+
if (!mappingId && (!key || !value)) {
|
|
705
|
+
throw new Error('Provide --key and --value to assign, or --unlink <mappingId> to remove');
|
|
706
|
+
}
|
|
707
|
+
await assetTagCommand({
|
|
708
|
+
io,
|
|
709
|
+
name,
|
|
710
|
+
key,
|
|
711
|
+
value,
|
|
712
|
+
mappingId,
|
|
713
|
+
yes: readBooleanOption(options, 'yes'),
|
|
714
|
+
apiUrl: readStringOption(options, 'apiUrl'),
|
|
715
|
+
json: readBooleanOption(options, 'json'),
|
|
716
|
+
});
|
|
717
|
+
});
|
|
718
|
+
const vendor = program.command('vendor').description('Manage vendors');
|
|
719
|
+
vendor
|
|
720
|
+
.command('list')
|
|
721
|
+
.description('List vendors with a total count')
|
|
722
|
+
.option('--name <text>', 'filter by name')
|
|
723
|
+
.option('--criticality <value>', 'filter by criticality')
|
|
724
|
+
.option('--relationship-status <value>', 'filter by relationship status')
|
|
725
|
+
.option('--page <n>', 'page number')
|
|
726
|
+
.option('--limit <n>', 'page size')
|
|
727
|
+
.option('--api-url <url>', 'override the API base URL for this command')
|
|
728
|
+
.option('--json', 'output raw JSON')
|
|
729
|
+
.action(async (options) => {
|
|
730
|
+
await vendorListCommand({
|
|
731
|
+
io,
|
|
732
|
+
name: readStringOption(options, 'name'),
|
|
733
|
+
criticality: readStringOption(options, 'criticality'),
|
|
734
|
+
relationshipStatus: readStringOption(options, 'relationshipStatus'),
|
|
735
|
+
page: readNumberOption(options, 'page'),
|
|
736
|
+
limit: readNumberOption(options, 'limit'),
|
|
737
|
+
apiUrl: readStringOption(options, 'apiUrl'),
|
|
738
|
+
json: readBooleanOption(options, 'json'),
|
|
739
|
+
});
|
|
740
|
+
});
|
|
741
|
+
vendor
|
|
742
|
+
.command('get <name>')
|
|
743
|
+
.description('Show one vendor')
|
|
744
|
+
.option('--api-url <url>', 'override the API base URL for this command')
|
|
745
|
+
.option('--json', 'output raw JSON')
|
|
746
|
+
.action(async (name, options) => {
|
|
747
|
+
await vendorGetCommand({
|
|
748
|
+
io,
|
|
749
|
+
name,
|
|
750
|
+
apiUrl: readStringOption(options, 'apiUrl'),
|
|
751
|
+
json: readBooleanOption(options, 'json'),
|
|
752
|
+
});
|
|
753
|
+
});
|
|
754
|
+
vendor
|
|
755
|
+
.command('create')
|
|
756
|
+
.description('Create a vendor')
|
|
757
|
+
.requiredOption('--name <name>', 'vendor name')
|
|
758
|
+
.option('--category <value>', 'category')
|
|
759
|
+
.option('--criticality <value>', 'criticality')
|
|
760
|
+
.option('--description <text>', 'description')
|
|
761
|
+
.option('--api-url <url>', 'override the API base URL for this command')
|
|
762
|
+
.option('--json', 'output raw JSON')
|
|
763
|
+
.action(async (options) => {
|
|
764
|
+
await vendorCreateCommand({
|
|
765
|
+
io,
|
|
766
|
+
name: readStringOption(options, 'name') ?? '',
|
|
767
|
+
category: readStringOption(options, 'category'),
|
|
768
|
+
criticality: readStringOption(options, 'criticality'),
|
|
769
|
+
description: readStringOption(options, 'description'),
|
|
770
|
+
apiUrl: readStringOption(options, 'apiUrl'),
|
|
771
|
+
json: readBooleanOption(options, 'json'),
|
|
772
|
+
});
|
|
773
|
+
});
|
|
774
|
+
vendor
|
|
775
|
+
.command('update <name>')
|
|
776
|
+
.description('Update vendor fields')
|
|
777
|
+
.option('--name <value>', 'rename')
|
|
778
|
+
.option('--website <value>', 'website')
|
|
779
|
+
.option('--description <value>', 'description')
|
|
780
|
+
.option('--category <value>', 'category')
|
|
781
|
+
.option('--criticality <value>', 'business criticality')
|
|
782
|
+
.option('--data-sensitivity <value>', 'public | internal | confidential | pii | phi')
|
|
783
|
+
.option('--relationship-status <value>', 'active | inactive | onboarding | terminated')
|
|
784
|
+
.option('--review-status <value>', 'pending | completed | expired')
|
|
785
|
+
.option('--internal-owner <value>', 'internal owner (email)')
|
|
786
|
+
.option('--api-url <url>', 'override the API base URL for this command')
|
|
787
|
+
.option('--json', 'output raw JSON')
|
|
788
|
+
.action(async (vendorName, options) => {
|
|
789
|
+
const changes = {};
|
|
790
|
+
const fields = [
|
|
791
|
+
'name',
|
|
792
|
+
'website',
|
|
793
|
+
'description',
|
|
794
|
+
'category',
|
|
795
|
+
'criticality',
|
|
796
|
+
'dataSensitivity',
|
|
797
|
+
'relationshipStatus',
|
|
798
|
+
'reviewStatus',
|
|
799
|
+
'internalOwner',
|
|
800
|
+
];
|
|
801
|
+
for (const field of fields) {
|
|
802
|
+
const value = readStringOption(options, field);
|
|
803
|
+
if (value !== undefined)
|
|
804
|
+
changes[field] = value;
|
|
805
|
+
}
|
|
806
|
+
await vendorUpdateCommand({
|
|
807
|
+
io,
|
|
808
|
+
name: vendorName,
|
|
809
|
+
changes,
|
|
810
|
+
apiUrl: readStringOption(options, 'apiUrl'),
|
|
811
|
+
json: readBooleanOption(options, 'json'),
|
|
812
|
+
});
|
|
813
|
+
});
|
|
814
|
+
vendor
|
|
815
|
+
.command('delete <name>')
|
|
816
|
+
.description('Delete a vendor')
|
|
817
|
+
.option('--yes', 'apply without prompting')
|
|
818
|
+
.option('--api-url <url>', 'override the API base URL for this command')
|
|
819
|
+
.option('--json', 'output raw JSON')
|
|
820
|
+
.action(async (name, options) => {
|
|
821
|
+
await vendorDeleteCommand({
|
|
822
|
+
io,
|
|
823
|
+
name,
|
|
824
|
+
yes: readBooleanOption(options, 'yes'),
|
|
825
|
+
apiUrl: readStringOption(options, 'apiUrl'),
|
|
826
|
+
json: readBooleanOption(options, 'json'),
|
|
827
|
+
});
|
|
828
|
+
});
|
|
829
|
+
const control = program
|
|
830
|
+
.command('control')
|
|
831
|
+
.description('Controls: status, applicability, owner and design requirements');
|
|
832
|
+
control
|
|
833
|
+
.command('list')
|
|
834
|
+
.description('List controls with their status, applicability and owner')
|
|
835
|
+
.option('--search <text>', 'filter by name or code')
|
|
836
|
+
.option('--domain <value>', 'filter by control domain')
|
|
837
|
+
.option('--status <csv>', 'filter by status')
|
|
838
|
+
.option('--applicability <value>', 'filter by applicability (in_scope|out_of_scope)')
|
|
839
|
+
.option('--page <n>', 'page number')
|
|
840
|
+
.option('--limit <n>', 'page size')
|
|
841
|
+
.option('--api-url <url>', 'override the API base URL for this command')
|
|
842
|
+
.option('--json', 'output raw JSON')
|
|
843
|
+
.action(async (options) => {
|
|
844
|
+
await controlListCommand({
|
|
845
|
+
io,
|
|
846
|
+
search: readStringOption(options, 'search'),
|
|
847
|
+
domain: readStringOption(options, 'domain'),
|
|
848
|
+
status: readStringOption(options, 'status'),
|
|
849
|
+
applicability: readStringOption(options, 'applicability'),
|
|
850
|
+
page: readNumberOption(options, 'page'),
|
|
851
|
+
limit: readNumberOption(options, 'limit'),
|
|
852
|
+
apiUrl: readStringOption(options, 'apiUrl'),
|
|
853
|
+
json: readBooleanOption(options, 'json'),
|
|
854
|
+
});
|
|
855
|
+
});
|
|
856
|
+
control
|
|
857
|
+
.command('get <control>')
|
|
858
|
+
.description('Show one control by name or code')
|
|
859
|
+
.option('--api-url <url>', 'override the API base URL for this command')
|
|
860
|
+
.option('--json', 'output raw JSON')
|
|
861
|
+
.action(async (name, options) => {
|
|
862
|
+
await controlGetCommand({
|
|
863
|
+
io,
|
|
864
|
+
name,
|
|
865
|
+
apiUrl: readStringOption(options, 'apiUrl'),
|
|
866
|
+
json: readBooleanOption(options, 'json'),
|
|
867
|
+
});
|
|
868
|
+
});
|
|
869
|
+
control
|
|
870
|
+
.command('items <control>')
|
|
871
|
+
.description('List the items linked to a control')
|
|
872
|
+
.option('--search <text>', 'filter by item name')
|
|
873
|
+
.option('--page <n>', 'page number')
|
|
874
|
+
.option('--limit <n>', 'page size')
|
|
875
|
+
.option('--api-url <url>', 'override the API base URL for this command')
|
|
876
|
+
.option('--json', 'output raw JSON')
|
|
877
|
+
.action(async (name, options) => {
|
|
878
|
+
await controlItemsCommand({
|
|
879
|
+
io,
|
|
880
|
+
name,
|
|
881
|
+
search: readStringOption(options, 'search'),
|
|
882
|
+
page: readNumberOption(options, 'page'),
|
|
883
|
+
limit: readNumberOption(options, 'limit'),
|
|
884
|
+
apiUrl: readStringOption(options, 'apiUrl'),
|
|
885
|
+
json: readBooleanOption(options, 'json'),
|
|
886
|
+
});
|
|
887
|
+
});
|
|
888
|
+
control
|
|
889
|
+
.command('available <control>')
|
|
890
|
+
.description('List items that can be linked to a control')
|
|
891
|
+
.requiredOption('--type <type>', 'item type: test or document')
|
|
892
|
+
.option('--search <text>', 'filter by item name')
|
|
893
|
+
.option('--page <n>', 'page number')
|
|
894
|
+
.option('--limit <n>', 'page size')
|
|
895
|
+
.option('--api-url <url>', 'override the API base URL for this command')
|
|
896
|
+
.option('--json', 'output raw JSON')
|
|
897
|
+
.action(async (name, options) => {
|
|
898
|
+
await controlAvailableItemsCommand({
|
|
899
|
+
io,
|
|
900
|
+
name,
|
|
901
|
+
itemType: readStringOption(options, 'type') ?? 'test',
|
|
902
|
+
search: readStringOption(options, 'search'),
|
|
903
|
+
page: readNumberOption(options, 'page'),
|
|
904
|
+
limit: readNumberOption(options, 'limit'),
|
|
905
|
+
apiUrl: readStringOption(options, 'apiUrl'),
|
|
906
|
+
json: readBooleanOption(options, 'json'),
|
|
907
|
+
});
|
|
908
|
+
});
|
|
909
|
+
control
|
|
910
|
+
.command('update <control>')
|
|
911
|
+
.description('Change owner, applicability, design requirements or notes')
|
|
912
|
+
.option('--owner <id|none>', 'owner user id, or none to clear')
|
|
913
|
+
.option('--applicability <value>', 'in_scope or out_of_scope')
|
|
914
|
+
.option('--justification <text>', 'why the applicability changed')
|
|
915
|
+
.option('--design-requirements <state>', 'not_assessed | not_needed | not_met | partial | met')
|
|
916
|
+
.option('--notes <text>', 'notes (Markdown)')
|
|
917
|
+
.option('--rename <text>', 'new name (custom controls only)')
|
|
918
|
+
.option('--yes', 'apply without prompting')
|
|
919
|
+
.option('--api-url <url>', 'override the API base URL for this command')
|
|
920
|
+
.option('--json', 'output raw JSON')
|
|
921
|
+
.action(async (name, options) => {
|
|
922
|
+
const changes = {};
|
|
923
|
+
const owner = readStringOption(options, 'owner');
|
|
924
|
+
if (owner !== undefined)
|
|
925
|
+
changes.ownerId = owner === 'none' ? null : owner;
|
|
926
|
+
for (const [flag, field] of [
|
|
927
|
+
['applicability', 'applicability'],
|
|
928
|
+
['justification', 'justification'],
|
|
929
|
+
['designRequirements', 'designRequirements'],
|
|
930
|
+
['notes', 'notes'],
|
|
931
|
+
['rename', 'name'],
|
|
932
|
+
]) {
|
|
933
|
+
const value = readStringOption(options, flag);
|
|
934
|
+
if (value !== undefined)
|
|
935
|
+
changes[field] = value;
|
|
936
|
+
}
|
|
937
|
+
await controlUpdateCommand({
|
|
938
|
+
io,
|
|
939
|
+
name,
|
|
940
|
+
changes,
|
|
941
|
+
yes: readBooleanOption(options, 'yes'),
|
|
942
|
+
apiUrl: readStringOption(options, 'apiUrl'),
|
|
943
|
+
json: readBooleanOption(options, 'json'),
|
|
944
|
+
});
|
|
945
|
+
});
|
|
946
|
+
control
|
|
947
|
+
.command('link <control> <itemId>')
|
|
948
|
+
.description('Link a test or document to a control')
|
|
949
|
+
.requiredOption('--type <type>', 'item type: test or document')
|
|
950
|
+
.option('--unlink', 'unlink instead of link')
|
|
951
|
+
.option('--yes', 'apply without prompting')
|
|
952
|
+
.option('--api-url <url>', 'override the API base URL for this command')
|
|
953
|
+
.option('--json', 'output raw JSON')
|
|
954
|
+
.action(async (name, itemId, options) => {
|
|
955
|
+
await controlLinkCommand({
|
|
956
|
+
io,
|
|
957
|
+
name,
|
|
958
|
+
itemId,
|
|
959
|
+
itemType: readStringOption(options, 'type') ?? 'test',
|
|
960
|
+
action: readBooleanOption(options, 'unlink') ? 'unlink' : 'link',
|
|
961
|
+
yes: readBooleanOption(options, 'yes'),
|
|
962
|
+
apiUrl: readStringOption(options, 'apiUrl'),
|
|
963
|
+
json: readBooleanOption(options, 'json'),
|
|
964
|
+
});
|
|
965
|
+
});
|
|
966
|
+
vendor
|
|
967
|
+
.command('files <name>')
|
|
968
|
+
.description('List the files attached to a vendor')
|
|
969
|
+
.option('--api-url <url>', 'override the API base URL for this command')
|
|
970
|
+
.option('--json', 'output raw JSON')
|
|
971
|
+
.action(async (name, options) => {
|
|
972
|
+
await vendorFilesCommand({
|
|
973
|
+
io,
|
|
974
|
+
name,
|
|
975
|
+
apiUrl: readStringOption(options, 'apiUrl'),
|
|
976
|
+
json: readBooleanOption(options, 'json'),
|
|
977
|
+
});
|
|
978
|
+
});
|
|
979
|
+
vendor
|
|
980
|
+
.command('attach <name>')
|
|
981
|
+
.description('Attach a file to a vendor')
|
|
982
|
+
.option('--file <path>', 'stage and attach a local file')
|
|
983
|
+
.option('--upload <id>', 'attach an already-staged upload')
|
|
984
|
+
.option('--yes', 'apply without prompting')
|
|
985
|
+
.option('--api-url <url>', 'override the API base URL for this command')
|
|
986
|
+
.option('--json', 'output raw JSON')
|
|
987
|
+
.action(async (name, options) => {
|
|
988
|
+
await vendorAttachCommand({
|
|
989
|
+
io,
|
|
990
|
+
name,
|
|
991
|
+
file: readStringOption(options, 'file'),
|
|
992
|
+
uploadId: readStringOption(options, 'upload'),
|
|
993
|
+
yes: readBooleanOption(options, 'yes'),
|
|
994
|
+
apiUrl: readStringOption(options, 'apiUrl'),
|
|
995
|
+
json: readBooleanOption(options, 'json'),
|
|
996
|
+
});
|
|
997
|
+
});
|
|
998
|
+
vendor
|
|
999
|
+
.command('detach <name> <mappingId>')
|
|
1000
|
+
.description('Detach a file from a vendor by its mapping id')
|
|
1001
|
+
.option('--yes', 'apply without prompting')
|
|
1002
|
+
.option('--api-url <url>', 'override the API base URL for this command')
|
|
1003
|
+
.option('--json', 'output raw JSON')
|
|
1004
|
+
.action(async (name, mappingId, options) => {
|
|
1005
|
+
await vendorDetachCommand({
|
|
1006
|
+
io,
|
|
1007
|
+
name,
|
|
1008
|
+
mappingId,
|
|
1009
|
+
yes: readBooleanOption(options, 'yes'),
|
|
1010
|
+
apiUrl: readStringOption(options, 'apiUrl'),
|
|
1011
|
+
json: readBooleanOption(options, 'json'),
|
|
1012
|
+
});
|
|
1013
|
+
});
|
|
1014
|
+
const document = program.command('document').description('Browse, read and change documents');
|
|
1015
|
+
document
|
|
1016
|
+
.command('list')
|
|
1017
|
+
.description('List documents with status and counts')
|
|
1018
|
+
.option('--name <text>', 'filter by name')
|
|
1019
|
+
.option('--status <csv>', 'filter by workflow status')
|
|
1020
|
+
.option('--page <n>', 'page number')
|
|
1021
|
+
.option('--limit <n>', 'page size')
|
|
1022
|
+
.option('--api-url <url>', 'override the API base URL for this command')
|
|
1023
|
+
.option('--json', 'output raw JSON')
|
|
1024
|
+
.action(async (options) => {
|
|
1025
|
+
await policyListCommand({
|
|
1026
|
+
io,
|
|
1027
|
+
apiUrl: readStringOption(options, 'apiUrl'),
|
|
1028
|
+
json: readBooleanOption(options, 'json'),
|
|
1029
|
+
name: readStringOption(options, 'name'),
|
|
1030
|
+
status: readStringOption(options, 'status'),
|
|
1031
|
+
page: readNumberOption(options, 'page'),
|
|
1032
|
+
limit: readNumberOption(options, 'limit'),
|
|
1033
|
+
});
|
|
1034
|
+
});
|
|
1035
|
+
document
|
|
1036
|
+
.command('get <document>')
|
|
1037
|
+
.description('Print a document as Markdown (by name or id)')
|
|
1038
|
+
.option('--version-number <n>', 'a specific version number')
|
|
1039
|
+
.option('--minor-number <n>', 'a specific minor version (with --version-number)')
|
|
1040
|
+
.option('--api-url <url>', 'override the API base URL for this command')
|
|
1041
|
+
.option('--json', 'output raw JSON')
|
|
1042
|
+
.action(async (policyName, options) => {
|
|
1043
|
+
await policyGetCommand({
|
|
1044
|
+
io,
|
|
1045
|
+
policy: policyName,
|
|
1046
|
+
version: readNumberOption(options, 'versionNumber'),
|
|
1047
|
+
minor: readNumberOption(options, 'minorNumber'),
|
|
1048
|
+
apiUrl: readStringOption(options, 'apiUrl'),
|
|
1049
|
+
json: readBooleanOption(options, 'json'),
|
|
1050
|
+
});
|
|
1051
|
+
});
|
|
1052
|
+
document
|
|
1053
|
+
.command('update <document>')
|
|
1054
|
+
.description('Update document metadata')
|
|
1055
|
+
.option('--name <value>', 'rename')
|
|
1056
|
+
.option('--description <value>', 'description')
|
|
1057
|
+
.option('--type <value>', 'document type')
|
|
1058
|
+
.option('--publish <on|off>', 'published state')
|
|
1059
|
+
.option('--api-url <url>', 'override the API base URL for this command')
|
|
1060
|
+
.option('--json', 'output raw JSON')
|
|
1061
|
+
.action(async (policyName, options) => {
|
|
1062
|
+
const changes = {};
|
|
1063
|
+
const name = readStringOption(options, 'name');
|
|
1064
|
+
const description = readStringOption(options, 'description');
|
|
1065
|
+
const type = readStringOption(options, 'type');
|
|
1066
|
+
const publish = readStringOption(options, 'publish');
|
|
1067
|
+
if (name !== undefined)
|
|
1068
|
+
changes.name = name;
|
|
1069
|
+
if (description !== undefined)
|
|
1070
|
+
changes.description = description;
|
|
1071
|
+
if (type !== undefined)
|
|
1072
|
+
changes.type = type;
|
|
1073
|
+
if (publish !== undefined)
|
|
1074
|
+
changes.publish = publish === 'on' || publish === 'true';
|
|
1075
|
+
await policyUpdateMetaCommand({
|
|
1076
|
+
io,
|
|
1077
|
+
policy: policyName,
|
|
1078
|
+
changes,
|
|
1079
|
+
apiUrl: readStringOption(options, 'apiUrl'),
|
|
1080
|
+
json: readBooleanOption(options, 'json'),
|
|
1081
|
+
});
|
|
1082
|
+
});
|
|
1083
|
+
document
|
|
1084
|
+
.command('versions <document>')
|
|
1085
|
+
.description('Show a document\u2019s version history')
|
|
1086
|
+
.option('--api-url <url>', 'override the API base URL for this command')
|
|
1087
|
+
.option('--json', 'output raw JSON')
|
|
1088
|
+
.action(async (policyName, options) => {
|
|
1089
|
+
await policyVersionsCommand({
|
|
1090
|
+
io,
|
|
1091
|
+
policy: policyName,
|
|
1092
|
+
apiUrl: readStringOption(options, 'apiUrl'),
|
|
1093
|
+
json: readBooleanOption(options, 'json'),
|
|
1094
|
+
});
|
|
1095
|
+
});
|
|
1096
|
+
document
|
|
1097
|
+
.command('create')
|
|
1098
|
+
.description('Create a new blank document')
|
|
1099
|
+
.requiredOption('--name <name>', 'policy name')
|
|
1100
|
+
.option('--type <type>', 'document type')
|
|
1101
|
+
.option('--description <text>', 'short description')
|
|
1102
|
+
.option('--api-url <url>', 'override the API base URL for this command')
|
|
1103
|
+
.option('--json', 'output raw JSON')
|
|
1104
|
+
.action(async (options) => {
|
|
1105
|
+
await policyCreateCommand({
|
|
1106
|
+
io,
|
|
1107
|
+
name: readStringOption(options, 'name') ?? '',
|
|
1108
|
+
type: readStringOption(options, 'type'),
|
|
1109
|
+
description: readStringOption(options, 'description'),
|
|
1110
|
+
apiUrl: readStringOption(options, 'apiUrl'),
|
|
1111
|
+
json: readBooleanOption(options, 'json'),
|
|
1112
|
+
});
|
|
1113
|
+
});
|
|
1114
|
+
document
|
|
1115
|
+
.command('edit <document>')
|
|
1116
|
+
.description('Replace the content of a draft document with Markdown')
|
|
1117
|
+
.option('--file <path>', 'read the new Markdown from a file')
|
|
1118
|
+
.option('--content <markdown>', 'new Markdown inline')
|
|
1119
|
+
.option('--yes', 'apply without prompting')
|
|
1120
|
+
.option('--api-url <url>', 'override the API base URL for this command')
|
|
1121
|
+
.option('--json', 'output raw JSON')
|
|
1122
|
+
.action(async (policyName, options) => {
|
|
1123
|
+
await policyEditCommand({
|
|
1124
|
+
io,
|
|
1125
|
+
policy: policyName,
|
|
1126
|
+
file: readStringOption(options, 'file'),
|
|
1127
|
+
content: readStringOption(options, 'content'),
|
|
1128
|
+
yes: readBooleanOption(options, 'yes'),
|
|
1129
|
+
apiUrl: readStringOption(options, 'apiUrl'),
|
|
1130
|
+
json: readBooleanOption(options, 'json'),
|
|
1131
|
+
});
|
|
1132
|
+
});
|
|
1133
|
+
document
|
|
1134
|
+
.command('version <document>')
|
|
1135
|
+
.description('Create a new draft version of a document')
|
|
1136
|
+
.option('--change-type <type>', 'change type')
|
|
1137
|
+
.option('--summary <text>', 'change summary')
|
|
1138
|
+
.option('--yes', 'apply without prompting')
|
|
1139
|
+
.option('--api-url <url>', 'override the API base URL for this command')
|
|
1140
|
+
.option('--json', 'output raw JSON')
|
|
1141
|
+
.action(async (policyName, options) => {
|
|
1142
|
+
await policyVersionCommand({
|
|
1143
|
+
io,
|
|
1144
|
+
policy: policyName,
|
|
1145
|
+
changeType: readStringOption(options, 'changeType'),
|
|
1146
|
+
summary: readStringOption(options, 'summary'),
|
|
1147
|
+
yes: readBooleanOption(options, 'yes'),
|
|
1148
|
+
apiUrl: readStringOption(options, 'apiUrl'),
|
|
1149
|
+
json: readBooleanOption(options, 'json'),
|
|
1150
|
+
});
|
|
1151
|
+
});
|
|
1152
|
+
document
|
|
1153
|
+
.command('submit <document>')
|
|
1154
|
+
.description('Submit a draft document for review')
|
|
1155
|
+
.option('--yes', 'apply without prompting')
|
|
1156
|
+
.option('--api-url <url>', 'override the API base URL for this command')
|
|
1157
|
+
.option('--json', 'output raw JSON')
|
|
1158
|
+
.action(async (policyName, options) => {
|
|
1159
|
+
await policySubmitCommand({
|
|
1160
|
+
io,
|
|
1161
|
+
policy: policyName,
|
|
1162
|
+
yes: readBooleanOption(options, 'yes'),
|
|
1163
|
+
apiUrl: readStringOption(options, 'apiUrl'),
|
|
1164
|
+
json: readBooleanOption(options, 'json'),
|
|
1165
|
+
});
|
|
1166
|
+
});
|
|
1167
|
+
document
|
|
1168
|
+
.command('approve <document>')
|
|
1169
|
+
.description('Approve a document awaiting approval')
|
|
1170
|
+
.option('--yes', 'apply without prompting')
|
|
1171
|
+
.option('--api-url <url>', 'override the API base URL for this command')
|
|
1172
|
+
.option('--json', 'output raw JSON')
|
|
1173
|
+
.action(async (policyName, options) => {
|
|
1174
|
+
await policyApproveCommand({
|
|
1175
|
+
io,
|
|
1176
|
+
policy: policyName,
|
|
1177
|
+
yes: readBooleanOption(options, 'yes'),
|
|
1178
|
+
apiUrl: readStringOption(options, 'apiUrl'),
|
|
1179
|
+
json: readBooleanOption(options, 'json'),
|
|
1180
|
+
});
|
|
1181
|
+
});
|
|
1182
|
+
document
|
|
1183
|
+
.command('delete <document>')
|
|
1184
|
+
.description('Delete a document')
|
|
1185
|
+
.option('--yes', 'apply without prompting')
|
|
1186
|
+
.option('--api-url <url>', 'override the API base URL for this command')
|
|
1187
|
+
.option('--json', 'output raw JSON')
|
|
1188
|
+
.action(async (policyName, options) => {
|
|
1189
|
+
await policyDeleteCommand({
|
|
1190
|
+
io,
|
|
1191
|
+
policy: policyName,
|
|
1192
|
+
yes: readBooleanOption(options, 'yes'),
|
|
1193
|
+
apiUrl: readStringOption(options, 'apiUrl'),
|
|
1194
|
+
json: readBooleanOption(options, 'json'),
|
|
1195
|
+
});
|
|
1196
|
+
});
|
|
1197
|
+
const risk = program.command('risk').description('Manage risks');
|
|
1198
|
+
risk
|
|
1199
|
+
.command('list')
|
|
1200
|
+
.description('List the risk register')
|
|
1201
|
+
.option('--status <csv>', 'filter by status')
|
|
1202
|
+
.option('--business-unit <value>', 'filter by business unit')
|
|
1203
|
+
.option('--page <n>', 'page number')
|
|
1204
|
+
.option('--limit <n>', 'page size')
|
|
1205
|
+
.option('--api-url <url>', 'override the API base URL for this command')
|
|
1206
|
+
.option('--json', 'output raw JSON')
|
|
1207
|
+
.action(async (options) => {
|
|
1208
|
+
await riskListCommand({
|
|
1209
|
+
io,
|
|
1210
|
+
status: readStringOption(options, 'status'),
|
|
1211
|
+
businessUnit: readStringOption(options, 'businessUnit'),
|
|
1212
|
+
page: readNumberOption(options, 'page'),
|
|
1213
|
+
limit: readNumberOption(options, 'limit'),
|
|
1214
|
+
apiUrl: readStringOption(options, 'apiUrl'),
|
|
1215
|
+
json: readBooleanOption(options, 'json'),
|
|
1216
|
+
});
|
|
1217
|
+
});
|
|
1218
|
+
risk
|
|
1219
|
+
.command('get <risk>')
|
|
1220
|
+
.description('Show one risk by title or id')
|
|
1221
|
+
.option('--api-url <url>', 'override the API base URL for this command')
|
|
1222
|
+
.option('--json', 'output raw JSON')
|
|
1223
|
+
.action(async (riskName, options) => {
|
|
1224
|
+
await riskGetCommand({
|
|
1225
|
+
io,
|
|
1226
|
+
risk: riskName,
|
|
1227
|
+
apiUrl: readStringOption(options, 'apiUrl'),
|
|
1228
|
+
json: readBooleanOption(options, 'json'),
|
|
1229
|
+
});
|
|
1230
|
+
});
|
|
1231
|
+
risk
|
|
1232
|
+
.command('update <risk>')
|
|
1233
|
+
.description('Update risk fields')
|
|
1234
|
+
.option('--status <value>', 'open | in_treatment | monitor | closed')
|
|
1235
|
+
.option('--title <value>', 'title')
|
|
1236
|
+
.option('--description <value>', 'description')
|
|
1237
|
+
.option('--owner <value>', 'owner')
|
|
1238
|
+
.option('--treatment-type <value>', 'treatment strategy')
|
|
1239
|
+
.option('--treatment-notes <text>', 'treatment notes (Markdown)')
|
|
1240
|
+
.option('--business-unit <value>', 'business unit')
|
|
1241
|
+
.option('--api-url <url>', 'override the API base URL for this command')
|
|
1242
|
+
.option('--json', 'output raw JSON')
|
|
1243
|
+
.action(async (riskName, options) => {
|
|
1244
|
+
const changes = {};
|
|
1245
|
+
for (const field of [
|
|
1246
|
+
'status',
|
|
1247
|
+
'title',
|
|
1248
|
+
'description',
|
|
1249
|
+
'owner',
|
|
1250
|
+
'businessUnit',
|
|
1251
|
+
'treatmentType',
|
|
1252
|
+
'treatmentNotes',
|
|
1253
|
+
]) {
|
|
1254
|
+
const value = readStringOption(options, field);
|
|
1255
|
+
if (value !== undefined)
|
|
1256
|
+
changes[field] = value;
|
|
1257
|
+
}
|
|
1258
|
+
await riskUpdateCommand({
|
|
1259
|
+
io,
|
|
1260
|
+
risk: riskName,
|
|
1261
|
+
changes,
|
|
1262
|
+
apiUrl: readStringOption(options, 'apiUrl'),
|
|
1263
|
+
json: readBooleanOption(options, 'json'),
|
|
1264
|
+
});
|
|
1265
|
+
});
|
|
1266
|
+
risk
|
|
1267
|
+
.command('create')
|
|
1268
|
+
.description('Create a risk')
|
|
1269
|
+
.requiredOption('--title <title>', 'risk title')
|
|
1270
|
+
.option('--category <value>', 'category')
|
|
1271
|
+
.option('--description <value>', 'description')
|
|
1272
|
+
.option('--api-url <url>', 'override the API base URL for this command')
|
|
1273
|
+
.option('--json', 'output raw JSON')
|
|
1274
|
+
.action(async (options) => {
|
|
1275
|
+
await riskCreateCommand({
|
|
1276
|
+
io,
|
|
1277
|
+
title: readStringOption(options, 'title') ?? '',
|
|
1278
|
+
category: readStringOption(options, 'category'),
|
|
1279
|
+
description: readStringOption(options, 'description'),
|
|
1280
|
+
apiUrl: readStringOption(options, 'apiUrl'),
|
|
1281
|
+
json: readBooleanOption(options, 'json'),
|
|
1282
|
+
});
|
|
1283
|
+
});
|
|
1284
|
+
risk
|
|
1285
|
+
.command('delete <risk>')
|
|
1286
|
+
.description('Delete a risk')
|
|
1287
|
+
.option('--yes', 'apply without prompting')
|
|
1288
|
+
.option('--api-url <url>', 'override the API base URL for this command')
|
|
1289
|
+
.option('--json', 'output raw JSON')
|
|
1290
|
+
.action(async (riskName, options) => {
|
|
1291
|
+
await riskDeleteCommand({
|
|
1292
|
+
io,
|
|
1293
|
+
risk: riskName,
|
|
1294
|
+
yes: readBooleanOption(options, 'yes'),
|
|
1295
|
+
apiUrl: readStringOption(options, 'apiUrl'),
|
|
1296
|
+
json: readBooleanOption(options, 'json'),
|
|
1297
|
+
});
|
|
1298
|
+
});
|
|
1299
|
+
risk
|
|
1300
|
+
.command('treatment <risk>')
|
|
1301
|
+
.description('Show a risk’s treatment strategy and notes')
|
|
1302
|
+
.option('--api-url <url>', 'override the API base URL for this command')
|
|
1303
|
+
.option('--json', 'output raw JSON')
|
|
1304
|
+
.action(async (risk, options) => {
|
|
1305
|
+
await riskTreatmentCommand({
|
|
1306
|
+
io,
|
|
1307
|
+
risk,
|
|
1308
|
+
apiUrl: readStringOption(options, 'apiUrl'),
|
|
1309
|
+
json: readBooleanOption(options, 'json'),
|
|
1310
|
+
});
|
|
1311
|
+
});
|
|
1312
|
+
risk
|
|
1313
|
+
.command('scores <risk>')
|
|
1314
|
+
.description('Show a risk’s scores')
|
|
1315
|
+
.option('--api-url <url>', 'override the API base URL for this command')
|
|
1316
|
+
.option('--json', 'output raw JSON')
|
|
1317
|
+
.action(async (risk, options) => {
|
|
1318
|
+
await riskScoresCommand({
|
|
1319
|
+
io,
|
|
1320
|
+
risk,
|
|
1321
|
+
apiUrl: readStringOption(options, 'apiUrl'),
|
|
1322
|
+
json: readBooleanOption(options, 'json'),
|
|
1323
|
+
});
|
|
1324
|
+
});
|
|
1325
|
+
risk
|
|
1326
|
+
.command('controls <risk>')
|
|
1327
|
+
.description('List the controls linked to a risk')
|
|
1328
|
+
.option('--api-url <url>', 'override the API base URL for this command')
|
|
1329
|
+
.option('--json', 'output raw JSON')
|
|
1330
|
+
.action(async (risk, options) => {
|
|
1331
|
+
await riskControlsCommand({
|
|
1332
|
+
io,
|
|
1333
|
+
risk,
|
|
1334
|
+
apiUrl: readStringOption(options, 'apiUrl'),
|
|
1335
|
+
json: readBooleanOption(options, 'json'),
|
|
1336
|
+
});
|
|
1337
|
+
});
|
|
1338
|
+
risk
|
|
1339
|
+
.command('link <risk>')
|
|
1340
|
+
.description('Link a control to a risk, or unlink a mapping')
|
|
1341
|
+
.option('--control <id>', 'control instance id to link')
|
|
1342
|
+
.option('--unlink <mappingId>', 'control mapping id to unlink')
|
|
1343
|
+
.option('--yes', 'apply without prompting')
|
|
1344
|
+
.option('--api-url <url>', 'override the API base URL for this command')
|
|
1345
|
+
.option('--json', 'output raw JSON')
|
|
1346
|
+
.action(async (risk, options) => {
|
|
1347
|
+
const mappingId = readStringOption(options, 'unlink');
|
|
1348
|
+
await riskLinkControlCommand({
|
|
1349
|
+
io,
|
|
1350
|
+
risk,
|
|
1351
|
+
controlInstanceId: readStringOption(options, 'control'),
|
|
1352
|
+
mappingId,
|
|
1353
|
+
action: mappingId ? 'unlink' : 'link',
|
|
1354
|
+
yes: readBooleanOption(options, 'yes'),
|
|
1355
|
+
apiUrl: readStringOption(options, 'apiUrl'),
|
|
1356
|
+
json: readBooleanOption(options, 'json'),
|
|
1357
|
+
});
|
|
1358
|
+
});
|
|
1359
|
+
risk
|
|
1360
|
+
.command('files <risk>')
|
|
1361
|
+
.description('List the files attached to a risk')
|
|
1362
|
+
.option('--api-url <url>', 'override the API base URL for this command')
|
|
1363
|
+
.option('--json', 'output raw JSON')
|
|
1364
|
+
.action(async (risk, options) => {
|
|
1365
|
+
await riskFilesCommand({
|
|
1366
|
+
io,
|
|
1367
|
+
risk,
|
|
1368
|
+
apiUrl: readStringOption(options, 'apiUrl'),
|
|
1369
|
+
json: readBooleanOption(options, 'json'),
|
|
1370
|
+
});
|
|
1371
|
+
});
|
|
1372
|
+
risk
|
|
1373
|
+
.command('attach <risk>')
|
|
1374
|
+
.description('Attach a file to a risk')
|
|
1375
|
+
.option('--file <path>', 'stage and attach a local file')
|
|
1376
|
+
.option('--upload <id>', 'attach an already-staged upload')
|
|
1377
|
+
.option('--yes', 'apply without prompting')
|
|
1378
|
+
.option('--api-url <url>', 'override the API base URL for this command')
|
|
1379
|
+
.option('--json', 'output raw JSON')
|
|
1380
|
+
.action(async (risk, options) => {
|
|
1381
|
+
await riskAttachCommand({
|
|
1382
|
+
io,
|
|
1383
|
+
risk,
|
|
1384
|
+
file: readStringOption(options, 'file'),
|
|
1385
|
+
uploadId: readStringOption(options, 'upload'),
|
|
1386
|
+
yes: readBooleanOption(options, 'yes'),
|
|
1387
|
+
apiUrl: readStringOption(options, 'apiUrl'),
|
|
1388
|
+
json: readBooleanOption(options, 'json'),
|
|
1389
|
+
});
|
|
1390
|
+
});
|
|
1391
|
+
risk
|
|
1392
|
+
.command('detach <risk> <mappingId>')
|
|
1393
|
+
.description('Detach a file from a risk by its mapping id')
|
|
1394
|
+
.option('--yes', 'apply without prompting')
|
|
1395
|
+
.option('--api-url <url>', 'override the API base URL for this command')
|
|
1396
|
+
.option('--json', 'output raw JSON')
|
|
1397
|
+
.action(async (risk, mappingId, options) => {
|
|
1398
|
+
await riskDetachCommand({
|
|
1399
|
+
io,
|
|
1400
|
+
risk,
|
|
1401
|
+
mappingId,
|
|
1402
|
+
yes: readBooleanOption(options, 'yes'),
|
|
1403
|
+
apiUrl: readStringOption(options, 'apiUrl'),
|
|
1404
|
+
json: readBooleanOption(options, 'json'),
|
|
1405
|
+
});
|
|
1406
|
+
});
|
|
1407
|
+
const check = program.command('check').description('Monitoring checks');
|
|
1408
|
+
check
|
|
1409
|
+
.command('list')
|
|
1410
|
+
.description('List monitoring checks')
|
|
1411
|
+
.option('--integration-type <csv>', 'filter by integration type')
|
|
1412
|
+
.option('--status <csv>', 'filter by status')
|
|
1413
|
+
.option('--page <n>', 'page number')
|
|
1414
|
+
.option('--limit <n>', 'page size')
|
|
1415
|
+
.option('--api-url <url>', 'override the API base URL for this command')
|
|
1416
|
+
.option('--json', 'output raw JSON')
|
|
1417
|
+
.action(async (options) => {
|
|
1418
|
+
await checkListCommand({
|
|
1419
|
+
io,
|
|
1420
|
+
integrationType: readStringOption(options, 'integrationType'),
|
|
1421
|
+
status: readStringOption(options, 'status'),
|
|
1422
|
+
page: readNumberOption(options, 'page'),
|
|
1423
|
+
limit: readNumberOption(options, 'limit'),
|
|
1424
|
+
apiUrl: readStringOption(options, 'apiUrl'),
|
|
1425
|
+
json: readBooleanOption(options, 'json'),
|
|
1426
|
+
});
|
|
1427
|
+
});
|
|
1428
|
+
check
|
|
1429
|
+
.command('resources <check>')
|
|
1430
|
+
.description('List the resources covered by a check')
|
|
1431
|
+
.option('--api-url <url>', 'override the API base URL for this command')
|
|
1432
|
+
.option('--json', 'output raw JSON')
|
|
1433
|
+
.action(async (checkName, options) => {
|
|
1434
|
+
await checkResourcesCommand({
|
|
1435
|
+
io,
|
|
1436
|
+
check: checkName,
|
|
1437
|
+
apiUrl: readStringOption(options, 'apiUrl'),
|
|
1438
|
+
json: readBooleanOption(options, 'json'),
|
|
1439
|
+
});
|
|
1440
|
+
});
|
|
1441
|
+
check
|
|
1442
|
+
.command('run <check>')
|
|
1443
|
+
.description('Show a check\u2019s latest run')
|
|
1444
|
+
.option('--api-url <url>', 'override the API base URL for this command')
|
|
1445
|
+
.option('--json', 'output raw JSON')
|
|
1446
|
+
.action(async (checkName, options) => {
|
|
1447
|
+
await checkRunCommand({
|
|
1448
|
+
io,
|
|
1449
|
+
check: checkName,
|
|
1450
|
+
apiUrl: readStringOption(options, 'apiUrl'),
|
|
1451
|
+
json: readBooleanOption(options, 'json'),
|
|
1452
|
+
});
|
|
1453
|
+
});
|
|
1454
|
+
check
|
|
1455
|
+
.command('integrations')
|
|
1456
|
+
.description('List check integration types')
|
|
1457
|
+
.option('--api-url <url>', 'override the API base URL for this command')
|
|
1458
|
+
.option('--json', 'output raw JSON')
|
|
1459
|
+
.action(async (options) => {
|
|
1460
|
+
await checkIntegrationsCommand({
|
|
1461
|
+
io,
|
|
1462
|
+
apiUrl: readStringOption(options, 'apiUrl'),
|
|
1463
|
+
json: readBooleanOption(options, 'json'),
|
|
1464
|
+
});
|
|
1465
|
+
});
|
|
1466
|
+
check
|
|
1467
|
+
.command('runs <check>')
|
|
1468
|
+
.description('List a check’s run history')
|
|
1469
|
+
.option('--page <n>', 'page number')
|
|
1470
|
+
.option('--limit <n>', 'page size')
|
|
1471
|
+
.option('--api-url <url>', 'override the API base URL for this command')
|
|
1472
|
+
.option('--json', 'output raw JSON')
|
|
1473
|
+
.action(async (name, options) => {
|
|
1474
|
+
await checkRunsCommand({
|
|
1475
|
+
io,
|
|
1476
|
+
check: name,
|
|
1477
|
+
page: readNumberOption(options, 'page'),
|
|
1478
|
+
limit: readNumberOption(options, 'limit'),
|
|
1479
|
+
apiUrl: readStringOption(options, 'apiUrl'),
|
|
1480
|
+
json: readBooleanOption(options, 'json'),
|
|
1481
|
+
});
|
|
1482
|
+
});
|
|
1483
|
+
check
|
|
1484
|
+
.command('findings <check>')
|
|
1485
|
+
.description('List the findings raised from a check')
|
|
1486
|
+
.option('--api-url <url>', 'override the API base URL for this command')
|
|
1487
|
+
.option('--json', 'output raw JSON')
|
|
1488
|
+
.action(async (name, options) => {
|
|
1489
|
+
await checkFindingsCommand({
|
|
1490
|
+
io,
|
|
1491
|
+
check: name,
|
|
1492
|
+
apiUrl: readStringOption(options, 'apiUrl'),
|
|
1493
|
+
json: readBooleanOption(options, 'json'),
|
|
1494
|
+
});
|
|
1495
|
+
});
|
|
1496
|
+
check
|
|
1497
|
+
.command('enable <check>')
|
|
1498
|
+
.description('Enable a check for this tenant')
|
|
1499
|
+
.option('--notes <text>', 'why')
|
|
1500
|
+
.option('--yes', 'apply without prompting')
|
|
1501
|
+
.option('--api-url <url>', 'override the API base URL for this command')
|
|
1502
|
+
.option('--json', 'output raw JSON')
|
|
1503
|
+
.action(async (name, options) => {
|
|
1504
|
+
await checkSetEnabledCommand({
|
|
1505
|
+
io,
|
|
1506
|
+
check: name,
|
|
1507
|
+
enabled: true,
|
|
1508
|
+
notes: readStringOption(options, 'notes'),
|
|
1509
|
+
yes: readBooleanOption(options, 'yes'),
|
|
1510
|
+
apiUrl: readStringOption(options, 'apiUrl'),
|
|
1511
|
+
json: readBooleanOption(options, 'json'),
|
|
1512
|
+
});
|
|
1513
|
+
});
|
|
1514
|
+
check
|
|
1515
|
+
.command('disable <check>')
|
|
1516
|
+
.description('Disable a check for this tenant')
|
|
1517
|
+
.option('--notes <text>', 'why')
|
|
1518
|
+
.option('--yes', 'apply without prompting')
|
|
1519
|
+
.option('--api-url <url>', 'override the API base URL for this command')
|
|
1520
|
+
.option('--json', 'output raw JSON')
|
|
1521
|
+
.action(async (name, options) => {
|
|
1522
|
+
await checkSetEnabledCommand({
|
|
1523
|
+
io,
|
|
1524
|
+
check: name,
|
|
1525
|
+
enabled: false,
|
|
1526
|
+
notes: readStringOption(options, 'notes'),
|
|
1527
|
+
yes: readBooleanOption(options, 'yes'),
|
|
1528
|
+
apiUrl: readStringOption(options, 'apiUrl'),
|
|
1529
|
+
json: readBooleanOption(options, 'json'),
|
|
1530
|
+
});
|
|
1531
|
+
});
|
|
1532
|
+
const job = program.command('job').description('Check background jobs started by other commands');
|
|
1533
|
+
job
|
|
1534
|
+
.command('status <jobId>')
|
|
1535
|
+
.description('Show the status of a background job')
|
|
1536
|
+
.option('--api-url <url>', 'override the API base URL for this command')
|
|
1537
|
+
.option('--json', 'output raw JSON')
|
|
1538
|
+
.action(async (jobId, options) => {
|
|
1539
|
+
await jobStatusCommand({
|
|
1540
|
+
io,
|
|
1541
|
+
jobId,
|
|
1542
|
+
apiUrl: readStringOption(options, 'apiUrl'),
|
|
1543
|
+
json: readBooleanOption(options, 'json'),
|
|
1544
|
+
});
|
|
1545
|
+
});
|
|
1546
|
+
// A bare `ciphrix` shows the banner and help; commands never print the banner.
|
|
1547
|
+
program.action(() => {
|
|
1548
|
+
writeBlock(io.stdout, `${formatBanner(io.theme)}\n\n`);
|
|
1549
|
+
program.outputHelp();
|
|
1550
|
+
});
|
|
1551
|
+
return program;
|
|
1552
|
+
};
|
|
1553
|
+
/**
|
|
1554
|
+
* Runs the CLI and resolves to a process exit code. `--help`/`--version` and
|
|
1555
|
+
* usage errors are handled by commander and mapped to their exit codes.
|
|
1556
|
+
*/
|
|
1557
|
+
export const main = async (argv = process.argv, io = createDefaultIO()) => {
|
|
1558
|
+
const program = createProgram(io);
|
|
1559
|
+
program.exitOverride();
|
|
1560
|
+
program.configureOutput({
|
|
1561
|
+
writeOut: (text) => io.stdout.write(text),
|
|
1562
|
+
writeErr: (text) => io.stderr.write(text),
|
|
1563
|
+
});
|
|
1564
|
+
try {
|
|
1565
|
+
await program.parseAsync([...argv]);
|
|
1566
|
+
return 0;
|
|
1567
|
+
}
|
|
1568
|
+
catch (error) {
|
|
1569
|
+
if (error instanceof CommanderError) {
|
|
1570
|
+
return error.exitCode;
|
|
1571
|
+
}
|
|
1572
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1573
|
+
writeLine(io.stderr, `${io.theme.red('error')} ${message}`);
|
|
1574
|
+
return 1;
|
|
1575
|
+
}
|
|
1576
|
+
finally {
|
|
1577
|
+
setCliInsecureHttpOptIn(false);
|
|
1578
|
+
}
|
|
1579
|
+
};
|