@cesarus777/opencode-cgp 0.2.3 → 0.3.0

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.
@@ -8,3 +8,4 @@ export * from "./snapshot.js";
8
8
  export * from "./family.js";
9
9
  export * from "./performance.js";
10
10
  export * from "./charts.js";
11
+ export * from "./modelanalytics.js";
@@ -0,0 +1,5 @@
1
+ import type { DashboardConnectionOptions } from "./connection.js";
2
+ export declare const dashboardHelp = "Usage: context-guard dashboard tui [--server URL] [--directory PATH]\nRead-only standalone terminal dashboard; never starts an OpenCode server.\nRequires an interactive terminal, Bun >=1.3.0 and @opentui/core@0.5.17.\nInstall the renderer beside this package: npm install @opentui/core@0.5.17\nLocal discovery defaults to the current directory.\nAn explicit server requires --directory with an owning absolute server path.\nRemote authentication: CONTEXT_GUARD_PASSWORD and optional CONTEXT_GUARD_USERNAME\n(default opencode). Credentials must never be supplied in the URL.\nKeys: 1 overview, 2 detail, 3 history, arrows/j/k move, / search, f filters,\nl location, w history window, r refresh, ? help, Escape back, q/Ctrl+C quit.";
3
+ export declare function parseDashboardArgs(args: readonly string[], cwd: string, env: Readonly<Record<string, string | undefined>>): DashboardConnectionOptions | "help";
4
+ export declare function checkTerminalRuntime(version: string, interactive: boolean): void;
5
+ export declare function loadTerminalRenderer(): Promise<typeof import("@opentui/core")>;
@@ -0,0 +1,36 @@
1
+ import { Service } from "@opencode/client/service";
2
+ import type { HistoryChartRead } from "../historycharts.js";
3
+ import type { SnapshotTransport } from "../tui/transport.js";
4
+ import type { DashboardPort, SessionPageInput } from "./port.js";
5
+ export interface DashboardConnectionOptions {
6
+ readonly directory: string;
7
+ readonly server?: string;
8
+ /** Explicit remote authentication, held only in this process. */
9
+ readonly password?: string;
10
+ readonly username?: string;
11
+ /** Isolated registration file for the connection spike. */
12
+ readonly serviceFile?: string;
13
+ }
14
+ /** One SDK client; no server lifecycle calls or session mutations are exposed. */
15
+ export declare function connectDashboard(options: DashboardConnectionOptions, discovery?: Pick<typeof Service, "discover" | "headers"> & {
16
+ readonly fetch?: typeof globalThis.fetch;
17
+ }): Promise<{
18
+ info(signal: AbortSignal): Promise<import("@opencode/client").ServerInfo>;
19
+ location: {
20
+ readonly directory: string;
21
+ };
22
+ atLocation(directory: string): DashboardPort;
23
+ listSessions(input: SessionPageInput, signal: AbortSignal): Promise<{
24
+ readonly data: readonly import("./port.js").DashboardSession[];
25
+ readonly cursor?: {
26
+ readonly next?: string | null;
27
+ };
28
+ }>;
29
+ activeSessions(ids: readonly string[], signal: AbortSignal): Promise<readonly string[]>;
30
+ monitor(signal: AbortSignal): Promise<{
31
+ readonly version: 1 | 2;
32
+ readonly transport: SnapshotTransport;
33
+ }>;
34
+ history(input: HistoryChartRead, signal: AbortSignal): Promise<import("../historycharts.js").HistoryChartResult>;
35
+ }>;
36
+ export type DashboardConnection = Awaited<ReturnType<typeof connectDashboard>>;
@@ -0,0 +1,57 @@
1
+ import { type InventoryLimits } from "./inventory.js";
2
+ import type { DashboardPort } from "./port.js";
3
+ import { type DashboardView, type DashboardScreen, type DashboardFilters, type HistoryWindow } from "./view.js";
4
+ export interface DashboardControllerOptions {
5
+ readonly limits?: InventoryLimits;
6
+ readonly now?: () => number;
7
+ readonly elapsed?: () => number;
8
+ /** Zero disables automatic inventory/age ticks for deterministic callers. */
9
+ readonly refreshIntervalMs?: number;
10
+ }
11
+ /** Shared application state; subscribers never acquire additional upstream streams. */
12
+ export declare class DashboardController {
13
+ private readonly locations;
14
+ private readonly cache;
15
+ private readonly listeners;
16
+ private readonly limits;
17
+ private readonly now;
18
+ private readonly elapsed;
19
+ private readonly refreshIntervalMs;
20
+ private state;
21
+ private scope;
22
+ private running;
23
+ private screen;
24
+ private filters;
25
+ private windowMs;
26
+ private generation;
27
+ private historyGeneration;
28
+ private pendingHistory;
29
+ private historyAbort;
30
+ private historyBusy;
31
+ private historyStatus;
32
+ private refreshTimer;
33
+ private ageTimer;
34
+ private view;
35
+ constructor(port: DashboardPort, options?: DashboardControllerOptions);
36
+ private createLocation;
37
+ getView(): DashboardView;
38
+ subscribe(listener: (view: DashboardView) => void): () => void;
39
+ private publish;
40
+ start(): Promise<void>;
41
+ stop(): void;
42
+ selectLocation(directory: string): Promise<void>;
43
+ selectSession(id: string): void;
44
+ setScreen(screen: DashboardScreen): void;
45
+ setWindow(windowMs: HistoryWindow): void;
46
+ setFilters(filters: Partial<DashboardFilters>): void;
47
+ refresh(): Promise<void>;
48
+ private current;
49
+ private scan;
50
+ private monitor;
51
+ private cacheKey;
52
+ private cancelHistory;
53
+ private queueHistory;
54
+ private pumpHistory;
55
+ private serverNow;
56
+ private buildView;
57
+ }
@@ -0,0 +1,17 @@
1
+ import type { DashboardPort, DashboardSession } from "./port.js";
2
+ export interface InventoryLimits {
3
+ readonly maxNodes: number;
4
+ readonly maxPages: number;
5
+ readonly maxDepth: number;
6
+ }
7
+ export type InventoryNotice = "node-limit" | "page-limit" | "depth-limit" | "repeated-cursor" | "inventory-unavailable" | "foreign-location" | "invalid-hierarchy";
8
+ export interface DashboardInventory {
9
+ readonly sessions: readonly DashboardSession[];
10
+ readonly complete: boolean;
11
+ readonly pages: number;
12
+ readonly notices: readonly InventoryNotice[];
13
+ }
14
+ export declare const inventoryLimits: InventoryLimits;
15
+ export declare function validateInventoryLimits(limits: InventoryLimits): void;
16
+ /** Bounded breadth-first discovery of a whole location, without transcript reads. */
17
+ export declare function discoverInventory(port: Pick<DashboardPort, "location" | "listSessions">, signal: AbortSignal, limits?: InventoryLimits): Promise<DashboardInventory>;
@@ -0,0 +1,41 @@
1
+ import type { ExecutionStatus } from "../family.js";
2
+ import type { HistoryChartRead, HistoryChartResult } from "../historycharts.js";
3
+ import type { SnapshotTransport } from "../tui/transport.js";
4
+ /** The inventory boundary deliberately excludes titles, metadata, and content. */
5
+ export interface DashboardSession {
6
+ readonly sessionId: string;
7
+ readonly directory: string;
8
+ readonly parentId?: string;
9
+ readonly agent?: string;
10
+ readonly model?: string;
11
+ readonly outcome?: "succeeded" | "failed" | "interrupted";
12
+ readonly terminalAt?: number;
13
+ }
14
+ export interface SessionPageInput {
15
+ readonly parentID?: string | null;
16
+ readonly cursor?: string;
17
+ readonly limit?: number;
18
+ }
19
+ export interface DashboardPort {
20
+ readonly location: {
21
+ readonly directory: string;
22
+ };
23
+ /** Rebind the same authenticated client, never create another server/client. */
24
+ atLocation(directory: string): DashboardPort;
25
+ listSessions(input: SessionPageInput, signal: AbortSignal): Promise<{
26
+ readonly data: readonly DashboardSession[];
27
+ readonly cursor?: {
28
+ readonly next?: string | null;
29
+ };
30
+ }>;
31
+ activeSessions(ids: readonly string[], signal: AbortSignal): Promise<readonly string[]>;
32
+ monitor(signal: AbortSignal): Promise<{
33
+ readonly version: 1 | 2;
34
+ readonly transport: SnapshotTransport;
35
+ }>;
36
+ history(input: HistoryChartRead, signal: AbortSignal): Promise<HistoryChartResult>;
37
+ }
38
+ export interface InventoryNode extends DashboardSession {
39
+ readonly status: ExecutionStatus;
40
+ readonly inventoryStale: boolean;
41
+ }
@@ -0,0 +1,36 @@
1
+ import type { CliRenderer, TextRenderable } from "@opentui/core";
2
+ import { DashboardController } from "./controller.js";
3
+ import { type DashboardConnectionOptions } from "./connection.js";
4
+ type Editor = "search" | "agent" | "model" | "location";
5
+ export interface TerminalKey {
6
+ readonly name: string;
7
+ readonly sequence?: string;
8
+ readonly ctrl?: boolean;
9
+ readonly meta?: boolean;
10
+ }
11
+ /** Only presentation state lives here; application state stays in the controller. */
12
+ export declare class TerminalShell {
13
+ readonly controller: DashboardController;
14
+ private readonly quit;
15
+ scroll: number;
16
+ metric: number;
17
+ sample: number;
18
+ help: boolean;
19
+ editor: Editor | undefined;
20
+ input: string;
21
+ notice: string;
22
+ private rowOffset;
23
+ private historyIdentity;
24
+ constructor(controller: DashboardController, quit: () => void);
25
+ private task;
26
+ paste(text: string): void;
27
+ key(key: TerminalKey, page?: number): void;
28
+ frame(width: number, height: number): string;
29
+ }
30
+ /** Public renderer lifecycle; injected renderer permits offline native tests. */
31
+ export declare function mountTerminal(controller: DashboardController, renderer: CliRenderer, text: TextRenderable, finished: (failed: boolean) => void): {
32
+ shell: TerminalShell;
33
+ shutdown: (failed?: boolean) => void;
34
+ };
35
+ export declare function runTerminal(options: DashboardConnectionOptions): Promise<void>;
36
+ export {};
@@ -0,0 +1,15 @@
1
+ import type { DashboardRow, DashboardView } from "./view.js";
2
+ export declare function overviewRow(row: DashboardRow): string;
3
+ export declare const snapshotFields: readonly ["sessionId", "parentId", "agent", "model", "revision", "observedAt", "measurement.tokens", "measurement.at", "measurement.model", "measurement.provenance", "measurement.scope", "measurement.confidence", "measurement.requestId", "limits.input", "limits.context", "limits.output", "capacityTokens", "outputReserveTokens", "peakTokens", "policy.id", "policy.configured.type", "policy.configured.value", "policy.triggerTokens", "policy.comparison", "policy.reason", "policy.crossed", "compaction.phase", "compaction.count", "compaction.armed", "compaction.blockedReason", "compaction.lastAttempt", "compaction.lastCompleted", "compaction.cooldownUntil", "cumulativeScope", "cumulative.input", "cumulative.output", "cumulative.reasoning", "cumulative.cache.read", "cumulative.cache.write", "lastObservedActive", "performance.sessionCreatedAt", "performance.sessionOutputTokens", "performance.observedSince", "performance.at", "performance.status", "performance.partial", "performance.liveTps", "performance.averageModelTps", "performance.averageSessionTps", "performance.ttftMs", "performance.latestRequestTps", "performance.requestId", "performance.requestAgent", "performance.requestModel", "performance.requestOutputTokens", "performance.requestStartedAt", "performance.firstOutputAt", "performance.streamEndedAt", "performance.estimatedOutputTokens", "performance.modelOutputTokens", "performance.modelDurationMs", "performance.completedRequests", "performance.tokenSource"];
4
+ export declare function terminalDetail(row: DashboardRow | undefined): string[];
5
+ interface Observation {
6
+ time: number;
7
+ value: number | undefined;
8
+ details: Readonly<Record<string, unknown>>;
9
+ }
10
+ export declare function historyObservations(view: DashboardView, metricIndex: number): Observation[];
11
+ export declare function terminalHistory(view: DashboardView, width: number, metricIndex: number, sample: number): string[];
12
+ export declare const terminalHelp: string[];
13
+ export declare function wrapped(lines: readonly string[], width: number): string[];
14
+ export declare function headerLines(view: DashboardView, width: number): string[];
15
+ export {};
@@ -0,0 +1,83 @@
1
+ import type { GuardRow } from "../family.js";
2
+ import type { MonitorHealth } from "../snapshot.js";
3
+ import type { HistoryChartResult } from "../historycharts.js";
4
+ import type { Connection } from "../tui/transport.js";
5
+ import type { InventoryNotice } from "./inventory.js";
6
+ export type Immutable<T> = T extends object ? {
7
+ readonly [K in keyof T]: Immutable<T[K]>;
8
+ } : T;
9
+ export declare const historyWindows: readonly [300000, 3600000, 86400000, 604800000];
10
+ export type HistoryWindow = (typeof historyWindows)[number];
11
+ export type DashboardScreen = "overview" | "detail" | "history";
12
+ export interface DashboardFilters {
13
+ readonly search: string;
14
+ readonly agent: string;
15
+ readonly model: string;
16
+ }
17
+ export interface DashboardRow extends GuardRow {
18
+ readonly inventoryStale: boolean;
19
+ readonly liveStale: boolean;
20
+ readonly measurementAgeMs?: number;
21
+ readonly performanceAvailable: boolean;
22
+ readonly performanceFreshness: "fresh" | "stale" | "missing" | "invalid";
23
+ }
24
+ export interface HistorySummary {
25
+ readonly source: "matched-request-records";
26
+ readonly observedRequests: number;
27
+ readonly matchedRequests: number;
28
+ readonly partial: boolean;
29
+ readonly outputTokens?: number;
30
+ readonly durationMs?: number;
31
+ readonly averageModelTps?: number;
32
+ }
33
+ export interface DashboardHistory {
34
+ readonly status: "idle" | "loading" | "ready" | "disabled" | "unavailable";
35
+ readonly windowMs: HistoryWindow;
36
+ readonly maxWindowMs: 604800000;
37
+ readonly maxPoints: 500;
38
+ readonly sessionId?: string;
39
+ readonly from?: number;
40
+ readonly to?: number;
41
+ readonly stale: boolean;
42
+ readonly notices: readonly string[];
43
+ readonly result?: Immutable<HistoryChartResult>;
44
+ readonly summary?: HistorySummary;
45
+ }
46
+ export type DashboardView = Immutable<{
47
+ schemaVersion: 1;
48
+ generatedAt: number;
49
+ running: boolean;
50
+ location: string;
51
+ locations: readonly string[];
52
+ screen: DashboardScreen;
53
+ filters: DashboardFilters;
54
+ connection: Connection;
55
+ monitorVersion?: 1 | 2;
56
+ health?: MonitorHealth;
57
+ inventory: {
58
+ refreshing: boolean;
59
+ complete: boolean;
60
+ stale: boolean;
61
+ pages: number;
62
+ total: number;
63
+ visible: number;
64
+ monitored: number;
65
+ active: number;
66
+ maxNodes: number;
67
+ maxPages: number;
68
+ maxDepth: number;
69
+ notices: readonly InventoryNotice[];
70
+ };
71
+ rows: readonly DashboardRow[];
72
+ selectedSessionId?: string;
73
+ selected?: DashboardRow;
74
+ history: DashboardHistory;
75
+ notices: readonly string[];
76
+ }>;
77
+ /** Clone before freezing so transport-owned objects never become UI-owned state. */
78
+ export declare function immutableView<T>(value: T): T;
79
+ export declare function projectHistory(result: HistoryChartResult, filters: DashboardFilters): {
80
+ result: HistoryChartResult;
81
+ summary: HistorySummary;
82
+ notices: string[];
83
+ };
@@ -0,0 +1,60 @@
1
+ import type { HistoryRecord } from "./history.js";
2
+ export interface ModelAnalyticsOptions {
3
+ /** Caller-owned location identity; never inferred from archive contents. */
4
+ location: string;
5
+ model?: string;
6
+ agent?: string;
7
+ from?: number;
8
+ to?: number;
9
+ maxRequests?: number;
10
+ }
11
+ export interface AnalyticsRequest {
12
+ location: string;
13
+ sessionId: string;
14
+ requestId: string;
15
+ startedAt: number;
16
+ completedAt?: number;
17
+ model?: string;
18
+ agent?: string;
19
+ attribution: "request" | "legacy-record" | "unknown";
20
+ partial: boolean;
21
+ outputTokens?: number;
22
+ ttftMs?: number;
23
+ }
24
+ export interface ModelSummary {
25
+ model?: string;
26
+ sessions: number;
27
+ completedRequests: number;
28
+ partialRequests: number;
29
+ legacyAttributionRequests: number;
30
+ tpsRequests: number;
31
+ ttftRequests: number;
32
+ excludedTpsRequests: number;
33
+ excludedTtftRequests: number;
34
+ outputTokens: number;
35
+ durationMs: number;
36
+ averageTps?: number;
37
+ meanTtftMs?: number;
38
+ medianTtftMs?: number;
39
+ p95TtftMs?: number;
40
+ }
41
+ export interface ModelAnalyticsResult {
42
+ schemaVersion: 1;
43
+ location: string;
44
+ models: ModelSummary[];
45
+ coverage: {
46
+ source: "retained-request-records";
47
+ scannedRecords: number;
48
+ unscannedRecords: number;
49
+ duplicateRecords: number;
50
+ conflictingRequests: number;
51
+ missingIdentityRecords: number;
52
+ incompleteRequests: number;
53
+ invalidTimingRequests: number;
54
+ gapRecords: number;
55
+ maxRequests: number;
56
+ partial: boolean;
57
+ };
58
+ }
59
+ /** Aggregate raw request records before any chart downsampling or session limits. */
60
+ export declare function aggregateModels(records: readonly HistoryRecord[], options: ModelAnalyticsOptions): ModelAnalyticsResult;
@@ -0,0 +1 @@
1
+ export declare function inspectModels(args: readonly string[]): Promise<void>;
@@ -1,4 +1,4 @@
1
- import { type ContextSample } from "./contextchart.js";
1
+ import { type ContextSample, type ContextChartSegment } from "./contextchart.js";
2
2
  import type { GuardRow } from "../family.js";
