@fiscalmindset/blindfold 0.4.3 โ†’ 0.4.5

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));
@@ -1683,8 +1703,12 @@ function renderMainHelp() {
1683
1703
  const dl = wrapText(cmd.summary, descW);
1684
1704
  lines.push(pad(c.bold(c.cyan(cmd.name)), nameW) + " " + (dl[0] ?? ""));
1685
1705
  for (let i = 1; i < dl.length; i++) lines.push(pad("", nameW) + " " + (dl[i] ?? ""));
1686
- const ex = fit(cmd.examples?.[0] ?? `blindfold ${cmd.name}`, descW - 5);
1687
- lines.push(pad("", nameW) + " " + c.gray("e.g. ") + c.green(ex));
1706
+ const gutter = pad("", nameW) + " ";
1707
+ if (cmd.usage) {
1708
+ lines.push(gutter + c.gray(pad("usage", 5)) + " " + c.cyan(fit(`blindfold ${cmd.name} ${cmd.usage}`, descW - 6)));
1709
+ }
1710
+ const ex = fit(cmd.examples?.[0] ?? `blindfold ${cmd.name}`, descW - 6);
1711
+ lines.push(gutter + c.gray(pad("e.g.", 5)) + " " + c.green(ex));
1688
1712
  }
1689
1713
  out.push(boxLines(group, lines));
1690
1714
  out.push("");
@@ -4248,15 +4272,22 @@ async function handleEnclave(cmd, argv, cmdArgs) {
4248
4272
  console.log(JSON.stringify(b, null, 2));
4249
4273
  return;
4250
4274
  }
4251
- console.log(head(`\u{1F4B3} Terminal 3 credit`) + c.gray(` \u2014 ${env.did || "(no tenant)"} (${env.mock ? "MOCK" : env.t3Env})`));
4252
- console.log(` available: ${c.bold(tok(b.available) + " tokens")} ${c.gray("(" + b.available.toLocaleString() + " base units)")}`);
4253
- console.log(` reserved: ${b.reserved.toLocaleString()} base units`);
4254
- console.log(` status: ${b.creditExhausted ? warn("\u26A0 EXHAUSTED") : ok("\u2705 ok")}`);
4275
+ const lines = [
4276
+ c.gray(`${env.did || "(no tenant)"} \xB7 ${env.mock ? "MOCK" : env.t3Env}`),
4277
+ "",
4278
+ `available: ${c.bold(c.green(tok(b.available) + " tokens"))} ${c.gray("(" + b.available.toLocaleString() + " base units)")}`,
4279
+ `reserved: ${b.reserved.toLocaleString()} base units`,
4280
+ `status: ${b.creditExhausted ? warn("\u26A0 EXHAUSTED") : ok("\u2705 ok")}`
4281
+ ];
4255
4282
  if (b.creditExhausted) {
4256
- console.log(` Top up testnet credits, then re-check with \`blindfold credit\`:`);
4257
- console.log(` https://docs.terminal3.io/developers/adk/get-started/prerequisites/request-test-tokens`);
4283
+ lines.push(
4284
+ "",
4285
+ c.yellow("Top up testnet credits, then re-check with `blindfold credit`:"),
4286
+ c.cyan(" https://docs.terminal3.io/developers/adk/get-started/prerequisites/request-test-tokens")
4287
+ );
4258
4288
  process.exitCode = 1;
4259
4289
  }
4290
+ console.log(boxLines("\u{1F4B3} Terminal 3 credit", lines));
4260
4291
  } finally {
4261
4292
  await client.close();
4262
4293
  }
@@ -4348,9 +4379,10 @@ async function handleEnclave(cmd, argv, cmdArgs) {
4348
4379
  console.log(`Seal one with: blindfold register --name <KV_KEY>`);
4349
4380
  return;
4350
4381
  }
