@ciphrix/cli 0.1.0 → 0.1.1

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/README.md CHANGED
@@ -164,7 +164,7 @@ one command. Installed help is authoritative. The complete generated reference i
164
164
  - Destructive and consequential actions require confirmation unless `--yes` is explicitly supplied.
165
165
  - JSON output is undecorated and suitable for programs and agents.
166
166
  - Requests time out, responses are size-limited, and local evidence uploads reject symbolic links and
167
- files over 50 MiB.
167
+ files over 25 MiB.
168
168
 
169
169
  Please report vulnerabilities privately as described in [SECURITY.md](SECURITY.md).
170
170
 
package/dist/cli.js CHANGED
@@ -305,6 +305,8 @@ export const createProgram = (io = createDefaultIO()) => {
305
305
  .requiredOption('--type <type>', 'item type: native_doc or check')
306
306
  .option('--run <id>', 'a specific run')
307
307
  .option('--search <text>', 'filter by item name')
308
+ .option('--page <n>', 'page number')
309
+ .option('--limit <n>', 'page size')
308
310
  .option('--api-url <url>', 'override the API base URL for this command')
309
311
  .option('--json', 'output raw JSON')
310
312
  .action(async (name, options) => {
@@ -314,6 +316,8 @@ export const createProgram = (io = createDefaultIO()) => {
314
316
  runId: readStringOption(options, 'run'),
315
317
  itemType: readStringOption(options, 'type') ?? 'native_doc',
316
318
  search: readStringOption(options, 'search'),
319
+ page: readNumberOption(options, 'page'),
320
+ limit: readNumberOption(options, 'limit'),
317
321
  apiUrl: readStringOption(options, 'apiUrl'),
318
322
  json: readBooleanOption(options, 'json'),
319
323
  });
@@ -454,6 +458,8 @@ export const createProgram = (io = createDefaultIO()) => {
454
458
  .description('List items that can be mapped to a clause')
455
459
  .requiredOption('--type <type>', 'item type: test or document')
456
460
  .option('--search <text>', 'filter by item name')
461
+ .option('--page <n>', 'page number')
462
+ .option('--limit <n>', 'page size')
457
463
  .option('--api-url <url>', 'override the API base URL for this command')
458
464
  .option('--json', 'output raw JSON')
459
465
  .action(async (frameworkName, clause, options) => {
@@ -463,6 +469,8 @@ export const createProgram = (io = createDefaultIO()) => {
463
469
  clause,
464
470
  itemType: readStringOption(options, 'type') ?? 'test',
465
471
  search: readStringOption(options, 'search'),
472
+ page: readNumberOption(options, 'page'),
473
+ limit: readNumberOption(options, 'limit'),
466
474
  apiUrl: readStringOption(options, 'apiUrl'),
467
475
  json: readBooleanOption(options, 'json'),
468
476
  });
@@ -523,10 +531,13 @@ export const createProgram = (io = createDefaultIO()) => {
523
531
  .command('list')
524
532
  .description('List assets with their code, status and classification')
525
533
  .option('--name <text>', 'filter by name')
526
- .option('--category <value>', 'filter by category')
534
+ .option('--category <csv>', 'filter by category')
535
+ .option('--sub-category <csv>', 'filter by sub-category')
527
536
  .option('--status <csv>', 'filter by status')
528
- .option('--business-impact <value>', 'filter by business impact')
529
- .option('--data-classification <value>', 'filter by data classification')
537
+ .option('--business-impact <csv>', 'filter by business impact')
538
+ .option('--data-classification <csv>', 'filter by data classification')
539
+ .option('--source <csv>', 'filter by discovery source (provider, e.g. aws, azure, gcp)')
540
+ .option('--technical-owner-email <email>', 'filter by technical owner email')
530
541
  .option('--page <n>', 'page number')
531
542
  .option('--limit <n>', 'page size')
532
543
  .option('--api-url <url>', 'override the API base URL for this command')
@@ -536,9 +547,12 @@ export const createProgram = (io = createDefaultIO()) => {
536
547
  io,
537
548
  name: readStringOption(options, 'name'),
538
549
  category: readStringOption(options, 'category'),
550
+ subCategory: readStringOption(options, 'subCategory'),
539
551
  status: readStringOption(options, 'status'),
540
552
  businessImpact: readStringOption(options, 'businessImpact'),
541
553
  dataClassification: readStringOption(options, 'dataClassification'),
554
+ source: readStringOption(options, 'source'),
555
+ technicalOwnerEmail: readStringOption(options, 'technicalOwnerEmail'),
542
556
  page: readNumberOption(options, 'page'),
543
557
  limit: readNumberOption(options, 'limit'),
544
558
  apiUrl: readStringOption(options, 'apiUrl'),
@@ -756,7 +770,10 @@ export const createProgram = (io = createDefaultIO()) => {
756
770
  .description('Create a vendor')
757
771
  .requiredOption('--name <name>', 'vendor name')
758
772
  .option('--category <value>', 'category')
773
+ .option('--subcategory <value>', 'sub-category')
759
774
  .option('--criticality <value>', 'criticality')
775
+ .option('--data-sensitivity <value>', 'data sensitivity')
776
+ .option('--service-availability <value>', 'service availability')
760
777
  .option('--description <text>', 'description')
761
778
  .option('--api-url <url>', 'override the API base URL for this command')
762
779
  .option('--json', 'output raw JSON')
@@ -765,7 +782,10 @@ export const createProgram = (io = createDefaultIO()) => {
765
782
  io,
766
783
  name: readStringOption(options, 'name') ?? '',
767
784
  category: readStringOption(options, 'category'),
785
+ subcategory: readStringOption(options, 'subcategory'),
768
786
  criticality: readStringOption(options, 'criticality'),
787
+ dataSensitivity: readStringOption(options, 'dataSensitivity'),
788
+ serviceAvailability: readStringOption(options, 'serviceAvailability'),
769
789
  description: readStringOption(options, 'description'),
770
790
  apiUrl: readStringOption(options, 'apiUrl'),
771
791
  json: readBooleanOption(options, 'json'),
@@ -836,6 +856,7 @@ export const createProgram = (io = createDefaultIO()) => {
836
856
  .option('--domain <value>', 'filter by control domain')
837
857
  .option('--status <csv>', 'filter by status')
838
858
  .option('--applicability <value>', 'filter by applicability (in_scope|out_of_scope)')
859
+ .option('--owner <csv>', 'filter by owner user id')
839
860
  .option('--page <n>', 'page number')
840
861
  .option('--limit <n>', 'page size')
841
862
  .option('--api-url <url>', 'override the API base URL for this command')
@@ -847,6 +868,7 @@ export const createProgram = (io = createDefaultIO()) => {
847
868
  domain: readStringOption(options, 'domain'),
848
869
  status: readStringOption(options, 'status'),
849
870
  applicability: readStringOption(options, 'applicability'),
871
+ ownerId: readStringOption(options, 'owner'),
850
872
  page: readNumberOption(options, 'page'),
851
873
  limit: readNumberOption(options, 'limit'),
852
874
  apiUrl: readStringOption(options, 'apiUrl'),
@@ -914,6 +936,7 @@ export const createProgram = (io = createDefaultIO()) => {
914
936
  .option('--justification <text>', 'why the applicability changed')
915
937
  .option('--design-requirements <state>', 'not_assessed | not_needed | not_met | partial | met')
916
938
  .option('--notes <text>', 'notes (Markdown)')
939
+ .option('--description <text>', 'description (custom controls only)')
917
940
  .option('--rename <text>', 'new name (custom controls only)')
918
941
  .option('--yes', 'apply without prompting')
919
942
  .option('--api-url <url>', 'override the API base URL for this command')
@@ -928,6 +951,7 @@ export const createProgram = (io = createDefaultIO()) => {
928
951
  ['justification', 'justification'],
929
952
  ['designRequirements', 'designRequirements'],
930
953
  ['notes', 'notes'],
954
+ ['description', 'description'],
931
955
  ['rename', 'name'],
932
956
  ]) {
933
957
  const value = readStringOption(options, flag);
@@ -6,12 +6,15 @@ export interface AssetCommonOptions {
6
6
  json?: boolean | undefined;
7
7
  credentialStore?: CredentialStore | undefined;
8
8
  }
9
- export declare const assetListCommand: ({ io, apiUrl, json, credentialStore, name, category, status, businessImpact, dataClassification, page, limit, }: AssetCommonOptions & {
9
+ export declare const assetListCommand: ({ io, apiUrl, json, credentialStore, name, category, subCategory, status, businessImpact, dataClassification, source, technicalOwnerEmail, page, limit, }: AssetCommonOptions & {
10
10
  name?: string | undefined;
11
11
  category?: string | undefined;
12
+ subCategory?: string | undefined;
12
13
  status?: string | undefined;
13
14
  businessImpact?: string | undefined;
14
15
  dataClassification?: string | undefined;
16
+ source?: string | undefined;
17
+ technicalOwnerEmail?: string | undefined;
15
18
  page?: number | undefined;
16
19
  limit?: number | undefined;
17
20
  }) => Promise<void>;
@@ -2,22 +2,29 @@ import { applyTool } from '../actions.js';
2
2
  import { writeLine, writeNextHint, writePageFooter, writeTable, clip } from '../io.js';
3
3
  import { asRecord, asText, callTool, errorMessage, resolveToolContext } from '../toolSurface.js';
4
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, }) => {
5
+ export const assetListCommand = async ({ io, apiUrl, json = false, credentialStore, name, category, subCategory, status, businessImpact, dataClassification, source, technicalOwnerEmail, page, limit, }) => {
6
6
  const ctx = await resolveToolContext(apiUrl, credentialStore);
7
+ const csv = (value) => value
8
+ .split(',')
9
+ .map((entry) => entry.trim())
10
+ .filter(Boolean);
7
11
  const input = {};
8
12
  if (name)
9
13
  input.name = name;
10
14
  if (category)
11
- input.category = [category];
15
+ input.category = csv(category);
16
+ if (subCategory)
17
+ input.subCategory = csv(subCategory);
12
18
  if (status)
13
- input.status = status
14
- .split(',')
15
- .map((value) => value.trim())
16
- .filter(Boolean);
19
+ input.status = csv(status);
17
20
  if (businessImpact)
18
- input.businessImpact = [businessImpact];
21
+ input.businessImpact = csv(businessImpact);
19
22
  if (dataClassification)
20
- input.dataClassification = [dataClassification];
23
+ input.dataClassification = csv(dataClassification);
24
+ if (source)
25
+ input.source = csv(source);
26
+ if (technicalOwnerEmail)
27
+ input.technicalOwnerEmail = technicalOwnerEmail;
21
28
  if (page)
22
29
  input.page = page;
23
30
  if (limit)
@@ -68,15 +68,17 @@ export const contextSetCommand = async ({ io, apiUrl, json = false, target, valu
68
68
  if (!questionId)
69
69
  throw new Error('A question id is required, for example business.company_size');
70
70
  const ctx = await resolveToolContext(apiUrl, credentialStore);
71
- const envelope = await callTool(ctx, 'set_context_answer', {
72
- store,
73
- questionId,
74
- value: parseValue(value),
71
+ const result = await applyTool({
72
+ ctx,
73
+ io,
74
+ toolName: 'set_context_answer',
75
+ input: { store, questionId, value: parseValue(value) },
76
+ fallback: 'Could not save the answer.',
75
77
  });
76
- if (envelope.status !== 'ok')
77
- throw new Error(errorMessage(envelope, 'Could not save the answer.'));
78
+ if (!result)
79
+ return;
78
80
  if (json) {
79
- writeLine(io.stdout, JSON.stringify(envelope.data, null, 2));
81
+ writeLine(io.stdout, JSON.stringify(result.data, null, 2));
80
82
  return;
81
83
  }
82
84
  writeLine(io.stdout, `${io.theme.green('✓')} ${store}.${questionId} saved`);
@@ -9,11 +9,12 @@ export interface ControlCommonOptions {
9
9
  export interface ControlReadOptions extends ControlCommonOptions {
10
10
  name: string;
11
11
  }
12
- export declare const controlListCommand: ({ io, apiUrl, json, credentialStore, search, domain, status, applicability, page, limit, }: ControlCommonOptions & {
12
+ export declare const controlListCommand: ({ io, apiUrl, json, credentialStore, search, domain, status, applicability, ownerId, page, limit, }: ControlCommonOptions & {
13
13
  search?: string | undefined;
14
14
  domain?: string | undefined;
15
15
  status?: string | undefined;
16
16
  applicability?: string | undefined;
17
+ ownerId?: string | undefined;
17
18
  page?: number | undefined;
18
19
  limit?: number | undefined;
19
20
  }) => Promise<void>;
@@ -2,7 +2,7 @@ import { applyTool } from '../actions.js';
2
2
  import { writeLine, writeNextHint, writePageFooter, writeTable } from '../io.js';
3
3
  import { asRecord, asText, callTool, errorMessage, resolveToolContext } from '../toolSurface.js';
4
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, }) => {
5
+ export const controlListCommand = async ({ io, apiUrl, json = false, credentialStore, search, domain, status, applicability, ownerId, page, limit, }) => {
6
6
  const ctx = await resolveToolContext(apiUrl, credentialStore);
7
7
  const input = {};
8
8
  if (search)
@@ -16,6 +16,11 @@ export const controlListCommand = async ({ io, apiUrl, json = false, credentialS
16
16
  .filter(Boolean);
17
17
  if (applicability)
18
18
  input.applicability = [applicability];
19
+ if (ownerId)
20
+ input.ownerId = ownerId
21
+ .split(',')
22
+ .map((value) => value.trim())
23
+ .filter(Boolean);
19
24
  if (page)
20
25
  input.page = page;
21
26
  if (limit)
@@ -145,6 +150,16 @@ export const controlAvailableItemsCommand = async ({ io, name, itemType, apiUrl,
145
150
  const record = asRecord(item);
146
151
  return [text(record.name, text(record.id)), text(record.status)];
147
152
  }));
153
+ const total = typeof data.total === 'number' ? data.total : items.length;
154
+ const currentPage = typeof data.page === 'number' ? data.page : 1;
155
+ const pageSize = typeof data.limit === 'number' ? data.limit : 25;
156
+ writePageFooter(io, {
157
+ shown: items.length,
158
+ total,
159
+ page: currentPage,
160
+ limit: pageSize,
161
+ hasMore: data.hasMore === true || currentPage * pageSize < total,
162
+ });
148
163
  };
149
164
  export const controlUpdateCommand = async ({ io, name, apiUrl, json = false, yes = false, confirm, credentialStore, changes, }) => {
150
165
  if (Object.keys(changes).length === 0) {
@@ -24,9 +24,11 @@ export declare const frameworkClausesCommand: ({ io, framework, apiUrl, json, cr
24
24
  }) => Promise<void>;
25
25
  export declare const clauseGetCommand: ({ io, framework, clause, apiUrl, json, credentialStore, }: ClauseTargetOptions) => Promise<void>;
26
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 & {
27
+ export declare const clauseAvailableItemsCommand: ({ io, framework, clause, itemType, apiUrl, json, credentialStore, search, page, limit, }: ClauseTargetOptions & {
28
28
  itemType: string;
29
29
  search?: string | undefined;
30
+ page?: number | undefined;
31
+ limit?: number | undefined;
30
32
  }) => Promise<void>;
31
33
  export declare const clauseUpdateCommand: ({ io, framework, clause, changes, apiUrl, json, yes, credentialStore, }: ClauseTargetOptions & {
32
34
  changes: Record<string, unknown>;
@@ -1,4 +1,4 @@
1
- import { writeLine, writeNextHint, writeTable } from '../io.js';
1
+ import { writeLine, writeNextHint, writePageFooter, writeTable } from '../io.js';
2
2
  import { asRecord, callTool, errorMessage, resolveToolContext, asText } from '../toolSurface.js';
3
3
  const text = (value, fallback = '-') => typeof value === 'string' ? value : typeof value === 'number' ? String(value) : fallback;
4
4
  export const frameworkListCommand = async ({ io, apiUrl, json = false, credentialStore, }) => {
@@ -118,11 +118,15 @@ export const clauseItemsCommand = async ({ io, framework, clause, apiUrl, json =
118
118
  return [text(record.name, text(record.id)), text(record.itemType ?? record.type)];
119
119
  }));
120
120
  };
121
- export const clauseAvailableItemsCommand = async ({ io, framework, clause, itemType, apiUrl, json = false, credentialStore, search, }) => {
121
+ export const clauseAvailableItemsCommand = async ({ io, framework, clause, itemType, apiUrl, json = false, credentialStore, search, page, limit, }) => {
122
122
  const ctx = await resolveToolContext(apiUrl, credentialStore);
123
123
  const input = { framework, clause, itemType };
124
124
  if (search)
125
125
  input.search = search;
126
+ if (page)
127
+ input.page = page;
128
+ if (limit)
129
+ input.limit = limit;
126
130
  const envelope = await callTool(ctx, 'list_clause_items_available', input);
127
131
  if (envelope.status !== 'ok')
128
132
  throw new Error(errorMessage(envelope, 'Could not read available items.'));
@@ -130,13 +134,24 @@ export const clauseAvailableItemsCommand = async ({ io, framework, clause, itemT
130
134
  writeLine(io.stdout, JSON.stringify(envelope.data, null, 2));
131
135
  return;
132
136
  }
133
- const items = asRecord(envelope.data).items;
137
+ const data = asRecord(envelope.data);
138
+ const items = data.items;
134
139
  const list = Array.isArray(items) ? items : [];
135
140
  writeLine(io.stdout, `${io.theme.bold(`Available ${itemType}s`)} ${io.theme.dim(`(${list.length})`)}`);
136
141
  for (const item of list) {
137
142
  const record = asRecord(item);
138
143
  writeLine(io.stdout, ` ${text(record.name, text(record.id))}`);
139
144
  }
145
+ const total = typeof data.total === 'number' ? data.total : list.length;
146
+ const currentPage = typeof data.page === 'number' ? data.page : 1;
147
+ const pageSize = typeof data.limit === 'number' ? data.limit : 25;
148
+ writePageFooter(io, {
149
+ shown: list.length,
150
+ total,
151
+ page: currentPage,
152
+ limit: pageSize,
153
+ hasMore: data.hasMore === true || currentPage * pageSize < total,
154
+ });
140
155
  };
141
156
  export const clauseUpdateCommand = async ({ io, framework, clause, changes, apiUrl, json = false, yes = false, credentialStore, }) => {
142
157
  const { applyTool } = await import('../actions.js');
@@ -20,7 +20,7 @@ export const policyListCommand = async ({ io, apiUrl, json = false, name, status
20
20
  if (envelope.status !== 'ok')
21
21
  throw new Error(errorMessage(envelope, 'Could not list documents.'));
22
22
  if (json) {
23
- writeLine(io.stdout, JSON.stringify(envelope, null, 2));
23
+ writeLine(io.stdout, JSON.stringify(envelope.data, null, 2));
24
24
  return;
25
25
  }
26
26
  const data = asRecord(envelope.data);
@@ -41,9 +41,11 @@ export interface TestAttachOptions extends TestReadOptions {
41
41
  unlink?: boolean | undefined;
42
42
  }
43
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 & {
44
+ export declare const testAvailableItemsCommand: ({ io, name, runId, itemType, apiUrl, json, credentialStore, search, page, limit, }: TestReadOptions & {
45
45
  itemType: string;
46
46
  search?: string | undefined;
47
+ page?: number | undefined;
48
+ limit?: number | undefined;
47
49
  }) => Promise<void>;
48
50
  export declare const testLinksCommand: ({ io, name, apiUrl, json, credentialStore, }: TestReadOptions) => Promise<void>;
49
51
  export declare const testUpdateCommand: ({ io, name, apiUrl, json, yes, confirm, credentialStore, changes, }: TestReadOptions & {
@@ -235,13 +235,17 @@ export const testAttachCommand = async ({ io, name, runId, apiUrl, json = false,
235
235
  const job = asText(data.aiTaskId);
236
236
  writeLine(io.stdout, `${io.theme.green('✓')} ${unlink ? 'unlinked' : 'linked'} ${resolvedItemType} on run ${text(data.runId)}${job ? io.theme.dim(` · job ${job}`) : ''}`);
237
237
  };
238
- export const testAvailableItemsCommand = async ({ io, name, runId, itemType, apiUrl, json = false, credentialStore, search, }) => {
238
+ export const testAvailableItemsCommand = async ({ io, name, runId, itemType, apiUrl, json = false, credentialStore, search, page, limit, }) => {
239
239
  const ctx = await resolveToolContext(apiUrl, credentialStore);
240
240
  const input = { name, itemType };
241
241
  if (runId)
242
242
  input.runId = runId;
243
243
  if (search)
244
244
  input.search = search;
245
+ if (page)
246
+ input.page = page;
247
+ if (limit)
248
+ input.limit = limit;
245
249
  const envelope = await callTool(ctx, 'list_test_items_available', input);
246
250
  if (envelope.status !== 'ok')
247
251
  throw new Error(errorMessage(envelope, 'Could not read available items.'));
@@ -264,6 +268,16 @@ export const testAvailableItemsCommand = async ({ io, name, runId, itemType, api
264
268
  text(record.status),
265
269
  ];
266
270
  }));
271
+ const total = typeof data.total === 'number' ? data.total : items.length;
272
+ const currentPage = typeof data.page === 'number' ? data.page : 1;
273
+ const pageSize = typeof data.limit === 'number' ? data.limit : 25;
274
+ writePageFooter(io, {
275
+ shown: items.length,
276
+ total,
277
+ page: currentPage,
278
+ limit: pageSize,
279
+ hasMore: data.hasMore === true || currentPage * pageSize < total,
280
+ });
267
281
  };
268
282
  export const testLinksCommand = async ({ io, name, apiUrl, json = false, credentialStore, }) => {
269
283
  const ctx = await resolveToolContext(apiUrl, credentialStore);
@@ -22,10 +22,13 @@ export declare const vendorListCommand: ({ io, apiUrl, json, credentialStore, na
22
22
  limit?: number | undefined;
23
23
  }) => Promise<void>;
24
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 & {
25
+ export declare const vendorCreateCommand: ({ io, apiUrl, json, credentialStore, name, category, subcategory, criticality, dataSensitivity, serviceAvailability, description, }: VendorCommonOptions & {
26
26
  name: string;
27
27
  category?: string | undefined;
28
+ subcategory?: string | undefined;
28
29
  criticality?: string | undefined;
30
+ dataSensitivity?: string | undefined;
31
+ serviceAvailability?: string | undefined;
29
32
  description?: string | undefined;
30
33
  }) => Promise<void>;
31
34
  export interface VendorUpdateOptions extends VendorReadOptions {
@@ -78,13 +78,19 @@ export const vendorGetCommand = async ({ io, name, apiUrl, json = false, credent
78
78
  writeLine(io.stdout, ` ${io.theme.dim(`${label}:`)} ${text(value)}`);
79
79
  }
80
80
  };
81
- export const vendorCreateCommand = async ({ io, apiUrl, json = false, credentialStore, name, category, criticality, description, }) => {
81
+ export const vendorCreateCommand = async ({ io, apiUrl, json = false, credentialStore, name, category, subcategory, criticality, dataSensitivity, serviceAvailability, description, }) => {
82
82
  const ctx = await resolveToolContext(apiUrl, credentialStore);
83
83
  const input = { name };
84
84
  if (category)
85
85
  input.category = category;
86
+ if (subcategory)
87
+ input.subcategory = subcategory;
86
88
  if (criticality)
87
89
  input.criticality = criticality;
90
+ if (dataSensitivity)
91
+ input.dataSensitivity = dataSensitivity;
92
+ if (serviceAvailability)
93
+ input.serviceAvailability = serviceAvailability;
88
94
  if (description)
89
95
  input.description = description;
90
96
  const result = await applyTool({
@@ -4,4 +4,4 @@
4
4
  */
5
5
  export declare const CLI_NAME = "ciphrix";
6
6
  export declare const CLI_TAGLINE = "Ciphrix compliance platform CLI";
7
- export declare const CLI_VERSION = "0.1.0";
7
+ export declare const CLI_VERSION = "0.1.1";
package/dist/constants.js CHANGED
@@ -4,4 +4,4 @@
4
4
  */
5
5
  export const CLI_NAME = 'ciphrix';
6
6
  export const CLI_TAGLINE = 'Ciphrix compliance platform CLI';
7
- export const CLI_VERSION = '0.1.0';
7
+ export const CLI_VERSION = '0.1.1';
@@ -17,4 +17,12 @@ export interface CallToolOptions {
17
17
  confirmationToken?: string | undefined;
18
18
  idempotencyKey?: string | undefined;
19
19
  }
20
+ /**
21
+ * The tool surface requires an idempotency key on every write and rejects a write without one. Writes
22
+ * must go through `applyTool`, which supplies the key and handles the preflight/confirmation contract.
23
+ * This set is the CLI-side mirror of the tool surface's write effects, so a write cannot be issued as a
24
+ * bare read-style call again. Keep it in step with the write tools the CLI exposes (ideally derived from
25
+ * the manifest once the CLI consumes it).
26
+ */
27
+ export declare const WRITE_TOOL_NAMES: ReadonlySet<string>;
20
28
  export declare const callTool: (ctx: ToolContext, toolName: string, input: unknown, options?: CallToolOptions) => Promise<Record<string, unknown>>;
@@ -34,7 +34,52 @@ export const resolveToolContext = async (apiUrl, store) => {
34
34
  throw new Error('Not signed in. Run `ciphrix login` first.');
35
35
  return { baseUrl, client: createApiClient(baseUrl), token: credential.token };
36
36
  };
37
+ /**
38
+ * The tool surface requires an idempotency key on every write and rejects a write without one. Writes
39
+ * must go through `applyTool`, which supplies the key and handles the preflight/confirmation contract.
40
+ * This set is the CLI-side mirror of the tool surface's write effects, so a write cannot be issued as a
41
+ * bare read-style call again. Keep it in step with the write tools the CLI exposes (ideally derived from
42
+ * the manifest once the CLI consumes it).
43
+ */
44
+ export const WRITE_TOOL_NAMES = new Set([
45
+ 'set_context_answer',
46
+ 'reset_context',
47
+ 'create_document',
48
+ 'edit_document_content',
49
+ 'create_document_version',
50
+ 'update_document',
51
+ 'submit_document',
52
+ 'approve_document',
53
+ 'delete_document',
54
+ 'create_test_run',
55
+ 'set_test_run_status',
56
+ 'delete_test_run',
57
+ 'attach_run_item',
58
+ 'update_test',
59
+ 'delete_test',
60
+ 'update_clause',
61
+ 'link_clause_item',
62
+ 'update_control',
63
+ 'link_control_item',
64
+ 'create_vendor',
65
+ 'update_vendor',
66
+ 'delete_vendor',
67
+ 'link_vendor_file',
68
+ 'create_risk',
69
+ 'update_risk',
70
+ 'delete_risk',
71
+ 'link_risk_control',
72
+ 'link_risk_file',
73
+ 'set_check_enabled',
74
+ 'create_asset',
75
+ 'update_asset',
76
+ 'delete_asset',
77
+ 'set_asset_tag',
78
+ ]);
37
79
  export const callTool = async (ctx, toolName, input, options = {}) => {
80
+ if (WRITE_TOOL_NAMES.has(toolName) && !options.idempotencyKey) {
81
+ throw new Error(`Refusing to call the write tool '${toolName}' without an idempotency key. Use applyTool.`);
82
+ }
38
83
  const body = { input };
39
84
  if (options.confirmationToken)
40
85
  body.confirmationToken = options.confirmationToken;
package/dist/upload.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { type ToolContext } from './toolSurface.js';
2
- /** Maximum evidence file size read and staged by the CLI (50 MiB). */
2
+ /** Maximum evidence file size read and staged by the CLI (25 MiB, matching the tool surface). */
3
3
  export declare const DEFAULT_MAX_UPLOAD_BYTES: number;
4
4
  export interface UploadLimits {
5
5
  maxUploadBytes?: number;
package/dist/upload.js CHANGED
@@ -19,8 +19,8 @@ const MIME_TYPES = {
19
19
  '.xls': 'application/vnd.ms-excel',
20
20
  '.xlsx': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
21
21
  };
22
- /** Maximum evidence file size read and staged by the CLI (50 MiB). */
23
- export const DEFAULT_MAX_UPLOAD_BYTES = 50 * 1024 * 1024;
22
+ /** Maximum evidence file size read and staged by the CLI (25 MiB, matching the tool surface). */
23
+ export const DEFAULT_MAX_UPLOAD_BYTES = 25 * 1024 * 1024;
24
24
  const readUploadFile = async (filePath, maxBytes) => {
25
25
  const initialStat = await lstat(filePath);
26
26
  if (initialStat.isSymbolicLink() || !initialStat.isFile()) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ciphrix/cli",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "Official command-line interface for the Ciphrix compliance platform.",
5
5
  "type": "module",
6
6
  "license": "Apache-2.0",
@@ -340,6 +340,8 @@ Options:
340
340
  --type <type> item type: native_doc or check
341
341
  --run <id> a specific run
342
342
  --search <text> filter by item name
343
+ --page <n> page number
344
+ --limit <n> page size
343
345
  --api-url <url> override the API base URL for this command
344
346
  --json output raw JSON
345
347
  -h, --help display help for command
@@ -485,6 +487,8 @@ List items that can be mapped to a clause
485
487
  Options:
486
488
  --type <type> item type: test or document
487
489
  --search <text> filter by item name
490
+ --page <n> page number
491
+ --limit <n> page size
488
492
  --api-url <url> override the API base URL for this command
489
493
  --json output raw JSON
490
494
  -h, --help display help for command
@@ -560,16 +564,20 @@ Usage: ciphrix asset list [options]
560
564
  List assets with their code, status and classification
561
565
 
562
566
  Options:
563
- --name <text> filter by name
564
- --category <value> filter by category
565
- --status <csv> filter by status
566
- --business-impact <value> filter by business impact
567
- --data-classification <value> filter by data classification
568
- --page <n> page number
569
- --limit <n> page size
570
- --api-url <url> override the API base URL for this command
571
- --json output raw JSON
572
- -h, --help display help for command
567
+ --name <text> filter by name
568
+ --category <csv> filter by category
569
+ --sub-category <csv> filter by sub-category
570
+ --status <csv> filter by status
571
+ --business-impact <csv> filter by business impact
572
+ --data-classification <csv> filter by data classification
573
+ --source <csv> filter by discovery source (provider, e.g.
574
+ aws, azure, gcp)
575
+ --technical-owner-email <email> filter by technical owner email
576
+ --page <n> page number
577
+ --limit <n> page size
578
+ --api-url <url> override the API base URL for this command
579
+ --json output raw JSON
580
+ -h, --help display help for command
573
581
  ```
574
582
 
575
583
  ## `ciphrix asset get`
@@ -766,13 +774,16 @@ Usage: ciphrix vendor create [options]
766
774
  Create a vendor
767
775
 
768
776
  Options:
769
- --name <name> vendor name
770
- --category <value> category
771
- --criticality <value> criticality
772
- --description <text> description
773
- --api-url <url> override the API base URL for this command
774
- --json output raw JSON
775
- -h, --help display help for command
777
+ --name <name> vendor name
778
+ --category <value> category
779
+ --subcategory <value> sub-category
780
+ --criticality <value> criticality
781
+ --data-sensitivity <value> data sensitivity
782
+ --service-availability <value> service availability
783
+ --description <text> description
784
+ --api-url <url> override the API base URL for this command
785
+ --json output raw JSON
786
+ -h, --help display help for command
776
787
  ```
777
788
 
778
789
  **Effect:** Creates a vendor; the CLI prompts if the API requests confirmation.
@@ -898,6 +909,7 @@ Options:
898
909
  --domain <value> filter by control domain
899
910
  --status <csv> filter by status
900
911
  --applicability <value> filter by applicability (in_scope|out_of_scope)
912
+ --owner <csv> filter by owner user id
901
913
  --page <n> page number
902
914
  --limit <n> page size
903
915
  --api-url <url> override the API base URL for this command
@@ -965,6 +977,7 @@ Options:
965
977
  --design-requirements <state> not_assessed | not_needed | not_met | partial |
966
978
  met
967
979
  --notes <text> notes (Markdown)
980
+ --description <text> description (custom controls only)
968
981
  --rename <text> new name (custom controls only)
969
982
  --yes apply without prompting
970
983
  --api-url <url> override the API base URL for this command