@cortexkit/aft-opencode 0.26.3 → 0.27.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.
- package/dist/bg-notifications.d.ts +3 -0
- package/dist/bg-notifications.d.ts.map +1 -1
- package/dist/configure-warnings.d.ts +2 -0
- package/dist/configure-warnings.d.ts.map +1 -1
- package/dist/hooks/auto-update-checker/index.d.ts.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +577 -506
- package/dist/notifications.d.ts +21 -0
- package/dist/notifications.d.ts.map +1 -1
- package/dist/shared/last-assistant-model.d.ts +0 -9
- package/dist/shared/last-assistant-model.d.ts.map +1 -1
- package/dist/shared/status.d.ts +20 -0
- package/dist/shared/status.d.ts.map +1 -1
- package/dist/tui.js +113 -12
- package/package.json +7 -7
- package/src/shared/last-assistant-model.ts +23 -2
- package/src/shared/status.ts +43 -0
- package/src/tui/index.tsx +49 -3
- package/src/tui/sidebar.tsx +81 -2
package/dist/notifications.d.ts
CHANGED
|
@@ -13,6 +13,25 @@
|
|
|
13
13
|
* Messages are identified by markers and cleaned up on subsequent startups
|
|
14
14
|
* when no longer relevant (Desktop only — TUI toasts are inherently transient).
|
|
15
15
|
*/
|
|
16
|
+
import type { BinaryBridge } from "@cortexkit/aft-bridge";
|
|
17
|
+
type SdkMessage = {
|
|
18
|
+
info?: {
|
|
19
|
+
id?: string;
|
|
20
|
+
role?: string;
|
|
21
|
+
};
|
|
22
|
+
parts?: Array<{
|
|
23
|
+
type?: string;
|
|
24
|
+
text?: string;
|
|
25
|
+
ignored?: boolean;
|
|
26
|
+
}>;
|
|
27
|
+
};
|
|
28
|
+
export declare const SESSION_MESSAGES_LIMIT = 50;
|
|
29
|
+
/**
|
|
30
|
+
* @internal — exported only so tests can pin the bounded-call contract.
|
|
31
|
+
* Production callers go through `sendStatus` / `cleanupWarnings`, both of
|
|
32
|
+
* which gate on a real `readDesktopState()` before reaching this helper.
|
|
33
|
+
*/
|
|
34
|
+
export declare function getSessionMessages(client: unknown, sessionId: string): Promise<SdkMessage[]>;
|
|
16
35
|
export interface NotificationOptions {
|
|
17
36
|
/** The OpenCode SDK client */
|
|
18
37
|
client: unknown;
|
|
@@ -32,6 +51,7 @@ export interface ConfigureWarning {
|
|
|
32
51
|
export interface ConfigureWarningOptions {
|
|
33
52
|
client: unknown;
|
|
34
53
|
sessionId: string;
|
|
54
|
+
bridge: Pick<BinaryBridge, "send">;
|
|
35
55
|
storageDir: string;
|
|
36
56
|
pluginVersion: string;
|
|
37
57
|
projectRoot?: string;
|
|
@@ -61,4 +81,5 @@ export declare function deliverConfigureWarnings(opts: ConfigureWarningOptions,
|
|
|
61
81
|
* Desktop only — TUI toasts are inherently transient and don't need cleanup.
|
|
62
82
|
*/
|
|
63
83
|
export declare function cleanupWarnings(opts: NotificationOptions): Promise<void>;
|
|
84
|
+
export {};
|
|
64
85
|
//# sourceMappingURL=notifications.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"notifications.d.ts","sourceRoot":"","sources":["../src/notifications.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;
|
|
1
|
+
{"version":3,"file":"notifications.d.ts","sourceRoot":"","sources":["../src/notifications.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAKH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AAmI1D,KAAK,UAAU,GAAG;IAChB,IAAI,CAAC,EAAE;QAAE,EAAE,CAAC,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IACtC,KAAK,CAAC,EAAE,KAAK,CAAC;QAAE,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,OAAO,CAAA;KAAE,CAAC,CAAC;CACpE,CAAC;AAyBF,eAAO,MAAM,sBAAsB,KAAK,CAAC;AAEzC;;;;GAIG;AACH,wBAAsB,kBAAkB,CACtC,MAAM,EAAE,OAAO,EACf,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,UAAU,EAAE,CAAC,CAqBvB;AAqED,MAAM,WAAW,mBAAmB;IAClC,8BAA8B;IAC9B,MAAM,EAAE,OAAO,CAAC;IAChB,iDAAiD;IACjD,SAAS,EAAE,MAAM,CAAC;IAClB,sEAAsE;IACtE,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,yBAAyB,GAAG,uBAAuB,GAAG,oBAAoB,CAAC;IACjF,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,uBAAuB;IACtC,MAAM,EAAE,OAAO,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,IAAI,CAAC,YAAY,EAAE,MAAM,CAAC,CAAC;IACnC,UAAU,EAAE,MAAM,CAAC;IACnB,aAAa,EAAE,MAAM,CAAC;IACtB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED;;;;GAIG;AACH,wBAAsB,WAAW,CAAC,IAAI,EAAE,mBAAmB,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAW3F;AAED;;;;GAIG;AACH,wBAAsB,UAAU,CAAC,IAAI,EAAE,mBAAmB,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CA0C1F;AAED;;;;;GAKG;AACH,wBAAsB,uBAAuB,CAC3C,IAAI,EAAE,mBAAmB,EACzB,OAAO,EAAE,MAAM,EACf,QAAQ,EAAE,MAAM,EAAE,EAClB,UAAU,CAAC,EAAE,MAAM,GAClB,OAAO,CAAC,IAAI,CAAC,CA2Cf;AA4ED,wBAAsB,wBAAwB,CAC5C,IAAI,EAAE,uBAAuB,EAC7B,QAAQ,EAAE,gBAAgB,EAAE,GAC3B,OAAO,CAAC,IAAI,CAAC,CAqBf;AAED;;;GAGG;AACH,wBAAsB,eAAe,CAAC,IAAI,EAAE,mBAAmB,GAAG,OAAO,CAAC,IAAI,CAAC,CA0C9E"}
|
|
@@ -31,15 +31,6 @@ export interface ResolvedPromptContext {
|
|
|
31
31
|
};
|
|
32
32
|
variant?: string;
|
|
33
33
|
}
|
|
34
|
-
/**
|
|
35
|
-
* Read recent messages from the OpenCode session and resolve the most
|
|
36
|
-
* recent assistant prompt context. Falls back to user messages if no
|
|
37
|
-
* assistant has the field. Merges across messages so partial fields are
|
|
38
|
-
* filled in. Returns null if no usable context is found.
|
|
39
|
-
*
|
|
40
|
-
* Mirrors `resolveSessionPromptParams` in `opencode-xtra` (the working
|
|
41
|
-
* reference implementation).
|
|
42
|
-
*/
|
|
43
34
|
export declare function resolvePromptContext(client: unknown, sessionId: string): Promise<ResolvedPromptContext | null>;
|
|
44
35
|
export interface LastAssistantModel {
|
|
45
36
|
providerID: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"last-assistant-model.d.ts","sourceRoot":"","sources":["../../src/shared/last-assistant-model.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAEH,MAAM,WAAW,qBAAqB;IACpC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE;QAAE,UAAU,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC;IAChD,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;
|
|
1
|
+
{"version":3,"file":"last-assistant-model.d.ts","sourceRoot":"","sources":["../../src/shared/last-assistant-model.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAEH,MAAM,WAAW,qBAAqB;IACpC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE;QAAE,UAAU,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC;IAChD,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAkGD,wBAAsB,oBAAoB,CACxC,MAAM,EAAE,OAAO,EACf,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,qBAAqB,GAAG,IAAI,CAAC,CA8CvC;AAID,MAAM,WAAW,kBAAkB;IACjC,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,6EAA6E;AAC7E,wBAAsB,qBAAqB,CACzC,MAAM,EAAE,OAAO,EACf,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,kBAAkB,GAAG,IAAI,CAAC,CAQpC"}
|
package/dist/shared/status.d.ts
CHANGED
|
@@ -1,3 +1,13 @@
|
|
|
1
|
+
export interface StatusCompressionAggregate {
|
|
2
|
+
events: number;
|
|
3
|
+
original_tokens: number;
|
|
4
|
+
compressed_tokens: number;
|
|
5
|
+
savings_tokens: number;
|
|
6
|
+
}
|
|
7
|
+
export interface StatusCompression {
|
|
8
|
+
project: StatusCompressionAggregate;
|
|
9
|
+
session: StatusCompressionAggregate;
|
|
10
|
+
}
|
|
1
11
|
export interface AftStatusSnapshot {
|
|
2
12
|
version: string;
|
|
3
13
|
project_root: string | null;
|
|
@@ -57,6 +67,16 @@ export interface AftStatusSnapshot {
|
|
|
57
67
|
tracked_files: number;
|
|
58
68
|
checkpoints: number;
|
|
59
69
|
};
|
|
70
|
+
/** Compression aggregate passthrough; rendering is added separately. */
|
|
71
|
+
compression?: StatusCompression;
|
|
72
|
+
/**
|
|
73
|
+
* Human-readable explanation for a synthetic snapshot (e.g.
|
|
74
|
+
* `cache_role === "not_initialized"`). When the plugin returns a placeholder
|
|
75
|
+
* because no bridge has been spawned yet, this message tells the user what
|
|
76
|
+
* to expect; the TUI dialog renders it instead of an empty grid of zeros.
|
|
77
|
+
* Empty string when the snapshot is real bridge data.
|
|
78
|
+
*/
|
|
79
|
+
message: string;
|
|
60
80
|
}
|
|
61
81
|
export declare function formatBytes(bytes: number): string;
|
|
62
82
|
export declare function coerceAftStatus(response: Record<string, unknown>): AftStatusSnapshot;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"status.d.ts","sourceRoot":"","sources":["../../src/shared/status.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,iBAAiB;IAChC,OAAO,EAAE,MAAM,CAAC;IAChB,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,UAAU,EAAE,MAAM,CAAC;IACnB;;;;;;;OAOG;IACH,QAAQ,EAAE,OAAO,CAAC;IAClB,+EAA+E;IAC/E,gBAAgB,EAAE,MAAM,EAAE,CAAC;IAC3B,QAAQ,EAAE;QACR,cAAc,EAAE,OAAO,CAAC;QACxB,gBAAgB,EAAE,MAAM,CAAC;QACzB,wBAAwB,EAAE,OAAO,CAAC;QAClC,YAAY,EAAE,OAAO,CAAC;QACtB,eAAe,EAAE,OAAO,CAAC;KAC1B,CAAC;IACF,YAAY,EAAE;QACZ,MAAM,EAAE,MAAM,CAAC;QACf,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;QACrB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;KACzB,CAAC;IACF,cAAc,EAAE;QACd,MAAM,EAAE,MAAM,CAAC;QACf,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;QACxB,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;QACtB,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;QACtB,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;QACtB,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;QAC7B,aAAa,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;QAC9B,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;QACvB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;QACzB,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;KACvB,CAAC;IACF,IAAI,EAAE;QACJ,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;QAC3B,kBAAkB,EAAE,MAAM,CAAC;QAC3B,mBAAmB,EAAE,MAAM,CAAC;KAC7B,CAAC;IACF,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE;QACZ,aAAa,EAAE,MAAM,CAAC;QACtB,YAAY,EAAE,MAAM,CAAC;KACtB,CAAC;IACF,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,iEAAiE;IACjE,iBAAiB,EAAE,MAAM,CAAC;IAC1B,4DAA4D;IAC5D,OAAO,EAAE;QACP,EAAE,EAAE,MAAM,CAAC;QACX,aAAa,EAAE,MAAM,CAAC;QACtB,WAAW,EAAE,MAAM,CAAC;KACrB,CAAC;
|
|
1
|
+
{"version":3,"file":"status.d.ts","sourceRoot":"","sources":["../../src/shared/status.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,0BAA0B;IACzC,MAAM,EAAE,MAAM,CAAC;IACf,eAAe,EAAE,MAAM,CAAC;IACxB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,cAAc,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,WAAW,iBAAiB;IAChC,OAAO,EAAE,0BAA0B,CAAC;IACpC,OAAO,EAAE,0BAA0B,CAAC;CACrC;AAED,MAAM,WAAW,iBAAiB;IAChC,OAAO,EAAE,MAAM,CAAC;IAChB,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,UAAU,EAAE,MAAM,CAAC;IACnB;;;;;;;OAOG;IACH,QAAQ,EAAE,OAAO,CAAC;IAClB,+EAA+E;IAC/E,gBAAgB,EAAE,MAAM,EAAE,CAAC;IAC3B,QAAQ,EAAE;QACR,cAAc,EAAE,OAAO,CAAC;QACxB,gBAAgB,EAAE,MAAM,CAAC;QACzB,wBAAwB,EAAE,OAAO,CAAC;QAClC,YAAY,EAAE,OAAO,CAAC;QACtB,eAAe,EAAE,OAAO,CAAC;KAC1B,CAAC;IACF,YAAY,EAAE;QACZ,MAAM,EAAE,MAAM,CAAC;QACf,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;QACrB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;KACzB,CAAC;IACF,cAAc,EAAE;QACd,MAAM,EAAE,MAAM,CAAC;QACf,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;QACxB,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;QACtB,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;QACtB,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;QACtB,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;QAC7B,aAAa,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;QAC9B,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;QACvB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;QACzB,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;KACvB,CAAC;IACF,IAAI,EAAE;QACJ,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;QAC3B,kBAAkB,EAAE,MAAM,CAAC;QAC3B,mBAAmB,EAAE,MAAM,CAAC;KAC7B,CAAC;IACF,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE;QACZ,aAAa,EAAE,MAAM,CAAC;QACtB,YAAY,EAAE,MAAM,CAAC;KACtB,CAAC;IACF,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,iEAAiE;IACjE,iBAAiB,EAAE,MAAM,CAAC;IAC1B,4DAA4D;IAC5D,OAAO,EAAE;QACP,EAAE,EAAE,MAAM,CAAC;QACX,aAAa,EAAE,MAAM,CAAC;QACtB,WAAW,EAAE,MAAM,CAAC;KACrB,CAAC;IACF,wEAAwE;IACxE,WAAW,CAAC,EAAE,iBAAiB,CAAC;IAChC;;;;;;OAMG;IACH,OAAO,EAAE,MAAM,CAAC;CACjB;AAqDD,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAajD;AAED,wBAAgB,eAAe,CAAC,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,iBAAiB,CAmEpF;AAED;;;;;;GAMG;AACH,wBAAgB,yBAAyB,CAAC,MAAM,EAAE,iBAAiB,GAAG,MAAM,CAuE3E;AAED,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,iBAAiB,GAAG,MAAM,CA0EtE"}
|
package/dist/tui.js
CHANGED
|
@@ -4,7 +4,7 @@ import { createMemo as createMemo2, createSignal as createSignal2, onCleanup as
|
|
|
4
4
|
// package.json
|
|
5
5
|
var package_default = {
|
|
6
6
|
name: "@cortexkit/aft-opencode",
|
|
7
|
-
version: "0.
|
|
7
|
+
version: "0.27.0",
|
|
8
8
|
type: "module",
|
|
9
9
|
description: "OpenCode plugin for Agent File Tools (AFT) \u2014 tree-sitter and lsp powered code analysis",
|
|
10
10
|
main: "dist/index.js",
|
|
@@ -32,7 +32,7 @@ var package_default = {
|
|
|
32
32
|
},
|
|
33
33
|
dependencies: {
|
|
34
34
|
"@clack/prompts": "^1.2.0",
|
|
35
|
-
"@cortexkit/aft-bridge": "0.
|
|
35
|
+
"@cortexkit/aft-bridge": "0.27.0",
|
|
36
36
|
"@opencode-ai/plugin": "^1.15.5",
|
|
37
37
|
"@opencode-ai/sdk": "^1.15.5",
|
|
38
38
|
"comment-json": "^4.6.2",
|
|
@@ -40,11 +40,11 @@ var package_default = {
|
|
|
40
40
|
zod: "^4.1.8"
|
|
41
41
|
},
|
|
42
42
|
optionalDependencies: {
|
|
43
|
-
"@cortexkit/aft-darwin-arm64": "0.
|
|
44
|
-
"@cortexkit/aft-darwin-x64": "0.
|
|
45
|
-
"@cortexkit/aft-linux-arm64": "0.
|
|
46
|
-
"@cortexkit/aft-linux-x64": "0.
|
|
47
|
-
"@cortexkit/aft-win32-x64": "0.
|
|
43
|
+
"@cortexkit/aft-darwin-arm64": "0.27.0",
|
|
44
|
+
"@cortexkit/aft-darwin-x64": "0.27.0",
|
|
45
|
+
"@cortexkit/aft-linux-arm64": "0.27.0",
|
|
46
|
+
"@cortexkit/aft-linux-x64": "0.27.0",
|
|
47
|
+
"@cortexkit/aft-win32-x64": "0.27.0"
|
|
48
48
|
},
|
|
49
49
|
devDependencies: {
|
|
50
50
|
"@types/node": "^22.0.0",
|
|
@@ -304,6 +304,24 @@ function readNumber(value, fallback = 0) {
|
|
|
304
304
|
function readOptionalNumber(value) {
|
|
305
305
|
return typeof value === "number" && Number.isFinite(value) ? value : null;
|
|
306
306
|
}
|
|
307
|
+
function readCompressionAggregate(value) {
|
|
308
|
+
const aggregate = asRecord(value);
|
|
309
|
+
return {
|
|
310
|
+
events: readNumber(aggregate.events),
|
|
311
|
+
original_tokens: readNumber(aggregate.original_tokens),
|
|
312
|
+
compressed_tokens: readNumber(aggregate.compressed_tokens),
|
|
313
|
+
savings_tokens: readNumber(aggregate.savings_tokens)
|
|
314
|
+
};
|
|
315
|
+
}
|
|
316
|
+
function readCompression(value) {
|
|
317
|
+
if (typeof value !== "object" || value === null)
|
|
318
|
+
return;
|
|
319
|
+
const compression = asRecord(value);
|
|
320
|
+
return {
|
|
321
|
+
project: readCompressionAggregate(compression.project),
|
|
322
|
+
session: readCompressionAggregate(compression.session)
|
|
323
|
+
};
|
|
324
|
+
}
|
|
307
325
|
function formatBytes(bytes) {
|
|
308
326
|
if (!Number.isFinite(bytes) || bytes <= 0)
|
|
309
327
|
return "0 B";
|
|
@@ -375,13 +393,15 @@ function coerceAftStatus(response) {
|
|
|
375
393
|
id: readString(session.id, "__default__"),
|
|
376
394
|
tracked_files: readNumber(session.tracked_files),
|
|
377
395
|
checkpoints: readNumber(session.checkpoints)
|
|
378
|
-
}
|
|
396
|
+
},
|
|
397
|
+
compression: readCompression(response.compression),
|
|
398
|
+
message: readString(response.message, "")
|
|
379
399
|
};
|
|
380
400
|
}
|
|
381
401
|
|
|
382
402
|
// src/tui/sidebar.tsx
|
|
383
403
|
import { createEffect, createMemo, createSignal, on, onCleanup } from "solid-js";
|
|
384
|
-
import { jsxDEV } from "@opentui/solid/jsx-dev-runtime";
|
|
404
|
+
import { jsxDEV, Fragment } from "@opentui/solid/jsx-dev-runtime";
|
|
385
405
|
var SINGLE_BORDER = { type: "single" };
|
|
386
406
|
var REFRESH_DEBOUNCE_MS = 200;
|
|
387
407
|
var POLL_INTERVAL_MS = 1500;
|
|
@@ -405,6 +425,23 @@ function formatCount(n) {
|
|
|
405
425
|
return `${Math.round(n / 1000)}K`;
|
|
406
426
|
return String(n);
|
|
407
427
|
}
|
|
428
|
+
function appendScope(rows, label, scope) {
|
|
429
|
+
const savings = scope.savings_tokens;
|
|
430
|
+
const pct = scope.original_tokens > 0 ? Math.round(savings / scope.original_tokens * 100) : 0;
|
|
431
|
+
rows.push({ kind: "scope", label });
|
|
432
|
+
rows.push({ kind: "stat", label: "Tokens Saved", value: savings.toLocaleString("en-US") });
|
|
433
|
+
rows.push({ kind: "stat", label: "Compression Ratio", value: `${pct}%` });
|
|
434
|
+
}
|
|
435
|
+
function formatCompressionSidebarRows(compression) {
|
|
436
|
+
if (!compression || compression.project.events <= 0)
|
|
437
|
+
return [];
|
|
438
|
+
const rows = [];
|
|
439
|
+
if (compression.session.events > 0) {
|
|
440
|
+
appendScope(rows, "Session", compression.session);
|
|
441
|
+
}
|
|
442
|
+
appendScope(rows, "Project", compression.project);
|
|
443
|
+
return rows;
|
|
444
|
+
}
|
|
408
445
|
function statusDisplay(status) {
|
|
409
446
|
switch (status) {
|
|
410
447
|
case "ready":
|
|
@@ -473,7 +510,7 @@ function getClient(directory) {
|
|
|
473
510
|
return client;
|
|
474
511
|
const home = process.env.HOME || process.env.USERPROFILE || "";
|
|
475
512
|
const dataHome = process.env.XDG_DATA_HOME || `${home}/.local/share`;
|
|
476
|
-
const storageDir = `${dataHome}/
|
|
513
|
+
const storageDir = `${dataHome}/cortexkit/aft`;
|
|
477
514
|
client = new AftRpcClient(storageDir, directory);
|
|
478
515
|
sidebarClients.set(directory, client);
|
|
479
516
|
return client;
|
|
@@ -561,6 +598,7 @@ var SidebarContent = (props) => {
|
|
|
561
598
|
const semanticStatus = () => statusDisplay(s()?.semantic_index?.status ?? "disabled");
|
|
562
599
|
const trigramBytes = () => s()?.disk?.trigram_disk_bytes ?? 0;
|
|
563
600
|
const semanticBytes = () => s()?.disk?.semantic_disk_bytes ?? 0;
|
|
601
|
+
const compressionRows = () => formatCompressionSidebarRows(s()?.compression);
|
|
564
602
|
const degradedReasonLabel = (reason) => {
|
|
565
603
|
if (reason === "home_root") {
|
|
566
604
|
return "project root is your home directory";
|
|
@@ -644,6 +682,14 @@ var SidebarContent = (props) => {
|
|
|
644
682
|
]
|
|
645
683
|
}, undefined, true, undefined, this)
|
|
646
684
|
}, undefined, false, undefined, this),
|
|
685
|
+
s()?.cache_role === "not_initialized" && /* @__PURE__ */ jsxDEV("box", {
|
|
686
|
+
marginTop: 1,
|
|
687
|
+
width: "100%",
|
|
688
|
+
children: /* @__PURE__ */ jsxDEV("text", {
|
|
689
|
+
fg: props.theme.textMuted,
|
|
690
|
+
children: s().message || "Waiting for first tool call to populate"
|
|
691
|
+
}, undefined, false, undefined, this)
|
|
692
|
+
}, undefined, false, undefined, this),
|
|
647
693
|
/* @__PURE__ */ jsxDEV(SectionHeader, {
|
|
648
694
|
theme: props.theme,
|
|
649
695
|
title: "Search Index"
|
|
@@ -694,6 +740,26 @@ var SidebarContent = (props) => {
|
|
|
694
740
|
value: formatBytes2(semanticBytes()),
|
|
695
741
|
tone: "muted"
|
|
696
742
|
}, undefined, false, undefined, this),
|
|
743
|
+
compressionRows().length > 0 && /* @__PURE__ */ jsxDEV(Fragment, {
|
|
744
|
+
children: [
|
|
745
|
+
/* @__PURE__ */ jsxDEV(SectionHeader, {
|
|
746
|
+
theme: props.theme,
|
|
747
|
+
title: "Compression"
|
|
748
|
+
}, undefined, false, undefined, this),
|
|
749
|
+
compressionRows().map((row) => row.kind === "scope" ? /* @__PURE__ */ jsxDEV("box", {
|
|
750
|
+
width: "100%",
|
|
751
|
+
children: /* @__PURE__ */ jsxDEV("text", {
|
|
752
|
+
fg: props.theme.text,
|
|
753
|
+
children: row.label
|
|
754
|
+
}, undefined, false, undefined, this)
|
|
755
|
+
}, undefined, false, undefined, this) : /* @__PURE__ */ jsxDEV(StatRow, {
|
|
756
|
+
theme: props.theme,
|
|
757
|
+
label: row.label,
|
|
758
|
+
value: row.value,
|
|
759
|
+
tone: "muted"
|
|
760
|
+
}, undefined, false, undefined, this))
|
|
761
|
+
]
|
|
762
|
+
}, undefined, true, undefined, this),
|
|
697
763
|
s()?.semantic_index?.status === "failed" && s()?.semantic_index?.error && /* @__PURE__ */ jsxDEV("box", {
|
|
698
764
|
marginTop: 1,
|
|
699
765
|
width: "100%",
|
|
@@ -735,7 +801,7 @@ function getRpcClient(directory) {
|
|
|
735
801
|
return client;
|
|
736
802
|
const home = process.env.HOME || process.env.USERPROFILE || "";
|
|
737
803
|
const dataHome = process.env.XDG_DATA_HOME || `${home}/.local/share`;
|
|
738
|
-
const storageDir = `${dataHome}/
|
|
804
|
+
const storageDir = `${dataHome}/cortexkit/aft`;
|
|
739
805
|
client = new AftRpcClient(storageDir, directory);
|
|
740
806
|
rpcClients.set(directory, client);
|
|
741
807
|
return client;
|
|
@@ -825,6 +891,7 @@ var StatusDialog = (props) => {
|
|
|
825
891
|
}, POLL_INTERVAL_MS2);
|
|
826
892
|
onCleanup2(() => clearInterval(timer));
|
|
827
893
|
const cacheRoleTone = (role) => role === "main" ? "accent" : role === "worktree" ? "warn" : "muted";
|
|
894
|
+
const compressionAggregateRows = () => formatCompressionSidebarRows(status()?.compression);
|
|
828
895
|
return /* @__PURE__ */ jsxDEV2("box", {
|
|
829
896
|
flexDirection: "column",
|
|
830
897
|
width: "100%",
|
|
@@ -863,7 +930,16 @@ var StatusDialog = (props) => {
|
|
|
863
930
|
children: error()
|
|
864
931
|
}, undefined, false, undefined, this)
|
|
865
932
|
}, undefined, false, undefined, this) : null,
|
|
866
|
-
status() ? /* @__PURE__ */ jsxDEV2("box", {
|
|
933
|
+
status()?.cache_role === "not_initialized" ? /* @__PURE__ */ jsxDEV2("box", {
|
|
934
|
+
width: "100%",
|
|
935
|
+
marginTop: 1,
|
|
936
|
+
justifyContent: "center",
|
|
937
|
+
children: /* @__PURE__ */ jsxDEV2("text", {
|
|
938
|
+
fg: t().textMuted,
|
|
939
|
+
children: status().message || "Waiting for first tool call to populate"
|
|
940
|
+
}, undefined, false, undefined, this)
|
|
941
|
+
}, undefined, false, undefined, this) : null,
|
|
942
|
+
status() && status().cache_role !== "not_initialized" ? /* @__PURE__ */ jsxDEV2("box", {
|
|
867
943
|
flexDirection: "column",
|
|
868
944
|
width: "100%",
|
|
869
945
|
marginBottom: 1,
|
|
@@ -1092,6 +1168,31 @@ var StatusDialog = (props) => {
|
|
|
1092
1168
|
]
|
|
1093
1169
|
}, undefined, true, undefined, this)
|
|
1094
1170
|
}, undefined, false, undefined, this) : null,
|
|
1171
|
+
compressionAggregateRows().length > 0 ? /* @__PURE__ */ jsxDEV2("box", {
|
|
1172
|
+
flexDirection: "column",
|
|
1173
|
+
width: "100%",
|
|
1174
|
+
marginTop: 1,
|
|
1175
|
+
children: [
|
|
1176
|
+
/* @__PURE__ */ jsxDEV2("text", {
|
|
1177
|
+
fg: t().text,
|
|
1178
|
+
children: /* @__PURE__ */ jsxDEV2("b", {
|
|
1179
|
+
children: "Compression"
|
|
1180
|
+
}, undefined, false, undefined, this)
|
|
1181
|
+
}, undefined, false, undefined, this),
|
|
1182
|
+
compressionAggregateRows().map((row) => row.kind === "scope" ? /* @__PURE__ */ jsxDEV2("box", {
|
|
1183
|
+
width: "100%",
|
|
1184
|
+
children: /* @__PURE__ */ jsxDEV2("text", {
|
|
1185
|
+
fg: t().text,
|
|
1186
|
+
children: row.label
|
|
1187
|
+
}, undefined, false, undefined, this)
|
|
1188
|
+
}, undefined, false, undefined, this) : /* @__PURE__ */ jsxDEV2(R, {
|
|
1189
|
+
theme: t(),
|
|
1190
|
+
label: row.label,
|
|
1191
|
+
value: row.value,
|
|
1192
|
+
tone: "muted"
|
|
1193
|
+
}, undefined, false, undefined, this))
|
|
1194
|
+
]
|
|
1195
|
+
}, undefined, true, undefined, this) : null,
|
|
1095
1196
|
/* @__PURE__ */ jsxDEV2("box", {
|
|
1096
1197
|
marginTop: 1,
|
|
1097
1198
|
justifyContent: "flex-end",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cortexkit/aft-opencode",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.27.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "OpenCode plugin for Agent File Tools (AFT) — tree-sitter and lsp powered code analysis",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
},
|
|
29
29
|
"dependencies": {
|
|
30
30
|
"@clack/prompts": "^1.2.0",
|
|
31
|
-
"@cortexkit/aft-bridge": "0.
|
|
31
|
+
"@cortexkit/aft-bridge": "0.27.0",
|
|
32
32
|
"@opencode-ai/plugin": "^1.15.5",
|
|
33
33
|
"@opencode-ai/sdk": "^1.15.5",
|
|
34
34
|
"comment-json": "^4.6.2",
|
|
@@ -36,11 +36,11 @@
|
|
|
36
36
|
"zod": "^4.1.8"
|
|
37
37
|
},
|
|
38
38
|
"optionalDependencies": {
|
|
39
|
-
"@cortexkit/aft-darwin-arm64": "0.
|
|
40
|
-
"@cortexkit/aft-darwin-x64": "0.
|
|
41
|
-
"@cortexkit/aft-linux-arm64": "0.
|
|
42
|
-
"@cortexkit/aft-linux-x64": "0.
|
|
43
|
-
"@cortexkit/aft-win32-x64": "0.
|
|
39
|
+
"@cortexkit/aft-darwin-arm64": "0.27.0",
|
|
40
|
+
"@cortexkit/aft-darwin-x64": "0.27.0",
|
|
41
|
+
"@cortexkit/aft-linux-arm64": "0.27.0",
|
|
42
|
+
"@cortexkit/aft-linux-x64": "0.27.0",
|
|
43
|
+
"@cortexkit/aft-win32-x64": "0.27.0"
|
|
44
44
|
},
|
|
45
45
|
"devDependencies": {
|
|
46
46
|
"@types/node": "^22.0.0",
|
|
@@ -110,7 +110,22 @@ function isComplete(ctx: ResolvedPromptContext): boolean {
|
|
|
110
110
|
*
|
|
111
111
|
* Mirrors `resolveSessionPromptParams` in `opencode-xtra` (the working
|
|
112
112
|
* reference implementation).
|
|
113
|
+
*
|
|
114
|
+
* Bounded via `query.limit` — without it, OpenCode's legacy
|
|
115
|
+
* `/session/{id}/message` endpoint hydrates the ENTIRE session. Large
|
|
116
|
+
* sessions can carry 30k-45k messages / 100k+ parts and blow the host's
|
|
117
|
+
* memory just to find the last assistant turn. We only need the most
|
|
118
|
+
* recent prompt context, so 50 is plenty (the very last assistant
|
|
119
|
+
* usually has everything; the merge fallback rarely needs more).
|
|
120
|
+
*
|
|
121
|
+
* Future v2 migration: once `@opencode-ai/sdk` exposes `client.v2.session.messages`
|
|
122
|
+
* with projected shapes (user / assistant / agent-switched / model-switched / ...),
|
|
123
|
+
* prefer that with `{ limit: 50, order: "desc" }` and fall back to this bounded
|
|
124
|
+
* legacy call when v2 returns no items (older sessions aren't backfilled into v2).
|
|
125
|
+
* See https://github.com/cortexkit/aft notes for tracking.
|
|
113
126
|
*/
|
|
127
|
+
const PROMPT_CONTEXT_MESSAGE_LIMIT = 50;
|
|
128
|
+
|
|
114
129
|
export async function resolvePromptContext(
|
|
115
130
|
client: unknown,
|
|
116
131
|
sessionId: string,
|
|
@@ -118,14 +133,20 @@ export async function resolvePromptContext(
|
|
|
118
133
|
if (!client || !sessionId) return null;
|
|
119
134
|
const c = client as {
|
|
120
135
|
session?: {
|
|
121
|
-
messages?: (input: {
|
|
136
|
+
messages?: (input: {
|
|
137
|
+
path: { id: string };
|
|
138
|
+
query?: { limit?: number };
|
|
139
|
+
}) => Promise<{ data?: unknown[] } | unknown[]>;
|
|
122
140
|
};
|
|
123
141
|
};
|
|
124
142
|
if (typeof c.session?.messages !== "function") return null;
|
|
125
143
|
|
|
126
144
|
let messages: unknown[] = [];
|
|
127
145
|
try {
|
|
128
|
-
const response = await c.session.messages({
|
|
146
|
+
const response = await c.session.messages({
|
|
147
|
+
path: { id: sessionId },
|
|
148
|
+
query: { limit: PROMPT_CONTEXT_MESSAGE_LIMIT },
|
|
149
|
+
});
|
|
129
150
|
messages = extractMessages(response);
|
|
130
151
|
} catch {
|
|
131
152
|
return null;
|
package/src/shared/status.ts
CHANGED
|
@@ -1,3 +1,15 @@
|
|
|
1
|
+
export interface StatusCompressionAggregate {
|
|
2
|
+
events: number;
|
|
3
|
+
original_tokens: number;
|
|
4
|
+
compressed_tokens: number;
|
|
5
|
+
savings_tokens: number;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export interface StatusCompression {
|
|
9
|
+
project: StatusCompressionAggregate;
|
|
10
|
+
session: StatusCompressionAggregate;
|
|
11
|
+
}
|
|
12
|
+
|
|
1
13
|
export interface AftStatusSnapshot {
|
|
2
14
|
version: string;
|
|
3
15
|
project_root: string | null;
|
|
@@ -57,6 +69,16 @@ export interface AftStatusSnapshot {
|
|
|
57
69
|
tracked_files: number;
|
|
58
70
|
checkpoints: number;
|
|
59
71
|
};
|
|
72
|
+
/** Compression aggregate passthrough; rendering is added separately. */
|
|
73
|
+
compression?: StatusCompression;
|
|
74
|
+
/**
|
|
75
|
+
* Human-readable explanation for a synthetic snapshot (e.g.
|
|
76
|
+
* `cache_role === "not_initialized"`). When the plugin returns a placeholder
|
|
77
|
+
* because no bridge has been spawned yet, this message tells the user what
|
|
78
|
+
* to expect; the TUI dialog renders it instead of an empty grid of zeros.
|
|
79
|
+
* Empty string when the snapshot is real bridge data.
|
|
80
|
+
*/
|
|
81
|
+
message: string;
|
|
60
82
|
}
|
|
61
83
|
|
|
62
84
|
function asRecord(value: unknown): Record<string, unknown> {
|
|
@@ -83,6 +105,25 @@ function readOptionalNumber(value: unknown): number | null {
|
|
|
83
105
|
return typeof value === "number" && Number.isFinite(value) ? value : null;
|
|
84
106
|
}
|
|
85
107
|
|
|
108
|
+
function readCompressionAggregate(value: unknown): StatusCompressionAggregate {
|
|
109
|
+
const aggregate = asRecord(value);
|
|
110
|
+
return {
|
|
111
|
+
events: readNumber(aggregate.events),
|
|
112
|
+
original_tokens: readNumber(aggregate.original_tokens),
|
|
113
|
+
compressed_tokens: readNumber(aggregate.compressed_tokens),
|
|
114
|
+
savings_tokens: readNumber(aggregate.savings_tokens),
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function readCompression(value: unknown): StatusCompression | undefined {
|
|
119
|
+
if (typeof value !== "object" || value === null) return undefined;
|
|
120
|
+
const compression = asRecord(value);
|
|
121
|
+
return {
|
|
122
|
+
project: readCompressionAggregate(compression.project),
|
|
123
|
+
session: readCompressionAggregate(compression.session),
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
|
|
86
127
|
function formatFlag(enabled: boolean): string {
|
|
87
128
|
return enabled ? "enabled" : "disabled";
|
|
88
129
|
}
|
|
@@ -170,6 +211,8 @@ export function coerceAftStatus(response: Record<string, unknown>): AftStatusSna
|
|
|
170
211
|
tracked_files: readNumber(session.tracked_files),
|
|
171
212
|
checkpoints: readNumber(session.checkpoints),
|
|
172
213
|
},
|
|
214
|
+
compression: readCompression(response.compression),
|
|
215
|
+
message: readString(response.message, ""),
|
|
173
216
|
};
|
|
174
217
|
}
|
|
175
218
|
|
package/src/tui/index.tsx
CHANGED
|
@@ -3,10 +3,11 @@
|
|
|
3
3
|
|
|
4
4
|
import type { TuiPlugin, TuiPluginApi, TuiThemeCurrent } from "@opencode-ai/plugin/tui";
|
|
5
5
|
import { createMemo, createSignal, onCleanup } from "solid-js";
|
|
6
|
+
|
|
6
7
|
import packageJson from "../../package.json";
|
|
7
8
|
import { AftRpcClient } from "../shared/rpc-client";
|
|
8
9
|
import { type AftStatusSnapshot, coerceAftStatus, formatBytes } from "../shared/status";
|
|
9
|
-
import { createAftSidebarSlot } from "./sidebar";
|
|
10
|
+
import { createAftSidebarSlot, formatCompressionSidebarRows } from "./sidebar";
|
|
10
11
|
|
|
11
12
|
// The TUI talks to the server plugin via AftRpcClient. The client reads the
|
|
12
13
|
// JSON port file written by AftRpcServer ({ port, token }) and includes that
|
|
@@ -22,9 +23,14 @@ function getRpcClient(directory: string): AftRpcClient {
|
|
|
22
23
|
let client = rpcClients.get(directory);
|
|
23
24
|
if (client) return client;
|
|
24
25
|
|
|
26
|
+
// v0.27 moved AFT storage to the CortexKit root. The TUI plugin must use
|
|
27
|
+
// the same path as the server plugin (`resolveCortexKitStorageRoot()`),
|
|
28
|
+
// otherwise it polls stale legacy port files written by older versions
|
|
29
|
+
// and never picks up the live RPC server, leaving the sidebar/dialog
|
|
30
|
+
// stuck on the "AFT is starting up" placeholder forever.
|
|
25
31
|
const home = process.env.HOME || process.env.USERPROFILE || "";
|
|
26
32
|
const dataHome = process.env.XDG_DATA_HOME || `${home}/.local/share`;
|
|
27
|
-
const storageDir = `${dataHome}/
|
|
33
|
+
const storageDir = `${dataHome}/cortexkit/aft`;
|
|
28
34
|
|
|
29
35
|
client = new AftRpcClient(storageDir, directory);
|
|
30
36
|
rpcClients.set(directory, client);
|
|
@@ -153,6 +159,11 @@ const StatusDialog = (props: StatusDialogProps) => {
|
|
|
153
159
|
// not_initialized is muted. Matches the sidebar convention.
|
|
154
160
|
const cacheRoleTone = (role: string): "accent" | "warn" | "muted" =>
|
|
155
161
|
role === "main" ? "accent" : role === "worktree" ? "warn" : "muted";
|
|
162
|
+
// Reuse the sidebar's label/value formatter so the dialog and sidebar
|
|
163
|
+
// render identical text (e.g. "Session" / "-174,489 tokens, 59% reduction").
|
|
164
|
+
// The earlier `formatCompressionDialogRows` returned padded strings that
|
|
165
|
+
// looked offset against neighboring sections.
|
|
166
|
+
const compressionAggregateRows = () => formatCompressionSidebarRows(status()?.compression);
|
|
156
167
|
|
|
157
168
|
return (
|
|
158
169
|
<box
|
|
@@ -178,8 +189,21 @@ const StatusDialog = (props: StatusDialogProps) => {
|
|
|
178
189
|
</box>
|
|
179
190
|
) : null}
|
|
180
191
|
|
|
192
|
+
{/* Lazy-bridge placeholder: when no bridge has spawned for this project
|
|
193
|
+
yet (because the user has not made any tool call since opening
|
|
194
|
+
OpenCode), the RPC server returns a synthetic snapshot with
|
|
195
|
+
cache_role === "not_initialized". Show the explanatory message
|
|
196
|
+
instead of an empty grid of zeros and "unknown" rows. */}
|
|
197
|
+
{status()?.cache_role === "not_initialized" ? (
|
|
198
|
+
<box width="100%" marginTop={1} justifyContent="center">
|
|
199
|
+
<text fg={t().textMuted}>
|
|
200
|
+
{status()!.message || "Waiting for first tool call to populate"}
|
|
201
|
+
</text>
|
|
202
|
+
</box>
|
|
203
|
+
) : null}
|
|
204
|
+
|
|
181
205
|
{/* Header rows — paths span full width since they can be long */}
|
|
182
|
-
{status() ? (
|
|
206
|
+
{status() && status()!.cache_role !== "not_initialized" ? (
|
|
183
207
|
<box flexDirection="column" width="100%" marginBottom={1}>
|
|
184
208
|
<R
|
|
185
209
|
theme={t()}
|
|
@@ -361,6 +385,28 @@ const StatusDialog = (props: StatusDialogProps) => {
|
|
|
361
385
|
</box>
|
|
362
386
|
) : null}
|
|
363
387
|
|
|
388
|
+
{/* Compression aggregates — tabular layout matching Search/Semantic
|
|
389
|
+
Index. Each scope ("Session", "Project") renders as a subheader
|
|
390
|
+
followed by two <R> rows (Tokens Saved, Compression Ratio) so the
|
|
391
|
+
numbers stay right-aligned under the value column instead of
|
|
392
|
+
crowding the label. */}
|
|
393
|
+
{compressionAggregateRows().length > 0 ? (
|
|
394
|
+
<box flexDirection="column" width="100%" marginTop={1}>
|
|
395
|
+
<text fg={t().text}>
|
|
396
|
+
<b>Compression</b>
|
|
397
|
+
</text>
|
|
398
|
+
{compressionAggregateRows().map((row) =>
|
|
399
|
+
row.kind === "scope" ? (
|
|
400
|
+
<box width="100%">
|
|
401
|
+
<text fg={t().text}>{row.label}</text>
|
|
402
|
+
</box>
|
|
403
|
+
) : (
|
|
404
|
+
<R theme={t()} label={row.label} value={row.value} tone="muted" />
|
|
405
|
+
),
|
|
406
|
+
)}
|
|
407
|
+
</box>
|
|
408
|
+
) : null}
|
|
409
|
+
|
|
364
410
|
{/* Footer */}
|
|
365
411
|
<box marginTop={1} justifyContent="flex-end" width="100%">
|
|
366
412
|
<text fg={t().textMuted}>Enter or Esc to close</text>
|