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