@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,189 @@
|
|
|
1
|
+
import { applyTool } from '../actions.js';
|
|
2
|
+
import { writeLine, writeNextHint, writePageFooter, writeTable } from '../io.js';
|
|
3
|
+
import { asRecord, asText, callTool, errorMessage, resolveToolContext } from '../toolSurface.js';
|
|
4
|
+
const text = (value, fallback = '-') => typeof value === 'string' ? value : typeof value === 'number' ? String(value) : fallback;
|
|
5
|
+
export const controlListCommand = async ({ io, apiUrl, json = false, credentialStore, search, domain, status, applicability, page, limit, }) => {
|
|
6
|
+
const ctx = await resolveToolContext(apiUrl, credentialStore);
|
|
7
|
+
const input = {};
|
|
8
|
+
if (search)
|
|
9
|
+
input.search = search;
|
|
10
|
+
if (domain)
|
|
11
|
+
input.domain = [domain];
|
|
12
|
+
if (status)
|
|
13
|
+
input.status = status
|
|
14
|
+
.split(',')
|
|
15
|
+
.map((value) => value.trim())
|
|
16
|
+
.filter(Boolean);
|
|
17
|
+
if (applicability)
|
|
18
|
+
input.applicability = [applicability];
|
|
19
|
+
if (page)
|
|
20
|
+
input.page = page;
|
|
21
|
+
if (limit)
|
|
22
|
+
input.limit = limit;
|
|
23
|
+
const envelope = await callTool(ctx, 'list_controls', input);
|
|
24
|
+
if (envelope.status !== 'ok')
|
|
25
|
+
throw new Error(errorMessage(envelope, 'Could not list controls.'));
|
|
26
|
+
if (json) {
|
|
27
|
+
writeLine(io.stdout, JSON.stringify(envelope.data, null, 2));
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
const data = asRecord(envelope.data);
|
|
31
|
+
const controls = Array.isArray(data.controls) ? data.controls : [];
|
|
32
|
+
writeLine(io.stdout, `${io.theme.bold('Controls')} ${io.theme.dim(`(${text(data.total, String(controls.length))} total)`)}`);
|
|
33
|
+
if (controls.length === 0) {
|
|
34
|
+
writeLine(io.stdout, 'No controls found.');
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
writeTable(io, ['CODE', 'NAME', 'DOMAIN', 'STATUS', 'APPLICABILITY', 'OWNER'], controls.map((control) => {
|
|
38
|
+
const record = asRecord(control);
|
|
39
|
+
const owner = asRecord(record.owner);
|
|
40
|
+
return [
|
|
41
|
+
text(record.code, '-'),
|
|
42
|
+
text(record.name),
|
|
43
|
+
text(record.domain),
|
|
44
|
+
text(record.status),
|
|
45
|
+
text(record.applicability),
|
|
46
|
+
text(owner.email ?? record.owner, '-'),
|
|
47
|
+
];
|
|
48
|
+
}));
|
|
49
|
+
writePageFooter(io, {
|
|
50
|
+
shown: controls.length,
|
|
51
|
+
total: typeof data.total === 'number' ? data.total : controls.length,
|
|
52
|
+
page: typeof data.page === 'number' ? data.page : 1,
|
|
53
|
+
limit: typeof data.limit === 'number' ? data.limit : 25,
|
|
54
|
+
hasMore: data.hasMore === true,
|
|
55
|
+
});
|
|
56
|
+
const next = asText(asRecord(controls[0]).code);
|
|
57
|
+
if (next)
|
|
58
|
+
writeNextHint(io, `ciphrix control get ${next}`);
|
|
59
|
+
};
|
|
60
|
+
export const controlGetCommand = async ({ io, name, apiUrl, json = false, credentialStore, }) => {
|
|
61
|
+
const ctx = await resolveToolContext(apiUrl, credentialStore);
|
|
62
|
+
const envelope = await callTool(ctx, 'get_control', { name });
|
|
63
|
+
if (envelope.status !== 'ok')
|
|
64
|
+
throw new Error(errorMessage(envelope, 'Control not found.'));
|
|
65
|
+
const control = asRecord(asRecord(envelope.data).control);
|
|
66
|
+
if (json) {
|
|
67
|
+
writeLine(io.stdout, JSON.stringify(control, null, 2));
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
writeLine(io.stdout, `${io.theme.bold(asText(control.name, 'Control'))} ${io.theme.dim(`· ${asText(control.code, '-')}`)}`);
|
|
71
|
+
const owner = asRecord(control.owner);
|
|
72
|
+
const fields = [
|
|
73
|
+
['Domain', control.domain],
|
|
74
|
+
['Status', control.status],
|
|
75
|
+
['Applicability', control.applicability],
|
|
76
|
+
['Owner', owner.email ?? control.owner],
|
|
77
|
+
['Design requirements', control.designRequirements],
|
|
78
|
+
];
|
|
79
|
+
for (const [label, value] of fields) {
|
|
80
|
+
if (value === undefined || value === null || value === '')
|
|
81
|
+
continue;
|
|
82
|
+
writeLine(io.stdout, ` ${io.theme.dim(`${label}:`)} ${text(value)}`);
|
|
83
|
+
}
|
|
84
|
+
if (typeof control.notes === 'string' && control.notes.trim() !== '') {
|
|
85
|
+
writeLine(io.stdout);
|
|
86
|
+
writeLine(io.stdout, control.notes);
|
|
87
|
+
}
|
|
88
|
+
};
|
|
89
|
+
export const controlItemsCommand = async ({ io, name, apiUrl, json = false, credentialStore, search, page, limit, }) => {
|
|
90
|
+
const ctx = await resolveToolContext(apiUrl, credentialStore);
|
|
91
|
+
const input = { name };
|
|
92
|
+
if (search)
|
|
93
|
+
input.search = search;
|
|
94
|
+
if (page)
|
|
95
|
+
input.page = page;
|
|
96
|
+
if (limit)
|
|
97
|
+
input.limit = limit;
|
|
98
|
+
const envelope = await callTool(ctx, 'list_control_items', input);
|
|
99
|
+
if (envelope.status !== 'ok')
|
|
100
|
+
throw new Error(errorMessage(envelope, 'Could not read control items.'));
|
|
101
|
+
if (json) {
|
|
102
|
+
writeLine(io.stdout, JSON.stringify(envelope.data, null, 2));
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
const data = asRecord(envelope.data);
|
|
106
|
+
const items = Array.isArray(data.items) ? data.items : [];
|
|
107
|
+
writeLine(io.stdout, `${io.theme.bold('Control items')} ${io.theme.dim(`(${items.length})`)}`);
|
|
108
|
+
if (items.length === 0) {
|
|
109
|
+
writeLine(io.stdout, 'No items linked.');
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
writeTable(io, ['ITEM', 'TYPE', 'STATUS'], items.map((item) => {
|
|
113
|
+
const record = asRecord(item);
|
|
114
|
+
return [
|
|
115
|
+
text(record.name, text(record.id)),
|
|
116
|
+
text(record.itemType ?? record.type),
|
|
117
|
+
text(record.status),
|
|
118
|
+
];
|
|
119
|
+
}));
|
|
120
|
+
};
|
|
121
|
+
export const controlAvailableItemsCommand = async ({ io, name, itemType, apiUrl, json = false, credentialStore, search, page, limit, }) => {
|
|
122
|
+
const ctx = await resolveToolContext(apiUrl, credentialStore);
|
|
123
|
+
const input = { name, itemType };
|
|
124
|
+
if (search)
|
|
125
|
+
input.search = search;
|
|
126
|
+
if (page)
|
|
127
|
+
input.page = page;
|
|
128
|
+
if (limit)
|
|
129
|
+
input.limit = limit;
|
|
130
|
+
const envelope = await callTool(ctx, 'list_control_items_available', input);
|
|
131
|
+
if (envelope.status !== 'ok')
|
|
132
|
+
throw new Error(errorMessage(envelope, 'Could not read available items.'));
|
|
133
|
+
if (json) {
|
|
134
|
+
writeLine(io.stdout, JSON.stringify(envelope.data, null, 2));
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
const data = asRecord(envelope.data);
|
|
138
|
+
const items = Array.isArray(data.items) ? data.items : [];
|
|
139
|
+
writeLine(io.stdout, `${io.theme.bold(`Available ${itemType}s`)} ${io.theme.dim(`(${items.length})`)}`);
|
|
140
|
+
if (items.length === 0) {
|
|
141
|
+
writeLine(io.stdout, 'Nothing available to link.');
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
writeTable(io, ['ITEM', 'STATUS'], items.map((item) => {
|
|
145
|
+
const record = asRecord(item);
|
|
146
|
+
return [text(record.name, text(record.id)), text(record.status)];
|
|
147
|
+
}));
|
|
148
|
+
};
|
|
149
|
+
export const controlUpdateCommand = async ({ io, name, apiUrl, json = false, yes = false, confirm, credentialStore, changes, }) => {
|
|
150
|
+
if (Object.keys(changes).length === 0) {
|
|
151
|
+
throw new Error('Provide at least one field, for example --applicability out_of_scope');
|
|
152
|
+
}
|
|
153
|
+
const ctx = await resolveToolContext(apiUrl, credentialStore);
|
|
154
|
+
const result = await applyTool({
|
|
155
|
+
ctx,
|
|
156
|
+
io,
|
|
157
|
+
toolName: 'update_control',
|
|
158
|
+
input: { name, changes },
|
|
159
|
+
fallback: 'Could not update the control.',
|
|
160
|
+
yes,
|
|
161
|
+
confirm,
|
|
162
|
+
});
|
|
163
|
+
if (!result)
|
|
164
|
+
return;
|
|
165
|
+
if (json)
|
|
166
|
+
writeLine(io.stdout, JSON.stringify(result.data, null, 2));
|
|
167
|
+
else {
|
|
168
|
+
const applied = asRecord(result.data).applied;
|
|
169
|
+
writeLine(io.stdout, `${io.theme.green('✓')} updated ${name}: ${Array.isArray(applied) ? applied.join(', ') : ''}`);
|
|
170
|
+
}
|
|
171
|
+
};
|
|
172
|
+
export const controlLinkCommand = async ({ io, name, action, itemType, itemId, apiUrl, json = false, yes = false, confirm, credentialStore, }) => {
|
|
173
|
+
const ctx = await resolveToolContext(apiUrl, credentialStore);
|
|
174
|
+
const result = await applyTool({
|
|
175
|
+
ctx,
|
|
176
|
+
io,
|
|
177
|
+
toolName: 'link_control_item',
|
|
178
|
+
input: { name, action, itemType, itemId },
|
|
179
|
+
fallback: 'Could not change the control item.',
|
|
180
|
+
yes,
|
|
181
|
+
confirm,
|
|
182
|
+
});
|
|
183
|
+
if (!result)
|
|
184
|
+
return;
|
|
185
|
+
if (json)
|
|
186
|
+
writeLine(io.stdout, JSON.stringify(result.data, null, 2));
|
|
187
|
+
else
|
|
188
|
+
writeLine(io.stdout, `${io.theme.green('✓')} ${action === 'unlink' ? 'unlinked' : 'linked'} ${itemType} on ${name}`);
|
|
189
|
+
};
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { type CliIO } from '../io.js';
|
|
2
|
+
import type { CredentialStore } from '../credentials.js';
|
|
3
|
+
export interface FrameworkListOptions {
|
|
4
|
+
io: CliIO;
|
|
5
|
+
apiUrl?: string | undefined;
|
|
6
|
+
json?: boolean | undefined;
|
|
7
|
+
credentialStore?: CredentialStore | undefined;
|
|
8
|
+
}
|
|
9
|
+
export declare const frameworkListCommand: ({ io, apiUrl, json, credentialStore, }: FrameworkListOptions) => Promise<void>;
|
|
10
|
+
export interface ClauseTargetOptions {
|
|
11
|
+
io: CliIO;
|
|
12
|
+
framework: string;
|
|
13
|
+
clause: string;
|
|
14
|
+
apiUrl?: string | undefined;
|
|
15
|
+
json?: boolean | undefined;
|
|
16
|
+
credentialStore?: CredentialStore | undefined;
|
|
17
|
+
}
|
|
18
|
+
export declare const frameworkClausesCommand: ({ io, framework, apiUrl, json, credentialStore, search, applicability, page, limit, }: FrameworkListOptions & {
|
|
19
|
+
framework: string;
|
|
20
|
+
search?: string | undefined;
|
|
21
|
+
applicability?: string | undefined;
|
|
22
|
+
page?: number | undefined;
|
|
23
|
+
limit?: number | undefined;
|
|
24
|
+
}) => Promise<void>;
|
|
25
|
+
export declare const clauseGetCommand: ({ io, framework, clause, apiUrl, json, credentialStore, }: ClauseTargetOptions) => Promise<void>;
|
|
26
|
+
export declare const clauseItemsCommand: ({ io, framework, clause, apiUrl, json, credentialStore, }: ClauseTargetOptions) => Promise<void>;
|
|
27
|
+
export declare const clauseAvailableItemsCommand: ({ io, framework, clause, itemType, apiUrl, json, credentialStore, search, }: ClauseTargetOptions & {
|
|
28
|
+
itemType: string;
|
|
29
|
+
search?: string | undefined;
|
|
30
|
+
}) => Promise<void>;
|
|
31
|
+
export declare const clauseUpdateCommand: ({ io, framework, clause, changes, apiUrl, json, yes, credentialStore, }: ClauseTargetOptions & {
|
|
32
|
+
changes: Record<string, unknown>;
|
|
33
|
+
yes?: boolean | undefined;
|
|
34
|
+
}) => Promise<void>;
|
|
35
|
+
export declare const clauseLinkCommand: ({ io, framework, clause, action, itemType, itemId, apiUrl, json, yes, credentialStore, }: ClauseTargetOptions & {
|
|
36
|
+
action: string;
|
|
37
|
+
itemType: string;
|
|
38
|
+
itemId: string;
|
|
39
|
+
yes?: boolean | undefined;
|
|
40
|
+
}) => Promise<void>;
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
import { writeLine, writeNextHint, writeTable } from '../io.js';
|
|
2
|
+
import { asRecord, callTool, errorMessage, resolveToolContext, asText } from '../toolSurface.js';
|
|
3
|
+
const text = (value, fallback = '-') => typeof value === 'string' ? value : typeof value === 'number' ? String(value) : fallback;
|
|
4
|
+
export const frameworkListCommand = async ({ io, apiUrl, json = false, credentialStore, }) => {
|
|
5
|
+
const ctx = await resolveToolContext(apiUrl, credentialStore);
|
|
6
|
+
const envelope = await callTool(ctx, 'list_frameworks', {});
|
|
7
|
+
if (envelope.status !== 'ok')
|
|
8
|
+
throw new Error(errorMessage(envelope, 'Could not list frameworks.'));
|
|
9
|
+
if (json) {
|
|
10
|
+
writeLine(io.stdout, JSON.stringify(envelope.data, null, 2));
|
|
11
|
+
return;
|
|
12
|
+
}
|
|
13
|
+
const data = asRecord(envelope.data);
|
|
14
|
+
const active = Array.isArray(data.active) ? data.active : [];
|
|
15
|
+
const available = Array.isArray(data.available) ? data.available : [];
|
|
16
|
+
writeLine(io.stdout, `${io.theme.bold('Frameworks')} ${io.theme.dim(`(${active.length} applied)`)}`);
|
|
17
|
+
if (active.length > 0) {
|
|
18
|
+
writeTable(io, ['CODE', 'NAME', 'STATUS', 'READINESS'], active.map((framework) => {
|
|
19
|
+
const record = asRecord(framework);
|
|
20
|
+
const readiness = record.readinessScore ?? record.complianceScore;
|
|
21
|
+
return [
|
|
22
|
+
text(record.frameworkCode, '-'),
|
|
23
|
+
text(record.frameworkName ?? record.name),
|
|
24
|
+
text(record.status),
|
|
25
|
+
readiness === undefined || readiness === null ? '-' : `${text(readiness)}%`,
|
|
26
|
+
];
|
|
27
|
+
}));
|
|
28
|
+
}
|
|
29
|
+
const firstFramework = asText(asRecord(active[0]).frameworkCode);
|
|
30
|
+
if (firstFramework)
|
|
31
|
+
writeNextHint(io, `ciphrix framework clauses ${firstFramework}`);
|
|
32
|
+
if (available.length > 0) {
|
|
33
|
+
writeLine(io.stdout);
|
|
34
|
+
writeLine(io.stdout, `${io.theme.dim(`Available to add (${available.length}):`)}`);
|
|
35
|
+
for (const framework of available) {
|
|
36
|
+
const record = asRecord(framework);
|
|
37
|
+
writeLine(io.stdout, ` ${text(record.frameworkName ?? record.name)}`);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
export const frameworkClausesCommand = async ({ io, framework, apiUrl, json = false, credentialStore, search, applicability, page, limit, }) => {
|
|
42
|
+
const ctx = await resolveToolContext(apiUrl, credentialStore);
|
|
43
|
+
const input = { framework };
|
|
44
|
+
if (search)
|
|
45
|
+
input.search = search;
|
|
46
|
+
if (applicability)
|
|
47
|
+
input.applicability = [applicability];
|
|
48
|
+
if (page)
|
|
49
|
+
input.page = page;
|
|
50
|
+
if (limit)
|
|
51
|
+
input.limit = limit;
|
|
52
|
+
const envelope = await callTool(ctx, 'list_framework_clauses', input);
|
|
53
|
+
if (envelope.status !== 'ok')
|
|
54
|
+
throw new Error(errorMessage(envelope, 'Could not list clauses.'));
|
|
55
|
+
if (json) {
|
|
56
|
+
writeLine(io.stdout, JSON.stringify(envelope.data, null, 2));
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
const data = asRecord(envelope.data);
|
|
60
|
+
const clauses = Array.isArray(data.clauses) ? data.clauses : [];
|
|
61
|
+
writeLine(io.stdout, `${io.theme.bold('Clauses')} ${io.theme.dim(`(${text(data.total, String(clauses.length))})`)}`);
|
|
62
|
+
if (clauses.length === 0) {
|
|
63
|
+
writeLine(io.stdout, 'No clauses found.');
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
const firstClause = asText(asRecord(clauses[0]).code);
|
|
67
|
+
if (firstClause)
|
|
68
|
+
writeNextHint(io, `ciphrix framework clause ${framework} ${firstClause}`);
|
|
69
|
+
writeTable(io, ['CODE', 'NAME', 'APPLICABILITY', 'DESIGN REQ'], clauses.map((clause) => {
|
|
70
|
+
const record = asRecord(clause);
|
|
71
|
+
return [
|
|
72
|
+
text(record.code),
|
|
73
|
+
text(record.name),
|
|
74
|
+
text(record.applicability),
|
|
75
|
+
text(record.designRequirements),
|
|
76
|
+
];
|
|
77
|
+
}));
|
|
78
|
+
};
|
|
79
|
+
export const clauseGetCommand = async ({ io, framework, clause, apiUrl, json = false, credentialStore, }) => {
|
|
80
|
+
const ctx = await resolveToolContext(apiUrl, credentialStore);
|
|
81
|
+
const envelope = await callTool(ctx, 'get_clause', { framework, clause });
|
|
82
|
+
if (envelope.status !== 'ok')
|
|
83
|
+
throw new Error(errorMessage(envelope, 'Clause not found.'));
|
|
84
|
+
const record = asRecord(asRecord(envelope.data).clause);
|
|
85
|
+
if (json) {
|
|
86
|
+
writeLine(io.stdout, JSON.stringify(record, null, 2));
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
writeLine(io.stdout, `${io.theme.bold(text(record.name, 'Clause'))} ${io.theme.dim(`· ${text(record.code)}`)}`);
|
|
90
|
+
const fields = [
|
|
91
|
+
['Applicability', record.applicability],
|
|
92
|
+
['Design requirements', record.designRequirements],
|
|
93
|
+
];
|
|
94
|
+
for (const [label, value] of fields) {
|
|
95
|
+
if (value === undefined || value === null || value === '')
|
|
96
|
+
continue;
|
|
97
|
+
writeLine(io.stdout, ` ${io.theme.dim(`${label}:`)} ${text(value)}`);
|
|
98
|
+
}
|
|
99
|
+
};
|
|
100
|
+
export const clauseItemsCommand = async ({ io, framework, clause, apiUrl, json = false, credentialStore, }) => {
|
|
101
|
+
const ctx = await resolveToolContext(apiUrl, credentialStore);
|
|
102
|
+
const envelope = await callTool(ctx, 'list_clause_items', { framework, clause });
|
|
103
|
+
if (envelope.status !== 'ok')
|
|
104
|
+
throw new Error(errorMessage(envelope, 'Could not read clause items.'));
|
|
105
|
+
if (json) {
|
|
106
|
+
writeLine(io.stdout, JSON.stringify(envelope.data, null, 2));
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
const items = asRecord(envelope.data).items;
|
|
110
|
+
const list = Array.isArray(items) ? items : [];
|
|
111
|
+
writeLine(io.stdout, `${io.theme.bold('Clause items')} ${io.theme.dim(`(${list.length})`)}`);
|
|
112
|
+
if (list.length === 0) {
|
|
113
|
+
writeLine(io.stdout, 'No items mapped.');
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
writeTable(io, ['ITEM', 'TYPE'], list.map((item) => {
|
|
117
|
+
const record = asRecord(item);
|
|
118
|
+
return [text(record.name, text(record.id)), text(record.itemType ?? record.type)];
|
|
119
|
+
}));
|
|
120
|
+
};
|
|
121
|
+
export const clauseAvailableItemsCommand = async ({ io, framework, clause, itemType, apiUrl, json = false, credentialStore, search, }) => {
|
|
122
|
+
const ctx = await resolveToolContext(apiUrl, credentialStore);
|
|
123
|
+
const input = { framework, clause, itemType };
|
|
124
|
+
if (search)
|
|
125
|
+
input.search = search;
|
|
126
|
+
const envelope = await callTool(ctx, 'list_clause_items_available', input);
|
|
127
|
+
if (envelope.status !== 'ok')
|
|
128
|
+
throw new Error(errorMessage(envelope, 'Could not read available items.'));
|
|
129
|
+
if (json) {
|
|
130
|
+
writeLine(io.stdout, JSON.stringify(envelope.data, null, 2));
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
const items = asRecord(envelope.data).items;
|
|
134
|
+
const list = Array.isArray(items) ? items : [];
|
|
135
|
+
writeLine(io.stdout, `${io.theme.bold(`Available ${itemType}s`)} ${io.theme.dim(`(${list.length})`)}`);
|
|
136
|
+
for (const item of list) {
|
|
137
|
+
const record = asRecord(item);
|
|
138
|
+
writeLine(io.stdout, ` ${text(record.name, text(record.id))}`);
|
|
139
|
+
}
|
|
140
|
+
};
|
|
141
|
+
export const clauseUpdateCommand = async ({ io, framework, clause, changes, apiUrl, json = false, yes = false, credentialStore, }) => {
|
|
142
|
+
const { applyTool } = await import('../actions.js');
|
|
143
|
+
if (Object.keys(changes).length === 0) {
|
|
144
|
+
throw new Error('Provide at least one field, for example --applicability out_of_scope');
|
|
145
|
+
}
|
|
146
|
+
const ctx = await resolveToolContext(apiUrl, credentialStore);
|
|
147
|
+
const result = await applyTool({
|
|
148
|
+
ctx,
|
|
149
|
+
io,
|
|
150
|
+
toolName: 'update_clause',
|
|
151
|
+
input: { framework, clause, changes },
|
|
152
|
+
fallback: 'Could not update the clause.',
|
|
153
|
+
yes,
|
|
154
|
+
});
|
|
155
|
+
if (!result)
|
|
156
|
+
return;
|
|
157
|
+
if (json)
|
|
158
|
+
writeLine(io.stdout, JSON.stringify(result.data, null, 2));
|
|
159
|
+
else {
|
|
160
|
+
const applied = asRecord(result.data).applied;
|
|
161
|
+
writeLine(io.stdout, `${io.theme.green('✓')} updated ${clause}: ${Array.isArray(applied) ? applied.join(', ') : ''}`);
|
|
162
|
+
}
|
|
163
|
+
};
|
|
164
|
+
export const clauseLinkCommand = async ({ io, framework, clause, action, itemType, itemId, apiUrl, json = false, yes = false, credentialStore, }) => {
|
|
165
|
+
const { applyTool } = await import('../actions.js');
|
|
166
|
+
const ctx = await resolveToolContext(apiUrl, credentialStore);
|
|
167
|
+
const result = await applyTool({
|
|
168
|
+
ctx,
|
|
169
|
+
io,
|
|
170
|
+
toolName: 'link_clause_item',
|
|
171
|
+
input: { framework, clause, action, itemType, itemId },
|
|
172
|
+
fallback: 'Could not change the clause item.',
|
|
173
|
+
yes,
|
|
174
|
+
});
|
|
175
|
+
if (!result)
|
|
176
|
+
return;
|
|
177
|
+
if (json)
|
|
178
|
+
writeLine(io.stdout, JSON.stringify(result.data, null, 2));
|
|
179
|
+
else
|
|
180
|
+
writeLine(io.stdout, `${io.theme.green('✓')} ${action === 'unlink' ? 'unlinked' : 'linked'} ${itemType} on ${clause}`);
|
|
181
|
+
};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { CredentialStore } from '../credentials.js';
|
|
2
|
+
import { type CliIO } from '../io.js';
|
|
3
|
+
export interface JobStatusOptions {
|
|
4
|
+
io: CliIO;
|
|
5
|
+
jobId: string;
|
|
6
|
+
apiUrl?: string | undefined;
|
|
7
|
+
json?: boolean | undefined;
|
|
8
|
+
credentialStore?: CredentialStore | undefined;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Check a background job started by another command (an evidence attachment, a generation job).
|
|
12
|
+
*
|
|
13
|
+
* Deliberately named "job": the product's "tasks" are user work items, and the interface never uses that
|
|
14
|
+
* word for background machinery.
|
|
15
|
+
*/
|
|
16
|
+
export declare const jobStatusCommand: ({ io, jobId, apiUrl, json, credentialStore, }: JobStatusOptions) => Promise<void>;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { writeLine } from '../io.js';
|
|
2
|
+
import { asRecord, asText, callTool, errorMessage, resolveToolContext } from '../toolSurface.js';
|
|
3
|
+
/**
|
|
4
|
+
* Check a background job started by another command (an evidence attachment, a generation job).
|
|
5
|
+
*
|
|
6
|
+
* Deliberately named "job": the product's "tasks" are user work items, and the interface never uses that
|
|
7
|
+
* word for background machinery.
|
|
8
|
+
*/
|
|
9
|
+
export const jobStatusCommand = async ({ io, jobId, apiUrl, json = false, credentialStore, }) => {
|
|
10
|
+
const ctx = await resolveToolContext(apiUrl, credentialStore);
|
|
11
|
+
const envelope = await callTool(ctx, 'get_job_status', { jobId });
|
|
12
|
+
if (envelope.status !== 'ok')
|
|
13
|
+
throw new Error(errorMessage(envelope, 'Job not found.'));
|
|
14
|
+
const data = asRecord(envelope.data);
|
|
15
|
+
if (json) {
|
|
16
|
+
writeLine(io.stdout, JSON.stringify(data, null, 2));
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
const status = asText(data.status, 'unknown');
|
|
20
|
+
const marker = status === 'completed'
|
|
21
|
+
? io.theme.green('✓')
|
|
22
|
+
: status === 'failed'
|
|
23
|
+
? io.theme.red('✗')
|
|
24
|
+
: io.theme.dim('…');
|
|
25
|
+
writeLine(io.stdout, `${marker} ${asText(data.type, 'job')} · ${status}`);
|
|
26
|
+
const result = data.result;
|
|
27
|
+
if (result !== null && result !== undefined) {
|
|
28
|
+
writeLine(io.stdout, JSON.stringify(result, null, 2));
|
|
29
|
+
}
|
|
30
|
+
};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { type CredentialStore } from '../credentials.js';
|
|
2
|
+
import { type CliIO } from '../io.js';
|
|
3
|
+
export interface LoginOptions {
|
|
4
|
+
io: CliIO;
|
|
5
|
+
apiUrl?: string | undefined;
|
|
6
|
+
clientId?: string | undefined;
|
|
7
|
+
deviceName?: string | undefined;
|
|
8
|
+
noOpen?: boolean | undefined;
|
|
9
|
+
store?: CredentialStore | undefined;
|
|
10
|
+
openBrowser?: ((url: string) => Promise<boolean>) | undefined;
|
|
11
|
+
}
|
|
12
|
+
export declare const loginCommand: ({ io, apiUrl, clientId, deviceName, noOpen, store: injectedStore, openBrowser, }: LoginOptions) => Promise<void>;
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { createApiClient } from '../api.js';
|
|
2
|
+
import { openInBrowser } from '../browser.js';
|
|
3
|
+
import { CLI_CLIENT_ID, resolveApiBaseUrl } from '../config.js';
|
|
4
|
+
import { createCredentialStore } from '../credentials.js';
|
|
5
|
+
import { resolveDeviceIdentity } from '../deviceInfo.js';
|
|
6
|
+
import { pollForToken, requestDeviceCode } from '../deviceFlow.js';
|
|
7
|
+
import { writeLine } from '../io.js';
|
|
8
|
+
export const loginCommand = async ({ io, apiUrl, clientId = CLI_CLIENT_ID, deviceName, noOpen = false, store: injectedStore, openBrowser = openInBrowser, }) => {
|
|
9
|
+
const baseUrl = resolveApiBaseUrl({ flag: apiUrl });
|
|
10
|
+
const client = createApiClient(baseUrl);
|
|
11
|
+
const store = injectedStore ?? (await createCredentialStore());
|
|
12
|
+
const identity = resolveDeviceIdentity(deviceName);
|
|
13
|
+
const code = await requestDeviceCode(client, clientId, identity);
|
|
14
|
+
const link = code.verificationUriComplete ?? code.verificationUri;
|
|
15
|
+
writeLine(io.stdout, io.theme.bold('Authorize the Ciphrix CLI'));
|
|
16
|
+
writeLine(io.stdout);
|
|
17
|
+
writeLine(io.stdout, ` ${io.theme.cyan(link)}`);
|
|
18
|
+
writeLine(io.stdout);
|
|
19
|
+
writeLine(io.stdout, io.theme.dim(`Device: ${identity.deviceName} (${identity.devicePlatform})`));
|
|
20
|
+
if (code.verificationUriComplete) {
|
|
21
|
+
writeLine(io.stdout, io.theme.dim(`Or enter the code ${code.userCode} at ${code.verificationUri}`));
|
|
22
|
+
}
|
|
23
|
+
else {
|
|
24
|
+
writeLine(io.stdout, io.theme.dim(`Enter the code ${code.userCode}`));
|
|
25
|
+
}
|
|
26
|
+
writeLine(io.stdout, io.theme.dim('Waiting for approval…'));
|
|
27
|
+
if (!noOpen) {
|
|
28
|
+
const opened = await openBrowser(link);
|
|
29
|
+
if (!opened) {
|
|
30
|
+
writeLine(io.stdout, io.theme.dim('Could not open a browser automatically. Open the link above.'));
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
const token = await pollForToken({
|
|
34
|
+
client,
|
|
35
|
+
deviceCode: code.deviceCode,
|
|
36
|
+
intervalSeconds: code.intervalSeconds,
|
|
37
|
+
expiresInSeconds: code.expiresInSeconds,
|
|
38
|
+
clientId,
|
|
39
|
+
});
|
|
40
|
+
await store.set({ apiUrl: baseUrl, token });
|
|
41
|
+
// Label this session so the user can recognise it in their device list. Best-effort: a failure here
|
|
42
|
+
// must not fail the login.
|
|
43
|
+
try {
|
|
44
|
+
await client.request('/user/profile/sessions/label', {
|
|
45
|
+
method: 'POST',
|
|
46
|
+
token,
|
|
47
|
+
body: { deviceName: identity.deviceName, devicePlatform: identity.devicePlatform },
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
catch {
|
|
51
|
+
// ignore
|
|
52
|
+
}
|
|
53
|
+
writeLine(io.stdout, `${io.theme.green('✓')} Signed in to ${baseUrl}`);
|
|
54
|
+
};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { type CredentialStore } from '../credentials.js';
|
|
2
|
+
import { type CliIO } from '../io.js';
|
|
3
|
+
export interface LogoutOptions {
|
|
4
|
+
io: CliIO;
|
|
5
|
+
apiUrl?: string | undefined;
|
|
6
|
+
store?: CredentialStore | undefined;
|
|
7
|
+
localOnly?: boolean | undefined;
|
|
8
|
+
}
|
|
9
|
+
export declare const logoutCommand: ({ io, apiUrl, store: injectedStore, localOnly, }: LogoutOptions) => Promise<void>;
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { ApiError, createApiClient } from '../api.js';
|
|
2
|
+
import { resolveApiBaseUrl } from '../config.js';
|
|
3
|
+
import { createCredentialStore } from '../credentials.js';
|
|
4
|
+
import { writeLine } from '../io.js';
|
|
5
|
+
const isInvalidCredential = (error) => {
|
|
6
|
+
if (!(error instanceof ApiError))
|
|
7
|
+
return false;
|
|
8
|
+
if (error.status === 401)
|
|
9
|
+
return true;
|
|
10
|
+
if (error.status !== 404)
|
|
11
|
+
return false;
|
|
12
|
+
return ['invalid_token', 'session_not_found', 'token_not_found', 'not_found'].includes(error.code?.toLowerCase() ?? '');
|
|
13
|
+
};
|
|
14
|
+
export const logoutCommand = async ({ io, apiUrl, store: injectedStore, localOnly = false, }) => {
|
|
15
|
+
const baseUrl = resolveApiBaseUrl({ flag: apiUrl });
|
|
16
|
+
const store = injectedStore ?? (await createCredentialStore());
|
|
17
|
+
const credential = await store.get(baseUrl);
|
|
18
|
+
if (!credential) {
|
|
19
|
+
writeLine(io.stdout, 'Not signed in.');
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
if (localOnly) {
|
|
23
|
+
await store.clear(baseUrl);
|
|
24
|
+
writeLine(io.stdout, `${io.theme.yellow('!')} Removed the local credential for ${baseUrl}.`);
|
|
25
|
+
writeLine(io.stdout, 'The remote session is still active. Revoke it from the account session settings.');
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
let alreadyInvalid = false;
|
|
29
|
+
try {
|
|
30
|
+
await createApiClient(baseUrl).request('/auth/sign-out', {
|
|
31
|
+
method: 'POST',
|
|
32
|
+
token: credential.token,
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
catch (error) {
|
|
36
|
+
if (!isInvalidCredential(error)) {
|
|
37
|
+
throw new Error(`Could not confirm remote session revocation. The credential was kept so you can retry. ` +
|
|
38
|
+
`Retry later, or use --local-only to remove it from this device without revoking the session.`, { cause: error });
|
|
39
|
+
}
|
|
40
|
+
alreadyInvalid = true;
|
|
41
|
+
}
|
|
42
|
+
try {
|
|
43
|
+
await store.clear(baseUrl);
|
|
44
|
+
}
|
|
45
|
+
catch {
|
|
46
|
+
if (alreadyInvalid) {
|
|
47
|
+
throw new Error('The remote session is no longer valid, but the local credential could not be removed. ' +
|
|
48
|
+
'Check the credential store and remove it manually if needed.');
|
|
49
|
+
}
|
|
50
|
+
throw new Error('The remote session was revoked, but the local credential could not be removed. ' +
|
|
51
|
+
'Check the credential store and remove it manually if needed.');
|
|
52
|
+
}
|
|
53
|
+
writeLine(io.stdout, alreadyInvalid
|
|
54
|
+
? `${io.theme.green('✓')} The remote session was already invalid; removed the local credential for ${baseUrl}.`
|
|
55
|
+
: `${io.theme.green('✓')} Revoked the remote session and removed the local credential for ${baseUrl}.`);
|
|
56
|
+
};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { CredentialStore } from '../credentials.js';
|
|
2
|
+
import { type CliIO } from '../io.js';
|
|
3
|
+
export interface PolicyListOptions {
|
|
4
|
+
io: CliIO;
|
|
5
|
+
apiUrl?: string | undefined;
|
|
6
|
+
json?: boolean | undefined;
|
|
7
|
+
name?: string | undefined;
|
|
8
|
+
status?: string | undefined;
|
|
9
|
+
page?: number | undefined;
|
|
10
|
+
limit?: number | undefined;
|
|
11
|
+
store?: CredentialStore | undefined;
|
|
12
|
+
}
|
|
13
|
+
export interface PolicyGetOptions {
|
|
14
|
+
io: CliIO;
|
|
15
|
+
apiUrl?: string | undefined;
|
|
16
|
+
json?: boolean | undefined;
|
|
17
|
+
policy: string;
|
|
18
|
+
version?: number | undefined;
|
|
19
|
+
minor?: number | undefined;
|
|
20
|
+
store?: CredentialStore | undefined;
|
|
21
|
+
}
|
|
22
|
+
export declare const policyListCommand: ({ io, apiUrl, json, name, status, page, limit, store, }: PolicyListOptions) => Promise<void>;
|
|
23
|
+
export declare const policyGetCommand: ({ io, apiUrl, json, policy, version, minor, store, }: PolicyGetOptions) => Promise<void>;
|