Run prettier on .pi folder

This commit is contained in:
Claudio Ortolina
2026-05-04 21:21:10 +01:00
parent 286d6fecf3
commit 7bf89d9a1d
8 changed files with 272 additions and 191 deletions
+12 -4
View File
@@ -35,7 +35,7 @@ export default function (pi: ExtensionAPI) {
// Proactively tell the agent which paths are off-limits so it doesn't try in the first place
pi.on("before_agent_start", async (event, _ctx) => {
if (config.blocked_paths.length === 0) return;
const pathList = config.blocked_paths.map((p) => ` - \`${p}\``).join('\n');
const pathList = config.blocked_paths.map((p) => ` - \`${p}\``).join("\n");
return {
systemPrompt:
event.systemPrompt +
@@ -63,7 +63,8 @@ export default function (pi: ExtensionAPI) {
`🚫 ACCESS DENIED by sensitive-file-guard: paths matching "${match}" are permanently protected. ` +
`DO NOT retry with other tools (bash, find, ls, grep, etc.). ` +
`Tell the user the file is protected and STOP.`;
if (ctx.hasUI) ctx.ui.notify(`Blocked sensitive path: ${path}`, "warning");
if (ctx.hasUI)
ctx.ui.notify(`Blocked sensitive path: ${path}`, "warning");
return { block: true, reason };
}
return;
@@ -79,14 +80,21 @@ export default function (pi: ExtensionAPI) {
// Rough substring match: does the pattern text (with glob stripped)
// appear in the command?
const literal = p.replace(/[*?]/g, "");
return literal.length > 0 && command.toLowerCase().includes(literal.toLowerCase());
return (
literal.length > 0 &&
command.toLowerCase().includes(literal.toLowerCase())
);
});
if (pathHit) {
const reason =
`🚫 ACCESS DENIED by sensitive-file-guard: commands targeting "${pathHit}" are permanently blocked. ` +
`DO NOT retry with alternative commands. Tell the user the path is protected and STOP.`;
if (ctx.hasUI) ctx.ui.notify(`Blocked sensitive path in bash: ${pathHit}`, "warning");
if (ctx.hasUI)
ctx.ui.notify(
`Blocked sensitive path in bash: ${pathHit}`,
"warning",
);
return { block: true, reason };
}
+98 -46
View File
@@ -23,11 +23,7 @@ import {
DEFAULT_MAX_LINES,
} from "@mariozechner/pi-coding-agent";
import { BorderedLoader } from "@mariozechner/pi-coding-agent";
import {
matchesKey,
Key,
truncateToWidth,
} from "@mariozechner/pi-tui";
import { matchesKey, Key, truncateToWidth } from "@mariozechner/pi-tui";
import { Type } from "typebox";
// ── Types ───────────────────────────────────────────────────────────────────
@@ -275,7 +271,10 @@ async function fetchErrors(
token: string,
signal?: AbortSignal,
): Promise<ErrorListResponse> {
return fetchApi(url, token, (data) => {
return fetchApi(
url,
token,
(data) => {
const obj = data as Record<string, unknown>;
if (!obj.errors || !Array.isArray(obj.errors)) {
throw new Error(
@@ -285,7 +284,9 @@ async function fetchErrors(
);
}
return data as ErrorListResponse;
}, signal);
},
signal,
);
}
async function fetchError(
@@ -293,7 +294,10 @@ async function fetchError(
token: string,
signal?: AbortSignal,
): Promise<ErrorDetailResponse> {
return fetchApi(url, token, (data) => {
return fetchApi(
url,
token,
(data) => {
const obj = data as Record<string, unknown>;
if (!obj.error || typeof obj.error !== "object") {
throw new Error(
@@ -303,7 +307,9 @@ async function fetchError(
);
}
return data as ErrorDetailResponse;
}, signal);
},
signal,
);
}
// ── TUI Theme ───────────────────────────────────────────────────────────────
@@ -378,7 +384,10 @@ class ErrorBrowser {
private readonly baseUrl: string;
private readonly token: string;
private readonly requestRender: () => void;
private readonly notify: (msg: string, level: "error" | "info" | "warning") => void;
private readonly notify: (
msg: string,
level: "error" | "info" | "warning",
) => void;
constructor(
errors: ErrorListItem[],
@@ -411,7 +420,10 @@ class ErrorBrowser {
if (this.errors.length === 0) {
this.cursorIndex = -1;
} else {
this.cursorIndex = Math.max(0, Math.min(this.cursorIndex, this.errors.length - 1));
this.cursorIndex = Math.max(
0,
Math.min(this.cursorIndex, this.errors.length - 1),
);
}
}
@@ -423,7 +435,10 @@ class ErrorBrowser {
} else if (this.cursorIndex >= this.scrollOffset + maxEntries) {
this.scrollOffset = this.cursorIndex - maxEntries + 1;
}
this.scrollOffset = Math.max(0, Math.min(this.scrollOffset, this.errors.length - 1));
this.scrollOffset = Math.max(
0,
Math.min(this.scrollOffset, this.errors.length - 1),
);
}
// ── HTTP helpers ───────────────────────────────────────────────────────
@@ -471,7 +486,11 @@ class ErrorBrowser {
this.invalidate();
this.requestRender();
const url = buildUrl(this.baseUrl, "/api/v1/errors", this.buildQueryParams());
const url = buildUrl(
this.baseUrl,
"/api/v1/errors",
this.buildQueryParams(),
);
const controller = new AbortController();
this.currentAbortController = controller;
@@ -508,7 +527,11 @@ class ErrorBrowser {
this.invalidate();
this.requestRender();
const url = buildUrl(this.baseUrl, "/api/v1/errors", this.buildQueryParams());
const url = buildUrl(
this.baseUrl,
"/api/v1/errors",
this.buildQueryParams(),
);
const controller = new AbortController();
this.currentAbortController = controller;
@@ -638,7 +661,10 @@ class ErrorBrowser {
const pageSize = Math.floor(this.visibleHeight / this.errorEntryLines);
this.cursorIndex -= pageSize;
moved = true;
} else if (matchesKey(data, Key.pageDown) || matchesKey(data, Key.ctrl("f"))) {
} else if (
matchesKey(data, Key.pageDown) ||
matchesKey(data, Key.ctrl("f"))
) {
const pageSize = Math.floor(this.visibleHeight / this.errorEntryLines);
this.cursorIndex += pageSize;
moved = true;
@@ -689,7 +715,10 @@ class ErrorBrowser {
} else if (matchesKey(data, Key.pageUp)) {
this.detailScrollOffset -= this.visibleHeight;
moved = true;
} else if (matchesKey(data, Key.pageDown) || matchesKey(data, Key.ctrl("f"))) {
} else if (
matchesKey(data, Key.pageDown) ||
matchesKey(data, Key.ctrl("f"))
) {
this.detailScrollOffset += this.visibleHeight;
moved = true;
} else if (matchesKey(data, Key.ctrl("b"))) {
@@ -699,13 +728,19 @@ class ErrorBrowser {
this.detailScrollOffset = 0;
moved = true;
} else if (matchesKey(data, Key.end) || matchesKey(data, "G")) {
this.detailScrollOffset = Math.max(0, this.buildDetailLines().length - 1);
this.detailScrollOffset = Math.max(
0,
this.buildDetailLines().length - 1,
);
moved = true;
}
if (moved) {
const maxOffset = Math.max(0, this.buildDetailLines().length - 1);
this.detailScrollOffset = Math.max(0, Math.min(this.detailScrollOffset, maxOffset));
this.detailScrollOffset = Math.max(
0,
Math.min(this.detailScrollOffset, maxOffset),
);
this.invalidate();
}
@@ -746,7 +781,9 @@ class ErrorBrowser {
for (let i = 1; i < viewportRows - 1; i++) {
if (i === mid) {
result.push(truncateToWidth(theme.fg("muted", " Loading…"), width, ""));
result.push(
truncateToWidth(theme.fg("muted", " Loading…"), width, ""),
);
} else {
result.push("");
}
@@ -809,12 +846,24 @@ class ErrorBrowser {
else if (error.status === "resolved") statusColor = "success";
const mutedLabel = error.muted ? " [MUTED]" : "";
const statusBadge = theme.fg(statusColor, `[${error.status.toUpperCase()}]`);
const statusBadge = theme.fg(
statusColor,
`[${error.status.toUpperCase()}]`,
);
// Line 1: cursor + status badge + kind + truncated reason
const reasonText = truncateReason(error.reason, Math.max(30, width - 25));
const reasonText = truncateReason(
error.reason,
Math.max(30, width - 25),
);
const line1 = `${cursor} ${statusBadge}${mutedLabel} ${error.kind}: ${reasonText}`;
result.push(truncateToWidth(isCursor ? theme.fg("accent", line1) : line1, width, ""));
result.push(
truncateToWidth(
isCursor ? theme.fg("accent", line1) : line1,
width,
"",
),
);
// Line 2: source info
const sourceInfo = error.source_function
@@ -857,9 +906,7 @@ class ErrorBrowser {
truncateToWidth(
theme.fg(
"dim",
hasMore
? ` l load more q quit`
: ` — end of results — q quit`,
hasMore ? ` l load more q quit` : ` — end of results — q quit`,
),
width,
"",
@@ -972,13 +1019,13 @@ export default function prodErrorsExtension(pi: ExtensionAPI) {
parameters: Type.Object({
status: Type.Optional(
Type.Union([Type.Literal("resolved"), Type.Literal("unresolved")], {
description:
"Filter by error status: 'resolved' or 'unresolved'.",
description: "Filter by error status: 'resolved' or 'unresolved'.",
}),
),
muted: Type.Optional(
Type.Boolean({
description: "Filter by muted state. true = only muted, false = only unmuted.",
description:
"Filter by muted state. true = only muted, false = only unmuted.",
}),
),
search: Type.Optional(
@@ -1000,13 +1047,7 @@ export default function prodErrorsExtension(pi: ExtensionAPI) {
}),
),
}),
async execute(
_toolCallId,
params,
signal,
_onUpdate,
_ctx,
) {
async execute(_toolCallId, params, signal, _onUpdate, _ctx) {
// Early abort check
if (signal?.aborted) {
return { content: [{ type: "text", text: "Cancelled" }] };
@@ -1043,7 +1084,11 @@ export default function prodErrorsExtension(pi: ExtensionAPI) {
if (params.muted !== undefined && params.muted !== null) {
queryParams.muted = String(params.muted);
}
if (params.search !== undefined && params.search !== null && params.search !== "") {
if (
params.search !== undefined &&
params.search !== null &&
params.search !== ""
) {
queryParams.search = params.search;
}
if (params.limit !== undefined && params.limit !== null) {
@@ -1075,8 +1120,18 @@ export default function prodErrorsExtension(pi: ExtensionAPI) {
// Handle empty results
if (data.errors.length === 0) {
return {
content: [{ type: "text", text: "No errors found matching the given filters." }],
details: { total: data.total, count: 0, offset: data.offset, limit: data.limit },
content: [
{
type: "text",
text: "No errors found matching the given filters.",
},
],
details: {
total: data.total,
count: 0,
offset: data.offset,
limit: data.limit,
},
};
}
@@ -1126,13 +1181,7 @@ export default function prodErrorsExtension(pi: ExtensionAPI) {
"The error ID (integer) to fetch full details for. Get this from fetch_production_errors.",
}),
}),
async execute(
_toolCallId,
params,
signal,
_onUpdate,
_ctx,
) {
async execute(_toolCallId, params, signal, _onUpdate, _ctx) {
// Early abort check
if (signal?.aborted) {
return { content: [{ type: "text", text: "Cancelled" }] };
@@ -1256,7 +1305,10 @@ export default function prodErrorsExtension(pi: ExtensionAPI) {
);
if (errorsData === null) {
ctx.ui.notify("Cancelled or fetch failed — check logs for details", "info");
ctx.ui.notify(
"Cancelled or fetch failed — check logs for details",
"info",
);
return;
}
+50 -33
View File
@@ -21,11 +21,7 @@ import {
DEFAULT_MAX_LINES,
} from "@mariozechner/pi-coding-agent";
import { Type } from "typebox";
import {
matchesKey,
Key,
truncateToWidth,
} from "@mariozechner/pi-tui";
import { matchesKey, Key, truncateToWidth } from "@mariozechner/pi-tui";
// ── Types ───────────────────────────────────────────────────────────────────
@@ -80,7 +76,10 @@ class LogViewer {
this.visualMode = false;
this.visualAnchor = 0;
this.scrollOffset = 0;
this.visibleHeight = Math.min(lines.length, Math.max(10, process.stdout.rows - chromeHeight));
this.visibleHeight = Math.min(
lines.length,
Math.max(10, process.stdout.rows - chromeHeight),
);
this.clampCursor();
this.clampViewport();
this.invalidate();
@@ -101,7 +100,10 @@ class LogViewer {
if (this.lines.length === 0) {
this.cursorIndex = -1;
} else {
this.cursorIndex = Math.max(0, Math.min(this.cursorIndex, this.lines.length - 1));
this.cursorIndex = Math.max(
0,
Math.min(this.cursorIndex, this.lines.length - 1),
);
}
}
@@ -129,7 +131,10 @@ class LogViewer {
}
// Escape or q in normal mode closes the viewer
if (!this.visualMode && (matchesKey(data, Key.escape) || matchesKey(data, "q"))) {
if (
!this.visualMode &&
(matchesKey(data, Key.escape) || matchesKey(data, "q"))
) {
this.onClose?.();
return;
}
@@ -156,7 +161,10 @@ class LogViewer {
const start = Math.min(this.visualAnchor, this.cursorIndex);
const end = Math.max(this.visualAnchor, this.cursorIndex);
// this.lines[0] = newest, so reverse the slice for oldest-first
const text = this.lines.slice(start, end + 1).reverse().join("\n");
const text = this.lines
.slice(start, end + 1)
.reverse()
.join("\n");
this.onCopy?.(text);
}
return;
@@ -184,7 +192,10 @@ class LogViewer {
} else if (matchesKey(data, Key.pageUp)) {
this.cursorIndex -= this.visibleHeight;
moved = true;
} else if (matchesKey(data, Key.pageDown) || matchesKey(data, Key.ctrl("f"))) {
} else if (
matchesKey(data, Key.pageDown) ||
matchesKey(data, Key.ctrl("f"))
) {
this.cursorIndex += this.visibleHeight;
moved = true;
} else if (matchesKey(data, Key.ctrl("b"))) {
@@ -237,9 +248,8 @@ class LogViewer {
const basePrefix = ` ${numStr}`;
const isCursor = absIdx === this.cursorIndex;
const isSelected = this.visualMode
&& absIdx >= selStart
&& absIdx <= selEnd;
const isSelected =
this.visualMode && absIdx >= selStart && absIdx <= selEnd;
if (isCursor) {
// Cursor takes priority over selection highlight
@@ -349,13 +359,7 @@ export default function prodLogsExtension(pi: ExtensionAPI) {
}),
),
}),
async execute(
_toolCallId,
params,
signal,
_onUpdate,
_ctx,
) {
async execute(_toolCallId, params, signal, _onUpdate, _ctx) {
// Early abort check
if (signal?.aborted) {
return { content: [{ type: "text", text: "Cancelled" }] };
@@ -498,7 +502,10 @@ export default function prodLogsExtension(pi: ExtensionAPI) {
);
if (logLines === null) {
ctx.ui.notify("Cancelled or fetch failed — check logs for details", "info");
ctx.ui.notify(
"Cancelled or fetch failed — check logs for details",
"info",
);
return;
}
@@ -519,7 +526,10 @@ export default function prodLogsExtension(pi: ExtensionAPI) {
let loading = false;
const visibleLogLines = () =>
Math.min(logLinesMut.length, Math.max(10, process.stdout.rows - chromeHeight));
Math.min(
logLinesMut.length,
Math.max(10, process.stdout.rows - chromeHeight),
);
const viewer = new LogViewer(logLinesMut, visibleLogLines());
@@ -553,7 +563,8 @@ export default function prodLogsExtension(pi: ExtensionAPI) {
return {
render(width: number) {
const pct = viewer.total > 0
const pct =
viewer.total > 0
? Math.round((viewer.offset / viewer.total) * 100)
: 0;
const from = viewer.offset + 1;
@@ -565,20 +576,28 @@ export default function prodLogsExtension(pi: ExtensionAPI) {
const result: string[] = [];
const border = theme.fg("accent", "─".repeat(width));
result.push(border);
result.push(truncateToWidth(
result.push(
truncateToWidth(
theme.fg("accent", theme.bold(" Production Logs")),
width,
"",
));
),
);
const countText = loading
? " Refreshing..."
: ` ${logLinesMut.length} lines`;
result.push(truncateToWidth(theme.fg("muted", countText), width, ""));
result.push(truncateToWidth(theme.fg("dim", "─".repeat(40)), width, ""));
result.push(
truncateToWidth(theme.fg("muted", countText), width, ""),
);
result.push(
truncateToWidth(theme.fg("dim", "─".repeat(40)), width, ""),
);
const infoText = ` Lines ${from}-${to} of ${logLinesMut.length} (${pct}%)`;
result.push(truncateToWidth(theme.fg("dim", infoText), width, ""));
result.push(
truncateToWidth(theme.fg("dim", infoText), width, ""),
);
// Log lines
const logRendered = viewer.render(width, theme);
@@ -590,11 +609,9 @@ export default function prodLogsExtension(pi: ExtensionAPI) {
: viewer.visualMode
? " VISUAL: j/k extend · y copy · Esc cancel · Enter copy line"
: " ↑↓/jk scroll · PgUp/PgDn page · Home/End jump · v visual · Enter copy line · r refresh · Esc close";
result.push(truncateToWidth(
theme.fg("dim", helpLine),
width,
"",
));
result.push(
truncateToWidth(theme.fg("dim", helpLine), width, ""),
);
result.push(border);
return result;
+22 -11
View File
@@ -33,7 +33,10 @@ const S3_REGION = "nbg1";
function humanSize(bytes: number): string {
if (bytes === 0) return "0 B";
const units = ["B", "KB", "MB", "GB", "TB"];
const i = Math.min(Math.floor(Math.log(bytes) / Math.log(1024)), units.length - 1);
const i = Math.min(
Math.floor(Math.log(bytes) / Math.log(1024)),
units.length - 1,
);
return `${(bytes / 1024 ** i).toFixed(1)} ${units[i]}`;
}
@@ -152,7 +155,10 @@ export default function s3BrowserExtension(pi: ExtensionAPI) {
);
if (objects === null) {
ctx.ui.notify("Cancelled or fetch failed — check logs for details", "info");
ctx.ui.notify(
"Cancelled or fetch failed — check logs for details",
"info",
);
return;
}
@@ -174,7 +180,8 @@ export default function s3BrowserExtension(pi: ExtensionAPI) {
// Summary header line
const summary = `${objects.length} files · ${humanSize(totalSize)} total`;
const selectedKey = await ctx.ui.custom<string | null>((tui, theme, _kb, done) => {
const selectedKey = await ctx.ui.custom<string | null>(
(tui, theme, _kb, done) => {
const container = new Container();
// Top border
@@ -184,16 +191,19 @@ export default function s3BrowserExtension(pi: ExtensionAPI) {
// Header
container.addChild(
new Text(theme.fg("accent", theme.bold("S3 Backups — ffmusiclibrary/prod/")), 1, 0),
);
container.addChild(
new Text(theme.fg("muted", summary), 1, 0),
new Text(
theme.fg(
"accent",
theme.bold("S3 Backups — ffmusiclibrary/prod/"),
),
1,
0,
),
);
container.addChild(new Text(theme.fg("muted", summary), 1, 0));
// Spacer before list
container.addChild(
new Text(theme.fg("dim", "─".repeat(40)), 1, 0),
);
container.addChild(new Text(theme.fg("dim", "─".repeat(40)), 1, 0));
// SelectList
const selectList = new SelectList(items, Math.min(items.length, 20), {
@@ -233,7 +243,8 @@ export default function s3BrowserExtension(pi: ExtensionAPI) {
tui.requestRender();
},
};
});
},
);
if (selectedKey) {
const obj = objects.find((o) => o.key === selectedKey);
+1
View File
@@ -1,6 +1,7 @@
---
description: Create a new backlog task
---
Let's create a new backlog task via a predefined process. **IT'S CRITICAL YOU FOLLOW THIS PROCESS**.
To start, ask me for:
+1 -5
View File
@@ -13,9 +13,5 @@
".gnupg/",
"~/.pi/agent/sessions/"
],
"blocked_commands": [
"printenv",
"\\benv\\b",
"\\bset\\b"
]
"blocked_commands": ["printenv", "\\benv\\b", "\\bset\\b"]
}
+2 -6
View File
@@ -1,8 +1,4 @@
{
"skills": [
"../.claude/skills"
],
"packages": [
"npm:pi-mcp-adapter"
]
"skills": ["../.claude/skills"],
"packages": ["npm:pi-mcp-adapter"]
}