@gavin-hu/my-pi-agent 0.16.0 → 0.18.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/README.md +29 -15
- package/README.zh-CN.md +27 -15
- package/extensions/bytefly/README.md +195 -115
- package/extensions/bytefly/footer/collect/entries.ts +1 -6
- package/extensions/bytefly/footer/collect/snapshot.ts +23 -9
- package/extensions/bytefly/footer/price/price.ts +12 -8
- package/extensions/bytefly/footer/register.ts +13 -1
- package/extensions/bytefly/footer/types.ts +11 -0
- package/extensions/bytefly/footer/unpriced.ts +36 -0
- package/extensions/bytefly/header/component.ts +15 -29
- package/extensions/bytefly/header/identity.ts +71 -0
- package/extensions/bytefly/header/register.ts +9 -3
- package/extensions/bytefly/index.ts +13 -5
- package/extensions/bytefly/message/format.ts +20 -13
- package/extensions/bytefly/message/register.ts +8 -7
- package/extensions/bytefly/notify/types.ts +1 -1
- package/extensions/bytefly/panel/commands.ts +46 -25
- package/extensions/bytefly/panel/format.ts +49 -14
- package/extensions/bytefly/panel/join.ts +35 -9
- package/extensions/bytefly/panel/settings.ts +29 -39
- package/extensions/bytefly/panel/skills.ts +51 -0
- package/extensions/bytefly/panel/tui/extensions-view.ts +7 -61
- package/extensions/bytefly/panel/tui/frame.ts +9 -1
- package/extensions/bytefly/panel/tui/list-view.ts +72 -0
- package/extensions/bytefly/panel/tui/nav.ts +71 -3
- package/extensions/bytefly/panel/tui/settings-section.ts +250 -0
- package/extensions/bytefly/panel/tui/skills-view.ts +50 -0
- package/extensions/bytefly/panel/tui.ts +223 -331
- package/extensions/bytefly/panel/types.ts +26 -8
- package/extensions/bytefly/picker/discovery.ts +32 -27
- package/extensions/bytefly/picker/resources.ts +16 -8
- package/extensions/bytefly/picker/runtime.ts +62 -34
- package/extensions/bytefly/picker/toggle.ts +16 -10
- package/extensions/bytefly/picker/types.ts +31 -13
- package/extensions/bytefly/update/format.ts +66 -8
- package/extensions/bytefly/update/patch.ts +66 -0
- package/extensions/bytefly/update/register.ts +4 -11
- package/extensions/bytefly/update/release.ts +65 -0
- package/extensions/bytefly/update/state.ts +14 -0
- package/extensions/bytefly/update/suppress.ts +47 -29
- package/extensions/bytefly/update/types.ts +15 -5
- package/extensions/check/tui.ts +2 -0
- package/extensions/context/README.md +21 -11
- package/extensions/context/format.ts +10 -8
- package/extensions/context/report.ts +21 -8
- package/extensions/context/tui.ts +26 -10
- package/extensions/context/types.ts +5 -3
- package/extensions/file-browse/README.md +89 -22
- package/extensions/file-browse/chrome.ts +83 -0
- package/extensions/file-browse/client.ts +74 -459
- package/extensions/file-browse/config.ts +12 -3
- package/extensions/file-browse/diff-html.ts +94 -0
- package/extensions/file-browse/html.ts +26 -152
- package/extensions/file-browse/live-client.ts +47 -1
- package/extensions/file-browse/live.ts +35 -14
- package/extensions/file-browse/palette.ts +224 -0
- package/extensions/file-browse/rev.ts +42 -0
- package/extensions/file-browse/router.ts +27 -6
- package/extensions/file-browse/server.ts +7 -0
- package/extensions/file-browse/styles.ts +10 -3
- package/extensions/file-browse/tree.ts +381 -0
- package/extensions/file-read/README.md +4 -1
- package/extensions/file-read/extract/odt.ts +7 -5
- package/extensions/goal/plan-route.ts +8 -2
- package/extensions/goal/tools.ts +6 -11
- package/extensions/job/README.md +7 -5
- package/extensions/job/index.ts +35 -52
- package/extensions/job/tools.ts +51 -77
- package/extensions/memory/README.md +3 -3
- package/extensions/memory/tools.ts +4 -6
- package/extensions/memory/tui.ts +5 -1
- package/extensions/plan/README.md +333 -84
- package/extensions/plan/commands.ts +73 -7
- package/extensions/plan/feedback.ts +300 -63
- package/extensions/plan/index.ts +1 -1
- package/extensions/plan/plan-index.ts +4 -2
- package/extensions/plan/plan-text.ts +18 -1
- package/extensions/plan/plans.ts +184 -58
- package/extensions/plan/review-prompt.ts +93 -0
- package/extensions/plan/review.ts +95 -95
- package/extensions/plan/runtime.ts +4 -2
- package/extensions/plan/split.ts +87 -0
- package/extensions/plan/steps.ts +123 -107
- package/extensions/plan/tools/cancel.ts +79 -0
- package/extensions/plan/tools/enter.ts +17 -12
- package/extensions/plan/tools/exit.ts +148 -59
- package/extensions/plan/tools/steps.ts +7 -19
- package/extensions/plan/tools/write.ts +29 -31
- package/extensions/plan/tools.ts +6 -3
- package/extensions/plan/tui.ts +60 -15
- package/extensions/plan/types.ts +45 -3
- package/extensions/plan/web-chrome.ts +102 -0
- package/extensions/plan/web-client.ts +177 -446
- package/extensions/plan/web-collab.ts +207 -0
- package/extensions/plan/web-comments.ts +269 -0
- package/extensions/plan/web-markdown.ts +222 -0
- package/extensions/plan/web-page.ts +131 -19
- package/extensions/plan/web-router.ts +148 -32
- package/extensions/plan/web-share-session.ts +197 -0
- package/extensions/plan/web-share.ts +169 -0
- package/extensions/plan/web-streams.ts +124 -0
- package/extensions/plan/web-tokens.ts +63 -0
- package/extensions/plan/web.ts +193 -45
- package/extensions/question/README.md +3 -0
- package/extensions/question/dialogs.ts +91 -1
- package/extensions/question/tools.ts +2 -1
- package/extensions/rewind/checklist.ts +4 -0
- package/extensions/rewind/review-tui.ts +3 -1
- package/extensions/rewind/rewind-tui.ts +4 -0
- package/extensions/session-bus/README.md +247 -0
- package/extensions/session-bus/commands.ts +165 -0
- package/extensions/session-bus/format.ts +124 -0
- package/extensions/session-bus/index.ts +69 -0
- package/extensions/session-bus/notice.ts +58 -0
- package/extensions/session-bus/protocol.ts +134 -0
- package/extensions/session-bus/result.ts +243 -0
- package/extensions/session-bus/rows.ts +169 -0
- package/extensions/session-bus/runtime.ts +575 -0
- package/extensions/session-bus/store.ts +329 -0
- package/extensions/session-bus/team.ts +102 -0
- package/extensions/session-bus/tools.ts +227 -0
- package/extensions/session-bus/tui.ts +394 -0
- package/extensions/session-bus/types.ts +98 -0
- package/extensions/subagent/README.md +26 -13
- package/extensions/subagent/format.ts +72 -25
- package/extensions/subagent/index.ts +21 -47
- package/extensions/subagent/orchestrate.ts +18 -41
- package/extensions/subagent/render.ts +29 -42
- package/extensions/subagent/stream.ts +24 -18
- package/extensions/subagent/types.ts +7 -0
- package/extensions/todo/tools.ts +5 -6
- package/extensions/todo/tui.ts +5 -1
- package/extensions/watch/README.md +9 -5
- package/extensions/watch/index.ts +25 -44
- package/extensions/watch/tools.ts +31 -59
- package/extensions/wechat/README.md +45 -36
- package/extensions/wechat/bridge.ts +46 -48
- package/extensions/wechat/client.ts +1 -4
- package/extensions/wechat/commands.ts +3 -3
- package/extensions/wechat/deliver.ts +57 -37
- package/extensions/wechat/interaction.ts +54 -18
- package/extensions/wechat/login-screen.ts +2 -3
- package/extensions/wechat/notices.ts +9 -7
- package/extensions/wechat/peer-worker.ts +19 -34
- package/extensions/wechat/presence.ts +3 -4
- package/extensions/wechat/prompt.ts +53 -23
- package/extensions/wechat/reply-pipeline.ts +12 -4
- package/extensions/wechat/slash.ts +14 -0
- package/extensions/wechat/status-screen.ts +2 -2
- package/extensions/wechat/tools.ts +3 -11
- package/extensions/worktree/README.md +48 -12
- package/extensions/worktree/commands.ts +109 -65
- package/extensions/worktree/enter.ts +1 -1
- package/extensions/worktree/exit.ts +11 -1
- package/extensions/worktree/git.ts +5 -0
- package/extensions/worktree/lifecycle.ts +9 -1
- package/extensions/worktree/registry.ts +11 -5
- package/extensions/worktree/screen.ts +263 -0
- package/extensions/worktree/status.ts +84 -40
- package/lib/README.md +12 -7
- package/lib/claims/overlap.ts +43 -0
- package/lib/config.ts +12 -0
- package/lib/format.ts +6 -2
- package/lib/http/router.ts +40 -13
- package/lib/http/server.ts +27 -13
- package/lib/interaction.ts +28 -1
- package/lib/notice.ts +29 -2
- package/lib/plan-entry.ts +7 -2
- package/lib/plans/sections.ts +172 -0
- package/lib/plans/setting.ts +52 -0
- package/lib/plans/split.ts +215 -0
- package/lib/plans/store.ts +19 -0
- package/lib/plans/team.ts +151 -0
- package/lib/qr.ts +64 -0
- package/lib/repo-key.ts +74 -0
- package/lib/report.ts +117 -25
- package/lib/requirements.ts +1 -2
- package/lib/tool-result.ts +54 -4
- package/lib/tui/README.md +30 -8
- package/lib/tui/chrome.ts +27 -6
- package/lib/tui/dock-screen.ts +91 -19
- package/lib/tui/gauge.ts +9 -5
- package/lib/tui/line-pane.ts +15 -4
- package/lib/tui/tool.ts +23 -4
- package/lib/usage.ts +26 -0
- package/lib/web/README.md +131 -0
- package/lib/web/base.ts +19 -2
- package/lib/web/components.ts +10 -6
- package/lib/web/layout.ts +2 -0
- package/lib/web/markup.ts +2 -1
- package/lib/web/runtime.ts +103 -25
- package/lib/web/tokens.ts +23 -7
- package/package.json +5 -2
- package/skills/changelog/SKILL.md +57 -45
- package/skills/init/SKILL.md +80 -45
- package/skills/init/references/agents-md-quality.md +26 -2
- package/skills/init/references/interview-questions.md +36 -14
- package/skills/next/SKILL.md +209 -0
- package/skills/parallel-dev/SKILL.md +150 -0
- package/extensions/wechat/qrcode.ts +0 -46
|
@@ -1,50 +1,48 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The unified bytefly panel:
|
|
2
|
+
* The unified bytefly panel: three sections around one docked list.
|
|
3
3
|
*
|
|
4
4
|
* Extensions lists every resolved extension with its enable checkbox and its
|
|
5
|
-
* health;
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* section
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
* the
|
|
5
|
+
* health; Skills lists every resolved skill the same way, with what its
|
|
6
|
+
* `SKILL.md` declares; Settings lists the curated preferences with their values.
|
|
7
|
+
* The component owns the section switch, the cursors, the focused row key per
|
|
8
|
+
* section, the expanded set, the pending toggle chain, and the notice; the
|
|
9
|
+
* section modules own their rows and detail panes, and the Settings section also
|
|
10
|
+
* owns its editor and JSON-pane state. It is derived: every render rebuilds the
|
|
11
|
+
* model from the picker runtime, the reports, the skill index, or the settings
|
|
12
|
+
* surface, and keeps the focused row's key across rebuilds. The screen is drawn
|
|
13
|
+
* from the design system — a label-first `titleRule`, one `listRow` grammar, one
|
|
13
14
|
* `checkbox` — and its scroll math from `lib/tui/cursor.ts`.
|
|
14
15
|
*/
|
|
15
16
|
|
|
16
17
|
import type { Theme } from "@earendil-works/pi-coding-agent";
|
|
17
18
|
import { Key, matchesKey, type Component, type TuiMouseEvent, type TuiMouseEventResult } from "@earendil-works/pi-tui";
|
|
18
|
-
import type { ViewportRowsSource } from "../../../lib/tui/chrome.ts";
|
|
19
|
-
import {
|
|
20
|
-
import { isCloseKey, ListCursor, navIntent, wheelDelta } from "../../../lib/tui/cursor.ts";
|
|
21
|
-
import { emptyState } from "../../../lib/tui/rows.ts";
|
|
19
|
+
import type { ScreenHint, ViewportRowsSource } from "../../../lib/tui/chrome.ts";
|
|
20
|
+
import { isCloseKey, ListCursor, wheelDelta } from "../../../lib/tui/cursor.ts";
|
|
22
21
|
import { titleRule } from "../../../lib/tui/rules.ts";
|
|
23
22
|
import type { ExtensionReport } from "../../../lib/requirements.ts";
|
|
24
|
-
import type {
|
|
25
|
-
import {
|
|
26
|
-
import {
|
|
23
|
+
import type { PickerRuntime } from "../picker/runtime.ts";
|
|
24
|
+
import type { ResourceKind } from "../picker/types.ts";
|
|
25
|
+
import { SECTION_ORDER, SKILL_NOUN } from "./format.ts";
|
|
26
|
+
import { buildPanelModel, buildSkillModel } from "./join.ts";
|
|
27
|
+
import { UNTRUSTED_SETTINGS_NOTE } from "./settings.ts";
|
|
27
28
|
import type { SettingsSurface } from "./settings-runtime.ts";
|
|
28
|
-
import type { PanelModel, PanelSection } from "./types.ts";
|
|
29
|
+
import type { PanelModel, PanelRow, PanelSection, SkillInfo } from "./types.ts";
|
|
29
30
|
import { entriesOf, entryKey, isSelectable, settingEntries, type PanelEntry } from "./tui/entries.ts";
|
|
30
|
-
import {
|
|
31
|
-
import { clampSelection, firstRow, lastRow, moveRows, retainFocus } from "./tui/nav.ts";
|
|
32
|
-
import { extensionDetail, extensionHints, extensionRow, extensionSummary } from "./tui/extensions-view.ts";
|
|
31
|
+
import { extensionDetail } from "./tui/extensions-view.ts";
|
|
33
32
|
import { BODY_START, detailRows, groupLine, hintLine, noticeLine, panelFrame, sectionBar } from "./tui/frame.ts";
|
|
34
|
-
import {
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
} from "./tui/settings-view.ts";
|
|
33
|
+
import { resourceRow, resourceSummary, sectionHints } from "./tui/list-view.ts";
|
|
34
|
+
import { clampSelection, moveRows, navigateEntries, retainFocus } from "./tui/nav.ts";
|
|
35
|
+
import { editHints, settingsHints } from "./tui/settings-view.ts";
|
|
36
|
+
import { SettingsSection } from "./tui/settings-section.ts";
|
|
37
|
+
import { skillDetail } from "./tui/skills-view.ts";
|
|
38
|
+
|
|
39
|
+
/** A section that draws a resource list rather than the settings table. */
|
|
40
|
+
type ListSection = ResourceKind;
|
|
43
41
|
|
|
44
42
|
/** Host seams for the panel screen, injected by the command runner. */
|
|
45
43
|
export interface PanelComponentOptions {
|
|
46
44
|
/** The session's picker runtime: items, scope, toggles, dirty flag. */
|
|
47
|
-
runtime:
|
|
45
|
+
runtime: PickerRuntime;
|
|
48
46
|
/** Current reports; read on every render so a refresh is picked up. */
|
|
49
47
|
reports: () => readonly ExtensionReport[];
|
|
50
48
|
/** The Settings section's rows, values, and writes. */
|
|
@@ -60,6 +58,8 @@ export interface PanelComponentOptions {
|
|
|
60
58
|
/** Live terminal height, for the row budget. */
|
|
61
59
|
viewportRows?: ViewportRowsSource;
|
|
62
60
|
requestRender: () => void;
|
|
61
|
+
/** Read every resolved skill's metadata; called once, when Skills first needs it. */
|
|
62
|
+
skills: () => ReadonlyMap<string, SkillInfo>;
|
|
63
63
|
/** The Extensions view probes on first show (no session cache, or the command asked to refresh). */
|
|
64
64
|
needsProbe?: boolean;
|
|
65
65
|
/** Force that first probe (`/bytefly … refresh`). */
|
|
@@ -69,14 +69,63 @@ export interface PanelComponentOptions {
|
|
|
69
69
|
onClose: () => void;
|
|
70
70
|
}
|
|
71
71
|
|
|
72
|
+
/** What a list section's detail pane receives; each section reads what it needs. */
|
|
73
|
+
interface DetailInput {
|
|
74
|
+
theme: Theme;
|
|
75
|
+
width: number;
|
|
76
|
+
row: PanelRow | undefined;
|
|
77
|
+
budget: number;
|
|
78
|
+
expanded: boolean;
|
|
79
|
+
dirty: boolean;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/** What a list section's hint row receives. */
|
|
83
|
+
interface HintInput {
|
|
84
|
+
expanded: boolean;
|
|
85
|
+
refreshing: boolean;
|
|
86
|
+
projectScope: boolean;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/** One list section's own vocabulary: what it calls empty, its pane, and its hints. */
|
|
90
|
+
interface ListView {
|
|
91
|
+
emptyLabel: string;
|
|
92
|
+
detail(input: DetailInput): string[];
|
|
93
|
+
hints(input: HintInput): ScreenHint[];
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* The two list sections. Everything else — the frame, the rows, the cursor, the
|
|
98
|
+
* keys, the scope — is shared, so a section only supplies what it alone can say.
|
|
99
|
+
*/
|
|
100
|
+
const LIST_VIEWS: Record<ListSection, ListView> = {
|
|
101
|
+
extensions: {
|
|
102
|
+
emptyLabel: "No extensions found.",
|
|
103
|
+
detail: (input) => extensionDetail(input.theme, input.width, input.row, input.budget, input.expanded, input.dirty),
|
|
104
|
+
hints: (input) =>
|
|
105
|
+
sectionHints({ expanded: input.expanded, projectScope: input.projectScope, refreshing: input.refreshing }),
|
|
106
|
+
},
|
|
107
|
+
skills: {
|
|
108
|
+
emptyLabel: "No skills found.",
|
|
109
|
+
detail: (input) => skillDetail(input),
|
|
110
|
+
// A skill has no probe to re-run, so the section never offers the refresh key.
|
|
111
|
+
hints: (input) => sectionHints({ expanded: input.expanded, projectScope: input.projectScope }),
|
|
112
|
+
},
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
/** The resource kind a list section toggles, or `undefined` for Settings. */
|
|
116
|
+
function kindOf(section: PanelSection): ResourceKind | undefined {
|
|
117
|
+
return section === "settings" ? undefined : section;
|
|
118
|
+
}
|
|
119
|
+
|
|
72
120
|
/** The docked bytefly panel. */
|
|
73
121
|
export class PanelComponent implements Component {
|
|
74
122
|
private readonly cursors: Record<PanelSection, ListCursor>;
|
|
75
|
-
/** Serialises
|
|
123
|
+
/** Serialises toggles so a rapid double press applies twice, not once. */
|
|
76
124
|
private chain: Promise<void> = Promise.resolve();
|
|
77
125
|
/** Focused row key per section, so it survives a rebuild that reorders rows. */
|
|
78
126
|
private readonly focusedKeys: Record<PanelSection, string | undefined> = {
|
|
79
127
|
extensions: undefined,
|
|
128
|
+
skills: undefined,
|
|
80
129
|
settings: undefined,
|
|
81
130
|
};
|
|
82
131
|
/** Row keys whose findings are expanded; Enter toggles only the focused row. */
|
|
@@ -85,21 +134,43 @@ export class PanelComponent implements Component {
|
|
|
85
134
|
private refreshing = false;
|
|
86
135
|
/** True once the Extensions view has asked for its first probe. */
|
|
87
136
|
private probed = false;
|
|
137
|
+
/** Skill metadata, read once when the Skills section first needs it. */
|
|
138
|
+
private skillIndex: ReadonlyMap<string, SkillInfo> | undefined;
|
|
88
139
|
/** Transient feedback (a refusal or a failed write), shown in the hint row. */
|
|
89
140
|
private notice: string | undefined;
|
|
90
|
-
/** The text row being edited, if any. */
|
|
91
|
-
private editing: EditingState | undefined;
|
|
92
|
-
/** True while the Settings detail pane shows the focused row's section as JSON. */
|
|
93
|
-
private json = false;
|
|
94
141
|
/** Whether the last Settings render kept the project note; the click mapping needs it. */
|
|
95
142
|
private noteShown = false;
|
|
143
|
+
/** The Settings section's editor and JSON-pane state. */
|
|
144
|
+
private readonly settingsSection: SettingsSection;
|
|
96
145
|
/** The visible section. */
|
|
97
146
|
private section: PanelSection;
|
|
98
147
|
|
|
99
148
|
constructor(private readonly options: PanelComponentOptions) {
|
|
100
149
|
const requestRender = (): void => this.options.requestRender();
|
|
101
|
-
this.cursors = {
|
|
150
|
+
this.cursors = {
|
|
151
|
+
extensions: new ListCursor(requestRender),
|
|
152
|
+
skills: new ListCursor(requestRender),
|
|
153
|
+
settings: new ListCursor(requestRender),
|
|
154
|
+
};
|
|
102
155
|
this.section = options.section ?? "extensions";
|
|
156
|
+
this.settingsSection = new SettingsSection({
|
|
157
|
+
surface: options.settings,
|
|
158
|
+
theme: options.theme,
|
|
159
|
+
scope: () => this.options.runtime.scope(),
|
|
160
|
+
cursor: this.cursors.settings,
|
|
161
|
+
viewportRows: options.viewportRows,
|
|
162
|
+
cwd: options.cwd,
|
|
163
|
+
agentDir: options.agentDir,
|
|
164
|
+
retain: (entries) => this.retain(entries),
|
|
165
|
+
navigate: (data) => this.navigate(data),
|
|
166
|
+
switchScope: () => this.switchScope(),
|
|
167
|
+
notify: (message) => this.notify(message),
|
|
168
|
+
requestRender,
|
|
169
|
+
setNoteShown: (shown) => {
|
|
170
|
+
this.noteShown = shown;
|
|
171
|
+
},
|
|
172
|
+
hint: (width) => this.hintRow(width, { jsonOpen: this.settingsSection.jsonOpen }),
|
|
173
|
+
});
|
|
103
174
|
}
|
|
104
175
|
|
|
105
176
|
/** The active section's cursor. */
|
|
@@ -112,7 +183,7 @@ export class PanelComponent implements Component {
|
|
|
112
183
|
return this.options.theme;
|
|
113
184
|
}
|
|
114
185
|
|
|
115
|
-
/** Whether anything has been written this session,
|
|
186
|
+
/** Whether anything has been written this session, in any section. */
|
|
116
187
|
private get dirty(): boolean {
|
|
117
188
|
return this.options.runtime.isDirty() || this.options.settings.isDirty();
|
|
118
189
|
}
|
|
@@ -121,21 +192,36 @@ export class PanelComponent implements Component {
|
|
|
121
192
|
// Everything is rebuilt from live state on each render.
|
|
122
193
|
}
|
|
123
194
|
|
|
124
|
-
/** The merged model for the current items
|
|
125
|
-
private model(): PanelModel {
|
|
195
|
+
/** The merged model for the current items of one list section. */
|
|
196
|
+
private model(kind: ListSection): PanelModel {
|
|
197
|
+
if (kind === "skills") {
|
|
198
|
+
return buildSkillModel({
|
|
199
|
+
items: this.options.runtime.items("skills"),
|
|
200
|
+
index: this.skills(),
|
|
201
|
+
agentDir: this.options.agentDir,
|
|
202
|
+
});
|
|
203
|
+
}
|
|
126
204
|
return buildPanelModel({
|
|
127
|
-
items: this.options.runtime.items(),
|
|
205
|
+
items: this.options.runtime.items("extensions"),
|
|
128
206
|
reports: this.options.reports(),
|
|
129
207
|
agentDir: this.options.agentDir,
|
|
130
208
|
filter: this.options.filter,
|
|
131
209
|
});
|
|
132
210
|
}
|
|
133
211
|
|
|
212
|
+
/** Skill metadata, loaded on first use and kept for the screen's lifetime. */
|
|
213
|
+
private skills(): ReadonlyMap<string, SkillInfo> {
|
|
214
|
+
this.skillIndex ??= this.options.skills();
|
|
215
|
+
return this.skillIndex;
|
|
216
|
+
}
|
|
217
|
+
|
|
134
218
|
/** The entries of the visible section. */
|
|
135
219
|
private activeEntries(): PanelEntry[] {
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
220
|
+
const section = this.section;
|
|
221
|
+
if (section === "settings") {
|
|
222
|
+
return settingEntries(this.options.settings.views(this.options.runtime.scope()));
|
|
223
|
+
}
|
|
224
|
+
return entriesOf(this.model(section));
|
|
139
225
|
}
|
|
140
226
|
|
|
141
227
|
/** Keep the focus on the same row key when the list shape changes. */
|
|
@@ -152,28 +238,18 @@ export class PanelComponent implements Component {
|
|
|
152
238
|
clampSelection(entries, this.cursor);
|
|
153
239
|
}
|
|
154
240
|
|
|
155
|
-
private select(index: number | undefined): void {
|
|
156
|
-
if (index === undefined) return;
|
|
157
|
-
this.cursor.set(index, this.activeEntries().length);
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
/** Move `delta` rows, skipping group headers. */
|
|
161
|
-
private move(delta: number): void {
|
|
162
|
-
moveRows(this.activeEntries(), this.cursor, delta);
|
|
163
|
-
}
|
|
164
|
-
|
|
165
241
|
private switchScope(): void {
|
|
166
242
|
const next = this.options.runtime.scope() === "global" ? "project" : "global";
|
|
167
243
|
if (next === "project" && !this.options.runtime.projectAvailable()) {
|
|
168
244
|
this.notify(
|
|
169
245
|
this.section === "settings"
|
|
170
246
|
? UNTRUSTED_SETTINGS_NOTE
|
|
171
|
-
:
|
|
247
|
+
: `Project is not trusted; manage ${this.section} in Global scope only.`,
|
|
172
248
|
);
|
|
173
249
|
return;
|
|
174
250
|
}
|
|
175
251
|
// The JSON pane names a file, so a scope switch has to close it.
|
|
176
|
-
this.
|
|
252
|
+
this.settingsSection.reset();
|
|
177
253
|
const focused = entryKey(this.activeEntries()[this.cursor.selected]);
|
|
178
254
|
this.options.runtime.setScope(next);
|
|
179
255
|
// Keep the setting under the cursor across the layer switch; the two scopes
|
|
@@ -187,6 +263,8 @@ export class PanelComponent implements Component {
|
|
|
187
263
|
}
|
|
188
264
|
|
|
189
265
|
private toggle(): void {
|
|
266
|
+
const kind = kindOf(this.section);
|
|
267
|
+
if (kind === undefined) return;
|
|
190
268
|
const entry = this.activeEntries()[this.cursor.selected];
|
|
191
269
|
if (entry?.kind !== "row") return;
|
|
192
270
|
const item = entry.row.item;
|
|
@@ -197,13 +275,15 @@ export class PanelComponent implements Component {
|
|
|
197
275
|
return;
|
|
198
276
|
}
|
|
199
277
|
this.chain = this.chain
|
|
200
|
-
.then(() => this.options.runtime.toggle(item))
|
|
278
|
+
.then(() => this.options.runtime.toggle(kind, item))
|
|
201
279
|
.catch(() => undefined)
|
|
202
280
|
.then(() => this.options.requestRender());
|
|
203
281
|
}
|
|
204
282
|
|
|
205
|
-
/** Drop the focused
|
|
283
|
+
/** Drop the focused resource's project override, restoring the inherited row. */
|
|
206
284
|
private clearOverride(): void {
|
|
285
|
+
const kind = kindOf(this.section);
|
|
286
|
+
if (kind === undefined) return;
|
|
207
287
|
const entry = this.activeEntries()[this.cursor.selected];
|
|
208
288
|
if (entry?.kind !== "row") return;
|
|
209
289
|
const item = entry.row.item;
|
|
@@ -212,98 +292,24 @@ export class PanelComponent implements Component {
|
|
|
212
292
|
return;
|
|
213
293
|
}
|
|
214
294
|
this.chain = this.chain
|
|
215
|
-
.then(() => this.options.runtime.clearOverride(item))
|
|
295
|
+
.then(() => this.options.runtime.clearOverride(kind, item))
|
|
216
296
|
.catch(() => undefined)
|
|
217
297
|
.then(() => this.options.requestRender());
|
|
218
298
|
}
|
|
219
299
|
|
|
220
|
-
/** Change the focused setting: cycle it, or open the editor for a text row. */
|
|
221
|
-
private changeSetting(): void {
|
|
222
|
-
const entry = this.activeEntries()[this.cursor.selected];
|
|
223
|
-
if (entry?.kind !== "setting") return;
|
|
224
|
-
const view = entry.setting;
|
|
225
|
-
if (view.kind === "text") {
|
|
226
|
-
this.editing = { id: view.id, buffer: view.value, caret: view.value.length };
|
|
227
|
-
this.options.requestRender();
|
|
228
|
-
return;
|
|
229
|
-
}
|
|
230
|
-
const scope = this.options.runtime.scope();
|
|
231
|
-
this.chain = this.chain
|
|
232
|
-
.then(() => this.options.settings.change(scope, view.id))
|
|
233
|
-
.catch((error: unknown) => this.notify(error instanceof Error ? error.message : String(error)))
|
|
234
|
-
.then(() => this.options.requestRender());
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
/** Delete the focused setting's leaf, so its default applies again. */
|
|
238
|
-
private resetSetting(): void {
|
|
239
|
-
const entry = this.activeEntries()[this.cursor.selected];
|
|
240
|
-
if (entry?.kind !== "setting") return;
|
|
241
|
-
const scope = this.options.runtime.scope();
|
|
242
|
-
this.chain = this.chain
|
|
243
|
-
.then(() => this.options.settings.reset(scope, entry.setting.id))
|
|
244
|
-
.catch((error: unknown) => this.notify(error instanceof Error ? error.message : String(error)))
|
|
245
|
-
.then(() => this.options.requestRender());
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
/** Toggle the detail pane between the row and its whole section as stored. */
|
|
249
|
-
private toggleJson(): void {
|
|
250
|
-
const entry = this.activeEntries()[this.cursor.selected];
|
|
251
|
-
if (entry?.kind !== "setting") return;
|
|
252
|
-
if (!entry.setting.config) {
|
|
253
|
-
// There is no section to show, so leave the mode off rather than keeping a
|
|
254
|
-
// hidden toggle that would reopen the pane on another row.
|
|
255
|
-
this.json = false;
|
|
256
|
-
this.notify(`${entry.setting.label} is a Pi setting; bytefly.json has no section for it.`);
|
|
257
|
-
return;
|
|
258
|
-
}
|
|
259
|
-
this.json = !this.json;
|
|
260
|
-
this.options.requestRender();
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
/** Store the editor's buffer; a refusal keeps it open with the reason shown. */
|
|
264
|
-
private commitEdit(): void {
|
|
265
|
-
const editing = this.editing;
|
|
266
|
-
if (!editing) return;
|
|
267
|
-
const scope = this.options.runtime.scope();
|
|
268
|
-
this.chain = this.chain
|
|
269
|
-
.then(() => this.options.settings.setValue(scope, editing.id, editing.buffer))
|
|
270
|
-
.then((saved) => {
|
|
271
|
-
if (saved) this.editing = undefined;
|
|
272
|
-
})
|
|
273
|
-
.catch((error: unknown) => this.notify(error instanceof Error ? error.message : String(error)))
|
|
274
|
-
.then(() => this.options.requestRender());
|
|
275
|
-
}
|
|
276
|
-
|
|
277
300
|
/**
|
|
278
|
-
*
|
|
279
|
-
*
|
|
301
|
+
* Ask for the work the newly visible section needs. The Extensions view probes
|
|
302
|
+
* when the session had no cache — doing it at mount would delay `/bytefly
|
|
303
|
+
* settings`, which never reads the reports — and the Skills view reads its
|
|
304
|
+
* metadata in the same one-shot way, so a scope switch repaints with names.
|
|
280
305
|
*/
|
|
281
|
-
|
|
282
|
-
const
|
|
283
|
-
if (
|
|
284
|
-
|
|
285
|
-
const action = applyEditKey(data, editing, max);
|
|
286
|
-
if (action.kind === "commit") {
|
|
287
|
-
void this.commitEdit();
|
|
288
|
-
return;
|
|
289
|
-
}
|
|
290
|
-
if (action.kind === "cancel") {
|
|
291
|
-
this.editing = undefined;
|
|
292
|
-
this.options.requestRender();
|
|
306
|
+
prime(): void {
|
|
307
|
+
const section = this.section;
|
|
308
|
+
if (section === "skills") {
|
|
309
|
+
this.skills();
|
|
293
310
|
return;
|
|
294
311
|
}
|
|
295
|
-
if (
|
|
296
|
-
this.editing = action.state;
|
|
297
|
-
this.options.requestRender();
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
/**
|
|
301
|
-
* Ask for the first Extensions probe when the session had no cache. The first
|
|
302
|
-
* time the section is visible is when the rows need health; doing it at mount
|
|
303
|
-
* would delay `/bytefly settings`, which never reads the reports.
|
|
304
|
-
*/
|
|
305
|
-
prime(): void {
|
|
306
|
-
if (this.probed || this.options.needsProbe !== true || this.section !== "extensions") return;
|
|
312
|
+
if (this.probed || this.options.needsProbe !== true || section !== "extensions") return;
|
|
307
313
|
this.probed = true;
|
|
308
314
|
void this.refresh(this.options.initialRefresh === true);
|
|
309
315
|
}
|
|
@@ -333,119 +339,67 @@ export class PanelComponent implements Component {
|
|
|
333
339
|
}
|
|
334
340
|
}
|
|
335
341
|
|
|
336
|
-
/**
|
|
337
|
-
|
|
338
|
-
|
|
342
|
+
/**
|
|
343
|
+
* Step one section left or right. The ends stop instead of wrapping, so the
|
|
344
|
+
* bar reads as the screen's order rather than a loop.
|
|
345
|
+
*/
|
|
346
|
+
private stepSection(direction: -1 | 1): void {
|
|
347
|
+
const next = SECTION_ORDER[SECTION_ORDER.indexOf(this.section) + direction];
|
|
348
|
+
if (next === undefined || next === this.section) return;
|
|
339
349
|
this.section = next;
|
|
340
|
-
this.
|
|
350
|
+
this.settingsSection.reset();
|
|
341
351
|
this.prime();
|
|
342
352
|
this.options.requestRender();
|
|
343
353
|
}
|
|
344
354
|
|
|
345
|
-
/**
|
|
355
|
+
/** Read one navigation key against the visible section. */
|
|
346
356
|
private navigate(data: string): void {
|
|
347
|
-
|
|
348
|
-
this.select(firstRow(this.activeEntries()));
|
|
349
|
-
return;
|
|
350
|
-
}
|
|
351
|
-
if (data === "G") {
|
|
352
|
-
this.select(lastRow(this.activeEntries()));
|
|
353
|
-
return;
|
|
354
|
-
}
|
|
355
|
-
// Only Extensions carries group headings; the Settings list is flat, so the
|
|
356
|
-
// keys would move the cursor nowhere there.
|
|
357
|
-
if (this.section === "extensions" && (data === "[" || data === "]")) {
|
|
358
|
-
this.jumpGroup(data === "[" ? -1 : 1);
|
|
359
|
-
return;
|
|
360
|
-
}
|
|
361
|
-
switch (navIntent(data)) {
|
|
362
|
-
case "up":
|
|
363
|
-
this.move(-1);
|
|
364
|
-
return;
|
|
365
|
-
case "down":
|
|
366
|
-
this.move(1);
|
|
367
|
-
return;
|
|
368
|
-
case "pageUp":
|
|
369
|
-
this.move(-Math.max(1, this.cursor.visible));
|
|
370
|
-
return;
|
|
371
|
-
case "pageDown":
|
|
372
|
-
this.move(Math.max(1, this.cursor.visible));
|
|
373
|
-
return;
|
|
374
|
-
case "home":
|
|
375
|
-
this.select(firstRow(this.activeEntries()));
|
|
376
|
-
return;
|
|
377
|
-
case "end":
|
|
378
|
-
this.select(lastRow(this.activeEntries()));
|
|
379
|
-
return;
|
|
380
|
-
}
|
|
357
|
+
navigateEntries(this.activeEntries(), this.cursor, data, { grouped: this.section !== "settings" });
|
|
381
358
|
}
|
|
382
359
|
|
|
383
|
-
/**
|
|
384
|
-
private
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
// Step past the focused row's own header: landing on it would not move.
|
|
389
|
-
const own = headers.filter((index) => index < selected).at(-1);
|
|
390
|
-
const target =
|
|
391
|
-
direction === 1
|
|
392
|
-
? headers.find((index) => index > selected)
|
|
393
|
-
: headers.filter((index) => index < (own ?? 0)).at(-1);
|
|
394
|
-
if (target === undefined) return;
|
|
395
|
-
for (let index = target + 1; index < entries.length; index += 1) {
|
|
396
|
-
if (isSelectable(entries[index])) {
|
|
397
|
-
this.select(index);
|
|
398
|
-
return;
|
|
399
|
-
}
|
|
400
|
-
}
|
|
401
|
-
}
|
|
402
|
-
|
|
403
|
-
/** Settings keys: change, scope, navigation. `←`/`→` stay on sections. */
|
|
404
|
-
private handleSettingsInput(data: string): void {
|
|
405
|
-
this.clampSelection(this.activeEntries());
|
|
406
|
-
if (matchesKey(data, Key.space) || matchesKey(data, Key.enter)) {
|
|
407
|
-
this.changeSetting();
|
|
408
|
-
return;
|
|
409
|
-
}
|
|
410
|
-
if (matchesKey(data, Key.tab)) {
|
|
411
|
-
this.switchScope();
|
|
412
|
-
return;
|
|
413
|
-
}
|
|
414
|
-
if (data === "u") {
|
|
415
|
-
this.resetSetting();
|
|
416
|
-
return;
|
|
417
|
-
}
|
|
418
|
-
if (data === "J") {
|
|
419
|
-
this.toggleJson();
|
|
420
|
-
return;
|
|
421
|
-
}
|
|
422
|
-
this.navigate(data);
|
|
360
|
+
/** Drop the hint row's notice; any key dismisses it and still acts. */
|
|
361
|
+
private dismissNotice(): void {
|
|
362
|
+
if (this.notice === undefined) return;
|
|
363
|
+
this.notice = undefined;
|
|
364
|
+
this.options.requestRender();
|
|
423
365
|
}
|
|
424
366
|
|
|
425
367
|
handleInput(data: string): void {
|
|
426
|
-
|
|
427
|
-
|
|
368
|
+
const section = this.section;
|
|
369
|
+
if (section === "settings") {
|
|
370
|
+
// The editor takes every key, `Esc` included: it cancels the edit rather
|
|
371
|
+
// than closing the screen.
|
|
372
|
+
if (this.settingsSection.isEditing) {
|
|
373
|
+
this.settingsSection.handleInput(data);
|
|
374
|
+
return;
|
|
375
|
+
}
|
|
376
|
+
if (isCloseKey(data)) {
|
|
377
|
+
this.options.onClose();
|
|
378
|
+
return;
|
|
379
|
+
}
|
|
380
|
+
this.dismissNotice();
|
|
381
|
+
if (matchesKey(data, Key.left)) {
|
|
382
|
+
this.stepSection(-1);
|
|
383
|
+
return;
|
|
384
|
+
}
|
|
385
|
+
if (matchesKey(data, Key.right)) {
|
|
386
|
+
this.stepSection(1);
|
|
387
|
+
return;
|
|
388
|
+
}
|
|
389
|
+
this.settingsSection.handleInput(data);
|
|
428
390
|
return;
|
|
429
391
|
}
|
|
430
392
|
if (isCloseKey(data)) {
|
|
431
393
|
this.options.onClose();
|
|
432
394
|
return;
|
|
433
395
|
}
|
|
434
|
-
|
|
435
|
-
if (this.notice !== undefined) {
|
|
436
|
-
this.notice = undefined;
|
|
437
|
-
this.options.requestRender();
|
|
438
|
-
}
|
|
396
|
+
this.dismissNotice();
|
|
439
397
|
if (matchesKey(data, Key.left)) {
|
|
440
|
-
this.
|
|
398
|
+
this.stepSection(-1);
|
|
441
399
|
return;
|
|
442
400
|
}
|
|
443
401
|
if (matchesKey(data, Key.right)) {
|
|
444
|
-
this.
|
|
445
|
-
return;
|
|
446
|
-
}
|
|
447
|
-
if (this.section === "settings") {
|
|
448
|
-
this.handleSettingsInput(data);
|
|
402
|
+
this.stepSection(1);
|
|
449
403
|
return;
|
|
450
404
|
}
|
|
451
405
|
// An input can arrive before the first render; put the focus on a row.
|
|
@@ -462,7 +416,8 @@ export class PanelComponent implements Component {
|
|
|
462
416
|
this.switchScope();
|
|
463
417
|
return;
|
|
464
418
|
}
|
|
465
|
-
|
|
419
|
+
// Re-probing is the Extensions view's job; a skill has nothing to probe.
|
|
420
|
+
if (section === "extensions" && matchesKey(data, "r")) {
|
|
466
421
|
void this.refresh(true);
|
|
467
422
|
return;
|
|
468
423
|
}
|
|
@@ -478,14 +433,13 @@ export class PanelComponent implements Component {
|
|
|
478
433
|
handleMouse(event: TuiMouseEvent): TuiMouseEventResult | undefined {
|
|
479
434
|
// A click while editing leaves the editor the way `Esc` does: nothing is
|
|
480
435
|
// written behind the screen.
|
|
481
|
-
if (this.
|
|
482
|
-
this.
|
|
483
|
-
this.options.requestRender();
|
|
436
|
+
if (this.settingsSection.isEditing) {
|
|
437
|
+
this.settingsSection.cancelEdit();
|
|
484
438
|
return { handled: true };
|
|
485
439
|
}
|
|
486
440
|
const delta = wheelDelta(event);
|
|
487
441
|
if (delta !== undefined) {
|
|
488
|
-
this.
|
|
442
|
+
moveRows(this.activeEntries(), this.cursor, delta);
|
|
489
443
|
return { handled: true };
|
|
490
444
|
}
|
|
491
445
|
if ((event.type !== "click" && event.type !== "press") || event.button !== "left") return undefined;
|
|
@@ -499,21 +453,21 @@ export class PanelComponent implements Component {
|
|
|
499
453
|
if (line < 0 || line >= window.end - window.start) return undefined;
|
|
500
454
|
const index = window.start + line;
|
|
501
455
|
if (!isSelectable(entries[index])) return undefined;
|
|
502
|
-
this.
|
|
456
|
+
this.cursor.set(index, entries.length);
|
|
503
457
|
return { handled: true };
|
|
504
458
|
}
|
|
505
459
|
|
|
506
460
|
/** Key hints for the active section; whole hints drop as the width runs out. */
|
|
507
461
|
private hint(width: number, options: { expanded?: boolean; jsonOpen?: boolean } = {}): string {
|
|
508
|
-
if (this.
|
|
509
|
-
|
|
510
|
-
this.
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
462
|
+
if (this.settingsSection.isEditing) return hintLine(this.theme, width, editHints());
|
|
463
|
+
if (this.section === "settings") {
|
|
464
|
+
return hintLine(this.theme, width, settingsHints(options.jsonOpen === true));
|
|
465
|
+
}
|
|
466
|
+
const hints = LIST_VIEWS[this.section].hints({
|
|
467
|
+
expanded: options.expanded === true,
|
|
468
|
+
refreshing: this.refreshing,
|
|
469
|
+
projectScope: this.options.runtime.scope() === "project",
|
|
470
|
+
});
|
|
517
471
|
return hintLine(this.theme, width, hints);
|
|
518
472
|
}
|
|
519
473
|
|
|
@@ -538,101 +492,38 @@ export class PanelComponent implements Component {
|
|
|
538
492
|
return entries[this.cursor.selected];
|
|
539
493
|
}
|
|
540
494
|
|
|
541
|
-
/** The Settings body: scope counts, the project note, rows, and detail. */
|
|
542
|
-
private renderSettings(width: number, header: string): string[] {
|
|
543
|
-
const scope = this.options.runtime.scope();
|
|
544
|
-
const views = this.options.settings.views(scope);
|
|
545
|
-
const summary = sectionBar(
|
|
546
|
-
this.theme,
|
|
547
|
-
width,
|
|
548
|
-
this.section,
|
|
549
|
-
settingsSummary(this.theme, width, scope, views.length),
|
|
550
|
-
);
|
|
551
|
-
const entries = settingEntries(views);
|
|
552
|
-
const note = scope === "project" ? projectNote(this.theme, width) : undefined;
|
|
553
|
-
|
|
554
|
-
const focusedEntry = this.retain(entries);
|
|
555
|
-
const focused = focusedEntry?.kind === "setting" ? focusedEntry.setting : undefined;
|
|
556
|
-
// A row with no section leaves the mode off, so moving away and back cannot
|
|
557
|
-
// reopen a pane the user closed.
|
|
558
|
-
if (focused && !focused.config) this.json = false;
|
|
559
|
-
const jsonConfig = this.json ? focused?.config : undefined;
|
|
560
|
-
const detail =
|
|
561
|
-
jsonConfig && focused
|
|
562
|
-
? sectionJsonPane({
|
|
563
|
-
theme: this.theme,
|
|
564
|
-
width,
|
|
565
|
-
section: jsonConfig.section,
|
|
566
|
-
document: this.options.settings.sectionDocument(scope, jsonConfig.section),
|
|
567
|
-
budget: detailRows(true, this.options.viewportRows),
|
|
568
|
-
dirty: this.dirty,
|
|
569
|
-
paths: { scope, cwd: this.options.cwd, agentDir: this.options.agentDir },
|
|
570
|
-
})
|
|
571
|
-
: settingDetail({
|
|
572
|
-
theme: this.theme,
|
|
573
|
-
width,
|
|
574
|
-
view: focused,
|
|
575
|
-
budget: detailRows(false, this.options.viewportRows),
|
|
576
|
-
dirty: this.dirty,
|
|
577
|
-
paths: { scope, cwd: this.options.cwd, agentDir: this.options.agentDir },
|
|
578
|
-
});
|
|
579
|
-
|
|
580
|
-
const { lines, noteShown } = panelFrame({
|
|
581
|
-
theme: this.theme,
|
|
582
|
-
viewportRows: this.options.viewportRows,
|
|
583
|
-
cursor: this.cursor,
|
|
584
|
-
width,
|
|
585
|
-
header,
|
|
586
|
-
summary,
|
|
587
|
-
entries,
|
|
588
|
-
note,
|
|
589
|
-
detail,
|
|
590
|
-
hint: this.hintRow(width, { jsonOpen: jsonConfig !== undefined }),
|
|
591
|
-
draw: (entry, index) => {
|
|
592
|
-
if (entry.kind === "group") return groupLine(this.theme, width, entry);
|
|
593
|
-
if (entry.kind !== "setting") return undefined;
|
|
594
|
-
const editing = this.editing?.id === entry.setting.id ? this.editing : undefined;
|
|
595
|
-
return settingRow(this.theme, width, entry.setting, index === this.cursor.selected, editing);
|
|
596
|
-
},
|
|
597
|
-
});
|
|
598
|
-
this.noteShown = noteShown;
|
|
599
|
-
return lines;
|
|
600
|
-
}
|
|
601
|
-
|
|
602
495
|
render(width: number): string[] {
|
|
603
496
|
const w = Math.max(1, width);
|
|
604
|
-
const header = titleRule(this.theme, w, "
|
|
605
|
-
|
|
497
|
+
const header = titleRule(this.theme, w, "Bytefly");
|
|
498
|
+
const section = this.section;
|
|
499
|
+
if (section === "settings") return this.settingsSection.render(w, header);
|
|
606
500
|
|
|
607
|
-
const model = this.model();
|
|
501
|
+
const model = this.model(section);
|
|
608
502
|
const entries = entriesOf(model);
|
|
609
503
|
const summary = sectionBar(
|
|
610
504
|
this.theme,
|
|
611
505
|
w,
|
|
612
|
-
|
|
613
|
-
|
|
506
|
+
section,
|
|
507
|
+
resourceSummary(
|
|
508
|
+
this.theme,
|
|
509
|
+
w,
|
|
510
|
+
model,
|
|
511
|
+
this.options.runtime.scope(),
|
|
512
|
+
section === "skills" ? { noun: SKILL_NOUN } : { filter: this.options.filter },
|
|
513
|
+
),
|
|
614
514
|
);
|
|
615
515
|
|
|
616
|
-
if (entries.length === 0) {
|
|
617
|
-
return dockListFrame({
|
|
618
|
-
header,
|
|
619
|
-
summary,
|
|
620
|
-
body: emptyState(this.theme, w, { label: "No extensions found." }),
|
|
621
|
-
hint: this.hintRow(w),
|
|
622
|
-
});
|
|
623
|
-
}
|
|
624
|
-
|
|
625
516
|
const focusedEntry = this.retain(entries);
|
|
626
517
|
const focused = focusedEntry?.kind === "row" ? focusedEntry.row : undefined;
|
|
627
518
|
const expanded = focused !== undefined && this.expanded.has(focused.key);
|
|
628
|
-
const detail =
|
|
629
|
-
this.theme,
|
|
630
|
-
w,
|
|
631
|
-
focused,
|
|
632
|
-
detailRows(expanded, this.options.viewportRows),
|
|
519
|
+
const detail = LIST_VIEWS[section].detail({
|
|
520
|
+
theme: this.theme,
|
|
521
|
+
width: w,
|
|
522
|
+
row: focused,
|
|
523
|
+
budget: detailRows(expanded, this.options.viewportRows),
|
|
633
524
|
expanded,
|
|
634
|
-
this.dirty,
|
|
635
|
-
);
|
|
525
|
+
dirty: this.dirty,
|
|
526
|
+
});
|
|
636
527
|
|
|
637
528
|
const { lines, noteShown } = panelFrame({
|
|
638
529
|
theme: this.theme,
|
|
@@ -642,12 +533,13 @@ export class PanelComponent implements Component {
|
|
|
642
533
|
header,
|
|
643
534
|
summary,
|
|
644
535
|
entries,
|
|
536
|
+
emptyLabel: LIST_VIEWS[section].emptyLabel,
|
|
645
537
|
detail,
|
|
646
538
|
hint: this.hintRow(w, { expanded }),
|
|
647
539
|
draw: (entry, index) => {
|
|
648
540
|
if (entry.kind === "group") return groupLine(this.theme, w, entry);
|
|
649
541
|
if (entry.kind !== "row") return undefined;
|
|
650
|
-
return
|
|
542
|
+
return resourceRow(this.theme, w, entry.row, index === this.cursor.selected);
|
|
651
543
|
},
|
|
652
544
|
});
|
|
653
545
|
this.noteShown = noteShown;
|