acuvo-code 0.3.3 → 0.3.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.
- package/ENTERPRISE.md +1 -1
- package/lib/banner.mjs +76 -48
- package/lib/chat.mjs +25 -1
- package/package.json +1 -1
package/ENTERPRISE.md
CHANGED
|
@@ -189,7 +189,7 @@ are the numbers to quote. Counting is the first thing a reviewer does.
|
|
|
189
189
|
|
|
190
190
|
⚠️ **This said "18 shipped files", then "41", then "90", then "101", then "108", and every
|
|
191
191
|
one went stale in turn.** The package ships **119 files — 117 in `lib/`, 2 in
|
|
192
|
-
`bin/` — about
|
|
192
|
+
`bin/` — about 77604 lines**, with **235 test files** beside them (counted 2026-08-22).
|
|
193
193
|
|
|
194
194
|
⭐ **AND THE 108 WENT STALE IN THE MOST INSTRUCTIVE WAY POSSIBLE: THREE OF THE FILES IT
|
|
195
195
|
MISSED WERE REACHABLE FROM NOTHING.** `wiring-reach.test.mjs` was naming
|
package/lib/banner.mjs
CHANGED
|
@@ -3,36 +3,61 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Roman, 2026-08-22, having typed it: *"it's not opening as a typable terminal,
|
|
5
5
|
* with acuvo logo top left and details up top etc, like how claude code opens.
|
|
6
|
-
* have you actually designed the page?"*
|
|
6
|
+
* have you actually designed the page?"* — and then: *"no we want OUR logo"*.
|
|
7
7
|
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
* they type anything.
|
|
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.
|
|
12
11
|
*
|
|
13
|
-
* ── ⚠️
|
|
12
|
+
* ── ⚠️ WHY IT IS EMBEDDED RATHER THAN DECODED AT RUNTIME ────────────────────
|
|
14
13
|
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
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.
|
|
23
35
|
*/
|
|
24
36
|
|
|
25
37
|
/**
|
|
26
|
-
*
|
|
27
|
-
*
|
|
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.
|
|
28
41
|
*/
|
|
29
|
-
const
|
|
30
|
-
'
|
|
31
|
-
'
|
|
42
|
+
const MARK = [
|
|
43
|
+
' ▄█',
|
|
44
|
+
' ▄███',
|
|
45
|
+
' ▄█▀▀██',
|
|
46
|
+
' ▄█▀ ▀██',
|
|
47
|
+
' ▄█▀ ██ ▀██',
|
|
48
|
+
' ▄████▀▀█████',
|
|
49
|
+
'█▀ █▀ ▀█',
|
|
32
50
|
];
|
|
33
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
|
+
|
|
34
59
|
/**
|
|
35
|
-
* Build the opening screen.
|
|
60
|
+
* Build the opening screen: mark on the left, facts on the right.
|
|
36
61
|
*
|
|
37
62
|
* @param {object} o
|
|
38
63
|
* @param {string} o.version
|
|
@@ -47,36 +72,47 @@ export function openingScreen({ version, workspace, model, billing, canRun, inte
|
|
|
47
72
|
/**
|
|
48
73
|
* ── ⚠️⚠️ ELIDED, BECAUSE THE VALUES ARE NOT OURS ────────────────────────────
|
|
49
74
|
*
|
|
50
|
-
* Every value
|
|
51
|
-
*
|
|
52
|
-
*
|
|
53
|
-
* perfectly ordinary inputs.
|
|
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.
|
|
54
78
|
*
|
|
55
|
-
* ⭐ ELIDED IN THE MIDDLE, NOT THE END. The informative parts of a path are
|
|
56
|
-
* drive and the leaf; chopping the tail leaves
|
|
57
|
-
* which identifies nothing. Same for a model
|
|
58
|
-
* front and the variant at the back.
|
|
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.
|
|
59
83
|
*/
|
|
60
|
-
const room =
|
|
84
|
+
const room = TEXT_COLUMNS - 11;
|
|
61
85
|
const fit = (v) => {
|
|
62
86
|
const s = String(v ?? '');
|
|
63
87
|
if (s.length <= room) return s;
|
|
64
88
|
const head = Math.ceil((room - 1) / 2);
|
|
65
|
-
|
|
66
|
-
return `${s.slice(0, head)}…${s.slice(s.length - tail)}`;
|
|
89
|
+
return `${s.slice(0, head)}…${s.slice(s.length - (room - 1 - head))}`;
|
|
67
90
|
};
|
|
68
|
-
|
|
69
|
-
const
|
|
70
|
-
''
|
|
71
|
-
` ${WORDMARK[0]}`,
|
|
72
|
-
` ${WORDMARK[1]} CODE${version ? ` ${version}` : ''}`,
|
|
91
|
+
|
|
92
|
+
const right = [
|
|
93
|
+
`ACUVO CODE${version ? ` ${version}` : ''}`,
|
|
73
94
|
'',
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
95
|
+
`workspace ${fit(workspace)}`,
|
|
96
|
+
`model ${fit(model)}`,
|
|
97
|
+
`billing ${fit(billing)}`,
|
|
98
|
+
`can run ${fit(canRun)}`,
|
|
78
99
|
'',
|
|
79
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
|
+
|
|
80
116
|
/**
|
|
81
117
|
* ⚠️ ONLY WHEN A PROMPT ACTUALLY FOLLOWS. Printing "type what you want done"
|
|
82
118
|
* above a one-shot run that has already been given its task is an instruction
|
|
@@ -87,11 +123,3 @@ export function openingScreen({ version, workspace, model, billing, canRun, inte
|
|
|
87
123
|
}
|
|
88
124
|
return lines.join('\n');
|
|
89
125
|
}
|
|
90
|
-
|
|
91
|
-
/**
|
|
92
|
-
* ⚠️ EXPORTED SO A TEST CAN ASSERT THE WIDTH RULE RATHER THAN TRUST IT. The
|
|
93
|
-
* banner is assembled from values supplied at runtime — a long model id or a
|
|
94
|
-
* deep workspace path is what would push it over, and neither is visible from
|
|
95
|
-
* reading this file.
|
|
96
|
-
*/
|
|
97
|
-
export const MAX_BANNER_COLUMNS = 80;
|
package/lib/chat.mjs
CHANGED
|
@@ -353,9 +353,33 @@ export async function runChat({
|
|
|
353
353
|
|
|
354
354
|
try {
|
|
355
355
|
for (;;) {
|
|
356
|
+
/**
|
|
357
|
+
* ── ⭐⭐ THE INPUT IS A BOX, BECAUSE A BARE `› ` IS NOT A PLACE TO TYPE ──
|
|
358
|
+
*
|
|
359
|
+
* Roman, comparing against a real Claude Code screenshot: *"you can see
|
|
360
|
+
* the box where you type, acuvo doesn't have that."* He is right — the
|
|
361
|
+
* prompt was two characters floating in the scrollback, which reads as
|
|
362
|
+
* output rather than as somewhere input goes.
|
|
363
|
+
*
|
|
364
|
+
* ⚠️ THE BOX OPENS BEFORE THE LINE AND CLOSES AFTER IT, deliberately.
|
|
365
|
+
* Drawing all four sides up front needs absolute cursor control, and
|
|
366
|
+
* readline owns the cursor once `question()` starts — every version of
|
|
367
|
+
* that fights the line editor the moment input wraps, a history entry is
|
|
368
|
+
* recalled, or the window is resized. Opening on entry and closing on
|
|
369
|
+
* submit needs no cursor math at all, survives all three, and leaves a
|
|
370
|
+
* transcript where each turn is visibly a closed unit.
|
|
371
|
+
*
|
|
372
|
+
* ⚠️ WIDTH IS READ FRESH EACH TURN — a terminal resized mid-session would
|
|
373
|
+
* otherwise draw rules at the old width for the rest of the run. Clamped,
|
|
374
|
+
* because `columns` is `undefined` when stdout is not a TTY and enormous
|
|
375
|
+
* when someone maximises on an ultrawide.
|
|
376
|
+
*/
|
|
377
|
+
const width = Math.max(40, Math.min(100, (output.columns ?? process.stdout.columns ?? 80) - 1));
|
|
378
|
+
if (interactive) output.write(`\n╭${'─'.repeat(width - 1)}\n`);
|
|
356
379
|
const line = interactive
|
|
357
|
-
? await ask(rl, '› ', state)
|
|
380
|
+
? await ask(rl, '│ › ', state)
|
|
358
381
|
: (queueIndex < queued.length ? queued[queueIndex++] : null);
|
|
382
|
+
if (interactive && line !== null) output.write(`╰${'─'.repeat(width - 1)}\n`);
|
|
359
383
|
// Echo a piped prompt so a scripted transcript reads like a session.
|
|
360
384
|
if (!interactive && line !== null && line.trim()) output.write(`› ${line.trim()}
|
|
361
385
|
`);
|