4351
- console.log(head("\u{1F510} Sealed keys") + c.gray(` (source: ${defaultSealedLogPath()})`) + "\n");
4352
- console.log(" WHEN NAME BYTES MODE WHERE");
4353
- console.log(" \u2500\u2500\u2500\u2500 \u2500\u2500\u2500\u2500 \u2500\u2500\u2500\u2500\u2500 \u2500\u2500\u2500\u2500 \u2500\u2500\u2500\u2500\u2500");
4382
+ console.log(boxLines("\u{1F510} Sealed keys", [c.gray(`source: ${defaultSealedLogPath()}`)]));
4383
+ console.log("");
4384
+ console.log(c.gray(" WHEN NAME BYTES MODE WHERE"));
4385
+ 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"));
4354
4386
  for (const e of entries) {
4355
4387
  const when = e.t.replace("T", " ").slice(0, 19);
4356
4388
  const where = e.map_name.length > 60 ? e.map_name.slice(0, 57) + "\u2026" : e.map_name;
@@ -4360,9 +4392,10 @@ async function handleEnclave(cmd, argv, cmdArgs) {
4360
4392
  return;
4361
4393
  }
4362
4394
  case "audit": {
4363
- console.log("\u{1F50D} Blindfold audit\n");
4395
+ console.log(boxLines("\u{1F50D} Blindfold audit", [c.gray("ledger hash-chain + reconciliation against the enclave")]));
4396
+ console.log("");
4364
4397
  const chain = verifyLedgerChain();
4365
- console.log(" 1. Ledger integrity (tamper-evidence)");
4398
+ console.log(c.bold(" 1. Ledger integrity (tamper-evidence)"));
4366
4399
  if (chain.total === 0) {
4367
4400
  console.log(" (ledger is empty)");
4368
4401
  } else if (chain.ok) {
@@ -4413,46 +4446,46 @@ async function handleEnclave(cmd, argv, cmdArgs) {
4413
4446
  }
4414
4447
  case "status": {
4415
4448
  const env = loadBlindfoldEnv();
4416
- console.log(head("\u{1F6E1}\uFE0F Blindfold status") + "\n");
4417
- console.log(` mode: ${env.mock ? "MOCK (BLINDFOLD_MOCK=1)" : "REAL"} \xB7 T3 env: ${env.t3Env}`);
4418
- if (env.t3BaseUrl) console.log(` node: ${env.t3BaseUrl} (override)`);
4449
+ const lines = [];
4450
+ lines.push(`mode: ${env.mock ? "MOCK (BLINDFOLD_MOCK=1)" : "REAL"} \xB7 T3 env: ${env.t3Env}`);
4451
+ if (env.t3BaseUrl) lines.push(`node: ${env.t3BaseUrl} (override)`);
4419
4452
  if (!env.mock) {
4420
4453
  try {
4421
4454
  const { openT3Client: openT3Client2 } = await Promise.resolve().then(() => (init_t3_client(), t3_client_exports));
4422
4455
  const client = await openT3Client2(env);
4423
4456
  const info2 = await client.me();
4424
- console.log(` tenant: ${ok("\u2705 " + info2.tenant)} (status=${info2.status ?? "?"})`);
4457
+ lines.push(`tenant: ${ok("\u2705 " + info2.tenant)} (status=${info2.status ?? "?"})`);
4425
4458
  } catch (e) {
4426
- console.log(` tenant: ${bad("\u2716 " + e.message.slice(0, 90))}`);
4427
- console.log(` \u2192 run \`blindfold doctor\` for a full diagnosis.`);
4459
+ lines.push(`tenant: ${bad("\u2716 " + e.message.slice(0, 90))}`);
4460
+ lines.push(` ${c.gray("\u2192 run `blindfold doctor` for a full diagnosis.")}`);
4428
4461
  process.exitCode = 1;
4429
4462
  }
4430
4463
  }
4431
4464
  const entries = readSealed();
4432
4465
  const latest = /* @__PURE__ */ new Map();
4433
4466
  for (const e of entries) latest.set(e.name, e);
4434
- console.log(`
4435
- Sealed secrets (${latest.size}):`);
4467
+ lines.push("", c.bold(`Sealed secrets (${latest.size})`));
4436
4468
  if (latest.size === 0) {
4437
- console.log(` (none yet) seal one: blindfold register --name <X> --from-env <X>`);
4469
+ lines.push(` ${c.gray("(none yet) seal one:")} ${c.cyan("blindfold register --name <X>")}`);
4438
4470
  } else {
4439
4471
  for (const e of latest.values()) {
4440
- console.log(` \u2022 ${e.name.padEnd(22)} ${String(e.length).padStart(4)} B ${e.mode}`);
4472
+ lines.push(` ${c.green("\u2022")} ${c.cyan(e.name.padEnd(22))} ${String(e.length).padStart(4)} B ${c.gray(e.mode)}`);
4441
4473
  }
4442
4474
  }
4443
- console.log(`
4444
- Next: blindfold use --name <secret> -- <command> (use it, no code)`);
4475
+ lines.push("", c.gray("Next: ") + c.cyan("blindfold use --name <secret> -- <command>") + c.gray(" (use it, no code)"));
4476
+ console.log(boxLines("\u{1F6E1}\uFE0F Blindfold status", lines));
4445
4477
  return;
4446
4478
  }
4447
4479
  case "doctor": {
4448
4480
  const env = loadBlindfoldEnv();
4449
- console.log(head("\u{1FA7A} Blindfold doctor"));
4450
- console.log(` mode: ${env.mock ? "MOCK (BLINDFOLD_MOCK=1)" : "REAL (T3)"}`);
4451
- console.log(` T3N_API_KEY set: ${env.t3nApiKey ? "yes" : "NO \u2716"}`);
4452
- console.log(` DID set: ${env.did ? "yes" : "NO \u2716"}`);
4453
- console.log(` T3 environment: ${env.t3Env}`);
4454
- console.log(` node URL: ${env.t3BaseUrl || `(SDK default for ${env.t3Env})`}`);
4455
- console.log(` default proxy port: ${env.port}`);
4481
+ console.log(boxLines("\u{1FA7A} Blindfold doctor", [
4482
+ `mode: ${env.mock ? "MOCK (BLINDFOLD_MOCK=1)" : "REAL (T3)"}`,
4483
+ `T3N_API_KEY: ${env.t3nApiKey ? ok("set") : bad("NOT set \u2716")}`,
4484
+ `DID: ${env.did ? ok("set") : bad("NOT set \u2716")}`,
4485
+ `T3 env: ${env.t3Env}`,
4486
+ `node URL: ${env.t3BaseUrl || c.gray(`(SDK default for ${env.t3Env})`)}`,
4487
+ `proxy port: ${env.port}`
4488
+ ]));
4456
4489
  if (!env.mock && (!env.t3nApiKey || !env.did)) {
4457
4490
  console.log("");
4458
4491
  console.log(` \u26A0 REAL mode is selected but credentials are missing.`);
@@ -4462,8 +4495,7 @@ async function handleEnclave(cmd, argv, cmdArgs) {
4462
4495
  return;
4463
4496
  }
4464
4497
  if (env.mock) return;
4465
- console.log("");
4466
- console.log(" Live check (handshake + authenticate + me) \u2026");
4498
+ console.log("\n" + rule("Live check \u2014 handshake + authenticate + me"));
4467
4499
  const { openT3Client: openT3Client2 } = await Promise.resolve().then(() => (init_t3_client(), t3_client_exports));
4468
4500
  let client;
4469
4501
  try {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fiscalmindset/blindfold",
3
- "version": "0.4.3",
3
+ "version": "0.4.5",
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
@@ -215,9 +215,14 @@ export function renderMainHelp(): string {
215
215
  // command name (bright) + summary
216
216
  lines.push(pad(c.bold(c.cyan(cmd.name)), nameW) + " " + (dl[0] ?? ""));
217
217
  for (let i = 1; i < dl.length; i++) lines.push(pad("", nameW) + " " + (dl[i] ?? ""));
218
+ const gutter = pad("", nameW) + " ";
219
+ // the shape โ€” with <placeholders> โ€” so you learn the pattern (only if it takes args)
220
+ if (cmd.usage) {
221
+ lines.push(gutter + c.gray(pad("usage", 5)) + " " + c.cyan(fit(`blindfold ${cmd.name} ${cmd.usage}`, descW - 6)));
222
+ }
218
223
  // 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));
224
+ const ex = fit(cmd.examples?.[0] ?? `blindfold ${cmd.name}`, descW - 6);
225
+ lines.push(gutter + c.gray(pad("e.g.", 5)) + " " + c.green(ex));
221
226
  }
222
227
  out.push(boxLines(group, lines));
223
228
  out.push("");
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();