@fiscalmindset/blindfold 0.4.2 β†’ 0.4.4

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.
@@ -16,6 +16,7 @@ import { runCompat } from "../src/compat.ts";
16
16
  import { defaultSealedLogPath, readSealed, verifyLedgerChain } from "../src/sealed-ledger.ts";
17
17
  import { type Argv, die, assetPath, fingerprint, resolveEnvVar } from "./cli-shared.ts";
18
18
  import { c, ok, bad, warn, head } from "../src/color.ts";
19
+ import { boxLines, rule } from "../src/tui.ts";
19
20
 
20
21
  export async function handleEnclave(cmd: string, argv: Argv, cmdArgs: string[]): Promise<void> {
21
22
  switch (cmd) {
@@ -32,15 +33,19 @@ export async function handleEnclave(cmd: string, argv: Argv, cmdArgs: string[]):
32
33
  const b = await client.getBalance();
33
34
  const tok = (n: number) => (n / BASE).toLocaleString(undefined, { maximumFractionDigits: 6 });
34
35
  if (argv.flags.json) { console.log(JSON.stringify(b, null, 2)); return; }
35
- console.log(head(`πŸ’³ Terminal 3 credit`) + c.gray(` β€” ${env.did || "(no tenant)"} (${env.mock ? "MOCK" : env.t3Env})`));
36
- console.log(` available: ${c.bold(tok(b.available) + " tokens")} ${c.gray("(" + b.available.toLocaleString() + " base units)")}`);
37
- console.log(` reserved: ${b.reserved.toLocaleString()} base units`);
38
- console.log(` status: ${b.creditExhausted ? warn("⚠ EXHAUSTED") : ok("βœ… ok")}`);
36
+ const lines = [
37
+ c.gray(`${env.did || "(no tenant)"} Β· ${env.mock ? "MOCK" : env.t3Env}`),
38
+ "",
39
+ `available: ${c.bold(c.green(tok(b.available) + " tokens"))} ${c.gray("(" + b.available.toLocaleString() + " base units)")}`,
40
+ `reserved: ${b.reserved.toLocaleString()} base units`,
41
+ `status: ${b.creditExhausted ? warn("⚠ EXHAUSTED") : ok("βœ… ok")}`,
42
+ ];
39
43
  if (b.creditExhausted) {
40
- console.log(` Top up testnet credits, then re-check with \`blindfold credit\`:`);
41
- console.log(` https://docs.terminal3.io/developers/adk/get-started/prerequisites/request-test-tokens`);
44
+ lines.push("", c.yellow("Top up testnet credits, then re-check with `blindfold credit`:"),
45
+ c.cyan(" https://docs.terminal3.io/developers/adk/get-started/prerequisites/request-test-tokens"));
42
46
  process.exitCode = 1;
43
47
  }
48
+ console.log(boxLines("πŸ’³ Terminal 3 credit", lines));
44
49
  } finally {
45
50
  await client.close();
46
51
  }
@@ -136,9 +141,10 @@ export async function handleEnclave(cmd: string, argv: Argv, cmdArgs: string[]):
136
141
  console.log(`Seal one with: blindfold register --name <KV_KEY>`);
137
142
  return;
138
143
  }
