trooth 0.1.0 → 0.2.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 +59 -11
  2. package/bin/trooth.mjs +189 -16
  3. package/package.json +11 -5
package/README.md CHANGED
@@ -1,39 +1,87 @@
1
1
  # trooth
2
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.
3
+ The trust network your terminal can check. Read any company's **witnessed** trust
4
+ standing from the [Trooth Network](https://trooth.co). Identity, security, privacy,
5
+ AI practices, and more, each witnessed, signed, and dated. It also scans your own Terraform
6
+ plans against SOC 2, ISO 27001, GDPR, HIPAA, NIST AI RMF, and the EU AI Act.
5
7
 
6
- **Advisory and report-only. Trooth never applies changes to your infrastructure.**
8
+ **`check` reads only public, already-published records. No key, no account.**
9
+ **`scan` is advisory and report-only. Trooth never applies changes to your infrastructure.**
7
10
 
8
11
  ## Install / run
9
12
 
10
13
  ```bash
11
14
  # No install needed:
12
- npx trooth scan ./plan.tfplan
15
+ npx trooth check stripe.com
13
16
 
14
- # or generate JSON yourself first:
15
- terraform show -json plan.tfplan > plan.json
16
- npx trooth scan plan.json
17
+ # or install it:
18
+ npm i -g trooth
19
+ trooth check trooth.co
17
20
  ```
18
21
 
19
22
  ## Commands
20
23
 
21
24
  | Command | What it does |
22
25
  | --- | --- |
26
+ | `trooth check <domain>` | Reads a company's witnessed standing from the live Trooth Network (`GET https://api.trooth.co/directory/api/vendors`) and prints the score, the per-discipline breakdown, live probes, attestations, and the badge / scan IDs you can verify. Returns an honest "not listed yet" when a company has no published standing. |
23
27
  | `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). |
28
+ | `trooth eu ...` | Pushes EU AI Act evidence (datasets, evals, drift) into Trooth from CI/CD. Needs a session token or company id. |
24
29
  | `trooth lint [path]` | Local, read-only IaC drift check. Never transmits your code. |
25
30
  | `trooth --help` / `--version` | Help / version. |
26
31
 
32
+ ## Examples
33
+
34
+ ```bash
35
+ trooth check trooth.co
36
+ ```
37
+
38
+ ```
39
+ Trooth Network // witnessed · public · read-only //
40
+ Trooth, LLC trooth.co
41
+ Standing: 91/100 Listed witnessed 2026-08-30
42
+
43
+ By discipline
44
+ Legal ██████████ 20/20
45
+ Security ████████░░ 16/20
46
+ AI governance ██████████ 19/20
47
+ Business █████████░ 18/20
48
+ IP █████████░ 18/20
49
+
50
+ Live probes 64/65 Attestations 27/35
51
+ Badge bronze_rw_... Scan rw_...
52
+
53
+ A witnessed, point-in-time reading of public evidence. Not a certification.
54
+ ```
55
+
56
+ ```bash
57
+ # For scripting: raw JSON, exit code 1 when a company is not listed:
58
+ trooth check acme.com --json
59
+ ```
60
+
27
61
  ## Flags
28
62
 
29
- - `--strict` — exit non-zero if there are findings (default: exit 0, advisory).
30
- - `--json` — print the raw signed Compliance Delta.
63
+ - `--json`: print the raw JSON record instead of the formatted view.
64
+ - `--strict`: scan only, exit non-zero if there are findings (default: exit 0, advisory).
31
65
 
32
66
  ## Notes
33
67
 
34
- - Only your **declared plan** is sent to the Pre-Flight API; nothing is written back, and
35
- Trooth never touches live infrastructure.
68
+ - `check` reads only the **public** witness directory. There is no key and no account, and
69
+ it never sends anything about you. It is a read.
70
+ - A "not listed" result is not a judgement. It means no witnessed standing has been
71
+ published for that domain yet. A company gets witnessed by listing at
72
+ <https://trooth.co/get-started>.
73
+ - `scan` sends only your **declared plan** to the Pre-Flight API; nothing is written back,
74
+ and Trooth never touches live infrastructure.
36
75
  - Set `TROOTH_API` to point at a different base URL (defaults to `https://api.trooth.co`).
37
76
  - Requires Node 18+ (uses built-in `fetch`).
38
77
 
78
+ ## Use it from an AI assistant
79
+
80
+ The same public network powers Trooth's read-only MCP server, so ChatGPT, Claude, Cursor,
81
+ or any MCP client can ask about a company in plain words:
82
+
83
+ ```
84
+ https://api.trooth.co/public/mcp
85
+ ```
86
+
39
87
  Trooth automates. Trooth never signs for you.
package/bin/trooth.mjs CHANGED
@@ -1,26 +1,29 @@
1
1
  #!/usr/bin/env node
2
- // Trooth CLI — `trooth`
2
+ // Trooth CLI. Command name: `trooth`
3
3
  // Copyright (c) 2026 Trooth, LLC. All rights reserved.
4
4
  //
5
5
  // Commands:
6
+ // trooth check <domain> Read a company's witnessed trust standing from the
7
+ // public Trooth Network (read-only, no key, no account).
6
8
  // trooth scan <plan> Send a Terraform plan to Trooth Pre-Flight (advisory, report-only).
7
9
  // <plan> may be a `terraform show -json` JSON file, OR a binary
8
10
  // .tfplan (the CLI will run `terraform show -json` for you).
11
+ // trooth eu ... Push EU AI Act evidence into Trooth from CI/CD (needs a token).
9
12
  // trooth lint [path] Local, read-only IaC drift check (never transmits your code).
10
13
  // trooth --help Show help. trooth --version Show version.
11
14
  //
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
+ // `check` reads only PUBLIC, already-published witness records. `scan` is ADVISORY and
16
+ // REPORT-ONLY: Trooth never applies changes to your infrastructure, and the scan posts
17
+ // only your declared plan. Trooth automates. Trooth never signs for you.
15
18
 
16
19
  import { readFileSync, existsSync } from 'node:fs';
17
20
  import { execFileSync } from 'node:child_process';
18
21
  import { createRequire } from 'node:module';
19
22
 
20
23
  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';
24
+ const J='\x1b[32m', D='\x1b[2m', B='\x1b[1m', R='\x1b[31m', A='\x1b[33m', C='\x1b[36m', X='\x1b[0m';
22
25
  const require = createRequire(import.meta.url);
23
- let VERSION = '0.1.0';
26
+ let VERSION = '0.2.0';
24
27
  try { VERSION = require('../package.json').version; } catch {}
25
28
 
26
29
  const argv = process.argv.slice(2);
@@ -28,40 +31,182 @@ const cmd = argv[0];
28
31
 
29
32
  function help() {
30
33
  console.log(`
31
- ${J}${B}trooth${X} ${D}v${VERSION} — compliance, automated (advisory, report-only)${X}
34
+ ${J}${B}trooth${X} ${D}v${VERSION} · the trust network your terminal can check${X}
32
35
 
33
36
  ${B}Usage${X}
34
- trooth scan <plan> Scan a Terraform plan via Trooth Pre-Flight
37
+ trooth check <domain> Read a company's witnessed standing on the Trooth Network
38
+ trooth scan <plan> Scan a Terraform plan via Trooth Pre-Flight (advisory)
35
39
  trooth lint [path] Local read-only IaC drift check
36
40
  trooth --help | --version
37
41
 
38
42
  ${B}Examples${X}
43
+ trooth check stripe.com ${D}# read a company's witnessed record${X}
44
+ trooth check trooth.co --json ${D}# raw JSON, for scripting${X}
39
45
  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
46
 
42
47
  ${B}Flags${X}
43
- --strict Exit non-zero if findings exist (default: exit 0, advisory)
44
- --json Print raw JSON response
48
+ --json Print raw JSON instead of the formatted view
49
+ --strict (scan) exit non-zero if findings exist (default: advisory, exit 0)
45
50
 
46
- ${D}Pre-Flight is advisory and report-only — Trooth never applies changes.
47
- Trooth automates. Trooth never signs for you.${X}
51
+ ${D}check reads only public, already-published witness records. No key, no account.
52
+ scan is advisory and report-only. Trooth automates. Trooth never signs for you.${X}
48
53
  `);
49
54
  }
50
55
 
56
+ /* --------------------------------------------------------------- check ---- */
57
+
58
+ /** Normalize a domain the way the Trooth Network does: strip scheme, path,
59
+ * leading www, case, and trailing slash. */
60
+ function normalizeDomain(input) {
61
+ if (!input) return '';
62
+ let s = String(input).trim().toLowerCase();
63
+ s = s.replace(/^[a-z]+:\/\//, ''); // scheme
64
+ s = s.replace(/\/.*$/, ''); // path/query/hash
65
+ s = s.replace(/^www\./, ''); // leading www
66
+ s = s.replace(/\.$/, ''); // trailing dot
67
+ return s;
68
+ }
69
+
70
+ const CATEGORY_LABELS = {
71
+ legal: 'Legal',
72
+ security: 'Security',
73
+ ai: 'AI governance',
74
+ business: 'Business',
75
+ ip: 'IP',
76
+ };
77
+
78
+ /** Pull a 0..100 score out of the feed's score field (object or number). */
79
+ function scorePct(score) {
80
+ if (score == null) return null;
81
+ if (typeof score === 'number') return Math.round(score);
82
+ if (typeof score === 'object') {
83
+ if (typeof score.rate === 'number') return Math.round(score.rate * 100);
84
+ if (typeof score.passed === 'number' && typeof score.total === 'number' && score.total > 0)
85
+ return Math.round((score.passed / score.total) * 100);
86
+ }
87
+ return null;
88
+ }
89
+
90
+ function bar(passed, total) {
91
+ if (!total) return '';
92
+ const filled = Math.round((passed / total) * 10);
93
+ return '█'.repeat(filled) + '░'.repeat(10 - filled);
94
+ }
95
+
96
+ function fmtDate(iso) {
97
+ if (!iso) return '';
98
+ const d = new Date(iso);
99
+ if (isNaN(d)) return String(iso);
100
+ return d.toISOString().slice(0, 10);
101
+ }
102
+
103
+ async function check() {
104
+ const domainArg = argv.find((a, i) => i > 0 && !a.startsWith('--'));
105
+ const asJson = argv.includes('--json');
106
+ const domain = normalizeDomain(domainArg);
107
+ if (!domain) {
108
+ console.error(`${R}error${X} missing <domain>. Try: trooth check stripe.com`);
109
+ process.exit(2);
110
+ }
111
+
112
+ let res;
113
+ try {
114
+ res = await fetch(`${API}/directory/api/vendors`, {
115
+ headers: { accept: 'application/json', 'user-agent': `trooth-cli/${VERSION}` },
116
+ });
117
+ } catch (e) {
118
+ console.error(`${R}error${X} could not reach the Trooth Network at ${API}: ${e.message}`);
119
+ process.exit(1);
120
+ }
121
+ if (!res.ok) {
122
+ console.error(`${R}error${X} the Trooth Network returned HTTP ${res.status}.`);
123
+ process.exit(1);
124
+ }
125
+ let data;
126
+ try { data = await res.json(); } catch { data = {}; }
127
+ const vendors = Array.isArray(data && data.vendors) ? data.vendors : [];
128
+ const vendor = vendors.find((v) => v && normalizeDomain(v.domain) === domain) || null;
129
+
130
+ if (asJson) {
131
+ console.log(JSON.stringify(vendor || { domain, listed: false }, null, 2));
132
+ process.exit(vendor ? 0 : 1);
133
+ }
134
+
135
+ if (!vendor) {
136
+ console.log(`\n${B}${domain}${X} ${D}//${X} ${A}not listed on the Trooth Network yet${X}`);
137
+ console.log(`\n${D}No witnessed standing has been published for this domain. That is not a`);
138
+ console.log(`judgement. It simply has not been witnessed. A company gets witnessed by`);
139
+ console.log(`listing at ${X}${C}https://trooth.co/get-started${X}${D}: the scan reads its public surface and`);
140
+ console.log(`publishes a signed, dated standing that anyone can check.${X}\n`);
141
+ process.exit(1);
142
+ }
143
+
144
+ const pct = scorePct(vendor.score);
145
+ const name = vendor.company_name || domain;
146
+ const tier = vendor.tier ? vendor.tier[0].toUpperCase() + vendor.tier.slice(1) : 'Listed';
147
+ const when = fmtDate(vendor.passed_at);
148
+
149
+ console.log(`\n${J}${B}Trooth Network${X} ${D}// witnessed · public · read-only //${X}`);
150
+ console.log(`${B}${name}${X} ${C}${domain}${X}`);
151
+ const scoreStr = pct == null ? `${D}not yet scored${X}` : `${B}${pct}/100${X}`;
152
+ console.log(`Standing: ${scoreStr} ${J}${tier}${X}${when ? ` ${D}witnessed ${when}${X}` : ''}`);
153
+
154
+ // Categories (object keyed by discipline).
155
+ const cats = vendor.categories;
156
+ if (cats && typeof cats === 'object' && !Array.isArray(cats)) {
157
+ console.log(`\n${B}By discipline${X}`);
158
+ for (const key of ['legal', 'security', 'ai', 'business', 'ip']) {
159
+ const c = cats[key];
160
+ if (!c || typeof c.total !== 'number') continue;
161
+ const label = (CATEGORY_LABELS[key] || key).padEnd(14);
162
+ const r = c.total > 0 ? c.passed / c.total : 0;
163
+ const col = r >= 0.9 ? J : r >= 0.5 ? A : R;
164
+ console.log(` ${label} ${col}${bar(c.passed, c.total)}${X} ${String(c.passed).padStart(2)}/${c.total}`);
165
+ }
166
+ }
167
+
168
+ const line = [];
169
+ if (vendor.probes && typeof vendor.probes.total === 'number')
170
+ line.push(`${B}Live probes${X} ${vendor.probes.passed}/${vendor.probes.total}`);
171
+ if (vendor.attested && typeof vendor.attested.total === 'number')
172
+ line.push(`${B}Attestations${X} ${vendor.attested.passed}/${vendor.attested.total}`);
173
+ if (line.length) console.log(`\n${line.join(' ')}`);
174
+
175
+ const ids = [];
176
+ if (vendor.badge_id) ids.push(`${D}Badge ${vendor.badge_id}${X}`);
177
+ if (vendor.scan_id) ids.push(`${D}Scan ${vendor.scan_id}${X}`);
178
+ if (ids.length) console.log(ids.join(' '));
179
+
180
+ const events = Array.isArray(vendor.events) ? vendor.events : [];
181
+ if (events.length) {
182
+ console.log(`\n${B}Recent witness events${X}`);
183
+ for (const e of events.slice(0, 3)) {
184
+ if (!e || !e.type) continue;
185
+ console.log(` ${J}•${X} ${D}${fmtDate(e.at)}${X} ${e.type.replace(/_/g, ' ')}${e.detail ? `${D} ${e.detail}${X}` : ''}`);
186
+ }
187
+ }
188
+
189
+ console.log(`\n${D}A witnessed, point-in-time reading of public evidence. Not a certification.`);
190
+ console.log(`Full record and directory: ${X}${C}https://trooth.co/network${X}${D} · Signing keys: ${API}/public/keys${X}\n`);
191
+ process.exit(0);
192
+ }
193
+
194
+ /* ---------------------------------------------------------------- scan ---- */
195
+
51
196
  function loadPlan(file) {
52
197
  if (!file) { console.error(`${R}error${X} missing <plan> argument. Try: trooth scan ./plan.json`); process.exit(2); }
53
198
  if (!existsSync(file)) { console.error(`${R}error${X} file not found: ${file}`); process.exit(2); }
54
199
  // Try to read as JSON (a `terraform show -json` document).
55
200
  let raw = '';
56
201
  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 */ }
202
+ try { return JSON.parse(raw); } catch { /* not JSON, likely a binary .tfplan */ }
58
203
  // Fall back: ask Terraform to render the plan as JSON.
59
204
  try {
60
205
  const out = execFileSync('terraform', ['show', '-json', file], { encoding: 'utf8', maxBuffer: 64 * 1024 * 1024 });
61
206
  return JSON.parse(out);
62
207
  } catch (e) {
63
208
  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}`);
209
+ console.error(`${D} Generate JSON first: terraform show -json ${file} > plan.json then trooth scan plan.json${X}`);
65
210
  process.exit(2);
66
211
  }
67
212
  }
@@ -92,7 +237,7 @@ async function scan() {
92
237
 
93
238
  if (asJson) { console.log(JSON.stringify(delta, null, 2)); process.exit(strict && (delta.summary?.fail > 0) ? 1 : 0); }
94
239
 
95
- const score = delta.score ?? '–';
240
+ const score = delta.score ?? '-';
96
241
  const verdict = (delta.verdict || 'reviewed').toUpperCase();
97
242
  const s = delta.summary || {};
98
243
  console.log(`\n${J}${B}Trooth Pre-Flight${X} ${D}// advisory · report-only //${X}`);
@@ -116,6 +261,32 @@ async function scan() {
116
261
  process.exit(strict && (s.fail > 0) ? 1 : 0); // advisory by default: exit 0
117
262
  }
118
263
 
264
+ /* ------------------------------------------------------------------ eu ---- */
265
+ // `trooth eu` pushes EU AI Act evidence into Trooth from CI/CD (automated observability).
266
+ // trooth eu --token <t> --datasets ds.json --evals ev.json --drift dr.json
267
+ // Each file is a JSON array. Auth: TROOTH_TOKEN env or --token (bearer or x-trooth-company).
268
+ async function eu() {
269
+ const flag = (name) => { const i = argv.indexOf(name); return i >= 0 ? argv[i + 1] : null; };
270
+ const token = flag('--token') || process.env.TROOTH_TOKEN || '';
271
+ const company = flag('--company') || process.env.TROOTH_COMPANY || '';
272
+ if (!token && !company) { console.error(`${R}error${X} provide --token <session> or --company <id> (or TROOTH_TOKEN / TROOTH_COMPANY env).`); process.exit(2); }
273
+ const load = (f) => { if (!f) return undefined; try { return JSON.parse(readFileSync(f, 'utf8')); } catch (e) { console.error(`${R}error${X} cannot read ${f}: ${e.message}`); process.exit(2); } };
274
+ const body = { datasets: load(flag('--datasets')), evals: load(flag('--evals')), drift: load(flag('--drift')) };
275
+ if (!body.datasets && !body.evals && !body.drift) { console.error(`${R}error${X} provide at least one of --datasets/--evals/--drift <file.json>`); process.exit(2); }
276
+ const headers = { 'content-type': 'application/json', 'user-agent': `trooth-cli/${VERSION}` };
277
+ if (token) headers['authorization'] = `Bearer ${token}`;
278
+ if (company) headers['x-trooth-company'] = company;
279
+ let res;
280
+ try { res = await fetch(`${API}/v1/eu/ingest`, { method: 'POST', headers, body: JSON.stringify(body) }); }
281
+ catch (e) { console.error(`${R}error${X} could not reach Trooth at ${API}: ${e.message}`); process.exit(1); }
282
+ if (!res.ok) { console.error(`${R}error${X} HTTP ${res.status}. ${(await res.text().catch(()=> '')).slice(0,300)}`); process.exit(1); }
283
+ const out = await res.json();
284
+ console.log(`\n${J}${B}Trooth EU AI Act${X} ${D}// evidence ingested //${X}`);
285
+ console.log(`datasets ${B}${out.ingested?.datasets ?? 0}${X} · evals ${B}${out.ingested?.evals ?? 0}${X} · drift ${B}${out.ingested?.drift ?? 0}${X}`);
286
+ console.log(`\n${D}${out.disclaimer || 'Advisory. Trooth automates, computes, and documents; it never signs.'}${X}\n`);
287
+ process.exit(0);
288
+ }
289
+
119
290
  async function lint() {
120
291
  // Local, read-only .tf drift check. Reuses the published @trooth/os linter if present,
121
292
  // otherwise runs a built-in minimal ruleset. Never transmits your code.
@@ -128,7 +299,9 @@ async function lint() {
128
299
  (async () => {
129
300
  if (cmd === '--version' || cmd === '-v') { console.log(VERSION); return; }
130
301
  if (!cmd || cmd === '--help' || cmd === '-h' || cmd === 'help') { help(); return; }
302
+ if (cmd === 'check') return check();
131
303
  if (cmd === 'scan') return scan();
304
+ if (cmd === 'eu') return eu();
132
305
  if (cmd === 'lint') return lint();
133
306
  console.error(`${R}error${X} unknown command: ${cmd}\n`); help(); process.exit(2);
134
307
  })();
package/package.json CHANGED
@@ -1,11 +1,9 @@
1
1
  {
2
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.",
3
+ "version": "0.2.0",
4
+ "description": "Trooth CLI. Check any company's witnessed trust standing from your terminal (read-only, no key), and scan a Terraform plan against SOC 2 / ISO 27001 / GDPR / HIPAA / NIST AI RMF / EU AI Act. Advisory and report-only. Trooth never applies changes.",
5
5
  "type": "module",
6
- "bin": {
7
- "trooth": "./bin/trooth.mjs"
8
- },
6
+ "bin": "bin/trooth.mjs",
9
7
  "files": [
10
8
  "bin/",
11
9
  "README.md"
@@ -14,6 +12,7 @@
14
12
  "node": ">=18"
15
13
  },
16
14
  "scripts": {
15
+ "check": "node ./bin/trooth.mjs check",
17
16
  "scan": "node ./bin/trooth.mjs scan"
18
17
  },
19
18
  "license": "MIT",
@@ -24,6 +23,13 @@
24
23
  "url": "https://github.com/trooth/trooth-cli.git"
25
24
  },
26
25
  "keywords": [
26
+ "trust",
27
+ "vendor-trust",
28
+ "trust-network",
29
+ "company-check",
30
+ "due-diligence",
31
+ "mcp",
32
+ "ai-agents",
27
33
  "compliance",
28
34
  "soc2",
29
35
  "iso27001",