@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.
Files changed (63) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +201 -0
  3. package/dist/actions.d.ts +19 -0
  4. package/dist/actions.js +30 -0
  5. package/dist/api.d.ts +27 -0
  6. package/dist/api.js +180 -0
  7. package/dist/banner.d.ts +2 -0
  8. package/dist/banner.js +12 -0
  9. package/dist/browser.d.ts +2 -0
  10. package/dist/browser.js +44 -0
  11. package/dist/cli.d.ts +14 -0
  12. package/dist/cli.js +1579 -0
  13. package/dist/commands/asset.d.ts +59 -0
  14. package/dist/commands/asset.js +237 -0
  15. package/dist/commands/check.d.ts +30 -0
  16. package/dist/commands/check.js +182 -0
  17. package/dist/commands/context.d.ts +23 -0
  18. package/dist/commands/context.js +102 -0
  19. package/dist/commands/control.d.ts +44 -0
  20. package/dist/commands/control.js +189 -0
  21. package/dist/commands/framework.d.ts +40 -0
  22. package/dist/commands/framework.js +181 -0
  23. package/dist/commands/job.d.ts +16 -0
  24. package/dist/commands/job.js +30 -0
  25. package/dist/commands/login.d.ts +12 -0
  26. package/dist/commands/login.js +54 -0
  27. package/dist/commands/logout.d.ts +9 -0
  28. package/dist/commands/logout.js +56 -0
  29. package/dist/commands/policy.d.ts +23 -0
  30. package/dist/commands/policy.js +112 -0
  31. package/dist/commands/policyActions.d.ts +37 -0
  32. package/dist/commands/policyActions.js +177 -0
  33. package/dist/commands/risk.d.ts +51 -0
  34. package/dist/commands/risk.js +289 -0
  35. package/dist/commands/test.d.ts +54 -0
  36. package/dist/commands/test.js +331 -0
  37. package/dist/commands/vendor.d.ts +45 -0
  38. package/dist/commands/vendor.js +218 -0
  39. package/dist/config.d.ts +12 -0
  40. package/dist/config.js +56 -0
  41. package/dist/constants.d.ts +7 -0
  42. package/dist/constants.js +7 -0
  43. package/dist/credentials.d.ts +19 -0
  44. package/dist/credentials.js +182 -0
  45. package/dist/deviceFlow.d.ts +32 -0
  46. package/dist/deviceFlow.js +115 -0
  47. package/dist/deviceInfo.d.ts +11 -0
  48. package/dist/deviceInfo.js +25 -0
  49. package/dist/index.d.ts +2 -0
  50. package/dist/index.js +4 -0
  51. package/dist/io.d.ts +46 -0
  52. package/dist/io.js +99 -0
  53. package/dist/output.d.ts +14 -0
  54. package/dist/output.js +37 -0
  55. package/dist/theme.d.ts +25 -0
  56. package/dist/theme.js +39 -0
  57. package/dist/toolSurface.d.ts +20 -0
  58. package/dist/toolSurface.js +49 -0
  59. package/dist/upload.d.ts +18 -0
  60. package/dist/upload.js +115 -0
  61. package/package.json +71 -0
  62. package/skills/ciphrix/SKILL.md +169 -0
  63. package/skills/ciphrix/references/commands.md +1592 -0
