@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
package/dist/theme.d.ts
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Colour handling for terminal output.
|
|
3
|
+
*
|
|
4
|
+
* Rules:
|
|
5
|
+
* - `NO_COLOR` (non-empty) disables colour, per https://no-color.org.
|
|
6
|
+
* - `FORCE_COLOR` (non-empty, not `0`) enables colour even when piped.
|
|
7
|
+
* - Otherwise colour is enabled only for an interactive TTY.
|
|
8
|
+
*
|
|
9
|
+
* Decoration must never leak into machine-readable output; callers gate that
|
|
10
|
+
* with the resolved `color` flag.
|
|
11
|
+
*/
|
|
12
|
+
export interface Theme {
|
|
13
|
+
readonly color: boolean;
|
|
14
|
+
bold(text: string): string;
|
|
15
|
+
dim(text: string): string;
|
|
16
|
+
cyan(text: string): string;
|
|
17
|
+
green(text: string): string;
|
|
18
|
+
red(text: string): string;
|
|
19
|
+
yellow(text: string): string;
|
|
20
|
+
grey(text: string): string;
|
|
21
|
+
}
|
|
22
|
+
export declare const colorEnabled: (stream?: Pick<NodeJS.WriteStream, "isTTY">, env?: NodeJS.ProcessEnv) => boolean;
|
|
23
|
+
export declare const createTheme: (color?: boolean) => Theme;
|
|
24
|
+
/** Restore only unguessable markers created by this module; forged marker-like text is discarded. */
|
|
25
|
+
export declare const renderThemeStyles: (text: string) => string;
|
package/dist/theme.js
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { randomUUID } from 'node:crypto';
|
|
2
|
+
const OPEN = '\uE000CXI';
|
|
3
|
+
const CLOSE = '\uE001';
|
|
4
|
+
const issuedMarkerIds = new Set();
|
|
5
|
+
const STYLE_CODES = {
|
|
6
|
+
'0': '\u001B[0m',
|
|
7
|
+
'1': '\u001B[1m',
|
|
8
|
+
'2': '\u001B[2m',
|
|
9
|
+
'31': '\u001B[31m',
|
|
10
|
+
'32': '\u001B[32m',
|
|
11
|
+
'33': '\u001B[33m',
|
|
12
|
+
'36': '\u001B[36m',
|
|
13
|
+
'90': '\u001B[90m',
|
|
14
|
+
};
|
|
15
|
+
const isSet = (value) => typeof value === 'string' && value !== '';
|
|
16
|
+
export const colorEnabled = (stream = process.stdout, env = process.env) => {
|
|
17
|
+
if (isSet(env.FORCE_COLOR) && env.FORCE_COLOR !== '0')
|
|
18
|
+
return true;
|
|
19
|
+
if (isSet(env.NO_COLOR))
|
|
20
|
+
return false;
|
|
21
|
+
return stream.isTTY === true;
|
|
22
|
+
};
|
|
23
|
+
const paint = (open, text, color, markerId) => color ? `${OPEN}${markerId}:${open}${CLOSE}${text}${OPEN}${markerId}:0${CLOSE}` : text;
|
|
24
|
+
export const createTheme = (color = colorEnabled()) => {
|
|
25
|
+
const markerId = randomUUID();
|
|
26
|
+
issuedMarkerIds.add(markerId);
|
|
27
|
+
return {
|
|
28
|
+
color,
|
|
29
|
+
bold: (text) => paint(1, text, color, markerId),
|
|
30
|
+
dim: (text) => paint(2, text, color, markerId),
|
|
31
|
+
cyan: (text) => paint(36, text, color, markerId),
|
|
32
|
+
green: (text) => paint(32, text, color, markerId),
|
|
33
|
+
red: (text) => paint(31, text, color, markerId),
|
|
34
|
+
yellow: (text) => paint(33, text, color, markerId),
|
|
35
|
+
grey: (text) => paint(90, text, color, markerId),
|
|
36
|
+
};
|
|
37
|
+
};
|
|
38
|
+
/** Restore only unguessable markers created by this module; forged marker-like text is discarded. */
|
|
39
|
+
export const renderThemeStyles = (text) => text.replace(/\uE000CXI([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}):(0|1|2|31|32|33|36|90)\uE001/gi, (marker, markerId, code) => issuedMarkerIds.has(markerId.toLowerCase()) ? (STYLE_CODES[code] ?? '') : '');
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { type ApiClient } from './api.js';
|
|
2
|
+
import { type CredentialStore } from './credentials.js';
|
|
3
|
+
export declare const asRecord: (value: unknown) => Record<string, unknown>;
|
|
4
|
+
export declare const asText: (value: unknown, fallback?: string) => string;
|
|
5
|
+
/**
|
|
6
|
+
* The message a caller should see. Ambiguous-name errors carry the candidates, so they are rendered here —
|
|
7
|
+
* otherwise the user is told "use a code" without being shown which codes exist.
|
|
8
|
+
*/
|
|
9
|
+
export declare const errorMessage: (envelope: Record<string, unknown>, fallback: string) => string;
|
|
10
|
+
export interface ToolContext {
|
|
11
|
+
baseUrl: string;
|
|
12
|
+
client: ApiClient;
|
|
13
|
+
token: string;
|
|
14
|
+
}
|
|
15
|
+
export declare const resolveToolContext: (apiUrl?: string, store?: CredentialStore) => Promise<ToolContext>;
|
|
16
|
+
export interface CallToolOptions {
|
|
17
|
+
confirmationToken?: string | undefined;
|
|
18
|
+
idempotencyKey?: string | undefined;
|
|
19
|
+
}
|
|
20
|
+
export declare const callTool: (ctx: ToolContext, toolName: string, input: unknown, options?: CallToolOptions) => Promise<Record<string, unknown>>;
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { createApiClient } from './api.js';
|
|
2
|
+
import { resolveApiBaseUrl } from './config.js';
|
|
3
|
+
import { createCredentialStore } from './credentials.js';
|
|
4
|
+
export const asRecord = (value) => value && typeof value === 'object' ? value : {};
|
|
5
|
+
export const asText = (value, fallback = '') => typeof value === 'string' ? value : fallback;
|
|
6
|
+
/**
|
|
7
|
+
* The message a caller should see. Ambiguous-name errors carry the candidates, so they are rendered here —
|
|
8
|
+
* otherwise the user is told "use a code" without being shown which codes exist.
|
|
9
|
+
*/
|
|
10
|
+
export const errorMessage = (envelope, fallback) => {
|
|
11
|
+
const error = asRecord(envelope.error);
|
|
12
|
+
const base = asText(error.message, fallback);
|
|
13
|
+
if (error.code === 'ambiguous' && Array.isArray(error.details)) {
|
|
14
|
+
const candidates = error.details
|
|
15
|
+
.map((item) => {
|
|
16
|
+
const record = asRecord(item);
|
|
17
|
+
const code = asText(record.code);
|
|
18
|
+
const name = asText(record.name);
|
|
19
|
+
if (code && name)
|
|
20
|
+
return `${code} ${name}`;
|
|
21
|
+
return code || name || asText(record.id);
|
|
22
|
+
})
|
|
23
|
+
.filter((value) => value !== '');
|
|
24
|
+
if (candidates.length > 0)
|
|
25
|
+
return `${base}\n ${candidates.join('\n ')}`;
|
|
26
|
+
}
|
|
27
|
+
return base;
|
|
28
|
+
};
|
|
29
|
+
export const resolveToolContext = async (apiUrl, store) => {
|
|
30
|
+
const baseUrl = resolveApiBaseUrl({ flag: apiUrl });
|
|
31
|
+
const credentialStore = store ?? (await createCredentialStore());
|
|
32
|
+
const credential = await credentialStore.get(baseUrl);
|
|
33
|
+
if (!credential)
|
|
34
|
+
throw new Error('Not signed in. Run `ciphrix login` first.');
|
|
35
|
+
return { baseUrl, client: createApiClient(baseUrl), token: credential.token };
|
|
36
|
+
};
|
|
37
|
+
export const callTool = async (ctx, toolName, input, options = {}) => {
|
|
38
|
+
const body = { input };
|
|
39
|
+
if (options.confirmationToken)
|
|
40
|
+
body.confirmationToken = options.confirmationToken;
|
|
41
|
+
if (options.idempotencyKey)
|
|
42
|
+
body.idempotencyKey = options.idempotencyKey;
|
|
43
|
+
const data = await ctx.client.request(`/tools/v1/${encodeURIComponent(toolName)}`, {
|
|
44
|
+
method: 'POST',
|
|
45
|
+
token: ctx.token,
|
|
46
|
+
body,
|
|
47
|
+
});
|
|
48
|
+
return asRecord(data);
|
|
49
|
+
};
|
package/dist/upload.d.ts
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { type ToolContext } from './toolSurface.js';
|
|
2
|
+
/** Maximum evidence file size read and staged by the CLI (50 MiB). */
|
|
3
|
+
export declare const DEFAULT_MAX_UPLOAD_BYTES: number;
|
|
4
|
+
export interface UploadLimits {
|
|
5
|
+
maxUploadBytes?: number;
|
|
6
|
+
timeoutMs?: number;
|
|
7
|
+
maxResponseBytes?: number;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Stages a local file through the `upload_file` tool and returns its upload id, ready to be attached
|
|
11
|
+
* to a target (a test run, a risk, a vendor). The file is sent as multipart to the tool surface with
|
|
12
|
+
* the bearer credential, so no cookie or CSRF token is involved.
|
|
13
|
+
*/
|
|
14
|
+
export declare const uploadLocalFile: (ctx: ToolContext, filePath: string, purpose: string, limits?: UploadLimits) => Promise<{
|
|
15
|
+
id: string;
|
|
16
|
+
filename: string;
|
|
17
|
+
size: number;
|
|
18
|
+
}>;
|
package/dist/upload.js
ADDED
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { randomUUID } from 'node:crypto';
|
|
2
|
+
import { constants } from 'node:fs';
|
|
3
|
+
import { lstat, open } from 'node:fs/promises';
|
|
4
|
+
import { basename } from 'node:path';
|
|
5
|
+
import { ApiError, DEFAULT_MAX_RESPONSE_BYTES, DEFAULT_REQUEST_TIMEOUT_MS, fetchWithoutRedirects, readLimitedText, withRequestTimeout, } from './api.js';
|
|
6
|
+
import { asRecord } from './toolSurface.js';
|
|
7
|
+
const MIME_TYPES = {
|
|
8
|
+
'.pdf': 'application/pdf',
|
|
9
|
+
'.png': 'image/png',
|
|
10
|
+
'.jpg': 'image/jpeg',
|
|
11
|
+
'.jpeg': 'image/jpeg',
|
|
12
|
+
'.gif': 'image/gif',
|
|
13
|
+
'.txt': 'text/plain',
|
|
14
|
+
'.md': 'text/markdown',
|
|
15
|
+
'.csv': 'text/csv',
|
|
16
|
+
'.json': 'application/json',
|
|
17
|
+
'.doc': 'application/msword',
|
|
18
|
+
'.docx': 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
|
|
19
|
+
'.xls': 'application/vnd.ms-excel',
|
|
20
|
+
'.xlsx': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
|
21
|
+
};
|
|
22
|
+
/** Maximum evidence file size read and staged by the CLI (50 MiB). */
|
|
23
|
+
export const DEFAULT_MAX_UPLOAD_BYTES = 50 * 1024 * 1024;
|
|
24
|
+
const readUploadFile = async (filePath, maxBytes) => {
|
|
25
|
+
const initialStat = await lstat(filePath);
|
|
26
|
+
if (initialStat.isSymbolicLink() || !initialStat.isFile()) {
|
|
27
|
+
throw new Error('Upload path must be a regular file, not a symlink or special file.');
|
|
28
|
+
}
|
|
29
|
+
if (initialStat.size > maxBytes) {
|
|
30
|
+
throw new Error(`Upload exceeds the ${maxBytes}-byte file size limit.`);
|
|
31
|
+
}
|
|
32
|
+
const noFollow = constants.O_NOFOLLOW ?? 0;
|
|
33
|
+
const file = await open(filePath, constants.O_RDONLY | noFollow);
|
|
34
|
+
try {
|
|
35
|
+
const openedStat = await file.stat();
|
|
36
|
+
if (!openedStat.isFile() ||
|
|
37
|
+
openedStat.dev !== initialStat.dev ||
|
|
38
|
+
openedStat.ino !== initialStat.ino) {
|
|
39
|
+
throw new Error('Upload path changed while it was being opened. Please retry.');
|
|
40
|
+
}
|
|
41
|
+
if (openedStat.size > maxBytes) {
|
|
42
|
+
throw new Error(`Upload exceeds the ${maxBytes}-byte file size limit.`);
|
|
43
|
+
}
|
|
44
|
+
const chunks = [];
|
|
45
|
+
let total = 0;
|
|
46
|
+
while (true) {
|
|
47
|
+
const remaining = maxBytes + 1 - total;
|
|
48
|
+
if (remaining <= 0)
|
|
49
|
+
throw new Error(`Upload exceeds the ${maxBytes}-byte file size limit.`);
|
|
50
|
+
const chunk = Buffer.allocUnsafe(Math.min(64 * 1024, remaining));
|
|
51
|
+
const { bytesRead } = await file.read(chunk, 0, chunk.length, null);
|
|
52
|
+
if (bytesRead === 0)
|
|
53
|
+
break;
|
|
54
|
+
total += bytesRead;
|
|
55
|
+
if (total > maxBytes)
|
|
56
|
+
throw new Error(`Upload exceeds the ${maxBytes}-byte file size limit.`);
|
|
57
|
+
chunks.push(chunk.subarray(0, bytesRead));
|
|
58
|
+
}
|
|
59
|
+
return Buffer.concat(chunks, total);
|
|
60
|
+
}
|
|
61
|
+
finally {
|
|
62
|
+
await file.close();
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
const contentTypeFor = (filename) => {
|
|
66
|
+
const dot = filename.lastIndexOf('.');
|
|
67
|
+
const extension = dot === -1 ? '' : filename.slice(dot).toLowerCase();
|
|
68
|
+
return MIME_TYPES[extension] ?? 'application/octet-stream';
|
|
69
|
+
};
|
|
70
|
+
/**
|
|
71
|
+
* Stages a local file through the `upload_file` tool and returns its upload id, ready to be attached
|
|
72
|
+
* to a target (a test run, a risk, a vendor). The file is sent as multipart to the tool surface with
|
|
73
|
+
* the bearer credential, so no cookie or CSRF token is involved.
|
|
74
|
+
*/
|
|
75
|
+
export const uploadLocalFile = async (ctx, filePath, purpose, limits = {}) => {
|
|
76
|
+
const filename = basename(filePath);
|
|
77
|
+
const contents = await readUploadFile(filePath, limits.maxUploadBytes ?? DEFAULT_MAX_UPLOAD_BYTES);
|
|
78
|
+
const form = new FormData();
|
|
79
|
+
form.append('purpose', purpose);
|
|
80
|
+
form.append('file', new Blob([new Uint8Array(contents)], { type: contentTypeFor(filename) }), filename);
|
|
81
|
+
return withRequestTimeout(limits.timeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS, async (signal) => {
|
|
82
|
+
const response = await fetchWithoutRedirects(`${ctx.baseUrl}/tools/v1/upload_file`, {
|
|
83
|
+
method: 'POST',
|
|
84
|
+
headers: { authorization: `Bearer ${ctx.token}`, 'idempotency-key': randomUUID() },
|
|
85
|
+
body: form,
|
|
86
|
+
signal,
|
|
87
|
+
});
|
|
88
|
+
const text = await readLimitedText(response, limits.maxResponseBytes ?? DEFAULT_MAX_RESPONSE_BYTES);
|
|
89
|
+
let data = null;
|
|
90
|
+
if (text) {
|
|
91
|
+
try {
|
|
92
|
+
data = JSON.parse(text);
|
|
93
|
+
}
|
|
94
|
+
catch {
|
|
95
|
+
data = text;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
const envelope = asRecord(data);
|
|
99
|
+
if (!response.ok || envelope.status !== 'ok') {
|
|
100
|
+
const message = typeof asRecord(envelope.error).message === 'string'
|
|
101
|
+
? asRecord(envelope.error).message
|
|
102
|
+
: `Upload failed with status ${response.status}`;
|
|
103
|
+
throw new ApiError(message, response.status);
|
|
104
|
+
}
|
|
105
|
+
const payload = asRecord(envelope.data);
|
|
106
|
+
const id = typeof payload.uploadId === 'string' ? payload.uploadId : null;
|
|
107
|
+
if (!id)
|
|
108
|
+
throw new ApiError('Upload did not return an id', 502);
|
|
109
|
+
return {
|
|
110
|
+
id,
|
|
111
|
+
filename: typeof payload.filename === 'string' ? payload.filename : filename,
|
|
112
|
+
size: typeof payload.size === 'number' ? payload.size : contents.length,
|
|
113
|
+
};
|
|
114
|
+
});
|
|
115
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@ciphrix/cli",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Official command-line interface for the Ciphrix compliance platform.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"license": "Apache-2.0",
|
|
7
|
+
"bin": {
|
|
8
|
+
"ciphrix": "dist/index.js"
|
|
9
|
+
},
|
|
10
|
+
"main": "dist/index.js",
|
|
11
|
+
"exports": {
|
|
12
|
+
".": "./dist/index.js"
|
|
13
|
+
},
|
|
14
|
+
"files": [
|
|
15
|
+
"dist",
|
|
16
|
+
"skills",
|
|
17
|
+
"README.md",
|
|
18
|
+
"LICENSE"
|
|
19
|
+
],
|
|
20
|
+
"engines": {
|
|
21
|
+
"node": ">=20.19.0"
|
|
22
|
+
},
|
|
23
|
+
"packageManager": "npm@11.19.1",
|
|
24
|
+
"scripts": {
|
|
25
|
+
"clean": "node scripts/clean.mjs",
|
|
26
|
+
"build": "npm run clean && tsc -p tsconfig.build.json",
|
|
27
|
+
"postbuild": "node scripts/set-bin-mode.mjs",
|
|
28
|
+
"ci": "npm run format:check && npm run lint && npm run typecheck && npm test && npm run build && npm run commands:check && npm run package:check",
|
|
29
|
+
"dev": "tsx src/index.ts",
|
|
30
|
+
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
31
|
+
"lint": "eslint .",
|
|
32
|
+
"lint:fix": "eslint . --fix",
|
|
33
|
+
"format": "prettier --write .",
|
|
34
|
+
"format:check": "prettier --check .",
|
|
35
|
+
"test": "vitest run",
|
|
36
|
+
"test:watch": "vitest",
|
|
37
|
+
"commands:generate": "npm run build && node scripts/generate-command-reference.mjs",
|
|
38
|
+
"commands:check": "node scripts/generate-command-reference.mjs --check",
|
|
39
|
+
"package:check": "node scripts/check-package-contents.mjs",
|
|
40
|
+
"prepack": "npm run build",
|
|
41
|
+
"audit:render": "node scripts/render-audit.mjs"
|
|
42
|
+
},
|
|
43
|
+
"repository": {
|
|
44
|
+
"type": "git",
|
|
45
|
+
"url": "git+https://github.com/CiphrixHQ/ciphrix-cli.git"
|
|
46
|
+
},
|
|
47
|
+
"bugs": {
|
|
48
|
+
"url": "https://github.com/CiphrixHQ/ciphrix-cli/issues"
|
|
49
|
+
},
|
|
50
|
+
"homepage": "https://github.com/CiphrixHQ/ciphrix-cli#readme",
|
|
51
|
+
"keywords": [
|
|
52
|
+
"ciphrix",
|
|
53
|
+
"compliance",
|
|
54
|
+
"cli",
|
|
55
|
+
"security"
|
|
56
|
+
],
|
|
57
|
+
"dependencies": {
|
|
58
|
+
"@napi-rs/keyring": "^2.1.0",
|
|
59
|
+
"commander": "^15.0.0"
|
|
60
|
+
},
|
|
61
|
+
"devDependencies": {
|
|
62
|
+
"@eslint/js": "^10.0.1",
|
|
63
|
+
"@types/node": "^26.6.2",
|
|
64
|
+
"eslint": "^10.11.0",
|
|
65
|
+
"prettier": "^3.9.8",
|
|
66
|
+
"tsx": "^4.23.15",
|
|
67
|
+
"typescript": "^5.9.3",
|
|
68
|
+
"typescript-eslint": "^8.70.1",
|
|
69
|
+
"vitest": "^5.0.1"
|
|
70
|
+
}
|
|
71
|
+
}
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: ciphrix
|
|
3
|
+
description: Operate a Ciphrix compliance programme with the `ciphrix` CLI. Use when an agent needs to understand or change a tenant's business context, documents, frameworks, controls, tests and evidence, risks, vendors, assets, monitoring checks, or background jobs.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Ciphrix
|
|
7
|
+
|
|
8
|
+
Ciphrix is an AI-native compliance automation platform. A tenant's compliance programme lives in one
|
|
9
|
+
place: how the organization operates, the documents it publishes, the frameworks it has adopted, the
|
|
10
|
+
tests those frameworks impose, the evidence gathered against those tests, and the risks, vendors, assets
|
|
11
|
+
and monitoring checks around them. AI does the heavy lifting — generating drafts, reading evidence,
|
|
12
|
+
judging relevance — but every formal decision stays with a human.
|
|
13
|
+
|
|
14
|
+
You are operating that programme through `ciphrix`. You resolve things by **name or code**, you act on
|
|
15
|
+
**intents**, and you report what actually happened — including when nothing did.
|
|
16
|
+
|
|
17
|
+
## Operating stance
|
|
18
|
+
|
|
19
|
+
Ciphrix treats compliance as a living model of how an organization operates, not a checklist assembled
|
|
20
|
+
for an audit. Context explains the organization; documents state how it intends to operate; frameworks,
|
|
21
|
+
clauses and controls express obligations and design; tests ask whether those claims hold; evidence and
|
|
22
|
+
monitoring show what actually happened; risks, vendors and assets connect that assurance to the real
|
|
23
|
+
business. Preserve those relationships when you work.
|
|
24
|
+
|
|
25
|
+
Apply these principles:
|
|
26
|
+
|
|
27
|
+
- **Reality before paperwork.** Start from the organization's actual context and current records. Do not
|
|
28
|
+
manufacture a polished compliance story that is unsupported by the system.
|
|
29
|
+
- **Evidence before assertion.** Treat evidence, monitoring results and recorded decisions as distinct
|
|
30
|
+
facts. A document saying that a control exists is not proof that it operated, and an AI assessment is
|
|
31
|
+
not a human decision.
|
|
32
|
+
- **Assistance without invented authority.** Help people understand, draft, organise and execute their
|
|
33
|
+
intent. Do not approve, attest, accept risk, record a test result or make another formal decision unless
|
|
34
|
+
the user clearly asked for that decision.
|
|
35
|
+
- **The API is the source of truth.** Never infer access, state or success from expectations. Report the
|
|
36
|
+
result returned by Ciphrix, preserve distinctions such as `not found`, `not permitted`, `pending` and
|
|
37
|
+
`not assessed`, and never work around authorization.
|
|
38
|
+
- **Small, attributable changes.** Read the relevant record before changing it, update only the requested
|
|
39
|
+
fields, and report the resulting state. Do not turn a narrow request into broad programme cleanup.
|
|
40
|
+
- **Human-readable by default, structured when needed.** Use normal output when working with a person.
|
|
41
|
+
Use `--json` when exact fields, pagination or reliable machine processing matter; do not scrape
|
|
42
|
+
decorated terminal output.
|
|
43
|
+
|
|
44
|
+
## How the platform is organised
|
|
45
|
+
|
|
46
|
+
Understanding these relationships is most of the job:
|
|
47
|
+
|
|
48
|
+
- **Context** is the foundation. _Business Context_ describes how the organization actually operates;
|
|
49
|
+
_Operating Context_ scopes the document and compliance programme. Everything else — discovery, document
|
|
50
|
+
generation, risk and vendor suggestions, tests — is grounded in this. Answers merge over time; a new
|
|
51
|
+
answer that conflicts with an existing one goes to review instead of silently overwriting.
|
|
52
|
+
- **Frameworks** (SOC 2, ISO 27001, …) are applied to the tenant and bring their **clauses** and
|
|
53
|
+
**tests** with them.
|
|
54
|
+
- A **test** is a single requirement check. Each test has **runs**, one per month — the server enforces
|
|
55
|
+
that cadence. A run collects **evidence items**: uploaded files, native documents, or results from
|
|
56
|
+
monitoring **checks**. Attaching evidence starts an AI pipeline that digests the item, judges its
|
|
57
|
+
**relevance** to the test, and produces an **assurance** view. A human then records the **run
|
|
58
|
+
result** (`passing`, `failing`, `skipped`, or back to `pending`), which is a formal decision.
|
|
59
|
+
- **Documents** (the Document Library) have a lifecycle: `draft` → review → approval → `approved`. Only a
|
|
60
|
+
draft is editable; changing an approved document means creating a new version.
|
|
61
|
+
- **Files** are uploads and the file system. Keep the two words apart: the Document Library holds design
|
|
62
|
+
documents; Files holds uploaded files.
|
|
63
|
+
- **Risks**, **vendors** and **assets** are registers with their own state (status, owner, criticality,
|
|
64
|
+
review status, classification). Each has a stable code, and state changes are auditable. A risk also
|
|
65
|
+
carries a **treatment** (strategy and notes) and **scores**; an asset carries **tags** and is mapped to
|
|
66
|
+
controls, risks, tests and checks.
|
|
67
|
+
- **Clauses** and **controls** are what you are assessed against. A clause belongs to a framework and
|
|
68
|
+
carries an applicability and a design-requirement assessment; a control is the tenant's own layer with
|
|
69
|
+
a status, an owner, an applicability and design requirements.
|
|
70
|
+
- **Monitoring checks** run continuously against connected integrations and produce results on their own.
|
|
71
|
+
A check can be turned on or off for the tenant, and its run history and linked findings are readable.
|
|
72
|
+
|
|
73
|
+
Two consequences worth internalising:
|
|
74
|
+
|
|
75
|
+
- **AI output is never a verdict.** An assessment that hasn't been evaluated reads as _not assessed_ —
|
|
76
|
+
never a guess. Honest absence beats a plausible answer.
|
|
77
|
+
- **Not found ≠ no access ≠ not assessed.** These are different facts and are reported differently.
|
|
78
|
+
Do not collapse them.
|
|
79
|
+
|
|
80
|
+
## How to operate
|
|
81
|
+
|
|
82
|
+
- **Names or codes, not ids.** Documents, tests, controls, clauses, risks, vendors, checks and assets are
|
|
83
|
+
addressed by their human name **or** their stable code (for example `ACME-DOC-A1B2`, `TSQ-001`,
|
|
84
|
+
`ACME-RSK-CQJ4`, `ACME-AST-M5SD`). Ids work but are never required, and you never invent one. Prefer the
|
|
85
|
+
code when a name is ambiguous.
|
|
86
|
+
- **Intents, not steps.** Ask for the outcome; don't orchestrate the underlying calls. One user-facing
|
|
87
|
+
request may be several platform operations — that composition is the CLI's job, not the user's.
|
|
88
|
+
- **Ask only for decisions.** Only actions that change something a person should review stop for
|
|
89
|
+
confirmation — deleting, editing content, linking, or changing a control, clause, test or asset. A
|
|
90
|
+
formal decision is its own verb (`approve`, `submit`, `test run result`) and applies directly. Everything
|
|
91
|
+
else just happens. Use `--yes` only when the user has already authorised that exact change; it removes
|
|
92
|
+
an interactive prompt, not the need for authority.
|
|
93
|
+
- **Async work returns a job id.** Attaching evidence or starting AI work returns a job id immediately and
|
|
94
|
+
does not block; report the id and let the user track it with `ciphrix job status`.
|
|
95
|
+
- **Rich text is Markdown.** Document content and descriptions come back as Markdown, and you supply
|
|
96
|
+
Markdown.
|
|
97
|
+
- **Lists paginate.** You'll see `showing 1–25 of N · next: --page 2`; page through rather than assuming
|
|
98
|
+
you have everything.
|
|
99
|
+
- **`--json`** gives the raw payload when you need exact fields.
|
|
100
|
+
|
|
101
|
+
## Patterns
|
|
102
|
+
|
|
103
|
+
These are the shapes of the work; run `ciphrix <resource> --help` for the exact flags.
|
|
104
|
+
|
|
105
|
+
**Answer "what does the platform know about us?"** Start with context: read Business and Operating
|
|
106
|
+
Context, and check what's still unanswered. This is the usual first move when a request is broad, because
|
|
107
|
+
everything downstream depends on it.
|
|
108
|
+
|
|
109
|
+
**Change a document.** Read it, decide whether the change is **content** or **metadata**, and act
|
|
110
|
+
accordingly. If the document is approved, you must open a new version first — editing an approved document
|
|
111
|
+
is refused by design. Publishing and approving are formal, separate steps; don't fold them into the edit.
|
|
112
|
+
When drafting content, preserve the user's intended policy position and clearly separate current practice
|
|
113
|
+
from proposals or future commitments.
|
|
114
|
+
|
|
115
|
+
**Attach evidence to a test.** Find the test, then attach in one step with a local file
|
|
116
|
+
(`test attach "<test>" --file <path>`). The command stages the file and attaches it; you do not need to
|
|
117
|
+
stage and attach separately. Only stage a file on its own when you intend to attach it later or reuse it
|
|
118
|
+
across several items. After attaching, report the returned job id and point at `ciphrix job status` — the
|
|
119
|
+
relevance judgement is not instant.
|
|
120
|
+
|
|
121
|
+
**See what you're assessed against.** A framework holds **clauses**; each clause carries an applicability
|
|
122
|
+
and a design-requirement assessment and is mapped to tests and documents. Read the clauses, then the one
|
|
123
|
+
that matters. **Controls** are the tenant's own layer over that: `control list` and `control get` show the
|
|
124
|
+
status, owner, applicability and design requirements, and `control items` shows what is mapped to it.
|
|
125
|
+
Ownership, applicability, design requirements and notes are yours to change; a control's or test's name and
|
|
126
|
+
description belong to the catalogue and are refused for system records (use `--rename` only on custom ones).
|
|
127
|
+
|
|
128
|
+
**Record a result.** A run result is a decision, not a note. `pending` is not "failed" — it reopens the
|
|
129
|
+
run for more evidence. Use it deliberately.
|
|
130
|
+
|
|
131
|
+
**Triage a risk, vendor or asset.** Read the current record first, then update only the fields you're
|
|
132
|
+
changing. Report which fields applied and which were refused; don't assume success. A risk's treatment and
|
|
133
|
+
scores are their own reads; a vendor's or risk's files are listed and changed separately from the record
|
|
134
|
+
itself.
|
|
135
|
+
|
|
136
|
+
**Work the checks.** `check list` shows status and compliance; `check runs` is the history; `check findings`
|
|
137
|
+
are what a check raised. `check disable` silences a check for the tenant — record why with `--notes`.
|
|
138
|
+
|
|
139
|
+
**Check on background work.** Anything that returns a job id is polled, not awaited.
|
|
140
|
+
|
|
141
|
+
## Guardrails
|
|
142
|
+
|
|
143
|
+
- Work only in the tenant and API environment the authenticated user selected. Never discover or switch
|
|
144
|
+
tenants, endpoints or credential stores by guessing.
|
|
145
|
+
- Do not expose access tokens, stored credentials or sensitive response data in prose, logs or commands.
|
|
146
|
+
Prefer the OS keychain. File-based credential storage and plaintext HTTP are explicit local/testing
|
|
147
|
+
opt-ins, never defaults.
|
|
148
|
+
- Do not retry denied actions through another command, weaken authorization, or reinterpret an access
|
|
149
|
+
failure as a missing record.
|
|
150
|
+
- Don't edit an approved document; create a new version.
|
|
151
|
+
- Don't treat `pending` as a failure or as "done".
|
|
152
|
+
- Don't invent AI assessments. If it isn't evaluated, say so.
|
|
153
|
+
- Don't guess when a name is ambiguous — the CLI lists the candidates and stops; take that as your answer
|
|
154
|
+
and ask the user.
|
|
155
|
+
- Don't stage a file and then attach it when a single attach-with-file command exists.
|
|
156
|
+
- Before a consequential write, make sure the target resolved to the intended record. After the write,
|
|
157
|
+
report the server's outcome rather than claiming success from the command exit alone.
|
|
158
|
+
|
|
159
|
+
## Finding exact syntax
|
|
160
|
+
|
|
161
|
+
The command surface is discoverable and always current — don't memorise or infer it:
|
|
162
|
+
|
|
163
|
+
- `ciphrix --help` — the resources and verbs.
|
|
164
|
+
- `ciphrix <resource> --help` — flags for a resource.
|
|
165
|
+
- `ciphrix <resource> <verb> --help` — exact arguments and write semantics.
|
|
166
|
+
- [`references/commands.md`](references/commands.md) — generated command and option reference for offline discovery; live help from the installed CLI remains authoritative.
|
|
167
|
+
|
|
168
|
+
If the CLI does not expose the requested capability, say so. Do not call private endpoints, reproduce
|
|
169
|
+
backend logic, or invent a command from this guide.
|