@luxass/pi-personal 0.1.0 → 0.1.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/README.md +4 -2
- package/package.json +1 -1
- package/src/custom-header.ts +126 -54
package/README.md
CHANGED
|
@@ -10,6 +10,8 @@ pi install npm:@luxass/pi-personal
|
|
|
10
10
|
|
|
11
11
|
## What it does
|
|
12
12
|
|
|
13
|
-
The
|
|
13
|
+
The custom header pairs an ASCII terminal-robot mascot with the project, model, and current thinking level. A rounded frame, vertical padding, and a separate shortcut row give it room on wide terminals. Narrow panes use a compact layout and drop shortcut hints before truncating them. The thinking shortcut follows your Pi keybindings.
|
|
14
14
|
|
|
15
|
-
|
|
15
|
+
Run `/resources` to inspect detected context files, skill commands, and extensions. This list is inferred from files and registered commands/tools, not a complete inventory of loaded resources.
|
|
16
|
+
|
|
17
|
+
The package also includes Crow provider support, compaction continuation, Git and package-manager interceptors, and a worker configuration guard. These defaults suit one workflow and are meant to be edited in place rather than configured through settings.
|
package/package.json
CHANGED
package/src/custom-header.ts
CHANGED
|
@@ -4,11 +4,26 @@ import { basename, dirname, join, relative, resolve } from "node:path";
|
|
|
4
4
|
import { fileURLToPath } from "node:url";
|
|
5
5
|
|
|
6
6
|
import type { ExtensionAPI, ExtensionContext, Theme } from "@earendil-works/pi-coding-agent";
|
|
7
|
-
import { VERSION } from "@earendil-works/pi-coding-agent";
|
|
8
|
-
import {
|
|
7
|
+
import { keyText, VERSION } from "@earendil-works/pi-coding-agent";
|
|
8
|
+
import {
|
|
9
|
+
Key,
|
|
10
|
+
matchesKey,
|
|
11
|
+
truncateToWidth,
|
|
12
|
+
visibleWidth,
|
|
13
|
+
wrapTextWithAnsi,
|
|
14
|
+
} from "@earendil-works/pi-tui";
|
|
9
15
|
|
|
10
|
-
const HEADER_MAX_WIDTH =
|
|
11
|
-
const
|
|
16
|
+
const HEADER_MAX_WIDTH = 88;
|
|
17
|
+
const LOGO_MIN_WIDTH = 64;
|
|
18
|
+
const PI_MASCOT = [
|
|
19
|
+
" o ",
|
|
20
|
+
" | ",
|
|
21
|
+
" .---+---.",
|
|
22
|
+
" | o o |",
|
|
23
|
+
" | \\_/ |",
|
|
24
|
+
" '-------'",
|
|
25
|
+
" / \\ ",
|
|
26
|
+
];
|
|
12
27
|
|
|
13
28
|
type ResourceSnapshot = {
|
|
14
29
|
context: string[];
|
|
@@ -21,34 +36,6 @@ function fit(text: string, width: number): string {
|
|
|
21
36
|
return clipped + " ".repeat(Math.max(0, width - visibleWidth(clipped)));
|
|
22
37
|
}
|
|
23
38
|
|
|
24
|
-
function wrap(text: string, width: number): string[] {
|
|
25
|
-
if (width < 1) return [""];
|
|
26
|
-
|
|
27
|
-
const lines: string[] = [];
|
|
28
|
-
let line = "";
|
|
29
|
-
for (const word of text.split(/\s+/).filter(Boolean)) {
|
|
30
|
-
if (word.length > width) {
|
|
31
|
-
if (line) lines.push(line);
|
|
32
|
-
line = "";
|
|
33
|
-
for (let index = 0; index < word.length; index += width) {
|
|
34
|
-
lines.push(word.slice(index, index + width));
|
|
35
|
-
}
|
|
36
|
-
continue;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
const next = line ? `${line} ${word}` : word;
|
|
40
|
-
if (next.length > width) {
|
|
41
|
-
lines.push(line);
|
|
42
|
-
line = word;
|
|
43
|
-
} else {
|
|
44
|
-
line = next;
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
if (line) lines.push(line);
|
|
49
|
-
return lines.length > 0 ? lines : [""];
|
|
50
|
-
}
|
|
51
|
-
|
|
52
39
|
function resourceName(path: string): string {
|
|
53
40
|
const parts = path.replaceAll("\\", "/").split("/");
|
|
54
41
|
const file = parts.at(-1) ?? path;
|
|
@@ -104,29 +91,112 @@ async function collectResources(
|
|
|
104
91
|
};
|
|
105
92
|
}
|
|
106
93
|
|
|
107
|
-
|
|
94
|
+
interface HeaderOptions {
|
|
95
|
+
theme: Theme;
|
|
96
|
+
width: number;
|
|
97
|
+
project: string;
|
|
98
|
+
model: Pick<NonNullable<ExtensionContext["model"]>, "provider" | "id" | "reasoning"> | undefined;
|
|
99
|
+
thinking: ReturnType<ExtensionAPI["getThinkingLevel"]>;
|
|
100
|
+
thinkingKey: string;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export function renderHeader({
|
|
104
|
+
theme,
|
|
105
|
+
width,
|
|
106
|
+
project,
|
|
107
|
+
model,
|
|
108
|
+
thinking,
|
|
109
|
+
thinkingKey,
|
|
110
|
+
}: HeaderOptions): string[] {
|
|
111
|
+
if (width < 1) return [""];
|
|
112
|
+
|
|
108
113
|
const accent = (text: string) => theme.fg("accent", text);
|
|
109
|
-
const heading = (text: string) => theme.fg("mdHeading", theme.bold(text));
|
|
110
114
|
const muted = (text: string) => theme.fg("muted", text);
|
|
111
115
|
const dim = (text: string) => theme.fg("dim", text);
|
|
112
|
-
const
|
|
116
|
+
const expanded = width >= LOGO_MIN_WIDTH;
|
|
117
|
+
const border = (text: string) => theme.fg(expanded ? "borderMuted" : "borderAccent", text);
|
|
118
|
+
const version = dim(`v${VERSION}`);
|
|
119
|
+
const title = `${accent("π")} ${theme.bold("Pi")} ${version}`;
|
|
120
|
+
const indent = width >= 44 ? " " : "";
|
|
121
|
+
const panelWidth = Math.min(expanded ? HEADER_MAX_WIDTH : 72, width - indent.length);
|
|
122
|
+
const boxed = width >= 24;
|
|
123
|
+
const contentWidth = boxed ? panelWidth - 4 : width;
|
|
124
|
+
const separator = dim(" · ");
|
|
125
|
+
const workspace = theme.bold(theme.fg("text", project));
|
|
126
|
+
const modelLabel = model
|
|
127
|
+
? `${width >= 44 ? dim(`${model.provider}/`) : ""}${theme.fg("text", model.id)}`
|
|
128
|
+
: muted("model pending");
|
|
129
|
+
const thinkingLabel = model?.reasoning ? `${muted("thinking ")}${accent(thinking)}` : "";
|
|
130
|
+
const metadata = [modelLabel, thinkingLabel].filter(Boolean).join(separator);
|
|
131
|
+
const line = (text = "") => `${indent}${border("│")} ${fit(text, contentWidth)} ${border("│")}`;
|
|
113
132
|
|
|
114
|
-
|
|
115
|
-
|
|
133
|
+
const hints = [
|
|
134
|
+
`${accent("/")} ${muted("commands")}`,
|
|
135
|
+
`${accent("!")} ${muted("shell")}`,
|
|
136
|
+
...(model?.reasoning && thinkingKey
|
|
137
|
+
? [`${accent(thinkingKey === "shift+tab" ? "⇧Tab" : thinkingKey)} ${muted("thinking")}`]
|
|
138
|
+
: []),
|
|
139
|
+
accent("/resources"),
|
|
140
|
+
];
|
|
141
|
+
// Drop complete hints, not half a shortcut. Keep /resources available longest.
|
|
142
|
+
const hintWidth = expanded ? contentWidth - 4 : contentWidth;
|
|
143
|
+
while (hints.length > 1 && visibleWidth(hints.join(" ")) > hintWidth) {
|
|
144
|
+
hints.splice(hints.length - 2, 1);
|
|
116
145
|
}
|
|
117
146
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
147
|
+
if (expanded) {
|
|
148
|
+
const logoWidth = Math.max(...PI_MASCOT.map(visibleWidth));
|
|
149
|
+
const detailsWidth = contentWidth - logoWidth - 5;
|
|
150
|
+
const name = theme.bold(theme.fg("text", "Pi"));
|
|
151
|
+
const details = [
|
|
152
|
+
`${name}${" ".repeat(Math.max(1, detailsWidth - visibleWidth(name) - visibleWidth(version)))}${version}`,
|
|
153
|
+
"",
|
|
154
|
+
workspace,
|
|
155
|
+
modelLabel,
|
|
156
|
+
thinkingLabel,
|
|
157
|
+
"",
|
|
158
|
+
];
|
|
159
|
+
|
|
160
|
+
return [
|
|
161
|
+
"",
|
|
162
|
+
`${indent}${border(`╭${"─".repeat(panelWidth - 2)}╮`)}`,
|
|
163
|
+
line(),
|
|
164
|
+
...PI_MASCOT.map((logo, index) =>
|
|
165
|
+
line(
|
|
166
|
+
` ${fit(theme.bold(accent(logo)), logoWidth)} ${fit(details[index] ?? "", detailsWidth)}`,
|
|
167
|
+
),
|
|
168
|
+
),
|
|
169
|
+
line(),
|
|
170
|
+
line(` ${hints.join(" ")}`),
|
|
171
|
+
line(),
|
|
172
|
+
`${indent}${border(`╰${"─".repeat(panelWidth - 2)}╯`)}`,
|
|
173
|
+
"",
|
|
174
|
+
];
|
|
175
|
+
}
|
|
122
176
|
|
|
177
|
+
const session = `${workspace}${separator}${metadata}`;
|
|
178
|
+
const lines: string[] = [];
|
|
179
|
+
if (visibleWidth(session) <= contentWidth) {
|
|
180
|
+
lines.push(session);
|
|
181
|
+
} else {
|
|
182
|
+
lines.push(workspace);
|
|
183
|
+
if (visibleWidth(metadata) <= contentWidth) lines.push(metadata);
|
|
184
|
+
else lines.push(modelLabel, ...(thinkingLabel ? [thinkingLabel] : []));
|
|
185
|
+
}
|
|
186
|
+
if (contentWidth >= visibleWidth("/resources")) {
|
|
187
|
+
if (boxed) lines.push("");
|
|
188
|
+
lines.push(hints.join(" "));
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
if (!boxed) return ["", ...[title, ...lines].map((line) => fit(line, width)), ""];
|
|
192
|
+
|
|
193
|
+
const label = truncateToWidth(title, panelWidth - 5, "…");
|
|
123
194
|
return [
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
` ${border(`╰${"─".repeat(panelWidth - 2)}╯`)}`,
|
|
195
|
+
"",
|
|
196
|
+
`${indent}${border("╭─ ")}${label}${border(` ${"─".repeat(panelWidth - 5 - visibleWidth(label))}╮`)}`,
|
|
197
|
+
...lines.map(line),
|
|
198
|
+
`${indent}${border(`╰${"─".repeat(panelWidth - 2)}╯`)}`,
|
|
199
|
+
"",
|
|
130
200
|
];
|
|
131
201
|
}
|
|
132
202
|
|
|
@@ -144,7 +214,7 @@ function renderResources(snapshot: ResourceSnapshot, theme: Theme, width: number
|
|
|
144
214
|
const lines = [
|
|
145
215
|
border(`╭${"─".repeat(panelWidth - 2)}╮`),
|
|
146
216
|
line(`${accent("π")} ${heading("Resources")}`),
|
|
147
|
-
line(dim("
|
|
217
|
+
line(dim("Detected context, skill commands, and extensions")),
|
|
148
218
|
line(),
|
|
149
219
|
];
|
|
150
220
|
|
|
@@ -155,7 +225,7 @@ function renderResources(snapshot: ResourceSnapshot, theme: Theme, width: number
|
|
|
155
225
|
];
|
|
156
226
|
for (const [label, items, marker] of sections) {
|
|
157
227
|
lines.push(line(heading(`${label} · ${items.length}`)));
|
|
158
|
-
const entries =
|
|
228
|
+
const entries = wrapTextWithAnsi(
|
|
159
229
|
items.length > 0 ? items.join(", ") : "none detected",
|
|
160
230
|
Math.max(1, contentWidth - 4),
|
|
161
231
|
);
|
|
@@ -177,18 +247,20 @@ export default function (pi: ExtensionAPI) {
|
|
|
177
247
|
|
|
178
248
|
ctx.ui.setHeader((_tui, theme) => ({
|
|
179
249
|
render: (width: number) =>
|
|
180
|
-
renderHeader(
|
|
250
|
+
renderHeader({
|
|
181
251
|
theme,
|
|
182
252
|
width,
|
|
183
|
-
basename(ctx.cwd) || "workspace",
|
|
184
|
-
|
|
185
|
-
|
|
253
|
+
project: basename(ctx.cwd) || "workspace",
|
|
254
|
+
model: ctx.model,
|
|
255
|
+
thinking: pi.getThinkingLevel(),
|
|
256
|
+
thinkingKey: keyText("app.thinking.cycle"),
|
|
257
|
+
}),
|
|
186
258
|
invalidate() {},
|
|
187
259
|
}));
|
|
188
260
|
});
|
|
189
261
|
|
|
190
262
|
pi.registerCommand("resources", {
|
|
191
|
-
description: "Show
|
|
263
|
+
description: "Show detected context, skill commands, and extensions",
|
|
192
264
|
handler: async (_args, ctx) => {
|
|
193
265
|
if (ctx.mode !== "tui") return;
|
|
194
266
|
const resources = await collectResources(pi, ctx);
|