@trigguard/cli 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.
Files changed (84) hide show
  1. package/README.md +70 -0
  2. package/data/execution-surfaces.json +28 -0
  3. package/dist/auth.js +20 -0
  4. package/dist/commands/authorize.d.ts +1 -0
  5. package/dist/commands/authorize.js +99 -0
  6. package/dist/commands/chaos.d.ts +1 -0
  7. package/dist/commands/chaos.js +35 -0
  8. package/dist/commands/dev.d.ts +1 -0
  9. package/dist/commands/dev.js +27 -0
  10. package/dist/commands/doctor.d.ts +1 -0
  11. package/dist/commands/doctor.js +50 -0
  12. package/dist/commands/log.d.ts +3 -0
  13. package/dist/commands/log.js +119 -0
  14. package/dist/commands/logMonitor.d.ts +1 -0
  15. package/dist/commands/logMonitor.js +65 -0
  16. package/dist/commands/login-web.d.ts +1 -0
  17. package/dist/commands/login-web.js +80 -0
  18. package/dist/commands/policy-distribution.d.ts +10 -0
  19. package/dist/commands/policy-distribution.js +108 -0
  20. package/dist/commands/policy-runtime.d.ts +4 -0
  21. package/dist/commands/policy-runtime.js +61 -0
  22. package/dist/commands/policy.d.ts +1 -0
  23. package/dist/commands/policy.js +123 -0
  24. package/dist/commands/policyLifecycle.d.ts +13 -0
  25. package/dist/commands/policyLifecycle.js +601 -0
  26. package/dist/commands/receiptFetch.d.ts +1 -0
  27. package/dist/commands/receiptFetch.js +44 -0
  28. package/dist/commands/receiptProof.d.ts +1 -0
  29. package/dist/commands/receiptProof.js +43 -0
  30. package/dist/commands/replay.d.ts +2 -0
  31. package/dist/commands/replay.js +130 -0
  32. package/dist/commands/session.d.ts +6 -0
  33. package/dist/commands/session.js +280 -0
  34. package/dist/commands/simulate.d.ts +5 -0
  35. package/dist/commands/simulate.js +89 -0
  36. package/dist/commands/tg-authorize.d.ts +12 -0
  37. package/dist/commands/tg-authorize.js +191 -0
  38. package/dist/commands/tg-init.d.ts +1 -0
  39. package/dist/commands/tg-init.js +149 -0
  40. package/dist/commands/tg-setup.d.ts +1 -0
  41. package/dist/commands/tg-setup.js +43 -0
  42. package/dist/commands/tg-surfaces.d.ts +7 -0
  43. package/dist/commands/tg-surfaces.js +50 -0
  44. package/dist/commands/tg-verify.d.ts +1 -0
  45. package/dist/commands/tg-verify.js +118 -0
  46. package/dist/commands/transparency.d.ts +2 -0
  47. package/dist/commands/transparency.js +65 -0
  48. package/dist/commands/verify.d.ts +1 -0
  49. package/dist/commands/verify.js +127 -0
  50. package/dist/commands/verifyBundle.d.ts +1 -0
  51. package/dist/commands/verifyBundle.js +109 -0
  52. package/dist/commands/verifyReceiptCmd.d.ts +1 -0
  53. package/dist/commands/verifyReceiptCmd.js +49 -0
  54. package/dist/commands/witness.d.ts +1 -0
  55. package/dist/commands/witness.js +22 -0
  56. package/dist/cp/cliDeviceAuth.d.ts +24 -0
  57. package/dist/cp/cliDeviceAuth.js +68 -0
  58. package/dist/cp/client.d.ts +19 -0
  59. package/dist/cp/client.js +73 -0
  60. package/dist/cp/config.d.ts +8 -0
  61. package/dist/cp/config.js +113 -0
  62. package/dist/cp/credentials.d.ts +9 -0
  63. package/dist/cp/credentials.js +31 -0
  64. package/dist/cp/provisionCliKey.d.ts +12 -0
  65. package/dist/cp/provisionCliKey.js +43 -0
  66. package/dist/cp/types.d.ts +37 -0
  67. package/dist/cp/types.js +1 -0
  68. package/dist/stdin.js +9 -0
  69. package/dist/tg/args.d.ts +3 -0
  70. package/dist/tg/args.js +28 -0
  71. package/dist/tg/authorize-format.d.ts +21 -0
  72. package/dist/tg/authorize-format.js +87 -0
  73. package/dist/tg/errors.d.ts +6 -0
  74. package/dist/tg/errors.js +53 -0
  75. package/dist/tg/gateway.d.ts +2 -0
  76. package/dist/tg/gateway.js +19 -0
  77. package/dist/tg/help.d.ts +7 -0
  78. package/dist/tg/help.js +164 -0
  79. package/dist/tg/receipt.d.ts +1 -0
  80. package/dist/tg/receipt.js +13 -0
  81. package/dist/tg/shellQuote.d.ts +1 -0
  82. package/dist/tg/shellQuote.js +6 -0
  83. package/dist/tg.js +92 -0
  84. package/package.json +50 -0
