@adeildo/pi-look 5.8.0 → 5.9.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 CHANGED
@@ -72,3 +72,8 @@ 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
+ <p align="center">
77
+ <a href="https://adeildo.dev"><img src="https://adeildo.dev/assets/images/cat.svg" width="42" height="42" alt="adeildo.dev"></a><br>
78
+ Brought to you by <a href="https://adeildo.dev">adeildo.dev</a>
79
+ </p>
Binary file
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adeildo/pi-look",
3
- "version": "5.8.0",
3
+ "version": "5.9.1",
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.8.0"
41
+ "@adeildo/pi-kit": "5.9.1"
42
42
  },
43
43
  "peerDependencies": {
44
44
  "@earendil-works/pi-ai": "*",
@@ -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): { tasks?: SessionTasks } {
327
- return { tasks: currentTasks(scope.events)?.state };
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(
@@ -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: "π",
@@ -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: "π",
@@ -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",
@@ -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 {
@@ -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",