@danypops/pi-packed 0.16.1 → 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 +11 -8
- package/extension/src/discover.ts +132 -140
- package/extension/src/index.ts +1 -1
- package/extension/src/keymap.ts +89 -0
- package/extension/src/menu-theme.ts +16 -1
- package/extension/src/resource-config.ts +177 -138
- package/extension/src/security-tui.ts +98 -18
- package/extension/src/tab-host.ts +30 -0
- package/extension/src/tui.ts +457 -349
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -10,13 +10,16 @@ The extension connects to Packed's authenticated user daemon and starts the pack
|
|
|
10
10
|
|
|
11
11
|
## Commands
|
|
12
12
|
|
|
13
|
-
- `/packed` --
|
|
14
|
-
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
-
|
|
13
|
+
- `/packed` -- one floating overlay, four tabs (Packages/Find/Config/Settings), always shown in a persistent tab bar at the top, each label's own first letter highlighted as its mnemonic. `Tab`/`Shift-Tab` (or `←`/`→`) sweep between them; from any tab other than Packages, `p`/`f`/`c`/`s` also jump straight to the matching one (never stolen while that tab's own filter/query box is capturing text). `Esc` returns to Packages from anywhere else, or closes the panel from Packages itself. Every approval and reload confirmation, on any tab, renders inline on this same panel (`y`/`n` keys), never a separate popup or native dialog. A standing test (`keymap.test.ts`, using Malevich's tree-style `findMnemonicConflicts`) verifies no two actions genuinely reachable at once share a key.
|
|
14
|
+
- **Packages** (the default tab) -- every installed Pi package, with update availability. Mnemonics follow lazy.nvim's own convention (uppercase acts on every row, lowercase on the one under the cursor):
|
|
15
|
+
- `u` / `U` -- update the selected package / update every outdated package, one combined confirmation and one reload. `U` shows a spinner inline next to the package currently updating, settling into a real ✓ or ✗ plus a short tail of that update's own captured output once it finishes -- the list stays visible throughout, nothing swaps to a separate screen. A reload is confirmed separately from the update itself -- decline it to defer: the update already happened, only picking it up in this Pi session is deferred until `/reload`.
|
|
16
|
+
- `x` -- remove the selected package.
|
|
17
|
+
- `d` -- disable (or re-enable) the selected package's own extensions.
|
|
18
|
+
- `c` -- jump to the Config tab, scoped to the selected package.
|
|
19
|
+
- `f` -- jump to the Find tab. `s` -- jump to the Settings tab.
|
|
20
|
+
- `Enter` opens a smaller floating action menu with all of the above. `r` refreshes, `/` filters, `v` cycles view modes.
|
|
21
|
+
- **Find** -- search the npm registry for new Pi packages and install one.
|
|
22
|
+
- **Config** -- enable or disable individual extensions, skills, prompt templates, and themes declared by installed packages, per package, at global or project scope (`v` switches global/project). `/packed config` opens the panel landing directly on this tab.
|
|
23
|
+
- **Settings** -- change whether package mutations (install/update/remove/toggle) require confirmation.
|
|
21
24
|
- `/packed setup plan [path] [--prune]` / `/packed setup apply [path] [--prune]` -- preview or apply a reproducible-environment manifest (`pi-setup.json`) against installed packages and profiles.
|
|
22
25
|
- `/profile [name]` -- switch to a named Pi profile (provider, model, tools, instructions, theme), reading `~/.pi/agent/profiles.json` and a trusted project's own `.pi/profiles.json`. `pi --profile <name>` activates one at startup; `Ctrl+Shift+U` cycles through them.
|
|
@@ -1,27 +1,26 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* discover.ts — /packed
|
|
3
|
-
* packages and install one. A
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
2
|
+
* discover.ts — /packed's Find tab: query the npm registry for installable
|
|
3
|
+
* Pi packages and install one. A real Component (not its own ctx.ui.custom
|
|
4
|
+
* overlay) so it plugs straight into TabbedContainer alongside
|
|
5
|
+
* Packages/Config/Settings on the same overlay -- it used to open a
|
|
6
|
+
* second, visually distinct screen (no Malevich Envelope, its own
|
|
7
|
+
* hand-rolled Container+DynamicBorder chrome), confirmed live as a real
|
|
8
|
+
* "opens a different TUI" complaint. Search itself never mutates; only
|
|
9
|
+
* Install (behind a quick inline confirm, then the standard approval
|
|
10
|
+
* every mutation surface requires) does.
|
|
7
11
|
*/
|
|
8
12
|
import type { ExtensionCommandContext } from "@earendil-works/pi-coding-agent";
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
13
|
+
import { rawKeyHint } from "@earendil-works/pi-coding-agent";
|
|
14
|
+
import { Input, truncateToWidth, visibleWidth } from "@earendil-works/pi-tui";
|
|
15
|
+
import type { Component } from "malevich-tui-components";
|
|
12
16
|
import { shouldSearch } from "./discover-model.js";
|
|
13
17
|
import type { Natives, PackageSummary } from "./packed.js";
|
|
14
18
|
import { InstallServiceError } from "./packed.js";
|
|
15
19
|
import { approvePackageOperation } from "./tools.js";
|
|
16
|
-
import {
|
|
20
|
+
import type { TabHost } from "./tab-host.js";
|
|
17
21
|
|
|
18
22
|
const SEARCH_LIMIT = 20;
|
|
19
23
|
|
|
20
|
-
interface FindPanelAction {
|
|
21
|
-
type: "install" | "close";
|
|
22
|
-
result?: PackageSummary;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
24
|
export type InstallOutcome = "installed" | "cancelled" | "failed";
|
|
26
25
|
|
|
27
26
|
/** Approve-then-install-then-reload, mirroring applyPackageChoice's own
|
|
@@ -53,141 +52,134 @@ export async function applyInstall(result: PackageSummary, natives: Natives, ctx
|
|
|
53
52
|
}
|
|
54
53
|
}
|
|
55
54
|
|
|
56
|
-
|
|
57
|
-
return ctx.ui.custom<boolean>(
|
|
58
|
-
(_tui, theme, _kb, done) => {
|
|
59
|
-
const items: MenuItem[] = [
|
|
60
|
-
{ label: `Install ${result.name}@${result.version}`, action: () => done(true) },
|
|
61
|
-
{ label: "Cancel", action: () => done(false) },
|
|
62
|
-
];
|
|
63
|
-
return new Menu({ items, theme: menuTheme(theme), onClose: () => done(false) });
|
|
64
|
-
},
|
|
65
|
-
{ overlay: true, overlayOptions: { width: 40, anchor: "center" } },
|
|
66
|
-
);
|
|
67
|
-
}
|
|
55
|
+
interface Theme { fg(color: string, s: string): string; bold(s: string): string; }
|
|
68
56
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
57
|
+
/** /packed's Find tab -- a real Component. Its query box always captures
|
|
58
|
+
* free text (isCapturingInput() is unconditionally true), unlike
|
|
59
|
+
* Packages/Config's toggleable search: there's no "browse mode" here to
|
|
60
|
+
* fall back to, searching is the whole point of this tab. */
|
|
61
|
+
export class FindTab implements Component {
|
|
62
|
+
private readonly queryInput = new Input();
|
|
63
|
+
private results: PackageSummary[] = [];
|
|
64
|
+
private lastSearchedQuery: string | undefined;
|
|
65
|
+
private selectedIndex = 0;
|
|
66
|
+
private searching = false;
|
|
67
|
+
private error: string | undefined;
|
|
68
|
+
private busy = false;
|
|
69
|
+
private readonly maxVisible = 15;
|
|
70
|
+
|
|
71
|
+
constructor(
|
|
72
|
+
private readonly natives: Natives,
|
|
73
|
+
private readonly host: TabHost,
|
|
74
|
+
private readonly theme: Theme,
|
|
75
|
+
) {}
|
|
76
|
+
|
|
77
|
+
/** Find's query box is always in text-edit mode (no separate toggle like
|
|
78
|
+
* Packages/Config's `/`) -- Left/Right must stay cursor movement, never
|
|
79
|
+
* the host's own tab-cycling. Escape, though, is only captured while an
|
|
80
|
+
* install is actually in flight (a genuine "don't let go" moment); no one
|
|
81
|
+
* types a literal Escape byte into a search query on purpose, so it's
|
|
82
|
+
* otherwise the host's own "back to Packages" to handle. */
|
|
83
|
+
capturesHorizontalArrows(): boolean {
|
|
84
|
+
return true;
|
|
73
85
|
}
|
|
74
86
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
if (action.type === "close") return;
|
|
78
|
-
if (!action.result) continue;
|
|
79
|
-
const install = await showInstallMenu(ctx, action.result);
|
|
80
|
-
if (!install) continue;
|
|
81
|
-
const outcome = await applyInstall(action.result, natives, ctx);
|
|
82
|
-
if (outcome === "installed") return; // ctx.reload() already replaced the session
|
|
87
|
+
capturesEscape(): boolean {
|
|
88
|
+
return this.busy;
|
|
83
89
|
}
|
|
84
|
-
}
|
|
85
90
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
let error: string | undefined;
|
|
94
|
-
const maxVisible = 15;
|
|
91
|
+
/** Unlike capturesEscape, this is ALWAYS true, busy or not: every
|
|
92
|
+
* printable character (a letter that would otherwise be a global
|
|
93
|
+
* tab-jump mnemonic included) is query text here, always -- there's no
|
|
94
|
+
* separate "browse mode" to fall back to the way Packages/Config have. */
|
|
95
|
+
capturesMnemonics(): boolean {
|
|
96
|
+
return true;
|
|
97
|
+
}
|
|
95
98
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
error = e instanceof Error ? e.message : String(e);
|
|
109
|
-
results = [];
|
|
110
|
-
} finally {
|
|
111
|
-
searching = false;
|
|
112
|
-
tui.requestRender();
|
|
113
|
-
}
|
|
99
|
+
invalidate(): void {}
|
|
100
|
+
|
|
101
|
+
render(width: number): string[] {
|
|
102
|
+
const { theme } = this;
|
|
103
|
+
const hint = rawKeyHint("enter", "search/install");
|
|
104
|
+
const status = this.searching ? "searching…" : this.error ? theme.fg("error", this.error) : `${this.results.length} result(s)`;
|
|
105
|
+
const spacing = Math.max(1, width - visibleWidth(hint) - visibleWidth(status));
|
|
106
|
+
const line1 = truncateToWidth(`${hint}${" ".repeat(spacing)}`, width, "") + status;
|
|
107
|
+
const lines = [line1, ...this.queryInput.render(width), ""];
|
|
108
|
+
if (this.results.length === 0) {
|
|
109
|
+
lines.push(theme.fg("muted", this.searching ? " …" : " Type a query and press enter to search npm"));
|
|
110
|
+
return lines;
|
|
114
111
|
}
|
|
112
|
+
const start = Math.max(0, Math.min(this.selectedIndex - Math.floor(this.maxVisible / 2), this.results.length - this.maxVisible));
|
|
113
|
+
const end = Math.min(start + this.maxVisible, this.results.length);
|
|
114
|
+
for (let i = start; i < end; i++) {
|
|
115
|
+
const result = this.results[i]!;
|
|
116
|
+
const selected = i === this.selectedIndex;
|
|
117
|
+
const cursor = selected ? theme.fg("accent", "❯") : " ";
|
|
118
|
+
const name = selected ? theme.bold(result.name) : result.name;
|
|
119
|
+
const ver = theme.fg("dim", `@${result.version}`);
|
|
120
|
+
const description = result.description ? theme.fg("muted", ` — ${result.description}`) : "";
|
|
121
|
+
lines.push(truncateToWidth(`${cursor} ${name}${ver}${description}`, width, ""));
|
|
122
|
+
}
|
|
123
|
+
return lines;
|
|
124
|
+
}
|
|
115
125
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
126
|
+
handleInput(data: string): void {
|
|
127
|
+
if (this.busy) return;
|
|
128
|
+
switch (data) {
|
|
129
|
+
case "\x1b[A":
|
|
130
|
+
if (this.results.length > 0) this.selectedIndex = (this.selectedIndex - 1 + this.results.length) % this.results.length;
|
|
131
|
+
break;
|
|
132
|
+
case "\x1b[B":
|
|
133
|
+
if (this.results.length > 0) this.selectedIndex = (this.selectedIndex + 1) % this.results.length;
|
|
134
|
+
break;
|
|
135
|
+
case "\r":
|
|
136
|
+
if (shouldSearch(this.queryInput.getValue(), this.lastSearchedQuery, this.results.length > 0)) void this.runSearch();
|
|
137
|
+
else void this.installSelected();
|
|
138
|
+
return;
|
|
139
|
+
default:
|
|
140
|
+
this.queryInput.handleInput(data);
|
|
141
|
+
break;
|
|
142
|
+
}
|
|
143
|
+
this.host.requestRender();
|
|
144
|
+
}
|
|
128
145
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
return lines;
|
|
149
|
-
},
|
|
150
|
-
};
|
|
146
|
+
private async runSearch(): Promise<void> {
|
|
147
|
+
const query = this.queryInput.getValue().trim();
|
|
148
|
+
if (!query) return;
|
|
149
|
+
this.searching = true;
|
|
150
|
+
this.error = undefined;
|
|
151
|
+
this.host.requestRender();
|
|
152
|
+
try {
|
|
153
|
+
const response = await this.natives.search(query, SEARCH_LIMIT);
|
|
154
|
+
this.results = response.results;
|
|
155
|
+
this.lastSearchedQuery = this.queryInput.getValue();
|
|
156
|
+
this.selectedIndex = 0;
|
|
157
|
+
} catch (e) {
|
|
158
|
+
this.error = e instanceof Error ? e.message : String(e);
|
|
159
|
+
this.results = [];
|
|
160
|
+
} finally {
|
|
161
|
+
this.searching = false;
|
|
162
|
+
this.host.requestRender();
|
|
163
|
+
}
|
|
164
|
+
}
|
|
151
165
|
|
|
152
|
-
|
|
153
|
-
const
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
166
|
+
private async installSelected(): Promise<void> {
|
|
167
|
+
const result = this.results[this.selectedIndex];
|
|
168
|
+
if (!result) return;
|
|
169
|
+
this.busy = true;
|
|
170
|
+
this.host.requestRender();
|
|
171
|
+
try {
|
|
172
|
+
const confirmed = await this.host.inlineCtx.ui.confirm(`Install ${result.name}@${result.version}`, "Search itself never mutates -- this is the first of two confirmations before anything installs.");
|
|
173
|
+
if (!confirmed) return;
|
|
174
|
+
const outcome = await applyInstall(result, this.natives, this.host.inlineCtx);
|
|
175
|
+
if (outcome === "installed") this.host.onSessionReplaced();
|
|
176
|
+
} finally {
|
|
177
|
+
this.busy = false;
|
|
178
|
+
this.host.requestRender();
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
}
|
|
162
182
|
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
invalidate: () => container.invalidate(),
|
|
166
|
-
handleInput(data: string) {
|
|
167
|
-
switch (data) {
|
|
168
|
-
case "\x1b[A": // up
|
|
169
|
-
if (results.length > 0) selectedIndex = (selectedIndex - 1 + results.length) % results.length;
|
|
170
|
-
break;
|
|
171
|
-
case "\x1b[B": // down
|
|
172
|
-
if (results.length > 0) selectedIndex = (selectedIndex + 1) % results.length;
|
|
173
|
-
break;
|
|
174
|
-
case "\r":
|
|
175
|
-
if (shouldSearch(queryInput.getValue(), lastSearchedQuery, results.length > 0)) {
|
|
176
|
-
void runSearch();
|
|
177
|
-
} else {
|
|
178
|
-
const result = results[selectedIndex];
|
|
179
|
-
if (result) done({ type: "install", result });
|
|
180
|
-
}
|
|
181
|
-
return;
|
|
182
|
-
case "\x1b":
|
|
183
|
-
done({ type: "close" });
|
|
184
|
-
return;
|
|
185
|
-
default:
|
|
186
|
-
queryInput.handleInput(data);
|
|
187
|
-
break;
|
|
188
|
-
}
|
|
189
|
-
tui.requestRender();
|
|
190
|
-
},
|
|
191
|
-
};
|
|
192
|
-
});
|
|
183
|
+
export function createFindTab(natives: Natives, host: TabHost, theme: Theme): FindTab {
|
|
184
|
+
return new FindTab(natives, host, theme);
|
|
193
185
|
}
|
package/extension/src/index.ts
CHANGED
|
@@ -28,7 +28,7 @@ export default async function (pi: ExtensionAPI) {
|
|
|
28
28
|
description: "Browse and manage installed Pi packages -- u/U update, x remove, d disable, c config, f find, s settings -- or run setup plan/apply or config directly",
|
|
29
29
|
handler: async (args, ctx) => {
|
|
30
30
|
if (await handleSetupCommand(args, ctx, natives)) return;
|
|
31
|
-
if (await handleResourceConfigCommand(args, ctx, natives)) return;
|
|
31
|
+
if (await handleResourceConfigCommand(args, ctx, natives, showPackedPanel)) return;
|
|
32
32
|
await showPackedPanel(ctx, natives);
|
|
33
33
|
},
|
|
34
34
|
});
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* keymap.ts — the /packed panel's own real keybindings, modeled as a
|
|
3
|
+
* Malevich MnemonicContext tree so findMnemonicConflicts can verify no
|
|
4
|
+
* two actions genuinely reachable at once share a key. This is a
|
|
5
|
+
* hand-maintained mirror of tui.ts/resource-config.ts/discover.ts/
|
|
6
|
+
* security-tui.ts's actual switch statements, not derived from them --
|
|
7
|
+
* keep it in sync when a keybinding changes; keymap.test.ts's own
|
|
8
|
+
* conflict-free assertion is the thing that actually catches drift into
|
|
9
|
+
* a genuine collision, not this file's own accuracy.
|
|
10
|
+
*
|
|
11
|
+
* Tree shape mirrors the real runtime dispatch in tui.ts's
|
|
12
|
+
* renderUnifiedPanel: Packages is a direct child of "global" (Tab/Shift-
|
|
13
|
+
* Tab/Left/Right/Escape only -- Packages' own f/c/s bindings already do
|
|
14
|
+
* the same jump the generic mnemonic dispatch would, so that dispatch is
|
|
15
|
+
* skipped entirely while Packages is active, and modeled here as a
|
|
16
|
+
* SEPARATE branch("mnemonic-jump") that only Find/Config/Settings fall
|
|
17
|
+
* under -- matching the real `if (activeKey !== "packages")` guard.
|
|
18
|
+
*/
|
|
19
|
+
import type { MnemonicContext } from "malevich-tui-components";
|
|
20
|
+
|
|
21
|
+
export const PANEL_MNEMONIC_TREE: MnemonicContext = {
|
|
22
|
+
name: "global",
|
|
23
|
+
bindings: [
|
|
24
|
+
{ key: "\x1b", description: "back to Packages, or close from Packages" },
|
|
25
|
+
{ key: "\t", description: "next tab" },
|
|
26
|
+
{ key: "\x1b[Z", description: "previous tab" },
|
|
27
|
+
{ key: "\x1b[C", description: "next tab" },
|
|
28
|
+
{ key: "\x1b[D", description: "previous tab" },
|
|
29
|
+
],
|
|
30
|
+
children: [
|
|
31
|
+
{
|
|
32
|
+
name: "packages",
|
|
33
|
+
bindings: [
|
|
34
|
+
{ key: "\x1b[A", description: "move up" },
|
|
35
|
+
{ key: "\x1b[B", description: "move down" },
|
|
36
|
+
{ key: "v", description: "cycle view mode" },
|
|
37
|
+
{ key: "/", description: "start filter" },
|
|
38
|
+
{ key: "r", description: "refresh" },
|
|
39
|
+
{ key: "U", description: "update every outdated package" },
|
|
40
|
+
{ key: "u", description: "update selected package" },
|
|
41
|
+
{ key: "x", description: "remove selected package" },
|
|
42
|
+
{ key: "d", description: "disable/enable selected package's extensions" },
|
|
43
|
+
{ key: "c", description: "jump to Config, scoped to selected package" },
|
|
44
|
+
{ key: "f", description: "jump to Find" },
|
|
45
|
+
{ key: "s", description: "jump to Settings" },
|
|
46
|
+
{ key: "\r", description: "open the row action menu" },
|
|
47
|
+
],
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
name: "mnemonic-jump (Find/Config/Settings only -- Packages' own f/c/s already cover this)",
|
|
51
|
+
bindings: [
|
|
52
|
+
{ key: "p", description: "jump to Packages" },
|
|
53
|
+
{ key: "f", description: "jump to Find" },
|
|
54
|
+
{ key: "c", description: "jump to Config" },
|
|
55
|
+
{ key: "s", description: "jump to Settings" },
|
|
56
|
+
],
|
|
57
|
+
children: [
|
|
58
|
+
{
|
|
59
|
+
name: "find",
|
|
60
|
+
bindings: [
|
|
61
|
+
{ key: "\r", description: "search, or install the highlighted result" },
|
|
62
|
+
{ key: "\x1b[A", description: "move up in results" },
|
|
63
|
+
{ key: "\x1b[B", description: "move down in results" },
|
|
64
|
+
],
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
name: "config",
|
|
68
|
+
bindings: [
|
|
69
|
+
{ key: "\x1b[A", description: "move up" },
|
|
70
|
+
{ key: "\x1b[B", description: "move down" },
|
|
71
|
+
{ key: "v", description: "switch global/project scope" },
|
|
72
|
+
{ key: "/", description: "start filter" },
|
|
73
|
+
{ key: "r", description: "refresh" },
|
|
74
|
+
{ key: " ", description: "toggle selected resource" },
|
|
75
|
+
{ key: "\r", description: "toggle selected resource" },
|
|
76
|
+
],
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
name: "settings",
|
|
80
|
+
bindings: [
|
|
81
|
+
{ key: "\x1b[A", description: "move up" },
|
|
82
|
+
{ key: "\x1b[B", description: "move down" },
|
|
83
|
+
{ key: "\r", description: "change selected option" },
|
|
84
|
+
],
|
|
85
|
+
},
|
|
86
|
+
],
|
|
87
|
+
},
|
|
88
|
+
],
|
|
89
|
+
};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { Theme } from "@earendil-works/pi-coding-agent";
|
|
2
|
-
import type { DialogTheme, MenuTheme } from "malevich-tui-components";
|
|
2
|
+
import type { DialogTheme, MenuTheme, TabBarTheme } from "malevich-tui-components";
|
|
3
3
|
|
|
4
4
|
/** Maps Pi's own Theme onto Malevich's Menu -- the one place this mapping
|
|
5
5
|
* exists, shared by every ctx.ui.custom overlay menu in this extension. */
|
|
@@ -25,3 +25,18 @@ export function dialogTheme(theme: Theme): DialogTheme {
|
|
|
25
25
|
dim: (s) => theme.fg("muted", s),
|
|
26
26
|
};
|
|
27
27
|
}
|
|
28
|
+
|
|
29
|
+
/** Maps Pi's own Theme onto Malevich's TabbedContainer -- the persistent
|
|
30
|
+
* Packages/Find/Config/Settings tab bar at the top of /packed's one
|
|
31
|
+
* overlay, replacing what used to be four separately-opened screens. */
|
|
32
|
+
export function tabBarTheme(theme: Theme): TabBarTheme {
|
|
33
|
+
return {
|
|
34
|
+
tab: (s) => theme.fg("muted", s),
|
|
35
|
+
activeTab: (s) => theme.bold(theme.fg("accent", s)),
|
|
36
|
+
// Every tab's first letter is a jump mnemonic (p/f/c/s) -- a distinct
|
|
37
|
+
// warning-colored bold, so it reads as "press this letter" on both the
|
|
38
|
+
// active and inactive tabs alike, not just whichever accent color
|
|
39
|
+
// activeTab already uses for the whole label.
|
|
40
|
+
mnemonic: (s) => theme.bold(theme.fg("warning", s)),
|
|
41
|
+
};
|
|
42
|
+
}
|