trooth 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 (3) hide show
  1. package/README.md +39 -0
  2. package/bin/trooth.mjs +134 -0
  3. package/package.json +38 -0
package/README.md ADDED
@@ -0,0 +1,39 @@
1
+ # trooth
2
+
3
+ Compliance, automated — from your terminal. Scan a Terraform plan against SOC 2, ISO 27001,
4
+ GDPR, HIPAA, NIST AI RMF, and the EU AI Act, and get a signed Compliance Delta back.
5
+
6
+ **Advisory and report-only. Trooth never applies changes to your infrastructure.**
7
+
8
+ ## Install / run
9
+
10
+ ```bash
11
+ # No install needed:
12
+ npx trooth scan ./plan.tfplan
13
+
14
+ # or generate JSON yourself first:
15
+ terraform show -json plan.tfplan > plan.json
16
+ npx trooth scan plan.json
17
+ ```
18
+
19
+ ## Commands
20
+
21
+ | Command | What it does |
22
+ | --- | --- |
23
+ | `trooth scan <plan>` | Posts your `terraform show -json` plan to Trooth Pre-Flight (`POST https://api.trooth.co/v1/preflight`) and prints the verdict, score, and findings. Accepts a JSON file or a binary `.tfplan` (it runs `terraform show -json` for you). |
24
+ | `trooth lint [path]` | Local, read-only IaC drift check. Never transmits your code. |
25
+ | `trooth --help` / `--version` | Help / version. |
26
+
27
+ ## Flags
28
+
29
+ - `--strict` — exit non-zero if there are findings (default: exit 0, advisory).
30
+ - `--json` — print the raw signed Compliance Delta.
31
+
32
+ ## Notes
33
+
34
+ - Only your **declared plan** is sent to the Pre-Flight API; nothing is written back, and
35
+ Trooth never touches live infrastructure.
36
+ - Set `TROOTH_API` to point at a different base URL (defaults to `https://api.trooth.co`).
37
+ - Requires Node 18+ (uses built-in `fetch`).
38
+
39
+ Trooth automates. Trooth never signs for you.
package/bin/trooth.mjs ADDED
@@ -0,0 +1,134 @@
1
+ #!/usr/bin/env node
2
+ // Trooth CLI — `trooth`
3
+ // Copyright (c) 2026 Trooth, LLC. All rights reserved.
4
+ //
5
+ // Commands:
6
+ // trooth scan <plan> Send a Terraform plan to Trooth Pre-Flight (advisory, report-only).
7
+ // <plan> may be a `terraform show -json` JSON file, OR a binary
8
+ // .tfplan (the CLI will run `terraform show -json` for you).
9
+ // trooth lint [path] Local, read-only IaC drift check (never transmits your code).
10
+ // trooth --help Show help. trooth --version Show version.
11
+ //
12
+ // Pre-Flight is ADVISORY and REPORT-ONLY. Trooth never applies changes to your
13
+ // infrastructure. The scan posts only your declared plan to the Pre-Flight API and
14
+ // returns a signed Compliance Delta. Trooth automates. Trooth never signs for you.
15
+
16
+ import { readFileSync, existsSync } from 'node:fs';
17
+ import { execFileSync } from 'node:child_process';
18
+ import { createRequire } from 'node:module';
19
+
20
+ const API = process.env.TROOTH_API || 'https://api.trooth.co';
21
+ const J='\x1b[32m', D='\x1b[2m', B='\x1b[1m', R='\x1b[31m', A='\x1b[33m', X='\x1b[0m';
22
+ const require = createRequire(import.meta.url);
23
+ let VERSION = '0.1.0';
24
+ try { VERSION = require('../package.json').version; } catch {}
25
+
26
+ const argv = process.argv.slice(2);
27
+ const cmd = argv[0];
28
+
29
+ function help() {
30
+ console.log(`
31
+ ${J}${B}trooth${X} ${D}v${VERSION} — compliance, automated (advisory, report-only)${X}
32
+
33
+ ${B}Usage${X}
34
+ trooth scan <plan> Scan a Terraform plan via Trooth Pre-Flight
35
+ trooth lint [path] Local read-only IaC drift check
36
+ trooth --help | --version
37
+
38
+ ${B}Examples${X}
39
+ terraform show -json plan.tfplan > plan.json && trooth scan plan.json
40
+ trooth scan ./plan.tfplan ${D}# runs \`terraform show -json\` for you${X}
41
+
42
+ ${B}Flags${X}
43
+ --strict Exit non-zero if findings exist (default: exit 0, advisory)
44
+ --json Print raw JSON response
45
+
46
+ ${D}Pre-Flight is advisory and report-only — Trooth never applies changes.
47
+ Trooth automates. Trooth never signs for you.${X}
48
+ `);
49
+ }
50
+
51
+ function loadPlan(file) {
52
+ if (!file) { console.error(`${R}error${X} missing <plan> argument. Try: trooth scan ./plan.json`); process.exit(2); }
53
+ if (!existsSync(file)) { console.error(`${R}error${X} file not found: ${file}`); process.exit(2); }
54
+ // Try to read as JSON (a `terraform show -json` document).
55
+ let raw = '';
56
+ try { raw = readFileSync(file, 'utf8'); } catch (e) { console.error(`${R}error${X} cannot read ${file}: ${e.message}`); process.exit(2); }
57
+ try { return JSON.parse(raw); } catch { /* not JSON — likely a binary .tfplan */ }
58
+ // Fall back: ask Terraform to render the plan as JSON.
59
+ try {
60
+ const out = execFileSync('terraform', ['show', '-json', file], { encoding: 'utf8', maxBuffer: 64 * 1024 * 1024 });
61
+ return JSON.parse(out);
62
+ } catch (e) {
63
+ console.error(`${R}error${X} ${file} is not JSON, and \`terraform show -json\` failed.`);
64
+ console.error(`${D} Generate JSON first: terraform show -json ${file} > plan.json → trooth scan plan.json${X}`);
65
+ process.exit(2);
66
+ }
67
+ }
68
+
69
+ async function scan() {
70
+ const file = argv.find((a, i) => i > 0 && !a.startsWith('--'));
71
+ const strict = argv.includes('--strict');
72
+ const asJson = argv.includes('--json');
73
+ const plan = loadPlan(file);
74
+
75
+ let res;
76
+ try {
77
+ res = await fetch(`${API}/v1/preflight`, {
78
+ method: 'POST',
79
+ headers: { 'content-type': 'application/json', 'user-agent': `trooth-cli/${VERSION}` },
80
+ body: JSON.stringify({ plan }),
81
+ });
82
+ } catch (e) {
83
+ console.error(`${R}error${X} could not reach Trooth Pre-Flight at ${API}: ${e.message}`);
84
+ process.exit(1);
85
+ }
86
+ if (!res.ok) {
87
+ const body = await res.text().catch(() => '');
88
+ console.error(`${R}error${X} Pre-Flight returned HTTP ${res.status}. ${body.slice(0, 300)}`);
89
+ process.exit(1);
90
+ }
91
+ const delta = await res.json();
92
+
93
+ if (asJson) { console.log(JSON.stringify(delta, null, 2)); process.exit(strict && (delta.summary?.fail > 0) ? 1 : 0); }
94
+
95
+ const score = delta.score ?? '–';
96
+ const verdict = (delta.verdict || 'reviewed').toUpperCase();
97
+ const s = delta.summary || {};
98
+ console.log(`\n${J}${B}Trooth Pre-Flight${X} ${D}// advisory · report-only //${X}`);
99
+ console.log(`Verdict: ${B}${verdict}${X} Score: ${B}${score}${typeof score === 'number' ? '/100' : ''}${X} ` +
100
+ `${J}pass ${s.pass ?? 0}${X} · ${A}fail ${s.fail ?? 0}${X} · ${D}n/a ${s.notApplicable ?? 0}${X}`);
101
+ const findings = delta.findings || [];
102
+ if (findings.length) {
103
+ console.log(`\n${B}${findings.length} finding(s):${X}\n`);
104
+ for (const f of findings.slice(0, 50)) {
105
+ const sev = (f.severity || '').toLowerCase();
106
+ const tag = sev === 'critical' || sev === 'high' ? `${R}● ${sev.toUpperCase()}${X}`
107
+ : sev === 'medium' ? `${A}● MEDIUM${X}` : `${D}● ${(sev||'LOW').toUpperCase()}${X}`;
108
+ console.log(` ${tag} ${B}${f.title || f.message || f.check || 'finding'}${X}`);
109
+ if (f.control || f.frameworks) console.log(` ${J}↳${X} ${f.control || (Array.isArray(f.frameworks) ? f.frameworks.join(', ') : f.frameworks)}`);
110
+ }
111
+ } else {
112
+ console.log(`\n${J}✓ No findings in the declared plan.${X}`);
113
+ }
114
+ console.log(`\n${D}${delta.disclaimer || 'Advisory. Analyzes declared infrastructure intent, not live production state. Trooth never applies changes.'}${X}\n`);
115
+
116
+ process.exit(strict && (s.fail > 0) ? 1 : 0); // advisory by default: exit 0
117
+ }
118
+
119
+ async function lint() {
120
+ // Local, read-only .tf drift check. Reuses the published @trooth/os linter if present,
121
+ // otherwise runs a built-in minimal ruleset. Never transmits your code.
122
+ const path = argv[1] && !argv[1].startsWith('--') ? argv[1] : '.';
123
+ const { spawnSync } = await import('node:child_process');
124
+ const r = spawnSync('npx', ['--yes', '@trooth/os', path], { stdio: 'inherit' });
125
+ process.exit(r.status ?? 0);
126
+ }
127
+
128
+ (async () => {
129
+ if (cmd === '--version' || cmd === '-v') { console.log(VERSION); return; }
130
+ if (!cmd || cmd === '--help' || cmd === '-h' || cmd === 'help') { help(); return; }
131
+ if (cmd === 'scan') return scan();
132
+ if (cmd === 'lint') return lint();
133
+ console.error(`${R}error${X} unknown command: ${cmd}\n`); help(); process.exit(2);
134
+ })();
package/package.json ADDED
@@ -0,0 +1,38 @@
1
+ {
2
+ "name": "trooth",
3
+ "version": "0.1.0",
4
+ "description": "Trooth CLI — scan your Terraform plan against SOC 2 / ISO 27001 / GDPR / HIPAA / NIST AI RMF / EU AI Act. Advisory and report-only. Trooth never applies changes.",
5
+ "type": "module",
6
+ "bin": {
7
+ "trooth": "./bin/trooth.mjs"
8
+ },
9
+ "files": [
10
+ "bin/",
11
+ "README.md"
12
+ ],
13
+ "engines": {
14
+ "node": ">=18"
15
+ },
16
+ "scripts": {
17
+ "scan": "node ./bin/trooth.mjs scan"
18
+ },
19
+ "license": "MIT",
20
+ "author": "Trooth, LLC",
21
+ "homepage": "https://trooth.co",
22
+ "repository": {
23
+ "type": "git",
24
+ "url": "https://github.com/trooth/trooth-cli.git"
25
+ },
26
+ "keywords": [
27
+ "compliance",
28
+ "soc2",
29
+ "iso27001",
30
+ "gdpr",
31
+ "hipaa",
32
+ "nist-ai-rmf",
33
+ "eu-ai-act",
34
+ "terraform",
35
+ "iac",
36
+ "trooth"
37
+ ]
38
+ }