139
- console.log(head("πŸ” Sealed keys") + c.gray(` (source: ${defaultSealedLogPath()})`) + "\n");
140
- console.log(" WHEN NAME BYTES MODE WHERE");
141
- console.log(" ──── ──── ───── ──── ─────");
144
+ console.log(boxLines("πŸ” Sealed keys", [c.gray(`source: ${defaultSealedLogPath()}`)]));
145
+ console.log("");
146
+ console.log(c.gray(" WHEN NAME BYTES MODE WHERE"));
147
+ console.log(c.gray(" ──── ──── ───── ──── ─────"));
142
148
  for (const e of entries) {
143
149
  const when = e.t.replace("T", " ").slice(0, 19);
144
150
  const where = e.map_name.length > 60 ? e.map_name.slice(0, 57) + "…" : e.map_name;
@@ -150,9 +156,10 @@ export async function handleEnclave(cmd: string, argv: Argv, cmdArgs: string[]):
150
156
  case "audit": {
151
157
  // (1) Tamper-evidence: verify the local ledger's hash-chain.
152
158
  // (2) Reconcile against the enclave β€” the actual source of truth.
153
- console.log("πŸ” Blindfold audit\n");
159
+ console.log(boxLines("πŸ” Blindfold audit", [c.gray("ledger hash-chain + reconciliation against the enclave")]));
160
+ console.log("");
154
161
  const chain = verifyLedgerChain();
155
- console.log(" 1. Ledger integrity (tamper-evidence)");
162
+ console.log(c.bold(" 1. Ledger integrity (tamper-evidence)"));
156
163
  if (chain.total === 0) {
157
164
  console.log(" (ledger is empty)");
158
165
  } else if (chain.ok) {
@@ -203,44 +210,46 @@ export async function handleEnclave(cmd: string, argv: Argv, cmdArgs: string[]):
203
210
  case "status": {
204
211
  // One-glance overview: health + sealed inventory + what to do next.
205
212
  const env = loadBlindfoldEnv();
206
- console.log(head("πŸ›‘οΈ Blindfold status") + "\n");
207
- console.log(` mode: ${env.mock ? "MOCK (BLINDFOLD_MOCK=1)" : "REAL"} Β· T3 env: ${env.t3Env}`);
208
- if (env.t3BaseUrl) console.log(` node: ${env.t3BaseUrl} (override)`);
213
+ const lines: string[] = [];
214
+ lines.push(`mode: ${env.mock ? "MOCK (BLINDFOLD_MOCK=1)" : "REAL"} Β· T3 env: ${env.t3Env}`);
215
+ if (env.t3BaseUrl) lines.push(`node: ${env.t3BaseUrl} (override)`);
209
216
  if (!env.mock) {
210
217
  try {
211
218
  const { openT3Client } = await import("../src/t3-client.ts");
212
219
  const client = await openT3Client(env);
213
220
  const info = await client.me();
214
- console.log(` tenant: ${ok("βœ… " + info.tenant)} (status=${info.status ?? "?"})`);
221
+ lines.push(`tenant: ${ok("βœ… " + info.tenant)} (status=${info.status ?? "?"})`);
215
222
  } catch (e) {
216
- console.log(` tenant: ${bad("βœ– " + (e as Error).message.slice(0, 90))}`);
217
- console.log(` β†’ run \`blindfold doctor\` for a full diagnosis.`);
223
+ lines.push(`tenant: ${bad("βœ– " + (e as Error).message.slice(0, 90))}`);
224
+ lines.push(` ${c.gray("β†’ run `blindfold doctor` for a full diagnosis.")}`);
218
225
  process.exitCode = 1;
219
226
  }
220
227
  }
221
228
  const entries = readSealed();
222
229
  const latest = new Map<string, (typeof entries)[number]>();
223
230
  for (const e of entries) latest.set(e.name, e); // last write wins
224
- console.log(`\n Sealed secrets (${latest.size}):`);
231
+ lines.push("", c.bold(`Sealed secrets (${latest.size})`));
225
232
  if (latest.size === 0) {
226
- console.log(` (none yet) seal one: blindfold register --name <X> --from-env <X>`);
233
+ lines.push(` ${c.gray("(none yet) seal one:")} ${c.cyan("blindfold register --name <X>")}`);
227
234
  } else {
228
235
  for (const e of latest.values()) {
229
- console.log(` β€’ ${e.name.padEnd(22)} ${String(e.length).padStart(4)} B ${e.mode}`);
236
+ lines.push(` ${c.green("β€’")} ${c.cyan(e.name.padEnd(22))} ${String(e.length).padStart(4)} B ${c.gray(e.mode)}`);
230
237
  }
231
238
  }
232
- console.log(`\n Next: blindfold use --name <secret> -- <command> (use it, no code)`);
239
+ lines.push("", c.gray("Next: ") + c.cyan("blindfold use --name <secret> -- <command>") + c.gray(" (use it, no code)"));
240
+ console.log(boxLines("πŸ›‘οΈ Blindfold status", lines));
233
241
  return;
234
242
  }
235
243
  case "doctor": {
236
244
  const env = loadBlindfoldEnv();
237
- console.log(head("🩺 Blindfold doctor"));
238
- console.log(` mode: ${env.mock ? "MOCK (BLINDFOLD_MOCK=1)" : "REAL (T3)"}`);
239
- console.log(` T3N_API_KEY set: ${env.t3nApiKey ? "yes" : "NO βœ–"}`);
240
- console.log(` DID set: ${env.did ? "yes" : "NO βœ–"}`);
241
- console.log(` T3 environment: ${env.t3Env}`);
242
- console.log(` node URL: ${env.t3BaseUrl || `(SDK default for ${env.t3Env})`}`);
243
- console.log(` default proxy port: ${env.port}`);
245
+ console.log(boxLines("🩺 Blindfold doctor", [
246
+ `mode: ${env.mock ? "MOCK (BLINDFOLD_MOCK=1)" : "REAL (T3)"}`,
247
+ `T3N_API_KEY: ${env.t3nApiKey ? ok("set") : bad("NOT set βœ–")}`,
248
+ `DID: ${env.did ? ok("set") : bad("NOT set βœ–")}`,
249
+ `T3 env: ${env.t3Env}`,
250
+ `node URL: ${env.t3BaseUrl || c.gray(`(SDK default for ${env.t3Env})`)}`,
251
+ `proxy port: ${env.port}`,
252
+ ]));
244
253
  if (!env.mock && (!env.t3nApiKey || !env.did)) {
245
254
  console.log("");
246
255
  console.log(` ⚠ REAL mode is selected but credentials are missing.`);
@@ -254,8 +263,7 @@ export async function handleEnclave(cmd: string, argv: Argv, cmdArgs: string[]):
254
263
  // LIVE check: authenticate, then read the tenant behind the key. This is
255
264
  // what catches the painful "key authenticates but has no tenant" case,
256
265
  // which the server otherwise reports only as a bare HTTP 500.
257
- console.log("");
258
- console.log(" Live check (handshake + authenticate + me) …");
266
+ console.log("\n" + rule("Live check β€” handshake + authenticate + me"));
259
267
  const { openT3Client } = await import("../src/t3-client.ts");
260
268
  let client;
261
269
  try {
package/dist/cli.mjs CHANGED
@@ -1309,6 +1309,7 @@ var c = {
1309
1309
  cyan: wrap("36"),
1310
1310
  gray: wrap("90")
1311
1311
  };
1312
+ var colorOn = on;
1312
1313
  var ok = (s) => c.green(s);
1313
1314
  var bad = (s) => c.red(s);
1314
1315
  var warn = (s) => c.yellow(s);
@@ -1434,12 +1435,31 @@ function boxLines(title, lines) {
1434
1435
  const w = termWidth();
1435
1436
  const inner = w - 4;
1436
1437
  const prefix = B.tl + B.h + " ";
1437
- const fill = Math.max(0, w - vlen(prefix) - vlen(title) - 2);
1438
- const out = [dim(prefix) + c.bold(title) + dim(" " + B.h.repeat(fill) + B.tr)];
1439
- for (const l of lines) out.push(dim(B.v) + " " + pad(l, inner) + " " + dim(B.v));
1438
+ const fill = Math.max(0, w - vlen(prefix) - vlen(clip(title, inner)) - 2);
1439
+ const out = [dim(prefix) + c.bold(clip(title, inner)) + dim(" " + B.h.repeat(fill) + B.tr)];
1440
+ for (const l of lines) out.push(dim(B.v) + " " + pad(clip(l, inner), inner) + " " + dim(B.v));
1440
1441
  out.push(dim(B.bl + B.h.repeat(w - 2) + B.br));
1441
1442
  return out.join("\n");
1442
1443
  }
1444
+ function clip(s, width) {
1445
+ if (vlen(s) <= width) return s;
1446
+ let out = "";
1447
+ let wSoFar = 0;
1448
+ for (const part of s.split(/(\x1b\[[0-9;]*m)/)) {
1449
+ if (part.startsWith("\x1B")) {
1450
+ out += part;
1451
+ continue;
1452
+ }
1453
+ for (const ch of part) {
1454
+ const cp = ch.codePointAt(0) ?? 0;
1455
+ const cw = cp === 8205 || cp >= 65024 && cp <= 65039 || cp >= 768 && cp <= 879 ? 0 : isWide(cp) ? 2 : 1;
1456
+ if (wSoFar + cw > width - 1) return out + "\u2026" + (colorOn ? "\x1B[0m" : "");
1457
+ out += ch;
1458
+ wSoFar += cw;
1459
+ }
1460
+ }
1461
+ return out + (colorOn ? "\x1B[0m" : "");
1462
+ }
1443
1463
  function rule(label = "") {
1444
1464
  const w = termWidth();
1445
1465
  if (!label) return dim(B.h.repeat(w));
@@ -1611,19 +1631,20 @@ var COMMANDS = [
1611
1631
  name: "grant",
1612
1632
  group: "\u{1F465} Team & sharing",
1613
1633
  usage: "--host <host>[,<host2>...]",
1614
- summary: "Authorize the contract to call these hosts (required before proxy/in-enclave can reach them).",
1615
- flags: [["--host <host,...>", "Comma-separated egress hosts to allow (additive)."]],
1616
- examples: ["blindfold grant --host api.openai.com", "blindfold grant --host api.github.com,api.stripe.com"]
1634
+ summary: "Allow the enclave to reach an API's server. Do this once per API before the proxy can call it \u2014 e.g. allow api.openai.com so a sealed OpenAI key can be used.",
1635
+ flags: [["--host <host,...>", "The API hostname(s) to allow, comma-separated. Each grant adds to the list."]],
1636
+ examples: ["blindfold grant --host api.openai.com", "blindfold grant --host api.github.com,api.stripe.com"],
1637
+ notes: "Why it's needed: the enclave refuses to call any host you haven't allowed (deny-by-default). Grant the host of each API whose key you've sealed."
1617
1638
  },
1618
1639
  {
1619
1640
  name: "share",
1620
1641
  group: "\u{1F465} Team & sharing",
1621
1642
  usage: "--to <agent-did> --host <host>[,...]",
1622
- summary: "Let a teammate's agent USE your sealed keys for a host \u2014 forward only, never the plaintext.",
1623
- flags: [["--to <agent-did>", "The teammate's agent DID to authorize."], ["--host <host,...>", "Hosts they may reach through the enclave."]],
1624
- examples: ["blindfold share --to did:t3n:\u2026 --host api.openai.com"]
1643
+ summary: "Let a teammate use your sealed key for an API \u2014 they can make calls but never see the key itself.",
1644
+ flags: [["--to <agent-did>", "The teammate's agent DID (their Terminal 3 identity)."], ["--host <host,...>", "Which API hosts they're allowed to reach through your enclave."]],
1645
+ examples: ["blindfold share --to did:t3n:abc\u2026 --host api.openai.com"]
1625
1646
  },
1626
- { name: "revoke", group: "\u{1F465} Team & sharing", usage: "--to <agent-did>", summary: "Remove a teammate's access \u2014 immediate and complete.", flags: [["--to <agent-did>", "The teammate's agent DID to revoke."]], examples: ["blindfold revoke --to did:t3n:\u2026"] },
1647
+ { name: "revoke", group: "\u{1F465} Team & sharing", usage: "--to <agent-did>", summary: "Take back a teammate's access you granted with `share` \u2014 immediate and complete.", flags: [["--to <agent-did>", "The teammate's agent DID to cut off."]], examples: ["blindfold revoke --to did:t3n:abc\u2026"] },
1627
1648
  // ── Enclave & admin ──────────────────────────────────────────────────────
1628
1649
  { name: "publish", group: "\u{1F4E6} Enclave & admin", usage: "[--wasm <path>]", summary: "Publish the Rust\u2192WASM contract to your tenant (one-time).", flags: [["--wasm <path>", "Path to blindfold_proxy.wasm (defaults to the bundled build)."]], examples: ["blindfold publish"] },
1629
1650
  { name: "status", group: "\u{1F4E6} Enclave & admin", usage: "", summary: "One-glance overview: mode, tenant health, and the list of sealed secrets.", examples: ["blindfold status"] },
@@ -1673,27 +1694,29 @@ function renderMainHelp() {
1673
1694
  ];
1674
1695
  const nameW = Math.min(12, COMMANDS.reduce((m, cmd) => Math.max(m, vlen(cmd.name)), 0));
1675
1696
  const descW = Math.max(16, w - 4 - nameW - 2);
1697
+ const fit = (s, max) => s.length > max ? s.slice(0, Math.max(1, max - 1)) + "\u2026" : s;
1676
1698
  for (const group of GROUP_ORDER) {
1677
1699
  const cmds = COMMANDS.filter((cmd) => cmd.group === group);
1678
1700
  const lines = [];
1679
- for (const cmd of cmds) {
1701
+ for (const [idx, cmd] of cmds.entries()) {
1702
+ if (idx > 0) lines.push("");
1680
1703
  const dl = wrapText(cmd.summary, descW);
1681
- lines.push(pad(c.cyan(cmd.name), nameW) + " " + (dl[0] ?? ""));
1682
- for (let i = 1; i < dl.length; i++) lines.push(pad("", nameW) + " " + c.gray(dl[i] ?? ""));
1683
- if (cmd.usage) {
1684
- lines.push(pad("", nameW) + " " + c.gray("\u21B3 blindfold " + cmd.name + " " + cmd.usage));
1685
- }
1704
+ lines.push(pad(c.bold(c.cyan(cmd.name)), nameW) + " " + (dl[0] ?? ""));
1705
+ for (let i = 1; i < dl.length; i++) lines.push(pad("", nameW) + " " + (dl[i] ?? ""));
1706
+ const ex = fit(cmd.examples?.[0] ?? `blindfold ${cmd.name}`, descW - 5);
1707
+ lines.push(pad("", nameW) + " " + c.gray("e.g. ") + c.green(ex));
1686
1708
  }
1687
1709
  out.push(boxLines(group, lines));
1688
1710
  out.push("");
1689
1711
  }
1690
1712
  out.push(
1691
- c.bold("Quick start"),
1692
- ` ${c.cyan("blindfold signup --email you@x.com")} ${c.gray("# create a funded testnet tenant")}`,
1693
- ` ${c.cyan("blindfold register --name openai_api_key")}`,
1694
- ` ${c.cyan("blindfold proxy")} ${c.gray("# point your agent at http://127.0.0.1:8787")}`,
1713
+ c.bold(c.magenta("\u25B6 Quick start")),
1714
+ ` ${c.green("blindfold signup --email you@x.com")} ${c.gray("# create a funded testnet tenant")}`,
1715
+ ` ${c.green("blindfold register --name openai_api_key")} ${c.gray("# seal a key (hidden prompt)")}`,
1716
+ ` ${c.green("blindfold proxy")} ${c.gray("# point your agent at http://127.0.0.1:8787")}`,
1695
1717
  "",
1696
- `${c.gray("Details for any command:")} ${c.cyan("blindfold <command> --help")} ${c.gray("\xB7")} ${c.gray("Docs:")} ${c.cyan("npmjs.com/package/@fiscalmindset/blindfold")}`,
1718
+ `${c.gray("Full flags + more examples for any command:")} ${c.cyan("blindfold <command> --help")}`,
1719
+ `${c.gray("Docs:")} ${c.cyan("npmjs.com/package/@fiscalmindset/blindfold")}`,
1697
1720
  ""
1698
1721
  );
1699
1722
  return out.join("\n");
@@ -4245,15 +4268,22 @@ async function handleEnclave(cmd, argv, cmdArgs) {
4245
4268
  console.log(JSON.stringify(b, null, 2));
4246
4269
  return;
4247
4270
  }
4248
- console.log(head(`\u{1F4B3} Terminal 3 credit`) + c.gray(` \u2014 ${env.did || "(no tenant)"} (${env.mock ? "MOCK" : env.t3Env})`));
4249
- console.log(` available: ${c.bold(tok(b.available) + " tokens")} ${c.gray("(" + b.available.toLocaleString() + " base units)")}`);
4250
- console.log(` reserved: ${b.reserved.toLocaleString()} base units`);
4251
- console.log(` status: ${b.creditExhausted ? warn("\u26A0 EXHAUSTED") : ok("\u2705 ok")}`);
4271
+ const lines = [
4272
+ c.gray(`${env.did || "(no tenant)"} \xB7 ${env.mock ? "MOCK" : env.t3Env}`),
4273
+ "",
4274
+ `available: ${c.bold(c.green(tok(b.available) + " tokens"))} ${c.gray("(" + b.available.toLocaleString() + " base units)")}`,
4275
+ `reserved: ${b.reserved.toLocaleString()} base units`,
4276
+ `status: ${b.creditExhausted ? warn("\u26A0 EXHAUSTED") : ok("\u2705 ok")}`
4277
+ ];
4252
4278
  if (b.creditExhausted) {
4253
- console.log(` Top up testnet credits, then re-check with \`blindfold credit\`:`);
4254
- console.log(` https://docs.terminal3.io/developers/adk/get-started/prerequisites/request-test-tokens`);
4279
+ lines.push(
4280
+ "",
4281
+ c.yellow("Top up testnet credits, then re-check with `blindfold credit`:"),
4282
+ c.cyan(" https://docs.terminal3.io/developers/adk/get-started/prerequisites/request-test-tokens")
4283
+ );
4255
4284
  process.exitCode = 1;
4256
4285
  }
4286
+ console.log(boxLines("\u{1F4B3} Terminal 3 credit", lines));
4257
4287
  } finally {
4258
4288
  await client.close();
4259
4289
  }
@@ -4345,9 +4375,10 @@ async function handleEnclave(cmd, argv, cmdArgs) {
4345
4375
  console.log(`Seal one with: blindfold register --name <KV_KEY>`);
4346
4376
  return;
4347
4377
  }
4348
- console.log(head("\u{1F510} Sealed keys") + c.gray(` (source: ${defaultSealedLogPath()})`) + "\n");
4349
- console.log(" WHEN NAME BYTES MODE WHERE");
4350
- console.log(" \u2500\u2500\u2500\u2500 \u2500\u2500\u2500\u2500 \u2500\u2500\u2500\u2500\u2500 \u2500\u2500\u2500\u2500 \u2500\u2500\u2500\u2500\u2500");
4378
+ console.log(boxLines("\u{1F510} Sealed keys", [c.gray(`source: ${defaultSealedLogPath()}`)]));
4379
+ console.log("");
4380
+ console.log(c.gray(" WHEN NAME BYTES MODE WHERE"));
4381
+ console.log(c.gray(" \u2500\u2500\u2500\u2500 \u2500\u2500\u2500\u2500 \u2500\u2500\u2500\u2500\u2500 \u2500\u2500\u2500\u2500 \u2500\u2500\u2500\u2500\u2500"));
4351
4382
  for (const e of entries) {
4352
4383
  const when = e.t.replace("T", " ").slice(0, 19);
4353
4384
  const where = e.map_name.length > 60 ? e.map_name.slice(0, 57) + "\u2026" : e.map_name;
@@ -4357,9 +4388,10 @@ async function handleEnclave(cmd, argv, cmdArgs) {
4357
4388
  return;
4358
4389
  }
4359
4390
  case "audit": {
4360
- console.log("\u{1F50D} Blindfold audit\n");
4391
+ console.log(boxLines("\u{1F50D} Blindfold audit", [c.gray("ledger hash-chain + reconciliation against the enclave")]));
4392
+ console.log("");
4361
4393
  const chain = verifyLedgerChain();
4362
- console.log(" 1. Ledger integrity (tamper-evidence)");
4394
+ console.log(c.bold(" 1. Ledger integrity (tamper-evidence)"));
4363
4395
  if (chain.total === 0) {
4364
4396
  console.log(" (ledger is empty)");
4365
4397
  } else if (chain.ok) {
@@ -4410,46 +4442,46 @@ async function handleEnclave(cmd, argv, cmdArgs) {
4410
4442
  }
4411
4443
  case "status": {
4412
4444
  const env = loadBlindfoldEnv();
4413
- console.log(head("\u{1F6E1}\uFE0F Blindfold status") + "\n");
4414
- console.log(` mode: ${env.mock ? "MOCK (BLINDFOLD_MOCK=1)" : "REAL"} \xB7 T3 env: ${env.t3Env}`);
4415
- if (env.t3BaseUrl) console.log(` node: ${env.t3BaseUrl} (override)`);
4445
+ const lines = [];
4446
+ lines.push(`mode: ${env.mock ? "MOCK (BLINDFOLD_MOCK=1)" : "REAL"} \xB7 T3 env: ${env.t3Env}`);
4447
+ if (env.t3BaseUrl) lines.push(`node: ${env.t3BaseUrl} (override)`);
4416
4448
  if (!env.mock) {
4417
4449
  try {
4418
4450
  const { openT3Client: openT3Client2 } = await Promise.resolve().then(() => (init_t3_client(), t3_client_exports));
4419
4451
  const client = await openT3Client2(env);
4420
4452
  const info2 = await client.me();
4421
- console.log(` tenant: ${ok("\u2705 " + info2.tenant)} (status=${info2.status ?? "?"})`);
4453
+ lines.push(`tenant: ${ok("\u2705 " + info2.tenant)} (status=${info2.status ?? "?"})`);
4422
4454
  } catch (e) {
4423
- console.log(` tenant: ${bad("\u2716 " + e.message.slice(0, 90))}`);
4424
- console.log(` \u2192 run \`blindfold doctor\` for a full diagnosis.`);
4455
+ lines.push(`tenant: ${bad("\u2716 " + e.message.slice(0, 90))}`);
4456
+ lines.push(` ${c.gray("\u2192 run `blindfold doctor` for a full diagnosis.")}`);
4425
4457
  process.exitCode = 1;
4426
4458
  }
4427
4459
  }
4428
4460
  const entries = readSealed();
4429
4461
  const latest = /* @__PURE__ */ new Map();
4430
4462
  for (const e of entries) latest.set(e.name, e);
4431
- console.log(`
4432
- Sealed secrets (${latest.size}):`);
4463
+ lines.push("", c.bold(`Sealed secrets (${latest.size})`));
4433
4464
  if (latest.size === 0) {
4434
- console.log(` (none yet) seal one: blindfold register --name <X> --from-env <X>`);
4465
+ lines.push(` ${c.gray("(none yet) seal one:")} ${c.cyan("blindfold register --name <X>")}`);
4435
4466
  } else {
4436
4467
  for (const e of latest.values()) {
4437
- console.log(` \u2022 ${e.name.padEnd(22)} ${String(e.length).padStart(4)} B ${e.mode}`);
4468
+ lines.push(` ${c.green("\u2022")} ${c.cyan(e.name.padEnd(22))} ${String(e.length).padStart(4)} B ${c.gray(e.mode)}`);
4438
4469
  }
4439
4470
  }
4440
- console.log(`
4441
- Next: blindfold use --name <secret> -- <command> (use it, no code)`);
4471
+ lines.push("", c.gray("Next: ") + c.cyan("blindfold use --name <secret> -- <command>") + c.gray(" (use it, no code)"));
4472
+ console.log(boxLines("\u{1F6E1}\uFE0F Blindfold status", lines));
4442
4473
  return;
4443
4474
  }
4444
4475
  case "doctor": {
4445
4476
  const env = loadBlindfoldEnv();
4446
- console.log(head("\u{1FA7A} Blindfold doctor"));
4447
- console.log(` mode: ${env.mock ? "MOCK (BLINDFOLD_MOCK=1)" : "REAL (T3)"}`);
4448
- console.log(` T3N_API_KEY set: ${env.t3nApiKey ? "yes" : "NO \u2716"}`);
4449
- console.log(` DID set: ${env.did ? "yes" : "NO \u2716"}`);
4450
- console.log(` T3 environment: ${env.t3Env}`);
4451
- console.log(` node URL: ${env.t3BaseUrl || `(SDK default for ${env.t3Env})`}`);
4452
- console.log(` default proxy port: ${env.port}`);
4477
+ console.log(boxLines("\u{1FA7A} Blindfold doctor", [
4478
+ `mode: ${env.mock ? "MOCK (BLINDFOLD_MOCK=1)" : "REAL (T3)"}`,
4479
+ `T3N_API_KEY: ${env.t3nApiKey ? ok("set") : bad("NOT set \u2716")}`,
4480
+ `DID: ${env.did ? ok("set") : bad("NOT set \u2716")}`,
4481
+ `T3 env: ${env.t3Env}`,
4482
+ `node URL: ${env.t3BaseUrl || c.gray(`(SDK default for ${env.t3Env})`)}`,
4483
+ `proxy port: ${env.port}`
4484
+ ]));
4453
4485
  if (!env.mock && (!env.t3nApiKey || !env.did)) {
4454
4486
  console.log("");
4455
4487
  console.log(` \u26A0 REAL mode is selected but credentials are missing.`);
@@ -4459,8 +4491,7 @@ async function handleEnclave(cmd, argv, cmdArgs) {
4459
4491
  return;
4460
4492
  }
4461
4493
  if (env.mock) return;
4462
- console.log("");
4463
- console.log(" Live check (handshake + authenticate + me) \u2026");
4494
+ console.log("\n" + rule("Live check \u2014 handshake + authenticate + me"));
4464
4495
  const { openT3Client: openT3Client2 } = await Promise.resolve().then(() => (init_t3_client(), t3_client_exports));
4465
4496
  let client;
4466
4497
  try {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fiscalmindset/blindfold",
3
- "version": "0.4.2",
3
+ "version": "0.4.4",
4
4
  "type": "module",
5
5
  "description": "Make any AI agent's API keys un-leakable, by sealing them inside a Terminal 3 TDX enclave.",
6
6
  "license": "MIT",
package/src/help.ts CHANGED
@@ -139,17 +139,18 @@ export const COMMANDS: CmdDef[] = [
139
139
  // ── Team & sharing ───────────────────────────────────────────────────────
140
140
  {
141
141
  name: "grant", group: "πŸ‘₯ Team & sharing", usage: "--host <host>[,<host2>...]",
142
- summary: "Authorize the contract to call these hosts (required before proxy/in-enclave can reach them).",
143
- flags: [["--host <host,...>", "Comma-separated egress hosts to allow (additive)."]],
142
+ summary: "Allow the enclave to reach an API's server. Do this once per API before the proxy can call it β€” e.g. allow api.openai.com so a sealed OpenAI key can be used.",
143
+ flags: [["--host <host,...>", "The API hostname(s) to allow, comma-separated. Each grant adds to the list."]],
144
144
  examples: ["blindfold grant --host api.openai.com", "blindfold grant --host api.github.com,api.stripe.com"],
145
+ notes: "Why it's needed: the enclave refuses to call any host you haven't allowed (deny-by-default). Grant the host of each API whose key you've sealed.",
145
146
  },
146
147
  {
147
148
  name: "share", group: "πŸ‘₯ Team & sharing", usage: "--to <agent-did> --host <host>[,...]",
148
- summary: "Let a teammate's agent USE your sealed keys for a host β€” forward only, never the plaintext.",
149
- flags: [["--to <agent-did>", "The teammate's agent DID to authorize."], ["--host <host,...>", "Hosts they may reach through the enclave."]],
150
- examples: ["blindfold share --to did:t3n:… --host api.openai.com"],
149
+ summary: "Let a teammate use your sealed key for an API β€” they can make calls but never see the key itself.",
150
+ flags: [["--to <agent-did>", "The teammate's agent DID (their Terminal 3 identity)."], ["--host <host,...>", "Which API hosts they're allowed to reach through your enclave."]],
151
+ examples: ["blindfold share --to did:t3n:abc… --host api.openai.com"],
151
152
  },
152
- { name: "revoke", group: "πŸ‘₯ Team & sharing", usage: "--to <agent-did>", summary: "Remove a teammate's access β€” immediate and complete.", flags: [["--to <agent-did>", "The teammate's agent DID to revoke."]], examples: ["blindfold revoke --to did:t3n:…"] },
153
+ { name: "revoke", group: "πŸ‘₯ Team & sharing", usage: "--to <agent-did>", summary: "Take back a teammate's access you granted with `share` β€” immediate and complete.", flags: [["--to <agent-did>", "The teammate's agent DID to cut off."]], examples: ["blindfold revoke --to did:t3n:abc…"] },
153
154
 
154
155
  // ── Enclave & admin ──────────────────────────────────────────────────────
155
156
  { name: "publish", group: "πŸ“¦ Enclave & admin", usage: "[--wasm <path>]", summary: "Publish the Rustβ†’WASM contract to your tenant (one-time).", flags: [["--wasm <path>", "Path to blindfold_proxy.wasm (defaults to the bundled build)."]], examples: ["blindfold publish"] },
@@ -203,29 +204,33 @@ export function renderMainHelp(): string {
203
204
 
204
205
  const nameW = Math.min(12, COMMANDS.reduce((m, cmd) => Math.max(m, vlen(cmd.name)), 0));
205
206
  const descW = Math.max(16, w - 4 - nameW - 2);
207
+ const fit = (s: string, max: number): string => (s.length > max ? s.slice(0, Math.max(1, max - 1)) + "…" : s);
206
208
 
207
209
  for (const group of GROUP_ORDER) {
208
210
  const cmds = COMMANDS.filter((cmd) => cmd.group === group);
209
211
  const lines: string[] = [];
210
- for (const cmd of cmds) {
212
+ for (const [idx, cmd] of cmds.entries()) {
213
+ if (idx > 0) lines.push(""); // breathing room between commands
211
214
  const dl = wrapText(cmd.summary, descW);
212
- lines.push(pad(c.cyan(cmd.name), nameW) + " " + (dl[0] ?? ""));
213
- for (let i = 1; i < dl.length; i++) lines.push(pad("", nameW) + " " + c.gray(dl[i] ?? ""));
214
- if (cmd.usage) {
215
- lines.push(pad("", nameW) + " " + c.gray("↳ blindfold " + cmd.name + " " + cmd.usage));
216
- }
215
+ // command name (bright) + summary
216
+ lines.push(pad(c.bold(c.cyan(cmd.name)), nameW) + " " + (dl[0] ?? ""));
217
+ for (let i = 1; i < dl.length; i++) lines.push(pad("", nameW) + " " + (dl[i] ?? ""));
218
+ // one concrete example β€” the fastest way to "get" the command
219
+ const ex = fit(cmd.examples?.[0] ?? `blindfold ${cmd.name}`, descW - 5);
220
+ lines.push(pad("", nameW) + " " + c.gray("e.g. ") + c.green(ex));
217
221
  }
218
222
  out.push(boxLines(group, lines));
219
223
  out.push("");
220
224
  }
221
225
 
222
226
  out.push(
223
- c.bold("Quick start"),
224
- ` ${c.cyan("blindfold signup --email you@x.com")} ${c.gray("# create a funded testnet tenant")}`,
225
- ` ${c.cyan("blindfold register --name openai_api_key")}`,
226
- ` ${c.cyan("blindfold proxy")} ${c.gray("# point your agent at http://127.0.0.1:8787")}`,
227
+ c.bold(c.magenta("β–Ά Quick start")),
228
+ ` ${c.green("blindfold signup --email you@x.com")} ${c.gray("# create a funded testnet tenant")}`,
229
+ ` ${c.green("blindfold register --name openai_api_key")} ${c.gray("# seal a key (hidden prompt)")}`,
230
+ ` ${c.green("blindfold proxy")} ${c.gray("# point your agent at http://127.0.0.1:8787")}`,
227
231
  "",
228
- `${c.gray("Details for any command:")} ${c.cyan("blindfold <command> --help")} ${c.gray("Β·")} ${c.gray("Docs:")} ${c.cyan("npmjs.com/package/@fiscalmindset/blindfold")}`,
232
+ `${c.gray("Full flags + more examples for any command:")} ${c.cyan("blindfold <command> --help")}`,
233
+ `${c.gray("Docs:")} ${c.cyan("npmjs.com/package/@fiscalmindset/blindfold")}`,
229
234
  "",
230
235
  );
231
236
  return out.join("\n");
package/src/tui.ts CHANGED
@@ -3,7 +3,7 @@
3
3
  * wrapping, rounded boxes, and a two-column command table. Used to render a
4
4
  * structured `blindfold help` that reflows to the terminal.
5
5
  */
6
- import { c } from "./color.ts";
6
+ import { c, colorOn } from "./color.ts";
7
7
 
8
8
  /** Terminal width, clamped to a readable range. */
9
9
  export function termWidth(): number {
@@ -124,13 +124,32 @@ export function boxLines(title: string, lines: string[]): string {
124
124
  const w = termWidth();
125
125
  const inner = w - 4;
126
126
  const prefix = B.tl + B.h + " ";
127
- const fill = Math.max(0, w - vlen(prefix) - vlen(title) - 2);
128
- const out: string[] = [dim(prefix) + c.bold(title) + dim(" " + B.h.repeat(fill) + B.tr)];
129
- for (const l of lines) out.push(dim(B.v) + " " + pad(l, inner) + " " + dim(B.v));
127
+ const fill = Math.max(0, w - vlen(prefix) - vlen(clip(title, inner)) - 2);
128
+ const out: string[] = [dim(prefix) + c.bold(clip(title, inner)) + dim(" " + B.h.repeat(fill) + B.tr)];
129
+ for (const l of lines) out.push(dim(B.v) + " " + pad(clip(l, inner), inner) + " " + dim(B.v));
130
130
  out.push(dim(B.bl + B.h.repeat(w - 2) + B.br));
131
131
  return out.join("\n");
132
132
  }
133
133
 
134
+ /** Truncate a string to display width `width` (ANSI/emoji aware), appending …
135
+ * and a reset so a clipped color can't bleed. No-op if it already fits. */
136
+ export function clip(s: string, width: number): string {
137
+ if (vlen(s) <= width) return s;
138
+ let out = "";
139
+ let wSoFar = 0;
140
+ for (const part of s.split(/(\x1b\[[0-9;]*m)/)) {
141
+ if (part.startsWith("\x1b")) { out += part; continue; }
142
+ for (const ch of part) {
143
+ const cp = ch.codePointAt(0) ?? 0;
144
+ const cw = cp === 0x200d || (cp >= 0xfe00 && cp <= 0xfe0f) || (cp >= 0x300 && cp <= 0x36f) ? 0 : isWide(cp) ? 2 : 1;
145
+ if (wSoFar + cw > width - 1) return out + "…" + (colorOn ? "\x1b[0m" : "");
146
+ out += ch;
147
+ wSoFar += cw;
148
+ }
149
+ }
150
+ return out + (colorOn ? "\x1b[0m" : "");
151
+ }
152
+
134
153
  /** A plain full-width rule with an optional bold label: "── Label ─────". */
135
154
  export function rule(label = ""): string {
136
155
  const w = termWidth();