Compare commits
1 Commits
nixos
..
03efbd500e
| Author | SHA1 | Date | |
|---|---|---|---|
| 03efbd500e |
@@ -377,7 +377,7 @@
|
|||||||
"osdPosition": 5,
|
"osdPosition": 5,
|
||||||
"osdVolumeEnabled": true,
|
"osdVolumeEnabled": true,
|
||||||
"osdMediaVolumeEnabled": true,
|
"osdMediaVolumeEnabled": true,
|
||||||
"osdMediaPlaybackEnabled": false,
|
"osdMediaPlaybackEnabled": true,
|
||||||
"osdBrightnessEnabled": true,
|
"osdBrightnessEnabled": true,
|
||||||
"osdIdleInhibitorEnabled": true,
|
"osdIdleInhibitorEnabled": true,
|
||||||
"osdMicMuteEnabled": true,
|
"osdMicMuteEnabled": true,
|
||||||
|
|||||||
@@ -1,18 +0,0 @@
|
|||||||
---@class SigilConfig
|
|
||||||
---@field target table<string, string|boolean>
|
|
||||||
---@field ignore? string[]
|
|
||||||
|
|
||||||
---@type SigilConfig
|
|
||||||
local config = {
|
|
||||||
target = {
|
|
||||||
linux = "~/.config/gsf",
|
|
||||||
default = "~/.config/gsf",
|
|
||||||
},
|
|
||||||
ignore = {
|
|
||||||
-- "**/.DS_Store",
|
|
||||||
-- "**/*.tmp",
|
|
||||||
-- "cache/**",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
return config
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
{
|
|
||||||
"mode": 0,
|
|
||||||
"sens_mult": 1.5,
|
|
||||||
"yx_ratio": 1.0,
|
|
||||||
"input_dpi": 400.0,
|
|
||||||
"angle_rotation": 0.0,
|
|
||||||
"accel": 2.0,
|
|
||||||
"offset_linear": 3.5,
|
|
||||||
"output_cap": 30.0,
|
|
||||||
"decay_rate": 0.1,
|
|
||||||
"offset_natural": 0.0,
|
|
||||||
"limit": 2.0,
|
|
||||||
"gamma": 1.0,
|
|
||||||
"smooth": 0.5,
|
|
||||||
"motivity": 1.5,
|
|
||||||
"sync_speed": 5.0
|
|
||||||
}
|
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"activePack": "glados",
|
"activePack": "glados",
|
||||||
"volume": 1,
|
"volume": 1,
|
||||||
"muted": false,
|
"muted": true,
|
||||||
"enabledCategories": {
|
"enabledCategories": {
|
||||||
"session.start": true,
|
"session.start": true,
|
||||||
"task.acknowledge": true,
|
"task.acknowledge": true,
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
{
|
{
|
||||||
"lastChangelogVersion": "0.63.1",
|
"lastChangelogVersion": "0.60.0",
|
||||||
"defaultProvider": "openai-codex",
|
"defaultProvider": "openai-codex",
|
||||||
"defaultModel": "gpt-5.3-codex",
|
"defaultModel": "gpt-5.3-codex",
|
||||||
"defaultThinkingLevel": "high",
|
"defaultThinkingLevel": "medium",
|
||||||
"theme": "matugen",
|
"theme": "matugen",
|
||||||
"lsp": {
|
"lsp": {
|
||||||
"hookMode": "edit_write"
|
"hookMode": "edit_write"
|
||||||
|
|||||||
@@ -1 +0,0 @@
|
|||||||
{}
|
|
||||||
@@ -9,8 +9,8 @@
|
|||||||
* The editor is determined by $VISUAL, then $EDITOR, then falls back to 'vi'.
|
* The editor is determined by $VISUAL, then $EDITOR, then falls back to 'vi'.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type { ExtensionAPI, ExtensionCommandContext, Theme } from "@mariozechner/pi-coding-agent";
|
import type { ExtensionAPI, ExtensionCommandContext } from "@mariozechner/pi-coding-agent";
|
||||||
import type { TUI, KeybindingsManager, Component } from "@mariozechner/pi-tui";
|
import type { TUI, Theme, KeybindingsManager, Component } from "@mariozechner/pi-tui";
|
||||||
import { spawnSync } from "node:child_process";
|
import { spawnSync } from "node:child_process";
|
||||||
|
|
||||||
export default function editSessionExtension(pi: ExtensionAPI) {
|
export default function editSessionExtension(pi: ExtensionAPI) {
|
||||||
@@ -59,7 +59,7 @@ export default function editSessionExtension(pi: ExtensionAPI) {
|
|||||||
ctx.ui.notify(`Editor exited with code ${result.status}`, "warning");
|
ctx.ui.notify(`Editor exited with code ${result.status}`, "warning");
|
||||||
}
|
}
|
||||||
|
|
||||||
done(undefined);
|
done();
|
||||||
|
|
||||||
// Return dummy component
|
// Return dummy component
|
||||||
return createDummyComponent();
|
return createDummyComponent();
|
||||||
@@ -69,7 +69,7 @@ export default function editSessionExtension(pi: ExtensionAPI) {
|
|||||||
await ctx.ui.custom<void>(factory);
|
await ctx.ui.custom<void>(factory);
|
||||||
|
|
||||||
// Signal that we're about to reload the session (so confirm-destructive skips)
|
// Signal that we're about to reload the session (so confirm-destructive skips)
|
||||||
pi.events.emit("edit-session:reload", undefined);
|
pi.events.emit("edit-session:reload");
|
||||||
|
|
||||||
// Reload the session by switching to the same file (forces re-read from disk)
|
// Reload the session by switching to the same file (forces re-read from disk)
|
||||||
ctx.ui.notify("Reloading session...", "info");
|
ctx.ui.notify("Reloading session...", "info");
|
||||||
|
|||||||
@@ -80,8 +80,7 @@ export default function (pi: ExtensionAPI) {
|
|||||||
loader.onAbort = () => done(null);
|
loader.onAbort = () => done(null);
|
||||||
|
|
||||||
const doGenerate = async () => {
|
const doGenerate = async () => {
|
||||||
const auth = await ctx.modelRegistry.getApiKeyAndHeaders(ctx.model!);
|
const apiKey = await ctx.modelRegistry.getApiKey(ctx.model!);
|
||||||
if (!auth.ok) throw new Error(auth.error);
|
|
||||||
|
|
||||||
const userMessage: Message = {
|
const userMessage: Message = {
|
||||||
role: "user",
|
role: "user",
|
||||||
@@ -97,7 +96,7 @@ export default function (pi: ExtensionAPI) {
|
|||||||
const response = await complete(
|
const response = await complete(
|
||||||
ctx.model!,
|
ctx.model!,
|
||||||
{ systemPrompt: SYSTEM_PROMPT, messages: [userMessage] },
|
{ systemPrompt: SYSTEM_PROMPT, messages: [userMessage] },
|
||||||
{ apiKey: auth.apiKey, headers: auth.headers, signal: loader.signal },
|
{ apiKey, signal: loader.signal },
|
||||||
);
|
);
|
||||||
|
|
||||||
if (response.stopReason === "aborted") {
|
if (response.stopReason === "aborted") {
|
||||||
|
|||||||
@@ -1,535 +0,0 @@
|
|||||||
import { existsSync, readFileSync, statSync } from "node:fs";
|
|
||||||
import { spawn } from "node:child_process";
|
|
||||||
import { basename, dirname, join, resolve } from "node:path";
|
|
||||||
import type { ExtensionAPI, ExtensionContext, ToolResultEvent } from "@mariozechner/pi-coding-agent";
|
|
||||||
|
|
||||||
const HOOK_TIMEOUT_MS = 10 * 60 * 1000;
|
|
||||||
|
|
||||||
type HookEventName = "PostToolUse" | "PostToolUseFailure";
|
|
||||||
|
|
||||||
type ResolvedCommandHook = {
|
|
||||||
eventName: HookEventName;
|
|
||||||
matcher?: RegExp;
|
|
||||||
matcherText?: string;
|
|
||||||
command: string;
|
|
||||||
source: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
type HookState = {
|
|
||||||
projectDir: string;
|
|
||||||
hooks: ResolvedCommandHook[];
|
|
||||||
};
|
|
||||||
|
|
||||||
type CommandRunResult = {
|
|
||||||
code: number;
|
|
||||||
stdout: string;
|
|
||||||
stderr: string;
|
|
||||||
elapsedMs: number;
|
|
||||||
timedOut: boolean;
|
|
||||||
};
|
|
||||||
|
|
||||||
function isFile(path: string): boolean {
|
|
||||||
try {
|
|
||||||
return statSync(path).isFile();
|
|
||||||
} catch {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function asRecord(value: unknown): Record<string, unknown> | undefined {
|
|
||||||
if (typeof value !== "object" || value === null) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
return value as Record<string, unknown>;
|
|
||||||
}
|
|
||||||
|
|
||||||
function walkUpDirectories(startDir: string, stopDir?: string): string[] {
|
|
||||||
const directories: string[] = [];
|
|
||||||
const hasStopDir = stopDir !== undefined;
|
|
||||||
let current = resolve(startDir);
|
|
||||||
let parent = dirname(current);
|
|
||||||
let reachedStopDir = hasStopDir && current === stopDir;
|
|
||||||
let reachedFilesystemRoot = parent === current;
|
|
||||||
|
|
||||||
directories.push(current);
|
|
||||||
while (!reachedStopDir && !reachedFilesystemRoot) {
|
|
||||||
current = parent;
|
|
||||||
parent = dirname(current);
|
|
||||||
reachedStopDir = hasStopDir && current === stopDir;
|
|
||||||
reachedFilesystemRoot = parent === current;
|
|
||||||
directories.push(current);
|
|
||||||
}
|
|
||||||
|
|
||||||
return directories;
|
|
||||||
}
|
|
||||||
|
|
||||||
function findNearestGitRoot(startDir: string): string | undefined {
|
|
||||||
for (const directory of walkUpDirectories(startDir)) {
|
|
||||||
if (existsSync(join(directory, ".git"))) {
|
|
||||||
return directory;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
function hasHooksConfig(directory: string): boolean {
|
|
||||||
const claudeSettingsPath = join(directory, ".claude", "settings.json");
|
|
||||||
const ruleSyncHooksPath = join(directory, ".rulesync", "hooks.json");
|
|
||||||
const piHooksPath = join(directory, ".pi", "hooks.json");
|
|
||||||
return isFile(claudeSettingsPath) || isFile(ruleSyncHooksPath) || isFile(piHooksPath);
|
|
||||||
}
|
|
||||||
|
|
||||||
function findProjectDir(cwd: string): string {
|
|
||||||
const gitRoot = findNearestGitRoot(cwd);
|
|
||||||
for (const directory of walkUpDirectories(cwd, gitRoot)) {
|
|
||||||
if (hasHooksConfig(directory)) {
|
|
||||||
return directory;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return gitRoot ?? resolve(cwd);
|
|
||||||
}
|
|
||||||
|
|
||||||
function readJsonFile(path: string): unknown | undefined {
|
|
||||||
if (!isFile(path)) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
return JSON.parse(readFileSync(path, "utf8")) as unknown;
|
|
||||||
} catch {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function resolveHookCommand(command: string, projectDir: string): string {
|
|
||||||
return command.replace(/\$CLAUDE_PROJECT_DIR\b/g, projectDir);
|
|
||||||
}
|
|
||||||
|
|
||||||
function compileMatcher(matcherText: string | undefined): RegExp | undefined {
|
|
||||||
if (matcherText === undefined) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
return new RegExp(matcherText);
|
|
||||||
} catch {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function createHook(
|
|
||||||
eventName: HookEventName,
|
|
||||||
matcherText: string | undefined,
|
|
||||||
command: string,
|
|
||||||
source: string,
|
|
||||||
projectDir: string,
|
|
||||||
): ResolvedCommandHook | undefined {
|
|
||||||
const matcher = compileMatcher(matcherText);
|
|
||||||
if (matcherText !== undefined && matcher === undefined) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
eventName,
|
|
||||||
matcher,
|
|
||||||
matcherText,
|
|
||||||
command: resolveHookCommand(command, projectDir),
|
|
||||||
source,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function getHookEntries(
|
|
||||||
hooksRecord: Record<string, unknown>,
|
|
||||||
eventName: HookEventName,
|
|
||||||
): unknown[] {
|
|
||||||
const keys =
|
|
||||||
eventName === "PostToolUse"
|
|
||||||
? ["PostToolUse", "postToolUse"]
|
|
||||||
: ["PostToolUseFailure", "postToolUseFailure"];
|
|
||||||
|
|
||||||
for (const key of keys) {
|
|
||||||
const value = hooksRecord[key];
|
|
||||||
if (Array.isArray(value)) {
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
function parseClaudeSettingsHooks(
|
|
||||||
config: unknown,
|
|
||||||
source: string,
|
|
||||||
projectDir: string,
|
|
||||||
): ResolvedCommandHook[] {
|
|
||||||
const root = asRecord(config);
|
|
||||||
const hooksRoot = root ? asRecord(root.hooks) : undefined;
|
|
||||||
if (!hooksRoot) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
const hooks: ResolvedCommandHook[] = [];
|
|
||||||
const events: HookEventName[] = ["PostToolUse", "PostToolUseFailure"];
|
|
||||||
|
|
||||||
for (const eventName of events) {
|
|
||||||
const entries = getHookEntries(hooksRoot, eventName);
|
|
||||||
for (const entry of entries) {
|
|
||||||
const entryRecord = asRecord(entry);
|
|
||||||
if (!entryRecord || !Array.isArray(entryRecord.hooks)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const matcherText =
|
|
||||||
typeof entryRecord.matcher === "string" ? entryRecord.matcher : undefined;
|
|
||||||
for (const nestedHook of entryRecord.hooks) {
|
|
||||||
const nestedHookRecord = asRecord(nestedHook);
|
|
||||||
if (!nestedHookRecord) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (nestedHookRecord.type !== "command") {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (typeof nestedHookRecord.command !== "string") {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const hook = createHook(
|
|
||||||
eventName,
|
|
||||||
matcherText,
|
|
||||||
nestedHookRecord.command,
|
|
||||||
source,
|
|
||||||
projectDir,
|
|
||||||
);
|
|
||||||
if (hook) {
|
|
||||||
hooks.push(hook);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return hooks;
|
|
||||||
}
|
|
||||||
|
|
||||||
function parseSimpleHooksFile(
|
|
||||||
config: unknown,
|
|
||||||
source: string,
|
|
||||||
projectDir: string,
|
|
||||||
): ResolvedCommandHook[] {
|
|
||||||
const root = asRecord(config);
|
|
||||||
const hooksRoot = root ? asRecord(root.hooks) : undefined;
|
|
||||||
if (!hooksRoot) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
const hooks: ResolvedCommandHook[] = [];
|
|
||||||
const events: HookEventName[] = ["PostToolUse", "PostToolUseFailure"];
|
|
||||||
|
|
||||||
for (const eventName of events) {
|
|
||||||
const entries = getHookEntries(hooksRoot, eventName);
|
|
||||||
for (const entry of entries) {
|
|
||||||
const entryRecord = asRecord(entry);
|
|
||||||
if (!entryRecord || typeof entryRecord.command !== "string") {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const matcherText =
|
|
||||||
typeof entryRecord.matcher === "string" ? entryRecord.matcher : undefined;
|
|
||||||
const hook = createHook(
|
|
||||||
eventName,
|
|
||||||
matcherText,
|
|
||||||
entryRecord.command,
|
|
||||||
source,
|
|
||||||
projectDir,
|
|
||||||
);
|
|
||||||
if (hook) {
|
|
||||||
hooks.push(hook);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return hooks;
|
|
||||||
}
|
|
||||||
|
|
||||||
function loadHooks(cwd: string): HookState {
|
|
||||||
const projectDir = findProjectDir(cwd);
|
|
||||||
const claudeSettingsPath = join(projectDir, ".claude", "settings.json");
|
|
||||||
const ruleSyncHooksPath = join(projectDir, ".rulesync", "hooks.json");
|
|
||||||
const piHooksPath = join(projectDir, ".pi", "hooks.json");
|
|
||||||
|
|
||||||
const hooks: ResolvedCommandHook[] = [];
|
|
||||||
|
|
||||||
const claudeSettings = readJsonFile(claudeSettingsPath);
|
|
||||||
if (claudeSettings !== undefined) {
|
|
||||||
hooks.push(...parseClaudeSettingsHooks(claudeSettings, claudeSettingsPath, projectDir));
|
|
||||||
}
|
|
||||||
|
|
||||||
const ruleSyncHooks = readJsonFile(ruleSyncHooksPath);
|
|
||||||
if (ruleSyncHooks !== undefined) {
|
|
||||||
hooks.push(...parseSimpleHooksFile(ruleSyncHooks, ruleSyncHooksPath, projectDir));
|
|
||||||
}
|
|
||||||
|
|
||||||
const piHooks = readJsonFile(piHooksPath);
|
|
||||||
if (piHooks !== undefined) {
|
|
||||||
hooks.push(...parseSimpleHooksFile(piHooks, piHooksPath, projectDir));
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
projectDir,
|
|
||||||
hooks,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function toClaudeToolName(toolName: string): string {
|
|
||||||
if (toolName === "ls") {
|
|
||||||
return "LS";
|
|
||||||
}
|
|
||||||
if (toolName.length === 0) {
|
|
||||||
return toolName;
|
|
||||||
}
|
|
||||||
return toolName[0].toUpperCase() + toolName.slice(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
function matchesHook(hook: ResolvedCommandHook, toolName: string): boolean {
|
|
||||||
if (!hook.matcher) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
const claudeToolName = toClaudeToolName(toolName);
|
|
||||||
hook.matcher.lastIndex = 0;
|
|
||||||
if (hook.matcher.test(toolName)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
hook.matcher.lastIndex = 0;
|
|
||||||
return hook.matcher.test(claudeToolName);
|
|
||||||
}
|
|
||||||
|
|
||||||
function extractTextContent(content: unknown): string {
|
|
||||||
if (!Array.isArray(content)) {
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
|
|
||||||
const parts: string[] = [];
|
|
||||||
for (const item of content) {
|
|
||||||
if (!item || typeof item !== "object") {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const itemRecord = item as Record<string, unknown>;
|
|
||||||
if (itemRecord.type === "text" && typeof itemRecord.text === "string") {
|
|
||||||
parts.push(itemRecord.text);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return parts.join("\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
function normalizeToolInput(input: Record<string, unknown>): Record<string, unknown> {
|
|
||||||
const normalized: Record<string, unknown> = { ...input };
|
|
||||||
const pathValue = typeof input.path === "string" ? input.path : undefined;
|
|
||||||
|
|
||||||
if (pathValue !== undefined) {
|
|
||||||
normalized.file_path = pathValue;
|
|
||||||
normalized.filePath = pathValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
return normalized;
|
|
||||||
}
|
|
||||||
|
|
||||||
function buildToolResponse(
|
|
||||||
event: ToolResultEvent,
|
|
||||||
normalizedInput: Record<string, unknown>,
|
|
||||||
): Record<string, unknown> {
|
|
||||||
const response: Record<string, unknown> = {
|
|
||||||
is_error: event.isError,
|
|
||||||
isError: event.isError,
|
|
||||||
content: event.content,
|
|
||||||
text: extractTextContent(event.content),
|
|
||||||
details: event.details ?? null,
|
|
||||||
};
|
|
||||||
|
|
||||||
const filePath =
|
|
||||||
typeof normalizedInput.file_path === "string" ? normalizedInput.file_path : undefined;
|
|
||||||
if (filePath !== undefined) {
|
|
||||||
response.file_path = filePath;
|
|
||||||
response.filePath = filePath;
|
|
||||||
}
|
|
||||||
|
|
||||||
return response;
|
|
||||||
}
|
|
||||||
|
|
||||||
function buildHookPayload(
|
|
||||||
event: ToolResultEvent,
|
|
||||||
eventName: HookEventName,
|
|
||||||
ctx: ExtensionContext,
|
|
||||||
projectDir: string,
|
|
||||||
): Record<string, unknown> {
|
|
||||||
const normalizedInput = normalizeToolInput(event.input);
|
|
||||||
const sessionId = ctx.sessionManager.getSessionFile() ?? "ephemeral";
|
|
||||||
|
|
||||||
return {
|
|
||||||
session_id: sessionId,
|
|
||||||
cwd: ctx.cwd,
|
|
||||||
claude_project_dir: projectDir,
|
|
||||||
hook_event_name: eventName,
|
|
||||||
tool_name: toClaudeToolName(event.toolName),
|
|
||||||
tool_call_id: event.toolCallId,
|
|
||||||
tool_input: normalizedInput,
|
|
||||||
tool_response: buildToolResponse(event, normalizedInput),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function runCommandHook(
|
|
||||||
command: string,
|
|
||||||
cwd: string,
|
|
||||||
payload: Record<string, unknown>,
|
|
||||||
): Promise<CommandRunResult> {
|
|
||||||
return new Promise((resolve) => {
|
|
||||||
const startedAt = Date.now();
|
|
||||||
const child = spawn("bash", ["-lc", command], {
|
|
||||||
cwd,
|
|
||||||
env: { ...process.env, CLAUDE_PROJECT_DIR: cwd },
|
|
||||||
stdio: ["pipe", "pipe", "pipe"],
|
|
||||||
});
|
|
||||||
|
|
||||||
let stdout = "";
|
|
||||||
let stderr = "";
|
|
||||||
let timedOut = false;
|
|
||||||
let resolved = false;
|
|
||||||
|
|
||||||
const finish = (code: number) => {
|
|
||||||
if (resolved) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
resolved = true;
|
|
||||||
resolve({
|
|
||||||
code,
|
|
||||||
stdout,
|
|
||||||
stderr,
|
|
||||||
elapsedMs: Date.now() - startedAt,
|
|
||||||
timedOut,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const timeout = setTimeout(() => {
|
|
||||||
timedOut = true;
|
|
||||||
child.kill("SIGTERM");
|
|
||||||
|
|
||||||
const killTimer = setTimeout(() => {
|
|
||||||
child.kill("SIGKILL");
|
|
||||||
}, 1000);
|
|
||||||
(killTimer as NodeJS.Timeout & { unref?: () => void }).unref?.();
|
|
||||||
}, HOOK_TIMEOUT_MS);
|
|
||||||
(timeout as NodeJS.Timeout & { unref?: () => void }).unref?.();
|
|
||||||
|
|
||||||
child.stdout.on("data", (chunk: Buffer) => {
|
|
||||||
stdout += chunk.toString("utf8");
|
|
||||||
});
|
|
||||||
child.stderr.on("data", (chunk: Buffer) => {
|
|
||||||
stderr += chunk.toString("utf8");
|
|
||||||
});
|
|
||||||
|
|
||||||
child.on("error", (error) => {
|
|
||||||
clearTimeout(timeout);
|
|
||||||
stderr += `${error.message}\n`;
|
|
||||||
finish(-1);
|
|
||||||
});
|
|
||||||
|
|
||||||
child.on("close", (code) => {
|
|
||||||
clearTimeout(timeout);
|
|
||||||
finish(code ?? -1);
|
|
||||||
});
|
|
||||||
|
|
||||||
try {
|
|
||||||
child.stdin.write(JSON.stringify(payload));
|
|
||||||
child.stdin.end();
|
|
||||||
} catch (error) {
|
|
||||||
const message = error instanceof Error ? error.message : String(error);
|
|
||||||
stderr += `${message}\n`;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function hookEventNameForResult(event: ToolResultEvent): HookEventName {
|
|
||||||
return event.isError ? "PostToolUseFailure" : "PostToolUse";
|
|
||||||
}
|
|
||||||
|
|
||||||
function formatDuration(elapsedMs: number): string {
|
|
||||||
if (elapsedMs < 1000) {
|
|
||||||
return `${elapsedMs}ms`;
|
|
||||||
}
|
|
||||||
return `${(elapsedMs / 1000).toFixed(1)}s`;
|
|
||||||
}
|
|
||||||
|
|
||||||
function hookName(command: string): string {
|
|
||||||
const shPathMatch = command.match(/[^\s|;&]+\.sh\b/);
|
|
||||||
if (shPathMatch) {
|
|
||||||
return basename(shPathMatch[0]);
|
|
||||||
}
|
|
||||||
|
|
||||||
const firstToken = command.trim().split(/\s+/)[0] ?? "hook";
|
|
||||||
return basename(firstToken);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function(pi: ExtensionAPI) {
|
|
||||||
let state: HookState = {
|
|
||||||
projectDir: process.cwd(),
|
|
||||||
hooks: [],
|
|
||||||
};
|
|
||||||
|
|
||||||
const refreshHooks = (cwd: string) => {
|
|
||||||
state = loadHooks(cwd);
|
|
||||||
};
|
|
||||||
|
|
||||||
pi.on("session_start", (_event, ctx) => {
|
|
||||||
refreshHooks(ctx.cwd);
|
|
||||||
});
|
|
||||||
|
|
||||||
pi.on("session_switch", (_event, ctx) => {
|
|
||||||
refreshHooks(ctx.cwd);
|
|
||||||
});
|
|
||||||
|
|
||||||
pi.on("tool_result", async (event, ctx) => {
|
|
||||||
if (state.hooks.length === 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const eventName = hookEventNameForResult(event);
|
|
||||||
const matchingHooks = state.hooks.filter(
|
|
||||||
(hook) => hook.eventName === eventName && matchesHook(hook, event.toolName),
|
|
||||||
);
|
|
||||||
if (matchingHooks.length === 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const payload = buildHookPayload(event, eventName, ctx, state.projectDir);
|
|
||||||
const executedCommands = new Set<string>();
|
|
||||||
|
|
||||||
for (const hook of matchingHooks) {
|
|
||||||
if (executedCommands.has(hook.command)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
executedCommands.add(hook.command);
|
|
||||||
|
|
||||||
const result = await runCommandHook(hook.command, state.projectDir, payload);
|
|
||||||
const name = hookName(hook.command);
|
|
||||||
const duration = formatDuration(result.elapsedMs);
|
|
||||||
|
|
||||||
if (result.code === 0) {
|
|
||||||
ctx.ui.notify(` Hook \`${name}\` executed, took ${duration}`, "info");
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const matcherLabel = hook.matcherText ?? "*";
|
|
||||||
const errorLine =
|
|
||||||
result.stderr.trim() || result.stdout.trim() || `exit code ${result.code}`;
|
|
||||||
ctx.ui.notify(
|
|
||||||
` Hook \`${name}\` failed after ${duration} (${matcherLabel}) from ${hook.source}: ${errorLine}`,
|
|
||||||
"warning",
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
@@ -13,11 +13,10 @@
|
|||||||
"vscode-languageserver-protocol": "^3.17.5"
|
"vscode-languageserver-protocol": "^3.17.5"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@mariozechner/pi-ai": "^0.63.1",
|
"@mariozechner/pi-ai": "^0.56.3",
|
||||||
"@mariozechner/pi-coding-agent": "^0.63.1",
|
"@mariozechner/pi-coding-agent": "^0.56.3",
|
||||||
"@mariozechner/pi-tui": "^0.63.1",
|
"@mariozechner/pi-tui": "^0.56.3",
|
||||||
"@types/node": "^25.3.3",
|
"@types/node": "^25.3.3",
|
||||||
"@types/turndown": "^5.0.6",
|
|
||||||
"typescript": "^5.7.0"
|
"typescript": "^5.7.0"
|
||||||
},
|
},
|
||||||
"pi": {},
|
"pi": {},
|
||||||
|
|||||||
@@ -211,12 +211,12 @@ function updateWidget(ctx: ExtensionContext): void {
|
|||||||
(resetMs > 0 ? theme.fg("dim", ` (resets in ${resetSec}s)`) : ""),
|
(resetMs > 0 ? theme.fg("dim", ` (resets in ${resetSec}s)`) : ""),
|
||||||
);
|
);
|
||||||
|
|
||||||
ctx.ui.setWidget("web-activity", lines);
|
ctx.ui.setWidget("web-activity", new Text(lines.join("\n"), 0, 0));
|
||||||
}
|
}
|
||||||
|
|
||||||
function formatEntryLine(
|
function formatEntryLine(
|
||||||
entry: ActivityEntry,
|
entry: ActivityEntry,
|
||||||
theme: ExtensionContext["ui"]["theme"],
|
theme: { fg: (color: string, text: string) => string },
|
||||||
): string {
|
): string {
|
||||||
const typeStr = entry.type === "api" ? "API" : "GET";
|
const typeStr = entry.type === "api" ? "API" : "GET";
|
||||||
const target =
|
const target =
|
||||||
@@ -550,7 +550,7 @@ export default function (pi: ExtensionAPI) {
|
|||||||
} else {
|
} else {
|
||||||
widgetUnsubscribe?.();
|
widgetUnsubscribe?.();
|
||||||
widgetUnsubscribe = null;
|
widgetUnsubscribe = null;
|
||||||
ctx.ui.setWidget("web-activity", undefined);
|
ctx.ui.setWidget("web-activity", null);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@@ -598,7 +598,7 @@ export default function (pi: ExtensionAPI) {
|
|||||||
})),
|
})),
|
||||||
}),
|
}),
|
||||||
|
|
||||||
async execute(_toolCallId, params, signal, onUpdate, ctx): Promise<any> {
|
async execute(_toolCallId, params, signal, onUpdate, ctx) {
|
||||||
const queryList = params.queries ?? (params.query ? [params.query] : []);
|
const queryList = params.queries ?? (params.query ? [params.query] : []);
|
||||||
const isMultiQuery = queryList.length > 1;
|
const isMultiQuery = queryList.length > 1;
|
||||||
const shouldCurate = params.curate !== false && ctx?.hasUI !== false;
|
const shouldCurate = params.curate !== false && ctx?.hasUI !== false;
|
||||||
@@ -613,10 +613,7 @@ export default function (pi: ExtensionAPI) {
|
|||||||
if (shouldCurate) {
|
if (shouldCurate) {
|
||||||
closeCurator();
|
closeCurator();
|
||||||
|
|
||||||
let resolvePromise!: (value: unknown) => void;
|
const { promise, resolve: resolvePromise } = Promise.withResolvers<unknown>();
|
||||||
const promise = new Promise<unknown>((resolve) => {
|
|
||||||
resolvePromise = resolve;
|
|
||||||
});
|
|
||||||
const includeContent = params.includeContent ?? false;
|
const includeContent = params.includeContent ?? false;
|
||||||
const searchResults = new Map<number, QueryResultData>();
|
const searchResults = new Map<number, QueryResultData>();
|
||||||
const allUrls: string[] = [];
|
const allUrls: string[] = [];
|
||||||
@@ -640,7 +637,7 @@ export default function (pi: ExtensionAPI) {
|
|||||||
queryList,
|
queryList,
|
||||||
includeContent,
|
includeContent,
|
||||||
numResults: params.numResults,
|
numResults: params.numResults,
|
||||||
recencyFilter: params.recencyFilter as "day" | "week" | "month" | "year" | undefined,
|
recencyFilter: params.recencyFilter,
|
||||||
domainFilter: params.domainFilter,
|
domainFilter: params.domainFilter,
|
||||||
availableProviders,
|
availableProviders,
|
||||||
defaultProvider,
|
defaultProvider,
|
||||||
@@ -687,7 +684,7 @@ export default function (pi: ExtensionAPI) {
|
|||||||
const { answer, results } = await search(queryList[qi], {
|
const { answer, results } = await search(queryList[qi], {
|
||||||
provider: defaultProvider as SearchProvider | undefined,
|
provider: defaultProvider as SearchProvider | undefined,
|
||||||
numResults: params.numResults,
|
numResults: params.numResults,
|
||||||
recencyFilter: params.recencyFilter as "day" | "week" | "month" | "year" | undefined,
|
recencyFilter: params.recencyFilter,
|
||||||
domainFilter: params.domainFilter,
|
domainFilter: params.domainFilter,
|
||||||
signal,
|
signal,
|
||||||
});
|
});
|
||||||
@@ -757,7 +754,7 @@ export default function (pi: ExtensionAPI) {
|
|||||||
text = `${searchResults.size} searches (${totalSources} sources) · ${curateLabel} to review · sending in ${remaining}s`;
|
text = `${searchResults.size} searches (${totalSources} sources) · ${curateLabel} to review · sending in ${remaining}s`;
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
content: [{ type: "text" as const, text }],
|
content: [{ type: "text", text }],
|
||||||
details: {
|
details: {
|
||||||
phase: "curate-window",
|
phase: "curate-window",
|
||||||
searchCount: searchResults.size,
|
searchCount: searchResults.size,
|
||||||
@@ -827,7 +824,7 @@ export default function (pi: ExtensionAPI) {
|
|||||||
const { answer, results } = await search(query, {
|
const { answer, results } = await search(query, {
|
||||||
provider: resolvedProvider as SearchProvider | undefined,
|
provider: resolvedProvider as SearchProvider | undefined,
|
||||||
numResults: params.numResults,
|
numResults: params.numResults,
|
||||||
recencyFilter: params.recencyFilter as "day" | "week" | "month" | "year" | undefined,
|
recencyFilter: params.recencyFilter,
|
||||||
domainFilter: params.domainFilter,
|
domainFilter: params.domainFilter,
|
||||||
signal,
|
signal,
|
||||||
});
|
});
|
||||||
@@ -1120,10 +1117,7 @@ export default function (pi: ExtensionAPI) {
|
|||||||
`Use get_search_content({ responseId: "${responseId}", urlIndex: 0 }) for full content.`;
|
`Use get_search_content({ responseId: "${responseId}", urlIndex: 0 }) for full content.`;
|
||||||
}
|
}
|
||||||
|
|
||||||
const content: Array<
|
const content: Array<{ type: string; text?: string; data?: string; mimeType?: string }> = [];
|
||||||
| { type: "image"; data: string; mimeType: string }
|
|
||||||
| { type: "text"; text: string }
|
|
||||||
> = [];
|
|
||||||
if (result.frames?.length) {
|
if (result.frames?.length) {
|
||||||
for (const frame of result.frames) {
|
for (const frame of result.frames) {
|
||||||
content.push({ type: "image", data: frame.data, mimeType: frame.mimeType });
|
content.push({ type: "image", data: frame.data, mimeType: frame.mimeType });
|
||||||
@@ -1296,7 +1290,7 @@ export default function (pi: ExtensionAPI) {
|
|||||||
urlIndex: Type.Optional(Type.Number({ description: "Get content for URL at index" })),
|
urlIndex: Type.Optional(Type.Number({ description: "Get content for URL at index" })),
|
||||||
}),
|
}),
|
||||||
|
|
||||||
async execute(_toolCallId, params, _signal, _onUpdate, _ctx): Promise<any> {
|
async execute(_toolCallId, params) {
|
||||||
const data = getResult(params.responseId);
|
const data = getResult(params.responseId);
|
||||||
if (!data) {
|
if (!data) {
|
||||||
return {
|
return {
|
||||||
@@ -1483,7 +1477,7 @@ export default function (pi: ExtensionAPI) {
|
|||||||
pi.sendMessage({
|
pi.sendMessage({
|
||||||
customType: "web-search-results",
|
customType: "web-search-results",
|
||||||
content: [{ type: "text", text }],
|
content: [{ type: "text", text }],
|
||||||
display: true,
|
display: "tool",
|
||||||
details: { queryCount: results.length, totalResults: urls.length },
|
details: { queryCount: results.length, totalResults: urls.length },
|
||||||
}, { triggerTurn: true, deliverAs: "followUp" });
|
}, { triggerTurn: true, deliverAs: "followUp" });
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -42,10 +42,9 @@ export async function extractPDFToMarkdown(
|
|||||||
|
|
||||||
const pdf = await getDocumentProxy(new Uint8Array(buffer));
|
const pdf = await getDocumentProxy(new Uint8Array(buffer));
|
||||||
const metadata = await pdf.getMetadata();
|
const metadata = await pdf.getMetadata();
|
||||||
const info = (metadata.info ?? {}) as Record<string, unknown>;
|
|
||||||
|
|
||||||
// Extract title from metadata or URL
|
// Extract title from metadata or URL
|
||||||
const metaTitle = typeof info.Title === "string" ? info.Title : undefined;
|
const metaTitle = metadata.info?.Title as string | undefined;
|
||||||
const urlTitle = extractTitleFromURL(url);
|
const urlTitle = extractTitleFromURL(url);
|
||||||
const title = metaTitle?.trim() || urlTitle;
|
const title = metaTitle?.trim() || urlTitle;
|
||||||
|
|
||||||
@@ -80,9 +79,8 @@ export async function extractPDFToMarkdown(
|
|||||||
lines.push("");
|
lines.push("");
|
||||||
lines.push(`> Source: ${url}`);
|
lines.push(`> Source: ${url}`);
|
||||||
lines.push(`> Pages: ${pdf.numPages}${truncated ? ` (extracted first ${pagesToExtract})` : ""}`);
|
lines.push(`> Pages: ${pdf.numPages}${truncated ? ` (extracted first ${pagesToExtract})` : ""}`);
|
||||||
const author = typeof info.Author === "string" ? info.Author : undefined;
|
if (metadata.info?.Author) {
|
||||||
if (author) {
|
lines.push(`> Author: ${metadata.info.Author}`);
|
||||||
lines.push(`> Author: ${author}`);
|
|
||||||
}
|
}
|
||||||
lines.push("");
|
lines.push("");
|
||||||
lines.push("---");
|
lines.push("---");
|
||||||
|
|||||||
@@ -245,8 +245,8 @@ export async function condenseSearchResults(
|
|||||||
const model = ctx.modelRegistry.find(provider, modelId);
|
const model = ctx.modelRegistry.find(provider, modelId);
|
||||||
if (!model) return null;
|
if (!model) return null;
|
||||||
|
|
||||||
const auth = await ctx.modelRegistry.getApiKeyAndHeaders(model);
|
const apiKey = await ctx.modelRegistry.getApiKey(model);
|
||||||
if (!auth.ok) return null;
|
if (!apiKey) return null;
|
||||||
|
|
||||||
const queryData = [...results.entries()]
|
const queryData = [...results.entries()]
|
||||||
.sort((a, b) => a[0] - b[0])
|
.sort((a, b) => a[0] - b[0])
|
||||||
@@ -281,8 +281,7 @@ export async function condenseSearchResults(
|
|||||||
: timeoutSignal;
|
: timeoutSignal;
|
||||||
|
|
||||||
const response = await complete(model, aiContext, {
|
const response = await complete(model, aiContext, {
|
||||||
apiKey: auth.apiKey,
|
apiKey,
|
||||||
headers: auth.headers,
|
|
||||||
signal: combinedSignal,
|
signal: combinedSignal,
|
||||||
max_tokens: MAX_TOKENS,
|
max_tokens: MAX_TOKENS,
|
||||||
} as any);
|
} as any);
|
||||||
|
|||||||
Generated
+22
-31
@@ -34,20 +34,17 @@ importers:
|
|||||||
version: 3.17.5
|
version: 3.17.5
|
||||||
devDependencies:
|
devDependencies:
|
||||||
'@mariozechner/pi-ai':
|
'@mariozechner/pi-ai':
|
||||||
specifier: ^0.63.1
|
specifier: ^0.56.3
|
||||||
version: 0.63.1(ws@8.19.0)(zod@4.3.6)
|
version: 0.56.3(ws@8.19.0)(zod@4.3.6)
|
||||||
'@mariozechner/pi-coding-agent':
|
'@mariozechner/pi-coding-agent':
|
||||||
specifier: ^0.63.1
|
specifier: ^0.56.3
|
||||||
version: 0.63.1(ws@8.19.0)(zod@4.3.6)
|
version: 0.56.3(ws@8.19.0)(zod@4.3.6)
|
||||||
'@mariozechner/pi-tui':
|
'@mariozechner/pi-tui':
|
||||||
specifier: ^0.63.1
|
specifier: ^0.56.3
|
||||||
version: 0.63.1
|
version: 0.56.3
|
||||||
'@types/node':
|
'@types/node':
|
||||||
specifier: ^25.3.3
|
specifier: ^25.3.3
|
||||||
version: 25.3.3
|
version: 25.3.3
|
||||||
'@types/turndown':
|
|
||||||
specifier: ^5.0.6
|
|
||||||
version: 5.0.6
|
|
||||||
typescript:
|
typescript:
|
||||||
specifier: ^5.7.0
|
specifier: ^5.7.0
|
||||||
version: 5.9.3
|
version: 5.9.3
|
||||||
@@ -292,22 +289,22 @@ packages:
|
|||||||
resolution: {integrity: sha512-faGUlTcXka5l7rv0lP3K3vGW/ejRuOS24RR2aSFWREUQqzjgdsuWNo/IiPqL3kWRGt6Ahl2+qcDAwtdeWeuGUw==}
|
resolution: {integrity: sha512-faGUlTcXka5l7rv0lP3K3vGW/ejRuOS24RR2aSFWREUQqzjgdsuWNo/IiPqL3kWRGt6Ahl2+qcDAwtdeWeuGUw==}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
'@mariozechner/pi-agent-core@0.63.1':
|
'@mariozechner/pi-agent-core@0.56.3':
|
||||||
resolution: {integrity: sha512-h0B20xfs/iEVR2EC4gwiE8hKI1TPeB8REdRJMgV+uXKH7gpeIZ9+s8Dp9nX35ZR0QUjkNey2+ULk2DxQtdg14Q==}
|
resolution: {integrity: sha512-TsI1zENf3wqqKPaERnj486Q4i6Y/y6lAZipLNcfDYUDxDrLwNfQ9EW9xukkbJfTZ8zjG3VZ2pBZe3C7wM51dVQ==}
|
||||||
engines: {node: '>=20.0.0'}
|
engines: {node: '>=20.0.0'}
|
||||||
|
|
||||||
'@mariozechner/pi-ai@0.63.1':
|
'@mariozechner/pi-ai@0.56.3':
|
||||||
resolution: {integrity: sha512-wjgwY+yfrFO6a9QdAfjWpH7iSrDean6GsKDDMohNcLCy6PreMxHOZvNM0NwJARL1tZoZovr7ikAQfLGFZbnjsw==}
|
resolution: {integrity: sha512-l4J+cVyVeBLAlGOY/osGDvsbTz0DySCQmR171G6SdbPvIeLGhIi6siZ+zHwq91GJYjv/wtu/08M08ag2mGZKeA==}
|
||||||
engines: {node: '>=20.0.0'}
|
engines: {node: '>=20.0.0'}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
'@mariozechner/pi-coding-agent@0.63.1':
|
'@mariozechner/pi-coding-agent@0.56.3':
|
||||||
resolution: {integrity: sha512-XSoMyLtuMA7ePK1UBWqSJ/BBdtBdJUHY9nbtnNyG6GeW7Gbgd+iqljIuwmAUf8wlYL981UIfYM/WIPQ6t/dIxw==}
|
resolution: {integrity: sha512-yHgnadye+TT/4NWKBirZUjw/LWdNWTa7M4HJdX2RxRbwuj4q7RZ0Aqy+lQbOHEPDQYhxK3kZb9hjiAbbGficZQ==}
|
||||||
engines: {node: '>=20.6.0'}
|
engines: {node: '>=20.6.0'}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
'@mariozechner/pi-tui@0.63.1':
|
'@mariozechner/pi-tui@0.56.3':
|
||||||
resolution: {integrity: sha512-G5p+eh1EPkFCNaaggX6vRrqttnDscK6npgmEOknoCQXZtch8XNgh9Lf3VJ0A2lZXSgR7IntG5dfXHPH/Ki64wA==}
|
resolution: {integrity: sha512-eZ1P9QRKHp78hwx+lITr/mujZqe+eCwL/bOS9vXXkFP070RW4VYum0j7TJ4BrFEH/nNkXRS1tYCXYU05une1bA==}
|
||||||
engines: {node: '>=20.0.0'}
|
engines: {node: '>=20.0.0'}
|
||||||
|
|
||||||
'@mistralai/mistralai@1.14.1':
|
'@mistralai/mistralai@1.14.1':
|
||||||
@@ -571,9 +568,6 @@ packages:
|
|||||||
'@types/retry@0.12.0':
|
'@types/retry@0.12.0':
|
||||||
resolution: {integrity: sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==}
|
resolution: {integrity: sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==}
|
||||||
|
|
||||||
'@types/turndown@5.0.6':
|
|
||||||
resolution: {integrity: sha512-ru00MoyeeouE5BX4gRL+6m/BsDfbRayOskWqUvh7CLGW+UXxHQItqALa38kKnOiZPqJrtzJUgAC2+F0rL1S4Pg==}
|
|
||||||
|
|
||||||
'@types/yauzl@2.10.3':
|
'@types/yauzl@2.10.3':
|
||||||
resolution: {integrity: sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==}
|
resolution: {integrity: sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==}
|
||||||
|
|
||||||
@@ -1728,9 +1722,9 @@ snapshots:
|
|||||||
std-env: 3.10.0
|
std-env: 3.10.0
|
||||||
yoctocolors: 2.1.2
|
yoctocolors: 2.1.2
|
||||||
|
|
||||||
'@mariozechner/pi-agent-core@0.63.1(ws@8.19.0)(zod@4.3.6)':
|
'@mariozechner/pi-agent-core@0.56.3(ws@8.19.0)(zod@4.3.6)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@mariozechner/pi-ai': 0.63.1(ws@8.19.0)(zod@4.3.6)
|
'@mariozechner/pi-ai': 0.56.3(ws@8.19.0)(zod@4.3.6)
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- '@modelcontextprotocol/sdk'
|
- '@modelcontextprotocol/sdk'
|
||||||
- aws-crt
|
- aws-crt
|
||||||
@@ -1740,7 +1734,7 @@ snapshots:
|
|||||||
- ws
|
- ws
|
||||||
- zod
|
- zod
|
||||||
|
|
||||||
'@mariozechner/pi-ai@0.63.1(ws@8.19.0)(zod@4.3.6)':
|
'@mariozechner/pi-ai@0.56.3(ws@8.19.0)(zod@4.3.6)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@anthropic-ai/sdk': 0.73.0(zod@4.3.6)
|
'@anthropic-ai/sdk': 0.73.0(zod@4.3.6)
|
||||||
'@aws-sdk/client-bedrock-runtime': 3.1002.0
|
'@aws-sdk/client-bedrock-runtime': 3.1002.0
|
||||||
@@ -1764,14 +1758,13 @@ snapshots:
|
|||||||
- ws
|
- ws
|
||||||
- zod
|
- zod
|
||||||
|
|
||||||
'@mariozechner/pi-coding-agent@0.63.1(ws@8.19.0)(zod@4.3.6)':
|
'@mariozechner/pi-coding-agent@0.56.3(ws@8.19.0)(zod@4.3.6)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@mariozechner/jiti': 2.6.5
|
'@mariozechner/jiti': 2.6.5
|
||||||
'@mariozechner/pi-agent-core': 0.63.1(ws@8.19.0)(zod@4.3.6)
|
'@mariozechner/pi-agent-core': 0.56.3(ws@8.19.0)(zod@4.3.6)
|
||||||
'@mariozechner/pi-ai': 0.63.1(ws@8.19.0)(zod@4.3.6)
|
'@mariozechner/pi-ai': 0.56.3(ws@8.19.0)(zod@4.3.6)
|
||||||
'@mariozechner/pi-tui': 0.63.1
|
'@mariozechner/pi-tui': 0.56.3
|
||||||
'@silvia-odwyer/photon-node': 0.3.4
|
'@silvia-odwyer/photon-node': 0.3.4
|
||||||
ajv: 8.18.0
|
|
||||||
chalk: 5.6.2
|
chalk: 5.6.2
|
||||||
cli-highlight: 2.1.11
|
cli-highlight: 2.1.11
|
||||||
diff: 8.0.3
|
diff: 8.0.3
|
||||||
@@ -1797,7 +1790,7 @@ snapshots:
|
|||||||
- ws
|
- ws
|
||||||
- zod
|
- zod
|
||||||
|
|
||||||
'@mariozechner/pi-tui@0.63.1':
|
'@mariozechner/pi-tui@0.56.3':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/mime-types': 2.1.4
|
'@types/mime-types': 2.1.4
|
||||||
chalk: 5.6.2
|
chalk: 5.6.2
|
||||||
@@ -2173,8 +2166,6 @@ snapshots:
|
|||||||
|
|
||||||
'@types/retry@0.12.0': {}
|
'@types/retry@0.12.0': {}
|
||||||
|
|
||||||
'@types/turndown@5.0.6': {}
|
|
||||||
|
|
||||||
'@types/yauzl@2.10.3':
|
'@types/yauzl@2.10.3':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/node': 25.3.3
|
'@types/node': 25.3.3
|
||||||
|
|||||||
@@ -135,11 +135,11 @@ export default function(pi: ExtensionAPI) {
|
|||||||
|
|
||||||
// Fire-and-forget: run auto-naming in background without blocking
|
// Fire-and-forget: run auto-naming in background without blocking
|
||||||
const doAutoName = async () => {
|
const doAutoName = async () => {
|
||||||
const auth = await ctx.modelRegistry.getApiKeyAndHeaders(AUTO_NAME_MODEL);
|
const apiKey = await ctx.modelRegistry.getApiKey(AUTO_NAME_MODEL);
|
||||||
log(`Got API key: ${auth.ok ? "yes" : "no"}`);
|
log(`Got API key: ${apiKey ? "yes" : "no"}`);
|
||||||
|
|
||||||
if (!auth.ok) {
|
if (!apiKey) {
|
||||||
log(`No API key available, aborting: ${auth.error}`);
|
log("No API key available, aborting");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -157,7 +157,7 @@ export default function(pi: ExtensionAPI) {
|
|||||||
const response = await complete(
|
const response = await complete(
|
||||||
AUTO_NAME_MODEL,
|
AUTO_NAME_MODEL,
|
||||||
{ systemPrompt: SYSTEM_PROMPT, messages: [userMessage] },
|
{ systemPrompt: SYSTEM_PROMPT, messages: [userMessage] },
|
||||||
{ apiKey: auth.apiKey, headers: auth.headers },
|
{ apiKey },
|
||||||
);
|
);
|
||||||
|
|
||||||
log(`Response received, stopReason: ${response.stopReason}`);
|
log(`Response received, stopReason: ${response.stopReason}`);
|
||||||
@@ -273,8 +273,7 @@ export default function(pi: ExtensionAPI) {
|
|||||||
loader.onAbort = () => done(null);
|
loader.onAbort = () => done(null);
|
||||||
|
|
||||||
const doGenerate = async () => {
|
const doGenerate = async () => {
|
||||||
const auth = await ctx.modelRegistry.getApiKeyAndHeaders(AUTO_NAME_MODEL);
|
const apiKey = await ctx.modelRegistry.getApiKey(AUTO_NAME_MODEL);
|
||||||
if (!auth.ok) throw new Error(auth.error);
|
|
||||||
|
|
||||||
const userMessage: Message = {
|
const userMessage: Message = {
|
||||||
role: "user",
|
role: "user",
|
||||||
@@ -290,7 +289,7 @@ export default function(pi: ExtensionAPI) {
|
|||||||
const response = await complete(
|
const response = await complete(
|
||||||
AUTO_NAME_MODEL,
|
AUTO_NAME_MODEL,
|
||||||
{ systemPrompt: SYSTEM_PROMPT, messages: [userMessage] },
|
{ systemPrompt: SYSTEM_PROMPT, messages: [userMessage] },
|
||||||
{ apiKey: auth.apiKey, headers: auth.headers, signal: loader.signal },
|
{ apiKey, signal: loader.signal },
|
||||||
);
|
);
|
||||||
|
|
||||||
if (response.stopReason === "aborted") {
|
if (response.stopReason === "aborted") {
|
||||||
|
|||||||
@@ -1,60 +0,0 @@
|
|||||||
import { existsSync, statSync } from "node:fs";
|
|
||||||
import { dirname, join, resolve } from "node:path";
|
|
||||||
import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
|
|
||||||
|
|
||||||
function isDirectory(path: string): boolean {
|
|
||||||
try {
|
|
||||||
return statSync(path).isDirectory();
|
|
||||||
} catch {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function walkUpDirectories(startDir: string, stopDir?: string): string[] {
|
|
||||||
const directories: string[] = [];
|
|
||||||
const hasStopDir = stopDir !== undefined;
|
|
||||||
|
|
||||||
let current = resolve(startDir);
|
|
||||||
let parent = dirname(current);
|
|
||||||
let reachedStopDir = hasStopDir && current === stopDir;
|
|
||||||
let reachedFilesystemRoot = parent === current;
|
|
||||||
|
|
||||||
directories.push(current);
|
|
||||||
while (!reachedStopDir && !reachedFilesystemRoot) {
|
|
||||||
current = parent;
|
|
||||||
parent = dirname(current);
|
|
||||||
reachedStopDir = hasStopDir && current === stopDir;
|
|
||||||
reachedFilesystemRoot = parent === current;
|
|
||||||
directories.push(current);
|
|
||||||
}
|
|
||||||
|
|
||||||
return directories;
|
|
||||||
}
|
|
||||||
|
|
||||||
function findNearestGitRoot(startDir: string): string | undefined {
|
|
||||||
for (const directory of walkUpDirectories(startDir)) {
|
|
||||||
if (existsSync(join(directory, ".git"))) {
|
|
||||||
return directory;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
function findClaudeSkillDirs(cwd: string): string[] {
|
|
||||||
const gitRoot = findNearestGitRoot(cwd);
|
|
||||||
|
|
||||||
return walkUpDirectories(cwd, gitRoot)
|
|
||||||
.map((directory) => join(directory, ".claude", "skills"))
|
|
||||||
.filter(isDirectory);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function(pi: ExtensionAPI) {
|
|
||||||
pi.on("resources_discover", (event) => {
|
|
||||||
const skillPaths = findClaudeSkillDirs(event.cwd);
|
|
||||||
if (skillPaths.length === 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
return { skillPaths };
|
|
||||||
});
|
|
||||||
}
|
|
||||||
@@ -6,7 +6,7 @@
|
|||||||
* - Injects timestamp markers without triggering extra turns
|
* - Injects timestamp markers without triggering extra turns
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type { ExtensionAPI, ExtensionContext } from "@mariozechner/pi-coding-agent";
|
import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
|
||||||
import { Box, Text } from "@mariozechner/pi-tui";
|
import { Box, Text } from "@mariozechner/pi-tui";
|
||||||
|
|
||||||
// Track session time
|
// Track session time
|
||||||
@@ -41,7 +41,12 @@ function formatDuration(ms: number): string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default function (pi: ExtensionAPI) {
|
export default function (pi: ExtensionAPI) {
|
||||||
const updateStatus = (ctx: ExtensionContext) => {
|
const updateStatus = (ctx: {
|
||||||
|
ui: {
|
||||||
|
setStatus: (id: string, text: string | undefined) => void;
|
||||||
|
theme: { fg: (color: string, text: string) => string };
|
||||||
|
};
|
||||||
|
}) => {
|
||||||
const elapsed = Date.now() - sessionStart;
|
const elapsed = Date.now() - sessionStart;
|
||||||
let status = ctx.ui.theme.fg("dim", `⏱ ${formatElapsed(elapsed)}`);
|
let status = ctx.ui.theme.fg("dim", `⏱ ${formatElapsed(elapsed)}`);
|
||||||
if (lastTurnDuration !== null) {
|
if (lastTurnDuration !== null) {
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
{
|
{
|
||||||
"app.model.select": "ctrl+space"
|
"selectModel": "ctrl+space"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,22 +1,28 @@
|
|||||||
layout {
|
layout {
|
||||||
default_tab_template {
|
default_tab_template {
|
||||||
children
|
children
|
||||||
pane size=1 borderless=true {
|
pane size=1 borderless=true {
|
||||||
plugin location="zellij:compact-bar"
|
plugin location="zellij:compact-bar"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
tab name="nvim + jjui" {
|
tab name="dotfiles" cwd="/home/thomasgl/.dotfiles" {
|
||||||
pane stacked=true {
|
pane split_direction="vertical" {
|
||||||
pane command="nvim"
|
pane stacked=true {
|
||||||
pane command="jjui"
|
pane
|
||||||
}
|
pane command="nvim"
|
||||||
}
|
}
|
||||||
|
pane size="40%" command="pi"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
tab name="pi + shell" {
|
tab name="NixOS" cwd="/home/thomasgl/etc/nixos" {
|
||||||
pane stacked=true {
|
pane split_direction="vertical" {
|
||||||
pane command="pi"
|
pane stacked=true {
|
||||||
pane
|
pane
|
||||||
}
|
pane command="nvim"
|
||||||
}
|
}
|
||||||
|
pane size="40%" command="pi"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user