@cortexkit/aft-opencode 0.38.0 → 0.39.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/dist/index.js +933 -576
- package/dist/tools/_shared.d.ts +1 -32
- package/dist/tools/_shared.d.ts.map +1 -1
- package/dist/tools/ast.d.ts.map +1 -1
- package/dist/tools/bash.d.ts.map +1 -1
- package/dist/tools/bash_watch.d.ts +0 -1
- package/dist/tools/bash_watch.d.ts.map +1 -1
- package/dist/tools/hoisted.d.ts.map +1 -1
- package/dist/tools/reading.d.ts.map +1 -1
- package/dist/tui/preferences.d.ts +33 -0
- package/dist/tui/preferences.d.ts.map +1 -0
- package/dist/tui.js +8089 -74
- package/package.json +8 -8
- package/src/tui/index.tsx +1 -1
- package/src/tui/preferences.ts +243 -0
- package/src/tui/sidebar.tsx +123 -77
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cortexkit/aft-opencode",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.39.1",
|
|
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",
|
|
@@ -30,19 +30,19 @@
|
|
|
30
30
|
},
|
|
31
31
|
"dependencies": {
|
|
32
32
|
"@clack/prompts": "^1.2.0",
|
|
33
|
-
"@cortexkit/aft-bridge": "0.
|
|
33
|
+
"@cortexkit/aft-bridge": "0.39.1",
|
|
34
34
|
"@xterm/headless": "^5.5.0",
|
|
35
35
|
"comment-json": "^4.6.2",
|
|
36
36
|
"undici": "^7.25.0",
|
|
37
37
|
"zod": "^4.1.8"
|
|
38
38
|
},
|
|
39
39
|
"optionalDependencies": {
|
|
40
|
-
"@cortexkit/aft-darwin-arm64": "0.
|
|
41
|
-
"@cortexkit/aft-darwin-x64": "0.
|
|
42
|
-
"@cortexkit/aft-linux-arm64": "0.
|
|
43
|
-
"@cortexkit/aft-linux-x64": "0.
|
|
44
|
-
"@cortexkit/aft-win32-arm64": "0.
|
|
45
|
-
"@cortexkit/aft-win32-x64": "0.
|
|
40
|
+
"@cortexkit/aft-darwin-arm64": "0.39.1",
|
|
41
|
+
"@cortexkit/aft-darwin-x64": "0.39.1",
|
|
42
|
+
"@cortexkit/aft-linux-arm64": "0.39.1",
|
|
43
|
+
"@cortexkit/aft-linux-x64": "0.39.1",
|
|
44
|
+
"@cortexkit/aft-win32-arm64": "0.39.1",
|
|
45
|
+
"@cortexkit/aft-win32-x64": "0.39.1"
|
|
46
46
|
},
|
|
47
47
|
"devDependencies": {
|
|
48
48
|
"@opencode-ai/plugin": "^1.15.11",
|
package/src/tui/index.tsx
CHANGED
|
@@ -727,7 +727,7 @@ const tui: TuiPlugin = async (api) => {
|
|
|
727
727
|
// command palette entry so the sidebar is available immediately when the
|
|
728
728
|
// user opens their first session.
|
|
729
729
|
try {
|
|
730
|
-
api.slots.register(createAftSidebarSlot(api, packageVersion));
|
|
730
|
+
api.slots.register(await createAftSidebarSlot(api, packageVersion));
|
|
731
731
|
} catch {
|
|
732
732
|
// Older OpenCode TUI hosts may not implement api.slots; fall through
|
|
733
733
|
// and keep the slash command working.
|
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
import { watch } from "node:fs";
|
|
2
|
+
import { mkdir, readFile, rename, writeFile } from "node:fs/promises";
|
|
3
|
+
import { homedir } from "node:os";
|
|
4
|
+
import { basename, dirname, join } from "node:path";
|
|
5
|
+
import { parse, stringify } from "comment-json";
|
|
6
|
+
|
|
7
|
+
export const TUI_PREFS_FILE_ENV = "OPENCODE_TUI_PREFERENCES_FILE";
|
|
8
|
+
const FILE_NAME = "tui-preferences.jsonc";
|
|
9
|
+
|
|
10
|
+
export function getTuiPreferencesFile(): string {
|
|
11
|
+
const override = process.env[TUI_PREFS_FILE_ENV];
|
|
12
|
+
if (override) return override;
|
|
13
|
+
const configDir =
|
|
14
|
+
process.env.OPENCODE_CONFIG_DIR ||
|
|
15
|
+
join(process.env.XDG_CONFIG_HOME || join(homedir(), ".config"), "opencode");
|
|
16
|
+
return join(configDir, FILE_NAME);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
20
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export async function readTuiPreferencesFile(): Promise<Record<string, unknown>> {
|
|
24
|
+
try {
|
|
25
|
+
const raw = await readFile(getTuiPreferencesFile(), "utf8");
|
|
26
|
+
const root: unknown = parse(raw);
|
|
27
|
+
return isRecord(root) ? (root as Record<string, unknown>) : {};
|
|
28
|
+
} catch {
|
|
29
|
+
return {};
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export const PLUGIN_KEY = "aft";
|
|
34
|
+
export const DEFAULT_SLOT_ORDER = 180;
|
|
35
|
+
|
|
36
|
+
export interface AftTuiPrefs {
|
|
37
|
+
forceToTop: boolean;
|
|
38
|
+
order: number;
|
|
39
|
+
startCollapsed: boolean;
|
|
40
|
+
rememberCollapsed: boolean;
|
|
41
|
+
collapsed: boolean | null;
|
|
42
|
+
header: {
|
|
43
|
+
label: string;
|
|
44
|
+
showVersion: boolean;
|
|
45
|
+
};
|
|
46
|
+
sections: {
|
|
47
|
+
searchIndex: boolean;
|
|
48
|
+
semanticIndex: boolean;
|
|
49
|
+
codeHealth: boolean;
|
|
50
|
+
compression: boolean;
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export const DEFAULT_PREFS: AftTuiPrefs = {
|
|
55
|
+
forceToTop: false,
|
|
56
|
+
order: DEFAULT_SLOT_ORDER,
|
|
57
|
+
startCollapsed: false,
|
|
58
|
+
rememberCollapsed: true,
|
|
59
|
+
collapsed: null,
|
|
60
|
+
header: { label: "AFT", showVersion: true },
|
|
61
|
+
sections: {
|
|
62
|
+
searchIndex: true,
|
|
63
|
+
semanticIndex: true,
|
|
64
|
+
codeHealth: true,
|
|
65
|
+
compression: true,
|
|
66
|
+
},
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
function bool(value: unknown, fallback: boolean): boolean {
|
|
70
|
+
return typeof value === "boolean" ? value : fallback;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function int(value: unknown, fallback: number, min: number, max: number): number {
|
|
74
|
+
if (typeof value !== "number" || !Number.isFinite(value)) return fallback;
|
|
75
|
+
return Math.min(Math.max(Math.round(value), min), max);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function label(value: unknown, fallback: string, maxLength: number): string {
|
|
79
|
+
if (typeof value !== "string" || value.length === 0) return fallback;
|
|
80
|
+
return value.slice(0, maxLength);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/** Initial collapsed state from persisted prefs or startCollapsed default. */
|
|
84
|
+
export function seedCollapsedFromPrefs(prefs: AftTuiPrefs): boolean {
|
|
85
|
+
return prefs.collapsed ?? prefs.startCollapsed;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export function resolveAftPrefs(root: Record<string, unknown>): AftTuiPrefs {
|
|
89
|
+
const entry = root[PLUGIN_KEY];
|
|
90
|
+
if (!isRecord(entry)) return structuredClone(DEFAULT_PREFS);
|
|
91
|
+
|
|
92
|
+
const d = DEFAULT_PREFS;
|
|
93
|
+
const header = isRecord(entry.header) ? entry.header : {};
|
|
94
|
+
const sections = isRecord(entry.sections) ? entry.sections : {};
|
|
95
|
+
|
|
96
|
+
return {
|
|
97
|
+
forceToTop: bool(entry.forceToTop, d.forceToTop),
|
|
98
|
+
order: int(entry.order, d.order, -10000, 10000),
|
|
99
|
+
startCollapsed: bool(entry.startCollapsed, d.startCollapsed),
|
|
100
|
+
rememberCollapsed: bool(entry.rememberCollapsed, d.rememberCollapsed),
|
|
101
|
+
collapsed: typeof entry.collapsed === "boolean" ? entry.collapsed : null,
|
|
102
|
+
header: {
|
|
103
|
+
label: label(header.label, d.header.label, 20),
|
|
104
|
+
showVersion: bool(header.showVersion, d.header.showVersion),
|
|
105
|
+
},
|
|
106
|
+
sections: {
|
|
107
|
+
searchIndex: bool(sections.searchIndex, d.sections.searchIndex),
|
|
108
|
+
semanticIndex: bool(sections.semanticIndex, d.sections.semanticIndex),
|
|
109
|
+
codeHealth: bool(sections.codeHealth, d.sections.codeHealth),
|
|
110
|
+
compression: bool(sections.compression, d.sections.compression),
|
|
111
|
+
},
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const FORCE_TOP_BASE = -100000;
|
|
116
|
+
|
|
117
|
+
export function computeEffectiveOrder(
|
|
118
|
+
root: Record<string, unknown>,
|
|
119
|
+
pluginKey: string,
|
|
120
|
+
defaultOrder: number,
|
|
121
|
+
): number {
|
|
122
|
+
const entry = root[pluginKey];
|
|
123
|
+
if (!isRecord(entry)) return defaultOrder;
|
|
124
|
+
if (entry.forceToTop === true) {
|
|
125
|
+
return FORCE_TOP_BASE + Object.keys(root).indexOf(pluginKey);
|
|
126
|
+
}
|
|
127
|
+
return int(entry.order, defaultOrder, -10000, 10000);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const TEMPLATE = `// Shared preferences for opencode TUI plugins.
|
|
131
|
+
// One top-level key per plugin (short name). See each plugin's README for
|
|
132
|
+
// its supported settings. This file is safe to hand-edit; plugins update
|
|
133
|
+
// individual keys in place and preserve comments.
|
|
134
|
+
{}
|
|
135
|
+
`;
|
|
136
|
+
|
|
137
|
+
type JsonValue = string | number | boolean | null;
|
|
138
|
+
|
|
139
|
+
function setNested(target: Record<string, unknown>, path: string[], value: JsonValue): void {
|
|
140
|
+
let current: Record<string, unknown> = target;
|
|
141
|
+
for (let i = 0; i < path.length - 1; i++) {
|
|
142
|
+
const key = path[i];
|
|
143
|
+
if (key === undefined) return;
|
|
144
|
+
const next = current[key];
|
|
145
|
+
if (!isRecord(next)) {
|
|
146
|
+
const created: Record<string, unknown> = {};
|
|
147
|
+
current[key] = created;
|
|
148
|
+
current = created;
|
|
149
|
+
} else {
|
|
150
|
+
current = next;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
const leaf = path[path.length - 1];
|
|
154
|
+
if (leaf === undefined) return;
|
|
155
|
+
current[leaf] = value;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
async function writePreference(pluginKey: string, path: string[], value: JsonValue): Promise<void> {
|
|
159
|
+
const file = getTuiPreferencesFile();
|
|
160
|
+
await mkdir(dirname(file), { recursive: true });
|
|
161
|
+
let text: string;
|
|
162
|
+
try {
|
|
163
|
+
text = await readFile(file, "utf8");
|
|
164
|
+
} catch {
|
|
165
|
+
text = "";
|
|
166
|
+
}
|
|
167
|
+
if (text.trim() === "") text = TEMPLATE;
|
|
168
|
+
|
|
169
|
+
let root: Record<string, unknown>;
|
|
170
|
+
try {
|
|
171
|
+
const parsed = parse(text);
|
|
172
|
+
root = isRecord(parsed) ? (parsed as Record<string, unknown>) : {};
|
|
173
|
+
} catch {
|
|
174
|
+
root = {};
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
if (!isRecord(root[pluginKey])) {
|
|
178
|
+
root[pluginKey] = {};
|
|
179
|
+
}
|
|
180
|
+
const entry = root[pluginKey] as Record<string, unknown>;
|
|
181
|
+
setNested(entry, path, value);
|
|
182
|
+
|
|
183
|
+
const next = `${stringify(root, null, 2)}\n`;
|
|
184
|
+
const tmp = `${file}.${process.pid}.tmp`;
|
|
185
|
+
await writeFile(tmp, next, "utf8");
|
|
186
|
+
await rename(tmp, file);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
let writeChain: Promise<void> = Promise.resolve();
|
|
190
|
+
|
|
191
|
+
export function queueTuiPreferenceUpdate(
|
|
192
|
+
pluginKey: string,
|
|
193
|
+
path: string[],
|
|
194
|
+
value: JsonValue,
|
|
195
|
+
): Promise<void> {
|
|
196
|
+
writeChain = writeChain.then(() => writePreference(pluginKey, path, value)).catch(() => {});
|
|
197
|
+
return writeChain;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
const WATCH_DEBOUNCE_MS = 150;
|
|
201
|
+
|
|
202
|
+
export function watchTuiPreferences(onChange: () => void): () => void {
|
|
203
|
+
const file = getTuiPreferencesFile();
|
|
204
|
+
const name = basename(file);
|
|
205
|
+
let timer: ReturnType<typeof setTimeout> | null = null;
|
|
206
|
+
let lastSeen: string | null = null;
|
|
207
|
+
void readFile(file, "utf8")
|
|
208
|
+
.then((text) => {
|
|
209
|
+
if (lastSeen === null) lastSeen = text;
|
|
210
|
+
})
|
|
211
|
+
.catch(() => {});
|
|
212
|
+
try {
|
|
213
|
+
const watcher = watch(dirname(file), (_event, filename) => {
|
|
214
|
+
const isOurs =
|
|
215
|
+
filename === name || (filename?.startsWith(`${name}.`) && filename.endsWith(".tmp"));
|
|
216
|
+
if (filename != null && !isOurs) return;
|
|
217
|
+
if (timer) clearTimeout(timer);
|
|
218
|
+
timer = setTimeout(() => {
|
|
219
|
+
timer = null;
|
|
220
|
+
void readFile(file, "utf8")
|
|
221
|
+
.catch(() => null)
|
|
222
|
+
.then((text) => {
|
|
223
|
+
if (text === null) return;
|
|
224
|
+
if (text === lastSeen) return;
|
|
225
|
+
lastSeen = text;
|
|
226
|
+
onChange();
|
|
227
|
+
});
|
|
228
|
+
}, WATCH_DEBOUNCE_MS);
|
|
229
|
+
});
|
|
230
|
+
return () => {
|
|
231
|
+
if (timer) clearTimeout(timer);
|
|
232
|
+
watcher.close();
|
|
233
|
+
};
|
|
234
|
+
} catch {
|
|
235
|
+
return () => {};
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
export function persistCollapsedIfEnabled(prefs: AftTuiPrefs, collapsed: boolean): void {
|
|
240
|
+
if (prefs.rememberCollapsed) {
|
|
241
|
+
void queueTuiPreferenceUpdate(PLUGIN_KEY, ["collapsed"], collapsed);
|
|
242
|
+
}
|
|
243
|
+
}
|
package/src/tui/sidebar.tsx
CHANGED
|
@@ -20,6 +20,18 @@ import {
|
|
|
20
20
|
type StatusCompression,
|
|
21
21
|
} from "../shared/status";
|
|
22
22
|
import { resolveCortexKitStorageRoot } from "../shared/storage-paths";
|
|
23
|
+
import {
|
|
24
|
+
type AftTuiPrefs,
|
|
25
|
+
computeEffectiveOrder,
|
|
26
|
+
DEFAULT_PREFS,
|
|
27
|
+
DEFAULT_SLOT_ORDER,
|
|
28
|
+
PLUGIN_KEY,
|
|
29
|
+
persistCollapsedIfEnabled,
|
|
30
|
+
readTuiPreferencesFile,
|
|
31
|
+
resolveAftPrefs,
|
|
32
|
+
seedCollapsedFromPrefs,
|
|
33
|
+
watchTuiPreferences,
|
|
34
|
+
} from "./preferences";
|
|
23
35
|
|
|
24
36
|
const SINGLE_BORDER = { type: "single" } as any;
|
|
25
37
|
const REFRESH_DEBOUNCE_MS = 200;
|
|
@@ -307,9 +319,8 @@ const SidebarContent = (props: {
|
|
|
307
319
|
pluginVersion: string;
|
|
308
320
|
}) => {
|
|
309
321
|
const [status, setStatus] = createSignal<ScopedSidebarStatus | null>(null);
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
const [collapsed, setCollapsed] = createSignal(false);
|
|
322
|
+
const [prefs, setPrefs] = createSignal<AftTuiPrefs>(structuredClone(DEFAULT_PREFS));
|
|
323
|
+
const [collapsed, setCollapsed] = createSignal(seedCollapsedFromPrefs(DEFAULT_PREFS));
|
|
313
324
|
let inflight: {
|
|
314
325
|
controller: AbortController;
|
|
315
326
|
generation: number;
|
|
@@ -428,7 +439,21 @@ const SidebarContent = (props: {
|
|
|
428
439
|
}, REFRESH_DEBOUNCE_MS);
|
|
429
440
|
};
|
|
430
441
|
|
|
442
|
+
const reloadPrefs = async () => {
|
|
443
|
+
const root = await readTuiPreferencesFile();
|
|
444
|
+
const next = resolveAftPrefs(root);
|
|
445
|
+
setPrefs(next);
|
|
446
|
+
setCollapsed(seedCollapsedFromPrefs(next));
|
|
447
|
+
requestRender();
|
|
448
|
+
};
|
|
449
|
+
|
|
450
|
+
void reloadPrefs();
|
|
451
|
+
const unwatchPrefs = watchTuiPreferences(() => {
|
|
452
|
+
void reloadPrefs();
|
|
453
|
+
});
|
|
454
|
+
|
|
431
455
|
onCleanup(() => {
|
|
456
|
+
unwatchPrefs();
|
|
432
457
|
generation++;
|
|
433
458
|
abortInflight();
|
|
434
459
|
if (debounceTimer) clearTimeout(debounceTimer);
|
|
@@ -560,7 +585,12 @@ const SidebarContent = (props: {
|
|
|
560
585
|
justifyContent="space-between"
|
|
561
586
|
alignItems="center"
|
|
562
587
|
onMouseDown={() => {
|
|
563
|
-
if (
|
|
588
|
+
if (notInitialized()) return;
|
|
589
|
+
setCollapsed((x) => {
|
|
590
|
+
const next = !x;
|
|
591
|
+
persistCollapsedIfEnabled(prefs(), next);
|
|
592
|
+
return next;
|
|
593
|
+
});
|
|
564
594
|
}}
|
|
565
595
|
>
|
|
566
596
|
<box flexDirection="row" alignItems="center">
|
|
@@ -570,7 +600,7 @@ const SidebarContent = (props: {
|
|
|
570
600
|
<text fg={props.theme.background}>
|
|
571
601
|
<b>
|
|
572
602
|
{notInitialized() ? "" : collapsed() ? "▶ " : "▼ "}
|
|
573
|
-
|
|
603
|
+
{prefs().header.label}
|
|
574
604
|
</b>
|
|
575
605
|
</text>
|
|
576
606
|
</box>
|
|
@@ -587,7 +617,7 @@ const SidebarContent = (props: {
|
|
|
587
617
|
</box>
|
|
588
618
|
)}
|
|
589
619
|
</box>
|
|
590
|
-
{!notInitialized() && (
|
|
620
|
+
{!notInitialized() && prefs().header.showVersion && (
|
|
591
621
|
<text fg={props.theme.textMuted}>v{s()?.version ?? props.pluginVersion}</text>
|
|
592
622
|
)}
|
|
593
623
|
</box>
|
|
@@ -622,13 +652,17 @@ const SidebarContent = (props: {
|
|
|
622
652
|
order of the expanded grid. */}
|
|
623
653
|
{!notInitialized() && collapsed() && (
|
|
624
654
|
<box width="100%" flexDirection="column">
|
|
625
|
-
|
|
626
|
-
<
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
655
|
+
{prefs().sections.searchIndex && (
|
|
656
|
+
<CollapsedRow theme={props.theme} label="Search Index">
|
|
657
|
+
<text fg={toneColor(props.theme, searchStatus().tone)}>●</text>
|
|
658
|
+
</CollapsedRow>
|
|
659
|
+
)}
|
|
660
|
+
{prefs().sections.semanticIndex && (
|
|
661
|
+
<CollapsedRow theme={props.theme} label="Semantic Index">
|
|
662
|
+
<text fg={toneColor(props.theme, semanticStatus().tone)}>●</text>
|
|
663
|
+
</CollapsedRow>
|
|
664
|
+
)}
|
|
665
|
+
{prefs().sections.codeHealth && collapsedHealthLights(statusBar()) && (
|
|
632
666
|
<CollapsedRow theme={props.theme} label="Code Health">
|
|
633
667
|
<box flexDirection="row" gap={1}>
|
|
634
668
|
<text fg={toneColor(props.theme, collapsedHealthLights(statusBar())!.diagnostics)}>
|
|
@@ -641,7 +675,7 @@ const SidebarContent = (props: {
|
|
|
641
675
|
</box>
|
|
642
676
|
</CollapsedRow>
|
|
643
677
|
)}
|
|
644
|
-
{collapsedCompressionValue(s()?.compression) && (
|
|
678
|
+
{prefs().sections.compression && collapsedCompressionValue(s()?.compression) && (
|
|
645
679
|
<CollapsedRow theme={props.theme} label="Compression">
|
|
646
680
|
<text fg={props.theme.textMuted}>
|
|
647
681
|
<b>{collapsedCompressionValue(s()?.compression)}</b>
|
|
@@ -654,69 +688,76 @@ const SidebarContent = (props: {
|
|
|
654
688
|
{/* Search index */}
|
|
655
689
|
{!notInitialized() && !collapsed() && (
|
|
656
690
|
<>
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
691
|
+
{prefs().sections.searchIndex && (
|
|
692
|
+
<>
|
|
693
|
+
<SectionHeader theme={props.theme} title="Search Index" />
|
|
694
|
+
<StatRow
|
|
695
|
+
theme={props.theme}
|
|
696
|
+
label="Status"
|
|
697
|
+
value={searchStatus().label}
|
|
698
|
+
tone={searchStatus().tone}
|
|
699
|
+
/>
|
|
700
|
+
{(s()?.search_index?.files ?? null) != null && (
|
|
701
|
+
<StatRow
|
|
702
|
+
theme={props.theme}
|
|
703
|
+
label="Files"
|
|
704
|
+
value={formatCount(s()!.search_index.files)}
|
|
705
|
+
tone="muted"
|
|
706
|
+
/>
|
|
707
|
+
)}
|
|
708
|
+
<StatRow
|
|
709
|
+
theme={props.theme}
|
|
710
|
+
label="Disk"
|
|
711
|
+
value={formatBytes(trigramBytes())}
|
|
712
|
+
tone="muted"
|
|
713
|
+
/>
|
|
714
|
+
</>
|
|
671
715
|
)}
|
|
672
|
-
<StatRow
|
|
673
|
-
theme={props.theme}
|
|
674
|
-
label="Disk"
|
|
675
|
-
value={formatBytes(trigramBytes())}
|
|
676
|
-
tone="muted"
|
|
677
|
-
/>
|
|
678
716
|
|
|
679
|
-
{
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
717
|
+
{prefs().sections.semanticIndex && (
|
|
718
|
+
<>
|
|
719
|
+
<SectionHeader theme={props.theme} title="Semantic Index" />
|
|
720
|
+
<StatRow
|
|
721
|
+
theme={props.theme}
|
|
722
|
+
label="Status"
|
|
723
|
+
value={semanticStatus().label}
|
|
724
|
+
tone={semanticStatus().tone}
|
|
725
|
+
/>
|
|
726
|
+
{semanticRefreshing() && (
|
|
727
|
+
<box width="100%">
|
|
728
|
+
<text fg={props.theme.textMuted}>{semanticRefreshing()}</text>
|
|
729
|
+
</box>
|
|
730
|
+
)}
|
|
731
|
+
{/* When loading, magic-context-style progress hint helps users see
|
|
693
732
|
background work is making progress instead of stuck. */}
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
733
|
+
{s()?.semantic_index?.status === "loading" &&
|
|
734
|
+
s()?.semantic_index?.entries_total != null &&
|
|
735
|
+
s()!.semantic_index.entries_total! > 0 && (
|
|
736
|
+
<StatRow
|
|
737
|
+
theme={props.theme}
|
|
738
|
+
label="Progress"
|
|
739
|
+
value={`${formatCount(s()!.semantic_index.entries_done)} / ${formatCount(
|
|
740
|
+
s()!.semantic_index.entries_total,
|
|
741
|
+
)}`}
|
|
742
|
+
tone="warn"
|
|
743
|
+
/>
|
|
744
|
+
)}
|
|
745
|
+
{(s()?.semantic_index?.entries ?? null) != null && (
|
|
746
|
+
<StatRow
|
|
747
|
+
theme={props.theme}
|
|
748
|
+
label="Entries"
|
|
749
|
+
value={formatCount(s()!.semantic_index.entries)}
|
|
750
|
+
tone="muted"
|
|
751
|
+
/>
|
|
752
|
+
)}
|
|
697
753
|
<StatRow
|
|
698
754
|
theme={props.theme}
|
|
699
|
-
label="
|
|
700
|
-
value={
|
|
701
|
-
|
|
702
|
-
)}`}
|
|
703
|
-
tone="warn"
|
|
755
|
+
label="Disk"
|
|
756
|
+
value={formatBytes(semanticBytes())}
|
|
757
|
+
tone="muted"
|
|
704
758
|
/>
|
|
705
|
-
|
|
706
|
-
{(s()?.semantic_index?.entries ?? null) != null && (
|
|
707
|
-
<StatRow
|
|
708
|
-
theme={props.theme}
|
|
709
|
-
label="Entries"
|
|
710
|
-
value={formatCount(s()!.semantic_index.entries)}
|
|
711
|
-
tone="muted"
|
|
712
|
-
/>
|
|
759
|
+
</>
|
|
713
760
|
)}
|
|
714
|
-
<StatRow
|
|
715
|
-
theme={props.theme}
|
|
716
|
-
label="Disk"
|
|
717
|
-
value={formatBytes(semanticBytes())}
|
|
718
|
-
tone="muted"
|
|
719
|
-
/>
|
|
720
761
|
|
|
721
762
|
{/* Code Health — the agent status-bar glance, surfaced for users.
|
|
722
763
|
Hidden until the Tier-2 cache is populated (status_bar undefined),
|
|
@@ -724,7 +765,7 @@ const SidebarContent = (props: {
|
|
|
724
765
|
diagnostics; D/U/C/T come from the last background scan. A `~` on
|
|
725
766
|
the section header flags the Tier-2 counts as predating the latest
|
|
726
767
|
edit. */}
|
|
727
|
-
{statusBar() && (
|
|
768
|
+
{prefs().sections.codeHealth && statusBar() && (
|
|
728
769
|
<>
|
|
729
770
|
<SectionHeader
|
|
730
771
|
theme={props.theme}
|
|
@@ -774,7 +815,7 @@ const SidebarContent = (props: {
|
|
|
774
815
|
subheader followed by two StatRows (Tokens Saved, Compression
|
|
775
816
|
Ratio). Keeps numbers right-aligned in the value column instead
|
|
776
817
|
of jamming them after the label on the same line. */}
|
|
777
|
-
{compressionRows().length > 0 && (
|
|
818
|
+
{prefs().sections.compression && compressionRows().length > 0 && (
|
|
778
819
|
<>
|
|
779
820
|
<SectionHeader theme={props.theme} title="Compression" />
|
|
780
821
|
{compressionRows().map((row) =>
|
|
@@ -802,12 +843,17 @@ const SidebarContent = (props: {
|
|
|
802
843
|
);
|
|
803
844
|
};
|
|
804
845
|
|
|
805
|
-
export function createAftSidebarSlot(
|
|
846
|
+
export async function createAftSidebarSlot(
|
|
847
|
+
api: TuiPluginApi,
|
|
848
|
+
pluginVersion: string,
|
|
849
|
+
): Promise<TuiSlotPlugin> {
|
|
850
|
+
const root = await readTuiPreferencesFile();
|
|
851
|
+
const order = computeEffectiveOrder(root, PLUGIN_KEY, DEFAULT_SLOT_ORDER);
|
|
806
852
|
return {
|
|
807
|
-
//
|
|
808
|
-
//
|
|
809
|
-
//
|
|
810
|
-
order
|
|
853
|
+
// DEFAULT_SLOT_ORDER (180) is AFT's coordinated default in the shared
|
|
854
|
+
// tui-preferences ladder (anthropic-auth 160, AFT 180, magic-context 200).
|
|
855
|
+
// Override via `order` or `forceToTop` in tui-preferences.jsonc.
|
|
856
|
+
order,
|
|
811
857
|
slots: {
|
|
812
858
|
sidebar_content: (ctx, value) => {
|
|
813
859
|
const theme = createMemo(() => (ctx as any).theme.current);
|