feat(cli): sunset wordmark and section hierarchy in terminal output

Add a section-chrome module (chrome.ts) and a log decorator
(log-render.ts) that give the CLI a coherent visual hierarchy beneath
the SHANNON splash, all drawn from the existing sunset ramp:

- splash.ts now sources the ramp from chrome.ts (one definition)
- start's launch info gets a solid-yellow rule under each section
  label, with aligned Label: values greyed
- streaming scan logs (logs, and start --follow) get one per-phase
  gutter bar that walks the ramp, dimmed timestamps, red on [ERROR],
  and the end-of-run summary framed in a corner panel

Named chrome.ts to avoid the existing ui.ts (spinner/step helpers).
Presentation only: the worker and workflow.log are untouched, the log
on disk stays plain text (tail/grep and the failure marker keep
working), and non-TTY / NO_COLOR output is byte-identical to before.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
george-keygraph
2026-08-31 10:19:30 -07:00
co-authored by Claude Opus 4.8
parent 6108de3cfc
commit d017d74519
6 changed files with 352 additions and 43 deletions
+8 -2
View File
@@ -11,8 +11,10 @@ import fs from 'node:fs';
import path from 'node:path';
import { setTimeout as sleep } from 'node:timers/promises';
import { watch } from 'chokidar';
import { field } from '../chrome.js';
import { fail } from '../errors.js';
import { getWorkspacesDir } from '../home.js';
import { LogRenderer } from '../log-render.js';
import { resolveRunFile } from '../paths.js';
import { resolveWorkflowId } from '../session.js';
import { waitForWorkflowClose } from '../temporal-client.js';
@@ -90,6 +92,9 @@ export function tailUntilComplete(logFile: string, opts: TailOptions = {}): Prom
let position = 0;
let done = false;
let sawFailure = false;
// Decorates the streamed log for the terminal; a pass-through when colour is off, so
// piped/redirected output stays byte-identical and the failure check still sees raw text.
const renderer = new LogRenderer();
const controller = new AbortController();
let watcher: ReturnType<typeof watch> | undefined;
@@ -99,7 +104,7 @@ export function tailUntilComplete(logFile: string, opts: TailOptions = {}): Prom
const { size } = fs.statSync(logFile);
if (size <= position) return;
const data = readRange(logFile, position, size);
process.stdout.write(data);
process.stdout.write(renderer.write(data));
position = size;
if (!sawFailure && FAILURE_MARKER.test(data)) {
sawFailure = true;
@@ -112,6 +117,7 @@ export function tailUntilComplete(logFile: string, opts: TailOptions = {}): Prom
function finish(): void {
if (done) return;
done = true;
process.stdout.write(renderer.end());
controller.abort();
if (watcher) {
watcher.close().finally(() => resolve({ sawFailure }));
@@ -165,7 +171,7 @@ export function tailUntilComplete(logFile: string, opts: TailOptions = {}): Prom
export function logs(workspaceId: string): void {
const logFile = resolveLogFile(workspaceId);
const workflowId = resolveWorkflowId(workspaceId);
console.error(stdoutIsTerminal() ? `Tailing scan log: ${logFile}` : 'Tailing scan log');
console.error(stdoutIsTerminal() ? field(`Tailing scan log: ${logFile}`) : 'Tailing scan log');
let unreachable = false;
tailUntilComplete(logFile, {
+14 -13
View File
@@ -10,6 +10,7 @@ import fs from 'node:fs';
import path from 'node:path';
import { setTimeout as sleep } from 'node:timers/promises';
import * as p from '@clack/prompts';
import { field, rule } from '../chrome.js';
import { ensureDocker, ensureImage, ensureInfra, randomSuffix, spawnWorker } from '../docker.js';
import { buildEnvFlags, loadEnv, resolveHostPiAuthPath, shouldUsePiAuth, validateCredentials } from '../env.js';
import { fail } from '../errors.js';
@@ -298,9 +299,9 @@ async function followScan(workspace: string, workspacesDir: string): Promise<nev
function printPreservedContainerHint(containerName: string): void {
console.log('');
console.log(` Worker container preserved: ${containerName}`);
console.log(` Inspect logs: docker logs ${containerName}`);
console.log(` Remove: docker rm ${containerName}`);
console.log(field(` Worker container preserved: ${containerName}`));
console.log(field(` Inspect logs: docker logs ${containerName}`));
console.log(field(` Remove: docker rm ${containerName}`));
console.log('');
}
@@ -312,19 +313,19 @@ function printInfo(args: StartArgs, workspace: string, repoPath: string, workspa
console.log('');
}
console.log(` Target: ${args.url}`);
console.log(` Repository: ${interactive ? repoPath : path.basename(repoPath)}`);
console.log(` Workspace: ${workspace}`);
console.log(field(` Target: ${args.url}`));
console.log(field(` Repository: ${interactive ? repoPath : path.basename(repoPath)}`));
console.log(field(` Workspace: ${workspace}`));
if (args.config) {
console.log(` Config: ${interactive ? path.resolve(args.config) : path.basename(args.config)}`);
console.log(field(` Config: ${interactive ? path.resolve(args.config) : path.basename(args.config)}`));
}
if (args.pipelineTesting) {
console.log(' Mode: Pipeline Testing');
console.log(field(' Mode: Pipeline Testing'));
}
const spec = resolveModelSpec();
if (typeof spec !== 'string') {
console.log(` Model: ${spec.providerId}:${spec.modelId}`);
console.log(field(` Model: ${spec.providerId}:${spec.modelId}`));
}
if (!interactive) {
@@ -338,13 +339,13 @@ function printInfo(args: StartArgs, workspace: string, repoPath: string, workspa
if (!args.follow) {
const prefix = commandPrefix();
console.log('');
console.log(' Watch scan progress:');
console.log(` Live logs: ${prefix} logs ${workspace}`);
console.log(` Progress: ${prefix} status ${workspace}`);
console.log(rule('Watch scan progress:'));
console.log(field(` Live logs: ${prefix} logs ${workspace}`));
console.log(field(` Progress: ${prefix} status ${workspace}`));
}
console.log('');
console.log(' Report (when the scan finishes):');
console.log(rule('Report (when the scan finishes):'));
console.log(` ${reportPath}`);
console.log('');
}