@astrofoundry/pi-astro 0.14.4 → 0.14.6

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.
@@ -0,0 +1,15 @@
1
+ ▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
2
+ ▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
3
+ ▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
4
+ ▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
5
+ ▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
6
+ ▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
7
+ ▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
8
+ ▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
9
+ ▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
10
+ ▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
11
+ ▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
12
+ ▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
13
+ ▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
14
+ ▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
15
+ ▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
@@ -1,35 +1,25 @@
1
1
  import { describe, expect, it } from "vitest";
2
- import { ART_HEIGHT, ART_WIDTH, pickVariant, renderArt, VARIANTS } from "./art.ts";
2
+ import { artHeight, artWidth, renderArt } from "./art.ts";
3
3
 
4
4
  describe("art", () => {
5
- it("exposes ART_WIDTH and ART_HEIGHT consistent with the variants", () => {
6
- expect(ART_WIDTH).toBeGreaterThan(0);
7
- expect(ART_HEIGHT).toBeGreaterThan(0);
8
- for (const v of VARIANTS) {
9
- expect(v[0].length).toBe(ART_WIDTH);
10
- expect(Math.ceil(v.length / 2)).toBe(ART_HEIGHT);
11
- }
5
+ it("loads non-zero dimensions from the bundled art.ansi", () => {
6
+ expect(artWidth()).toBeGreaterThan(0);
7
+ expect(artHeight()).toBeGreaterThan(0);
12
8
  });
13
9
 
14
- it("pickVariant is deterministic for the same seed", () => {
15
- expect(pickVariant(0)).toBe(pickVariant(0));
16
- expect(pickVariant(7)).toBe(pickVariant(7));
10
+ it("renderArt returns artHeight() lines", () => {
11
+ const lines = renderArt();
12
+ expect(lines).toHaveLength(artHeight());
17
13
  });
18
14
 
19
- it("pickVariant handles negative seeds without throwing", () => {
20
- expect(() => pickVariant(-100)).not.toThrow();
21
- });
22
-
23
- it("renderArt produces ART_HEIGHT rows", () => {
24
- const lines = renderArt(VARIANTS[0]);
25
- expect(lines).toHaveLength(ART_HEIGHT);
26
- });
27
-
28
- it("renderArt embeds 24-bit ANSI fg+bg colours and the upper-half block glyph", () => {
29
- const lines = renderArt(VARIANTS[0]);
30
- const joined = lines.join("\n");
15
+ it("each line embeds 24-bit ANSI fg+bg colours and the upper-half block glyph", () => {
16
+ const joined = renderArt().join("\n");
31
17
  expect(joined).toContain("[38;2;");
32
- expect(joined).toContain(";48;2;");
18
+ expect(joined).toContain("[48;2;");
33
19
  expect(joined).toContain("â–€");
34
20
  });
21
+
22
+ it("subsequent calls return the same cached array", () => {
23
+ expect(renderArt()).toBe(renderArt());
24
+ });
35
25
  });
@@ -1,119 +1,38 @@
1
- import { fgBg, PALETTE, type RGB } from "./colors.ts";
1
+ import * as fs from "node:fs";
2
+ import * as path from "node:path";
3
+ import { fileURLToPath } from "node:url";
4
+ import { visibleWidth } from "@mariozechner/pi-tui";
2
5
 
3
- type PaletteKey =
4
- | "·"
5
- | "S"
6
- | "X"
7
- | "A"
8
- | "Y"
9
- | "L"
10
- | "H"
11
- | "B"
12
- | "V"
13
- | "K"
14
- | "W"
15
- | "M"
16
- | "D"
17
- | "G"
18
- | "T"
19
- | "E"
20
- | "F"
21
- | "O"
22
- | "R";
6
+ const extensionDir = path.dirname(fileURLToPath(import.meta.url));
7
+ const artPath = path.resolve(extensionDir, "art.ansi");
23
8
 
24
- const PIXEL_COLOR: Record<PaletteKey, RGB | null> = {
25
- "·": null,
26
- S: PALETTE.suitLight,
27
- X: PALETTE.suitMid,
28
- A: PALETTE.suitShadow,
29
- Y: PALETTE.suitTrim,
30
- L: PALETTE.suitGoldHigh,
31
- H: PALETTE.suitGoldShadow,
32
- B: PALETTE.flagBlue,
33
- V: PALETTE.voidBlack,
34
- K: PALETTE.outline,
35
- W: PALETTE.visorLow,
36
- M: PALETTE.visorMid,
37
- D: PALETTE.visorTop,
38
- G: PALETTE.backpack,
39
- T: PALETTE.earthBlue,
40
- E: PALETTE.earthGreen,
41
- F: PALETTE.flameYellow,
42
- O: PALETTE.flameOrange,
43
- R: PALETTE.flameRed,
44
- };
9
+ interface ArtData {
10
+ lines: string[];
11
+ width: number;
12
+ height: number;
13
+ }
45
14
 
46
- const ASTRONAUT_PORTRAIT: readonly string[] = [
47
- "········································",
48
- "···················YY···················",
49
- "·················YYYYYY·················",
50
- "··············YYYYYYYYYYYY··············",
51
- "············YYYYYYYYYYYYYYYY············",
52
- "··········YYYYYYYYYYYYYYYYYYYY··········",
53
- "·········YYYYYYYYYYYYYYYYYYYYYY·········",
54
- "········YYYYYYYYYYYYYYYYYYYYYYYY········",
55
- "········YYYYYYKKKKKKKKKKKKYYYYYY········",
56
- "········YYYYKKKKKKKKKKKKKKKKYYYY········",
57
- "········YYYKKKKKKKKKKKKKKKKKKYYY········",
58
- "········YYYKKKKKKKKKKKKKKKKKKYYY········",
59
- "········YYYKKKKKKKKKKKKKKKKKKYYY········",
60
- "········YYYKKKKKKKKKKKKKKKKKKYYY········",
61
- "········YYYKKKKKKKKKKKKKKKKKKYYY········",
62
- "········YYYYKKKKKKKKKKKKKKKKYYYY········",
63
- "········YYYYYYKKKKKKKKKKKKYYYYYY········",
64
- "········YYYYYYYYYYYYYYYYYYYYYYYY········",
65
- "·········YYYYYYYYYYYYYYYYYYYYYY·········",
66
- "··········YYYYYYYYYYYYYYYYYYYY··········",
67
- "············YYYYYYYYYYYYYYYY············",
68
- "··············YYYYYYYYYYYY··············",
69
- "················YYYYYYYY················",
70
- "················YYYYYYYY················",
71
- "·············YYYYYYYYYYYYYY·············",
72
- "··········YYYYYYYYYYYYYYYYYYYY··········",
73
- "········YYYYYYYYYYYYYYYYYYYYYYYY········",
74
- "······YYYYYYYYYYYYYYYYYYYYYYYYYYYY······",
75
- "·····YYYYYYYYYYYYYYYYYYYYYYYYYYYYYY·····",
76
- "····YYYYYYYYYYYYYYYYYYYYYYYBSYYYYYYY····",
77
- "····YYYYYYYYYYYYYYYYYYYYYBSBBYYYYYYY····",
78
- "····YYYYYYYYYYYYYYYYYYYBBBBBBYYYYYYY····",
79
- "····YYYYYYYYYYYYYYYYYBBSBBBBBYYYYYYY····",
80
- "····YYYYYYYYYYYYYYYBBBBBBBBBBYYYYYYY····",
81
- "····YYYYYYYYYYYYYBBSBBBBBBBBBYYYYYYY····",
82
- "····YYYYYYYYYYYBBBBBBBBBBBBBBYYYYYYY····",
83
- "····YYYYYYYYYBBSBBBBBBBBBBBBBYYYYYYY····",
84
- "····YYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY····",
85
- "···YYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY···",
86
- "··YYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY··",
87
- ];
15
+ let cached: ArtData | null = null;
88
16
 
89
- export const VARIANTS = [ASTRONAUT_PORTRAIT] as const;
17
+ function load(): ArtData {
18
+ if (cached !== null) return cached;
19
+ const raw = fs.readFileSync(artPath, "utf8");
20
+ const lines = raw.split("\n");
21
+ while (lines.length > 0 && lines[lines.length - 1] === "") lines.pop();
22
+ const width = lines.length > 0 ? visibleWidth(lines[0]) : 0;
23
+ const height = lines.length;
24
+ cached = { lines, width, height };
25
+ return cached;
26
+ }
90
27
 
91
- export function pickVariant(seed: number): readonly string[] {
92
- const idx = Math.abs(seed) % VARIANTS.length;
93
- return VARIANTS[idx];
28
+ export function renderArt(): string[] {
29
+ return load().lines;
94
30
  }
95
31
 
96
- export function renderArt(grid: readonly string[]): string[] {
97
- const rows: string[] = [];
98
- for (let y = 0; y < grid.length; y += 2) {
99
- const top = grid[y];
100
- const bot = grid[y + 1] ?? "·".repeat(top.length);
101
- let line = "";
102
- for (let x = 0; x < top.length; x++) {
103
- const tk = (top[x] ?? "·") as PaletteKey;
104
- const bk = (bot[x] ?? "·") as PaletteKey;
105
- const tc = PIXEL_COLOR[tk] ?? null;
106
- const bc = PIXEL_COLOR[bk] ?? null;
107
- if (!tc && !bc) {
108
- line += " ";
109
- continue;
110
- }
111
- line += fgBg(tc ?? PALETTE.space, bc ?? PALETTE.space);
112
- }
113
- rows.push(line);
114
- }
115
- return rows;
32
+ export function artWidth(): number {
33
+ return load().width;
116
34
  }
117
35
 
118
- export const ART_WIDTH = ASTRONAUT_PORTRAIT[0].length;
119
- export const ART_HEIGHT = Math.ceil(ASTRONAUT_PORTRAIT.length / 2);
36
+ export function artHeight(): number {
37
+ return load().height;
38
+ }
@@ -1,6 +1,6 @@
1
1
  import { visibleWidth } from "@mariozechner/pi-tui";
2
2
  import { fg, PALETTE } from "./colors.ts";
3
- import { ART_HEIGHT, ART_WIDTH, pickVariant, renderArt } from "./art.ts";
3
+ import { artHeight, artWidth, renderArt } from "./art.ts";
4
4
  import { pickMessage } from "./messages.ts";
5
5
  import { renderStarfield, type Mask } from "./stars.ts";
6
6
 
@@ -14,6 +14,12 @@ export interface WelcomeStats {
14
14
 
15
15
  const TITLE = "★ ASTRO PI ★";
16
16
  const HINT = "Press any key to launch · auto-dismiss in 6s";
17
+ const FRAME_TL = "â•­";
18
+ const FRAME_TR = "â•®";
19
+ const FRAME_BL = "â•°";
20
+ const FRAME_BR = "╯";
21
+ const FRAME_HORIZONTAL = "─";
22
+ const FRAME_VERTICAL = "│";
17
23
 
18
24
  function formatTokens(n: number | undefined): string {
19
25
  if (n === undefined || n <= 0) return "—";
@@ -69,16 +75,30 @@ class CompositeMask implements Mask {
69
75
  }
70
76
  }
71
77
 
78
+ function truncate(line: string, width: number): string {
79
+ return visibleWidth(line) <= width ? line : line.slice(0, width);
80
+ }
81
+
72
82
  export function composeWelcome(width: number, seed: number, stats: WelcomeStats): string[] {
73
83
  const hasVersion = !!stats.pkgVersion;
74
- const versionRow = 1;
75
- const artStartRow = hasVersion ? 2 : 1;
76
- const totalRows = artStartRow + ART_HEIGHT + 6;
84
+ const aw = artWidth();
85
+ const ah = artHeight();
86
+ const frameWidth = aw + 2;
77
87
  const safeWidth = Math.max(40, width);
78
- const artStartCol = Math.max(0, Math.floor((safeWidth - ART_WIDTH) / 2));
88
+
89
+ const versionRow = 1;
90
+ const frameTopRow = hasVersion ? 2 : 1;
91
+ const artStartRow = frameTopRow + 1;
92
+ const frameBottomRow = artStartRow + ah;
93
+ const taglineRow = frameBottomRow + 2;
94
+ const statsRow = taglineRow + 2;
95
+ const hintRow = statsRow + 2;
96
+ const totalRows = hintRow + 1;
97
+
98
+ const frameStartCol = Math.max(0, Math.floor((safeWidth - frameWidth) / 2));
79
99
 
80
100
  const mask = new CompositeMask(totalRows, safeWidth);
81
- mask.occupyArt(artStartRow, artStartCol, ART_HEIGHT, ART_WIDTH);
101
+ mask.occupyArt(frameTopRow, frameStartCol, frameBottomRow - frameTopRow + 1, frameWidth);
82
102
 
83
103
  const titleLine = fg(PALETTE.title, TITLE);
84
104
  const titlePlaced = centerInLine(titleLine, safeWidth);
@@ -93,53 +113,43 @@ export function composeWelcome(width: number, seed: number, stats: WelcomeStats)
93
113
 
94
114
  const taglineRaw = pickMessage(seed);
95
115
  const taglineLine = fg(PALETTE.tagline, taglineRaw);
96
- const taglineRow = artStartRow + ART_HEIGHT + 1;
97
116
  const taglinePlaced = centerInLine(taglineLine, safeWidth);
98
117
  mask.occupy(taglineRow, taglinePlaced.col, visibleWidth(taglineLine));
99
118
 
100
119
  const statsRaw = statsLine(stats);
101
120
  const statsColored = fg(PALETTE.stats, statsRaw);
102
- const statsRow = taglineRow + 2;
103
121
  const statsPlaced = centerInLine(statsColored, safeWidth);
104
122
  mask.occupy(statsRow, statsPlaced.col, visibleWidth(statsColored));
105
123
 
106
124
  const hintColored = fg(PALETTE.hint, HINT);
107
- const hintRow = statsRow + 2;
108
125
  const hintPlaced = centerInLine(hintColored, safeWidth);
109
126
  mask.occupy(hintRow, hintPlaced.col, visibleWidth(hintColored));
110
127
 
111
128
  const stars = renderStarfield(safeWidth, totalRows, seed, mask);
112
- const artLines = renderArt(pickVariant(seed));
129
+ const artLines = renderArt();
130
+
131
+ const frameTop = fg(PALETTE.title, FRAME_TL + FRAME_HORIZONTAL.repeat(aw) + FRAME_TR);
132
+ const frameBot = fg(PALETTE.title, FRAME_BL + FRAME_HORIZONTAL.repeat(aw) + FRAME_BR);
133
+ const frameLeft = fg(PALETTE.title, FRAME_VERTICAL);
134
+ const frameRight = fg(PALETTE.title, FRAME_VERTICAL);
135
+ const padLeft = " ".repeat(frameStartCol);
113
136
 
114
137
  const composed: string[] = [];
115
138
  for (let r = 0; r < totalRows; r++) {
116
139
  let line = stars[r] ?? " ".repeat(safeWidth);
117
140
 
118
- if (r === 0) line = overlay(line, titlePlaced.line, safeWidth);
119
- if (versionPlaced && r === versionRow) line = overlay(line, versionPlaced.line, safeWidth);
120
- if (r === taglineRow) line = overlay(line, taglinePlaced.line, safeWidth);
121
- if (r === statsRow) line = overlay(line, statsPlaced.line, safeWidth);
122
- if (r === hintRow) line = overlay(line, hintPlaced.line, safeWidth);
123
-
124
- if (r >= artStartRow && r < artStartRow + ART_HEIGHT) {
141
+ if (r === 0) line = truncate(titlePlaced.line, safeWidth);
142
+ else if (versionPlaced && r === versionRow) line = truncate(versionPlaced.line, safeWidth);
143
+ else if (r === frameTopRow) line = truncate(padLeft + frameTop, safeWidth);
144
+ else if (r === frameBottomRow) line = truncate(padLeft + frameBot, safeWidth);
145
+ else if (r > frameTopRow && r < frameBottomRow) {
125
146
  const artLine = artLines[r - artStartRow];
126
- const artLineWidth = visibleWidth(artLine);
127
- line = overlayAt(line, " ".repeat(artStartCol) + artLine, safeWidth, artStartCol, artLineWidth);
128
- }
147
+ line = truncate(padLeft + frameLeft + artLine + frameRight, safeWidth);
148
+ } else if (r === taglineRow) line = truncate(taglinePlaced.line, safeWidth);
149
+ else if (r === statsRow) line = truncate(statsPlaced.line, safeWidth);
150
+ else if (r === hintRow) line = truncate(hintPlaced.line, safeWidth);
129
151
 
130
152
  composed.push(line);
131
153
  }
132
154
  return composed;
133
155
  }
134
-
135
- function overlay(starLine: string, payload: string, width: number): string {
136
- return truncate(payload, width);
137
- }
138
-
139
- function overlayAt(_starLine: string, payload: string, width: number, _startCol: number, _payloadWidth: number): string {
140
- return truncate(payload, width);
141
- }
142
-
143
- function truncate(line: string, width: number): string {
144
- return visibleWidth(line) <= width ? line : line.slice(0, width);
145
- }
@@ -1,12 +1,12 @@
1
1
  import type { ExtensionAPI, ExtensionContext } from "@mariozechner/pi-coding-agent";
2
2
  import { composeWelcome, type WelcomeStats } from "./compose.ts";
3
- import { ART_HEIGHT, ART_WIDTH } from "./art.ts";
3
+ import { artHeight, artWidth } from "./art.ts";
4
4
  import { readPackageVersion } from "./version.ts";
5
5
 
6
6
  const DISMISS_AFTER_MS = 6_000;
7
7
  const MOUNT_DELAY_MS = 100;
8
- const OVERLAY_WIDTH = Math.max(ART_WIDTH + 30, 70);
9
- const OVERLAY_HEIGHT = ART_HEIGHT + 8;
8
+ const OVERLAY_HORIZONTAL_PADDING = 30;
9
+ const OVERLAY_VERTICAL_PADDING = 8;
10
10
 
11
11
  function readCavemanLevel(ctx: ExtensionContext): string | undefined {
12
12
  const entries = ctx.sessionManager.getEntries();
@@ -103,8 +103,8 @@ export default function astroWelcomeExtension(pi: ExtensionAPI): void {
103
103
  overlay: true,
104
104
  overlayOptions: {
105
105
  anchor: "center",
106
- width: OVERLAY_WIDTH,
107
- maxHeight: OVERLAY_HEIGHT,
106
+ width: Math.max(artWidth() + OVERLAY_HORIZONTAL_PADDING, 70),
107
+ maxHeight: artHeight() + OVERLAY_VERTICAL_PADDING,
108
108
  },
109
109
  },
110
110
  )
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@astrofoundry/pi-astro",
3
- "version": "0.14.4",
3
+ "version": "0.14.6",
4
4
  "description": "Personal pi customizations (extensions, skills, prompts, themes) for the pi coding agent.",
5
5
  "keywords": [
6
6
  "pi-package"