acuvo-code 0.4.3 → 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.
package/bin/acuvo.mjs CHANGED
@@ -1511,6 +1511,13 @@ ${formatBoard(listed)}
1511
1511
  billing,
1512
1512
  canRun: mode,
1513
1513
  interactive: false,
1514
+ /**
1515
+ * ⚠️ THE PAINTER IS BUILT HERE, WHERE THE STREAM IS KNOWN. `colourEnabled`
1516
+ * owns NO_COLOR, FORCE_COLOR, TERM=dumb and TTY detection — so a redirected
1517
+ * run gets a plain banner and nobody has to remember an `if` at the call
1518
+ * site.
1519
+ */
1520
+ paint: createPainter(colourEnabled()),
1514
1521
  });
1515
1522
  if (opts.json) process.stderr.write(banner);
1516
1523
  else process.stdout.write(banner);
package/lib/banner.mjs CHANGED
@@ -1,125 +1,155 @@
1
- /**
2
- * ── ⭐⭐⭐ WHAT YOU SEE WHEN YOU TYPE `acuvo` ────────────────────────────────
3
- *
4
- * Roman, 2026-08-22, having typed it: *"it's not opening as a typable terminal,
5
- * with acuvo logo top left and details up top etc, like how claude code opens.
6
- * have you actually designed the page?"* — and then: *"no we want OUR logo"*.
7
- *
8
- * So this is the real mark, not letters spelling the name. It is
9
- * `console/public/brand/acuvo-mark.png` — the angular A — resampled to
10
- * half-blocks at 14x14 and embedded as text.
11
- *
12
- * ── ⚠️ WHY IT IS EMBEDDED RATHER THAN DECODED AT RUNTIME ────────────────────
13
- *
14
- * This package has ZERO dependencies, deliberately, and Node cannot decode a
15
- * PNG without one. Converting at build time and pasting the result keeps that
16
- * promise and costs nothing at startup. If the mark ever changes, re-run the
17
- * conversion — the logo is DERIVED from the brand asset, not drawn by hand, so
18
- * it stays honest to it.
19
- *
20
- * ── ⚠️ THE CONSTRAINTS, WHICH ARE NOT PREFERENCES ───────────────────────────
21
- *
22
- * · **Half-block glyphs only** (▀ ▄ █). Braille and box-drawing render as tofu
23
- * in cmd.exe and in many CI log viewers, and a logo that renders as question
24
- * marks is worse than no logo — on the exact platform this is developed on.
25
- * · **No colour escapes here.** The caller owns colour and honours NO_COLOR; a
26
- * module that hard-codes them emits garbage the moment output is piped.
27
- * · **Every line under 80 columns.** The narrowest terminal in real use is 80,
28
- * and a wrapped banner does not read as dense, it reads as broken.
29
- *
30
- * ⭐ A NOTE ON DOING BETTER: `lib/terminal-graphics.mjs` can send a real PNG
31
- * inline on Kitty and iTerm2. It is deliberately NOT used here — Windows
32
- * Terminal speaks neither protocol, so the block art is what most users would
33
- * see anyway, and one rendering that is the same everywhere beats two that
34
- * disagree.
35
- */
36
-
37
- /**
38
- * The Acuvo mark, resampled from the brand PNG. Seven rows: tall enough to be
39
- * the mark rather than a smudge, short enough for something run forty times a
40
- * day rather than opened once.
41
- */
42
- const MARK = [
43
- ' ▄█',
44
- ' ▄███',
45
- ' ▄█▀▀██',
46
- ' ▄█▀ ▀██',
47
- ' ▄█▀ ██ ▀██',
48
- ' ▄████▀▀█████',
49
- '█▀ █▀ ▀█',
50
- ];
51
-
52
- const MARK_WIDTH = Math.max(...MARK.map((l) => l.length));
53
- const GUTTER = 2;
54
- export const MAX_BANNER_COLUMNS = 80;
55
-
56
- /** Columns left for the detail rows once the mark and gutter are placed. */
57
- const TEXT_COLUMNS = MAX_BANNER_COLUMNS - MARK_WIDTH - GUTTER;
58
-
59
- /**
60
- * Build the opening screen: mark on the left, facts on the right.
61
- *
62
- * @param {object} o
63
- * @param {string} o.version
64
- * @param {string} o.workspace already shortened by the caller
65
- * @param {string} o.model
66
- * @param {string} o.billing who this run will charge
67
- * @param {string} o.canRun what it may execute, or that it may not
68
- * @param {boolean} [o.interactive] whether a prompt follows
69
- * @returns {string}
70
- */
71
- export function openingScreen({ version, workspace, model, billing, canRun, interactive = false }) {
72
- /**
73
- * ── ⚠️⚠️ ELIDED, BECAUSE THE VALUES ARE NOT OURS ────────────────────────────
74
- *
75
- * Every value arrives at runtime: a deep monorepo path, a long model id, the
76
- * shell-mode warning. The first version assumed they would be short and its
77
- * own test caught it wrapping at 80 columns on ordinary inputs.
78
- *
79
- * ELIDED IN THE MIDDLE, NOT THE END. The informative parts of a path are
80
- * the drive and the leaf; chopping the tail leaves
81
- * `C:\Users\somebody\Projects\a-`, which identifies nothing. Same for a model
82
- * id, where the family is at the front and the variant at the back.
83
- */
84
- const room = TEXT_COLUMNS - 11;
85
- const fit = (v) => {
86
- const s = String(v ?? '');
87
- if (s.length <= room) return s;
88
- const head = Math.ceil((room - 1) / 2);
89
- return `${s.slice(0, head)}…${s.slice(s.length - (room - 1 - head))}`;
90
- };
91
-
92
- const right = [
93
- `ACUVO CODE${version ? ` ${version}` : ''}`,
94
- '',
95
- `workspace ${fit(workspace)}`,
96
- `model ${fit(model)}`,
97
- `billing ${fit(billing)}`,
98
- `can run ${fit(canRun)}`,
99
- '',
100
- ];
101
-
102
- /**
103
- * ⚠️ THE TWO COLUMNS ARE ZIPPED, NOT CONCATENATED, and the row counts are
104
- * allowed to differ whichever is shorter simply runs out. Assuming they
105
- * match would break the layout the first time a row is added to either side.
106
- */
107
- const rows = Math.max(MARK.length, right.length);
108
- const lines = [''];
109
- for (let i = 0; i < rows; i += 1) {
110
- const mark = (MARK[i] ?? '').padEnd(MARK_WIDTH + GUTTER);
111
- const text = right[i] ?? '';
112
- lines.push(`${mark}${text}`.trimEnd());
113
- }
114
- lines.push('');
115
-
116
- /**
117
- * ⚠️ ONLY WHEN A PROMPT ACTUALLY FOLLOWS. Printing "type what you want done"
118
- * above a one-shot run that has already been given its task is an instruction
119
- * for something the user cannot do, on the screen of a thing already working.
120
- */
121
- if (interactive) {
122
- lines.push(' Type what you want done. /help for commands · exit to leave', '');
123
- }
124
- return lines.join('\n');
125
- }
1
+ /**
2
+ * ── ⭐⭐⭐ WHAT YOU SEE WHEN YOU TYPE `acuvo` ────────────────────────────────
3
+ *
4
+ * Roman, 2026-08-22, having typed it: *"it's not opening as a typable terminal,
5
+ * with acuvo logo top left and details up top etc, like how claude code opens.
6
+ * have you actually designed the page?"* — and then: *"no we want OUR logo"*.
7
+ *
8
+ * So this is the real mark, not letters spelling the name. It is
9
+ * `console/public/brand/acuvo-mark.png` — the angular A — resampled to
10
+ * half-blocks at 14x14 and embedded as text.
11
+ *
12
+ * ── ⚠️ WHY IT IS EMBEDDED RATHER THAN DECODED AT RUNTIME ────────────────────
13
+ *
14
+ * This package has ZERO dependencies, deliberately, and Node cannot decode a
15
+ * PNG without one. Converting at build time and pasting the result keeps that
16
+ * promise and costs nothing at startup. If the mark ever changes, re-run the
17
+ * conversion — the logo is DERIVED from the brand asset, not drawn by hand, so
18
+ * it stays honest to it.
19
+ *
20
+ * ── ⚠️ THE CONSTRAINTS, WHICH ARE NOT PREFERENCES ───────────────────────────
21
+ *
22
+ * · **Half-block glyphs only** (▀ ▄ █). Braille and box-drawing render as tofu
23
+ * in cmd.exe and in many CI log viewers, and a logo that renders as question
24
+ * marks is worse than no logo — on the exact platform this is developed on.
25
+ * · **No colour escapes here.** The caller owns colour and honours NO_COLOR; a
26
+ * module that hard-codes them emits garbage the moment output is piped.
27
+ * · **Every line under 80 columns.** The narrowest terminal in real use is 80,
28
+ * and a wrapped banner does not read as dense, it reads as broken.
29
+ *
30
+ * ⭐ A NOTE ON DOING BETTER: `lib/terminal-graphics.mjs` can send a real PNG
31
+ * inline on Kitty and iTerm2. It is deliberately NOT used here — Windows
32
+ * Terminal speaks neither protocol, so the block art is what most users would
33
+ * see anyway, and one rendering that is the same everywhere beats two that
34
+ * disagree.
35
+ */
36
+
37
+ /**
38
+ * The Acuvo mark, resampled from the brand PNG. Seven rows: tall enough to be
39
+ * the mark rather than a smudge, short enough for something run forty times a
40
+ * day rather than opened once.
41
+ */
42
+ const MARK = [
43
+ ' ▄█',
44
+ ' ▄███',
45
+ ' ▄█▀▀██',
46
+ ' ▄█▀ ▀██',
47
+ ' ▄█▀ ██ ▀██',
48
+ ' ▄████▀▀█████',
49
+ '█▀ █▀ ▀█',
50
+ ];
51
+
52
+ const MARK_WIDTH = Math.max(...MARK.map((l) => l.length));
53
+ const GUTTER = 2;
54
+ export const MAX_BANNER_COLUMNS = 80;
55
+
56
+ /** Columns left for the detail rows once the mark and gutter are placed. */
57
+ const TEXT_COLUMNS = MAX_BANNER_COLUMNS - MARK_WIDTH - GUTTER;
58
+
59
+ /**
60
+ * Build the opening screen: mark on the left, facts on the right.
61
+ *
62
+ * @param {object} o
63
+ * @param {string} o.version
64
+ * @param {string} o.workspace already shortened by the caller
65
+ * @param {string} o.model
66
+ * @param {string} o.billing who this run will charge
67
+ * @param {string} o.canRun what it may execute, or that it may not
68
+ * @param {boolean} [o.interactive] whether a prompt follows
69
+ * @returns {string}
70
+ */
71
+ export function openingScreen({ version, workspace, model, billing, canRun, interactive = false, paint = null }) {
72
+ /**
73
+ * ── THE MARK IS BRAND GREEN, AND ONLY THE MARK ────────────────────────────
74
+ *
75
+ * Roman: *"can you colour our logo green in the terminal?"* #C8E91E, sampled
76
+ * from the brand PNG rather than picked by eye.
77
+ *
78
+ * ⚠️ THE PAINTER IS INJECTED, NOT IMPORTED. The caller already decided whether
79
+ * this stream can take colour it owns NO_COLOR, FORCE_COLOR, TERM=dumb and
80
+ * TTY detection. A module that reaches for colour itself will eventually
81
+ * disagree with that decision and write escapes into somebody's redirected
82
+ * file.
83
+ *
84
+ * ⚠️ AND ONLY THE LOGO. Colouring the detail rows would make the one line that
85
+ * must be read as a warning — `billing: YOUR OWN OpenRouter key` — compete
86
+ * with decoration.
87
+ */
88
+ const brand = paint?.brand ?? ((s) => s);
89
+ /**
90
+ * ── ⚠️⚠️ ELIDED, BECAUSE THE VALUES ARE NOT OURS ────────────────────────────
91
+ *
92
+ * Every value arrives at runtime: a deep monorepo path, a long model id, the
93
+ * shell-mode warning. The first version assumed they would be short and its
94
+ * own test caught it wrapping at 80 columns on ordinary inputs.
95
+ *
96
+ * ⭐ ELIDED IN THE MIDDLE, NOT THE END. The informative parts of a path are
97
+ * the drive and the leaf; chopping the tail leaves
98
+ * `C:\Users\somebody\Projects\a-`, which identifies nothing. Same for a model
99
+ * id, where the family is at the front and the variant at the back.
100
+ */
101
+ const room = TEXT_COLUMNS - 11;
102
+ const fit = (v) => {
103
+ const s = String(v ?? '');
104
+ if (s.length <= room) return s;
105
+ const head = Math.ceil((room - 1) / 2);
106
+ return `${s.slice(0, head)}…${s.slice(s.length - (room - 1 - head))}`;
107
+ };
108
+
109
+ const right = [
110
+ `ACUVO CODE${version ? ` ${version}` : ''}`,
111
+ '',
112
+ `workspace ${fit(workspace)}`,
113
+ `model ${fit(model)}`,
114
+ `billing ${fit(billing)}`,
115
+ `can run ${fit(canRun)}`,
116
+ '',
117
+ ];
118
+
119
+ /**
120
+ * ⚠️ THE TWO COLUMNS ARE ZIPPED, NOT CONCATENATED, and the row counts are
121
+ * allowed to differ — whichever is shorter simply runs out. Assuming they
122
+ * match would break the layout the first time a row is added to either side.
123
+ */
124
+ const rows = Math.max(MARK.length, right.length);
125
+ const lines = [''];
126
+ for (let i = 0; i < rows; i += 1) {
127
+ const text = right[i] ?? '';
128
+ /**
129
+ * ⚠️ PADDED FIRST, PAINTED SECOND — escape codes have no width, so padding a
130
+ * coloured string aligns the text against invisible bytes and the whole
131
+ * right-hand column drifts.
132
+ *
133
+ * ⚠️⚠️ AND PADDED ONLY WHEN SOMETHING FOLLOWS. On a mark-only row the
134
+ * padding sits INSIDE the colour, before the reset, where the `trimEnd`
135
+ * below cannot reach it — so the coloured banner carried trailing
136
+ * whitespace the plain one did not. Invisible, but it means colour changed
137
+ * the layout, which is exactly what the guard forbids.
138
+ */
139
+ const rawMark = MARK[i] ?? '';
140
+ const padded = text ? rawMark.padEnd(MARK_WIDTH + GUTTER) : rawMark;
141
+ const mark = MARK[i] ? brand(padded) : padded;
142
+ lines.push(`${mark}${text}`.trimEnd());
143
+ }
144
+ lines.push('');
145
+
146
+ /**
147
+ * ⚠️ ONLY WHEN A PROMPT ACTUALLY FOLLOWS. Printing "type what you want done"
148
+ * above a one-shot run that has already been given its task is an instruction
149
+ * for something the user cannot do, on the screen of a thing already working.
150
+ */
151
+ if (interactive) {
152
+ lines.push(' Type what you want done. /help for commands · exit to leave', '');
153
+ }
154
+ return lines.join('\n');
155
+ }
package/lib/colour.mjs CHANGED
@@ -32,6 +32,21 @@ const CODES = {
32
32
  green: '\x1b[32m', // it worked
33
33
  red: '\x1b[31m', // it failed or was refused
34
34
  cyan: '\x1b[36m', // a remote thing: MCP, a service
35
+ /**
36
+ * ── ⭐ THE BRAND GREEN, SAMPLED FROM THE MARK ITSELF ───────────────────────
37
+ *
38
+ * #C8E91E — the most common opaque pixel in
39
+ * `console/public/brand/acuvo-mark.png`, read rather than eyeballed, so the
40
+ * logo in a terminal is the same green as the one on the website.
41
+ *
42
+ * ⚠️ TRUECOLOR WITH A 256-COLOUR FALLBACK. `ESC[38;2;R;G;Bm` is exact and
43
+ * widely supported (Windows Terminal, iTerm2, kitty, modern VS Code) — but a
44
+ * terminal that does not speak it renders the sequence as LITERAL TEXT across
45
+ * the logo. Index 190 is the nearest lime in the 256-colour cube and is
46
+ * understood almost everywhere, so it ships unless truecolor is advertised.
47
+ */
48
+ brand: '\x1b[38;5;190m',
49
+ brandTrue: '\x1b[38;2;200;233;30m',
35
50
  };
36
51
 
37
52
  /**
@@ -66,6 +81,16 @@ export function createPainter(enabled = colourEnabled()) {
66
81
  green: wrap(CODES.green),
67
82
  red: wrap(CODES.red),
68
83
  cyan: wrap(CODES.cyan),
84
+ /**
85
+ * ⚠️ TRUECOLOR ONLY WHEN THE TERMINAL SAYS SO. `COLORTERM=truecolor` is the
86
+ * signal every emitter uses; without it a 24-bit sequence can print as raw
87
+ * text, which would smear escape codes across the first thing a user sees.
88
+ */
89
+ brand: wrap(
90
+ (process.env.COLORTERM === 'truecolor' || process.env.COLORTERM === '24bit')
91
+ ? CODES.brandTrue
92
+ : CODES.brand,
93
+ ),
69
94
  };
70
95
  }
71
96
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "acuvo-code",
3
- "version": "0.4.3",
3
+ "version": "0.4.4",
4
4
  "description": "Acuvo Code — the terminal client for the Acuvo capability registry. Zero dependencies, by design.",
5
5
  "type": "module",
6
6
  "bin": {