@gridlock-compute/native-worker 0.1.1

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/README.md ADDED
@@ -0,0 +1,222 @@
1
+ # worker-cli
2
+
3
+ Headless GPU worker for the [Gridlock](https://grid-lock.tech) decentralized inference network. Connects to the [router](https://github.com/Gridlockcompute/router) over WebSocket, registers your **EVM wallet** (`0x…` on Robinhood Chain) as operator identity, and runs chat jobs locally via **Ollama** or **vLLM**.
4
+
5
+ **Production router:** [https://api.grid-lock.tech](https://api.grid-lock.tech)
6
+
7
+ ## What it is
8
+
9
+ `worker-cli` (npm package `@gridlock-compute/native-worker`) is the command-line worker client for operators who want to contribute GPU compute without a desktop UI. It:
10
+
11
+ 1. Detects your hardware (GPU or CPU) and runs a short throughput benchmark
12
+ 2. Registers with the Gridlock router (`POST /v1/workers/register`) and sets status **Active** so jobs can be dispatched
13
+ 3. Maintains REST heartbeats and a persistent WebSocket at `/v1/ws`
14
+ 4. Executes inference jobs pushed by the router and reports TTFT, TPOT, and token counts
15
+ 5. Optionally attaches a TEE attestation quote at registration and computes job attestation hashes for confidential work
16
+
17
+ Use the same **0x EVM address** here as on the [web worker dashboard](https://grid-lock.tech/worker) so earnings, jobs, and connection status appear in one place.
18
+
19
+ Set your **earnings wallet** on the worker dashboard (or via `PATCH /v1/workers/{address}/earnings-wallet`). SLA misses deduct from pending worker earnings; customers receive automatic ledger credits.
20
+
21
+ Stake native ETH separately from the web console for fee-share boosts — the CLI never needs your private key.
22
+
23
+ ## Features
24
+
25
+ - **Headless operation** — no Electron, no browser; ideal for servers and Linux GPU boxes
26
+ - **EVM identity** — validates `0x` addresses locally before registration
27
+ - **Live terminal dashboard** — status table with uptime, jobs, throughput, queue depth, and a progress bar while inference runs
28
+ - **WebSocket job dispatch** — real-time job push from the router with automatic reconnect
29
+ - **Hardware detection** — NVIDIA (`nvidia-smi`), AMD (`rocm-smi`), or `CPU · {model}` when no inference GPU is present
30
+ - **Benchmark mode** — `--benchmark` runs throughput test and exits
31
+ - **Confidential jobs** — registration attestation quote + per-job SHA-256 hash for TEE-tier work
32
+ - **Configurable roles** — register as Prefill, Decode, or other worker roles via env/flag
33
+
34
+ ## Prerequisites
35
+
36
+ - **Node.js** 18 or later
37
+ - **NVIDIA GPU** with CUDA drivers (recommended for local inference)
38
+ - **Ollama** ([download](https://ollama.com/download)) — recommended on Windows and macOS
39
+ - **vLLM** — recommended on Linux/WSL with an OpenAI-compatible server on port 8000
40
+ - An **EVM wallet address** (`0x…`) — private key never required in the CLI
41
+
42
+ ## Installation
43
+
44
+ **From npm (recommended):**
45
+
46
+ ```bash
47
+ npm install -g @gridlock-compute/native-worker
48
+ gridlock-native-worker --help
49
+ ```
50
+
51
+ **From source:**
52
+
53
+ ```bash
54
+ git clone https://github.com/Gridlockcompute/worker-cli.git
55
+ cd worker-cli
56
+ npm install
57
+ npm run build
58
+ ```
59
+
60
+ Install globally from source (optional):
61
+
62
+ ```bash
63
+ npm link
64
+ gridlock-native-worker --help
65
+ ```
66
+
67
+ Or run directly:
68
+
69
+ ```bash
70
+ node dist/index.js --help
71
+ ```
72
+
73
+ ## Quick start
74
+
75
+ ```bash
76
+ # 1. Install Ollama (https://ollama.com/download) or start vLLM on this machine
77
+
78
+ # 2. Start the worker — connects to https://api.grid-lock.tech
79
+ gridlock-native-worker --wallet 0xYourEvmAddress
80
+ ```
81
+
82
+ On first run with Ollama, the worker lists installed models and prompts you to pick one (or pulls the default if none are installed).
83
+
84
+ If you already have models, you'll see a selector like:
85
+
86
+ ```
87
+ Installed Ollama models:
88
+
89
+ 1. llama3.2:3b (2.0 GB)
90
+ 2. phi3:mini (2.2 GB)
91
+ 3. Pull llama3.1:8b (default — not installed yet)
92
+
93
+ Select model [1-3, default 1]:
94
+ ```
95
+
96
+ Pass `--model llama3.2:3b` or set `GRIDLOCK_OLLAMA_MODEL` to skip the prompt.
97
+
98
+ Windows PowerShell example:
99
+
100
+ ```powershell
101
+ npm install
102
+ npm run build
103
+ node dist/index.js --wallet 0xYourEvmAddress
104
+ ```
105
+
106
+ ## Configuration
107
+
108
+ ### CLI flags
109
+
110
+ | Flag | Default | Description |
111
+ |------|---------|-------------|
112
+ | `--wallet <address>` | — | EVM wallet address `0x…` (**required**, or set `GRIDLOCK_WALLET`) |
113
+ | `--inference <backend>` | `auto` | `auto`, `ollama`, or `vllm` |
114
+ | `--model <tag>` | — | Ollama model (e.g. `llama3.2:3b`); interactive picker if omitted and multiple models exist |
115
+ | `--benchmark` | — | Run benchmark only, then exit |
116
+ | `--version` | — | Print version |
117
+ | `--help` | — | Print help |
118
+
119
+ ### Environment variables
120
+
121
+ | Variable | Default | Description |
122
+ |----------|---------|-------------|
123
+ | `GRIDLOCK_WALLET` | — | EVM address (alternative to `--wallet`) |
124
+ | `GRIDLOCK_EARNINGS_WALLET` | operator wallet | Payout wallet sent at registration (`earnings_wallet`) |
125
+ | `GRIDLOCK_INFERENCE` | `auto` | Inference backend: `auto`, `ollama`, or `vllm` |
126
+ | `GRIDLOCK_OLLAMA_URL` | Ollama on this machine (port 11434) | Override if Ollama listens elsewhere |
127
+ | `GRIDLOCK_OLLAMA_MODEL` | `llama3.1:8b` | Ollama model tag |
128
+ | `GRIDLOCK_VLLM_URL` | vLLM on this machine (port 8000) | OpenAI-compatible vLLM base URL |
129
+ | `GRIDLOCK_VLLM_MODEL` | `meta-llama/Llama-3.1-8B-Instruct` | vLLM model id |
130
+ | `GRIDLOCK_ROLE` | `Prefill` | Worker role sent at registration |
131
+ | `GRIDLOCK_TEE_CAPABLE` | `false` | Set `true` to register as TEE-capable |
132
+ | `GRIDLOCK_ATTESTATION_QUOTE_FILE` | — | Path to production NRAS/SEV quote JSON for registration |
133
+ | `GRIDLOCK_ATTESTATION_QUOTE_JSON` | — | Inline attestation quote JSON (alternative to file) |
134
+ | `GRIDLOCK_TEE_TYPE` | `nvidia_cc` | TEE type in dev quotes (`nvidia_cc`, `amd_sev_snp`) |
135
+ | `GRIDLOCK_PLAIN_LOGS` | — | Set `true` to disable the live dashboard and use plain log lines |
136
+
137
+ All workers connect to **[https://api.grid-lock.tech](https://api.grid-lock.tech)** for registration, heartbeats, and WebSocket job dispatch. Inference (Ollama/vLLM) runs on your machine.
138
+
139
+ Only **one worker process per wallet** is allowed. A second instance will exit immediately (lock file in `~/.gridlock/`).
140
+
141
+ ## Usage examples
142
+
143
+ **Standard run:**
144
+
145
+ ```bash
146
+ export GRIDLOCK_WALLET=0xYourEvmAddress
147
+ gridlock-native-worker
148
+ ```
149
+
150
+ **Force vLLM backend:**
151
+
152
+ ```bash
153
+ gridlock-native-worker --wallet 0xYourEvmAddress --inference vllm
154
+ ```
155
+
156
+ **Benchmark only (no registration):**
157
+
158
+ ```bash
159
+ gridlock-native-worker --wallet 0xYourEvmAddress --benchmark
160
+ ```
161
+
162
+ **TEE / confidential registration (dev attestation quote from router challenge):**
163
+
164
+ ```bash
165
+ GRIDLOCK_TEE_CAPABLE=true gridlock-native-worker --wallet 0xYourEvmAddress
166
+ ```
167
+
168
+ For production CC GPUs, provide a vendor attestation quote:
169
+
170
+ ```bash
171
+ export GRIDLOCK_TEE_CAPABLE=true
172
+ export GRIDLOCK_ATTESTATION_QUOTE_FILE=/path/to/quote.json
173
+ gridlock-native-worker --wallet 0xYourEvmAddress
174
+ ```
175
+
176
+ ## On-chain registration
177
+
178
+ The CLI does **not** sign Robinhood Chain transactions itself. When the router has `WORKER_REGISTRY_ONCHAIN_ENABLED=true` and `EVM_*` contract addresses configured, the **router** may register your worker on-chain after `POST /v1/workers/register`. Staking, payouts, and billing are handled via the web dashboard and router APIs.
179
+
180
+ After starting, the worker connects to `wss://api.grid-lock.tech/v1/ws`, sends `worker:register` with `worker_type: native`, and waits for `job:new` messages.
181
+
182
+ Monitor your operator on the web: [https://grid-lock.tech/worker](https://grid-lock.tech/worker)
183
+
184
+ ## Development
185
+
186
+ ```bash
187
+ npm run dev # tsx watch — src/index.ts
188
+ npm run build # compile TypeScript → dist/
189
+ npm start # node dist/index.js
190
+ ```
191
+
192
+ Project layout:
193
+
194
+ ```
195
+ worker-cli/
196
+ ├── src/
197
+ │ ├── index.ts # CLI entry (commander)
198
+ │ ├── worker.ts # WebSocket session + job loop
199
+ │ ├── wallet.ts # EVM address validation
200
+ │ ├── attestation-quote.ts # Registration TEE quotes
201
+ │ ├── inference.ts # Ollama / vLLM adapters
202
+ │ ├── ollama.ts # Ollama bootstrap
203
+ │ ├── gpu.ts # GPU detection
204
+ │ └── attestation.ts # Per-job confidential hashing
205
+ ├── bin/
206
+ │ └── gridlock-native-worker.cjs
207
+ └── package.json
208
+ ```
209
+
210
+ ## Related repos
211
+
212
+ | Repo | Role |
213
+ |------|------|
214
+ | [router](https://github.com/Gridlockcompute/router) | Hono API — job routing, WebSocket hub, billing |
215
+ | [worker-desktop](https://github.com/Gridlockcompute/worker-desktop) | Electron GUI worker with setup wizard |
216
+ | [contracts](https://github.com/Gridlockcompute/gridlockcompute) | EVM Solidity contracts on Robinhood Chain |
217
+
218
+ **Website:** [https://grid-lock.tech](https://grid-lock.tech) · **API docs:** [https://grid-lock.tech/docs](https://grid-lock.tech/docs)
219
+
220
+ ## License
221
+
222
+ MIT
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+
4
+ import("../dist/index.js");
@@ -0,0 +1,11 @@
1
+ export interface AttestationQuotePayload {
2
+ worker_pubkey: string;
3
+ tee_type: string;
4
+ nonce: string;
5
+ enclave_pubkey: string;
6
+ report_bytes: string;
7
+ timestamp: number;
8
+ certificate_chain?: string[];
9
+ }
10
+ /** Resolve TEE attestation quote for worker registration (production file or dev challenge). */
11
+ export declare function resolveRegistrationAttestationQuote(backendUrl: string, wallet: string, teeCapable: boolean): Promise<AttestationQuotePayload | undefined>;
@@ -0,0 +1,56 @@
1
+ import { readFileSync } from "node:fs";
2
+ async function fetchChallenge(backendUrl, wallet) {
3
+ const base = backendUrl.replace(/\/$/, "");
4
+ const res = await fetch(`${base}/v1/attestation/challenge?wallet=${encodeURIComponent(wallet)}`);
5
+ if (!res.ok) {
6
+ const text = await res.text();
7
+ throw new Error(`attestation challenge failed (${res.status}): ${text.slice(0, 200)}`);
8
+ }
9
+ const data = (await res.json());
10
+ if (!data.nonce)
11
+ throw new Error("attestation challenge missing nonce");
12
+ return data.nonce;
13
+ }
14
+ function loadQuoteFromEnv() {
15
+ const file = process.env.GRIDLOCK_ATTESTATION_QUOTE_FILE?.trim();
16
+ if (file) {
17
+ return JSON.parse(readFileSync(file, "utf8"));
18
+ }
19
+ const json = process.env.GRIDLOCK_ATTESTATION_QUOTE_JSON?.trim();
20
+ if (json) {
21
+ return JSON.parse(json);
22
+ }
23
+ return null;
24
+ }
25
+ function buildDevAttestationQuote(wallet, nonce) {
26
+ const reportBytes = Buffer.from(`mock-${wallet.toLowerCase()}-${nonce}-${Date.now()}`, "utf8").toString("base64");
27
+ return {
28
+ worker_pubkey: wallet,
29
+ tee_type: process.env.GRIDLOCK_TEE_TYPE?.trim() || "nvidia_cc",
30
+ nonce,
31
+ enclave_pubkey: process.env.GRIDLOCK_ENCLAVE_PUBKEY?.trim() || wallet,
32
+ report_bytes: reportBytes,
33
+ timestamp: Date.now(),
34
+ certificate_chain: [],
35
+ };
36
+ }
37
+ /** Resolve TEE attestation quote for worker registration (production file or dev challenge). */
38
+ export async function resolveRegistrationAttestationQuote(backendUrl, wallet, teeCapable) {
39
+ if (!teeCapable)
40
+ return undefined;
41
+ const fromEnv = loadQuoteFromEnv();
42
+ if (fromEnv) {
43
+ console.log("[attestation] Using quote from GRIDLOCK_ATTESTATION_QUOTE_FILE / _JSON");
44
+ return fromEnv;
45
+ }
46
+ try {
47
+ const nonce = await fetchChallenge(backendUrl, wallet);
48
+ console.log("[attestation] Dev quote from router challenge (production CC GPUs: set GRIDLOCK_ATTESTATION_QUOTE_FILE)");
49
+ return buildDevAttestationQuote(wallet, nonce);
50
+ }
51
+ catch (error) {
52
+ const msg = error instanceof Error ? error.message : String(error);
53
+ console.warn(`[attestation] Skipping registration quote: ${msg}`);
54
+ return undefined;
55
+ }
56
+ }
@@ -0,0 +1,2 @@
1
+ /** Must match gridlock-backend/src/attestation.ts computeJobAttestationHash. */
2
+ export declare function computeJobAttestationHash(jobId: string, workerAddress: string, response: string): string;
@@ -0,0 +1,11 @@
1
+ import { createHash } from "node:crypto";
2
+ /** Must match gridlock-backend/src/attestation.ts computeJobAttestationHash. */
3
+ export function computeJobAttestationHash(jobId, workerAddress, response) {
4
+ return createHash("sha256")
5
+ .update(JSON.stringify({
6
+ jobId,
7
+ workerAddress,
8
+ response: response.slice(0, 512),
9
+ }))
10
+ .digest("hex");
11
+ }
@@ -0,0 +1,2 @@
1
+ /** Print banner; returns how many terminal rows it consumed. */
2
+ export declare function printStartupBanner(): number;
package/dist/banner.js ADDED
@@ -0,0 +1,41 @@
1
+ import { readFileSync } from "node:fs";
2
+ import { dirname, join } from "node:path";
3
+ import { fileURLToPath } from "node:url";
4
+ const __dirname = dirname(fileURLToPath(import.meta.url));
5
+ const LIME_GREEN = "\x1b[38;2;182;255;60m";
6
+ const RESET = "\x1b[0m";
7
+ const CHEVRONS = `
8
+ █████████ ███████████ █████ ██████████ █████ ███████ █████████ █████ ████
9
+ ███▒▒▒▒▒███▒▒███▒▒▒▒▒███ ▒▒███ ▒▒███▒▒▒▒███ ▒▒███ ███▒▒▒▒▒███ ███▒▒▒▒▒███▒▒███ ███▒
10
+ ███ ▒▒▒ ▒███ ▒███ ▒███ ▒███ ▒▒███ ▒███ ███ ▒▒███ ███ ▒▒▒ ▒███ ███
11
+ ▒███ ▒██████████ ▒███ ▒███ ▒███ ▒███ ▒███ ▒███▒███ ▒███████
12
+ ▒███ █████ ▒███▒▒▒▒▒███ ▒███ ▒███ ▒███ ▒███ ▒███ ▒███▒███ ▒███▒▒███
13
+ ▒▒███ ▒▒███ ▒███ ▒███ ▒███ ▒███ ███ ▒███ █▒▒███ ███ ▒▒███ ███ ▒███ ▒▒███
14
+ ▒▒█████████ █████ █████ █████ ██████████ ███████████ ▒▒▒███████▒ ▒▒█████████ █████ ▒▒████
15
+ ▒▒▒▒▒▒▒▒▒ ▒▒▒▒▒ ▒▒▒▒▒ ▒▒▒▒▒ ▒▒▒▒▒▒▒▒▒▒ ▒▒▒▒▒▒▒▒▒▒▒ ▒▒▒▒▒▒▒ ▒▒▒▒▒▒▒▒▒ ▒▒▒▒▒ ▒▒▒▒
16
+
17
+
18
+
19
+ `.trimEnd();
20
+ function shouldColorize() {
21
+ const { FORCE_COLOR, NO_COLOR } = process.env;
22
+ if (FORCE_COLOR !== undefined) {
23
+ return FORCE_COLOR !== "0" && FORCE_COLOR.toLowerCase() !== "false";
24
+ }
25
+ if (NO_COLOR !== undefined)
26
+ return false;
27
+ return process.stdout.isTTY === true;
28
+ }
29
+ function colorize(text) {
30
+ if (!shouldColorize())
31
+ return text;
32
+ return `${LIME_GREEN}${text}${RESET}`;
33
+ }
34
+ /** Print banner; returns how many terminal rows it consumed. */
35
+ export function printStartupBanner() {
36
+ const pkg = JSON.parse(readFileSync(join(__dirname, "..", "package.json"), "utf-8"));
37
+ console.log(colorize(CHEVRONS));
38
+ console.log(colorize(` Gridlock Native Worker v${pkg.version}`));
39
+ console.log();
40
+ return CHEVRONS.split("\n").length + 2;
41
+ }
@@ -0,0 +1,21 @@
1
+ /** Gridlock API — all workers connect here. */
2
+ export declare const GRIDLOCK_API_URL = "https://api.grid-lock.tech";
3
+ export declare function getBackendUrl(): string;
4
+ /** @deprecated Use getBackendUrl() */
5
+ export declare const DEFAULT_BACKEND_URL = "https://api.grid-lock.tech";
6
+ /** Gridlock router — jobs, registration, WebSocket. */
7
+ export declare const ROUTER_URL = "https://api.grid-lock.tech";
8
+ /** vLLM OpenAI-compatible API on this machine (separate inference process). */
9
+ export declare const VLLM_BASE_URL: string;
10
+ /** Resolved at runtime after probing Ollama on this host. */
11
+ export declare let OLLAMA_URL: string;
12
+ export declare function setOllamaUrl(url: string): void;
13
+ export declare const OLLAMA_URL_CANDIDATES: string[];
14
+ export type InferenceBackend = "auto" | "ollama" | "vllm";
15
+ export declare const INFERENCE_BACKEND: InferenceBackend;
16
+ export declare const VLLM_MODEL: string;
17
+ export declare const OLLAMA_MODEL: string;
18
+ export declare const DEFAULT_ROLE: string;
19
+ export declare const BENCHMARK_TOKENS = 32;
20
+ export declare const MAX_OUTPUT_TOKENS = 512;
21
+ export declare function wsUrl(httpBase: string): string;
package/dist/config.js ADDED
@@ -0,0 +1,34 @@
1
+ /** Gridlock API — all workers connect here. */
2
+ export const GRIDLOCK_API_URL = "https://api.grid-lock.tech";
3
+ export function getBackendUrl() {
4
+ return GRIDLOCK_API_URL;
5
+ }
6
+ /** @deprecated Use getBackendUrl() */
7
+ export const DEFAULT_BACKEND_URL = GRIDLOCK_API_URL;
8
+ /** Gridlock router — jobs, registration, WebSocket. */
9
+ export const ROUTER_URL = GRIDLOCK_API_URL;
10
+ const DEFAULT_OLLAMA_PORT = 11434;
11
+ const DEFAULT_VLLM_PORT = 8000;
12
+ /** vLLM OpenAI-compatible API on this machine (separate inference process). */
13
+ export const VLLM_BASE_URL = (process.env.GRIDLOCK_VLLM_URL ?? `http://127.0.0.1:${DEFAULT_VLLM_PORT}/v1`).replace(/\/$/, "");
14
+ /** Resolved at runtime after probing Ollama on this host. */
15
+ export let OLLAMA_URL = (process.env.GRIDLOCK_OLLAMA_URL ?? `http://127.0.0.1:${DEFAULT_OLLAMA_PORT}`).replace(/\/$/, "");
16
+ export function setOllamaUrl(url) {
17
+ OLLAMA_URL = url.replace(/\/$/, "");
18
+ }
19
+ export const OLLAMA_URL_CANDIDATES = [
20
+ process.env.GRIDLOCK_OLLAMA_URL?.replace(/\/$/, ""),
21
+ `http://127.0.0.1:${DEFAULT_OLLAMA_PORT}`,
22
+ ].filter(Boolean);
23
+ export const INFERENCE_BACKEND = (process.env.GRIDLOCK_INFERENCE ?? "auto");
24
+ export const VLLM_MODEL = process.env.GRIDLOCK_VLLM_MODEL ?? process.env.GRIDLOCK_MODEL ?? "meta-llama/Llama-3.1-8B-Instruct";
25
+ export const OLLAMA_MODEL = process.env.GRIDLOCK_OLLAMA_MODEL ?? "llama3.1:8b";
26
+ export const DEFAULT_ROLE = process.env.GRIDLOCK_ROLE ?? "Prefill";
27
+ export const BENCHMARK_TOKENS = 32;
28
+ export const MAX_OUTPUT_TOKENS = 512;
29
+ export function wsUrl(httpBase) {
30
+ const base = httpBase.replace(/\/$/, "");
31
+ if (base.startsWith("https://"))
32
+ return base.replace("https://", "wss://") + "/v1/ws";
33
+ return base.replace("http://", "ws://") + "/v1/ws";
34
+ }
@@ -0,0 +1,2 @@
1
+ /** Log without breaking the live stdout dashboard (uses stderr in dashboard mode). */
2
+ export declare function workerLog(...args: unknown[]): void;
@@ -0,0 +1,10 @@
1
+ import { isDashboardMode } from "./dashboard.js";
2
+ /** Log without breaking the live stdout dashboard (uses stderr in dashboard mode). */
3
+ export function workerLog(...args) {
4
+ if (isDashboardMode()) {
5
+ console.error(...args);
6
+ }
7
+ else {
8
+ console.log(...args);
9
+ }
10
+ }
@@ -0,0 +1,54 @@
1
+ export type ConnectionStatus = "starting" | "connected" | "disconnected" | "reconnecting";
2
+ export interface DashboardSnapshot {
3
+ wallet: string;
4
+ api: string;
5
+ hardware: string;
6
+ model: string;
7
+ inference: string;
8
+ benchmarkTokPerSec: number;
9
+ connection: ConnectionStatus;
10
+ workerStatus: string;
11
+ jobsCompleted: number;
12
+ jobsFailed: number;
13
+ jobsToday: number;
14
+ tokensTodayBase: number;
15
+ liveTokPerSec: number | null;
16
+ queueJobs: number | null;
17
+ inflightJobs: number | null;
18
+ workerInflightJobs: number | null;
19
+ currentJob: {
20
+ id: string;
21
+ tokens: number;
22
+ maxTokens: number;
23
+ startedAt: number;
24
+ } | null;
25
+ }
26
+ export interface LiveStatsPatch {
27
+ jobsToday: number;
28
+ tokensToday: number;
29
+ jobsFailedToday: number;
30
+ workerStatus: string;
31
+ wsTokPerSec: number;
32
+ queueJobs: number;
33
+ inflightJobs: number;
34
+ workerInflightJobs: number;
35
+ }
36
+ export interface DashboardOptions {
37
+ /** First terminal row for the dashboard box (1-based, below banner). */
38
+ anchorRow?: number;
39
+ }
40
+ export interface WorkerDashboard {
41
+ update(patch: Partial<DashboardSnapshot>): void;
42
+ applyLiveStats(stats: LiveStatsPatch): void;
43
+ setJob(jobId: string, maxTokens: number): void;
44
+ setJobProgress(tokens: number, maxTokens?: number): void;
45
+ clearJob(jobId: string, success: boolean, tokens: number, elapsedMs: number): void;
46
+ setConnection(status: ConnectionStatus): void;
47
+ note(message: string): void;
48
+ start(): void;
49
+ stop(): void;
50
+ }
51
+ /** Interactive TTY dashboard — suppresses startup log lines. */
52
+ export declare function isDashboardMode(): boolean;
53
+ export declare function createDashboard(initial: DashboardSnapshot, options?: DashboardOptions): WorkerDashboard;
54
+ export declare function plainLog(msg: string): void;