@adeildo/pi-look 5.7.2 → 5.9.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 +9 -0
- package/assets/preview.png +0 -0
- package/package.json +2 -2
- package/src/data/snapshot.ts +11 -1
- package/src/index.ts +9 -4
- package/src/render/fit.ts +1 -1
- package/src/render/format.ts +5 -5
- package/src/render/glyphs.ts +6 -2
- package/src/render/paint.ts +8 -0
- package/src/render/segments.ts +31 -1
- package/src/settings.ts +1 -1
- package/src/ui/header.ts +2 -2
- package/src/ui/tool-frame.ts +1 -1
package/README.md
CHANGED
|
@@ -72,3 +72,12 @@ The look never names a colour. Every piece names a theme token, so it follows wh
|
|
|
72
72
|
The framed editor, and the idea of writing into its borders, come from [pi-open-tui](https://github.com/OldSuns/pi-open-tui), which this replaces. The segments with priorities come from [oh-my-pi](https://github.com/can1357/oh-my-pi).
|
|
73
73
|
|
|
74
74
|
Part of [pi-harness](https://github.com/felipeadeildo/pi-harness), which brings every piece in one install.
|
|
75
|
+
|
|
76
|
+
## License
|
|
77
|
+
|
|
78
|
+
MIT
|
|
79
|
+
|
|
80
|
+
<p align="center">
|
|
81
|
+
<a href="https://adeildo.dev"><img src="https://adeildo.dev/assets/images/cat.svg" width="42" height="42" alt="adeildo.dev"></a><br>
|
|
82
|
+
Brought to you by <a href="https://adeildo.dev">adeildo.dev</a>
|
|
83
|
+
</p>
|
package/assets/preview.png
CHANGED
|
Binary file
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@adeildo/pi-look",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.9.0",
|
|
4
4
|
"description": "Pi's start screen, a framed editor and the footer, redrawn around the data that matters and painted only with the theme's colours.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"pi",
|
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
"access": "public"
|
|
39
39
|
},
|
|
40
40
|
"dependencies": {
|
|
41
|
-
"@adeildo/pi-kit": "5.
|
|
41
|
+
"@adeildo/pi-kit": "5.9.0"
|
|
42
42
|
},
|
|
43
43
|
"peerDependencies": {
|
|
44
44
|
"@earendil-works/pi-ai": "*",
|
package/src/data/snapshot.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { hostname } from "node:os";
|
|
2
2
|
|
|
3
|
-
import type { AccountState, SessionTasks } from "@adeildo/pi-kit";
|
|
3
|
+
import type { AccountState, PeerMark, SessionTasks } from "@adeildo/pi-kit";
|
|
4
4
|
import { VERSION, type ExtensionContext, type SessionEntry } from "@earendil-works/pi-coding-agent";
|
|
5
5
|
|
|
6
6
|
import type { GitState } from "./git.ts";
|
|
@@ -51,6 +51,10 @@ export interface Snapshot {
|
|
|
51
51
|
statuses: ReadonlyMap<string, string>;
|
|
52
52
|
/** What the session is doing, when a tasks feature keeps it. */
|
|
53
53
|
tasks: SessionTasks | undefined;
|
|
54
|
+
/** The other sessions in this folder, when a peers feature keeps them. */
|
|
55
|
+
peers: PeerMark[];
|
|
56
|
+
/** Messages a peer sent this session that its agent has not read yet. */
|
|
57
|
+
peersUnread: number;
|
|
54
58
|
}
|
|
55
59
|
|
|
56
60
|
export function emptySnapshot(): Snapshot {
|
|
@@ -77,6 +81,8 @@ export function emptySnapshot(): Snapshot {
|
|
|
77
81
|
averages: { decode: undefined, prefill: undefined },
|
|
78
82
|
statuses: new Map(),
|
|
79
83
|
tasks: undefined,
|
|
84
|
+
peers: [],
|
|
85
|
+
peersUnread: 0,
|
|
80
86
|
};
|
|
81
87
|
}
|
|
82
88
|
|
|
@@ -89,6 +95,8 @@ export interface LiveInputs {
|
|
|
89
95
|
run: RunView | undefined;
|
|
90
96
|
statuses: ReadonlyMap<string, string>;
|
|
91
97
|
tasks?: SessionTasks | undefined;
|
|
98
|
+
peers?: PeerMark[] | undefined;
|
|
99
|
+
peersUnread?: number | undefined;
|
|
92
100
|
}
|
|
93
101
|
|
|
94
102
|
interface Derived {
|
|
@@ -139,6 +147,8 @@ export class SnapshotReader {
|
|
|
139
147
|
.filter(([, text]) => text !== ""),
|
|
140
148
|
),
|
|
141
149
|
tasks: live.tasks,
|
|
150
|
+
peers: live.peers ?? [],
|
|
151
|
+
peersUnread: live.peersUnread ?? 0,
|
|
142
152
|
};
|
|
143
153
|
}
|
|
144
154
|
|
package/src/index.ts
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import {
|
|
2
2
|
accountStateOf,
|
|
3
3
|
createApp,
|
|
4
|
+
currentPeers,
|
|
4
5
|
currentTasks,
|
|
5
6
|
defineFeature,
|
|
6
7
|
drawSkillRefs,
|
|
7
8
|
type FeatureScope,
|
|
8
9
|
SKILL_REF,
|
|
9
|
-
type SessionTasks,
|
|
10
10
|
skillNames,
|
|
11
11
|
} from "@adeildo/pi-kit";
|
|
12
12
|
import type {
|
|
@@ -17,7 +17,7 @@ import type {
|
|
|
17
17
|
import type { TUI } from "@earendil-works/pi-tui";
|
|
18
18
|
|
|
19
19
|
import { type GitState, readGit } from "./data/git.ts";
|
|
20
|
-
import { emptySnapshot, type Snapshot, SnapshotReader } from "./data/snapshot.ts";
|
|
20
|
+
import { emptySnapshot, type LiveInputs, type Snapshot, SnapshotReader } from "./data/snapshot.ts";
|
|
21
21
|
import { Telemetry } from "./data/telemetry.ts";
|
|
22
22
|
import type { DeltaKind } from "./data/telemetry.ts";
|
|
23
23
|
import { REQUEST_ENTRY, type RequestRecord } from "./data/totals.ts";
|
|
@@ -323,8 +323,13 @@ function createScreen(
|
|
|
323
323
|
};
|
|
324
324
|
}
|
|
325
325
|
|
|
326
|
-
function tasksOf(scope: FeatureScope):
|
|
327
|
-
|
|
326
|
+
function tasksOf(scope: FeatureScope): Pick<LiveInputs, "tasks" | "peers" | "peersUnread"> {
|
|
327
|
+
const peers = currentPeers(scope.events);
|
|
328
|
+
return {
|
|
329
|
+
tasks: currentTasks(scope.events)?.state,
|
|
330
|
+
peers: peers?.peers,
|
|
331
|
+
peersUnread: peers?.unread,
|
|
332
|
+
};
|
|
328
333
|
}
|
|
329
334
|
|
|
330
335
|
function installEditor(
|
package/src/render/fit.ts
CHANGED
|
@@ -55,7 +55,7 @@ export function fitRegions(
|
|
|
55
55
|
return joined.map((text) =>
|
|
56
56
|
text === ""
|
|
57
57
|
? ""
|
|
58
|
-
: truncateToWidth(text, Math.max(0, width - overhead), options.ellipsis ?? "
|
|
58
|
+
: truncateToWidth(text, Math.max(0, width - overhead), options.ellipsis ?? "..."),
|
|
59
59
|
);
|
|
60
60
|
}
|
|
61
61
|
|
package/src/render/format.ts
CHANGED
|
@@ -72,11 +72,11 @@ export function shortenPath(cwd: string, home: string | undefined, maxWidth: num
|
|
|
72
72
|
let kept = "";
|
|
73
73
|
for (let index = parts.length - 1; index > 0; index--) {
|
|
74
74
|
const candidate = `${sep}${parts[index] ?? ""}${kept}`;
|
|
75
|
-
if (visibleWidth(
|
|
75
|
+
if (visibleWidth(`...${candidate}`) > maxWidth) break;
|
|
76
76
|
kept = candidate;
|
|
77
77
|
}
|
|
78
78
|
if (kept === "") return truncateTail(parts.at(-1) ?? full, maxWidth);
|
|
79
|
-
return
|
|
79
|
+
return `...${kept}`;
|
|
80
80
|
}
|
|
81
81
|
|
|
82
82
|
export function basename(cwd: string): string {
|
|
@@ -92,7 +92,7 @@ export function truncateTail(text: string, maxWidth: number): string {
|
|
|
92
92
|
if (maxWidth <= 0) return "";
|
|
93
93
|
if (visibleWidth(text) <= maxWidth) return text;
|
|
94
94
|
const chars = [...text];
|
|
95
|
-
let width =
|
|
95
|
+
let width = 3;
|
|
96
96
|
let start = chars.length;
|
|
97
97
|
while (start > 0) {
|
|
98
98
|
const next = visibleWidth(chars[start - 1] ?? "");
|
|
@@ -100,10 +100,10 @@ export function truncateTail(text: string, maxWidth: number): string {
|
|
|
100
100
|
width += next;
|
|
101
101
|
start--;
|
|
102
102
|
}
|
|
103
|
-
return
|
|
103
|
+
return `...${chars.slice(start).join("")}`;
|
|
104
104
|
}
|
|
105
105
|
|
|
106
|
-
export function fitWidth(text: string, width: number, ellipsis = "
|
|
106
|
+
export function fitWidth(text: string, width: number, ellipsis = "..."): string {
|
|
107
107
|
return truncateToWidth(text, Math.max(0, width), ellipsis);
|
|
108
108
|
}
|
|
109
109
|
|
package/src/render/glyphs.ts
CHANGED
|
@@ -8,6 +8,7 @@ export interface Glyphs {
|
|
|
8
8
|
host: string;
|
|
9
9
|
session: string;
|
|
10
10
|
tasks: string;
|
|
11
|
+
peers: string;
|
|
11
12
|
waiting: string;
|
|
12
13
|
queued: string;
|
|
13
14
|
version: string;
|
|
@@ -57,6 +58,7 @@ export const NERD: Glyphs = {
|
|
|
57
58
|
host: "\u{f108}", // nf-fa-desktop
|
|
58
59
|
session: "\u{f02b}", // nf-fa-tag
|
|
59
60
|
tasks: "\u{f04fe}", // nf-md-target
|
|
61
|
+
peers: "\u{f0c0}", // nf-fa-users
|
|
60
62
|
waiting: "\u{f024}", // nf-fa-flag
|
|
61
63
|
queued: "\u{f051f}", // nf-md-timer_sand
|
|
62
64
|
version: "π",
|
|
@@ -76,7 +78,7 @@ export const NERD: Glyphs = {
|
|
|
76
78
|
fill: "█",
|
|
77
79
|
empty: "░",
|
|
78
80
|
levels: ["▂", "▃", "▄", "▅", "▆", "▇"],
|
|
79
|
-
ellipsis: "
|
|
81
|
+
ellipsis: "...",
|
|
80
82
|
};
|
|
81
83
|
|
|
82
84
|
export const UNICODE: Glyphs = {
|
|
@@ -87,6 +89,7 @@ export const UNICODE: Glyphs = {
|
|
|
87
89
|
host: "⌂",
|
|
88
90
|
session: "#",
|
|
89
91
|
tasks: "▸",
|
|
92
|
+
peers: "◆",
|
|
90
93
|
waiting: "⚑",
|
|
91
94
|
queued: "⧗",
|
|
92
95
|
version: "π",
|
|
@@ -106,7 +109,7 @@ export const UNICODE: Glyphs = {
|
|
|
106
109
|
fill: "█",
|
|
107
110
|
empty: "░",
|
|
108
111
|
levels: ["▂", "▃", "▄", "▅", "▆", "▇"],
|
|
109
|
-
ellipsis: "
|
|
112
|
+
ellipsis: "...",
|
|
110
113
|
};
|
|
111
114
|
|
|
112
115
|
export const ASCII: Glyphs = {
|
|
@@ -116,6 +119,7 @@ export const ASCII: Glyphs = {
|
|
|
116
119
|
host: "@",
|
|
117
120
|
session: "#",
|
|
118
121
|
tasks: ">",
|
|
122
|
+
peers: "@",
|
|
119
123
|
waiting: "",
|
|
120
124
|
queued: "",
|
|
121
125
|
version: "v",
|
package/src/render/paint.ts
CHANGED
|
@@ -80,6 +80,8 @@ export interface Paint {
|
|
|
80
80
|
stress(percent: number, text: string): string;
|
|
81
81
|
money(amount: number, text: string): string;
|
|
82
82
|
frame(text: string): string;
|
|
83
|
+
/** A theme colour a package named in a contract, like a peer's own colour. */
|
|
84
|
+
raw(color: string, text: string): string;
|
|
83
85
|
}
|
|
84
86
|
|
|
85
87
|
export function themePaint(theme: Theme, frame?: (text: string) => string): Paint {
|
|
@@ -94,6 +96,11 @@ export function themePaint(theme: Theme, frame?: (text: string) => string): Pain
|
|
|
94
96
|
stress: (percent, text) => theme.fg(stressToken(percent), text),
|
|
95
97
|
money: (amount, text) => theme.fg(moneyToken(amount), text),
|
|
96
98
|
frame: frame ?? ((text) => theme.fg("border", text)),
|
|
99
|
+
raw: (color, text) =>
|
|
100
|
+
theme.fg(
|
|
101
|
+
theme.colors[color as ThemeColor] === undefined ? "muted" : (color as ThemeColor),
|
|
102
|
+
text,
|
|
103
|
+
),
|
|
97
104
|
};
|
|
98
105
|
}
|
|
99
106
|
|
|
@@ -120,6 +127,7 @@ export const PLAIN: Paint = {
|
|
|
120
127
|
stress: (_percent, text) => text,
|
|
121
128
|
money: (_amount, text) => text,
|
|
122
129
|
frame: identity,
|
|
130
|
+
raw: (_color, text) => text,
|
|
123
131
|
};
|
|
124
132
|
|
|
125
133
|
function identity(text: string): string {
|
package/src/render/segments.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type AccountWindow, tasksWith } from "@adeildo/pi-kit";
|
|
1
|
+
import { type AccountWindow, type PeerMark, tasksWith } from "@adeildo/pi-kit";
|
|
2
2
|
import { visibleWidth } from "@earendil-works/pi-tui";
|
|
3
3
|
|
|
4
4
|
import type { Snapshot } from "../data/snapshot.ts";
|
|
@@ -28,6 +28,7 @@ export const SEGMENT_IDS = [
|
|
|
28
28
|
"host",
|
|
29
29
|
"session",
|
|
30
30
|
"tasks",
|
|
31
|
+
"peers",
|
|
31
32
|
"version",
|
|
32
33
|
"clock",
|
|
33
34
|
"provider",
|
|
@@ -67,6 +68,7 @@ export function isSegmentId(value: string): value is SegmentId {
|
|
|
67
68
|
const CLAIMED_BY_SEGMENT: Partial<Record<BuiltinSegment, string>> = {
|
|
68
69
|
model: "pi-providers:account",
|
|
69
70
|
tasks: "pi-tasks",
|
|
71
|
+
peers: "pi-peers",
|
|
70
72
|
};
|
|
71
73
|
|
|
72
74
|
export function claimedStatuses(slots: readonly (readonly SegmentId[])[]): Set<string> {
|
|
@@ -155,6 +157,29 @@ export const SEGMENTS: Record<BuiltinSegment, Segment> = {
|
|
|
155
157
|
? undefined
|
|
156
158
|
: { text: `${mark(glyphs.host, paint, "host")}${paint.role("host", snapshot.host)}` },
|
|
157
159
|
},
|
|
160
|
+
peers: {
|
|
161
|
+
priority: 25,
|
|
162
|
+
describe:
|
|
163
|
+
"the other agents in this folder, each by the name it chose and in its colour, then @ how many messages wait for an answer",
|
|
164
|
+
// The whole folder, then one name, then the bare mark, as the line runs out of room.
|
|
165
|
+
render: ({ snapshot, glyphs, paint }) => {
|
|
166
|
+
const peers = snapshot.peers;
|
|
167
|
+
const mentions =
|
|
168
|
+
snapshot.peersUnread > 0 ? paint.role("waiting", `@${snapshot.peersUnread}`) : "";
|
|
169
|
+
const first = peers[0];
|
|
170
|
+
if (first === undefined) return mentions === "" ? undefined : { text: mentions };
|
|
171
|
+
const named = (peer: PeerMark) => paint.raw(peer.color, `${glyphs.peers} ${peer.handle}`);
|
|
172
|
+
const rest = peers.length > 1 ? paint.dim(` +${peers.length - 1}`) : "";
|
|
173
|
+
const full = joined([...peers].map(named).join(" "), mentions);
|
|
174
|
+
const short = joined(named(first) + rest, mentions);
|
|
175
|
+
const least = joined(paint.raw(first.color, glyphs.peers), mentions);
|
|
176
|
+
return {
|
|
177
|
+
text: full,
|
|
178
|
+
shrink: (width) =>
|
|
179
|
+
visibleWidth(full) <= width ? full : visibleWidth(short) <= width ? short : least,
|
|
180
|
+
};
|
|
181
|
+
},
|
|
182
|
+
},
|
|
158
183
|
tasks: {
|
|
159
184
|
priority: 30,
|
|
160
185
|
describe:
|
|
@@ -516,6 +541,11 @@ function quotaWindow(paint: Paint, window: AccountWindow): string {
|
|
|
516
541
|
return `${paint.dim(window.name)} ${used}${reset}`;
|
|
517
542
|
}
|
|
518
543
|
|
|
544
|
+
/** The parts that have something to say, with the gap the footer uses between things. */
|
|
545
|
+
function joined(...parts: string[]): string {
|
|
546
|
+
return parts.filter((part) => part !== "").join(" ");
|
|
547
|
+
}
|
|
548
|
+
|
|
519
549
|
function mark(glyph: string, paint: Paint, role: Role): string {
|
|
520
550
|
return glyph === "" ? "" : `${paint.role(role, glyph)} `;
|
|
521
551
|
}
|
package/src/settings.ts
CHANGED
|
@@ -89,7 +89,7 @@ export const stripRight = slot(
|
|
|
89
89
|
"stripRight",
|
|
90
90
|
"Answer strip, on the right",
|
|
91
91
|
"The other side of that line. When it does not fit beside the strip, it gets a line of its own under it.",
|
|
92
|
-
["tasks"],
|
|
92
|
+
["tasks", "peers"],
|
|
93
93
|
);
|
|
94
94
|
export const topLeft = slot("frame.topLeft", "Frame, top left", "After the working spinner.", [
|
|
95
95
|
"branch",
|
package/src/ui/header.ts
CHANGED
|
@@ -110,7 +110,7 @@ export class HeaderComponent implements Component {
|
|
|
110
110
|
const hints = HINTS()
|
|
111
111
|
.map((hint) => `${paint.role("brand", hint.key)} ${paint.dim(hint.does)}`)
|
|
112
112
|
.join(separator);
|
|
113
|
-
return [first, hints, ""].map((line) => truncateToWidth(` ${line}`, width, "
|
|
113
|
+
return [first, hints, ""].map((line) => truncateToWidth(` ${line}`, width, "..."));
|
|
114
114
|
}
|
|
115
115
|
|
|
116
116
|
#title(paint: Paint): string {
|
|
@@ -192,7 +192,7 @@ export function halfBlocks(pixels: readonly string[]): string[] {
|
|
|
192
192
|
}
|
|
193
193
|
|
|
194
194
|
function pad(text: string, width: number): string {
|
|
195
|
-
const clipped = truncateToWidth(text, Math.max(0, width), "
|
|
195
|
+
const clipped = truncateToWidth(text, Math.max(0, width), "...");
|
|
196
196
|
return `${clipped}${" ".repeat(Math.max(0, width - visibleWidth(clipped)))}`;
|
|
197
197
|
}
|
|
198
198
|
|
package/src/ui/tool-frame.ts
CHANGED
|
@@ -358,7 +358,7 @@ function inside(lines: readonly string[], width: number, theme: Theme): string[]
|
|
|
358
358
|
const inner = room(width);
|
|
359
359
|
return trim(lines).map((line) => {
|
|
360
360
|
// Measuring is cheap, and cutting a line that already fits is not.
|
|
361
|
-
const text = visibleWidth(line) > inner ? truncateToWidth(line, inner, "
|
|
361
|
+
const text = visibleWidth(line) > inner ? truncateToWidth(line, inner, "...", true) : line;
|
|
362
362
|
const pad = " ".repeat(Math.max(0, inner - visibleWidth(text)));
|
|
363
363
|
return `${paint("\u2502")} ${text}${pad} ${paint("\u2502")}`;
|
|
364
364
|
});
|