@opencode-cockpit/status 0.3.1 → 0.3.2
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/dist/cli/preview.js +1 -1
- package/dist/core/custom.js +19 -1
- package/dist/core/instructions.js +31 -0
- package/dist/core/report.js +54 -0
- package/dist/tui/index.js +48 -0
- package/package.json +4 -3
- package/skills/statusline-design/SKILL.md +21 -1
- package/types/cli/preview.d.ts +1 -0
- package/types/core/instructions.d.ts +19 -0
- package/types/core/report.d.ts +50 -0
package/dist/cli/preview.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
1
2
|
/**
|
|
2
3
|
* Draw your statusline in this terminal, against sample sessions, without restarting OpenCode.
|
|
3
4
|
*
|
|
@@ -10,7 +11,6 @@
|
|
|
10
11
|
* three of the mistakes were glyph choices that read differently in a terminal than they do in a
|
|
11
12
|
* sentence. Nothing here can tell you a design is good; it can tell you what it looks like.
|
|
12
13
|
*/
|
|
13
|
-
|
|
14
14
|
import { watch } from "node:fs";
|
|
15
15
|
import { asSegmentConfig, loadStatusConfig, resolveLines } from "../core/config.js";
|
|
16
16
|
import { loadCustomSegments, resolveModulePath } from "../core/custom.js";
|
package/dist/core/custom.js
CHANGED
|
@@ -55,8 +55,26 @@ const AUTHORING = "@opencode-cockpit/status/segment";
|
|
|
55
55
|
* imports still work. Only on failure: a module inside a project that installed the bay never
|
|
56
56
|
* takes this path.
|
|
57
57
|
*/
|
|
58
|
+
/**
|
|
59
|
+
* This file's own authoring module, from a checkout or from a build.
|
|
60
|
+
*
|
|
61
|
+
* The specifier is a string argument rather than an import, so the build's `./x.ts` → `./x.js`
|
|
62
|
+
* rewrite never touched it: published copies asked for a `.ts` that is not beside them and threw,
|
|
63
|
+
* which took out the whole fallback and with it every module living outside a project. Ask for
|
|
64
|
+
* both, in the order that keeps a checkout resolving to its source.
|
|
65
|
+
*/
|
|
66
|
+
function authoringModule() {
|
|
67
|
+
for (const candidate of ["./authoring.ts", "./authoring.js"]) {
|
|
68
|
+
try {
|
|
69
|
+
return Bun.resolveSync(candidate, import.meta.dir);
|
|
70
|
+
} catch {
|
|
71
|
+
// try the other extension
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
throw new Error("cannot find the statusline authoring module beside this one");
|
|
75
|
+
}
|
|
58
76
|
async function importWithAuthoring(full) {
|
|
59
|
-
const resolved =
|
|
77
|
+
const resolved = authoringModule();
|
|
60
78
|
const source = await Bun.file(full).text();
|
|
61
79
|
const patched = source.replaceAll(AUTHORING, pathToFileURL(resolved).href);
|
|
62
80
|
if (patched === source) throw new Error(`does not import ${AUTHORING}`);
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The brief `/statusline` hands to the agent.
|
|
3
|
+
*
|
|
4
|
+
* The command draws nothing. Customising a statusline is an editing job — a JSON file the TUI never
|
|
5
|
+
* names, or a TypeScript module against an api that is written down only on a website — so the
|
|
6
|
+
* useful thing to put on screen is not a help panel the user then has to act on themselves. It is a
|
|
7
|
+
* message to the agent already sitting in the session, carrying the facts it cannot look up: which
|
|
8
|
+
* config file this project actually reads, what is in it right now, and where the taste rules live.
|
|
9
|
+
*
|
|
10
|
+
* Plain text rather than a UI also means the same brief works from anywhere the agent can be asked
|
|
11
|
+
* a question, and can be tested as a string.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { BUILTINS } from "./builtins/index.js";
|
|
15
|
+
import { PRESETS } from "./config.js";
|
|
16
|
+
/** The file an edit should go to: the project's if it exists, else the one for every project. */
|
|
17
|
+
export function targetConfig(report) {
|
|
18
|
+
const project = report.sources[1];
|
|
19
|
+
const global = report.sources[0];
|
|
20
|
+
const chosen = project?.found ? project : global ?? project;
|
|
21
|
+
return {
|
|
22
|
+
path: chosen?.path ?? "",
|
|
23
|
+
exists: chosen?.found ?? false
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
export function statuslineBrief(report) {
|
|
27
|
+
const target = targetConfig(report);
|
|
28
|
+
const drawing = report.lines.length === 0 ? "nothing — no line is configured" : report.lines.map(line => `${line.surface} (${line.stack}), ${line.segments} segment${line.segments === 1 ? "" : "s"}`).join("; ");
|
|
29
|
+
const modules = report.modules.listed.length === 0 ? "none" : `${report.modules.listed.join(", ")} — ${report.modules.registered} segment${report.modules.registered === 1 ? "" : "s"} registered`;
|
|
30
|
+
return ["Help me customise my opencode-cockpit statusline.", "", "## Where it stands", "", `- Config to edit: ${target.path}${target.exists ? "" : " (does not exist yet — create it)"}`, `- Drawing now: ${drawing}`, `- My modules: ${modules}`, ...(report.modules.errors.length > 0 ? ["- Failing to load:", ...report.modules.errors.map(error => ` - ${error}`)] : []), `- Version: ${report.version}`, "", "## What you can change", "", 'The config file holds `{ "statusline": { ... } }`. Two surfaces: `bottom` (a line under the', "prompt) and `sidebar` (a column). A whole line by name with `preset`, then anything written", "beside it wins:", "", ...Object.entries(PRESETS).map(([name, preset]) => `- \`"preset": "${name}"\` — ${preset.about} (${preset.surface})`), "", `Built-in segment names: ${BUILTINS.map(segment => segment.name).join(", ")}.`, 'A segment can also be `{ "type": "...", ... }` with its own settings, or a shell command.', "", "For anything the built-ins do not cover, write a TypeScript module and list it in `modules`:", "it exports `{ segments: { name(ctx, config) { return { runs: [...] } } } }` against", "`@opencode-cockpit/status/segment`, is handed a snapshot rather than OpenCode's api, and is", "called on every repaint so it can keep history. Returning `undefined` hides a segment.", "", "## Before you say it is done", "", "Look at it. Do not edit, restart OpenCode and judge from a sentence:", "", "```sh", "bunx @opencode-cockpit/status preview --watch # redraws on every save", "bunx @opencode-cockpit/status preview --debug # mark segments that drew nothing", "bunx @opencode-cockpit/status preview --module <my module> --state full", "```", "", "The design rules are a skill shipped with the package at", "`node_modules/@opencode-cockpit/status/skills/statusline-design/SKILL.md` — read it before", "designing anything, and copy the examples beside it rather than inventing glyphs. The reference", "is https://codestz.github.io/opencode-cockpit/status/.", "", "Ask me what I want it to show before you edit anything."].join("\n");
|
|
31
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What the statusline is actually doing right now, as data.
|
|
3
|
+
*
|
|
4
|
+
* The bay's own rule is that a segment with nothing to say says nothing, which is right on screen
|
|
5
|
+
* and leaves exactly one question unanswerable from the screen itself: is this line quiet because
|
|
6
|
+
* there is nothing to report, or because the config never arrived? This is the answer — which files
|
|
7
|
+
* were read, which surfaces are drawing, how many segments each carries, and what a module did when
|
|
8
|
+
* it failed to load. The `/statusline` command draws it; it lives here because it is a pure
|
|
9
|
+
* function of the settings, and so can be tested without a terminal.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { existsSync } from "node:fs";
|
|
13
|
+
import { join } from "node:path";
|
|
14
|
+
import { globalConfigPath, PROJECT_FILE } from "./config.js";
|
|
15
|
+
export function buildReport(input) {
|
|
16
|
+
const global = globalConfigPath(input.env ?? process.env);
|
|
17
|
+
const project = join(input.directory, PROJECT_FILE);
|
|
18
|
+
return {
|
|
19
|
+
version: input.version,
|
|
20
|
+
sources: [global, project].map(path => ({
|
|
21
|
+
path,
|
|
22
|
+
found: existsSync(path)
|
|
23
|
+
})),
|
|
24
|
+
lines: input.lines.map(line => ({
|
|
25
|
+
surface: line.surface,
|
|
26
|
+
stack: line.stack,
|
|
27
|
+
segments: line.segments.length,
|
|
28
|
+
maxRows: line.stack === "vertical" ? line.maxRows : undefined
|
|
29
|
+
})),
|
|
30
|
+
modules: {
|
|
31
|
+
listed: [...(input.modules ?? [])],
|
|
32
|
+
registered: input.registered,
|
|
33
|
+
errors: [...input.errors]
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* The one sentence a report is worth opening for: whether anything is drawing at all, and if not,
|
|
40
|
+
* the likeliest reason given what was found.
|
|
41
|
+
*/
|
|
42
|
+
export function reportHeadline(report) {
|
|
43
|
+
if (report.modules.errors.length > 0) {
|
|
44
|
+
const count = report.modules.errors.length;
|
|
45
|
+
return `${count} module${count === 1 ? "" : "s"} failed to load — segments from ${count === 1 ? "it" : "them"} are missing`;
|
|
46
|
+
}
|
|
47
|
+
if (report.lines.length === 0) return "Nothing is drawing: no line is configured";
|
|
48
|
+
if (report.sources.every(source => !source.found)) {
|
|
49
|
+
return "Drawing the defaults — neither config file exists yet";
|
|
50
|
+
}
|
|
51
|
+
const rows = report.lines.reduce((sum, line) => sum + line.segments, 0);
|
|
52
|
+
const where = [...new Set(report.lines.map(line => line.surface))].join(" and ");
|
|
53
|
+
return `${rows} segment${rows === 1 ? "" : "s"} across the ${where}`;
|
|
54
|
+
}
|
package/dist/tui/index.js
CHANGED
|
@@ -7,7 +7,9 @@ import { createMemo } from "solid-js";
|
|
|
7
7
|
import pkg from "../../package.json" with { type: "json" };
|
|
8
8
|
import { asSegmentConfig, loadStatusConfig, resolveLines } from "../core/config.js";
|
|
9
9
|
import { loadCustomSegments } from "../core/custom.js";
|
|
10
|
+
import { statuslineBrief } from "../core/instructions.js";
|
|
10
11
|
import { fit, fitColumn } from "../core/render.js";
|
|
12
|
+
import { buildReport } from "../core/report.js";
|
|
11
13
|
import { buildSegments } from "../core/segments.js";
|
|
12
14
|
import { StatusLine } from "./components/statusline.js";
|
|
13
15
|
import { buildContext } from "./state/snapshot.js";
|
|
@@ -37,6 +39,7 @@ export function createStatusTui({
|
|
|
37
39
|
|
|
38
40
|
// Your own segments, loaded before the first draw so they are never missing from frame one.
|
|
39
41
|
let custom = new Map();
|
|
42
|
+
const moduleErrors = [];
|
|
40
43
|
if (config.modules?.length) {
|
|
41
44
|
const loaded = await loadCustomSegments(config.modules, directory);
|
|
42
45
|
custom = loaded.segments;
|
|
@@ -47,6 +50,7 @@ export function createStatusTui({
|
|
|
47
50
|
* — a whole session was once spent diagnosing an import that had already explained itself
|
|
48
51
|
* and then disappeared.
|
|
49
52
|
*/
|
|
53
|
+
moduleErrors.push(...loaded.errors);
|
|
50
54
|
for (const error of loaded.errors) {
|
|
51
55
|
api.ui.toast({
|
|
52
56
|
variant: "error",
|
|
@@ -101,6 +105,50 @@ export function createStatusTui({
|
|
|
101
105
|
}
|
|
102
106
|
});
|
|
103
107
|
};
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* `/statusline` draws nothing. Customising a line is an editing job in a file the TUI never
|
|
111
|
+
* names, so the useful thing is not a help panel the user then has to act on themselves — it is
|
|
112
|
+
* a brief handed to the agent already in the session, carrying what it cannot look up: which
|
|
113
|
+
* config file this project reads, what is in it now, and what would not load.
|
|
114
|
+
*/
|
|
115
|
+
api.keymap.registerLayer({
|
|
116
|
+
commands: [{
|
|
117
|
+
name: "cockpit.status.customise",
|
|
118
|
+
title: "Statusline: ask the agent to customise it",
|
|
119
|
+
category: "Statusline",
|
|
120
|
+
namespace: "palette",
|
|
121
|
+
slashName: "statusline",
|
|
122
|
+
run: () => {
|
|
123
|
+
const brief = statuslineBrief(buildReport({
|
|
124
|
+
version: pkg.version,
|
|
125
|
+
directory,
|
|
126
|
+
lines,
|
|
127
|
+
modules: config.modules,
|
|
128
|
+
registered: custom.size,
|
|
129
|
+
errors: moduleErrors
|
|
130
|
+
}));
|
|
131
|
+
/**
|
|
132
|
+
* Sent, not left in the prompt. The brief is forty lines; parked in the input it is a
|
|
133
|
+
* wall of text the user has to scroll past to type their own sentence, and it ends by
|
|
134
|
+
* asking what they want anyway — so the agent is the right place for it to land.
|
|
135
|
+
*
|
|
136
|
+
* On the next tick, because running a slash command clears the prompt it was typed
|
|
137
|
+
* into: writing during the command itself is wiped a moment later, which looks exactly
|
|
138
|
+
* like a command that did nothing.
|
|
139
|
+
*/
|
|
140
|
+
setTimeout(() => {
|
|
141
|
+
void api.client.tui.appendPrompt({
|
|
142
|
+
text: brief
|
|
143
|
+
}).then(() => api.client.tui.submitPrompt()).catch(() => api.ui.toast({
|
|
144
|
+
variant: "error",
|
|
145
|
+
title: "Statusline",
|
|
146
|
+
message: "could not reach the prompt"
|
|
147
|
+
}));
|
|
148
|
+
}, 0);
|
|
149
|
+
}
|
|
150
|
+
}]
|
|
151
|
+
});
|
|
104
152
|
const on = surface => lines.filter(spec => spec.surface === surface);
|
|
105
153
|
const bottom = on("bottom");
|
|
106
154
|
const sidebar = on("sidebar");
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@opencode-cockpit/status",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.2",
|
|
4
4
|
"description": "A statusline for OpenCode you can actually configure: declarative segments, a typed module, or your existing Claude Code statusline command",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -57,7 +57,7 @@
|
|
|
57
57
|
"access": "public"
|
|
58
58
|
},
|
|
59
59
|
"dependencies": {
|
|
60
|
-
"@opencode-cockpit/client": "0.3.
|
|
60
|
+
"@opencode-cockpit/client": "0.3.2",
|
|
61
61
|
"@opencode-ai/plugin": "1.18.31"
|
|
62
62
|
},
|
|
63
63
|
"devDependencies": {
|
|
@@ -67,6 +67,7 @@
|
|
|
67
67
|
"solid-js": "1.9.12"
|
|
68
68
|
},
|
|
69
69
|
"bin": {
|
|
70
|
-
"opencode-statusline": "./dist/cli/preview.js"
|
|
70
|
+
"opencode-statusline": "./dist/cli/preview.js",
|
|
71
|
+
"status": "./dist/cli/preview.js"
|
|
71
72
|
}
|
|
72
73
|
}
|
|
@@ -36,15 +36,35 @@ look solid", and the two interpretations share no code.
|
|
|
36
36
|
| **No walls of zeroes on a fresh session.** Check the `fresh` and `empty` fixtures. | Most designs look right mid-session and read as broken before the first reply. |
|
|
37
37
|
| **Every number gets a word.** Colour may repeat the meaning, never carry it alone. | A row distinguished only by colour is unreadable: "I read `mix` and I don't understand the colours." |
|
|
38
38
|
| **Labels in a fixed-width column, values after.** | Alignment is what makes a column read as designed rather than as output. |
|
|
39
|
-
| **Bars are solid.** Filled cells `█` coloured by level; the empty track is `█` in
|
|
39
|
+
| **Bars are solid.** Filled cells `█` coloured by level; the empty track is `█` in **`border`** tone. | `░` reads as floating gaps and `─` reads as `-----`. Both were rejected on sight. Swapping one rejected glyph for another is not iteration. `panel` is the colour of the panel the bar sits on, so a track drawn in it is invisible. |
|
|
40
40
|
| **Single-width glyphs only.** | An emoji is two cells in most terminals and one in a few — exactly what shears a fixed-width line. |
|
|
41
41
|
| **Prefer a coloured rule `▌` to a filled pill.** | A filled block must be as wide as its text, so a short label leaves a slab of colour and an empty one leaves an empty box. |
|
|
42
42
|
| **Prefer a figure to a moving picture.** | A sparkline redraws its shape every second; movement in peripheral vision is the one thing a statusline must not do. `+1.2%/min · 48m left` changes digits and nothing else. |
|
|
43
43
|
| **No section headings above optional rows.** | A heading cannot know whether the rows under it will draw, so `SPEND` strands itself above nothing on an unpriced model. Self-label the rows instead. |
|
|
44
44
|
| **Emphasis is a bonus, never the meaning.** Bold, italic and underline are `<b>`, `<i>`, `<u>` markup — and a terminal with no bold face draws bold identically to plain. | Colour and background always render; weight may not. There is no strikethrough or inverse at all. |
|
|
45
45
|
| **Colours come from tones, not hexes.** `text muted accent success warning error info background panel border` | A literal ignores the user's theme, which is the first thing that makes a plugin look bolted on. Use a hex only where the exact colour *is* the meaning. |
|
|
46
|
+
| **No end caps on a bar in a column.** `▕` and `▏` are eighth-blocks whose ink sits hard against one edge of the cell. | An opening cap indents the row by most of a column, and the bar stops lining up with the labels above and below it. Caps are fine on a line, where nothing has to align. |
|
|
47
|
+
| **Print a number once.** A bar and the figure beside it are one row; the same percentage on the row below is a second copy. | In a column of ten rows the repeated figure is the thing the eye catches on. Let the bar be cells and let the labelled row carry the number. |
|
|
46
48
|
| **Say what a number means in the word, not the docs.** `cache` is cache reads, `write` is cache writes, `in` is fresh prompt tokens, `out` is output plus reasoning. | Read and write are not in and out; a reader who has to learn your mapping will misread it. |
|
|
47
49
|
|
|
50
|
+
## The six states, and what each one catches
|
|
51
|
+
|
|
52
|
+
A design is judged mid-session and ships broken everywhere else. `--state <name>` draws one; with
|
|
53
|
+
no flag the preview draws all six. Every one of these has caught something real:
|
|
54
|
+
|
|
55
|
+
| State | What it is | What it catches |
|
|
56
|
+
| --- | --- | --- |
|
|
57
|
+
| `fresh` | a session before the first reply: no model, no tokens, no cost | the wall of zeroes, and `0%` against a window nobody has declared |
|
|
58
|
+
| `working` | a few turns in, most of the window served from cache | the ordinary case — and that `cache` dwarfs `in` and `out`, which a layout has to survive |
|
|
59
|
+
| `full` | nearly out of room, a long session | the widest every number gets: `191.6k`, `100%`, four-figure spend. Column widths that only fit `85.2k` shear here |
|
|
60
|
+
| `unpriced` | behind a proxy, nothing declared in the catalogue | segments that invent `$0.00` rather than staying silent |
|
|
61
|
+
| `retrying` | a stalled turn, retry pending | a status that reads "busy" forever, and rows that vanish mid-turn because a streaming message reports zeroes |
|
|
62
|
+
| `empty` | no session at all — what a window shows at start-up | the half of the line that is mounted before anything exists |
|
|
63
|
+
|
|
64
|
+
Two rules fall out of them: **check `fresh` and `empty` before you call anything done**, because
|
|
65
|
+
they are what a new user sees first; and **size every column against `full`**, not against the
|
|
66
|
+
state you happen to be looking at.
|
|
67
|
+
|
|
48
68
|
## The renderer's contract
|
|
49
69
|
|
|
50
70
|
- One segment draws **one row**, unless it returns an **array** of pieces — then each element is a
|
package/types/cli/preview.d.ts
CHANGED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The brief `/statusline` hands to the agent.
|
|
3
|
+
*
|
|
4
|
+
* The command draws nothing. Customising a statusline is an editing job — a JSON file the TUI never
|
|
5
|
+
* names, or a TypeScript module against an api that is written down only on a website — so the
|
|
6
|
+
* useful thing to put on screen is not a help panel the user then has to act on themselves. It is a
|
|
7
|
+
* message to the agent already sitting in the session, carrying the facts it cannot look up: which
|
|
8
|
+
* config file this project actually reads, what is in it right now, and where the taste rules live.
|
|
9
|
+
*
|
|
10
|
+
* Plain text rather than a UI also means the same brief works from anywhere the agent can be asked
|
|
11
|
+
* a question, and can be tested as a string.
|
|
12
|
+
*/
|
|
13
|
+
import type { StatusReport } from "./report.ts";
|
|
14
|
+
/** The file an edit should go to: the project's if it exists, else the one for every project. */
|
|
15
|
+
export declare function targetConfig(report: StatusReport): {
|
|
16
|
+
path: string;
|
|
17
|
+
exists: boolean;
|
|
18
|
+
};
|
|
19
|
+
export declare function statuslineBrief(report: StatusReport): string;
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What the statusline is actually doing right now, as data.
|
|
3
|
+
*
|
|
4
|
+
* The bay's own rule is that a segment with nothing to say says nothing, which is right on screen
|
|
5
|
+
* and leaves exactly one question unanswerable from the screen itself: is this line quiet because
|
|
6
|
+
* there is nothing to report, or because the config never arrived? This is the answer — which files
|
|
7
|
+
* were read, which surfaces are drawing, how many segments each carries, and what a module did when
|
|
8
|
+
* it failed to load. The `/statusline` command draws it; it lives here because it is a pure
|
|
9
|
+
* function of the settings, and so can be tested without a terminal.
|
|
10
|
+
*/
|
|
11
|
+
import { type ResolvedLine } from "./config.ts";
|
|
12
|
+
export interface ReportLine {
|
|
13
|
+
surface: string;
|
|
14
|
+
stack: string;
|
|
15
|
+
segments: number;
|
|
16
|
+
/** Vertical lines only: the cap that decides which rows survive. */
|
|
17
|
+
maxRows?: number;
|
|
18
|
+
}
|
|
19
|
+
export interface StatusReport {
|
|
20
|
+
version: string;
|
|
21
|
+
/** Every config file the settings could have come from, in merge order, and whether it exists. */
|
|
22
|
+
sources: {
|
|
23
|
+
path: string;
|
|
24
|
+
found: boolean;
|
|
25
|
+
}[];
|
|
26
|
+
lines: ReportLine[];
|
|
27
|
+
modules: {
|
|
28
|
+
/** Paths listed in `modules`, as written. */
|
|
29
|
+
listed: string[];
|
|
30
|
+
/** Segments those modules actually registered. */
|
|
31
|
+
registered: number;
|
|
32
|
+
/** One per module that would not load, already phrased for a human. */
|
|
33
|
+
errors: string[];
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
export interface ReportInput {
|
|
37
|
+
version: string;
|
|
38
|
+
directory: string;
|
|
39
|
+
lines: readonly ResolvedLine[];
|
|
40
|
+
modules?: readonly string[];
|
|
41
|
+
registered: number;
|
|
42
|
+
errors: readonly string[];
|
|
43
|
+
env?: Record<string, string | undefined>;
|
|
44
|
+
}
|
|
45
|
+
export declare function buildReport(input: ReportInput): StatusReport;
|
|
46
|
+
/**
|
|
47
|
+
* The one sentence a report is worth opening for: whether anything is drawing at all, and if not,
|
|
48
|
+
* the likeliest reason given what was found.
|
|
49
|
+
*/
|
|
50
|
+
export declare function reportHeadline(report: StatusReport): string;
|