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 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 **121 files — 119 in `lib/`, 2 in
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 121 files and 79376 lines,
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
+ }
@@ -28,8 +28,22 @@ import { accountDir } from './account.mjs';
28
28
 
29
29
  export const PACKAGE_NAME = 'acuvo-code';
30
30
 
31
- /** How long between registry checks. A day is plenty and keeps npm quiet. */
32
- export const CHECK_INTERVAL_MS = 24 * 60 * 60 * 1000;
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
- if (!force && cached && now() - (cached.at ?? 0) < intervalMs) {
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
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "acuvo-code",
3
- "version": "0.6.4",
3
+ "version": "0.6.6",
4
4
  "description": "Acuvo Code — the terminal client for the Acuvo capability registry. Zero dependencies, by design.",
5
5
  "type": "module",
6
6
  "bin": {