pi-jar 0.1.1 → 0.1.3
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 +16 -12
- package/docs/DEVELOPMENT.md +4 -2
- package/docs/WORKFLOWS.md +2 -1
- package/extensions/index.ts +212 -89
- package/package.json +1 -1
- package/src/activity-view.ts +252 -0
- package/src/advisor.ts +30 -6
- package/src/changes.ts +12 -20
- package/src/compact-tools.ts +62 -18
- package/src/composer.ts +36 -14
- package/src/delegate.ts +187 -23
- package/src/diff-view.ts +2 -2
- package/src/footer.ts +105 -18
- package/src/goal-loop.ts +34 -9
- package/src/goals.ts +13 -2
- package/src/history-ui.ts +2 -1
- package/src/icons.ts +57 -0
- package/src/info-panels.ts +19 -7
- package/src/plan.ts +37 -10
- package/src/prompt-search.ts +27 -8
- package/src/quota.ts +14 -4
- package/src/session-branch.ts +24 -0
- package/src/session-gallery.ts +10 -5
- package/src/session-ui.ts +119 -45
- package/src/settings-ui.ts +5 -2
- package/src/settings.ts +5 -1
- package/src/shells.ts +77 -99
- package/src/side-model.ts +14 -2
- package/src/status.ts +36 -24
- package/src/suggest.ts +6 -25
- package/src/task-tool.ts +35 -11
- package/src/tasks.ts +24 -7
- package/src/usage.ts +2 -1
- package/src/welcome.ts +32 -12
- package/src/working.ts +3 -1
package/src/welcome.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { sliceByColumn, stripTerminalSequences, truncateToWidth, visibleWidth } from "@earendil-works/pi-tui";
|
|
2
2
|
import { FLAME_RAMP, FLAME_WIDTH, flameFrame, rgb, supportsTruecolor } from "./flame.ts";
|
|
3
3
|
import { cleanText } from "./status.ts";
|
|
4
|
+
import { iconSet, withIcon } from "./icons.ts";
|
|
4
5
|
|
|
5
6
|
export type WelcomeAction = "settings" | "refresh" | "roles" | "plan" | "goal" | `resume:${number}`;
|
|
6
7
|
|
|
@@ -89,8 +90,11 @@ const center = (line: string, width: number) => {
|
|
|
89
90
|
const left = Math.floor(room / 2);
|
|
90
91
|
return " ".repeat(left) + line + " ".repeat(room - left);
|
|
91
92
|
};
|
|
93
|
+
// truncateToWidth re-segments every grapheme even when a line already fits; visibleWidth is
|
|
94
|
+
// memoized by Pi, so measure first and only truncate real overflow (same output either way).
|
|
95
|
+
const fitTo = (line: string, width: number) => visibleWidth(line) <= width ? line : truncateToWidth(line, width);
|
|
92
96
|
const fixedCell = (line: string, width: number) => {
|
|
93
|
-
const value =
|
|
97
|
+
const value = fitTo(line, width);
|
|
94
98
|
return value + " ".repeat(Math.max(0, width - visibleWidth(value)));
|
|
95
99
|
};
|
|
96
100
|
|
|
@@ -157,12 +161,12 @@ function card(width: number, fg: Paint, info: WelcomeInfo): string[] {
|
|
|
157
161
|
const label = (name: string, value: string, color: WelcomeColor = "muted") =>
|
|
158
162
|
fg("dim", name.padEnd(labelWidth)) + fg(color, cleanText(value, 120));
|
|
159
163
|
|
|
160
|
-
const header = [fg("accent", "pi-jar" + (info.version ? " " + info.version : "")),
|
|
161
|
-
info.model ? fg("muted", cleanText(info.model, 40)) : fg("dim", "no model"),
|
|
164
|
+
const header = [fg("accent", withIcon("pi", "pi-jar" + (info.version ? " " + info.version : ""))),
|
|
165
|
+
info.model ? fg("muted", withIcon("model", cleanText(info.model, 40))) : fg("dim", "no model"),
|
|
162
166
|
info.effort && info.effort !== "off" ? fg("muted", info.effort) : "",
|
|
163
167
|
info.activeRole ? fg("warning", "role:" + cleanText(info.activeRole, 16)) : ""].filter(Boolean).join(fg("dim", " · "));
|
|
164
168
|
|
|
165
|
-
const git = info.branch ? "
|
|
169
|
+
const git = info.branch ? withIcon("branch", cleanText(info.branch, 24)) + (info.dirty ? " · dirty" : "") : withIcon("branch", "unavailable");
|
|
166
170
|
const quota = info.quota != null ? `quota ${Math.round(info.quota)}%` : `quota ${info.quotaEnabled ? "unavailable" : "off"}`;
|
|
167
171
|
const plan = info.plan?.enabled ? "◆ planning" + (info.plan.title ? " · " + info.plan.title : "")
|
|
168
172
|
: info.plan?.title ? "last · " + info.plan.title + ` (${info.plan.steps} steps)` : "none · /plan <idea>";
|
|
@@ -185,7 +189,7 @@ function card(width: number, fg: Paint, info: WelcomeInfo): string[] {
|
|
|
185
189
|
cell(center(fg("dim", "— welcome to ") + fg("accent", "pi-jar") + fg("dim", " —"), inner)),
|
|
186
190
|
cell(""),
|
|
187
191
|
divider,
|
|
188
|
-
cell(label("PROJECT", (info.project || "unavailable") + " · " + git, info.dirty ? "warning" : "muted")),
|
|
192
|
+
cell(label("PROJECT", withIcon("folder", info.project || "unavailable") + " · " + git, info.dirty ? "warning" : "muted")),
|
|
189
193
|
cell(label("SESSION", [info.context ?? "ctx ?", quota, info.cost ?? ""].filter(Boolean).join(" · "))),
|
|
190
194
|
divider,
|
|
191
195
|
cell(label("PLAN", plan, info.plan?.enabled ? "warning" : "muted")),
|
|
@@ -203,6 +207,16 @@ function card(width: number, fg: Paint, info: WelcomeInfo): string[] {
|
|
|
203
207
|
];
|
|
204
208
|
}
|
|
205
209
|
|
|
210
|
+
const CARD_COLORS: readonly WelcomeColor[] = ["accent", "warning", "error", "muted", "dim"];
|
|
211
|
+
let cardCache: { key: string; lines: string[] } | undefined;
|
|
212
|
+
|
|
213
|
+
/** The card only changes with its inputs and theme; the flame beside it repaints every tick. */
|
|
214
|
+
function cachedCard(width: number, fg: Paint, info: WelcomeInfo): string[] {
|
|
215
|
+
const key = `${width}\0${supportsTruecolor()}\0${iconSet()}\0${CARD_COLORS.map((color) => fg(color, "x")).join("")}\0${JSON.stringify(info)}`;
|
|
216
|
+
if (cardCache?.key !== key) cardCache = { key, lines: card(width, fg, info) };
|
|
217
|
+
return cardCache.lines;
|
|
218
|
+
}
|
|
219
|
+
|
|
206
220
|
/** Which welcome action (if any) sits under a zero-based cell in the rendered lines. */
|
|
207
221
|
export function welcomeHit(lines: readonly string[], x: number, y: number): WelcomeAction | undefined {
|
|
208
222
|
const line = lines[y];
|
|
@@ -227,7 +241,7 @@ export function welcomeSettingsHit(lines: readonly string[], x: number, y: numbe
|
|
|
227
241
|
|
|
228
242
|
export function welcomeLines(width: number, frame: number, fg: Paint, info: WelcomeInfo = {}): string[] {
|
|
229
243
|
if (width <= 0) return [];
|
|
230
|
-
const fit = (line: string) =>
|
|
244
|
+
const fit = (line: string) => fitTo(line, width);
|
|
231
245
|
const seed = info.flameSeed ?? 1;
|
|
232
246
|
const flame = flameFrame(frame, seed, (color, text) => fg(color, text));
|
|
233
247
|
if (width < 32) {
|
|
@@ -242,19 +256,25 @@ export function welcomeLines(width: number, frame: number, fg: Paint, info: Welc
|
|
|
242
256
|
];
|
|
243
257
|
}
|
|
244
258
|
const wide = width >= 72;
|
|
245
|
-
|
|
259
|
+
// FlameSim rows are exactly FLAME_WIDTH single-cell glyphs, so center them without measuring:
|
|
260
|
+
// re-measuring freshly colored rows every 150 ms tick was most of the welcome's render time.
|
|
261
|
+
const flameLeft = " ".repeat(Math.floor((ART_WIDTH - FLAME_WIDTH) / 2));
|
|
262
|
+
const flameRight = " ".repeat(ART_WIDTH - FLAME_WIDTH - flameLeft.length);
|
|
263
|
+
const flameArt = flame.map((line) => flameLeft + line + flameRight);
|
|
246
264
|
if (!wide) {
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
return [...art
|
|
265
|
+
// width >= 32 here, so ART_WIDTH-wide art rows always fit.
|
|
266
|
+
const art = [...flameArt.slice(2), ...PI_COMPACT.map((line) => fg("accent", center(line, ART_WIDTH)))];
|
|
267
|
+
return [...art, ...cachedCard(width, fg, info).map(fit), ""];
|
|
250
268
|
}
|
|
251
269
|
const art = [...flameArt, ...PI_LARGE.map((line) => fg("accent", center(fixedCell(line, 21), ART_WIDTH)))];
|
|
252
|
-
const details =
|
|
270
|
+
const details = cachedCard(width - ART_WIDTH - 2, fg, info);
|
|
253
271
|
const topPad = Math.max(0, Math.floor((details.length - art.length) / 2));
|
|
254
272
|
const artRows = [...Array.from({ length: topPad }, () => " ".repeat(ART_WIDTH)), ...art];
|
|
255
273
|
const merged = Array.from({ length: Math.max(artRows.length, details.length) }, (_, index) => {
|
|
256
274
|
const left = artRows[index] ?? " ".repeat(ART_WIDTH);
|
|
257
|
-
|
|
275
|
+
const right = details[index] ?? "";
|
|
276
|
+
// Every art row is ART_WIDTH cells; the cached card rows hit Pi's width cache.
|
|
277
|
+
return ART_WIDTH + 2 + visibleWidth(right) <= width ? left + " " + right : truncateToWidth(left + " " + right, width);
|
|
258
278
|
});
|
|
259
279
|
return [...merged, "", ""];
|
|
260
280
|
}
|
package/src/working.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { icon } from "./icons.ts";
|
|
1
2
|
import { cleanText } from "./status.ts";
|
|
2
3
|
|
|
3
4
|
export type WorkingPhase = "idle" | "generating" | "tool" | "waiting";
|
|
@@ -54,7 +55,8 @@ export class WorkingState {
|
|
|
54
55
|
const symbols = phase === "tool" ? ["◐", "◓", "◑", "◒"] : phase === "generating" ? ["✢", "✣", "✤", "✣"] : ["✢"];
|
|
55
56
|
const frames = animations ? symbols.map((symbol) => fg(color, symbol)) : [fg(color, symbols[0] ?? "✢")];
|
|
56
57
|
const details = phase === "idle" || this.startedAt === undefined ? "" :
|
|
57
|
-
` (${duration(elapsed)}${this.outputTokens ? ` · ↓ ${tokenCount(this.outputTokens)} tokens` : ""}${effort && effort !== "off" ? ` · ${effort} effort` : ""}
|
|
58
|
+
` (${duration(elapsed)}${this.outputTokens ? ` · ↓ ${tokenCount(this.outputTokens)} tokens` : ""}${effort && effort !== "off" ? ` · ${effort} effort` : ""}`
|
|
59
|
+
+ `${phase === "waiting" ? "" : ` · ${icon("esc") === "esc" ? "esc" : icon("esc") + " esc"} to interrupt`})`;
|
|
58
60
|
return { message: word ? fg(color, word) + fg("muted", details) : undefined, frames, color };
|
|
59
61
|
}
|
|
60
62
|
}
|