@blockrun/franklin 3.2.0 → 3.2.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 (2) hide show
  1. package/dist/banner.js +125 -9
  2. package/package.json +1 -1
package/dist/banner.js CHANGED
@@ -1,5 +1,39 @@
1
1
  import chalk from 'chalk';
2
- // "FRANKLIN" the AI agent with a wallet
2
+ // ─── Ben Franklin portrait ─────────────────────────────────────────────────
3
+ //
4
+ // Generated once, at build time, from the Joseph Duplessis 1785 oil painting
5
+ // of Benjamin Franklin (same source as the portrait on the US $100 bill).
6
+ // Public domain image from Wikimedia Commons:
7
+ // https://commons.wikimedia.org/wiki/File:BenFranklinDuplessis.jpg
8
+ //
9
+ // Rendered via chafa with:
10
+ // chafa --size=20x10 --symbols=block --colors=256 ben-franklin.jpg
11
+ //
12
+ // The raw ANSI escape codes are hex-encoded below so the TS source stays
13
+ // readable and diff-friendly. Each string is one row of the portrait.
14
+ // Visible dimensions: ~17 characters wide × 10 rows tall.
15
+ //
16
+ // Rendered best in a 256-color or truecolor terminal. Degrades gracefully
17
+ // (shows as block-character garbage) on ancient terminals — but those
18
+ // are long gone and we don't support them.
19
+ const BEN_PORTRAIT_ROWS = [
20
+ '\x1b[0m\x1b[38;5;232;48;5;16m▏ \x1b[48;5;232m \x1b[48;5;16m▂\x1b[48;5;232m \x1b[38;5;233m▃▃\x1b[48;5;233m \x1b[0m',
21
+ '\x1b[38;5;232;48;5;16m▏ \x1b[38;5;234m▂\x1b[38;5;95;48;5;232m▄\x1b[38;5;137;48;5;233m▅\x1b[38;5;173m▅\x1b[38;5;137;48;5;234m▃\x1b[38;5;235;48;5;233m▁ \x1b[38;5;234m▄ \x1b[0m',
22
+ '\x1b[38;5;232;48;5;16m▏▕ \x1b[38;5;234;48;5;232m▁\x1b[38;5;235;48;5;237m▎\x1b[38;5;58;48;5;179m▌\x1b[38;5;131m▄\x1b[38;5;94m▖\x1b[38;5;58;48;5;137m▗\x1b[48;5;235m▍\x1b[38;5;235;48;5;234m▆▆▄▅ \x1b[0m',
23
+ '\x1b[38;5;233;48;5;232m▏\x1b[38;5;232;48;5;16m▏\x1b[38;5;16;48;5;232m▏\x1b[38;5;233m▗\x1b[38;5;236;48;5;240m▎\x1b[38;5;235;48;5;238m▄\x1b[38;5;95;48;5;173m▎\x1b[38;5;173;48;5;179m▖\x1b[38;5;137m▁\x1b[48;5;94m▍\x1b[38;5;94;48;5;233m▋\x1b[38;5;233;48;5;235m▖\x1b[38;5;236m▄ \x1b[38;5;235;48;5;234m▌ \x1b[0m',
24
+ '\x1b[38;5;233;48;5;232m▏ \x1b[38;5;232;48;5;234m▌\x1b[38;5;240;48;5;236m▁\x1b[38;5;95;48;5;235m▁\x1b[38;5;186;48;5;137m▗ \x1b[38;5;95;48;5;173m▃\x1b[38;5;137;48;5;94m▘\x1b[38;5;58;48;5;234m▍\x1b[38;5;234;48;5;236m▘ \x1b[38;5;236;48;5;235m▏ \x1b[38;5;232;48;5;234m▄\x1b[0m',
25
+ '\x1b[38;5;233;48;5;232m▏\x1b[38;5;235m▁\x1b[38;5;95;48;5;234m▄\x1b[38;5;137;48;5;236m▆\x1b[48;5;95m \x1b[38;5;101m▁\x1b[38;5;137m▔\x1b[48;5;186m▄\x1b[48;5;95m▍\x1b[48;5;236m▃\x1b[38;5;143;48;5;235m▃\x1b[38;5;236m▏\x1b[38;5;235;48;5;236m▃\x1b[48;5;235m \x1b[38;5;234m▁\x1b[38;5;235;48;5;232m▘\x1b[38;5;232;48;5;16m▔\x1b[0m',
26
+ '\x1b[38;5;238;48;5;233m▗\x1b[38;5;8;48;5;137m▘\x1b[38;5;138m▘ \x1b[38;5;137;48;5;95m▊\x1b[38;5;95;48;5;101m▎\x1b[38;5;137;48;5;95m▎\x1b[48;5;101m▌\x1b[48;5;95m \x1b[38;5;95;48;5;101m▏\x1b[38;5;143m▔\x1b[38;5;101;48;5;236m▅\x1b[38;5;240;48;5;234m▖\x1b[38;5;235m▞\x1b[38;5;234;48;5;232m▘\x1b[38;5;232;48;5;16m▔ \x1b[0m',
27
+ '\x1b[38;5;52;48;5;95m▋\x1b[48;5;137m \x1b[38;5;95;48;5;101m▕\x1b[38;5;240;48;5;137m▌\x1b[38;5;101m▂ \x1b[48;5;95m▏\x1b[38;5;239m▖▂\x1b[38;5;237m▄\x1b[38;5;101;48;5;234m▘\x1b[38;5;234;48;5;233m▝\x1b[48;5;232m▖\x1b[48;5;16m \x1b[0m',
28
+ '\x1b[38;5;235;48;5;95m▌\x1b[38;5;95;48;5;137m▄\x1b[38;5;101m▁ \x1b[48;5;95m▌\x1b[38;5;238m▋\x1b[38;5;240;48;5;101m▃ \x1b[38;5;95;48;5;240m▎\x1b[38;5;236;48;5;239m▝\x1b[38;5;95;48;5;235m▆\x1b[38;5;240m▆\x1b[38;5;237;48;5;233m▆\x1b[48;5;234m▃\x1b[38;5;235;48;5;233m▎\x1b[38;5;233;48;5;232m▖\x1b[48;5;16m \x1b[0m',
29
+ '\x1b[38;5;234;48;5;95m▌ \x1b[38;5;95;48;5;101m▃\x1b[38;5;239;48;5;95m▗\x1b[38;5;238;48;5;234m▋\x1b[38;5;236;48;5;101m▎\x1b[38;5;101;48;5;95m▋\x1b[38;5;239m▂▎ \x1b[48;5;240m▃\x1b[38;5;8;48;5;236m▍\x1b[38;5;235;48;5;233m▋\x1b[38;5;232m▅\x1b[38;5;233;48;5;232m▖\x1b[0m',
30
+ ];
31
+ // ─── FRANKLIN text banner (gold → emerald gradient) ────────────────────────
32
+ //
33
+ // Kept from v3.1.0. The text is laid out as 6 block-letter rows. Each row
34
+ // is tinted with a color interpolated between GOLD_START and EMERALD_END,
35
+ // giving the smooth vertical gradient that's been Franklin's banner since
36
+ // v3.1.0.
3
37
  const FRANKLIN_ART = [
4
38
  ' ███████╗██████╗ █████╗ ███╗ ██╗██╗ ██╗██╗ ██╗███╗ ██╗',
5
39
  ' ██╔════╝██╔══██╗██╔══██╗████╗ ██║██║ ██╔╝██║ ██║████╗ ██║',
@@ -8,10 +42,6 @@ const FRANKLIN_ART = [
8
42
  ' ██║ ██║ ██║██║ ██║██║ ╚████║██║ ██╗███████╗██║██║ ╚████║',
9
43
  ' ╚═╝ ╚═╝ ╚═╝╚═╝ ╚═╝╚═╝ ╚═══╝╚═╝ ╚═╝╚══════╝╚═╝╚═╝ ╚═══╝',
10
44
  ];
11
- // Franklin brand gradient: gold → emerald
12
- // Gold (#FFD700) is the $100 bill / Benjamins color — marketing product energy
13
- // Emerald (#10B981) is the trading / money-moving color
14
- // The gradient between them tells the Franklin story in one glance
15
45
  const GOLD_START = '#FFD700';
16
46
  const EMERALD_END = '#10B981';
17
47
  function hexToRgb(hex) {
@@ -31,11 +61,97 @@ function interpolateHex(start, end, t) {
31
61
  const [r2, g2, b2] = hexToRgb(end);
32
62
  return rgbToHex(r1 + (r2 - r1) * t, g1 + (g2 - g1) * t, b1 + (b2 - b1) * t);
33
63
  }
64
+ // ─── Banner layout ─────────────────────────────────────────────────────────
65
+ // Minimum terminal width to show the side-by-side portrait + text layout.
66
+ // The portrait is ~17 chars, the FRANKLIN text is ~65 chars, plus a 4-char
67
+ // gap = 86 chars. We add a small margin so ~90 cols is the threshold.
68
+ const MIN_WIDTH_FOR_PORTRAIT = 90;
69
+ /**
70
+ * Pad a line to an exact visual width, ignoring ANSI escape codes when
71
+ * measuring. Used to align the portrait's right edge before the text block.
72
+ */
73
+ function padVisible(s, targetWidth) {
74
+ // Strip ANSI color codes to measure visible length
75
+ // eslint-disable-next-line no-control-regex
76
+ const visible = s.replace(/\x1b\[[0-9;]*m/g, '');
77
+ // Unicode block characters are width 1 (they're half-blocks, not double-width)
78
+ const current = [...visible].length;
79
+ if (current >= targetWidth)
80
+ return s;
81
+ // Append a reset + padding so background colors don't bleed into the gap
82
+ return s + '\x1b[0m' + ' '.repeat(targetWidth - current);
83
+ }
34
84
  export function printBanner(version) {
35
- // Smooth 6-row gradient: each line gets its own interpolated color
36
- const steps = FRANKLIN_ART.length;
37
- for (let i = 0; i < steps; i++) {
38
- const t = i / (steps - 1);
85
+ const termWidth = process.stdout.columns ?? 80;
86
+ const useSideBySide = termWidth >= MIN_WIDTH_FOR_PORTRAIT;
87
+ if (useSideBySide) {
88
+ printSideBySide(version);
89
+ }
90
+ else {
91
+ printTextOnly(version);
92
+ }
93
+ }
94
+ /**
95
+ * Full layout: Ben Franklin portrait on the left, FRANKLIN text block on the
96
+ * right. Portrait is 10 rows, text is 6 rows — portrait extends 4 rows below
97
+ * the text, so the 2-row tagline sits under the text and the last 2 rows
98
+ * below the portrait are just the bottom of the portrait.
99
+ *
100
+ * [portrait row 1] [empty]
101
+ * [portrait row 2] [empty]
102
+ * [portrait row 3] ███████╗██████╗ █████╗ ...
103
+ * [portrait row 4] ██╔════╝██╔══██╗██╔══██╗...
104
+ * [portrait row 5] █████╗ ██████╔╝███████║...
105
+ * [portrait row 6] ██╔══╝ ██╔══██╗██╔══██║...
106
+ * [portrait row 7] ██║ ██║ ██║██║ ██║...
107
+ * [portrait row 8] ╚═╝ ╚═╝ ╚═╝╚═╝ ╚═╝...
108
+ * [portrait row 9] Franklin · The AI agent with a wallet · vX
109
+ * [portrait row 10] (empty)
110
+ *
111
+ * The text is vertically centered within the portrait — its top edge sits
112
+ * at portrait row 3 so there's a 2-row header padding above.
113
+ */
114
+ function printSideBySide(version) {
115
+ const TEXT_TOP_OFFSET = 2; // rows of portrait above the text
116
+ const PORTRAIT_WIDTH = 18; // columns (char width) of the portrait + 1 pad
117
+ const GAP = ' '; // gap between portrait and text
118
+ const portraitRows = BEN_PORTRAIT_ROWS;
119
+ const textRows = FRANKLIN_ART.length;
120
+ const totalRows = Math.max(portraitRows.length, TEXT_TOP_OFFSET + textRows + 2);
121
+ for (let i = 0; i < totalRows; i++) {
122
+ const portraitLine = i < portraitRows.length
123
+ ? padVisible(portraitRows[i], PORTRAIT_WIDTH)
124
+ : ' '.repeat(PORTRAIT_WIDTH);
125
+ // Text column content
126
+ let textCol = '';
127
+ const textIdx = i - TEXT_TOP_OFFSET;
128
+ if (textIdx >= 0 && textIdx < textRows) {
129
+ // FRANKLIN block letters with gradient colour
130
+ const t = textRows === 1 ? 0 : textIdx / (textRows - 1);
131
+ const color = interpolateHex(GOLD_START, EMERALD_END, t);
132
+ textCol = chalk.hex(color)(FRANKLIN_ART[textIdx]);
133
+ }
134
+ else if (textIdx === textRows) {
135
+ // Tagline row sits right under the FRANKLIN block
136
+ textCol =
137
+ chalk.bold.hex(GOLD_START)(' Franklin') +
138
+ chalk.dim(' · The AI agent with a wallet · v' + version);
139
+ }
140
+ // Write with a reset at the very start to prevent stray bg from the
141
+ // previous line bleeding into the current row's portrait column.
142
+ process.stdout.write('\x1b[0m' + portraitLine + GAP + textCol + '\x1b[0m\n');
143
+ }
144
+ // Trailing blank line for breathing room
145
+ process.stdout.write('\n');
146
+ }
147
+ /**
148
+ * Compact layout for narrow terminals: just the FRANKLIN text block with
149
+ * its gradient, no portrait. Matches the v3.1.0 banner exactly.
150
+ */
151
+ function printTextOnly(version) {
152
+ const textRows = FRANKLIN_ART.length;
153
+ for (let i = 0; i < textRows; i++) {
154
+ const t = textRows === 1 ? 0 : i / (textRows - 1);
39
155
  const color = interpolateHex(GOLD_START, EMERALD_END, t);
40
156
  console.log(chalk.hex(color)(FRANKLIN_ART[i]));
41
157
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blockrun/franklin",
3
- "version": "3.2.0",
3
+ "version": "3.2.1",
4
4
  "description": "Franklin — The AI agent with a wallet. Spends USDC autonomously to get real work done. Pay per action, no subscriptions.",
5
5
  "type": "module",
6
6
  "exports": {