refactor: remove dead code (unused exports) (#1486)

Remove exported functions/constants/classes that are never imported
anywhere else in the codebase:

Fully removed (not used anywhere):
- nativeAvailable (native)
- getApiProviders, unregisterApiProviders (pi-ai/api-registry)
- createAssistantMessageEventStream (pi-ai/event-stream)
- getOverflowPatterns (pi-ai/overflow)
- validateToolCall (pi-ai/validation)
- getToolsDir (pi-coding-agent/config)
- emitSessionShutdownEvent (extensions/runner)
- syncContent, notifySaved (lsp/client)
- getServerForFile, hasCapability (lsp/config)
- severityToIcon, formatPosition, formatTextEdit, symbolKindToName (lsp/utils)
- clearApiKeyCache (model-registry)
- restoreModelFromSession (model-resolver)
- isLightTheme (theme)
- loadPhoton + all internal helpers (photon)
- extractAnsiCode (pi-tui/utils)

De-exported (used locally, not externally):
- extractRetryAfterMs, inferCopilotInitiator, extractRetryDelay,
  buildRequest, requiresToolCallId, registerBuiltInApiProviders,
  streamProxy, isBunRuntime, detectInstallMethod, getPackageDir,
  getPackageJsonPath, ansiToHtml, DEFAULT_APP_KEYBINDINGS,
  DEFAULT_KEYBINDINGS, shutdownClient, sendNotification, shutdownAll,
  applyTextEditsToString, wrapWithLspmux, severityToString,
  COMPACTION_SUMMARY_PREFIX/SUFFIX, BRANCH_SUMMARY_PREFIX/SUFFIX,
  bashExecutionToText, defaultModelPerProvider, parseModelPattern,
  parseCommandArgs, substituteArgs, loadEntriesFromFile,
  findMostRecentSession, FileSettingsStorage, InMemorySettingsStorage,
  migrateAuthToAuthJson, migrateSessionsFromAgentRoot,
  parseSearchQuery, matchSession, compareVersions, isWaylandSession,
  getToolPath, wordWrapLine
This commit is contained in:
Juan Francisco Lebrero 2026-03-19 18:33:32 -03:00 committed by GitHub
parent c10e42b392
commit 4e29ca4544
35 changed files with 44 additions and 483 deletions

View file

@ -151,6 +151,3 @@ export const native = loadNative() as {
parseStreamingJson: (text: string) => unknown;
xxHash32: (input: string, seed: number) => number;
};
/** True when the native addon loaded successfully. False on unsupported platforms. */
export const nativeAvailable = _loadedSuccessfully;

View file

@ -82,7 +82,7 @@ export interface ProxyStreamOptions extends SimpleStreamOptions {
* });
* ```
*/
export function streamProxy(model: Model<any>, context: Context, options: ProxyStreamOptions): ProxyMessageEventStream {
function streamProxy(model: Model<any>, context: Context, options: ProxyStreamOptions): ProxyMessageEventStream {
const stream = new ProxyMessageEventStream();
(async () => {

View file

@ -81,18 +81,6 @@ export function getApiProvider(api: Api): ApiProviderInternal | undefined {
return apiProviderRegistry.get(api)?.provider;
}
export function getApiProviders(): ApiProviderInternal[] {
return Array.from(apiProviderRegistry.values(), (entry) => entry.provider);
}
export function unregisterApiProviders(sourceId: string): void {
for (const [api, entry] of apiProviderRegistry.entries()) {
if (entry.sourceId === sourceId) {
apiProviderRegistry.delete(api);
}
}
}
export function clearApiProviders(): void {
apiProviderRegistry.clear();
}

View file

@ -230,7 +230,7 @@ function isTransientNetworkError(error: unknown): boolean {
* Checks: retry-after (seconds or RFC date), x-ratelimit-reset-requests, x-ratelimit-reset-tokens.
* Returns undefined if no valid delay is found or if the delay is in the past.
*/
export function extractRetryAfterMs(headers: Headers | { get(name: string): string | null }, errorText = ""): number | undefined {
function extractRetryAfterMs(headers: Headers | { get(name: string): string | null }, errorText = ""): number | undefined {
const normalizeDelay = (ms: number): number | undefined => (ms > 0 ? Math.ceil(ms + 1000) : undefined);
const retryAfter = headers.get("retry-after");

View file

@ -2,7 +2,7 @@ import type { Message } from "../types.js";
// Copilot expects X-Initiator to indicate whether the request is user-initiated
// or agent-initiated (e.g. follow-up after assistant/tool messages).
export function inferCopilotInitiator(messages: Message[]): "user" | "agent" {
function inferCopilotInitiator(messages: Message[]): "user" | "agent" {
const last = messages[messages.length - 1];
return last && last.role !== "user" ? "agent" : "user";
}

View file

@ -112,7 +112,7 @@ const CLAUDE_THINKING_BETA_HEADER = "interleaved-thinking-2025-05-14";
* - "Please retry in Xs" or "Please retry in Xms"
* - "retryDelay": "34.074824224s" (JSON field)
*/
export function extractRetryDelay(errorText: string, response?: Response | Headers): number | undefined {
function extractRetryDelay(errorText: string, response?: Response | Headers): number | undefined {
const normalizeDelay = (ms: number): number | undefined => (ms > 0 ? Math.ceil(ms + 1000) : undefined);
const headers = response instanceof Headers ? response : response?.headers;
@ -865,7 +865,7 @@ export const streamSimpleGoogleGeminiCli: StreamFunction<"google-gemini-cli", Si
} satisfies GoogleGeminiCliOptions);
};
export function buildRequest(
function buildRequest(
model: Model<"google-gemini-cli">,
context: Context,
projectId: string,

View file

@ -66,7 +66,7 @@ function resolveThoughtSignature(isSameProviderAndModel: boolean, signature: str
/**
* Models via Google APIs that require explicit tool call IDs in function calls/responses.
*/
export function requiresToolCallId(modelId: string): boolean {
function requiresToolCallId(modelId: string): boolean {
return modelId.startsWith("claude-") || modelId.startsWith("gpt-oss-");
}

View file

@ -116,7 +116,7 @@ function streamSimpleBedrockLazy(
return outer;
}
export function registerBuiltInApiProviders(): void {
function registerBuiltInApiProviders(): void {
registerApiProvider({
api: "anthropic-messages",
stream: streamAnthropic,

View file

@ -80,8 +80,3 @@ export class AssistantMessageEventStream extends EventStream<AssistantMessageEve
);
}
}
/** Factory function for AssistantMessageEventStream (for use in extensions) */
export function createAssistantMessageEventStream(): AssistantMessageEventStream {
return new AssistantMessageEventStream();
}

View file

@ -113,11 +113,4 @@ export function isContextOverflow(message: AssistantMessage, contextWindow?: num
}
return false;
}
/**
* Get the overflow patterns for testing purposes.
*/
export function getOverflowPatterns(): RegExp[] {
return [...OVERFLOW_PATTERNS];
}
}

View file

@ -28,21 +28,6 @@ if (!isBrowserExtension) {
}
}
/**
* Finds a tool by name and validates the tool call arguments against its TypeBox schema
* @param tools Array of tool definitions
* @param toolCall The tool call from the LLM
* @returns The validated arguments
* @throws Error if tool is not found or validation fails
*/
export function validateToolCall(tools: Tool[], toolCall: ToolCall): any {
const tool = tools.find((t) => t.name === toolCall.name);
if (!tool) {
throw new Error(`Tool "${toolCall.name}" not found`);
}
return validateToolArguments(tool, toolCall);
}
/**
* Validates tool call arguments against the tool's TypeBox schema
* @param tool The tool definition with TypeBox schema

View file

@ -18,15 +18,15 @@ export const isBunBinary =
import.meta.url.includes("$bunfs") || import.meta.url.includes("~BUN") || import.meta.url.includes("%7EBUN");
/** Detect if Bun is the runtime (compiled binary or bun run) */
export const isBunRuntime = !!process.versions.bun;
const isBunRuntime = !!process.versions.bun;
// =============================================================================
// Install Method Detection
// =============================================================================
export type InstallMethod = "bun-binary" | "npm" | "pnpm" | "yarn" | "bun" | "unknown";
type InstallMethod = "bun-binary" | "npm" | "pnpm" | "yarn" | "bun" | "unknown";
export function detectInstallMethod(): InstallMethod {
function detectInstallMethod(): InstallMethod {
if (isBunBinary) {
return "bun-binary";
}
@ -79,7 +79,7 @@ export function getUpdateInstruction(packageName: string): string {
*/
let _cachedPackageDir: string | undefined;
export function getPackageDir(): string {
function getPackageDir(): string {
if (_cachedPackageDir !== undefined) return _cachedPackageDir;
// Allow override via environment variable (useful for Nix/Guix where store paths tokenize poorly)
@ -138,7 +138,7 @@ export function getExportTemplateDir(): string {
}
/** Get path to package.json */
export function getPackageJsonPath(): string {
function getPackageJsonPath(): string {
return join(getPackageDir(), "package.json");
}
@ -219,11 +219,6 @@ export function getSettingsPath(): string {
return join(getAgentDir(), "settings.json");
}
/** Get path to tools directory */
export function getToolsDir(): string {
return join(getAgentDir(), "tools");
}
/** Get path to managed binaries directory (fd, rg) */
export function getBinDir(): string {
return join(getAgentDir(), "bin");

View file

@ -195,7 +195,7 @@ const ANSI_REGEX = /\x1b\[([\d;]*)m/g;
/**
* Convert ANSI-escaped text to HTML with inline styles.
*/
export function ansiToHtml(text: string): string {
function ansiToHtml(text: string): string {
const style = createEmptyStyle();
let result = "";
let lastIndex = 0;

View file

@ -153,19 +153,6 @@ export type ReloadHandler = () => Promise<void>;
export type ShutdownHandler = () => void;
/**
* Helper function to emit session_shutdown event to extensions.
* Returns true if the event was emitted, false if there were no handlers.
*/
export async function emitSessionShutdownEvent(extensionRunner: ExtensionRunner | undefined): Promise<boolean> {
if (extensionRunner?.hasHandlers("session_shutdown")) {
await extensionRunner.emit({
type: "session_shutdown",
});
return true;
}
return false;
}
const noOpUIContext: ExtensionUIContext = {
select: async () => undefined,

View file

@ -50,7 +50,7 @@ export type KeybindingsConfig = {
/**
* Default application keybindings.
*/
export const DEFAULT_APP_KEYBINDINGS: Record<AppAction, KeyId | KeyId[]> = {
const DEFAULT_APP_KEYBINDINGS: Record<AppAction, KeyId | KeyId[]> = {
interrupt: "escape",
clear: "ctrl+c",
exit: "ctrl+d",
@ -75,7 +75,7 @@ export const DEFAULT_APP_KEYBINDINGS: Record<AppAction, KeyId | KeyId[]> = {
/**
* All default keybindings (app + editor).
*/
export const DEFAULT_KEYBINDINGS: Required<KeybindingsConfig> = {
const DEFAULT_KEYBINDINGS: Required<KeybindingsConfig> = {
...DEFAULT_EDITOR_KEYBINDINGS,
...DEFAULT_APP_KEYBINDINGS,
};

View file

@ -618,76 +618,6 @@ export async function ensureFileOpen(client: LspClient, filePath: string, signal
}
}
/**
* Sync in-memory content to the LSP client without reading from disk.
*/
export async function syncContent(
client: LspClient,
filePath: string,
content: string,
signal?: AbortSignal,
): Promise<void> {
const uri = fileToUri(filePath);
const lockKey = `${client.name}:${uri}`;
throwIfAborted(signal);
const existingLock = fileOperationLocks.get(lockKey);
if (existingLock) {
await untilAborted(signal, () => existingLock);
}
const syncPromise = (async () => {
client.diagnostics.delete(uri);
const info = client.openFiles.get(uri);
if (!info) {
const languageId = detectLanguageId(filePath);
throwIfAborted(signal);
await sendNotification(client, "textDocument/didOpen", {
textDocument: {
uri,
languageId,
version: 1,
text: content,
},
});
client.openFiles.set(uri, { version: 1, languageId });
client.lastActivity = Date.now();
return;
}
const version = ++info.version;
throwIfAborted(signal);
await sendNotification(client, "textDocument/didChange", {
textDocument: { uri, version },
contentChanges: [{ text: content }],
});
client.lastActivity = Date.now();
})();
fileOperationLocks.set(lockKey, syncPromise);
try {
await syncPromise;
} finally {
fileOperationLocks.delete(lockKey);
}
}
/**
* Notify LSP that a file was saved.
*/
export async function notifySaved(client: LspClient, filePath: string, signal?: AbortSignal): Promise<void> {
const uri = fileToUri(filePath);
const info = client.openFiles.get(uri);
if (!info) return;
throwIfAborted(signal);
await sendNotification(client, "textDocument/didSave", {
textDocument: { uri },
});
client.lastActivity = Date.now();
}
/**
* Refresh a file in the LSP client.
@ -761,7 +691,7 @@ export function notifyFileChanged(filePath: string): void {
/**
* Shutdown a specific client by key.
*/
export function shutdownClient(key: string): void {
function shutdownClient(key: string): void {
const client = clients.get(key);
if (!client) return;
@ -865,7 +795,7 @@ export async function sendRequest(
return promise;
}
export async function sendNotification(client: LspClient, method: string, params: unknown): Promise<void> {
async function sendNotification(client: LspClient, method: string, params: unknown): Promise<void> {
const notification: LspJsonRpcNotification = {
jsonrpc: "2.0",
method,
@ -889,7 +819,7 @@ export async function sendNotification(client: LspClient, method: string, params
/**
* Shutdown all LSP clients.
*/
export function shutdownAll(): void {
function shutdownAll(): void {
const clientsToShutdown = Array.from(clients.values());
clients.clear();

View file

@ -319,14 +319,3 @@ export function getServersForFile(config: LspConfig, filePath: string): Array<[s
});
}
export function getServerForFile(config: LspConfig, filePath: string): [string, ServerConfig] | null {
const servers = getServersForFile(config, filePath);
return servers.length > 0 ? servers[0] : null;
}
export function hasCapability(
config: ServerConfig,
capability: keyof NonNullable<ServerConfig["capabilities"]>,
): boolean {
return config.capabilities?.[capability] === true;
}

View file

@ -11,7 +11,7 @@ import { uriToFile } from "./utils.js";
* Apply text edits to a string in-memory.
* Edits are applied in reverse order (bottom-to-top) to preserve line/character indices.
*/
export function applyTextEditsToString(content: string, edits: TextEdit[]): string {
function applyTextEditsToString(content: string, edits: TextEdit[]): string {
const lines = content.split("\n");
// Sort edits in reverse order (bottom-to-top, right-to-left)

View file

@ -172,7 +172,7 @@ export interface LspmuxWrappedCommand {
env?: Record<string, string>;
}
export function wrapWithLspmux(
function wrapWithLspmux(
originalCommand: string,
originalArgs: string[] | undefined,
state: LspmuxState,

View file

@ -192,7 +192,7 @@ const SEVERITY_NAMES: Record<DiagnosticSeverity, string> = {
4: "hint",
};
export function severityToString(severity?: DiagnosticSeverity): string {
function severityToString(severity?: DiagnosticSeverity): string {
return SEVERITY_NAMES[severity ?? 1] ?? "unknown";
}
@ -211,21 +211,6 @@ export function sortDiagnostics(diagnostics: Diagnostic[]): Diagnostic[] {
});
}
export function severityToIcon(severity?: DiagnosticSeverity): string {
switch (severity ?? 1) {
case 1:
return "[E]";
case 2:
return "[W]";
case 3:
return "[I]";
case 4:
return "[H]";
default:
return "[E]";
}
}
function stripDiagnosticNoise(message: string): string {
return message
.split("\n")
@ -353,10 +338,6 @@ export function formatLocation(location: Location, cwd: string): string {
return `${file}:${line}:${col}`;
}
export function formatPosition(line: number, col: number): string {
return `${line}:${col}`;
}
// =============================================================================
// WorkspaceEdit Formatting
// =============================================================================
@ -397,15 +378,6 @@ export function formatWorkspaceEdit(edit: WorkspaceEdit, cwd: string): string[]
return results;
}
export function formatTextEdit(edit: TextEdit, maxLength = 50): string {
const range = `${edit.range.start.line + 1}:${edit.range.start.character + 1}`;
const preview =
edit.newText.length > maxLength
? `${edit.newText.slice(0, maxLength).replace(/\n/g, "\\n")}`
: edit.newText.replace(/\n/g, "\\n");
return `line ${range} -> "${preview}"`;
}
// =============================================================================
// Symbol Formatting
// =============================================================================
@ -443,10 +415,6 @@ export function symbolKindToIcon(kind: SymbolKind): string {
return `[${SYMBOL_KIND_LABELS[kind] ?? "?"}]`;
}
export function symbolKindToName(kind: SymbolKind): string {
return SYMBOL_KIND_LABELS[kind] ?? "Unknown";
}
export function formatDocumentSymbol(symbol: DocumentSymbol, indent = 0): string[] {
const prefix = " ".repeat(indent);
const icon = symbolKindToIcon(symbol.kind);

View file

@ -8,20 +8,20 @@
import type { AgentMessage } from "@gsd/pi-agent-core";
import type { ImageContent, Message, TextContent } from "@gsd/pi-ai";
export const COMPACTION_SUMMARY_PREFIX = `The conversation history before this point was compacted into the following summary:
const COMPACTION_SUMMARY_PREFIX = `The conversation history before this point was compacted into the following summary:
<summary>
`;
export const COMPACTION_SUMMARY_SUFFIX = `
const COMPACTION_SUMMARY_SUFFIX = `
</summary>`;
export const BRANCH_SUMMARY_PREFIX = `The following is a summary of a branch that this conversation came back from:
const BRANCH_SUMMARY_PREFIX = `The following is a summary of a branch that this conversation came back from:
<summary>
`;
export const BRANCH_SUMMARY_SUFFIX = `</summary>`;
const BRANCH_SUMMARY_SUFFIX = `</summary>`;
/**
* Message type for bash executions via the ! command.
@ -79,7 +79,7 @@ declare module "@gsd/pi-agent-core" {
/**
* Convert a BashExecutionMessage to user message text for LLM context.
*/
export function bashExecutionToText(msg: BashExecutionMessage): string {
function bashExecutionToText(msg: BashExecutionMessage): string {
let text = `Ran \`${msg.command}\`\n`;
if (msg.output) {
text += `\`\`\`\n${msg.output}\n\`\`\``;

View file

@ -216,8 +216,6 @@ function applyModelOverride(model: Model<Api>, override: ModelOverride): Model<A
return result;
}
/** Clear the config value command cache. Exported for testing. */
export const clearApiKeyCache = clearConfigValueCache;
/**
* Model registry - loads and manages models, resolves API keys via AuthStorage.

View file

@ -11,7 +11,7 @@ import { DEFAULT_THINKING_LEVEL } from "./defaults.js";
import type { ModelRegistry } from "./model-registry.js";
/** Default model IDs for each known provider */
export const defaultModelPerProvider: Record<KnownProvider, string> = {
const defaultModelPerProvider: Record<KnownProvider, string> = {
"amazon-bedrock": "us.anthropic.claude-opus-4-6-v1",
anthropic: "claude-opus-4-6[1m]",
openai: "gpt-5.4",
@ -143,9 +143,9 @@ function buildFallbackModel(provider: string, modelId: string, availableModels:
* - If suffix is valid thinking level, use it and recurse on prefix
* - If suffix is invalid, warn and recurse on prefix with "off"
*
* @internal Exported for testing
* @internal
*/
export function parseModelPattern(
function parseModelPattern(
pattern: string,
availableModels: Model<Api>[],
options?: { allowInvalidThinkingLevelFallback?: boolean },
@ -546,77 +546,3 @@ export async function findInitialModel(options: {
// 5. No model found
return { model: undefined, thinkingLevel: DEFAULT_THINKING_LEVEL, fallbackMessage: undefined };
}
/**
* Restore model from session, with fallback to available models
*/
export async function restoreModelFromSession(
savedProvider: string,
savedModelId: string,
currentModel: Model<Api> | undefined,
shouldPrintMessages: boolean,
modelRegistry: ModelRegistry,
): Promise<{ model: Model<Api> | undefined; fallbackMessage: string | undefined }> {
const restoredModel = modelRegistry.find(savedProvider, savedModelId);
// Check if restored model exists and has a valid API key
const hasApiKey = restoredModel ? !!(await modelRegistry.getApiKey(restoredModel)) : false;
if (restoredModel && hasApiKey) {
if (shouldPrintMessages) {
console.log(chalk.dim(`Restored model: ${savedProvider}/${savedModelId}`));
}
return { model: restoredModel, fallbackMessage: undefined };
}
// Model not found or no API key - fall back
const reason = !restoredModel ? "model no longer exists" : "no API key available";
if (shouldPrintMessages) {
console.error(chalk.yellow(`Warning: Could not restore model ${savedProvider}/${savedModelId} (${reason}).`));
}
// If we already have a model, use it as fallback
if (currentModel) {
if (shouldPrintMessages) {
console.log(chalk.dim(`Falling back to: ${currentModel.provider}/${currentModel.id}`));
}
return {
model: currentModel,
fallbackMessage: `Could not restore model ${savedProvider}/${savedModelId} (${reason}). Using ${currentModel.provider}/${currentModel.id}.`,
};
}
// Try to find any available model
const availableModels = await modelRegistry.getAvailable();
if (availableModels.length > 0) {
// Try to find a default model from known providers
let fallbackModel: Model<Api> | undefined;
for (const provider of Object.keys(defaultModelPerProvider) as KnownProvider[]) {
const defaultId = defaultModelPerProvider[provider];
const match = availableModels.find((m) => m.provider === provider && m.id === defaultId);
if (match) {
fallbackModel = match;
break;
}
}
// If no default found, use first available
if (!fallbackModel) {
fallbackModel = availableModels[0];
}
if (shouldPrintMessages) {
console.log(chalk.dim(`Falling back to: ${fallbackModel.provider}/${fallbackModel.id}`));
}
return {
model: fallbackModel,
fallbackMessage: `Could not restore model ${savedProvider}/${savedModelId} (${reason}). Using ${fallbackModel.provider}/${fallbackModel.id}.`,
};
}
// No models available
return { model: undefined, fallbackMessage: undefined };
}

View file

@ -19,7 +19,7 @@ export interface PromptTemplate {
* Parse command arguments respecting quoted strings (bash-style)
* Returns array of arguments
*/
export function parseCommandArgs(argsString: string): string[] {
function parseCommandArgs(argsString: string): string[] {
const args: string[] = [];
let current = "";
let inQuote: string | null = null;
@ -63,7 +63,7 @@ export function parseCommandArgs(argsString: string): string[] {
* Note: Replacement happens on the template string only. Argument values
* containing patterns like $1, $@, or $ARGUMENTS are NOT recursively substituted.
*/
export function substituteArgs(content: string, args: string[]): string {
function substituteArgs(content: string, args: string[]): string {
let result = content;
// Replace $1, $2, etc. with positional args FIRST (before wildcards)

View file

@ -577,8 +577,7 @@ function resolveBlobRefsInEntries(entries: FileEntry[], blobStore: BlobStore): v
}
}
/** Exported for testing */
export function loadEntriesFromFile(filePath: string): FileEntry[] {
function loadEntriesFromFile(filePath: string): FileEntry[] {
if (!existsSync(filePath)) return [];
const content = readFileSync(filePath, "utf8");
@ -620,8 +619,7 @@ function isValidSessionFile(filePath: string): boolean {
}
}
/** Exported for testing */
export function findMostRecentSession(sessionDir: string): string | null {
function findMostRecentSession(sessionDir: string): string | null {
try {
const files = readdirSync(sessionDir)
.filter((f) => f.endsWith(".jsonl"))

View file

@ -195,7 +195,7 @@ export interface SettingsError {
error: Error;
}
export class FileSettingsStorage implements SettingsStorage {
class FileSettingsStorage implements SettingsStorage {
private globalSettingsPath: string;
private projectSettingsPath: string;
@ -262,7 +262,7 @@ export class FileSettingsStorage implements SettingsStorage {
}
}
export class InMemorySettingsStorage implements SettingsStorage {
class InMemorySettingsStorage implements SettingsStorage {
private global: string | undefined;
private project: string | undefined;

View file

@ -16,7 +16,7 @@ const EXTENSIONS_DOC_URL = "https://github.com/badlogic/pi-mono/blob/main/packag
*
* @returns Array of provider names that were migrated
*/
export function migrateAuthToAuthJson(): string[] {
function migrateAuthToAuthJson(): string[] {
const agentDir = getAgentDir();
const authPath = join(agentDir, "auth.json");
const oauthPath = join(agentDir, "oauth.json");
@ -79,7 +79,7 @@ export function migrateAuthToAuthJson(): string[] {
*
* See: https://github.com/badlogic/pi-mono/issues/320
*/
export function migrateSessionsFromAgentRoot(): void {
function migrateSessionsFromAgentRoot(): void {
const agentDir = getAgentDir();
// Find all .jsonl files directly in agentDir (not in subdirectories)

View file

@ -36,7 +36,7 @@ function matchesNameFilter(session: SessionInfo, filter: NameFilter): boolean {
return hasSessionName(session);
}
export function parseSearchQuery(query: string): ParsedSearchQuery {
function parseSearchQuery(query: string): ParsedSearchQuery {
const trimmed = query.trim();
if (!trimmed) {
return { mode: "tokens", tokens: [], regex: null };
@ -113,7 +113,7 @@ export function parseSearchQuery(query: string): ParsedSearchQuery {
return { mode: "tokens", tokens, regex: null };
}
export function matchSession(session: SessionInfo, parsed: ParsedSearchQuery): MatchResult {
function matchSession(session: SessionInfo, parsed: ParsedSearchQuery): MatchResult {
const text = getSessionSearchText(session);
if (parsed.mode === "regex") {

View file

@ -880,14 +880,6 @@ export function getResolvedThemeColors(themeName?: string): Record<string, strin
return cssColors;
}
/**
* Check if a theme is a "light" theme (for CSS that needs light/dark variants).
*/
export function isLightTheme(themeName?: string): boolean {
// Currently just check the name - could be extended to analyze colors
return themeName === "light";
}
/**
* Get explicit export colors from theme JSON, if specified.
* Returns undefined for each color that isn't explicitly set.

View file

@ -73,7 +73,7 @@ export function parseChangelog(changelogPath: string): ChangelogEntry[] {
/**
* Compare versions. Returns: -1 if v1 < v2, 0 if v1 === v2, 1 if v1 > v2
*/
export function compareVersions(v1: ChangelogEntry, v2: ChangelogEntry): number {
function compareVersions(v1: ChangelogEntry, v2: ChangelogEntry): number {
if (v1.major !== v2.major) return v1.major - v2.major;
if (v1.minor !== v2.minor) return v1.minor - v2.minor;
return v1.patch - v2.patch;

View file

@ -14,7 +14,7 @@ const DEFAULT_LIST_TIMEOUT_MS = 1000;
const DEFAULT_READ_TIMEOUT_MS = 3000;
const DEFAULT_MAX_BUFFER_BYTES = 50 * 1024 * 1024;
export function isWaylandSession(env: NodeJS.ProcessEnv = process.env): boolean {
function isWaylandSession(env: NodeJS.ProcessEnv = process.env): boolean {
return Boolean(env.WAYLAND_DISPLAY) || env.XDG_SESSION_TYPE === "wayland";
}

View file

@ -1,139 +1,2 @@
/**
* Photon image processing wrapper.
*
* This module provides a unified interface to @silvia-odwyer/photon-node that works in:
* 1. Node.js (development, npm run build)
* 2. Bun compiled binaries (standalone distribution)
*
* The challenge: photon-node's CJS entry uses fs.readFileSync(__dirname + '/photon_rs_bg.wasm')
* which bakes the build machine's absolute path into Bun compiled binaries.
*
* Solution:
* 1. Patch fs.readFileSync to redirect missing photon_rs_bg.wasm reads
* 2. Copy photon_rs_bg.wasm next to the executable in build:binary
*/
import type { PathOrFileDescriptor } from "fs";
import { createRequire } from "module";
import * as path from "path";
import { fileURLToPath } from "url";
const require = createRequire(import.meta.url);
const fs = require("fs") as typeof import("fs");
// Re-export types from the main package
export type { PhotonImage as PhotonImageType } from "@silvia-odwyer/photon-node";
type ReadFileSync = typeof fs.readFileSync;
const WASM_FILENAME = "photon_rs_bg.wasm";
// Lazy-loaded photon module
let photonModule: typeof import("@silvia-odwyer/photon-node") | null = null;
let loadPromise: Promise<typeof import("@silvia-odwyer/photon-node") | null> | null = null;
function pathOrNull(file: PathOrFileDescriptor): string | null {
if (typeof file === "string") {
return file;
}
if (file instanceof URL) {
return fileURLToPath(file);
}
return null;
}
function getFallbackWasmPaths(): string[] {
const execDir = path.dirname(process.execPath);
return [
path.join(execDir, WASM_FILENAME),
path.join(execDir, "photon", WASM_FILENAME),
path.join(process.cwd(), WASM_FILENAME),
];
}
function patchPhotonWasmRead(): () => void {
const originalReadFileSync: ReadFileSync = fs.readFileSync.bind(fs);
const fallbackPaths = getFallbackWasmPaths();
const mutableFs = fs as { readFileSync: ReadFileSync };
const patchedReadFileSync: ReadFileSync = ((...args: Parameters<ReadFileSync>) => {
const [file, options] = args;
const resolvedPath = pathOrNull(file);
if (resolvedPath?.endsWith(WASM_FILENAME)) {
try {
return originalReadFileSync(...args);
} catch (error) {
const err = error as NodeJS.ErrnoException;
if (err?.code && err.code !== "ENOENT") {
throw error;
}
for (const fallbackPath of fallbackPaths) {
if (!fs.existsSync(fallbackPath)) {
continue;
}
if (options === undefined) {
return originalReadFileSync(fallbackPath);
}
return originalReadFileSync(fallbackPath, options);
}
throw error;
}
}
return originalReadFileSync(...args);
}) as ReadFileSync;
try {
mutableFs.readFileSync = patchedReadFileSync;
} catch {
Object.defineProperty(fs, "readFileSync", {
value: patchedReadFileSync,
writable: true,
configurable: true,
});
}
return () => {
try {
mutableFs.readFileSync = originalReadFileSync;
} catch {
Object.defineProperty(fs, "readFileSync", {
value: originalReadFileSync,
writable: true,
configurable: true,
});
}
};
}
/**
* Load the photon module asynchronously.
* Returns cached module on subsequent calls.
*/
export async function loadPhoton(): Promise<typeof import("@silvia-odwyer/photon-node") | null> {
if (photonModule) {
return photonModule;
}
if (loadPromise) {
return loadPromise;
}
loadPromise = (async () => {
const restoreReadFileSync = patchPhotonWasmRead();
try {
photonModule = await import("@silvia-odwyer/photon-node");
return photonModule;
} catch {
photonModule = null;
return photonModule;
} finally {
restoreReadFileSync();
}
})();
return loadPromise;
}

View file

@ -80,7 +80,7 @@ function commandExists(cmd: string): boolean {
}
// Get the path to a tool (system-wide or in our tools dir)
export function getToolPath(tool: "fd" | "rg"): string | null {
function getToolPath(tool: "fd" | "rg"): string | null {
const config = TOOLS[tool];
if (!config) return null;

View file

@ -28,7 +28,7 @@ export interface TextChunk {
* @param maxWidth - Maximum visible width per chunk
* @returns Array of chunks with text and position information
*/
export function wordWrapLine(line: string, maxWidth: number): TextChunk[] {
function wordWrapLine(line: string, maxWidth: number): TextChunk[] {
if (!line || maxWidth <= 0) {
return [{ text: "", startIndex: 0, endIndex: 0 }];
}

View file

@ -33,49 +33,6 @@ export function isPunctuationChar(char: string): boolean {
return PUNCTUATION_REGEX.test(char);
}
/**
* Extract ANSI escape sequences from a string at the given position.
*/
export function extractAnsiCode(str: string, pos: number): { code: string; length: number } | null {
if (pos >= str.length || str[pos] !== "\x1b") return null;
const next = str[pos + 1];
// CSI sequence: ESC [ ... m/G/K/H/J
if (next === "[") {
let j = pos + 2;
while (j < str.length && !/[mGKHJ]/.test(str[j]!)) j++;
if (j < str.length) return { code: str.substring(pos, j + 1), length: j + 1 - pos };
return null;
}
// OSC sequence: ESC ] ... BEL or ESC ] ... ST (ESC \)
// Used for hyperlinks (OSC 8), window titles, etc.
if (next === "]") {
let j = pos + 2;
while (j < str.length) {
if (str[j] === "\x07") return { code: str.substring(pos, j + 1), length: j + 1 - pos };
if (str[j] === "\x1b" && str[j + 1] === "\\") return { code: str.substring(pos, j + 2), length: j + 2 - pos };
j++;
}
return null;
}
// APC sequence: ESC _ ... BEL or ESC _ ... ST (ESC \)
// Used for cursor marker and application-specific commands
if (next === "_") {
let j = pos + 2;
while (j < str.length) {
if (str[j] === "\x07") return { code: str.substring(pos, j + 1), length: j + 1 - pos };
if (str[j] === "\x1b" && str[j + 1] === "\\") return { code: str.substring(pos, j + 2), length: j + 2 - pos };
j++;
}
return null;
}
return null;
}
// ---------------------------------------------------------------------------
// Native text module wrappers
// ---------------------------------------------------------------------------