gentle-pi 0.1.21 → 0.1.22
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/extensions/gentle-ai.ts +141 -1
- package/package.json +1 -1
package/extensions/gentle-ai.ts
CHANGED
|
@@ -8,12 +8,18 @@ import {
|
|
|
8
8
|
import { homedir } from "node:os";
|
|
9
9
|
import { dirname, join } from "node:path";
|
|
10
10
|
import { fileURLToPath } from "node:url";
|
|
11
|
+
import { VERSION } from "@earendil-works/pi-coding-agent";
|
|
11
12
|
import type {
|
|
12
13
|
ExtensionAPI,
|
|
13
14
|
ExtensionContext,
|
|
15
|
+
Theme,
|
|
14
16
|
ToolCallEventResult,
|
|
15
17
|
} from "@earendil-works/pi-coding-agent";
|
|
16
|
-
import {
|
|
18
|
+
import {
|
|
19
|
+
matchesKey,
|
|
20
|
+
truncateToWidth,
|
|
21
|
+
visibleWidth,
|
|
22
|
+
} from "@earendil-works/pi-tui";
|
|
17
23
|
|
|
18
24
|
const PACKAGE_ROOT = dirname(dirname(fileURLToPath(import.meta.url)));
|
|
19
25
|
const ASSETS_DIR = join(PACKAGE_ROOT, "assets");
|
|
@@ -43,6 +49,139 @@ const NEUTRAL_PERSONA_PROMPT = `Persona:
|
|
|
43
49
|
- Push back when the user asks for code without enough context or understanding.
|
|
44
50
|
- Correct errors directly, explain why, and show the better path.`;
|
|
45
51
|
|
|
52
|
+
const ROSE_LOGO_LINES = [
|
|
53
|
+
" ⣠⣾⣷⣶⣦⣤⣤⣄⣠⣄⣀ ⢀⣀⣀",
|
|
54
|
+
" ⢀⣴⣿⣿⠿⣋⣭⣭⣯⣭⣍⣭⣿⣟⠛⠛⠿⠿⣿⣷⣄",
|
|
55
|
+
" ⢀⣴⣾⡟⢻⣿⡟⠁⣼⣿⠏⣵⢻⣿⣻⣿⣿⢿⡻⣿⣿⣶⡌⢿⣿⣷⣦⣤⡄",
|
|
56
|
+
" ⣤⣶⣾⣿⣿⠏ ⠈⢿⣄ ⢹⣏⠠⠟⣾⣿⣿⣿⣿⣿⠷⣏⣼⠟⢡⣿⡟⠋⢻⣿⣿⡄",
|
|
57
|
+
" ⠈⣿⣿⣿⣿⡆ ⣽⢧⡘⠈⠳⣦⣍⠛⠛⢦⣉⣴⣛⣫⣭⣴⡟⠋ ⣾⣿⣿⡿",
|
|
58
|
+
" ⢀⠹⣿⣿⣿⣷⣤⡄ ⠋ ⠙⢆ ⣠⠴⠟⠛⣛⣛⣛⠟⠋⠁⠺⡇ ⣀⣴⣿⣿⡟⠁",
|
|
59
|
+
" ⠈⣀⠈⠛⠷⠿⣿⣿⣷⣤⣀ ⢠⠋ ⠈⠉⠉ ⣠⣴⣥⠾⠛⠉⣰⣿⣷",
|
|
60
|
+
" ⠹⣯⣝⠛⠛⠷⢶⣤⣤⣀ ⢀⡠⠖⠋⠉⢉⣀⣀⣴⣾⣿⠿⠟⠃ ⠠⠦",
|
|
61
|
+
"⠁ ⠖ ⠘⠻⢿⣦⣄⡀ ⠉⠛⢦⠠⢊⠤⠴⢒⣛⣛⣩⣽⡿⠟⠁⢀⡀",
|
|
62
|
+
"⠲⠶⣦⠴⠶⠶⠶⠶⡶⠶⢶⣤⣄⡀⠨⠭⠽⠟⣓⢦⣀⠈⢇⡥⠖⠛⠋⠉⠉⠉ ⠈ ⢠⡤",
|
|
63
|
+
" ⠈⢷ ⠐⠂⢤⣽⣄ ⠰⡎⠙⠳⣄⡀ ⠈⢣⠘⢦⠋⣀⡬⠟⠛⠛⠉⢀⣀⣀⣠⡤⠄⠃",
|
|
64
|
+
" ⠈⢳⣀⡒⠉⠉⣉⠙⡲⣽⣄ ⣏⠳⡄ ⠘⡇ ⡾⠁ ⢀⡤⠖⣻⣿⡏⢡⡎ ⠰⠄",
|
|
65
|
+
" ⠛⠻⢦⣄⣉⡁⣀⣀⣈⣙⣺⣌⡇⢠⢀⡇⡾ ⣴⣿⡷⠊ ⢲⣠⠟",
|
|
66
|
+
" ⠈⠉ ⠈⠳⡄⣸⢱⠇⢀⣰⣯⣭⣥⠭⠾⠛⠃",
|
|
67
|
+
" ⡷⠡⡯⢖⠉ ⢠⠤",
|
|
68
|
+
" ⡠⢊⡴⠤⠂⠃ ⠒",
|
|
69
|
+
" ⢀⡴⢪⠔⣉⠔⠋",
|
|
70
|
+
" ⠐⠈",
|
|
71
|
+
];
|
|
72
|
+
|
|
73
|
+
const ROSE_FADE_STEPS = 12;
|
|
74
|
+
const ROSE_FADE_INTERVAL_MS = 45;
|
|
75
|
+
const ROSE_INTRO_HOLD_MS = 180;
|
|
76
|
+
|
|
77
|
+
function rgb(r: number, g: number, b: number, text: string): string {
|
|
78
|
+
return `\x1b[38;2;${r};${g};${b}m${text}\x1b[39m`;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function italic(text: string): string {
|
|
82
|
+
return `\x1b[3m${text}\x1b[23m`;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function bold(text: string): string {
|
|
86
|
+
return `\x1b[1m${text}\x1b[22m`;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function centerLine(line: string, width: number): string {
|
|
90
|
+
const clipped = truncateToWidth(line, width, "");
|
|
91
|
+
const padding = Math.max(0, width - visibleWidth(clipped));
|
|
92
|
+
return `${" ".repeat(Math.floor(padding / 2))}${clipped}`;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function pinkFade(text: string, frame: number): string {
|
|
96
|
+
const progress = Math.max(0, Math.min(1, frame / ROSE_FADE_STEPS));
|
|
97
|
+
const eased = 1 - (1 - progress) ** 3;
|
|
98
|
+
const r = Math.round(72 + (255 - 72) * eased);
|
|
99
|
+
const g = Math.round(38 + (122 - 38) * eased);
|
|
100
|
+
const b = Math.round(58 + (198 - 58) * eased);
|
|
101
|
+
return rgb(r, g, b, text);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function buildGentlemanTitle(width: number, frame: number): string[] {
|
|
105
|
+
const title = "✧ 𝓮𝓵 𝓖𝓮𝓷𝓽𝓵𝓮𝓶𝓪𝓷 ✧";
|
|
106
|
+
const version = `━━ v${VERSION} ━━`;
|
|
107
|
+
return [
|
|
108
|
+
pinkFade(bold(italic(centerLine(title, width))), frame),
|
|
109
|
+
pinkFade(italic(centerLine(version, width)), frame),
|
|
110
|
+
];
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function buildRoseHeader(
|
|
114
|
+
_theme: Theme,
|
|
115
|
+
width: number,
|
|
116
|
+
frame: number,
|
|
117
|
+
): string[] {
|
|
118
|
+
return [
|
|
119
|
+
"",
|
|
120
|
+
...ROSE_LOGO_LINES.map((line) => pinkFade(centerLine(line, width), frame)),
|
|
121
|
+
...buildGentlemanTitle(width, frame),
|
|
122
|
+
"",
|
|
123
|
+
];
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function installRoseHeader(ctx: ExtensionContext): void {
|
|
127
|
+
if (!ctx.hasUI) return;
|
|
128
|
+
|
|
129
|
+
process.stdout.write("\x1b[2J\x1b[3J\x1b[H");
|
|
130
|
+
|
|
131
|
+
let closeIntro: (() => void) | undefined;
|
|
132
|
+
const closeIntroSafely = () => {
|
|
133
|
+
const close = closeIntro;
|
|
134
|
+
closeIntro = undefined;
|
|
135
|
+
try {
|
|
136
|
+
close?.();
|
|
137
|
+
} catch {
|
|
138
|
+
// Ignore shutdown races during startup/reload.
|
|
139
|
+
}
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
void ctx.ui
|
|
143
|
+
.custom((_tui, _theme, _keybindings, done) => {
|
|
144
|
+
closeIntro = () => done(undefined);
|
|
145
|
+
return {
|
|
146
|
+
render: () => [""],
|
|
147
|
+
invalidate: () => {},
|
|
148
|
+
handleInput: () => {},
|
|
149
|
+
};
|
|
150
|
+
})
|
|
151
|
+
.catch(() => {
|
|
152
|
+
closeIntro = undefined;
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
const state: { frame: number; timer?: NodeJS.Timeout } = { frame: 0 };
|
|
156
|
+
ctx.ui.setHeader((tui, theme) => {
|
|
157
|
+
if (state.timer) clearInterval(state.timer);
|
|
158
|
+
state.timer = setInterval(() => {
|
|
159
|
+
state.frame += 1;
|
|
160
|
+
tui.requestRender();
|
|
161
|
+
if (
|
|
162
|
+
state.frame <
|
|
163
|
+
ROSE_FADE_STEPS + Math.ceil(ROSE_INTRO_HOLD_MS / ROSE_FADE_INTERVAL_MS)
|
|
164
|
+
) {
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
if (state.timer) clearInterval(state.timer);
|
|
168
|
+
state.timer = undefined;
|
|
169
|
+
closeIntroSafely();
|
|
170
|
+
}, ROSE_FADE_INTERVAL_MS);
|
|
171
|
+
|
|
172
|
+
return {
|
|
173
|
+
render(width: number): string[] {
|
|
174
|
+
return buildRoseHeader(theme, width, state.frame);
|
|
175
|
+
},
|
|
176
|
+
invalidate() {
|
|
177
|
+
if (state.timer) clearInterval(state.timer);
|
|
178
|
+
state.timer = undefined;
|
|
179
|
+
closeIntroSafely();
|
|
180
|
+
},
|
|
181
|
+
};
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
|
|
46
185
|
function buildGentlePrompt(persona: PersonaMode): string {
|
|
47
186
|
const personaPrompt =
|
|
48
187
|
persona === "neutral" ? NEUTRAL_PERSONA_PROMPT : GENTLEMAN_PERSONA_PROMPT;
|
|
@@ -772,6 +911,7 @@ async function handlePersonaCommand(ctx: ExtensionContext): Promise<void> {
|
|
|
772
911
|
|
|
773
912
|
export default function gentleAi(pi: ExtensionAPI): void {
|
|
774
913
|
pi.on("session_start", (_event, ctx) => {
|
|
914
|
+
installRoseHeader(ctx);
|
|
775
915
|
const result = installSddAssets(ctx.cwd, false);
|
|
776
916
|
const modelResult = applyModelConfig(ctx.cwd, readModelConfig(ctx.cwd));
|
|
777
917
|
if (
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gentle-pi",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.22",
|
|
4
4
|
"description": "Turn Pi into el Gentleman: a senior-architect development harness with SDD/OpenSpec, subagents, strict TDD evidence, review guardrails, and skill discovery.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|