diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..772ed03 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,30 @@ +name: CI + +on: + push: + pull_request: + +permissions: + contents: read + +jobs: + validate: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup Node + uses: actions/setup-node@v4 + with: + node-version: 22.19.0 + cache: npm + + - name: Install dependencies + run: npm ci --ignore-scripts + + - name: Typecheck + run: npm run typecheck + + - name: Test + run: npm test diff --git a/README.md b/README.md index 9c51976..0ea0f9b 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,14 @@ Multiple Pi sessions can stay alive concurrently. Exactly one session owns the t ![pi-sessions switcher UI](screenshot.png) +## Compatibility + +- Node.js `>=22.19.0` +- Tested with `@earendil-works/pi-coding-agent` and `@earendil-works/pi-tui` 0.84.4 +- No tmux, Zellij, Ghostty, or other external multiplexer is required + +`pi-sessions` uses pi's advanced runtime and interactive-mode APIs. It checks the host capabilities it relies on at startup. An incompatible host leaves pi running and shows a warning naming the missing capability instead of silently disabling behavior. + ## Install ```bash @@ -37,7 +45,7 @@ All session operations happen inside that switcher. - `Enter` — switch to selected live session. Selecting `parent` switches back to parent. - `Ctrl-O` — open `FileExplorer`; selecting a folder creates a new child session in that folder and switches to it. - `Ctrl-R` — open a one-off resume flow; selecting a saved Pi session opens it as a live child and switches to it. -- `Ctrl-K` — stop selected live child session. +- The configured `killKey` (`Ctrl-K` by default) — stop the selected live child session. - `Esc` — close switcher. ## Runtime model @@ -57,6 +65,36 @@ Child sessions are real native `InteractiveMode` instances, not embedded panels. ## Path locks -All live sessions share one in-process lock manager. Before write/edit/mutating shell tools run, `pi-sessions` checks for conflicting path locks and blocks conflicting writes. +All live sessions in one pi process share a lock manager. Before a write, edit, redirect, or recognized mutating shell command runs, `pi-sessions` checks for overlapping paths and blocks a conflicting tool call from another session. Recognized shell mutations include common filesystem commands plus mutating `git`, package-manager, `make`, `cargo`, `terraform`, and `dbt` operations. + +A lock starts at `tool_call` and is released at `tool_result`. It is a **per-tool-call race guard**, not transactional isolation across a session's multi-command workflow. Separate pi processes do not share locks. + +Use one live session per repository when a task requires a coherent sequence of reads and writes. Running multiple sessions in the same repository can still interleave changes between tool calls even though simultaneous conflicting calls are blocked. + +## Configuration + +Create `~/.pi/agent/pi-sessions.json` to override package-specific keys: + +```json +{ + "killKey": "ctrl+shift+k" +} +``` + +`killKey` uses pi's key format, such as `delete`, `ctrl+shift+k`, or `alt+k`. The default remains `ctrl+k`. Restart pi after changing this file. Invalid JSON or key names produce a visible warning and fall back to the default. + +## Development + +```bash +npm ci --ignore-scripts +npm run typecheck +npm test +``` + +Interactive changes also require a terminal smoke test: -This prevents two live sessions from editing the same path tree at once. +1. Start `pi -e `. +2. Open the switcher with `Ctrl-R`. +3. Spawn a child session in a selected folder. +4. Switch between the parent and child. +5. Confirm working and idle indicators follow agent activity. diff --git a/index.ts b/index.ts index b6ed6a8..b2b4b31 100644 --- a/index.ts +++ b/index.ts @@ -1,34 +1,166 @@ -// @ts-nocheck import fs from "node:fs"; import os from "node:os"; import path from "node:path"; -import { pathToFileURL } from "node:url"; +import { fileURLToPath } from "node:url"; +import * as PiCodingAgent from "@earendil-works/pi-coding-agent"; +import type { + AgentSessionRuntime, + AgentSessionRuntimeDiagnostic, + CreateAgentSessionFromServicesOptions, + CreateAgentSessionRuntimeFactory, + ExtensionAPI, + ExtensionCommandContext, + ExtensionContext, + SessionManager as SessionManagerType, + SettingsManager as SettingsManagerType, +} from "@earendil-works/pi-coding-agent"; +import type { ThinkingLevel } from "@earendil-works/pi-agent-core"; +import type { KeyId, TUI, Terminal } from "@earendil-works/pi-tui"; import { + SessionWidget, + showSessionsView, + type SavedSessionInfo, + type SessionInfo, + type WidgetSnapshot, +} from "./ui.ts"; + +const { createAgentSessionFromServices, createAgentSessionRuntime, createAgentSessionServices, getAgentDir, - getPackageDir, hasTrustRequiringProjectResources, InteractiveMode, ProjectTrustStore, SessionManager, SettingsManager, - type CreateAgentSessionRuntimeFactory, -} from "@earendil-works/pi-coding-agent"; -import { SessionWidget, showSessionsView } from "./ui.ts"; + resolveModelScopeWithDiagnostics, +} = PiCodingAgent; const PARENT_SESSION_ID = "__parent__"; const HOST_KEY = "__PI_SESSIONS_HOST__"; -const INTERACTIVE_MODE_SPINNER_PATCHED = Symbol.for( - "pi-sessions.interactiveMode.spinnerPatched", -); +const TESTED_PI_RANGE = ">=0.84.4"; +const DEFAULT_KILL_KEY: KeyId = "ctrl+k"; +const SPECIAL_KEYS = new Map([ + ["escape", "escape"], + ["esc", "esc"], + ["enter", "enter"], + ["return", "return"], + ["tab", "tab"], + ["space", "space"], + ["backspace", "backspace"], + ["delete", "delete"], + ["insert", "insert"], + ["clear", "clear"], + ["home", "home"], + ["end", "end"], + ["pageup", "pageUp"], + ["pagedown", "pageDown"], + ["up", "up"], + ["down", "down"], + ["left", "left"], + ["right", "right"], +]); + +export type PiSessionsConfig = { killKey: KeyId }; +type ConfigResult = { config: PiSessionsConfig; warning?: string }; + +function normalizeKeyId(value: string): KeyId | undefined { + let remaining = value.trim().toLowerCase(); + const modifiers: string[] = []; + while (true) { + const match = /^(ctrl|shift|alt|super)\+/.exec(remaining); + if (!match) break; + modifiers.push(match[1]!); + remaining = remaining.slice(match[0].length); + } + if (new Set(modifiers).size !== modifiers.length) return undefined; + const base = + SPECIAL_KEYS.get(remaining) ?? + (/^[a-z0-9]$/.test(remaining) || + /^f(?:[1-9]|1[0-2])$/.test(remaining) || + /^[`\-=\[\]\\;',./!@#$%^&*()_+|~{}:<>?]$/.test(remaining) + ? remaining + : undefined); + if (!base) return undefined; + return [...modifiers, base].join("+") as KeyId; +} + +export function parsePiSessionsConfig(value: unknown): ConfigResult { + if (typeof value !== "object" || value === null) { + return { + config: { killKey: DEFAULT_KILL_KEY }, + warning: "pi-sessions config must be a JSON object", + }; + } + const killKey = "killKey" in value ? value.killKey : undefined; + if (killKey === undefined) return { config: { killKey: DEFAULT_KILL_KEY } }; + const normalizedKillKey = + typeof killKey === "string" ? normalizeKeyId(killKey) : undefined; + if (!normalizedKillKey) { + return { + config: { killKey: DEFAULT_KILL_KEY }, + warning: `invalid pi-sessions killKey ${JSON.stringify(killKey)}; using ${DEFAULT_KILL_KEY}`, + }; + } + return { config: { killKey: normalizedKillKey } }; +} + +function loadPiSessionsConfig(): ConfigResult { + const configPath = path.join(getAgentDir(), "pi-sessions.json"); + try { + return parsePiSessionsConfig(JSON.parse(fs.readFileSync(configPath, "utf8"))); + } catch (error) { + if ((error as NodeJS.ErrnoException).code === "ENOENT") { + return { config: { killKey: DEFAULT_KILL_KEY } }; + } + return { + config: { killKey: DEFAULT_KILL_KEY }, + warning: `could not load ${configPath}: ${error instanceof Error ? error.message : String(error)}`, + }; + } +} +export const EXTENSION_PATH = fileURLToPath(import.meta.url); +export function getChildResourceLoaderOptions(): { + additionalExtensionPaths: string[]; +} { + return { additionalExtensionPaths: [EXTENSION_PATH] }; +} + +const REQUIRED_PI_EXPORTS = [ + "createAgentSessionFromServices", + "createAgentSessionRuntime", + "createAgentSessionServices", + "getAgentDir", + "hasTrustRequiringProjectResources", + "InteractiveMode", + "ProjectTrustStore", + "SessionManager", + "SettingsManager", + "resolveModelScopeWithDiagnostics", +] as const; -type ExtensionAPI = any; -type CommandContext = any; type Activity = "idle" | "working" | "waiting"; type LiveState = "active" | "suspended" | "starting" | "stopped" | "error"; -type WorkingIndicatorOptions = { frames?: string[]; intervalMs?: number }; +type SessionOptions = Pick< + CreateAgentSessionFromServicesOptions, + "model" | "thinkingLevel" | "scopedModels" | "tools" +>; +type RuntimeInheritance = { + ctx?: ExtensionContext; + sessionOptions?: SessionOptions; +}; +type InteractiveUI = { + terminal?: Pick; + start?: () => void; + stop?: () => void; + requestRender?: (force?: boolean) => void; +}; +type InteractiveModeAccess = { + ui?: InteractiveUI; + run: () => Promise; + stop?: () => void; +}; type LiveSessionRecord = { id: string; @@ -45,18 +177,44 @@ type LiveSessionRecord = { lastActivityAt: number; status?: string; transcript?: string; - runtime?: any; - mode?: any; + runtime?: AgentSessionRuntime; + mode?: InteractiveModeAccess; adapter?: InteractiveModeAdapter; - sessionManager?: any; - context?: CommandContext; - inheritance?: any; + sessionManager?: SessionManagerType | ExtensionContext["sessionManager"]; + context?: ExtensionContext; + inheritance?: RuntimeInheritance; started?: boolean; runPromise?: Promise; expectedStop?: boolean; error?: string; }; +function debugFailure(scope: string, error: unknown): void { + if (process.env.PI_SESSIONS_DEBUG) { + console.debug(`[pi-sessions] ${scope}:`, error); + } +} + +export function findCompatibilityIssues( + piModule: Partial>, + interactivePrototype: { run?: unknown; stop?: unknown } | undefined, +): string[] { + const issues = REQUIRED_PI_EXPORTS.filter( + (name) => typeof piModule[name] === "undefined", + ).map((name) => `missing export ${name}`); + if (typeof interactivePrototype?.run !== "function") { + issues.push("missing InteractiveMode.prototype.run"); + } + if (typeof interactivePrototype?.stop !== "function") { + issues.push("missing InteractiveMode.prototype.stop"); + } + return issues; +} + +function getCompatibilityIssues(): string[] { + return findCompatibilityIssues(PiCodingAgent, InteractiveMode?.prototype); +} + function readFirstMessage(filePath: string | undefined): string { if (!filePath) return ""; try { @@ -73,14 +231,27 @@ function readFirstMessage(filePath: string | undefined): string { ? msg.content : Array.isArray(msg.content) ? msg.content - .filter((p: any) => p.type === "text") - .map((p: any) => p.text) + .filter( + (p: unknown): p is { type: "text"; text: string } => + typeof p === "object" && + p !== null && + "type" in p && + p.type === "text" && + "text" in p && + typeof p.text === "string", + ) + .map((p: { type: "text"; text: string }) => p.text) .join(" ") : ""; if (text.trim()) return text.trim().slice(0, 200); - } catch {} + } catch (error) { + // Session logs may contain a partially written final line. + debugFailure("ignored malformed session-log line", error); + } } - } catch {} + } catch (error) { + debugFailure("could not read session transcript", error); + } return ""; } @@ -101,57 +272,61 @@ function sanitizeName(name: string): string { ); } -let modelResolverPromise: Promise | null = null; -const runtimeInheritanceBySessionManager = new WeakMap(); - -async function loadModelResolver(): Promise { - modelResolverPromise ??= import( - pathToFileURL(path.join(getPackageDir(), "dist/core/model-resolver.js")) - .href - ); - return await modelResolverPromise; -} +const runtimeInheritanceBySessionManager = new WeakMap< + object, + RuntimeInheritance +>(); -function sameModel(a: any, b: any): boolean { +function sameModel( + a: NonNullable | undefined, + b: NonNullable | undefined, +): boolean { return !!a && !!b && a.provider === b.provider && a.id === b.id; } -function hasExistingMessages(sessionManager: any): boolean { - return (sessionManager.buildSessionContext?.().messages?.length ?? 0) > 0; +function hasExistingMessages( + sessionManager: SessionManagerType | ExtensionContext["sessionManager"], +): boolean { + return sessionManager.getBranch().some((entry) => entry.type === "message"); } -function inferThinkingLevel(ctx: CommandContext): string | undefined { +function inferThinkingLevel(ctx: ExtensionContext): ThinkingLevel | undefined { const branch = ctx.sessionManager?.getBranch?.() ?? []; for (let i = branch.length - 1; i >= 0; i--) { const entry = branch[i]; if (entry?.type === "thinking_level_change" && entry.thinkingLevel) { - return entry.thinkingLevel; + return entry.thinkingLevel as ThinkingLevel; } } return undefined; } -function collectRuntimeInheritance(ctx?: CommandContext): any { +function collectRuntimeInheritance( + ctx?: ExtensionContext, +): RuntimeInheritance { if (!ctx) return {}; - const promptOptions = ctx.getSystemPromptOptions?.() ?? {}; - const sessionOptions: any = {}; - if (Array.isArray(promptOptions.selectedTools)) { - sessionOptions.tools = [...promptOptions.selectedTools]; + const sessionOptions: SessionOptions = {}; + if ("getSystemPromptOptions" in ctx) { + const promptOptions = ( + ctx as ExtensionCommandContext + ).getSystemPromptOptions(); + if (Array.isArray(promptOptions.selectedTools)) { + sessionOptions.tools = [...promptOptions.selectedTools]; + } } if (ctx.model) sessionOptions.model = ctx.model; const thinkingLevel = inferThinkingLevel(ctx); if (thinkingLevel) sessionOptions.thinkingLevel = thinkingLevel; - return { - ctx, - authStorage: ctx.modelRegistry?.authStorage, - sessionOptions, - }; + return { ctx, sessionOptions }; } -function safeCollectRuntimeInheritance(ctx?: CommandContext): any { +function safeCollectRuntimeInheritance( + ctx?: ExtensionContext, +): RuntimeInheritance { try { return collectRuntimeInheritance(ctx); - } catch { + } catch (error) { + debugFailure("could not collect inherited runtime options", error); return {}; } } @@ -159,15 +334,18 @@ function safeCollectRuntimeInheritance(ctx?: CommandContext): any { function createInheritedSettingsManager( cwd: string, agentDir: string, - inheritance: any, -): { settingsManager: any; diagnostics: any[] } { - const diagnostics: any[] = []; + inheritance: RuntimeInheritance, +): { + settingsManager: SettingsManagerType; + diagnostics: AgentSessionRuntimeDiagnostic[]; +} { + const diagnostics: AgentSessionRuntimeDiagnostic[] = []; const sameCwd = - inheritance?.ctx?.cwd && + inheritance.ctx !== undefined && path.resolve(inheritance.ctx.cwd) === path.resolve(cwd); let projectTrusted = true; - if (sameCwd) { - projectTrusted = inheritance.ctx.isProjectTrusted?.() ?? true; + if (sameCwd && inheritance.ctx) { + projectTrusted = inheritance.ctx.isProjectTrusted(); } else if (hasTrustRequiringProjectResources(cwd)) { const trustStore = new ProjectTrustStore(agentDir); projectTrusted = trustStore.get(cwd) === true; @@ -185,11 +363,11 @@ function createInheritedSettingsManager( } async function resolveChildSessionOptions( - services: any, - sessionManager: any, - inheritance: any, -): Promise { - const options: any = { ...(inheritance?.sessionOptions ?? {}) }; + services: PiCodingAgent.AgentSessionServices, + sessionManager: SessionManagerType, + inheritance: RuntimeInheritance, +): Promise { + const options: SessionOptions = { ...(inheritance.sessionOptions ?? {}) }; const existing = hasExistingMessages(sessionManager); if (existing) { delete options.model; @@ -199,11 +377,9 @@ async function resolveChildSessionOptions( const patterns = services.settingsManager?.getEnabledModels?.(); if (!patterns?.length) return options; - const { resolveModelScope } = await loadModelResolver(); - const scopedModels = await resolveModelScope( - patterns, - services.modelRegistry, - ); + const { scopedModels, diagnostics } = + await resolveModelScopeWithDiagnostics(patterns, services.modelRuntime); + services.diagnostics.push(...diagnostics); if (!scopedModels.length) return options; options.scopedModels = scopedModels; @@ -213,13 +389,11 @@ async function resolveChildSessionOptions( const savedModelId = services.settingsManager?.getDefaultModel?.(); const savedModel = savedProvider && savedModelId - ? services.modelRegistry.find(savedProvider, savedModelId) + ? services.modelRuntime.getModel(savedProvider, savedModelId) : undefined; const selected = - scopedModels.find((scoped: any) => - sameModel(scoped.model, inheritedModel), - ) ?? - scopedModels.find((scoped: any) => sameModel(scoped.model, savedModel)) ?? + scopedModels.find((scoped) => sameModel(scoped.model, inheritedModel)) ?? + scopedModels.find((scoped) => sameModel(scoped.model, savedModel)) ?? scopedModels[0]; options.model = selected.model; if (selected.thinkingLevel) options.thinkingLevel = selected.thinkingLevel; @@ -232,34 +406,84 @@ function asString(value: unknown): string | null { return typeof value === "string" && value.trim() ? value : null; } -function inferToolPaths(toolName: string, input: any): string[] { +type ToolInput = Record; + +function shellTokens(command: string): string[] { + return [...command.matchAll(/"(?:\\.|[^"\\])*"|'[^']*'|[^\s;&|]+/g)].map( + (match) => match[0].replace(/^["']|["']$/g, ""), + ); +} + +function mutatesWorkingTreeByDefault(command: string): boolean { + return ( + /\bgit\s+(add|am|apply|branch\s+(-[dDmM]|--delete|--move)|checkout|cherry-pick|clean|commit|merge|mv|rebase|reset|restore|revert|rm|stash|switch|tag)\b/.test( + command, + ) || + /\b(npm|pnpm|yarn|bun)\s+(add|install|link|remove|uninstall|update|upgrade)\b/.test( + command, + ) || + /\bmake\b(?![^;&|]*(?:--dry-run|-n)\b)/.test(command) || + /\bcargo\s+(add|build|clean|fix|install|remove|run|update)\b/.test(command) || + /\bterraform\s+(apply|destroy|fmt|import|taint|untaint)\b/.test(command) || + /\bdbt\s+(build|clean|deps|docs\s+generate|run|seed|snapshot|test)\b/.test( + command, + ) + ); +} + +function isMutatingShellCommand(command: string): boolean { + if ( + /\b(rm|mv|cp|touch|mkdir|rmdir|chmod|chown|install|tee)\b/.test( + command, + ) || + /\b(sed|perl)\s+-[^\s]*i\b/.test(command) || + /\bpython(?:3)?\b[^;&|]*\b(open|write)\b/.test(command) || + /\bnode\b[^;&|]*\b(writeFile|appendFile|mkdir|rm)\b/.test(command) + ) { + return true; + } + return mutatesWorkingTreeByDefault(command); +} + +export function inferToolPaths( + toolName: string, + input: ToolInput, +): string[] { const paths = new Set(); if (toolName === "write" || toolName === "edit") { const p = - asString(input?.path) || - asString(input?.file_path) || - asString(input?.filePath); + asString(input.path) || + asString(input.file_path) || + asString(input.filePath); if (p) paths.add(p); } if (toolName === "bash") { - const command = asString(input?.command) || ""; - const redir = [...command.matchAll(/(?:>|>>|2>|&>)\s*([^\s;&|]+)/g)].map( - (m) => m[1], - ); - for (const p of redir) { - if (p && !p.startsWith("/dev/")) paths.add(p.replace(/^["']|["']$/g, "")); + const command = asString(input.command) || ""; + const redirections = [ + ...command.matchAll(/(?:^|\s)(?:>|>>|2>|&>)\s*("[^"]+"|'[^']+'|[^\s;&|]+)/g), + ]; + for (const match of redirections) { + const target = match[1]?.replace(/^["']|["']$/g, ""); + if (target && !target.startsWith("/dev/")) paths.add(target); } - const mutating = - /\b(rm|mv|cp|touch|mkdir|rmdir|chmod|chown|install|tee|sed\s+-i|perl\s+-i|python\b.*\b(open|write)|node\b.*writeFile)\b/.test( - command, - ); - if (mutating) { - const tokens = command.match(/(?:\.\.?|~|\/)?[\w@%+=:,./-]+/g) || []; + if (isMutatingShellCommand(command)) { + const pathsBeforeTokenInference = paths.size; + const tokens = shellTokens(command); for (const token of tokens) { - if (token.includes("/") || token.startsWith(".")) - paths.add(token.replace(/^["']|["']$/g, "")); + if ( + !token.startsWith("-") && + !token.includes("|") && + (token.includes("/") || token.startsWith(".")) + ) { + paths.add(token); + } + } + if ( + mutatesWorkingTreeByDefault(command) || + paths.size === pathsBeforeTokenInference + ) { + paths.add("."); } - if (paths.size === 0) paths.add("."); } } return [...paths]; @@ -267,7 +491,7 @@ function inferToolPaths(toolName: string, input: any): string[] { function needsPermission( toolName: string, - input: any, + input: ToolInput, sessionName: string, ): string | null { if (toolName === "bash") { @@ -282,7 +506,10 @@ function needsPermission( function resetExtendedKeyboardModesForHandoff(): void { try { process.stdout.write("\x1b[<999u\x1b[>4;0m"); - } catch {} + } catch (error) { + // A closed stdout during process teardown cannot be reported safely. + debugFailure("could not reset terminal keyboard modes", error); + } } function normalizeLockPath(p: string, cwd: string): string | null { @@ -297,17 +524,31 @@ function pathsConflict(a: string, b: string): boolean { return a === b || a.startsWith(br) || b.startsWith(ar); } -class LockManager { - locks = new Map(); +type LockConflict = { + path: string; + heldPath: string; + by: string; +}; +type LockResult = + | { ok: true; paths: string[] } + | { ok: false; conflicts: LockConflict[] }; + +export class LockManager { + locks = new Map< + string, + { sessionId: string; acquiredAt: number; acquisitions: number } + >(); heldByToolCall = new Map(); - acquire(sessionId: string, rawPaths: string[], cwd: string) { + acquire(sessionId: string, rawPaths: string[], cwd: string): LockResult { const paths = [ ...new Set( - (rawPaths || []).map((p) => normalizeLockPath(p, cwd)).filter(Boolean), + (rawPaths || []) + .map((p) => normalizeLockPath(p, cwd)) + .filter((p): p is string => p !== null), ), ].sort(); - const conflicts = []; + const conflicts: LockConflict[] = []; for (const p of paths) { for (const [held, info] of this.locks.entries()) { if (info.sessionId !== sessionId && pathsConflict(p, held)) { @@ -317,23 +558,33 @@ class LockManager { } if (conflicts.length) return { ok: false, conflicts }; const acquiredAt = Date.now(); - for (const p of paths) this.locks.set(p, { sessionId, acquiredAt }); + for (const p of paths) { + const existing = this.locks.get(p); + if (existing?.sessionId === sessionId) { + existing.acquisitions++; + } else { + this.locks.set(p, { sessionId, acquiredAt, acquisitions: 1 }); + } + } return { ok: true, paths }; } - release(sessionId: string, rawPaths?: string[]) { + release(sessionId: string, rawPaths?: string[]): string[] { const wanted = rawPaths?.length ? new Set(rawPaths) : null; - const released = []; + const released: string[] = []; for (const [p, info] of this.locks.entries()) { - if (info.sessionId === sessionId && (!wanted || wanted.has(p))) { + if (info.sessionId !== sessionId || (wanted && !wanted.has(p))) continue; + if (wanted && info.acquisitions > 1) { + info.acquisitions--; + } else { this.locks.delete(p); - released.push(p); } + released.push(p); } return released; } - releaseByToolCall(toolCallId: string) { + releaseByToolCall(toolCallId: string): string[] { const held = this.heldByToolCall.get(toolCallId); if (!held) return []; this.heldByToolCall.delete(toolCallId); @@ -344,18 +595,18 @@ class LockManager { class InteractiveModeAdapter { state: "never-started" | "active" | "suspended" | "stopped" = "never-started"; private terminalGateInstalled = false; - private originalSetProgress?: any; - private originalSetTitle?: any; + private originalSetProgress?: Terminal["setProgress"]; + private originalSetTitle?: Terminal["setTitle"]; constructor( readonly id: string, - readonly runtime: any, - readonly mode: any, + readonly runtime: AgentSessionRuntime, + readonly mode: InteractiveModeAccess, private readonly host: PiSessionsHost, ) {} - get ui(): any { - return (this.mode as any).ui; + get ui(): InteractiveUI | undefined { + return this.mode.ui; } installTerminalGate(): void { @@ -365,14 +616,16 @@ class InteractiveModeAdapter { this.terminalGateInstalled = true; this.originalSetProgress = terminal.setProgress?.bind(terminal); this.originalSetTitle = terminal.setTitle?.bind(terminal); - if (this.originalSetProgress) { + const setProgress = this.originalSetProgress; + if (setProgress) { terminal.setProgress = (active: boolean) => { - if (this.host.activeId === this.id) this.originalSetProgress(active); + if (this.host.activeId === this.id) setProgress(active); }; } - if (this.originalSetTitle) { - terminal.setTitle = (...args: any[]) => { - if (this.host.activeId === this.id) this.originalSetTitle(...args); + const setTitle = this.originalSetTitle; + if (setTitle) { + terminal.setTitle = (...args: Parameters) => { + if (this.host.activeId === this.id) setTitle(...args); }; } } @@ -385,9 +638,9 @@ class InteractiveModeAdapter { if (record) { record.started = true; record.state = "active"; - record.runPromise = this.mode.run().catch((error: any) => { + record.runPromise = this.mode.run().catch((error: unknown) => { record.state = record.expectedStop ? "stopped" : "error"; - record.error = String(error?.message || error); + record.error = error instanceof Error ? error.message : String(error); record.status = record.error; this.host.locks.release(record.id); this.host.notify(); @@ -402,7 +655,10 @@ class InteractiveModeAdapter { try { this.ui?.stop?.(); resetExtendedKeyboardModesForHandoff(); - } catch {} + } catch (error) { + // Terminal handoff cleanup is best-effort; throwing here strands raw mode. + debugFailure("could not suspend child TUI", error); + } this.state = "suspended"; const record = this.host.get(this.id); if (record && record.state !== "stopped" && record.state !== "error") @@ -415,7 +671,10 @@ class InteractiveModeAdapter { try { this.ui?.start?.(); this.ui?.requestRender?.(true); - } catch {} + } catch (error) { + // Resume may race with shutdown; preserve host state and report in debug mode. + debugFailure("could not resume child TUI", error); + } this.state = "active"; const record = this.host.get(this.id); if (record) record.state = "active"; @@ -430,14 +689,17 @@ class InteractiveModeAdapter { if (ui && originalUiStop && !canTouchTerminal) { ui.stop = () => {}; } - this.mode?.stop?.(); - } catch { + this.mode.stop?.(); + } catch (error) { + debugFailure("could not stop child interactive mode", error); } finally { if (ui && originalUiStop) ui.stop = originalUiStop; } try { - await this.runtime?.dispose?.(); - } catch {} + await this.runtime.dispose(); + } catch (error) { + debugFailure("could not dispose child runtime", error); + } } } @@ -470,11 +732,13 @@ const createRuntime: CreateAgentSessionRuntimeFactory = async ({ const services = await createAgentSessionServices({ cwd, agentDir, - authStorage: inheritance.authStorage, settingsManager: inheritedSettings.settingsManager, + // Child runtimes have a fresh resource loader. Re-add this extension so + // commands, shortcuts, event handlers, and widgets survive cwd changes. + resourceLoaderOptions: getChildResourceLoaderOptions(), }); services.diagnostics.push(...inheritedSettings.diagnostics); - let sessionOptions: any = {}; + let sessionOptions: SessionOptions = {}; try { sessionOptions = await resolveChildSessionOptions( services, @@ -500,17 +764,16 @@ const createRuntime: CreateAgentSessionRuntimeFactory = async ({ }; }; -class PiSessionsHost { +export class PiSessionsHost { activeId = PARENT_SESSION_ID; records = new Map(); subscribers = new Set<() => void>(); locks = new LockManager(); - parentTui: any = null; + parentTui: TUI | null = null; parentDone: (() => void) | null = null; parentHandoffActive = false; activationInProgress: Promise | null = null; queuedActivation: string | null = null; - workingIndicator: WorkingIndicatorOptions | undefined = undefined; constructor() { this.records.set(PARENT_SESSION_ID, { @@ -523,7 +786,6 @@ class PiSessionsHost { createdAt: Date.now(), lastActivityAt: Date.now(), status: "parent", - pid: process.pid, }); } @@ -543,11 +805,14 @@ class PiSessionsHost { for (const listener of [...this.subscribers]) { try { listener(); - } catch {} + } catch (error) { + // One broken widget subscriber must not suppress updates to the others. + debugFailure("session-widget subscriber failed", error); + } } } - publicSession(record: LiveSessionRecord): any { + publicSession(record: LiveSessionRecord): SessionInfo { return { id: record.id, name: record.name, @@ -561,7 +826,7 @@ class PiSessionsHost { }; } - snapshot(): any { + snapshot(): WidgetSnapshot { return { attached: this.activeId, updatedAt: Date.now(), @@ -574,10 +839,10 @@ class PiSessionsHost { const children = [...this.records.values()].filter( (r) => r.kind === "child" && !["stopped", "error"].includes(r.state), ); - return [parent, ...children].filter(Boolean); + return parent ? [parent, ...children] : children; } - registerParent(ctx: CommandContext): void { + registerParent(ctx: ExtensionContext): void { const record = this.records.get(PARENT_SESSION_ID)!; record.cwd = ctx.cwd || process.cwd(); record.context = ctx; @@ -595,7 +860,7 @@ class PiSessionsHost { private updateChildFromContext( child: LiveSessionRecord, - ctx: CommandContext, + ctx: ExtensionContext, ): LiveSessionRecord { child.context = ctx; child.cwd = ctx.cwd || child.cwd; @@ -612,7 +877,7 @@ class PiSessionsHost { return child; } - bindSessionContext(ctx: CommandContext): LiveSessionRecord { + bindSessionContext(ctx: ExtensionContext): LiveSessionRecord { const sessionId = ctx.sessionManager?.getSessionId?.(); const sessionFile = ctx.sessionManager?.getSessionFile?.(); const child = [...this.records.values()].find( @@ -645,19 +910,19 @@ class PiSessionsHost { return parent; } - updateActivity(ctx: CommandContext, activity: Activity): void { + updateActivity(ctx: ExtensionContext, activity: Activity): void { const record = this.bindSessionContext(ctx); record.activity = activity; record.lastActivityAt = Date.now(); this.notify(); } - currentContextId(ctx: CommandContext): string { + currentContextId(ctx: ExtensionContext): string { return this.bindSessionContext(ctx).id; } async createChildFromContext( - ctx: CommandContext, + ctx: ExtensionContext, cwd: string, ): Promise { this.bindSessionContext(ctx); @@ -673,7 +938,7 @@ class PiSessionsHost { async openSavedSessionAsLive( sessionPath: string, cwdOverride?: string, - ctx?: CommandContext, + ctx?: ExtensionContext, ): Promise { const existing = [...this.records.values()].find( (r) => @@ -704,9 +969,9 @@ class PiSessionsHost { private async createRecordForSessionManager(opts: { name: string; cwd: string; - sessionManager: any; + sessionManager: SessionManagerType; parent?: LiveSessionRecord; - inheritance?: any; + inheritance?: RuntimeInheritance; }): Promise { const id = `${sanitizeName(opts.name)}-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 7)}`; const record: LiveSessionRecord = { @@ -741,7 +1006,7 @@ class PiSessionsHost { cwd: opts.cwd, agentDir: getAgentDir(), sessionManager: opts.sessionManager, - sessionStartEvent: { type: "session_start", reason: "startup" } as any, + sessionStartEvent: { type: "session_start", reason: "startup" }, }); const mode = new InteractiveMode(runtime, { migratedProviders: [], @@ -750,9 +1015,15 @@ class PiSessionsHost { initialImages: [], initialMessages: [], }); + const modeAccess = mode as unknown as InteractiveModeAccess; record.runtime = runtime; - record.mode = mode; - record.adapter = new InteractiveModeAdapter(id, runtime, mode, this); + record.mode = modeAccess; + record.adapter = new InteractiveModeAdapter( + id, + runtime, + modeAccess, + this, + ); record.state = "suspended"; record.transcript = resolveTranscriptName( opts.sessionManager.getSessionName?.(), @@ -771,13 +1042,27 @@ class PiSessionsHost { record.state = "stopped"; record.status = "stopped"; this.locks.release(record.id); + if (wasActive) { + // A command cannot await disposal of the runtime executing that command. + // Hand terminal ownership back first, let the command return, then dispose. + await this.activate(PARENT_SESSION_ID); + this.records.delete(record.id); + this.notify(); + const adapter = record.adapter; + setTimeout(() => { + void adapter?.dispose().catch((error: unknown) => { + debugFailure("could not dispose active child session", error); + }); + }, 0); + return; + } try { - if (wasActive) record.adapter?.suspend(); await record.adapter?.dispose(); - } catch {} + } catch (error) { + debugFailure("could not stop child session", error); + } this.records.delete(record.id); this.notify(); - if (wasActive) await this.activate(PARENT_SESSION_ID); } async activate(targetIdOrName: string): Promise { @@ -810,7 +1095,9 @@ class PiSessionsHost { this.parentTui?.terminal?.setProgress?.(false); this.parentTui?.start?.(); this.parentTui?.requestRender?.(true); - } catch {} + } catch (error) { + debugFailure("could not resume parent TUI", error); + } const done = this.parentDone; this.parentTui = null; this.parentDone = null; @@ -827,27 +1114,38 @@ class PiSessionsHost { this.notify(); } - async enterFromParent(ctx: CommandContext, targetId: string): Promise { + async enterFromParent( + ctx: ExtensionContext, + targetId: string, + ): Promise { if (this.parentHandoffActive) return this.activate(targetId); - await ctx.ui.custom( - (tui: any, _theme: any, _keybindings: any, done: () => void) => { + await ctx.ui.custom( + (tui, _theme, _keybindings, done) => { this.parentTui = tui; - this.parentDone = done; + this.parentDone = () => done(undefined); this.parentHandoffActive = true; try { tui.stop(); resetExtendedKeyboardModesForHandoff(); - } catch {} - void this.activate(targetId).catch((error) => { + } catch (error) { + // The child cannot own the terminal until the parent releases it. + debugFailure("could not release parent TUI", error); + } + void this.activate(targetId).catch((error: unknown) => { try { tui.start(); tui.requestRender(true); - } catch {} + } catch (resumeError) { + debugFailure("could not restore parent TUI", resumeError); + } this.parentHandoffActive = false; this.parentTui = null; this.parentDone = null; - ctx.ui.notify(String(error?.message || error), "error"); - done(); + ctx.ui.notify( + error instanceof Error ? error.message : String(error), + "error", + ); + done(undefined); }); return { render: () => [], invalidate: () => {}, dispose: () => {} }; }, @@ -855,7 +1153,7 @@ class PiSessionsHost { } async activateFromContext( - ctx: CommandContext, + ctx: ExtensionContext, targetId: string, ): Promise { const current = this.currentContextId(ctx); @@ -868,48 +1166,18 @@ class PiSessionsHost { } function getHost(): PiSessionsHost { - const g = globalThis as any; - if (!g[HOST_KEY]) g[HOST_KEY] = new PiSessionsHost(); - return g[HOST_KEY]; -} - -function patchInteractiveModeWorkingIndicator(host: PiSessionsHost): void { - const proto = (InteractiveMode as any)?.prototype; - if ( - !proto || - proto[INTERACTIVE_MODE_SPINNER_PATCHED] || - typeof proto.setWorkingIndicator !== "function" - ) { - return; - } - const original = proto.setWorkingIndicator; - proto.setWorkingIndicator = function (options?: WorkingIndicatorOptions) { - const source = [...host.records.values()].find((record) => record.mode === this); - const teardownReset = - options === undefined && - source !== undefined && - (source.expectedStop === true || - source.state === "stopped" || - source.state === "error"); - if (!teardownReset) { - host.workingIndicator = options; - host.notify(); - } - return original.call(this, options); + const shared = globalThis as typeof globalThis & { + [HOST_KEY]?: PiSessionsHost; }; - proto[INTERACTIVE_MODE_SPINNER_PATCHED] = true; + shared[HOST_KEY] ??= new PiSessionsHost(); + return shared[HOST_KEY]; } -function installWidget(ctx: CommandContext, host: PiSessionsHost): void { - ctx.ui.setWidget("pi-sessions", (tui: any, theme: any) => { +function installWidget(ctx: ExtensionContext, host: PiSessionsHost): void { + ctx.ui.setWidget("pi-sessions", (tui, theme) => { const requestRender = () => tui.requestRender(); const unsubscribe = host.subscribe(requestRender); - const widget = new SessionWidget( - theme, - () => host.snapshot(), - requestRender, - () => host.workingIndicator, - ); + const widget = new SessionWidget(theme, () => host.snapshot(), requestRender); return { render: (width: number) => widget.render(width), invalidate: () => widget.invalidate(), @@ -921,20 +1189,22 @@ function installWidget(ctx: CommandContext, host: PiSessionsHost): void { }); } -async function getResumeSessions(): Promise { +async function getResumeSessions(): Promise { const sessions = await SessionManager.listAll(); return sessions.sort( - (a: any, b: any) => Number(b.modified) - Number(a.modified), + (a, b) => Number(b.modified) - Number(a.modified), ); } async function openSessions( - ctx: CommandContext, + ctx: ExtensionContext, host: PiSessionsHost, + config: PiSessionsConfig, ): Promise { let targetToActivate: string | null = null; let targetToKill: string | null = null; await showSessionsView(ctx, { + killKey: config.killKey, getSessions: async () => host.listLive().map((record) => host.publicSession(record)), getResumeSessions, @@ -975,50 +1245,64 @@ async function openSessions( notify: (message: string, type?: "info" | "warning" | "error") => ctx.ui.notify(message, type || "info"), }); - if (targetToKill) { - await host.stopChild(targetToKill); + const killTarget = targetToKill as string | null; + if (killTarget) { + await host.stopChild(killTarget); return; } - if (!targetToActivate || targetToActivate === host.activeId) return; - await host.activateFromContext(ctx, targetToActivate); + const activationTarget = targetToActivate as string | null; + if (!activationTarget || activationTarget === host.activeId) return; + await host.activateFromContext(ctx, activationTarget); } -export default function (pi: ExtensionAPI) { - const host = getHost(); - patchInteractiveModeWorkingIndicator(host); +export function registerPiSessions( + pi: ExtensionAPI, + compatibilityIssues = getCompatibilityIssues(), +): void { + if (compatibilityIssues.length > 0) { + pi.on("session_start", (_event, ctx) => { + ctx.ui.notify( + `pi-sessions disabled: incompatible pi host (${TESTED_PI_RANGE}); ${compatibilityIssues.join(", ")}`, + "warning", + ); + }); + return; + } + const host = getHost(); + const { config, warning: configWarning } = loadPiSessionsConfig(); pi.registerCommand("sessions", { description: "Open the pi-sessions switcher", - handler: async (_args: string, ctx: CommandContext) => - openSessions(ctx, host), + handler: async (_args, ctx) => openSessions(ctx, host, config), }); pi.registerShortcut("ctrl+r", { description: "Open sessions switcher", - handler: async (ctx: CommandContext) => openSessions(ctx, host), + handler: async (ctx) => openSessions(ctx, host, config), }); - pi.on("session_start", (_event: any, ctx: CommandContext) => { + pi.on("session_start", (_event, ctx) => { host.bindSessionContext(ctx); installWidget(ctx, host); + if (configWarning) ctx.ui.notify(configWarning, "warning"); }); - pi.on("agent_start", (_event: any, ctx: CommandContext) => { + pi.on("agent_start", (_event, ctx) => { host.updateActivity(ctx, "working"); }); - pi.on("agent_end", (_event: any, ctx: CommandContext) => { + pi.on("agent_end", (_event, ctx) => { host.updateActivity(ctx, "idle"); }); - pi.on("tool_call", async (event: any, ctx: CommandContext) => { + pi.on("tool_call", async (event, ctx) => { const record = host.bindSessionContext(ctx); const reason = needsPermission(event.toolName, event.input, record.name); if (reason) { if (record.id !== host.activeId) host.updateActivity(ctx, "waiting"); const ok = await ctx.ui.confirm("pi-sessions permission", reason, { timeout: 60000, - } as any); + }); if (ok && record.id !== host.activeId) { record.activity = "working"; record.lastActivityAt = Date.now(); @@ -1046,7 +1330,7 @@ export default function (pi: ExtensionAPI) { return undefined; }); - pi.on("tool_result", async (event: any, ctx: CommandContext) => { + pi.on("tool_result", async (event, ctx) => { host.locks.releaseByToolCall(event.toolCallId); const record = host.bindSessionContext(ctx); if (record.activity === "waiting") { @@ -1057,12 +1341,17 @@ export default function (pi: ExtensionAPI) { return undefined; }); - pi.on("session_shutdown", (_event: any, ctx: CommandContext) => { + pi.on("session_shutdown", (_event, ctx) => { const record = host.bindSessionContext(ctx); host.locks.release(record.id); try { ctx.ui.setWidget("pi-sessions", undefined); - } catch {} + } catch (error) { + // Shutdown may invalidate UI before extension cleanup runs. + debugFailure("could not remove session widget", error); + } host.notify(); }); } + +export default registerPiSessions; diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..302bf3f --- /dev/null +++ b/package-lock.json @@ -0,0 +1,2428 @@ +{ + "name": "pi-parallel-sessions", + "version": "0.2.8", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "pi-parallel-sessions", + "version": "0.2.8", + "license": "MIT", + "devDependencies": { + "@earendil-works/pi-coding-agent": "0.84.4", + "@earendil-works/pi-tui": "0.84.4", + "@types/node": "22.15.3", + "tsx": "4.19.4", + "typescript": "5.8.3" + }, + "engines": { + "node": ">=22.19.0" + }, + "peerDependencies": { + "@earendil-works/pi-coding-agent": ">=0.84.4", + "@earendil-works/pi-tui": ">=0.84.4" + } + }, + "node_modules/@anthropic-ai/sdk": { + "version": "0.91.1", + "resolved": "https://registry.npmjs.org/@anthropic-ai/sdk/-/sdk-0.91.1.tgz", + "integrity": "sha512-LAmu761tSN9r66ixvmciswUj/ZC+1Q4iAfpedTfSVLeswRwnY3n2Nb6Tsk+cLPP28aLOPWeMgIuTuCcMC6W/iw==", + "dev": true, + "license": "MIT", + "dependencies": { + "json-schema-to-ts": "^3.1.1" + }, + "bin": { + "anthropic-ai-sdk": "bin/cli" + }, + "peerDependencies": { + "zod": "^3.25.0 || ^4.0.0" + }, + "peerDependenciesMeta": { + "zod": { + "optional": true + } + } + }, + "node_modules/@aws-crypto/sha256-browser": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-browser/-/sha256-browser-5.2.0.tgz", + "integrity": "sha512-AXfN/lGotSQwu6HNcEsIASo7kWXZ5HYWvfOmSNKDsEqC4OashTp8alTmaz+F7TC2L083SFv5RdB+qU3Vs1kZqw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha256-js": "^5.2.0", + "@aws-crypto/supports-web-crypto": "^5.2.0", + "@aws-crypto/util": "^5.2.0", + "@aws-sdk/types": "^3.222.0", + "@aws-sdk/util-locate-window": "^3.0.0", + "@smithy/util-utf8": "^2.0.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-crypto/sha256-js": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-js/-/sha256-js-5.2.0.tgz", + "integrity": "sha512-FFQQyu7edu4ufvIZ+OadFpHHOt+eSTBaYaki44c+akjg7qZg9oOQeLlk77F6tSYqjDAFClrHJk9tMf0HdVyOvA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/util": "^5.2.0", + "@aws-sdk/types": "^3.222.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-crypto/supports-web-crypto": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/supports-web-crypto/-/supports-web-crypto-5.2.0.tgz", + "integrity": "sha512-iAvUotm021kM33eCdNfwIN//F77/IADDSs58i+MDaOqFrVjZo9bAal0NK7HurRuWLLpF1iLX7gbWrjHjeo+YFg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-crypto/util": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/util/-/util-5.2.0.tgz", + "integrity": "sha512-4RkU9EsI6ZpBve5fseQlGNUWKMa1RLPQ1dnjnQoe07ldfIzcsGb5hC5W0Dm7u423KWzawlrpbjXBrXCEv9zazQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.222.0", + "@smithy/util-utf8": "^2.0.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-sdk/client-bedrock-runtime": { + "version": "3.1048.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-bedrock-runtime/-/client-bedrock-runtime-3.1048.0.tgz", + "integrity": "sha512-u+NT61JZEkRFtpL0CAw1N1dwxnaLgwVXQl/zjJxTGgLyS/jTIdg2SdoEoCTHxgDyCnqa1HEi9QOoE9/pYRNpOQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "^3.974.11", + "@aws-sdk/credential-provider-node": "^3.972.42", + "@aws-sdk/eventstream-handler-node": "^3.972.16", + "@aws-sdk/middleware-eventstream": "^3.972.12", + "@aws-sdk/middleware-websocket": "^3.972.19", + "@aws-sdk/token-providers": "3.1048.0", + "@aws-sdk/types": "^3.973.8", + "@smithy/core": "^3.24.2", + "@smithy/fetch-http-handler": "^5.4.2", + "@smithy/node-http-handler": "^4.7.2", + "@smithy/types": "^4.14.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/core": { + "version": "3.977.9", + "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.977.9.tgz", + "integrity": "sha512-reqPFEQrZxDZpeGj4PFMepBeR5LGYHRqq/L0motTzgFkCRBA4rFdaVXDSLYyGHhxVz7sT2PDnPN9CluGSfgyJA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.974.5", + "@aws-sdk/xml-builder": "^3.972.40", + "@aws/lambda-invoke-store": "^0.3.0", + "@smithy/core": "^3.33.3", + "@smithy/signature-v4": "^5.6.12", + "@smithy/types": "^4.17.2", + "bowser": "^2.11.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-env": { + "version": "3.972.70", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.972.70.tgz", + "integrity": "sha512-H404B7dJl2mCrBqahDEYsanB0xhdDp6tXnXcTUnXmmpy2Q3J0Ho0bUajZ2jr/RdwzCyS59Gi8xXIFwPLGBl6Uw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.977.9", + "@aws-sdk/types": "^3.974.5", + "@smithy/core": "^3.33.3", + "@smithy/types": "^4.17.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-http": { + "version": "3.972.72", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.972.72.tgz", + "integrity": "sha512-X98zYOrVOeuosCX+6ktf29FC2N2GHPLia7qv6mzPzTc+RPAuHWCDS++Z6JK7eGYqb/v6uaW7bAXaOvDBfol+0w==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.977.9", + "@aws-sdk/types": "^3.974.5", + "@smithy/core": "^3.33.3", + "@smithy/fetch-http-handler": "^5.7.2", + "@smithy/node-http-handler": "^4.11.3", + "@smithy/types": "^4.17.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-http/node_modules/@smithy/node-http-handler": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.12.0.tgz", + "integrity": "sha512-0mq1pHadfyXCYCqm2cNpbjNIT+fbaUpNxewZb/YNr2L0IrEVMOb8gM/Fl4K6XvHCW3uSNDFwPl/+iKm0bx9jYg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/core": "^3.33.3", + "@smithy/types": "^4.17.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-ini": { + "version": "3.973.15", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.973.15.tgz", + "integrity": "sha512-Rykg6s5ceBuynMOGWgoowO4N+27JfnqXAnVaSunZl0hOO1XodSrxGNz6sCEbnmS0lAfQZDKyb3fbr46gSuv6Sg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.977.9", + "@aws-sdk/credential-provider-env": "^3.972.70", + "@aws-sdk/credential-provider-http": "^3.972.72", + "@aws-sdk/credential-provider-login": "^3.972.77", + "@aws-sdk/credential-provider-process": "^3.972.70", + "@aws-sdk/credential-provider-sso": "^3.973.14", + "@aws-sdk/credential-provider-web-identity": "^3.972.76", + "@aws-sdk/nested-clients": "^3.997.44", + "@aws-sdk/types": "^3.974.5", + "@smithy/core": "^3.33.3", + "@smithy/credential-provider-imds": "^4.4.16", + "@smithy/types": "^4.17.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-login": { + "version": "3.972.77", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-login/-/credential-provider-login-3.972.77.tgz", + "integrity": "sha512-Jb59xfEISoN5mmbnA+HYqdtrSX3CgCtJoof+V5D8/TgUI56W63GEEd5Y58WijU3Ou6+WEgaLD1feVzaRXV5IDQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.977.9", + "@aws-sdk/nested-clients": "^3.997.44", + "@aws-sdk/types": "^3.974.5", + "@smithy/core": "^3.33.3", + "@smithy/types": "^4.17.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-node": { + "version": "3.972.82", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.972.82.tgz", + "integrity": "sha512-znDkEOGXB8W3kG1LJUKP3foBZY/9qLM0eil/DxWXSp37XsdsRLQHE/d/OaCGGVgKpA6znR38h/+INk8do1FjiA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/credential-provider-env": "^3.972.70", + "@aws-sdk/credential-provider-http": "^3.972.72", + "@aws-sdk/credential-provider-ini": "^3.973.15", + "@aws-sdk/credential-provider-process": "^3.972.70", + "@aws-sdk/credential-provider-sso": "^3.973.14", + "@aws-sdk/credential-provider-web-identity": "^3.972.76", + "@aws-sdk/types": "^3.974.5", + "@smithy/core": "^3.33.3", + "@smithy/credential-provider-imds": "^4.4.16", + "@smithy/types": "^4.17.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-process": { + "version": "3.972.70", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.972.70.tgz", + "integrity": "sha512-2ry03fGRJr4sV3jI+ocjj5JqALnFD6ymM5KiNCDZMvq8bX2GSbE0vji4aM43TVCl2nXqqLRZaUxdq/KeWRAY4Q==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.977.9", + "@aws-sdk/types": "^3.974.5", + "@smithy/core": "^3.33.3", + "@smithy/types": "^4.17.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-sso": { + "version": "3.973.14", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.973.14.tgz", + "integrity": "sha512-jkhg/8ocAAoc0RFyLMhCw+/zZh7gystQgd4F4hznNa8P4Cc501PQmxd+jGLiMHodPJ+7Zv/3znM62gZojyasmA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.977.9", + "@aws-sdk/nested-clients": "^3.997.44", + "@aws-sdk/token-providers": "3.1116.0", + "@aws-sdk/types": "^3.974.5", + "@smithy/core": "^3.33.3", + "@smithy/types": "^4.17.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-sso/node_modules/@aws-sdk/token-providers": { + "version": "3.1116.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.1116.0.tgz", + "integrity": "sha512-ygIivKqh8aHzNkucOCXHyIBgBpLPfrSI0mCqXF+vLBsPTUKqj0VSqAY0GFPe7lQl4HntjOcQ+KSyS7oUV2C54Q==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.977.9", + "@aws-sdk/nested-clients": "^3.997.44", + "@aws-sdk/types": "^3.974.5", + "@smithy/core": "^3.33.3", + "@smithy/types": "^4.17.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-web-identity": { + "version": "3.972.76", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.972.76.tgz", + "integrity": "sha512-d3AGyVu759PGr35mEB2s22xxlNEA5rpdxtSPJthfPFJvoQ8dt357iVPECqWfUxXp1toJAvKmbtcIYVGigaGsCA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.977.9", + "@aws-sdk/nested-clients": "^3.997.44", + "@aws-sdk/types": "^3.974.5", + "@smithy/core": "^3.33.3", + "@smithy/types": "^4.17.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/eventstream-handler-node": { + "version": "3.972.34", + "resolved": "https://registry.npmjs.org/@aws-sdk/eventstream-handler-node/-/eventstream-handler-node-3.972.34.tgz", + "integrity": "sha512-cTeVzpu1xEAkryTZBYhGwnQ6gOGyp8ZYZvmn0Sg/nI/ABmy/CRHHxPDJDUi9PxwxUtGGaatvfRUB3FCgT/rSWw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.974.5", + "@smithy/core": "^3.33.3", + "@smithy/types": "^4.17.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/middleware-eventstream": { + "version": "3.972.29", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-eventstream/-/middleware-eventstream-3.972.29.tgz", + "integrity": "sha512-dlRzHCgyB8W6hLuDC5pcT5q+ziPt00n4QGgGBE17ucLVU4zMa6lsbuUdQ2Pm75Z5VA8GF+R/+SgrRcaTdIzSIQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.974.5", + "@smithy/core": "^3.33.3", + "@smithy/types": "^4.17.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/middleware-websocket": { + "version": "3.972.52", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-websocket/-/middleware-websocket-3.972.52.tgz", + "integrity": "sha512-vsPPM+nMbKJlUCFU+eoGZbdxdxDIAX9LbpjSXaR5Ufpmqgp8TdYQnoExhLu4T3umW/JIIPny1ydbhWidZZYokQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.977.9", + "@aws-sdk/types": "^3.974.5", + "@smithy/core": "^3.33.3", + "@smithy/fetch-http-handler": "^5.7.2", + "@smithy/signature-v4": "^5.6.12", + "@smithy/types": "^4.17.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@aws-sdk/nested-clients": { + "version": "3.997.44", + "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.997.44.tgz", + "integrity": "sha512-NhEgryjlBF9w38ZXqGymQV28IhkYa1mKhlbYnqIis57AYwWGVYfUPgg/qC2rLRqOUfblxx++irvju10kVTa8Vw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.977.9", + "@aws-sdk/signature-v4-multi-region": "^3.996.46", + "@aws-sdk/types": "^3.974.5", + "@smithy/core": "^3.33.3", + "@smithy/fetch-http-handler": "^5.7.2", + "@smithy/node-http-handler": "^4.11.3", + "@smithy/types": "^4.17.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/nested-clients/node_modules/@smithy/node-http-handler": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.12.0.tgz", + "integrity": "sha512-0mq1pHadfyXCYCqm2cNpbjNIT+fbaUpNxewZb/YNr2L0IrEVMOb8gM/Fl4K6XvHCW3uSNDFwPl/+iKm0bx9jYg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/core": "^3.33.3", + "@smithy/types": "^4.17.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/signature-v4-multi-region": { + "version": "3.996.46", + "resolved": "https://registry.npmjs.org/@aws-sdk/signature-v4-multi-region/-/signature-v4-multi-region-3.996.46.tgz", + "integrity": "sha512-L+2xZTye/2T96f3lwCws0Zw6GG2JHZW9e8FpVgGBeeExSKyeoZ6CWRpBml/7DNiK/O26jrgPM9F+Ay8VkgzUWQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.974.5", + "@smithy/signature-v4": "^5.6.12", + "@smithy/types": "^4.17.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/token-providers": { + "version": "3.1048.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.1048.0.tgz", + "integrity": "sha512-k0y/GcuesuSfWyUM0WamrGyeZmltRYaPbHO82UDA6mZ/doB+FOHKutikPAtSXMn/hDz970cF+iRuuiYO9VEbAA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.974.11", + "@aws-sdk/nested-clients": "^3.997.9", + "@aws-sdk/types": "^3.973.8", + "@smithy/core": "^3.24.2", + "@smithy/types": "^4.14.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/types": { + "version": "3.974.5", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.974.5.tgz", + "integrity": "sha512-LkwLL2BLbC6wNNm4JaH9mbEqBMdOZCct6VAYqhdN4U1xrWM+fUJQEfbHwQgDypapOWTRtlk25akb5afM0P8CIQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.17.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/util-locate-window": { + "version": "3.965.10", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-locate-window/-/util-locate-window-3.965.10.tgz", + "integrity": "sha512-ycwH6Zd2GhuSqdXX9ihbCjeGTB6xOJs+O3+Jb8/zDG9978XU80qs75dfkPJRMNKe5MvBZPuNeFpd4JZKPoUF4g==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/xml-builder": { + "version": "3.972.40", + "resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.972.40.tgz", + "integrity": "sha512-wlFmCIGUlwF4zx/kncw+bmxTQh1HeSJq4mYV/V5cZUSJadDP3kXvGW8Rn21cimj/7y9ju+47oYWXi97vF7czaA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.17.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws/lambda-invoke-store": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@aws/lambda-invoke-store/-/lambda-invoke-store-0.3.0.tgz", + "integrity": "sha512-sl4Bm6yiMNYrZKkqqDFWN0UfnWhlS8ivKxrYl+6t0gCLrqr8y3B2IqZZbFRkfaVVp7C/baApyh71P+LeE1A2sQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@babel/runtime": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.7.tgz", + "integrity": "sha512-Nq8OhGWiZIZGV6hLHoyAKLLcJihP/xFeBMGJoUrxTX2psI8dCifzLhZISFb+VWS3wFMRDmCGw5R+dOySCqPLhw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@earendil-works/pi-agent-core": { + "version": "0.84.4", + "resolved": "https://registry.npmjs.org/@earendil-works/pi-agent-core/-/pi-agent-core-0.84.4.tgz", + "integrity": "sha512-HyUnjaOXj6oN/6SNcr8A1J/ElRQA50FtIE0XUTSKAQVqmdlb9qdojOyUQwF/jULE5+yOEtGuVgi/N1RnBiNG+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@earendil-works/pi-ai": "^0.84.4", + "@earendil-works/pi-telemetry": "^0.84.4", + "diff": "8.0.4", + "ignore": "7.0.5", + "typebox": "1.3.7", + "yaml": "2.9.0" + }, + "engines": { + "node": ">=22.19.0" + } + }, + "node_modules/@earendil-works/pi-ai": { + "version": "0.84.4", + "resolved": "https://registry.npmjs.org/@earendil-works/pi-ai/-/pi-ai-0.84.4.tgz", + "integrity": "sha512-AClAZxf5+c4RRu44NJPS6wyQy+Nmq+Mzyyrdvm4ZVMNuixelO02RZX4G4Aq1F145Yzp43wnM5S+hLlSI7ypfVw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@anthropic-ai/sdk": "0.91.1", + "@aws-sdk/client-bedrock-runtime": "3.1048.0", + "@earendil-works/pi-telemetry": "^0.84.4", + "@google/genai": "1.52.0", + "@smithy/node-http-handler": "4.7.3", + "http-proxy-agent": "7.0.2", + "https-proxy-agent": "7.0.6", + "openai": "6.40.0", + "partial-json": "0.1.7", + "typebox": "1.3.7" + }, + "bin": { + "pi-ai": "dist/cli.js" + }, + "engines": { + "node": ">=22.19.0" + } + }, + "node_modules/@earendil-works/pi-client": { + "version": "0.84.4", + "resolved": "https://registry.npmjs.org/@earendil-works/pi-client/-/pi-client-0.84.4.tgz", + "integrity": "sha512-q398WY/3ZQHTizk7IKxApzqFV0xt4yM9LkSkwyqeLK5Bj5RwRjOWxESt26z4LgNp4O+8hqhqFPf/8fj4H5rE4A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@earendil-works/pi-protocol": "^0.84.4" + }, + "engines": { + "node": ">=22.19.0" + } + }, + "node_modules/@earendil-works/pi-coding-agent": { + "version": "0.84.4", + "resolved": "https://registry.npmjs.org/@earendil-works/pi-coding-agent/-/pi-coding-agent-0.84.4.tgz", + "integrity": "sha512-jmOlrqUmvhh/siNWFRXjYLJzhKFIHNsAQaysRwzQPQFnPAaV/vhqHsLH/MBsIISA1Rjj7WTUFR3nJrpXoLx39w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@earendil-works/pi-agent-core": "^0.84.4", + "@earendil-works/pi-ai": "^0.84.4", + "@earendil-works/pi-client": "^0.84.4", + "@earendil-works/pi-protocol": "^0.84.4", + "@earendil-works/pi-tui": "^0.84.4", + "@silvia-odwyer/photon-node": "0.3.4", + "chalk": "5.6.2", + "cross-spawn": "7.0.6", + "diff": "8.0.4", + "grok-mermaid": "0.2.2", + "highlight.js": "10.7.3", + "hosted-git-info": "9.0.3", + "ignore": "7.0.5", + "jiti": "2.7.0", + "minimatch": "10.2.5", + "proper-lockfile": "4.1.2", + "semver": "7.8.0", + "typebox": "1.3.7", + "undici": "8.9.0", + "yaml": "2.9.0" + }, + "bin": { + "pi": "dist/bundle/cli.js" + }, + "engines": { + "node": ">=22.19.0" + }, + "optionalDependencies": { + "@mariozechner/clipboard": "0.3.9" + } + }, + "node_modules/@earendil-works/pi-protocol": { + "version": "0.84.4", + "resolved": "https://registry.npmjs.org/@earendil-works/pi-protocol/-/pi-protocol-0.84.4.tgz", + "integrity": "sha512-acyE9ozxkMiWiz/xyWpU0O9vwnYv0hyG889Vniv6Sg9c9zfsX+8MePnDNphBacY2Fvm1rxdsGmiVDSZl9yuDFA==", + "dev": true, + "license": "MIT", + "dependencies": { + "typebox": "1.3.7" + }, + "engines": { + "node": ">=22.19.0" + } + }, + "node_modules/@earendil-works/pi-telemetry": { + "version": "0.84.4", + "resolved": "https://registry.npmjs.org/@earendil-works/pi-telemetry/-/pi-telemetry-0.84.4.tgz", + "integrity": "sha512-8e2CuxM+ht+hedQXTZmi5JVl6/xDK9RpSDL2+MbITevKYQhMZ/z6lJOTFgox3HQyGxO8mOZEtYGVeQNaD4OzqA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=22.19.0" + } + }, + "node_modules/@earendil-works/pi-tui": { + "version": "0.84.4", + "resolved": "https://registry.npmjs.org/@earendil-works/pi-tui/-/pi-tui-0.84.4.tgz", + "integrity": "sha512-nPUnwDkLtupPXnZQYrCwPFcuTydCDqTY6ZbFqhsL4S4kVq0AT418kPa/6uXwtaCD+MjBNBltb7ScTYX65yeE1w==", + "dev": true, + "license": "MIT", + "dependencies": { + "get-east-asian-width": "1.6.0", + "marked": "18.0.5" + }, + "engines": { + "node": ">=22.19.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz", + "integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.12.tgz", + "integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz", + "integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.12.tgz", + "integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz", + "integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz", + "integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz", + "integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz", + "integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz", + "integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz", + "integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz", + "integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz", + "integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz", + "integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz", + "integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz", + "integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz", + "integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz", + "integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz", + "integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz", + "integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz", + "integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz", + "integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz", + "integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz", + "integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz", + "integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz", + "integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz", + "integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@google/genai": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/@google/genai/-/genai-1.52.0.tgz", + "integrity": "sha512-gwSvbpiN/17O9TbsqSsE/OzZcpv5Fo4RQjdngGgogtuB9RsyJ8ZHhX5KjHj1bp5N9snN2eK8LDGXSaWW2hof8Q==", + "dev": true, + "hasInstallScript": true, + "license": "Apache-2.0", + "dependencies": { + "google-auth-library": "^10.3.0", + "p-retry": "^4.6.2", + "protobufjs": "^7.5.4", + "ws": "^8.18.0" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "@modelcontextprotocol/sdk": "^1.25.2" + }, + "peerDependenciesMeta": { + "@modelcontextprotocol/sdk": { + "optional": true + } + } + }, + "node_modules/@mariozechner/clipboard": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@mariozechner/clipboard/-/clipboard-0.3.9.tgz", + "integrity": "sha512-ABnA53mdfkGZwOFUdZNv2S0CWGO/EIuPj8Vv9xmBFmSYg/qFc7ihO6q5FcQjvoE67kZpWkEc4AhD6B/os04yuA==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">= 10" + }, + "optionalDependencies": { + "@mariozechner/clipboard-darwin-arm64": "0.3.9", + "@mariozechner/clipboard-darwin-universal": "0.3.9", + "@mariozechner/clipboard-darwin-x64": "0.3.9", + "@mariozechner/clipboard-linux-arm64-gnu": "0.3.9", + "@mariozechner/clipboard-linux-arm64-musl": "0.3.9", + "@mariozechner/clipboard-linux-riscv64-gnu": "0.3.9", + "@mariozechner/clipboard-linux-x64-gnu": "0.3.9", + "@mariozechner/clipboard-linux-x64-musl": "0.3.9", + "@mariozechner/clipboard-win32-arm64-msvc": "0.3.9", + "@mariozechner/clipboard-win32-x64-msvc": "0.3.9" + } + }, + "node_modules/@mariozechner/clipboard-darwin-arm64": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@mariozechner/clipboard-darwin-arm64/-/clipboard-darwin-arm64-0.3.9.tgz", + "integrity": "sha512-BfgV7vCEWZwJwZJw03r6bP5+tf0iI/ANuQYCxi9RNn7FrWB3yzGuMKCrNLRl6V761vXRdL8+OqZ0wd4TqlsNOQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@mariozechner/clipboard-darwin-universal": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@mariozechner/clipboard-darwin-universal/-/clipboard-darwin-universal-0.3.9.tgz", + "integrity": "sha512-BGGR4iA9Z2shAjI65eI5xtyb3LYNlDW9X3gxKxDbqtbnREohsrqznov6zpKoIrsRWpzlYVEdKphS7ksJ0/ndSQ==", + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@mariozechner/clipboard-darwin-x64": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@mariozechner/clipboard-darwin-x64/-/clipboard-darwin-x64-0.3.9.tgz", + "integrity": "sha512-4kURmCbS6nt8uYhtmWpUcJWyPHfmAr5dTpXD1nO3pIfa+TSQ9DbrGOYCKH+aEFW47XhQ4Vp8ZTszie+wfFvDKg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@mariozechner/clipboard-linux-arm64-gnu": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@mariozechner/clipboard-linux-arm64-gnu/-/clipboard-linux-arm64-gnu-0.3.9.tgz", + "integrity": "sha512-g59OkUGP2DDfCOIKypHeYgv2M55u/cKvXa5dSxFbEJ34XvIQMdcVmpKCkGUro3ZgefXiGVdwguvTMQGpHWzIXw==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@mariozechner/clipboard-linux-arm64-musl": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@mariozechner/clipboard-linux-arm64-musl/-/clipboard-linux-arm64-musl-0.3.9.tgz", + "integrity": "sha512-AGuJdgKsmJdm4Pych7kv3sqe591ERRaAHW3xjLooiFzn8J+PxUyof++7YZrB5Y5tpnTO+K18Og3taj2NpluCRQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@mariozechner/clipboard-linux-riscv64-gnu": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@mariozechner/clipboard-linux-riscv64-gnu/-/clipboard-linux-riscv64-gnu-0.3.9.tgz", + "integrity": "sha512-DXBEAiuMpk7dhS1a9NzNxVAFi1vaKoPu7rQNgY8LIDLGrK3lnIp3nT10DUum+PKVJoJppIP+NAA8IZe4DMNDPw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@mariozechner/clipboard-linux-x64-gnu": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@mariozechner/clipboard-linux-x64-gnu/-/clipboard-linux-x64-gnu-0.3.9.tgz", + "integrity": "sha512-WORrMLd6EpElEME7JRKfSaY34nW1P5LbdgK5YNCS1ncG2LqmITsSMEJ8nh2mpvxb3TxqbOOKgY7k9eMJYlW9Mw==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@mariozechner/clipboard-linux-x64-musl": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@mariozechner/clipboard-linux-x64-musl/-/clipboard-linux-x64-musl-0.3.9.tgz", + "integrity": "sha512-/DHn+1DrfL6oRaPPWXaOKvonFFrni666fxd+zFqiQEfvBH0tsHVWjq9iqBk0oDp0qaPA72lIMy5BptxISBEhZQ==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@mariozechner/clipboard-win32-arm64-msvc": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@mariozechner/clipboard-win32-arm64-msvc/-/clipboard-win32-arm64-msvc-0.3.9.tgz", + "integrity": "sha512-O5FHD3ErkMwMhNzAfu3ggy0ug4z7btZuoQgwwxlzPrwV2bxlD6WDpqBY4NCgICAgZdDKdp+loUEKVAVt8aYnhQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@mariozechner/clipboard-win32-x64-msvc": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@mariozechner/clipboard-win32-x64-msvc/-/clipboard-win32-x64-msvc-0.3.9.tgz", + "integrity": "sha512-ihQC3EufqEY81vhXBgVBtK4prL+wc62zJsSvxrgz7K1hsdt6OObz6v9p3Rn1OG3GJksTTKMJF0u/guMISHPhSA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@protobufjs/aspromise": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", + "integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/base64": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz", + "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/codegen": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.5.tgz", + "integrity": "sha512-zgXFLzW3Ap33e6d0Wlj4MGIm6Ce8O89n/apUaGNB/jx+hw+ruWEp7EwGUshdLKVRCxZW12fp9r40E1mQrf/34g==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/eventemitter": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.1.tgz", + "integrity": "sha512-vW1GmwMZNnL+gMRaovlh9yZX74kc+TTU3FObkkurpMaRtBfLP3ldjS9KQWlwZgraRE0+dheEEoAxdzcJQ8eXZg==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/fetch": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.1.tgz", + "integrity": "sha512-GpptLrs57adMSuHi3VNj0mAF8dwh36LMaYF6XyJ6JMWlVsc+t42tm1HSEDmOs3A8fC9yyeisgLhsTVQokOZ0zw==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@protobufjs/aspromise": "^1.1.1" + } + }, + "node_modules/@protobufjs/float": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz", + "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/path": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz", + "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/pool": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz", + "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/utf8": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.2.tgz", + "integrity": "sha512-b1UQwcEZ4yCnMCD8DAL1VlbvBJE9/IX4FTIp7BG1xYpf29SLazLSrqUkj4w7Y5y7cCVP6E5tcqqcI0xemPkHug==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/@silvia-odwyer/photon-node": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/@silvia-odwyer/photon-node/-/photon-node-0.3.4.tgz", + "integrity": "sha512-bnly4BKB3KDTFxrUIcgCLbaeVVS8lrAkri1pEzskpmxu9MdfGQTy8b8EgcD83ywD3RPMsIulY8xJH5Awa+t9fA==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/@smithy/core": { + "version": "3.33.3", + "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.33.3.tgz", + "integrity": "sha512-CsOeKq/9kA3y6VJHt+/+VTCtBaxJ4OTFpgrjIUhPpDIKxBci1k2bJaQASF2h/ELWrulGp+t97DZ0mevfAD8idg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.17.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/credential-provider-imds": { + "version": "4.5.2", + "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-4.5.2.tgz", + "integrity": "sha512-A9uSdn72ozbRUSit0eib0TW7nXuNPlaeM0zcGkJ+nE6tFcSDbnmtwoxbTCFBukVQcszDAyvsd7+rTduPTXpygg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/core": "^3.33.2", + "@smithy/types": "^4.17.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/fetch-http-handler": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-5.7.2.tgz", + "integrity": "sha512-nZyWTmSpJEXl6VtWVMBJve/7x12DZu6sIX1z1a+ZMaHlQQRs9Zpu6NbTe/gmxYXVRpkjxyDYpZ5gx2IM6f/Wkw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/core": "^3.33.2", + "@smithy/types": "^4.17.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/is-array-buffer": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-2.2.0.tgz", + "integrity": "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@smithy/node-http-handler": { + "version": "4.7.3", + "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.7.3.tgz", + "integrity": "sha512-/jPhevcTFPMVl6KNjbaI47iOg1zxC7IsnX4PQDGVZKMFceOXtB8IEYaB7a9VvkP/3oC60WzTeKocvSI7vLT0vA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/core": "^3.24.3", + "@smithy/types": "^4.14.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/signature-v4": { + "version": "5.7.3", + "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-5.7.3.tgz", + "integrity": "sha512-7ImGm+FkHRLcBaRttIAMZ6bzJZWb2cJGoYjq46F2UjycujWzrL9GEN9h4w7eQyXJYnltrUhxbbieBAIRrdqpow==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/core": "^3.33.3", + "@smithy/types": "^4.17.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/types": { + "version": "4.17.2", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.17.2.tgz", + "integrity": "sha512-FOKpVZob9MPTn2znRzGrnsMHv7BOsKVw3XiP/cOyYLDVZ9qKp4nifIiSCuUU/fIj5Vu0UOAxCFr+qRAtG0NUkA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-buffer-from": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-2.2.0.tgz", + "integrity": "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/is-array-buffer": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@smithy/util-utf8": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.3.0.tgz", + "integrity": "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/util-buffer-from": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@types/node": { + "version": "22.15.3", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.15.3.tgz", + "integrity": "sha512-lX7HFZeHf4QG/J7tBZqrCAXwz9J5RD56Y6MpP0eJkka8p+K0RY/yBTW7CYFJ4VGCclxqOLKmiGP5juQc6MKgcw==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/@types/retry": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.0.tgz", + "integrity": "sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==", + "dev": true, + "license": "MIT" + }, + "node_modules/agent-base": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", + "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, + "node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/bignumber.js": { + "version": "9.3.1", + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.3.1.tgz", + "integrity": "sha512-Ko0uX15oIUS7wJ3Rb30Fs6SkVbLmPBAKdlm7q9+ak9bbIeFf0MwuBsQV6z7+X768/cHsfg+WlysDWJcmthjsjQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/bowser": { + "version": "2.14.1", + "resolved": "https://registry.npmjs.org/bowser/-/bowser-2.14.1.tgz", + "integrity": "sha512-tzPjzCxygAKWFOJP011oxFHs57HzIhOEracIgAePE4pqB3LikALKnSzUyU4MGs9/iCEUuHlAJTjTc5M+u7YEGg==", + "dev": true, + "license": "MIT" + }, + "node_modules/brace-expansion": { + "version": "5.0.9", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.9.tgz", + "integrity": "sha512-ScQ4IuvIEF1TMlP7Zt+vjJ//9zlPb2SDcxWxM3bk8s6t6GGdJ7KO1dCcTidOPJKePW30LE/2cT7wCyPho9/Wxg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/chalk": { + "version": "5.6.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", + "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/data-uri-to-buffer": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz", + "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/diff": { + "version": "8.0.4", + "resolved": "https://registry.npmjs.org/diff/-/diff-8.0.4.tgz", + "integrity": "sha512-DPi0FmjiSU5EvQV0++GFDOJ9ASQUVFh5kD+OzOnYdi7n3Wpm9hWWGfB/O2blfHcMVTL5WkQXSnRiK9makhrcnw==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/ecdsa-sig-formatter": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "safe-buffer": "^5.0.1" + } + }, + "node_modules/esbuild": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz", + "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.12", + "@esbuild/android-arm": "0.25.12", + "@esbuild/android-arm64": "0.25.12", + "@esbuild/android-x64": "0.25.12", + "@esbuild/darwin-arm64": "0.25.12", + "@esbuild/darwin-x64": "0.25.12", + "@esbuild/freebsd-arm64": "0.25.12", + "@esbuild/freebsd-x64": "0.25.12", + "@esbuild/linux-arm": "0.25.12", + "@esbuild/linux-arm64": "0.25.12", + "@esbuild/linux-ia32": "0.25.12", + "@esbuild/linux-loong64": "0.25.12", + "@esbuild/linux-mips64el": "0.25.12", + "@esbuild/linux-ppc64": "0.25.12", + "@esbuild/linux-riscv64": "0.25.12", + "@esbuild/linux-s390x": "0.25.12", + "@esbuild/linux-x64": "0.25.12", + "@esbuild/netbsd-arm64": "0.25.12", + "@esbuild/netbsd-x64": "0.25.12", + "@esbuild/openbsd-arm64": "0.25.12", + "@esbuild/openbsd-x64": "0.25.12", + "@esbuild/openharmony-arm64": "0.25.12", + "@esbuild/sunos-x64": "0.25.12", + "@esbuild/win32-arm64": "0.25.12", + "@esbuild/win32-ia32": "0.25.12", + "@esbuild/win32-x64": "0.25.12" + } + }, + "node_modules/extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "dev": true, + "license": "MIT" + }, + "node_modules/fetch-blob": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz", + "integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "paypal", + "url": "https://paypal.me/jimmywarting" + } + ], + "license": "MIT", + "dependencies": { + "node-domexception": "^1.0.0", + "web-streams-polyfill": "^3.0.3" + }, + "engines": { + "node": "^12.20 || >= 14.13" + } + }, + "node_modules/formdata-polyfill": { + "version": "4.0.10", + "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz", + "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fetch-blob": "^3.1.2" + }, + "engines": { + "node": ">=12.20.0" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/gaxios": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-7.3.1.tgz", + "integrity": "sha512-kB3rzJV7d9juLZh8/56QTXCwQfxyhdOMdyYk1HdQKFtF8TJTDTZQJtixWIwXdE9Jji91mC41DUNpjleo4L4eAQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "extend": "^3.0.2", + "https-proxy-agent": "^7.0.1", + "node-fetch": "^3.3.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/gcp-metadata": { + "version": "8.1.2", + "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-8.1.2.tgz", + "integrity": "sha512-zV/5HKTfCeKWnxG0Dmrw51hEWFGfcF2xiXqcA3+J90WDuP0SvoiSO5ORvcBsifmx/FoIjgQN3oNOGaQ5PhLFkg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "gaxios": "^7.0.0", + "google-logging-utils": "^1.0.0", + "json-bigint": "^1.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/get-east-asian-width": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.6.0.tgz", + "integrity": "sha512-QRbvDIbx6YklUe6RxeTeleMR0yv3cYH6PsPZHcnVn7xv7zO1BHN8r0XETu8n6Ye3Q+ahtSarc3WgtNWmehIBfA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/get-tsconfig": { + "version": "4.14.3", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.14.3.tgz", + "integrity": "sha512-++QEw4DIY7WGoukz+/+A/8dGYPT9l9yIadnmSgZ8Rjr3YVSVDipQSO9CdnJo9ePqFqUUqh+wk9uIaoiAwsiPkA==", + "dev": true, + "license": "MIT", + "dependencies": { + "resolve-pkg-maps": "^1.0.0" + }, + "funding": { + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" + } + }, + "node_modules/google-auth-library": { + "version": "10.9.1", + "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-10.9.1.tgz", + "integrity": "sha512-i1ydyHrqcIxXkWh/uBmVkzCvIuq5yiK2ATndIe5XxKholrG/MTYP9xGYka4sQhrbIAgGjL2B6NOE7rFaiF3fXw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "base64-js": "^1.3.0", + "ecdsa-sig-formatter": "^1.0.11", + "gaxios": "^7.1.4", + "gcp-metadata": "8.1.2", + "google-logging-utils": "1.1.3", + "jws": "^4.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/google-logging-utils": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/google-logging-utils/-/google-logging-utils-1.1.3.tgz", + "integrity": "sha512-eAmLkjDjAFCVXg7A1unxHsLf961m6y17QFqXqAXGj/gVkKFrEICfStRfwUlGNfeCEjNRa32JEWOUTlYXPyyKvA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=14" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/grok-mermaid": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/grok-mermaid/-/grok-mermaid-0.2.2.tgz", + "integrity": "sha512-XcJEP5dDC8liHBh52mlLjU18fNvu1ckFsu0QpIG3+APZ270fsj9wxpiA6cOURmbUEuoMVgjbC2+UYgTdCqqgzA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/highlight.js": { + "version": "10.7.3", + "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-10.7.3.tgz", + "integrity": "sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": "*" + } + }, + "node_modules/hosted-git-info": { + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-9.0.3.tgz", + "integrity": "sha512-Hc+ghLoSt6QaYZUv0WBiIvmMDZuZZ7oaDvdH8MbfOO4lOsxdXLEvuC6ePoGs9H1X9oCLyq6+NVN0MKqD+ydxyg==", + "dev": true, + "license": "ISC", + "dependencies": { + "lru-cache": "^11.1.0" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/http-proxy-agent": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", + "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.0", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/https-proxy-agent": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/ignore": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", + "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/jiti": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.7.0.tgz", + "integrity": "sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==", + "dev": true, + "license": "MIT", + "bin": { + "jiti": "lib/jiti-cli.mjs" + } + }, + "node_modules/json-bigint": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-bigint/-/json-bigint-1.0.0.tgz", + "integrity": "sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "bignumber.js": "^9.0.0" + } + }, + "node_modules/json-schema-to-ts": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/json-schema-to-ts/-/json-schema-to-ts-3.1.1.tgz", + "integrity": "sha512-+DWg8jCJG2TEnpy7kOm/7/AxaYoaRbjVB4LFZLySZlWn8exGs3A4OLJR966cVvU26N7X9TWxl+Jsw7dzAqKT6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.18.3", + "ts-algebra": "^2.0.0" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/jwa": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.1.tgz", + "integrity": "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer-equal-constant-time": "^1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/jws": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.1.tgz", + "integrity": "sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA==", + "dev": true, + "license": "MIT", + "dependencies": { + "jwa": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/long": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/long/-/long-5.3.2.tgz", + "integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/lru-cache": { + "version": "11.5.2", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.5.2.tgz", + "integrity": "sha512-4pfM1Ff0x50o0tQwb5ucw/RzNyD0/YJME6IVcStalZuMWxdt3sR3huStTtxz4PUmvZfRguvDejasvQ2kifR11g==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/marked": { + "version": "18.0.5", + "resolved": "https://registry.npmjs.org/marked/-/marked-18.0.5.tgz", + "integrity": "sha512-S6GcvALHg6K4ohtu4E7x0a1AqhAjp6cV8KhLSyN9qVapnzJkusVBxZRcIU9AeYsbe6P1hKDusSbEOzGyyuce6w==", + "dev": true, + "license": "MIT", + "bin": { + "marked": "bin/marked.js" + }, + "engines": { + "node": ">= 20" + } + }, + "node_modules/minimatch": { + "version": "10.2.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", + "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.5" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/node-domexception": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", + "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", + "deprecated": "Use your platform's native DOMException instead", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "github", + "url": "https://paypal.me/jimmywarting" + } + ], + "license": "MIT", + "engines": { + "node": ">=10.5.0" + } + }, + "node_modules/node-fetch": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz", + "integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==", + "dev": true, + "license": "MIT", + "dependencies": { + "data-uri-to-buffer": "^4.0.0", + "fetch-blob": "^3.1.4", + "formdata-polyfill": "^4.0.10" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/node-fetch" + } + }, + "node_modules/openai": { + "version": "6.40.0", + "resolved": "https://registry.npmjs.org/openai/-/openai-6.40.0.tgz", + "integrity": "sha512-MWtTjd/gQt4jpbji61NTgFWJLoY/PdRJ6wG9/ZDRMYNMlBKrCrSlkLI+KgHP1vR1qT6LKSAyAqIxno6lcK9JiA==", + "dev": true, + "license": "Apache-2.0", + "peerDependencies": { + "ws": "^8.18.0", + "zod": "^3.25 || ^4.0" + }, + "peerDependenciesMeta": { + "ws": { + "optional": true + }, + "zod": { + "optional": true + } + } + }, + "node_modules/p-retry": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-4.6.2.tgz", + "integrity": "sha512-312Id396EbJdvRONlngUx0NydfrIQ5lsYu0znKVUzVvArzEIt08V1qhtyESbGVd1FGX7UKtiFp5uwKZdM8wIuQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/retry": "0.12.0", + "retry": "^0.13.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/partial-json": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/partial-json/-/partial-json-0.1.7.tgz", + "integrity": "sha512-Njv/59hHaokb/hRUjce3Hdv12wd60MtM9Z5Olmn+nehe0QDAsRtRbJPvJ0Z91TusF0SuZRIvnM+S4l6EIP8leA==", + "dev": true, + "license": "MIT" + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/proper-lockfile": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/proper-lockfile/-/proper-lockfile-4.1.2.tgz", + "integrity": "sha512-TjNPblN4BwAWMXU8s9AEz4JmQxnD1NNL7bNOY/AKUzyamc379FWASUhc/K1pL2noVb+XmZKLL68cjzLsiOAMaA==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.4", + "retry": "^0.12.0", + "signal-exit": "^3.0.2" + } + }, + "node_modules/proper-lockfile/node_modules/retry": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", + "integrity": "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/protobufjs": { + "version": "7.6.6", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.6.6.tgz", + "integrity": "sha512-dYDWdjSl5RNb7SgPxGQcRU+GtvP7s2fpkrY0r432PcOIaZ0/rBcxEZnQN67iJhFuQiVw754JDoPruPCNdGsbjg==", + "dev": true, + "hasInstallScript": true, + "license": "BSD-3-Clause", + "dependencies": { + "@protobufjs/aspromise": "^1.1.2", + "@protobufjs/base64": "^1.1.2", + "@protobufjs/codegen": "^2.0.5", + "@protobufjs/eventemitter": "^1.1.1", + "@protobufjs/fetch": "^1.1.1", + "@protobufjs/float": "^1.0.2", + "@protobufjs/path": "^1.1.2", + "@protobufjs/pool": "^1.1.0", + "@protobufjs/utf8": "^1.1.1", + "@types/node": ">=13.7.0", + "long": "^5.3.2" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/resolve-pkg-maps": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", + "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" + } + }, + "node_modules/retry": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", + "integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/semver": { + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.0.tgz", + "integrity": "sha512-AcM7dV/5ul4EekoQ29Agm5vri8JNqRyj39o0qpX6vDF2GZrtutZl5RwgD1XnZjiTAfncsJhMI48QQH3sN87YNA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/ts-algebra": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ts-algebra/-/ts-algebra-2.0.0.tgz", + "integrity": "sha512-FPAhNPFMrkwz76P7cdjdmiShwMynZYN6SgOujD1urY4oNm80Ou9oMdmbR45LotcKOXoy7wSmHkRFE6Mxbrhefw==", + "dev": true, + "license": "MIT" + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true, + "license": "0BSD" + }, + "node_modules/tsx": { + "version": "4.19.4", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.19.4.tgz", + "integrity": "sha512-gK5GVzDkJK1SI1zwHf32Mqxf2tSJkNx+eYcNly5+nHvWqXUJYUkWBQtKauoESz3ymezAI++ZwT855x5p5eop+Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "~0.25.0", + "get-tsconfig": "^4.7.5" + }, + "bin": { + "tsx": "dist/cli.mjs" + }, + "engines": { + "node": ">=18.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + } + }, + "node_modules/typebox": { + "version": "1.3.7", + "resolved": "https://registry.npmjs.org/typebox/-/typebox-1.3.7.tgz", + "integrity": "sha512-meKuifc33Pccx0O6PdIzYMq3Og8zvP4TIi/a+Bw3AEMZMxOD0+RHGQvpglEe6Zdy3wZ8nqn/j95h8LUZLk/6Hg==", + "dev": true, + "license": "MIT" + }, + "node_modules/typescript": { + "version": "5.8.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz", + "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici": { + "version": "8.9.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-8.9.0.tgz", + "integrity": "sha512-aWZpUj7XoGonMClx4gdDRfgBjqeA+F473aDmROQQbM9n6PRfK/u1q/a0X4wMTgcHfT8H6fpbt98PFuDUwFg2YA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=22.19.0" + } + }, + "node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/web-streams-polyfill": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz", + "integrity": "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/ws": { + "version": "8.21.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.21.3.tgz", + "integrity": "sha512-201TZ/kPWxoPr/OKWjquZR1SWKXcvxdH+e1xrx89b3YbmzLMFCLfnaG1HFIgWzJOEWZ7MvpK++odZufgYR50Rw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/yaml": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.9.0.tgz", + "integrity": "sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA==", + "dev": true, + "license": "ISC", + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14.6" + }, + "funding": { + "url": "https://github.com/sponsors/eemeli" + } + } + } +} diff --git a/package.json b/package.json index 0637862..188e477 100644 --- a/package.json +++ b/package.json @@ -5,6 +5,13 @@ "type": "module", "license": "MIT", "author": "liushihao456", + "engines": { + "node": ">=22.19.0" + }, + "scripts": { + "typecheck": "tsc --noEmit", + "test": "tsx --test test/**/*.test.ts" + }, "repository": { "type": "git", "url": "git+https://github.com/liushihao456/pi-sessions.git" @@ -28,8 +35,15 @@ "LICENSE" ], "peerDependencies": { - "@earendil-works/pi-coding-agent": "*", - "@earendil-works/pi-tui": "*" + "@earendil-works/pi-coding-agent": ">=0.84.4", + "@earendil-works/pi-tui": ">=0.84.4" + }, + "devDependencies": { + "@earendil-works/pi-coding-agent": "0.84.4", + "@earendil-works/pi-tui": "0.84.4", + "@types/node": "22.15.3", + "tsx": "4.19.4", + "typescript": "5.8.3" }, "pi": { "extensions": [ diff --git a/test/compatibility.test.ts b/test/compatibility.test.ts new file mode 100644 index 0000000..8af6f97 --- /dev/null +++ b/test/compatibility.test.ts @@ -0,0 +1,89 @@ +import assert from "node:assert/strict"; +import { it } from "node:test"; +import * as PiCodingAgent from "@earendil-works/pi-coding-agent"; +import type { + ExtensionAPI, + ExtensionContext, + SessionStartEvent, +} from "@earendil-works/pi-coding-agent"; +import { + EXTENSION_PATH, + findCompatibilityIssues, + getChildResourceLoaderOptions, + parsePiSessionsConfig, + registerPiSessions, +} from "../index.ts"; + +it("accepts the tested pi host capabilities", () => { + assert.deepEqual( + findCompatibilityIssues(PiCodingAgent, PiCodingAgent.InteractiveMode.prototype), + [], + ); +}); + +it("propagates the extension into fresh child resource loaders", () => { + assert.deepEqual(getChildResourceLoaderOptions(), { + additionalExtensionPaths: [EXTENSION_PATH], + }); + assert.match(EXTENSION_PATH, /index\.ts$/); +}); + +it("uses a validated package-specific kill key", () => { + assert.deepEqual(parsePiSessionsConfig({}), { + config: { killKey: "ctrl+k" }, + }); + assert.deepEqual(parsePiSessionsConfig({ killKey: "CTRL+SHIFT+K" }), { + config: { killKey: "ctrl+shift+k" }, + }); + assert.deepEqual(parsePiSessionsConfig({ killKey: "CTRL+PAGEUP" }), { + config: { killKey: "ctrl+pageUp" }, + }); + assert.deepEqual(parsePiSessionsConfig({ killKey: "ctrl++" }), { + config: { killKey: "ctrl++" }, + }); + const invalid = parsePiSessionsConfig({ killKey: "not-a-key" }); + assert.equal(invalid.config.killKey, "ctrl+k"); + assert.match(invalid.warning ?? "", /invalid pi-sessions killKey/); +}); + +it("names missing exports and interactive targets", () => { + const issues = findCompatibilityIssues({}, undefined); + assert.ok(issues.includes("missing export createAgentSessionRuntime")); + assert.ok(issues.includes("missing InteractiveMode.prototype.run")); + assert.ok(issues.includes("missing InteractiveMode.prototype.stop")); +}); + +it("warns without registering behavior when compatibility preflight fails", () => { + let sessionStartHandler: + | ((event: SessionStartEvent, ctx: ExtensionContext) => void) + | undefined; + const pi = { + on: ( + event: string, + handler: (event: SessionStartEvent, ctx: ExtensionContext) => void, + ) => { + if (event === "session_start") sessionStartHandler = handler; + }, + } as unknown as ExtensionAPI; + const notifications: Array<{ message: string; type?: string }> = []; + + registerPiSessions(pi, ["missing export InteractiveMode"]); + assert.ok(sessionStartHandler); + sessionStartHandler( + { type: "session_start", reason: "startup" }, + { + ui: { + notify: (message: string, type?: string) => + notifications.push({ message, type }), + }, + } as never, + ); + + assert.deepEqual(notifications, [ + { + message: + "pi-sessions disabled: incompatible pi host (>=0.84.4); missing export InteractiveMode", + type: "warning", + }, + ]); +}); diff --git a/test/locks.test.ts b/test/locks.test.ts new file mode 100644 index 0000000..5faa844 --- /dev/null +++ b/test/locks.test.ts @@ -0,0 +1,158 @@ +import assert from "node:assert/strict"; +import { describe, it } from "node:test"; +import { inferToolPaths, LockManager, PiSessionsHost } from "../index.ts"; + +const cwd = "/work/repo"; + +describe("inferToolPaths", () => { + it("extracts write and edit paths", () => { + assert.deepEqual(inferToolPaths("write", { path: "src/a.ts" }), [ + "src/a.ts", + ]); + assert.deepEqual(inferToolPaths("edit", { file_path: "src/b.ts" }), [ + "src/b.ts", + ]); + }); + + it("extracts quoted redirects and ignores devices", () => { + assert.deepEqual( + inferToolPaths("bash", { + command: "printf x > 'build/output file.txt' 2> /dev/null", + }), + ["build/output file.txt"], + ); + }); + + it("recognizes repository, package, and build mutators", () => { + for (const command of [ + "git checkout feature", + "npm install", + "make build", + "cargo build", + "terraform apply -auto-approve", + "dbt run", + ]) { + assert.ok(inferToolPaths("bash", { command }).includes("."), command); + } + }); + + it("does not lock known read-only command forms", () => { + for (const command of [ + "git status", + "git log --oneline", + "npm list", + "make --dry-run build", + "cargo check", + "terraform plan", + "dbt compile", + ]) { + assert.deepEqual(inferToolPaths("bash", { command }), [], command); + } + }); + + it("does not treat sed replacement text as filesystem paths", () => { + const paths = inferToolPaths("bash", { + command: "sed -i 's|/usr/bin|/opt|' config.txt", + }); + assert.ok(paths.includes(".")); + assert.ok(!paths.includes("/usr/bin")); + assert.ok(!paths.includes("/opt")); + }); +}); + +describe("PiSessionsHost", () => { + it("returns to parent before disposing the active child runtime", async () => { + const host = new PiSessionsHost(); + const childId = "child-1"; + host.records.set(childId, { + id: childId, + kind: "child", + name: "other-repo", + cwd: "/work/other-repo", + state: "active", + activity: "idle", + createdAt: Date.now(), + lastActivityAt: Date.now(), + adapter: { + suspend: () => {}, + dispose: () => new Promise(() => {}), + } as never, + }); + host.activeId = childId; + + await host.stopChild(childId); + + assert.equal(host.activeId, "__parent__"); + assert.equal(host.records.has(childId), false); + }); +}); + +describe("LockManager", () => { + it("detects equal, ancestor, and descendant conflicts", () => { + const locks = new LockManager(); + assert.equal(locks.acquire("a", ["src"], cwd).ok, true); + assert.equal(locks.acquire("b", ["src"], cwd).ok, false); + assert.equal(locks.acquire("b", ["src/lib/a.ts"], cwd).ok, false); + assert.equal(locks.acquire("b", ["."], cwd).ok, false); + }); + + it("allows same-session and non-overlapping paths", () => { + const locks = new LockManager(); + assert.equal(locks.acquire("a", ["src"], cwd).ok, true); + assert.equal(locks.acquire("a", ["src/lib"], cwd).ok, true); + assert.equal(locks.acquire("b", ["test"], cwd).ok, true); + }); + + it("releases only the paths held by a completed tool call", () => { + const locks = new LockManager(); + const result = locks.acquire("a", ["src"], cwd); + assert.equal(result.ok, true); + if (!result.ok) return; + locks.heldByToolCall.set("call-1", { + sessionId: "a", + paths: result.paths, + }); + assert.equal(locks.acquire("b", ["src"], cwd).ok, false); + assert.deepEqual(locks.releaseByToolCall("call-1"), ["/work/repo/src"]); + assert.equal(locks.acquire("b", ["src"], cwd).ok, true); + }); + + it("keeps a shared same-session path locked until every tool call releases it", () => { + const locks = new LockManager(); + const first = locks.acquire("a", ["src"], cwd); + const second = locks.acquire("a", ["src"], cwd); + assert.equal(first.ok, true); + assert.equal(second.ok, true); + if (!first.ok || !second.ok) return; + locks.heldByToolCall.set("call-1", { + sessionId: "a", + paths: first.paths, + }); + locks.heldByToolCall.set("call-2", { + sessionId: "a", + paths: second.paths, + }); + + locks.releaseByToolCall("call-1"); + assert.equal(locks.acquire("b", ["src"], cwd).ok, false); + locks.releaseByToolCall("call-2"); + assert.equal(locks.acquire("b", ["src"], cwd).ok, true); + }); + + it("cleans up every lock owned by a stopped session", () => { + const locks = new LockManager(); + locks.acquire("a", ["src", "docs"], cwd); + assert.deepEqual(locks.release("a").sort(), [ + "/work/repo/docs", + "/work/repo/src", + ]); + assert.equal(locks.locks.size, 0); + }); + + it("blocks concurrent git checkout in the same repository", () => { + const locks = new LockManager(); + const paths = inferToolPaths("bash", { command: "git checkout main" }); + assert.equal(locks.acquire("a", paths, cwd).ok, true); + assert.equal(locks.acquire("b", paths, cwd).ok, false); + }); +}); diff --git a/test/widget.test.ts b/test/widget.test.ts new file mode 100644 index 0000000..2c8d10d --- /dev/null +++ b/test/widget.test.ts @@ -0,0 +1,61 @@ +import assert from "node:assert/strict"; +import { it } from "node:test"; +import type { Theme } from "@earendil-works/pi-coding-agent"; +import { SessionWidget, type WidgetSnapshot } from "../ui.ts"; + +const theme = { + fg: (_color: string, text: string) => text, +} as unknown as Theme; + +it("labels the protected parent independently of its working directory", () => { + const widget = new SessionWidget( + theme, + () => ({ + attached: "__parent__", + updatedAt: Date.now(), + sessions: [ + { + id: "__parent__", + name: "parent", + cwd: "/work/pi-sessions", + state: "active", + agentStatus: "idle", + }, + ], + }), + () => {}, + ); + + assert.match(widget.render(80)[0] ?? "", /✓ parent/); + assert.doesNotMatch(widget.render(80)[0] ?? "", /pi-sessions/); + widget.dispose(); +}); + +it("renders agent lifecycle activity as working then idle", () => { + let snapshot: WidgetSnapshot = { + attached: "session-1", + updatedAt: Date.now(), + sessions: [ + { + id: "session-1", + name: "repo", + cwd: "/work/repo", + state: "active", + agentStatus: "working", + }, + ], + }; + const widget = new SessionWidget(theme, () => snapshot, () => {}); + + assert.match(widget.render(80)[0] ?? "", /⠋ repo/); + snapshot = { + ...snapshot, + updatedAt: Date.now(), + sessions: snapshot.sessions.map((session) => ({ + ...session, + agentStatus: "idle", + })), + }; + assert.match(widget.render(80)[0] ?? "", /✓ repo/); + widget.dispose(); +}); diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..9c771a9 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,13 @@ +{ + "compilerOptions": { + "target": "ES2023", + "module": "NodeNext", + "moduleResolution": "NodeNext", + "strict": true, + "noEmit": true, + "allowImportingTsExtensions": true, + "skipLibCheck": true, + "types": ["node"] + }, + "include": ["index.ts", "ui.ts", "test/**/*.ts"] +} diff --git a/ui.ts b/ui.ts index e980225..9403b00 100644 --- a/ui.ts +++ b/ui.ts @@ -1,8 +1,10 @@ -// @ts-nocheck import { existsSync, readdirSync, statSync } from "node:fs"; import { homedir } from "node:os"; import path from "node:path"; -import type { Theme } from "@earendil-works/pi-coding-agent"; +import type { + ExtensionContext, + Theme, +} from "@earendil-works/pi-coding-agent"; import { fuzzyFilter, getKeybindings, @@ -13,9 +15,10 @@ import { visibleWidth, type Component, type Focusable, + type KeyId, } from "@earendil-works/pi-tui"; -type SessionInfo = { +export type SessionInfo = { id: string; name: string; cwd: string; @@ -28,7 +31,7 @@ type SessionInfo = { shortName?: string; }; -type SavedSessionInfo = { +export type SavedSessionInfo = { path: string; id: string; cwd: string; @@ -39,6 +42,7 @@ type SavedSessionInfo = { }; type SessionsActions = { + killKey: KeyId; getSessions: () => Promise; getResumeSessions?: () => Promise; getAttached: () => string | null; @@ -51,7 +55,7 @@ type SessionsActions = { notify: (message: string, type?: "info" | "warning" | "error") => void; }; -type WidgetSnapshot = { +export type WidgetSnapshot = { attached: string | null; sessions: SessionInfo[]; updatedAt: number; @@ -61,11 +65,6 @@ const PARENT_SESSION_ID = "__parent__"; const DEFAULT_SPINNER_FRAMES = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"]; const DEFAULT_SPINNER_INTERVAL_MS = 80; -type WorkingIndicatorOptions = { - frames?: string[]; - intervalMs?: number; -}; - function isCtrl(data: string, key: "o" | "r" | "k" | "p" | "n"): boolean { const codes: Record = { o: "\x0f", @@ -96,7 +95,10 @@ function cwdBasename(cwd: string): string { function computeShortNames(sessions: SessionInfo[]): void { const counts = new Map(); for (const session of sessions) { - const base = cwdBasename(session.cwd || "") || session.name; + const base = + session.id === PARENT_SESSION_ID + ? "parent" + : cwdBasename(session.cwd || "") || session.name; const n = counts.get(base) ?? 0; counts.set(base, n + 1); session.shortName = n === 0 ? base : `${base}<${n}>`; @@ -112,7 +114,6 @@ export class SessionWidget implements Component { private readonly theme: Theme, private readonly getSnapshot: () => WidgetSnapshot | null, private readonly requestRender: () => void, - private readonly getWorkingIndicator?: () => WorkingIndicatorOptions | undefined, ) {} render(width: number): string[] { @@ -157,19 +158,9 @@ export class SessionWidget implements Component { } private spinnerFrame(): string { - const indicator = this.getWorkingIndicator?.(); - const frames = - indicator?.frames !== undefined - ? [...indicator.frames] - : DEFAULT_SPINNER_FRAMES; - if (!frames.length) return ""; - const frame = frames[this.frame % frames.length] ?? ""; - return indicator !== undefined ? frame : this.theme.fg("accent", frame); - } - - private spinnerIntervalMs(): number { - const interval = this.getWorkingIndicator?.()?.intervalMs; - return interval && interval > 0 ? interval : DEFAULT_SPINNER_INTERVAL_MS; + const frame = + DEFAULT_SPINNER_FRAMES[this.frame % DEFAULT_SPINNER_FRAMES.length] ?? ""; + return this.theme.fg("accent", frame); } private isWaiting(session: SessionInfo): boolean { @@ -203,7 +194,7 @@ export class SessionWidget implements Component { } private updateTimer(shouldRun: boolean): void { - const interval = this.spinnerIntervalMs(); + const interval = DEFAULT_SPINNER_INTERVAL_MS; if (shouldRun && this.timer && this.timerIntervalMs !== interval) { clearInterval(this.timer); this.timer = null; @@ -641,7 +632,7 @@ class ResumeSessionPicker implements Component, Focusable { private readonly filterInput = new Input(); constructor( - private readonly theme: any, + private readonly theme: Theme, private readonly loadSessions: () => Promise, private readonly onDone: (sessionPath: string | null) => void, private readonly requestRender: () => void, @@ -849,7 +840,7 @@ class SessionsView { private initialSelectionSet = false; private nameWidth = 30; private readonly filterInput = new Input(); - private readonly theme: any; + private readonly theme: Theme; private readonly done: () => void; private readonly actions: SessionsActions; private readonly requestRender: () => void; @@ -858,7 +849,7 @@ class SessionsView { private timer: NodeJS.Timeout | null = null; constructor( - theme: any, + theme: Theme, done: () => void, actions: SessionsActions, requestRender: () => void, @@ -1048,7 +1039,7 @@ class SessionsView { void this.actions.switchTo(session.id).then(() => this.close()); return; } - if (isCtrl(data, "k")) { + if (matchesKey(data, this.actions.killKey)) { const session = this.selectedSession(); if (!session) return; if (session.id === PARENT_SESSION_ID) { @@ -1187,7 +1178,7 @@ class SessionsView { muted(" new in folder · ") + dim("") + muted(" resume · ") + - dim("") + + dim(`<${this.actions.killKey}>`) + muted(" kill · ") + dim("") + muted(" close"), @@ -1209,11 +1200,16 @@ class SessionsView { } export async function showSessionsView( - ctx: any, + ctx: ExtensionContext, actions: SessionsActions, ): Promise { - await ctx.ui.custom( - (tui: any, theme: any, _keybindings: any, done: () => void) => - new SessionsView(theme, done, actions, () => tui.requestRender()), + await ctx.ui.custom( + (tui, theme, _keybindings, done) => + new SessionsView( + theme, + () => done(undefined), + actions, + () => tui.requestRender(), + ), ); }