@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,54 @@
|
|
|
1
|
+
import type { CredentialStore } from '../credentials.js';
|
|
2
|
+
import { type CliIO } from '../io.js';
|
|
3
|
+
export interface TestCommonOptions {
|
|
4
|
+
io: CliIO;
|
|
5
|
+
apiUrl?: string | undefined;
|
|
6
|
+
json?: boolean | undefined;
|
|
7
|
+
credentialStore?: CredentialStore | undefined;
|
|
8
|
+
}
|
|
9
|
+
export interface TestReadOptions extends TestCommonOptions {
|
|
10
|
+
name: string;
|
|
11
|
+
runId?: string | undefined;
|
|
12
|
+
}
|
|
13
|
+
export interface TestActionOptions extends TestCommonOptions {
|
|
14
|
+
name: string;
|
|
15
|
+
runId?: string | undefined;
|
|
16
|
+
yes?: boolean | undefined;
|
|
17
|
+
confirm?: ((question: string) => Promise<boolean>) | undefined;
|
|
18
|
+
}
|
|
19
|
+
export declare const testListCommand: ({ io, apiUrl, json, credentialStore, search, status, page, limit, }: TestCommonOptions & {
|
|
20
|
+
search?: string | undefined;
|
|
21
|
+
status?: string | undefined;
|
|
22
|
+
page?: number | undefined;
|
|
23
|
+
limit?: number | undefined;
|
|
24
|
+
}) => Promise<void>;
|
|
25
|
+
export declare const testGetCommand: ({ io, name, apiUrl, json, credentialStore, }: TestReadOptions) => Promise<void>;
|
|
26
|
+
export declare const testRunsCommand: ({ io, name, apiUrl, json, credentialStore, }: TestReadOptions) => Promise<void>;
|
|
27
|
+
export declare const testItemsCommand: ({ io, name, runId, apiUrl, json, credentialStore, }: TestReadOptions) => Promise<void>;
|
|
28
|
+
export declare const testRunCreateCommand: ({ io, name, apiUrl, json, credentialStore, runAt, }: TestReadOptions & {
|
|
29
|
+
runAt?: string | undefined;
|
|
30
|
+
}) => Promise<void>;
|
|
31
|
+
export declare const testRunResultCommand: ({ io, name, runId, apiUrl, json, credentialStore, status, note, }: TestReadOptions & {
|
|
32
|
+
status: string;
|
|
33
|
+
note?: string | undefined;
|
|
34
|
+
}) => Promise<void>;
|
|
35
|
+
export declare const testRunDeleteCommand: ({ io, name, runId, apiUrl, json, yes, confirm, credentialStore, }: TestActionOptions) => Promise<void>;
|
|
36
|
+
export interface TestAttachOptions extends TestReadOptions {
|
|
37
|
+
itemType: string;
|
|
38
|
+
itemId?: string | undefined;
|
|
39
|
+
uploadIds?: string[] | undefined;
|
|
40
|
+
file?: string | undefined;
|
|
41
|
+
unlink?: boolean | undefined;
|
|
42
|
+
}
|
|
43
|
+
export declare const testAttachCommand: ({ io, name, runId, apiUrl, json, credentialStore, itemType, itemId, uploadIds, file, unlink, }: TestAttachOptions) => Promise<void>;
|
|
44
|
+
export declare const testAvailableItemsCommand: ({ io, name, runId, itemType, apiUrl, json, credentialStore, search, }: TestReadOptions & {
|
|
45
|
+
itemType: string;
|
|
46
|
+
search?: string | undefined;
|
|
47
|
+
}) => Promise<void>;
|
|
48
|
+
export declare const testLinksCommand: ({ io, name, apiUrl, json, credentialStore, }: TestReadOptions) => Promise<void>;
|
|
49
|
+
export declare const testUpdateCommand: ({ io, name, apiUrl, json, yes, confirm, credentialStore, changes, }: TestReadOptions & {
|
|
50
|
+
yes?: boolean | undefined;
|
|
51
|
+
confirm?: ((question: string) => Promise<boolean>) | undefined;
|
|
52
|
+
changes: Record<string, unknown>;
|
|
53
|
+
}) => Promise<void>;
|
|
54
|
+
export declare const testDeleteCommand: ({ io, name, apiUrl, json, yes, confirm, credentialStore, }: TestActionOptions) => Promise<void>;
|
|
@@ -0,0 +1,331 @@
|
|
|
1
|
+
import { applyTool } from '../actions.js';
|
|
2
|
+
import { randomUUID } from 'node:crypto';
|
|
3
|
+
import { uploadLocalFile } from '../upload.js';
|
|
4
|
+
import { writeLine, writeNextHint, writePageFooter, writeTable } from '../io.js';
|
|
5
|
+
import { asRecord, asText, callTool, errorMessage, resolveToolContext } from '../toolSurface.js';
|
|
6
|
+
const text = (value, fallback = '-') => {
|
|
7
|
+
if (typeof value === 'string')
|
|
8
|
+
return value;
|
|
9
|
+
if (typeof value === 'number')
|
|
10
|
+
return String(value);
|
|
11
|
+
return fallback;
|
|
12
|
+
};
|
|
13
|
+
const assessmentLabel = (assessment) => {
|
|
14
|
+
const record = asRecord(assessment);
|
|
15
|
+
const state = asText(record.state, 'unknown');
|
|
16
|
+
if (state !== 'ready')
|
|
17
|
+
return state;
|
|
18
|
+
return `${text(record.level, '-')} (${state})`;
|
|
19
|
+
};
|
|
20
|
+
export const testListCommand = async ({ io, apiUrl, json = false, credentialStore, search, status, page, limit, }) => {
|
|
21
|
+
const ctx = await resolveToolContext(apiUrl, credentialStore);
|
|
22
|
+
const input = {};
|
|
23
|
+
if (search)
|
|
24
|
+
input.search = search;
|
|
25
|
+
if (status)
|
|
26
|
+
input.latestRunStatus = status
|
|
27
|
+
.split(',')
|
|
28
|
+
.map((value) => value.trim())
|
|
29
|
+
.filter(Boolean);
|
|
30
|
+
if (page)
|
|
31
|
+
input.page = page;
|
|
32
|
+
if (limit)
|
|
33
|
+
input.limit = limit;
|
|
34
|
+
const envelope = await callTool(ctx, 'list_tests', input);
|
|
35
|
+
if (envelope.status !== 'ok')
|
|
36
|
+
throw new Error(errorMessage(envelope, 'Could not list tests.'));
|
|
37
|
+
if (json) {
|
|
38
|
+
writeLine(io.stdout, JSON.stringify(envelope.data, null, 2));
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
const data = asRecord(envelope.data);
|
|
42
|
+
const tests = Array.isArray(data.tests) ? data.tests : [];
|
|
43
|
+
writeLine(io.stdout, `${io.theme.bold('Tests')} ${io.theme.dim(`(${text(data.total, String(tests.length))})`)}`);
|
|
44
|
+
if (tests.length === 0) {
|
|
45
|
+
writeLine(io.stdout, 'No tests found.');
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
writeTable(io, ['CODE', 'NAME', 'APPLICABILITY', 'LATEST RUN'], tests.map((test) => {
|
|
49
|
+
const record = asRecord(test);
|
|
50
|
+
return [
|
|
51
|
+
text(record.code, '-'),
|
|
52
|
+
text(record.name),
|
|
53
|
+
text(record.applicability),
|
|
54
|
+
text(record.latestRunStatus),
|
|
55
|
+
];
|
|
56
|
+
}));
|
|
57
|
+
writePageFooter(io, {
|
|
58
|
+
shown: tests.length,
|
|
59
|
+
total: typeof data.total === 'number' ? data.total : tests.length,
|
|
60
|
+
page: typeof data.page === 'number' ? data.page : 1,
|
|
61
|
+
limit: typeof data.limit === 'number' ? data.limit : 25,
|
|
62
|
+
hasMore: data.hasMore === true,
|
|
63
|
+
});
|
|
64
|
+
const next = asText(asRecord(tests[0]).code);
|
|
65
|
+
if (next)
|
|
66
|
+
writeNextHint(io, `ciphrix test get ${next}`);
|
|
67
|
+
};
|
|
68
|
+
export const testGetCommand = async ({ io, name, apiUrl, json = false, credentialStore, }) => {
|
|
69
|
+
const ctx = await resolveToolContext(apiUrl, credentialStore);
|
|
70
|
+
const envelope = await callTool(ctx, 'get_test', { name });
|
|
71
|
+
if (envelope.status !== 'ok')
|
|
72
|
+
throw new Error(errorMessage(envelope, 'Test not found.'));
|
|
73
|
+
if (json) {
|
|
74
|
+
writeLine(io.stdout, JSON.stringify(envelope.data, null, 2));
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
const test = asRecord(asRecord(envelope.data).test);
|
|
78
|
+
const runs = Array.isArray(test.runs) ? test.runs : [];
|
|
79
|
+
const assurance = asRecord(asRecord(test.aiAssessments).runs);
|
|
80
|
+
writeLine(io.stdout, `${io.theme.bold(text(test.testName, 'Test'))} ${io.theme.dim(`· ${text(test.applicability)} · ${runs.length} run(s)`)}`);
|
|
81
|
+
writeLine(io.stdout);
|
|
82
|
+
renderRuns(io, runs, assurance);
|
|
83
|
+
};
|
|
84
|
+
export const testRunsCommand = async ({ io, name, apiUrl, json = false, credentialStore, }) => {
|
|
85
|
+
const ctx = await resolveToolContext(apiUrl, credentialStore);
|
|
86
|
+
const envelope = await callTool(ctx, 'list_test_runs', { name });
|
|
87
|
+
if (envelope.status !== 'ok')
|
|
88
|
+
throw new Error(errorMessage(envelope, 'Test not found.'));
|
|
89
|
+
if (json) {
|
|
90
|
+
writeLine(io.stdout, JSON.stringify(envelope.data, null, 2));
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
const data = asRecord(envelope.data);
|
|
94
|
+
const runs = Array.isArray(data.runs) ? data.runs : [];
|
|
95
|
+
writeLine(io.stdout, `${io.theme.bold(text(data.name, 'Test'))} ${io.theme.dim(`· ${runs.length} run(s)`)}`);
|
|
96
|
+
writeLine(io.stdout);
|
|
97
|
+
renderRuns(io, runs, asRecord(data.assurance));
|
|
98
|
+
};
|
|
99
|
+
const renderRuns = (io, runs, assurance) => {
|
|
100
|
+
if (runs.length === 0) {
|
|
101
|
+
writeLine(io.stdout, 'No runs yet.');
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
writeTable(io, ['RUN', 'STATUS', 'WHEN', 'ITEMS', 'ASSURANCE'], runs.map((run) => {
|
|
105
|
+
const record = asRecord(run);
|
|
106
|
+
const counts = asRecord(record.itemCounts);
|
|
107
|
+
const items = Number(counts.upload ?? 0) + Number(counts.native_doc ?? 0) + Number(counts.check ?? 0);
|
|
108
|
+
return [
|
|
109
|
+
text(record.id),
|
|
110
|
+
text(record.status),
|
|
111
|
+
text(record.runAt).slice(0, 10),
|
|
112
|
+
String(items),
|
|
113
|
+
assuranceLabel(assurance[text(record.id)]),
|
|
114
|
+
];
|
|
115
|
+
}));
|
|
116
|
+
};
|
|
117
|
+
const assuranceLabel = (assessment) => assessmentLabel(assessment);
|
|
118
|
+
export const testItemsCommand = async ({ io, name, runId, apiUrl, json = false, credentialStore, }) => {
|
|
119
|
+
const ctx = await resolveToolContext(apiUrl, credentialStore);
|
|
120
|
+
const input = { name };
|
|
121
|
+
if (runId)
|
|
122
|
+
input.runId = runId;
|
|
123
|
+
const envelope = await callTool(ctx, 'list_run_items', input);
|
|
124
|
+
if (envelope.status !== 'ok')
|
|
125
|
+
throw new Error(errorMessage(envelope, 'Could not read run items.'));
|
|
126
|
+
if (json) {
|
|
127
|
+
writeLine(io.stdout, JSON.stringify(envelope.data, null, 2));
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
const data = asRecord(envelope.data);
|
|
131
|
+
const items = Array.isArray(data.items) ? data.items : [];
|
|
132
|
+
const relevance = asRecord(data.relevance);
|
|
133
|
+
writeLine(io.stdout, `${io.theme.bold('Run items')} ${io.theme.dim(`· run ${text(data.runId)}`)}`);
|
|
134
|
+
if (items.length === 0) {
|
|
135
|
+
writeLine(io.stdout, 'No evidence attached.');
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
writeTable(io, ['ITEM', 'TYPE', 'RELEVANCE'], items.map((item) => {
|
|
139
|
+
const record = asRecord(item);
|
|
140
|
+
return [
|
|
141
|
+
text(record.name, text(record.id)),
|
|
142
|
+
text(record.itemType ?? record.type),
|
|
143
|
+
assessmentLabel(relevance[text(record.id)]),
|
|
144
|
+
];
|
|
145
|
+
}));
|
|
146
|
+
};
|
|
147
|
+
export const testRunCreateCommand = async ({ io, name, apiUrl, json = false, credentialStore, runAt, }) => {
|
|
148
|
+
const ctx = await resolveToolContext(apiUrl, credentialStore);
|
|
149
|
+
const input = { name };
|
|
150
|
+
if (runAt)
|
|
151
|
+
input.runAt = runAt;
|
|
152
|
+
const result = await applyTool({
|
|
153
|
+
ctx,
|
|
154
|
+
io,
|
|
155
|
+
toolName: 'create_test_run',
|
|
156
|
+
input,
|
|
157
|
+
fallback: 'Could not create a run.',
|
|
158
|
+
});
|
|
159
|
+
if (json && result)
|
|
160
|
+
writeLine(io.stdout, JSON.stringify(result.data, null, 2));
|
|
161
|
+
else if (result)
|
|
162
|
+
writeLine(io.stdout, `${io.theme.green('✓')} run created: ${text(asRecord(result.data).runId)}`);
|
|
163
|
+
};
|
|
164
|
+
export const testRunResultCommand = async ({ io, name, runId, apiUrl, json = false, credentialStore, status, note, }) => {
|
|
165
|
+
const ctx = await resolveToolContext(apiUrl, credentialStore);
|
|
166
|
+
const input = { name, status };
|
|
167
|
+
if (runId)
|
|
168
|
+
input.runId = runId;
|
|
169
|
+
if (note)
|
|
170
|
+
input.note = note;
|
|
171
|
+
const result = await applyTool({
|
|
172
|
+
ctx,
|
|
173
|
+
io,
|
|
174
|
+
toolName: 'set_test_run_status',
|
|
175
|
+
input,
|
|
176
|
+
fallback: 'Could not record the result.',
|
|
177
|
+
});
|
|
178
|
+
if (!result)
|
|
179
|
+
return;
|
|
180
|
+
if (json)
|
|
181
|
+
writeLine(io.stdout, JSON.stringify(result.data, null, 2));
|
|
182
|
+
else
|
|
183
|
+
writeLine(io.stdout, `${io.theme.green('✓')} run result: ${status}`);
|
|
184
|
+
};
|
|
185
|
+
export const testRunDeleteCommand = async ({ io, name, runId, apiUrl, json = false, yes = false, confirm, credentialStore, }) => {
|
|
186
|
+
if (!runId)
|
|
187
|
+
throw new Error('Provide --run <id> to delete a run.');
|
|
188
|
+
const ctx = await resolveToolContext(apiUrl, credentialStore);
|
|
189
|
+
const result = await applyTool({
|
|
190
|
+
ctx,
|
|
191
|
+
io,
|
|
192
|
+
toolName: 'delete_test_run',
|
|
193
|
+
input: { name, runId },
|
|
194
|
+
fallback: 'Could not delete the run.',
|
|
195
|
+
yes,
|
|
196
|
+
confirm,
|
|
197
|
+
});
|
|
198
|
+
if (!result)
|
|
199
|
+
return;
|
|
200
|
+
if (json)
|
|
201
|
+
writeLine(io.stdout, JSON.stringify(result.data, null, 2));
|
|
202
|
+
else
|
|
203
|
+
writeLine(io.stdout, `${io.theme.green('✓')} run ${runId} deleted`);
|
|
204
|
+
};
|
|
205
|
+
export const testAttachCommand = async ({ io, name, runId, apiUrl, json = false, credentialStore, itemType, itemId, uploadIds, file, unlink = false, }) => {
|
|
206
|
+
const ctx = await resolveToolContext(apiUrl, credentialStore);
|
|
207
|
+
let resolvedItemType = itemType;
|
|
208
|
+
let resolvedItemId = itemId;
|
|
209
|
+
let resolvedUploadIds = uploadIds;
|
|
210
|
+
if (file) {
|
|
211
|
+
const upload = await uploadLocalFile(ctx, file, 'test');
|
|
212
|
+
resolvedItemType = 'upload';
|
|
213
|
+
resolvedItemId = undefined;
|
|
214
|
+
resolvedUploadIds = [upload.id];
|
|
215
|
+
}
|
|
216
|
+
const input = {
|
|
217
|
+
name,
|
|
218
|
+
action: unlink ? 'unlink' : 'link',
|
|
219
|
+
itemType: resolvedItemType,
|
|
220
|
+
};
|
|
221
|
+
if (runId)
|
|
222
|
+
input.runId = runId;
|
|
223
|
+
if (resolvedItemId)
|
|
224
|
+
input.itemId = resolvedItemId;
|
|
225
|
+
if (resolvedUploadIds && resolvedUploadIds.length > 0)
|
|
226
|
+
input.uploadIds = resolvedUploadIds;
|
|
227
|
+
const envelope = await callTool(ctx, 'attach_run_item', input, { idempotencyKey: randomUUID() });
|
|
228
|
+
if (envelope.status !== 'ok')
|
|
229
|
+
throw new Error(errorMessage(envelope, 'Could not attach the item.'));
|
|
230
|
+
if (json) {
|
|
231
|
+
writeLine(io.stdout, JSON.stringify(envelope.data, null, 2));
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
234
|
+
const data = asRecord(envelope.data);
|
|
235
|
+
const job = asText(data.aiTaskId);
|
|
236
|
+
writeLine(io.stdout, `${io.theme.green('✓')} ${unlink ? 'unlinked' : 'linked'} ${resolvedItemType} on run ${text(data.runId)}${job ? io.theme.dim(` · job ${job}`) : ''}`);
|
|
237
|
+
};
|
|
238
|
+
export const testAvailableItemsCommand = async ({ io, name, runId, itemType, apiUrl, json = false, credentialStore, search, }) => {
|
|
239
|
+
const ctx = await resolveToolContext(apiUrl, credentialStore);
|
|
240
|
+
const input = { name, itemType };
|
|
241
|
+
if (runId)
|
|
242
|
+
input.runId = runId;
|
|
243
|
+
if (search)
|
|
244
|
+
input.search = search;
|
|
245
|
+
const envelope = await callTool(ctx, 'list_test_items_available', input);
|
|
246
|
+
if (envelope.status !== 'ok')
|
|
247
|
+
throw new Error(errorMessage(envelope, 'Could not read available items.'));
|
|
248
|
+
if (json) {
|
|
249
|
+
writeLine(io.stdout, JSON.stringify(envelope.data, null, 2));
|
|
250
|
+
return;
|
|
251
|
+
}
|
|
252
|
+
const data = asRecord(envelope.data);
|
|
253
|
+
const items = Array.isArray(data.items) ? data.items : [];
|
|
254
|
+
writeLine(io.stdout, `${io.theme.bold(`Available ${itemType}s`)} ${io.theme.dim(`(${items.length})`)}`);
|
|
255
|
+
if (items.length === 0) {
|
|
256
|
+
writeLine(io.stdout, 'Nothing available to attach.');
|
|
257
|
+
return;
|
|
258
|
+
}
|
|
259
|
+
writeTable(io, ['ITEM', 'TYPE', 'STATUS'], items.map((item) => {
|
|
260
|
+
const record = asRecord(item);
|
|
261
|
+
return [
|
|
262
|
+
text(record.name, text(record.id)),
|
|
263
|
+
text(record.itemType ?? record.type),
|
|
264
|
+
text(record.status),
|
|
265
|
+
];
|
|
266
|
+
}));
|
|
267
|
+
};
|
|
268
|
+
export const testLinksCommand = async ({ io, name, apiUrl, json = false, credentialStore, }) => {
|
|
269
|
+
const ctx = await resolveToolContext(apiUrl, credentialStore);
|
|
270
|
+
const envelope = await callTool(ctx, 'list_test_links', { name });
|
|
271
|
+
if (envelope.status !== 'ok')
|
|
272
|
+
throw new Error(errorMessage(envelope, 'Could not read the test links.'));
|
|
273
|
+
if (json) {
|
|
274
|
+
writeLine(io.stdout, JSON.stringify(envelope.data, null, 2));
|
|
275
|
+
return;
|
|
276
|
+
}
|
|
277
|
+
const data = asRecord(envelope.data);
|
|
278
|
+
const controls = Array.isArray(data.controls) ? data.controls : [];
|
|
279
|
+
const clauses = Array.isArray(data.clauses) ? data.clauses : [];
|
|
280
|
+
writeLine(io.stdout, `${io.theme.bold('Controls')} ${io.theme.dim(`(${controls.length})`)}`);
|
|
281
|
+
for (const control of controls) {
|
|
282
|
+
const record = asRecord(control);
|
|
283
|
+
writeLine(io.stdout, ` ${text(record.controlCode ?? record.code)} ${text(record.controlName ?? record.name, text(record.id))}`);
|
|
284
|
+
}
|
|
285
|
+
writeLine(io.stdout, `${io.theme.bold('Clauses')} ${io.theme.dim(`(${clauses.length})`)}`);
|
|
286
|
+
for (const clause of clauses) {
|
|
287
|
+
const record = asRecord(clause);
|
|
288
|
+
writeLine(io.stdout, ` ${text(record.clauseCode ?? record.code)} ${text(record.clauseName ?? record.name, text(record.id))}`);
|
|
289
|
+
}
|
|
290
|
+
};
|
|
291
|
+
export const testUpdateCommand = async ({ io, name, apiUrl, json = false, yes = false, confirm, credentialStore, changes, }) => {
|
|
292
|
+
if (Object.keys(changes).length === 0) {
|
|
293
|
+
throw new Error('Provide at least one field, for example --applicability in_scope');
|
|
294
|
+
}
|
|
295
|
+
const ctx = await resolveToolContext(apiUrl, credentialStore);
|
|
296
|
+
const result = await applyTool({
|
|
297
|
+
ctx,
|
|
298
|
+
io,
|
|
299
|
+
toolName: 'update_test',
|
|
300
|
+
input: { name, changes },
|
|
301
|
+
fallback: 'Could not update the test.',
|
|
302
|
+
yes,
|
|
303
|
+
confirm,
|
|
304
|
+
});
|
|
305
|
+
if (!result)
|
|
306
|
+
return;
|
|
307
|
+
if (json)
|
|
308
|
+
writeLine(io.stdout, JSON.stringify(result.data, null, 2));
|
|
309
|
+
else {
|
|
310
|
+
const applied = asRecord(result.data).applied;
|
|
311
|
+
writeLine(io.stdout, `${io.theme.green('✓')} updated ${name}: ${Array.isArray(applied) ? applied.join(', ') : ''}`);
|
|
312
|
+
}
|
|
313
|
+
};
|
|
314
|
+
export const testDeleteCommand = async ({ io, name, apiUrl, json = false, yes = false, confirm, credentialStore, }) => {
|
|
315
|
+
const ctx = await resolveToolContext(apiUrl, credentialStore);
|
|
316
|
+
const result = await applyTool({
|
|
317
|
+
ctx,
|
|
318
|
+
io,
|
|
319
|
+
toolName: 'delete_test',
|
|
320
|
+
input: { name },
|
|
321
|
+
fallback: 'Could not delete the test.',
|
|
322
|
+
yes,
|
|
323
|
+
confirm,
|
|
324
|
+
});
|
|
325
|
+
if (!result)
|
|
326
|
+
return;
|
|
327
|
+
if (json)
|
|
328
|
+
writeLine(io.stdout, JSON.stringify(result.data, null, 2));
|
|
329
|
+
else
|
|
330
|
+
writeLine(io.stdout, `${io.theme.green('✓')} test ${name} deleted`);
|
|
331
|
+
};
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import type { CredentialStore } from '../credentials.js';
|
|
2
|
+
import { type CliIO } from '../io.js';
|
|
3
|
+
export interface VendorCommonOptions {
|
|
4
|
+
io: CliIO;
|
|
5
|
+
apiUrl?: string | undefined;
|
|
6
|
+
json?: boolean | undefined;
|
|
7
|
+
credentialStore?: CredentialStore | undefined;
|
|
8
|
+
}
|
|
9
|
+
export interface VendorReadOptions extends VendorCommonOptions {
|
|
10
|
+
name: string;
|
|
11
|
+
}
|
|
12
|
+
export interface VendorActionOptions extends VendorCommonOptions {
|
|
13
|
+
name?: string | undefined;
|
|
14
|
+
yes?: boolean | undefined;
|
|
15
|
+
confirm?: ((question: string) => Promise<boolean>) | undefined;
|
|
16
|
+
}
|
|
17
|
+
export declare const vendorListCommand: ({ io, apiUrl, json, credentialStore, name, criticality, relationshipStatus, page, limit, }: VendorCommonOptions & {
|
|
18
|
+
name?: string | undefined;
|
|
19
|
+
criticality?: string | undefined;
|
|
20
|
+
relationshipStatus?: string | undefined;
|
|
21
|
+
page?: number | undefined;
|
|
22
|
+
limit?: number | undefined;
|
|
23
|
+
}) => Promise<void>;
|
|
24
|
+
export declare const vendorGetCommand: ({ io, name, apiUrl, json, credentialStore, }: VendorReadOptions) => Promise<void>;
|
|
25
|
+
export declare const vendorCreateCommand: ({ io, apiUrl, json, credentialStore, name, category, criticality, description, }: VendorCommonOptions & {
|
|
26
|
+
name: string;
|
|
27
|
+
category?: string | undefined;
|
|
28
|
+
criticality?: string | undefined;
|
|
29
|
+
description?: string | undefined;
|
|
30
|
+
}) => Promise<void>;
|
|
31
|
+
export interface VendorUpdateOptions extends VendorReadOptions {
|
|
32
|
+
changes: Record<string, string>;
|
|
33
|
+
}
|
|
34
|
+
export declare const vendorUpdateCommand: ({ io, name, apiUrl, json, credentialStore, changes, }: VendorUpdateOptions) => Promise<void>;
|
|
35
|
+
export declare const vendorDeleteCommand: ({ io, name, apiUrl, json, yes, confirm, credentialStore, }: VendorActionOptions) => Promise<void>;
|
|
36
|
+
export declare const vendorFilesCommand: ({ io, name, apiUrl, json, credentialStore, }: VendorReadOptions) => Promise<void>;
|
|
37
|
+
export declare const vendorAttachCommand: ({ io, name, file, uploadId, apiUrl, json, yes, credentialStore, }: VendorReadOptions & {
|
|
38
|
+
file?: string | undefined;
|
|
39
|
+
uploadId?: string | undefined;
|
|
40
|
+
yes?: boolean | undefined;
|
|
41
|
+
}) => Promise<void>;
|
|
42
|
+
export declare const vendorDetachCommand: ({ io, name, mappingId, apiUrl, json, yes, credentialStore, }: VendorReadOptions & {
|
|
43
|
+
mappingId: string;
|
|
44
|
+
yes?: boolean | undefined;
|
|
45
|
+
}) => Promise<void>;
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
import { applyTool } from '../actions.js';
|
|
2
|
+
import { writeLine, writeNextHint, writePageFooter, writeTable } from '../io.js';
|
|
3
|
+
import { asRecord, callTool, errorMessage, resolveToolContext, asText } from '../toolSurface.js';
|
|
4
|
+
import { uploadLocalFile } from '../upload.js';
|
|
5
|
+
const text = (value, fallback = '-') => typeof value === 'string' ? value : typeof value === 'number' ? String(value) : fallback;
|
|
6
|
+
export const vendorListCommand = async ({ io, apiUrl, json = false, credentialStore, name, criticality, relationshipStatus, page, limit, }) => {
|
|
7
|
+
const ctx = await resolveToolContext(apiUrl, credentialStore);
|
|
8
|
+
const input = {};
|
|
9
|
+
if (name)
|
|
10
|
+
input.name = name;
|
|
11
|
+
if (criticality)
|
|
12
|
+
input.criticality = criticality;
|
|
13
|
+
if (relationshipStatus)
|
|
14
|
+
input.relationshipStatus = relationshipStatus;
|
|
15
|
+
if (page)
|
|
16
|
+
input.page = page;
|
|
17
|
+
if (limit)
|
|
18
|
+
input.limit = limit;
|
|
19
|
+
const envelope = await callTool(ctx, 'list_vendors', input);
|
|
20
|
+
if (envelope.status !== 'ok')
|
|
21
|
+
throw new Error(errorMessage(envelope, 'Could not list vendors.'));
|
|
22
|
+
if (json) {
|
|
23
|
+
writeLine(io.stdout, JSON.stringify(envelope.data, null, 2));
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
const data = asRecord(envelope.data);
|
|
27
|
+
const vendors = Array.isArray(data.vendors) ? data.vendors : [];
|
|
28
|
+
writeLine(io.stdout, `${io.theme.bold('Vendors')} ${io.theme.dim(`(${text(data.total, String(vendors.length))} total)`)}`);
|
|
29
|
+
if (vendors.length === 0) {
|
|
30
|
+
writeLine(io.stdout, 'No vendors found.');
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
writeTable(io, ['CODE', 'NAME', 'CATEGORY', 'CRITICALITY', 'RELATIONSHIP', 'REVIEW'], vendors.map((vendor) => {
|
|
34
|
+
const record = asRecord(vendor);
|
|
35
|
+
return [
|
|
36
|
+
text(record.code, '-'),
|
|
37
|
+
text(record.name),
|
|
38
|
+
text(record.category),
|
|
39
|
+
text(record.criticality),
|
|
40
|
+
text(record.relationshipStatus),
|
|
41
|
+
text(record.reviewStatus),
|
|
42
|
+
];
|
|
43
|
+
}));
|
|
44
|
+
writePageFooter(io, {
|
|
45
|
+
shown: vendors.length,
|
|
46
|
+
total: typeof data.total === 'number' ? data.total : vendors.length,
|
|
47
|
+
page: typeof data.page === 'number' ? data.page : 1,
|
|
48
|
+
limit: typeof data.limit === 'number' ? data.limit : 25,
|
|
49
|
+
hasMore: data.hasMore === true,
|
|
50
|
+
});
|
|
51
|
+
const next = asText(asRecord(vendors[0]).code);
|
|
52
|
+
if (next)
|
|
53
|
+
writeNextHint(io, `ciphrix vendor get ${next}`);
|
|
54
|
+
};
|
|
55
|
+
export const vendorGetCommand = async ({ io, name, apiUrl, json = false, credentialStore, }) => {
|
|
56
|
+
const ctx = await resolveToolContext(apiUrl, credentialStore);
|
|
57
|
+
const envelope = await callTool(ctx, 'get_vendor', { name });
|
|
58
|
+
if (envelope.status !== 'ok')
|
|
59
|
+
throw new Error(errorMessage(envelope, 'Vendor not found.'));
|
|
60
|
+
const vendor = asRecord(asRecord(envelope.data).vendor);
|
|
61
|
+
if (json) {
|
|
62
|
+
writeLine(io.stdout, JSON.stringify(vendor, null, 2));
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
writeLine(io.stdout, io.theme.bold(text(vendor.vendor_name ?? vendor.name, 'Vendor')));
|
|
66
|
+
const fields = [
|
|
67
|
+
['Category', vendor.vendor_category ?? vendor.category],
|
|
68
|
+
['Criticality', vendor.business_criticality ?? vendor.criticality],
|
|
69
|
+
['Data sensitivity', vendor.data_sensitivity],
|
|
70
|
+
['Relationship', vendor.vendor_relationship_status],
|
|
71
|
+
['Review status', vendor.workflow_status ?? vendor.vendor_review_status],
|
|
72
|
+
['Website', vendor.vendor_website ?? vendor.website],
|
|
73
|
+
['Description', vendor.vendor_description ?? vendor.description],
|
|
74
|
+
];
|
|
75
|
+
for (const [label, value] of fields) {
|
|
76
|
+
if (value === undefined || value === null || value === '')
|
|
77
|
+
continue;
|
|
78
|
+
writeLine(io.stdout, ` ${io.theme.dim(`${label}:`)} ${text(value)}`);
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
export const vendorCreateCommand = async ({ io, apiUrl, json = false, credentialStore, name, category, criticality, description, }) => {
|
|
82
|
+
const ctx = await resolveToolContext(apiUrl, credentialStore);
|
|
83
|
+
const input = { name };
|
|
84
|
+
if (category)
|
|
85
|
+
input.category = category;
|
|
86
|
+
if (criticality)
|
|
87
|
+
input.criticality = criticality;
|
|
88
|
+
if (description)
|
|
89
|
+
input.description = description;
|
|
90
|
+
const result = await applyTool({
|
|
91
|
+
ctx,
|
|
92
|
+
io,
|
|
93
|
+
toolName: 'create_vendor',
|
|
94
|
+
input,
|
|
95
|
+
fallback: 'Could not create the vendor.',
|
|
96
|
+
});
|
|
97
|
+
if (!result)
|
|
98
|
+
return;
|
|
99
|
+
if (json)
|
|
100
|
+
writeLine(io.stdout, JSON.stringify(result.data, null, 2));
|
|
101
|
+
else
|
|
102
|
+
writeLine(io.stdout, `${io.theme.green('✓')} vendor created: ${text(asRecord(result.data).name)}`);
|
|
103
|
+
};
|
|
104
|
+
export const vendorUpdateCommand = async ({ io, name, apiUrl, json = false, credentialStore, changes, }) => {
|
|
105
|
+
if (Object.keys(changes).length === 0) {
|
|
106
|
+
throw new Error('Provide at least one field, for example --criticality high');
|
|
107
|
+
}
|
|
108
|
+
const ctx = await resolveToolContext(apiUrl, credentialStore);
|
|
109
|
+
const result = await applyTool({
|
|
110
|
+
ctx,
|
|
111
|
+
io,
|
|
112
|
+
toolName: 'update_vendor',
|
|
113
|
+
input: { name, changes },
|
|
114
|
+
fallback: 'Could not update the vendor.',
|
|
115
|
+
});
|
|
116
|
+
if (!result)
|
|
117
|
+
return;
|
|
118
|
+
if (json)
|
|
119
|
+
writeLine(io.stdout, JSON.stringify(result.data, null, 2));
|
|
120
|
+
else {
|
|
121
|
+
const data = asRecord(result.data);
|
|
122
|
+
const applied = Array.isArray(data.applied)
|
|
123
|
+
? data.applied.map((value) => text(value)).join(', ')
|
|
124
|
+
: '';
|
|
125
|
+
writeLine(io.stdout, `${io.theme.green('✓')} updated ${name}: ${applied}`);
|
|
126
|
+
}
|
|
127
|
+
};
|
|
128
|
+
export const vendorDeleteCommand = async ({ io, name, apiUrl, json = false, yes = false, confirm, credentialStore, }) => {
|
|
129
|
+
if (!name)
|
|
130
|
+
throw new Error('Provide the vendor name.');
|
|
131
|
+
const ctx = await resolveToolContext(apiUrl, credentialStore);
|
|
132
|
+
const result = await applyTool({
|
|
133
|
+
ctx,
|
|
134
|
+
io,
|
|
135
|
+
toolName: 'delete_vendor',
|
|
136
|
+
input: { name },
|
|
137
|
+
fallback: 'Could not delete the vendor.',
|
|
138
|
+
yes,
|
|
139
|
+
confirm,
|
|
140
|
+
});
|
|
141
|
+
if (!result)
|
|
142
|
+
return;
|
|
143
|
+
if (json)
|
|
144
|
+
writeLine(io.stdout, JSON.stringify(result.data, null, 2));
|
|
145
|
+
else
|
|
146
|
+
writeLine(io.stdout, `${io.theme.green('✓')} vendor ${name} deleted`);
|
|
147
|
+
};
|
|
148
|
+
export const vendorFilesCommand = async ({ io, name, apiUrl, json = false, credentialStore, }) => {
|
|
149
|
+
const ctx = await resolveToolContext(apiUrl, credentialStore);
|
|
150
|
+
const envelope = await callTool(ctx, 'list_vendor_files', { name });
|
|
151
|
+
if (envelope.status !== 'ok')
|
|
152
|
+
throw new Error(errorMessage(envelope, 'Could not read vendor files.'));
|
|
153
|
+
if (json) {
|
|
154
|
+
writeLine(io.stdout, JSON.stringify(envelope.data, null, 2));
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
const data = asRecord(envelope.data);
|
|
158
|
+
const files = Array.isArray(data.files) ? data.files : [];
|
|
159
|
+
writeLine(io.stdout, `${io.theme.bold('Vendor files')} ${io.theme.dim(`(${files.length})`)}`);
|
|
160
|
+
if (files.length === 0) {
|
|
161
|
+
writeLine(io.stdout, 'No files attached.');
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
writeTable(io, ['MAPPING', 'FILE', 'ATTACHED'], files.map((file) => {
|
|
165
|
+
const record = asRecord(file);
|
|
166
|
+
return [
|
|
167
|
+
text(record.mappingId, '-'),
|
|
168
|
+
text(record.name),
|
|
169
|
+
asText(record.attachedAt).slice(0, 10) || '-',
|
|
170
|
+
];
|
|
171
|
+
}));
|
|
172
|
+
};
|
|
173
|
+
export const vendorAttachCommand = async ({ io, name, file, uploadId, apiUrl, json = false, yes = false, credentialStore, }) => {
|
|
174
|
+
if (!file && !uploadId)
|
|
175
|
+
throw new Error('Provide --file <path> or --upload <uploadId>');
|
|
176
|
+
const ctx = await resolveToolContext(apiUrl, credentialStore);
|
|
177
|
+
let uploadIds;
|
|
178
|
+
if (uploadId) {
|
|
179
|
+
uploadIds = [uploadId];
|
|
180
|
+
}
|
|
181
|
+
else if (file) {
|
|
182
|
+
uploadIds = [(await uploadLocalFile(ctx, file, 'vendor')).id];
|
|
183
|
+
}
|
|
184
|
+
else {
|
|
185
|
+
throw new Error('Provide --file <path> or --upload <uploadId>');
|
|
186
|
+
}
|
|
187
|
+
const result = await applyTool({
|
|
188
|
+
ctx,
|
|
189
|
+
io,
|
|
190
|
+
toolName: 'link_vendor_file',
|
|
191
|
+
input: { name, action: 'link', uploadIds },
|
|
192
|
+
fallback: 'Could not attach the file.',
|
|
193
|
+
yes,
|
|
194
|
+
});
|
|
195
|
+
if (!result)
|
|
196
|
+
return;
|
|
197
|
+
if (json)
|
|
198
|
+
writeLine(io.stdout, JSON.stringify(result.data, null, 2));
|
|
199
|
+
else
|
|
200
|
+
writeLine(io.stdout, `${io.theme.green('✓')} attached ${uploadIds.length} file(s) to ${name}`);
|
|
201
|
+
};
|
|
202
|
+
export const vendorDetachCommand = async ({ io, name, mappingId, apiUrl, json = false, yes = false, credentialStore, }) => {
|
|
203
|
+
const ctx = await resolveToolContext(apiUrl, credentialStore);
|
|
204
|
+
const result = await applyTool({
|
|
205
|
+
ctx,
|
|
206
|
+
io,
|
|
207
|
+
toolName: 'link_vendor_file',
|
|
208
|
+
input: { name, action: 'unlink', mappingId },
|
|
209
|
+
fallback: 'Could not detach the file.',
|
|
210
|
+
yes,
|
|
211
|
+
});
|
|
212
|
+
if (!result)
|
|
213
|
+
return;
|
|
214
|
+
if (json)
|
|
215
|
+
writeLine(io.stdout, JSON.stringify(result.data, null, 2));
|
|
216
|
+
else
|
|
217
|
+
writeLine(io.stdout, `${io.theme.green('✓')} detached ${mappingId} from ${name}`);
|
|
218
|
+
};
|
package/dist/config.d.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/** Public client identifier registered with the Ciphrix device-authorization endpoint. */
|
|
2
|
+
export declare const CLI_CLIENT_ID = "ciphrix-cli";
|
|
3
|
+
/** Production API base. Override with `--api-url` or `CIPHRIX_API_URL`. */
|
|
4
|
+
export declare const DEFAULT_API_BASE_URL = "https://global.api.ciphrix.app/api";
|
|
5
|
+
/** Used by the CLI hooks to scope the command-line opt-in to one command invocation. */
|
|
6
|
+
export declare const setCliInsecureHttpOptIn: (allowed: boolean) => void;
|
|
7
|
+
export declare const normalizeBaseUrl: (value: string) => string;
|
|
8
|
+
export declare const resolveApiBaseUrl: ({ flag, env, }?: {
|
|
9
|
+
flag?: string | undefined;
|
|
10
|
+
env?: NodeJS.ProcessEnv | undefined;
|
|
11
|
+
}) => string;
|
|
12
|
+
export declare const configDir: (env?: NodeJS.ProcessEnv) => string;
|