@@ -0,0 +1,59 @@
1
+ import type { CredentialStore } from '../credentials.js';
2
+ import { type CliIO } from '../io.js';
3
+ export interface AssetCommonOptions {
4
+ io: CliIO;
5
+ apiUrl?: string | undefined;
6
+ json?: boolean | undefined;
7
+ credentialStore?: CredentialStore | undefined;
8
+ }
9
+ export declare const assetListCommand: ({ io, apiUrl, json, credentialStore, name, category, status, businessImpact, dataClassification, page, limit, }: AssetCommonOptions & {
10
+ name?: string | undefined;
11
+ category?: string | undefined;
12
+ status?: string | undefined;
13
+ businessImpact?: string | undefined;
14
+ dataClassification?: string | undefined;
15
+ page?: number | undefined;
16
+ limit?: number | undefined;
17
+ }) => Promise<void>;
18
+ export declare const assetGetCommand: ({ io, name, apiUrl, json, credentialStore, }: AssetCommonOptions & {
19
+ name: string;
20
+ }) => Promise<void>;
21
+ export declare const assetLinksCommand: ({ io, name, type, apiUrl, json, credentialStore, page, limit, }: AssetCommonOptions & {
22
+ name: string;
23
+ type: string;
24
+ page?: number | undefined;
25
+ limit?: number | undefined;
26
+ }) => Promise<void>;
27
+ export declare const assetUpdateCommand: ({ io, name, apiUrl, json, yes, credentialStore, changes, }: AssetCommonOptions & {
28
+ name: string;
29
+ yes?: boolean | undefined;
30
+ changes: Record<string, unknown>;
31
+ }) => Promise<void>;
32
+ export declare const assetCreateCommand: ({ io, apiUrl, json, credentialStore, name, category, subCategory, customerAssetKey, externalRefId, description, status, businessImpact, dataClassification, technicalOwner, businessOwner, location, }: AssetCommonOptions & {
33
+ name: string;
34
+ category: string;
35
+ subCategory: string;
36
+ customerAssetKey?: string | undefined;
37
+ externalRefId?: string | undefined;
38
+ description?: string | undefined;
39
+ status?: string | undefined;
40
+ businessImpact?: string | undefined;
41
+ dataClassification?: string | undefined;
42
+ technicalOwner?: string | undefined;
43
+ businessOwner?: string | undefined;
44
+ location?: string | undefined;
45
+ }) => Promise<void>;
46
+ export declare const assetDeleteCommand: ({ io, name, apiUrl, json, yes, credentialStore, }: AssetCommonOptions & {
47
+ name: string;
48
+ yes?: boolean | undefined;
49
+ }) => Promise<void>;
50
+ export declare const assetTagsCommand: ({ io, name, apiUrl, json, credentialStore, }: AssetCommonOptions & {
51
+ name: string;
52
+ }) => Promise<void>;
53
+ export declare const assetTagCommand: ({ io, name, key, value, mappingId, apiUrl, json, yes, credentialStore, }: AssetCommonOptions & {
54
+ name: string;
55
+ key?: string | undefined;
56
+ value?: string | undefined;
57
+ mappingId?: string | undefined;
58
+ yes?: boolean | undefined;
59
+ }) => Promise<void>;
@@ -0,0 +1,237 @@
1
+ import { applyTool } from '../actions.js';
2
+ import { writeLine, writeNextHint, writePageFooter, writeTable, clip } 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 assetListCommand = async ({ io, apiUrl, json = false, credentialStore, name, category, status, businessImpact, dataClassification, page, limit, }) => {
6
+ const ctx = await resolveToolContext(apiUrl, credentialStore);
7
+ const input = {};
8
+ if (name)
9
+ input.name = name;
10
+ if (category)
11
+ input.category = [category];
12
+ if (status)
13
+ input.status = status
14
+ .split(',')
15
+ .map((value) => value.trim())
16
+ .filter(Boolean);
17
+ if (businessImpact)
18
+ input.businessImpact = [businessImpact];
19
+ if (dataClassification)
20
+ input.dataClassification = [dataClassification];
21
+ if (page)
22
+ input.page = page;
23
+ if (limit)
24
+ input.limit = limit;
25
+ const envelope = await callTool(ctx, 'list_assets', input);
26
+ if (envelope.status !== 'ok')
27
+ throw new Error(errorMessage(envelope, 'Could not list assets.'));
28
+ if (json) {
29
+ writeLine(io.stdout, JSON.stringify(envelope.data, null, 2));
30
+ return;
31
+ }
32
+ const data = asRecord(envelope.data);
33
+ const assets = Array.isArray(data.assets) ? data.assets : [];
34
+ writeLine(io.stdout, `${io.theme.bold('Assets')} ${io.theme.dim(`(${text(data.total, String(assets.length))} total)`)}`);
35
+ if (assets.length === 0) {
36
+ writeLine(io.stdout, 'No assets found.');
37
+ return;
38
+ }
39
+ writeTable(io, ['CODE', 'NAME', 'CATEGORY', 'STATUS', 'IMPACT', 'CLASSIFICATION'], assets.map((asset) => {
40
+ const record = asRecord(asset);
41
+ return [
42
+ text(record.code, '-'),
43
+ clip(text(record.name), 60),
44
+ text(record.category),
45
+ text(record.status),
46
+ text(record.businessImpact),
47
+ text(record.dataClassification),
48
+ ];
49
+ }));
50
+ writePageFooter(io, {
51
+ shown: assets.length,
52
+ total: typeof data.total === 'number' ? data.total : assets.length,
53
+ page: typeof data.page === 'number' ? data.page : 1,
54
+ limit: typeof data.limit === 'number' ? data.limit : 25,
55
+ hasMore: data.hasMore === true,
56
+ });
57
+ const next = asText(asRecord(assets[0]).code);
58
+ if (next)
59
+ writeNextHint(io, `ciphrix asset get ${next}`);
60
+ };
61
+ export const assetGetCommand = async ({ io, name, apiUrl, json = false, credentialStore, }) => {
62
+ const ctx = await resolveToolContext(apiUrl, credentialStore);
63
+ const envelope = await callTool(ctx, 'get_asset', { name });
64
+ if (envelope.status !== 'ok')
65
+ throw new Error(errorMessage(envelope, 'Asset not found.'));
66
+ const asset = asRecord(asRecord(envelope.data).asset);
67
+ if (json) {
68
+ writeLine(io.stdout, JSON.stringify(asset, null, 2));
69
+ return;
70
+ }
71
+ writeLine(io.stdout, `${io.theme.bold(asText(asset.name, 'Asset'))} ${io.theme.dim(`· ${asText(asset.code)}`)}`);
72
+ const fields = [
73
+ ['Customer key', asset.customerAssetKey],
74
+ ['Category', asset.category],
75
+ ['Sub-category', asset.subCategory],
76
+ ['Status', asset.status],
77
+ ['Business impact', asset.businessImpact],
78
+ ['Data classification', asset.dataClassification],
79
+ ['Technical owner', asset.technicalOwner],
80
+ ['Business owner', asset.businessOwner],
81
+ ['External ref', asset.externalRefId],
82
+ ['Location', asset.location],
83
+ ];
84
+ for (const [label, value] of fields) {
85
+ if (value === undefined || value === null || value === '')
86
+ continue;
87
+ writeLine(io.stdout, ` ${io.theme.dim(`${label}:`)} ${text(value)}`);
88
+ }
89
+ if (typeof asset.description === 'string' && asset.description.trim() !== '') {
90
+ writeLine(io.stdout);
91
+ writeLine(io.stdout, asset.description);
92
+ }
93
+ };
94
+ export const assetLinksCommand = async ({ io, name, type, apiUrl, json = false, credentialStore, page, limit, }) => {
95
+ const ctx = await resolveToolContext(apiUrl, credentialStore);
96
+ const input = { name, type };
97
+ if (page)
98
+ input.page = page;
99
+ if (limit)
100
+ input.limit = limit;
101
+ const envelope = await callTool(ctx, 'list_asset_links', input);
102
+ if (envelope.status !== 'ok')
103
+ throw new Error(errorMessage(envelope, `Could not read asset ${type}.`));
104
+ if (json) {
105
+ writeLine(io.stdout, JSON.stringify(envelope.data, null, 2));
106
+ return;
107
+ }
108
+ const data = asRecord(envelope.data);
109
+ const items = Array.isArray(data.items) ? data.items : [];
110
+ writeLine(io.stdout, `${io.theme.bold(`Asset ${type}`)} ${io.theme.dim(`(${text(data.total, String(items.length))})`)}`);
111
+ if (items.length === 0) {
112
+ writeLine(io.stdout, `No ${type} mapped.`);
113
+ return;
114
+ }
115
+ writeTable(io, ['ITEM', 'STATUS'], items.map((item) => {
116
+ const record = asRecord(item);
117
+ return [
118
+ text(record.name ?? record.asset_name ?? record.control_name ?? record.risk_title ?? record.id),
119
+ text(record.status),
120
+ ];
121
+ }));
122
+ };
123
+ export const assetUpdateCommand = async ({ io, name, apiUrl, json = false, yes = false, credentialStore, changes, }) => {
124
+ if (Object.keys(changes).length === 0) {
125
+ throw new Error('Provide at least one field, for example --status active');
126
+ }
127
+ const ctx = await resolveToolContext(apiUrl, credentialStore);
128
+ const result = await applyTool({
129
+ ctx,
130
+ io,
131
+ toolName: 'update_asset',
132
+ input: { name, changes },
133
+ fallback: 'Could not update the asset.',
134
+ yes,
135
+ });
136
+ if (!result)
137
+ return;
138
+ if (json)
139
+ writeLine(io.stdout, JSON.stringify(result.data, null, 2));
140
+ else {
141
+ const data = asRecord(result.data);
142
+ const applied = Array.isArray(data.applied) ? data.applied.join(', ') : '';
143
+ const failed = Array.isArray(data.failed) ? data.failed.length : 0;
144
+ writeLine(io.stdout, `${io.theme.green('✓')} updated ${name}: ${applied}${failed > 0 ? io.theme.yellow(` · ${failed} refused`) : ''}`);
145
+ }
146
+ };
147
+ export const assetCreateCommand = async ({ io, apiUrl, json = false, credentialStore, name, category, subCategory, customerAssetKey, externalRefId, description, status, businessImpact, dataClassification, technicalOwner, businessOwner, location, }) => {
148
+ const ctx = await resolveToolContext(apiUrl, credentialStore);
149
+ const input = { name, category, subCategory };
150
+ for (const [key, value] of [
151
+ ['customerAssetKey', customerAssetKey],
152
+ ['externalRefId', externalRefId],
153
+ ['description', description],
154
+ ['status', status],
155
+ ['businessImpact', businessImpact],
156
+ ['dataClassification', dataClassification],
157
+ ['technicalOwner', technicalOwner],
158
+ ['businessOwner', businessOwner],
159
+ ['location', location],
160
+ ]) {
161
+ if (value !== undefined)
162
+ input[key] = value;
163
+ }
164
+ const result = await applyTool({
165
+ ctx,
166
+ io,
167
+ toolName: 'create_asset',
168
+ input,
169
+ fallback: 'Could not create the asset.',
170
+ });
171
+ if (!result)
172
+ return;
173
+ const data = asRecord(result.data);
174
+ if (json) {
175
+ writeLine(io.stdout, JSON.stringify(data, null, 2));
176
+ return;
177
+ }
178
+ writeLine(io.stdout, `${io.theme.green('✓')} asset created: ${asText(data.code, '') || asText(data.id)}`);
179
+ };
180
+ export const assetDeleteCommand = async ({ io, name, apiUrl, json = false, yes = false, credentialStore, }) => {
181
+ const ctx = await resolveToolContext(apiUrl, credentialStore);
182
+ const result = await applyTool({
183
+ ctx,
184
+ io,
185
+ toolName: 'delete_asset',
186
+ input: { name },
187
+ fallback: 'Could not delete the asset.',
188
+ yes,
189
+ });
190
+ if (!result)
191
+ return;
192
+ if (json)
193
+ writeLine(io.stdout, JSON.stringify(result.data, null, 2));
194
+ else
195
+ writeLine(io.stdout, `${io.theme.green('✓')} asset ${name} deleted`);
196
+ };
197
+ export const assetTagsCommand = async ({ io, name, apiUrl, json = false, credentialStore, }) => {
198
+ const ctx = await resolveToolContext(apiUrl, credentialStore);
199
+ const envelope = await callTool(ctx, 'list_asset_tags', { name });
200
+ if (envelope.status !== 'ok')
201
+ throw new Error(errorMessage(envelope, 'Could not read the tags.'));
202
+ const data = asRecord(envelope.data);
203
+ const tags = Array.isArray(data.tags) ? data.tags : [];
204
+ if (json) {
205
+ writeLine(io.stdout, JSON.stringify(data, null, 2));
206
+ return;
207
+ }
208
+ writeLine(io.stdout, `${io.theme.bold('Asset tags')} ${io.theme.dim(`(${tags.length})`)}`);
209
+ if (tags.length === 0) {
210
+ writeLine(io.stdout, 'No tags assigned.');
211
+ return;
212
+ }
213
+ writeTable(io, ['MAPPING', 'KEY', 'VALUE'], tags.map((tag) => {
214
+ const record = asRecord(tag);
215
+ return [asText(record.mappingId, '-'), asText(record.key), asText(record.value)];
216
+ }));
217
+ };
218
+ export const assetTagCommand = async ({ io, name, key, value, mappingId, apiUrl, json = false, yes = false, credentialStore, }) => {
219
+ const ctx = await resolveToolContext(apiUrl, credentialStore);
220
+ const input = mappingId
221
+ ? { name, action: 'remove', mappingId }
222
+ : { name, action: 'assign', tags: [{ key: key, value: value }] };
223
+ const result = await applyTool({
224
+ ctx,
225
+ io,
226
+ toolName: 'set_asset_tag',
227
+ input,
228
+ fallback: 'Could not change the tag.',
229
+ yes,
230
+ });
231
+ if (!result)
232
+ return;
233
+ if (json)
234
+ writeLine(io.stdout, JSON.stringify(result.data, null, 2));
235
+ else
236
+ writeLine(io.stdout, `${io.theme.green('✓')} tag ${mappingId ? 'removed from' : 'assigned to'} ${name}`);
237
+ };
@@ -0,0 +1,30 @@
1
+ import type { CredentialStore } from '../credentials.js';
2
+ import { type CliIO } from '../io.js';
3
+ export interface CheckCommonOptions {
4
+ io: CliIO;
5
+ apiUrl?: string | undefined;
6
+ json?: boolean | undefined;
7
+ credentialStore?: CredentialStore | undefined;
8
+ }
9
+ export interface CheckTargetOptions extends CheckCommonOptions {
10
+ check: string;
11
+ }
12
+ export declare const checkListCommand: ({ io, apiUrl, json, credentialStore, integrationType, status, page, limit, }: CheckCommonOptions & {
13
+ integrationType?: string | undefined;
14
+ status?: string | undefined;
15
+ page?: number | undefined;
16
+ limit?: number | undefined;
17
+ }) => Promise<void>;
18
+ export declare const checkResourcesCommand: ({ io, check, apiUrl, json, credentialStore, }: CheckTargetOptions) => Promise<void>;
19
+ export declare const checkRunCommand: ({ io, check, apiUrl, json, credentialStore, }: CheckTargetOptions) => Promise<void>;
20
+ export declare const checkIntegrationsCommand: ({ io, apiUrl, json, credentialStore, }: CheckCommonOptions) => Promise<void>;
21
+ export declare const checkRunsCommand: ({ io, check, apiUrl, json, credentialStore, page, limit, }: CheckTargetOptions & {
22
+ page?: number | undefined;
23
+ limit?: number | undefined;
24
+ }) => Promise<void>;
25
+ export declare const checkFindingsCommand: ({ io, check, apiUrl, json, credentialStore, }: CheckTargetOptions) => Promise<void>;
26
+ export declare const checkSetEnabledCommand: ({ io, check, enabled, notes, apiUrl, json, yes, credentialStore, }: CheckTargetOptions & {
27
+ enabled: boolean;
28
+ notes?: string | undefined;
29
+ yes?: boolean | undefined;
30
+ }) => Promise<void>;
@@ -0,0 +1,182 @@
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 checkListCommand = async ({ io, apiUrl, json = false, credentialStore, integrationType, status, page, limit, }) => {
6
+ const ctx = await resolveToolContext(apiUrl, credentialStore);
7
+ const input = {};
8
+ if (integrationType)
9
+ input.integrationType = integrationType
10
+ .split(',')
11
+ .map((value) => value.trim())
12
+ .filter(Boolean);
13
+ if (status)
14
+ input.status = status
15
+ .split(',')
16
+ .map((value) => value.trim())
17
+ .filter(Boolean);
18
+ if (page)
19
+ input.page = page;
20
+ if (limit)
21
+ input.limit = limit;
22
+ const envelope = await callTool(ctx, 'list_checks', input);
23
+ if (envelope.status !== 'ok')
24
+ throw new Error(errorMessage(envelope, 'Could not list checks.'));
25
+ if (json) {
26
+ writeLine(io.stdout, JSON.stringify(envelope.data, null, 2));
27
+ return;
28
+ }
29
+ const data = asRecord(envelope.data);
30
+ const checks = Array.isArray(data.checks) ? data.checks : [];
31
+ writeLine(io.stdout, `${io.theme.bold('Checks')} ${io.theme.dim(`(${text(data.total, String(checks.length))} total)`)}`);
32
+ if (checks.length === 0) {
33
+ writeLine(io.stdout, 'No checks found.');
34
+ return;
35
+ }
36
+ writeTable(io, ['CODE', 'NAME', 'INTEGRATION', 'SEVERITY', 'STATUS', 'COMPLIANCE'], checks.map((check) => {
37
+ const record = asRecord(check);
38
+ return [
39
+ text(record.code, '-'),
40
+ text(record.name),
41
+ text(record.integration),
42
+ text(record.severity),
43
+ text(record.status),
44
+ record.complianceRate === null || record.complianceRate === undefined
45
+ ? '-'
46
+ : `${text(record.complianceRate)}%`,
47
+ ];
48
+ }));
49
+ writePageFooter(io, {
50
+ shown: checks.length,
51
+ total: typeof data.total === 'number' ? data.total : checks.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(checks[0]).code);
57
+ if (next)
58
+ writeNextHint(io, `ciphrix check resources ${next}`);
59
+ };
60
+ export const checkResourcesCommand = async ({ io, check, apiUrl, json = false, credentialStore, }) => {
61
+ const ctx = await resolveToolContext(apiUrl, credentialStore);
62
+ const envelope = await callTool(ctx, 'list_check_resources', { name: check });
63
+ if (envelope.status !== 'ok')
64
+ throw new Error(errorMessage(envelope, 'Could not list check resources.'));
65
+ const data = asRecord(envelope.data);
66
+ const resources = Array.isArray(data.resources) ? data.resources : [];
67
+ if (json) {
68
+ writeLine(io.stdout, JSON.stringify(data, null, 2));
69
+ return;
70
+ }
71
+ writeLine(io.stdout, `${io.theme.bold(check)} ${io.theme.dim(`· ${resources.length} resource(s)`)}`);
72
+ for (const resource of resources) {
73
+ const record = asRecord(resource);
74
+ writeLine(io.stdout, ` ${text(record.resource_name ?? record.name ?? record.resource_id ?? record.id)}`);
75
+ }
76
+ };
77
+ export const checkRunCommand = async ({ io, check, apiUrl, json = false, credentialStore, }) => {
78
+ const ctx = await resolveToolContext(apiUrl, credentialStore);
79
+ const envelope = await callTool(ctx, 'get_check_run', { name: check });
80
+ if (envelope.status !== 'ok')
81
+ throw new Error(errorMessage(envelope, 'Could not read the check run.'));
82
+ if (json) {
83
+ writeLine(io.stdout, JSON.stringify(envelope.data, null, 2));
84
+ return;
85
+ }
86
+ const run = asRecord(asRecord(envelope.data).run);
87
+ writeLine(io.stdout, `${io.theme.bold(check)} ${io.theme.dim('· latest run')}`);
88
+ for (const [key, value] of Object.entries(run)) {
89
+ if (value === undefined || value === null || typeof value === 'object')
90
+ continue;
91
+ writeLine(io.stdout, ` ${io.theme.dim(`${key}:`)} ${text(value)}`);
92
+ }
93
+ };
94
+ export const checkIntegrationsCommand = async ({ io, apiUrl, json = false, credentialStore, }) => {
95
+ const ctx = await resolveToolContext(apiUrl, credentialStore);
96
+ const envelope = await callTool(ctx, 'list_integrations', {});
97
+ if (envelope.status !== 'ok')
98
+ throw new Error(errorMessage(envelope, 'Could not list integrations.'));
99
+ const integrations = Array.isArray(asRecord(envelope.data).integrations)
100
+ ? asRecord(envelope.data).integrations
101
+ : [];
102
+ if (json) {
103
+ writeLine(io.stdout, JSON.stringify(integrations, null, 2));
104
+ return;
105
+ }
106
+ writeLine(io.stdout, `${io.theme.bold('Integrations')} ${io.theme.dim(`(${integrations.length})`)}`);
107
+ for (const integration of integrations) {
108
+ writeLine(io.stdout, ` ${asText(integration, text(integration))}`);
109
+ }
110
+ };
111
+ export const checkRunsCommand = async ({ io, check, apiUrl, json = false, credentialStore, page, limit, }) => {
112
+ const ctx = await resolveToolContext(apiUrl, credentialStore);
113
+ const input = { name: check };
114
+ if (page)
115
+ input.page = page;
116
+ if (limit)
117
+ input.limit = limit;
118
+ const envelope = await callTool(ctx, 'list_check_runs', input);
119
+ if (envelope.status !== 'ok')
120
+ throw new Error(errorMessage(envelope, 'Could not read the run history.'));
121
+ const data = asRecord(envelope.data);
122
+ const runs = Array.isArray(data.runs) ? data.runs : [];
123
+ if (json) {
124
+ writeLine(io.stdout, JSON.stringify(data, null, 2));
125
+ return;
126
+ }
127
+ writeLine(io.stdout, `${io.theme.bold('Check runs')} ${io.theme.dim(`(${runs.length})`)}`);
128
+ if (runs.length === 0) {
129
+ writeLine(io.stdout, 'No runs recorded.');
130
+ return;
131
+ }
132
+ writeTable(io, ['WHEN', 'STATUS', 'COMPLIANCE'], runs.map((run) => {
133
+ const record = asRecord(run);
134
+ const rate = record.complianceRate;
135
+ return [
136
+ asText(record.runDate).slice(0, 10) || '-',
137
+ asText(record.status, '-'),
138
+ typeof rate === 'number' ? `${rate}%` : '-',
139
+ ];
140
+ }));
141
+ };
142
+ export const checkFindingsCommand = async ({ io, check, apiUrl, json = false, credentialStore, }) => {
143
+ const ctx = await resolveToolContext(apiUrl, credentialStore);
144
+ const envelope = await callTool(ctx, 'list_check_findings', { name: check });
145
+ if (envelope.status !== 'ok')
146
+ throw new Error(errorMessage(envelope, 'Could not read the findings.'));
147
+ const data = asRecord(envelope.data);
148
+ const findings = Array.isArray(data.findings) ? data.findings : [];
149
+ if (json) {
150
+ writeLine(io.stdout, JSON.stringify(data, null, 2));
151
+ return;
152
+ }
153
+ writeLine(io.stdout, `${io.theme.bold('Findings')} ${io.theme.dim(`(${findings.length})`)}`);
154
+ if (findings.length === 0) {
155
+ writeLine(io.stdout, 'No findings raised from this check.');
156
+ return;
157
+ }
158
+ writeTable(io, ['FINDING', 'STATUS'], findings.map((finding) => {
159
+ const record = asRecord(finding);
160
+ return [asText(record.title ?? record.name, asText(record.id)), asText(record.status, '-')];
161
+ }));
162
+ };
163
+ export const checkSetEnabledCommand = async ({ io, check, enabled, notes, apiUrl, json = false, yes = false, credentialStore, }) => {
164
+ const ctx = await resolveToolContext(apiUrl, credentialStore);
165
+ const input = { name: check, enabled };
166
+ if (notes)
167
+ input.notes = notes;
168
+ const result = await applyTool({
169
+ ctx,
170
+ io,
171
+ toolName: 'set_check_enabled',
172
+ input,
173
+ fallback: 'Could not change the check.',
174
+ yes,
175
+ });
176
+ if (!result)
177
+ return;
178
+ if (json)
179
+ writeLine(io.stdout, JSON.stringify(result.data, null, 2));
180
+ else
181
+ writeLine(io.stdout, `${io.theme.green('\u2713')} ${enabled ? 'enabled' : 'disabled'} ${check}`);
182
+ };
@@ -0,0 +1,23 @@
1
+ import type { CredentialStore } from '../credentials.js';
2
+ import { type CliIO } from '../io.js';
3
+ export interface ContextCommonOptions {
4
+ io: CliIO;
5
+ apiUrl?: string | undefined;
6
+ json?: boolean | undefined;
7
+ credentialStore?: CredentialStore | undefined;
8
+ }
9
+ export interface ContextReadOptions extends ContextCommonOptions {
10
+ store: string;
11
+ }
12
+ export interface ContextActionOptions extends ContextCommonOptions {
13
+ yes?: boolean | undefined;
14
+ confirm?: ((question: string) => Promise<boolean>) | undefined;
15
+ }
16
+ export declare const contextGetCommand: ({ io, store, apiUrl, json, credentialStore, }: ContextReadOptions) => Promise<void>;
17
+ export declare const contextStatusCommand: ({ io, store, apiUrl, json, credentialStore, }: ContextReadOptions) => Promise<void>;
18
+ export interface ContextSetOptions extends ContextCommonOptions {
19
+ target: string;
20
+ value: string;
21
+ }
22
+ export declare const contextSetCommand: ({ io, apiUrl, json, target, value, credentialStore, }: ContextSetOptions) => Promise<void>;
23
+ export declare const contextResetCommand: ({ io, apiUrl, json, yes, confirm, credentialStore, }: ContextActionOptions) => Promise<void>;
@@ -0,0 +1,102 @@
1
+ import { applyTool } from '../actions.js';
2
+ import { writeLine } from '../io.js';
3
+ import { asRecord, asText, callTool, errorMessage, resolveToolContext } from '../toolSurface.js';
4
+ const assertStore = (store) => {
5
+ if (store !== 'business' && store !== 'operating') {
6
+ throw new Error(`Unknown context store "${store}". Use "business" or "operating".`);
7
+ }
8
+ };
9
+ const count = (value) => typeof value === 'number' ? String(value) : asText(value, '0');
10
+ export const contextGetCommand = async ({ io, store, apiUrl, json = false, credentialStore, }) => {
11
+ assertStore(store);
12
+ const ctx = await resolveToolContext(apiUrl, credentialStore);
13
+ const envelope = await callTool(ctx, 'get_context', { store });
14
+ if (envelope.status !== 'ok')
15
+ throw new Error(errorMessage(envelope, 'Could not read context.'));
16
+ if (json) {
17
+ writeLine(io.stdout, JSON.stringify(envelope.data, null, 2));
18
+ return;
19
+ }
20
+ const data = asRecord(envelope.data);
21
+ writeLine(io.stdout, `${io.theme.bold('Context')} ${io.theme.dim(`· ${store} · ${count(data.answeredCount)}/${count(data.questionCount)} answered`)}`);
22
+ writeLine(io.stdout);
23
+ const digest = asText(data.digestMarkdown);
24
+ if (digest.trim() === '') {
25
+ writeLine(io.stdout, io.theme.dim('(no digest yet — answer some questions)'));
26
+ return;
27
+ }
28
+ writeLine(io.stdout, digest.trimEnd());
29
+ };
30
+ export const contextStatusCommand = async ({ io, store, apiUrl, json = false, credentialStore, }) => {
31
+ assertStore(store);
32
+ const ctx = await resolveToolContext(apiUrl, credentialStore);
33
+ const envelope = await callTool(ctx, 'get_context_status', { store });
34
+ if (envelope.status !== 'ok')
35
+ throw new Error(errorMessage(envelope, 'Could not read context status.'));
36
+ if (json) {
37
+ writeLine(io.stdout, JSON.stringify(envelope.data, null, 2));
38
+ return;
39
+ }
40
+ const data = asRecord(envelope.data);
41
+ writeLine(io.stdout, `${io.theme.bold('Context status')} ${io.theme.dim(`· ${store} · ${count(data.answeredCount)}/${count(data.questionCount)} answered · ${count(data.unansweredCount)} open`)}`);
42
+ const unanswered = Array.isArray(data.unanswered) ? data.unanswered : [];
43
+ if (unanswered.length === 0) {
44
+ writeLine(io.stdout, `${io.theme.green('✓')} Nothing outstanding.`);
45
+ return;
46
+ }
47
+ for (const item of unanswered) {
48
+ const record = asRecord(item);
49
+ writeLine(io.stdout, ` ${io.theme.cyan(asText(record.id))} — ${asText(record.label)}`);
50
+ }
51
+ };
52
+ const parseValue = (raw) => {
53
+ try {
54
+ return JSON.parse(raw);
55
+ }
56
+ catch {
57
+ return raw;
58
+ }
59
+ };
60
+ export const contextSetCommand = async ({ io, apiUrl, json = false, target, value, credentialStore, }) => {
61
+ const separator = target.indexOf('.');
62
+ if (separator === -1) {
63
+ throw new Error('Use <store>.<questionId>, for example business.company_size');
64
+ }
65
+ const store = target.slice(0, separator);
66
+ const questionId = target.slice(separator + 1);
67
+ assertStore(store);
68
+ if (!questionId)
69
+ throw new Error('A question id is required, for example business.company_size');
70
+ const ctx = await resolveToolContext(apiUrl, credentialStore);
71
+ const envelope = await callTool(ctx, 'set_context_answer', {
72
+ store,
73
+ questionId,
74
+ value: parseValue(value),
75
+ });
76
+ if (envelope.status !== 'ok')
77
+ throw new Error(errorMessage(envelope, 'Could not save the answer.'));
78
+ if (json) {
79
+ writeLine(io.stdout, JSON.stringify(envelope.data, null, 2));
80
+ return;
81
+ }
82
+ writeLine(io.stdout, `${io.theme.green('✓')} ${store}.${questionId} saved`);
83
+ };
84
+ export const contextResetCommand = async ({ io, apiUrl, json = false, yes = false, confirm, credentialStore, }) => {
85
+ const ctx = await resolveToolContext(apiUrl, credentialStore);
86
+ const result = await applyTool({
87
+ ctx,
88
+ io,
89
+ toolName: 'reset_context',
90
+ input: { store: 'business' },
91
+ fallback: 'Could not reset the context.',
92
+ yes,
93
+ confirm,
94
+ });
95
+ if (!result)
96
+ return;
97
+ if (json) {
98
+ writeLine(io.stdout, JSON.stringify(result, null, 2));
99
+ return;
100
+ }
101
+ writeLine(io.stdout, `${io.theme.green('✓')} Business Context reset`);
102
+ };
@@ -0,0 +1,44 @@
1
+ import type { CredentialStore } from '../credentials.js';
2
+ import { type CliIO } from '../io.js';
3
+ export interface ControlCommonOptions {
4
+ io: CliIO;
5
+ apiUrl?: string | undefined;
6
+ json?: boolean | undefined;
7
+ credentialStore?: CredentialStore | undefined;
8
+ }
9
+ export interface ControlReadOptions extends ControlCommonOptions {
10
+ name: string;
11
+ }
12
+ export declare const controlListCommand: ({ io, apiUrl, json, credentialStore, search, domain, status, applicability, page, limit, }: ControlCommonOptions & {
13
+ search?: string | undefined;
14
+ domain?: string | undefined;
15
+ status?: string | undefined;
16
+ applicability?: string | undefined;
17
+ page?: number | undefined;
18
+ limit?: number | undefined;
19
+ }) => Promise<void>;
20
+ export declare const controlGetCommand: ({ io, name, apiUrl, json, credentialStore, }: ControlReadOptions) => Promise<void>;
21
+ export declare const controlItemsCommand: ({ io, name, apiUrl, json, credentialStore, search, page, limit, }: ControlReadOptions & {
22
+ search?: string | undefined;
23
+ page?: number | undefined;
24
+ limit?: number | undefined;
25
+ }) => Promise<void>;
26
+ export declare const controlAvailableItemsCommand: ({ io, name, itemType, apiUrl, json, credentialStore, search, page, limit, }: ControlReadOptions & {
27
+ itemType: string;
28
+ search?: string | undefined;
29
+ page?: number | undefined;
30
+ limit?: number | undefined;
31
+ }) => Promise<void>;
32
+ export declare const controlUpdateCommand: ({ io, name, apiUrl, json, yes, confirm, credentialStore, changes, }: ControlReadOptions & {
33
+ yes?: boolean | undefined;
34
+ confirm?: ((question: string) => Promise<boolean>) | undefined;
35
+ changes: Record<string, unknown>;
36
+ }) => Promise<void>;
37
+ export declare const controlLinkCommand: ({ io, name, action, itemType, itemId, apiUrl, json, yes, confirm, credentialStore, }: ControlCommonOptions & {
38
+ name: string;
39
+ action: string;
40
+ itemType: string;
41
+ itemId: string;
42
+ yes?: boolean | undefined;
43
+ confirm?: ((question: string) => Promise<boolean>) | undefined;
44
+ }) => Promise<void>;