@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
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import { writeLine, writeNextHint, writePageFooter, writeTable } from '../io.js';
|
|
2
|
+
import { asRecord, asText, callTool, errorMessage, resolveToolContext } from '../toolSurface.js';
|
|
3
|
+
const UUID = /^[0-9a-f]{8}-[0-9a-f-]{27}$/i;
|
|
4
|
+
const num = (value, fallback) => typeof value === 'number' ? String(value) : asText(value, fallback);
|
|
5
|
+
export const policyListCommand = async ({ io, apiUrl, json = false, name, status, page, limit, store, }) => {
|
|
6
|
+
const ctx = await resolveToolContext(apiUrl, store);
|
|
7
|
+
const input = {};
|
|
8
|
+
if (name)
|
|
9
|
+
input.name = name;
|
|
10
|
+
if (status)
|
|
11
|
+
input.status = status
|
|
12
|
+
.split(',')
|
|
13
|
+
.map((value) => value.trim())
|
|
14
|
+
.filter(Boolean);
|
|
15
|
+
if (page)
|
|
16
|
+
input.page = page;
|
|
17
|
+
if (limit)
|
|
18
|
+
input.limit = limit;
|
|
19
|
+
const envelope = await callTool(ctx, 'list_documents', input);
|
|
20
|
+
if (envelope.status !== 'ok')
|
|
21
|
+
throw new Error(errorMessage(envelope, 'Could not list documents.'));
|
|
22
|
+
if (json) {
|
|
23
|
+
writeLine(io.stdout, JSON.stringify(envelope, null, 2));
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
const data = asRecord(envelope.data);
|
|
27
|
+
const policies = Array.isArray(data.policies) ? data.policies : [];
|
|
28
|
+
const counts = asRecord(data.counts);
|
|
29
|
+
const count = (value) => typeof value === 'number' ? String(value) : asText(value, '0');
|
|
30
|
+
writeLine(io.stdout, `${io.theme.bold('Documents')} ${io.theme.dim(`(${count(data.total)} total · ${count(counts.draft)} draft · ${count(counts.pending)} pending · ${count(counts.published)} published)`)}`);
|
|
31
|
+
if (policies.length === 0) {
|
|
32
|
+
writeLine(io.stdout, 'No documents found.');
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
const rows = policies.map((policy) => {
|
|
36
|
+
const record = asRecord(policy);
|
|
37
|
+
return [
|
|
38
|
+
asText(record.code, '-'),
|
|
39
|
+
asText(record.name),
|
|
40
|
+
asText(record.status, '-'),
|
|
41
|
+
record.isPublished === true ? 'published' : '-',
|
|
42
|
+
];
|
|
43
|
+
});
|
|
44
|
+
writeTable(io, ['CODE', 'NAME', 'STATUS', 'PUBLISHED'], rows);
|
|
45
|
+
writePageFooter(io, {
|
|
46
|
+
shown: policies.length,
|
|
47
|
+
total: typeof data.total === 'number' ? data.total : policies.length,
|
|
48
|
+
page: typeof data.page === 'number' ? data.page : 1,
|
|
49
|
+
limit: typeof data.limit === 'number' ? data.limit : 25,
|
|
50
|
+
hasMore: data.hasMore === true,
|
|
51
|
+
});
|
|
52
|
+
const next = asText(asRecord(policies[0]).code);
|
|
53
|
+
if (next)
|
|
54
|
+
writeNextHint(io, `ciphrix document get ${next}`);
|
|
55
|
+
};
|
|
56
|
+
export const policyGetCommand = async ({ io, apiUrl, json = false, policy, version, minor, store, }) => {
|
|
57
|
+
const ctx = await resolveToolContext(apiUrl, store);
|
|
58
|
+
if (version) {
|
|
59
|
+
const versionEnvelope = await callTool(ctx, 'get_document_version', {
|
|
60
|
+
...(UUID.test(policy) ? { policyId: policy } : { name: policy }),
|
|
61
|
+
versionNumber: version,
|
|
62
|
+
...(minor !== undefined ? { minorVersionNumber: minor } : {}),
|
|
63
|
+
});
|
|
64
|
+
if (versionEnvelope.status !== 'ok')
|
|
65
|
+
throw new Error(errorMessage(versionEnvelope, 'Document version not found.'));
|
|
66
|
+
const recordV = asRecord(versionEnvelope.data);
|
|
67
|
+
if (json) {
|
|
68
|
+
writeLine(io.stdout, JSON.stringify(recordV, null, 2));
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
writeLine(io.stdout, `${io.theme.bold(policy)} ${io.theme.dim(`· version ${num(recordV.versionNumber, String(version))}.${num(recordV.minorVersionNumber, '0')} · ${asText(recordV.status, 'unknown')}`)}`);
|
|
72
|
+
const versionMeta = [
|
|
73
|
+
['Reviewer', recordV.reviewedBy],
|
|
74
|
+
['Approver', recordV.approvedBy],
|
|
75
|
+
['Approved', recordV.approvedAt],
|
|
76
|
+
];
|
|
77
|
+
for (const [label, value] of versionMeta) {
|
|
78
|
+
if (value === undefined || value === null || value === '')
|
|
79
|
+
continue;
|
|
80
|
+
writeLine(io.stdout, ` ${io.theme.dim(`${label}:`)} ${asText(value)}`);
|
|
81
|
+
}
|
|
82
|
+
writeLine(io.stdout);
|
|
83
|
+
const body = asText(recordV.contentMarkdown);
|
|
84
|
+
writeLine(io.stdout, body.trim() === '' ? io.theme.dim('(no content)') : body.trimEnd());
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
const envelope = await callTool(ctx, 'get_document', UUID.test(policy) ? { policyId: policy } : { name: policy });
|
|
88
|
+
if (envelope.status !== 'ok')
|
|
89
|
+
throw new Error(errorMessage(envelope, 'Document not found.'));
|
|
90
|
+
const record = asRecord(asRecord(envelope.data).policy);
|
|
91
|
+
if (json) {
|
|
92
|
+
writeLine(io.stdout, JSON.stringify(record, null, 2));
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
const currentVersion = asRecord(record.currentVersion);
|
|
96
|
+
const meta = [
|
|
97
|
+
['Reviewer', currentVersion.reviewer ?? currentVersion.reviewedBy],
|
|
98
|
+
['Approver', currentVersion.approver ?? currentVersion.approvedBy],
|
|
99
|
+
['Approved', currentVersion.approvedAt],
|
|
100
|
+
];
|
|
101
|
+
for (const [label, value] of meta) {
|
|
102
|
+
if (value === undefined || value === null || value === '')
|
|
103
|
+
continue;
|
|
104
|
+
writeLine(io.stdout, ` ${io.theme.dim(`${label}:`)} ${asText(value)}`);
|
|
105
|
+
}
|
|
106
|
+
if (meta.some(([, value]) => value !== undefined && value !== null && value !== ''))
|
|
107
|
+
writeLine(io.stdout);
|
|
108
|
+
const markdown = asText(currentVersion.contentMarkdown);
|
|
109
|
+
writeLine(io.stdout, `${io.theme.bold(asText(record.name, 'Document'))} ${io.theme.dim(`· ${asText(record.status, 'unknown')}${record.isPublished === true ? ' · published' : ''}`)}`);
|
|
110
|
+
writeLine(io.stdout);
|
|
111
|
+
writeLine(io.stdout, markdown.trim() === '' ? io.theme.dim('(no content)') : markdown.trimEnd());
|
|
112
|
+
};
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { CredentialStore } from '../credentials.js';
|
|
2
|
+
import { type CliIO } from '../io.js';
|
|
3
|
+
export interface PolicyActionOptions {
|
|
4
|
+
io: CliIO;
|
|
5
|
+
apiUrl?: string | undefined;
|
|
6
|
+
json?: boolean | undefined;
|
|
7
|
+
yes?: boolean | undefined;
|
|
8
|
+
store?: CredentialStore | undefined;
|
|
9
|
+
confirm?: ((question: string) => Promise<boolean>) | undefined;
|
|
10
|
+
}
|
|
11
|
+
export interface PolicyTargetOptions extends PolicyActionOptions {
|
|
12
|
+
policy: string;
|
|
13
|
+
}
|
|
14
|
+
export interface PolicyCreateOptions extends PolicyActionOptions {
|
|
15
|
+
name: string;
|
|
16
|
+
type?: string | undefined;
|
|
17
|
+
description?: string | undefined;
|
|
18
|
+
}
|
|
19
|
+
export declare const policyCreateCommand: ({ io, apiUrl, json, yes, store, confirm, name, type, description, }: PolicyCreateOptions) => Promise<void>;
|
|
20
|
+
export interface PolicyEditOptions extends PolicyTargetOptions {
|
|
21
|
+
content?: string | undefined;
|
|
22
|
+
file?: string | undefined;
|
|
23
|
+
}
|
|
24
|
+
export declare const policyEditCommand: ({ io, apiUrl, json, yes, store, confirm, policy, content, file, }: PolicyEditOptions) => Promise<void>;
|
|
25
|
+
export interface PolicyVersionOptions extends PolicyTargetOptions {
|
|
26
|
+
changeType?: string | undefined;
|
|
27
|
+
summary?: string | undefined;
|
|
28
|
+
}
|
|
29
|
+
export declare const policyVersionCommand: ({ io, apiUrl, json, yes, store, confirm, policy, changeType, summary, }: PolicyVersionOptions) => Promise<void>;
|
|
30
|
+
export declare const policySubmitCommand: (options: PolicyTargetOptions) => Promise<void>;
|
|
31
|
+
export declare const policyApproveCommand: (options: PolicyTargetOptions) => Promise<void>;
|
|
32
|
+
export declare const policyDeleteCommand: ({ io, apiUrl, json, yes, store, confirm, policy, }: PolicyTargetOptions) => Promise<void>;
|
|
33
|
+
export interface PolicyMetadataUpdateOptions extends PolicyTargetOptions {
|
|
34
|
+
changes: Record<string, unknown>;
|
|
35
|
+
}
|
|
36
|
+
export declare const policyUpdateMetaCommand: ({ io, apiUrl, json, store, policy, changes, }: PolicyMetadataUpdateOptions) => Promise<void>;
|
|
37
|
+
export declare const policyVersionsCommand: ({ io, apiUrl, json, store, policy, }: PolicyTargetOptions) => Promise<void>;
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
import { readFile } from 'node:fs/promises';
|
|
2
|
+
import { applyTool } from '../actions.js';
|
|
3
|
+
import { writeLine, writeTable } from '../io.js';
|
|
4
|
+
import { asRecord, asText, callTool, errorMessage, resolveToolContext } from '../toolSurface.js';
|
|
5
|
+
const UUID = /^[0-9a-f]{8}-[0-9a-f-]{27}$/i;
|
|
6
|
+
const num = (value, fallback) => typeof value === 'number' ? String(value) : asText(value, fallback);
|
|
7
|
+
const target = (policy) => UUID.test(policy) ? { policyId: policy } : { name: policy };
|
|
8
|
+
const renderResult = (io, json, result) => {
|
|
9
|
+
if (json) {
|
|
10
|
+
writeLine(io.stdout, JSON.stringify(result, null, 2));
|
|
11
|
+
return;
|
|
12
|
+
}
|
|
13
|
+
const data = asRecord(result.data);
|
|
14
|
+
const name = asText(data.name, asText(data.policyId, 'document'));
|
|
15
|
+
const status = asText(data.status, 'done');
|
|
16
|
+
writeLine(io.stdout, `${io.theme.green('✓')} ${name}: ${status}`);
|
|
17
|
+
};
|
|
18
|
+
export const policyCreateCommand = async ({ io, apiUrl, json = false, yes = false, store, confirm, name, type, description, }) => {
|
|
19
|
+
const ctx = await resolveToolContext(apiUrl, store);
|
|
20
|
+
const input = { name };
|
|
21
|
+
if (type)
|
|
22
|
+
input.type = type;
|
|
23
|
+
if (description)
|
|
24
|
+
input.description = description;
|
|
25
|
+
const result = await applyTool({
|
|
26
|
+
ctx,
|
|
27
|
+
io,
|
|
28
|
+
toolName: 'create_document',
|
|
29
|
+
input,
|
|
30
|
+
fallback: 'Could not create the document.',
|
|
31
|
+
yes,
|
|
32
|
+
confirm,
|
|
33
|
+
});
|
|
34
|
+
if (result)
|
|
35
|
+
renderResult(io, json, result);
|
|
36
|
+
};
|
|
37
|
+
export const policyEditCommand = async ({ io, apiUrl, json = false, yes = false, store, confirm, policy, content, file, }) => {
|
|
38
|
+
const markdown = file ? await readFile(file, 'utf8') : content;
|
|
39
|
+
if (!markdown) {
|
|
40
|
+
throw new Error('Provide the new content with --file <path> or --content <markdown>.');
|
|
41
|
+
}
|
|
42
|
+
const ctx = await resolveToolContext(apiUrl, store);
|
|
43
|
+
const result = await applyTool({
|
|
44
|
+
ctx,
|
|
45
|
+
io,
|
|
46
|
+
toolName: 'edit_document_content',
|
|
47
|
+
input: { ...target(policy), contentMarkdown: markdown },
|
|
48
|
+
fallback: 'Could not edit the document.',
|
|
49
|
+
yes,
|
|
50
|
+
confirm,
|
|
51
|
+
});
|
|
52
|
+
if (result)
|
|
53
|
+
renderResult(io, json, result);
|
|
54
|
+
};
|
|
55
|
+
export const policyVersionCommand = async ({ io, apiUrl, json = false, yes = false, store, confirm, policy, changeType, summary, }) => {
|
|
56
|
+
const ctx = await resolveToolContext(apiUrl, store);
|
|
57
|
+
const input = { ...target(policy) };
|
|
58
|
+
if (changeType)
|
|
59
|
+
input.changeType = changeType;
|
|
60
|
+
if (summary)
|
|
61
|
+
input.changeSummary = summary;
|
|
62
|
+
const result = await applyTool({
|
|
63
|
+
ctx,
|
|
64
|
+
io,
|
|
65
|
+
toolName: 'create_document_version',
|
|
66
|
+
input,
|
|
67
|
+
fallback: 'Could not create a new version.',
|
|
68
|
+
yes,
|
|
69
|
+
confirm,
|
|
70
|
+
});
|
|
71
|
+
if (result)
|
|
72
|
+
renderResult(io, json, result);
|
|
73
|
+
};
|
|
74
|
+
export const policySubmitCommand = async (options) => {
|
|
75
|
+
const { io, apiUrl, json = false, yes = false, store, confirm, policy } = options;
|
|
76
|
+
const ctx = await resolveToolContext(apiUrl, store);
|
|
77
|
+
const result = await applyTool({
|
|
78
|
+
ctx,
|
|
79
|
+
io,
|
|
80
|
+
toolName: 'submit_document',
|
|
81
|
+
input: target(policy),
|
|
82
|
+
fallback: 'Could not submit the document for review.',
|
|
83
|
+
yes,
|
|
84
|
+
confirm,
|
|
85
|
+
});
|
|
86
|
+
if (result)
|
|
87
|
+
renderResult(io, json, result);
|
|
88
|
+
};
|
|
89
|
+
export const policyApproveCommand = async (options) => {
|
|
90
|
+
const { io, apiUrl, json = false, yes = false, store, confirm, policy } = options;
|
|
91
|
+
const ctx = await resolveToolContext(apiUrl, store);
|
|
92
|
+
const result = await applyTool({
|
|
93
|
+
ctx,
|
|
94
|
+
io,
|
|
95
|
+
toolName: 'approve_document',
|
|
96
|
+
input: target(policy),
|
|
97
|
+
fallback: 'Could not approve the document.',
|
|
98
|
+
yes,
|
|
99
|
+
confirm,
|
|
100
|
+
});
|
|
101
|
+
if (result)
|
|
102
|
+
renderResult(io, json, result);
|
|
103
|
+
};
|
|
104
|
+
export const policyDeleteCommand = async ({ io, apiUrl, json = false, yes = false, store, confirm, policy, }) => {
|
|
105
|
+
const ctx = await resolveToolContext(apiUrl, store);
|
|
106
|
+
const result = await applyTool({
|
|
107
|
+
ctx,
|
|
108
|
+
io,
|
|
109
|
+
toolName: 'delete_document',
|
|
110
|
+
input: target(policy),
|
|
111
|
+
fallback: 'Could not delete the document.',
|
|
112
|
+
yes,
|
|
113
|
+
confirm,
|
|
114
|
+
});
|
|
115
|
+
if (result)
|
|
116
|
+
renderResult(io, json, result);
|
|
117
|
+
};
|
|
118
|
+
export const policyUpdateMetaCommand = async ({ io, apiUrl, json = false, store, policy, changes, }) => {
|
|
119
|
+
if (Object.keys(changes).length === 0) {
|
|
120
|
+
throw new Error('Provide at least one field, for example --type manual or --publish on');
|
|
121
|
+
}
|
|
122
|
+
const ctx = await resolveToolContext(apiUrl, store);
|
|
123
|
+
const result = await applyTool({
|
|
124
|
+
ctx,
|
|
125
|
+
io,
|
|
126
|
+
toolName: 'update_document',
|
|
127
|
+
input: { ...target(policy), changes },
|
|
128
|
+
fallback: 'Could not update the document.',
|
|
129
|
+
});
|
|
130
|
+
if (!result)
|
|
131
|
+
return;
|
|
132
|
+
if (json) {
|
|
133
|
+
writeLine(io.stdout, JSON.stringify(result.data, null, 2));
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
const applied = asRecord(result.data).applied;
|
|
137
|
+
const failed = asRecord(result.data).failed;
|
|
138
|
+
const appliedList = Array.isArray(applied)
|
|
139
|
+
? applied.map((value) => asText(value)).join(', ')
|
|
140
|
+
: '';
|
|
141
|
+
writeLine(io.stdout, `${io.theme.green('✓')} updated ${policy}: ${appliedList}`);
|
|
142
|
+
if (Array.isArray(failed) && failed.length > 0) {
|
|
143
|
+
for (const item of failed) {
|
|
144
|
+
const record = asRecord(item);
|
|
145
|
+
writeLine(io.stdout, `${io.theme.yellow('!')} ${asText(record.field)}: ${asText(record.error)}`);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
};
|
|
149
|
+
export const policyVersionsCommand = async ({ io, apiUrl, json = false, store, policy, }) => {
|
|
150
|
+
const ctx = await resolveToolContext(apiUrl, store);
|
|
151
|
+
const envelope = await callTool(ctx, 'list_document_versions', target(policy));
|
|
152
|
+
if (envelope.status !== 'ok')
|
|
153
|
+
throw new Error(errorMessage(envelope, 'Could not list document versions.'));
|
|
154
|
+
const data = asRecord(envelope.data);
|
|
155
|
+
const versions = Array.isArray(data.versions) ? data.versions : [];
|
|
156
|
+
if (json) {
|
|
157
|
+
writeLine(io.stdout, JSON.stringify(data, null, 2));
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
writeLine(io.stdout, `${io.theme.bold(policy)} ${io.theme.dim(`· ${versions.length} version(s)`)}`);
|
|
161
|
+
if (versions.length === 0) {
|
|
162
|
+
writeLine(io.stdout, 'No versions found.');
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
writeTable(io, ['VERSION', 'STATUS', 'REVIEWED BY', 'APPROVED BY', 'APPROVED'], versions.map((version) => {
|
|
166
|
+
const record = asRecord(version);
|
|
167
|
+
const major = num(record.versionNumber, '?');
|
|
168
|
+
const minor = num(record.minorVersionNumber, '0');
|
|
169
|
+
return [
|
|
170
|
+
`${major}.${minor}`,
|
|
171
|
+
asText(record.status, '-'),
|
|
172
|
+
asText(record.reviewedBy, '-'),
|
|
173
|
+
asText(record.approvedBy, '-'),
|
|
174
|
+
asText(record.approvedAt, '-').slice(0, 10),
|
|
175
|
+
];
|
|
176
|
+
}));
|
|
177
|
+
};
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import type { CredentialStore } from '../credentials.js';
|
|
2
|
+
import { type CliIO } from '../io.js';
|
|
3
|
+
export interface RiskCommonOptions {
|
|
4
|
+
io: CliIO;
|
|
5
|
+
apiUrl?: string | undefined;
|
|
6
|
+
json?: boolean | undefined;
|
|
7
|
+
credentialStore?: CredentialStore | undefined;
|
|
8
|
+
}
|
|
9
|
+
export interface RiskTargetOptions extends RiskCommonOptions {
|
|
10
|
+
risk: string;
|
|
11
|
+
}
|
|
12
|
+
export interface RiskActionOptions extends RiskTargetOptions {
|
|
13
|
+
yes?: boolean | undefined;
|
|
14
|
+
confirm?: ((question: string) => Promise<boolean>) | undefined;
|
|
15
|
+
}
|
|
16
|
+
export declare const riskListCommand: ({ io, apiUrl, json, credentialStore, status, businessUnit, page, limit, }: RiskCommonOptions & {
|
|
17
|
+
status?: string | undefined;
|
|
18
|
+
businessUnit?: string | undefined;
|
|
19
|
+
page?: number | undefined;
|
|
20
|
+
limit?: number | undefined;
|
|
21
|
+
}) => Promise<void>;
|
|
22
|
+
export declare const riskGetCommand: ({ io, risk, apiUrl, json, credentialStore, }: RiskTargetOptions) => Promise<void>;
|
|
23
|
+
export interface RiskUpdateOptions extends RiskTargetOptions {
|
|
24
|
+
changes: Record<string, string>;
|
|
25
|
+
}
|
|
26
|
+
export declare const riskUpdateCommand: ({ io, risk, changes, apiUrl, json, credentialStore, }: RiskUpdateOptions) => Promise<void>;
|
|
27
|
+
export declare const riskCreateCommand: ({ io, apiUrl, json, credentialStore, title, category, description, }: RiskCommonOptions & {
|
|
28
|
+
title: string;
|
|
29
|
+
category?: string | undefined;
|
|
30
|
+
description?: string | undefined;
|
|
31
|
+
}) => Promise<void>;
|
|
32
|
+
export declare const riskDeleteCommand: ({ io, risk, apiUrl, json, yes, confirm, credentialStore, }: RiskActionOptions) => Promise<void>;
|
|
33
|
+
export declare const riskTreatmentCommand: ({ io, risk, apiUrl, json, credentialStore, }: RiskTargetOptions) => Promise<void>;
|
|
34
|
+
export declare const riskScoresCommand: ({ io, risk, apiUrl, json, credentialStore, }: RiskTargetOptions) => Promise<void>;
|
|
35
|
+
export declare const riskControlsCommand: ({ io, risk, apiUrl, json, credentialStore, }: RiskTargetOptions) => Promise<void>;
|
|
36
|
+
export declare const riskLinkControlCommand: ({ io, risk, controlInstanceId, mappingId, action, apiUrl, json, yes, credentialStore, }: RiskTargetOptions & {
|
|
37
|
+
controlInstanceId?: string | undefined;
|
|
38
|
+
mappingId?: string | undefined;
|
|
39
|
+
action: string;
|
|
40
|
+
yes?: boolean | undefined;
|
|
41
|
+
}) => Promise<void>;
|
|
42
|
+
export declare const riskFilesCommand: ({ io, risk, apiUrl, json, credentialStore, }: RiskTargetOptions) => Promise<void>;
|
|
43
|
+
export declare const riskAttachCommand: ({ io, risk, file, uploadId, apiUrl, json, yes, credentialStore, }: RiskTargetOptions & {
|
|
44
|
+
file?: string | undefined;
|
|
45
|
+
uploadId?: string | undefined;
|
|
46
|
+
yes?: boolean | undefined;
|
|
47
|
+
}) => Promise<void>;
|
|
48
|
+
export declare const riskDetachCommand: ({ io, risk, mappingId, apiUrl, json, yes, credentialStore, }: RiskTargetOptions & {
|
|
49
|
+
mappingId: string;
|
|
50
|
+
yes?: boolean | undefined;
|
|
51
|
+
}) => Promise<void>;
|
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
import { applyTool } from '../actions.js';
|
|
2
|
+
import { writeLine, writeNextHint, writePageFooter, writeTable } from '../io.js';
|
|
3
|
+
import { uploadLocalFile } from '../upload.js';
|
|
4
|
+
import { asRecord, callTool, errorMessage, resolveToolContext, asText } from '../toolSurface.js';
|
|
5
|
+
const text = (value, fallback = '-') => typeof value === 'string' ? value : typeof value === 'number' ? String(value) : fallback;
|
|
6
|
+
const UUID = /^[0-9a-f]{8}-[0-9a-f-]{27}$/i;
|
|
7
|
+
const target = (risk) => UUID.test(risk) ? { riskId: risk } : { title: risk };
|
|
8
|
+
export const riskListCommand = async ({ io, apiUrl, json = false, credentialStore, status, businessUnit, page, limit, }) => {
|
|
9
|
+
const ctx = await resolveToolContext(apiUrl, credentialStore);
|
|
10
|
+
const input = {};
|
|
11
|
+
if (status)
|
|
12
|
+
input.status = status
|
|
13
|
+
.split(',')
|
|
14
|
+
.map((value) => value.trim())
|
|
15
|
+
.filter(Boolean);
|
|
16
|
+
if (businessUnit)
|
|
17
|
+
input.businessUnit = businessUnit;
|
|
18
|
+
if (page)
|
|
19
|
+
input.page = page;
|
|
20
|
+
if (limit)
|
|
21
|
+
input.limit = limit;
|
|
22
|
+
const envelope = await callTool(ctx, 'list_risks', input);
|
|
23
|
+
if (envelope.status !== 'ok')
|
|
24
|
+
throw new Error(errorMessage(envelope, 'Could not list risks.'));
|
|
25
|
+
if (json) {
|
|
26
|
+
writeLine(io.stdout, JSON.stringify(envelope.data, null, 2));
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
const data = asRecord(envelope.data);
|
|
30
|
+
const risks = Array.isArray(data.risks) ? data.risks : [];
|
|
31
|
+
writeLine(io.stdout, `${io.theme.bold('Risks')} ${io.theme.dim(`(${text(data.total, String(risks.length))} total)`)}`);
|
|
32
|
+
if (risks.length === 0) {
|
|
33
|
+
writeLine(io.stdout, 'No risks found.');
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
writeTable(io, ['CODE', 'TITLE', 'STATUS', 'OWNER', 'BUSINESS UNIT', 'SCORE'], risks.map((risk) => {
|
|
37
|
+
const record = asRecord(risk);
|
|
38
|
+
return [
|
|
39
|
+
text(record.code, '-'),
|
|
40
|
+
text(record.title),
|
|
41
|
+
text(record.status),
|
|
42
|
+
text(record.owner),
|
|
43
|
+
text(record.businessUnit),
|
|
44
|
+
text(record.score),
|
|
45
|
+
];
|
|
46
|
+
}));
|
|
47
|
+
writePageFooter(io, {
|
|
48
|
+
shown: risks.length,
|
|
49
|
+
total: typeof data.total === 'number' ? data.total : risks.length,
|
|
50
|
+
page: typeof data.page === 'number' ? data.page : 1,
|
|
51
|
+
limit: typeof data.limit === 'number' ? data.limit : 25,
|
|
52
|
+
hasMore: data.hasMore === true,
|
|
53
|
+
});
|
|
54
|
+
const next = asText(asRecord(risks[0]).code);
|
|
55
|
+
if (next)
|
|
56
|
+
writeNextHint(io, `ciphrix risk get ${next}`);
|
|
57
|
+
};
|
|
58
|
+
export const riskGetCommand = async ({ io, risk, apiUrl, json = false, credentialStore, }) => {
|
|
59
|
+
const ctx = await resolveToolContext(apiUrl, credentialStore);
|
|
60
|
+
const envelope = await callTool(ctx, 'get_risk', target(risk));
|
|
61
|
+
if (envelope.status !== 'ok')
|
|
62
|
+
throw new Error(errorMessage(envelope, 'Risk not found.'));
|
|
63
|
+
const detail = asRecord(asRecord(envelope.data).risk);
|
|
64
|
+
if (json) {
|
|
65
|
+
writeLine(io.stdout, JSON.stringify(detail, null, 2));
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
writeLine(io.stdout, io.theme.bold(text(detail.risk_title ?? detail.title, 'Risk')));
|
|
69
|
+
const fields = [
|
|
70
|
+
['Status', detail.risk_status ?? detail.status],
|
|
71
|
+
['Category', detail.risk_category ?? detail.category],
|
|
72
|
+
['Business unit', detail.business_unit ?? detail.businessUnit],
|
|
73
|
+
['Owner', detail.risk_owner ?? detail.owner],
|
|
74
|
+
['Code', detail.risk_code],
|
|
75
|
+
['Description', detail.risk_description ?? detail.description],
|
|
76
|
+
['Context', detail.risk_context ?? detail.context],
|
|
77
|
+
];
|
|
78
|
+
for (const [label, value] of fields) {
|
|
79
|
+
if (value === undefined || value === null || value === '')
|
|
80
|
+
continue;
|
|
81
|
+
writeLine(io.stdout, ` ${io.theme.dim(`${label}:`)} ${text(value)}`);
|
|
82
|
+
}
|
|
83
|
+
};
|
|
84
|
+
export const riskUpdateCommand = async ({ io, risk, changes, apiUrl, json = false, credentialStore, }) => {
|
|
85
|
+
if (Object.keys(changes).length === 0) {
|
|
86
|
+
throw new Error('Provide at least one field, for example --status closed');
|
|
87
|
+
}
|
|
88
|
+
const ctx = await resolveToolContext(apiUrl, credentialStore);
|
|
89
|
+
const result = await applyTool({
|
|
90
|
+
ctx,
|
|
91
|
+
io,
|
|
92
|
+
toolName: 'update_risk',
|
|
93
|
+
input: { ...target(risk), changes },
|
|
94
|
+
fallback: 'Could not update the risk.',
|
|
95
|
+
});
|
|
96
|
+
if (!result)
|
|
97
|
+
return;
|
|
98
|
+
if (json)
|
|
99
|
+
writeLine(io.stdout, JSON.stringify(result.data, null, 2));
|
|
100
|
+
else {
|
|
101
|
+
const data = asRecord(result.data);
|
|
102
|
+
const applied = Array.isArray(data.applied)
|
|
103
|
+
? data.applied.map((value) => text(value)).join(', ')
|
|
104
|
+
: '';
|
|
105
|
+
writeLine(io.stdout, `${io.theme.green('✓')} updated ${risk}: ${applied}`);
|
|
106
|
+
}
|
|
107
|
+
};
|
|
108
|
+
export const riskCreateCommand = async ({ io, apiUrl, json = false, credentialStore, title, category, description, }) => {
|
|
109
|
+
const ctx = await resolveToolContext(apiUrl, credentialStore);
|
|
110
|
+
const input = { title };
|
|
111
|
+
if (category)
|
|
112
|
+
input.category = category;
|
|
113
|
+
if (description)
|
|
114
|
+
input.description = description;
|
|
115
|
+
const result = await applyTool({
|
|
116
|
+
ctx,
|
|
117
|
+
io,
|
|
118
|
+
toolName: 'create_risk',
|
|
119
|
+
input,
|
|
120
|
+
fallback: 'Could not create the risk.',
|
|
121
|
+
});
|
|
122
|
+
if (!result)
|
|
123
|
+
return;
|
|
124
|
+
if (json)
|
|
125
|
+
writeLine(io.stdout, JSON.stringify(result.data, null, 2));
|
|
126
|
+
else
|
|
127
|
+
writeLine(io.stdout, `${io.theme.green('✓')} risk created: ${title}`);
|
|
128
|
+
};
|
|
129
|
+
export const riskDeleteCommand = async ({ io, risk, apiUrl, json = false, yes = false, confirm, credentialStore, }) => {
|
|
130
|
+
const ctx = await resolveToolContext(apiUrl, credentialStore);
|
|
131
|
+
const result = await applyTool({
|
|
132
|
+
ctx,
|
|
133
|
+
io,
|
|
134
|
+
toolName: 'delete_risk',
|
|
135
|
+
input: target(risk),
|
|
136
|
+
fallback: 'Could not delete the risk.',
|
|
137
|
+
yes,
|
|
138
|
+
confirm,
|
|
139
|
+
});
|
|
140
|
+
if (!result)
|
|
141
|
+
return;
|
|
142
|
+
if (json)
|
|
143
|
+
writeLine(io.stdout, JSON.stringify(result.data, null, 2));
|
|
144
|
+
else
|
|
145
|
+
writeLine(io.stdout, `${io.theme.green('✓')} risk ${risk} deleted`);
|
|
146
|
+
};
|
|
147
|
+
const riskTarget = (risk) => {
|
|
148
|
+
const looksLikeCode = /^[A-Z0-9]{2,10}-[A-Z]{2,5}-[A-Z0-9]{2,8}$/.test(risk);
|
|
149
|
+
return looksLikeCode ? { code: risk } : { name: risk };
|
|
150
|
+
};
|
|
151
|
+
export const riskTreatmentCommand = async ({ io, risk, apiUrl, json = false, credentialStore, }) => {
|
|
152
|
+
const ctx = await resolveToolContext(apiUrl, credentialStore);
|
|
153
|
+
const envelope = await callTool(ctx, 'get_risk_treatment', riskTarget(risk));
|
|
154
|
+
if (envelope.status !== 'ok')
|
|
155
|
+
throw new Error(errorMessage(envelope, 'Could not read the treatment.'));
|
|
156
|
+
const data = asRecord(envelope.data);
|
|
157
|
+
if (json) {
|
|
158
|
+
writeLine(io.stdout, JSON.stringify(data, null, 2));
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
writeLine(io.stdout, `${io.theme.bold('Treatment')} ${io.theme.dim(`· ${risk}`)}`);
|
|
162
|
+
writeLine(io.stdout, ` ${io.theme.dim('Strategy:')} ${asText(data.treatmentType, 'not_set')}`);
|
|
163
|
+
if (typeof data.treatmentNotes === 'string' && data.treatmentNotes.trim() !== '') {
|
|
164
|
+
writeLine(io.stdout);
|
|
165
|
+
writeLine(io.stdout, data.treatmentNotes);
|
|
166
|
+
}
|
|
167
|
+
};
|
|
168
|
+
export const riskScoresCommand = async ({ io, risk, apiUrl, json = false, credentialStore, }) => {
|
|
169
|
+
const ctx = await resolveToolContext(apiUrl, credentialStore);
|
|
170
|
+
const envelope = await callTool(ctx, 'list_risk_scores', riskTarget(risk));
|
|
171
|
+
if (envelope.status !== 'ok')
|
|
172
|
+
throw new Error(errorMessage(envelope, 'Could not read the scores.'));
|
|
173
|
+
if (json) {
|
|
174
|
+
writeLine(io.stdout, JSON.stringify(envelope.data, null, 2));
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
const scores = asRecord(asRecord(envelope.data).scores);
|
|
178
|
+
writeLine(io.stdout, `${io.theme.bold('Scores')} ${io.theme.dim(`· ${risk}`)}`);
|
|
179
|
+
for (const [label, value] of Object.entries(scores)) {
|
|
180
|
+
if (value === null || value === undefined || value === '')
|
|
181
|
+
continue;
|
|
182
|
+
writeLine(io.stdout, ` ${io.theme.dim(`${label}:`)} ${typeof value === 'string' ? value : JSON.stringify(value)}`);
|
|
183
|
+
}
|
|
184
|
+
};
|
|
185
|
+
export const riskControlsCommand = async ({ io, risk, apiUrl, json = false, credentialStore, }) => {
|
|
186
|
+
const ctx = await resolveToolContext(apiUrl, credentialStore);
|
|
187
|
+
const envelope = await callTool(ctx, 'list_risk_controls', riskTarget(risk));
|
|
188
|
+
if (envelope.status !== 'ok')
|
|
189
|
+
throw new Error(errorMessage(envelope, 'Could not read the linked controls.'));
|
|
190
|
+
const data = asRecord(envelope.data);
|
|
191
|
+
const controls = Array.isArray(data.controls) ? data.controls : [];
|
|
192
|
+
if (json) {
|
|
193
|
+
writeLine(io.stdout, JSON.stringify(data, null, 2));
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
writeLine(io.stdout, `${io.theme.bold('Linked controls')} ${io.theme.dim(`(${controls.length})`)}`);
|
|
197
|
+
if (controls.length === 0) {
|
|
198
|
+
writeLine(io.stdout, 'No controls linked.');
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
201
|
+
writeTable(io, ['CONTROL', 'CODE'], controls.map((control) => {
|
|
202
|
+
const record = asRecord(control);
|
|
203
|
+
return [asText(record.name, asText(record.id)), asText(record.code, '-')];
|
|
204
|
+
}));
|
|
205
|
+
};
|
|
206
|
+
export const riskLinkControlCommand = async ({ io, risk, controlInstanceId, mappingId, action, apiUrl, json = false, yes = false, credentialStore, }) => {
|
|
207
|
+
const ctx = await resolveToolContext(apiUrl, credentialStore);
|
|
208
|
+
const input = { ...riskTarget(risk), action };
|
|
209
|
+
if (controlInstanceId)
|
|
210
|
+
input.controlInstanceId = controlInstanceId;
|
|
211
|
+
if (mappingId)
|
|
212
|
+
input.mappingId = mappingId;
|
|
213
|
+
const result = await applyTool({
|
|
214
|
+
ctx,
|
|
215
|
+
io,
|
|
216
|
+
toolName: 'link_risk_control',
|
|
217
|
+
input,
|
|
218
|
+
fallback: 'Could not change the risk control.',
|
|
219
|
+
yes,
|
|
220
|
+
});
|
|
221
|
+
if (!result)
|
|
222
|
+
return;
|
|
223
|
+
if (json)
|
|
224
|
+
writeLine(io.stdout, JSON.stringify(result.data, null, 2));
|
|
225
|
+
else
|
|
226
|
+
writeLine(io.stdout, `${io.theme.green('✓')} control ${action === 'unlink' ? 'unlinked' : 'linked'} on ${risk}`);
|
|
227
|
+
};
|
|
228
|
+
export const riskFilesCommand = async ({ io, risk, apiUrl, json = false, credentialStore, }) => {
|
|
229
|
+
const ctx = await resolveToolContext(apiUrl, credentialStore);
|
|
230
|
+
const envelope = await callTool(ctx, 'list_risk_files', riskTarget(risk));
|
|
231
|
+
if (envelope.status !== 'ok')
|
|
232
|
+
throw new Error(errorMessage(envelope, 'Could not read risk files.'));
|
|
233
|
+
const data = asRecord(envelope.data);
|
|
234
|
+
const files = Array.isArray(data.files) ? data.files : [];
|
|
235
|
+
if (json) {
|
|
236
|
+
writeLine(io.stdout, JSON.stringify(data, null, 2));
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
239
|
+
writeLine(io.stdout, `${io.theme.bold('Risk files')} ${io.theme.dim(`(${files.length})`)}`);
|
|
240
|
+
if (files.length === 0) {
|
|
241
|
+
writeLine(io.stdout, 'No files attached.');
|
|
242
|
+
return;
|
|
243
|
+
}
|
|
244
|
+
writeTable(io, ['MAPPING', 'FILE'], files.map((file) => {
|
|
245
|
+
const record = asRecord(file);
|
|
246
|
+
return [asText(record.mappingId, '-'), asText(record.name)];
|
|
247
|
+
}));
|
|
248
|
+
};
|
|
249
|
+
export const riskAttachCommand = async ({ io, risk, file, uploadId, apiUrl, json = false, yes = false, credentialStore, }) => {
|
|
250
|
+
const ctx = await resolveToolContext(apiUrl, credentialStore);
|
|
251
|
+
let uploadIds;
|
|
252
|
+
if (uploadId)
|
|
253
|
+
uploadIds = [uploadId];
|
|
254
|
+
else if (file)
|
|
255
|
+
uploadIds = [(await uploadLocalFile(ctx, file, 'risk')).id];
|
|
256
|
+
else
|
|
257
|
+
throw new Error('Provide --file <path> or --upload <uploadId>');
|
|
258
|
+
const result = await applyTool({
|
|
259
|
+
ctx,
|
|
260
|
+
io,
|
|
261
|
+
toolName: 'link_risk_file',
|
|
262
|
+
input: { ...riskTarget(risk), action: 'link', uploadIds },
|
|
263
|
+
fallback: 'Could not attach the file.',
|
|
264
|
+
yes,
|
|
265
|
+
});
|
|
266
|
+
if (!result)
|
|
267
|
+
return;
|
|
268
|
+
if (json)
|
|
269
|
+
writeLine(io.stdout, JSON.stringify(result.data, null, 2));
|
|
270
|
+
else
|
|
271
|
+
writeLine(io.stdout, `${io.theme.green('✓')} attached ${uploadIds.length} file(s) to ${risk}`);
|
|
272
|
+
};
|
|
273
|
+
export const riskDetachCommand = async ({ io, risk, mappingId, apiUrl, json = false, yes = false, credentialStore, }) => {
|
|
274
|
+
const ctx = await resolveToolContext(apiUrl, credentialStore);
|
|
275
|
+
const result = await applyTool({
|
|
276
|
+
ctx,
|
|
277
|
+
io,
|
|
278
|
+
toolName: 'link_risk_file',
|
|
279
|
+
input: { ...riskTarget(risk), action: 'unlink', mappingId },
|
|
280
|
+
fallback: 'Could not detach the file.',
|
|
281
|
+
yes,
|
|
282
|
+
});
|
|
283
|
+
if (!result)
|
|
284
|
+
return;
|
|
285
|
+
if (json)
|
|
286
|
+
writeLine(io.stdout, JSON.stringify(result.data, null, 2));
|
|
287
|
+
else
|
|
288
|
+
writeLine(io.stdout, `${io.theme.green('✓')} detached ${mappingId} from ${risk}`);
|
|
289
|
+
};
|