@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
package/README.md ADDED
@@ -0,0 +1,70 @@
1
+ # `@trigguard/cli`
2
+
3
+ Developer CLI for the execution gateway: **authorize**, **verify** receipts, plus **policy** / **simulate** stubs for future releases.
4
+
5
+ ## Install (from monorepo)
6
+
7
+ ```bash
8
+ cd packages/trigguard-cli && npm ci && npm run build
9
+ node dist/index.js --help
10
+ npm test
11
+ ```
12
+
13
+ Global install after publish: `npm install -g @trigguard/cli`
14
+
15
+ ## Commands
16
+
17
+ ```bash
18
+ export TRIGGUARD_GATEWAY_URL=https://your-run-url.run.app
19
+ export TRIGGUARD_BEARER="$(gcloud auth print-identity-token --audiences=$TRIGGUARD_GATEWAY_URL)"
20
+
21
+ trigguard authorize --surface deploy.release --json
22
+ trigguard verify ./receipt.json --json
23
+ ```
24
+
25
+ ### Provider mode (vendor JSON → gateway)
26
+
27
+ Map a vendor-shaped payload with a built-in adapter (`packages/trigguard-providers`), then authorize:
28
+
29
+ ```bash
30
+ trigguard authorize --provider stripe --input ./payment.json --json
31
+ cat payment.json | trigguard authorize --provider stripe --input - --json
32
+ ```
33
+
34
+ Use `--surface` + optional `--context <file.json>` when you already have canonical fields and do not need `mapAction`.
35
+
36
+ Or `TRIGGUARD_USE_GCLOUD=1` to obtain the identity token via `gcloud` automatically.
37
+
38
+ ## CI vs local
39
+
40
+ | Environment | Auth |
41
+ |-------------|------|
42
+ | GitHub Actions | `TrigGuard-AI/authorize@v1` (OIDC → GCP) |
43
+ | Local / scripts | `TRIGGUARD_BEARER` or `TRIGGUARD_USE_GCLOUD=1` |
44
+
45
+ ## Local execution authority (dev)
46
+
47
+ ```bash
48
+ npm run build
49
+ node dist/index.js dev --port 8787
50
+ # or: npx trigguard dev
51
+ ```
52
+
53
+ - **`trigguard doctor`** — Node version, monorepo detection, optional `/health` on `127.0.0.1:8787`
54
+ - **`trigguard verify-receipt <file.json> --public-key <hex>`** — offline verify for Execution Authority **flat** JSON (same as `sdk/node` `verifyReceipt`)
55
+ - Optional **`--swift`** uses `tg_execution_authority` when `TG_AUTHORITY_PRIVATE_KEY` and a binary are available; otherwise the CLI falls back to the Node mock.
56
+
57
+ See [../../docs/getting-started/local-authority.md](../../docs/getting-started/local-authority.md).
58
+
59
+ ## Offline receipt verification
60
+
61
+ `trigguard verify` uses **`@trigguard/receipt-verify`**. When you pass a known authority public key, verification is **fully offline** (no `/.well-known` fetch):
62
+
63
+ ```bash
64
+ trigguard verify ./receipt.json --public-key <64-hex-ed25519-raw-or-pem>
65
+ trigguard verify ./receipt.json --public-key-file ./authority.pem
66
+ ```
67
+
68
+ Precedence: **`--public-key`** → **`--public-key-file`** → keys from **`--keys-url`** / **`TRIGGUARD_KEYS_URL`** (with optional bearer for gated endpoints).
69
+
70
+ For Execution Authority `/decide`-shaped receipts, this matches the same canonical signing material as **`sdk/node`** / Swift.
@@ -0,0 +1,28 @@
1
+ {
2
+ "version": "1.0.0",
3
+ "registry_id": "trigguard-core",
4
+ "surfaces": [
5
+ "deploy.release",
6
+ "infra.apply",
7
+ "artifact.publish",
8
+ "data.export",
9
+ "payment.execute",
10
+ "secrets.access",
11
+ "database.migrate",
12
+ "production.write",
13
+ "pr.docs.write",
14
+ "pr.tests.write",
15
+ "pr.examples.write",
16
+ "pr.scripts.write",
17
+ "pr.workflows.write",
18
+ "pr.governance.write",
19
+ "pr.runtime.write",
20
+ "pr.kernel.write",
21
+ "pr.mixed.write",
22
+ "spendCommit",
23
+ "timeCommit",
24
+ "dataExport",
25
+ "identityAssertion",
26
+ "externalAuthorityDelegation"
27
+ ]
28
+ }
package/dist/auth.js ADDED
@@ -0,0 +1,20 @@
1
+ import { spawnSync } from "node:child_process";
2
+ /**
3
+ * Resolve Bearer token: TRIGGUARD_BEARER, or gcloud identity token when TRIGGUARD_USE_GCLOUD=1.
4
+ */
5
+ export async function resolveBearer(gatewayUrl) {
6
+ const direct = process.env.TRIGGUARD_BEARER?.trim();
7
+ if (direct)
8
+ return direct;
9
+ if (process.env.TRIGGUARD_USE_GCLOUD === "1") {
10
+ const aud = gatewayUrl.replace(/\/$/, "");
11
+ const r = spawnSync("gcloud", ["auth", "print-identity-token", `--audiences=${aud}`], { encoding: "utf8" });
12
+ if (r.error)
13
+ throw r.error;
14
+ if (r.status !== 0) {
15
+ throw new Error(`gcloud failed: ${r.stderr || r.stdout}`);
16
+ }
17
+ return r.stdout.trim();
18
+ }
19
+ return undefined;
20
+ }
@@ -0,0 +1 @@
1
+ export declare function runAuthorize(argv: string[]): Promise<void>;
@@ -0,0 +1,99 @@
1
+ import { parseArgs } from "node:util";
2
+ import { readFile } from "node:fs/promises";
3
+ import { createExecutionClient, fetchPublicKeys, verifyReceiptSignature, } from "@trigguard/execution-sdk";
4
+ import { createDefaultProviderRegistry } from "@trigguard/providers";
5
+ import { resolveBearer } from "../auth.js";
6
+ import { readStdin } from "../stdin.js";
7
+ export async function runAuthorize(argv) {
8
+ const { values } = parseArgs({
9
+ args: argv,
10
+ options: {
11
+ surface: { type: "string" },
12
+ provider: { type: "string" },
13
+ input: { type: "string" },
14
+ "gateway-url": { type: "string" },
15
+ "actor-id": { type: "string" },
16
+ context: { type: "string" },
17
+ json: { type: "boolean", default: false },
18
+ },
19
+ allowPositionals: true,
20
+ strict: true,
21
+ });
22
+ const gatewayUrl = values["gateway-url"] ||
23
+ process.env.TRIGGUARD_GATEWAY_URL ||
24
+ "http://127.0.0.1:8080";
25
+ let actorId = values["actor-id"] || process.env.TRIGGUARD_ACTOR_ID || "trigguard-cli";
26
+ let surface;
27
+ let context;
28
+ let subjectDigest;
29
+ const providerId = values.provider?.trim();
30
+ if (providerId) {
31
+ const registry = createDefaultProviderRegistry();
32
+ const provider = registry.get(providerId);
33
+ if (!provider) {
34
+ console.error(`Unknown --provider "${providerId}". Try: ${registry.listIds().join(", ")}`);
35
+ process.exit(1);
36
+ }
37
+ const inputPath = values.input;
38
+ if (!inputPath) {
39
+ console.error("With --provider, pass vendor JSON via --input <file> or --input - (stdin)");
40
+ process.exit(1);
41
+ }
42
+ const raw = inputPath === "-"
43
+ ? await readStdin()
44
+ : await readFile(inputPath, "utf8");
45
+ const vendorPayload = JSON.parse(raw);
46
+ const req = provider.mapAction(vendorPayload);
47
+ surface = req.surface;
48
+ actorId = req.actorId ?? actorId;
49
+ context = req.context;
50
+ subjectDigest = req.subjectDigest;
51
+ }
52
+ else {
53
+ const s = values.surface;
54
+ if (!s) {
55
+ console.error("Missing required --surface (or use --provider with --input)");
56
+ process.exit(1);
57
+ }
58
+ surface = s;
59
+ if (values.context) {
60
+ const raw = await readFile(values.context, "utf8");
61
+ context = JSON.parse(raw);
62
+ }
63
+ }
64
+ const getBearerToken = () => resolveBearer(gatewayUrl);
65
+ const client = createExecutionClient({ gatewayUrl, getBearerToken });
66
+ const result = await client.authorize({ surface, actorId, context, subjectDigest });
67
+ if (!result.ok || !result.receipt) {
68
+ if (values.json) {
69
+ console.log(JSON.stringify({ ok: false, status: result.status, body: result.body }));
70
+ }
71
+ else {
72
+ console.error(`Request failed: HTTP ${result.status}`);
73
+ console.error(JSON.stringify(result.body, null, 2));
74
+ }
75
+ process.exit(1);
76
+ }
77
+ const receipt = result.receipt;
78
+ const keys = await fetchPublicKeys(gatewayUrl, getBearerToken);
79
+ const valid = verifyReceiptSignature(receipt, keys);
80
+ if (values.json) {
81
+ console.log(JSON.stringify({
82
+ ok: true,
83
+ decision: result.decision,
84
+ executionId: result.executionId,
85
+ receiptValid: valid,
86
+ receipt,
87
+ }));
88
+ }
89
+ else {
90
+ console.log(`Decision: ${result.decision}`);
91
+ console.log(`Receipt signature valid: ${valid}`);
92
+ console.log(`Execution ID: ${result.executionId ?? ""}`);
93
+ }
94
+ if (!valid)
95
+ process.exit(1);
96
+ const dec = String(result.decision || "").toUpperCase();
97
+ if (dec !== "PERMIT")
98
+ process.exit(1);
99
+ }
@@ -0,0 +1 @@
1
+ export declare function runChaos(argv: string[]): Promise<void>;
@@ -0,0 +1,35 @@
1
+ import { runChaosScenario } from "@trigguard/chaos";
2
+ function usage() {
3
+ console.error("Usage:\n" +
4
+ " trigguard chaos run engine-crash\n" +
5
+ " trigguard chaos run bundle-corruption\n" +
6
+ " trigguard chaos run network-delay\n" +
7
+ " trigguard chaos run clock-skew\n" +
8
+ " trigguard chaos run governance-corruption\n" +
9
+ " trigguard chaos run partial-rollout-failure");
10
+ process.exit(1);
11
+ }
12
+ export async function runChaos(argv) {
13
+ const sub = argv[0];
14
+ if (sub !== "run")
15
+ usage();
16
+ const scenario = argv[1];
17
+ if (!scenario)
18
+ usage();
19
+ const map = {
20
+ "engine-crash": "engine-crash",
21
+ "bundle-corruption": "bundle-corruption",
22
+ "network-delay": "network-delay",
23
+ "clock-skew": "clock-skew",
24
+ "governance-corruption": "governance-corruption",
25
+ "partial-rollout-failure": "partial-rollout-failure",
26
+ };
27
+ const sc = map[scenario];
28
+ if (!sc)
29
+ usage();
30
+ const out = await runChaosScenario(sc);
31
+ console.log(JSON.stringify(out, null, 2));
32
+ if (out.result !== "ok") {
33
+ process.exitCode = 2;
34
+ }
35
+ }
@@ -0,0 +1 @@
1
+ export declare function runDev(argv: string[]): Promise<void>;
@@ -0,0 +1,27 @@
1
+ import { parseArgs } from "node:util";
2
+ import { DEFAULT_DEV_PORT, printDevBanner, startDevAuthority } from "../dev/authority.js";
3
+ export async function runDev(argv) {
4
+ const { values } = parseArgs({
5
+ args: argv,
6
+ options: {
7
+ port: { type: "string", default: String(DEFAULT_DEV_PORT) },
8
+ swift: { type: "boolean", default: false },
9
+ },
10
+ allowPositionals: false,
11
+ strict: true,
12
+ });
13
+ const port = Math.max(1, Math.min(65535, parseInt(String(values.port), 10) || DEFAULT_DEV_PORT));
14
+ const running = await startDevAuthority({ port, preferSwift: values.swift });
15
+ printDevBanner(running);
16
+ await new Promise((resolve) => {
17
+ const onExit = () => resolve();
18
+ running.process.on("exit", onExit);
19
+ const sig = () => {
20
+ running.process.removeListener("exit", onExit);
21
+ running.process.kill("SIGTERM");
22
+ setTimeout(() => process.exit(0), 200);
23
+ };
24
+ process.on("SIGINT", sig);
25
+ process.on("SIGTERM", sig);
26
+ });
27
+ }
@@ -0,0 +1 @@
1
+ export declare function runDoctor(_argv: string[]): Promise<void>;
@@ -0,0 +1,50 @@
1
+ import { createConnection } from "node:net";
2
+ import { findMonorepoRoot } from "../dev/repoRoot.js";
3
+ import { DEFAULT_DEV_PORT, MOCK_AUTHORITY_PUBLIC_KEY_HEX } from "../dev/authority.js";
4
+ function checkPortOpen(port, host) {
5
+ return new Promise((resolve) => {
6
+ const c = createConnection({ port, host, timeout: 2000 });
7
+ c.once("connect", () => {
8
+ c.destroy();
9
+ resolve(true);
10
+ });
11
+ c.once("error", () => resolve(false));
12
+ });
13
+ }
14
+ export async function runDoctor(_argv) {
15
+ const ver = process.version;
16
+ const major = parseInt(ver.slice(1).split(".")[0] || "0", 10);
17
+ if (major < 20) {
18
+ console.log(`❌ Node ${ver} — TrigGuard CLI expects Node 20+`);
19
+ }
20
+ else {
21
+ console.log(`✅ Node ${ver}`);
22
+ }
23
+ const root = findMonorepoRoot();
24
+ if (root) {
25
+ console.log(`✅ Monorepo root: ${root}`);
26
+ }
27
+ else {
28
+ console.log("⚠️ Monorepo root not detected (no sdk/node @trigguard/decision from cwd). `trigguard dev` needs a TrigGuard clone or TRIGGUARD_MOCK_AUTHORITY_SCRIPT.");
29
+ }
30
+ const port = Math.max(1, Math.min(65535, parseInt(process.env.TRIGGUARD_DEV_PORT || String(DEFAULT_DEV_PORT), 10) || DEFAULT_DEV_PORT));
31
+ const up = await checkPortOpen(port, "127.0.0.1");
32
+ if (up) {
33
+ const http = await import("node:http");
34
+ const ok = await new Promise((resolve) => {
35
+ http
36
+ .get(`http://127.0.0.1:${port}/health`, (r) => {
37
+ resolve(r.statusCode === 200);
38
+ })
39
+ .on("error", () => resolve(false));
40
+ });
41
+ console.log(ok
42
+ ? `✅ Local authority on http://127.0.0.1:${port}/health (responding)`
43
+ : `⚠️ Port ${port} is open but /health did not return 200`);
44
+ }
45
+ else {
46
+ console.log(`ℹ️ No process listening on 127.0.0.1:${port} — run: trigguard dev (or set TRIGGUARD_DEV_PORT for doctor checks)`);
47
+ }
48
+ console.log("Fixture public key (mock authority):", MOCK_AUTHORITY_PUBLIC_KEY_HEX);
49
+ process.exit(0);
50
+ }
@@ -0,0 +1,3 @@
1
+ export declare function runLogRoot(argv: string[]): Promise<void>;
2
+ export declare function runLogInclusion(argv: string[]): Promise<void>;
3
+ export declare function runLogVerify(argv: string[]): Promise<void>;
@@ -0,0 +1,119 @@
1
+ import { parseArgs } from "node:util";
2
+ import { verifyConsistency } from "trigguard-log-core/consistencyVerifier";
3
+ function baseUrl(values) {
4
+ const raw = (typeof values["log-url"] === "string" && values["log-url"]) ||
5
+ process.env.TRIGGUARD_DECISION_LOG_URL ||
6
+ "http://127.0.0.1:3001";
7
+ return String(raw).replace(/\/$/, "");
8
+ }
9
+ function asString(value) {
10
+ if (value === null || value === undefined)
11
+ return "";
12
+ return typeof value === "string" || typeof value === "number" || typeof value === "boolean"
13
+ ? String(value)
14
+ : "";
15
+ }
16
+ export async function runLogRoot(argv) {
17
+ const { values } = parseArgs({
18
+ args: argv,
19
+ options: { "log-url": { type: "string" }, json: { type: "boolean", default: false } },
20
+ allowPositionals: true,
21
+ strict: true,
22
+ });
23
+ const res = await fetch(`${baseUrl(values)}/v1/log/root`, { headers: { Accept: "application/json" } });
24
+ if (!res.ok)
25
+ throw new Error(`log root failed: HTTP ${res.status}`);
26
+ const out = (await res.json());
27
+ if (values.json)
28
+ console.log(JSON.stringify(out));
29
+ else {
30
+ console.log(`log_id: ${asString(out.log_id)}`);
31
+ console.log(`tree_size: ${asString(out.tree_size)}`);
32
+ console.log(`root_hash: ${asString(out.root_hash)}`);
33
+ console.log(`timestamp: ${asString(out.timestamp)}`);
34
+ }
35
+ }
36
+ export async function runLogInclusion(argv) {
37
+ const { values, positionals } = parseArgs({
38
+ args: argv,
39
+ options: {
40
+ "log-url": { type: "string" },
41
+ "execution-id": { type: "string" },
42
+ "leaf-index": { type: "string" },
43
+ json: { type: "boolean", default: false },
44
+ },
45
+ allowPositionals: true,
46
+ strict: true,
47
+ });
48
+ const executionId = (typeof values["execution-id"] === "string" && values["execution-id"]) || positionals[0];
49
+ const leafIndex = typeof values["leaf-index"] === "string" ? values["leaf-index"] : undefined;
50
+ if (!executionId && !leafIndex) {
51
+ throw new Error("Usage: trigguard log inclusion (--execution-id <id> | --leaf-index <n>) [--log-url URL] [--json]");
52
+ }
53
+ const qs = new URLSearchParams();
54
+ if (executionId)
55
+ qs.set("execution_id", executionId);
56
+ if (leafIndex)
57
+ qs.set("leaf_index", leafIndex);
58
+ const res = await fetch(`${baseUrl(values)}/v1/log/inclusion?${qs.toString()}`, { headers: { Accept: "application/json" } });
59
+ if (!res.ok)
60
+ throw new Error(`log inclusion failed: HTTP ${res.status}`);
61
+ const out = (await res.json());
62
+ if (values.json)
63
+ console.log(JSON.stringify(out));
64
+ else {
65
+ console.log(`log_index: ${asString(out.log_index)}`);
66
+ console.log(`execution_id: ${asString(out.execution_id)}`);
67
+ const tree = (out.tree_head ?? {});
68
+ console.log(`tree_size: ${asString(tree.tree_size)}`);
69
+ console.log(`root_hash: ${asString(tree.root_hash)}`);
70
+ }
71
+ }
72
+ export async function runLogVerify(argv) {
73
+ const { values } = parseArgs({
74
+ args: argv,
75
+ options: {
76
+ "log-url": { type: "string" },
77
+ from: { type: "string" },
78
+ to: { type: "string" },
79
+ json: { type: "boolean", default: false },
80
+ },
81
+ allowPositionals: true,
82
+ strict: true,
83
+ });
84
+ const from = Number(values.from);
85
+ const to = Number(values.to);
86
+ if (!Number.isInteger(from) || !Number.isInteger(to) || from <= 0 || to <= from) {
87
+ throw new Error("Usage: trigguard log verify --from <oldTreeSize> --to <newTreeSize> [--log-url URL] [--json]");
88
+ }
89
+ const b = baseUrl(values);
90
+ const [consistencyRes, rootRes] = await Promise.all([
91
+ fetch(`${b}/v1/log/consistency?from=${from}&to=${to}`, { headers: { Accept: "application/json" } }),
92
+ fetch(`${b}/v1/log/root`, { headers: { Accept: "application/json" } }),
93
+ ]);
94
+ if (!consistencyRes.ok)
95
+ throw new Error(`log consistency fetch failed: HTTP ${consistencyRes.status}`);
96
+ if (!rootRes.ok)
97
+ throw new Error(`log root fetch failed: HTTP ${rootRes.status}`);
98
+ const consistency = (await consistencyRes.json());
99
+ const currentRoot = (await rootRes.json());
100
+ const hashes = Array.isArray(consistency.consistency_proof_hashes)
101
+ ? consistency.consistency_proof_hashes.map(String)
102
+ : [];
103
+ const ok = verifyConsistency({
104
+ treeSize: from,
105
+ rootHash: asString(consistency.old_root_hash),
106
+ timestamp: Date.now(),
107
+ }, {
108
+ treeSize: to,
109
+ rootHash: asString(consistency.new_root_hash) || asString(currentRoot.root_hash),
110
+ timestamp: Date.now(),
111
+ }, { hashes });
112
+ const out = { valid: ok, from, to, proof_len: hashes.length };
113
+ if (values.json)
114
+ console.log(JSON.stringify(out));
115
+ else
116
+ console.log(ok ? `OK append-only consistency verified (${from} -> ${to})` : `INVALID consistency proof (${from} -> ${to})`);
117
+ if (!ok)
118
+ process.exit(2);
119
+ }
@@ -0,0 +1 @@
1
+ export declare function runLogMonitor(argv: string[]): Promise<void>;
@@ -0,0 +1,65 @@
1
+ import { parseArgs } from "node:util";
2
+ function baseUrlFromArgs(values) {
3
+ const raw = (typeof values["log-url"] === "string" && values["log-url"]) ||
4
+ process.env.TRIGGUARD_RECEIPT_LOG_URL ||
5
+ "http://127.0.0.1:3847";
6
+ return String(raw).replace(/\/$/, "");
7
+ }
8
+ async function fetchJson(url) {
9
+ const res = await fetch(url, { headers: { Accept: "application/json" } });
10
+ if (!res.ok)
11
+ throw new Error(`HTTP ${res.status}`);
12
+ return (await res.json());
13
+ }
14
+ export async function runLogMonitor(argv) {
15
+ const { values } = parseArgs({
16
+ args: argv,
17
+ options: {
18
+ "log-url": { type: "string" },
19
+ interval: { type: "string" },
20
+ once: { type: "boolean", default: false },
21
+ json: { type: "boolean", default: false },
22
+ },
23
+ allowPositionals: true,
24
+ strict: true,
25
+ });
26
+ const base = baseUrlFromArgs(values);
27
+ const intervalMs = Math.max(1000, parseInt(String(values.interval || "5000"), 10) || 5000);
28
+ let lastRoot = "";
29
+ let lastCount = -1;
30
+ const poll = async () => {
31
+ const list = await fetchJson(`${base}/v1/receipts`);
32
+ const count = Number(list.count || 0);
33
+ if (count === 0) {
34
+ const msg = { ok: true, count, note: "no_receipts" };
35
+ console.log(values.json ? JSON.stringify(msg) : "log monitor: no receipts");
36
+ return;
37
+ }
38
+ const latestId = list.receipts[count - 1]?.receipt_id;
39
+ if (!latestId)
40
+ throw new Error("missing latest receipt id");
41
+ const proof = await fetchJson(`${base}/v1/receipts/${encodeURIComponent(latestId)}/proof`);
42
+ if (lastCount === count && lastRoot && proof.merkle_root !== lastRoot) {
43
+ throw new Error(`fork_detected count=${count} previous_root=${lastRoot} new_root=${proof.merkle_root}`);
44
+ }
45
+ lastCount = count;
46
+ lastRoot = proof.merkle_root;
47
+ const out = {
48
+ ok: true,
49
+ count,
50
+ merkle_root: proof.merkle_root,
51
+ latest_receipt_id: latestId,
52
+ latest_leaf_index: proof.leaf_index,
53
+ proof_len: Array.isArray(proof.inclusion_proof) ? proof.inclusion_proof.length : 0,
54
+ };
55
+ console.log(values.json ? JSON.stringify(out) : `log monitor: count=${count} root=${proof.merkle_root}`);
56
+ };
57
+ await poll();
58
+ if (values.once)
59
+ return;
60
+ setInterval(() => {
61
+ poll().catch((e) => {
62
+ console.error(`log monitor error: ${e instanceof Error ? e.message : String(e)}`);
63
+ });
64
+ }, intervalMs);
65
+ }
@@ -0,0 +1 @@
1
+ export declare function runLoginWeb(args: string[]): Promise<void>;
@@ -0,0 +1,80 @@
1
+ import { spawn } from "node:child_process";
2
+ import { hasFlag } from "../tg/args.js";
3
+ import { loadConfig, saveConfig } from "../cp/config.js";
4
+ import { defaultMachineLabel, ensureCliApiKey, } from "../cp/provisionCliKey.js";
5
+ import { startCliDeviceAuth, waitForCliDeviceAuth, } from "../cp/cliDeviceAuth.js";
6
+ async function openBrowser(url) {
7
+ const platform = process.platform;
8
+ let cmd;
9
+ let args;
10
+ if (platform === "darwin") {
11
+ cmd = "open";
12
+ args = [url];
13
+ }
14
+ else if (platform === "win32") {
15
+ cmd = "cmd";
16
+ args = ["/c", "start", "", url];
17
+ }
18
+ else {
19
+ cmd = "xdg-open";
20
+ args = [url];
21
+ }
22
+ await new Promise((resolve, reject) => {
23
+ const child = spawn(cmd, args, { stdio: "ignore", detached: true });
24
+ child.on("error", reject);
25
+ child.unref();
26
+ resolve();
27
+ });
28
+ }
29
+ export async function runLoginWeb(args) {
30
+ const json = hasFlag(args, "--json");
31
+ const config = loadConfig();
32
+ const machineLabel = defaultMachineLabel();
33
+ const started = await startCliDeviceAuth(config, machineLabel);
34
+ if (!json) {
35
+ console.log("Opening browser to sign in to TrigGuard...");
36
+ console.log(`If the browser does not open, visit:\n ${started.verificationUrl}`);
37
+ console.log(`Enter code: ${started.userCode}`);
38
+ }
39
+ try {
40
+ await openBrowser(started.verificationUrl);
41
+ }
42
+ catch {
43
+ if (!json) {
44
+ console.log("Could not launch a browser automatically. Use the URL above.");
45
+ }
46
+ }
47
+ const complete = await waitForCliDeviceAuth(config, started.deviceCode, started.expiresIn);
48
+ const orgId = complete.activeOrgId;
49
+ if (!orgId) {
50
+ throw new Error("No workspace available after login. Verify email in console, then retry.");
51
+ }
52
+ const key = await ensureCliApiKey({ ...config, sessionToken: complete.token }, orgId, complete.rawApiKey, machineLabel, complete.apiKeyId);
53
+ saveConfig({
54
+ version: 1,
55
+ controlPlaneUrl: config.controlPlaneUrl,
56
+ environment: config.environment,
57
+ sessionToken: complete.token,
58
+ activeOrgId: orgId,
59
+ user: { id: complete.user.id, email: complete.user.email },
60
+ apiKey: key.rawKey,
61
+ apiKeyId: key.keyId,
62
+ apiKeyDisplayName: key.displayName,
63
+ });
64
+ if (json) {
65
+ console.log(JSON.stringify({
66
+ ok: true,
67
+ user: complete.user,
68
+ activeOrgId: orgId,
69
+ orgName: complete.orgName,
70
+ apiKeyConfigured: true,
71
+ apiKeySource: "config",
72
+ }, null, 2));
73
+ return;
74
+ }
75
+ console.log(`Signed in as ${complete.user.email}`);
76
+ console.log(`Workspace: ${complete.orgName ?? orgId}`);
77
+ console.log("API key configured for this machine.");
78
+ console.log("");
79
+ console.log("Next: tg authorize --surface deploy.release --actor demo --intent \"test deploy\"");
80
+ }
@@ -0,0 +1,10 @@
1
+ export declare function runPolicySync(argv: string[]): Promise<void>;
2
+ export declare function runPolicyState(): Promise<void>;
3
+ export declare function runPolicyVerifyManifest(argv: string[]): Promise<void>;
4
+ export declare function runPolicyRequestChange(argv: string[]): Promise<void>;
5
+ export declare function runPolicyApprove(argv: string[]): Promise<void>;
6
+ export declare function runPolicyReject(argv: string[]): Promise<void>;
7
+ export declare function runPolicySetWindow(argv: string[]): Promise<void>;
8
+ export declare function runPolicyBreakGlass(argv: string[]): Promise<void>;
9
+ export declare function runPolicyBreakGlassEnd(): Promise<void>;
10
+ export declare function runPolicyPending(): Promise<void>;