acuvo-code 0.6.4 → 0.6.6
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 +2 -2
- package/bin/acuvo.mjs +42 -1
- package/lib/render-report.mjs +136 -0
- package/lib/self-update.mjs +32 -3
- package/package.json +1 -1
package/ENTERPRISE.md
CHANGED
|
@@ -188,7 +188,7 @@ copy, but it *is* a place a process starts, and this is a list of those. Six and
|
|
|
188
188
|
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
|
-
one went stale in turn.** The package ships **
|
|
191
|
+
one went stale in turn.** The package ships **122 files — 120 in `lib/`, 2 in
|
|
192
192
|
`bin/` — about 78511 lines**, with **238 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
|
|
@@ -892,7 +892,7 @@ For completeness, the properties none of them offers:
|
|
|
892
892
|
(`lib/media.mjs`), and generates imagery with no configuration and no account
|
|
893
893
|
(`lib/imagegen.mjs`) — critiqued before it is accepted, and reported as unreviewed when
|
|
894
894
|
no critic is available.
|
|
895
|
-
- ⭐ **Zero dependencies.** The entire auditable surface is
|
|
895
|
+
- ⭐ **Zero dependencies.** The entire auditable surface is 122 files and 79582 lines,
|
|
896
896
|
and there is no `node_modules` behind it. (Counted 2026-08-22 from
|
|
897
897
|
`lib/*.mjs` + `bin/*.mjs`; `test/docs-truth.test.mjs` fails the build if this number
|
|
898
898
|
drifts, which is why it went 18 → 41 → 46 → 52 → 53 → 57 → 60 → 61 → 62 → 65 → 66 → 69 → 70 → 71 → 72 → 73 → 80 → 84 → 90 → 100 → 101 → 102 → 103 → 107 → 108 → 111 as modules landed (111 = the three that were WRITTEN and imported by nothing — `python.mjs`, `cache-floor.mjs`, `plan-coherence.mjs`; 108 = `warm-provider.mjs`, which keeps a session on the upstream that holds its prompt cache; 107 = `login.mjs`, the command that stores an Acuvo credential — until it existed, `writeAccount` was called by nothing and every user fell through to BYOK). ⚠️ Two of those three landed on this count while remaining UNREACHABLE, which is the sharpest illustration this document has that a file count is a claim about bytes, never about capability. ⭐ A
|
package/bin/acuvo.mjs
CHANGED
|
@@ -143,6 +143,23 @@ const asker = createAsker();
|
|
|
143
143
|
* word "compaction" to stop paying for a transcript they cannot see.
|
|
144
144
|
*/
|
|
145
145
|
import { runDoctor, formatDoctor } from '../lib/doctor.mjs';
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* The package version, readable from anywhere in this file.
|
|
149
|
+
*
|
|
150
|
+
* ⚠️ THE LOCAL `pkgVersion` IS DECLARED 300 LINES BELOW ITS FIRST USE and is a
|
|
151
|
+
* `const`, so reaching for it earlier is a temporal-dead-zone ReferenceError
|
|
152
|
+
* rather than an undefined — which is how `--render-report` crashed the moment
|
|
153
|
+
* it was wired in. A second reader is the small fix; hoisting the const would
|
|
154
|
+
* move a declaration other code depends on being late.
|
|
155
|
+
*/
|
|
156
|
+
function readPkgVersion() {
|
|
157
|
+
try {
|
|
158
|
+
return JSON.parse(readFileSync(new URL('../package.json', import.meta.url), 'utf8')).version;
|
|
159
|
+
} catch {
|
|
160
|
+
return '';
|
|
161
|
+
}
|
|
162
|
+
}
|
|
146
163
|
/**
|
|
147
164
|
* ── ⭐ SHELL COMPLETION — built, tested, and reachable from nothing until now ──
|
|
148
165
|
* `lib/completion.mjs` is 509 lines that generate bash, zsh and fish scripts
|
|
@@ -309,6 +326,7 @@ const LIFECYCLE_USAGE = [
|
|
|
309
326
|
* sentence changed.
|
|
310
327
|
*/
|
|
311
328
|
' --doctor Say what is actually working here: key, model chain, media',
|
|
329
|
+
' --render-report Print how this terminal draws the opening screen, to paste back',
|
|
312
330
|
' endpoints, which tools would be offered, git. Every dark or',
|
|
313
331
|
' broken line names the exact variable that fixes it. Exits 0',
|
|
314
332
|
' when nothing is broken. ⚠️ It VERIFIES over the network: your',
|
|
@@ -351,7 +369,7 @@ const VALUED_LIFECYCLE_FLAGS = new Map([
|
|
|
351
369
|
function extractLifecycleFlags(argv) {
|
|
352
370
|
const flags = {
|
|
353
371
|
sessions: false, resume: null, continueLatest: false, save: true, audit: true,
|
|
354
|
-
doctor: false, replay: null, diff: null, only: null, design: null,
|
|
372
|
+
doctor: false, renderReport: false, replay: null, diff: null, only: null, design: null,
|
|
355
373
|
login: false, loginToken: null, logout: false, whoami: false,
|
|
356
374
|
};
|
|
357
375
|
const rest = [];
|
|
@@ -359,6 +377,13 @@ function extractLifecycleFlags(argv) {
|
|
|
359
377
|
const arg = argv[i];
|
|
360
378
|
if (arg === '--sessions') { flags.sessions = true; continue; }
|
|
361
379
|
if (arg === '--doctor') { flags.doctor = true; continue; }
|
|
380
|
+
/**
|
|
381
|
+
* ⭐ THE INSTRUMENT FOR A SCREEN I CANNOT SEE. Five reports, four fixes,
|
|
382
|
+
* and every one reasoned from bytes that render perfectly on my machine.
|
|
383
|
+
* This prints the facts that decide the layout so the next round is
|
|
384
|
+
* arithmetic instead of inference.
|
|
385
|
+
*/
|
|
386
|
+
if (arg === '--render-report' || arg === '--render') { flags.renderReport = true; continue; }
|
|
362
387
|
if (arg === '--logout') { flags.logout = true; continue; }
|
|
363
388
|
if (arg === '--whoami') { flags.whoami = true; continue; }
|
|
364
389
|
/**
|
|
@@ -1185,6 +1210,22 @@ ${formatBoard(listed)}
|
|
|
1185
1210
|
return EXIT_OK;
|
|
1186
1211
|
}
|
|
1187
1212
|
|
|
1213
|
+
/**
|
|
1214
|
+
* ⚠️ BEFORE `--doctor`, AND BEFORE ANYTHING THAT PRINTS. This report is about
|
|
1215
|
+
* how the terminal DRAWS, so nothing may have written to the screen ahead of
|
|
1216
|
+
* it — a banner above the ruler would be measuring a screen we had already
|
|
1217
|
+
* disturbed.
|
|
1218
|
+
*/
|
|
1219
|
+
if (life.renderReport) {
|
|
1220
|
+
const { renderReport, formatRenderReport } = await import('../lib/render-report.mjs');
|
|
1221
|
+
const rep = await renderReport({ input: process.stdin, output: process.stdout, env: process.env, version: readPkgVersion() });
|
|
1222
|
+
if (opts.json) process.stdout.write(`${JSON.stringify(rep, null, 2)}
|
|
1223
|
+
`);
|
|
1224
|
+
else process.stdout.write(`${formatRenderReport(rep)}
|
|
1225
|
+
`);
|
|
1226
|
+
return EXIT_OK;
|
|
1227
|
+
}
|
|
1228
|
+
|
|
1188
1229
|
if (life.doctor) {
|
|
1189
1230
|
const report = await runDoctor({ root, allowRun: opts.allowRun, maxRounds: opts.maxRounds, skipNetwork: opts.offline === true });
|
|
1190
1231
|
if (opts.json) process.stdout.write(`${JSON.stringify(report, null, 2)}\n`);
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ── ⭐⭐⭐ STOP GUESSING AT SOMEBODY ELSE'S SCREEN ───────────────────────────
|
|
3
|
+
*
|
|
4
|
+
* Roman has now reported this screen wrong five times: "not opening as a
|
|
5
|
+
* typable terminal", "you have to scroll down", "now it's just the box,
|
|
6
|
+
* everything else is gone", "I can't even see our logo, it doesn't fit", and
|
|
7
|
+
* "CLI structure is still cooked, it's not loading like our Claude reference."
|
|
8
|
+
*
|
|
9
|
+
* ⚠️ FIVE REPORTS, FOUR FIXES, AND I HAVE NEVER ONCE SEEN THE THING I AM
|
|
10
|
+
* FIXING. Every attempt was reasoned from byte sequences that render perfectly
|
|
11
|
+
* here, and the only instrument that has ever caught a defect was him opening a
|
|
12
|
+
* terminal and describing it in words. That is a slow, lossy channel, and after
|
|
13
|
+
* four rounds it is plainly not converging.
|
|
14
|
+
*
|
|
15
|
+
* ⭐ SO THIS IS THE INSTRUMENT. One command prints every fact that determines
|
|
16
|
+
* how the opening screen draws — the emulator's identity, its real size, the
|
|
17
|
+
* MEASURED cell width of the glyphs the logo is built from, which layout that
|
|
18
|
+
* selects, and then the banner itself under a column ruler. Paste it back and
|
|
19
|
+
* the diagnosis is arithmetic instead of inference.
|
|
20
|
+
*
|
|
21
|
+
* ⚠️ IT PRINTS FACTS, NOT A VERDICT. A report that says "looks fine to me" when
|
|
22
|
+
* the user is staring at a broken screen is worse than no report — it argues
|
|
23
|
+
* with them. Every line here is something observed, and where nothing could be
|
|
24
|
+
* observed it says so.
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
import { openingScreen } from './banner.mjs';
|
|
28
|
+
import { measureCellWidth, bannerStyle } from './glyph-width.mjs';
|
|
29
|
+
|
|
30
|
+
/** The environment variables that actually change how this draws. */
|
|
31
|
+
const RELEVANT_ENV = [
|
|
32
|
+
'TERM', 'TERM_PROGRAM', 'TERM_PROGRAM_VERSION', 'COLORTERM',
|
|
33
|
+
'WT_SESSION', 'ConEmuANSI', 'SESSIONNAME',
|
|
34
|
+
'NO_COLOR', 'FORCE_COLOR', 'CI',
|
|
35
|
+
'ACUVO_BANNER', 'ACUVO_PIN', 'LANG', 'LC_ALL',
|
|
36
|
+
];
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Gather everything, measuring what can be measured.
|
|
40
|
+
*
|
|
41
|
+
* @param {object} o
|
|
42
|
+
* @param {NodeJS.ReadStream} o.input
|
|
43
|
+
* @param {NodeJS.WriteStream} o.output
|
|
44
|
+
* @param {Record<string,string|undefined>} [o.env]
|
|
45
|
+
* @param {string} [o.version]
|
|
46
|
+
*/
|
|
47
|
+
export async function renderReport({ input, output, env = process.env, version = '' }) {
|
|
48
|
+
/**
|
|
49
|
+
* ⚠️ MEASURED, NOT ASSUMED — and the ASCII fallback glyphs are measured too.
|
|
50
|
+
* If a terminal renders `/` or `\` at anything other than one cell we would
|
|
51
|
+
* want to know, because the "safe" fallback would not be safe either and the
|
|
52
|
+
* whole strategy needs rethinking rather than another patch.
|
|
53
|
+
*/
|
|
54
|
+
const block = await measureCellWidth({ glyph: '█', input, output });
|
|
55
|
+
const slash = await measureCellWidth({ glyph: '/', input, output });
|
|
56
|
+
|
|
57
|
+
const style = bannerStyle({ cellWidth: block, env });
|
|
58
|
+
|
|
59
|
+
return {
|
|
60
|
+
columns: output?.columns ?? null,
|
|
61
|
+
rows: output?.rows ?? null,
|
|
62
|
+
isTTY: Boolean(output?.isTTY),
|
|
63
|
+
stdinIsTTY: Boolean(input?.isTTY),
|
|
64
|
+
blockCellWidth: block,
|
|
65
|
+
asciiCellWidth: slash,
|
|
66
|
+
style,
|
|
67
|
+
env: Object.fromEntries(RELEVANT_ENV.map((k) => [k, env[k] ?? null])),
|
|
68
|
+
version,
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** A ruler that makes an off-by-N obvious at a glance. */
|
|
73
|
+
export function ruler(width) {
|
|
74
|
+
const w = Math.max(1, Math.min(400, Number(width) || 80));
|
|
75
|
+
let out = '';
|
|
76
|
+
for (let i = 1; i <= w; i += 1) {
|
|
77
|
+
out += i % 10 === 0 ? String((i / 10) % 10) : i % 5 === 0 ? '+' : '.';
|
|
78
|
+
}
|
|
79
|
+
return out;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Render the report as text safe to paste into a chat.
|
|
84
|
+
*
|
|
85
|
+
* ⚠️ NO COLOUR ANYWHERE IN THIS OUTPUT. It exists to be copied into a message,
|
|
86
|
+
* and escape codes pasted into a chat window arrive as visual noise that hides
|
|
87
|
+
* the very alignment the report is about.
|
|
88
|
+
*/
|
|
89
|
+
export function formatRenderReport(r) {
|
|
90
|
+
const say = (v) => (v === null || v === undefined ? 'unknown' : String(v));
|
|
91
|
+
const lines = [];
|
|
92
|
+
|
|
93
|
+
lines.push('ACUVO RENDER REPORT' + (r.version ? ` ${r.version}` : ''));
|
|
94
|
+
lines.push('');
|
|
95
|
+
lines.push(`terminal size ${say(r.columns)} x ${say(r.rows)}`);
|
|
96
|
+
lines.push(`stdout is a TTY ${r.isTTY}`);
|
|
97
|
+
lines.push(`stdin is a TTY ${r.stdinIsTTY}`);
|
|
98
|
+
lines.push('');
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* ⭐ THE TWO LINES THE WHOLE INVESTIGATION TURNS ON. A block width of 2 means
|
|
102
|
+
* the half-block logo CANNOT be drawn correctly here, and no amount of layout
|
|
103
|
+
* work will fix it — the answer is the ASCII mark. A width of 1 rules that
|
|
104
|
+
* out entirely and points the search somewhere else.
|
|
105
|
+
*/
|
|
106
|
+
lines.push(`block glyph width ${say(r.blockCellWidth)} cell(s) <- 1 = safe, 2 = the logo tears`);
|
|
107
|
+
lines.push(`ascii glyph width ${say(r.asciiCellWidth)} cell(s) <- must be 1, or the fallback is unsafe too`);
|
|
108
|
+
lines.push(`chosen mark ${r.style}`);
|
|
109
|
+
lines.push('');
|
|
110
|
+
|
|
111
|
+
const envLines = Object.entries(r.env).filter(([, v]) => v !== null);
|
|
112
|
+
lines.push(envLines.length ? 'environment' : 'environment (none of the relevant variables are set)');
|
|
113
|
+
for (const [k, v] of envLines) lines.push(` ${k.padEnd(20)} ${v}`);
|
|
114
|
+
lines.push('');
|
|
115
|
+
|
|
116
|
+
const width = r.columns ?? 80;
|
|
117
|
+
lines.push(`ruler (${width} columns) — the banner below must never reach the end of this line:`);
|
|
118
|
+
lines.push(ruler(width));
|
|
119
|
+
lines.push('');
|
|
120
|
+
lines.push(openingScreen({
|
|
121
|
+
version: r.version || '0.0.0',
|
|
122
|
+
workspace: 'C:/example/workspace',
|
|
123
|
+
model: 'Acuvo Flash 1',
|
|
124
|
+
billing: 'Acuvo account',
|
|
125
|
+
canRun: 'read + write + shell',
|
|
126
|
+
interactive: true,
|
|
127
|
+
style: r.style,
|
|
128
|
+
columns: r.columns,
|
|
129
|
+
}));
|
|
130
|
+
lines.push(ruler(width));
|
|
131
|
+
lines.push('');
|
|
132
|
+
lines.push('If the mark above looks torn or the rows do not line up, paste this whole');
|
|
133
|
+
lines.push('report back. Every number needed to diagnose it is in here.');
|
|
134
|
+
|
|
135
|
+
return lines.join('\n');
|
|
136
|
+
}
|
package/lib/self-update.mjs
CHANGED
|
@@ -28,8 +28,22 @@ import { accountDir } from './account.mjs';
|
|
|
28
28
|
|
|
29
29
|
export const PACKAGE_NAME = 'acuvo-code';
|
|
30
30
|
|
|
31
|
-
/**
|
|
32
|
-
|
|
31
|
+
/**
|
|
32
|
+
* How long between registry checks.
|
|
33
|
+
*
|
|
34
|
+
* ⚠️ WAS 24 HOURS, AND THAT NUMBER COST US FIVE ROUNDS OF A BUG REPORT.
|
|
35
|
+
* Measured on Roman's machine 2026-08-22: the cache read
|
|
36
|
+
* `{"at":13:47,"latest":"0.2.1"}`. It was correct when written. Four hours
|
|
37
|
+
* later 0.6.5 was out, and his install would not look again until the
|
|
38
|
+
* following afternoon — so three consecutive fixes to the opening screen were
|
|
39
|
+
* invisible to him and he kept reporting a defect that had already been fixed
|
|
40
|
+
* twice. "Users get updates as soon as we ship" was false by up to a day.
|
|
41
|
+
*
|
|
42
|
+
* ⭐ Six hours, so a fix shipped in the morning lands the same working day.
|
|
43
|
+
* The registry call is one HTTP request with a 3s timeout, at EXIT — four a
|
|
44
|
+
* day is not rude, and being a day stale on a young product is expensive.
|
|
45
|
+
*/
|
|
46
|
+
export const CHECK_INTERVAL_MS = 6 * 60 * 60 * 1000;
|
|
33
47
|
|
|
34
48
|
/**
|
|
35
49
|
* Compare two semver-ish strings.
|
|
@@ -96,7 +110,22 @@ export async function checkForUpdate({
|
|
|
96
110
|
force = false,
|
|
97
111
|
} = {}) {
|
|
98
112
|
const cached = readCache(cachePath);
|
|
99
|
-
|
|
113
|
+
/**
|
|
114
|
+
* ── ⭐⭐⭐ A CACHE THAT CLAIMS AN OLDER VERSION IS "LATEST" IS PROVABLY WRONG
|
|
115
|
+
*
|
|
116
|
+
* If we are RUNNING 0.6.2 and the cache says the newest published version is
|
|
117
|
+
* 0.2.1, then the cache is stale — no reasoning about clocks required, it is
|
|
118
|
+
* contradicted by the file we are executing from. This is exactly the state
|
|
119
|
+
* Roman's machine was in: he had manually installed a newer build, which left
|
|
120
|
+
* a throttle entry insisting on a version four minors behind it, and that
|
|
121
|
+
* entry then suppressed every check for the rest of the day.
|
|
122
|
+
*
|
|
123
|
+
* ⭐ It costs one comparison and it converts the worst failure mode of a
|
|
124
|
+
* throttle — silently pinning somebody to an answer that is already known to
|
|
125
|
+
* be wrong — into a single extra HTTP request.
|
|
126
|
+
*/
|
|
127
|
+
const cacheIsStale = cached && compareVersions(current, cached.latest) > 0;
|
|
128
|
+
if (!force && !cacheIsStale && cached && now() - (cached.at ?? 0) < intervalMs) {
|
|
100
129
|
return { latest: cached.latest ?? current, isNewer: compareVersions(cached.latest, current) > 0, checked: false };
|
|
101
130
|
}
|
|
102
131
|
|