@@ -0,0 +1,127 @@
1
+ import { parseArgs } from "node:util";
2
+ import { readFile } from "node:fs/promises";
3
+ import { basename, dirname, join } from "node:path";
4
+ import { readdirSync, writeFileSync } from "node:fs";
5
+ import { verifyReceipt as verifyExternalReceipt, generateEvidencePack, } from "trigguard-verifier";
6
+ function usage() {
7
+ console.error("Usage: trigguard verify <receipt.json> [--public-key <pem>] [--public-key-file <path>] [--bundle <bundle.json>] " +
8
+ "[--log-public-key <pem>] [--log-public-key-file <path>] [--engine-version <x.y.z>] [--evidence-pack <path>] " +
9
+ "[--audit <receipts/*.json>] [--json]");
10
+ process.exit(1);
11
+ }
12
+ function resolveGlob(pattern) {
13
+ if (!pattern.includes("*"))
14
+ return [pattern];
15
+ const dir = dirname(pattern);
16
+ const mask = basename(pattern);
17
+ const [prefix, suffix] = mask.split("*");
18
+ return readdirSync(dir)
19
+ .filter((name) => name.startsWith(prefix || "") && name.endsWith(suffix || ""))
20
+ .map((name) => join(dir, name))
21
+ .sort((a, b) => a.localeCompare(b, "en"));
22
+ }
23
+ export async function runVerify(argv) {
24
+ const { values, positionals } = parseArgs({
25
+ args: argv,
26
+ options: {
27
+ "public-key": { type: "string" },
28
+ "public-key-file": { type: "string" },
29
+ "log-public-key": { type: "string" },
30
+ "log-public-key-file": { type: "string" },
31
+ bundle: { type: "string" },
32
+ "engine-version": { type: "string" },
33
+ "evidence-pack": { type: "string" },
34
+ audit: { type: "string" },
35
+ json: { type: "boolean", default: false },
36
+ },
37
+ allowPositionals: true,
38
+ strict: true,
39
+ });
40
+ const readText = async (p) => readFile(p, "utf8");
41
+ const pickKey = async (inlineKey, filePath, envVar) => {
42
+ if (inlineKey?.trim())
43
+ return inlineKey.trim();
44
+ if (filePath?.trim())
45
+ return (await readText(filePath.trim())).trim();
46
+ return (process.env[envVar] || "").trim();
47
+ };
48
+ const authorityPublicKeyPem = await pickKey(typeof values["public-key"] === "string" ? values["public-key"] : undefined, typeof values["public-key-file"] === "string" ? values["public-key-file"] : undefined, "TRIGGUARD_AUTHORITY_PUBLIC_KEY");
49
+ if (!authorityPublicKeyPem) {
50
+ console.error("Missing authority public key. Set --public-key, --public-key-file, or TRIGGUARD_AUTHORITY_PUBLIC_KEY.");
51
+ process.exit(1);
52
+ }
53
+ const logPublicKeyPem = await pickKey(typeof values["log-public-key"] === "string" ? values["log-public-key"] : undefined, typeof values["log-public-key-file"] === "string" ? values["log-public-key-file"] : undefined, "TRIGGUARD_LOG_PUBLIC_KEY");
54
+ const bundlePath = typeof values.bundle === "string" ? values.bundle.trim() : "";
55
+ const bundle = bundlePath ? JSON.parse(await readText(bundlePath)) : undefined;
56
+ const expectedEngineVersion = (typeof values["engine-version"] === "string" ? values["engine-version"] : undefined) ||
57
+ process.env.TRIGGUARD_ENGINE_VERSION ||
58
+ "1.0.0";
59
+ const verifyOne = async (receiptPath) => {
60
+ const raw = await readText(receiptPath);
61
+ const receipt = JSON.parse(raw);
62
+ const result = verifyExternalReceipt(receipt, {
63
+ authorityPublicKeyPem,
64
+ logPublicKeyPem: logPublicKeyPem || undefined,
65
+ bundle,
66
+ expectedEngineVersion,
67
+ });
68
+ return { path: receiptPath, result };
69
+ };
70
+ const auditPattern = typeof values.audit === "string" ? values.audit.trim() : "";
71
+ if (auditPattern) {
72
+ const files = resolveGlob(auditPattern);
73
+ if (files.length === 0) {
74
+ console.error(`No receipts matched --audit pattern: ${auditPattern}`);
75
+ process.exit(1);
76
+ }
77
+ const results = await Promise.all(files.map((f) => verifyOne(f)));
78
+ const verified = results.filter((r) => r.result.valid).length;
79
+ const failed = results.length - verified;
80
+ const bundleVersions = Array.from(new Set(results
81
+ .map((r) => {
82
+ if (!r.result.valid)
83
+ return "";
84
+ const v = (r.result.verified_bundle_hash || "").trim();
85
+ return v;
86
+ })
87
+ .filter(Boolean)));
88
+ const policyVersions = Array.from(new Set(results.map(() => expectedEngineVersion)));
89
+ const summary = {
90
+ total_receipts: results.length,
91
+ verified,
92
+ failed,
93
+ bundle_versions: bundleVersions,
94
+ policy_versions: policyVersions,
95
+ results: results.map((r) => ({ file: r.path, ...r.result })),
96
+ };
97
+ writeFileSync("audit-summary.json", `${JSON.stringify(summary, null, 2)}\n`, "utf8");
98
+ if (values.json)
99
+ console.log(JSON.stringify(summary));
100
+ else
101
+ console.log("Audit complete: audit-summary.json");
102
+ process.exit(failed === 0 ? 0 : 1);
103
+ }
104
+ const file = positionals[0];
105
+ if (!file)
106
+ usage();
107
+ const out = await verifyOne(file);
108
+ if (values["evidence-pack"] && typeof values["evidence-pack"] === "string") {
109
+ const receiptRaw = JSON.parse(await readText(file));
110
+ generateEvidencePack(receiptRaw, out.result, values["evidence-pack"]);
111
+ }
112
+ if (values.json) {
113
+ console.log(JSON.stringify(out.result));
114
+ process.exit(out.result.valid ? 0 : 1);
115
+ }
116
+ if (out.result.valid) {
117
+ console.log("VERIFIED");
118
+ console.log(`decision: ${out.result.decision}`);
119
+ if (out.result.verified_bundle_hash)
120
+ console.log(`bundle_hash: ${out.result.verified_bundle_hash}`);
121
+ console.log(`engine_version: ${out.result.verified_engine_version}`);
122
+ process.exit(0);
123
+ }
124
+ console.log("VERIFICATION FAILED");
125
+ console.log(`${out.result.code}: ${out.result.reason}`);
126
+ process.exit(1);
127
+ }
@@ -0,0 +1 @@
1
+ export declare function runVerifyBundle(argv: string[]): Promise<void>;
@@ -0,0 +1,109 @@
1
+ import { parseArgs } from "node:util";
2
+ import { readFile } from "node:fs/promises";
3
+ import { availableParallelism } from "node:os";
4
+ import { verifyReceipt } from "@trigguard/receipt-verify";
5
+ async function mapPool(items, concurrency, fn) {
6
+ const results = new Array(items.length);
7
+ let next = 0;
8
+ async function worker() {
9
+ for (;;) {
10
+ const i = next++;
11
+ if (i >= items.length)
12
+ return;
13
+ results[i] = await fn(items[i], i);
14
+ }
15
+ }
16
+ const n = Math.max(1, Math.min(concurrency, items.length));
17
+ await Promise.all(Array.from({ length: n }, () => worker()));
18
+ return results;
19
+ }
20
+ export async function runVerifyBundle(argv) {
21
+ const { values, positionals } = parseArgs({
22
+ args: argv,
23
+ options: {
24
+ "public-key": { type: "string" },
25
+ "public-key-file": { type: "string" },
26
+ "keys-url": { type: "string" },
27
+ concurrency: { type: "string" },
28
+ json: { type: "boolean", default: false },
29
+ },
30
+ allowPositionals: true,
31
+ strict: true,
32
+ });
33
+ const file = positionals[0];
34
+ if (!file) {
35
+ console.error("Usage: trigguard verify-bundle <receipts.jsonl> [--public-key …] [--public-key-file …] [--keys-url URL] [--concurrency N]");
36
+ process.exit(1);
37
+ }
38
+ const publicKey = values["public-key"]?.trim();
39
+ const publicKeyFile = values["public-key-file"]?.trim();
40
+ if (!publicKey && !publicKeyFile && !process.env.TRIGGUARD_KEYS_URL?.trim()) {
41
+ console.error("Provide --public-key or --public-key-file (offline), or TRIGGUARD_KEYS_URL / --keys-url for key fetch.");
42
+ process.exit(1);
43
+ }
44
+ const keysUrl = values["keys-url"]?.trim() || process.env.TRIGGUARD_KEYS_URL?.trim();
45
+ const conc = Math.max(1, parseInt(String(values.concurrency || ""), 10) || availableParallelism());
46
+ const text = await readFile(file, "utf8");
47
+ const lines = text.split("\n").map((s) => s.trim()).filter(Boolean);
48
+ const t0 = Date.now();
49
+ const lineResults = await mapPool(lines, conc, async (line, idx) => {
50
+ const lineNo = idx + 1;
51
+ let obj;
52
+ try {
53
+ obj = JSON.parse(line);
54
+ }
55
+ catch {
56
+ return { line: lineNo, valid: false, reason: "invalid_json" };
57
+ }
58
+ if (typeof obj !== "object" || obj === null || Array.isArray(obj)) {
59
+ return { line: lineNo, valid: false, reason: "not_object" };
60
+ }
61
+ const rec = "receipt" in obj && typeof obj.receipt === "object"
62
+ ? obj.receipt
63
+ : obj;
64
+ try {
65
+ const vr = await verifyReceipt(rec, {
66
+ ...(publicKey ? { publicKey } : {}),
67
+ ...(publicKeyFile ? { publicKeyFile } : {}),
68
+ ...(keysUrl ? { keysUrl } : {}),
69
+ });
70
+ return {
71
+ line: lineNo,
72
+ valid: Boolean(vr.valid),
73
+ reason: vr.reason,
74
+ receipt_id: vr.receipt_id,
75
+ };
76
+ }
77
+ catch (e) {
78
+ return {
79
+ line: lineNo,
80
+ valid: false,
81
+ reason: e instanceof Error ? e.message : String(e),
82
+ };
83
+ }
84
+ });
85
+ const valid = lineResults.filter((r) => r.valid).length;
86
+ const invalid = lineResults.length - valid;
87
+ const ms = Date.now() - t0;
88
+ if (values.json) {
89
+ console.log(JSON.stringify({
90
+ total: lineResults.length,
91
+ valid,
92
+ invalid,
93
+ ms,
94
+ results: lineResults,
95
+ }));
96
+ }
97
+ else {
98
+ console.log(`Total receipts: ${lineResults.length}`);
99
+ console.log(`Valid: ${valid}`);
100
+ console.log(`Invalid: ${invalid}`);
101
+ console.log(`Verification time: ${ms} ms (concurrency=${conc})`);
102
+ for (const r of lineResults) {
103
+ if (!r.valid) {
104
+ console.log(` line ${r.line}: FAIL ${r.reason ?? ""}`);
105
+ }
106
+ }
107
+ }
108
+ process.exit(invalid > 0 ? 1 : 0);
109
+ }
@@ -0,0 +1 @@
1
+ export declare function runVerifyReceipt(argv: string[]): Promise<void>;
@@ -0,0 +1,49 @@
1
+ import { createRequire } from "node:module";
2
+ import { parseArgs } from "node:util";
3
+ import { readFile } from "node:fs/promises";
4
+ import { readStdin } from "../stdin.js";
5
+ // eslint-disable-next-line @typescript-eslint/no-require-imports, @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access
6
+ const { verifyReceipt } = createRequire(import.meta.url)("@trigguard/decision");
7
+ export async function runVerifyReceipt(argv) {
8
+ const { values, positionals } = parseArgs({
9
+ args: argv,
10
+ options: {
11
+ "public-key": { type: "string" },
12
+ json: { type: "boolean", default: false },
13
+ },
14
+ allowPositionals: true,
15
+ strict: true,
16
+ });
17
+ const pub = values["public-key"]?.trim() ||
18
+ process.env.TRIGGUARD_AUTHORITY_PUBLIC_KEY?.trim() ||
19
+ process.env.AUTHORITY_PUBLIC_KEY?.trim();
20
+ if (!pub) {
21
+ console.error("Usage: trigguard verify-receipt <file.json> [--public-key 64-hex] (or set TRIGGUARD_AUTHORITY_PUBLIC_KEY)");
22
+ process.exit(1);
23
+ }
24
+ const first = positionals[0];
25
+ if (!first) {
26
+ console.error("Usage: trigguard verify-receipt <file.json|-");
27
+ process.exit(1);
28
+ }
29
+ const text = first === "-" ? await readStdin() : await readFile(first, "utf8");
30
+ const body = JSON.parse(text);
31
+ const decision = String(body.decision ?? "");
32
+ const reason = String(body.reason ?? "");
33
+ const policyFingerprint = String(body.policyFingerprint ?? "");
34
+ const timestamp = String(body.timestamp ?? "");
35
+ const receiptSignature = String(body.receiptSignature ||
36
+ String(body.receipt?.signature || ""));
37
+ if (!receiptSignature) {
38
+ console.error("JSON must include receiptSignature (Execution Authority /decide response shape)");
39
+ process.exit(1);
40
+ }
41
+ const ok = verifyReceipt({ decision, reason, policyFingerprint, timestamp }, receiptSignature, pub);
42
+ if (values.json) {
43
+ console.log(JSON.stringify({ valid: ok, decision, reason }));
44
+ }
45
+ else {
46
+ console.log(ok ? "OK receipt signature is valid" : "INVALID receipt signature");
47
+ }
48
+ process.exit(ok ? 0 : 1);
49
+ }
@@ -0,0 +1 @@
1
+ export declare function runWitness(argv: string[]): Promise<void>;
@@ -0,0 +1,22 @@
1
+ import { spawn } from "node:child_process";
2
+ export async function runWitness(argv) {
3
+ const sub = argv[0];
4
+ if (sub !== "run") {
5
+ console.error("Usage: trigguard witness run");
6
+ process.exit(1);
7
+ return;
8
+ }
9
+ const child = spawn("npm", ["--workspace", "packages/trigguard-log-witness", "run", "start"], {
10
+ stdio: "inherit",
11
+ env: process.env,
12
+ });
13
+ await new Promise((resolve, reject) => {
14
+ child.on("exit", (code) => {
15
+ if (code === 0)
16
+ resolve();
17
+ else
18
+ reject(new Error(`witness exited with code ${String(code)}`));
19
+ });
20
+ child.on("error", reject);
21
+ });
22
+ }
@@ -0,0 +1,24 @@
1
+ import type { TrigGuardCliConfig } from "./types.js";
2
+ export interface CliDeviceStartResponse {
3
+ deviceCode: string;
4
+ userCode: string;
5
+ expiresIn: number;
6
+ verificationUrl: string;
7
+ }
8
+ export interface CliDeviceCompleteResponse {
9
+ status: "complete";
10
+ token: string;
11
+ user: {
12
+ id: string;
13
+ email: string;
14
+ };
15
+ activeOrgId: string | null;
16
+ orgName: string | null;
17
+ rawApiKey: string;
18
+ apiKeyId: string;
19
+ }
20
+ export declare function startCliDeviceAuth(config: TrigGuardCliConfig, machineLabel: string): Promise<CliDeviceStartResponse>;
21
+ export declare function pollCliDeviceAuth(config: TrigGuardCliConfig, deviceCode: string): Promise<{
22
+ status: "pending";
23
+ } | CliDeviceCompleteResponse>;
24
+ export declare function waitForCliDeviceAuth(config: TrigGuardCliConfig, deviceCode: string, expiresInSec: number): Promise<CliDeviceCompleteResponse>;
@@ -0,0 +1,68 @@
1
+ export async function startCliDeviceAuth(config, machineLabel) {
2
+ const res = await fetch(`${config.controlPlaneUrl}/auth/cli/device/start`, {
3
+ method: "POST",
4
+ headers: { Accept: "application/json", "Content-Type": "application/json" },
5
+ body: JSON.stringify({ machineLabel }),
6
+ signal: AbortSignal.timeout(30_000),
7
+ });
8
+ const body = (await res.json().catch(() => ({})));
9
+ if (!res.ok) {
10
+ throw new Error(String(body.error ?? `device_start_failed_${res.status}`));
11
+ }
12
+ const deviceCode = typeof body.deviceCode === "string" ? body.deviceCode : "";
13
+ const userCode = typeof body.userCode === "string" ? body.userCode : "";
14
+ const verificationUrl = typeof body.verificationUrl === "string" ? body.verificationUrl : "";
15
+ if (!deviceCode || !userCode || !verificationUrl) {
16
+ throw new Error("device_start_invalid");
17
+ }
18
+ return {
19
+ deviceCode,
20
+ userCode,
21
+ expiresIn: typeof body.expiresIn === "number" ? body.expiresIn : 900,
22
+ verificationUrl,
23
+ };
24
+ }
25
+ export async function pollCliDeviceAuth(config, deviceCode) {
26
+ const url = new URL(`${config.controlPlaneUrl}/auth/cli/device/poll`);
27
+ url.searchParams.set("device_code", deviceCode);
28
+ const res = await fetch(url, {
29
+ headers: { Accept: "application/json" },
30
+ signal: AbortSignal.timeout(30_000),
31
+ });
32
+ const body = (await res.json().catch(() => ({})));
33
+ if (res.status === 410) {
34
+ throw new Error("device_code_expired");
35
+ }
36
+ if (!res.ok) {
37
+ throw new Error(String(body.error ?? `device_poll_failed_${res.status}`));
38
+ }
39
+ if (body.status === "pending") {
40
+ return { status: "pending" };
41
+ }
42
+ const token = typeof body.token === "string" ? body.token : "";
43
+ const user = body.user;
44
+ const rawApiKey = typeof body.rawApiKey === "string" ? body.rawApiKey : "";
45
+ const apiKeyId = typeof body.apiKeyId === "string" ? body.apiKeyId : "";
46
+ if (!token || !user?.email || !rawApiKey) {
47
+ throw new Error("device_complete_invalid");
48
+ }
49
+ return {
50
+ status: "complete",
51
+ token,
52
+ user: { id: String(user.id ?? ""), email: user.email },
53
+ activeOrgId: typeof body.activeOrgId === "string" ? body.activeOrgId : null,
54
+ orgName: typeof body.orgName === "string" ? body.orgName : null,
55
+ rawApiKey,
56
+ apiKeyId,
57
+ };
58
+ }
59
+ export async function waitForCliDeviceAuth(config, deviceCode, expiresInSec) {
60
+ const deadline = Date.now() + expiresInSec * 1000;
61
+ while (Date.now() < deadline) {
62
+ const polled = await pollCliDeviceAuth(config, deviceCode);
63
+ if (polled.status === "complete")
64
+ return polled;
65
+ await new Promise((r) => setTimeout(r, 2000));
66
+ }
67
+ throw new Error("device_login_timeout");
68
+ }
@@ -0,0 +1,19 @@
1
+ import type { ControlPlaneOrgSummary, ControlPlaneUser, TrigGuardCliConfig } from "./types.js";
2
+ export declare class ControlPlaneError extends Error {
3
+ readonly status: number;
4
+ readonly code: string;
5
+ constructor(status: number, code: string, message?: string);
6
+ }
7
+ export declare function controlPlaneFetch<T>(config: TrigGuardCliConfig, path: string, init?: RequestInit & {
8
+ sessionToken?: string | null;
9
+ }): Promise<T>;
10
+ export declare function loginWithPassword(config: TrigGuardCliConfig, email: string, password: string): Promise<{
11
+ token: string;
12
+ user: ControlPlaneUser;
13
+ }>;
14
+ export declare function fetchMe(config: TrigGuardCliConfig): Promise<ControlPlaneUser>;
15
+ export declare function fetchDashboardOrgs(config: TrigGuardCliConfig): Promise<{
16
+ user: ControlPlaneUser;
17
+ orgs: ControlPlaneOrgSummary[];
18
+ }>;
19
+ export declare function resolveActiveOrg(orgs: readonly ControlPlaneOrgSummary[], activeOrgId: string | undefined): ControlPlaneOrgSummary | null;
@@ -0,0 +1,73 @@
1
+ export class ControlPlaneError extends Error {
2
+ status;
3
+ code;
4
+ constructor(status, code, message) {
5
+ super(message ?? code);
6
+ this.status = status;
7
+ this.code = code;
8
+ }
9
+ }
10
+ async function parseJson(res) {
11
+ try {
12
+ const data = await res.json();
13
+ return data && typeof data === "object" ? data : {};
14
+ }
15
+ catch {
16
+ return {};
17
+ }
18
+ }
19
+ export async function controlPlaneFetch(config, path, init = {}) {
20
+ const token = init.sessionToken !== undefined ? init.sessionToken : config.sessionToken;
21
+ const headers = new Headers(init.headers);
22
+ headers.set("Accept", "application/json");
23
+ if (token)
24
+ headers.set("Authorization", `Bearer ${token}`);
25
+ const res = await fetch(`${config.controlPlaneUrl}${path}`, {
26
+ ...init,
27
+ headers,
28
+ signal: AbortSignal.timeout(30_000),
29
+ });
30
+ const body = await parseJson(res);
31
+ if (!res.ok) {
32
+ const code = typeof body.error === "string" ? body.error : `http_${res.status}`;
33
+ throw new ControlPlaneError(res.status, code);
34
+ }
35
+ return body;
36
+ }
37
+ export async function loginWithPassword(config, email, password) {
38
+ const data = await controlPlaneFetch(config, "/login", {
39
+ method: "POST",
40
+ headers: { "Content-Type": "application/json" },
41
+ body: JSON.stringify({ email: email.trim(), password }),
42
+ sessionToken: null,
43
+ });
44
+ const token = typeof data.token === "string" ? data.token : "";
45
+ const user = data.user;
46
+ if (!token || !user?.id || !user.email) {
47
+ throw new Error("login_response_invalid");
48
+ }
49
+ return { token, user };
50
+ }
51
+ export async function fetchMe(config) {
52
+ const me = await controlPlaneFetch(config, "/me", { method: "GET" });
53
+ if (!me.id || !me.email)
54
+ throw new Error("me_response_invalid");
55
+ return me;
56
+ }
57
+ export async function fetchDashboardOrgs(config) {
58
+ const data = await controlPlaneFetch(config, "/dashboard/org", { method: "GET" });
59
+ return {
60
+ user: data.user ?? { id: "", email: "", createdAt: "", emailVerifiedAt: null },
61
+ orgs: Array.isArray(data.orgs) ? data.orgs : [],
62
+ };
63
+ }
64
+ export function resolveActiveOrg(orgs, activeOrgId) {
65
+ if (!orgs.length)
66
+ return null;
67
+ if (activeOrgId) {
68
+ const match = orgs.find((o) => o.orgId === activeOrgId);
69
+ if (match)
70
+ return match;
71
+ }
72
+ return orgs[0] ?? null;
73
+ }
@@ -0,0 +1,8 @@
1
+ import type { TrigGuardCliConfig, TrigGuardEnvironment } from "./types.js";
2
+ export declare function configDir(): string;
3
+ export declare function configPath(): string;
4
+ export declare function defaultControlPlaneUrl(): string;
5
+ export declare function defaultEnvironment(): TrigGuardEnvironment;
6
+ export declare function loadConfig(): TrigGuardCliConfig;
7
+ export declare function saveConfig(config: TrigGuardCliConfig): void;
8
+ export declare function clearSession(config: TrigGuardCliConfig): TrigGuardCliConfig;
@@ -0,0 +1,113 @@
1
+ import fs from "node:fs";
2
+ import os from "node:os";
3
+ import path from "node:path";
4
+ const CONFIG_VERSION = 1;
5
+ const DEFAULT_CP_URL = "https://control.trigguardai.com";
6
+ export function configDir() {
7
+ const override = process.env.TRIGGUARD_CONFIG_DIR?.trim();
8
+ if (override)
9
+ return override;
10
+ return path.join(os.homedir(), ".trigguard");
11
+ }
12
+ export function configPath() {
13
+ return path.join(configDir(), "config.json");
14
+ }
15
+ function parseEnvironment(raw) {
16
+ switch ((raw ?? "production").trim().toLowerCase()) {
17
+ case "staging":
18
+ return "staging";
19
+ case "preview":
20
+ return "preview";
21
+ case "local":
22
+ case "dev":
23
+ case "development":
24
+ return "local";
25
+ default:
26
+ return "production";
27
+ }
28
+ }
29
+ export function defaultControlPlaneUrl() {
30
+ return (process.env.TRIGGUARD_CONTROL_PLANE_URL ?? DEFAULT_CP_URL).replace(/\/$/, "");
31
+ }
32
+ export function defaultEnvironment() {
33
+ return parseEnvironment(process.env.TRIGGUARD_ENVIRONMENT);
34
+ }
35
+ function readConfigFile(base) {
36
+ const file = configPath();
37
+ if (!fs.existsSync(file))
38
+ return null;
39
+ try {
40
+ return JSON.parse(fs.readFileSync(file, "utf8"));
41
+ }
42
+ catch {
43
+ return null;
44
+ }
45
+ }
46
+ export function loadConfig() {
47
+ const base = {
48
+ version: CONFIG_VERSION,
49
+ controlPlaneUrl: defaultControlPlaneUrl(),
50
+ environment: defaultEnvironment(),
51
+ };
52
+ const parsed = readConfigFile(base);
53
+ if (parsed?.sessionRevoked) {
54
+ return {
55
+ version: CONFIG_VERSION,
56
+ controlPlaneUrl: (parsed.controlPlaneUrl ?? base.controlPlaneUrl).replace(/\/$/, ""),
57
+ environment: parseEnvironment(parsed.environment ?? base.environment),
58
+ sessionRevoked: true,
59
+ };
60
+ }
61
+ const envToken = process.env.TRIGGUARD_SESSION_TOKEN?.trim();
62
+ if (envToken) {
63
+ return {
64
+ ...base,
65
+ sessionToken: envToken,
66
+ activeOrgId: process.env.TRIGGUARD_ORG_ID?.trim() || parsed?.activeOrgId?.trim() || undefined,
67
+ user: parsed?.user,
68
+ apiKey: parsed?.apiKey?.trim() || undefined,
69
+ apiKeyId: parsed?.apiKeyId?.trim() || undefined,
70
+ apiKeyDisplayName: parsed?.apiKeyDisplayName?.trim() || undefined,
71
+ };
72
+ }
73
+ if (!parsed)
74
+ return base;
75
+ return {
76
+ version: CONFIG_VERSION,
77
+ controlPlaneUrl: (parsed.controlPlaneUrl ?? base.controlPlaneUrl).replace(/\/$/, ""),
78
+ environment: parseEnvironment(parsed.environment ?? base.environment),
79
+ sessionToken: parsed.sessionToken?.trim() || undefined,
80
+ activeOrgId: parsed.activeOrgId?.trim() || process.env.TRIGGUARD_ORG_ID?.trim() || undefined,
81
+ user: parsed.user,
82
+ apiKey: parsed.apiKey?.trim() || undefined,
83
+ apiKeyId: parsed.apiKeyId?.trim() || undefined,
84
+ apiKeyDisplayName: parsed.apiKeyDisplayName?.trim() || undefined,
85
+ };
86
+ }
87
+ export function saveConfig(config) {
88
+ const dir = configDir();
89
+ fs.mkdirSync(dir, { recursive: true, mode: 0o700 });
90
+ const payload = {
91
+ version: CONFIG_VERSION,
92
+ controlPlaneUrl: config.controlPlaneUrl.replace(/\/$/, ""),
93
+ environment: config.environment,
94
+ sessionToken: config.sessionToken,
95
+ activeOrgId: config.activeOrgId,
96
+ user: config.user,
97
+ apiKey: config.apiKey,
98
+ apiKeyId: config.apiKeyId,
99
+ apiKeyDisplayName: config.apiKeyDisplayName,
100
+ ...(config.sessionToken ? {} : config.sessionRevoked ? { sessionRevoked: true } : {}),
101
+ };
102
+ fs.writeFileSync(configPath(), `${JSON.stringify(payload, null, 2)}\n`, { mode: 0o600 });
103
+ }
104
+ export function clearSession(config) {
105
+ const next = {
106
+ version: CONFIG_VERSION,
107
+ controlPlaneUrl: config.controlPlaneUrl,
108
+ environment: config.environment,
109
+ sessionRevoked: true,
110
+ };
111
+ saveConfig(next);
112
+ return next;
113
+ }
@@ -0,0 +1,9 @@
1
+ import type { TrigGuardCliConfig } from "./types.js";
2
+ export type ApiKeySource = "env" | "config" | "none";
3
+ export declare function resolveApiKeyFromEnv(): string | undefined;
4
+ export declare function resolveApiKey(config: TrigGuardCliConfig): string | undefined;
5
+ export declare function resolveApiKeySource(config: TrigGuardCliConfig): ApiKeySource;
6
+ export declare function storedCliApiKeyForSession(config: TrigGuardCliConfig, user: {
7
+ email: string;
8
+ }, activeOrgId: string | undefined): string | null;
9
+ export declare function missingConfiguredApiKeyMessage(): string;
@@ -0,0 +1,31 @@
1
+ export function resolveApiKeyFromEnv() {
2
+ return (process.env.TRIGGUARD_API_KEY?.trim() ||
3
+ process.env.TG_API_KEY?.trim() ||
4
+ undefined);
5
+ }
6
+ export function resolveApiKey(config) {
7
+ return resolveApiKeyFromEnv() ?? (config.apiKey?.trim() || undefined);
8
+ }
9
+ export function resolveApiKeySource(config) {
10
+ if (resolveApiKeyFromEnv())
11
+ return "env";
12
+ if (config.apiKey?.trim())
13
+ return "config";
14
+ return "none";
15
+ }
16
+ export function storedCliApiKeyForSession(config, user, activeOrgId) {
17
+ if (!activeOrgId ||
18
+ config.user?.email !== user.email ||
19
+ config.activeOrgId !== activeOrgId ||
20
+ !config.apiKey?.startsWith("tg_live_")) {
21
+ return null;
22
+ }
23
+ return config.apiKey;
24
+ }
25
+ export function missingConfiguredApiKeyMessage() {
26
+ return [
27
+ "No API key configured.",
28
+ "Run: tg login",
29
+ "Or export TRIGGUARD_API_KEY for CI/scripts.",
30
+ ].join("\n");
31
+ }