3
3
  export declare function tokens(value: number | undefined): string;
4
4
  export declare function columns(left: string, right: string, width: number): string[];
@@ -6,8 +6,9 @@ export declare function progress(ratio: number | undefined, width: number): stri
6
6
  export interface MainSection {
7
7
  readonly tone: "base" | "execution" | "pressure" | "measurement" | "threshold";
8
8
  readonly lines: readonly string[];
9
+ readonly segments?: readonly ContextChartSegment[];
9
10
  }
10
- export declare function mainSections(row: GuardRow | undefined, width: number, age: number, samples?: readonly ContextSample[]): MainSection[];
11
+ export declare function mainSections(row: GuardRow | undefined, width: number, age: number, samples?: readonly ContextSample[], chartCapacity?: number): MainSection[];
11
12
  export declare function mainLines(row: GuardRow | undefined, width: number, age: number): string[];
12
13
  export declare const statusSymbol: {
13
14
  readonly running: "●";
@@ -2,18 +2,35 @@ import type { GuardRow } from "../family.js";
2
2
  export interface ContextSample {
3
3
  readonly at: number;
4
4
  readonly tokens?: number;
5
+ /** Display-only carry-forward; never a new measurement. */
6
+ readonly held?: boolean;
7
+ readonly compacted?: boolean;
5
8
  }
6
9
  export interface ContextTrace {
7
10
  readonly sessionId: string;
8
11
  readonly model: string | undefined;
9
12
  readonly capacity: number | undefined;
10
13
  readonly samples: readonly ContextSample[];
14
+ readonly lastCompleted?: number;
15
+ readonly pendingCompleted?: number;
11
16
  }
12
- /** Keep only actual measurements, never copies made by age/render ticks. */
17
+ /** Bounded display history. Raw usage and enforcement remain in the snapshot. */
13
18
  export declare function updateContextTrace(previous: ContextTrace | undefined, row: GuardRow | undefined): ContextTrace;
19
+ /** Share traces across sidebar/panel mounts without persisting provider data. */
20
+ export declare class ContextTraceStore {
21
+ private readonly traces;
22
+ update(row: GuardRow | undefined): ContextTrace;
23
+ clear(): void;
24
+ }
25
+ export interface ContextChartSegment {
26
+ readonly text: string;
27
+ readonly held: boolean;
28
+ readonly tone: "axis" | "threshold" | "value" | "held";
29
+ }
14
30
  export interface ContextChartLine {
15
31
  readonly line: string;
16
32
  readonly threshold: boolean;
33
+ readonly segments?: readonly ContextChartSegment[];
17
34
  }
18
- /** Fixed capacity scale and observation order; no interpolation across missing samples. */
19
- export declare function contextChart(samples: readonly ContextSample[], capacity: number | undefined, trigger: number | undefined, width: number): ContextChartLine[];
35
+ /** Fixed capacity scale; held levels preserve bar geometry and render in a muted color. */
36
+ export declare function contextChart(samples: readonly ContextSample[], capacity: number | undefined, trigger: number | undefined, width: number, now?: number): ContextChartLine[];
@@ -1,4 +1,5 @@
1
1
  import type { Plugin } from "@opencode/plugin/tui";
2
+ import { type GuardRow } from "../family.js";
2
3
  export declare function createGuardState(context: Plugin.Context): {
3
4
  version: import("solid-js").Accessor<number>;
4
5
  acquire: (id: string) => {
@@ -6,8 +7,9 @@ export declare function createGuardState(context: Plugin.Context): {
6
7
  release(): void;
7
8
  };
8
9
  history(id: string, from: number, to: number, signal: AbortSignal): Promise<import("../historycharts.js").HistoryChartResult>;
9
- rows: (stableOrder?: readonly string[], allDescendants?: boolean) => import("../family.js").GuardRow[];
10
- dispose: () => void;
10
+ rows: (stableOrder?: readonly string[], allDescendants?: boolean) => GuardRow[];
11
+ contextTrace: (row: GuardRow | undefined) => import("./contextchart.js").ContextTrace;
12
+ dispose(): void;
11
13
  age(id: string): number;
12
14
  health(): import("../snapshot.js").MonitorHealth | undefined;
13
15
  connection(): "unavailable" | "loading" | "live" | "reconnecting" | "incompatible";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cesarus777/opencode-cgp",
3
- "version": "0.2.3",
3
+ "version": "0.3.0",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": {
@@ -45,7 +45,9 @@
45
45
  "validate": "bun run format:check && bun run typecheck && bun test && bun run test:package",
46
46
  "test:metrics-live": "bun scripts/metrics-live.ts",
47
47
  "charts": "bun src/cli.ts charts",
48
- "test:metrics-server-live": "bun scripts/metrics-server-live.ts"
48
+ "test:metrics-server-live": "bun scripts/metrics-server-live.ts",
49
+ "test:dashboard-spike": "bun scripts/dashboard-spike.ts",
50
+ "test:dashboard-terminal": "bun scripts/dashboard-terminal-live.ts"
49
51
  },
50
52
  "dependencies": {
51
53
  "@opencode/plugin": "2.0.29",
@@ -104,6 +106,7 @@
104
106
  "files": [
105
107
  "dist",
106
108
  "LICENSE",
109
+ "CHANGELOG.md",
107
110
  "README.md"
108
111
  ],
109
112
  "bin": {